@integraledger/lcp-binding-evm-mpp 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.
- package/CHANGELOG.md +13 -0
- package/LICENSE +202 -0
- package/NOTICE +14 -0
- package/README.md +198 -0
- package/dist/adapter.d.ts +59 -0
- package/dist/adapter.d.ts.map +1 -0
- package/dist/adapter.js +76 -0
- package/dist/adapter.js.map +1 -0
- package/dist/credential-type.d.ts +42 -0
- package/dist/credential-type.d.ts.map +1 -0
- package/dist/credential-type.js +17 -0
- package/dist/credential-type.js.map +1 -0
- package/dist/id-reuse.d.ts +130 -0
- package/dist/id-reuse.d.ts.map +1 -0
- package/dist/id-reuse.js +166 -0
- package/dist/id-reuse.js.map +1 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +8 -0
- package/dist/index.js.map +1 -0
- package/dist/manifest.d.ts +66 -0
- package/dist/manifest.d.ts.map +1 -0
- package/dist/manifest.js +83 -0
- package/dist/manifest.js.map +1 -0
- package/package.json +63 -0
- package/src/adapter.ts +192 -0
- package/src/credential-type.ts +49 -0
- package/src/id-reuse.ts +210 -0
- package/src/index.ts +18 -0
- package/src/manifest.ts +84 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"credential-type.js","sourceRoot":"","sources":["../src/credential-type.ts"],"names":[],"mappings":"AAiCA;;;;;;;GAOG;AACH,MAAM,UAAU,8BAA8B,CAAC,KAAa;IAC1D,OAAO;QACL,OAAO,EAAE,IAAI;QACb,SAAS,EAAE,sBAAsB;QACjC,IAAI,EAAE,2CAA2C;QACjD,MAAM,EAAE,SAAS,KAAK,2ZAA2Z;KAClb,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MPP-EVM Id-Reuse (LCP §8.3.5): the host's derivation rule, the weld it produces, and the *only* kind of
|
|
3
|
+
* check the resulting on-chain value permits.
|
|
4
|
+
*
|
|
5
|
+
* **THE HOST RULE, quoted.** `draft-evm-charge-00` §5.3.1 (paymentauth.org, read 2026-07-30):
|
|
6
|
+
*
|
|
7
|
+
* nonce = keccak256(abi.encodePacked(challenge.id, challenge.realm))
|
|
8
|
+
*
|
|
9
|
+
* — with the accompanying sentence "This specification requires the nonce to be set to the `challengeHash`".
|
|
10
|
+
* The nonce is therefore a *derivation*, not a slot: there is nothing here for LCP to occupy. What LCP does
|
|
11
|
+
* instead is supply an input — the seller sets `challenge.id = atrHash` — so the host's own required
|
|
12
|
+
* derivation carries the record into the settlement transaction.
|
|
13
|
+
*
|
|
14
|
+
* **WHAT MAKES THAT SOUND IS NOT UNIQUENESS, AND AN EARLIER DOCBLOCK SAID IT WAS.** It argued that MPP's
|
|
15
|
+
* "Unique challenge identifier" requirement (`draft-httpauth-payment` §5.1.1) was satisfied by making each
|
|
16
|
+
* ATR unique per transaction. Every citation in it was exact; the sufficiency was not. LCP v1.38 §C.1
|
|
17
|
+
* (:1282-1289):
|
|
18
|
+
*
|
|
19
|
+
* > **It is not simply available, and §8.3.5's uniqueness advice is not sufficient for it.** MPP requires
|
|
20
|
+
* > the server to bind the challenge `id` *to the challenge parameters* … expressly to prevent a client
|
|
21
|
+
* > altering the challenge it presents. **Making each ATR unique per transaction satisfies uniqueness but
|
|
22
|
+
* > not that binding.** A per-transaction ATR that itself states the transaction parameters (Section 6.1)
|
|
23
|
+
* > satisfies both, because the parameters are then inside the hashed document; a terms document made
|
|
24
|
+
* > unique by a timestamp alone does not.
|
|
25
|
+
*
|
|
26
|
+
* **THE REQUIREMENT IS ON THE ATR, AND THE TREE ALREADY VERIFIES IT.** This binding holds a hash, never the
|
|
27
|
+
* document, so it cannot inspect what the ATR says — and it should not: where the ATR lives is the seller's
|
|
28
|
+
* and buyer's business, and a rail binding that needed the document would assert a custody LCP does not
|
|
29
|
+
* require. The property §C.1 asks for is **OFR**: the ATR's offer slot is bound, meaning the transaction
|
|
30
|
+
* parameters are inside the hashed document. `verify` implements it as `offerBoundStep`
|
|
31
|
+
* (`verify/src/composition.ts`) and requires it at **TC-4** (`verify/src/required.ts`). So a record whose
|
|
32
|
+
* class is TC-4 has had this checked by the layer that holds the document; a record below TC-4 has not, and
|
|
33
|
+
* the §8.3.5 discharge on this rail is only as good as that rung.
|
|
34
|
+
*
|
|
35
|
+
* The host is internally inconsistent here and the split is kept visible deliberately: the core draft
|
|
36
|
+
* states the challenge-parameter binding as a **MUST** in the `id` field description and as a **SHOULD** in
|
|
37
|
+
* its challenge-binding section. §C.1 names that inconsistency rather than resolving it, and so does this.
|
|
38
|
+
*
|
|
39
|
+
* **Zero-party-recoverable on-chain binding on this rail still requires an Overlay Contract per §8.3.2**
|
|
40
|
+
* (§C.1:1289). Nothing here recovers an atrHash, and nothing may be added that does — see the closing note.
|
|
41
|
+
*
|
|
42
|
+
* **`abi.encodePacked` over two strings is raw UTF-8 concatenation** — no length prefix, no padding. That was
|
|
43
|
+
* confirmed against Foundry's own encoder rather than assumed, and every oracle in
|
|
44
|
+
* `vectors/binding/mpp-evm-id-reuse.json` was produced by two independent keccak-256 implementations,
|
|
45
|
+
* neither of them the one used here.
|
|
46
|
+
*
|
|
47
|
+
* **The atrHash's SPELLING is part of the preimage.** The derivation hashes the id *as a string*, so
|
|
48
|
+
* `0x7f83…` and `7f83…` are different challenge ids with different nonces. This module therefore canonicalizes
|
|
49
|
+
* to LCP's own spelling — lowercase, `0x`-prefixed, 32 bytes — before deriving OR comparing, on both the
|
|
50
|
+
* candidate and the observed nonce. An uppercase-hex spelling of the same bytes is a legal input that reaches
|
|
51
|
+
* the same nonce, not a second wire convention: it is normalized, and the vectors pin that both ways round.
|
|
52
|
+
* What is REJECTED is a value that is not 32 hex bytes at all — a missing `0x`, a wrong length, a non-hex
|
|
53
|
+
* digit. Those are different challenge ids rather than spellings of this one, and there is no canonical form
|
|
54
|
+
* to map them to.
|
|
55
|
+
*
|
|
56
|
+
* **Packed concatenation is undelimited, and the fixed length is what makes that safe.** `('ab','c')` and
|
|
57
|
+
* `('a','bc')` pack identically; the vectors pin that as an observed property of the host rule. It is
|
|
58
|
+
* unreachable for this binding because `challenge.id` is always exactly 66 characters, so no other split of
|
|
59
|
+
* the preimage produces a legal id. The length check below is therefore load-bearing, not defensive noise.
|
|
60
|
+
*
|
|
61
|
+
* **NOTHING HERE RECOVERS AN atrHash, and no future edit may add such a path.** keccak-256 has no inverse:
|
|
62
|
+
* the honest surface is confirmation of a candidate the auditor already holds. Re-deriving from a stored
|
|
63
|
+
* challenge would be service-record recovery wearing the name of zero-party recovery, and would falsify
|
|
64
|
+
* `zeroPartyRecoverable: false` in the manifest.
|
|
65
|
+
*/
|
|
66
|
+
import type { Outcome, Refusal } from "@integraledger/lcp-binding-core";
|
|
67
|
+
import { type Hex } from "viem";
|
|
68
|
+
/** The challenge the seller emits, and the nonce the buyer's authorization will therefore carry. */
|
|
69
|
+
export interface MppEvmChallengeBinding {
|
|
70
|
+
/** `challenge.id` — the atrHash itself, in LCP's canonical lowercase `0x` spelling. */
|
|
71
|
+
readonly challengeId: Hex;
|
|
72
|
+
/** `challenge.realm` — MPP's protection space, the second half of the preimage. */
|
|
73
|
+
readonly realm: string;
|
|
74
|
+
/** The EIP-3009 nonce `keccak256(abi.encodePacked(id, realm))` requires. DERIVED, never chosen. */
|
|
75
|
+
readonly nonce: Hex;
|
|
76
|
+
}
|
|
77
|
+
/** A candidate atrHash confirmed against a settlement's on-chain nonce. Confirmation, never recovery. */
|
|
78
|
+
export interface MppEvmCandidateConfirmation {
|
|
79
|
+
readonly confirmed: true;
|
|
80
|
+
/** The candidate that reproduced the on-chain nonce, canonicalized. */
|
|
81
|
+
readonly atrHash: Hex;
|
|
82
|
+
/** The protection space the derivation was performed under — the confirmation is scoped to it. */
|
|
83
|
+
readonly realm: string;
|
|
84
|
+
/** The on-chain nonce the candidate reproduced. */
|
|
85
|
+
readonly nonce: Hex;
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* MPP's derivation, exactly as its EVM method specifies it: `keccak256(abi.encodePacked(id, realm))`.
|
|
89
|
+
*
|
|
90
|
+
* Total over any two non-empty strings, because it implements MPP's rule and not an LCP-specific one — an
|
|
91
|
+
* ordinary opaque challenge id derives here too, which is what makes this the host's function rather than
|
|
92
|
+
* ours. THROWS on an empty id or realm: MPP requires both on every challenge (`realm` is a MUST per
|
|
93
|
+
* §5.1.1), so an empty one is a wiring defect in the caller, not a value to hash.
|
|
94
|
+
*/
|
|
95
|
+
export declare function deriveChallengeHash(challengeId: string, realm: string): Hex;
|
|
96
|
+
/**
|
|
97
|
+
* The Id-Reuse weld at PROPOSAL time: `challenge.id = atrHash`, and the nonce that follows from it.
|
|
98
|
+
*
|
|
99
|
+
* THROWS on a malformed atrHash, via `canonicalAtrHash` — a seller welding a hash that is not 32 bytes is a
|
|
100
|
+
* wiring defect, and the guard is also what keeps the fixed-length argument above true of the code.
|
|
101
|
+
*
|
|
102
|
+
* **Do not reach for `binding-evm-common`'s `assertBytes32` here.** It enforces the same 32 bytes but
|
|
103
|
+
* explains them as the value that will "ride as the EIP-3009 nonce" — true on x402, false on this rail, and
|
|
104
|
+
* precisely the cross-rail misreading `binding-evm-x402`'s KNOWN-BAD note exists to stop. On MPP-EVM the
|
|
105
|
+
* atrHash rides `challenge.id` and the nonce is DERIVED from it, never occupied by it. `canonicalAtrHash`
|
|
106
|
+
* states the shape requirement and nothing about where the value rides, which is why it is the right guard:
|
|
107
|
+
* the rail-specific reasoning belongs in this docblock and in the manifest, not in an error string.
|
|
108
|
+
*/
|
|
109
|
+
export declare function bindAtrHash(atrHash: string, realm: string): MppEvmChallengeBinding;
|
|
110
|
+
/**
|
|
111
|
+
* Confirm a candidate atrHash against the `AuthorizationUsed` nonces ONE settlement emitted.
|
|
112
|
+
*
|
|
113
|
+
* The auditor brings the atrHash (from the record, or from `legal-context.json`); the chain confirms it.
|
|
114
|
+
* Refusals rather than throws, because both the candidate and the observed nonces are data under audit —
|
|
115
|
+
* the realm is not: it is the verifier's own configuration, so an empty one throws.
|
|
116
|
+
*
|
|
117
|
+
* Every observed nonce is validated BEFORE any is matched: a malformed entry refuses loudly instead of being
|
|
118
|
+
* skipped past, because silently narrowing a settlement view could turn "this transaction is not what you
|
|
119
|
+
* think" into "no match found".
|
|
120
|
+
*/
|
|
121
|
+
export declare function checkCandidate(atrHash: string, realm: string, observedNonces: readonly string[]): Outcome<MppEvmCandidateConfirmation>;
|
|
122
|
+
/**
|
|
123
|
+
* The refusal recovery always returns, stated in one place so the reason travels with the code.
|
|
124
|
+
*
|
|
125
|
+
* Not a "not implemented yet" and not a gap to be filled: the on-chain value is a hash over the atrHash, so
|
|
126
|
+
* there is no function from settlement to atrHash to write. A caller wanting the record must bring the
|
|
127
|
+
* candidate and use `verifyCandidate`.
|
|
128
|
+
*/
|
|
129
|
+
export declare function notRecoverableByConstruction(): Refusal;
|
|
130
|
+
//# sourceMappingURL=id-reuse.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"id-reuse.d.ts","sourceRoot":"","sources":["../src/id-reuse.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgEG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,iCAAiC,CAAC;AAExE,OAAO,EAAU,KAAK,GAAG,EAA4B,MAAM,MAAM,CAAC;AAKlE,oGAAoG;AACpG,MAAM,WAAW,sBAAsB;IACrC,uFAAuF;IACvF,QAAQ,CAAC,WAAW,EAAE,GAAG,CAAC;IAC1B,mFAAmF;IACnF,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,mGAAmG;IACnG,QAAQ,CAAC,KAAK,EAAE,GAAG,CAAC;CACrB;AAED,yGAAyG;AACzG,MAAM,WAAW,2BAA2B;IAC1C,QAAQ,CAAC,SAAS,EAAE,IAAI,CAAC;IACzB,uEAAuE;IACvE,QAAQ,CAAC,OAAO,EAAE,GAAG,CAAC;IACtB,kGAAkG;IAClG,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,mDAAmD;IACnD,QAAQ,CAAC,KAAK,EAAE,GAAG,CAAC;CACrB;AAED;;;;;;;GAOG;AACH,wBAAgB,mBAAmB,CAAC,WAAW,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,GAAG,CAU3E;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,WAAW,CACzB,OAAO,EAAE,MAAM,EACf,KAAK,EAAE,MAAM,GACZ,sBAAsB,CAOxB;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,cAAc,CAC5B,OAAO,EAAE,MAAM,EACf,KAAK,EAAE,MAAM,EACb,cAAc,EAAE,SAAS,MAAM,EAAE,GAChC,OAAO,CAAC,2BAA2B,CAAC,CAsCtC;AAED;;;;;;GAMG;AACH,wBAAgB,4BAA4B,IAAI,OAAO,CAQtD"}
|
package/dist/id-reuse.js
ADDED
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MPP-EVM Id-Reuse (LCP §8.3.5): the host's derivation rule, the weld it produces, and the *only* kind of
|
|
3
|
+
* check the resulting on-chain value permits.
|
|
4
|
+
*
|
|
5
|
+
* **THE HOST RULE, quoted.** `draft-evm-charge-00` §5.3.1 (paymentauth.org, read 2026-07-30):
|
|
6
|
+
*
|
|
7
|
+
* nonce = keccak256(abi.encodePacked(challenge.id, challenge.realm))
|
|
8
|
+
*
|
|
9
|
+
* — with the accompanying sentence "This specification requires the nonce to be set to the `challengeHash`".
|
|
10
|
+
* The nonce is therefore a *derivation*, not a slot: there is nothing here for LCP to occupy. What LCP does
|
|
11
|
+
* instead is supply an input — the seller sets `challenge.id = atrHash` — so the host's own required
|
|
12
|
+
* derivation carries the record into the settlement transaction.
|
|
13
|
+
*
|
|
14
|
+
* **WHAT MAKES THAT SOUND IS NOT UNIQUENESS, AND AN EARLIER DOCBLOCK SAID IT WAS.** It argued that MPP's
|
|
15
|
+
* "Unique challenge identifier" requirement (`draft-httpauth-payment` §5.1.1) was satisfied by making each
|
|
16
|
+
* ATR unique per transaction. Every citation in it was exact; the sufficiency was not. LCP v1.38 §C.1
|
|
17
|
+
* (:1282-1289):
|
|
18
|
+
*
|
|
19
|
+
* > **It is not simply available, and §8.3.5's uniqueness advice is not sufficient for it.** MPP requires
|
|
20
|
+
* > the server to bind the challenge `id` *to the challenge parameters* … expressly to prevent a client
|
|
21
|
+
* > altering the challenge it presents. **Making each ATR unique per transaction satisfies uniqueness but
|
|
22
|
+
* > not that binding.** A per-transaction ATR that itself states the transaction parameters (Section 6.1)
|
|
23
|
+
* > satisfies both, because the parameters are then inside the hashed document; a terms document made
|
|
24
|
+
* > unique by a timestamp alone does not.
|
|
25
|
+
*
|
|
26
|
+
* **THE REQUIREMENT IS ON THE ATR, AND THE TREE ALREADY VERIFIES IT.** This binding holds a hash, never the
|
|
27
|
+
* document, so it cannot inspect what the ATR says — and it should not: where the ATR lives is the seller's
|
|
28
|
+
* and buyer's business, and a rail binding that needed the document would assert a custody LCP does not
|
|
29
|
+
* require. The property §C.1 asks for is **OFR**: the ATR's offer slot is bound, meaning the transaction
|
|
30
|
+
* parameters are inside the hashed document. `verify` implements it as `offerBoundStep`
|
|
31
|
+
* (`verify/src/composition.ts`) and requires it at **TC-4** (`verify/src/required.ts`). So a record whose
|
|
32
|
+
* class is TC-4 has had this checked by the layer that holds the document; a record below TC-4 has not, and
|
|
33
|
+
* the §8.3.5 discharge on this rail is only as good as that rung.
|
|
34
|
+
*
|
|
35
|
+
* The host is internally inconsistent here and the split is kept visible deliberately: the core draft
|
|
36
|
+
* states the challenge-parameter binding as a **MUST** in the `id` field description and as a **SHOULD** in
|
|
37
|
+
* its challenge-binding section. §C.1 names that inconsistency rather than resolving it, and so does this.
|
|
38
|
+
*
|
|
39
|
+
* **Zero-party-recoverable on-chain binding on this rail still requires an Overlay Contract per §8.3.2**
|
|
40
|
+
* (§C.1:1289). Nothing here recovers an atrHash, and nothing may be added that does — see the closing note.
|
|
41
|
+
*
|
|
42
|
+
* **`abi.encodePacked` over two strings is raw UTF-8 concatenation** — no length prefix, no padding. That was
|
|
43
|
+
* confirmed against Foundry's own encoder rather than assumed, and every oracle in
|
|
44
|
+
* `vectors/binding/mpp-evm-id-reuse.json` was produced by two independent keccak-256 implementations,
|
|
45
|
+
* neither of them the one used here.
|
|
46
|
+
*
|
|
47
|
+
* **The atrHash's SPELLING is part of the preimage.** The derivation hashes the id *as a string*, so
|
|
48
|
+
* `0x7f83…` and `7f83…` are different challenge ids with different nonces. This module therefore canonicalizes
|
|
49
|
+
* to LCP's own spelling — lowercase, `0x`-prefixed, 32 bytes — before deriving OR comparing, on both the
|
|
50
|
+
* candidate and the observed nonce. An uppercase-hex spelling of the same bytes is a legal input that reaches
|
|
51
|
+
* the same nonce, not a second wire convention: it is normalized, and the vectors pin that both ways round.
|
|
52
|
+
* What is REJECTED is a value that is not 32 hex bytes at all — a missing `0x`, a wrong length, a non-hex
|
|
53
|
+
* digit. Those are different challenge ids rather than spellings of this one, and there is no canonical form
|
|
54
|
+
* to map them to.
|
|
55
|
+
*
|
|
56
|
+
* **Packed concatenation is undelimited, and the fixed length is what makes that safe.** `('ab','c')` and
|
|
57
|
+
* `('a','bc')` pack identically; the vectors pin that as an observed property of the host rule. It is
|
|
58
|
+
* unreachable for this binding because `challenge.id` is always exactly 66 characters, so no other split of
|
|
59
|
+
* the preimage produces a legal id. The length check below is therefore load-bearing, not defensive noise.
|
|
60
|
+
*
|
|
61
|
+
* **NOTHING HERE RECOVERS AN atrHash, and no future edit may add such a path.** keccak-256 has no inverse:
|
|
62
|
+
* the honest surface is confirmation of a candidate the auditor already holds. Re-deriving from a stored
|
|
63
|
+
* challenge would be service-record recovery wearing the name of zero-party recovery, and would falsify
|
|
64
|
+
* `zeroPartyRecoverable: false` in the manifest.
|
|
65
|
+
*/
|
|
66
|
+
import { canonicalAtrHash } from "@integraledger/lcp-kernel";
|
|
67
|
+
import { concat, keccak256, stringToBytes } from "viem";
|
|
68
|
+
/** A 32-byte value in the canonical lowercase-`0x` spelling this binding hashes and compares. */
|
|
69
|
+
const BYTES32 = /^0x[0-9a-fA-F]{64}$/;
|
|
70
|
+
/**
|
|
71
|
+
* MPP's derivation, exactly as its EVM method specifies it: `keccak256(abi.encodePacked(id, realm))`.
|
|
72
|
+
*
|
|
73
|
+
* Total over any two non-empty strings, because it implements MPP's rule and not an LCP-specific one — an
|
|
74
|
+
* ordinary opaque challenge id derives here too, which is what makes this the host's function rather than
|
|
75
|
+
* ours. THROWS on an empty id or realm: MPP requires both on every challenge (`realm` is a MUST per
|
|
76
|
+
* §5.1.1), so an empty one is a wiring defect in the caller, not a value to hash.
|
|
77
|
+
*/
|
|
78
|
+
export function deriveChallengeHash(challengeId, realm) {
|
|
79
|
+
if (challengeId === "")
|
|
80
|
+
throw new Error("MPP challenge id must be non-empty — it is half the nonce preimage");
|
|
81
|
+
if (realm === "")
|
|
82
|
+
throw new Error("MPP realm must be non-empty — the core scheme makes realm a MUST on every challenge, and it is half the nonce preimage");
|
|
83
|
+
return keccak256(concat([stringToBytes(challengeId), stringToBytes(realm)]));
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* The Id-Reuse weld at PROPOSAL time: `challenge.id = atrHash`, and the nonce that follows from it.
|
|
87
|
+
*
|
|
88
|
+
* THROWS on a malformed atrHash, via `canonicalAtrHash` — a seller welding a hash that is not 32 bytes is a
|
|
89
|
+
* wiring defect, and the guard is also what keeps the fixed-length argument above true of the code.
|
|
90
|
+
*
|
|
91
|
+
* **Do not reach for `binding-evm-common`'s `assertBytes32` here.** It enforces the same 32 bytes but
|
|
92
|
+
* explains them as the value that will "ride as the EIP-3009 nonce" — true on x402, false on this rail, and
|
|
93
|
+
* precisely the cross-rail misreading `binding-evm-x402`'s KNOWN-BAD note exists to stop. On MPP-EVM the
|
|
94
|
+
* atrHash rides `challenge.id` and the nonce is DERIVED from it, never occupied by it. `canonicalAtrHash`
|
|
95
|
+
* states the shape requirement and nothing about where the value rides, which is why it is the right guard:
|
|
96
|
+
* the rail-specific reasoning belongs in this docblock and in the manifest, not in an error string.
|
|
97
|
+
*/
|
|
98
|
+
export function bindAtrHash(atrHash, realm) {
|
|
99
|
+
const challengeId = canonicalAtrHash(atrHash, "bindAtrHash", "on this rail it rides challenge.id and the EIP-3009 nonce is DERIVED from it (draft-evm-charge-00 §5.3.1), never occupied by it");
|
|
100
|
+
return { challengeId, realm, nonce: deriveChallengeHash(challengeId, realm) };
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Confirm a candidate atrHash against the `AuthorizationUsed` nonces ONE settlement emitted.
|
|
104
|
+
*
|
|
105
|
+
* The auditor brings the atrHash (from the record, or from `legal-context.json`); the chain confirms it.
|
|
106
|
+
* Refusals rather than throws, because both the candidate and the observed nonces are data under audit —
|
|
107
|
+
* the realm is not: it is the verifier's own configuration, so an empty one throws.
|
|
108
|
+
*
|
|
109
|
+
* Every observed nonce is validated BEFORE any is matched: a malformed entry refuses loudly instead of being
|
|
110
|
+
* skipped past, because silently narrowing a settlement view could turn "this transaction is not what you
|
|
111
|
+
* think" into "no match found".
|
|
112
|
+
*/
|
|
113
|
+
export function checkCandidate(atrHash, realm, observedNonces) {
|
|
114
|
+
if (!BYTES32.test(atrHash))
|
|
115
|
+
return {
|
|
116
|
+
refused: true,
|
|
117
|
+
haltClass: "verification-failure",
|
|
118
|
+
code: "mpp-evm/candidate-malformed",
|
|
119
|
+
detail: `a candidate atrHash must be a 0x-prefixed 32-byte value to be a legal MPP challenge id, got "${atrHash}"`,
|
|
120
|
+
};
|
|
121
|
+
for (const observed of observedNonces)
|
|
122
|
+
if (!BYTES32.test(observed))
|
|
123
|
+
return {
|
|
124
|
+
refused: true,
|
|
125
|
+
haltClass: "verification-failure",
|
|
126
|
+
code: "mpp-evm/nonce-malformed",
|
|
127
|
+
detail: `an observed EIP-3009 nonce must be a 0x-prefixed 32-byte value, got "${observed}"`,
|
|
128
|
+
};
|
|
129
|
+
if (observedNonces.length === 0)
|
|
130
|
+
return {
|
|
131
|
+
refused: true,
|
|
132
|
+
haltClass: "verification-failure",
|
|
133
|
+
code: "mpp-evm/no-settlement-event",
|
|
134
|
+
detail: "no EIP-3009 AuthorizationUsed nonce was observed for this settlement — there is nothing to verify against",
|
|
135
|
+
};
|
|
136
|
+
const candidate = canonicalAtrHash(atrHash, "checkCandidate");
|
|
137
|
+
const nonce = deriveChallengeHash(candidate, realm);
|
|
138
|
+
const matched = observedNonces.find((o) => o.toLowerCase() === nonce);
|
|
139
|
+
if (matched === undefined)
|
|
140
|
+
return {
|
|
141
|
+
refused: true,
|
|
142
|
+
haltClass: "verification-failure",
|
|
143
|
+
code: "mpp-evm/candidate-mismatch",
|
|
144
|
+
detail: `no observed nonce equals keccak256(packed("${candidate}", "${realm}")) = ${nonce}`,
|
|
145
|
+
};
|
|
146
|
+
return {
|
|
147
|
+
ok: true,
|
|
148
|
+
value: { confirmed: true, atrHash: candidate, realm, nonce },
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* The refusal recovery always returns, stated in one place so the reason travels with the code.
|
|
153
|
+
*
|
|
154
|
+
* Not a "not implemented yet" and not a gap to be filled: the on-chain value is a hash over the atrHash, so
|
|
155
|
+
* there is no function from settlement to atrHash to write. A caller wanting the record must bring the
|
|
156
|
+
* candidate and use `verifyCandidate`.
|
|
157
|
+
*/
|
|
158
|
+
export function notRecoverableByConstruction() {
|
|
159
|
+
return {
|
|
160
|
+
refused: true,
|
|
161
|
+
haltClass: "verification-failure",
|
|
162
|
+
code: "mpp-evm/not-recoverable-by-construction",
|
|
163
|
+
detail: "MPP-EVM is an Id-Reuse binding (LCP §8.3.5): the on-chain EIP-3009 nonce is keccak256 over the atrHash and the realm, so no atrHash can be recovered from a settlement — bring the candidate atrHash and use verifyCandidate",
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
//# sourceMappingURL=id-reuse.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"id-reuse.js","sourceRoot":"","sources":["../src/id-reuse.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export { createMppEvmAdapter, type MppEvmAdapter, type MppEvmAdapterConfig, } from "./adapter.js";
|
|
2
|
+
export { notAuthorizationCredentialType } from "./credential-type.js";
|
|
3
|
+
export { bindAtrHash, checkCandidate, deriveChallengeHash, type MppEvmCandidateConfirmation, type MppEvmChallengeBinding, notRecoverableByConstruction, } from "./id-reuse.js";
|
|
4
|
+
export { MPP_EVM_MANIFEST } from "./manifest.js";
|
|
5
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,mBAAmB,EACnB,KAAK,aAAa,EAClB,KAAK,mBAAmB,GACzB,MAAM,cAAc,CAAC;AAItB,OAAO,EAAE,8BAA8B,EAAE,MAAM,sBAAsB,CAAC;AACtE,OAAO,EACL,WAAW,EACX,cAAc,EACd,mBAAmB,EACnB,KAAK,2BAA2B,EAChC,KAAK,sBAAsB,EAC3B,4BAA4B,GAC7B,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export { createMppEvmAdapter, } from "./adapter.js";
|
|
2
|
+
// `assetWasTransferred` and `ERC20_TRANSFER_TOPIC0` moved to `@integraledger/lcp-binding-evm-common` when
|
|
3
|
+
// binding-evm-x402 needed the same predicate. They are NOT re-exported from here: a symbol with two homes
|
|
4
|
+
// is how the two copies drift, which is the thing the move was for.
|
|
5
|
+
export { notAuthorizationCredentialType } from "./credential-type.js";
|
|
6
|
+
export { bindAtrHash, checkCandidate, deriveChallengeHash, notRecoverableByConstruction, } from "./id-reuse.js";
|
|
7
|
+
export { MPP_EVM_MANIFEST } from "./manifest.js";
|
|
8
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,mBAAmB,GAGpB,MAAM,cAAc,CAAC;AACtB,0GAA0G;AAC1G,0GAA0G;AAC1G,oEAAoE;AACpE,OAAO,EAAE,8BAA8B,EAAE,MAAM,sBAAsB,CAAC;AACtE,OAAO,EACL,WAAW,EACX,cAAc,EACd,mBAAmB,EAGnB,4BAA4B,GAC7B,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC"}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import type { BindingManifest } from "@integraledger/lcp-binding-core";
|
|
2
|
+
/**
|
|
3
|
+
* The MPP-EVM Id-Reuse binding manifest (published as `mpp-evm-derived-v1`).
|
|
4
|
+
*
|
|
5
|
+
* **pattern = "id-reuse"** (LCP §8.3.5), and this is the one member of the family that differs *at the
|
|
6
|
+
* weld*. On MPP's EVM charge method the EIP-3009 nonce is not ours to occupy: `draft-evm-charge-00` §5.3.1
|
|
7
|
+
* REQUIRES `nonce = keccak256(abi.encodePacked(challenge.id, challenge.realm))`, and §5.2.3 fixes the
|
|
8
|
+
* Permit2 witness type string with a MUST ("Implementations MUST use the exact type string above"). There is
|
|
9
|
+
* no free field on this path. What remains available is Id-Reuse: the seller sets `challenge.id = atrHash`,
|
|
10
|
+
* so the host's own derivation carries the record into the settlement transaction.
|
|
11
|
+
*
|
|
12
|
+
* **Do NOT reconcile this manifest against `binding-evm-x402`'s.** That package's docblock records an
|
|
13
|
+
* archived declaration marked KNOWN-BAD for reading MPP-EVM's derivation-MUST onto the x402 path, where no
|
|
14
|
+
* derivation exists. This is where that reading is correct, and the two manifests legitimately differ: x402
|
|
15
|
+
* is off-canonical Native Field on the same nonce field, MPP-EVM is Id-Reuse over a derivation. "Fixing" one
|
|
16
|
+
* to look like the other reintroduces the bug the archive comment exists to stop.
|
|
17
|
+
*
|
|
18
|
+
* **`nativeField` is absent, and its absence is substantive** — not merely the schema's `iff`. Nothing of
|
|
19
|
+
* ours occupies a protocol field here; the atrHash rides an identifier the host protocol already requires.
|
|
20
|
+
*
|
|
21
|
+
* **SCOPE — this manifest declares ONE of MPP's four credential types, and the scope is load-bearing.**
|
|
22
|
+
* `draft-evm-charge-00` defines four ways a client may present payment, and the derived `challengeHash`
|
|
23
|
+
* reaches the chain in exactly one of them:
|
|
24
|
+
* - **§5.3 `authorization`** — the derived value IS the EIP-3009 nonce, so it is committed on-chain. Opt-in
|
|
25
|
+
* and token-conditional: §4.2.2 "Servers MUST only include `"authorization"` when the `currency` token is
|
|
26
|
+
* known to implement EIP-3009", and §5.3.2 the same rule as a prohibition ("Servers MUST NOT advertise
|
|
27
|
+
* `"authorization"` in `credentialTypes` unless the `currency` token is known to implement EIP-3009").
|
|
28
|
+
* **This is the credential type this binding covers, and the only one it can.**
|
|
29
|
+
* - **§5.2 `permit2`** — "The RECOMMENDED credential type", and it carries the SAME derived `challengeHash`
|
|
30
|
+
* inside the EIP-712 `PaymentWitness` struct, which the client signs but which never appears in calldata or
|
|
31
|
+
* an event log; §10.4 places it "in the EIP-712 witness data (Permit2)". Signature-committed, off-chain,
|
|
32
|
+
* unreadable from a settlement.
|
|
33
|
+
* - **§5.4 `transaction`** and **§5.5 `hash`** — plain ERC-20 transfers. No challengeHash anywhere; §10.4
|
|
34
|
+
* says so outright ("weaker challenge binding than Permit2 credentials").
|
|
35
|
+
*
|
|
36
|
+
* So `weldGrades` is keyed by MPP's own credential-type name rather than by a coinage like `derived`: the key
|
|
37
|
+
* IS the scope, machine-readably, and `finality.note` repeats it in prose for whoever reads only the published
|
|
38
|
+
* profile JSON. A grade for `permit2` would be declared from the specification rather than from this binding —
|
|
39
|
+
* the package constructs no witness — and `transaction`/`hash` have no weld to grade. Generalizing the
|
|
40
|
+
* strongest leg to the whole rail is the failure this scoping exists to prevent, and the adapter enforces the
|
|
41
|
+
* same scope at runtime: a settlement that moved the token without an `AuthorizationUsed` beside it is refused
|
|
42
|
+
* by credential type, never reported as an absence of settlement.
|
|
43
|
+
*
|
|
44
|
+
* **The WLD-3 triple, under that scope:**
|
|
45
|
+
* - `onChain: true` — the derived value is the EIP-3009 nonce, emitted as an indexed topic of
|
|
46
|
+
* `AuthorizationUsed(address indexed authorizer, bytes32 indexed nonce)` and consumed on-chain by the
|
|
47
|
+
* token contract (§8: "The nonce is consumed on-chain by the token contract itself"). **Read from the host
|
|
48
|
+
* specification and from the adapter's own behaviour on synthetic logs; NOT yet from a live MPP-EVM
|
|
49
|
+
* settlement** — the repository's opt-in on-chain integration suite discharges that against a real transaction, and until
|
|
50
|
+
* it runs, this member is the one still owed a live proof.
|
|
51
|
+
* - `zeroPartyRecoverable: false` — keccak-256 has no inverse. An auditor holding only the transaction hash
|
|
52
|
+
* reads the nonce and cannot obtain the atrHash from it. Verification of a *candidate* is the whole of
|
|
53
|
+
* what this rail offers, which is why the adapter exposes `verifyCandidate` and why `recover` refuses.
|
|
54
|
+
* Observed of the code: no member maps a settlement to an atrHash, and `recover` takes no arguments.
|
|
55
|
+
* - `forwardIndexable: false` — §8.3's criterion is enumeration "bound to a given `atrHash`", and §8.3.5
|
|
56
|
+
* states it directly: "Not forward-indexable by `atrHash` alone — the on-chain value is a hash over
|
|
57
|
+
* `atrHash` and other inputs." Knowing the realm as well, one *could* derive the nonce and topic-filter it,
|
|
58
|
+
* but that is realm-scoped, and it is degenerate besides: §8.3.5's uniqueness satisfaction makes each ATR
|
|
59
|
+
* unique per transaction, so there is at most one settlement per atrHash to enumerate. Hence `indexing:
|
|
60
|
+
* "none"` and no `enumerate` member. Observed of the code: no `enumerate` exists to call.
|
|
61
|
+
*
|
|
62
|
+
* `weldGrades.authorization = "signature"`: the payer's EIP-3009 typed-data message covers the `nonce` field,
|
|
63
|
+
* so the buyer's signature — not merely an inclusion in a transaction — commits the derived value.
|
|
64
|
+
*/
|
|
65
|
+
export declare const MPP_EVM_MANIFEST: BindingManifest;
|
|
66
|
+
//# sourceMappingURL=manifest.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"manifest.d.ts","sourceRoot":"","sources":["../src/manifest.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,iCAAiC,CAAC;AAEvE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8DG;AACH,eAAO,MAAM,gBAAgB,EAAE,eAkB9B,CAAC"}
|
package/dist/manifest.js
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The MPP-EVM Id-Reuse binding manifest (published as `mpp-evm-derived-v1`).
|
|
3
|
+
*
|
|
4
|
+
* **pattern = "id-reuse"** (LCP §8.3.5), and this is the one member of the family that differs *at the
|
|
5
|
+
* weld*. On MPP's EVM charge method the EIP-3009 nonce is not ours to occupy: `draft-evm-charge-00` §5.3.1
|
|
6
|
+
* REQUIRES `nonce = keccak256(abi.encodePacked(challenge.id, challenge.realm))`, and §5.2.3 fixes the
|
|
7
|
+
* Permit2 witness type string with a MUST ("Implementations MUST use the exact type string above"). There is
|
|
8
|
+
* no free field on this path. What remains available is Id-Reuse: the seller sets `challenge.id = atrHash`,
|
|
9
|
+
* so the host's own derivation carries the record into the settlement transaction.
|
|
10
|
+
*
|
|
11
|
+
* **Do NOT reconcile this manifest against `binding-evm-x402`'s.** That package's docblock records an
|
|
12
|
+
* archived declaration marked KNOWN-BAD for reading MPP-EVM's derivation-MUST onto the x402 path, where no
|
|
13
|
+
* derivation exists. This is where that reading is correct, and the two manifests legitimately differ: x402
|
|
14
|
+
* is off-canonical Native Field on the same nonce field, MPP-EVM is Id-Reuse over a derivation. "Fixing" one
|
|
15
|
+
* to look like the other reintroduces the bug the archive comment exists to stop.
|
|
16
|
+
*
|
|
17
|
+
* **`nativeField` is absent, and its absence is substantive** — not merely the schema's `iff`. Nothing of
|
|
18
|
+
* ours occupies a protocol field here; the atrHash rides an identifier the host protocol already requires.
|
|
19
|
+
*
|
|
20
|
+
* **SCOPE — this manifest declares ONE of MPP's four credential types, and the scope is load-bearing.**
|
|
21
|
+
* `draft-evm-charge-00` defines four ways a client may present payment, and the derived `challengeHash`
|
|
22
|
+
* reaches the chain in exactly one of them:
|
|
23
|
+
* - **§5.3 `authorization`** — the derived value IS the EIP-3009 nonce, so it is committed on-chain. Opt-in
|
|
24
|
+
* and token-conditional: §4.2.2 "Servers MUST only include `"authorization"` when the `currency` token is
|
|
25
|
+
* known to implement EIP-3009", and §5.3.2 the same rule as a prohibition ("Servers MUST NOT advertise
|
|
26
|
+
* `"authorization"` in `credentialTypes` unless the `currency` token is known to implement EIP-3009").
|
|
27
|
+
* **This is the credential type this binding covers, and the only one it can.**
|
|
28
|
+
* - **§5.2 `permit2`** — "The RECOMMENDED credential type", and it carries the SAME derived `challengeHash`
|
|
29
|
+
* inside the EIP-712 `PaymentWitness` struct, which the client signs but which never appears in calldata or
|
|
30
|
+
* an event log; §10.4 places it "in the EIP-712 witness data (Permit2)". Signature-committed, off-chain,
|
|
31
|
+
* unreadable from a settlement.
|
|
32
|
+
* - **§5.4 `transaction`** and **§5.5 `hash`** — plain ERC-20 transfers. No challengeHash anywhere; §10.4
|
|
33
|
+
* says so outright ("weaker challenge binding than Permit2 credentials").
|
|
34
|
+
*
|
|
35
|
+
* So `weldGrades` is keyed by MPP's own credential-type name rather than by a coinage like `derived`: the key
|
|
36
|
+
* IS the scope, machine-readably, and `finality.note` repeats it in prose for whoever reads only the published
|
|
37
|
+
* profile JSON. A grade for `permit2` would be declared from the specification rather than from this binding —
|
|
38
|
+
* the package constructs no witness — and `transaction`/`hash` have no weld to grade. Generalizing the
|
|
39
|
+
* strongest leg to the whole rail is the failure this scoping exists to prevent, and the adapter enforces the
|
|
40
|
+
* same scope at runtime: a settlement that moved the token without an `AuthorizationUsed` beside it is refused
|
|
41
|
+
* by credential type, never reported as an absence of settlement.
|
|
42
|
+
*
|
|
43
|
+
* **The WLD-3 triple, under that scope:**
|
|
44
|
+
* - `onChain: true` — the derived value is the EIP-3009 nonce, emitted as an indexed topic of
|
|
45
|
+
* `AuthorizationUsed(address indexed authorizer, bytes32 indexed nonce)` and consumed on-chain by the
|
|
46
|
+
* token contract (§8: "The nonce is consumed on-chain by the token contract itself"). **Read from the host
|
|
47
|
+
* specification and from the adapter's own behaviour on synthetic logs; NOT yet from a live MPP-EVM
|
|
48
|
+
* settlement** — the repository's opt-in on-chain integration suite discharges that against a real transaction, and until
|
|
49
|
+
* it runs, this member is the one still owed a live proof.
|
|
50
|
+
* - `zeroPartyRecoverable: false` — keccak-256 has no inverse. An auditor holding only the transaction hash
|
|
51
|
+
* reads the nonce and cannot obtain the atrHash from it. Verification of a *candidate* is the whole of
|
|
52
|
+
* what this rail offers, which is why the adapter exposes `verifyCandidate` and why `recover` refuses.
|
|
53
|
+
* Observed of the code: no member maps a settlement to an atrHash, and `recover` takes no arguments.
|
|
54
|
+
* - `forwardIndexable: false` — §8.3's criterion is enumeration "bound to a given `atrHash`", and §8.3.5
|
|
55
|
+
* states it directly: "Not forward-indexable by `atrHash` alone — the on-chain value is a hash over
|
|
56
|
+
* `atrHash` and other inputs." Knowing the realm as well, one *could* derive the nonce and topic-filter it,
|
|
57
|
+
* but that is realm-scoped, and it is degenerate besides: §8.3.5's uniqueness satisfaction makes each ATR
|
|
58
|
+
* unique per transaction, so there is at most one settlement per atrHash to enumerate. Hence `indexing:
|
|
59
|
+
* "none"` and no `enumerate` member. Observed of the code: no `enumerate` exists to call.
|
|
60
|
+
*
|
|
61
|
+
* `weldGrades.authorization = "signature"`: the payer's EIP-3009 typed-data message covers the `nonce` field,
|
|
62
|
+
* so the buyer's signature — not merely an inclusion in a transaction — commits the derived value.
|
|
63
|
+
*/
|
|
64
|
+
export const MPP_EVM_MANIFEST = {
|
|
65
|
+
rail: "evm:mpp",
|
|
66
|
+
protocol: "mpp",
|
|
67
|
+
pattern: "id-reuse",
|
|
68
|
+
recovery: {
|
|
69
|
+
onChain: true,
|
|
70
|
+
zeroPartyRecoverable: false,
|
|
71
|
+
forwardIndexable: false,
|
|
72
|
+
},
|
|
73
|
+
assetBinding: "filtered", // candidate verification filters the configured token's own log (AuthorizationUsed / assetWasTransferred)
|
|
74
|
+
successGate: "structural", // a reverted tx emits no logs, so the weld event cannot exist
|
|
75
|
+
indexing: "none",
|
|
76
|
+
finality: {
|
|
77
|
+
reversible: false,
|
|
78
|
+
note: "scoped to MPP's `authorization` credential type (§5.3), the only one of the four whose challengeHash reaches the chain — under the RECOMMENDED `permit2` type (§5.2) the same derived value is signature-committed in an off-chain EIP-712 witness, and `transaction`/`hash` (§5.4/§5.5) bind no challenge at all. Within that scope: final on settlement — the EIP-3009 authorization is consumed on-chain and there is no on-rail reversal; the weld is derivation-bound (nonce = keccak256(abi.encodePacked(challenge.id, challenge.realm)), draft-evm-charge-00 §5.3.1), so a candidate atrHash is VERIFIED against the on-chain nonce and never recovered from it; recourse is the record's elected forum (PAY-3/RCS-5), never dispute resolution. The §8.3.5 discharge rests on the ATR STATING the transaction parameters (LCP §6.1), not on ATR uniqueness: MPP binds the challenge id to the challenge parameters, and uniqueness alone does not satisfy that (§C.1). The tree checks it as OFR — `offerBoundStep`, required at TC-4 — so this weld is only as strong as the record's class. Zero-party-recoverable on-chain binding on this rail still requires an Overlay Contract per §8.3.2",
|
|
79
|
+
},
|
|
80
|
+
weldGrades: { authorization: "signature" },
|
|
81
|
+
lifecycleStates: ["proposed", "settled"],
|
|
82
|
+
};
|
|
83
|
+
//# sourceMappingURL=manifest.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"manifest.js","sourceRoot":"","sources":["../src/manifest.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8DG;AACH,MAAM,CAAC,MAAM,gBAAgB,GAAoB;IAC/C,IAAI,EAAE,SAAS;IACf,QAAQ,EAAE,KAAK;IACf,OAAO,EAAE,UAAU;IACnB,QAAQ,EAAE;QACR,OAAO,EAAE,IAAI;QACb,oBAAoB,EAAE,KAAK;QAC3B,gBAAgB,EAAE,KAAK;KACxB;IACD,YAAY,EAAE,UAAU,EAAE,0GAA0G;IACpI,WAAW,EAAE,YAAY,EAAE,8DAA8D;IACzF,QAAQ,EAAE,MAAM;IAChB,QAAQ,EAAE;QACR,UAAU,EAAE,KAAK;QACjB,IAAI,EAAE,0oCAA0oC;KACjpC;IACD,UAAU,EAAE,EAAE,aAAa,EAAE,WAAW,EAAE;IAC1C,eAAe,EAAE,CAAC,UAAU,EAAE,SAAS,CAAC;CACzC,CAAC"}
|
package/package.json
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@integraledger/lcp-binding-evm-mpp",
|
|
3
|
+
"version": "0.9.0",
|
|
4
|
+
"description": "Welds an LCP record into an MPP settlement on EVM by Id-Reuse (LCP §8.3.5).",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"lcp",
|
|
7
|
+
"legal-context-protocol",
|
|
8
|
+
"legal-terms",
|
|
9
|
+
"agentic-commerce",
|
|
10
|
+
"settlement",
|
|
11
|
+
"atr-hash",
|
|
12
|
+
"evm",
|
|
13
|
+
"mpp"
|
|
14
|
+
],
|
|
15
|
+
"type": "module",
|
|
16
|
+
"sideEffects": false,
|
|
17
|
+
"exports": {
|
|
18
|
+
".": {
|
|
19
|
+
"types": "./dist/index.d.ts",
|
|
20
|
+
"default": "./dist/index.js"
|
|
21
|
+
},
|
|
22
|
+
"./package.json": "./package.json"
|
|
23
|
+
},
|
|
24
|
+
"files": [
|
|
25
|
+
"dist",
|
|
26
|
+
"src",
|
|
27
|
+
"CHANGELOG.md",
|
|
28
|
+
"LICENSE",
|
|
29
|
+
"NOTICE"
|
|
30
|
+
],
|
|
31
|
+
"publishConfig": {
|
|
32
|
+
"registry": "https://registry.npmjs.org",
|
|
33
|
+
"access": "public"
|
|
34
|
+
},
|
|
35
|
+
"repository": {
|
|
36
|
+
"type": "git",
|
|
37
|
+
"url": "git+https://github.com/IntegraLedger/integra-protocol.git",
|
|
38
|
+
"directory": "packages/binding-evm-mpp"
|
|
39
|
+
},
|
|
40
|
+
"bugs": {
|
|
41
|
+
"url": "https://github.com/IntegraLedger/integra-protocol/issues"
|
|
42
|
+
},
|
|
43
|
+
"homepage": "https://github.com/IntegraLedger/integra-protocol/tree/main/packages/binding-evm-mpp#readme",
|
|
44
|
+
"dependencies": {
|
|
45
|
+
"viem": "2.55.11",
|
|
46
|
+
"@integraledger/lcp-binding-core": "0.9.0",
|
|
47
|
+
"@integraledger/lcp-binding-evm-common": "0.9.0",
|
|
48
|
+
"@integraledger/lcp-kernel": "0.9.0"
|
|
49
|
+
},
|
|
50
|
+
"devDependencies": {
|
|
51
|
+
"@types/node": "24.13.3",
|
|
52
|
+
"vitest": "4.1.10"
|
|
53
|
+
},
|
|
54
|
+
"license": "Apache-2.0",
|
|
55
|
+
"engines": {
|
|
56
|
+
"node": ">=24"
|
|
57
|
+
},
|
|
58
|
+
"scripts": {
|
|
59
|
+
"build": "tsc -p tsconfig.build.json",
|
|
60
|
+
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
61
|
+
"test": "vitest run"
|
|
62
|
+
}
|
|
63
|
+
}
|