Quickstart
From zero to a registered, reputation-tracked AI agent on Arc testnet in about 10 minutes. You need Node.js 18+, Arc testnet USDC, and an RPC URL.
Install the SDK
npm install @sigvara/protocol-sdk ethers
Get testnet stake tokens
Call the public faucet on the testnet SVRToken. This is a valueless test ERC-20 that exists so the staking path can be exercised — it is not the mainnet SVR, which has not launched. It mints up to 10,000 per call, one call per wallet per 24 hours.
import { ethers } from 'ethers';
const provider = new ethers.JsonRpcProvider(process.env.RPC_URL);
const signer = new ethers.Wallet(process.env.OPERATOR_PRIVATE_KEY, provider);
const svr = new ethers.Contract(
'0x41De2D6D55318e197a00E8f5B496eA2790e23E6c',
['function faucet(uint256 amount) external'],
signer
);
await svr.faucet(ethers.parseEther('1000')); // Mints 1,000 SVR to your wallet
Generate an Ed25519 keypair
Your agent's cryptographic identity. The public key is stored on-chain. Store the private key securely — it cannot be recovered.
import { SigvaraAgent } from '@sigvara/protocol-sdk';
const { agent, privateKey } = SigvaraAgent.generate({
agentAddress: signer.address,
chainId: 5042002,
});
console.log(agent.did);
// → did:sigvara:5042002:0xYourAddress
// Store privateKey in your secrets manager — never commit it.
Register on-chain, then post the bond
Registration comes first, and the bond is what activates the agent — not the other way round. registerAgent leaves it PendingBond: resolvable, but not Active, not scoreable and not slashable. The first depositStake that carries it over minimumStake makes it Active. depositStake checks your allowance, sends the ERC-20 approval only if it is short, then deposits. The contract computes your didHash from the agent address and chain ID.
The agent address must also sign for its own registration, which is what stops anyone claiming an address they do not control and choosing the key verifiers would check against it. Pass a signer for that address, or { signature } if it was signed elsewhere — an HSM, a Safe, or any ERC-1271 contract agent.
import { registerAgent, depositStake } from '@sigvara/protocol-sdk';
const IDENTITY_ADDRESS = '0x7e3aFC532eE5d922ab3cc3FFb510c7C8151477Dd';
const STAKING_ADDRESS = '0xA69d62B2a6774D21A2c15d5d83b27277eD31d35B';
const minStake = ethers.parseEther('1000'); // minimumStake on testnet
// agentSigner proves control of the agent address. Often the same wallet as the
// operator; when it is not, pass { signature } and sign with its key separately.
const agentSigner = signer;
const { didHash, txHash } = await registerAgent(
signer, agent.agentAddress, agent.publicKeyBytes32, IDENTITY_ADDRESS, { agentSigner }
);
console.log('Registered:', `https://explorer.testnet.arc.io/tx/${txHash}`);
const bond = await depositStake(signer, didHash, minStake, STAKING_ADDRESS);
console.log('Bonded:', bond.txHash, bond.approveTxHash ? '(approval sent first)' : '');
Verify registration and check score
import { SigvaraVerifier } from '@sigvara/protocol-sdk';
const REPUTATION_ADDRESS = '0x6603C96275e85F724Cdf74666b399365e4cA29ed';
const verifier = new SigvaraVerifier({
rpcUrl: process.env.RPC_URL,
addresses: { identity: IDENTITY_ADDRESS, reputation: REPUTATION_ADDRESS, staking: STAKING_ADDRESS },
chainId: 5042002,
});
const identity = await verifier.getIdentity(agent.did);
console.log(identity.status); // → Active
const { total } = await verifier.getReputation(agent.did);
console.log(total); // → 0 until the first epoch finalizes
Two reasons that reads 0 at first. The oracle has to run an epoch and the proposed score has to clear its challenge window before anything is finalized. And total is the matured score, which climbs toward the earned one over days rather than landing at once — getEarnedScore on the contract shows the raw figure. A freshly bonded agent with no work earns 5, the community baseline, and spends it gradually.
This snippet previously called getTotalScore on the verifier, which the published SDK does not have: it went out as a plain TypeError for anyone following along. The method exists in the source now, but getReputation is used here because it works against the version actually on npm. The factor breakdown comes back in the same object.
Sign a challenge (A2A authentication)
When a peer agent asks your agent to prove who it is, sign the challenge with your Ed25519 key. The challenge names both your agent and who is asking, so a signature proves your agent was talking to that verifier specifically and cannot be relayed onward by whoever receives it.
const myAgent = new SigvaraAgent({
privateKey: process.env.AGENT_ED25519_SEED,
agentAddress: signer.address,
chainId: 5042002,
});
// Peer issues a challenge naming itself as the audience, then your agent signs it
const challenge = peerAgent.issueChallenge(myAgent.did);
const signature = myAgent.signChallenge(challenge.payload);
// Peer verifies: signature + on-chain pubkey + reputation threshold.
// The last argument is the expected audience. Leaving it off still compiles,
// and still accepts a proof that could have been relayed from elsewhere.
const valid = await verifier.verifySignature(
myAgent.did, challenge.payload, signature, 300, peerAgent.did,
);
const trusted = await verifier.meetsThreshold(myAgent.did, 60);
Requires @sigvara/protocol-sdk 1.0.0-alpha.8 or newer. Earlier versions bound no audience, so a verifier holding a valid response could present it to another verifier and be accepted as the agent.
Audit every action with your DID
Pass agent_did in each CounterAudit ingest call. Your on-chain identity and live reputation score are sealed into every packet.
await fetch('https://api.counteraudit.io/v1/audit/ingest', {
method: 'POST',
headers: {
'Authorization': `Bearer ${CA_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
connector_id: 'my-agent',
agent_did: myAgent.did,
raw_event: { action: 'tool_call', tool: 'web_search', query: '...' },
}),
});
Next steps
Now that your agent is registered, explore the rest of the documentation: