@correntelabs/beeai-ashlar-bridge 1.0.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/README.md +75 -0
- package/STRESS_REPORT.md +71 -0
- package/dist/packages/beeai-ashlar-bridge/src/ashlar-tool.d.ts +84 -0
- package/dist/packages/beeai-ashlar-bridge/src/ashlar-tool.js +202 -0
- package/dist/packages/beeai-ashlar-bridge/src/beehive-swarm.d.ts +1 -0
- package/dist/packages/beeai-ashlar-bridge/src/beehive-swarm.js +157 -0
- package/dist/packages/beeai-ashlar-bridge/src/catalog-tool.d.ts +28 -0
- package/dist/packages/beeai-ashlar-bridge/src/catalog-tool.js +64 -0
- package/dist/packages/beeai-ashlar-bridge/src/compliance-tool.d.ts +34 -0
- package/dist/packages/beeai-ashlar-bridge/src/compliance-tool.js +73 -0
- package/dist/packages/beeai-ashlar-bridge/src/demo-agent.d.ts +1 -0
- package/dist/packages/beeai-ashlar-bridge/src/demo-agent.js +149 -0
- package/dist/packages/beeai-ashlar-bridge/src/index.d.ts +3 -0
- package/dist/packages/beeai-ashlar-bridge/src/index.js +3 -0
- package/dist/packages/beeai-ashlar-bridge/src/large-swarm-stress.d.ts +1 -0
- package/dist/packages/beeai-ashlar-bridge/src/large-swarm-stress.js +152 -0
- package/dist/src/x402/mandate.d.ts +449 -0
- package/dist/src/x402/mandate.js +1234 -0
- package/dist/src/x402/manifest-sig.d.ts +163 -0
- package/dist/src/x402/manifest-sig.js +259 -0
- package/dist/src/x402/merkle-transcript.d.ts +73 -0
- package/dist/src/x402/merkle-transcript.js +159 -0
- package/package.json +37 -0
- package/src/ashlar-tool.ts +277 -0
- package/src/beehive-swarm.ts +172 -0
- package/src/catalog-tool.ts +80 -0
- package/src/compliance-tool.ts +89 -0
- package/src/demo-agent.ts +163 -0
- package/src/index.ts +3 -0
- package/src/large-swarm-stress.ts +180 -0
- package/tsconfig.json +21 -0
|
@@ -0,0 +1,449 @@
|
|
|
1
|
+
import { type Ed25519Signer } from './manifest-sig.js';
|
|
2
|
+
export declare const MANDATE_VERSION = "x402-mandate/1";
|
|
3
|
+
/** Recipient sentinel: an EXPLICIT, signed opt-in to an unconstrained payee set. Must be the SOLE element. */
|
|
4
|
+
export declare const ANY_RECIPIENT = "*";
|
|
5
|
+
/** Accountant sentinel selecting Model B: spend entries are co-signed by their payees; no single accountant key. */
|
|
6
|
+
export declare const PAYEES_ACCOUNTANT = "payees";
|
|
7
|
+
export interface Mandate {
|
|
8
|
+
v: typeof MANDATE_VERSION;
|
|
9
|
+
/** base64url Ed25519 public key of the PRINCIPAL that issued this grant. */
|
|
10
|
+
issuer: string;
|
|
11
|
+
/** The agent payer identity this grant authorizes. Bound to the payment's payer. */
|
|
12
|
+
subject: string;
|
|
13
|
+
/** Asset the cap and amounts are denominated in. */
|
|
14
|
+
asset: string;
|
|
15
|
+
/** Maximum CUMULATIVE spend, integer minor units as a decimal string. */
|
|
16
|
+
cap: string;
|
|
17
|
+
/** Optional maximum for any SINGLE payment, integer minor units. MUST be <= cap. */
|
|
18
|
+
perPayment?: string;
|
|
19
|
+
/**
|
|
20
|
+
* Allowed payees. Under Model B ('payees' accountant) each recipient MUST
|
|
21
|
+
* itself be a base64url Ed25519 key — the identity IS the attesting key.
|
|
22
|
+
* Exactly [ANY_RECIPIENT] is a signed opt-in to any payee; mixing '*' with
|
|
23
|
+
* named recipients is invalid.
|
|
24
|
+
*/
|
|
25
|
+
recipients: string[];
|
|
26
|
+
/**
|
|
27
|
+
* THE TRUST ROOT FOR ACCOUNTING: either one base64url Ed25519 key (Model A
|
|
28
|
+
* — this exact key must sign every committed head) or PAYEES_ACCOUNTANT
|
|
29
|
+
* (Model B). A verifier MUST take the accountant from here, never from the
|
|
30
|
+
* presenter.
|
|
31
|
+
*/
|
|
32
|
+
accountant: string;
|
|
33
|
+
/** Human-readable purpose, carried verbatim. */
|
|
34
|
+
purpose: string;
|
|
35
|
+
/** Expiry, strict RFC 3339 UTC (must end in 'Z'). */
|
|
36
|
+
notAfter: string;
|
|
37
|
+
/** Uniqueness — distinguishes two otherwise-identical grants. */
|
|
38
|
+
nonce: string;
|
|
39
|
+
/**
|
|
40
|
+
* DELEGATION: the sha256:<hex> digest of the PARENT mandate this grant is
|
|
41
|
+
* carved from. Absent on a root grant. A delegated grant is signed by the
|
|
42
|
+
* parent's SUBJECT and may only NARROW every bound (see
|
|
43
|
+
* issueDelegatedMandate / verifyDelegationChain).
|
|
44
|
+
*/
|
|
45
|
+
parent?: string;
|
|
46
|
+
}
|
|
47
|
+
export interface SignedMandate {
|
|
48
|
+
mandate: Mandate;
|
|
49
|
+
alg: 'Ed25519';
|
|
50
|
+
/** base64url Ed25519 signature over DS_TAG || jcsCanonical(mandate). */
|
|
51
|
+
sig: string;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* One split leg: an ADDITIONAL recipient paid by the same payment. Mirrors an
|
|
55
|
+
* entry of x402's `PaymentRequirements.extra.splits` (platform fees, revenue
|
|
56
|
+
* share, referral commissions), reduced to the two fields authority cares
|
|
57
|
+
* about. All legs share the payment's `asset`; a scheme that splits ACROSS
|
|
58
|
+
* assets is out of scope here and MUST NOT be modelled with this field.
|
|
59
|
+
*/
|
|
60
|
+
export interface PaymentSplit {
|
|
61
|
+
recipient: string;
|
|
62
|
+
amount: string;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* One payment presented for authorization against a Mandate.
|
|
66
|
+
*
|
|
67
|
+
* MULTI-PAYEE: `recipient`/`amount` are the primary (`payTo`) leg; `splits`
|
|
68
|
+
* carries any ADDITIONAL legs of the SAME payment, matching x402's model where
|
|
69
|
+
* `extra.splits` is additional to `payTo`. Absent or empty `splits` is exactly
|
|
70
|
+
* today's single-leg behaviour, so nothing existing changes. When splits ARE
|
|
71
|
+
* present, EVERY leg's recipient is scope-checked and the per-payment bound and
|
|
72
|
+
* cap bind the TOTAL across all legs — a payment cannot pay eight strangers, or
|
|
73
|
+
* move more than its cap, by moving the money into legs the check never reads.
|
|
74
|
+
*/
|
|
75
|
+
export interface MandatePayment {
|
|
76
|
+
payer: string;
|
|
77
|
+
recipient: string;
|
|
78
|
+
asset: string;
|
|
79
|
+
amount: string;
|
|
80
|
+
/** 'sha256:...' of the mandate this payment was made under — bound into the payment preimage. */
|
|
81
|
+
mandateDigest: string;
|
|
82
|
+
/** When the payment settled, strict RFC 3339 UTC. Advisory only — expiry is judged on the verifier's clock. */
|
|
83
|
+
at?: string;
|
|
84
|
+
/** ADDITIONAL legs beyond `recipient`/`amount`. Absent = single-leg. */
|
|
85
|
+
splits?: PaymentSplit[];
|
|
86
|
+
}
|
|
87
|
+
export interface MandateVerdict {
|
|
88
|
+
ok: boolean;
|
|
89
|
+
reasons: string[];
|
|
90
|
+
warnings: string[];
|
|
91
|
+
}
|
|
92
|
+
/** The digest a payment binds to (domain-separated). */
|
|
93
|
+
export declare function mandateDigest(m: Mandate): string;
|
|
94
|
+
/** Sign a mandate with the principal's key. Rejects malformed grants, perPayment>cap, and a signer that is not the issuer. */
|
|
95
|
+
export declare function issueMandate(m: Mandate, signer: Ed25519Signer): SignedMandate;
|
|
96
|
+
/** Verify the issuer's signature over the (domain-separated) grant, offline. False on any structural or crypto failure. */
|
|
97
|
+
export declare function verifyMandateSignature(sm: SignedMandate): boolean;
|
|
98
|
+
/**
|
|
99
|
+
* The offline authority check for ONE payment against a Mandate. Total —
|
|
100
|
+
* returns a verdict, never throws — and judges expiry on the VERIFIER's clock,
|
|
101
|
+
* not the payer's self-asserted `at`.
|
|
102
|
+
*
|
|
103
|
+
* MULTI-PAYEE (`payment.splits`): a payment may pay several recipients at once
|
|
104
|
+
* (x402 `extra.splits`). EVERY leg is scope-checked, and the per-payment bound
|
|
105
|
+
* and the cap bind the TOTAL across all legs. Without that, a mandate scoped to
|
|
106
|
+
* one merchant would authorize a payment that also pays eight strangers, and a
|
|
107
|
+
* cap would bind only the primary leg while the real outflow exceeded it — the
|
|
108
|
+
* exact failure this object exists to prevent, re-entering through a scheme
|
|
109
|
+
* feature. Absent/empty `splits` is byte-for-byte today's behaviour.
|
|
110
|
+
*
|
|
111
|
+
* HONESTY: this bounds ONE payment's total. The spend LOG still records a
|
|
112
|
+
* single (recipient, amount) per entry, so a multi-leg payment's per-leg
|
|
113
|
+
* breakdown is not represented in the Merkle log — cumulative accounting sees
|
|
114
|
+
* the primary leg only. Log the TOTAL as the entry amount until SpendEntry
|
|
115
|
+
* grows legs, and treat per-leg cumulative attribution as unsolved.
|
|
116
|
+
*/
|
|
117
|
+
export declare function checkPayment(sm: SignedMandate, payment: MandatePayment, opts?: {
|
|
118
|
+
now?: () => number;
|
|
119
|
+
}): MandateVerdict;
|
|
120
|
+
/** One committed spend under a mandate. Bound to the mandate + asset, and chained to the prior root (append-only). */
|
|
121
|
+
export interface SpendEntry {
|
|
122
|
+
mandateDigest: string;
|
|
123
|
+
asset: string;
|
|
124
|
+
paymentId: string;
|
|
125
|
+
recipient: string;
|
|
126
|
+
amount: string;
|
|
127
|
+
/** Running total after this payment. Equals previous cumulative + amount. */
|
|
128
|
+
cumulative: string;
|
|
129
|
+
/** Merkle root of the log BEFORE this entry — an edit/reorder/insert breaks the chain. */
|
|
130
|
+
priorRoot: string;
|
|
131
|
+
}
|
|
132
|
+
/** An append-only, mandate-bound, Merkle-committed record of spend under one mandate. */
|
|
133
|
+
export declare class SpendLog {
|
|
134
|
+
private readonly mandateDigestHex;
|
|
135
|
+
private readonly asset;
|
|
136
|
+
private entries;
|
|
137
|
+
private ids;
|
|
138
|
+
constructor(mandateDigestHex: string, asset: string);
|
|
139
|
+
add(paymentId: string, recipient: string, amount: string): this;
|
|
140
|
+
get size(): number;
|
|
141
|
+
all(): SpendEntry[];
|
|
142
|
+
total(): string;
|
|
143
|
+
root(): string;
|
|
144
|
+
prove(index: number): {
|
|
145
|
+
entry: SpendEntry;
|
|
146
|
+
index: number;
|
|
147
|
+
size: number;
|
|
148
|
+
proof: string[];
|
|
149
|
+
};
|
|
150
|
+
/**
|
|
151
|
+
* Produce the committed head over the current (root, total) at a monotonic
|
|
152
|
+
* seq. This is the ONLY blessed committing path: the root and total are
|
|
153
|
+
* derived from the entries the log actually holds, never handed in — an
|
|
154
|
+
* accountant signing a (root, total) it did not derive is signing blind.
|
|
155
|
+
*/
|
|
156
|
+
commit(seq: number, accountant: Ed25519Signer, at: string): SignedCommitment;
|
|
157
|
+
}
|
|
158
|
+
/** Verify one spend entry belongs to a committed spend-log root, holding nothing else. */
|
|
159
|
+
export declare function verifySpendEntry(entry: SpendEntry, index: number, size: number, proof: string[], root: string): boolean;
|
|
160
|
+
/**
|
|
161
|
+
* Verify a presented spend history is INTERNALLY sound against a mandate:
|
|
162
|
+
* authenticated grant, every entry bound to THIS mandate + asset, scope and
|
|
163
|
+
* per-payment re-checked, duplicate paymentIds refused, append-only chain
|
|
164
|
+
* verified, running totals gap-free, total within cap.
|
|
165
|
+
*
|
|
166
|
+
* HONESTY: a truncated PREFIX of the true history passes all of this. This
|
|
167
|
+
* function alone is tamper-evidence, not completeness — completeness comes
|
|
168
|
+
* from a committed head (Model A) with a freshness floor.
|
|
169
|
+
*/
|
|
170
|
+
export declare function verifySpendWithinCap(sm: SignedMandate, entries: SpendEntry[]): MandateVerdict;
|
|
171
|
+
export declare const COMMIT_VERSION = "x402-mandate-commit/1";
|
|
172
|
+
/** A signed statement that "as of seq, the spend log for this mandate has this root and this total." */
|
|
173
|
+
export interface SpendCommitment {
|
|
174
|
+
v: typeof COMMIT_VERSION;
|
|
175
|
+
mandateDigest: string;
|
|
176
|
+
/** The Ed25519 key that signs this head. MUST equal the mandate's accountant to be acceptable. */
|
|
177
|
+
accountant: string;
|
|
178
|
+
/** Monotonic per mandate — a later head MUST carry a strictly greater seq. */
|
|
179
|
+
seq: number;
|
|
180
|
+
root: string;
|
|
181
|
+
total: string;
|
|
182
|
+
at: string;
|
|
183
|
+
}
|
|
184
|
+
export interface SignedCommitment {
|
|
185
|
+
commitment: SpendCommitment;
|
|
186
|
+
alg: 'Ed25519';
|
|
187
|
+
sig: string;
|
|
188
|
+
}
|
|
189
|
+
/** Sign a committed head. Prefer SpendLog.commit, which derives root/total from real entries. */
|
|
190
|
+
export declare function commitHead(c: SpendCommitment, accountant: Ed25519Signer): SignedCommitment;
|
|
191
|
+
/**
|
|
192
|
+
* Verify a head is SELF-consistent: signed by the key it names as accountant.
|
|
193
|
+
* Self-consistency is not authorization — verifySpendAgainstCommitment also
|
|
194
|
+
* requires that key to equal the MANDATE's accountant (the trust root).
|
|
195
|
+
*/
|
|
196
|
+
export declare function verifyCommitment(sc: SignedCommitment): boolean;
|
|
197
|
+
/** LAYER 2: the digest anchored on-chain / in a transparency log. */
|
|
198
|
+
export declare function commitmentAnchorDigest(sc: SignedCommitment): string;
|
|
199
|
+
/**
|
|
200
|
+
* LAYER 2 calldata (the evidence lane's x402note/1 grammar). Carries the
|
|
201
|
+
* mandate + seq so a scanner can find the highest anchored head for a mandate,
|
|
202
|
+
* and `by=<accountant>` so anchors are attributable without fetching the head.
|
|
203
|
+
* Anchoring is permissionless: an anchor proves WHEN, never honesty.
|
|
204
|
+
*/
|
|
205
|
+
export declare function commitmentAnchorCalldata(sc: SignedCommitment): string;
|
|
206
|
+
/** Verify an on-chain anchor's calldata commits to exactly THIS head. Pair with the tx's block timestamp for the WHEN. */
|
|
207
|
+
export declare function verifyCommitmentAnchor(sc: SignedCommitment, calldata: string): boolean;
|
|
208
|
+
/**
|
|
209
|
+
* MODEL A cap check against a committed head. The accountant is taken from the
|
|
210
|
+
* MANDATE (the trust root) — a presenter can never name their own. The
|
|
211
|
+
* presented entries must reproduce the committed root AND total.
|
|
212
|
+
*
|
|
213
|
+
* `lastSeq` is REQUIRED: pass the highest seq you have previously accepted for
|
|
214
|
+
* this mandate (from your own state, or the highest anchored seq you read from
|
|
215
|
+
* Layer 2). Passing `null` means you have no freshness floor — the check still
|
|
216
|
+
* runs, but the verdict carries a loud warning: without a floor you get
|
|
217
|
+
* tamper-evidence, NOT completeness, because an old validly-signed head plus
|
|
218
|
+
* its prefix log passes (first-contact truncation).
|
|
219
|
+
*/
|
|
220
|
+
export declare function verifySpendAgainstCommitment(sm: SignedMandate, entries: SpendEntry[], sc: SignedCommitment, lastSeq: number | null): MandateVerdict;
|
|
221
|
+
/**
|
|
222
|
+
* The counterparty's POST-SETTLE check (MUST-rule 2): the payment it just
|
|
223
|
+
* accepted is INCLUDED under a NEW committed head strictly newer than the one
|
|
224
|
+
* it saw before settling. This is what forces the head to advance per payment
|
|
225
|
+
* — an authorized payment that is never committed is otherwise invisible.
|
|
226
|
+
*/
|
|
227
|
+
export declare function verifyPaymentCommitted(sm: SignedMandate, paymentId: string, proof: {
|
|
228
|
+
entry: SpendEntry;
|
|
229
|
+
index: number;
|
|
230
|
+
size: number;
|
|
231
|
+
proof: string[];
|
|
232
|
+
}, newHead: SignedCommitment, prevSeq: number,
|
|
233
|
+
/** OPTIONAL but STRONGLY RECOMMENDED: the fields decoded from the settled
|
|
234
|
+
* artifact. When supplied, the included entry's recipient/asset/amount MUST
|
|
235
|
+
* equal them — this is what stops an agent committing a truthful-looking
|
|
236
|
+
* head over an amount smaller than what actually settled (see §7/§13). */
|
|
237
|
+
settled?: {
|
|
238
|
+
recipient: string;
|
|
239
|
+
asset: string;
|
|
240
|
+
amount: string;
|
|
241
|
+
}): MandateVerdict;
|
|
242
|
+
export declare const PAYEE_ATT_VERSION = "x402-mandate-payee/1";
|
|
243
|
+
/**
|
|
244
|
+
* A payee's signed acknowledgement that it received a specific payment under a
|
|
245
|
+
* mandate. The payee identity IS its Ed25519 key (enforced by the mandate's
|
|
246
|
+
* shape when accountant='payees'), so there is no prose-level payee↔key
|
|
247
|
+
* binding for an agent to spoof: the signature verifies against the entry's
|
|
248
|
+
* recipient, or it does not verify.
|
|
249
|
+
*/
|
|
250
|
+
export interface PayeeAttestation {
|
|
251
|
+
v: typeof PAYEE_ATT_VERSION;
|
|
252
|
+
mandateDigest: string;
|
|
253
|
+
paymentId: string;
|
|
254
|
+
/** The payee — the base64url Ed25519 key that is BOTH the recipient identity and the signer. */
|
|
255
|
+
payee: string;
|
|
256
|
+
asset: string;
|
|
257
|
+
amount: string;
|
|
258
|
+
at: string;
|
|
259
|
+
}
|
|
260
|
+
export interface SignedPayeeAttestation {
|
|
261
|
+
attestation: PayeeAttestation;
|
|
262
|
+
alg: 'Ed25519';
|
|
263
|
+
sig: string;
|
|
264
|
+
}
|
|
265
|
+
/** The payee signs "I received this payment under this mandate." Signer's key must BE the payee identity. */
|
|
266
|
+
export declare function attestPayment(a: PayeeAttestation, payee: Ed25519Signer): SignedPayeeAttestation;
|
|
267
|
+
/** Verify a payee attestation's signature against the payee identity itself, offline. */
|
|
268
|
+
export declare function verifyPayeeAttestation(spa: SignedPayeeAttestation): boolean;
|
|
269
|
+
/**
|
|
270
|
+
* MODEL B check: every presented entry must be backed by an authentic
|
|
271
|
+
* attestation signed by the RECIPIENT ITSELF (identity == key). Requires the
|
|
272
|
+
* mandate's accountant to be 'payees'.
|
|
273
|
+
*
|
|
274
|
+
* WHAT THIS PROVES: no fabricated entries — an agent cannot invent a payment a
|
|
275
|
+
* payee never acknowledged.
|
|
276
|
+
* WHAT IT DOES NOT PROVE: completeness. Dropping an entry AND its attestation
|
|
277
|
+
* passes. Do not present this check alone as omission-proof — completeness in
|
|
278
|
+
* Model B comes from reconcilePayeeEvidence below, run against payee evidence
|
|
279
|
+
* the verifier gathered independently of the agent.
|
|
280
|
+
*/
|
|
281
|
+
export declare function verifySpendWithPayeeAttestations(sm: SignedMandate, entries: SpendEntry[], attestations: SignedPayeeAttestation[]): MandateVerdict;
|
|
282
|
+
/** The digest a payee attestation is anchored under. */
|
|
283
|
+
export declare function payeeAttestationAnchorDigest(spa: SignedPayeeAttestation): string;
|
|
284
|
+
/**
|
|
285
|
+
* A payee anchors ITS OWN evidence (the x402note/1 grammar; `@<paymentId>`
|
|
286
|
+
* keeps attestation anchors distinguishable from head anchors' `#<seq>`). This
|
|
287
|
+
* is what makes an omitted payment publicly discoverable: the agent controls
|
|
288
|
+
* its log, but it does not control the payee's anchor.
|
|
289
|
+
*/
|
|
290
|
+
export declare function payeeAttestationAnchorCalldata(spa: SignedPayeeAttestation): string;
|
|
291
|
+
/**
|
|
292
|
+
* MODEL B OMISSION-RESISTANCE — the payee-evidence reconciler. `evidence` is
|
|
293
|
+
* the set of payee attestations the VERIFIER gathered INDEPENDENTLY (from the
|
|
294
|
+
* payees, or by scanning their anchors) — never from the agent. Then:
|
|
295
|
+
*
|
|
296
|
+
* 1. the presented entries must all be payee-backed (no fabrication), AND
|
|
297
|
+
* 2. every valid attestation for THIS mandate must appear in the log — an
|
|
298
|
+
* attested payment with no entry is the OMISSION, caught by the party who
|
|
299
|
+
* cannot un-know it was paid, AND
|
|
300
|
+
* 3. the attested floor (sum over all attested payments, whether logged or
|
|
301
|
+
* not) must be within cap — a short log cannot hide over-spend the payees
|
|
302
|
+
* can prove.
|
|
303
|
+
*
|
|
304
|
+
* Hostile-input hygiene: attestations that fail signature verification or
|
|
305
|
+
* belong to other mandates are IGNORED with a warning (a stranger cannot DoS a
|
|
306
|
+
* verdict with junk); two authentic attestations from one payee for one
|
|
307
|
+
* paymentId with different facts = payee equivocation, refused loudly.
|
|
308
|
+
*/
|
|
309
|
+
export declare function reconcilePayeeEvidence(sm: SignedMandate, entries: SpendEntry[], evidence: SignedPayeeAttestation[]): MandateVerdict;
|
|
310
|
+
/** The parent's subject carves a narrowed child grant. Refuses any widening. */
|
|
311
|
+
export declare function issueDelegatedMandate(child: Mandate, parentSm: SignedMandate, delegator: Ed25519Signer): SignedMandate;
|
|
312
|
+
/**
|
|
313
|
+
* Walk a delegation chain root→leaf, entirely offline: every link signed,
|
|
314
|
+
* every hop narrowing, the leaf usable exactly like any mandate. Returns the
|
|
315
|
+
* ROOT ISSUER — the human origin, recoverable at any depth.
|
|
316
|
+
*/
|
|
317
|
+
export declare function verifyDelegationChain(chain: SignedMandate[]): MandateVerdict & {
|
|
318
|
+
rootIssuer?: string;
|
|
319
|
+
};
|
|
320
|
+
export declare const BINDING_VERSION = "x402-mandate-binding/1";
|
|
321
|
+
/** The bytes32 nonce an EIP-3009 TransferWithAuthorization MUST carry for this (mandate, payment). */
|
|
322
|
+
export declare function eip3009BindingNonce(mandateDigestStr: string, paymentId: string): string;
|
|
323
|
+
/** The uint256 nonce (decimal string) a Permit2 PermitTransferFrom MUST carry. */
|
|
324
|
+
export declare function permit2BindingNonce(mandateDigestStr: string, paymentId: string): string;
|
|
325
|
+
/** The InvoiceID (uppercase 64-hex, XRPL convention) a Payment tx MUST carry. */
|
|
326
|
+
export declare function xrplBindingInvoiceId(mandateDigestStr: string, paymentId: string): string;
|
|
327
|
+
export type BindingScheme = 'eip3009' | 'permit2' | 'xrpl';
|
|
328
|
+
/**
|
|
329
|
+
* Verify the binding FROM THE SETTLED ARTIFACT: pass the value read out of the
|
|
330
|
+
* settled authorization/tx (the EIP-3009 nonce, the Permit2 nonce, the XRPL
|
|
331
|
+
* InvoiceID) and the claimed (mandate, paymentId). True iff the slot commits to
|
|
332
|
+
* exactly this pair. Total — false on anything malformed.
|
|
333
|
+
*/
|
|
334
|
+
export declare function verifyPaymentBinding(scheme: BindingScheme, settledValue: string, mandateDigestStr: string, paymentId: string): boolean;
|
|
335
|
+
/** The fields a counterparty MUST decode OUT of the settled artifact (never presented). */
|
|
336
|
+
export interface SettledTransfer {
|
|
337
|
+
scheme: BindingScheme;
|
|
338
|
+
/** The binding slot: EIP-3009 nonce / Permit2 nonce / XRPL InvoiceID, read from the settled tx. */
|
|
339
|
+
slot: string;
|
|
340
|
+
/** from / Account. */
|
|
341
|
+
payer: string;
|
|
342
|
+
/** to / Destination. */
|
|
343
|
+
recipient: string;
|
|
344
|
+
/** token contract / XRPL currency. */
|
|
345
|
+
asset: string;
|
|
346
|
+
/** value / delivered_amount, integer minor units. */
|
|
347
|
+
amount: string;
|
|
348
|
+
/**
|
|
349
|
+
* ADDITIONAL legs decoded from the SAME settled artifact (a multi-payee
|
|
350
|
+
* split: x402 `extra.splits`, one on-chain transfer per leg). A verifier
|
|
351
|
+
* that decodes only the primary transfer of a split settlement under-counts
|
|
352
|
+
* the outflow — decode EVERY transfer the artifact performed, or the cap
|
|
353
|
+
* binds nothing. Absent = single-transfer settlement.
|
|
354
|
+
*/
|
|
355
|
+
splits?: PaymentSplit[];
|
|
356
|
+
}
|
|
357
|
+
/**
|
|
358
|
+
* THE ENFORCEMENT the §7 binding alone cannot give: the binding slot commits
|
|
359
|
+
* only to (mandate, paymentId), NOT to amount/recipient/asset. A cap that binds
|
|
360
|
+
* only self-asserted amounts binds nothing on a rail the agent controls. This
|
|
361
|
+
* check reads the ACTUAL transferred fields out of the settled artifact and
|
|
362
|
+
* runs the full §6 authority check on THEM — so an agent that settles 1,000,000
|
|
363
|
+
* cannot log amount='1'. A counterparty deriving cumulative-spend conclusions
|
|
364
|
+
* MUST call this, not verifyPaymentBinding alone. Total — never throws.
|
|
365
|
+
*/
|
|
366
|
+
export declare function verifySettledPayment(sm: SignedMandate, settled: SettledTransfer, paymentId: string, opts?: {
|
|
367
|
+
now?: () => number;
|
|
368
|
+
}): MandateVerdict;
|
|
369
|
+
export interface ParsedHeadAnchor {
|
|
370
|
+
anchorDigest: string;
|
|
371
|
+
mandateDigest: string;
|
|
372
|
+
seq: number;
|
|
373
|
+
/** Absent on pre-hardening anchors (the historic 2026-08-19 anchor has no by=). */
|
|
374
|
+
by?: string;
|
|
375
|
+
}
|
|
376
|
+
/**
|
|
377
|
+
* Parse an on-chain head-anchor calldata. Tolerates the historic pre-`by=`
|
|
378
|
+
* grammar so scanners can read every anchor ever made. Returns null on
|
|
379
|
+
* anything that is not a head anchor (payee anchors use `@`, not `#`).
|
|
380
|
+
*/
|
|
381
|
+
export declare function parseCommitmentAnchorCalldata(calldata: string): ParsedHeadAnchor | null;
|
|
382
|
+
/**
|
|
383
|
+
* From a batch of decoded anchor calldatas (a scanner's haul), the highest
|
|
384
|
+
* anchored seq for a mandate — the FIRST-CONTACT FRESHNESS FLOOR to pass as
|
|
385
|
+
* lastSeq. When the mandate names a single-key accountant, anchors not
|
|
386
|
+
* carrying that `by=` are ignored (permissionless anchoring means strangers
|
|
387
|
+
* can anchor anything; only the accountant's own anchors set the floor).
|
|
388
|
+
* Returns null when no qualifying anchor exists — which a verifier must treat
|
|
389
|
+
* as "no floor", not "seq 0".
|
|
390
|
+
*/
|
|
391
|
+
/** An anchor read off-chain, paired with the head its digest points at. */
|
|
392
|
+
export interface AnchorEvidence {
|
|
393
|
+
/** The `x402note/1` calldata as read from the chain. */
|
|
394
|
+
calldata: string;
|
|
395
|
+
/**
|
|
396
|
+
* The head retrieved out-of-band for this anchor's digest. Absent when the
|
|
397
|
+
* content could not be fetched — such an anchor is NOT floor-eligible.
|
|
398
|
+
*/
|
|
399
|
+
head?: SignedCommitment;
|
|
400
|
+
}
|
|
401
|
+
/**
|
|
402
|
+
* RUNG 3, done properly: derive a freshness floor ONLY from anchors whose
|
|
403
|
+
* content has been retrieved and re-verified.
|
|
404
|
+
*
|
|
405
|
+
* WHY THIS EXISTS: anchoring is permissionless and `by=` is unauthenticated
|
|
406
|
+
* plaintext, while the accountant's key is public (it is named in the grant).
|
|
407
|
+
* So `highestAnchoredSeq` — which trusts the seq printed in calldata — is
|
|
408
|
+
* POISONABLE: a stranger anchors `…#999999999;by=<the real accountant>` and
|
|
409
|
+
* every honest head afterwards is refused as a rollback. That failure is not a
|
|
410
|
+
* missed omission, it is an honest record permanently refused.
|
|
411
|
+
*
|
|
412
|
+
* Here an anchored seq is a POINTER, never a fact. For an anchor to move the
|
|
413
|
+
* floor, all of the following must hold: the calldata parses and names THIS
|
|
414
|
+
* mandate; `by=` equals the mandate's accountant; the head was retrieved; the
|
|
415
|
+
* anchor digest recomputes over that head; the head's signature verifies; and
|
|
416
|
+
* the head itself names this mandate, this accountant, and this seq. Anything
|
|
417
|
+
* short of that is ignored with a warning — which can only LOWER the floor
|
|
418
|
+
* toward the tamper-evidence downgrade, never raise it.
|
|
419
|
+
*/
|
|
420
|
+
export declare function verifiedAnchoredSeq(sm: SignedMandate, anchors: AnchorEvidence[]): {
|
|
421
|
+
seq: number | null;
|
|
422
|
+
reasons: string[];
|
|
423
|
+
warnings: string[];
|
|
424
|
+
};
|
|
425
|
+
/**
|
|
426
|
+
* ⚠️ UNVERIFIED floor: trusts the seq printed in permissionless calldata and is
|
|
427
|
+
* therefore POISONABLE by any chain writer (see verifiedAnchoredSeq). Retained
|
|
428
|
+
* for scanning/equivocation surveys where a claimed seq is a lead to follow,
|
|
429
|
+
* NOT for deriving the floor a verifier will refuse honest heads against.
|
|
430
|
+
*/
|
|
431
|
+
export declare function highestAnchoredSeq(calldatas: string[], mandateDigestStr: string, accountant?: string): number | null;
|
|
432
|
+
/**
|
|
433
|
+
* The accountant-side check the delegation section's honest limit calls for:
|
|
434
|
+
* given a parent grant and the set of children issued under it, every child
|
|
435
|
+
* must be an authentic, correctly-narrowing child of THIS parent, digests
|
|
436
|
+
* distinct, and the SUM of child caps must fit within the parent cap.
|
|
437
|
+
*
|
|
438
|
+
* HONESTY: this binds over the set it is GIVEN. Only the party that registers
|
|
439
|
+
* delegations (the accountant, or the delegator's own ledger) knows the set is
|
|
440
|
+
* complete — a verifier handed a partial set gets a partial answer. That is
|
|
441
|
+
* the same completeness shape as spend, and the same parties close it.
|
|
442
|
+
*/
|
|
443
|
+
export declare function verifySiblingBudget(parentSm: SignedMandate, children: SignedMandate[]): MandateVerdict;
|
|
444
|
+
/** Authorize one payment against the LEAF of a verified delegation chain. */
|
|
445
|
+
export declare function checkDelegatedPayment(chain: SignedMandate[], payment: MandatePayment, opts?: {
|
|
446
|
+
now?: () => number;
|
|
447
|
+
}): MandateVerdict & {
|
|
448
|
+
rootIssuer?: string;
|
|
449
|
+
};
|