Skip to content

Latest commit

 

History

History
577 lines (428 loc) · 17.1 KB

File metadata and controls

577 lines (428 loc) · 17.1 KB

Bridgelet-Core API Reference

Complete rustdoc-style reference for all public contract functions. Intended for SDK developers integrating with Bridgelet contracts.


EphemeralAccount Contract

Manages a single-use restricted account that accepts one or more token payments and enforces authorized sweep or expiry logic.

Functions

initialize

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,
    admin: 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() / sweep_claim() on behalf of this account.
admin Address Address authorized to perform WASM contract upgrades (upgrade).

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 }


record_payment

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 }

sweep

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 { ... }


expire

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 { ... }


is_expired

Returns true if the current ledger sequence has reached or passed expiry_ledger.

fn is_expired(env: Env) -> bool

get_status

Returns the current lifecycle status of the account.

fn get_status(env: Env) -> AccountStatus
enum 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
}

get_info

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
}

reclaim_reserve

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.

get_reserve_remaining

Returns the reserve amount (stroops) still awaiting reclaim.

fn get_reserve_remaining(env: Env) -> i128

get_reserve_available

Returns the reserve amount (stroops) currently available for transfer.

fn get_reserve_available(env: Env) -> i128

is_reserve_reclaimed

Returns true if the full base reserve has been reclaimed.

fn is_reserve_reclaimed(env: Env) -> bool

get_last_reserve_event

Returns the most recently emitted ReserveReclaimed event payload, or None.

fn get_last_reserve_event(env: Env) -> Option<ReserveReclaimed>

get_reserve_reclaim_event_count

Returns the total number of ReserveReclaimed events emitted by this contract.

fn get_reserve_reclaim_event_count(env: Env) -> u32

Events

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

Error Codes

Code Variant Description
As of Issue #248, error codes are namespaced per contract in 1000-wide blocks so a bare numeric code is never ambiguous across contracts. This contract owns the 1000-1999 range.
Code Variant Description
1000 AlreadyInitialized Contract already initialized.
1001 NotInitialized Contract not initialized.
1002 PaymentAlreadyReceived Deprecated. Use DuplicateAsset (code 1012).
1003 InvalidAmount Payment amount is zero or negative.
1004 InvalidExpiry expiry_ledger is not in the future.
1005 NotExpired Attempted to expire before expiry_ledger.
1006 AlreadySwept Account already swept.
1007 Unauthorized authorized_controller did not authorize the call.
1008 InvalidSignature Cryptographic signature format is invalid.
1009 NoPaymentReceived Cannot sweep without a recorded payment.
1010 AccountExpired Cannot sweep an expired account.
1011 InvalidStatus Action is invalid for the current account status.
1012 DuplicateAsset Asset already has a recorded payment.
1013 TooManyPayments Maximum of 10 distinct assets reached.
1014 NotUpgradeAdmin Caller is not authorized to perform contract upgrades.

SweepController Contract

Orchestrates sweep authorization using Ed25519 signature verification and executes atomic token transfers.

Functions

initialize

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>
) -> Result<(), Error>
Parameter Type Description
creator Address Address allowed to manage controller configuration.
authorized_signer BytesN<32> Ed25519 public key for verifying sweep signatures.

execute_sweep

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>

claim

Experimental gas-free claim flow. The recipient authorizes the invocation via Soroban auth entries, and a relayer/SDK can submit the transaction and pay the fees.

fn claim(
    env: Env,
    recipient: Address,
    ephemeral_account: Address
) -> Result<(), Error>

can_sweep

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.

update_authorized_destination

Allows the creator to update the locked destination before any signed sweep has occurred. Checks that nonce == 0 (returns AccountAlreadySwept if nonce > 0).

Note on Nonce Tracking: Because execute_sweep() increments nonce on success while claim() does not currently increment nonce, this guard specifically tracks whether execute_sweep() has been executed. If accounts were swept exclusively via claim(), nonce remains 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 signed sweep has been executed (nonce > 0); destination is now immutable.

Auth required: creator.require_auth()

Events emitted: DestinationUpdated { old_destination, new_destination }


Events

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

Error Codes

Code Variant Description
Per Issue #248, this contract owns the 2000-2999 error code range. Code 2011 is intentionally unused, preserving a gap from the original enum.
Code Variant Description
2000 InvalidAccount Account is not in a valid state for the requested operation.
2001 TransferFailed A SEP-41 token transfer failed.
2002 AuthorizationFailed Signature invalid, caller not authorized, or already initialized.
2003 InsufficientBalance Reserved for future use.
2004 AccountNotReady Account has no payments or zero total amount.
2005 AccountExpired Account has expired.
2006 AccountAlreadySwept A sweep has already been executed; destination cannot be changed.
2007 InvalidSignature Signature format is invalid.
2008 SignatureVerificationFailed Ed25519 verification failure.
2009 AuthorizedSignerNotSet Controller was not initialized with an authorized signer.
2010 InvalidNonce Security nonce is invalid or out of sequence.
2012 UnauthorizedDestination Destination does not match the locked authorized_destination.

Usage Examples

Rust SDK Integration — Single Asset

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);
}

Rust SDK Integration — Multi-Asset

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);
}

Gas-Free Claim Flow

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);
}

CLI Invocation

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