Skip to main content

Getting Started

From a cold start to an anchored, proven memory.

Prerequisites

ToolVersionInstall
Bun1.3.0 - pinned in packageManagercurl -fsSL https://bun.sh/install | bash
Postgres15 or any modern releasebrew install postgresql@15, or use Docker
Anchor CLI1.1.2 - pinned in Anchor.tomlcargo install --git https://github.com/solana-foundation/anchor avm && avm install 1.1.2 && avm use 1.1.2
Solana CLI4.1.1 (Agave)sh -c "$(curl -sSfL https://release.anza.xyz/stable/install)"
Rust1.89.0curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
Anthropic API key-Only for ingest(). The store → anchor → prove loop needs no LLM

You do not need to deploy anything. toda-commit is already live on devnet at 5SwaBH3pXzEKhmL6pbqZ1JgeDiGaf6i5oVj8951RX9sm.

Install

bun install

Database

createdb toda
cd packages/db && bun x drizzle-kit migrate && cd ../..

This creates seven tables: users, memories, commit_batches, merkle_trees, siws_nonces, api_keys, stake_lots.

drizzle.config.ts reads DATABASE_URL, defaulting to postgres://postgres:postgres@localhost:5432/toda. On Homebrew, your Postgres superuser is usually your own username - export the right URL:

export DATABASE_URL="postgres://$(whoami)@localhost:5432/toda"

Environment

cp .env.example .env

Three values must be real. The rest have working defaults.

# Fail-fast at startup if any of these are missing or malformed.
DATABASE_URL="postgres://…/toda"
JWT_SECRET="$(openssl rand -hex 32)" # ≥ 32 chars
MASTER_ENCRYPTION_KEY="$(openssl rand -base64 32)"
TODA_PROGRAM_ID="5SwaBH3pXzEKhmL6pbqZ1JgeDiGaf6i5oVj8951RX9sm"
These are the two keys that read your data

MASTER_ENCRYPTION_KEY unwraps every user's data key. JWT_SECRET mints sessions for any user. Anyone holding either reads plaintext. Generate them fresh, keep them in a secrets manager, and never commit them. There is no rotation path.

Anchoring authority

The anchor service needs a funded devnet keypair to pay transaction fees.

bin/dev-keypair.sh # idempotent; generates and funds ./authority-keypair.json

The file is gitignored. Never commit it. If devnet's faucet is dry, fund it from faucet.solana.com.

Run

bun run dev
ServiceURL
Dashboardhttp://localhost:3000
APIhttp://localhost:3001
Extension APIhttp://localhost:3002
Docshttp://localhost:3003

bun run dev does not start the anchor service. It is a distinct long-running process, and you want exactly one of it:

bun run apps/api/src/worker/anchor.ts

Or bring up Postgres, migrations, the API, the anchor service (worker), and the dashboard together:

docker compose up

The extension API and these docs are not in the compose stack - run them with bun run dev.

Your first memory

Open the dashboard, connect a Solana wallet, and sign the SIWS message. Then mint an API key from the dashboard, or curl for one after authenticating.

import { Toda } from "@toda/sdk";

const toda = new Toda({
apiUrl: "http://localhost:3001",
apiKey: process.env.TODA_API_KEY!,
});

const { id, leafHash, leafIndex } = await toda.remember(
"The user always deploys on Fridays.",
{ kind: "preference" },
);

console.log({ id, leafHash, leafIndex });

The memory is now encrypted, hashed, and appended to your log. It is not yet anchored:

await toda.verify(id); // → false

That is correct. The seal policy fires when 64 leaves accumulate or the oldest pending leaf turns 5 minutes old. With one memory, you wait for the timeout - or force a sweep:

import { anchorUser } from "./apps/api/src/worker/anchor.ts";
await anchorUser(userId, { force: true });

Then:

await toda.verify(id); // → true

Your memory's leaf is folded into a Merkle root, and that root now sits in a PDA on Solana that toda cannot rewrite.

Look at the chain

import { Connection, PublicKey } from "@solana/web3.js";
import { readUserCommit } from "@toda/solana";
import { bytesToHex } from "@toda/crypto";

const connection = new Connection("https://api.devnet.solana.com");
const tip = await readUserCommit(connection, new PublicKey(myWallet));

console.log({
root: bytesToHex(tip.merkleRoot), // 32 bytes committing to your whole log
count: tip.memoryCount, // 1n
nonce: tip.nonce, // 1n - one commit has landed
});

This read goes to Solana, not to toda. It is the only fact in the system that toda cannot fabricate.

Give an agent memory

cd examples/chat-agent
TODA_API_URL=http://localhost:3001 \
TODA_API_KEY=toda_… \
ANTHROPIC_API_KEY=sk-ant-… \
bun run src/agent.ts "I prefer TypeScript and I deploy on Fridays."

Run it again in a fresh process, ask what it knows about you, and it remembers. The memory did not live in that process.

Build the programs

Only needed if you change Rust, or want to run the staking tests.

anchor build # emits target/idl/*.json and target/types/*.ts

@toda/solana consumes committed copies of the IDL in packages/solana/src/idl/. Refresh them after any IDL change, or the typed client will drift from the deployed program.

bun test programs/toda-commit/tests/ programs/toda-staking/tests/

A cold anchor build compiles the full Solana dependency graph and takes several minutes. Incremental rebuilds are ~12 seconds. The staking suite loads target/deploy/toda_staking.so, so build before you test.

Where to go next