Most deposits finalize automatically — you only need this flow if yours didn’t. Confirm the deposit actually failed before you start (see Before you start). If you already received your bridged BTC, the deposit completed and no refund is needed.
- You sent BTC to a bridge deposit address.
- The deposit never completed on NEAR — you never received the bridged BTC.
- You want the BTC returned to a Bitcoin address you control.
Before you start: confirm the deposit never finalized
A refund only works if your deposit never completed on NEAR. The contract has no single “is it finalized” view method, so check the result of finalization instead:- Did you receive your bridged BTC? Finalization mints nBTC to the recipient. If the recipient holds the nBTC, the deposit completed — you don’t need a refund.
- The contract is the final word. Step 1 below (
request_refund) is rejected if the deposit already finalized viaverify_depositorsafe_verify_deposit— so a successful step 1 confirms it never completed.
How it works
The refund runs as three on-chain operations:- Request the refund — pin the original Bitcoin transaction to a refund address (
bridge-cli). - Execute the refund — call the BTC connector contract after a timelock passes (
near-cli). - Sign the Bitcoin transaction — trigger MPC signing so the relayer can broadcast the refund on Bitcoin (
bridge-cli).
bridge-cli. Step 2 is a direct contract call through near-cli, because it only becomes callable after a timelock and can be invoked by anyone — not just the original depositor.
The contract that owns this flow is satoshi-bridge, deployed on mainnet as btc-connector.bridge.near.
Prerequisites
-
bridge-cli— download a binary from the releases page, or build it from source: -
near-cli— used for theexecute_refundcall in step 2. -
The Bitcoin transaction hash of your original deposit, and the output index (
vout) of the deposit address within it. - A funded NEAR account to sign the transactions and cover gas and storage.
bridge-cli through environment variables (the preferred method) or a .env file:
Refund the deposit
Submit the refund request
The minimal invocation is the chain and the BTC transaction hash. The CLI asks the bridge indexer which output of the transaction is a tracked deposit address, recovers the original Optional arguments:
Example with manual args (the
DepositMsg, and uses its refund_address as the refund destination:| Argument | When to use it |
|---|---|
--vout N | Pick a specific output when the transaction has more than one tracked deposit address. The CLI tells you which vouts to choose from. |
--refund-address X | Only used when the original DepositMsg has no refund_address. If the deposit message carries one, that address is used as the refund destination and this flag is ignored. |
--recipient-id, --fee, --msg, --no-deposit-refund-address | Supply the original deposit args manually instead of relying on the indexer lookup. These must match the values used at deposit time — the contract recomputes the deposit address from them and rejects mismatches. |
--dry-run | Print the unsigned request_refund NEAR transaction (base64 borsh) for offline or hardware-wallet signing instead of submitting it. Requires --near-public-key. |
safe_deposit.msg path, where receiver_id inside --msg is the intents account the deposit was routed to):Wait for the timelock, then execute the refund
After the refund request, call
Pass that amount as the attached deposit:
execute_refund directly on the BTC connector contract. Anyone can call it — the Bitcoin transaction is already pinned to your refund_address by step 1.The wait depends on whether a refund address was set on the original deposit:| Timelock | Condition |
|---|---|
| 2 days | refund_address was provided in the original deposit |
| 14 days | refund_address was not provided in the original deposit |
| Instant | The caller holds the DAO or RefundOperator role on the connector |
utxo_storage_key is <btc_tx_hash>@<vout> of the original deposit. Attach a deposit to cover storage for the BTCPendingInfo entry — the connector exposes the amount it expects through the required_balance_for_execute_refund view method (1 NEAR on mainnet at the time of writing):Per the contract, this deposit covers storage for the refund and is not returned to you — treat it as a fee on the refund, not part of the recovered amount.
Trigger MPC signing of the refund transaction
execute_refund creates a BTCPendingInfo and emits a GenerateBtcPendingInfo event. Find btc_pending_id in the event logs of the execute_refund transaction (NEAR explorer or near tx-status) and pass it below. Once signed, the relayer broadcasts the Bitcoin transaction:Notes
- If the original deposit’s
DepositMsg.refund_addresswas set, that address is the refund destination —--refund-addressis ignored. The contract enforces that the refund transaction pays out to exactly that address. request_refundis rejected if the deposit was already finalized viaverify_depositorsafe_verify_deposit.- Replace all placeholder values (transaction hashes, addresses, account IDs) with your own before running any command.
Next steps
Token Bridges
See which bridges route assets between NEAR Intents and external chains
Withdrawals
Learn how withdrawals move assets out through bridges