ReinkeyDocsConsole

Reins quickstart

Give an agent a Stellar account it can pay from, with limits the network enforces.

1. Install

bash
npm i @reinkey/sdk

The packages are pre-release. Until they are on npm, use them from the monorepo: clone github.com/devbugra/Reinkey, run pnpm install at the root, and import @reinkey/sdk from the workspace (or pnpm build && pnpm pack in packages/sdk).

2. Create an account with a policy

A Reinkey account is a contract. Its constructor takes the owner key (you) and a policy (what the agent may do).

Open the console → Reins → Create your own agent account. Connect a Stellar wallet, set the per-transaction cap, daily cap, allowed payees, validity and whether exchange trades are allowed, and sign one transaction.

  • Your wallet becomes the owner. Only it can change the limits or freeze the account later.
  • The agent key is generated in your browser and its secret is shown once. It never reaches our server.
  • You get the account address (C…). Send it testnet USDC — see Testnet setup.

See Policy & rejections for every field and the Console guide for the screens.

Self-hosting the whole stack instead? ./scripts/deploy-testnet.sh deploys the channel and reinkey-account contracts with the Stellar CLI and writes the identifiers to deployments/testnet.json.

3. Fund a relayer, connect the agent

The agent is never the transaction source. A relayer account that you fund submits the agent's transactions and pays their fees; the agent only signs the authorization entry, so the agent key holds no XLM. On testnet, create a keypair and fund it with friendbot:

bash
curl "https://friendbot.stellar.org?addr=<RELAYER_PUBLIC_KEY>"

Contract identifiers come from the facilitator: GET /demo/info returns channelContract, usdc, xlm, dexRouter, rpcUrl and networkPassphrase.

TypeScript
import { Keypair } from "@stellar/stellar-sdk";
import { ReinkeyAccount } from "@reinkey/sdk";

const info = await (await fetch(`${api}/demo/info`)).json();

const account = new ReinkeyAccount({
  rpcUrl: info.rpcUrl,
  networkPassphrase: info.networkPassphrase,
  accountId,                              // C… the Reinkey account you created
  channelContractId: info.channelContract,
  usdcContractId: info.usdc,
  xlmContractId: info.xlm,                // needed by swap() and openChannelWith()
  dexRouterId: info.dexRouter,            // needed by swap() and openChannelWith()
  agent: Keypair.fromSecret(process.env.AGENT_SECRET),     // the key named in the policy
  relayer: Keypair.fromSecret(process.env.RELAYER_SECRET), // pays the agent's transaction fees
});

Settlement (claim) is a separate transaction and its fee is paid by the facilitator, not by you.

4. Open a channel

TypeScript
import { randomBytes } from "node:crypto";

const voucherSecret = randomBytes(32);
const { channelId } = await account.openChannel({
  payee,                // the seller's address, must be in the policy's payees
  deposit: 500_000n,    // 0.05 USDC
  voucherSecret,
});

One transaction. The voucher key is specific to this channel and separate from the agent key.

Pay with XLM instead

The account doesn't need to hold USDC. openChannelWith converts just enough XLM on the DEX and then locks the deposit; the seller still receives USDC.

TypeScript
const { channelId, swap } = await account.openChannelWith({
  payee, deposit: 100_000n, voucherSecret, payWith: "XLM",
});
// swap.sold XLM → swap.bought USDC, then the channel opens

Two transactions (Soroban allows one contract call per transaction), both checked by the policy: the XLM → USDC pair must be allowed, minOut is set to the deposit, and the deposit counts against the caps. The input is quoted with account.quoteIn() plus 1% headroom (slippageBps); any surplus USDC stays in the account.

5. Pay per call

TypeScript
import { ChannelSigner, x402Fetch } from "@reinkey/sdk";

const signer = new ChannelSigner({
  networkPassphrase, channelContract: channelContractId,
  channelId, secret: voucherSecret, deposit: 500_000n,
});

const { res, receipt } = await x402Fetch(`${api}/demo/book`, { signer, network: "stellar:testnet" });

x402Fetch makes the request, reads the 402, signs the next voucher and retries. No chain round-trip.

6. Pay per second or per token

TypeScript
import { streamPaid } from "@reinkey/sdk";

await streamPaid({
  url: `${api}/demo/ticker/stream`, apiUrl: api, signer, network: "stellar:testnet",
  onEvent: (e) => e.type === "tick" && console.log(e.data),
});

The SDK sends a new voucher each time a slice runs out, and stops paying the moment you stop listening.

7. Trade within limits

TypeScript
await account.swap({ amountIn: 1_000_000n, minOut: 1n });

Only pairs named in the policy are allowed, and minOut is required: a swap without slippage protection is rejected with SLIPPAGE_UNBOUNDED.

Reinkey runs on Stellar testnet. The contracts are unaudited. Don't send mainnet funds.