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
| Requirement | Why |
|---|---|
| Node.js 18+ (22 recommended) | SDK uses Node.js crypto module |
| npm (or yarn/pnpm) | Package manager |
| A Solana devnet wallet | To pay SIGIL + heartbeat fees (free devnet SOL) |
| A terminal | All 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 bs58Create a file called agent.ts:
touch agent.tsAll 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 addressSet Up the Payment Helper (Recommended)
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.tsYou 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_HASHStep 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 Class | Fee |
|---|---|
| Narrow Task | 0.05 SOL |
| Autonomous | 0.15 SOL |
| Orchestrator | 0.35 SOL |
| Sandbox | Free |
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/submitvalidated 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:
spawnon parent,registrationon 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.tsHeartbeat Fee Schedule
Heartbeat fees decrease with volume:
| Heartbeat # | Fee per Heartbeat |
|---|---|
| 1–100 | 0.0005 SOL |
| 101–1,000 | 0.0003 SOL |
| 1,001+ | 0.0002 SOL |
Sandbox tier agents heartbeat for free (capped at 100).
SIGIL Fee Schedule
| Identity Class | Fee | Use Case |
|---|---|---|
| Narrow Task | 0.05 SOL | Single-purpose bots, monitors |
| Autonomous | 0.15 SOL | Trading agents, autonomous systems |
| Orchestrator | 0.35 SOL | Multi-agent coordinators |
| Sandbox | Free | Testing, QA, integration |
Troubleshooting
| Problem | Cause | Fix |
|---|---|---|
SIGIL_REQUIRED (403) | Root agent tried to heartbeat without SIGIL | Purchase a SIGIL first (Step 3) |
HEARTBEAT_TOO_SOON (429) | Less than 10 beats since last heartbeat | Wait ~10 minutes between heartbeats |
AGENT_FROZEN (403) | Agent missed too many heartbeat windows | Call agent.resync() to reactivate |
RATE_LIMITED (429) | Too many requests | Wait for retry_after_ms |
INVALID_PAYMENT (400) | Wrong amount or wrong recipient | Check fee schedule and ops wallet address |
INSUFFICIENT_BEATS (400) | Not enough beats for spawn | Increase beatsNeeded (spawn cost is 1000 at depth 0) |
What’s Next?
- Architecture — How Beats, Registry, and SDK fit together
- Beat Chain — How VDF time works
- SIGIL — Identity namespace details
- Heartbeat API — Volume tiers, sponsorship
- Spawn API — Child agent creation
- Verification — Public trust queries
- SDK Reference — Full BeatAgent API
- MCP Integration — 30-second onboarding for Claude/Cursor/Windsurf