The user-facing behaviour is Bridging in, and the guarantees each route does and
does not give are in Bridge routes. This page is the SDK surface behind it.
The shape is always the same: derive a one-time receiving address from the user’s own keys, get a
quote, let the user send on the origin chain, then shield what arrives. Nothing here is stored for
you — every address is re-derivable from nk, which is why a closed tab loses nothing.
Shielding runs on the client. Funds that arrive while nothing is running wait at the receiving
address until a later call picks them up. That is what listBridgeDeposits is for, and it is why an
integration needs a resume path rather than a single happy-path call.
Deriving the receiving address
index is a small sequential counter, not a timestamp. Discovery derives 0…N and reads each
one on chain, so a sparse index space makes the user’s own deposits undiscoverable. deriveBridgeReceiver
throws on a non-integer, a negative value, or anything above MAX_RECEIVER_INDEX.
Quoting, and refusing a bad one
assessBridgeQuote returns what the user actually ends up with, and whether the quote is worth
taking at all: viable is false when what would arrive cannot clear the program’s deposit minimum
once costs are covered. It throws rather than compute on a nonsensical quote (a negative amount, or
a route claiming to deliver more than it was given). renderAssessment formats the same numbers for
a CLI.
WITHDRAW_FIXED_FEE and WITHDRAW_FEE_BPS are compile-time fallbacks for quote arithmetic, used
when live values are not supplied. The authority is the program’s per-mint pool_config PDA, and the
fee is collected on-chain by the program — see Fee model. Never present these
constants as the fee the user will be charged.
Verifying the deposit address yourself
One route signs its quote, including the deposit address; the other signs nothing. Running
verifyQuoteSignature client-side is what lets a caller detect a substituted deposit address
independently, rather than trusting whatever forwarded the quote. A route that returns no signature
comes back { valid: false } with a reason — treat that as “this route cannot be verified”, not as
an error to swallow.
Finding what already arrived
Discovery derives receiving addresses from nk and reads each on chain, stopping after
stopAfterUnused consecutive empty ones (default 5) or at scanDepth (default 20, capped at
MAX_RECEIVER_INDEX + 1). This is the resume path: the user reopens the app with the same wallet and
anything unshielded is found again from their keys alone.
Completing and cleaning up
A receiving address arrives holding no SOL, so it cannot pay for its own shield. The paymaster
helpers cover that, and cleanupReceivingAddress closes the token account afterwards and sweeps the
remainder. validatePaymasterTopUpTransaction checks a top-up is what it claims before it is signed.
Retrying without double-spending
The distinction that matters: a request that failed before submission can be retried freely, and
one that failed after it may already have landed. classifyPostFailure is what separates them;
withShieldRetries wraps the whole shield with that policy already applied.