# Pay on Stellar

> How an agent pays Inferit per request with x402 on Stellar: the network, the ITC asset and its contract, free XLM and the trustline, the faucet, the client code, where the balance is kept and the limits.

An agent can pay Inferit per request with x402 on Stellar: one transfer of ITC to Inferit's treasury account, signed by the agent, with the network fee paid by Inferit. No account with Inferit and no API key.

## Network

x402 names the network `stellar:testnet` (a CAIP-2 id). Every payment is signed for its passphrase. The 402 lists one entry per network Inferit takes payments on: Whitechain (`eip155:1874`) and Stellar. A client that registers only the Stellar scheme pays with the Stellar entry.

| What | Value |
| --- | --- |
| Network id | `stellar:testnet` |
| Passphrase | `Test SDF Network ; September 2015` |
| Soroban RPC | `https://soroban-testnet.stellar.org` |
| Horizon | `https://horizon-testnet.stellar.org` |
| Explorer | [stellar.expert/explorer/testnet](https://stellar.expert/explorer/testnet) |
| Free XLM for a new account | `https://friendbot.stellar.org?addr=G…` (friendbot) |

## The asset

Payments on Stellar are in ITC, the same credit Inferit prices in on Whitechain: one ITC is one dollar of price. On Stellar, ITC is a classic asset issued by Inferit's issuing account, and x402 moves it through its Stellar Asset Contract (the asset's contract address, `C…`). The contract is derived from the asset itself: `new Asset(code, issuer).contractId(passphrase)` in stellar-sdk. So a client can check the contract it is asked to pay against the code and issuer it pinned.

ITC on Stellar has 7 decimals; Inferit's API amounts are whole millionths of a dollar (6 decimals). So the API asks for 10 base units per millionth of a dollar: the minimum payment, $0.01 (10,000 millionths), is `"100000"` on Stellar. What arrives is credited as the base units divided by 10, rounded down, so an amount below 10 base units is never credited. One ITC is 10,000,000 base units.

### Live values

Read from `GET /v1/rails` (the rail with `"id": "stellar"`), so this always matches the API. Each address links to the explorer.

## A payer account

1.  **A key.** Any ed25519 Stellar key: its public key (`G…`) is the payer. `Keypair.random()` in stellar-sdk makes one, and `createEd25519Signer` from `@x402/stellar` signs with it. Stellar addresses are case-sensitive: never lower-case one.
2.  **Free XLM.** Friendbot creates the account with free XLM: `https://friendbot.stellar.org?addr=G…`. The XLM pays the account's reserve and the trustline below. x402 payments cost the agent no XLM: Inferit pays their network fee.
3.  **A trustline to ITC.** A Stellar account holds an asset only after it opts in: one `changeTrust` operation for ITC, with the code and issuer from `GET /v1/rails`.
4.  **ITC from Inferit's faucet.** `POST /v1/faucet/stellar` with `{"address":"G…"}` sends ITC to the address, once per address per 24 h, at no charge. Without the trustline the faucet answers with an error that says how to add it. It sends to `G…` accounts only, the accounts x402 payments here come from.

## How a payment works

1.  Call `POST /v1/chat/completions` with no `Authorization` header. It answers `402 Payment Required`; the `PAYMENT-REQUIRED` header (base64 JSON) and the body carry the same document. Its Stellar entry in `accepts`: scheme `exact`, network `stellar:testnet`, `asset` = the ITC contract (`C…`), `payTo` = Inferit's treasury account (`G…`), `amount` in base units, `maxTimeoutSeconds`, and `extra.areFeesSponsored: true`.
2.  The amount is the worst-case cost of this exact request body, as on Whitechain: the input estimate plus the output limit, all-in, and never less than $0.01; then times 10 for the 7 decimals. The quote is pinned for 120 s. The transfer must be exactly this amount.
3.  The client builds the contract call `transfer(from, payTo, amount)` on the ITC contract, simulates it, and signs only its authorization entry. The signature is valid until a ledger number: the current ledger plus `maxTimeoutSeconds` divided by about 5 seconds a ledger. It resends the same body with the transaction in the `PAYMENT-SIGNATURE` header.
4.  Inferit's API checks the payment with the official x402 facilitator code (`@x402/stellar@2.28.0`): one transfer, of ITC, from the signer to the treasury, of exactly the amount, on this network, before its ledger expiry. It then wraps the transaction in one from its own fee-payer account, pays the fee in XLM and submits it. If the payment does not verify, the API answers 402 again, with the reason in `error`.
5.  Once the transfer is in a ledger, it is credited to the payer's balance with Inferit: base units divided by 10, rounded down. Each payment is credited once; a replayed payment is refused.
6.  The request is served (streaming included) and charged its exact cost against that balance, never more than this one payment. The response carries `PAYMENT-RESPONSE` (base64: the Stellar transaction hash, the network and the payer) and `x-inferit-buyer-cost-micro`, the cost in millionths of a dollar.
7.  Every call without a key gets its own 402 and its own payment, and is charged against that payment only: what a payment does not spend adds to your balance ([spending it](https://inferit.cc/docs/payments/stellar.md#balance)).

## The client code

### TypeScript, with the official x402 fetch client

A brand-new key works: friendbot funds the account, the faucet gives it ITC, and the agent never pays a network fee for a payment. Pinned versions: `@x402/fetch@2.28.0`, `@x402/stellar@2.28.0` and `@stellar/stellar-sdk@16.4.1`.

_agent.mts_

```typescript
// npm i @x402/fetch@2.28.0 @x402/stellar@2.28.0 @stellar/stellar-sdk@16.4.1
// then: npx tsx agent.mts    (Node 22+)
import { Asset, BASE_FEE, Horizon, Keypair, Networks, Operation, TransactionBuilder } from '@stellar/stellar-sdk';
import { decodePaymentResponseHeader, wrapFetchWithPayment, x402Client } from '@x402/fetch';
import { createEd25519Signer } from '@x402/stellar';
import { ExactStellarScheme } from '@x402/stellar/exact/client';

const API = 'https://api-production-c74b9.up.railway.app';
const NETWORK = 'stellar:testnet'; // pinned: the one network this agent pays on
const horizon = new Horizon.Server('https://horizon-testnet.stellar.org');
// A brand-new key works: friendbot gives it free XLM, and Inferit pays the network fee of every payment.
// Your balance with Inferit belongs to the key that pays: set STELLAR_SECRET_KEY to keep using one (the key you sign in
// with on Inferit, to see and spend that balance).
const keypair = process.env.STELLAR_SECRET_KEY ? Keypair.fromSecret(process.env.STELLAR_SECRET_KEY) : Keypair.random();
const address = keypair.publicKey(); // case-sensitive: never lower-case a Stellar address
if (!process.env.STELLAR_SECRET_KEY) console.log('new key', address, '- keep it to reuse its balance: STELLAR_SECRET_KEY=' + keypair.secret());

// 1. The asset: code and issuer from the API, its contract checked against them. Pin both in real code.
const rails = await (await fetch(`${API}/v1/rails`)).json();
const d = rails.rails.find((r: { id: string }) => r.id === 'stellar').details;
const [code, issuer] = d.asset.split(':');
const ITC = new Asset(code, issuer);
const SAC = ITC.contractId(Networks.TESTNET);
if (d.network !== NETWORK || d.assetContract !== SAC) throw new Error('unexpected Stellar network or asset');

// 2. Create the account with free XLM, then opt in to ITC with a trustline.
await fetch(`https://friendbot.stellar.org?addr=${address}`);
const trust = new TransactionBuilder(await horizon.loadAccount(address), { fee: BASE_FEE, networkPassphrase: Networks.TESTNET })
  .addOperation(Operation.changeTrust({ asset: ITC }))
  .setTimeout(60)
  .build();
trust.sign(keypair);
await horizon.submitTransaction(trust);

// 3. ITC from Inferit's faucet, once per address per 24 h.
const faucet = await fetch(`${API}/v1/faucet/stellar`, {
  method: 'POST',
  headers: { 'content-type': 'application/json' },
  body: JSON.stringify({ address }),
});
console.log('faucet', faucet.status, await faucet.text());

// 4. The standard x402 v2 client with a Stellar signer. It pays only in ITC, and at most 1 ITC
//    (10000000 base units, 7 decimals) for one request, whatever the server quotes.
const client = new x402Client()
  .register(NETWORK, new ExactStellarScheme(createEd25519Signer(keypair.secret(), NETWORK)))
  .setSpendControls({ allowedAssets: [{ network: NETWORK, asset: SAC, maxAmountPerPayment: '10000000' }] });
const fetchWithPayment = wrapFetchWithPayment(fetch, client);

// 5. Call the API with no key: 402 -> sign -> paid retry, all inside fetchWithPayment.
const res = await fetchWithPayment(`${API}/v1/chat/completions`, {
  method: 'POST',
  headers: { 'content-type': 'application/json' },
  body: JSON.stringify({
    model: 'qwen/qwen-2.5-14b-instruct',
    messages: [{ role: 'user', content: 'Say hello in five words.' }],
    max_tokens: 64, // the quote is the worst case for this body, so a small max_tokens keeps it small
  }),
});
const body = await res.json();
console.log(res.status, body.choices?.[0]?.message?.content ?? body);
console.log('model', res.headers.get('x-inferit-model'));
const receipt = res.headers.get('PAYMENT-RESPONSE');
if (receipt) console.log(decodePaymentResponseHeader(receipt)); // the Stellar transaction, the network and the payer
console.log('cost', res.headers.get('x-inferit-buyer-cost-micro'), 'millionths of a dollar; the rest stays your balance');
// A call without a key never draws on that balance: an API key made after signing in with this key spends it.
```

The `setSpendControls` line is configuration of the standard client, not a patch: the client only pays in tokens it knows, and the ITC contract is not on its built-in list. Its `maxAmountPerPayment` (`"10000000"`, 1 ITC, $1) is the most the client signs for one request: a setting in your client, not a rule the API enforces.

A complete agent, with the account, the trustline, the faucet, checks on what it is asked to pay and a printed receipt: [stellar-x402-agent](https://github.com/InferitAI/inferit-examples/tree/main/stellar-x402-agent) in inferit-examples.

### curl: see the Stellar entry without paying

_the 402, without paying_

```bash
curl -s https://api-production-c74b9.up.railway.app/v1/chat/completions \
  -H 'content-type: application/json' \
  -d '{"model":"qwen/qwen-2.5-14b-instruct","messages":[{"role":"user","content":"Hi"}],"max_tokens":64}' \
  | jq '.accepts[] | select(.network == "stellar:testnet")'
# scheme "exact", network, asset (the contract, C…), payTo (the treasury, G…), amount (7 decimals),
# maxTimeoutSeconds, extra.areFeesSponsored: true
```

## Where the money is

-   **The treasury.** Every payment on Stellar goes to Inferit's treasury account, the `payTo` of the Stellar entry. Inferit holds what your requests have not spent, as your balance in its ledger, under your Stellar address.
-   **Unlike Whitechain.** On Whitechain a payment lands in the MarketEscrow contract, under limits the contract enforces ([Whitechain escrow](https://inferit.cc/docs/payments/whitechain.md#x402)). On Stellar the `exact` scheme allows one plain transfer to `payTo`, so the balance is kept by Inferit's API, and the limits below are the API's.
-   **Spending it.** x402 payments add to your balance; a call without a key never draws on it. An API key does: sign in with the same Stellar key (`POST /v1/auth/stellar/challenge`, then `/verify` with the message signed as a SEP-53 message, or [Buy](https://inferit.app/buy) with a Stellar wallet), make a key with `POST /v1/keys`, and each request with that key is charged its exact cost against the balance. Once a payment would take the balance past $25, x402 payments are refused (`invalid_exact_stellar_balance_over_limit`) until a key spends it down. Withdrawals from it are planned.
-   **Sellers.** A request paid on Stellar goes to the cheapest healthy seller of the model on Inferit, whichever network the seller registered on. What the seller is owed is recorded under the address it signed in with.

## Limits

| What | Default | Why |
| --- | --- | --- |
| Minimum payment | $0.01 (0.01 ITC, `"100000"`) | Each payment is a transfer on the ledger whose fee Inferit pays |
| Most per payment | $5 (5 ITC, `"50000000"`) | Inferit holds what a payment does not spend, so each payment stays small |
| Most a balance may reach | $25 (25 ITC) | A payment that would take your balance past it is refused before it settles: nothing is transferred. An API key from a Stellar sign-in spends the balance ([Spending it](https://inferit.cc/docs/payments/stellar.md#spending)) |
| Amount | Exactly the quote | The `exact` scheme: a transfer of any other amount is refused |
| Signature validity | `maxTimeoutSeconds` (120 s), counted in ledgers of about 5 s | The authorization entry names its last valid ledger |
| Quote pin | 120 s, per request (path, routing headers and JSON body) | The paid retry verifies against the quote it was shown; a changed request gets a new 402 |
| Signer | A Stellar account key (`G…`), x402 version 2 | `createEd25519Signer` signs the authorization entry; the facilitator pays the fee |
| x402 payments | 30 paid attempts a minute per paying address, 60 per IP | Payments from one address are handled one at a time |
| Client ceiling (the code above) | $1 per payment | Your own setting, in the client's spend controls |

The API reports its own limits in `GET /v1/rails` (`maxPaymentMicro`, `maxBalanceMicro`; [Live values](https://inferit.cc/docs/payments/stellar.md#live)). Agents should start at [Agents](https://inferit.cc/agents) or [agents.md](https://inferit.cc/agents.md); the whole x402 exchange is in the [API reference](https://inferit.cc/docs/api-reference.md#x402-stellar).

## What is public

Each payment is a Stellar transaction: the payer's address, the treasury, the asset and the amount are public on the ledger, and Inferit's fee-payer account is the transaction's source. Your balance with Inferit and what each request cost are in Inferit's own ledger, not on chain: the response headers and your requests are the record.

---

Canonical page: https://inferit.cc/docs/payments/stellar
All pages as markdown: https://inferit.cc/llms.txt
