@keelcodes/settlement 0.1.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/LICENSE +201 -0
- package/README.md +316 -0
- package/dist/a2a.d.ts +107 -0
- package/dist/a2a.d.ts.map +1 -0
- package/dist/a2a.js +254 -0
- package/dist/a2a.js.map +1 -0
- package/dist/erc8183.d.ts +108 -0
- package/dist/erc8183.d.ts.map +1 -0
- package/dist/erc8183.js +206 -0
- package/dist/erc8183.js.map +1 -0
- package/dist/index.d.ts +21 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +11 -0
- package/dist/index.js.map +1 -0
- package/dist/ledger.d.ts +37 -0
- package/dist/ledger.d.ts.map +1 -0
- package/dist/ledger.js +114 -0
- package/dist/ledger.js.map +1 -0
- package/dist/mpp.d.ts +78 -0
- package/dist/mpp.d.ts.map +1 -0
- package/dist/mpp.js +129 -0
- package/dist/mpp.js.map +1 -0
- package/dist/rails.d.ts +132 -0
- package/dist/rails.d.ts.map +1 -0
- package/dist/rails.js +147 -0
- package/dist/rails.js.map +1 -0
- package/dist/receipts.d.ts +25 -0
- package/dist/receipts.d.ts.map +1 -0
- package/dist/receipts.js +44 -0
- package/dist/receipts.js.map +1 -0
- package/dist/types.d.ts +100 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +19 -0
- package/dist/types.js.map +1 -0
- package/dist/webhooks.d.ts +106 -0
- package/dist/webhooks.d.ts.map +1 -0
- package/dist/webhooks.js +162 -0
- package/dist/webhooks.js.map +1 -0
- package/dist/wire.d.ts +31 -0
- package/dist/wire.d.ts.map +1 -0
- package/dist/wire.js +224 -0
- package/dist/wire.js.map +1 -0
- package/dist/x402.d.ts +93 -0
- package/dist/x402.d.ts.map +1 -0
- package/dist/x402.js +196 -0
- package/dist/x402.js.map +1 -0
- package/package.json +52 -0
package/dist/rails.js
ADDED
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import { SettlementError } from './types.js';
|
|
2
|
+
/** The stable order used as the final, documented kind tie-break. */
|
|
3
|
+
export const RAIL_KIND_ORDER = ['x402', 'mpp', 'chain', 'fiat'];
|
|
4
|
+
function assertDecimals(decimals) {
|
|
5
|
+
if (!Number.isInteger(decimals) || decimals < 0) {
|
|
6
|
+
throw new SettlementError('malformed-payload', `decimals must be a non-negative integer, got ${decimals}`);
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
const DECIMAL = /^\d+(\.\d+)?$/;
|
|
10
|
+
/**
|
|
11
|
+
* Parses a human decimal amount into atomic units for `decimals` places, e.g.
|
|
12
|
+
* `parseUnits('0.01', 6) === 10000n`.
|
|
13
|
+
*
|
|
14
|
+
* Hand-rolled on purpose rather than delegated to `viem`'s `parseUnits`. viem
|
|
15
|
+
* rounds when the input carries more fractional digits than the asset supports
|
|
16
|
+
* (`1.234` at 2dp yields `123n`), and its parsing rules have changed between
|
|
17
|
+
* releases — inheriting either is a poor foundation for settlement arithmetic.
|
|
18
|
+
* More fractional digits than the asset supports is an error here, not a silent
|
|
19
|
+
* truncation: rounding someone's money is a bug, not a convenience.
|
|
20
|
+
*
|
|
21
|
+
* `rails.units.viem.test.ts` pins both the agreement on the accepted domain and
|
|
22
|
+
* this intentional divergence, so neither can drift unnoticed.
|
|
23
|
+
*/
|
|
24
|
+
export function parseUnits(value, decimals) {
|
|
25
|
+
assertDecimals(decimals);
|
|
26
|
+
if (typeof value !== 'string' || !DECIMAL.test(value)) {
|
|
27
|
+
throw new SettlementError('malformed-payload', `${JSON.stringify(value)} is not a decimal amount`);
|
|
28
|
+
}
|
|
29
|
+
const [whole = '0', fraction = ''] = value.split('.');
|
|
30
|
+
if (fraction.length > decimals) {
|
|
31
|
+
throw new SettlementError('malformed-payload', `${value} has more than ${decimals} decimal places`);
|
|
32
|
+
}
|
|
33
|
+
return BigInt(whole + fraction.padEnd(decimals, '0'));
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Renders atomic units as a human decimal amount, trimming trailing zeros so
|
|
37
|
+
* that `parseUnits(formatUnits(n, d), d) === n` round-trips exactly.
|
|
38
|
+
*/
|
|
39
|
+
export function formatUnits(value, decimals) {
|
|
40
|
+
assertDecimals(decimals);
|
|
41
|
+
if (decimals === 0)
|
|
42
|
+
return value.toString();
|
|
43
|
+
const negative = value < 0n;
|
|
44
|
+
const digits = (negative ? -value : value).toString().padStart(decimals + 1, '0');
|
|
45
|
+
const whole = digits.slice(0, digits.length - decimals);
|
|
46
|
+
const fraction = digits.slice(digits.length - decimals).replace(/0+$/, '');
|
|
47
|
+
const rendered = fraction === '' ? whole : `${whole}.${fraction}`;
|
|
48
|
+
return negative ? `-${rendered}` : rendered;
|
|
49
|
+
}
|
|
50
|
+
function matchesAsset(asset, ref) {
|
|
51
|
+
return (asset.address.toLowerCase() === ref.toLowerCase() ||
|
|
52
|
+
asset.symbol.toLowerCase() === ref.toLowerCase());
|
|
53
|
+
}
|
|
54
|
+
function findAsset(rail, network, ref) {
|
|
55
|
+
return rail.assets.find((candidate) => (candidate.networks ?? rail.networks).includes(network) && matchesAsset(candidate, ref));
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Resolves the asset descriptor for `assetRef` on `network`, proving the rail
|
|
59
|
+
* accepts it there. Throws a `requirement-mismatch` rather than routing a
|
|
60
|
+
* payment the rail cannot actually settle.
|
|
61
|
+
*/
|
|
62
|
+
export function resolveRailAsset(rail, network, assetRef) {
|
|
63
|
+
const asset = findAsset(rail, network, assetRef);
|
|
64
|
+
if (asset === undefined) {
|
|
65
|
+
throw new SettlementError('requirement-mismatch', `rail ${rail.id} does not accept ${JSON.stringify(assetRef)} on ${network}`);
|
|
66
|
+
}
|
|
67
|
+
return asset;
|
|
68
|
+
}
|
|
69
|
+
function isEligible(rail, intent, constraints) {
|
|
70
|
+
if (constraints.kinds !== undefined && !constraints.kinds.includes(rail.kind))
|
|
71
|
+
return false;
|
|
72
|
+
// The plan settles on the intent's network, so it must be an allowed one.
|
|
73
|
+
if (constraints.networks !== undefined && !constraints.networks.includes(intent.network))
|
|
74
|
+
return false;
|
|
75
|
+
if (!rail.networks.includes(intent.network))
|
|
76
|
+
return false;
|
|
77
|
+
const asset = findAsset(rail, intent.network, intent.asset);
|
|
78
|
+
if (asset === undefined)
|
|
79
|
+
return false;
|
|
80
|
+
if (constraints.assets !== undefined &&
|
|
81
|
+
!constraints.assets.some((ref) => matchesAsset(asset, ref))) {
|
|
82
|
+
return false;
|
|
83
|
+
}
|
|
84
|
+
if (rail.kind === 'fiat' && (rail.checkout === undefined || rail.checkout === ''))
|
|
85
|
+
return false;
|
|
86
|
+
if (constraints.maxAmount !== undefined && intent.amount + (rail.cost ?? 0n) > constraints.maxAmount) {
|
|
87
|
+
return false;
|
|
88
|
+
}
|
|
89
|
+
return true;
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Total order used for the deterministic pick: cheapest `cost`, then lowest
|
|
93
|
+
* `priority`, then {@link RAIL_KIND_ORDER}, then lexicographic `id`.
|
|
94
|
+
*/
|
|
95
|
+
function compareRails(a, b) {
|
|
96
|
+
const costA = a.cost ?? 0n;
|
|
97
|
+
const costB = b.cost ?? 0n;
|
|
98
|
+
if (costA !== costB)
|
|
99
|
+
return costA < costB ? -1 : 1;
|
|
100
|
+
const priorityA = a.priority ?? 0;
|
|
101
|
+
const priorityB = b.priority ?? 0;
|
|
102
|
+
if (priorityA !== priorityB)
|
|
103
|
+
return priorityA - priorityB;
|
|
104
|
+
const kindA = RAIL_KIND_ORDER.indexOf(a.kind);
|
|
105
|
+
const kindB = RAIL_KIND_ORDER.indexOf(b.kind);
|
|
106
|
+
if (kindA !== kindB)
|
|
107
|
+
return kindA - kindB;
|
|
108
|
+
if (a.id < b.id)
|
|
109
|
+
return -1;
|
|
110
|
+
if (a.id > b.id)
|
|
111
|
+
return 1;
|
|
112
|
+
return 0;
|
|
113
|
+
}
|
|
114
|
+
function describeConstraints(constraints) {
|
|
115
|
+
return JSON.stringify(constraints, (_key, value) => typeof value === 'bigint' ? value.toString() : value);
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Picks the cheapest eligible rail for an intent and materialises the plan.
|
|
119
|
+
*
|
|
120
|
+
* Throws `no-acceptable-requirement` when nothing can satisfy the intent and
|
|
121
|
+
* constraints — never returns a "best effort" pick, because silently routing a
|
|
122
|
+
* payment on terms the payer did not accept is how an agent overpays.
|
|
123
|
+
*/
|
|
124
|
+
export function selectRail(rails, intent, constraints = {}) {
|
|
125
|
+
const eligible = rails.filter((rail) => isEligible(rail, intent, constraints));
|
|
126
|
+
if (eligible.length === 0) {
|
|
127
|
+
throw new SettlementError('no-acceptable-requirement', `no rail can settle intent ${intent.id} (${intent.amount.toString()} ${intent.asset} on ${intent.network}) under ${describeConstraints(constraints)}`);
|
|
128
|
+
}
|
|
129
|
+
const rail = eligible.reduce((best, candidate) => compareRails(candidate, best) < 0 ? candidate : best);
|
|
130
|
+
const asset = resolveRailAsset(rail, intent.network, intent.asset);
|
|
131
|
+
const cost = rail.cost ?? 0n;
|
|
132
|
+
const plan = {
|
|
133
|
+
railId: rail.id,
|
|
134
|
+
kind: rail.kind,
|
|
135
|
+
network: intent.network,
|
|
136
|
+
asset,
|
|
137
|
+
amount: intent.amount,
|
|
138
|
+
amountDecimal: formatUnits(intent.amount, asset.decimals),
|
|
139
|
+
cost,
|
|
140
|
+
total: intent.amount + cost,
|
|
141
|
+
payTo: rail.payTo,
|
|
142
|
+
};
|
|
143
|
+
if (rail.checkout !== undefined)
|
|
144
|
+
plan.checkoutHandle = rail.checkout;
|
|
145
|
+
return { rail, plan };
|
|
146
|
+
}
|
|
147
|
+
//# sourceMappingURL=rails.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rails.js","sourceRoot":"","sources":["../src/rails.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { type PaymentIntent, type SettlementReceipt } from './types.js';
|
|
2
|
+
import { type X402Requirements, type X402SettlementResponse } from './x402.js';
|
|
3
|
+
/**
|
|
4
|
+
* Turns a protocol response into a {@link SettlementReceipt}.
|
|
5
|
+
*
|
|
6
|
+
* Both builders are deliberately strict: a receipt is evidence, so it is only
|
|
7
|
+
* produced once the payment demonstrably succeeded *and* the terms still match
|
|
8
|
+
* the intent. A "successful" settlement of the wrong amount is still wrong.
|
|
9
|
+
*/
|
|
10
|
+
export interface X402ReceiptArgs {
|
|
11
|
+
intent: PaymentIntent;
|
|
12
|
+
requirement: X402Requirements;
|
|
13
|
+
response: X402SettlementResponse;
|
|
14
|
+
/** ISO-8601 timestamp of settlement. */
|
|
15
|
+
settledAt: string;
|
|
16
|
+
}
|
|
17
|
+
export declare function receiptFromX402(args: X402ReceiptArgs): SettlementReceipt;
|
|
18
|
+
export interface MppReceiptArgs {
|
|
19
|
+
intent: PaymentIntent;
|
|
20
|
+
/** The `Payment-Receipt` header value, kept verbatim as the proof. */
|
|
21
|
+
receipt: string;
|
|
22
|
+
settledAt: string;
|
|
23
|
+
}
|
|
24
|
+
export declare function receiptFromMpp(args: MppReceiptArgs): SettlementReceipt;
|
|
25
|
+
//# sourceMappingURL=receipts.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"receipts.d.ts","sourceRoot":"","sources":["../src/receipts.ts"],"names":[],"mappings":"AAAA,OAAO,EAAmB,KAAK,aAAa,EAAE,KAAK,iBAAiB,EAAE,MAAM,YAAY,CAAC;AACzF,OAAO,EAAqB,KAAK,gBAAgB,EAAE,KAAK,sBAAsB,EAAE,MAAM,WAAW,CAAC;AAElG;;;;;;GAMG;AAEH,MAAM,WAAW,eAAe;IAC9B,MAAM,EAAE,aAAa,CAAC;IACtB,WAAW,EAAE,gBAAgB,CAAC;IAC9B,QAAQ,EAAE,sBAAsB,CAAC;IACjC,wCAAwC;IACxC,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,wBAAgB,eAAe,CAAC,IAAI,EAAE,eAAe,GAAG,iBAAiB,CAwBxE;AAED,MAAM,WAAW,cAAc;IAC7B,MAAM,EAAE,aAAa,CAAC;IACtB,sEAAsE;IACtE,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,wBAAgB,cAAc,CAAC,IAAI,EAAE,cAAc,GAAG,iBAAiB,CAoBtE"}
|
package/dist/receipts.js
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { SettlementError } from './types.js';
|
|
2
|
+
import { verifyRequirement } from './x402.js';
|
|
3
|
+
export function receiptFromX402(args) {
|
|
4
|
+
const { intent, requirement, response } = args;
|
|
5
|
+
if (!response.success) {
|
|
6
|
+
throw new SettlementError('unsuccessful-settlement', `x402 settlement failed: ${response.errorReason ?? 'no reason given'}`);
|
|
7
|
+
}
|
|
8
|
+
// Re-check after the fact: this response is what the receipt attests to.
|
|
9
|
+
verifyRequirement(requirement, intent);
|
|
10
|
+
return {
|
|
11
|
+
id: response.transaction ?? `${intent.id}@${args.settledAt}`,
|
|
12
|
+
protocol: 'x402',
|
|
13
|
+
network: intent.network,
|
|
14
|
+
asset: intent.asset,
|
|
15
|
+
amount: intent.amount,
|
|
16
|
+
payer: response.payer ?? intent.payer,
|
|
17
|
+
payee: intent.payee,
|
|
18
|
+
transaction: response.transaction,
|
|
19
|
+
settledAt: args.settledAt,
|
|
20
|
+
intentId: intent.id,
|
|
21
|
+
proof: { requirement, response },
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
export function receiptFromMpp(args) {
|
|
25
|
+
const token = args.receipt.trim();
|
|
26
|
+
if (token === '') {
|
|
27
|
+
throw new SettlementError('missing-field', 'MPP settlement returned an empty Payment-Receipt');
|
|
28
|
+
}
|
|
29
|
+
return {
|
|
30
|
+
// The receipt value identifies the settlement, so re-delivering the same
|
|
31
|
+
// receipt surfaces as a duplicate rather than a second, phantom payment.
|
|
32
|
+
id: token,
|
|
33
|
+
protocol: 'mpp',
|
|
34
|
+
network: args.intent.network,
|
|
35
|
+
asset: args.intent.asset,
|
|
36
|
+
amount: args.intent.amount,
|
|
37
|
+
payer: args.intent.payer,
|
|
38
|
+
payee: args.intent.payee,
|
|
39
|
+
settledAt: args.settledAt,
|
|
40
|
+
intentId: args.intent.id,
|
|
41
|
+
proof: { receipt: token },
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
//# sourceMappingURL=receipts.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"receipts.js","sourceRoot":"","sources":["../src/receipts.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAA8C,MAAM,YAAY,CAAC;AACzF,OAAO,EAAE,iBAAiB,EAAsD,MAAM,WAAW,CAAC;AAkBlG,MAAM,UAAU,eAAe,CAAC,IAAqB;IACnD,MAAM,EAAE,MAAM,EAAE,WAAW,EAAE,QAAQ,EAAE,GAAG,IAAI,CAAC;IAC/C,IAAI,CAAC,QAAQ,CAAC,OAAO,EAAE,CAAC;QACtB,MAAM,IAAI,eAAe,CACvB,yBAAyB,EACzB,2BAA2B,QAAQ,CAAC,WAAW,IAAI,iBAAiB,EAAE,CACvE,CAAC;IACJ,CAAC;IACD,yEAAyE;IACzE,iBAAiB,CAAC,WAAW,EAAE,MAAM,CAAC,CAAC;IAEvC,OAAO;QACL,EAAE,EAAE,QAAQ,CAAC,WAAW,IAAI,GAAG,MAAM,CAAC,EAAE,IAAI,IAAI,CAAC,SAAS,EAAE;QAC5D,QAAQ,EAAE,MAAM;QAChB,OAAO,EAAE,MAAM,CAAC,OAAO;QACvB,KAAK,EAAE,MAAM,CAAC,KAAK;QACnB,MAAM,EAAE,MAAM,CAAC,MAAM;QACrB,KAAK,EAAE,QAAQ,CAAC,KAAK,IAAI,MAAM,CAAC,KAAK;QACrC,KAAK,EAAE,MAAM,CAAC,KAAK;QACnB,WAAW,EAAE,QAAQ,CAAC,WAAW;QACjC,SAAS,EAAE,IAAI,CAAC,SAAS;QACzB,QAAQ,EAAE,MAAM,CAAC,EAAE;QACnB,KAAK,EAAE,EAAE,WAAW,EAAE,QAAQ,EAAE;KACjC,CAAC;AACJ,CAAC;AASD,MAAM,UAAU,cAAc,CAAC,IAAoB;IACjD,MAAM,KAAK,GAAG,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,CAAC;IAClC,IAAI,KAAK,KAAK,EAAE,EAAE,CAAC;QACjB,MAAM,IAAI,eAAe,CAAC,eAAe,EAAE,kDAAkD,CAAC,CAAC;IACjG,CAAC;IAED,OAAO;QACL,yEAAyE;QACzE,yEAAyE;QACzE,EAAE,EAAE,KAAK;QACT,QAAQ,EAAE,KAAK;QACf,OAAO,EAAE,IAAI,CAAC,MAAM,CAAC,OAAO;QAC5B,KAAK,EAAE,IAAI,CAAC,MAAM,CAAC,KAAK;QACxB,MAAM,EAAE,IAAI,CAAC,MAAM,CAAC,MAAM;QAC1B,KAAK,EAAE,IAAI,CAAC,MAAM,CAAC,KAAK;QACxB,KAAK,EAAE,IAAI,CAAC,MAAM,CAAC,KAAK;QACxB,SAAS,EAAE,IAAI,CAAC,SAAS;QACzB,QAAQ,EAAE,IAAI,CAAC,MAAM,CAAC,EAAE;QACxB,KAAK,EAAE,EAAE,OAAO,EAAE,KAAK,EAAE;KAC1B,CAAC;AACJ,CAAC"}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Which settlement rail a record belongs to. `x402`, `mpp` and `erc8183` are
|
|
3
|
+
* wire protocols; `chain` (an on-chain stablecoin transfer) and `fiat` (a
|
|
4
|
+
* hosted checkout) are rail kinds with no Keel codec of their own.
|
|
5
|
+
*/
|
|
6
|
+
export type Protocol = 'x402' | 'mpp' | 'erc8183' | 'chain' | 'fiat';
|
|
7
|
+
/** CAIP-2 chain identifier, e.g. `eip155:8453`. */
|
|
8
|
+
export type Caip2 = `${string}:${string}`;
|
|
9
|
+
export type SettlementErrorCode = 'malformed-payload' | 'unsupported-version' | 'missing-field' | 'invalid-caip2' | 'no-acceptable-requirement' | 'requirement-mismatch' | 'unsuccessful-settlement' | 'duplicate-receipt' | 'unknown-intent' | 'invalid-transition' | 'unauthorized-actor' | 'precondition-failed';
|
|
10
|
+
export declare class SettlementError extends Error {
|
|
11
|
+
readonly code: SettlementErrorCode;
|
|
12
|
+
constructor(code: SettlementErrorCode, message: string);
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* What the payer intends to pay, in protocol-neutral terms. This is the thing a
|
|
16
|
+
* client verifies a server's payment requirements *against* — a server can hand
|
|
17
|
+
* back any `payTo` it likes, so the intent is the anchor that catches it.
|
|
18
|
+
*/
|
|
19
|
+
export interface PaymentIntent {
|
|
20
|
+
id: string;
|
|
21
|
+
protocol: Protocol;
|
|
22
|
+
network: Caip2;
|
|
23
|
+
/** `native` or a token contract address. */
|
|
24
|
+
asset: string;
|
|
25
|
+
/** Atomic units (e.g. 10000 = 0.01 USDC at 6 decimals). */
|
|
26
|
+
amount: bigint;
|
|
27
|
+
payer: string;
|
|
28
|
+
payee: string;
|
|
29
|
+
/** Free-form resource/order reference. */
|
|
30
|
+
reference?: string;
|
|
31
|
+
/** ISO-8601 expiry, when the protocol carries one. */
|
|
32
|
+
expiresAt?: string;
|
|
33
|
+
metadata?: Record<string, unknown>;
|
|
34
|
+
}
|
|
35
|
+
export type IntentStatus = 'pending' | 'settled' | 'failed';
|
|
36
|
+
/**
|
|
37
|
+
* Proof that a payment settled. `id` is stable and unique per settlement — the
|
|
38
|
+
* on-chain transaction hash where the protocol provides one, and an
|
|
39
|
+
* intent-scoped reference otherwise.
|
|
40
|
+
*/
|
|
41
|
+
export interface SettlementReceipt {
|
|
42
|
+
id: string;
|
|
43
|
+
protocol: Protocol;
|
|
44
|
+
network: Caip2;
|
|
45
|
+
asset: string;
|
|
46
|
+
amount: bigint;
|
|
47
|
+
payer: string;
|
|
48
|
+
payee: string;
|
|
49
|
+
transaction?: string;
|
|
50
|
+
settledAt: string;
|
|
51
|
+
intentId?: string;
|
|
52
|
+
/** Protocol-specific proof, kept verbatim for audit. */
|
|
53
|
+
proof?: Record<string, unknown>;
|
|
54
|
+
}
|
|
55
|
+
/** A signed value movement on the ledger: positive credits, negative debits. */
|
|
56
|
+
export interface LedgerEntry {
|
|
57
|
+
account: string;
|
|
58
|
+
network: string;
|
|
59
|
+
asset: string;
|
|
60
|
+
amount: bigint;
|
|
61
|
+
reference?: string;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* The reconciliation ledger is an append-only event log; intent status and
|
|
65
|
+
* balances are *derived* by folding it, never stored mutably.
|
|
66
|
+
*
|
|
67
|
+
* Whether a settled payment moves a balance is a billing concern and is
|
|
68
|
+
* deliberately not modelled here — `entry` events are the only thing that
|
|
69
|
+
* affects `balanceOf`.
|
|
70
|
+
*/
|
|
71
|
+
export type LedgerEvent = {
|
|
72
|
+
kind: 'intent';
|
|
73
|
+
at: string;
|
|
74
|
+
intent: PaymentIntent;
|
|
75
|
+
} | {
|
|
76
|
+
kind: 'receipt';
|
|
77
|
+
at: string;
|
|
78
|
+
receipt: SettlementReceipt;
|
|
79
|
+
} | {
|
|
80
|
+
kind: 'failure';
|
|
81
|
+
at: string;
|
|
82
|
+
intentId: string;
|
|
83
|
+
reason: string;
|
|
84
|
+
} | {
|
|
85
|
+
kind: 'entry';
|
|
86
|
+
at: string;
|
|
87
|
+
entry: LedgerEntry;
|
|
88
|
+
};
|
|
89
|
+
export interface Reconciliation {
|
|
90
|
+
/** Intent ids with no receipt — expected money that never arrived. */
|
|
91
|
+
unsettled: string[];
|
|
92
|
+
/** Receipt ids with no matching intent — money from an unknown intent. */
|
|
93
|
+
orphans: string[];
|
|
94
|
+
/** Intent ids with more than one receipt — a possible double settlement. */
|
|
95
|
+
doubleSettled: string[];
|
|
96
|
+
ok: boolean;
|
|
97
|
+
}
|
|
98
|
+
export declare function isCaip2(value: string): value is Caip2;
|
|
99
|
+
export declare function assertCaip2(value: unknown, field: string): Caip2;
|
|
100
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export class SettlementError extends Error {
|
|
2
|
+
code;
|
|
3
|
+
constructor(code, message) {
|
|
4
|
+
super(message);
|
|
5
|
+
this.name = 'SettlementError';
|
|
6
|
+
this.code = code;
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
const CAIP2 = /^[a-z0-9-]{3,8}:[a-zA-Z0-9-]{1,64}$/;
|
|
10
|
+
export function isCaip2(value) {
|
|
11
|
+
return CAIP2.test(value);
|
|
12
|
+
}
|
|
13
|
+
export function assertCaip2(value, field) {
|
|
14
|
+
if (typeof value !== 'string' || !isCaip2(value)) {
|
|
15
|
+
throw new SettlementError('invalid-caip2', `${field} is not a CAIP-2 chain id: ${JSON.stringify(value)}`);
|
|
16
|
+
}
|
|
17
|
+
return value;
|
|
18
|
+
}
|
|
19
|
+
//# sourceMappingURL=types.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAwBA,MAAM,OAAO,eAAgB,SAAQ,KAAK;IAC/B,IAAI,CAAsB;IAEnC,YAAY,IAAyB,EAAE,OAAe;QACpD,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,IAAI,GAAG,iBAAiB,CAAC;QAC9B,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;IACnB,CAAC;CACF;AA+ED,MAAM,KAAK,GAAG,qCAAqC,CAAC;AAEpD,MAAM,UAAU,OAAO,CAAC,KAAa;IACnC,OAAO,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;AAC3B,CAAC;AAED,MAAM,UAAU,WAAW,CAAC,KAAc,EAAE,KAAa;IACvD,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;QACjD,MAAM,IAAI,eAAe,CAAC,eAAe,EAAE,GAAG,KAAK,8BAA8B,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;IAC5G,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC"}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { type LedgerEvent, type Protocol } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Multi-protocol webhook egress.
|
|
4
|
+
*
|
|
5
|
+
* Every rail in this package — x402, MPP, A2A's x402 extension, ERC-8183
|
|
6
|
+
* escrow — folds into the same append-only {@link LedgerEvent} log, so outbound
|
|
7
|
+
* notifications need only one normalised event shape. A {@link WebhookEvent}
|
|
8
|
+
* is that shape; {@link WebhookDispatcher} matches it against subscriptions and
|
|
9
|
+
* delivers it with retries and a per-delivery audit trail.
|
|
10
|
+
*
|
|
11
|
+
* Signing is a port, not baked in: the core carries no crypto dependency and
|
|
12
|
+
* runs anywhere, so the host wires its own HMAC (`WebhookSigner`) — the same
|
|
13
|
+
* way scheme payloads are left to the caller. A delivery is only signed when a
|
|
14
|
+
* subscription carries a `secret` and a signer is configured.
|
|
15
|
+
*/
|
|
16
|
+
export type WebhookEventType = 'intent.created' | 'settlement.succeeded' | 'settlement.failed' | 'ledger.entry';
|
|
17
|
+
/** The transport-neutral event a subscription receives. */
|
|
18
|
+
export interface WebhookEvent {
|
|
19
|
+
/** Stable per source event — safe to use as an idempotency key. */
|
|
20
|
+
id: string;
|
|
21
|
+
type: WebhookEventType;
|
|
22
|
+
/** ISO-8601, taken from the ledger event. */
|
|
23
|
+
at: string;
|
|
24
|
+
protocol?: Protocol;
|
|
25
|
+
data: unknown;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Normalises a ledger event into a webhook event. Ids are derived from the
|
|
29
|
+
* source record so replaying the same log yields the same delivery ids.
|
|
30
|
+
*/
|
|
31
|
+
export declare function webhookEventFromLedgerEvent(event: LedgerEvent): WebhookEvent;
|
|
32
|
+
export interface WebhookSubscription {
|
|
33
|
+
id: string;
|
|
34
|
+
url: string;
|
|
35
|
+
/** Event types to receive; all when omitted. */
|
|
36
|
+
events?: readonly WebhookEventType[];
|
|
37
|
+
/** Restrict to these rails; events without a protocol are dropped. */
|
|
38
|
+
protocols?: readonly Protocol[];
|
|
39
|
+
/** Enables signing for this subscription. */
|
|
40
|
+
secret?: string;
|
|
41
|
+
/** Defaults to active. */
|
|
42
|
+
active?: boolean;
|
|
43
|
+
}
|
|
44
|
+
export declare function matchesWebhook(subscription: WebhookSubscription, event: WebhookEvent): boolean;
|
|
45
|
+
export interface WebhookRequest {
|
|
46
|
+
subscriptionId: string;
|
|
47
|
+
eventId: string;
|
|
48
|
+
url: string;
|
|
49
|
+
/** JSON body. */
|
|
50
|
+
body: string;
|
|
51
|
+
headers: Record<string, string>;
|
|
52
|
+
}
|
|
53
|
+
export interface WebhookResponse {
|
|
54
|
+
status: number;
|
|
55
|
+
}
|
|
56
|
+
/** How a request actually leaves the process — `fetch`, a queue, a test double. */
|
|
57
|
+
export type WebhookTransport = (request: WebhookRequest) => Promise<WebhookResponse>;
|
|
58
|
+
export type WebhookSigner = (args: {
|
|
59
|
+
secret: string;
|
|
60
|
+
/** Unix seconds, also sent in `x-keel-timestamp`. */
|
|
61
|
+
timestamp: string;
|
|
62
|
+
body: string;
|
|
63
|
+
}) => string | Promise<string>;
|
|
64
|
+
export interface WebhookAttempt {
|
|
65
|
+
attempt: number;
|
|
66
|
+
at: string;
|
|
67
|
+
status?: number;
|
|
68
|
+
error?: string;
|
|
69
|
+
ok: boolean;
|
|
70
|
+
}
|
|
71
|
+
export interface WebhookDelivery {
|
|
72
|
+
subscriptionId: string;
|
|
73
|
+
eventId: string;
|
|
74
|
+
attempts: WebhookAttempt[];
|
|
75
|
+
ok: boolean;
|
|
76
|
+
}
|
|
77
|
+
export interface WebhookDispatcherOptions {
|
|
78
|
+
transport: WebhookTransport;
|
|
79
|
+
signer?: WebhookSigner;
|
|
80
|
+
clock?: () => Date;
|
|
81
|
+
/** Total tries per delivery. Defaults to 3. */
|
|
82
|
+
maxAttempts?: number;
|
|
83
|
+
/** Delay before the next attempt; defaults to exponential backoff. */
|
|
84
|
+
backoffMs?: (attempt: number) => number;
|
|
85
|
+
sleep?: (ms: number) => Promise<void>;
|
|
86
|
+
/** Static extra headers, or a function of the event/subscription. */
|
|
87
|
+
headers?: Record<string, string> | ((event: WebhookEvent, subscription: WebhookSubscription) => Record<string, string>);
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Fans a webhook event out to matching subscriptions and records what happened.
|
|
91
|
+
*
|
|
92
|
+
* A delivery is retried on a network throw or a non-2xx status, up to
|
|
93
|
+
* `maxAttempts`; the returned {@link WebhookDelivery} keeps every attempt so a
|
|
94
|
+
* consumer can audit or replay it.
|
|
95
|
+
*/
|
|
96
|
+
export declare class WebhookDispatcher {
|
|
97
|
+
private readonly options;
|
|
98
|
+
constructor(options: WebhookDispatcherOptions);
|
|
99
|
+
dispatch(subscriptions: readonly WebhookSubscription[], event: WebhookEvent): Promise<WebhookDelivery[]>;
|
|
100
|
+
private now;
|
|
101
|
+
private backoff;
|
|
102
|
+
private sleep;
|
|
103
|
+
private buildHeaders;
|
|
104
|
+
private deliver;
|
|
105
|
+
}
|
|
106
|
+
//# sourceMappingURL=webhooks.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"webhooks.d.ts","sourceRoot":"","sources":["../src/webhooks.ts"],"names":[],"mappings":"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"}
|
package/dist/webhooks.js
ADDED
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
import { SettlementError } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Normalises a ledger event into a webhook event. Ids are derived from the
|
|
4
|
+
* source record so replaying the same log yields the same delivery ids.
|
|
5
|
+
*/
|
|
6
|
+
export function webhookEventFromLedgerEvent(event) {
|
|
7
|
+
switch (event.kind) {
|
|
8
|
+
case 'intent':
|
|
9
|
+
return {
|
|
10
|
+
id: `intent.created:${event.intent.id}`,
|
|
11
|
+
type: 'intent.created',
|
|
12
|
+
at: event.at,
|
|
13
|
+
protocol: event.intent.protocol,
|
|
14
|
+
data: event.intent,
|
|
15
|
+
};
|
|
16
|
+
case 'receipt':
|
|
17
|
+
return {
|
|
18
|
+
id: `settlement.succeeded:${event.receipt.id}`,
|
|
19
|
+
type: 'settlement.succeeded',
|
|
20
|
+
at: event.at,
|
|
21
|
+
protocol: event.receipt.protocol,
|
|
22
|
+
data: event.receipt,
|
|
23
|
+
};
|
|
24
|
+
case 'failure':
|
|
25
|
+
return {
|
|
26
|
+
id: `settlement.failed:${event.intentId}@${event.at}`,
|
|
27
|
+
type: 'settlement.failed',
|
|
28
|
+
at: event.at,
|
|
29
|
+
data: { intentId: event.intentId, reason: event.reason },
|
|
30
|
+
};
|
|
31
|
+
case 'entry':
|
|
32
|
+
return {
|
|
33
|
+
id: `ledger.entry:${event.entry.account}@${event.at}`,
|
|
34
|
+
type: 'ledger.entry',
|
|
35
|
+
at: event.at,
|
|
36
|
+
data: event.entry,
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
export function matchesWebhook(subscription, event) {
|
|
41
|
+
if (subscription.active === false)
|
|
42
|
+
return false;
|
|
43
|
+
if (subscription.events !== undefined && !subscription.events.includes(event.type))
|
|
44
|
+
return false;
|
|
45
|
+
if (subscription.protocols !== undefined) {
|
|
46
|
+
if (event.protocol === undefined)
|
|
47
|
+
return false;
|
|
48
|
+
if (!subscription.protocols.includes(event.protocol))
|
|
49
|
+
return false;
|
|
50
|
+
}
|
|
51
|
+
return true;
|
|
52
|
+
}
|
|
53
|
+
function isSuccess(status) {
|
|
54
|
+
return status >= 200 && status < 300;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* JSON encoding with `bigint` rendered as a decimal string. Ledger records carry
|
|
58
|
+
* atomic-unit amounts as `bigint`, and `JSON.stringify` throws on those.
|
|
59
|
+
*/
|
|
60
|
+
function encodeBody(event) {
|
|
61
|
+
return JSON.stringify({
|
|
62
|
+
id: event.id,
|
|
63
|
+
type: event.type,
|
|
64
|
+
at: event.at,
|
|
65
|
+
protocol: event.protocol,
|
|
66
|
+
data: event.data,
|
|
67
|
+
}, (_key, value) => (typeof value === 'bigint' ? value.toString() : value));
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Fans a webhook event out to matching subscriptions and records what happened.
|
|
71
|
+
*
|
|
72
|
+
* A delivery is retried on a network throw or a non-2xx status, up to
|
|
73
|
+
* `maxAttempts`; the returned {@link WebhookDelivery} keeps every attempt so a
|
|
74
|
+
* consumer can audit or replay it.
|
|
75
|
+
*/
|
|
76
|
+
export class WebhookDispatcher {
|
|
77
|
+
options;
|
|
78
|
+
constructor(options) {
|
|
79
|
+
this.options = options;
|
|
80
|
+
}
|
|
81
|
+
async dispatch(subscriptions, event) {
|
|
82
|
+
const deliveries = [];
|
|
83
|
+
for (const subscription of subscriptions) {
|
|
84
|
+
if (!matchesWebhook(subscription, event))
|
|
85
|
+
continue;
|
|
86
|
+
deliveries.push(await this.deliver(subscription, event));
|
|
87
|
+
}
|
|
88
|
+
return deliveries;
|
|
89
|
+
}
|
|
90
|
+
now() {
|
|
91
|
+
return this.options.clock?.() ?? new Date();
|
|
92
|
+
}
|
|
93
|
+
backoff(attempt) {
|
|
94
|
+
if (this.options.backoffMs !== undefined)
|
|
95
|
+
return this.options.backoffMs(attempt);
|
|
96
|
+
return 250 * 2 ** (attempt - 1);
|
|
97
|
+
}
|
|
98
|
+
sleep(ms) {
|
|
99
|
+
if (this.options.sleep !== undefined)
|
|
100
|
+
return this.options.sleep(ms);
|
|
101
|
+
const schedule = globalThis
|
|
102
|
+
.setTimeout;
|
|
103
|
+
if (schedule === undefined) {
|
|
104
|
+
throw new SettlementError('precondition-failed', 'no timer available; pass options.sleep');
|
|
105
|
+
}
|
|
106
|
+
return new Promise((resolve) => {
|
|
107
|
+
schedule(() => resolve(), ms);
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
async buildHeaders(subscription, event, body) {
|
|
111
|
+
const timestamp = Math.floor(this.now().getTime() / 1000).toString();
|
|
112
|
+
const extra = typeof this.options.headers === 'function'
|
|
113
|
+
? this.options.headers(event, subscription)
|
|
114
|
+
: this.options.headers ?? {};
|
|
115
|
+
const headers = {
|
|
116
|
+
'content-type': 'application/json',
|
|
117
|
+
'x-keel-event': event.type,
|
|
118
|
+
'x-keel-event-id': event.id,
|
|
119
|
+
'x-keel-timestamp': timestamp,
|
|
120
|
+
...extra,
|
|
121
|
+
};
|
|
122
|
+
if (this.options.signer !== undefined && subscription.secret !== undefined) {
|
|
123
|
+
const value = await this.options.signer({ secret: subscription.secret, timestamp, body });
|
|
124
|
+
headers['x-keel-signature'] = `t=${timestamp},v1=${value}`;
|
|
125
|
+
}
|
|
126
|
+
return headers;
|
|
127
|
+
}
|
|
128
|
+
async deliver(subscription, event) {
|
|
129
|
+
const maxAttempts = this.options.maxAttempts ?? 3;
|
|
130
|
+
const body = encodeBody(event);
|
|
131
|
+
const attempts = [];
|
|
132
|
+
for (let attempt = 1; attempt <= maxAttempts; attempt += 1) {
|
|
133
|
+
const at = this.now().toISOString();
|
|
134
|
+
try {
|
|
135
|
+
const headers = await this.buildHeaders(subscription, event, body);
|
|
136
|
+
const response = await this.options.transport({
|
|
137
|
+
subscriptionId: subscription.id,
|
|
138
|
+
eventId: event.id,
|
|
139
|
+
url: subscription.url,
|
|
140
|
+
body,
|
|
141
|
+
headers,
|
|
142
|
+
});
|
|
143
|
+
const ok = isSuccess(response.status);
|
|
144
|
+
attempts.push({ attempt, at, status: response.status, ok });
|
|
145
|
+
if (ok)
|
|
146
|
+
return { subscriptionId: subscription.id, eventId: event.id, attempts, ok: true };
|
|
147
|
+
}
|
|
148
|
+
catch (error) {
|
|
149
|
+
attempts.push({
|
|
150
|
+
attempt,
|
|
151
|
+
at,
|
|
152
|
+
error: error instanceof Error ? error.message : String(error),
|
|
153
|
+
ok: false,
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
if (attempt < maxAttempts)
|
|
157
|
+
await this.sleep(this.backoff(attempt));
|
|
158
|
+
}
|
|
159
|
+
return { subscriptionId: subscription.id, eventId: event.id, attempts, ok: false };
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
//# sourceMappingURL=webhooks.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"webhooks.js","sourceRoot":"","sources":["../src/webhooks.ts"],"names":[],"mappings":"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"}
|