Skip to content
Docs

Guides

Build an agent

Drive a fund from code with the SDK and the API.

An agent is any program holding the fund's agent key. It drives the fund through the same API and instructions as the app, and every rule is still enforced on chain.

The SDK

The TypeScript SDK lives in web/lib/protocol and uses @solana/web3.js:

ExportWhat it does
NodClientSigns in, prepares and submits exact transactions, follows receipts, requests routes
buildTransactionBuilds a v0 transaction with a fresh finalized blockhash and the one-time intent memo
proposeInstruction, directSwapInstructionPost a proposal or swap inside the budget, from a committed route
investorInstructionsDeposit, redeem and update NAV
conditionalInstructions, marketInstructionsSplit, merge, redeem copies; trade; add and remove liquidity
decisionInstruction, executeInstruction, expireInstructionStart, finalize, execute and expire
JupiterClientQuotes a route from Jupiter directly and composes it onto the fund's vaults
derivePushFeedThe Pyth push feed account for a feed ID

A proposal from code

import { Connection, PublicKey } from "@solana/web3.js";
import { NodClient, buildTransaction, derivePushFeed, proposeInstruction } from "./lib/protocol";

const connection = new Connection(rpcUrl, "confirmed");
const api = new NodClient("https://<your-nod-app>/api/v1");
await api.authenticate(wallet); // signs the one-time sign-in message

const fund = await api.fund(fundAddress);
const route = await api.route({ fund: fundAddress, direction: "buy", amount: 3_000_000_000n, slippageBps: 100 });
const action = {
  program: new PublicKey(route.action.program),
  accounts: route.action.accounts.map(meta => ({ ...meta, pubkey: new PublicKey(meta.pubkey) })),
  data: Buffer.from(route.action.data, "base64"),
  inputAmount: BigInt(route.action.inputAmount),
  minOutput: BigInt(route.action.minOutput),
  quoteToAsset: route.action.quoteToAsset,
};

const instruction = proposeInstruction({
  agent: wallet.publicKey, fund: new PublicKey(fundAddress), data: fund.data,
  sequence: BigInt(fund.data.nextSequence), oracle: derivePushFeed(assetFeedId), quoteOracle: derivePushFeed(usdcFeedId), action,
});
const table = (await connection.getAddressLookupTable(new PublicKey(fund.lookupTable))).value!;
const transaction = await buildTransaction(connection, wallet.publicKey, [instruction], [table]);

const receipt = await api.signAndSubmit(wallet, transaction, "proposal:2026-10-06:buy-sol", [table]);
await api.waitForTransaction(receipt.id);

The amount is in base units: 3,000 USDC is 3_000_000_000n. assetFeedId is the registered asset's Pyth feed ID and usdcFeedId the configuration's USDC/USD feed ID, both 32 bytes. The fund's lookup table keeps a proposal, which carries its whole route as data, inside one transaction.

Idempotency

Give every intended action a stable idempotency key of 8 to 128 characters. Retrying with the same key returns the same prepared transaction and never signs a second one. A new key is a new action.

If a submission's answer is lost, the SDK throws SubmissionUncertainError with the transaction's ID; retrySubmission sends exactly the same signed bytes again. If another signed transaction for the same fund is still settling, the API answers PENDING_INTENT_CONFLICT with that receipt's ID before your wallet is asked to sign.

The reference agent

npm run protocol:agent in web runs one configured buy or sell, as a proposal or inside the budget, then exits. It reads its settings from the environment:

VariableValue
NOD_API_URLThe app's API, ending in /api/v1
SOLANA_RPC_URL, NOD_EXPECTED_GENESIS_HASHThe network to act on, and its genesis hash
NOD_AGENT_KEYPAIRPath to the agent's keypair file
NOD_FUNDThe fund's address
NOD_PRICE_UPDATE, NOD_QUOTE_PRICE_UPDATEThe asset/USD and USDC/USD price accounts
NOD_SWAP_DIRECTIONbuy or sell
NOD_AGENT_MODEproposal or direct
NOD_SWAP_INPUT, NOD_SWAP_MINIMUM_OUTPUTBase units in, and the least out
NOD_SLIPPAGE_BPS0 to 500
NOD_IDEMPOTENCY_KEYA stable key for this one action
JUPITER_API_URL, JUPITER_API_KEYJupiter's Swap API and its key

It signs in, checks it is the fund's agent, quotes a route, commits it and waits for the receipt. Run it again with the same key and it reports the existing receipt instead of acting twice.