@integraledger/lcp-binding-evm-common 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,73 @@
1
+ /**
2
+ * The EIP-3009 `TransferWithAuthorization` typed-data machinery for the LCP canonical EVM binding
3
+ * `LCP-X402-EVM-NONCE-1`: the buyer signs an EIP-3009 authorization whose `nonce` field IS the
4
+ * `atrHash`, so the fingerprint is committed on-chain in the token's `AuthorizationUsed(authorizer,
5
+ * indexed nonce)` event — no overlay contract.
6
+ *
7
+ * The x402-client scheme class is deliberately NOT here; it lives in binding-evm-x402. This module is
8
+ * protocol-agnostic typed-data construction, viem-only.
9
+ */
10
+ import { getAddress } from "viem";
11
+ /** EIP-3009 TransferWithAuthorization typed-data field layout (USDC FiatTokenV2). */
12
+ export const TRANSFER_WITH_AUTHORIZATION_TYPE = {
13
+ TransferWithAuthorization: [
14
+ { name: "from", type: "address" },
15
+ { name: "to", type: "address" },
16
+ { name: "value", type: "uint256" },
17
+ { name: "validAfter", type: "uint256" },
18
+ { name: "validBefore", type: "uint256" },
19
+ { name: "nonce", type: "bytes32" },
20
+ ],
21
+ };
22
+ /** Parse the EIP-155 chain id out of a CAIP-2 network string; throws on anything that is not
23
+ * `eip155:<digits>` (never defaults — the chain id binds the EIP-712 domain). */
24
+ export function eip155ChainId(network) {
25
+ const m = /^eip155:(\d+)$/.exec(network);
26
+ if (!m)
27
+ throw new Error(`expected CAIP-2 network "eip155:<id>", got "${network}"`);
28
+ return Number(m[1]);
29
+ }
30
+ /** The atrHash rides the EIP-3009 nonce as a 0x-prefixed 32-byte value. Any-case digits accepted
31
+ * (ATR canon), then lowercased for the on-chain nonce (the nonce is matched lowercase on decode). */
32
+ export function assertBytes32(value) {
33
+ if (!/^0x[0-9a-fA-F]{64}$/.test(value))
34
+ throw new Error(`atrHash must be a 0x-prefixed 32-byte (64 hex) value to ride as the EIP-3009 nonce, got "${value}"`);
35
+ return value.toLowerCase();
36
+ }
37
+ /**
38
+ * Build the EIP-3009 authorization + the EIP-712 typed-data to sign, with `nonce = atrHash`.
39
+ * `authorization` is the wire payload; `typedData` is what the signer signs.
40
+ */
41
+ export function buildEip3009TypedData(i) {
42
+ const nonce = assertBytes32(i.atrHash);
43
+ const from = getAddress(i.from);
44
+ const to = getAddress(i.to);
45
+ const authorization = {
46
+ from,
47
+ to,
48
+ value: i.value,
49
+ validAfter: i.validAfter,
50
+ validBefore: i.validBefore,
51
+ nonce,
52
+ };
53
+ const typedData = {
54
+ domain: {
55
+ name: i.tokenName,
56
+ version: i.tokenVersion,
57
+ chainId: i.chainId,
58
+ verifyingContract: getAddress(i.verifyingContract),
59
+ },
60
+ types: TRANSFER_WITH_AUTHORIZATION_TYPE,
61
+ primaryType: "TransferWithAuthorization",
62
+ message: {
63
+ from,
64
+ to,
65
+ value: BigInt(i.value),
66
+ validAfter: BigInt(i.validAfter),
67
+ validBefore: BigInt(i.validBefore),
68
+ nonce,
69
+ },
70
+ };
71
+ return { authorization, typedData };
72
+ }
73
+ //# sourceMappingURL=eip3009.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"eip3009.js","sourceRoot":"","sources":["../src/eip3009.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,OAAO,EAAgB,UAAU,EAAkC,MAAM,MAAM,CAAC;AAEhF,qFAAqF;AACrF,MAAM,CAAC,MAAM,gCAAgC,GAAG;IAC9C,yBAAyB,EAAE;QACzB,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,SAAS,EAAE;QACjC,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,SAAS,EAAE;QAC/B,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE;QAClC,EAAE,IAAI,EAAE,YAAY,EAAE,IAAI,EAAE,SAAS,EAAE;QACvC,EAAE,IAAI,EAAE,aAAa,EAAE,IAAI,EAAE,SAAS,EAAE;QACxC,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE;KACnC;CACO,CAAC;AAEX;kFACkF;AAClF,MAAM,UAAU,aAAa,CAAC,OAAe;IAC3C,MAAM,CAAC,GAAG,gBAAgB,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;IACzC,IAAI,CAAC,CAAC;QACJ,MAAM,IAAI,KAAK,CAAC,+CAA+C,OAAO,GAAG,CAAC,CAAC;IAC7E,OAAO,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AACtB,CAAC;AAED;sGACsG;AACtG,MAAM,UAAU,aAAa,CAAC,KAAa;IACzC,IAAI,CAAC,qBAAqB,CAAC,IAAI,CAAC,KAAK,CAAC;QACpC,MAAM,IAAI,KAAK,CACb,4FAA4F,KAAK,GAAG,CACrG,CAAC;IACJ,OAAO,KAAK,CAAC,WAAW,EAAS,CAAC;AACpC,CAAC;AAoDD;;;GAGG;AACH,MAAM,UAAU,qBAAqB,CAAC,CAAoB;IAIxD,MAAM,KAAK,GAAG,aAAa,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC;IACvC,MAAM,IAAI,GAAG,UAAU,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;IAChC,MAAM,EAAE,GAAG,UAAU,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;IAC5B,MAAM,aAAa,GAAyB;QAC1C,IAAI;QACJ,EAAE;QACF,KAAK,EAAE,CAAC,CAAC,KAAK;QACd,UAAU,EAAE,CAAC,CAAC,UAAU;QACxB,WAAW,EAAE,CAAC,CAAC,WAAW;QAC1B,KAAK;KACN,CAAC;IACF,MAAM,SAAS,GAAqB;QAClC,MAAM,EAAE;YACN,IAAI,EAAE,CAAC,CAAC,SAAS;YACjB,OAAO,EAAE,CAAC,CAAC,YAAY;YACvB,OAAO,EAAE,CAAC,CAAC,OAAO;YAClB,iBAAiB,EAAE,UAAU,CAAC,CAAC,CAAC,iBAAiB,CAAC;SACnD;QACD,KAAK,EAAE,gCAAgC;QACvC,WAAW,EAAE,2BAA2B;QACxC,OAAO,EAAE;YACP,IAAI;YACJ,EAAE;YACF,KAAK,EAAE,MAAM,CAAC,CAAC,CAAC,KAAK,CAAC;YACtB,UAAU,EAAE,MAAM,CAAC,CAAC,CAAC,UAAU,CAAC;YAChC,WAAW,EAAE,MAAM,CAAC,CAAC,CAAC,WAAW,CAAC;YAClC,KAAK;SACN;KACF,CAAC;IACF,OAAO,EAAE,aAAa,EAAE,SAAS,EAAE,CAAC;AACtC,CAAC"}
@@ -0,0 +1,8 @@
1
+ import type { Eip3009TypedData } from "./eip3009.js";
2
+ /**
3
+ * The EIP-712 digest of an EIP-3009 typed-data message — viem's reference EIP-712 hashing
4
+ * (keccak256(0x1901 ‖ domainSeparator ‖ hashStruct(message))). This is what the signer signs over
5
+ * and what pins the typed-data structure in the vector fixtures.
6
+ */
7
+ export declare function hashEip712(typedData: Eip3009TypedData): `0x${string}`;
8
+ //# sourceMappingURL=eip712.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"eip712.d.ts","sourceRoot":"","sources":["../src/eip712.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAErD;;;;GAIG;AACH,wBAAgB,UAAU,CAAC,SAAS,EAAE,gBAAgB,GAAG,KAAK,MAAM,EAAE,CAErE"}
package/dist/eip712.js ADDED
@@ -0,0 +1,10 @@
1
+ import { hashTypedData } from "viem";
2
+ /**
3
+ * The EIP-712 digest of an EIP-3009 typed-data message — viem's reference EIP-712 hashing
4
+ * (keccak256(0x1901 ‖ domainSeparator ‖ hashStruct(message))). This is what the signer signs over
5
+ * and what pins the typed-data structure in the vector fixtures.
6
+ */
7
+ export function hashEip712(typedData) {
8
+ return hashTypedData(typedData);
9
+ }
10
+ //# sourceMappingURL=eip712.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"eip712.js","sourceRoot":"","sources":["../src/eip712.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,MAAM,CAAC;AAGrC;;;;GAIG;AACH,MAAM,UAAU,UAAU,CAAC,SAA2B;IACpD,OAAO,aAAa,CAAC,SAAS,CAAC,CAAC;AAClC,CAAC"}
@@ -0,0 +1,147 @@
1
+ /**
2
+ * Acceptance-signature verification (TRM-6) — the EVM implementation of `authority`'s `SignatureVerifier`
3
+ * port. viem lives here. It verifies the LCP acceptance envelope: a stock
4
+ * Coinbase Smart Wallet (CDP Server Wallet v2) exposes only `signTypedData`, so raw-`atrHash` signing is
5
+ * refused and the acceptance rides an EIP-712 envelope `Acceptance{ bytes32 atrHash, uint256 signedAt }`
6
+ * under domain `{ name: "LCP Acceptance", version: "1", chainId }` (no verifyingContract). Note:
7
+ * `verifyTypedData` returns true for the stock-SDK signature — counterfactual accounts wrap it ERC-6492,
8
+ * deployed accounts return a plain ERC-1271 sig; both verify through viem's universal validator.
9
+ *
10
+ * Two verification paths, split by scheme:
11
+ * - EOA (`evm:eip191`/`evm:eip712`) — recovered OFFLINE (no chain), so these are the schemes the
12
+ * deterministic conformance corpus and this package's tests exercise;
13
+ * - smart account (`evm:erc1271`/`evm:erc6492`) — verified on-chain through the injected `PublicClient`
14
+ * (viem's universal `verifyTypedData`/`verifyMessage` handle ERC-1271 and the ERC-6492 counterfactual
15
+ * wrapper). No client → fail-loud (never a silent false — that would impeach a valid smart-account
16
+ * signature).
17
+ *
18
+ * `AcceptanceScheme` is an OPEN namespaced set and `signer` is scheme-canonical (rail-neutral core); THIS
19
+ * adapter is where the narrowing happens: it accepts exactly the four `evm:*` schemes and the 0x-address
20
+ * signer form, and throws — never a silent false — on a record that belongs to a different family.
21
+ */
22
+ import type { SignatureVerifier } from "@integraledger/lcp-authority";
23
+ import { type Hex, type PublicClient } from "viem";
24
+ import { type LcpEvmSigner } from "./eip3009.js";
25
+ /** The EIP-712 acceptance-envelope struct. */
26
+ export declare const ACCEPTANCE_ENVELOPE_TYPE: {
27
+ readonly Acceptance: readonly [{
28
+ readonly name: "atrHash";
29
+ readonly type: "bytes32";
30
+ }, {
31
+ readonly name: "signedAt";
32
+ readonly type: "uint256";
33
+ }];
34
+ };
35
+ /** The acceptance envelope's EIP-712 domain — name/version pinned, chainId per network, no verifyingContract. */
36
+ export declare const ACCEPTANCE_DOMAIN_NAME = "LCP Acceptance";
37
+ /** The acceptance envelope's EIP-712 domain `version`. Pinned, and part of what every acceptance
38
+ * signature commits to — changing it invalidates every signature made under the old value, so it moves
39
+ * only alongside a deliberate envelope revision. */
40
+ export declare const ACCEPTANCE_DOMAIN_VERSION = "1";
41
+ /** The typed-data object the accepting party signs (message.signedAt is unix seconds — the on-chain uint256). */
42
+ export type AcceptanceTypedData = {
43
+ domain: {
44
+ name: string;
45
+ version: string;
46
+ chainId: number;
47
+ };
48
+ types: typeof ACCEPTANCE_ENVELOPE_TYPE;
49
+ primaryType: "Acceptance";
50
+ message: {
51
+ atrHash: Hex;
52
+ signedAt: bigint;
53
+ };
54
+ };
55
+ /**
56
+ * Convert TRM-6's RFC 3339 `signedAt` (the acceptance record's timestamp) into the uint256 unix seconds the
57
+ * EIP-712 envelope commits to. Fail-loud on anything Date cannot parse — a mis-parsed timestamp would build
58
+ * a different envelope and silently fail verification, hiding a malformed record behind a bad-signature error.
59
+ */
60
+ export declare function rfc3339ToUnixSeconds(signedAt: string): bigint;
61
+ /** Build the acceptance envelope typed-data for a fixed atrHash + unix-seconds signedAt + chainId. */
62
+ export declare function buildAcceptanceTypedData(i: {
63
+ atrHash: string;
64
+ signedAt: bigint;
65
+ chainId: number;
66
+ }): AcceptanceTypedData;
67
+ /** The four schemes this port implements — the EVM half of the open `AcceptanceScheme` set. */
68
+ export declare const EVM_ACCEPTANCE_SCHEMES: readonly ["evm:eip191", "evm:eip712", "evm:erc1271", "evm:erc6492"];
69
+ /** How an acceptance was signed. Two are EOA schemes (`eip191`, `eip712`) and verify offline; two are
70
+ * smart-account schemes (`erc1271`, `erc6492`) and REQUIRE a chain read, which is why
71
+ * {@link AcceptanceVerifyOpts.client} is conditionally required. */
72
+ export type EvmAcceptanceScheme = (typeof EVM_ACCEPTANCE_SCHEMES)[number];
73
+ /** Narrow a string to an {@link EvmAcceptanceScheme}. The set is closed — an unrecognised scheme is not a
74
+ * scheme this port can verify, and treating it as one would mean reporting an unchecked signature. */
75
+ export declare function isEvmAcceptanceScheme(v: string): v is EvmAcceptanceScheme;
76
+ /** The two payload formats this port can reconstruct for recovery. */
77
+ export declare const EVM_PAYLOAD_TYPES: readonly ["atrHash", "eip712-acceptance-envelope"];
78
+ /** WHAT was signed, as distinct from HOW. `atrHash` means the bare hash was signed;
79
+ * `eip712-acceptance-envelope` means the structured envelope was, and that form additionally needs a
80
+ * `chainId` to reconstruct its domain. The two are not interchangeable — verifying under the wrong one
81
+ * reconstructs a different payload and fails. */
82
+ export type EvmPayloadType = (typeof EVM_PAYLOAD_TYPES)[number];
83
+ /** Narrow a string to an {@link EvmPayloadType}. Closed set, same reason as the scheme guard. */
84
+ export declare function isEvmPayloadType(v: string): v is EvmPayloadType;
85
+ /** The fields the EVM verifier reads off an acceptance record (a structural subset of `SignedAcceptance`). */
86
+ export interface AcceptanceSignatureInput {
87
+ atrHash: string;
88
+ signer: string;
89
+ scheme: EvmAcceptanceScheme;
90
+ signature: string;
91
+ /** RFC 3339 — converted to the envelope's uint256 signedAt. */
92
+ signedAt: string;
93
+ payloadType: EvmPayloadType;
94
+ }
95
+ /** The context an acceptance check may need. Both fields are optional in the TYPE and conditionally
96
+ * REQUIRED in fact: `chainId` for the `eip712-acceptance-envelope` payload, `client` for the
97
+ * smart-account schemes. Omitting a required one is refused rather than defaulted — a signature verified
98
+ * against a guessed domain, or not verified at all, must never read as verified. */
99
+ export interface AcceptanceVerifyOpts {
100
+ /** The EIP-155 chain id binding the envelope domain — REQUIRED for the eip712-acceptance-envelope payload. */
101
+ chainId?: number;
102
+ /** A viem public client — REQUIRED for smart-account (erc1271/erc6492) schemes; unused for EOA schemes. */
103
+ client?: PublicClient;
104
+ }
105
+ /**
106
+ * Verify an acceptance signature. EOA schemes recover offline; smart-account schemes verify on-chain through
107
+ * `opts.client`. Returns a boolean (the port contract).
108
+ *
109
+ * THREE OUTCOMES, and keeping them distinct is the whole point:
110
+ *
111
+ * - **A malformed CONFIGURATION throws** (absent `chainId` for the envelope, absent client for a smart
112
+ * account) — an integration error the caller must fix, not a fact about the record.
113
+ * - **A malformed RECORD throws** (an unparseable `signedAt`, a non-32-byte `atrHash`) — a garbled
114
+ * timestamp is a different fact from a forged signature, and `rfc3339ToUnixSeconds` exists precisely to
115
+ * stop a malformed record hiding behind a bad-signature verdict. These are normalized BEFORE the guard
116
+ * below, so they stay loud.
117
+ * - **A malformed SIGNATURE returns `false`.** Recovery over forged or corrupted bytes fails deep in the
118
+ * curve math ("Point is not on curve"), and a verifier that crashes on a forgery cannot report the
119
+ * forgery — the adversarial case is exactly the one verification exists for. An unrecoverable signature
120
+ * is definitively not a valid signature, so the walk records an honest `failed(verification-failure)`.
121
+ *
122
+ * The guard therefore wraps ONLY the recovery/verification calls. Widening it to cover the normalization
123
+ * above would collapse the second outcome into the third.
124
+ */
125
+ export declare function verifyAcceptanceSignature(input: AcceptanceSignatureInput, opts?: AcceptanceVerifyOpts): Promise<boolean>;
126
+ /**
127
+ * Adapt the EVM verifier to `authority`'s `SignatureVerifier` port (hexagonal: the adapter depends on the
128
+ * domain's port). `authority`'s pure `verifyAcceptance` composes this to check the cryptographic gate.
129
+ *
130
+ * This is where the OPEN acceptance surface narrows to the closed set this port implements. A scheme or
131
+ * payload type outside that set is a ROUTING error — the caller wired the wrong port for the record — and
132
+ * throws loud (the configuration outcome of the three-outcomes doctrine above). Returning `false` instead
133
+ * would report "forged signature" about a signature this port never examined, collapsing "we cannot check
134
+ * this family" into "checked, and it failed" — the exact distinction fail-closed verification exists to keep.
135
+ */
136
+ export declare function makeEvmAcceptanceVerifier(opts?: AcceptanceVerifyOpts): SignatureVerifier;
137
+ /**
138
+ * A local EOA `LcpEvmSigner` over a raw private key (viem's `privateKeyToAccount`). The producer-side
139
+ * counterpart to `makeEvmAcceptanceVerifier`: it keeps viem isolated in this package so a producer
140
+ * can sign the acceptance envelope through the injected `LcpEvmSigner` port without importing viem. The
141
+ * returned signer's `signTypedData` is viem's `LocalAccount.signTypedData`, which produces the exact
142
+ * EIP-712 signature `verifyAcceptanceSignature` recovers offline (eip191/eip712) — producer and verifier
143
+ * agree by construction. This is a reference/test signer: production buyer wallets inject their own
144
+ * `LcpEvmSigner` (a viem account, a CDP server account, or any wallet exposing address + signTypedData).
145
+ */
146
+ export declare function makeLocalEvmSigner(privateKey: Hex): LcpEvmSigner;
147
+ //# sourceMappingURL=erc1271.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"erc1271.d.ts","sourceRoot":"","sources":["../src/erc1271.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,OAAO,KAAK,EACV,iBAAiB,EAElB,MAAM,8BAA8B,CAAC;AACtC,OAAO,EACL,KAAK,GAAG,EAER,KAAK,YAAY,EAGlB,MAAM,MAAM,CAAC;AAEd,OAAO,EAAiB,KAAK,YAAY,EAAE,MAAM,cAAc,CAAC;AAEhE,8CAA8C;AAC9C,eAAO,MAAM,wBAAwB;aACnC,UAAU;iBACN,IAAI,EAAE,SAAS;iBAAE,IAAI,EAAE,SAAS;;iBAChC,IAAI,EAAE,UAAU;iBAAE,IAAI,EAAE,SAAS;;CAE7B,CAAC;AAEX,iHAAiH;AACjH,eAAO,MAAM,sBAAsB,mBAAmB,CAAC;AACvD;;qDAEqD;AACrD,eAAO,MAAM,yBAAyB,MAAM,CAAC;AAE7C,iHAAiH;AACjH,MAAM,MAAM,mBAAmB,GAAG;IAChC,MAAM,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC;IAC3D,KAAK,EAAE,OAAO,wBAAwB,CAAC;IACvC,WAAW,EAAE,YAAY,CAAC;IAC1B,OAAO,EAAE;QAAE,OAAO,EAAE,GAAG,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,CAAC;CAC7C,CAAC;AAEF;;;;GAIG;AACH,wBAAgB,oBAAoB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAO7D;AAED,sGAAsG;AACtG,wBAAgB,wBAAwB,CAAC,CAAC,EAAE;IAC1C,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;CACjB,GAAG,mBAAmB,CAWtB;AAED,+FAA+F;AAC/F,eAAO,MAAM,sBAAsB,YACjC,YAAY,EACZ,YAAY,EACZ,aAAa,EACb,aAAa,CACL,CAAC;AACX;;qEAEqE;AACrE,MAAM,MAAM,mBAAmB,GAAG,CAAC,OAAO,sBAAsB,CAAC,CAAC,MAAM,CAAC,CAAC;AAE1E;uGACuG;AACvG,wBAAgB,qBAAqB,CAAC,CAAC,EAAE,MAAM,GAAG,CAAC,IAAI,mBAAmB,CAEzE;AAED,sEAAsE;AACtE,eAAO,MAAM,iBAAiB,YAC5B,SAAS,EACT,4BAA4B,CACpB,CAAC;AACX;;;kDAGkD;AAClD,MAAM,MAAM,cAAc,GAAG,CAAC,OAAO,iBAAiB,CAAC,CAAC,MAAM,CAAC,CAAC;AAEhE,iGAAiG;AACjG,wBAAgB,gBAAgB,CAAC,CAAC,EAAE,MAAM,GAAG,CAAC,IAAI,cAAc,CAE/D;AAED,8GAA8G;AAC9G,MAAM,WAAW,wBAAwB;IACvC,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,mBAAmB,CAAC;IAC5B,SAAS,EAAE,MAAM,CAAC;IAClB,+DAA+D;IAC/D,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,cAAc,CAAC;CAC7B;AAED;;;qFAGqF;AACrF,MAAM,WAAW,oBAAoB;IACnC,8GAA8G;IAC9G,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,2GAA2G;IAC3G,MAAM,CAAC,EAAE,YAAY,CAAC;CACvB;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAsB,yBAAyB,CAC7C,KAAK,EAAE,wBAAwB,EAC/B,IAAI,CAAC,EAAE,oBAAoB,GAC1B,OAAO,CAAC,OAAO,CAAC,CAQlB;AAgFD;;;;;;;;;GASG;AACH,wBAAgB,yBAAyB,CACvC,IAAI,CAAC,EAAE,oBAAoB,GAC1B,iBAAiB,CA0BnB;AAED;;;;;;;;GAQG;AACH,wBAAgB,kBAAkB,CAAC,UAAU,EAAE,GAAG,GAAG,YAAY,CAchE"}
@@ -0,0 +1,197 @@
1
+ import { isAddress, recoverMessageAddress, recoverTypedDataAddress, } from "viem";
2
+ import { privateKeyToAccount } from "viem/accounts";
3
+ import { assertBytes32 } from "./eip3009.js";
4
+ /** The EIP-712 acceptance-envelope struct. */
5
+ export const ACCEPTANCE_ENVELOPE_TYPE = {
6
+ Acceptance: [
7
+ { name: "atrHash", type: "bytes32" },
8
+ { name: "signedAt", type: "uint256" },
9
+ ],
10
+ };
11
+ /** The acceptance envelope's EIP-712 domain — name/version pinned, chainId per network, no verifyingContract. */
12
+ export const ACCEPTANCE_DOMAIN_NAME = "LCP Acceptance";
13
+ /** The acceptance envelope's EIP-712 domain `version`. Pinned, and part of what every acceptance
14
+ * signature commits to — changing it invalidates every signature made under the old value, so it moves
15
+ * only alongside a deliberate envelope revision. */
16
+ export const ACCEPTANCE_DOMAIN_VERSION = "1";
17
+ /**
18
+ * Convert TRM-6's RFC 3339 `signedAt` (the acceptance record's timestamp) into the uint256 unix seconds the
19
+ * EIP-712 envelope commits to. Fail-loud on anything Date cannot parse — a mis-parsed timestamp would build
20
+ * a different envelope and silently fail verification, hiding a malformed record behind a bad-signature error.
21
+ */
22
+ export function rfc3339ToUnixSeconds(signedAt) {
23
+ const ms = Date.parse(signedAt);
24
+ if (Number.isNaN(ms))
25
+ throw new Error(`acceptance signedAt is not a parseable RFC 3339 timestamp: "${signedAt}"`);
26
+ return BigInt(Math.floor(ms / 1000));
27
+ }
28
+ /** Build the acceptance envelope typed-data for a fixed atrHash + unix-seconds signedAt + chainId. */
29
+ export function buildAcceptanceTypedData(i) {
30
+ return {
31
+ domain: {
32
+ name: ACCEPTANCE_DOMAIN_NAME,
33
+ version: ACCEPTANCE_DOMAIN_VERSION,
34
+ chainId: i.chainId,
35
+ },
36
+ types: ACCEPTANCE_ENVELOPE_TYPE,
37
+ primaryType: "Acceptance",
38
+ message: { atrHash: assertBytes32(i.atrHash), signedAt: i.signedAt },
39
+ };
40
+ }
41
+ /** The four schemes this port implements — the EVM half of the open `AcceptanceScheme` set. */
42
+ export const EVM_ACCEPTANCE_SCHEMES = [
43
+ "evm:eip191",
44
+ "evm:eip712",
45
+ "evm:erc1271",
46
+ "evm:erc6492",
47
+ ];
48
+ /** Narrow a string to an {@link EvmAcceptanceScheme}. The set is closed — an unrecognised scheme is not a
49
+ * scheme this port can verify, and treating it as one would mean reporting an unchecked signature. */
50
+ export function isEvmAcceptanceScheme(v) {
51
+ return EVM_ACCEPTANCE_SCHEMES.includes(v);
52
+ }
53
+ /** The two payload formats this port can reconstruct for recovery. */
54
+ export const EVM_PAYLOAD_TYPES = [
55
+ "atrHash",
56
+ "eip712-acceptance-envelope",
57
+ ];
58
+ /** Narrow a string to an {@link EvmPayloadType}. Closed set, same reason as the scheme guard. */
59
+ export function isEvmPayloadType(v) {
60
+ return EVM_PAYLOAD_TYPES.includes(v);
61
+ }
62
+ /**
63
+ * Verify an acceptance signature. EOA schemes recover offline; smart-account schemes verify on-chain through
64
+ * `opts.client`. Returns a boolean (the port contract).
65
+ *
66
+ * THREE OUTCOMES, and keeping them distinct is the whole point:
67
+ *
68
+ * - **A malformed CONFIGURATION throws** (absent `chainId` for the envelope, absent client for a smart
69
+ * account) — an integration error the caller must fix, not a fact about the record.
70
+ * - **A malformed RECORD throws** (an unparseable `signedAt`, a non-32-byte `atrHash`) — a garbled
71
+ * timestamp is a different fact from a forged signature, and `rfc3339ToUnixSeconds` exists precisely to
72
+ * stop a malformed record hiding behind a bad-signature verdict. These are normalized BEFORE the guard
73
+ * below, so they stay loud.
74
+ * - **A malformed SIGNATURE returns `false`.** Recovery over forged or corrupted bytes fails deep in the
75
+ * curve math ("Point is not on curve"), and a verifier that crashes on a forgery cannot report the
76
+ * forgery — the adversarial case is exactly the one verification exists for. An unrecoverable signature
77
+ * is definitively not a valid signature, so the walk records an honest `failed(verification-failure)`.
78
+ *
79
+ * The guard therefore wraps ONLY the recovery/verification calls. Widening it to cover the normalization
80
+ * above would collapse the second outcome into the third.
81
+ */
82
+ export async function verifyAcceptanceSignature(input, opts) {
83
+ // Normalization + configuration: OUTSIDE the guard, so both stay loud.
84
+ const recover = prepareRecovery(input, opts);
85
+ try {
86
+ return await recover();
87
+ }
88
+ catch {
89
+ return false; // the signature itself did not recover — not a valid signature
90
+ }
91
+ }
92
+ /** Raised for integration errors (absent chainId/client). */
93
+ class ConfigurationError extends Error {
94
+ }
95
+ /**
96
+ * Normalize the record's fields and bind the scheme-appropriate recovery call, returning it UNEXECUTED so
97
+ * the caller can guard the recovery alone. Anything wrong with the configuration or the record's own fields
98
+ * throws from here, before the guard exists.
99
+ */
100
+ function prepareRecovery(input, opts) {
101
+ // A signer that is not an address is a malformed RECORD, not a forgery — thrown here, before the guard,
102
+ // for the same reason rfc3339ToUnixSeconds throws: recovery over it would fail deep in viem and read as
103
+ // a bad-signature verdict about a signature nobody could have examined.
104
+ if (!isAddress(input.signer, { strict: false }))
105
+ throw new Error(`evm:* acceptance signer must be a 0x-address (the scheme's canonical form); got "${input.signer}"`);
106
+ const signer = input.signer;
107
+ const isContract = input.scheme === "evm:erc1271" || input.scheme === "evm:erc6492";
108
+ const signature = input.signature;
109
+ if (input.payloadType === "eip712-acceptance-envelope") {
110
+ if (opts?.chainId === undefined)
111
+ throw new ConfigurationError("eip712-acceptance-envelope verification requires opts.chainId to bind the envelope domain");
112
+ // Throws loud on an unparseable signedAt or a malformed atrHash — a malformed RECORD, not a forgery.
113
+ const typedData = buildAcceptanceTypedData({
114
+ atrHash: input.atrHash,
115
+ signedAt: rfc3339ToUnixSeconds(input.signedAt),
116
+ chainId: opts.chainId,
117
+ });
118
+ if (isContract) {
119
+ // `opts` (not `opts?.`): the chainId gate above already proved it defined — an optional chain here
120
+ // would be dead syntax guarding an impossible state, and the mutation run flags it as equivalent.
121
+ const client = requireClient(opts.client, input.scheme);
122
+ return () => client.verifyTypedData({ address: signer, ...typedData, signature });
123
+ }
124
+ return async () => {
125
+ const recovered = await recoverTypedDataAddress({
126
+ ...typedData,
127
+ signature,
128
+ });
129
+ return recovered.toLowerCase() === signer.toLowerCase();
130
+ };
131
+ }
132
+ // payloadType === "atrHash": the raw 32-byte atrHash signed as a personal_sign message.
133
+ const raw = assertBytes32(input.atrHash); // throws loud on a malformed atrHash
134
+ if (isContract) {
135
+ const client = requireClient(opts?.client, input.scheme);
136
+ return () => client.verifyMessage({ address: signer, message: { raw }, signature });
137
+ }
138
+ return async () => {
139
+ const recovered = await recoverMessageAddress({
140
+ message: { raw },
141
+ signature,
142
+ });
143
+ return recovered.toLowerCase() === signer.toLowerCase();
144
+ };
145
+ }
146
+ function requireClient(client, scheme) {
147
+ if (client === undefined)
148
+ throw new ConfigurationError(`${scheme} verification requires a chain client (opts.client) — smart-account signatures verify on-chain (ERC-1271/6492)`);
149
+ return client;
150
+ }
151
+ /**
152
+ * Adapt the EVM verifier to `authority`'s `SignatureVerifier` port (hexagonal: the adapter depends on the
153
+ * domain's port). `authority`'s pure `verifyAcceptance` composes this to check the cryptographic gate.
154
+ *
155
+ * This is where the OPEN acceptance surface narrows to the closed set this port implements. A scheme or
156
+ * payload type outside that set is a ROUTING error — the caller wired the wrong port for the record — and
157
+ * throws loud (the configuration outcome of the three-outcomes doctrine above). Returning `false` instead
158
+ * would report "forged signature" about a signature this port never examined, collapsing "we cannot check
159
+ * this family" into "checked, and it failed" — the exact distinction fail-closed verification exists to keep.
160
+ */
161
+ export function makeEvmAcceptanceVerifier(opts) {
162
+ return {
163
+ // async so the routing throws below surface as REJECTIONS — the same channel the
164
+ // configuration/record throws inside `verifyAcceptanceSignature` already use.
165
+ verify: async (acceptance) => {
166
+ if (!isEvmAcceptanceScheme(acceptance.scheme))
167
+ throw new ConfigurationError(`scheme "${acceptance.scheme}" is not an EVM acceptance scheme (${EVM_ACCEPTANCE_SCHEMES.join(", ")}) — route the record to the port that implements its family`);
168
+ if (!isEvmPayloadType(acceptance.payloadType))
169
+ throw new ConfigurationError(`payloadType "${acceptance.payloadType}" is not one this port reconstructs (${EVM_PAYLOAD_TYPES.join(", ")})`);
170
+ return verifyAcceptanceSignature({
171
+ atrHash: acceptance.atrHash,
172
+ signer: acceptance.signer,
173
+ scheme: acceptance.scheme,
174
+ signature: acceptance.signature,
175
+ signedAt: acceptance.signedAt,
176
+ payloadType: acceptance.payloadType,
177
+ }, opts);
178
+ },
179
+ };
180
+ }
181
+ /**
182
+ * A local EOA `LcpEvmSigner` over a raw private key (viem's `privateKeyToAccount`). The producer-side
183
+ * counterpart to `makeEvmAcceptanceVerifier`: it keeps viem isolated in this package so a producer
184
+ * can sign the acceptance envelope through the injected `LcpEvmSigner` port without importing viem. The
185
+ * returned signer's `signTypedData` is viem's `LocalAccount.signTypedData`, which produces the exact
186
+ * EIP-712 signature `verifyAcceptanceSignature` recovers offline (eip191/eip712) — producer and verifier
187
+ * agree by construction. This is a reference/test signer: production buyer wallets inject their own
188
+ * `LcpEvmSigner` (a viem account, a CDP server account, or any wallet exposing address + signTypedData).
189
+ */
190
+ export function makeLocalEvmSigner(privateKey) {
191
+ const account = privateKeyToAccount(privateKey);
192
+ return {
193
+ address: account.address,
194
+ signTypedData: (message) => account.signTypedData(message),
195
+ };
196
+ }
197
+ //# sourceMappingURL=erc1271.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"erc1271.js","sourceRoot":"","sources":["../src/erc1271.ts"],"names":[],"mappings":"AAyBA,OAAO,EAEL,SAAS,EAET,qBAAqB,EACrB,uBAAuB,GACxB,MAAM,MAAM,CAAC;AACd,OAAO,EAAE,mBAAmB,EAAE,MAAM,eAAe,CAAC;AACpD,OAAO,EAAE,aAAa,EAAqB,MAAM,cAAc,CAAC;AAEhE,8CAA8C;AAC9C,MAAM,CAAC,MAAM,wBAAwB,GAAG;IACtC,UAAU,EAAE;QACV,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,EAAE,SAAS,EAAE;QACpC,EAAE,IAAI,EAAE,UAAU,EAAE,IAAI,EAAE,SAAS,EAAE;KACtC;CACO,CAAC;AAEX,iHAAiH;AACjH,MAAM,CAAC,MAAM,sBAAsB,GAAG,gBAAgB,CAAC;AACvD;;qDAEqD;AACrD,MAAM,CAAC,MAAM,yBAAyB,GAAG,GAAG,CAAC;AAU7C;;;;GAIG;AACH,MAAM,UAAU,oBAAoB,CAAC,QAAgB;IACnD,MAAM,EAAE,GAAG,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC;IAChC,IAAI,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC;QAClB,MAAM,IAAI,KAAK,CACb,+DAA+D,QAAQ,GAAG,CAC3E,CAAC;IACJ,OAAO,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,GAAG,IAAI,CAAC,CAAC,CAAC;AACvC,CAAC;AAED,sGAAsG;AACtG,MAAM,UAAU,wBAAwB,CAAC,CAIxC;IACC,OAAO;QACL,MAAM,EAAE;YACN,IAAI,EAAE,sBAAsB;YAC5B,OAAO,EAAE,yBAAyB;YAClC,OAAO,EAAE,CAAC,CAAC,OAAO;SACnB;QACD,KAAK,EAAE,wBAAwB;QAC/B,WAAW,EAAE,YAAY;QACzB,OAAO,EAAE,EAAE,OAAO,EAAE,aAAa,CAAC,CAAC,CAAC,OAAO,CAAC,EAAE,QAAQ,EAAE,CAAC,CAAC,QAAQ,EAAE;KACrE,CAAC;AACJ,CAAC;AAED,+FAA+F;AAC/F,MAAM,CAAC,MAAM,sBAAsB,GAAG;IACpC,YAAY;IACZ,YAAY;IACZ,aAAa;IACb,aAAa;CACL,CAAC;AAMX;uGACuG;AACvG,MAAM,UAAU,qBAAqB,CAAC,CAAS;IAC7C,OAAQ,sBAA4C,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC;AACnE,CAAC;AAED,sEAAsE;AACtE,MAAM,CAAC,MAAM,iBAAiB,GAAG;IAC/B,SAAS;IACT,4BAA4B;CACpB,CAAC;AAOX,iGAAiG;AACjG,MAAM,UAAU,gBAAgB,CAAC,CAAS;IACxC,OAAQ,iBAAuC,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC;AAC9D,CAAC;AAwBD;;;;;;;;;;;;;;;;;;;GAmBG;AACH,MAAM,CAAC,KAAK,UAAU,yBAAyB,CAC7C,KAA+B,EAC/B,IAA2B;IAE3B,uEAAuE;IACvE,MAAM,OAAO,GAAG,eAAe,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC;IAC7C,IAAI,CAAC;QACH,OAAO,MAAM,OAAO,EAAE,CAAC;IACzB,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC,CAAC,+DAA+D;IAC/E,CAAC;AACH,CAAC;AAED,6DAA6D;AAC7D,MAAM,kBAAmB,SAAQ,KAAK;CAAG;AAEzC;;;;GAIG;AACH,SAAS,eAAe,CACtB,KAA+B,EAC/B,IAA2B;IAE3B,wGAAwG;IACxG,wGAAwG;IACxG,wEAAwE;IACxE,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,MAAM,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC;QAC7C,MAAM,IAAI,KAAK,CACb,oFAAoF,KAAK,CAAC,MAAM,GAAG,CACpG,CAAC;IACJ,MAAM,MAAM,GAAG,KAAK,CAAC,MAAa,CAAC;IACnC,MAAM,UAAU,GACd,KAAK,CAAC,MAAM,KAAK,aAAa,IAAI,KAAK,CAAC,MAAM,KAAK,aAAa,CAAC;IACnE,MAAM,SAAS,GAAG,KAAK,CAAC,SAAgB,CAAC;IAEzC,IAAI,KAAK,CAAC,WAAW,KAAK,4BAA4B,EAAE,CAAC;QACvD,IAAI,IAAI,EAAE,OAAO,KAAK,SAAS;YAC7B,MAAM,IAAI,kBAAkB,CAC1B,2FAA2F,CAC5F,CAAC;QACJ,qGAAqG;QACrG,MAAM,SAAS,GAAG,wBAAwB,CAAC;YACzC,OAAO,EAAE,KAAK,CAAC,OAAO;YACtB,QAAQ,EAAE,oBAAoB,CAAC,KAAK,CAAC,QAAQ,CAAC;YAC9C,OAAO,EAAE,IAAI,CAAC,OAAO;SACtB,CAAC,CAAC;QACH,IAAI,UAAU,EAAE,CAAC;YACf,mGAAmG;YACnG,kGAAkG;YAClG,MAAM,MAAM,GAAG,aAAa,CAAC,IAAI,CAAC,MAAM,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;YACxD,OAAO,GAAG,EAAE,CACV,MAAM,CAAC,eAAe,CAAC,EAAE,OAAO,EAAE,MAAM,EAAE,GAAG,SAAS,EAAE,SAAS,EAAE,CAAC,CAAC;QACzE,CAAC;QACD,OAAO,KAAK,IAAI,EAAE;YAChB,MAAM,SAAS,GAAG,MAAM,uBAAuB,CAAC;gBAC9C,GAAG,SAAS;gBACZ,SAAS;aACV,CAAC,CAAC;YACH,OAAO,SAAS,CAAC,WAAW,EAAE,KAAK,MAAM,CAAC,WAAW,EAAE,CAAC;QAC1D,CAAC,CAAC;IACJ,CAAC;IAED,wFAAwF;IACxF,MAAM,GAAG,GAAG,aAAa,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,qCAAqC;IAC/E,IAAI,UAAU,EAAE,CAAC;QACf,MAAM,MAAM,GAAG,aAAa,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;QACzD,OAAO,GAAG,EAAE,CACV,MAAM,CAAC,aAAa,CAAC,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,EAAE,GAAG,EAAE,EAAE,SAAS,EAAE,CAAC,CAAC;IAC3E,CAAC;IACD,OAAO,KAAK,IAAI,EAAE;QAChB,MAAM,SAAS,GAAG,MAAM,qBAAqB,CAAC;YAC5C,OAAO,EAAE,EAAE,GAAG,EAAE;YAChB,SAAS;SACV,CAAC,CAAC;QACH,OAAO,SAAS,CAAC,WAAW,EAAE,KAAK,MAAM,CAAC,WAAW,EAAE,CAAC;IAC1D,CAAC,CAAC;AACJ,CAAC;AAED,SAAS,aAAa,CACpB,MAAgC,EAChC,MAAc;IAEd,IAAI,MAAM,KAAK,SAAS;QACtB,MAAM,IAAI,kBAAkB,CAC1B,GAAG,MAAM,gHAAgH,CAC1H,CAAC;IACJ,OAAO,MAAM,CAAC;AAChB,CAAC;AAED;;;;;;;;;GASG;AACH,MAAM,UAAU,yBAAyB,CACvC,IAA2B;IAE3B,OAAO;QACL,iFAAiF;QACjF,8EAA8E;QAC9E,MAAM,EAAE,KAAK,EAAE,UAA4B,EAAoB,EAAE;YAC/D,IAAI,CAAC,qBAAqB,CAAC,UAAU,CAAC,MAAM,CAAC;gBAC3C,MAAM,IAAI,kBAAkB,CAC1B,WAAW,UAAU,CAAC,MAAM,sCAAsC,sBAAsB,CAAC,IAAI,CAAC,IAAI,CAAC,6DAA6D,CACjK,CAAC;YACJ,IAAI,CAAC,gBAAgB,CAAC,UAAU,CAAC,WAAW,CAAC;gBAC3C,MAAM,IAAI,kBAAkB,CAC1B,gBAAgB,UAAU,CAAC,WAAW,wCAAwC,iBAAiB,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAC9G,CAAC;YACJ,OAAO,yBAAyB,CAC9B;gBACE,OAAO,EAAE,UAAU,CAAC,OAAO;gBAC3B,MAAM,EAAE,UAAU,CAAC,MAAM;gBACzB,MAAM,EAAE,UAAU,CAAC,MAAM;gBACzB,SAAS,EAAE,UAAU,CAAC,SAAS;gBAC/B,QAAQ,EAAE,UAAU,CAAC,QAAQ;gBAC7B,WAAW,EAAE,UAAU,CAAC,WAAW;aACpC,EACD,IAAI,CACL,CAAC;QACJ,CAAC;KACF,CAAC;AACJ,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,kBAAkB,CAAC,UAAe;IAChD,MAAM,OAAO,GAAG,mBAAmB,CAAC,UAAU,CAAC,CAAC;IAChD,OAAO;QACL,OAAO,EAAE,OAAO,CAAC,OAAO;QACxB,aAAa,EAAE,CAAC,OAKf,EAAgB,EAAE,CACjB,OAAO,CAAC,aAAa,CACnB,OAAsD,CACvD;KACJ,CAAC;AACJ,CAAC"}
@@ -0,0 +1,36 @@
1
+ /**
2
+ * The one piece of ERC-20 knowledge the EVM welds share: did this transaction move a given token at all?
3
+ *
4
+ * It lives here because two rails need the same answer for the same reason, and the reason is not
5
+ * MPP-specific or x402-specific. Every EVM weld reads its binding out of an event the settlement emits. When
6
+ * that event is absent, the absence has TWO meanings and they are not the same report:
7
+ *
8
+ * - the token did not move ⇒ this transaction settled none of that asset. A true, useful answer.
9
+ * - the token DID move ⇒ the transaction settled, through a path this binding cannot read the weld from.
10
+ * Reporting that as an absence is a silent wrong answer at a verification boundary.
11
+ *
12
+ * The rails differ in WHICH unreadable path they are looking at — MPP's three non-`authorization` credential
13
+ * types, x402's Permit2 fallback — so each states its own refusal in its own vocabulary. What they share is
14
+ * this predicate, and duplicating it once per rail is how the two drift.
15
+ *
16
+ * Topic-0 identity only, deliberately: this module classifies, it never decodes amounts or recipients.
17
+ * Matching a transfer against an offer's terms is the settle path's job (`executedTransferCoversOffer`), and
18
+ * doing it here would be a second, weaker copy of a check that already has an owner.
19
+ */
20
+ import type { Log } from "viem";
21
+ /**
22
+ * `keccak256("Transfer(address,address,uint256)")` — ERC-20's `Transfer` event topic0.
23
+ *
24
+ * Derived by two independent keccak-256 oracles that agree byte-for-byte, neither of them viem:
25
+ * `cast keccak 'Transfer(address,address,uint256)'` and pycryptodome `keccak(digest_bits=256)`.
26
+ */
27
+ export declare const ERC20_TRANSFER_TOPIC0 = "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef";
28
+ /**
29
+ * Did this transaction move the configured token at all?
30
+ *
31
+ * Topic-0 identity only — no `decodeEventLog`, because the question is "did this asset move", not "how much,
32
+ * to whom". An indexed-parameter count is not checked either: any log the token emitted under ERC-20's
33
+ * `Transfer` signature settles the question this predicate is asked.
34
+ */
35
+ export declare function assetWasTransferred(logs: readonly Log[], asset: string): boolean;
36
+ //# sourceMappingURL=erc20.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"erc20.d.ts","sourceRoot":"","sources":["../src/erc20.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AACH,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,MAAM,CAAC;AAEhC;;;;;GAKG;AACH,eAAO,MAAM,qBAAqB,uEACoC,CAAC;AAEvE;;;;;;GAMG;AACH,wBAAgB,mBAAmB,CACjC,IAAI,EAAE,SAAS,GAAG,EAAE,EACpB,KAAK,EAAE,MAAM,GACZ,OAAO,CAOT"}
package/dist/erc20.js ADDED
@@ -0,0 +1,20 @@
1
+ /**
2
+ * `keccak256("Transfer(address,address,uint256)")` — ERC-20's `Transfer` event topic0.
3
+ *
4
+ * Derived by two independent keccak-256 oracles that agree byte-for-byte, neither of them viem:
5
+ * `cast keccak 'Transfer(address,address,uint256)'` and pycryptodome `keccak(digest_bits=256)`.
6
+ */
7
+ export const ERC20_TRANSFER_TOPIC0 = "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef";
8
+ /**
9
+ * Did this transaction move the configured token at all?
10
+ *
11
+ * Topic-0 identity only — no `decodeEventLog`, because the question is "did this asset move", not "how much,
12
+ * to whom". An indexed-parameter count is not checked either: any log the token emitted under ERC-20's
13
+ * `Transfer` signature settles the question this predicate is asked.
14
+ */
15
+ export function assetWasTransferred(logs, asset) {
16
+ const want = asset.toLowerCase();
17
+ return logs.some((log) => log.address.toLowerCase() === want &&
18
+ log.topics[0]?.toLowerCase() === ERC20_TRANSFER_TOPIC0);
19
+ }
20
+ //# sourceMappingURL=erc20.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"erc20.js","sourceRoot":"","sources":["../src/erc20.ts"],"names":[],"mappings":"AAqBA;;;;;GAKG;AACH,MAAM,CAAC,MAAM,qBAAqB,GAChC,oEAAoE,CAAC;AAEvE;;;;;;GAMG;AACH,MAAM,UAAU,mBAAmB,CACjC,IAAoB,EACpB,KAAa;IAEb,MAAM,IAAI,GAAG,KAAK,CAAC,WAAW,EAAE,CAAC;IACjC,OAAO,IAAI,CAAC,IAAI,CACd,CAAC,GAAG,EAAE,EAAE,CACN,GAAG,CAAC,OAAO,CAAC,WAAW,EAAE,KAAK,IAAI;QAClC,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,WAAW,EAAE,KAAK,qBAAqB,CACzD,CAAC;AACJ,CAAC"}
@@ -0,0 +1,67 @@
1
+ /**
2
+ * On-chain event helpers for the EIP-3009 binding: the `AuthorizationUsed` ABI and the transport-free
3
+ * atrHash-recovery scan (the caller fetches the receipt with its own viem client and passes `{ logs }`).
4
+ */
5
+ import type { SettlementRef } from "@integraledger/lcp-binding-core";
6
+ import { type Hex, type Log } from "viem";
7
+ /** Build a `SettlementRef` without materializing absent fields — `exactOptionalPropertyTypes` forbids
8
+ * `txHash: undefined`, so the spreads keep the ref exactly as sparse as the caller's knowledge. Shared
9
+ * by the EVM adapters (it was byte-identical in two of them and inlined in the third). */
10
+ export declare function refOf(chainId: number, txHash: `0x${string}` | undefined, logIndex: number | null): SettlementRef;
11
+ /** USDC `AuthorizationUsed(authorizer, nonce)` — both indexed; the on-chain artifact of every EIP-3009
12
+ * settlement. `nonce` carries the atrHash (WLD-3: recoverable, forward-indexable on the nonce topic). */
13
+ export declare const AUTHORIZATION_USED_ABI: readonly [{
14
+ readonly type: "event";
15
+ readonly name: "AuthorizationUsed";
16
+ readonly inputs: readonly [{
17
+ readonly name: "authorizer";
18
+ readonly type: "address";
19
+ readonly indexed: true;
20
+ }, {
21
+ readonly name: "nonce";
22
+ readonly type: "bytes32";
23
+ readonly indexed: true;
24
+ }];
25
+ readonly anonymous: false;
26
+ }];
27
+ /** The answer to one question: did this settlement transaction commit this atrHash as an EIP-3009 nonce?
28
+ * `ok` is the whole verdict and `onChainNonce` is the matched value for a report to quote. It proves the
29
+ * NONCE matched — it says nothing about which token moved, or how much, which is a separate check. */
30
+ export interface OnChainAtrHashProof {
31
+ /** True iff the settlement tx emitted `AuthorizationUsed` with `nonce === atrHash`. */
32
+ ok: boolean;
33
+ /** The on-chain nonce that matched (lowercase 0x hex), or `null` if none did. */
34
+ onChainNonce: Hex | null;
35
+ }
36
+ /** A decoded `AuthorizationUsed` occurrence: the nonce it committed and where it sat in the tx. */
37
+ export interface AuthorizationUsedEvent {
38
+ /** The committed nonce (lowercase 0x hex) — the atrHash on the x402 binding. */
39
+ nonce: Hex;
40
+ /** The authorizing account (lowercase 0x hex) — EIP-3009's `authorizer`, the account the executed
41
+ * transfer draws from. Faithful decoding of the event's first indexed argument, NOT an identity
42
+ * attestation: who the counterparty IS remains the acceptance/authority layer's claim. Carried so a
43
+ * consumer can pair this authorization with the `Transfer` it executed in the same settlement. */
44
+ authorizer: Hex;
45
+ /** The log's position in the transaction, or `null` if the source log carried none. */
46
+ logIndex: number | null;
47
+ /** The emitting contract (lowercase 0x hex) — the payment token. */
48
+ address: Hex;
49
+ }
50
+ /**
51
+ * Decode every `AuthorizationUsed(authorizer, indexed nonce)` in a set of logs (a single settlement's
52
+ * logs, from `ChainReader.getTransactionLogs`, or a range query). This is the EXTRACT path (recover the
53
+ * committed nonce), distinct from `verifyAtrHashOnChain`'s CHECK path (assert a known nonce is present).
54
+ * When `asset` is supplied only that token's events are read; otherwise every AuthorizationUsed is decoded.
55
+ */
56
+ export declare function readAuthorizationUsed(logs: readonly Log[], asset?: string): AuthorizationUsedEvent[];
57
+ /**
58
+ * Confirm the atrHash was committed on-chain: scan a settlement tx receipt for the token's
59
+ * `AuthorizationUsed(authorizer, indexed nonce)` event and assert `nonce === atrHash` (case-insensitive).
60
+ */
61
+ export declare function verifyAtrHashOnChain(receipt: {
62
+ logs: readonly Log[];
63
+ }, params: {
64
+ asset: string;
65
+ atrHash: string;
66
+ }): OnChainAtrHashProof;
67
+ //# sourceMappingURL=events.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"events.d.ts","sourceRoot":"","sources":["../src/events.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,iCAAiC,CAAC;AAErE,OAAO,EAAkB,KAAK,GAAG,EAAE,KAAK,GAAG,EAAE,MAAM,MAAM,CAAC;AAE1D;;2FAE2F;AAC3F,wBAAgB,KAAK,CACnB,OAAO,EAAE,MAAM,EACf,MAAM,EAAE,KAAK,MAAM,EAAE,GAAG,SAAS,EACjC,QAAQ,EAAE,MAAM,GAAG,IAAI,GACtB,aAAa,CAMf;AAED;0GAC0G;AAC1G,eAAO,MAAM,sBAAsB;aAE/B,IAAI,EAAE,OAAO;aACb,IAAI,EAAE,mBAAmB;aACzB,MAAM;iBACF,IAAI,EAAE,YAAY;iBAAE,IAAI,EAAE,SAAS;iBAAE,OAAO;;iBAC5C,IAAI,EAAE,OAAO;iBAAE,IAAI,EAAE,SAAS;iBAAE,OAAO;;aAE3C,SAAS;EAEH,CAAC;AAEX;;uGAEuG;AACvG,MAAM,WAAW,mBAAmB;IAClC,uFAAuF;IACvF,EAAE,EAAE,OAAO,CAAC;IACZ,iFAAiF;IACjF,YAAY,EAAE,GAAG,GAAG,IAAI,CAAC;CAC1B;AAED,mGAAmG;AACnG,MAAM,WAAW,sBAAsB;IACrC,gFAAgF;IAChF,KAAK,EAAE,GAAG,CAAC;IACX;;;uGAGmG;IACnG,UAAU,EAAE,GAAG,CAAC;IAChB,uFAAuF;IACvF,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,oEAAoE;IACpE,OAAO,EAAE,GAAG,CAAC;CACd;AAED;;;;;GAKG;AACH,wBAAgB,qBAAqB,CACnC,IAAI,EAAE,SAAS,GAAG,EAAE,EACpB,KAAK,CAAC,EAAE,MAAM,GACb,sBAAsB,EAAE,CAwB1B;AAED;;;GAGG;AACH,wBAAgB,oBAAoB,CAClC,OAAO,EAAE;IAAE,IAAI,EAAE,SAAS,GAAG,EAAE,CAAA;CAAE,EACjC,MAAM,EAAE;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,GACzC,mBAAmB,CAoBrB"}