Client
createXebraClient, its options and methods, errors, and waiting for delivery.
The client is a typed wrapper over the HTTP API. Each method is one request, except
waitForDelivery, which polls. Every response is checked against the SDK's schema before you get it.
import { } from "@xebra/sdk";
const = ();Options
const = ({
: "https://api.xebra.exchange",
: 15_000,
});Prop
Type
Methods
| Method | Request | Returns |
|---|---|---|
routes() | GET /v1/routes | Routes |
quote(q) | GET /v1/quote | Quote |
createTransfer(r) | POST /v1/transfers | TransferBuild |
getTransfer(chain, tx, signal?) | GET /v1/transfers/{chain}/{tx} | TransferStatus |
waitForDelivery(chain, tx, opts?) | polls getTransfer | TransferStatus |
createClaim(r) | POST /v1/claims | ClaimBuild |
health() | GET /v1/health | Health |
routes()
Every route Xebra sends on right now, read from the contracts and the live configuration. If part
of the list could not be read, complete is false and the list may be short; ask again shortly.
const { , } = await .();
const = .(() => . === "stellar").(() => .);quote(q)
What a transfer costs and what the recipient gets. Read-only.
const = await .({
: "arc",
: "solana",
: "100",
: "fast", // optional; "standard" when left out
: "7xKX...", // pass it for Solana: opening a USDC account costs more
});For Solana, pass recipient. A wallet with no USDC account yet costs more to deliver to, and the
quote includes that only if it knows the recipient.
Prop
Type
createTransfer(r)
The unsigned transactions for a transfer, in signing order, and the quote they were built at.
const { , } = await .({
: "base",
: "arc",
: "50",
: "0xSender...",
: "0xRecipient...",
: "0.10",
});sender is the address that signs and pays. recipient is who receives on the destination. Set
maxTotalFee to the total you showed the user: if fees have risen above it, the request fails with
fee_above_limit instead of building a more expensive transfer.
The API checks what it can before it builds: the recipient's format, the sender's USDC balance, and on Stellar a simulation of the contract call. A transfer that would fail is refused with a code, before anyone is asked to sign.
Each step:
Prop
Type
Ask the wallet for each step in order, and wait for each to confirm before the next. An approve
must be included on chain before the burn that spends it.
let : string | undefined;
for (const of ) {
const = await (); // your wallet code; wait for confirmation
if (. === "burn") = ;
}getTransfer(chain, tx)
Where a transfer is, by the chain it started on and the burn's transaction hash (or Sui digest).
For Stellar the hash is 64 hex characters without 0x; for an EVM chain it is 0x and 64 hex
characters.
const = await .("base", );Prop
Type
waitForDelivery(chain, tx, opts?)
Polls getTransfer until the state is delivered, needs_claim or failed, and returns that
status. A moment where Circle or the API cannot be reached (upstream_unavailable, rate_limited,
network_error, timeout) is waited out, not thrown. Any other error is thrown at once.
const = new ();
const = await .("stellar", , {
: 5_000, // default 5 s
: 30 * 60_000, // default 30 min
: ., // stop early, for example when the user leaves the page
});If the deadline passes first, it throws XebraApiError with code timeout. That does not mean the
transfer failed: it may still settle. Ask again later.
Aborting the signal rejects with the signal's reason, not with an XebraApiError.
createClaim(r)
The unsigned mint for a transfer whose delivery did not complete on its own (state needs_claim
or failed). payer signs and pays the destination's network fee. See
Claim a stuck transfer.
const { } = await .({
: "stellar", // the chain the burn was made on
: ,
: "0xPayer...", // on the destination chain
});For a claim on Solana where the recipient's USDC account must be opened, also pass
recipientOwner, the wallet that owns it. The answer then may start with an open_account step.
health()
{ status: "ok", version } when the API is up. version is the API's SDK version.
Errors
Every failure is thrown as XebraApiError. Its code is stable and safe to branch on; its
detail is a sentence written for a person.
try {
await .({ : "stellar", : "base", : "500" });
} catch () {
if ( instanceof && . === "amount_above_maximum") {
.(`At most ${..maximum} USDC per transfer.`);
} else if ( instanceof ) {
.(., ., .);
} else {
throw ;
}
}Prop
Type
Three codes come from the client itself, not the API:
network_error: the request did not reach the API.timeout: no answer withintimeoutMs, orwaitForDeliveryran out of time.invalid_response: the API answered with something that is not JSON or does not match the SDK's schema.
The list of every code, with its HTTP status, is exported as ERROR_STATUS and ERROR_CODES.
Cancelling
getTransfer and waitForDelivery take an AbortSignal. The client's own timeout still applies to
each request.