Skip to content

Verify Vera yourself

Recover the signer of any recorded plan and check it against the live agentSigner().

You can prove Vera authored a plan’s risk assessment without trusting Monvera. Read agentSigner() live from the VeraRecord contract, fetch the signed RiskInference payload, ECDSA-recover its signer, and assert the two match. Then confirm the same planId appears in an on-chain RecommendationCommitted log. Every value below is on-chain or on a public endpoint.

This proves authorship, not performance. It says Vera’s key signed that exact risk call before the trade, nothing about whether the plan makes money. What she signs versus what the user signs is covered in accountable AI.

Identity Chain Registry Agent id Role
Canonical ERC-8004 Base (8453) 0x8004a169fb4a3325136eb29fa0ceb6d2e539a432 58228 Portable identity, registered via Virtuals ACP
Monvera IdentityRegistry Robinhood Chain (4663) 0x751ae640cfa816404b017fbb8234dd21abafbbdc 1 The agent id recorded with every plan on 4663

The signature you recover on this page is checked against agentSigner() on VeraRecord, chain 4663. The Base entry is declared in registrations[0] of her agent card and is the one the card labels canonical; do not label the 4663 registry canonical unless the published card changes.

GET /.well-known/agent-card.json is Vera’s discovery document, cached s-maxage=3600. It is a custom shape, not the generic A2A schema.

Terminal window
curl https://monvera.best/.well-known/agent-card.json

Top-level fields, in order:

Field Type or value What it holds
name "Vera" The agent name
description string Human-readable summary
image "https://monvera.best/icon-512.png" Avatar
active true Whether the agent is live
persona { role, blurb } Role and blurb
services { web: { url } } The web app URL
endpoints { app, agent, strategies } App, agent-card, and strategies URLs
supportedTrust ["reputation"] The trust model Vera declares
skills 5 slugs portfolio-allocation, plain-language-allocation, dca-autopilot, trading-strategy, risk-management
domains ["finance","investing","real-world-assets"] Subject domains
x402 false No x402 payment gating on this deployment
registrations array On-chain identity registrations
metadata object Chain, contracts, and the current signer

metadata carries what you need to verify a record on 4663:

{
"version": "2.3",
"chain": "robinhood-chain",
"chainId": 4663,
"app": "https://monvera.best/app",
"explorer": "https://robinhoodchain.blockscout.com",
"identityRegistry": "0x751ae640cfa816404b017fbb8234dd21abafbbdc",
"agentId": 1,
"agentSigner": "0xe532105523d4eD559c3a53E3E82D616bE1a085c5"
}

Vera signs an EIP-712 RiskInference typed message. The type and domain are fixed by the deployed contract:

keccak256("RiskInference(bytes32 planId,uint16 assessedRisk,uint16 maxRisk,uint256 expiry)")

Fields in order: bytes32 planId, uint16 assessedRisk, uint16 maxRisk, uint256 expiry. The domain:

{
"name": "VeraRecord",
"version": "1",
"chainId": 4663,
"verifyingContract": "0x7ff1a5ee19330c165146488a7ad8af6cb41da1df"
}

VeraRecord.record(...) recovers the signer of this payload on-chain and reverts if anything is wrong, so a plan cannot be recorded with a forged or stale assessment, and the same planId cannot be recorded twice. The record lands in the same transaction as the buys, which is what makes the track record uneditable after the fact. The revert order is in conventions.

  1. Read agentSigner() live.

    import {
    createPublicClient,
    http,
    parseAbiItem,
    recoverTypedDataAddress,
    } from 'viem';
    const VERA_RECORD = '0x7ff1a5ee19330c165146488a7ad8af6cb41da1df';
    const client = createPublicClient({
    transport: http('https://rpc.mainnet.chain.robinhood.com'),
    });
    const agentSigner = await client.readContract({
    address: VERA_RECORD,
    abi: [parseAbiItem('function agentSigner() view returns (address)')],
    functionName: 'agentSigner',
    });
  2. Fetch the signed payload and signature. GET /api/vera-record?user=<address> returns each recorded plan with its RiskInference fields and Vera’s signature. It is a public read.

    Terminal window
    ACCOUNT=0xYourAccountAddress
    curl "https://monvera.best/api/vera-record?user=$ACCOUNT"
  3. Confirm the plan is on-chain. Pull RecommendationCommitted logs from the deploy block. Public RPC caps eth_getLogs at 10,000 blocks, so scan in chunks of 9,999.

    const event = parseAbiItem(
    'event RecommendationCommitted(bytes32 indexed planId, address indexed user, bytes32 recHash, uint16 riskScore, uint256 agentId)',
    );
    const DEPLOY_BLOCK = 2429508n;
    const STEP = 9999n;
    const latest = await client.getBlockNumber();
    let logs = [];
    for (let from = DEPLOY_BLOCK; from <= latest; from += STEP + 1n) {
    const to = from + STEP > latest ? latest : from + STEP;
    logs = logs.concat(
    await client.getLogs({ address: VERA_RECORD, event, fromBlock: from, toBlock: to }),
    );
    }
    const onChain = logs.find((l) => l.args.planId === planId);
    // onChain.args.riskScore equals assessedRisk; onChain.args.agentId is 1n
  4. Recover and assert.

    const recovered = await recoverTypedDataAddress({
    domain: {
    name: 'VeraRecord',
    version: '1',
    chainId: 4663,
    verifyingContract: VERA_RECORD,
    },
    types: {
    RiskInference: [
    { name: 'planId', type: 'bytes32' },
    { name: 'assessedRisk', type: 'uint16' },
    { name: 'maxRisk', type: 'uint16' },
    { name: 'expiry', type: 'uint256' },
    ],
    },
    primaryType: 'RiskInference',
    message: { planId, assessedRisk, maxRisk, expiry: BigInt(expiry) },
    signature,
    });
    const authored = recovered.toLowerCase() === agentSigner.toLowerCase();
    // true means Vera's agentSigner authored this exact risk assessment

When authored is true and the same planId appears in a RecommendationCommitted log, you have confirmed two things without trusting anyone: Vera’s signer authored that risk assessment, and it was recorded in the transaction that ran the trades. Browse the same records for any address on Blockscout at 0x7ff1a5ee19330c165146488a7ad8af6cb41da1df.

reputationScore(agentId) on the IdentityRegistry returns 0 until feedback is written — the score is owner-updated through feedback, not computed live. Do not read a nonzero score as proof of anything yet.

const IDENTITY_REGISTRY = '0x751ae640cfa816404b017fbb8234dd21abafbbdc';
const score = await client.readContract({
address: IDENTITY_REGISTRY,
abi: [parseAbiItem('function reputationScore(uint256 agentId) view returns (uint256)')],
functionName: 'reputationScore',
args: [1n],
});
// 0n until feedback is written for agent #1

/api/vera-record returns that same live score alongside the recovered signer, the agent id, and Vera’s recent recommendations, so you do not have to reassemble history from log chunks yourself. Its full response shape is at /api/vera-record.

© 2026 Aibora · Documentation interface. Original Monvera materials retain their upstream attribution andMIT license.