Skip to content

RFQ on testnet with curl ​

RFQ is the instant path: you ask, makers answer within the corridor reply budget (~750 ms on this corridor), and you get a firm signed quote plus ready calldata bound to your wallet. You broadcast it yourself. It fills whole or not at all. There is no partial fill and no resting order.

Base URL: https://api.textilecredit.com. Auth: a test key (tx_test_…). Live keys cannot touch chain 97, and a test key cannot touch mainnet. This guide uses BSC testnet; the same key also works on Base Sepolia (84532, cNGN ↔ USDC). Swap in the chain ID and the token addresses from Testnet and every step below is the same, except USDC there is 6 decimals, not 18.

Tokens and chain ​

Chain97 (BNB Smart Chain testnet)
cNGN0x8a078b182bA9649c03982c2a80CDcc81cdc99dA8 (6 decimals)
USDT0x337610d27c682E347C9cD60BD4b3b107C9d34dDd (18 decimals)
Reactor0x65B2f3847b391783e15554D5f2826FBaF318a7Df

Fund the taker wallet with the sell token + testnet BNB for gas. You approve the reactor, not Permit2. Permit2 is the maker's side.

bash
export TEXTILE_TEST_API_KEY='tx_test_…'
export CNGN=0x8a078b182bA9649c03982c2a80CDcc81cdc99dA8
export USDT=0x337610d27c682E347C9cD60BD4b3b107C9d34dDd
export TAKER=0xYourWallet   # the wallet that approves and broadcasts

The chain-97 size floor is about $0.01 of notional (the mainnet $1 corridor floor scaled down). For USDT that is 10000000000000000 (0.01 at 18dp). Soft legs convert through the corridor rate, so the atomic minimum in cNGN moves with NGN. Below the floor is a 400.

0. Preview (optional) ​

Free indicative price off the makers' published levels. No wallet, no lock, no slot. Use it while a user types.

bash
curl -sS -X POST 'https://api.textilecredit.com/v2/rfq/preview' \
  -H "Authorization: Bearer $TEXTILE_TEST_API_KEY" \
  -H 'Content-Type: application/json' \
  -d "{\"chainId\":97,\"sellToken\":\"$CNGN\",\"buyToken\":\"$USDT\",\"sellAmount\":\"140000000\"}"

Read availableSellAmount for the published depth. A preview is not a promise. The firm price can differ, and a size that previews fine can still come back no_quote.

1. Request a firm quote ​

This call blocks while makers answer (750 ms reply budget on this corridor). Set your client timeout to 75 s — a cold corridor can also spend up to 60 s on an on-chain decimals preflight before the reply clock starts.

Send exactly one of sellAmount (exact-input, a gross fee-inclusive spend cap) or buyAmount (exact-output, the exact amount you receive).

bash
curl -sS -X POST 'https://api.textilecredit.com/v2/rfq/request' \
  -H "Authorization: Bearer $TEXTILE_TEST_API_KEY" \
  -H 'Content-Type: application/json' \
  -d "{
    \"chainId\": 97,
    \"sellToken\": \"$CNGN\",
    \"buyToken\": \"$USDT\",
    \"sellAmount\": \"140000000\",
    \"taker\": \"$TAKER\"
  }" | tee /tmp/rfq.json

With a partner key that is the whole body. Without a key you also send takerProof, a TakerControl signature by the taker wallet — EIP-712 by default, or scheme: "eip191" with personal_sign for hardware wallets. See proof of control.

Either way the taker wallet must already hold the sell token: /request reads the balance before waking makers (proof of funds). Allowance isn't checked here.

The three numbers that matter:

  • takerPays is exactly what leaves your wallet (maker output + protocol fee). Never more than the sellAmount you sent. Approve this.
  • buyAmount is what you receive.
  • expiresAt is your accept cutoff. Past it, do not broadcast.
bash
jq '.data.status, .data.quote.takerPays, .data.quote.buyAmount, .data.quote.expiresAt' /tmp/rfq.json

Returned exactly once

claimToken and the signed order blobs come back on this response only. GET /v2/rfq/{id} never replays them. Store the claim token. It is what authorizes cancel, submit and status.

Other direction (0.5 USDT → cNGN): flip the tokens and send "sellAmount": "500000000000000000".

No quote ​

status: "no_quote" is a 200, not an error. Branch on data.status.

reasonWhat happened
no_makers_onlineNo maker connected on this corridor. Back off; tell us if it persists on testnet.
no_valid_quoteMakers were asked, none priced it in time. Retrying immediately is fine.
no_restricted_liquidityYou restricted to wallets that couldn't quote.

If availableSellAmount comes back, that is the real depth, so retry at that size. If retryAfterMs comes back, somebody else's live quote is holding the liquidity. Wait that long.

2. Approve and broadcast ​

The response hands you both transactions, already encoded. value is always "0". The fee is taken in the sell token, not native.

bash
export RFQ_ID=$(jq -r '.data.rfqId' /tmp/rfq.json)
export CLAIM=$(jq -r '.data.claimToken' /tmp/rfq.json)
SWAP_TO=$(jq -r '.data.transactions.swap.to' /tmp/rfq.json)
SWAP_DATA=$(jq -r '.data.transactions.swap.data' /tmp/rfq.json)
APPROVE_TO=$(jq -r '.data.transactions.approval.to' /tmp/rfq.json)
APPROVE_DATA=$(jq -r '.data.transactions.approval.data' /tmp/rfq.json)

# Skip the approval if the standing allowance already covers takerPays.
cast send "$APPROVE_TO" "$APPROVE_DATA" --rpc-url $BSC_TESTNET_RPC --private-key $PK
cast send "$SWAP_TO" "$SWAP_DATA" --rpc-url $BSC_TESTNET_RPC --private-key $PK

Always broadcast transactions.swap. When no single maker covers the size, Textile splits it and swap is an executeBatch over every signed order; executing the top-level encodedOrder alone would under-fill the bundle.

Re-check the clock before sending. A slow approval can push you past expiresAt. If it does, request a new quote instead of broadcasting a dead one. TTL is 60 s at most and can be shorter.

Changed your mind? Release the maker's inventory instead of letting it lapse:

bash
curl -sS -X POST "https://api.textilecredit.com/v2/rfq/$RFQ_ID/cancel" \
  -H "X-Rfq-Claim: $CLAIM"

3. Report and track ​

Tell us the hash so we can track it. Idempotent on the same hash; a different hash for an already-reported RFQ is a 409.

bash
curl -sS -X POST "https://api.textilecredit.com/v2/rfq/$RFQ_ID/submit" \
  -H "X-Rfq-Claim: $CLAIM" -H 'Content-Type: application/json' \
  -d '{"txHash":"0xabc…"}'

curl -sS "https://api.textilecredit.com/v2/rfq/$RFQ_ID" -H "X-Rfq-Claim: $CLAIM"

Reporting is a courtesy. Settlement is reconciled from the chain either way, so a failed /submit is not a failed swap. Prefer the rfq.filled webhook over polling. Note that expired and failed are provisional for 24 h: a late-indexed fill flips the status and sends a second rfq.filled with late: true.

Gotchas ​

  • You may hold 4 outstanding RFQs. A firm quote holds its slot until it fills or its signed order deadline passes. Cancelling and reporting do not free it. Over the cap is a 429 with no Retry-After. The earliest latestOrderDeadline among your live quotes is when the next slot frees.
  • Live quotes reserve your balance. Admission is balance ≥ committed + required, so a wallet holding 100 USDT can't hold two 100 USDT quotes open.
  • Exact-input is a cap, not a spend. sellAmount is the most you'll pay; takerPays is the actual debit. Read it if you need the exact figure.
  • Need an exact figure? Use buyAmount (exact-output). It names the amount you receive, and makers price the sell side.

Scopes ​

StepScope
Previewnone, key optional
Request / cancel / submittrades:write
Get by idtrades:read

Anonymous callers use the claim token instead; it isn't scoped.