Skip to main content

Documentation Index

Fetch the complete documentation index at: https://glide-9da73dea.mintlify.app/llms.txt

Use this file to discover all available pages before exploring further.

Resolves agent_did counterparties to on-chain settling addresses. Four resolution schemes: Glide-internal same-tenant vault (free, instant), ERC-8004 Identity Registry (Ethereum mainnet), ENS multi-chain addresses via ENSIP-9, and did:web HTTPS document fetch. Ships a DNS-rebinding-safe fetch helper for the did:web path. Pure functions with injected resolver implementations. On-chain reads and network IO live in apps/web.

Install

npm install @glideco/a2a-resolver
npmjs.com/package/@glideco/a2a-resolver

Why a resolution chain

Agents reference each other by DID, not by a fixed on-chain address. A DID-addressed counterparty can route to whichever chain the recipient prefers today and change that preference later. The resolution chain runs in priority order — Glide-internal first (cheapest), then on-chain registries, then the HTTPS document fallback — so same-tenant payments are never sent on-chain when an internal vault hop is available. ERC-8004 went live on Ethereum mainnet on 2026-01-29. The did:erc8004 scheme is the canonical anchor for agents with on-chain identity. ENS (did:ens) and did:web support agents that predate ERC-8004 or prefer off-chain identity.

DID URI schemes

SchemeFormatResolution path
did:keydid:key:z<base58btc>Glide-internal tenant lookup by agent_principals.did_key
did:erc8004did:erc8004:0x<64 hex>On-chain resolveAgent(bytes32) via viem
did:ensdid:ens:<name>.ethENS multi-chain addresses (ENSIP-9)
did:webdid:web:<domain>HTTPS fetch of <domain>/.well-known/did.json

Resolving a counterparty

import { resolveAgentDid } from '@glideco/a2a-resolver';

const outcome = await resolveAgentDid(
  {
    kind: 'agent_did',
    did: 'did:web:agent.example.com',
    preferredChain: 'base',
    preferredToken: 'USDC',
  },
  {
    resolveDidWeb: async (did) => {
      // apps/web injects safeFetchDidWeb here
      const doc = await safeFetchDidWeb(did);
      return extractSettlingAddress(doc, preferredChain, preferredToken);
    },
  }
);

if (outcome.ok) {
  // outcome.resolved.address  → '0xabc...def'
  // outcome.resolved.chain    → 'base'
  // outcome.resolved.token    → 'USDC'
  // outcome.resolved.source   → 'did-web'
  // outcome.resolved.confidence → 0.8
} else {
  // outcome.reason → 'no resolver could map ... to a settling address'
}
The deps object is the injection point. Pass only the resolvers your environment supports — the orchestrator skips schemes with no injected resolver.

Validation

import { isValidAgentDid, classifyAgentDid } from '@glideco/a2a-resolver';

isValidAgentDid('did:key:z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK');
// → true

isValidAgentDid('did:web:metadata.aws.internal');
// → false (caught by regex before any fetch)

classifyAgentDid('did:erc8004:0x' + 'a'.repeat(64));
// → 'did:erc8004'
isValidAgentDid enforces:
  • did:key — base58btc alphabet only (z prefix, no 0OIl).
  • did:web — requires at least one dot, no IPv4 literals, no IPv6 brackets, TLD ≥ 2 ASCII letters.
  • did:erc8004 — exactly 64 hex digits (bytes32, not bytes20/EVM address).
  • Max length 512 to prevent regex-DoS on pathological inputs.

DNS-rebinding-safe did:web fetch

String-level validation alone doesn’t prevent SSRF — a public domain can have an A record pointing at 169.254.169.254 or an RFC 1918 address. safeFetchDidWeb resolves the hostname first, rejects if any A/AAAA record is private, then connects via undici with the pre-resolved IP pinned so a rebinding attack between the resolution and the TCP connect can’t swap the destination.
import { safeFetchDidWeb, isPublicIp } from '@glideco/a2a-resolver';

// isPublicIp covers: loopback, link-local, RFC 1918, CGNAT,
// multicast, cloud metadata IPs (100.100.100.200, 168.63.129.16, etc.)
isPublicIp('192.168.1.1');         // false
isPublicIp('100.100.100.200');     // false (Alibaba metadata)
isPublicIp('2600:1f18::1');        // true

const didDoc = await safeFetchDidWeb('did:web:agent.example.com', {
  timeoutMs: 3000,
  maxBodyBytes: 512 * 1024,
});

Audit trail

import { buildA2aAuditRecord } from '@glideco/a2a-resolver';

const record = buildA2aAuditRecord({
  senderDid: 'did:key:z6Mk...',
  recipient: { kind: 'agent_did', did: 'did:web:agent.example.com' },
  resolved: outcome.resolved,
  paymentId: 'pay_abc123',
  amountCents: 5000,
  currency: 'USD',
});

// record.event_type           → 'a2a.payment.routed'
// record.resolution_source    → 'did-web'
// record.resolution_confidence → 0.8
// Append to mcp_audit_events.
The audit record captures both sender and recipient DID so the mcp_audit_events table stores the full A2A graph — needed for ERC-8004 reputation feedback in a future release.

Reading list