Skip to main content

frUSD Deposits from Other Chains

Status: early production

The contracts on this page are live on Ethereum, BNB Chain, Polygon and Base, and so is the relay that broadcasts signed deposits on the source chain. The first deposit through the relay went from Base USDC to frUSD on Bitcoin end to end on 7 October 2026 (below). The SUBFROST app has no screen for it yet. Integrators register intents with the peg-in service over subtun0; every encoding is in Encoding Pegs and Swaps. The worked example below predates the current forwarders: it went through the first-generation BNB Chain forwarder, which is now retired, but every step after the bridge is unchanged.

The frUSD vault lives on Ethereum. This page shows how stablecoins that start on BNB Chain, Polygon or Base reach it, by following one real deposit (BNB Chain USDT): 3,000.000000 USDT sent on BNB Chain on 30 September 2026, which became payment #52 in the frUSD vault, 2,989.017990 USDT owed as frUSD on Bitcoin.

The short version​

  1. The depositor commits to one intent on the source chain. A DepositIntent names the asset, the exact amount, a hash of the Bitcoin recipient, the refund rule, an expiry and a nonce. In the worked example the commitment is a Permit2 signature whose witness is the intent; there are four ways in in all.
  2. The forwarder pulls the stablecoin and bridges it. The forwarder for that chain and token takes the stablecoin and hands it to the bridge (Stargate, or Circle CCTP on Base), addressed to a deposit address (the gateway) on Ethereum that is derived from the intent.
  3. The bridge delivers it. On Ethereum the bridge pays the stablecoin to the gateway, which has no code yet.
  4. settle sweeps it into the vault. One call on the GatewayFactory deploys the gateway, sweeps it and deposits the stablecoin into the frUSD vault.
  5. The vault queues a payment. The FROST signer group mints the matching frUSD on Bitcoin, to the script the intent committed to.

These steps follow the worked example, which took a Stargate route. The Base route differs only in steps 2 and 3: its forwarder burns the USDC on Base through Circle CCTP v2, and the USDC is minted to the same gateway on Ethereum once Circle's attestation of the burn is submitted there. From step 4 on, nothing changes. Base USDC also supports EIP-2612, so on Base the depositor can sign a permit in place of the one-time approval and never has to send a transaction; see The relayer on the source chain below.

BNB CHAINDepositorsigns one intentPermit2SweepForwarderpull and bridge, one transactionStargate pooltakes the USDT in3,000.000000Permit23,000.000000sendTokenLayerZero, 38 sStargate fee 6.0000012,993.999999ETHEREUMStargate poolreleases the USDTgatewayno code until settle2,993.99999900:32:11GatewayFactorysettle, 00:44:35deployed and swept in one transactionfrUSDvault2,993.999999depositAndBridgeBITCOIN L1payment #522,989.017990 after feesFROSTsigner groupfrUSDbc1pcpfr…qvhvqwdmints

Amounts are USDT from the worked example; the dashed box is the gateway, which held the deposit with no code until settle deployed it and emptied it in the same transaction.

Routes​

Five bridged routes are live. Each starts with a stablecoin on a source chain and arrives on Ethereum as one of the two tokens the frUSD vault holds.

Source chainTokenDecimalsBridgeArrives on Ethereum as
BNB ChainUSDT18StargateUSDT (asset id 1)
BNB ChainUSDC18StargateUSDC (asset id 0)
PolygonUSDC6StargateUSDC (asset id 0)
PolygonUSDT06StargateUSDT (asset id 1)
BaseUSDC6Circle CCTP v2USDC (asset id 0)
  • Base has no USDT route. A pair that is not in the table is not supported.
  • Base USDC goes over CCTP. Circle burns it on Base and mints it on Ethereum, so there is no pool and no native bridge fee; the deposit is finished on Ethereum by a receiveMessage call, which the frusd-sweep group sends.
  • Polygon's USDT reports its symbol as USDT0. It is the USDT contract on Polygon (0xc2132D05D31c914a87C6611C10748AEb04B58e8F), and it arrives as Ethereum USDT.
  • The Polygon USDC route is native USDC, not USDC.e.
  • USDC or USDT already on Ethereum needs no bridge. The deposit address receives an ordinary ERC-20 transfer on Ethereum, from a wallet or an exchange withdrawal.
  • A forwarder belongs to a route, not to a chain. Each forwarder is built for one token and one bridge on one chain, so BNB Chain has two, and Base has two for USDC: the CCTP one, which is the route being opened, and a Stargate one. The same address can be a different contract on another chain, so a client must look a forwarder up by chain and token, never by chain alone. The addresses are in the Contracts table below.

Four ways in​

Every current forwarder accepts the stablecoin four ways. All four end in the same bridge transfer to the same gateway, because the forwarder derives the destination from the intent, not from its caller.

LaneWhat the depositor doesForwarder entry point
Deposit addressSends the stablecoin to a per-intent address on the source chain, from any wallet or exchangebridgeFromEscrow, reached through SourceEscrowFactory.sweep
Permit2Signs a Permit2 witness transfer (after a one-time approve(Permit2))sweepAndBridge
EIP-2612 permitSigns a token permit to Permit2 plus the Permit2 transfer: no approval transactionsweepWithPermit (not on BNB Chain, whose tokens have no permit)
Direct depositApproves the forwarder and calls it, paying gasdepositAndBridge

The typed data for each is in Encoding Pegs and Swaps.

The intent​

Everything about a deposit is fixed by one DepositIntent. These are its fields, in order, with the values of the worked example:

FieldWhat it holdsWorked example
assetId (uint8)The vault's asset id: 0 = USDC, 1 = USDT.1
token (address)The token the frUSD vault holds for that id, on Ethereum.0xdAC17F958D2ee523a2206206994597C13D831ec7 (Ethereum USDT)
amount (uint256)The exact amount that must arrive at the gateway, in the token's base units.2992000000 (2,992.000000 USDT)
recipientScriptHash (bytes32)keccak256 of the Bitcoin scriptPubKey that receives the frUSD.0x615f28775040b5eecc619a53fce969d88d2a058566d0242818329a772bd723bb
convertBps (uint32)0 = pure frUSD; above 0, that share in basis points is converted to BTC.0
btcDestHash (bytes32)keccak256 of the BTC destination when convertBps is above 0, else zero.zero
maxSlippageBps (uint16)Slippage limit for the BTC conversion; unused when convertBps is 0.0
refundAddress (address)Where a recoverable deposit is returned. Zero means no refund.zero address
expiry (uint64)Unix seconds.1790805138 (2026-09-30 21:52:18 UTC)
nonce (uint256)Anti-replay; also the Permit2 nonce.1

The intent is hashed with EIP-712 under the domain name SUBFROST frUSD Gateway, version 1, chain id 1, and the GatewayFactory as verifying contract. The type string is part of the hash, so the field order and the field names are part of it too:

DepositIntent(uint8 assetId,address token,uint256 amount,bytes32 recipientScriptHash,uint32 convertBps,bytes32 btcDestHash,uint16 maxSlippageBps,address refundAddress,uint64 expiry,uint256 nonce)

typehash = 0xd0ba66893952bd61d59c1120e0819129666508130451c7f40bba1c37f5d730c5

Every implementation that hashes an intent (the contracts, a wallet, an off-chain service) must produce the same bytes. Reordering two fields, renaming one, or packing the narrow integers instead of widening them to 32 bytes gives a different hash, and with it a different deposit address. The worked example's intent hash is 0xf00c4c2bb3e7a7f0553508e6d4da138400426a81ce6912c665bfde24dd9858da; intentHashOf on the GatewayFactory returns the same value.

Two fields read oddly from the source chain's side:

  • token names the Ethereum token the route arrives as (USDT or USDC), not the token on the source chain. In the worked example that is Ethereum USDT, although the money started as USDT on BNB Chain. The intent is the Ethereum side's object, hashed under the GatewayFactory's domain, and the vault holds Ethereum USDC and USDT, not the source chain's tokens. The forwarder refuses an intent whose assetId and token are not the pair it is wired for.
  • amount is what must arrive, not what is sent. settle reverts if the gateway holds less. The forwarder derives the bridge floor from it: amount × 10^(source decimals - 6), because the vault's tokens have 6 decimals. That factor is 10^12 for both BNB Chain tokens (18 decimals) and 1 on Polygon and Base (6 decimals). A client sets amount a little below the bridge's quoted arrival (the client library's default slack is 20 bps): set too high, the deposit is stranded, because settle reverts until the gateway is topped up (or reclaimed, if the intent has a refund address); set lower, nothing is lost, because over-funding is relayed in full. In the worked example the depositor sent 3,000.000000 against an amount of 2,992.000000, so Stargate's fee fit above the floor.

One hash, four jobs​

The intent hash is used in four places, on two chains:

  1. The CREATE2 salt of the gateway. The hash fixes the deposit address.
  2. The Permit2 witness on the source chain. The depositor's signature covers the whole intent. The forwarder sets the Permit2 nonce to the intent's nonce, so Permit2 spends the intent itself, not just one signature: the same intent cannot be pulled twice, even through another forwarder.
  3. The registration key on Ethereum. register stores the intent and its Bitcoin script under the hash, and the single-use consumed flag is keyed by it.
  4. The argument to settle. settle(intentHash, tokens) needs nothing else to find the intent, the gateway and the recipient.

The consequence: the bridge carries only an address and an amount. On a Stargate route, the forwarder sends Stargate the gateway address and the stablecoin, with an empty compose message. The Bitcoin recipient, the asset, the amount floor and the refund rule never cross chains as instructions; they were fixed by the hash before any token moved.

The deposit address (the gateway)​

Each intent gets its own gateway: an EIP-1167 minimal-proxy clone of one GatewayProxy implementation, deployed with CREATE2 and salted with the intent hash.

salt     = intentHash
initCode = 3d602d80600a3d3981f3363d3d373d3d3d363d73 <implementation> 5af43d82803e903d91602b57fd5bf3
gateway = last20(keccak256(0xff ++ GatewayFactory ++ salt ++ keccak256(initCode)))

# worked example
GatewayFactory = 0xaF4C39A0Da304eF39D56783d785ea52bb8431362
implementation = 0xdbA4c9fe841A7cf0b37B041126bA4d4640B4F296
salt = 0xf00c4c2bb3e7a7f0553508e6d4da138400426a81ce6912c665bfde24dd9858da
keccak256(initCode) = 0xae78bd81aa686ad972e9cf69b3b526123eae9615a544c18a6b64819d63072968
gateway = 0x0d69fc4dc9c1d34bf63d76bf147a1e1e86065263

The init code depends only on the implementation address, so the gateway is a pure function of the factory and the intent hash. Anyone can derive it before anything exists: offline with the formula above, with gatewayFor(intentHash) or gatewayForIntent(intent) on the GatewayFactory, or with preview(intent, srcAmount) on the forwarder, which on a Stargate forwarder also returns the floor, the LayerZero fee quote and the amount Stargate expects to deliver.

A gateway can only move tokens when the GatewayFactory calls it; the factory address is fixed in the implementation's code. The factory picks destinations only from fields inside the intent hash: itself for the intent's token (to relay into the vault), and refundAddress for anything else.

In the worked example the Stargate pool paid 2,993.999999 USDT into the gateway at 00:32:11 UTC, when the address had no code. It held the deposit that way for about 12 minutes, and got its code at 00:44:35 in the same settle transaction that emptied it. Gateways never self-destruct, so once an address has code it keeps that code.

The deposit address exists only on Ethereum

The gateway is derived from the GatewayFactory on Ethereum and is deployed only there. The same hex on BNB Chain, Polygon or Base is not a deposit address. Tokens sent to it on any other chain cannot be recovered by this system.

The Bitcoin address is a commitment​

The intent carries only recipientScriptHash, the keccak256 of the Bitcoin scriptPubKey. The source chain never sees the address.

register on Ethereum reveals the script and checks that its keccak256 equals recipientScriptHash. Anyone may call it, and that is safe: a different script has a different hash, so it belongs to a different intent, whose gateway is a different address with no money at it. register also accepts only standard script forms (P2WPKH, P2WSH, P2TR, P2SH, P2PKH) and reverts on anything else, because the vault stores the recipient without checking it.

To re-check the worked example:

# the scriptPubKey revealed by register (34 bytes, P2TR: OP_1, PUSH32, 32-byte key)
recipientScript = 5120c052331c3d07822a338e86cd14d3d011e21e562380ea96f6093f7cc233dab49e

# keccak256 over those 34 bytes
keccak256(script) = 0x615f28775040b5eecc619a53fce969d88d2a058566d0242818329a772bd723bb
= intent.recipientScriptHash

# the same script as a bech32m address (witness version 1, the 32 bytes after 5120)
address = bc1pcpfrx8paq7pz5vuwsmx3f57sz83pu43rsr4fdasf8a7vyv76kj0qvhvqwd

Register, settle, reclaim​

The GatewayFactory on Ethereum has three calls a depositor needs to know. All three are permissionless.

CallWho may callWhat it checksWhat it pays outGas refund
register(intent, recipientScript, btcDestination)AnyoneThe intent is not registered yet. keccak256 of each script matches the hash in the intent. The vault's assets(assetId) is token. The scripts are standard and convertBps is at most 10,000.Nothing. It stores the intent and the scripts.No
settle(intentHash, tokens)AnyoneThe intent is registered and not yet consumed. It has not expired, if it has a refund address. tokens lists the intent's token exactly once (at most 8 entries). The gateway holds at least amount.Deploys the gateway if needed and sweeps it. The intent's token goes into the frUSD vault through depositAndBridge; any other listed token goes to refundAddress.Yes, from the float, best effort
reclaim(intentHash, token)AnyoneThe intent is registered and has a non-zero refundAddress. It has expired, or the token is not the intent's token, or the balance is below amount, or the intent was already settled.The gateway's whole balance of that token, to refundAddress only.No

settleMany settles up to 16 intents in one call, with one refund for the batch. reclaimETH returns ETH sent to a gateway by mistake, also to refundAddress only.

The rules that matter to a depositor:

  • Under-funded is not settleable. If less than amount arrives, settle reverts. reclaim is one way out, and it needs a refund address. The other is to bring the gateway's balance up to amount with a further transfer of the same token on Ethereum, after which settle is valid again, unless the intent has a refund address and has already expired; then only reclaim applies.
  • Over-funded relays the whole balance. The extra goes into the vault with the rest. The worked example was over-funded: 2,993.999999 arrived against an amount of 2,992.000000.
  • Expiry blocks settle only when a refund address is set. With a refund address, an expired deposit belongs to reclaim. Without one, settle stays valid after expiry, because relaying is the only way the depositor is paid.
  • refundAddress = zero means no refund path at all. reclaim and reclaimETH always revert with NoRefundAddress, and settle reverts if it is asked to move any other token. A wrong token or ETH sent to such a gateway cannot be moved by any call. The worked example used a zero refund address.
  • A non-zero refund address is what makes a mistake recoverable. A wrong token, a short deposit, a late deposit, or anything that arrives after settlement can be sent back to it by anyone, because it is the only destination reclaim can use.

settle reimburses its caller's gas in ETH from a float that the SUBFROST operators fund on the GatewayFactory. The refund is capped per call and priced at no more than the block's base fee plus a capped tip. When the float is empty, settlement still works: the caller is not reimbursed and the factory emits GasRefundShortfall. When the figures on this page were read, the float held 0 wei, so callers were paying their own gas.

The factory's owner can set the refund parameters and withdraw the float's ETH. It has no path to move a deposited token or to redirect a settlement.

The relayer on the source chain​

A signed intent is broadcast on the source chain by a relayer calling sweepAndBridge(intent, owner, srcAmount, deadline, signature) (or sweepWithPermit, or SourceEscrowFactory.sweep for a deposit address) on the forwarder. Anyone may call it. SUBFROST runs a relayer for this: the pegin-relay group, a 2-of-3 cggmp21 group whose EOA is 0x7F6f63dEE1EC0096EE81ba7d02E6de72a76b3001 (see Signer Groups and Contracts). What the relayer can and cannot do follows from the contract:

  • It cannot redirect the funds. Nothing about the destination is a parameter. The forwarder re-derives the intent hash and the gateway address from the signed intent, with the same code the GatewayFactory runs.
  • It cannot accept a bad fill. The minimum Stargate must deliver is derived from the intent (amount × 10^(source decimals - 6)), so Stargate reverts rather than deliver less than settle will accept.
  • It cannot leave funds half-way. The Permit2 pull and the Stargate send are one transaction. If the bridge reverts, the pull reverts with it and nothing leaves the depositor's wallet.
  • It cannot reuse the signature elsewhere. Permit2 binds the spender to the forwarder, so the signature can only be redeemed by this forwarder on this chain.
  • Its only choices are when to broadcast and how much native fee to pay. Both are its own money: it pays the gas and the LayerZero fee, and Stargate returns any unused native fee to it.

The list above describes the Stargate forwarders of the worked example. The Base CCTP forwarder pulls the same way and derives the same gateway from the intent, so it cannot redirect the funds either. Its bridge leg is Circle's instead of Stargate's, so its relayer pays gas but no LayerZero fee.

Permit2 can only move a token it has an allowance for, so before the first deposit a Permit2 allowance must exist for the stablecoin on that chain: approve(Permit2, max), once ever per token per chain. USDT on BNB Chain has no permit, so there that allowance can only be set by a transaction. Every deposit after it is a signature only. USDC on Base has one (EIP-2612), and the Base forwarders accept it in the same call as the intent (sweepWithPermit): the depositor signs the permit and the intent, and sends no transaction at all. The Permit2 deadline may be at most 7 days after the broadcast; a signature with a later deadline is refused.

Fees​

StepUSDTRule
Sent on BNB Chain3,000.000000
Stargate fee6.000001Sent minus arrived. Stargate's fee, 0.2% in this deposit, set by Stargate, not by SUBFROST. It is not fixed: it varies with the pool's balance at the time, so it is quoted live (quoteOFT) before signing.
Arrived at the gateway2,993.999999What settle relays.
Vault flat fee1.990000flatFee()
Vault protocol fee2.992009protocolFeeBps() = 10, on the remainder after the flat fee, rounded down
Queued for Bitcoin2,989.017990Payment #52
Total10.9820100.366% of 3,000.000000

The order is fixed: Stargate takes its fee in transit, so the vault sees only what arrived. The vault then takes its flat fee first and the percentage on what remains. Both vault fees are read live at settle and can be changed on chain; they apply to bridge-outs too. For current values see frUSD Overview.

On the Base route Circle takes the place of Stargate. The Base forwarder burns at Circle's standard finality (MIN_FINALITY_THRESHOLD = 2000), for which Circle's fee from Base to Ethereum was 0 when read on 5 October 2026 (the faster finality costs 1.3 basis points). While that fee is 0, the amount that reaches the gateway is the amount sent. The vault fees are the same on every route.

The network costs are separate. In this deposit the relayer paid the BNB Chain gas (453,336) and the LayerZero fee (0.002225801385214171 BNB). On Ethereum, register used 253,158 gas and settle 628,204, paid by whoever sent them.

What the vault records​

The frUSD vault stores each deposit as a payment. Payment #52, as getPayment(52) returns it:

getPayment(52)
depositor 0xaF4C39A0Da304eF39D56783d785ea52bb8431362 # the GatewayFactory, not the user
assetAmount 2989017990 # 2,989.017990 USDT, after fees
bridgeData 0x12225120c052331c3d07822a338e86cd14d3d011e21e562380ea96f6093f7cc233dab49e
timestamp 1790729075 # 2026-09-30 00:44:35 UTC
assetId 1 # USDT
  • depositor is the GatewayFactory. The factory made the vault call, so it is recorded as the depositor. Who is paid on Bitcoin is decided by bridgeData alone.
  • bridgeData is protobuf field 2 plus the script: 0x12 (field 2, bytes), 0x22 (length 34), then the 34-byte scriptPubKey from register. The vault stores it verbatim and never parses it; the factory builds it from the registered script and validates it before every vault call.
  • frAmount is fixed at deposit time. The stored record and the PaymentQueued event carry frAmount = 298,901,799,000, the same value in frUSD's 8 decimals, so the conversion from 6 to 8 decimals is settled when the payment is queued.

There is no "processed" flag on a payment. The alkane on Bitcoin records how far it has minted, and the at-most-once key for a mint is the pair (vault address, payment id). From Ethereum you can see that a payment is queued, not whether it has been minted.

The reserve​

On every deposit, after queuing the payment, the vault moves its idle USDT into its Curve 3pool strategy, but only once the idle balance (not counting fees) reaches minSweep, which is 1,000 USDT. Smaller deposits wait in the vault and go in with a later one. That is why this settle swept 3,011.989995 USDT: this payment's 2,989.017990 plus 22.972005 left idle by payment #51, an earlier 24.984999 USDT intent settled at 2026-09-29 20:22:47 UTC that stayed under minSweep. Fees are never swept; they stay in the vault.

For integrators

A settle that triggers the sweep costs more gas: 628,204 for this one, against 403,384 for the previous settle, which did not sweep. A gas limit sized for the path without the sweep fails whenever a deposit triggers one, so size it for the sweep.

pending(assetId) is a running total of every net amount ever queued for that asset, and it is never decremented. It is not the outstanding liability. Read on 1 October 2026, pending(1) was 1,364,365.728510 USDT, more than the vault held: 1,504.071913 idle, which is exactly feesCollected, plus 1,362,094.122333 in the strategy. The vault had 53 payments at that point.

The worked example, end to end​

Time (UTC)ChainWhat happenedTransaction
2026-09-29 21:52:35Ethereumregister: the intent and the Bitcoin script are stored under hash 0xf00c…58da. 253,158 gas.0x4fce…2e1a
2026-09-30 00:31:33BNB ChainsweepAndBridge: Permit2 pulls 3,000.000000 USDT and the forwarder calls Stargate sendToken to the gateway. A relayer pays 453,336 gas and 0.002225801385214171 BNB of LayerZero fee.0x8611…8767
2026-09-30 00:32:11Ethereum38 s later, the Stargate pool releases 2,993.999999 USDT to the gateway, which has no code.0xa4cd…fd15, LayerZero message
2026-09-30 00:44:35Ethereumsettle: the gateway is deployed and swept, 2,993.999999 USDT goes into the frUSD vault, payment #52 is queued for 2,989.017990, and the vault sweeps 3,011.989995 USDT into its strategy. 628,204 gas.0xd0ec…46cd

From the send on BNB Chain to the queued payment took 13 minutes 2 seconds. register and settle were sent from the depositor's own wallet; both calls are open to anyone.

The first run through the relay​

The first deposit to go all the way through SUBFROST's own relay groups, on 7 October 2026: 10.000000 USDC on Base, by EIP-2612 permit (no approval transaction), over CCTP.

ChainWhat happenedTransaction
BaseThe pegin-relay group calls sweepWithPermit on the CCTP forwarder: the permit and the Permit2 witness are applied, the USDC is pulled and burned by CCTP toward the intent's gateway0x4932…94d1
EthereumThe frusd-sweep group completes the CCTP message (the USDC is minted to the gateway), then settleMany sweeps the gateway into the vault: payment #62, 8.001990 USDC after the vault's fees0x7aae…4de4
BitcoinThe frUSD signer group mints the frUSD to the intent's recipient script (output 0) and returns frUSD-MINT to itself (output 1). Block 970,356d833c5b3…209b

Contracts​

ContractChainAddressRole
GatewayFactoryEthereum0xaF4C39A0Da304eF39D56783d785ea52bb8431362Derives gateways; register, settle, reclaim; relays into the vault.
GatewayProxy implementationEthereum0xdbA4c9fe841A7cf0b37B041126bA4d4640B4F296The code every gateway clones.
Permit2SweepForwarder (USDT)BNB Chain0xC1d512684B80D6A179F468595074AcdDFAAe3c1DTakes USDT (four ways in) and bridges it to the gateway over Stargate.
Permit2SweepForwarder (USDC)BNB Chain0x8BFB31f205Ff5a51779b5B25B9BeD4F608d4C722Takes USDC and bridges it over Stargate.
Permit2SweepForwarder (USDC)Polygon0x91F3b7Fc7585421EAEF404eb69F90A52C83BE353Takes native USDC and bridges it over Stargate.
Permit2SweepForwarder (USDT0)Polygon0x9b728A1366EFD0D892eb50e3E75DFFF5eD666EC7Takes USDT0 and bridges it over Stargate.
Permit2CctpForwarder (USDC)Base0x5e656a3e5680046e1E71361d59fdfbe058546518Takes USDC and bridges it over Circle CCTP v2.
SourceEscrowFactoryBNB Chain0xdebE84e9FB41232CfAC5A1BFba50b15C36EBb0c5Per-intent deposit addresses on BNB Chain.
SourceEscrowFactoryPolygon0x76af40680f85f6A6c36CcF43eD2e0B8B78B241F3Per-intent deposit addresses on Polygon.
SourceEscrowFactoryBase0x03aEBD3ACE98Ce26081fBF51E3970ab33cA70E60Per-intent deposit addresses on Base.
Stargate pool (USDT)BNB Chain0x138EB30f73BC423c6455C53df6D89CB01d9eBc63Takes the USDT in on BNB Chain.
Stargate pool (USDC)BNB Chain0x962Bd449E630b0d928f308Ce63f1A21F02576057Takes the USDC in on BNB Chain.
Stargate pool (USDC)Polygon0x9Aa02D4Fae7F58b8E8f34c66E756cC734DAc7fe4Takes the USDC in on Polygon.
Stargate pool (USDT0)Polygon0xd47b03ee6d86Cf251ee7860FB2ACf9f91B9fD4d7Takes the USDT0 in on Polygon.
Stargate pool (USDT)Ethereum0x933597a323Eb81cAe705C5bC29985172fd5A3973Releases USDT to the gateway on Ethereum.
Stargate pool (USDC)Ethereum0xc026395860Db2d07ee33e05fE50ed7bD583189C7Releases USDC to the gateway on Ethereum.
Permit2BNB Chain, Polygon, Base0x000000000022D473030F116dDEE9F6B43aC78BA3The canonical Permit2 the forwarders pull through, at the same address on every chain listed.
frUSD vault (proxy)Ethereum0x95779E7E1C943042255B8a78273fe6DE4823CF06Takes the deposit and queues the payment for Bitcoin.
frUSD vault implementationEthereum0x5376beedba9ba7f5b0396d28bd75473027b8bd55The code behind the vault proxy.
frUSD vault strategyEthereum0x5c5d66c82e2c634074661c3e7427668737c70100Holds the reserve in Curve 3pool.
Retired Base forwarders

Two earlier Base forwarders are still deployed but retired: 0x24167a1b385145057bdc93aabf5dbe2047e5c7c6 (Stargate) and 0x9A2Bd5Ab5A9fB426d7F754eae14B54b3C3F93F19 (CCTP). The relayer does not serve them, and a Permit2 signature names its forwarder as the spender, so an intent signed for a retired forwarder cannot be redeemed through a current one. One call tells the generations apart: escrowFactory() answers on both current Base forwarders and reverts on the retired ones. It does not say which route is open. On other chains the same two addresses are deployed, unrelated forwarders (Polygon USDT0 and BNB Chain USDC in the table above), one more reason to look a forwarder up by chain and token.

Permit2Gateway, the gasless path for funds already on Ethereum, is not deployed.

The first-generation forwarders (BNB Chain 0xb24bccc6256309d0dff6883ab874e84179d53da9 and 0x9A2Bd5Ab5A9fB426d7F754eae14B54b3C3F93F19, Polygon 0xb24bccc6256309d0dff6883ab874e84179d53da9 and 0x24167a1b385145057bdc93aabf5dbe2047e5c7c6, Base 0x24167a1b385145057bdc93aabf5dbe2047e5c7c6) are retired. The worked example went through the first of them. They support only sweepAndBridge; don't build on them. The full registry, with peg-out contracts, is in Signer Groups and Contracts.

Where to go next​