SSeev PlusDocs
Stellar Anchor

SEP-38: Quotes

Request exchange rates and lock in firm quotes for GHS/USDC conversions using the SEP-38 Anchor RFQ API.

SEP-38 provides a Request for Quote (RFQ) API that lets you discover available asset pairs, check indicative prices, and lock in firm quotes before initiating a deposit or withdrawal. Using quotes ensures the customer knows exactly how much they'll pay or receive.

Endpoints

MethodPathDescription
GET/sep38/pricesList available asset pairs and indicative rates
GET/sep38/priceGet an indicative price for a specific pair
POST/sep38/quoteCreate a firm quote with a locked rate
GET/sep38/quote/:idRetrieve an existing quote

All endpoints require a valid SEP-10 JWT token.

Asset identifiers

SEP-38 uses standardized asset identifiers:

AssetIdentifier
USDC on Stellarstellar:USDC:GBBD47IF6LWK7P7MDEVSCWR7DPUWV3NY3DTQEVFL4NAT4AQH3ZLLFLA5
Ghana Cedisiso4217:GHS

GET /prices — List available pairs

curl -X GET "https://<your-anchor-domain>/sep38/prices?\
sell_asset=iso4217:GHS&\
sell_amount=100" \
  -H "Authorization: Bearer <sep10_jwt_token>"
import { Wallet, Keypair } from '@stellar/typescript-wallet-sdk';

// Note: The wallet SDK does not have SEP-38 built in.
// Use fetch for quote operations until SDK support is added.
const wallet = Wallet.TestNet();
const anchor = wallet.anchor({ homeDomain: '<your-anchor-domain>' });
const accountKp = Keypair.fromSecret('SCZANGBA5YHTNYVVV3C7CAZMCLXPJLNS2YFGXDNASLFTGBPFMRKCY6ML');

// Authenticate
const sep10 = await anchor.sep10();
const authToken = await sep10.authenticate({ accountKp });

// Fetch prices via SEP-38 (raw fetch — not yet in wallet SDK)
const toml = await anchor.getInfo();
const quoteServer = toml.anchorQuoteServer;

const params = new URLSearchParams({
  sell_asset: 'iso4217:GHS',
  sell_amount: '100',
});

const res = await fetch(`${quoteServer}/sep38/prices?${params}`, {
  headers: { Authorization: `Bearer ${authToken}` },
});
const data = await res.json();
console.log("Available buy assets:", data.buy_assets);

Response:

{
  "price": "15.38",
  "sell_amount": "100.00",
  "buy_amount": "6.50",
  "fee": {
    "total": "0.10",
    "asset": "stellar:USDC:GBBD47IF6LWK7P7MDEVSCWR7DPUWV3NY3DTQEVFL4NAT4AQH3ZLLFLA5"
  }
}

Indicative prices are for display purposes. They are not guaranteed and may change. Use POST /quote to lock in a firm rate.

POST /quote — Create a firm quote

Lock in a rate for a specific conversion. Firm quotes have an expiration time.

Deposit quote (GHS → USDC)

curl -X POST "https://<your-anchor-domain>/sep38/quote" \
  -H "Authorization: Bearer <sep10_jwt_token>" \
  -H "Content-Type: application/json" \
  -d '{
    "sell_asset": "iso4217:GHS",
    "buy_asset": "stellar:USDC:GBBD47IF6LWK7P7MDEVSCWR7DPUWV3NY3DTQEVFL4NAT4AQH3ZLLFLA5",
    "sell_amount": "100",
    "context": "sep6"
  }'
import { Wallet, Keypair } from '@stellar/typescript-wallet-sdk';

// Note: SEP-38 is not yet built into the wallet SDK. Use fetch.
const wallet = Wallet.TestNet();
const anchor = wallet.anchor({ homeDomain: '<your-anchor-domain>' });
const accountKp = Keypair.fromSecret('SCZANGBA5YHTNYVVV3C7CAZMCLXPJLNS2YFGXDNASLFTGBPFMRKCY6ML');

const sep10 = await anchor.sep10();
const authToken = await sep10.authenticate({ accountKp });

const toml = await anchor.getInfo();
const quoteServer = toml.anchorQuoteServer;

const res = await fetch(`${quoteServer}/sep38/quote`, {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${authToken}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    sell_asset: 'iso4217:GHS',
    buy_asset: 'stellar:USDC:GBBD47IF6LWK7P7MDEVSCWR7DPUWV3NY3DTQEVFL4NAT4AQH3ZLLFLA5',
    sell_amount: '100',
    context: 'sep6',
  }),
});

const quote = await res.json();
console.log(`Quote ID: ${quote.id}, expires: ${quote.expires_at}`);

Response:

{
  "id": "quote_a1b2c3d4-e5f6-7a8b-9c0d-1e2f3a4b5c6d",
  "expires_at": "2026-08-05T01:49:15Z",
  "price": "15.38",
  "sell_asset": "iso4217:GHS",
  "sell_amount": "100.00",
  "buy_asset": "stellar:USDC:GBBD47IF6LWK7P7MDEVSCWR7DPUWV3NY3DTQEVFL4NAT4AQH3ZLLFLA5",
  "buy_amount": "6.50",
  "fee": {
    "total": "0.10",
    "asset": "stellar:USDC:GBBD47IF6LWK7P7MDEVSCWR7DPUWV3NY3DTQEVFL4NAT4AQH3ZLLFLA5"
  }
}

Withdrawal quote (USDC → GHS)

curl -X POST "https://<your-anchor-domain>/sep38/quote" \
  -H "Authorization: Bearer <sep10_jwt_token>" \
  -H "Content-Type: application/json" \
  -d '{
    "sell_asset": "stellar:USDC:GBBD47IF6LWK7P7MDEVSCWR7DPUWV3NY3DTQEVFL4NAT4AQH3ZLLFLA5",
    "buy_asset": "iso4217:GHS",
    "sell_amount": "6.5",
    "context": "sep6"
  }'
import { Wallet, Keypair } from '@stellar/typescript-wallet-sdk';

// Note: SEP-38 is not yet built into the wallet SDK. Use fetch.
const wallet = Wallet.TestNet();
const anchor = wallet.anchor({ homeDomain: '<your-anchor-domain>' });
const accountKp = Keypair.fromSecret('SCZANGBA5YHTNYVVV3C7CAZMCLXPJLNS2YFGXDNASLFTGBPFMRKCY6ML');

const sep10 = await anchor.sep10();
const authToken = await sep10.authenticate({ accountKp });

const toml = await anchor.getInfo();
const quoteServer = toml.anchorQuoteServer;

const res = await fetch(`${quoteServer}/sep38/quote`, {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${authToken}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    sell_asset: 'stellar:USDC:GBBD47IF6LWK7P7MDEVSCWR7DPUWV3NY3DTQEVFL4NAT4AQH3ZLLFLA5',
    buy_asset: 'iso4217:GHS',
    sell_amount: '6.5',
    context: 'sep6',
  }),
});

const quote = await res.json();
console.log(`Quote ID: ${quote.id}, you get: ${quote.buy_amount} GHS`);

Response:

{
  "id": "quote_b2c3d4e5-f6a7-8b9c-0d1e-2f3a4b5c6d7e",
  "expires_at": "2026-08-05T01:49:15Z",
  "price": "0.065",
  "sell_asset": "stellar:USDC:GBBD47IF6LWK7P7MDEVSCWR7DPUWV3NY3DTQEVFL4NAT4AQH3ZLLFLA5",
  "sell_amount": "6.50",
  "buy_asset": "iso4217:GHS",
  "buy_amount": "100.00",
  "fee": {
    "total": "0.10",
    "asset": "stellar:USDC:GBBD47IF6LWK7P7MDEVSCWR7DPUWV3NY3DTQEVFL4NAT4AQH3ZLLFLA5"
  }
}

Using a quote with SEP-6

Pass the quote_id when initiating a deposit or withdrawal to use the locked rate:

curl -X GET "https://<your-anchor-domain>/sep6/deposit?\
asset_code=USDC&\
account=GCEXAMPLE4KEYPAIR7HERE2REPLACE5WITH5YOUR5ACTUAL5STELLAR5KEY&\
amount=6.5&\
funding_method=mobile&\
quote_id=quote_a1b2c3d4-e5f6-7a8b-9c0d-1e2f3a4b5c6d" \
  -H "Authorization: Bearer <sep10_jwt_token>"
import { Wallet, Keypair } from '@stellar/typescript-wallet-sdk';

const wallet = Wallet.TestNet();
const anchor = wallet.anchor({ homeDomain: '<your-anchor-domain>' });
const accountKp = Keypair.fromSecret('SCZANGBA5YHTNYVVV3C7CAZMCLXPJLNS2YFGXDNASLFTGBPFMRKCY6ML');

const sep10 = await anchor.sep10();
const authToken = await sep10.authenticate({ accountKp });

// Use the quote_id with SEP-6 deposit via the wallet SDK
const sep6 = anchor.sep6();
const deposit = await sep6.deposit({
  authToken,
  params: {
    asset_code: 'USDC',
    account: accountKp.publicKey,
    amount: '6.5',
    funding_method: 'mobile',
    quote_id: 'quote_a1b2c3d4-e5f6-7a8b-9c0d-1e2f3a4b5c6d',
  },
});
console.log("Deposit with locked quote:", deposit.id);

Quotes expire. If the quote has expired when you initiate the transaction, the anchor will return a 400 error. Create a new quote and retry.

TypeScript example

import { Wallet, Keypair } from '@stellar/typescript-wallet-sdk';

// Note: SEP-38 quotes are not yet built into the wallet SDK.
// Use the wallet SDK for authentication, then fetch for quotes.
const wallet = Wallet.TestNet();
const ANCHOR_HOME_DOMAIN = '<your-anchor-domain>';
const anchor = wallet.anchor({ homeDomain: ANCHOR_HOME_DOMAIN });
const accountKp = Keypair.fromSecret('SCZANGBA5YHTNYVVV3C7CAZMCLXPJLNS2YFGXDNASLFTGBPFMRKCY6ML');

// Authenticate using wallet SDK
const sep10 = await anchor.sep10();
const authToken = await sep10.authenticate({ accountKp });

// Get anchor info to find quote server URL
const toml = await anchor.getInfo();
const quoteServer = toml.anchorQuoteServer;

interface Quote {
  id: string;
  expires_at: string;
  price: string;
  sell_asset: string;
  sell_amount: string;
  buy_asset: string;
  buy_amount: string;
  fee: { total: string; asset: string };
}

async function getIndicativePrice(
  sellAsset: string,
  buyAsset: string,
  sellAmount: string
) {
  const params = new URLSearchParams({ sell_asset: sellAsset, buy_asset: buyAsset, sell_amount: sellAmount });
  const res = await fetch(`${quoteServer}/sep38/price?${params}`, {
    headers: { Authorization: `Bearer ${authToken}` },
  });
  return res.json();
}

async function createQuote(
  sellAsset: string,
  buyAsset: string,
  sellAmount: string
): Promise<Quote> {
  const res = await fetch(`${quoteServer}/sep38/quote`, {
    method: 'POST',
    headers: {
      Authorization: `Bearer ${authToken}`,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({
      sell_asset: sellAsset,
      buy_asset: buyAsset,
      sell_amount: sellAmount,
      context: 'sep6',
    }),
  });

  if (!res.ok) {
    const error = await res.json();
    throw new Error(`Quote failed: ${error.error}`);
  }

  return res.json();
}

// Usage: Get a firm quote for depositing 100 GHS
const quote = await createQuote(
  'iso4217:GHS',
  'stellar:USDC:GBBD47IF6LWK7P7MDEVSCWR7DPUWV3NY3DTQEVFL4NAT4AQH3ZLLFLA5',
  '100'
);

console.log(`Quote ${quote.id}: ${quote.sell_amount} GHS → ${quote.buy_amount} USDC`);
console.log(`Rate: 1 USDC = ${quote.price} GHS`);
console.log(`Expires: ${quote.expires_at}`);

// Use the quote with SEP-6 deposit via wallet SDK
const sep6 = anchor.sep6();
const deposit = await sep6.deposit({
  authToken,
  params: {
    asset_code: 'USDC',
    account: accountKp.publicKey,
    amount: quote.buy_amount,
    funding_method: 'mobile',
    quote_id: quote.id,
  },
});
console.log("Deposit with locked quote:", deposit.id);

Quote parameters

ParameterRequiredDescription
sell_assetYesAsset identifier being sold
buy_assetYesAsset identifier being bought
sell_amountOne ofAmount of sell asset (mutually exclusive with buy_amount)
buy_amountOne ofAmount of buy asset (mutually exclusive with sell_amount)
contextYesContext for the quote — sep6 or sep24

Error responses

StatusErrorMeaning
400invalid_assetAsset identifier not recognized
400invalid_amountAmount outside supported range
400quote_expiredReferenced quote has expired
404quote_not_foundQuote ID does not exist
500internal_errorAnchor-side error

On this page