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