Skip to Content
XebraDocs
Guides

Track a transfer

Follow a burn to delivery with getTransfer, or wait for it to settle with waitForDelivery.

A transfer is identified by the chain it started on and its burn: the transaction that the user signed on the source chain. Xebra reads its progress from Circle.

Sourcechaintx
StellarstellarThe transaction hash: 64 hex characters, without 0x
An EVM chainIts id, such as arcThe transaction hash: 0x and 64 hex characters
SuisuiThe transaction digest (base58)

Over HTTP this is GET /v1/transfers/{chain}/{tx}.

States

stateMeaningWhat to do
pendingCircle has not attested the burn yet, or has not seen it yetWait
deliveringAttested; Circle's Forwarding Service is minting on the destinationWait
deliveredThe USDC is on the destination. deliveryTx is the mint, when Circle reports itDone
needs_claimAttested, but the delivery did not complete on its ownClaim it
failedCircle reports the delivery failed. reason has Circle's wordsClaim it

pending and delivering change on their own. delivered, needs_claim and failed do not change until someone acts.

When a transfer from Stellar or an EVM chain needs a claim, claimUrl links to the claim page on xebra.exchange, where the recipient (or anyone) can finish it with their own wallet. For Sui transfers claimUrl is null; finish them through the API.

Ask once

import { ,  } from "@xebra/sdk";

const  = ();
declare const : string;

try {
  const  = await .("stellar", );
  switch (.) {
    case "pending":
    case "delivering":
      break; // ask again later
    case "delivered":
      .("Minted in", .);
      break;
    case "needs_claim":
    case "failed":
      .(., .);
      break;
  }
} catch () {
  if ( instanceof  && . === "upstream_unavailable") {
    // Circle could not be read just now. This says nothing about the transfer; ask again.
  } else {
    throw ;
  }
}

Wait until it settles

waitForDelivery polls until the state is delivered, needs_claim or failed, and returns that status. By default it asks every 5 seconds for up to 30 minutes. Moments where Circle or the API cannot be reached (upstream_unavailable, rate_limited, a network error or a timeout) are waited out, not thrown. If the time runs out it throws an XebraApiError with code: "timeout"; the transfer may still settle, so ask again later.

import {  } from "@xebra/sdk";

const  = ();
declare const : `0x${string}`;

const  = new (); // call controller.abort() to stop waiting
const  = await .("arc", , {
  : 10_000,
  : 60 * 60_000,
  : .,
});
.(.);

Unavailable is not failed

When Circle cannot be read, the API answers upstream_unavailable (HTTP 503, with Retry-After). That is never a verdict on the transfer: a burn that landed will be attested. Show "checking" and ask again. Report a transfer as failed only when the state is failed.

Other answers:

  • invalid_request: tx is not the right shape for that chain, or the chain cannot start a transfer.
  • route_unavailable: you asked about a Sui transfer while Sui to Stellar is switched off.

Webhooks are coming

Until webhooks ship, polling is the way to follow a transfer. Poll no faster than every few seconds.

On this page