Contract b5227c305b916c80a81d675dd8fbd8103e7d62e91c501e044ec93e365e686cd8

← Back to Index 📥 Download WASM

Meta

rssdkver 27.0.6#60926a20d1f9f0a669d5fe551636f42a1302f0c0
rsver 1.97.1

Instances

  • CCPVTWII2TW6VVSVA4JQ2KFHICGBG4QVAHPIGUPSIW3E4VNPUI4Y2WQR

Interface

fn pause(
    env: soroban_sdk::Env,
    caller: soroban_sdk::Address,
) -> Result<(), soroban_sdk::Error>

What a transfer of amount to destination_domain costs. recipient_needs_account and max_fee must match what will be passed to bridge, or the quote shown and the amount charged will differ. On the relay path max_fee does not affect the price and is ignored; on the forward path it is the delivery fee, so the price cannot be quoted without it.

fn quote(
    env: soroban_sdk::Env,
    amount: i128,
    recipient_needs_account: bool,
    destination_domain: u32,
    max_fee: i128,
) -> Result
fn bridge(
    env: soroban_sdk::Env,
    req: BridgeRequest,
) -> Result
fn get_seq(env: soroban_sdk::Env) -> u64

Unpause is admin-only: a pauser can stop the world but not restart it.

fn unpause(env: soroban_sdk::Env) -> Result<(), soroban_sdk::Error>
fn get_usdc(env: soroban_sdk::Env) -> soroban_sdk::Address
fn get_admin(env: soroban_sdk::Env) -> soroban_sdk::Address
fn is_paused(env: soroban_sdk::Env) -> bool
fn get_params(env: soroban_sdk::Env) -> Params
fn get_pauser(env: soroban_sdk::Env) -> soroban_sdk::Address
fn get_domains(env: soroban_sdk::Env) -> soroban_sdk::Vec
fn accept_admin(env: soroban_sdk::Env) -> Result<(), soroban_sdk::Error>

usdc and token_messenger are immutable for the life of the contract — there are no setters and no upgrade entrypoint. A Circle migration means redeploying and repointing the frontend, which strands nobody precisely because no funds are held between transactions.

fn __constructor(
    env: soroban_sdk::Env,
    usdc: soroban_sdk::Address,
    token_messenger: soroban_sdk::Address,
    admin: soroban_sdk::Address,
    pauser: soroban_sdk::Address,
    fee_recipient: soroban_sdk::Address,
    params: Params,
    domains: soroban_sdk::Vec,
) -> Result<(), soroban_sdk::Error>
fn check_max_fee(
    env: soroban_sdk::Env,
    net_burned: i128,
    max_fee: i128,
    cap_bps: u32,
) -> Result
fn commit_domain(env: soroban_sdk::Env) -> Result<(), soroban_sdk::Error>
fn commit_params(env: soroban_sdk::Env) -> Result<(), soroban_sdk::Error>
fn commit_pauser(env: soroban_sdk::Env) -> Result<(), soroban_sdk::Error>

The fee split. account_fee is added on top of the percentage-or-floor part, and is zero unless the destination needs a token account created.

fn compute_split(
    env: soroban_sdk::Env,
    amount: i128,
    fee_bps: u32,
    min_fee: i128,
    account_fee: i128,
) -> Result
fn propose_admin(
    env: soroban_sdk::Env,
    new_admin: soroban_sdk::Address,
) -> Result
fn remove_domain(
    env: soroban_sdk::Env,
    caller: soroban_sdk::Address,
    domain: u32,
) -> Result<(), soroban_sdk::Error>
fn withdraw_fees(env: soroban_sdk::Env, amount: i128) -> Result<(), soroban_sdk::Error>

The pauser's veto over every pending change, in one call.

fn cancel_pending(
    env: soroban_sdk::Env,
    caller: soroban_sdk::Address,
) -> Result<(), soroban_sdk::Error>
fn propose_domain(
    env: soroban_sdk::Env,
    cfg: DomainCfg,
) -> Result
fn propose_params(
    env: soroban_sdk::Env,
    params: Params,
) -> Result

Replacing the pauser is timelocked, and that is the point.

The pauser's veto over pending proposals is the only defence against a stolen admin key during the 48h window. While this was instant that defence did not exist: an attacker holding the admin key would replace the pauser with themselves in one transaction, then propose maximum fees and a new fee recipient with nobody able to cancel them. Timelocked, the sitting pauser sees pauser_proposed and can cancel_pending.

The cost is real and worth stating: if the pauser key is lost, rotating to a new one now takes 48 hours. That is the trade — a lost pauser key is an inconvenience, a stolen admin key is an incident.

fn propose_pauser(
    env: soroban_sdk::Env,
    new_pauser: soroban_sdk::Address,
) -> Result

Instant, exposure-reducing changes to a domain: turn forwarding off, or lower its cap. Admin or pauser, like remove_domain — it is the kill switch if Circle's service misbehaves or its hook format changes, and it must not wait 48 hours or need the admin.

It can only tighten. Turning forward on or raising the cap is propose_domain. A call that changes nothing is refused so it cannot masquerade as activity.

fn tighten_domain(
    env: soroban_sdk::Env,
    caller: soroban_sdk::Address,
    domain: u32,
    forward: bool,
    max_forward_fee: i128,
    account_creation: bool,
    max_forward_fee_new_account: i128,
) -> Result<(), soroban_sdk::Error>
fn tighten_params(
    env: soroban_sdk::Env,
    params: Params,
) -> Result<(), soroban_sdk::Error>
fn get_accrued_fees(env: soroban_sdk::Env) -> i128

The bounds on a forwarded transfer's max_fee, which Circle charges in full.

Two, and neither can be overridden by the caller: the destination's own max_forward_fee, and a tenth of what is burned (the same 10% rule check_max_fee applies on the relay path). The per-domain cap is itself bounded by MAX_FORWARD_FEE_CEILING when it is configured.

fn check_forward_fee(
    env: soroban_sdk::Env,
    q: Quote,
    cfg: DomainCfg,
) -> Result<(), soroban_sdk::Error>
fn get_fee_recipient(env: soroban_sdk::Env) -> soroban_sdk::Address
fn get_token_messenger(env: soroban_sdk::Env) -> soroban_sdk::Address
fn commit_fee_recipient(env: soroban_sdk::Env) -> Result<(), soroban_sdk::Error>

The forward-path split. Pure, like compute_split, so it can be tested without a ledger.

The recipient's promise is the same as on the relay path — amount - fee — and the forward fee is paid out of fee, not on top of it:

fee          = max(base, M)        base = max(amount * bps, min_fee), M = forward fee
wrapper_take = fee - M             what accrues; never negative
burned       = amount - wrapper_take, rounded down to a multiple of 10
recipient    = burned - M          = amount - fee - remainder

When M >= base the user pays M and we keep nothing on that transfer: gas is passed through at cost rather than the transfer being refused. max_fee is rounded up to a multiple of 10 stroops (Stellar USDC has 7 decimals, CCTP's canonical amount 6), exactly as check_max_fee does for the relay path. The per-domain cap and the 10% bound are applied by check_forward_fee, which needs the domain.

fn compute_forward_split(
    env: soroban_sdk::Env,
    amount: i128,
    fee_bps: u32,
    min_fee: i128,
    max_fee: i128,
) -> Result
fn propose_fee_recipient(
    env: soroban_sdk::Env,
    new_recipient: soroban_sdk::Address,
) -> Result

Imports

WebAssembly Text (WAT) ▶