A Sui deposit is a single programmable transaction block that calls deposit on the Blink Move package, carrying the quote hash and a Coin holding exactly the deposit amount. There is no approval step: coin ownership is authorized by the signature on the transaction itself. The package id is 0xa23543c67476e6fa5a2707ede5c8e7ffd9b257d58f2d9b3982ab3f645ddaa81b, returned as router_address on the quote. The funds land in the shared escrow object (0x3ea3f77dbf7463cb583be24460895dabd228a0e60818dd23825a7c9dbe52336a), returned as escrow by GetChains; both ids together are everything needed to build the deposit.

The transaction block

  1. Collect the depositor’s coins of src_token and merge them if the balance is spread across several objects.
  2. Split off expected_amount_in.
  3. Call deposit with the escrow object, the 32-byte quote hash, and the split coin.
The type argument T is the full coin type of src_token. This path applies when src_token equals lock_token, which on Sui is native USDC. When the user pays in any other coin, including native SUI, the user opens a swap intent instead and the relayer swaps; see Swap-first deposits. A depositor without SUI for gas can use a gasless deposit instead.

With the SDK

buildSuiDeposit takes the amount from the depositor’s coins and address balance through the Sui SDK’s coinWithBalance, and assembles the move call. It returns a Transaction ready for a wallet to sign; the wallet estimates the gas budget.
Or let the adapter handle it, which is the same path the other chains take:
A plain Sui deposit is a single step.

Swap-first deposits

When src_token differs from lock_token, the depositor never runs the swap. An aggregator route executed in a transaction the depositor sends would run with the depositor as sender, and anything keyed on the sender (an address alias, an address keyed account, an admin role) would be within its reach. Instead the depositor opens an intent, and the relayer runs the swap in a separate transaction whose sender is the package’s swap executor and whose gas Blink’s gas sponsor pays. The depositor’s transaction block holds only these commands, with no aggregator code:
  1. Collect and merge coins of src_token and split off exactly expected_amount_in. For native SUI, split it from the gas coin unless the deposit is gasless.
  2. 0x2::coin::send_funds<In> the split coin to the depositor’s own address, so it sits in their address balance. Always fund the full expected_amount_in, even when the address balance already holds some of it, for example after an earlier intent lapsed: a transaction that carries no gas coin is charged its gas from the sender’s address balance, so a balance that was only just enough can be short by the time the relayer draws on it. Whatever the address balance holds beyond expected_amount_in stays the depositor’s. The SDK does this automatically.
  3. 0x1::option::none<0x2::allowance::RateLimit>().
  4. 0x2::allowance::propose_for_app<Balance<In>, <router_address>::blink::SourceSwapFunding> with the spender set to the chain’s swap_executor from GetChains, a lifetime cap of exactly expected_amount_in, no start time, an expiration at most five minutes after the current checkpoint time, and the rate limit from the previous command.
  5. open_source_swap with the escrow, the proposal, the quote hash, the quote’s min_lock_amount, and the clock (0x6).
In is the coin type of src_token and T the coin type of lock_token. The call issues an allowance that only the Blink package can spend, only for the swap executor, only up to expected_amount_in, and only until the expiration, and it shares a SourceSwapIntent with the quote hash and the minimum output. It aborts if the spender is not the escrow’s swap executor, if the allowance has no lifetime cap or no expiration, has a start time or a rate limit, or expires more than five minutes out. It emits SourceSwapOpened. Nothing has left the depositor at this point. The relayer then sends a transaction that calls begin_source_swap, which deletes the intent and draws exactly expected_amount_in through the allowance, runs the route on that coin, and calls finish_source_swap, which aborts below min_lock_amount, puts the output in the escrow, and emits SwapDeposit with the depositor as from. The depositor’s funds therefore move in exactly one transaction, and either all of it lands or none of it does. The relayer makes at most 3 attempts at the swap. If none of them lands before the intent expires, for example because the route would deliver less than min_lock_amount or the address balance no longer covers expected_amount_in, the intent lapses: the allowance expires unused and the coins stay in the depositor’s address balance, which they own and can spend like any coin. The depositor can also revoke the allowance at any time with its AllowanceCap. The swap’s gas is paid by Blink’s gas sponsor, never by the treasury or the depositor, and charged in the quote as the src_swap_gas fee component.

With the SDK

The adapter covers swap-first deposits with no extra code. steps returns a deposit step that builds and sends the intent block, then a swap step that waits until the relayer has run the swap and resolves to the swap transaction digest:
The swap step throws if the intent lapses or is still unswapped two minutes after it expires; the error says so, and the depositor’s funds never left their wallet. To build the block directly, buildSuiSourceSwapOpen takes the quote’s terms and the chain’s swap executor:
To add the intent to a transaction block you are already building, addSuiSourceSwapOpenCall takes the same terms plus the sender, expiresAtMs, and an optional payment coin to move into the address balance first, which suiSourceSwapExpiry(rpcUrl) computes from the latest checkpoint. suiOrderDepositCall does the same for a BlinkOrder when given the swap executor and the expiry; it reads the sender from the transaction, so set it first. Poll GetSourceSwap to follow the swap.

Gasless deposits

A quote requested with gasless: true returns two extra fields: sponsor_address, the address that pays the gas, and sponsor_gas_budget, the largest gas budget in MIST the sponsor accepts. Plain deposits and swap-first intents can both be gasless. For a swap-first deposit the sponsor pays for the intent block and for the swap, which is charged as src_swap_gas. A gasless block must take the coin from the depositor’s coin objects, never from the gas coin, which belongs to the sponsor. A gasless plain deposit may also take part or all of the amount from the depositor’s address balance. Build the deposit block as above, then make it a sponsored transaction:
  1. Set the gas owner to sponsor_address.
  2. Leave the gas payment empty, so gas comes from the sponsor’s address balance rather than from coin objects.
  3. Simulate the transaction and set the gas budget to its computation plus storage cost with a margin of at most 1.5 times, and no more than sponsor_gas_budget. Sui needs the budget to cover gross storage even though most of it comes back as a rebate.
  4. Give it a ValidDuring expiration that starts no later than the current epoch and ends no later than the next one. The Sui TypeScript SDK sets this itself when the gas payment is empty.
The user signs the built transaction without executing it. Hand the BCS bytes and the signature to SubmitGaslessOrder: the API checks the block against the quote and verifies the signature, then the sponsor co-signs, simulates, and executes it. The block may do only what the quote describes. The API rejects it unless all of these hold:
  • No command uses the gas coin, and every input is a pure value, an owned or immutable object, or a shared object. A plain deposit may also have one withdrawal from the sender’s own address balance, of the quote’s source coin, reserving no more than expected_amount_in. Withdrawals from the sponsor or through an allowance, withdrawals in a swap-first intent, and received objects are refused.
  • Before the closing commands, the block only merges and splits coins, and redeems the withdrawal, if there is one, with a single 0x2::coin::redeem_funds of the quote’s source coin. A plain deposit may also hand back a leftover coin of the quote’s source coin with 0x2::coin::send_funds to the sender or 0x2::coin::destroy_zero, as the Sui SDK’s coinWithBalance does.
  • A plain deposit closes with deposit on the quote’s package and the escrow, with the quote’s coin type and quote hash. Its deposit coin is split off at expected_amount_in, or is the redeemed withdrawal when that reserves exactly expected_amount_in, and is left untouched until the deposit.
  • A swap-first intent closes with the commands listed in Swap-first deposits: optionally send_funds to the sender of a coin split off at no more than expected_amount_in and left untouched, which an address balance already holding the amount does not need; option::none<RateLimit>; propose_for_app with the quote’s source coin, the package’s SourceSwapFunding, a lifetime cap of exactly expected_amount_in, no start time, an expiration, and that none as the rate limit; and open_source_swap on the quote’s package and the escrow with the quote’s coin types, quote hash, and min_lock_amount.
  • The only shared objects are the escrow, plus the clock (0x6) for a swap-first intent.
The sponsor also refuses a block whose simulated net gas, after storage rebates, would cost more than the gas the quote priced for the deposit, at the gas price of the quote’s epoch. Once the block executes, the sponsor reads its gas summary from a quorum of RPC providers. If a deposit whose relay is priced out of the user’s output (not one paid with sponsor_fees) cost more net gas than sponsor_gas, the overrun is recorded on the submission and counts against the chain’s gasless capacity for an hour, and the client that requested the quote cannot submit gasless deposits for 24 hours.

With the SDK

gaslessSteps builds the block, sponsors it, has the wallet sign it, and submits it. Its deposit step resolves once the sponsor has broadcast the block; for a swap-first deposit a swap step follows, as in Swap-first deposits:
To sponsor a block you built yourself, sponsorSuiTransaction(rpcUrl, transaction, sponsorAddress, sponsorGasBudget) sets the gas fields, simulates once to size the budget, and returns the built transaction, ready for the wallet’s signTransaction.

Confirmation

For a plain deposit, the transaction digest is the order’s source transaction hash. For a swap-first deposit, the order’s source transaction is the relayer’s swap transaction, which the swap step returns and GetSourceSwap reports as swap_tx. Sui checkpoints are final: the deposit is picked up as soon as its transaction lands in a checkpoint, and one confirmation is all that is ever required. Nothing else is submitted by the user. Move to Track an order.