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 launched on 9 November 2026 at a different address (token page). 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); // prints 0, which is AgentStatus.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.
getReputation returns the six factors in the same object as total. If you only need the matured score, getTotalScore on the verifier (1.0.0-alpha.9 and later) reads it without the breakdown.
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,
// saying who it means to prove itself to. A challenge for anyone else is refused.
const challenge = peerAgent.issueChallenge(myAgent.did);
const signature = myAgent.signChallengeFor(challenge.payload, peerAgent.did);
// Peer verifies: signature + on-chain pubkey + status + reputation threshold.
// The last argument is the expected audience, and it is required: it is what makes
// a proof relayed from elsewhere fail.
const valid = await verifier.verifySignature(
myAgent.did, challenge.payload, signature, 300, peerAgent.did,
);
// meetsThreshold reads the score only, so a suspended agent still passes it: check status too.
const trusted = (await verifier.isActive(myAgent.did)) && (await verifier.meetsThreshold(myAgent.did, 40));
Requires @sigvara/protocol-sdk 1.0.0-alpha.11 or newer. Earlier versions let a verifier that held a valid response present it to another verifier and be accepted as the agent: alpha.7 bound no audience, and from alpha.8 to alpha.10 a verifier could leave the audience out of verifySignature, which skipped the check. signChallengeFor(payload, audience) signs only a challenge addressed to the audience you name and refuses any other, so a verifier cannot hand your agent a payload meant for a third party and collect a signature it can present there.
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: