If you’re a remote blockchain partner, you can submit to withdraw from xReserve
after a user burns tokens on your chain. This tutorial walks through the process
using Canton TestNet as the example remote blockchain and Ethereum Sepolia as
both the example source and destination blockchain.
Note: This tutorial is intended for remote blockchain partners. If you’re
a developer or user, visit the remote blockchain website or contact them for
instructions on how to withdraw funds from xReserve.
Step 1. Monitor burn events
Listen for burn events on your blockchain. Each event should include at least:
- The recipient wallet address on the destination chain.
- The
remote domain
ID.
Step 2. Prepare the withdrawal
Send the burn intent data to the
/prepare-withdrawal
endpoint. The following example request contains data to withdraw 2500 USDC on
Ethereum:
{
"batches": [
{
"token": "USDC",
"valueExcludingFees": "2500.00",
"remoteDomain": 10001,
"remoteDepositor": "0x000000000000000000000000c0ffee254729296a45a3885639ac7e10f9d54979",
"finalDestinationDomain": 0,
"finalDestinationRecipient": "0x000000000000000000000000aabbee11223344556677889900aabbccddeeff00",
"finalDestinationCaller": "0x00000000000000000000000000112233445566778899aabbccddeeff00112233",
"useCircleForwarding": true,
"salt": "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef"
}
]
}
Response
The API returns a fully encoded
burn intent and the canonical
message hash
for you to sign, for example:
{
"batches": [
{
"burnIntents": [
{
"maxBlockHeight": "18976543",
"maxFee": "750000",
"spec": {
"version": 1,
"sourceDomain": 0,
"destinationDomain": 0,
"sourceContract": "0x0000000000000000000000000077777d7eba4688bdef3e311b846f25870a19b9",
"destinationContract": "0x0000000000000000000000000022222abe238cc2c7bb1f21003f0a260052475b",
"sourceToken": "0x74ed63088c070c8fd5d8ad71f2a1cef868c63d00e0ac6dc2a6722d171691a422",
"destinationToken": "0x0000000000000000000000001c7d4b196cb0c7b01d743fbc6116a902379c7238",
"sourceDepositor": "0x000000000000000000000000c0ffee254729296a45a3885639ac7e10f9d54979",
"destinationRecipient": "0x000000000000000000000000aabbee11223344556677889900aabbccddeeff00",
"sourceSigner": "0x0000000000000000000000005555555555555555555555555555555555555555",
"destinationCaller": "0x00000000000000000000000000112233445566778899aabbccddeeff00112233",
"value": "2500000000",
"salt": "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef",
"hookData": {
"remoteDomain": 10001,
"remoteDepositor": "0xc0ffee254729296a45a3885639ac7e10f9d54979",
"remoteToken": "0x74ed63088c070c8fd5d8ad71f2a1cef868c63d00e0ac6dc2a6722d171691a422",
"forwardingContractAddress": "0x0000000000000000000000000000000000000000",
"forwardingCalldata": "0x"
}
}
}
],
"encoded": "0x8ddf3a...f9a2",
"messageHashToSign": "0x4f5c6d7e8f90123456789abcdeffedcba9876543210fedcba9876543210abcd"
}
]
}
Step 3. Sign the message hash
Use your attester keys to sign
the messageHashToSign returned in the previous step. This produces the burn
intent signatures.
Step 4. Submit the withdrawal
Send the encoded burn intent payload, the array of burn signatures, and the burn
transaction hash to the
/withdraw endpoint. Set
useCircleForwarding to true if you want Circle to submit the withdrawal on
behalf of your user.
Note: If you don’t use Circle
forwarding, you’ll
need to perform an additional crosschain transfer to withdraw funds from
xReserve.
The following is an example request:
{
"batches": [
{
"burnIntents": {
"maxBlockHeight": "18976543",
"maxFee": "750000",
"spec": {
"version": 1,
"sourceDomain": 0,
"destinationDomain": 0,
"sourceContract": "0x0000000000000000000000000077777d7eba4688bdef3e311b846f25870a19b9",
"destinationContract": "0x0000000000000000000000000022222abe238cc2c7bb1f21003f0a260052475b",
"sourceToken": "0x74ed63088c070c8fd5d8ad71f2a1cef868c63d00e0ac6dc2a6722d171691a422",
"destinationToken": "0x0000000000000000000000001c7d4b196cb0c7b01d743fbc6116a902379c7238",
"sourceDepositor": "0x000000000000000000000000c0ffee254729296a45a3885639ac7e10f9d54979",
"destinationRecipient": "0x000000000000000000000000aabbee11223344556677889900aabbccddeeff00",
"sourceSigner": "0x0000000000000000000000005555555555555555555555555555555555555555",
"destinationCaller": "0x00000000000000000000000000112233445566778899aabbccddeeff00112233",
"value": "2500000000",
"salt": "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef",
"hookData": {
"remoteDomain": 10001,
"remoteDepositor": "0xc0ffee254729296a45a3885639ac7e10f9d54979",
"remoteToken": "0x74ed63088c070c8fd5d8ad71f2a1cef868c63d00e0ac6dc2a6722d171691a422",
"forwardingContractAddress": "0x0000000000000000000000000000000000000000",
"forwardingCalldata": "0x"
}
}
},
"burnSignatures": [
"0x9c1d2e3f4a5b6c7d8e9f00112233445566778899aabbccddeeff0011223344556677",
"0x7f6e5d4c3b2a19080706050403020100ffeeddccbbaa99887766554433221100"
],
"burnTxId": "0x5a7b3c1d4e6f8091a2b3c4d5e6f708192a3b4c5d6e7f8091a2b3c4d5e6f70819",
"useCircleForwarding": true
}
]
}
Response
The API returns withdrawalId, a unique identifier for each withdrawal batch.
For example:
[
{
"withdrawalId": "6149dc3d-71bf-4d57-8cc1-5e2d4c0a8e70",
"transferSpecHashes": [
"0x23f8c66d5a8bb5b712f2f4bc1c0a5a51d7c2b4f93d587f1449bcf3da6d8e1f20"
],
"verificationStatus": "unverified",
"attestation": null,
"requireCircleRelay": true
}
]
Step 5. Monitor withdrawal status
Check the status of your withdrawal by calling
GET /withdrawals/{withdrawalId}.
xReserve releases funds once the status is finalized. If the status remains
created, verified, or unverified, keep polling. If the status changes to
failed, resolve the issue and resubmit the withdrawal.