Skip to Content
Devnet Preview: data may reset, no production guarantees.
Getting StartedFull Walkthrough

Full Walkthrough — Register to Heartbeat

A complete, step-by-step tutorial that walks you through every action in the Provenonce agent lifecycle. Copy-paste every command. Nothing is skipped.

By the end you will have:

  • Registered an agent with a cryptographic identity
  • Purchased a SIGIL (on-chain identity commitment)
  • Sent a heartbeat (paid liveness proof)
  • Spawned a child agent
  • Submitted VDF beats (proof of computational work)
  • Verified your agent publicly

Time: ~15 minutes on devnet.


Prerequisites

RequirementWhy
Node.js 18+ (22 recommended)SDK uses Node.js crypto module
npm (or yarn/pnpm)Package manager
A Solana devnet walletTo pay SIGIL + heartbeat fees (free devnet SOL)
A terminalAll commands are copy-paste

If you don’t have a Solana wallet, we’ll create one in Step 0.


Step 0: Set Up Your Workspace

Create a new project and install dependencies:

mkdir my-provenonce-agent && cd my-provenonce-agent npm init -y npm install @provenonce/sdk @solana/web3.js bs58

Create a file called agent.ts:

touch agent.ts

All code below goes into agent.ts. We’ll build it up step by step.

Get Devnet SOL (Free)

You need ~0.5 SOL on devnet to cover SIGIL + heartbeat fees. Get free devnet SOL:

# Option A: Solana CLI (if installed) solana airdrop 2 --url devnet # Option B: Web faucet # Visit https://faucet.solana.com and paste your wallet address

The SDK provides a SolanaPayer helper that handles fee lookup, transaction construction, and confirmation in a single call:

import { SolanaPayer, createPayer } from '@provenonce/sdk/solana'; import { Keypair } from '@solana/web3.js'; const REGISTRY = 'https://provenonce.io'; // From a base58 secret key: const payer = createPayer(process.env.WALLET_SECRET!, REGISTRY); // Or from a Keypair: const keypair = Keypair.generate(); const payer2 = new SolanaPayer({ keypair }, REGISTRY);

This replaces all manual Solana transaction boilerplate below. See the SolanaPayer reference for details.


Step 1: Register Your Agent

Registration creates a cryptographic identity for your agent. It’s free and takes ~2 seconds.

Add this to agent.ts:

import { register, BeatAgent } from '@provenonce/sdk'; import { Connection, Keypair, SystemProgram, Transaction, PublicKey } from '@solana/web3.js'; import bs58 from 'bs58'; const REGISTRY = 'https://provenonce.io'; const SOLANA_RPC = 'https://api.devnet.solana.com'; // ─── Step 1: Register ─────────────────────────────────────── const creds = await register('my-first-agent', { registryUrl: REGISTRY, // No wallet needed for registration — identity only }); console.log('✓ Agent registered!'); console.log(` Hash: ${creds.hash}`); console.log(` API Key: ${creds.api_key.slice(0, 30)}...`); console.log(` Save this API key — it is shown only once.`);

Run it:

npx tsx agent.ts

You should see:

✓ Agent registered! Hash: 0xabc123... API Key: pvn_eyJoIjoi... Save this API key — it is shown only once.

What happened behind the scenes:

  • Supabase: INSERT INTO agents (hash, name, type=root, depth=0)
  • Supabase: INSERT INTO agent_api_keys (HMAC-signed key)
  • Solana: Birth memo written (~120 bytes, SPL Memo Program)

Your agent now exists on-chain. Anyone can verify it at:

https://provenonce.io/certificate/YOUR_HASH

Step 2: Initialize the Beat Chain

The beat chain is your agent’s logical clock. init() synchronizes with the global network anchor.

Add after Step 1:

// ─── Step 2: Initialize ───────────────────────────────────── const agent = new BeatAgent({ apiKey: creds.api_key, registryUrl: REGISTRY, verbose: true, // shows what's happening }); await agent.init(); console.log('✓ Beat chain initialized'); console.log(` Genesis hash: ${agent.getLocalState().latestHash}`); console.log(` Difficulty: ${agent.getLocalState().difficulty}`);

What happened: Your agent synchronized with the global anchor (a shared VDF-derived timestamp) and established its position in logical time.


Step 3: Purchase a SIGIL (Identity Commitment)

A SIGIL is your agent’s named identity. Root agents (depth=0) must have a SIGIL before they can heartbeat. This is the identity commitment step.

3a: Pay and Purchase SIGIL

Using SolanaPayer (recommended — 3 lines):

// ─── Step 3: SIGIL Purchase ────────────────────────────────── const { signature: sigilTxSig } = await payer.paySigil('narrow_task'); const sigil = await agent.purchaseSigil({ identity_class: 'narrow_task', principal: 'my-org', // your organization name tier: 'ind', // ind = independent payment_tx: sigilTxSig, // the Solana tx signature }); console.log('✓ SIGIL purchased!'); console.log(` Full SIGIL: ${sigil.sigil}`); console.log(` Name: ${sigil.sigil_name}`); console.log(` Class: ${sigil.identity_class}`); console.log(` Tier: ${sigil.tier}`);

Current fees (devnet):

Identity ClassFee
Narrow Task0.05 SOL
Autonomous0.15 SOL
Orchestrator0.35 SOL
SandboxFree

Manual payment (without SolanaPayer)

If you need full control over the Solana transaction:

import { Connection, Keypair, SystemProgram, Transaction, PublicKey } from '@solana/web3.js'; const connection = new Connection(SOLANA_RPC, 'confirmed'); // Get the operations wallet address from the fee summary const feeRes = await fetch(`${REGISTRY}/api/v1/fees/summary`); const fees = await feeRes.json(); const opsWallet = fees.payment_address; const tx = new Transaction().add( SystemProgram.transfer({ fromPubkey: walletKeypair.publicKey, toPubkey: new PublicKey(opsWallet), lamports: 50_000_000, // 0.05 SOL for narrow_task }) ); tx.recentBlockhash = (await connection.getLatestBlockhash()).blockhash; tx.feePayer = walletKeypair.publicKey; tx.sign(walletKeypair); const sigilTxSig = await connection.sendRawTransaction(tx.serialize()); await connection.confirmTransaction(sigilTxSig, 'confirmed');

What happened:

  • Server verified the Solana payment (correct amount, correct recipient)
  • Supabase: Agent’s SIGIL fields updated
  • Solana: Enriched SIGIL memo written (~200 bytes)
  • Lineage event recorded (type: sigil_issued)
  • Fee split calculated: 30% ops / 30% contributors / 15% genesis (decaying) / 15% treasury / 10% referral

Your agent now has a named identity: my-first-agent*my-org*ind


Step 4: Send a Heartbeat (Liveness Proof)

Heartbeats are paid liveness proofs. Each heartbeat says: “I am alive, and I am willing to pay to prove it.”

4a: Send a Heartbeat

Using SolanaPayer (2 lines):

// ─── Step 4: Heartbeat ────────────────────────────────────── const { signature: hbTxSig } = await payer.payHeartbeat(); const hbResult = await agent.heartbeat(hbTxSig); console.log('✓ Heartbeat accepted!'); console.log(` Count: ${hbResult.heartbeat_count_epoch}`); console.log(` Passport: ${hbResult.passport ? 'issued' : 'pending'}`);

What happened:

  • Server verified the Solana payment
  • Supabase: UPDATE agent_beats (last_checkin_beat, heartbeat_count++)
  • Lineage event recorded (type: heartbeat)
  • Passport (signed credential) issued and returned

4b: Autonomous Heartbeats (Optional)

For long-running agents, use the automatic heartbeat loop with createHeartbeatPaymentFn:

import { createHeartbeatPaymentFn } from '@provenonce/sdk/solana'; // One-liner: automatic heartbeats every 5 minutes agent.startHeartbeat(createHeartbeatPaymentFn(payer)); // ... your agent does its work here ... // Stop when done agent.stopHeartbeat();

Step 5: Submit VDF Beats (Proof of Work)

Beats are computational work proofs — sequential SHA-256 hash chains that credit your agent’s “Lifetime Beats” counter. This is separate from heartbeats (which are paid SOL liveness proofs).

// ─── Step 5: VDF Beat Submission ──────────────────────────── const beatsResult = await agent.submitBeats(100); // compute 100 beats if (beatsResult.ok) { console.log('✓ Beats submitted!'); console.log(` Accepted: ${beatsResult.beats_accepted} beats`); console.log(` Lifetime total: ${beatsResult.total_beats} beats`); } else { console.log(` Beat submission failed: ${beatsResult.error}`); }

What happened:

  • SDK computed 100 sequential VDF beats (100 × 1000 SHA-256 iterations = 100,000 hashes)
  • Spot checks collected at key positions for server verification
  • POST /agent/beats/submit validated the chain
  • Supabase: UPDATE agent_beats (total_beats, latest_beat_index, latest_beat_hash)

Step 6: Spawn a Child Agent (Optional)

Parent agents can spawn children. Children inherit provenance from their parent. Spawning costs computational work (VDF beats), not SOL.

// ─── Step 6: Spawn ────────────────────────────────────────── // Step 1: Get spawn authorization (computes VDF work-proof automatically) const spawnResult = await agent.requestSpawnWithBeatsProof({ childName: 'my-worker-1', beatsNeeded: 1050, // 1000 spawn cost + 50 buffer at depth 0 }); if (spawnResult.spawn_authorization) { console.log('✓ Spawn authorized'); // Step 2: Register the child (using parent's API key + spawn token) const childRes = await fetch(`${REGISTRY}/api/v1/register`, { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${creds.api_key}`, }, body: JSON.stringify({ name: 'my-worker-1', parent: creds.hash, spawn_authorization: spawnResult.spawn_authorization, }), }).then(r => r.json()); console.log(` Child hash: ${childRes.hash}`); console.log(` Depth: ${childRes.depth}`); // Step 3: Finalize spawn (links child in parent's lineage) await agent.requestSpawn('my-worker-1', childRes.hash); // Use the child's API key to create a new BeatAgent const child = new BeatAgent({ apiKey: childRes.api_key, registryUrl: REGISTRY, }); await child.init(); console.log(' Child beat chain initialized'); }

What happened:

  • SDK computed 1,050 VDF beats as a work-proof receipt
  • Beats service verified the proof and issued a signed receipt
  • Registry validated receipt and created child agent
  • Supabase: INSERT agents (child, depth=1, parent_hash)
  • Lineage event: spawn on parent, registration on child
  • Solana: Birth memo for child

Step 7: Verify Your Agent (Public — No Auth)

Anyone can verify any agent. No API key. No cost. This is the trust anchor.

# From any terminal — no auth needed curl https://provenonce.io/api/v1/verify/YOUR_AGENT_HASH | jq .

Or in code:

// ─── Step 7: Verify ───────────────────────────────────────── const verifyRes = await fetch(`${REGISTRY}/api/v1/verify/${creds.hash}`); const verification = await verifyRes.json(); console.log('✓ Public verification:'); console.log(` Standing: ${verification.standing}`); console.log(` Lifetime Beats: ${verification.beat_state?.lifetime_beats ?? 0}`); console.log(` Heartbeats: ${verification.beat_state?.total_heartbeats ?? 0}`); console.log(` SIGIL: ${verification.sigil?.sigil || 'none'}`); console.log(` Liveness Score: ${verification.liveness?.score ?? 'N/A'}`); console.log(`\nCertificate: ${REGISTRY}/certificate/${creds.hash}`);

Verification is the whole point. Regulators, counterparties, other agents — anyone can check:

  • Is this agent registered?
  • Does it have a named identity (SIGIL)?
  • Is it actively heartbeating?
  • What is its lineage (parent chain)?
  • How much computational work has it done?

Step 8: Offline Passport Verification (Optional)

Passports are signed credentials you can verify without any network call:

// ─── Step 8: Offline Verification ──────────────────────────── // Get the Registry's authority public key (cache this — it rarely changes) const akRes = await fetch(`${REGISTRY}/api/v1/.well-known/authority-key`); const authorityKey = await akRes.json(); // Get your latest passport const passport = agent.getPassport(); if (passport) { const result = BeatAgent.verifyPassportLocally( passport, authorityKey.public_key_hex ); console.log(`Passport valid: ${result.valid}`); console.log(` Expires: ${new Date(passport.valid_until).toISOString()}`); }

Complete Script

Here’s everything in one file (agent.ts):

import { register, BeatAgent } from '@provenonce/sdk'; import { SolanaPayer } from '@provenonce/sdk/solana'; import { Keypair, Connection } from '@solana/web3.js'; const REGISTRY = 'https://provenonce.io'; const SOLANA_RPC = 'https://api.devnet.solana.com'; async function main() { // ── 1. Register ────────────────────────────────────────── const creds = await register('my-first-agent', { registryUrl: REGISTRY, }); console.log(`\n1. Registered: ${creds.hash}`); // ── 2. Init beat chain ─────────────────────────────────── const agent = new BeatAgent({ apiKey: creds.api_key, registryUrl: REGISTRY, verbose: true, }); await agent.init(); console.log('2. Beat chain initialized'); // ── 3. Fund wallet & set up payer ──────────────────────── const wallet = Keypair.generate(); const connection = new Connection(SOLANA_RPC, 'confirmed'); const airdrop = await connection.requestAirdrop(wallet.publicKey, 500_000_000); await connection.confirmTransaction(airdrop, 'confirmed'); const payer = new SolanaPayer({ keypair: wallet, rpcUrl: SOLANA_RPC }, REGISTRY); // ── 4. Buy SIGIL (3 lines) ─────────────────────────────── const { signature: sigilSig } = await payer.paySigil('narrow_task'); const sigil = await agent.purchaseSigil({ identity_class: 'narrow_task', principal: 'my-org', tier: 'ind', payment_tx: sigilSig, }); console.log(`3. SIGIL: ${sigil.sigil}`); // ── 5. Heartbeat (2 lines) ────────────────────────────── const { signature: hbSig } = await payer.payHeartbeat(); const hb = await agent.heartbeat(hbSig); console.log(`4. Heartbeat: count=${hb.heartbeat_count_epoch}`); // ── 6. Submit VDF beats ────────────────────────────────── const beats = await agent.submitBeats(100); console.log(`5. Beats: ${beats.total_beats} lifetime`); // ── 7. Spawn child ─────────────────────────────────────── const spawn = await agent.requestSpawnWithBeatsProof({ childName: 'my-worker-1', beatsNeeded: 1050, }); console.log(`6. Child: ${spawn.child_hash}`); // ── 8. Verify ──────────────────────────────────────────── const v = await fetch(`${REGISTRY}/api/v1/verify/${creds.hash}`).then(r => r.json()); console.log(`7. Verified: standing=${v.standing}, beats=${v.beat_state?.lifetime_beats}`); console.log(` Certificate: ${REGISTRY}/certificate/${creds.hash}`); } main().catch(console.error);

Run it:

npx tsx agent.ts

Heartbeat Fee Schedule

Heartbeat fees decrease with volume:

Heartbeat #Fee per Heartbeat
1–1000.0005 SOL
101–1,0000.0003 SOL
1,001+0.0002 SOL

Sandbox tier agents heartbeat for free (capped at 100).


SIGIL Fee Schedule

Identity ClassFeeUse Case
Narrow Task0.05 SOLSingle-purpose bots, monitors
Autonomous0.15 SOLTrading agents, autonomous systems
Orchestrator0.35 SOLMulti-agent coordinators
SandboxFreeTesting, QA, integration

Troubleshooting

ProblemCauseFix
SIGIL_REQUIRED (403)Root agent tried to heartbeat without SIGILPurchase a SIGIL first (Step 3)
HEARTBEAT_TOO_SOON (429)Less than 10 beats since last heartbeatWait ~10 minutes between heartbeats
AGENT_FROZEN (403)Agent missed too many heartbeat windowsCall agent.resync() to reactivate
RATE_LIMITED (429)Too many requestsWait for retry_after_ms
INVALID_PAYMENT (400)Wrong amount or wrong recipientCheck fee schedule and ops wallet address
INSUFFICIENT_BEATS (400)Not enough beats for spawnIncrease beatsNeeded (spawn cost is 1000 at depth 0)

What’s Next?

Last updated on