- Lock and mint: lock USDT or USDT0 and mint the net amount as USDT on Bitcoin to an RGB wallet.
- Burn and unlock: burn USDT on Bitcoin and unlock the net amount as USDT or USDT0 to the EVM address committed in the burn.
Supported networks and routes
The bridge holds its mint and burn liquidity on Arbitrum One. A mint from Arbitrum calls the bridge contract directly. A mint from another supported source network first deposits into a Utexo entrypoint contract, which forwards the funds to the Arbitrum bridge before the RGB mint is executed. A burn is always released from the Arbitrum bridge: to Arbitrum directly, or to another supported network through USDT0 / LayerZero.The network IDs above are part of the bridge API contract. Contract addresses, token IDs, and operational availability are deployment-specific; obtain those values from
GET /networks and GET /networks/{networkId}/supported-tokens in the target environment.Base URL (staging):
https://transfer.gateway.stage.utexo.com/api/v0Contact the Utexo team for an operator API key, the supported rgb-lib version, and production access.Before you start
You need:- An operator API key. Send it in the
X-API-Keyheader onPOST /mint/op-id,GET /lock/{opId}/status, andPOST /unlock. Keep the key on your backend. Do not put it in a browser app or a wallet you do not control. The read-only estimate and capacity endpoints do not require the key. - An account on the source network with the supported USDT or USDT0 token and native currency for gas or fees. A direct Arbitrum mint also requires ETH. Ethereum, Polygon PoS, Plasma, and Tron use a Utexo entrypoint contract; see Deposit from another network.
- An RGB wallet using the supported
rgb-libversion. This guide calls its Rust API directly. - Bitcoin for wallet operations. The receiving wallet needs confirmed sats to create RGB-colorable UTXOs. A burn also needs an empty colorable UTXO and enough sats for its Bitcoin transaction fee.
rgb-lib amounts in this guide are integers in token base units. USDT and USDT0 have 6 decimals, so 1000000 is 1 USDT.
Find the network and token IDs
Use the protocol network IDs listed above. Discover contract addresses, token IDs, asset IDs, and active route configuration from the environment you are integrating with.
Estimate the result
Use the public estimate endpoint before either a mint or a burn:amount is always the gross input in base units:
- for a mint, it is the amount deposited on the source network;
- for a burn, it is the amount burned on Bitcoin.
receiveAmount is the expected amount on the destination after the bridge token commission, and:
Direct mint estimate from Arbitrum
For Arbitrum One, the grossamount is deposited directly into the bridge contract. Quote a gross deposit of 1 USDT or USDT0 like this:
senderAddress is required for the direct Arbitrum route. The bridge commission is calculated on the gross deposit, so:
amount in POST /mint/op-id. Its depositAmount is the value approved and passed to fundsIn, while mintAmount is the quoted net RGB amount.
Mint estimate from an entrypoint-based network
Ethereum, Polygon PoS, Plasma, and Tron use a two-stage quote:- The source entrypoint quotes how much of the gross deposit is guaranteed to arrive at the Arbitrum bridge.
- The bridge calculates its token commission on that guaranteed Arbitrum delivery and returns the final RGB
receiveAmount.
senderAddress, because the entrypoint quote and generated deposit parameters depend on the source account. For example, quote a Polygon deposit:
Append
?senderAddress={sourceAccount} to each path. Discover the source network’s USDT token ID through its supported-tokens endpoint; do not assume it is identical in every environment.
For an entrypoint-based network, the following equality applies to the amount guaranteed to arrive on Arbitrum, not necessarily to the original source deposit:
receiveAmount + totalTokenCommission may be less than the gross source amount. After POST /mint/op-id, use the returned entrypoint.depositParams, nativeFee, and other entrypoint fields to submit the source-chain deposit; do not construct those values from the estimate.
Estimate lifecycle and exact receive amounts
The endpoint is read-only: it does not reserve liquidity, prepare a mint, move funds, or require an API key. Treat the result as a quote, not a reservation. Fees or upstream delivery quotes may change before you prepare or submit the operation; the response fromPOST /mint/op-id is authoritative for that prepared mint.
To target an exact RGB receive amount, search for a gross input whose receiveAmount equals the target. Start with the target as the gross input, add the difference between the target and receiveAmount, and quote again until the endpoint returns the exact target. Integer rounding and a base fee mean that simply adding one percentage may not produce an exact result.
Use rgb-lib for RGB wallet operations
rgb-lib is a Rust wallet library for receiving, transferring, and tracking RGB assets. Utexo’s USDT on Bitcoin uses the BFA (Burnable Fungible Asset) schema, which allows the asset to be burned for release on a supported EVM network.
rgb-lib is a library rather than a hosted Utexo API. Call it directly from Rust or expose it through your own wallet service. The integration uses these methods:
blind_receivecreates a blinded invoice for a mint;refreshdiscovers and settles incoming transfers;burncreates and broadcasts the irreversible asset burn;get_asset_balanceandlist_unspentscheck readiness before a burn;get_send_consignment_pathlocates the raw burn consignment submitted to/unlock.
Add and initialize rgb-lib
Pin the supported revision and enable one supported indexer backend:rgb-lib and patched RGB crate revisions together; do not upgrade them independently.
Create or load the wallet with persistent keys and a persistent data directory, then take it online:
wallet.get_address() with Bitcoin on the configured network. After the funding transaction confirms, create RGB-colorable UTXOs:
Lock and mint
1
Quote the gross deposit
Call the estimate endpoint with the source EVM network, RGB destination network, EVM token ID, and proposed gross deposit. Record
receiveAmount as the amount the RGB wallet should receive.If the customer specifies the desired RGB amount instead, iterate estimates as described in Estimate the result to find an exact gross input.2
Create a blinded RGB invoice
A bridge mint requires a blinded recipient. Do not use a witness invoice: a witness recipient requires a satoshi amount in the Bitcoin output, while the bridge mint does not carry one.Create a new blinded recipient with Persist the invoice and
Wallet::blind_receive:batch_transfer_idx with your operation state. For a bridge mint, the invoice normally has an unspecified assignment; the bridge binds the prepared mintAmount to that blinded recipient.An invoice may omit the asset ID and amount. If another client produces an invoice that includes an asset ID or fungible assignment, they must match the supported USDT asset and the prepared mintAmount.Use a new invoice for every mint. The Mint refuses an invoice it has already paid.3
Prepare the mint
Send the blinded invoice and the gross source deposit from the estimate as Before moving funds, verify all of the following:
amount:depositAmountequals the gross amount you intend to deposit;mintAmountequals the amount in the blinded invoice and the amount the customer accepted;settlementDatais retained byte-for-byte for the contract call;- enough time remains in
ttlSecondsfor the deposit to be submitted.
4
Lock USDT or USDT0
Approve the bridge contract to spend exactly Submit the exact prepared amount. A different amount will not match the prepared mint.
depositAmount. If the token requires resetting an existing allowance, set it to 0 first. Then call fundsIn with the returned settlementData unchanged. destinationAddress is an empty string because the RGB recipient was supplied in the invoice.5
Track the mint
Poll the mint until it reaches
completed or expired:Signing starts only after the deposit is final on Arbitrum, so
funds_in_confirmed can last for a while before bridge_signed.6
Receive the USDT in rgb-lib
Refresh the wallet until the incoming transfer settles:
future includes transfers known to the wallet; spendable is the balance currently available to burn or transfer. Do not start a burn until the required amount is spendable.Deposit from another network
From Ethereum, Polygon PoS, Plasma, or Tron, quote and prepare the mint with that network assourceNetworkId. Include the depositor in the estimate’s senderAddress query parameter and in the /mint/op-id request’s sourceAddress, and include the source tokenId.
amount remains the gross amount deposited on the source network. The response adds an entrypoint object; mintAmount is the net amount quoted to arrive as USDT on RGB after the upstream delivery and bridge commission.
Instead of calling fundsIn directly:
- Approve
entrypoint.contractAddressto spenddepositParams.amountLDof the source token. - Call
deposit(depositParams)on the entrypoint contract withentrypoint.nativeFeeas the transaction value.
p is entrypoint.depositParams. extraOptions and payload are returned without the 0x prefix. On Tron, make the equivalent calls with TronWeb. Then track the mint with GET /lock/{opId}/status.
Burn and unlock
1
Quote the burn
Quote the gross amount to burn by reversing the route:Show the customer both the gross burn and
receiveAmount. The EVM recipient receives receiveAmount; totalTokenCommission is deducted during release. The quote does not check current release liquidity, so perform the capacity check separately.2
Check release capacity
available. It is the available USDT or USDT0 liquidity in the bridge, less releases in progress, capped by the current outflow limit when one is set. Also read minFundsOutAmount from the destination bridge contract and do not burn less than that minimum. Capacity is a snapshot and is not reserved for you, so recheck it immediately before the burn.3
Prepare the rgb-lib wallet
Refresh the wallet and verify the BFA balance is spendable:A burn needs enough spendable BFA assignments and an empty colorable UTXO with sats for the Bitcoin transaction. If no suitable UTXO is available, fund the wallet if necessary and call
Wallet::create_utxos before proceeding:4
Burn with rgb-lib
Convert the 20-byte EVM recipient into the 32-byte value required by BFA: remove Decode and validate the address before building the 32-byte recipient. Then call
0x, lowercase it, validate that it has 40 hexadecimal characters, and left-pad it with 24 zeroes.Wallet::burn with the gross RGB amount, Bitcoin fee rate, and wallet confirmation policy:Wallet::burn builds and signs the PSBT, writes the BFA consignment, broadcasts the Bitcoin anchor transaction, and returns its transaction ID and transfer index. Persist both values before continuing. If the call fails after submission and it is unclear whether a transaction was created, inspect wallet.list_transfers(AssetFilter::Id(rgb_asset_id.to_owned()), None) before retrying. Never retry an ambiguous burn automatically.5
Read and encode the burn consignment
Ask Send
rgb-lib for the consignment created by the burn, then read and base64-encode the raw bytes:base64_consignment as the consignment field of /unlock. Do not encode the path or a hex representation of the file; /unlock expects standard base64 of the raw consignment bytes.6
Submit the burn
maxFeeRate is optional. When supplied, it is a non-negative decimal string containing the maximum EVM gas price in wei that you accept.You may submit immediately after the burn. The release waits until the Bitcoin relay has observed the burn and the required confirmation depth has been reached. Until then, the API returns 409 with code 6, 7, or 8; resend the same request later. The amount and recipient are read from the consignment and cannot be overridden by the request.7
Confirm the release
A successful submission returns:
submitted means the release transaction was sent to the destination EVM network; it is not yet a confirmation. Verify that recipient is the address committed in the burn, then follow evmTxHash until the transaction confirms. The recipient receives the estimate’s net amount.Release to another network
To release to Ethereum, Polygon PoS, Plasma, or Tron, use that network as the destination in every burn request, and use the token ID of USDT on that network:POST /unlock, set destinationNetworkId to the destination network. tokenId is required for these releases:
evmTxHash is the Arbitrum transaction. A confirmed Arbitrum transaction means the send was submitted; delivery to the recipient happens later, in a separate transaction on the destination network. The estimate does not include a fee that USDT0 / LayerZero may take on delivery.
Retries and errors
Errors have the shape{"error":"...","code":4,"details":{...}}. details is present only when the error has structured retry information.
On
POST /mint/op-id, a 400 may indicate that the deposit does not cover the commission, the invoice is malformed, the invoice uses a witness recipient, the invoice amount or asset does not match, or the route is invalid. A conflict indicating that the RGB invoice was already fulfilled means you must create a new blinded invoice.
For unlock codes 6, 7, and 8, nothing was sent to the destination EVM network, and retrying the same request with the same consignment is safe. For transport errors or a 500 during an irreversible or externally submitted step, first reconcile the RGB wallet, bridge status, and EVM transaction state. Do not assume the operation failed merely because the response was lost.