@semore/ap2-adapter
v0.2.0
Published
AP2 (Agent Payments Protocol) Intent/Cart/Payment VC helpers — Ed25519 did:web signature chain scaffold.
Maintainers
Readme
@semore/ap2-adapter
Part of Semore — Agentic Commerce on the AI Grand Cycle. Korean vertical multi-adapter. (ADR-0111)
W3C Verifiable Credentials helpers for the AP2 (Agent Payments Protocol) Intent → Cart → Payment mandate chain.
AP2 establishes a three-step cryptographic chain that lets an AI agent make a payment on behalf of a user while every party (user, agent, merchant) can independently verify the scope and provenance of the authorisation.
What this means for an LLM agent. The mandate chain is how Semore exposes commerce capability overhang to you: you (the agent) author the IntentMandate the moment a user expresses a goal, refine it into a CartMandate against a live catalog, and surface a PaymentMandate at the merchant boundary — all without ever seeing PAN/CVV, and with every step independently verifiable. Capability that used to require a hosted-page redirect now lives inside the agent loop.
Source of Truth: this directory in the Semore monorepo until repo split. The production chain issuance (
issueMandateVC,verifyChain) lives in the internal Semore API atapps/api/src/lib/ap2.ts. This package exposes the stable public shapes and a referenceverifyMandateChainscaffold.Standards-body posture [LEGAL-PENDING]: References to W3C Verifiable Credentials 2.0 and the AP2 Agent Payments Protocol are nominative fair use. Semore claims no endorsement by, affiliation with, or co-authorship of W3C or the AP2 working group. Any co-branded integration is (proposed, subject to joint agreement) with the respective body. [EXTERNAL-ADVISORY]
The 3-VC chain
IntentMandate ──(parent)──▶ CartMandate ──(parent)──▶ PaymentMandate
issuer = user-DID issuer = agent-DID issuer = merchant-DID
what user wants what agent picked what will settleEach mandate is a W3C VC 2.0 JWT (EdDSA) signed by its issuer DID.
Semore issues the merchant-side link of the chain via did:web:semore.net.
Install
npm install @semore/ap2-adapter
# or
pnpm add @semore/ap2-adapterUsage
import {
createIntentMandate,
createCartMandate,
createPaymentMandate,
verifyMandateChain,
} from "@semore/ap2-adapter";
const intent = createIntentMandate({
issuerDid: "did:web:user.example",
subjectDid: "did:web:user.example",
claims: { naturalLanguage: "buy Korean sunscreen under $30" },
});
const cart = createCartMandate({
issuerDid: "did:web:agent.example",
subjectDid: "did:web:user.example",
parentVcId: intent.id,
claims: { items: [{ skuId: "sku_abc", qty: 1, priceUsd: "24.00" }] },
});
const payment = createPaymentMandate({
issuerDid: "did:web:semore.net",
subjectDid: "did:web:user.example",
parentVcId: cart.id,
claims: { paymentProof: { rail: "card", authCode: "auth_123" } },
});
const result = await verifyMandateChain([intent, cart, payment], {
resolveDid: async (did) => fetchDidDocument(did),
});What this package provides
- Public shapes for
IntentMandate,CartMandate,PaymentMandate(W3C VC 2.0). create*Mandate(...)factories that assemble the VC envelope (signing is delegated to the caller — bring your own Ed25519 key via thesignoption).verifyMandateChain(chain, opts)— structural + parent-link + signature verification.
What this package does not provide
- Key management. Use Cloudflare Workers Secrets, AWS KMS, HSM, or your preferred vault.
- DID document resolution. Supply a
resolveDidcallback — the canonicaldid:webresolver is a singlefetch(...). - The production Semore issuer.
did:web:semore.netkeys are not in this repo.
DID Resolution
Semore publishes its DID document at https://semore.net/.well-known/did.json.
Your resolveDid callback fetches the document, walks verificationMethod, and
imports each public JWK into a CryptoKey keyed by its id.
import type { VerifyChainOptions } from "@semore/ap2-adapter";
interface DidDocument {
readonly id: string;
readonly verificationMethod: ReadonlyArray<{
readonly id: string;
readonly type: string;
readonly controller: string;
readonly publicKeyJwk: JsonWebKey;
}>;
}
const resolveDid: VerifyChainOptions["resolveDid"] = async (did) => {
const url =
did === "did:web:semore.net"
? "https://semore.net/.well-known/did.json"
: `https://${did.replace("did:web:", "")}/.well-known/did.json`;
const res = await fetch(url);
const doc = (await res.json()) as DidDocument;
const keys: Record<string, CryptoKey> = {};
for (const vm of doc.verificationMethod) {
keys[vm.id] = await crypto.subtle.importKey(
"jwk",
vm.publicKeyJwk,
{ name: "Ed25519" },
true,
["verify"],
);
}
return keys;
};Cache the resolved keys per-DID (Cloudflare KV, an in-memory LRU, or your framework's cache primitive) to avoid re-fetching on every verify call.
Reference
- AP2 spec: https://ap2-protocol.org
- W3C VC 2.0: https://www.w3.org/TR/vc-data-model-2.0/
- Contact:
[email protected]· GitHub @semore_hq
License
Apache-2.0 — see LICENSE.
Copyright (c) Semore Founding Team.
