Docs
Docs
Integrate an agent in three lines, understand the limits model, and know exactly what the chain enforces.
- 01
Create a vault and give the agent's public key a limit. Deposit some funds.
- 02
Send the agent ~0.01 SOL for fees. It pays its own transaction fees.
- 03
Import
@/sdk. It is an in-repo module (not on npm in v1): copysrc/sdkandsrc/libinto your agent.
The 3-line integration
const rl = await Reinlock.fromVault(conn, VAULT, agent);
console.log("left:", await rl.remaining(USDC));
await rl.transfer({ mint: USDC, to: VENUE, amount: 5_000_000n });
Copy-paste example
Generated for Mainnet: its RPC endpoint and USDC mint.
import { Connection, Keypair, PublicKey } from "@solana/web3.js";
import bs58 from "bs58";
import { Reinlock, ReinlockCapError, ReinlockFeeError } from "@/sdk";
// Mainnet moves real funds. Use a dedicated RPC in production.
const RPC = process.env.SOLANA_RPC_URL ?? "https://api.mainnet-beta.solana.com";
const connection = new Connection(RPC, "confirmed");
// The agent's own key. Never your wallet's key.
const secret = bs58.decode(process.env.AGENT_SECRET_KEY!);
const agentKeypair = Keypair.fromSecretKey(secret);
// "Vault address" from the vault page.
const VAULT = new PublicKey(process.env.REINLOCK_VAULT!);
// Must be on the limit's allowlist, if it has one.
const VENUE = new PublicKey(process.env.REINLOCK_DESTINATION!);
const USDC_MINT = new PublicKey("EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v"); // Mainnet USDC
const rl = await Reinlock.fromVault(connection, VAULT, agentKeypair);
console.log("left:", await rl.remaining(USDC_MINT));
try {
// 5 USDC (6 decimals). Amounts are always base units.
const signature = await rl.transfer({
mint: USDC_MINT,
to: VENUE,
amount: 5_000_000n,
});
console.log("sent", signature);
} catch (e) {
if (e instanceof ReinlockCapError) {
console.log("over cap:", e.requested, e.remaining, e.stage);
} else if (e instanceof ReinlockFeeError) {
console.log("agent needs SOL for fees");
} else {
throw e;
}
}
// For SOL limits use Reinlock.SOL as the mint and lamports as the amount:
// await rl.transfer({ mint: Reinlock.SOL, to: VENUE, amount: 50_000_000n });
SDK reference
Reinlock.fromVault(connection, vault, agentKeypair, opts?)
Resolves the vault (multisig address, deposit address, or a limit address) and loads the limits whose member is the agent. Pass { spendingLimits: [address] } to skip the discovery scan.
- Returns
Promise<Reinlock>- Throws
- ReinlockError (VAULT_NOT_FOUND)
rl.remaining(mint)
Base units the agent can still send for that mint right now, read from chain. Accounts for daily limits that are due to reset on the next use.
- Returns
Promise<bigint>- Throws
- RPC errors only
rl.transfer({ mint, to, amount, memo?, checkLocally? })
Sends through Squads spending_limit_use, waits for confirmation and returns the signature. For SPL tokens it creates the destination token account if needed (the agent pays rent). checkLocally: false skips the local check so the program decides.
- Returns
Promise<string>- Throws
- ReinlockCapErrorReinlockDestinationErrorReinlockNoLimitErrorReinlockFeeErrorReinlockTransactionError
ReinlockCapError carries requested, remaining and stage: thrown by the local check before sending, by RPC simulation, or after an on-chain rejection. All errors extend ReinlockError.
The limits model
Vault
A Squads Protocol v4 multisig in “controlled” mode: your wallet is the only member and config authority, threshold 1. Funds live in its vault account (index 0). Reinlock writes no program of its own.
Spending limit
One SpendingLimit account per agent and token: an amount, a period (Daily or Lifetime), the agent as member and an optional destinations list. Daily limits reset once 24 hours have passed since last_reset, on the next use.amount − remaining_amount = spent
Revoke & kill switch
RemoveSpendingLimit, signed by you as config authority, closes the limit account (its rent returns to you). The kill switch puts one removal per limit in a single transaction.
Expiry
Squads limits have no expiry field. Reinlock writes your chosen expiry into an SPL Memo in the creation transaction and shows “Expired, revoke now” after that time. Removal still needs your signature.
Enforced vs not
Enforced on-chain by Squads v4
Cap: the agent cannot send more than the remaining amount.
Token: each limit covers exactly one mint.
Destinations: if you list addresses, only those can receive.
Revoke: once removed, the agent's next transfer fails.
Not enforced on-chain
Expiry: Squads limits have no native expiry. Reinlock records your date in an on-chain memo and prompts you to revoke; removal needs your signature.
Swaps and DeFi: v1 covers transfers only.
Your agent's judgement: the cap bounds the damage from bugs or bad decisions inside it; it does not judge them.
Also not enforced on-chain: the passkey step (a local check in your browser before your wallet signs) and the SDK's local cap check (a convenience; the program is the real guard).
Notes
The spend meter shows amount sent out versus your cap; it is not profit and loss. Reinlock's own code (this site and the SDK) is unaudited; the on-chain rules come from Squads Protocol v4.