Skip to main content
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.
  • Bridging in — the user-facing flow, costs and recovery
  • Bridge routes — what each route does and does not guarantee
  • Fee model — where the fee actually comes from