merit-attest
v1.0.14
Published
Create, sign, and anchor WorkAttestations on Base. Drop-in helpers for CodeGrid, EVO, CTRL and similar tools.
Maintainers
Readme
merit-attest
Create, sign, and anchor WorkAttestations on Base.
A WorkAttestation is a JSON record of work done by an agent or hybrid team. The SDK produces a stable canonical hash of the record. Submit only the hash, producer address, and one metric value to the on-chain contract. The full payload and proofs stay off-chain.
Version 1.0.14 is now live on npm. The core model has been validated with real mainnet transactions, including direct use by Bankr-powered agents. We've got a solid build process, tests, and docs covering the changes.
Install
npm install merit-attestBasic flow
import {
createAttestation,
hashAttestation,
signAttestation,
submitAttestation,
getBaseWalletClient,
MERIT_ATTEST_ADDRESS,
MERIT_ATTEST_ABI
} from 'merit-attest';
// 1. Build from your agent output
const att = createAttestation({
producer: {
type: 'agent',
id: 'agent-42',
pubkey: '0xYourEvmAddress',
framework: 'my-system'
},
context: {
prompt_hash: 'sha256 of the task prompt',
repo: 'owner/repo',
files: ['src/foo.ts']
},
output: {
type: 'code_patch',
payload: 'the diff or result here',
metrics_claimed: { coverage: 87 }
}
});
// 2. Hash (sorted keys + sha256)
const hash = hashAttestation(att);
// 3. Sign (attach signature to the attestation object)
const signed = await signAttestation(att, {
address: '0xYourEvmAddress',
sign: async (h) => { /* return signature bytes for the hash */ }
});
// 4. Anchor on Base (real tx)
const walletClient = getBaseWalletClient(); // injected wallet on Base mainnet (after user switches chain in wallet)
const tx = await submitAttestation(signed, walletClient);Only the hash + producer + metric are written on-chain. Anyone can later call isAnchored(hash) to confirm it was anchored.
How the SDK proves an agent did what it said it did
Here's the core flow, kept straightforward:
An agent produces some output (a code change, a strategy, an analysis, etc.). The SDK wraps that into a structured record called a WorkAttestation. This record captures the context (the prompt or task details), the actual output or payload, and any metrics or proofs the agent claims.
The SDK then creates a unique, canonical hash of that entire record. The agent signs this hash with its private key. The signature is attached to the record, proving the agent is the one making the claim about that exact output.
Only the hash (plus the agent's address and one key metric) gets sent to the smart contract on Base. The contract records it as anchored, with a blockchain timestamp. The full details of the work stay off-chain.
To verify later:
- Take the full record.
- Recompute the hash from it.
- Check that the attached signature matches the agent's public key.
- Confirm on-chain (via the contract or Basescan) that this exact hash is recorded as anchored by that agent.
If everything matches, it proves the agent committed to precisely that work at that time. Any alteration to the record would change the hash, breaking the proof. The on-chain anchor adds immutability and a public timestamp without exposing the full work.
This is the "gossip first, anchor later" model: full data can be shared privately or via other channels, but the cryptographic root is permanently verifiable on Base.
Helpers for existing tools
import {
fromCodeGridSession,
fromEvoExperiment,
fromCtrlStrategy,
fromAi2HumanReview,
createMcpSubmitMeritTool
} from 'merit-attest';fromCodeGridSession(session)- turns a CodeGrid export into attestation inputfromEvoExperiment(exp)- turns an EVO run result into attestation inputfromCtrlStrategy(strat)- turns a CTRL / strategy output into attestation inputfromAi2HumanReview(review)- turns a human review into attestation inputcreateMcpSubmitMeritTool(submitFn)- returns an MCP tool definition namedsubmit_meritfor use in CTRL, Hermes, etc.
Example:
const partial = fromCodeGridSession(mySession);
const att = createAttestation({
producer: { type: 'agent', id: '...', pubkey: '0x...' },
...partial
});Contract
Live on Base mainnet:
0x9bE80d727cea67504a58eF5F7ADa0B92339BE163
https://basescan.org/address/0x9bE80d727cea67504a58eF5F7ADa0B92339BE163
The contract exposes:
submitAttestation(bytes32 hash, address producer, int256 metricValue, string metric)isAnchored(bytes32) view returns (bool)AttestationAnchoredevent
The helpers default to Base mainnet (base chain from viem, live contract at 0x9bE80d727cea67504a58eF5F7ADa0B92339BE163 ). Pass your own clients if you need a custom RPC.
Verify an anchor
import { createPublicClient, http, parseAbi } from 'viem';
import { base } from 'viem/chains';
const client = createPublicClient({
chain: base,
transport: http()
});
const anchored = await client.readContract({
address: MERIT_ATTEST_ADDRESS,
abi: MERIT_ATTEST_ABI,
functionName: 'isAnchored',
args: [hashAsBytes32]
});Types
See WorkAttestation, Producer, and the exported functions in the package for the exact shape.
Full design and schema notes are in the repo docs. The on-chain record is intentionally minimal: only the cryptographic root is anchored.
Using with private keys (CLI / agent runtimes / Bankr)
Many agent systems (including Bankr launch identities) manage a plain EVM private key. You can use it directly:
import { createWalletClient, http } from 'viem';
import { base } from 'viem/chains';
import { privateKeyToAccount } from 'viem/accounts';
import { createAttestation, signAttestation, submitAttestation, hashAttestation } from 'merit-attest';
const account = privateKeyToAccount('0x...your key...');
const walletClient = createWalletClient({
account,
chain: base,
transport: http('https://mainnet.base.org'),
});
const att = createAttestation({ /* ... */ });
const signed = await signAttestation(att, {
address: account.address,
sign: async (hash) => hexToBytes(await account.sign({ hash })),
});
const tx = await submitAttestation(signed, walletClient);The same pattern works for Bankr's launch wallet JSON.
Bankr + Agentic Usage
Bankr maintains a persistent EVM launch identity (see ~/.grok/skills/bankr/state/launch_wallet.json). The Merit SDK works natively with it for agents to attest their work (launches, optimizations, reviews, etc.):
import { createWalletClient, http, hexToBytes } from 'viem';
import { base } from 'viem/chains';
import { privateKeyToAccount } from 'viem/accounts';
import { createAttestation, signAttestation, submitAttestation } from 'merit-attest';
import fs from 'node:fs';
const bankrWallet = JSON.parse(
fs.readFileSync(process.env.HOME + '/.grok/skills/bankr/state/launch_wallet.json', 'utf8')
);
const account = privateKeyToAccount(bankrWallet.private_key);
const walletClient = createWalletClient({
account,
chain: base,
transport: http(process.env.BASE_RPC || 'https://mainnet.base.org'),
});
const attestation = createAttestation({
producer: {
type: 'agent',
id: 'grok-bankr-launcher',
pubkey: account.address,
framework: 'bankr',
},
context: {
prompt_hash: 'bankr-launch-' + Date.now(),
extra: { action: 'token-launch' },
},
output: {
type: 'strategy_config',
payload: JSON.stringify({ token: 'Example', narrative: '...' }),
metrics_claimed: { launches: 1 },
},
});
const signed = await signAttestation(attestation, {
address: account.address,
sign: async (hash) => hexToBytes(await account.sign({ hash })),
});
const tx = await submitAttestation(signed, walletClient);
console.log('Anchored tx:', tx);This enables "gossip first, anchor later": agents produce full records that can be shared privately, while the cryptographic root is permanently verifiable on Base.
Status
Version 1.0.14 is the current release. It is ready for use by agents and platforms.
The core model has been validated with real Base mainnet transactions, including direct integration with Bankr.
Check the CHANGELOG for the 1.0 updates like the build, tests, and Bankr docs.
Full design notes are in the project repository.
(Note: the hexToBytes import from viem is needed in the examples above.)
