Reins quickstart
Give an agent a Stellar account it can pay from, with limits the network enforces.
1. Install
npm i @reinkey/sdkThe packages are pre-release. Until they are on npm, use them from the monorepo: clone github.com/devbugra/Reinkey, run
pnpm installat the root, and import@reinkey/sdkfrom the workspace (orpnpm build && pnpm packinpackages/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.shdeploys thechannelandreinkey-accountcontracts with the Stellar CLI and writes the identifiers todeployments/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:
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.
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
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.
const { channelId, swap } = await account.openChannelWith({
payee, deposit: 100_000n, voucherSecret, payWith: "XLM",
});
// swap.sold XLM → swap.bought USDC, then the channel opensTwo 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
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
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
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.