Sol.College

Full lesson

agentic_commerce

What it does

From the project README:

credit score decides how long the money waits. Strangers wait. Agents with a record settle at once.
Original
edison9733/agentic_commerce
SuperStack fork
Blockchains/agentic_commerce @ dfd70aa
Stars
0
Last push (original)
2026-10-12
Languages
TypeScript, Rust, HTML, JavaScript, CSS, Dockerfile
Detected stack
@solana/kitAnchor
Categories
AI & Agents
Licence
MIT
Homepage
https://agentic-commerce-two-theta.vercel.app

Architecture

136 source files. By extension: .ts ×110, .tsx ×20, .rs ×5, .js ×1.

Top-level directorySource files
packages64
apps54
scripts13
programs5

On-chain programs: programs/tessera

Dapp: devnet deploy pending

⏳ Devnet deploy pending — goes live when the devnet wallet is topped up. This fork's own dapp is not deployed yet, so there is no program address or transaction to show. It is queued in dapp-queue.txt; once devnet SOL is available it is deployed, exercised with real devnet transactions, and this section switches to the live dapp at /dapps/agentic-commerce/. Until then, the lesson and step-by-step guide below are complete, and you can run it locally with the commands below.

Key code, explained

Excerpts chosen automatically where the code uses the most Solana concepts, pinned to the fork's commit.

edison9733/agentic_commerce · programs/tessera/src/lib.rs L985–1008 @ dfd70aa · MIT · original
        init,        payer = payer,        space = 8 + Order::INIT_SPACE,        seeds = [b"order", order_id.as_ref()],        bump    )]    pub order: Box<Account<'info, Order>>,    /// The escrow vault. Its owner, the order account, is what gets quoted as    /// the x402 `payTo`, so a buyer can derive and check it from the order id.    #[account(        init_if_needed,        payer = payer,        associated_token::mint = mint,        associated_token::authority = order    )]    pub vault: Box<Account<'info, TokenAccount>>,    #[account(address = config.mint @ TesseraError::MintMismatch)]    pub mint: Box<Account<'info, Mint>>,    /// CHECK: recorded as the buyer; does not sign.    pub buyer: UncheckedAccount<'info>,    /// The merchant agrees to every order opened in its name.    pub merchant: Signer<'info>,    #[account(mut, seeds = [b"agent", buyer.key().as_ref()], bump = buyer_agent.bump)]    pub buyer_agent: Box<Account<'info, Agent>>,
  1. Line 985 init,: Creates and pays for a new account. Space and payer must be specified.
  2. Line 988 seeds = [b"order", order_id.as_ref()],: Declares the PDA seeds. Anchor re-derives the address and rejects the account if it doesn't match.
  3. Line 989 bump: Uses the bump seed, the extra byte that pushes the derived address off the ed25519 curve.
  4. Line 993 /// the x402 `payTo`, so a buyer can derive and check it from the order id.: Uses x402, an HTTP 402 pattern where an agent pays per request with an on-chain payment.
  5. Line 995 init_if_needed,: Creates the account only if it doesn't exist. Watch for re-initialisation bugs.
  6. Line 1006 pub merchant: Signer<'info>,: Requires this account to have signed the transaction.
edison9733/agentic_commerce · apps/web/src/lib/burner.ts L37–60 @ dfd70aa · MIT · original
    try {      localStorage.setItem(KEY, hex);    } catch {      // not persisted; the wallet lasts until the tab closes    }  }  return createKeyPairSignerFromPrivateKeyBytes(fromHex(hex));} /** Send with plain HTTP: preflight, then poll the signature. */export async function sendWith(payer: TransactionSigner, instructions: Instruction[]): Promise<string> {  const { value: latest } = await readRpc.getLatestBlockhash({ commitment: 'confirmed' }).send();  const message = pipe(    createTransactionMessage({ version: 0 }),    (m) => setTransactionMessageFeePayerSigner(payer, m),    (m) => setTransactionMessageLifetimeUsingBlockhash(latest, m),    (m) => appendTransactionMessageInstructions(instructions, m),  );  const signed = await signTransactionMessageWithSigners(message);  const signature = getSignatureFromTransaction(signed);  const wire = getBase64EncodedWireTransaction(signed);  await readRpc.sendTransaction(wire, { encoding: 'base64', preflightCommitment: 'confirmed' }).send();  const started = Date.now();  for (;;) {
  1. Line 48 const { value: latest } = await readRpc.getLatestBlockhash({ commitment: 'confirmed' }).se: Fetches a recent blockhash. Transactions expire if it gets too old.
  2. Line 58 await readRpc.sendTransaction(wire, { encoding: 'base64', preflightCommitment: 'confirmed': Submits the signed transaction to the cluster.
edison9733/agentic_commerce · apps/agents/src/merchant.ts L169–192 @ dfd70aa · MIT · original
 * The x402 payment is one token transfer of the order's amount into the * order's vault, authorised by `buyer`, and `buyer` really signed it. Checked * here, not left to the facilitator: a facilitator that says "success" for a * payment that did not fund this order must not make us deliver it. */async function paysThisOrder(payment: PaymentPayload, terms: EscrowTerms): Promise<boolean> {  try {    const buyer = terms.buyer;    const tx = decodeTransactionFromPayload(payment.payload as { transaction: string });    if (getTokenPayerFromTransaction(tx) !== buyer) return false;    const signature = tx.signatures[buyer];    if (!signature || !(await verifySignature(await getPublicKeyFromAddress(buyer), signature, tx.messageBytes))) return false;     const msg = getCompiledTransactionMessageDecoder().decode(tx.messageBytes);    // the payment scheme signs legacy or v0 messages with no lookup tables    if (msg.version === 1 || (msg.version === 0 && msg.addressTableLookups?.length)) return false;    const keys = msg.staticAccounts;    const tokenIxs = msg.instructions.filter((ix) => keys[ix.programAddressIndex] === TOKEN_PROGRAM_ADDRESS);    if (tokenIxs.length !== 1) return false;    const ix = tokenIxs[0]!;    const data = ix.data;    // TransferChecked: [12, amount u64 LE, decimals]; accounts source, mint, destination, authority    if (!data || data.length !== 10 || data[0] !== 12) return false;    const amount = new DataView(data.buffer, data.byteOffset, data.byteLength).getBigUint64(1, true);
  1. Line 169 * The x402 payment is one token transfer of the order's amount into the: Uses x402, an HTTP 402 pattern where an agent pays per request with an on-chain payment.
  2. Line 190 // TransferChecked: [12, amount u64 LE, decimals]; accounts source, mint, destination, aut: A checked token transfer, which also verifies the mint and decimals.
edison9733/agentic_commerce · scripts/lib.ts L277–300 @ dfd70aa · MIT · original
 * send with preflight, then poll the signature. No websocket per client, * which is what public endpoints rate-limit hardest. */export function clientForSigner(kp: KeyPairSigner, cluster: Cluster = DEVNET) {  const transport = transportFor(cluster);  const rpc = createSolanaRpcFromTransport(transport);  // A blockhash is good for about a minute; reuse it for a few seconds on shared endpoints.  const reuseMs = cluster.budgetPer10s ? 4000 : 0;   async function sendTransaction(instructions: Instruction[]): Promise<{ context: { signature: string } }> {    let latest = blockhashes.get(transport);    if (!latest || Date.now() - latest.at > reuseMs) {      const { value } = await rpc.getLatestBlockhash({ commitment: 'confirmed' }).send();      latest = { at: Date.now(), value };      blockhashes.set(transport, latest);    }    const message = pipe(      createTransactionMessage({ version: 0 }),      (m) => setTransactionMessageFeePayerSigner(kp, m),      (m) => setTransactionMessageLifetimeUsingBlockhash(latest!.value as never, m),      (m) => appendTransactionMessageInstructions([getSetComputeUnitLimitInstruction({ units: 400_000 }), ...instructions], m),    );    const signed = await signTransactionMessageWithSigners(message);    const signature = getSignatureFromTransaction(signed);
  1. Line 286 async function sendTransaction(instructions: Instruction[]): Promise<{ context: { signatur: Submits the signed transaction to the cluster.
  2. Line 289 const { value } = await rpc.getLatestBlockhash({ commitment: 'confirmed' }).send();: Fetches a recent blockhash. Transactions expire if it gets too old.

How to run, build and test

These commands come from the repo's own manifests (package.json scripts, Anchor.toml, Cargo, go.mod, Makefile). Check the README for prerequisites and environment variables.

DirectoryCommandRuns
apps/clinpm run starttsx src/cli.ts
apps/webnpm run devvite --port 5173 --strictPort
apps/webnpm run buildtsc --noEmit && vite build
apps/agentsnpm run starttsx src/server.ts
apps/apinpm run starttsx src/server.ts
apps/mcpnpm run starttsx src/server.ts
.anchor buildcompile programs and generate IDLs
.anchor testlocal validator + test suite
.anchor run testnpm run test:local
.cargo buildRust workspace build
.cargo testRust tests

What you could build from it

Used in lessons