Complete rustdoc-style reference for all public contract functions. Intended for SDK developers integrating with Bridgelet contracts.
Manages a single-use restricted account that accepts one or more token payments and enforces authorized sweep or expiry logic.
Initializes the ephemeral account. Must be called exactly once. Subsequent calls return AlreadyInitialized.
fn initialize(
env: Env,
creator: Address,
expiry_ledger: u32,
recovery_address: Address,
authorized_controller: Address,
) -> Result<(), Error>| Parameter | Type | Description |
|---|---|---|
creator |
Address |
The account that created this contract. Must authorize this call. |
expiry_ledger |
u32 |
Ledger sequence number at which the account expires. Must be in the future. |
recovery_address |
Address |
Address that receives funds if the account expires without being swept. |
authorized_controller |
Address |
The SweepController contract address authorized to call sweep() on behalf of this account. |
Returns: Ok(()) on success.
Errors:
| Error | Condition |
|---|---|
AlreadyInitialized |
initialize has already been called on this contract. |
InvalidExpiry |
expiry_ledger is less than or equal to the current ledger sequence. |
Auth required: creator.require_auth()
Events emitted: AccountCreated { creator, expiry_ledger }
Records an inbound token payment. Supports multiple assets; each asset may only be recorded once. Maximum of 10 distinct assets.
fn record_payment(env: Env, amount: i128, asset: Address) -> Result<(), Error>| Parameter | Type | Description |
|---|---|---|
amount |
i128 |
Payment amount in the asset's base unit. Must be positive (> 0). |
asset |
Address |
Token contract address (SEP-41 compatible). |
Returns: Ok(()) on success.
Errors:
| Error | Condition |
|---|---|
NotInitialized |
initialize has not been called. |
InvalidAmount |
amount is zero or negative. |
DuplicateAsset |
A payment for asset has already been recorded. |
TooManyPayments |
10 distinct assets are already recorded. |
Auth required: None. Any caller may record a payment.
Events emitted:
- First payment:
PaymentReceived { amount, asset } - Subsequent payments:
MultiPaymentReceived { asset, amount }
Marks the account as swept and authorizes fund transfers to destination. All recorded payments are included. The actual token transfers are executed by SweepController after this call completes.
fn sweep(
env: Env,
destination: Address,
auth_signature: BytesN<64>,
) -> Result<(), Error>| Parameter | Type | Description |
|---|---|---|
destination |
Address |
Recipient wallet address for all recorded funds. |
auth_signature |
BytesN<64> |
Ed25519 signature covering destination + nonce + contract_id. In the current MVP this parameter is accepted but verification is delegated to authorized_controller.require_auth(). |
Returns: Ok(()) on success.
Errors:
| Error | Condition |
|---|---|
NotInitialized |
initialize has not been called. |
AlreadySwept |
Sweep has already been executed. |
NoPaymentReceived |
No payments have been recorded. |
AccountExpired |
Current ledger ≥ expiry_ledger. |
Unauthorized |
authorized_controller did not authorize this call. |
Auth required: authorized_controller.require_auth() — enforced via SweepController's authorize_as_current_contract().
State update: Sets status = Swept before any further work, preventing reentrancy.
Events emitted: SweepExecutedMulti { destination, payments }, ReserveReclaimed { ... }
Marks the account as expired and routes funds to recovery_address. Can only be called after expiry_ledger is reached.
fn expire(env: Env) -> Result<(), Error>Returns: Ok(()) on success.
Errors:
| Error | Condition |
|---|---|
NotInitialized |
initialize has not been called. |
InvalidStatus |
Account is already Swept or Expired. |
NotExpired |
Current ledger < expiry_ledger. |
Auth required: None. Any caller may trigger expiry once the ledger threshold is passed.
Events emitted: AccountExpired { recovery_address, total_amount, reserve_amount }, ReserveReclaimed { ... }
Returns true if the current ledger sequence has reached or passed expiry_ledger.
fn is_expired(env: Env) -> boolReturns the current lifecycle status of the account.
fn get_status(env: Env) -> AccountStatusenum AccountStatus {
Active = 0, // Initialized, no payment yet
PaymentReceived = 1, // At least one payment recorded
Swept = 2, // Sweep executed
Expired = 3, // Account expired, funds sent to recovery
}Returns the complete state of the account.
fn get_info(env: Env) -> Result<AccountInfo, Error>Errors: NotInitialized if initialize has not been called.
struct AccountInfo {
creator: Address,
status: AccountStatus,
expiry_ledger: u32,
recovery_address: Address,
payment_received: bool, // true if payment_count > 0
payment_count: u32,
payments: Vec<Payment>,
swept_to: Option<Address>, // set after sweep or expire
}
struct Payment {
asset: Address,
amount: i128,
timestamp: u64, // ledger timestamp at time of record_payment
}Reclaims any remaining base reserve (1 XLM denominated in stroops) that has not yet been transferred. Safe to call repeatedly; returns 0 once fully reclaimed.
fn reclaim_reserve(env: Env) -> Result<i128, Error>Returns: Amount reclaimed in this call (in stroops).
Errors:
| Error | Condition |
|---|---|
NotInitialized |
initialize has not been called. |
InvalidStatus |
Account is neither Swept nor Expired. |
Returns the reserve amount (stroops) still awaiting reclaim.
fn get_reserve_remaining(env: Env) -> i128Returns the reserve amount (stroops) currently available for transfer.
fn get_reserve_available(env: Env) -> i128Returns true if the full base reserve has been reclaimed.
fn is_reserve_reclaimed(env: Env) -> boolReturns the most recently emitted ReserveReclaimed event payload, or None.
fn get_last_reserve_event(env: Env) -> Option<ReserveReclaimed>Returns the total number of ReserveReclaimed events emitted by this contract.
fn get_reserve_reclaim_event_count(env: Env) -> u32| Topic | Struct | Trigger |
|---|---|---|
created |
AccountCreated { creator, expiry_ledger } |
initialize success |
payment |
PaymentReceived { amount, asset } |
First record_payment call |
multi_pay |
MultiPaymentReceived { asset, amount } |
Second and subsequent record_payment calls |
swept_mul |
SweepExecutedMulti { destination, payments } |
sweep success |
expired |
AccountExpired { recovery_address, amount_returned, reserve_amount } |
expire success |
reserve |
ReserveReclaimed { destination, amount, sweep_id, fully_reclaimed, remaining_reserve } |
After each sweep or expire that transfers reserve |
| Code | Variant | Description |
|---|---|---|
| 1 | AlreadyInitialized |
Contract already initialized. |
| 2 | NotInitialized |
Contract not initialized. |
| 3 | PaymentAlreadyReceived |
Deprecated. Use DuplicateAsset (code 13). |
| 4 | InvalidAmount |
Payment amount is zero or negative. |
| 5 | InvalidExpiry |
expiry_ledger is not in the future. |
| 6 | NotExpired |
Attempted to expire before expiry_ledger. |
| 7 | AlreadySwept |
Account already swept. |
| 8 | Unauthorized |
authorized_controller did not authorize the call. |
| 9 | InvalidSignature |
Cryptographic signature format is invalid. |
| 10 | NoPaymentReceived |
Cannot sweep without a recorded payment. |
| 11 | AccountExpired |
Cannot sweep an expired account. |
| 12 | InvalidStatus |
Action is invalid for the current account status. |
| 13 | DuplicateAsset |
Asset already has a recorded payment. |
| 14 | TooManyPayments |
Maximum of 10 distinct assets reached. |
Orchestrates sweep authorization using Ed25519 signature verification and executes atomic token transfers.
Sets up the controller with an authorized Ed25519 signer and an optional locked destination address. Can only be called once.
fn initialize(
env: Env,
creator: Address,
authorized_signer: BytesN<32>,
authorized_destination: Option<Address>,
) -> Result<(), Error>| Parameter | Type | Description |
|---|---|---|
creator |
Address |
Address that owns this controller instance. Required to authorize future update_authorized_destination calls. Must authorize this call. |
authorized_signer |
BytesN<32> |
Ed25519 public key used to verify all sweep authorization signatures. |
authorized_destination |
Option<Address> |
If Some(addr), the controller operates in locked mode: sweeps can only transfer to this specific address. If None, any destination is accepted (flexible mode). |
Returns: Ok(()) on success.
Errors:
| Error | Condition |
|---|---|
AuthorizationFailed |
initialize has already been called. |
Auth required: creator.require_auth()
Events emitted: DestinationAuthorized { destination } (only when authorized_destination is Some).
Verifies the Ed25519 authorization signature, then calls EphemeralAccount::sweep() and executes the token transfers to destination.
fn execute_sweep(
env: Env,
ephemeral_account: Address,
destination: Address,
auth_signature: BytesN<64>,
) -> Result<(), Error>| Parameter | Type | Description |
|---|---|---|
ephemeral_account |
Address |
Address of the EphemeralAccount contract to sweep. |
destination |
Address |
Recipient wallet address for all swept funds. |
auth_signature |
BytesN<64> |
Ed25519 signature over SHA256(destination_xdr || nonce_u64_be || contract_id_xdr). Must be signed by the key in authorized_signer. |
Returns: Ok(()) on success.
Errors:
| Error | Condition |
|---|---|
UnauthorizedDestination |
Controller is in locked mode and destination ≠ authorized_destination. |
AuthorizationFailed |
authorized_signer is not set (controller not initialized). |
AuthorizedSignerNotSet |
Ed25519 public key has not been stored. |
SignatureVerificationFailed |
Signature does not verify against the current nonce and destination. |
AccountNotReady |
Ephemeral account has no recorded payments or zero total amount. |
TransferFailed |
A SEP-41 token transfer() call failed. |
Signature message format:
message = SHA256(
destination.to_xdr()
|| nonce as u64 big-endian (8 bytes)
|| controller_contract_address.to_xdr()
)
The nonce is incremented after each successful execute_sweep call to prevent replay attacks.
Events emitted: SweepCompleted { ephemeral_account, destination, amount }
Gas-free claim path for the recipient. The recipient signs a Soroban auth entry for claim(recipient, ephemeral_account) only; a relayer or SDK submits the transaction and pays fees.
Internally the controller uses authorize_as_current_contract() to satisfy authorized_controller.require_auth() inside EphemeralAccount::sweep().
fn claim(env: Env, recipient: Address, ephemeral_account: Address) -> Result<(), Error>| Parameter | Type | Description |
|---|---|---|
recipient |
Address |
The address claiming the funds. Must authorize this call. |
ephemeral_account |
Address |
Address of the EphemeralAccount contract to sweep. |
Returns: Ok(()) on success.
Errors:
| Error | Condition |
|---|---|
UnauthorizedDestination |
Controller is in locked mode and recipient ≠ authorized_destination. |
Auth required: recipient.require_auth()
Events emitted: SweepCompleted { ephemeral_account, destination: recipient, amount }
Returns true if the ephemeral account has a recorded payment, is in PaymentReceived status, and has not expired.
fn can_sweep(env: Env, ephemeral_account: Address) -> bool| Parameter | Type | Description |
|---|---|---|
ephemeral_account |
Address |
Address of the EphemeralAccount contract to check. |
Allows the creator to update the locked destination before any sweep has occurred. Fails if a sweep has already been executed (nonce > 0).
fn update_authorized_destination(env: Env, new_destination: Address) -> Result<(), Error>| Parameter | Type | Description |
|---|---|---|
new_destination |
Address |
The new address sweeps will be restricted to. |
Returns: Ok(()) on success.
Errors:
| Error | Condition |
|---|---|
AuthorizationFailed |
Caller is not the creator or controller is not initialized. |
AccountAlreadySwept |
At least one sweep has been executed (nonce > 0); destination is now immutable. |
Auth required: creator.require_auth()
Events emitted: DestinationUpdated { old_destination, new_destination }
| Topic | Struct | Trigger |
|---|---|---|
sweep |
SweepCompleted { ephemeral_account, destination, amount } |
execute_sweep or claim success |
dest_auth |
DestinationAuthorized { destination } |
initialize with a non-None authorized_destination |
dest_upd |
DestinationUpdated { old_destination, new_destination } |
update_authorized_destination success |
| Code | Variant | Description |
|---|---|---|
| 1 | InvalidAccount |
Account is not in a valid state for the requested operation. |
| 2 | TransferFailed |
A SEP-41 token transfer failed. |
| 3 | AuthorizationFailed |
Signature invalid, caller not authorized, or already initialized. |
| 4 | InsufficientBalance |
Reserved for future use. |
| 5 | AccountNotReady |
Account has no payments or zero total amount. |
| 6 | AccountExpired |
Account has expired. |
| 7 | AccountAlreadySwept |
A sweep has already been executed; destination cannot be changed. |
| 8 | InvalidSignature |
Signature format is invalid. |
| 9 | SignatureVerificationFailed |
Ed25519 verification failure. |
| 10 | AuthorizedSignerNotSet |
Controller was not initialized with an authorized signer. |
| 11 | InvalidNonce |
Security nonce is invalid or out of sequence. |
| 13 | UnauthorizedDestination |
Destination does not match the locked authorized_destination. |
use soroban_sdk::{Address, BytesN, Env};
use ephemeral_account::EphemeralAccountContractClient as EphemeralClient;
use sweep_controller::SweepControllerClient;
fn example_single_asset(
env: &Env,
controller_id: &Address,
ephemeral_id: &Address,
creator: &Address,
recovery: &Address,
authorized_controller: &Address,
usdc_addr: &Address,
destination: &Address,
auth_sig: BytesN<64>,
) {
let ephemeral = EphemeralClient::new(env, ephemeral_id);
let controller = SweepControllerClient::new(env, controller_id);
// 1. Initialize ephemeral account, referencing this SweepController
ephemeral.initialize(
creator,
&(env.ledger().sequence() + 1000),
recovery,
authorized_controller,
);
// 2. Record incoming USDC payment (called by off-chain watcher)
ephemeral.record_payment(&100_000_000, usdc_addr); // 100 USDC (7 decimals)
// 3. Execute sweep via the controller (signature generated off-chain)
controller.execute_sweep(ephemeral_id, destination, &auth_sig);
}fn example_multi_asset(
env: &Env,
ephemeral_id: &Address,
controller_id: &Address,
usdc_addr: &Address,
xlm_addr: &Address,
destination: &Address,
auth_sig: BytesN<64>,
) {
let ephemeral = EphemeralAccountContractClient::new(env, ephemeral_id);
let controller = SweepControllerClient::new(env, controller_id);
// Record multiple asset payments
ephemeral.record_payment(&100_000_000, usdc_addr);
ephemeral.record_payment(&5_000_000_000, xlm_addr); // 500 XLM in stroops
// Sweep transfers ALL recorded assets atomically
controller.execute_sweep(ephemeral_id, destination, &auth_sig);
}fn example_claim(
env: &Env,
controller_id: &Address,
ephemeral_id: &Address,
recipient: &Address,
) {
// recipient only signs the `claim` auth entry; relayer pays fees
let controller = SweepControllerClient::new(env, controller_id);
controller.claim(recipient, ephemeral_id);
}Initialize ephemeral account:
soroban contract invoke \
--id <EPHEMERAL_CONTRACT_ID> \
--network testnet \
--source <CREATOR_SECRET> \
-- \
initialize \
--creator <CREATOR_ADDRESS> \
--expiry_ledger 123456 \
--recovery_address <RECOVERY_ADDRESS> \
--authorized_controller <SWEEP_CONTROLLER_ID>Initialize sweep controller (locked mode):
soroban contract invoke \
--id <CONTROLLER_CONTRACT_ID> \
--network testnet \
--source <CREATOR_SECRET> \
-- \
initialize \
--creator <CREATOR_ADDRESS> \
--authorized_signer <ED25519_PUBLIC_KEY_HEX> \
--authorized_destination <DESTINATION_ADDRESS>Record payment:
soroban contract invoke \
--id <EPHEMERAL_CONTRACT_ID> \
--network testnet \
--source <ANY_SOURCE> \
-- \
record_payment \
--amount 100000000 \
--asset <TOKEN_CONTRACT_ID>Execute sweep:
soroban contract invoke \
--id <CONTROLLER_CONTRACT_ID> \
--network testnet \
--source <RELAYER_SECRET> \
-- \
execute_sweep \
--ephemeral_account <EPHEMERAL_CONTRACT_ID> \
--destination <DESTINATION_ADDRESS> \
--auth_signature <64_BYTE_ED25519_SIG_HEX>Expire account:
soroban contract invoke \
--id <EPHEMERAL_CONTRACT_ID> \
--network testnet \
--source <ANY_SOURCE> \
-- \
expire