vBTC Raw Integration Guide
Esta página aún no está disponible en tu idioma.
This guide is for integrations whose VFX keys never reach the node: web wallets, exchange signers, HSMs. A node builds each vBTC transaction unsigned, the client checks it and signs its hash, and the node verifies and broadcasts the signed transaction. The node needs no account, key or balance of yours.
It describes vBTC V2 on Core 9.0. Read vBTC first for the concepts (vaults, owners and holders, watched and legacy vaults, networks and heights, transaction types): this page covers what differs when you sign outside the node. All routes are under /vbtcapi/VBTC on the node’s API (http://localhost:7292, testnet 17292); send the apitoken header if the node requires one.
How the raw routes work
Section titled “How the raw routes work”Every action that sends a VFX transaction follows the same four steps.
1. Build
Section titled “1. Build”Call the action’s GetRaw… route. It checks the request against the same rules block validation applies and answers either with a refusal ("Success": false and a Message) or with the unsigned transaction:
{ "Success": true, "Hash": "39ff6b9b…a22b", "Timestamp": 1792774390, "FromAddress": "<signer>", "ToAddress": "<signer or recipient>", "Amount": 0.0, "Fee": 0.00001375, "Nonce": 2, "TransactionType": "VBTC_V2_WITHDRAWAL_REQUEST", "Data": "{\"Function\":\"VBTCWithdrawalRequestMultiV2()\",…}", "HashPreimage": "1792774390<signer><signer>0.00.000013752VBTC_V2_WITHDRAWAL_REQUEST{\"Function\":…}", "Message": "Sign the Hash field and submit via SendRawRequestWithdrawalTx. …"}The build is kept on that node for about 65 minutes. Submit it to the same node.
2. Check before signing
Section titled “2. Check before signing”Never sign a hash you have not checked. The hash is:
Hash = SHA-256( lowercase hex of SHA-256( HashPreimage as UTF-8 ) ), as lowercase hexHashPreimage = Timestamp + FromAddress + ToAddress + Amount + Fee + Nonce + TransactionType + Datawith no separators, TransactionType as its name (for example VBTC_V2_WITHDRAWAL_REQUEST), and Data exactly as returned. A reserve (xRBX) transfer appends its UnlockTime.
- Recompute the hash. Where the route returns
HashPreimage(vault creation, multi-vault transfer, multi-vault withdrawal), hash it and compare withHash, and check that the preimage starts withTimestampandFromAddressand containsDataverbatim. The single transfer, fee-raise and release builders return every field of the preimage. The single withdrawal-request and cancel builders leave outToAddressandAmount, which are always the sender’s address and0.0.AmountandFeeenter the preimage as the node’s decimal text (0.0for zero), which a JSON parser may not reproduce, so compare field by field if you can’t rebuild the text exactly. - Read
Dataand check that it says what you asked for: the action’sFunction, the vault (ContractUID), the amount or allocation, the Bitcoin address and the fee rate. The sections below list what to check for each action. - Check
FromAddressis the address whose key you are about to use, andFee(in VFX) is what you expect.
3. Sign
Section titled “3. Sign”Sign Hash (the hex string, as UTF-8 text) with the sender’s key, ECDSA over secp256k1, exactly as for any VFX transaction (see Transaction Creation). The signature is the DER signature in Base64, a ., and the public key (64 bytes, without the 04 prefix) in Base58.
4. Submit
Section titled “4. Submit”Post to the matching SendRaw… route:
{ "Hash": "<the Hash the build returned>", "Signature": "<Base64 DER signature>.<Base58 public key>", "PublicKey": "<the signer's public key>"}The node runs its full transaction check on the signed build, then broadcasts it:
{"Success": true, "Hash": "…", "Message": "Withdrawal request transaction broadcast successfully."}- A bad signature:
Transaction verification failed: Signature Failed to verify.The build is kept, so sign again and resubmit the same hash. - An unknown or already used hash:
No pending transaction found for hash …. Call GetRawRequestWithdrawalTxData first., orTransaction … was already submitted. - With the sender’s other transactions still waiting to be mined it would overspend:
Not broadcast: together with this address's transactions still waiting to be mined it would overspend (…). Submit it again once they are mined. Hash, Signature, and PublicKey are required.
The withdrawal-request, cancel, completion, fee-raise and release SendRaw… routes accept only their own build type, and a fee raise can’t be sent as a release or the reverse. The transfer and vault-creation routes don’t check the type, so always submit a build through its matching route. Submit vBTC builds through these routes, not the general txapi/TXV1 raw routes.
The builders take the sender’s next nonce from chain state when they build. Build and submit one transaction at a time per address, and build the next once the previous one is in a block, unless the route lets you pass Nonce.
Reading state without a node wallet
Section titled “Reading state without a node wallet”The read routes answer from chain state, so any node answers for any address or vault:
- Balances:
GET GetAllVBTCBalances/{address},GET GetVBTCBalance/{address}/{scUID}. - A vault:
GET GetContractDetails/{scUID},GET GetVaultBitcoin/{scUID},GET GetMPCDepositAddress/{scUID}. - A withdrawal:
GET GetWithdrawalPin/{scUID}/{withdrawalRequestHash}and, for every share of a request,GET GetWithdrawalRequestShares/{withdrawalRequestHash}. - The network:
GET GetBitcoinAttestationStatus.
The answers are described on the vBTC page.
POST PrepareCompleteWithdrawalRaw also tells a raw client where a withdrawal stands. On a watched vault it prepares nothing and returns the completion answer (Attested: true, Status, Paid, NothingToDo, NotMinedYet, PinnedBTCTransactionHash, BTCTransactionHash and the Withdrawal object); on a legacy vault it prepares a FROST signing session (see Legacy vaults). Read Attested to tell the two apart.
{ "OwnerAddress": "<requester>", "SmartContractUID": "<contract UID>", "WithdrawalRequestHash": "<request hash>" }Create a vault
Section titled “Create a vault”A vault is created by its owner in two parts: a key ceremony among the validators, authorised by the owner’s signatures, and the creation transaction (type 25).
-
Prepare the ceremony.
POST PrepareMPCCeremonyRawwith{"OwnerAddress": "<owner>"}. The node checks that enough validators are reachable and returns the messages to sign:{"Success": true,"CeremonyId": "<32 hex>:<unix time>","SessionId": "<guid>","OwnerAddress": "<owner>","ValidatorCount": 13,"Threshold": 51,"StartMessage": "<sessionId>.<owner>.<timestamp>","StartTimestamp": 1792760000,"ShareDistributionMessage": "<sessionId>.<owner>.<timestamp + 1>","ShareDistributionTimestamp": 1792760001,"RetrySessions": [ { "SessionId": "…", "StartMessage": "…", "StartTimestamp": 0, "ShareDistributionMessage": "…", "ShareDistributionTimestamp": 0 } ]} -
Sign
StartMessageandShareDistributionMessagewith the owner’s key (the same signature format). Check that each message names your owner address. Sign theRetrySessionsmessages too: they let the ceremony run again without a validator that fails it. Without them it runs once. -
Run it.
POST ExecuteMPCCeremonyRaw:{"OwnerAddress": "<owner>","CeremonyId": "<ceremony id>","SessionId": "<session id>","StartTimestamp": 1792760000,"StartSignature": "<signature>","ShareDistributionTimestamp": 1792760001,"ShareDistributionSignature": "<signature>","RetrySessions": [ { "SessionId": "…", "StartTimestamp": 0, "StartSignature": "…", "ShareDistributionTimestamp": 0, "ShareDistributionSignature": "…" } ]}Errors:
Invalid start signature,Invalid share distribution signature,Invalid retry session signature (…),Active ceremony already in progress. -
Wait. Poll
GET GetCeremonyStatus/{ceremonyId}untilStatusisCompleted(orFailed, withErrorMessage). UseContractUIDfrom that answer. -
Build the creation.
POST GetRawCreateContractTxDataon the same node (the ceremony’s result is held in that node’s memory):{ "OwnerAddress": "<owner>", "CeremonyId": "<ceremony id>", "Name": "Treasury vault", "Description": "", "Ticker": "vBTC" }The answer has
Hash,HashPreimage,SmartContractUID,DepositAddress,DepositAddressConfirmed: falseand the transaction’s fields. Check:TransactionTypeisVBTC_V2_CONTRACT_CREATE,FromAddressandToAddressare the owner,Amountis 0, andDatais[{"Function":"Mint()","ContractUID":"<the ceremony's contract UID>",…}]. -
Sign and submit with
POST SendRawCreateContractTx:vBTC contract creation transaction broadcast successfully. -
Get the deposit address with
GET GetMPCDepositAddress/{scUID}once the creation is in a block. Do not deposit before this route returns the address.
CreateVBTCContractRaw is not an external-signer route: it verifies a signed request but then signs the creation with the owner’s account on the node.
Transfer vBTC
Section titled “Transfer vBTC”From one vault
Section titled “From one vault”-
POST GetRawTransferVBTCData:{ "SmartContractUID": "<contract UID>", "FromAddress": "<sender>", "ToAddress": "<recipient>", "Amount": 0.00003 } -
Check:
TransactionTypeisVBTC_V2_TRANSFER,Amount(the VFX amount) is 0, andDatais{"Function":"TransferVBTCV2()","ContractUID":…,"FromAddress":<sender>,"ToAddress":<recipient>,"Amount":<vBTC amount>}. For a reserve (xRBX) sender,IsReserveSendistrueandUnlockTimeis part of the hash: the transfer is held for 24 hours and can be called back until then. -
Sign and submit with
POST SendRawTransferVBTCTx.
Refusals at build: Insufficient balance. Available: …, Requested: …, Reserve accounts cannot send vBTC to another Reserve Account.
From several vaults (bulk transfer)
Section titled “From several vaults (bulk transfer)”-
POST GetRawTransferVBTCMultiData:{"FromAddress": "<sender>","ToAddress": "<recipient>","TotalAmount": 0.00012,"Inputs": [ { "SCUID": "<vault A UID>", "Amount": 0.00008 }, { "SCUID": "<vault B UID>", "Amount": 0.00004 } ],"Nonce": null}Pass
Inputsyourself where you can: the node checks them against chain state. WithoutInputsthe node plans the split from the vaults it knows.Nonceis optional; without it the node uses the next one. -
Check:
HashPreimagehashes toHash;FunctionisTransferVBTCMultiV2()(orTransferVBTCV2()when one vault covers the total,IsMultiContract: false);Allocationssum toTotalAmount;Datanames your recipient. -
Sign and submit with
POST SendRawTransferVBTCTx.
At most 25 vaults per transfer, amounts with at most 8 decimals, no reserve senders and no shielded recipient.
Transfer ownership
Section titled “Transfer ownership”Ownership moves with a smart-contract transfer (type 18, TKNZ_TX) built through the general raw transaction routes.
GET /vbtcapi/VBTC/GetVBTCOwnershipTransferData/{scUID}/{toAddress}/NAreturns the transaction’sData:[{"Function":"Transfer()","ContractUID":…,"ToAddress":<new owner>,"Data":…,"Locators":"NA","MD5List":"NA"}]. While the network uses the default vBTC image no beacon upload is needed and the locator isNA.- Build a transaction with
TransactionType18,FromAddressthe current owner,ToAddressthe new owner,Amount0 and thatData, then usetxapi/TXV1/GetRawTxFee,GetTxHash, sign,VerifyRawTransactionandSendRawTransactionas in Transaction Creation.
The new owner takes over the vault’s vBTC that no holder holds.
Withdraw to Bitcoin
Section titled “Withdraw to Bitcoin”Request
Section titled “Request”-
POST GetRawRequestWithdrawalTxData:{"SmartContractUID": "<contract UID>","RequestorAddress": "<holder>","BTCAddress": "bc1q…","Amount": 0.00002,"FeeRate": 3,"UniqueId": null} -
Check before signing:
TransactionTypeisVBTC_V2_WITHDRAWAL_REQUEST;FromAddressis the holder;Datais{"Function":"VBTCWithdrawalRequest()","ContractUID":<vault>,"RequestorAddress":<holder>,"BTCAddress":<your destination, exactly>,"Amount":<amount>,"FeeRate":<rate>,"UniqueId":…}. Validate the Bitcoin address yourself for the right network. Work out what the destination will receive:AmountlessFeeRate × 154sats (more if the vault needs several coins), which must leave at least 546 sats. -
Sign and submit with
POST SendRawRequestWithdrawalTx. The transaction’s hash is the withdrawal’sRequestHashfrom now on.
Refusals at build: Fee rate must be greater than zero, the fee-floor message (… cannot pay a Bitcoin withdrawal at … sat/vB. …), Insufficient balance. Available: …, Requested: …, and at submit the consensus refusals listed on the vBTC page (address not on this network, fee rate above 5,000, less than 546 sats left, a frozen vault).
RequestWithdrawalRaw is not part of this flow: it only records a request on that node and puts nothing on chain.
Follow it
Section titled “Follow it”On a watched vault nothing more needs to be sent. In the block that mines the request the chain pins the Bitcoin transaction that pays it; the vault’s signers sign it and broadcast it; it completes when Bitcoin confirms it. Follow it with GET GetWithdrawalPin/{scUID}/{requestHash} (or PrepareCompleteWithdrawalRaw): State goes from Waiting or Pinned to Settled, or to Cancelled, Expired, Void or Released with the vBTC refunded. Until the request is mined the answer is Success: false, Attested: true, Withdrawal: null; ask again after the next block.
The node reports a withdrawal’s pin in the same block as the request. The explorer API shows it only after a later Bitcoin report, so follow withdrawals through the node.
Cancel while waiting
Section titled “Cancel while waiting”While State is Waiting (CanCancel: true) the requester can cancel at once, with no vote:
POST GetRawCancelWithdrawalTxDatawith{"SmartContractUID": "<vault>", "RequestorAddress": "<requester>", "WithdrawalRequestHash": "<request hash>"}.- Check:
TransactionTypeisVBTC_V2_WITHDRAWAL_CANCEL,Datais{"Function":"VBTCWithdrawalCancel()","ContractUID":<vault>,"WithdrawalRequestHash":<request hash>}, andAttested: true(the message says no vote is held). - Sign and submit with
POST SendRawCancelWithdrawalTx. When it is mined the withdrawal closes asCancelledand the vBTC is back.
The builder refuses before you sign when the cancel can’t be accepted: Only the original requestor can cancel., Cannot cancel an already completed withdrawal., and once the withdrawal is pinned This withdrawal's Bitcoin transaction is fixed and may already be signed, so it cannot be cancelled. It completes when Bitcoin confirms it. (with the Withdrawal object). CancelWithdrawalRaw does not cancel anything: it answers with the message to use these routes.
Raise the fee
Section titled “Raise the fee”When a pinned withdrawal is not confirming (CanRaiseFee: true):
-
POST GetRawBumpWithdrawalFeeTxData:{ "SmartContractUID": "<vault>", "RequestorAddress": "<requester>", "WithdrawalRequestHash": "<request hash>", "FeeRate": 8 } -
Check:
TransactionTypeisVBTC_V2_COIN_PIN(TransactionTypeId44),FromAddressandToAddressare the requester,Amountis 0, andDatais{"Function":"VBTCWithdrawalFeeBump()","ContractUID":<vault>,"WithdrawalRequestHash":<request hash>,"FeeRate":<new rate>}. The new rate must be above theFeeRatethe withdrawal pays now. The higher fee comes out of the withdrawal: the destination receives less. -
Sign and submit with
POST SendRawBumpWithdrawalFeeTx. When it is mined, the chain pins a replacement that spends the same coins; follow it withGetWithdrawalPin.
Release
Section titled “Release”When a pinned withdrawal is not confirming and the requester would rather have the vBTC back (CanRelease: true):
POST GetRawReleaseWithdrawalTxDatawith the same body as a fee raise.- Check: as for a fee raise, with
FunctionVBTCWithdrawalRelease(). The release’s Bitcoin fee comes out of the refund. - Sign and submit with
POST SendRawReleaseWithdrawalTx. When it is mined, the chain pins a Bitcoin transaction returning the coins to the vault; when Bitcoin shows it, the withdrawal closes asReleasedand the vBTC is refunded less that fee. If one of the withdrawal’s payments confirms first, it is paid instead.
Both builders check the request against the rule block validation applies and refuse with the reason and the Withdrawal object, for example The new fee rate must be above the 3 sat/vB the withdrawal's newest Bitcoin transaction pays. or This withdrawal has no Bitcoin transaction yet. Cancel it and request again with the fee rate you want. A fee raise sent to the release route, or the reverse, is refused: This transaction is a fee bump; submit it with SendRawBumpWithdrawalFeeTx.
Multi-vault withdrawal
Section titled “Multi-vault withdrawal”This is how an exchange or any holder with vBTC on several vaults withdraws a total amount to one Bitcoin address. One VFX transaction (type 27, VBTCWithdrawalRequestMultiV2()) opens the withdrawal; each vault in it is a share, paid by its own Bitcoin transaction from that vault. The requester pays one miner fee per share, out of that share, and shares settle independently, so a withdrawal can be partly paid. A total that one vault covers is built as an ordinary single withdrawal. At most 10 vaults per request.
POST GetRawRequestWithdrawalMultiTxData:
{ "RequestorAddress": "<holder>", "BTCAddress": "bc1q…", "TotalAmount": 0.00006, "FeeRate": 2, "Inputs": [ { "SCUID": "<vault A UID>", "Amount": 0.00003 }, { "SCUID": "<vault B UID>", "Amount": 0.00003 } ], "UniqueId": null}Pass Inputs (each vault and the amount to draw from it) whenever you can: the node checks them against chain state, so any node can build for any holder. Without Inputs the node allocates the total itself, largest available balance first, from the vaults it can see.
{ "Success": true, "Hash": "39ff6b9b…a22b", "Timestamp": 1792774390, "FromAddress": "<holder>", "Fee": 0.00001375, "Nonce": 2, "TransactionType": "VBTC_V2_WITHDRAWAL_REQUEST", "Data": "{\"Function\":\"VBTCWithdrawalRequestMultiV2()\",\"RequestorAddress\":\"<holder>\",\"BTCAddress\":\"bc1q…\",\"TotalAmount\":0.00006,\"FeeRate\":2,\"Inputs\":[{\"SCUID\":\"<vault A UID>\",\"Amount\":0.00003},{\"SCUID\":\"<vault B UID>\",\"Amount\":0.00003}],\"UniqueId\":null}", "HashPreimage": "…", "Function": "VBTCWithdrawalRequestMultiV2()", "IsMultiContract": true, "Allocations": [ { "SCUID": "<vault A UID>", "Amount": 0.00003 }, { "SCUID": "<vault B UID>", "Amount": 0.00003 } ], "BitcoinTransactions": 2, "TotalAmount": 0.00006, "Destination": "bc1q…", "Message": "Sign the Hash field and submit via SendRawRequestWithdrawalTx. …"}On a watched vault, ignore the part of Message that asks you to complete each contract: the vault’s signers pay every share.
Check before signing
Section titled “Check before signing”- Hash. SHA-256 of the lowercase hex of SHA-256(
HashPreimage) equalsHash. The preimage starts withTimestampand the holder’s address (asFromAddressandToAddress) and ends withDataexactly as returned. - Allocation.
Data.Inputs(andAllocations) name the vaults and amounts you intended, each vault once, and add up toTotalAmount. - Destination.
Data.BTCAddressis your destination exactly, valid for the network. Check it yourself before you build. - Fees.
Data.FeeRateis the rate you meant. Each share paysFeeRate × 154sats or more out of its own amount, so the destination receivesTotalAmountlessBitcoinTransactionsfees, and every share must leave at least 546 sats after its fee.Feeis the VFX fee of the request. - Signer.
FromAddressis the holder whose key you are using.
Sign and submit
Section titled “Sign and submit”Sign Hash with the holder’s key and submit with POST SendRawRequestWithdrawalTx, as for a single withdrawal. On testnet a two-share request at 2 sat/vB was mined in the next block, each share pinned in that block to its own Bitcoin transaction (2,692 sats to the destination, 308 sats fee each), and both closed paid when Bitcoin confirmed them.
Follow each share
Section titled “Follow each share”GET GetWithdrawalRequestShares/{requestHash}lists the shares once the request is mined: each vault, amount,Status,IsCompleted,Paid,RefundedandBTCTxHash, and the totalsPaidCount,RefundedCount,AllPaid.GET GetWithdrawalPin/{shareVaultUID}/{requestHash}gives one share’s state, its pinned Bitcoin transaction and what can be done with it. A share goes through the same states as a single withdrawal.
To raise the fee of, release or cancel one share, use the routes above with that share’s vault UID as SmartContractUID and the request hash as WithdrawalRequestHash. Each is one transaction for one share; the other shares are not touched.
You don’t need CompleteWithdrawalMulti or PrepareCompleteWithdrawalRaw/ExecuteCompleteWithdrawalRaw for shares on watched vaults: they send nothing for them.
Refusals at build
Section titled “Refusals at build”| Situation | Message |
|---|---|
Inputs don’t add up |
TotalAmount must equal the sum of input amounts for multi-contract vBTC withdrawal. |
| A vault listed twice | Multi-contract vBTC withdrawal inputs must reference distinct contracts. |
| An input above the holder’s balance on that vault | Insufficient vBTC balance for withdrawal input …. Available: …, Requested: … |
No Inputs and not enough across the vaults |
Insufficient combined vBTC balance. Available: …, Requested: … |
| The holder has nothing | No spendable vBTC balance found for this address. |
| More than 8 decimals | TotalAmount cannot have more than 8 decimal places |
| More than 10 vaults | Multi-contract vBTC withdrawal exceeds the maximum of 10 inputs. |
| A share too small for its fee | Multi-contract vBTC withdrawal input …: … BTC cannot pay a Bitcoin withdrawal at … sat/vB. … |
| Fee rate 0 | FeeRate must be greater than zero |
| Not a Bitcoin address on this network | The withdrawal's Bitcoin address is not an address on this network: … |
| A reserve holder | Reserve accounts cannot request BTC withdrawals. Move the vBTC to a normal VFX address first. |
Legacy vaults
Section titled “Legacy vaults”On a vault created before the attestation recording height, until the enforcement height, a withdrawal is completed by its requester after the request is mined:
POST PrepareCompleteWithdrawalRaw(OwnerAddress= the requester,SmartContractUID,WithdrawalRequestHash) returnsSessionId,StartMessage,ShareDistributionMessage, their timestamps,InputCountandStartMessages(one per Bitcoin input), plus the withdrawal’sAmount,BTCDestinationandFeeRateto confirm.Unpayable: truemeans the request can never be paid at its fee rate: cancel it.- Sign every message in
StartMessagesandShareDistributionMessage, andPOST ExecuteCompleteWithdrawalRawwithStartSignature,ShareDistributionSignature,StartSignaturesand the confirmed fields. The validators sign the Bitcoin transaction and the node returnsSignedBTCTxHex. - Broadcast
SignedBTCTxHexto Bitcoin. IfCompleteAfterConfirmationistrue, wait for its first confirmation. - Record the completion:
POST GetRawCompleteWithdrawalTxData(SmartContractUID,FromAddress,WithdrawalRequestHash,BTCTransactionHash,Amount,BTCDestination), sign, andPOST SendRawCompleteWithdrawalTx(type 28, no fee).
A cancel on a legacy vault is the same type 29 transaction, after which the vault’s validators vote (type 30). From the enforcement height a legacy vault is frozen: transfers, withdrawals and cancels on it are refused, and types 28 and 30 are refused for every vault.
V1 vBTC is retired
Section titled “V1 vBTC is retired”The raw V1 flow (btcapi/BTCV2/GetTokenizationDetails, TokenizeBitcoin, WithdrawalCoinRawTX, transaction types 17 to 21) was retired at block 7,400,500 on mainnet. The network no longer accepts V1 tokens, transfers or withdrawals, and V1 balances are frozen as they stand.