Capacity Attest
Permite que agentes de IA deixem um registro assinado e verificável de que uma entrega paga (horas de GPU, armazenamento, créditos de API) realmente aconteceu, publicado on-chain via EAS e ERC-8004 na Base.
Documentação
capacity-attest
Nederlandse versie / Dutch version: README.nl.md
Servidor MCP para atestados de entrega na negociação de capacidade x402 entre agentes de IA.
Status: MVP, publicado no npm (
npm install capacity-attest) e no registro oficial do MCP (io.github.holistis/capacity-attest).
Verificado de forma independente, não apenas declarado
Toda declaração sobre este projeto abaixo é clicável e verificável de forma independente, não algo que você precisa aceitar pela nossa palavra.
| O quê | Por quem | Status |
|---|---|---|
Usa capacity-attest@0.2.0 como dependência real, verifica digest/claimId/assinatura do claim por meio do nosso próprio código | YE-YI7/asm-spec#18 | Mesclado |
| Adaptador de trilho BSV no mesmo formato de claim endereçado por conteúdo | YE-YI7/asm-spec#19 (autor EmbryoSpace) | Mesclado |
| Nossas declarações de limite ("descoberta ≠ completude") verificadas de forma independente on-chain por terceiros, nada aceito por fé | x402-foundation/x402#3379 | Público, em andamento |
| Claims de entrega ao vivo como atestados on-chain na mainnet da Base, decodificáveis por qualquer pessoa | delivered=yes · delivered=no | Ao vivo |
Uma chamada real de giveFeedback() no Registro de Reputação ERC-8004, mainnet da Base | tx 0x2217...efc0 | Ao vivo |
| Proposto como adição do lado do comprador à especificação de agente de outra pessoa | omworldprotocol/om-world#18 | Em revisão, ainda não mesclado |
| Um verificador Python reconstruído de forma independente, apenas com stdlib (implementação própria de secp256k1/EIP-191/JSON canônico, sem código compartilhado), 7/7 de correspondência com nossos próprios vetores de teste, incluindo ambos os controles negativos (assinatura forjada, claim adulterado), agora um teste de regressão permanente em um projeto separado e publicado de forma independente | x402-foundation/x402#2887 (comentário), autor goun7, integrado ao Tamga (pip install tamga-protocol) | Integrado permanentemente ao projeto de outra pessoa |
Mantido honestamente separado da tabela acima, porque isto não é revisão de terceiros: antes da 0.6.0 (a primeira versão que realmente grava na blockchain), realizamos nossa própria revisão de segurança adversarial. Dez descobertas, todas corrigidas com um teste de regressão dedicado, não uma auditoria independente por uma parte externa. Relatório completo e verificável: docs/SECURITY-REVIEW-2026-09-11.md.
Por que isto existe
Quando um agente de IA paga via protocolo x402 por capacidade (horas de GPU, armazenamento, créditos de API/inferência, largura de banda) de outro agente ou serviço, não há prova após o pagamento de que o que foi prometido foi realmente entregue. O agente comprador sabe em primeira mão (viu a saída, ou não), mas esse conhecimento se perde no momento em que a sessão termina. O próximo agente que quiser fazer negócios com o mesmo vendedor começa às cegas novamente.
capacity-attest fecha essa lacuna específica: após a liquidação, o agente pagador deixa para trás um claim factual e criptograficamente assinado (delivered: yes/no/partial + um hash da evidência). Outros agentes podem consultar esse histórico antes de fazer negócios com o mesmo vendedor.
Sem julgamento. Sem pontuação de reputação. Sem "veredito" — apenas um recibo assinado mais claim, da mesma forma que você receberia um comprovante de entrega para um envio físico.
O que isto deliberadamente NÃO é
Isto é deliberada e permanentemente não:
- Não é uma pontuação de reputação ou avaliação.
get_delivery_historyretorna a lista bruta e cronológica de claims, sem média, sem porcentagem, sem "pontuação de confiança". Resumir isso em um único número é implicitamente um julgamento, e isso foi explicitamente rejeitado durante a fase de design deste projeto. - Não é um produto financeiro. Sem juros, sem desconto temporal em pagamentos, sem rendimento sobre saldo contábil (não há saldo, isto não é um escrow), sem empréstimos, sem garantias, sem financiamento/factoring de faturas.
assetTypeé um enum fechado de tipos de capacidade (gpu-hours,storage,api-credits,bandwidth) e deliberadamente não contém nada que se assemelhe a um instrumento financeiro. - Não é seu próprio token ou moeda. Os pagamentos ocorrem via x402/USDC como de costume; este projeto apenas registra o recibo de uma liquidação que já aconteceu em outro lugar.
- Não é extensão de crédito. Um claim só é criado após um pagamento concluído. Este projeto não financia nada; documenta uma transação ijara (aluguel/serviço) já concluída.
- Não é sua própria camada de identidade, autoridade ou resolução de disputas.
externalRefs(veja abaixo) é puramente uma citação ao sistema de outra pessoa (ERC-8004, AP2, Legal Context Protocol, ...). Este projeto nunca resolve, verifica ou julga essa referência por si mesmo. Veja DECISIONS.md D-007 a D-013 para saber por que isto deliberadamente não se tornou seu próprio protocolo.
Esta é uma escolha de design deliberada e formalmente revisada, não um limite de escopo incidental. Veja a seção de proteções no resumo do projeto se você estiver considerando adicionar algo aqui: quando estiver em dúvida se um campo/função toca essa linha, deixe-o de fora.
Recursos deliberadamente adiados (ancoragem, status intermediário, vetores formais de conformidade), incluindo a condição exata sob a qual os construiríamos mesmo assim: veja DECISIONS.md.
Leitura adicional: nove maneiras de falsificar um claim de entrega, e por que nenhuma delas funcionou totalmente, o texto público de D-014/D-018 acima.
Como funciona
1. record_delivery
O agente pagador (o comprador) chama isto após uma liquidação x402, uma vez que se sabe se o que foi prometido chegou. O claim contém:
| Campo | Significado |
|---|---|
sellerAddress | Endereço 0x da parte que foi paga |
buyerAddress | Endereço 0x do agente pagador, deve corresponder ao endereço recuperado de signature |
assetType | gpu-hours | storage | api-credits | bandwidth |
promisedSpec | O que foi prometido: texto livre ou um objeto estruturado |
delivered | yes | no | partial |
evidenceHash | sha256 hex da evidência de suporte (logs, payload de resposta, ...); a evidência em si não é armazenada |
settlementRef | Referência de pagamento x402 ou hash de transação on-chain do pagamento subjacente |
timestamp | Carimbo de data/hora ISO-8601 |
claimId | Hash sha256 endereçado por conteúdo de todos os campos acima, veja computeClaimId() em src/schema.ts |
signature | Assinatura EIP-191 personal-sign pelo comprador sobre claimId |
externalRefs | (opcional, desde 0.3.0) referências não verificadas a outra infraestrutura da economia de agentes: sellerAgentRef/buyerAgentRef (por exemplo, um ID de agente ERC-8004 ou DID), mandateRef+mandateIssuerDid (um mandato AP2/AAE emitido externamente), intentRef (um IntentMandate AP2 externo), disputeContext (protocol+termsHash+resolutionRef opcional, por exemplo, uma referência Legal Context Protocol). Veja DECISIONS.md D-007 a D-013 |
priorClaimId | (opcional, desde 0.4.0) o claimId do seu claim anterior sobre o mesmo sellerAddress, para que seus claims sobre esse vendedor formem uma cadeia. Omitido no seu primeiro claim sobre um vendedor. Parte do conteúdo assinado, então um host não pode removê-lo. Permite que um leitor detecte um host escondendo um claim intermediário. Veja DECISIONS.md D-006 |
O servidor primeiro valida o esquema, depois se claimId realmente é o hash do conteúdo, depois se signature realmente recupera para buyerAddress. Somente então o claim é anexado ao registro somente-anexação (data/claims.jsonl). Uma assinatura inválida ou um claim já armazenado (mesmo claimId) é rejeitado.
2. get_delivery_history
Dado um sellerAddress, isto retorna todos os claims conhecidos contra esse vendedor nesta instalação, cronologicamente (mais antigos primeiro). Puramente factual, sem número resumido. Um agente comprador chama isto antes de pagar, para ver o histórico bruto de entregas de um vendedor em potencial e julgá-lo por si mesmo.
A resposta inclui sellerAddress, count e claims, além de scope (sempre "local-ledger") e note: um texto fixo e factual explicando que este resultado reflete apenas o registro local desta instalação. Um histórico vazio ou curto não significa que o vendedor tem um registro limpo — também pode significar que nenhum claim foi registrado aqui ainda.
Desde 0.4.0, a resposta também inclui completeness: uma análise das cadeias por comprador (priorClaimId) dentro exatamente deste conjunto de resultados. Se um claim mostrado aqui se refere a um claim que NÃO está no conjunto de resultados, isso aparece em possibleOmissions. Esse é um sinal concreto e verificável de que o host pode estar escondendo um claim intermediário, em vez de uma suspeita vaga.
Observe, e este é o ponto mais importante: esse campo completeness é calculado pelo mesmo servidor que retorna os claims. Se você não confia nesse servidor, não confie nesse campo também — um host desonesto pode simplesmente definir "tudo está completo" independentemente. A garantia real vive no priorClaimId assinado dentro dos próprios claims, que um host não pode forjar ou remover. Então recalcule a verificação você mesmo sobre os claims que recebeu:
// recompute-completeness.mjs
import { verifyClaim } from "capacity-attest/dist/signing.js";
import { analyzeCompleteness } from "capacity-attest/dist/completeness.js";
// `claims` = the array from the get_delivery_history response.
const allSigned = claims.every((c) => verifyClaim(c).ok); // is every claim genuine?
const report = analyzeCompleteness(claims); // recompute yourself, don't trust the host field
console.log({ allSigned, chainConsistent: report.chainConsistent, possibleOmissions: report.possibleOmissions });
Limite honesto: mesmo recalculando você mesmo, não pegará um último claim escondido, ou um comprador inteiro escondido, porque não há link para tropeçar em nenhum dos casos. E uma referência pendente não é necessariamente jogo sujo — o claim anterior pode simplesmente ter sido registrado em uma instalação diferente (o caso D-005). Para certeza real, você ainda precisa de testemunhas externas: sua própria cópia retida acima e o pagamento on-chain via settlementRef. Veja DECISIONS.md D-006.
Um exemplo público e auto-verificável com um host simulando ocultação e casos de teste deliberadamente quebrados está em docs/COMPLETENESS-FIXTURE.md. Execute com npm run fixture; as mesmas verificações são executadas a cada push como teste. Então você pode verificar nossa afirmação você mesmo em vez de aceitar nossa palavra.
Encontrando claims de outras instalações (D-005)
get_delivery_history é local por definição: o comprador B não vê o que o comprador A registrou sobre o mesmo vendedor em uma instalação diferente. Como cada claim é auto-verificável, a descoberta não precisa de um índice confiável. discoverDeliveryHistory(seller, sources) (veja src/discovery.ts) lê os claims de um vendedor de múltiplas fontes independentes e NÃO CONFIÁVEIS (seu registro local mais qualquer substrato independente de host que você queira ler), deduplica, re-verifica cada claim, filtra outros vendedores e executa a verificação de completude sobre o conjunto combinado. Uma fonte que injeta falsificações é rejeitada; uma fonte que omite coisas é o problema D-006 — contabilizado, não magicamente resolvido.
O substrato de produção (EAS na Base, ERC-8004) deliberadamente ainda não está conectado ao vivo: isso custa gás e está esperando um integrador real. A costura está pronta. Um exemplo público e executável com duas instalações simuladas está em docs/DISCOVERY-FIXTURE.md, execute com npm run discovery-fixture. Veja DECISIONS.md D-005.
3. resolve_agent_identity (desde 0.3.0)
Consulta somente-leitura contra um Registro de Identidade ERC-8004: quem possui agentId (ownerOf) e onde está seu arquivo de registro (tokenURI). Apenas a interface padrão ERC-721 é chamada, nada específico de ERC-8004. Requer que o chamador forneça tanto agentRegistryRef ("eip155:<chainId>:<registryAddress>") quanto um rpcUrl para essa cadeia: este projeto deliberadamente não inclui seu próprio provedor de RPC nem um endereço de registro canônico, já que ERC-8004 tem implantações independentes por cadeia e o próprio texto do EIP não nomeia um endereço fixo. Deliberadamente NUNCA busca o que tokenURI aponta (isso permanece um ponteiro que o chamador pode recuperar por si mesmo se quiser); fazer isso seria um risco em forma de SSRF em dados on-chain controlados pelo chamador.
Testado contra um ContractFactory injetável (src/erc8004.test.ts, sem dependência de rede) e ao vivo contra o registro real implantado na mainnet da Base (examples/verify-erc8004-live.mjs, npm run build && node examples/verify-erc8004-live.mjs). Consulte DECISIONS.md D-007 para o contexto completo.
4. publishReputationFeedback (desde 0.6.0, função de biblioteca, não uma ferramenta MCP)
Nota (revisão adversarial em 2026-09-11): no momento da escrita, o npm ainda tem a versão 0.5.0 publicada, sem esta função. Quem ler via GitHub e executar imediatamente npm install capacity-attest conforme descrito abaixo ainda não obterá publishReputationFeedback — verifique npm view capacity-attest version para a versão realmente publicada antes de importar isto. Tudo abaixo descreve o código como está no branch main.
Publica o fato delivered de uma reivindicação já assinada no giveFeedback() de um Registro de Reputação ERC-8004, o mesmo lugar onde cerca de 500 mil agentes registrados já podem procurar sinais de reputação, em vez de apenas o ledger desta instalação ou o EAS. O contrato exige um campo numérico value+valueDecimals; este pacote deliberadamente não inventa sua própria escala de avaliação para isso. value é um espelho literal e mecânico de delivered (sim=1.0, parcial=0.5, não=0.0), nunca um novo julgamento, e capacity-attest nunca lê ou exibe esse número de volta em lugar nenhum. Re-verifica a assinatura da reivindicação antes de escrever qualquer coisa on-chain.
Exige que o chamador forneça reputationRegistryRef ("eip155:<chainId>:<registryAddress>", o Registro de Reputação, não o Registro de Identidade), agentRegistryRef (mesma chain, mas o Registro de Identidade) e um rpcUrl, a mesma postura de "o chamador fornece tudo" de resolve_agent_identity. agentId (o agente ERC-8004 do vendedor) já deve ser um agente do Registro de Identidade validamente registrado; o próprio contrato recusa feedback do próprio dono do agente ("Self-feedback not allowed"). Desde a revisão adversarial de 2026-09-11, o dono registrado de agentId (via agentRegistryRef) também é sempre verificado contra claim.sellerAddress antes de qualquer coisa ser escrita on-chain — sem essa verificação, um chamador poderia anexar uma reivindicação genuína e validamente assinada a um agentId arbitrário.
Deliberadamente NÃO é uma ferramenta MCP, pela mesma razão do publishClaim do EAS: esta é uma ação de escrita que exige um signatário real, com fundos e gas, e este servidor deliberadamente não agrupa nem armazena nenhuma chave privada própria. Disponível como importação direta (src/erc8004-reputation.ts) para quem gerencia seu próprio signatário.
Testado contra um ReputationContractFactory injetável (src/erc8004-reputation.test.ts, 19 testes, sem dependência de rede, incluindo um teste explícito de que value depende somente de delivered, e três testes para a verificação de propriedade do agentId) e ao vivo contra o registro real implantado na mainnet da Base (examples/erc8004-reputation-live-demo.ts, npm run erc8004-reputation-demo): confirmado em 2026-09-10 via nosso próprio agente de teste descartável (agentId 85888) e uma chamada real giveFeedback(), tx 0x221797800d5941dff62e87022083e7c6dfba3e07b35c84e56b10fdca8967efc0, verificado de forma independente via uma chamada separada eth_getTransactionReceipt. Consulte DECISIONS.md D-016 para o contexto completo, incluindo por que isso foi construído hoje apesar do próprio critério de gatilho do D-005.
Assinatura
A reivindicação é assinada pelo comprador (a parte que pagou e, portanto, sabe o que chegou ou não), não pelo vendedor. Isso é deliberadamente EIP-191 personal_sign simples sobre claimId (via ethers.Signer#signMessage), não dados tipados EIP-712. Isso mantém a superfície criptográfica deste MVP pequena e fácil de auditar. Uma atualização posterior para EIP-712 (como em mcp-paywall/src/x402.mjs) é possível de forma aditiva, sem invalidar reivindicações existentes.
Verificação independente de uma reivindicação
Cada reivindicação no ledger pode ser re-verificada com apenas o pacote npm e os bytes brutos da reivindicação, sem acesso a este projeto ou uma chamada de rede para nós. Sem conta, sem chamada hospedada.
npm install capacity-attest
// verify.mjs, run as an ES module (top-level await)
import { verifyClaim } from "capacity-attest/dist/signing.js";
const claim = JSON.parse(await (await fetch("<url to a claim.jsonl line>")).text());
console.log(verifyClaim(claim));
// { ok: true } if claimId really is the hash of the content AND signature really recovers to buyerAddress
Nota: importe capacity-attest/dist/signing.js diretamente, não a raiz do pacote. A raiz (dist/index.js) inicia o servidor MCP via stdio no momento em que é importada, o que travará um script de verificação autônomo.
verifyClaim() verifica exatamente duas coisas: que claimId é o hash endereçado por conteúdo dos campos da reivindicação, e que signature (EIP-191) recupera para buyerAddress. Não verifica se a liquidação subjacente (settlementRef) realmente confere on-chain — essa é uma verificação separada contra a chain relevante — e não verifica se delivered é verdadeiro ou se evidenceHash cobre evidências reais; isso permanece como declaração do próprio agente pagador.
Um exemplo funcional, reproduzido externamente, desses passos exatos está em github.com/YE-YI7/asm-spec, PR #18: um projeto independente que executou isso contra uma reivindicação real e registrada ao vivo.
Compartilhando suas próprias reivindicações enviadas, independente de um host (D-006)
get_delivery_history depende da honestidade de quem opera o servidor MCP: veja o note na resposta dessa ferramenta e DECISIONS.md (D-006). Cada reivindicação exibida é genuinamente real (a assinatura também foi re-verificada na leitura desde 2026-09-06, não apenas na escrita), mas nada prova que o host está mostrando o conjunto COMPLETO que realmente possui.
Se você é o comprador que enviou uma reivindicação, não precisa esperar por esse host: você já assinou essa reivindicação, então pode mostrá-la diretamente a uma contraparte cética, ignorando qualquer host.
// export-my-claims.mjs
import { claimsForSeller } from "capacity-attest/dist/ledger.js";
const myAddress = "0x..."; // your buyerAddress
const seller = "0x..."; // the seller in question
const mine = (await claimsForSeller(seller)).filter(
(c) => c.buyerAddress.toLowerCase() === myAddress.toLowerCase(),
);
console.log(JSON.stringify(mine, null, 2));
Cada reivindicação nessa lista é verificável de forma independente com verifyClaim() (veja acima), sem que o destinatário precise confiar na sua instalação ou em qualquer host. Isso não resolve a descoberta (D-005: como outra pessoa encontra sua reivindicação se você não a compartilha) nem a completude entre TODOS os compradores juntos (D-006: isso só prova o que VOCÊ enviou, não o que um host poderia estar retendo de outros compradores), mas dá a você uma maneira concreta e gratuita de provar uma disputa específica sem precisar confiar em um host.
Executando localmente
npm install
npm run build # tsc -> dist/
npm run typecheck # tsc --noEmit
npm test # vitest run
npm run demo # end-to-end local demo with TEST keys, no live infrastructure
npm start # start the MCP server over stdio (e.g. for Claude Desktop/Code as a local MCP server)
A localização do ledger é configurável via CAPACITY_ATTEST_DATA_DIR (padrão: ./data neste pacote). Testes e a demo sempre usam seu próprio diretório temporário descartável, nunca a pasta real data/.
Arquitetura
src/
schema.ts DeliveryClaim zod schema + content-addressing (computeClaimId, canonicalize)
signing.ts sign/verify a claim (ethers, EIP-191 personal-sign)
ledger.ts append-only JSONL storage (data/claims.jsonl), never mutated
tools.ts the actual logic behind both MCP tools, transport-agnostic
config.ts where the ledger directory lives, lazy so tests can override it
index.ts MCP server wiring (registers record_delivery + get_delivery_history)
examples/demo.ts end-to-end local example with TEST keys
tools.ts contém a lógica de negócios real; index.ts apenas traduz isso em chamadas de ferramentas MCP. Dessa forma, testes e a demo podem chamar a mesma lógica diretamente sem iniciar um transporte stdio.
Relação com x402
Este projeto em si não verifica nem liquida pagamentos x402 — isso já acontece na etapa de pagamento (veja, por exemplo, mcp-paywall/src/x402.mjs neste ecossistema para uma implementação completa de verificação/liquidação EIP-3009). settlementRef simplesmente aponta para essa liquidação já concluída. Isso também significa que a integração MVP com um facilitador x402 real pode permanecer simples: settlementRef é texto livre, na suposição de que o comprador o preenche honestamente. Uma versão posterior poderia opcionalmente verificar esse campo contra um facilitador real (TODO, não neste MVP).
Relação com AWS Bedrock AgentCore Payments
Sem sobreposição, sem competição: etapa diferente na cadeia. Bedrock AgentCore Payments (Amazon, desde 2026) lida com a etapa de pagamento em si, até e incluindo o momento em que "o comerciante verifica a prova de pagamento... [e] retorna o conteúdo solicitado" (documentação oficial da AWS). Essa prova é prova de pagamento, não de entrega: em nenhum lugar é registrado se o agente realmente recebeu o que foi prometido após essa etapa. É exatamente aí que capacity-attest entra. O mesmo que com x402 acima: este projeto não liquida pagamentos e não compete com o trilho de pagamento — ele registra o que realmente chegou ou não após o pagamento, independentemente de qual trilho (x402 ou outro) lidou com esse pagamento.