From 35607af7cde390fc3f324e02205a6aeb61f1fd92 Mon Sep 17 00:00:00 2001 From: Greg Soucy Date: Mon, 20 Jul 2026 22:02:30 -0400 Subject: [PATCH] Add ERC-8004 claim registration tracking --- .env.example | 5 +++ api/admin/claim.js | 7 +++- api/admin/pin-agent-cards.js | 8 +++- api/claims/status.js | 14 ++++++- db/migrations/012_agent_registrations.sql | 48 +++++++++++++++++++++++ lib/claims/agent-registrations.js | 40 +++++++++++++++++++ public/admin/claims.html | 2 +- tests/agent-registrations.test.js | 31 +++++++++++++++ 8 files changed, 151 insertions(+), 4 deletions(-) create mode 100644 db/migrations/012_agent_registrations.sql create mode 100644 lib/claims/agent-registrations.js create mode 100644 tests/agent-registrations.test.js diff --git a/.env.example b/.env.example index 3640339..23eef8d 100644 --- a/.env.example +++ b/.env.example @@ -2,3 +2,8 @@ # Used by api/verify-receipt.js (proxy) and api/health.js. # Example: https://runtime.commandlayer.org RUNTIME_BASE_URL=https://runtime.commandlayer.org + +# ERC-8004 discovery metadata recorded when claimed agent cards are pinned. +# Tracking these values does not submit an onchain transaction. +ERC8004_CHAIN_ID=1 +ERC8004_REGISTRY_ADDRESS= diff --git a/api/admin/claim.js b/api/admin/claim.js index baca6f0..90b12bc 100644 --- a/api/admin/claim.js +++ b/api/admin/claim.js @@ -82,7 +82,12 @@ module.exports = async function handler(req, res) { latestPayment = await queryOptionalOne('select * from claim_payments where claim_id = $1 order by updated_at desc nulls last, paid_at desc nulls last, id desc limit 1', [claimId], 'claim_payments'); } - return res.status(200).json({ ok: true, claim: stripClaimSecrets(claim), agents: agentsResult.rows, events: eventsResult.rows, transitions, cards, latestPayment }); + let registrations = []; + if (await hasTable('agent_registrations')) { + registrations = await queryOptionalRows('select * from agent_registrations where claim_id = $1 order by ens asc, standard asc', [claimId], [], 'agent_registrations'); + } + + return res.status(200).json({ ok: true, claim: stripClaimSecrets(claim), agents: agentsResult.rows, events: eventsResult.rows, transitions, cards, registrations, latestPayment }); } catch (error) { console.error('[admin.claim] failed to load claim detail', { code: error && error.code }); const payload = { diff --git a/api/admin/pin-agent-cards.js b/api/admin/pin-agent-cards.js index e875cf2..6ea230a 100644 --- a/api/admin/pin-agent-cards.js +++ b/api/admin/pin-agent-cards.js @@ -2,6 +2,7 @@ const db = require('../../lib/db'); const { stableStringify, sha256Hex, pinJsonToPinata } = require('../../lib/ipfsPinning'); +const { trackPinnedCardRegistration } = require('../../lib/claims/agent-registrations'); function firstObjectValue(obj) { if (!obj || typeof obj !== 'object' || Array.isArray(obj)) return null; @@ -114,7 +115,10 @@ module.exports = async function handler(req, res) { } const allPinned = cardsResult.rows.every((r) => r.card_cid && r.card_ipfs_uri && r.card_sha256); - if (allPinned) return res.status(200).json({ ok: true, status: 'ALREADY_PINNED', claimId, cards: cardsResult.rows }); + if (allPinned) { + await Promise.all(cardsResult.rows.map(trackPinnedCardRegistration)); + return res.status(200).json({ ok: true, status: 'ALREADY_PINNED', claimId, cards: cardsResult.rows }); + } const gatewayBase = (process.env.IPFS_GATEWAY_BASE_URL || 'https://gateway.pinata.cloud/ipfs').replace(/\/$/, ''); @@ -130,6 +134,7 @@ module.exports = async function handler(req, res) { const hash = sha256Hex(canonical); if (row.card_cid && row.card_ipfs_uri && row.card_sha256) { + await trackPinnedCardRegistration(row); pinned.push({ ens: row.ens, card_cid: row.card_cid, card_sha256: row.card_sha256, card_gateway_url: row.card_gateway_url }); continue; } @@ -145,6 +150,7 @@ module.exports = async function handler(req, res) { where id = $1`, [row.id, cid, ipfsUri, gatewayUrl, hash, provider] ); + await trackPinnedCardRegistration({ ...row, card_cid: cid, card_ipfs_uri: ipfsUri }); pinned.push({ ens: row.ens, card_cid: cid, card_sha256: hash, card_gateway_url: gatewayUrl }); } catch (error) { await db.query("update agent_cards set pin_status = 'error', pin_error = $2 where id = $1", [row.id, String(error && error.message ? error.message : 'pinning_failed')]); diff --git a/api/claims/status.js b/api/claims/status.js index a399f58..88e8589 100644 --- a/api/claims/status.js +++ b/api/claims/status.js @@ -35,6 +35,7 @@ module.exports = async function handler(req, res) { const auth = getClaimAuth(req, claim); if (!auth.ok) return unauthorizedClaimResponse(res); let cards = []; + let registrations = []; try { const cardsResult = await db.query( `select ens, card_cid, card_ipfs_uri, card_gateway_url, card_sha256, pin_status from agent_cards where claim_id = $1 order by ens asc`, @@ -44,6 +45,17 @@ module.exports = async function handler(req, res) { } catch (_error) { cards = []; } + try { + const registrationsResult = await db.query( + `select standard, ens, chain_id, registry_address, agent_id, agent_uri, registration_status, + ensip25_status, verified_at + from agent_registrations where claim_id = $1 order by ens asc, standard asc`, + [claimId] + ); + registrations = registrationsResult.rows; + } catch (_error) { + registrations = []; + } const recordsVerified = claim.tenant_signer_record_status === 'records_verified' || claim.tenant_signer_record_status === 'verified'; const paymentConfirmed = claim.status === 'paid' || claim.status === 'cards_pinned' || claim.status === 'active' || claim.payment_status === 'paid' || Boolean(claim.paid_at); const pipeline = { @@ -58,7 +70,7 @@ module.exports = async function handler(req, res) { first_action_receipt: claim.first_action_receipt_status || 'not_generated', agent_live: paymentConfirmed && recordsVerified && cardsStatus(cards) === 'cards_pinned' && claim.genesis_receipt_id && claim.tenant_proof_status === 'verified' && (claim.first_action_receipt_status === 'verified' || typeof claim.first_action_receipt_status === 'undefined') ? 'live' : 'not_live', }; - return res.status(200).json({ ok: true, read_only: true, claim: { ...stripClaimSecrets(claim), cardsStatus: cardsStatus(cards), managed_ens_publication: { status: claim.managed_ens_publication_status || 'not_started', signer_ens: claim.tenant_signer_ens || null, parent_namespace: claim.managed_ens_parent_namespace || null, record_names: Object.keys(claim.managed_ens_required_txt_records || claim.tenant_signer_txt_records || {}), helper_copy: 'The operator must publish the generated TXT records to ENS before signer verification can pass.', verified_at: claim.managed_ens_verified_at || null, error: claim.managed_ens_publication_error || null } }, pipeline, cards }); + return res.status(200).json({ ok: true, read_only: true, claim: { ...stripClaimSecrets(claim), cardsStatus: cardsStatus(cards), managed_ens_publication: { status: claim.managed_ens_publication_status || 'not_started', signer_ens: claim.tenant_signer_ens || null, parent_namespace: claim.managed_ens_parent_namespace || null, record_names: Object.keys(claim.managed_ens_required_txt_records || claim.tenant_signer_txt_records || {}), helper_copy: 'The operator must publish the generated TXT records to ENS before signer verification can pass.', verified_at: claim.managed_ens_verified_at || null, error: claim.managed_ens_publication_error || null } }, pipeline, cards, registrations }); } catch (_error) { return res.status(500).json({ ok: false, status: 'CLAIM_STATUS_UNAVAILABLE' }); } diff --git a/db/migrations/012_agent_registrations.sql b/db/migrations/012_agent_registrations.sql new file mode 100644 index 0000000..cc564bb --- /dev/null +++ b/db/migrations/012_agent_registrations.sql @@ -0,0 +1,48 @@ +create table if not exists public.agent_registrations ( + id uuid primary key default gen_random_uuid(), + claim_id text not null, + ens text not null, + standard text not null default 'erc8004', + chain_id text not null, + registry_address text not null, + agent_id text, + agent_uri text, + agent_card_cid text, + registration_tx_hash text, + registration_status text not null default 'pending', + ensip25_status text not null default 'not_checked', + ensip25_claim_key text, + ensip25_verified_at timestamptz, + registered_at timestamptz, + verified_at timestamptz, + metadata_json jsonb, + last_error text, + last_error_at timestamptz, + created_at timestamptz not null default now(), + updated_at timestamptz not null default now(), + constraint agent_registrations_claim_id_fkey foreign key (claim_id) + references public.claim_requests(claim_id) on delete cascade +); + +create index if not exists idx_agent_registrations_claim_id on public.agent_registrations(claim_id); +create index if not exists idx_agent_registrations_ens on public.agent_registrations(ens); +create index if not exists idx_agent_registrations_standard on public.agent_registrations(standard); +create index if not exists idx_agent_registrations_registration_status on public.agent_registrations(registration_status); +create index if not exists idx_agent_registrations_agent_id on public.agent_registrations(agent_id); +create unique index if not exists idx_agent_registrations_registry_identity + on public.agent_registrations(standard, ens, chain_id, registry_address); + +create or replace function public.set_agent_registrations_updated_at() +returns trigger +language plpgsql +as $$ +begin + new.updated_at = now(); + return new; +end; +$$; + +drop trigger if exists set_agent_registrations_updated_at on public.agent_registrations; +create trigger set_agent_registrations_updated_at +before update on public.agent_registrations +for each row execute function public.set_agent_registrations_updated_at(); diff --git a/lib/claims/agent-registrations.js b/lib/claims/agent-registrations.js new file mode 100644 index 0000000..22422f2 --- /dev/null +++ b/lib/claims/agent-registrations.js @@ -0,0 +1,40 @@ +'use strict'; + +const db = require('../db'); + +/** Idempotently records card discovery metadata; it does not perform registration or verification. */ +async function upsertErc8004Registration({ claimId, ens, chainId, registryAddress, agentUri = null, agentCardCid = null, metadata = null }) { + if (!claimId || !ens || !chainId || !registryAddress) { + throw new Error('claimId, ens, chainId, and registryAddress are required'); + } + + const result = await db.query( + `insert into agent_registrations + (claim_id, ens, standard, chain_id, registry_address, agent_uri, agent_card_cid, metadata_json) + values ($1, $2, 'erc8004', $3, $4, $5, $6, $7::jsonb) + on conflict (standard, ens, chain_id, registry_address) do update + set claim_id = excluded.claim_id, + agent_uri = coalesce(excluded.agent_uri, agent_registrations.agent_uri), + agent_card_cid = coalesce(excluded.agent_card_cid, agent_registrations.agent_card_cid), + metadata_json = coalesce(excluded.metadata_json, agent_registrations.metadata_json) + returning *`, + [claimId, ens, chainId, registryAddress, agentUri, agentCardCid, metadata == null ? null : JSON.stringify(metadata)] + ); + return result.rows[0] || null; +} + +async function trackPinnedCardRegistration(card) { + const chainId = process.env.ERC8004_CHAIN_ID; + const registryAddress = process.env.ERC8004_REGISTRY_ADDRESS; + if (!chainId || !registryAddress) return null; + return upsertErc8004Registration({ + claimId: card.claim_id, + ens: card.ens, + chainId, + registryAddress, + agentUri: card.card_ipfs_uri || null, + agentCardCid: card.card_cid || null, + }); +} + +module.exports = { upsertErc8004Registration, trackPinnedCardRegistration }; diff --git a/public/admin/claims.html b/public/admin/claims.html index a3f05b5..cc49b1f 100644 --- a/public/admin/claims.html +++ b/public/admin/claims.html @@ -81,7 +81,7 @@

CommandLayer Claims Admin

Internal operator dashboard f function advancementPanel(){const a=s.activationAdvanced;if(!a)return '';const err=a.errors||{};const rows=[['Managed ENS','verified'],['Agent card pinning',a.agent_cards||'not_started'],['Genesis receipt',a.genesis_receipt||'not_started'],['First Action Receipt',a.first_action_receipt||'not_generated']];return `

Managed ENS verified — activation advanced${rows.map(([k,v])=>`
${k}: ${v}
`).join('')}${Object.keys(err).length?`
Advancement errors: ${Object.entries(err).map(([k,v])=>`${k}: ${v}`).join(' · ')}
`:''}
`;} function txtRecordBlocks(claim){const records=(s.managedEnsPublication&&s.managedEnsPublication.required_txt_records)||claim.managed_ens_required_txt_records||claim.tenant_signer_txt_records||{};return Object.entries(records).map(([k,v])=>`
${k}
${v}
`).join('')||'

Prepare TXT records to display required values.

';} -function renderDetail(loadingMsg=''){const el=document.getElementById('detailPanel');if(loadingMsg){el.innerHTML=`

${loadingMsg}

`;return;}if(!s.detail){el.innerHTML=`

${s.error||'Select a claim to review.'}

`;return;}const payload=s.detail||{};const claim=payload.claim||{};const agents=payload.agents||[];const genesis=claim.genesis_receipt_json||null;const events=payload.events||[];const transitions=payload.transitions||[];const cards=payload.cards||[];const urls=cards.map(c=>c.card_url).filter(Boolean);const missing=claim.status==='cards_published'&&agents.some(a=>!a.card_url);const firstUrl=urls[0];el.innerHTML=`${s.error?`
${s.error}
`:''}

Claim ${short(claim.claim_id,16)} ${badge(claim.status)}

Tenant: ${claim.tenant||'-'} · Wallet: ${claim.authenticated_address||'-'} · Status: ${claim.status||'-'} · Payment: ${claim.payment_status||'-'} · Agents: ${agents.length} · Created: ${dt(claim.created_at)}

Pipeline

${pipeline(claim.status)}${advancementPanel()}

Signer identity

Activation mode: ${claim.activation_mode||'-'}
Signer ENS: ${claim.tenant_signer_ens||'-'}
ENS records: ${claim.tenant_signer_record_status||'records_pending'} · Network: ${claim.tenant_signer_records_network||'ethereum-mainnet'}
Managed ENS publication: ${claim.managed_ens_publication_status||'-'}
Publication error: ${claim.managed_ens_publication_error||'-'}
Desired parent namespace: ${claim.managed_ens_parent_namespace||'-'}
Parent-control/resolver authority audited: ${claim.managed_ens_parent_authority_audited?'yes':'no'}
Tenant proof: ${claim.tenant_proof_status||'not_submitted'} · signer ${claim.tenant_proof_signer||'-'}
Required managed ENS TXT records${txtRecordBlocks(claim)}
Four public TXT records
${JSON.stringify(claim.tenant_signer_txt_records||{},null,2)}

Network required: Ethereum mainnet. Managed ENS publication is operator-ready only; no onchain TXT writes are executed here.

Next action

Agents

${agents.map(a=>``).join('')}
ENSCapabilityParentCardActions
${a.ens||'-'}${a.capability||'-'}${a.canonical_parent||'-'}${a.card_status||'-'}${a.card_url?`Open `:'-'}
${claim.status==='payment_pending'?`

Payment pending

Checkout has been created. Open it to complete payment.

${claim.stripe_checkout_session_id||'-'}

`:''}${claim.status==='paid'?`

Payment received

Next: ERC-8004 registration

`:''}${claim.status==='cards_published'?`

Agent cards published

Founding Activation: $20 first year for 10 Trust Verification namespaces.

${urls.length} cards${missing?' · Missing card URLs detected':''}

${s.checkoutUrl?`
Checkout created
Open checkout
${s.checkoutUrl}
`:''}
${firstUrl?``:''}
${urls.map(u=>`
${u.split('/').pop()?.replace(/\.json$/,'')||u}Open${copyBtn(u)}
`).join('')}
${missing?`

Some cards are missing URLs. Use Repair / Publish cards.

`:''}`:''}

Events

${events.map(e=>`
${e.event_type}
${e.message||''}
${dt(e.created_at)}
metadata
${JSON.stringify(e.event_json||{},null,2)}
`).join('')||'

No events.

'}

Genesis Receipt

Status: ${genesis?'generated':'not generated'}
Receipt ID: ${claim.genesis_receipt_id||'-'}
Receipt Hash: ${claim.genesis_receipt_hash||'-'}
Chain Root: ${claim.receipt_chain_root||'-'}
Generated at: ${dt(claim.genesis_generated_at)}
${s.genesisStatus?`
${s.genesisStatus}
`:''}${genesis?``:''}

First Action Receipt

Status: ${claim.first_action_receipt_status||'not_generated'}
Receipt ID: ${claim.first_action_receipt_id||'-'}
Receipt Hash: ${claim.first_action_receipt_hash||'-'}
${claim.first_action_receipt_error||'This proves the claimed agent can produce a scoped execution receipt. It does not prove payment settlement.'}

Transitions

${transitions.map(t=>`
${t.from_status||'-'} → ${t.to_status||'-'} · ${t.action||'-'} · ${t.actor||'-'}
${dt(t.created_at)} ${t.reason?`· ${t.reason}`:''}
`).join('')||'

No transitions.

'}
Show raw JSON
${JSON.stringify(s.detail,null,2)}
`; +function renderDetail(loadingMsg=''){const el=document.getElementById('detailPanel');if(loadingMsg){el.innerHTML=`

${loadingMsg}

`;return;}if(!s.detail){el.innerHTML=`

${s.error||'Select a claim to review.'}

`;return;}const payload=s.detail||{};const claim=payload.claim||{};const agents=payload.agents||[];const genesis=claim.genesis_receipt_json||null;const events=payload.events||[];const transitions=payload.transitions||[];const cards=payload.cards||[];const registrations=payload.registrations||[];const urls=cards.map(c=>c.card_url).filter(Boolean);const missing=claim.status==='cards_published'&&agents.some(a=>!a.card_url);const firstUrl=urls[0];el.innerHTML=`${s.error?`
${s.error}
`:''}

Claim ${short(claim.claim_id,16)} ${badge(claim.status)}

Tenant: ${claim.tenant||'-'} · Wallet: ${claim.authenticated_address||'-'} · Status: ${claim.status||'-'} · Payment: ${claim.payment_status||'-'} · Agents: ${agents.length} · Created: ${dt(claim.created_at)}

Pipeline

${pipeline(claim.status)}${advancementPanel()}

Signer identity

Activation mode: ${claim.activation_mode||'-'}
Signer ENS: ${claim.tenant_signer_ens||'-'}
ENS records: ${claim.tenant_signer_record_status||'records_pending'} · Network: ${claim.tenant_signer_records_network||'ethereum-mainnet'}
Managed ENS publication: ${claim.managed_ens_publication_status||'-'}
Publication error: ${claim.managed_ens_publication_error||'-'}
Desired parent namespace: ${claim.managed_ens_parent_namespace||'-'}
Parent-control/resolver authority audited: ${claim.managed_ens_parent_authority_audited?'yes':'no'}
Tenant proof: ${claim.tenant_proof_status||'not_submitted'} · signer ${claim.tenant_proof_signer||'-'}
Required managed ENS TXT records${txtRecordBlocks(claim)}
Four public TXT records
${JSON.stringify(claim.tenant_signer_txt_records||{},null,2)}

Network required: Ethereum mainnet. Managed ENS publication is operator-ready only; no onchain TXT writes are executed here.

Next action

Agents

${agents.map(a=>``).join('')}
ENSCapabilityParentCardActions
${a.ens||'-'}${a.capability||'-'}${a.canonical_parent||'-'}${a.card_status||'-'}${a.card_url?`Open `:'-'}

Registrations

${registrations.map(r=>``).join('')}
StandardENSChainRegistryAgent IDAgent URIRegistration statusENSIP-25 statusVerified at
${r.standard||'-'}${r.ens||'-'}${r.chain_id||'-'}${r.registry_address||'-'}${r.agent_id||'-'}${r.agent_uri||'-'}${r.registration_status||'-'}${r.ensip25_status||'-'}${dt(r.verified_at)}
${registrations.length?'':`

No ERC-8004 registration metadata has been tracked yet.

`}${claim.status==='payment_pending'?`

Payment pending

Checkout has been created. Open it to complete payment.

${claim.stripe_checkout_session_id||'-'}

`:''}${claim.status==='paid'?`

Payment received

Next: ERC-8004 registration

`:''}${claim.status==='cards_published'?`

Agent cards published

Founding Activation: $20 first year for 10 Trust Verification namespaces.

${urls.length} cards${missing?' · Missing card URLs detected':''}

${s.checkoutUrl?`
Checkout created
Open checkout
${s.checkoutUrl}
`:''}
${firstUrl?``:''}
${urls.map(u=>`
${u.split('/').pop()?.replace(/\.json$/,'')||u}Open${copyBtn(u)}
`).join('')}
${missing?`

Some cards are missing URLs. Use Repair / Publish cards.

`:''}`:''}

Events

${events.map(e=>`
${e.event_type}
${e.message||''}
${dt(e.created_at)}
metadata
${JSON.stringify(e.event_json||{},null,2)}
`).join('')||'

No events.

'}

Genesis Receipt

Status: ${genesis?'generated':'not generated'}
Receipt ID: ${claim.genesis_receipt_id||'-'}
Receipt Hash: ${claim.genesis_receipt_hash||'-'}
Chain Root: ${claim.receipt_chain_root||'-'}
Generated at: ${dt(claim.genesis_generated_at)}
${s.genesisStatus?`
${s.genesisStatus}
`:''}${genesis?``:''}

First Action Receipt

Status: ${claim.first_action_receipt_status||'not_generated'}
Receipt ID: ${claim.first_action_receipt_id||'-'}
Receipt Hash: ${claim.first_action_receipt_hash||'-'}
${claim.first_action_receipt_error||'This proves the claimed agent can produce a scoped execution receipt. It does not prove payment settlement.'}

Transitions

${transitions.map(t=>`
${t.from_status||'-'} → ${t.to_status||'-'} · ${t.action||'-'} · ${t.actor||'-'}
${dt(t.created_at)} ${t.reason?`· ${t.reason}`:''}
`).join('')||'

No transitions.

'}
Show raw JSON
${JSON.stringify(s.detail,null,2)}
`; const a=document.getElementById('actions');const mk=(t,c,cls='btn',opts={})=>{const b=document.createElement('button');b.type='button';b.textContent=t;b.className=cls;b.disabled=Boolean(opts.disabled);if(opts.id)b.id=opts.id;b.onclick=c;a.appendChild(b);}; if(claim.status==='created'){mk('Approve',()=>action('approve',{notes:document.getElementById('notesInput').value}),'btn btn-primary');mk('Reject',()=>action('reject',{reason:document.getElementById('reasonInput').value}),'btn btn-secondary');mk('Mark failed',()=>action('mark_failed',{reason:document.getElementById('reasonInput').value}),'btn btn-danger');} if(claim.status==='approved'){mk('Publish agent cards',()=>publish(),'btn btn-primary');mk('Mark failed',()=>action('mark_failed',{reason:document.getElementById('reasonInput').value}),'btn btn-danger');mk('Add note',()=>action('add_note',{notes:document.getElementById('notesInput').value,reason:document.getElementById('reasonInput').value}),'btn');} diff --git a/tests/agent-registrations.test.js b/tests/agent-registrations.test.js new file mode 100644 index 0000000..e85294e --- /dev/null +++ b/tests/agent-registrations.test.js @@ -0,0 +1,31 @@ +'use strict'; + +const test = require('node:test'); +const assert = require('node:assert/strict'); +const db = require('../lib/db'); +const { upsertErc8004Registration, trackPinnedCardRegistration } = require('../lib/claims/agent-registrations'); + +test('upsert ERC-8004 metadata is idempotent and leaves proof fields untouched', async () => { + let query; + let values; + db.query = async (q, v) => { query = q; values = v; return { rows: [{ registration_status: 'pending' }] }; }; + const row = await upsertErc8004Registration({ claimId: 'c1', ens: 'agent.eth', chainId: '1', registryAddress: '0xregistry', agentUri: 'ipfs://cid', agentCardCid: 'cid' }); + assert.match(query, /on conflict \(standard, ens, chain_id, registry_address\) do update/); + assert.equal(query.includes('agent_id ='), false); + assert.equal(query.includes('registration_tx_hash ='), false); + assert.deepEqual(values.slice(0, 6), ['c1', 'agent.eth', '1', '0xregistry', 'ipfs://cid', 'cid']); + assert.equal(row.registration_status, 'pending'); +}); + +test('pinned card tracking requires explicit registry configuration', async () => { + const oldChain = process.env.ERC8004_CHAIN_ID; + const oldRegistry = process.env.ERC8004_REGISTRY_ADDRESS; + delete process.env.ERC8004_CHAIN_ID; + delete process.env.ERC8004_REGISTRY_ADDRESS; + let called = false; + db.query = async () => { called = true; return { rows: [] }; }; + assert.equal(await trackPinnedCardRegistration({ claim_id: 'c1', ens: 'agent.eth' }), null); + assert.equal(called, false); + if (oldChain === undefined) delete process.env.ERC8004_CHAIN_ID; else process.env.ERC8004_CHAIN_ID = oldChain; + if (oldRegistry === undefined) delete process.env.ERC8004_REGISTRY_ADDRESS; else process.env.ERC8004_REGISTRY_ADDRESS = oldRegistry; +});