Skip to Content
XebraDocs
SDK

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

MethodRequestReturns
routes()GET /v1/routesRoutes
quote(q)GET /v1/quoteQuote
createTransfer(r)POST /v1/transfersTransferBuild
getTransfer(chain, tx, signal?)GET /v1/transfers/{chain}/{tx}TransferStatus
waitForDelivery(chain, tx, opts?)polls getTransferTransferStatus
createClaim(r)POST /v1/claimsClaimBuild
health()GET /v1/healthHealth

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 within timeoutMs, or waitForDelivery ran 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.

On this page