/

Product

How to Integrate Crypto Payments in One API Call

How to Integrate Crypto Payments in One API Call

orda's team

Apr 17, 2026

You're building an app and you want your users to be able to move money. Maybe it's a consumer wallet that needs to let people swap tokens, maybe it's a fintech app that needs to get users on-chain, maybe it's a payroll platform that needs to pay contractors in stablecoins. The specifics change, but the underlying requirement is usually the same: your users have money in one form, and they want it in another.

You start mapping out what that actually requires. You need a way to swap tokens on the same chain. You need a way to bridge tokens between chains. You need a way to handle cross-chain swaps, which are different from both of those. You need an on-ramp so users can get crypto using their bank account. You need an off-ramp so they can turn crypto back into cash. That's five different flows, and in the traditional way of building this, each one is a separate integration with a separate vendor on a separate timeline.

You start looking at providers. Swap aggregators for the DEX side. Bridge protocols for cross-chain. An on-ramp provider with KYC and bank rails. An off-ramp provider, usually a different company, with its own KYC and its own payout flows. Each has its own API, its own authentication, its own webhook format, its own way of representing a quote, and its own rules for what you can and can't do with it. You start to realize that what you thought was one feature is actually a small project, and most of the work isn't building anything new - it's translating between five APIs that all speak different dialects of the same language.

This is the problem orda exists to solve. Instead of five integrations, one. Instead of five APIs, one endpoint that handles everything. The goal of this post is to walk through what that actually looks like from a developer's perspective, so you can judge for yourself whether it saves as much time as we think it does.

The Basic Idea

orda is a single API that handles every direction of crypto payments. Same-chain swaps, cross-chain transfers, cross-chain swaps, fiat-to-crypto on-ramps, and crypto-to-fiat off-ramps - all of them go through the same core pattern, and most of them go through literally the same endpoint.

The core endpoint is POST /v1/quote, and it's used for every crypto-origin flow regardless of what you're trying to do. Swapping USDC for ETH on Base uses this endpoint. Bridging USDC from Ethereum to Arbitrum uses this endpoint. A cross-chain swap from ETH on mainnet to USDC on Polygon uses this endpoint. You don't pick a different method for each flow - you describe what you want, and orda's solver network figures out the best route to get there.

The fiat flows have their own endpoints (/v1/onramp/quote and /v1/offramp/quote) because they involve bank rails instead of blockchain transactions, but the request structure mirrors the crypto endpoint closely enough that learning one means you've already learned most of the others. By the time you've integrated one flow, the rest are mostly the same code with a different URL.

This unified structure is the whole design philosophy. Most crypto infrastructure is built around the specific thing it does - a DEX aggregator has a swap API, a bridge has a bridge API, an on-ramp has an on-ramp API. orda is built around what developers actually want to do, which is move value from one place to another without caring about the underlying mechanism. Swap, bridge, on-ramp, off-ramp - these are implementation details to the solver, not separate products to the integrator.

How It Actually Works

Every request follows the same shape. You specify where the money is coming from, where it's going, how much, and who's sending it. orda handles the rest.

Here's what a crypto-to-crypto request looks like:

const quote = await orda.quote.request({
  // Source
  fromChain: '8453',           // Base
  fromToken: '0x833589fcd6edb6e08f4c7c32d4f71b54bda02913',  // USDC
  fromAddress: '0xUserWallet',

  // Amount
  intent: {
    method: 'usd',
    value: '100'
  },

  // Destination
  settlementDetails: {
    toChain: '42161',          // Arbitrum
    toToken: '0xaf88d065e77c8cC2239327C5EDb3A432268e5831',  // USDC
    toAddress: '0xRecipientWallet'
  }
});
const quote = await orda.quote.request({
  // Source
  fromChain: '8453',           // Base
  fromToken: '0x833589fcd6edb6e08f4c7c32d4f71b54bda02913',  // USDC
  fromAddress: '0xUserWallet',

  // Amount
  intent: {
    method: 'usd',
    value: '100'
  },

  // Destination
  settlementDetails: {
    toChain: '42161',          // Arbitrum
    toToken: '0xaf88d065e77c8cC2239327C5EDb3A432268e5831',  // USDC
    toAddress: '0xRecipientWallet'
  }
});
const quote = await orda.quote.request({
  // Source
  fromChain: '8453',           // Base
  fromToken: '0x833589fcd6edb6e08f4c7c32d4f71b54bda02913',  // USDC
  fromAddress: '0xUserWallet',

  // Amount
  intent: {
    method: 'usd',
    value: '100'
  },

  // Destination
  settlementDetails: {
    toChain: '42161',          // Arbitrum
    toToken: '0xaf88d065e77c8cC2239327C5EDb3A432268e5831',  // USDC
    toAddress: '0xRecipientWallet'
  }
});
const quote = await orda.quote.request({
  // Source
  fromChain: '8453',           // Base
  fromToken: '0x833589fcd6edb6e08f4c7c32d4f71b54bda02913',  // USDC
  fromAddress: '0xUserWallet',

  // Amount
  intent: {
    method: 'usd',
    value: '100'
  },

  // Destination
  settlementDetails: {
    toChain: '42161',          // Arbitrum
    toToken: '0xaf88d065e77c8cC2239327C5EDb3A432268e5831',  // USDC
    toAddress: '0xRecipientWallet'
  }
});

Four things. Source, amount, destination, wallet. The request is the same shape whether you're doing a same-chain swap, a cross-chain bridge, or a cross-chain swap - you just change the settlementDetails to reflect what you want. Same chain and same token means it's a no-op. Same chain and different token is a swap. Different chain and same token is a bridge. Different chain and different token is a cross-chain swap. The solver works out which category the request falls into and routes it accordingly.

The response tells you exactly what will happen before you commit to anything. You get the route the solver chose, the amount the user will receive, the fees involved, the estimated duration, and a pre-built transaction that your wallet integration can sign directly. You don't assemble the transaction yourself, you don't decide which bridge to use, and you don't need to know whether the route runs through Relay or Across or anything else. You pass the transaction request to the user's wallet, they sign it, and the routing happens automatically from there.

Execution is one of the cleanest parts of the API, and it's deliberately designed to feel like any other wallet transaction. Here's what executing a quote looks like:

// Handle approval if the token needs it
if (quote.approvalTxParams) {
  const approval = await signer.sendTransaction(quote.approvalTxParams);
  await approval.wait();
}

// Execute the swap
const tx = await signer.sendTransaction(quote.transactionRequest);
await tx.wait();
// Handle approval if the token needs it
if (quote.approvalTxParams) {
  const approval = await signer.sendTransaction(quote.approvalTxParams);
  await approval.wait();
}

// Execute the swap
const tx = await signer.sendTransaction(quote.transactionRequest);
await tx.wait();
// Handle approval if the token needs it
if (quote.approvalTxParams) {
  const approval = await signer.sendTransaction(quote.approvalTxParams);
  await approval.wait();
}

// Execute the swap
const tx = await signer.sendTransaction(quote.transactionRequest);
await tx.wait();
// Handle approval if the token needs it
if (quote.approvalTxParams) {
  const approval = await signer.sendTransaction(quote.approvalTxParams);
  await approval.wait();
}

// Execute the swap
const tx = await signer.sendTransaction(quote.transactionRequest);
await tx.wait();

That's the entire execution layer. Two transactions at most - one for token approval if the user hasn't approved the contract before, one for the swap itself. The user sees a single transaction in their wallet, signs it, and the funds move. From the integrator's side, it's two function calls.

The Fiat Flows

Off-ramps and on-ramps follow the same philosophy, but they have one twist each that's worth understanding if you're going to build them.

For off-ramps, the surprising thing is how similar the flow is to a crypto-to-crypto swap. The user still has tokens in a wallet, they still sign a blockchain transaction, and that transaction still kicks off the same flow you'd use for any other transfer. The only difference is what happens next. Instead of settling into tokens on another chain, the funds are converted to fiat and sent to the user's bank account. Same signing flow, same wallet integration, same execution pattern the outcome is different, but the code on your side is nearly identical.

The request does need a few extra fields for regulated fiat payouts. orda currently supports Brazil, which means BRL payouts via PIX, which means the recipient needs a PIX key and the sender needs KYC information. Here's what that looks like:

const quote = await orda.offRamp.requestQuote({
  fromChain: '8453',
  fromToken: '0x833589fcd6edb6e08f4c7c32d4f71b54bda02913',
  fromAddress: userWallet,
  intent: { method: 'usd', value: '100' },
  fiatSettlementDetails: {
    toCurrency: 'BRL',
    pixKey: 'user@email.com'
  },
  kycInformation: {
    name: 'User Full Name',
    taxId: '12345678901',
    taxIdCountry: 'BRA',
    email: 'user@email.com'
  }
});
const quote = await orda.offRamp.requestQuote({
  fromChain: '8453',
  fromToken: '0x833589fcd6edb6e08f4c7c32d4f71b54bda02913',
  fromAddress: userWallet,
  intent: { method: 'usd', value: '100' },
  fiatSettlementDetails: {
    toCurrency: 'BRL',
    pixKey: 'user@email.com'
  },
  kycInformation: {
    name: 'User Full Name',
    taxId: '12345678901',
    taxIdCountry: 'BRA',
    email: 'user@email.com'
  }
});
const quote = await orda.offRamp.requestQuote({
  fromChain: '8453',
  fromToken: '0x833589fcd6edb6e08f4c7c32d4f71b54bda02913',
  fromAddress: userWallet,
  intent: { method: 'usd', value: '100' },
  fiatSettlementDetails: {
    toCurrency: 'BRL',
    pixKey: 'user@email.com'
  },
  kycInformation: {
    name: 'User Full Name',
    taxId: '12345678901',
    taxIdCountry: 'BRA',
    email: 'user@email.com'
  }
});
const quote = await orda.offRamp.requestQuote({
  fromChain: '8453',
  fromToken: '0x833589fcd6edb6e08f4c7c32d4f71b54bda02913',
  fromAddress: userWallet,
  intent: { method: 'usd', value: '100' },
  fiatSettlementDetails: {
    toCurrency: 'BRL',
    pixKey: 'user@email.com'
  },
  kycInformation: {
    name: 'User Full Name',
    taxId: '12345678901',
    taxIdCountry: 'BRA',
    email: 'user@email.com'
  }
});

Once you've got the quote, execution is the same two-transaction pattern as any crypto-to-crypto flow. Approval if needed, then the off-ramp transaction itself. The user signs, the transaction is processed on-chain, and the fiat conversion starts on the back end. Most off-ramps to BRL complete in under ten minutes end-to-end, with the blockchain part finishing in seconds and the PIX transfer taking the rest.

On-ramps are the one flow that's structurally different, and the difference is worth understanding because it changes how your UI has to work. When a user is converting crypto to something else, they have tokens in a wallet, so they can sign a transaction. When a user is converting fiat to crypto, they have money in a bank, so there's nothing for them to sign. The authorization happens in their banking app, not your application.

This means on-ramp integration is the odd one out. Instead of building a transaction and passing it to a wallet, you request a quote and get back a set of deposit instructions - a PIX key, an amount, a QR code, and an expiry time. You display those instructions to the user, they pay from their banking app, and your application waits for the settlement to complete. The whole middle of the flow happens outside your app, in the user's banking app, and you have no visibility into it until the settlement is confirmed and the user's crypto is delivered to their wallet.

const quote = await orda.onRamp.requestQuote({
  fromCurrency: 'BRL',
  intent: { method: 'fromAmount', value: '500.00' },
  settlementDetails: {
    toChain: '8453',
    toToken: '0x833589fcd6edb6e08f4c7c32d4f71b54bda02913',
    toAddress: '0xUserWallet'
  }
});
const quote = await orda.onRamp.requestQuote({
  fromCurrency: 'BRL',
  intent: { method: 'fromAmount', value: '500.00' },
  settlementDetails: {
    toChain: '8453',
    toToken: '0x833589fcd6edb6e08f4c7c32d4f71b54bda02913',
    toAddress: '0xUserWallet'
  }
});
const quote = await orda.onRamp.requestQuote({
  fromCurrency: 'BRL',
  intent: { method: 'fromAmount', value: '500.00' },
  settlementDetails: {
    toChain: '8453',
    toToken: '0x833589fcd6edb6e08f4c7c32d4f71b54bda02913',
    toAddress: '0xUserWallet'
  }
});
const quote = await orda.onRamp.requestQuote({
  fromCurrency: 'BRL',
  intent: { method: 'fromAmount', value: '500.00' },
  settlementDetails: {
    toChain: '8453',
    toToken: '0x833589fcd6edb6e08f4c7c32d4f71b54bda02913',
    toAddress: '0xUserWallet'
  }
});

The response includes the QR code as a base64-encoded image you can drop straight into an <img> tag, which is a nice detail - you don't need to render QR codes yourself. From the user's perspective, they see a screen with a QR code, an amount, and a countdown. They scan, they pay, they wait, and crypto arrives in their wallet. From your perspective, you're building a payment instruction screen and a polling loop, not a wallet signing flow.

Monitoring and Status

Every transaction, regardless of flow, returns a transactionId that you can use to check status. The pattern is the same across all three flows, with slightly different endpoints:

const status = await orda.transactions.getStatus({
  transactionId: quote.transactionId
});

console.log(status.transaction.status);
const status = await orda.transactions.getStatus({
  transactionId: quote.transactionId
});

console.log(status.transaction.status);
const status = await orda.transactions.getStatus({
  transactionId: quote.transactionId
});

console.log(status.transaction.status);
const status = await orda.transactions.getStatus({
  transactionId: quote.transactionId
});

console.log(status.transaction.status);

Status values move through a predictable lifecycle. Pending means the transaction has been submitted but not yet confirmed. In progress means it's been confirmed on the source chain and settlement is happening. Completed means the funds have arrived at the destination. Failed and refunded are the two terminal error states, and they tell you whether the transaction broke on the way through or whether orda returned the funds to the sender.

For convenience, the SDK ships with a helper that polls the status endpoint until the transaction reaches a terminal state. You pass it a transaction ID and a callback, and it handles the polling loop, the intervals, and the timeout logic:

await orda.transactions.waitForCompletion(quote.transactionId, {
  intervalMs: 5000,
  timeoutMs: 600000,
  onStatusUpdate: (status) => console.log(status.status)
});
await orda.transactions.waitForCompletion(quote.transactionId, {
  intervalMs: 5000,
  timeoutMs: 600000,
  onStatusUpdate: (status) => console.log(status.status)
});
await orda.transactions.waitForCompletion(quote.transactionId, {
  intervalMs: 5000,
  timeoutMs: 600000,
  onStatusUpdate: (status) => console.log(status.status)
});
await orda.transactions.waitForCompletion(quote.transactionId, {
  intervalMs: 5000,
  timeoutMs: 600000,
  onStatusUpdate: (status) => console.log(status.status)
});

Different flows have different natural polling intervals. Same-chain swaps settle fast and can be polled every few seconds. Cross-chain transfers take longer because the transaction needs to be confirmed on two chains, so a slower interval makes sense. Off-ramps involve bank rails that move in minutes rather than seconds, so there's no point checking every three seconds. On-ramps are the longest of all because you're waiting for a human to go pay from their banking app, and that human might have gotten distracted, or might be dealing with two-factor authentication, or might just be slow. Give the flow room to breathe and poll on intervals that match how fast the underlying system actually moves.

Why Integrators Choose This Pattern

There are three things the unified-endpoint design gives you that stitching together five separate providers doesn't.

The first is integration time. Building one integration with one authentication scheme and one request shape is fundamentally less work than building five. You learn the API once, you handle errors once, you test once. The mental model scales across every flow you add, because every flow is a variation of the same underlying pattern. This is the difference between "I need to add on-ramps next quarter" being a one-week project and a one-month project.

The second is consistency. When you're pulling quotes from five different APIs, you have five different sources of truth about how much a transaction will cost, how long it will take, and what can go wrong. Each one has its own edge cases, its own rate limits, and its own idea of what "completed" means. A unified API means a single, consistent response shape, a single status vocabulary, and a single mental model for every flow your users touch. Bugs get easier to track down, your monitoring gets simpler, and your users see a more predictable experience.

The third is the solver network doing work you'd otherwise be doing yourself. orda doesn't just route to a single bridge or a single DEX - it runs a solver competition behind the scenes to find the best route for every quote. When a user wants to move USDC from Base to Arbitrum, the solver considers multiple paths and picks the one with the best output for that specific request. You'd need to integrate and compare several providers yourself to get the same result, and even then you'd need to maintain those comparisons as providers change. orda's solver network handles that optimization automatically, which means you get best-available routing without doing the work.

The Short Version

Crypto payments infrastructure has historically required stitching together a bunch of separate providers to do what should be one job. Swap aggregators for DEX trading, bridge protocols for cross-chain, separate on-ramp and off-ramp providers for fiat flows, each with their own APIs, their own quirks, and their own maintenance burden.

orda's approach is to collapse all of it into one integration. One API, one authentication layer, one request shape that handles every flow, and a solver network that routes transactions through the best available path without you having to pick providers yourself. The specifics of swap versus bridge versus cross-chain swap versus on-ramp versus off-ramp become implementation details the API handles for you, not separate products you have to integrate and maintain.

If you're building an app that needs to move value across chains, across tokens, or across the line between crypto and fiat, the question isn't whether to use orda it's whether you really want to build and maintain five integrations when one will do the same job. The code samples in this post are from the real API, and you can start building against them today.

Check out more at https://docs.orda.network/

Start building with orda

APIs, SDKs, and ramp infra to route value globally - without rebuilding every corridor from scratch.

Start building with orda

APIs, SDKs, and ramp infra to route value globally - without rebuilding every corridor from scratch.

Start building with orda

APIs, SDKs, and ramp infra to route value globally - without rebuilding every corridor from scratch.

Start building with orda

APIs, SDKs, and ramp infra to route value globally - without rebuilding every corridor from scratch.