@integraledger/lcp-binding-evm-escrow 0.10.1 → 0.12.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 CHANGED
@@ -1,5 +1,62 @@
1
1
  # @integraledger/lcp-binding-evm-escrow
2
2
 
3
+ ## 0.12.0
4
+
5
+ ### Patch Changes
6
+
7
+ - @integraledger/lcp-binding-core@0.12.0
8
+ - @integraledger/lcp-binding-evm-common@0.12.0
9
+
10
+ ## 0.10.2
11
+
12
+ ### Patch Changes
13
+
14
+ - b2ffecc: Report the class a record actually supports, place a terms URL on UCP, and stop refusing a conformant UCP
15
+ profile — the remediation of the 2026-08-19 conformance re-audit.
16
+
17
+ **`verify` now computes `supportedClass` instead of echoing the claim.** It was `anyFailed ? "TC-0" :
18
+ claimedClass`, so a record proving nothing — no settlement, no acceptance, no authority chain — reported
19
+ whatever class the caller named, while the field's own published docblock promised "what the record
20
+ honestly supports, not what the caller asked for". It is now the highest class every one of whose required
21
+ steps is `proved`, `TC-0` on any failure, computed from the steps alone: neither capped by the claim (rungs
22
+ that reach TC-3 read TC-3 where the caller claimed TC-2) nor lifted by it. White paper #4 §5 defines the
23
+ class of a transaction as "the highest class whose criteria it fully meets", and this is that.
24
+
25
+ The claim is not discarded — the report gains **`claimedClass`**, a required member, because `verified`
26
+ answers "did the record reach the class it claimed?" and cannot be read without it. The two fields are the
27
+ report's two halves: an input echoed, and a finding computed. Where they differ, the record did not reach
28
+ its own shape. An out-of-taxonomy claim now lands only in the echo and can no longer masquerade as a
29
+ finding.
30
+
31
+ **UCP can advertise a terms URL.** Its policy object declares `url` — "Optional link to the full policy
32
+ document", `format: uri` — on the very entry this placement writes, and §C.3's illustration carries `url`
33
+ and `atrHash` side by side there. The manifest previously said the protocol had no slot, citing `links[]`,
34
+ which §C.3 separates as "a standing page, not a per-transaction record". The obstacle was mechanical:
35
+ `termsUrlFields` addresses document paths, and a tagged-array entry's index is chosen at write time. The
36
+ `tagged-array` container therefore gains `termsUrlField`, written onto the same entry in the same write, and
37
+ read back through the same first-match rule. UCP was the last shipped protocol that refused an
38
+ advertisement carrying its own locator (integra-protocol#8).
39
+
40
+ **`readUcpProfile` no longer refuses a conformant business profile.** `requireHttps` mapped an ABSENT `spec`
41
+ to the same branch as a malformed one, and the live host requires `spec` only of a platform declaration —
42
+ as this repository's own README already said. It is now `requireHttpsIfDeclared`: absence is absence, and a
43
+ declared value is still held to the host's https MUST.
44
+
45
+ Also: `requireWritten` replaces an unchecked cast in the x402 override, so a broken postcondition throws
46
+ instead of returning a success carrying no document; six x402 citations move to the revision that actually
47
+ touches the file they name, and a new gate refuses any `owner/repo@sha` in source that `spec-pins.json` does
48
+ not record; four spec citations move from line anchors to section anchors; the escrow binding states why it
49
+ declares no §8.3.1 off-canonical variant, and asserts it; and §C.3's `policies[]` illustration is recorded as
50
+ invalid against the live UCP schema, which shows `description` as a bare string where the host requires an
51
+ object — owed upstream, not a defect here.
52
+
53
+ Corpus 844 → 847, root `ec4ad1b02a81538b…`.
54
+
55
+ - Updated dependencies [b2ffecc]
56
+ - Updated dependencies [822190a]
57
+ - @integraledger/lcp-binding-core@0.11.0
58
+ - @integraledger/lcp-binding-evm-common@0.10.2
59
+
3
60
  ## 0.10.1
4
61
 
5
62
  **0.10.0 was staged and withdrawn before approval; this is that release, re-cut.** The conformance corpus
package/README.md CHANGED
@@ -48,6 +48,14 @@ atrHashFromSalt(0n); // the inverse, for a salt you already hold
48
48
  The ATR hash rides `PaymentInfo.salt` — an existing field the service already controls. No derivation,
49
49
  no overlay contract.
50
50
 
51
+ **The ATR hash must be per-transaction.** `salt` is the struct's entropy source — *"a source of entropy to
52
+ ensure unique hashes across different payments"* — and an ATR hash carries none: it is deterministic and
53
+ identical for every payment made under one terms document. The escrow keys its state on the hash of the
54
+ whole `PaymentInfo` and refuses one it has already collected, so a repeat purchase under the same ATR with
55
+ the same payer, caps and expiries reverts on chain with `PaymentAlreadyCollected`. Nothing in this package
56
+ can detect that — the collision is between two transactions it never sees together. Mint a fresh ATR per
57
+ transaction, which LCP §6.1 wants anyway.
58
+
51
59
  ```ts
52
60
  import { EVENT_TO_STATE } from "@integraledger/lcp-binding-evm-escrow";
53
61
 
@@ -8,6 +8,16 @@ import type { BindingManifest } from "@integraledger/lcp-binding-core";
8
8
  * in `PaymentAuthorized`/`PaymentCharged` event data, so `recover`/`enumerate` decode the event and read
9
9
  * `salt` (proven on-chain).
10
10
  *
11
+ * **NO `offCanonical`, and that silence is examined rather than default.** §8.3.1 admits the marker only
12
+ * where the field is "specified as client-chosen or as a deterministic derivation that excludes
13
+ * `atrHash`", and `salt` meets NEITHER disjunct: `charge`/`authorize` are
14
+ * `onlySender(paymentInfo.operator)`, so the SERVICE constructs the whole struct and the payer signs only
15
+ * a hash of it, and the welded value IS `uint256(atrHash)` — a derivation that includes it. Nothing
16
+ * on-chain constrains the field's value, which is §8.3.1's Tier A condition. The x402 sibling declares
17
+ * `offCanonical` over the EIP-3009 nonce because THAT field is payer-chosen and its weld therefore needs
18
+ * a cooperating client stack; this one needs none, so mirroring the sibling would publish a
19
+ * controlled-client caveat that does not apply here.
20
+ *
11
21
  * **recovery.forwardIndexable = false** — the criterion is enumeration bound to a GIVEN atrHash, and this
12
22
  * rail cannot do it. `paymentInfoHash` is the only indexed topic, and it is a hash of the whole
13
23
  * `PaymentInfo` struct: a caller holding the complete struct could topic-filter, but a caller holding only
@@ -1 +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;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgDG;AACH,eAAO,MAAM,eAAe,EAAE,eA+B7B,CAAC"}
1
+ {"version":3,"file":"manifest.d.ts","sourceRoot":"","sources":["../src/manifest.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,iCAAiC,CAAC;AAEvE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA0DG;AACH,eAAO,MAAM,eAAe,EAAE,eA+B7B,CAAC"}
package/dist/manifest.js CHANGED
@@ -7,6 +7,16 @@
7
7
  * in `PaymentAuthorized`/`PaymentCharged` event data, so `recover`/`enumerate` decode the event and read
8
8
  * `salt` (proven on-chain).
9
9
  *
10
+ * **NO `offCanonical`, and that silence is examined rather than default.** §8.3.1 admits the marker only
11
+ * where the field is "specified as client-chosen or as a deterministic derivation that excludes
12
+ * `atrHash`", and `salt` meets NEITHER disjunct: `charge`/`authorize` are
13
+ * `onlySender(paymentInfo.operator)`, so the SERVICE constructs the whole struct and the payer signs only
14
+ * a hash of it, and the welded value IS `uint256(atrHash)` — a derivation that includes it. Nothing
15
+ * on-chain constrains the field's value, which is §8.3.1's Tier A condition. The x402 sibling declares
16
+ * `offCanonical` over the EIP-3009 nonce because THAT field is payer-chosen and its weld therefore needs
17
+ * a cooperating client stack; this one needs none, so mirroring the sibling would publish a
18
+ * controlled-client caveat that does not apply here.
19
+ *
10
20
  * **recovery.forwardIndexable = false** — the criterion is enumeration bound to a GIVEN atrHash, and this
11
21
  * rail cannot do it. `paymentInfoHash` is the only indexed topic, and it is a hash of the whole
12
22
  * `PaymentInfo` struct: a caller holding the complete struct could topic-filter, but a caller holding only
@@ -61,7 +71,7 @@ export const ESCROW_MANIFEST = {
61
71
  indexing: "event-data-scan:paymentInfoHash",
62
72
  finality: {
63
73
  reversible: true,
64
- note: 'on-rail void/refund within refundExpiry — not dispute resolution (RCS-5); capture is not reversed, refund is a fresh on-rail remedy The atrHash welded into PaymentInfo.salt MUST be per-transaction: salt is specified as the struct\'s entropy source ("a source of entropy to ensure unique hashes across different payments") and an atrHash carries none, so a repeat purchase under one ATR with the same payer, caps and expiries produces a PaymentInfo the escrow has already seen and reverts.',
74
+ note: 'on-rail void/refund within refundExpiry — not dispute resolution (RCS-5); capture is not reversed, refund is a fresh on-rail remedy. The atrHash welded into PaymentInfo.salt MUST be per-transaction: salt is specified as the struct\'s entropy source ("a source of entropy to ensure unique hashes across different payments") and an atrHash carries none, so a repeat purchase under one ATR with the same payer, caps and expiries produces a PaymentInfo the escrow has already seen and reverts.',
65
75
  },
66
76
  weldGrades: {
67
77
  ERC3009: "signature",
@@ -1 +1 @@
1
- {"version":3,"file":"manifest.js","sourceRoot":"","sources":["../src/manifest.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgDG;AACH,MAAM,CAAC,MAAM,eAAe,GAAoB;IAC9C,IAAI,EAAE,YAAY;IAClB,OAAO,EAAE,cAAc;IACvB,WAAW,EAAE,kBAAkB;IAC/B,QAAQ,EAAE;QACR,OAAO,EAAE,IAAI;QACb,oBAAoB,EAAE,IAAI;QAC1B,gBAAgB,EAAE,KAAK;KACxB;IACD,YAAY,EAAE,SAAS,EAAE,uFAAuF;IAChH,WAAW,EAAE,YAAY,EAAE,qEAAqE;IAChG,QAAQ,EAAE,iCAAiC;IAC3C,QAAQ,EAAE;QACR,UAAU,EAAE,IAAI;QAChB,IAAI,EAAE,0eAA0e;KACjf;IACD,UAAU,EAAE;QACV,OAAO,EAAE,WAAW;QACpB,OAAO,EAAE,WAAW;QACpB,eAAe,EAAE,WAAW;QAC5B,WAAW,EAAE,IAAI;KAClB;IACD,eAAe,EAAE;QACf,UAAU;QACV,YAAY;QACZ,UAAU;QACV,SAAS;QACT,QAAQ;QACR,WAAW;QACX,UAAU;KACX;CACF,CAAC"}
1
+ {"version":3,"file":"manifest.js","sourceRoot":"","sources":["../src/manifest.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA0DG;AACH,MAAM,CAAC,MAAM,eAAe,GAAoB;IAC9C,IAAI,EAAE,YAAY;IAClB,OAAO,EAAE,cAAc;IACvB,WAAW,EAAE,kBAAkB;IAC/B,QAAQ,EAAE;QACR,OAAO,EAAE,IAAI;QACb,oBAAoB,EAAE,IAAI;QAC1B,gBAAgB,EAAE,KAAK;KACxB;IACD,YAAY,EAAE,SAAS,EAAE,uFAAuF;IAChH,WAAW,EAAE,YAAY,EAAE,qEAAqE;IAChG,QAAQ,EAAE,iCAAiC;IAC3C,QAAQ,EAAE;QACR,UAAU,EAAE,IAAI;QAChB,IAAI,EAAE,2eAA2e;KAClf;IACD,UAAU,EAAE;QACV,OAAO,EAAE,WAAW;QACpB,OAAO,EAAE,WAAW;QACpB,eAAe,EAAE,WAAW;QAC5B,WAAW,EAAE,IAAI;KAClB;IACD,eAAe,EAAE;QACf,UAAU;QACV,YAAY;QACZ,UAAU;QACV,SAAS;QACT,QAAQ;QACR,WAAW;QACX,UAAU;KACX;CACF,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@integraledger/lcp-binding-evm-escrow",
3
- "version": "0.10.1",
3
+ "version": "0.12.0",
4
4
  "description": "Welds an ATR hash into an authorize-and-capture escrow settlement on EVM chains.",
5
5
  "keywords": [
6
6
  "lcp",
@@ -43,13 +43,13 @@
43
43
  "homepage": "https://github.com/IntegraLedger/integra-protocol/tree/main/packages/binding-evm-escrow#readme",
44
44
  "dependencies": {
45
45
  "viem": "2.55.11",
46
- "@integraledger/lcp-binding-evm-common": "0.10.1",
47
- "@integraledger/lcp-binding-core": "0.10.1"
46
+ "@integraledger/lcp-binding-core": "0.12.0",
47
+ "@integraledger/lcp-binding-evm-common": "0.12.0"
48
48
  },
49
49
  "devDependencies": {
50
50
  "@types/node": "24.13.3",
51
51
  "vitest": "4.1.10",
52
- "@integraledger/lcp-kernel": "0.10.1"
52
+ "@integraledger/lcp-kernel": "0.12.0"
53
53
  },
54
54
  "license": "Apache-2.0",
55
55
  "engines": {
package/src/manifest.ts CHANGED
@@ -9,6 +9,16 @@ import type { BindingManifest } from "@integraledger/lcp-binding-core";
9
9
  * in `PaymentAuthorized`/`PaymentCharged` event data, so `recover`/`enumerate` decode the event and read
10
10
  * `salt` (proven on-chain).
11
11
  *
12
+ * **NO `offCanonical`, and that silence is examined rather than default.** §8.3.1 admits the marker only
13
+ * where the field is "specified as client-chosen or as a deterministic derivation that excludes
14
+ * `atrHash`", and `salt` meets NEITHER disjunct: `charge`/`authorize` are
15
+ * `onlySender(paymentInfo.operator)`, so the SERVICE constructs the whole struct and the payer signs only
16
+ * a hash of it, and the welded value IS `uint256(atrHash)` — a derivation that includes it. Nothing
17
+ * on-chain constrains the field's value, which is §8.3.1's Tier A condition. The x402 sibling declares
18
+ * `offCanonical` over the EIP-3009 nonce because THAT field is payer-chosen and its weld therefore needs
19
+ * a cooperating client stack; this one needs none, so mirroring the sibling would publish a
20
+ * controlled-client caveat that does not apply here.
21
+ *
12
22
  * **recovery.forwardIndexable = false** — the criterion is enumeration bound to a GIVEN atrHash, and this
13
23
  * rail cannot do it. `paymentInfoHash` is the only indexed topic, and it is a hash of the whole
14
24
  * `PaymentInfo` struct: a caller holding the complete struct could topic-filter, but a caller holding only
@@ -63,7 +73,7 @@ export const ESCROW_MANIFEST: BindingManifest = {
63
73
  indexing: "event-data-scan:paymentInfoHash",
64
74
  finality: {
65
75
  reversible: true,
66
- note: 'on-rail void/refund within refundExpiry — not dispute resolution (RCS-5); capture is not reversed, refund is a fresh on-rail remedy The atrHash welded into PaymentInfo.salt MUST be per-transaction: salt is specified as the struct\'s entropy source ("a source of entropy to ensure unique hashes across different payments") and an atrHash carries none, so a repeat purchase under one ATR with the same payer, caps and expiries produces a PaymentInfo the escrow has already seen and reverts.',
76
+ note: 'on-rail void/refund within refundExpiry — not dispute resolution (RCS-5); capture is not reversed, refund is a fresh on-rail remedy. The atrHash welded into PaymentInfo.salt MUST be per-transaction: salt is specified as the struct\'s entropy source ("a source of entropy to ensure unique hashes across different payments") and an atrHash carries none, so a repeat purchase under one ATR with the same payer, caps and expiries produces a PaymentInfo the escrow has already seen and reverts.',
67
77
  },
68
78
  weldGrades: {
69
79
  ERC3009: "signature",