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 directory | Source files |
|---|---|
packages | 64 |
apps | 54 |
scripts | 13 |
programs | 5 |
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.
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>>,- Line 985
init,: Creates and pays for a new account. Space and payer must be specified. - 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. - Line 989
bump: Uses the bump seed, the extra byte that pushes the derived address off the ed25519 curve. - 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. - Line 995
init_if_needed,: Creates the account only if it doesn't exist. Watch for re-initialisation bugs. - Line 1006
pub merchant: Signer<'info>,: Requires this account to have signed the transaction.
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 (;;) {- Line 48
const { value: latest } = await readRpc.getLatestBlockhash({ commitment: 'confirmed' }).se: Fetches a recent blockhash. Transactions expire if it gets too old. - Line 58
await readRpc.sendTransaction(wire, { encoding: 'base64', preflightCommitment: 'confirmed': Submits the signed transaction to the cluster.
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);- 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. - Line 190
// TransferChecked: [12, amount u64 LE, decimals]; accounts source, mint, destination, aut: A checked token transfer, which also verifies the mint and decimals.
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);- Line 286
async function sendTransaction(instructions: Instruction[]): Promise<{ context: { signatur: Submits the signed transaction to the cluster. - 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.
| Directory | Command | Runs |
|---|---|---|
apps/cli | npm run start | tsx src/cli.ts |
apps/web | npm run dev | vite --port 5173 --strictPort |
apps/web | npm run build | tsc --noEmit && vite build |
apps/agents | npm run start | tsx src/server.ts |
apps/api | npm run start | tsx src/server.ts |
apps/mcp | npm run start | tsx src/server.ts |
. | anchor build | compile programs and generate IDLs |
. | anchor test | local validator + test suite |
. | anchor run test | npm run test:local |
. | cargo build | Rust workspace build |
. | cargo test | Rust tests |
What you could build from it
- Fork the SuperStack copy and extend it within the AI & Agents gaps listed on the idea page.
- Learn the pattern first: AI agents on Solana
- Learn the pattern first: Building and sending transactions from a client