@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
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,UAAU,EACV,YAAY,EACZ,YAAY,EACZ,iBAAiB,EACjB,iBAAiB,EACjB,gBAAgB,EAChB,eAAe,GAChB,MAAM,WAAW,CAAC;AAGnB,OAAO,EACL,kBAAkB,EAClB,UAAU,EACV,UAAU,EACV,mBAAmB,EACnB,gBAAgB,EAChB,gBAAgB,EAChB,gBAAgB,EAChB,qBAAqB,EACrB,SAAS,EACT,eAAe,EACf,oBAAoB,GACrB,MAAM,UAAU,CAAC;AASlB,OAAO,EACL,YAAY,EACZ,YAAY,EACZ,sBAAsB,EACtB,qBAAqB,EACrB,wBAAwB,EACxB,qBAAqB,EACrB,oBAAoB,EACpB,qBAAqB,EACrB,uBAAuB,EACvB,iBAAiB,EACjB,iBAAiB,GAClB,MAAM,WAAW,CAAC;AAUnB,OAAO,EACL,eAAe,EACf,qBAAqB,EACrB,iBAAiB,EACjB,sBAAsB,EACtB,SAAS,EACT,mBAAmB,EACnB,oBAAoB,EACpB,gBAAgB,EAChB,cAAc,EACd,mBAAmB,EACnB,oBAAoB,EACpB,mBAAmB,EACnB,4BAA4B,EAC5B,mBAAmB,EACnB,cAAc,EACd,oBAAoB,GACrB,MAAM,UAAU,CAAC;AAalB,OAAO,EACL,YAAY,EACZ,cAAc,EACd,aAAa,EACb,iBAAiB,EACjB,aAAa,EACb,YAAY,EACZ,aAAa,EACb,mBAAmB,EACnB,iBAAiB,GAClB,MAAM,cAAc,CAAC;AAWtB,OAAO,EACL,eAAe,EACf,WAAW,EACX,UAAU,EACV,gBAAgB,EAChB,UAAU,GACX,MAAM,YAAY,CAAC;AAUpB,OAAO,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAGhE,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAGrC,OAAO,EAAE,iBAAiB,EAAE,cAAc,EAAE,2BAA2B,EAAE,MAAM,eAAe,CAAC;AAc/F,OAAO,EAAE,eAAe,EAAE,WAAW,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC"}
|
package/dist/ledger.d.ts
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { type IntentStatus, type LedgerEntry, type LedgerEvent, type PaymentIntent, type Reconciliation, type SettlementReceipt } from './types.js';
|
|
2
|
+
export interface AssetRef {
|
|
3
|
+
network: string;
|
|
4
|
+
asset: string;
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* An append-only reconciliation ledger.
|
|
8
|
+
*
|
|
9
|
+
* Nothing is stored mutably: intent status and balances are folded out of the
|
|
10
|
+
* event log on demand, so the log is the single source of truth and can be
|
|
11
|
+
* replayed or shipped anywhere. Receipt ids are deduplicated on append, which
|
|
12
|
+
* makes a replayed settlement a loud error instead of a double count.
|
|
13
|
+
*/
|
|
14
|
+
export declare class Ledger {
|
|
15
|
+
private readonly log;
|
|
16
|
+
private readonly receiptsById;
|
|
17
|
+
get events(): readonly LedgerEvent[];
|
|
18
|
+
append(event: LedgerEvent): void;
|
|
19
|
+
intents(): PaymentIntent[];
|
|
20
|
+
intentOf(id: string): PaymentIntent | undefined;
|
|
21
|
+
receipts(): SettlementReceipt[];
|
|
22
|
+
entries(): LedgerEntry[];
|
|
23
|
+
receiptsOf(intentId: string): SettlementReceipt[];
|
|
24
|
+
/** The latest lifecycle event for an intent decides its status. */
|
|
25
|
+
statusOf(intentId: string): IntentStatus;
|
|
26
|
+
/** Signed sum of `entry` movements — credits positive, debits negative. */
|
|
27
|
+
balanceOf(account: string, ref: AssetRef): bigint;
|
|
28
|
+
/** Every event touching an intent, in append order. */
|
|
29
|
+
trail(intentId: string): LedgerEvent[];
|
|
30
|
+
/**
|
|
31
|
+
* Cross-checks intents against receipts. Three failure modes are worth
|
|
32
|
+
* surfacing on their own: money that never arrived, receipts for intents the
|
|
33
|
+
* ledger never saw, and an intent paid twice.
|
|
34
|
+
*/
|
|
35
|
+
reconcile(): Reconciliation;
|
|
36
|
+
}
|
|
37
|
+
//# sourceMappingURL=ledger.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ledger.d.ts","sourceRoot":"","sources":["../src/ledger.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,YAAY,EACjB,KAAK,WAAW,EAChB,KAAK,WAAW,EAChB,KAAK,aAAa,EAClB,KAAK,cAAc,EACnB,KAAK,iBAAiB,EACvB,MAAM,YAAY,CAAC;AAEpB,MAAM,WAAW,QAAQ;IACvB,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;CACf;AAED;;;;;;;GAOG;AACH,qBAAa,MAAM;IACjB,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAqB;IACzC,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAwC;IAErE,IAAI,MAAM,IAAI,SAAS,WAAW,EAAE,CAEnC;IAED,MAAM,CAAC,KAAK,EAAE,WAAW,GAAG,IAAI;IAahC,OAAO,IAAI,aAAa,EAAE;IAI1B,QAAQ,CAAC,EAAE,EAAE,MAAM,GAAG,aAAa,GAAG,SAAS;IAI/C,QAAQ,IAAI,iBAAiB,EAAE;IAI/B,OAAO,IAAI,WAAW,EAAE;IAIxB,UAAU,CAAC,QAAQ,EAAE,MAAM,GAAG,iBAAiB,EAAE;IAIjD,mEAAmE;IACnE,QAAQ,CAAC,QAAQ,EAAE,MAAM,GAAG,YAAY;IAaxC,2EAA2E;IAC3E,SAAS,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,EAAE,QAAQ,GAAG,MAAM;IAWjD,uDAAuD;IACvD,KAAK,CAAC,QAAQ,EAAE,MAAM,GAAG,WAAW,EAAE;IAetC;;;;OAIG;IACH,SAAS,IAAI,cAAc;CA+B5B"}
|
package/dist/ledger.js
ADDED
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import { SettlementError, } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* An append-only reconciliation ledger.
|
|
4
|
+
*
|
|
5
|
+
* Nothing is stored mutably: intent status and balances are folded out of the
|
|
6
|
+
* event log on demand, so the log is the single source of truth and can be
|
|
7
|
+
* replayed or shipped anywhere. Receipt ids are deduplicated on append, which
|
|
8
|
+
* makes a replayed settlement a loud error instead of a double count.
|
|
9
|
+
*/
|
|
10
|
+
export class Ledger {
|
|
11
|
+
log = [];
|
|
12
|
+
receiptsById = new Map();
|
|
13
|
+
get events() {
|
|
14
|
+
return this.log;
|
|
15
|
+
}
|
|
16
|
+
append(event) {
|
|
17
|
+
if (event.kind === 'receipt') {
|
|
18
|
+
if (this.receiptsById.has(event.receipt.id)) {
|
|
19
|
+
throw new SettlementError('duplicate-receipt', `receipt ${event.receipt.id} is already recorded`);
|
|
20
|
+
}
|
|
21
|
+
this.receiptsById.set(event.receipt.id, event.receipt);
|
|
22
|
+
}
|
|
23
|
+
this.log.push(event);
|
|
24
|
+
}
|
|
25
|
+
intents() {
|
|
26
|
+
return this.log.flatMap((event) => (event.kind === 'intent' ? [event.intent] : []));
|
|
27
|
+
}
|
|
28
|
+
intentOf(id) {
|
|
29
|
+
return this.intents().find((intent) => intent.id === id);
|
|
30
|
+
}
|
|
31
|
+
receipts() {
|
|
32
|
+
return [...this.receiptsById.values()];
|
|
33
|
+
}
|
|
34
|
+
entries() {
|
|
35
|
+
return this.log.flatMap((event) => (event.kind === 'entry' ? [event.entry] : []));
|
|
36
|
+
}
|
|
37
|
+
receiptsOf(intentId) {
|
|
38
|
+
return this.receipts().filter((receipt) => receipt.intentId === intentId);
|
|
39
|
+
}
|
|
40
|
+
/** The latest lifecycle event for an intent decides its status. */
|
|
41
|
+
statusOf(intentId) {
|
|
42
|
+
let status;
|
|
43
|
+
for (const event of this.log) {
|
|
44
|
+
if (event.kind === 'intent' && event.intent.id === intentId)
|
|
45
|
+
status = 'pending';
|
|
46
|
+
else if (event.kind === 'receipt' && event.receipt.intentId === intentId)
|
|
47
|
+
status = 'settled';
|
|
48
|
+
else if (event.kind === 'failure' && event.intentId === intentId)
|
|
49
|
+
status = 'failed';
|
|
50
|
+
}
|
|
51
|
+
if (status === undefined) {
|
|
52
|
+
throw new SettlementError('unknown-intent', `no intent ${intentId} in the ledger`);
|
|
53
|
+
}
|
|
54
|
+
return status;
|
|
55
|
+
}
|
|
56
|
+
/** Signed sum of `entry` movements — credits positive, debits negative. */
|
|
57
|
+
balanceOf(account, ref) {
|
|
58
|
+
return this.entries()
|
|
59
|
+
.filter((entry) => entry.account === account &&
|
|
60
|
+
entry.network === ref.network &&
|
|
61
|
+
entry.asset.toLowerCase() === ref.asset.toLowerCase())
|
|
62
|
+
.reduce((total, entry) => total + entry.amount, 0n);
|
|
63
|
+
}
|
|
64
|
+
/** Every event touching an intent, in append order. */
|
|
65
|
+
trail(intentId) {
|
|
66
|
+
return this.log.filter((event) => {
|
|
67
|
+
switch (event.kind) {
|
|
68
|
+
case 'intent':
|
|
69
|
+
return event.intent.id === intentId;
|
|
70
|
+
case 'receipt':
|
|
71
|
+
return event.receipt.intentId === intentId;
|
|
72
|
+
case 'failure':
|
|
73
|
+
return event.intentId === intentId;
|
|
74
|
+
case 'entry':
|
|
75
|
+
return event.entry.reference === intentId;
|
|
76
|
+
}
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Cross-checks intents against receipts. Three failure modes are worth
|
|
81
|
+
* surfacing on their own: money that never arrived, receipts for intents the
|
|
82
|
+
* ledger never saw, and an intent paid twice.
|
|
83
|
+
*/
|
|
84
|
+
reconcile() {
|
|
85
|
+
const intents = new Set();
|
|
86
|
+
for (const event of this.log) {
|
|
87
|
+
if (event.kind === 'intent')
|
|
88
|
+
intents.add(event.intent.id);
|
|
89
|
+
}
|
|
90
|
+
const byIntent = new Map();
|
|
91
|
+
const orphans = [];
|
|
92
|
+
for (const receipt of this.receipts()) {
|
|
93
|
+
const intentId = receipt.intentId;
|
|
94
|
+
if (intentId === undefined || !intents.has(intentId)) {
|
|
95
|
+
orphans.push(receipt.id);
|
|
96
|
+
continue;
|
|
97
|
+
}
|
|
98
|
+
const ids = byIntent.get(intentId) ?? [];
|
|
99
|
+
ids.push(receipt.id);
|
|
100
|
+
byIntent.set(intentId, ids);
|
|
101
|
+
}
|
|
102
|
+
const doubleSettled = [...byIntent.entries()]
|
|
103
|
+
.filter(([, ids]) => ids.length > 1)
|
|
104
|
+
.map(([intentId]) => intentId);
|
|
105
|
+
const unsettled = [...intents].filter((intentId) => !byIntent.has(intentId));
|
|
106
|
+
return {
|
|
107
|
+
unsettled,
|
|
108
|
+
orphans,
|
|
109
|
+
doubleSettled,
|
|
110
|
+
ok: unsettled.length === 0 && orphans.length === 0 && doubleSettled.length === 0,
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
//# sourceMappingURL=ledger.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ledger.js","sourceRoot":"","sources":["../src/ledger.ts"],"names":[],"mappings":"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"}
|
package/dist/mpp.d.ts
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { type PaymentIntent } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* MPP — the Machine Payments Protocol, i.e. the `Payment` HTTP authentication
|
|
4
|
+
* scheme of IETF `draft-httpauth-payment-00` (Tempo Labs / Stripe).
|
|
5
|
+
*
|
|
6
|
+
* The core spec is payment-method agnostic: a challenge names a registered
|
|
7
|
+
* payment method and carries a method-specific `request` blob. Keel therefore
|
|
8
|
+
* models the *core* surface exactly — challenge, credential, status semantics —
|
|
9
|
+
* and keeps `request` opaque, decoded by whichever method spec the caller
|
|
10
|
+
* implements. Nothing method-specific is guessed at here.
|
|
11
|
+
*/
|
|
12
|
+
export declare const MPP_SCHEME = "Payment";
|
|
13
|
+
/** Server → client, carries the receipt once payment is verified. */
|
|
14
|
+
export declare const MPP_RECEIPT_HEADER = "Payment-Receipt";
|
|
15
|
+
/** Status codes from the draft's response table (draft §4.2). */
|
|
16
|
+
export declare const MPP_STATUS: {
|
|
17
|
+
readonly granted: 200;
|
|
18
|
+
/** Payment barrier: fresh challenge, or a problem describing why it failed. */
|
|
19
|
+
readonly paymentRequired: 402;
|
|
20
|
+
/** Payment was valid, but policy denies access — a fresh challenge would not help. */
|
|
21
|
+
readonly policyDenied: 403;
|
|
22
|
+
};
|
|
23
|
+
/** Problem `code` values the draft defines for 402 responses. */
|
|
24
|
+
export type MppProblemCode = 'malformed-credential' | 'invalid-challenge' | 'verification-failed';
|
|
25
|
+
export interface MppChallenge {
|
|
26
|
+
/** Challenge id; single-use, so a replay is rejected as `invalid-challenge`. */
|
|
27
|
+
id: string;
|
|
28
|
+
/** Registered payment method identifier. */
|
|
29
|
+
method: string;
|
|
30
|
+
/** Registered payment intent identifier, e.g. a one-time charge. */
|
|
31
|
+
intent: string;
|
|
32
|
+
/** base64url JSON, method-specific. Opaque to the core protocol. */
|
|
33
|
+
request: string;
|
|
34
|
+
/** Any further challenge parameters, preserved verbatim. */
|
|
35
|
+
params?: Record<string, string>;
|
|
36
|
+
}
|
|
37
|
+
export type MppResponseKind = 'challenge' | 'granted' | 'policy-denied' | 'other';
|
|
38
|
+
/**
|
|
39
|
+
* Classifies a response to a payment attempt.
|
|
40
|
+
*
|
|
41
|
+
* The distinction that matters: 403 means the payment *was* accepted but policy
|
|
42
|
+
* denied access, so re-paying is pointless — only 402 is a retry signal.
|
|
43
|
+
*/
|
|
44
|
+
export declare function classifyMppResponse(status: number): MppResponseKind;
|
|
45
|
+
/** Parses a `WWW-Authenticate: Payment …` challenge. */
|
|
46
|
+
export declare function parseWwwAuthenticate(headerValue: string): MppChallenge;
|
|
47
|
+
/** Serialises a `WWW-Authenticate: Payment …` challenge. */
|
|
48
|
+
export declare function formatWwwAuthenticate(challenge: MppChallenge): string;
|
|
49
|
+
/** Encodes a method-specific request blob for the `request` challenge param. */
|
|
50
|
+
export declare function encodeMppRequest(value: unknown): string;
|
|
51
|
+
/** Decodes the method-specific `request` blob using the caller's method spec. */
|
|
52
|
+
export declare function decodeMppRequest<T>(challenge: MppChallenge): T;
|
|
53
|
+
export interface MppIntentArgs {
|
|
54
|
+
payer: string;
|
|
55
|
+
payee: string;
|
|
56
|
+
network: string;
|
|
57
|
+
asset: string;
|
|
58
|
+
amount: bigint;
|
|
59
|
+
reference?: string;
|
|
60
|
+
expiresAt?: string;
|
|
61
|
+
metadata?: Record<string, unknown>;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Builds the client-side intent for a challenge. The value terms come from the
|
|
65
|
+
* caller's method-specific decode of `request`; the challenge only supplies the
|
|
66
|
+
* identity.
|
|
67
|
+
*/
|
|
68
|
+
export declare function mppIntent(challenge: MppChallenge, args: MppIntentArgs): PaymentIntent;
|
|
69
|
+
/** Renders the `Authorization: Payment <token>` credential header. */
|
|
70
|
+
export declare function formatCredential(token: string): string;
|
|
71
|
+
export interface MppCredential {
|
|
72
|
+
scheme: string;
|
|
73
|
+
/** Method-specific proof, opaque to the core protocol. */
|
|
74
|
+
token: string;
|
|
75
|
+
}
|
|
76
|
+
/** Parses an `Authorization: Payment <token>` credential header. */
|
|
77
|
+
export declare function parseCredential(headerValue: string): MppCredential;
|
|
78
|
+
//# sourceMappingURL=mpp.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mpp.d.ts","sourceRoot":"","sources":["../src/mpp.ts"],"names":[],"mappings":"AAAA,OAAO,EAAgC,KAAK,aAAa,EAAE,MAAM,YAAY,CAAC;AAG9E;;;;;;;;;GASG;AAEH,eAAO,MAAM,UAAU,YAAY,CAAC;AAEpC,qEAAqE;AACrE,eAAO,MAAM,kBAAkB,oBAAoB,CAAC;AAEpD,iEAAiE;AACjE,eAAO,MAAM,UAAU;;IAErB,+EAA+E;;IAE/E,sFAAsF;;CAE9E,CAAC;AAEX,iEAAiE;AACjE,MAAM,MAAM,cAAc,GAAG,sBAAsB,GAAG,mBAAmB,GAAG,qBAAqB,CAAC;AAIlG,MAAM,WAAW,YAAY;IAC3B,gFAAgF;IAChF,EAAE,EAAE,MAAM,CAAC;IACX,4CAA4C;IAC5C,MAAM,EAAE,MAAM,CAAC;IACf,oEAAoE;IACpE,MAAM,EAAE,MAAM,CAAC;IACf,oEAAoE;IACpE,OAAO,EAAE,MAAM,CAAC;IAChB,4DAA4D;IAC5D,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACjC;AAED,MAAM,MAAM,eAAe,GAAG,WAAW,GAAG,SAAS,GAAG,eAAe,GAAG,OAAO,CAAC;AAElF;;;;;GAKG;AACH,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,MAAM,GAAG,eAAe,CAKnE;AAED,wDAAwD;AACxD,wBAAgB,oBAAoB,CAAC,WAAW,EAAE,MAAM,GAAG,YAAY,CA8BtE;AAED,4DAA4D;AAC5D,wBAAgB,qBAAqB,CAAC,SAAS,EAAE,YAAY,GAAG,MAAM,CAWrE;AAED,gFAAgF;AAChF,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAEvD;AAED,iFAAiF;AACjF,wBAAgB,gBAAgB,CAAC,CAAC,EAAE,SAAS,EAAE,YAAY,GAAG,CAAC,CAE9D;AAED,MAAM,WAAW,aAAa;IAC5B,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACpC;AAED;;;;GAIG;AACH,wBAAgB,SAAS,CAAC,SAAS,EAAE,YAAY,EAAE,IAAI,EAAE,aAAa,GAAG,aAAa,CAcrF;AAED,sEAAsE;AACtE,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAKtD;AAED,MAAM,WAAW,aAAa;IAC5B,MAAM,EAAE,MAAM,CAAC;IACf,0DAA0D;IAC1D,KAAK,EAAE,MAAM,CAAC;CACf;AAED,oEAAoE;AACpE,wBAAgB,eAAe,CAAC,WAAW,EAAE,MAAM,GAAG,aAAa,CAgBlE"}
|
package/dist/mpp.js
ADDED
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import { SettlementError, assertCaip2 } from './types.js';
|
|
2
|
+
import { authScheme, decodeJsonPayload, encodeJsonPayload, formatAuthHeader, parseAuthHeader } from './wire.js';
|
|
3
|
+
/**
|
|
4
|
+
* MPP — the Machine Payments Protocol, i.e. the `Payment` HTTP authentication
|
|
5
|
+
* scheme of IETF `draft-httpauth-payment-00` (Tempo Labs / Stripe).
|
|
6
|
+
*
|
|
7
|
+
* The core spec is payment-method agnostic: a challenge names a registered
|
|
8
|
+
* payment method and carries a method-specific `request` blob. Keel therefore
|
|
9
|
+
* models the *core* surface exactly — challenge, credential, status semantics —
|
|
10
|
+
* and keeps `request` opaque, decoded by whichever method spec the caller
|
|
11
|
+
* implements. Nothing method-specific is guessed at here.
|
|
12
|
+
*/
|
|
13
|
+
export const MPP_SCHEME = 'Payment';
|
|
14
|
+
/** Server → client, carries the receipt once payment is verified. */
|
|
15
|
+
export const MPP_RECEIPT_HEADER = 'Payment-Receipt';
|
|
16
|
+
/** Status codes from the draft's response table (draft §4.2). */
|
|
17
|
+
export const MPP_STATUS = {
|
|
18
|
+
granted: 200,
|
|
19
|
+
/** Payment barrier: fresh challenge, or a problem describing why it failed. */
|
|
20
|
+
paymentRequired: 402,
|
|
21
|
+
/** Payment was valid, but policy denies access — a fresh challenge would not help. */
|
|
22
|
+
policyDenied: 403,
|
|
23
|
+
};
|
|
24
|
+
const REQUIRED_PARAMS = ['id', 'method', 'intent', 'request'];
|
|
25
|
+
/**
|
|
26
|
+
* Classifies a response to a payment attempt.
|
|
27
|
+
*
|
|
28
|
+
* The distinction that matters: 403 means the payment *was* accepted but policy
|
|
29
|
+
* denied access, so re-paying is pointless — only 402 is a retry signal.
|
|
30
|
+
*/
|
|
31
|
+
export function classifyMppResponse(status) {
|
|
32
|
+
if (status === MPP_STATUS.granted)
|
|
33
|
+
return 'granted';
|
|
34
|
+
if (status === MPP_STATUS.paymentRequired)
|
|
35
|
+
return 'challenge';
|
|
36
|
+
if (status === MPP_STATUS.policyDenied)
|
|
37
|
+
return 'policy-denied';
|
|
38
|
+
return 'other';
|
|
39
|
+
}
|
|
40
|
+
/** Parses a `WWW-Authenticate: Payment …` challenge. */
|
|
41
|
+
export function parseWwwAuthenticate(headerValue) {
|
|
42
|
+
const scheme = authScheme(headerValue);
|
|
43
|
+
if (scheme.toLowerCase() !== MPP_SCHEME.toLowerCase()) {
|
|
44
|
+
throw new SettlementError('malformed-payload', `expected the ${MPP_SCHEME} auth scheme, got ${JSON.stringify(scheme)}`);
|
|
45
|
+
}
|
|
46
|
+
const { params } = parseAuthHeader(headerValue);
|
|
47
|
+
const challenge = {
|
|
48
|
+
id: params['id'] ?? '',
|
|
49
|
+
method: params['method'] ?? '',
|
|
50
|
+
intent: params['intent'] ?? '',
|
|
51
|
+
request: params['request'] ?? '',
|
|
52
|
+
};
|
|
53
|
+
for (const field of REQUIRED_PARAMS) {
|
|
54
|
+
if (challenge[field] === '') {
|
|
55
|
+
throw new SettlementError('missing-field', `Payment challenge is missing ${field}`);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
const extra = Object.entries(params).filter(([name]) => !REQUIRED_PARAMS.includes(name));
|
|
59
|
+
if (extra.length > 0) {
|
|
60
|
+
challenge.params = Object.fromEntries(extra);
|
|
61
|
+
}
|
|
62
|
+
return challenge;
|
|
63
|
+
}
|
|
64
|
+
/** Serialises a `WWW-Authenticate: Payment …` challenge. */
|
|
65
|
+
export function formatWwwAuthenticate(challenge) {
|
|
66
|
+
return formatAuthHeader({
|
|
67
|
+
scheme: MPP_SCHEME,
|
|
68
|
+
params: {
|
|
69
|
+
id: challenge.id,
|
|
70
|
+
method: challenge.method,
|
|
71
|
+
intent: challenge.intent,
|
|
72
|
+
request: challenge.request,
|
|
73
|
+
...(challenge.params ?? {}),
|
|
74
|
+
},
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
/** Encodes a method-specific request blob for the `request` challenge param. */
|
|
78
|
+
export function encodeMppRequest(value) {
|
|
79
|
+
return encodeJsonPayload(value, { urlSafe: true });
|
|
80
|
+
}
|
|
81
|
+
/** Decodes the method-specific `request` blob using the caller's method spec. */
|
|
82
|
+
export function decodeMppRequest(challenge) {
|
|
83
|
+
return decodeJsonPayload(challenge.request);
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Builds the client-side intent for a challenge. The value terms come from the
|
|
87
|
+
* caller's method-specific decode of `request`; the challenge only supplies the
|
|
88
|
+
* identity.
|
|
89
|
+
*/
|
|
90
|
+
export function mppIntent(challenge, args) {
|
|
91
|
+
const intent = {
|
|
92
|
+
id: challenge.id,
|
|
93
|
+
protocol: 'mpp',
|
|
94
|
+
network: assertCaip2(args.network, 'network'),
|
|
95
|
+
asset: args.asset,
|
|
96
|
+
amount: args.amount,
|
|
97
|
+
payer: args.payer,
|
|
98
|
+
payee: args.payee,
|
|
99
|
+
};
|
|
100
|
+
if (args.reference !== undefined)
|
|
101
|
+
intent.reference = args.reference;
|
|
102
|
+
if (args.expiresAt !== undefined)
|
|
103
|
+
intent.expiresAt = args.expiresAt;
|
|
104
|
+
if (args.metadata !== undefined)
|
|
105
|
+
intent.metadata = args.metadata;
|
|
106
|
+
return intent;
|
|
107
|
+
}
|
|
108
|
+
/** Renders the `Authorization: Payment <token>` credential header. */
|
|
109
|
+
export function formatCredential(token) {
|
|
110
|
+
if (token.trim() === '') {
|
|
111
|
+
throw new SettlementError('missing-field', 'payment credential token is empty');
|
|
112
|
+
}
|
|
113
|
+
return `${MPP_SCHEME} ${token}`;
|
|
114
|
+
}
|
|
115
|
+
/** Parses an `Authorization: Payment <token>` credential header. */
|
|
116
|
+
export function parseCredential(headerValue) {
|
|
117
|
+
const trimmed = headerValue.trim();
|
|
118
|
+
const separator = trimmed.indexOf(' ');
|
|
119
|
+
const scheme = separator === -1 ? trimmed : trimmed.slice(0, separator);
|
|
120
|
+
const token = separator === -1 ? '' : trimmed.slice(separator + 1).trim();
|
|
121
|
+
if (scheme.toLowerCase() !== MPP_SCHEME.toLowerCase()) {
|
|
122
|
+
throw new SettlementError('malformed-payload', `expected the ${MPP_SCHEME} auth scheme, got ${JSON.stringify(scheme)}`);
|
|
123
|
+
}
|
|
124
|
+
if (token === '') {
|
|
125
|
+
throw new SettlementError('missing-field', 'payment credential token is empty');
|
|
126
|
+
}
|
|
127
|
+
return { scheme, token };
|
|
128
|
+
}
|
|
129
|
+
//# sourceMappingURL=mpp.js.map
|
package/dist/mpp.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mpp.js","sourceRoot":"","sources":["../src/mpp.ts"],"names":[],"mappings":"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"}
|
package/dist/rails.d.ts
ADDED
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import { type Caip2, type PaymentIntent } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Protocol-agnostic rail selection for fiat / stablecoin orchestration.
|
|
4
|
+
*
|
|
5
|
+
* A {@link PaymentIntent} says *what* the payer wants to move; a
|
|
6
|
+
* {@link PaymentRail} says *how* it could move. This module is the glue between
|
|
7
|
+
* the two: it filters a catalogue of rails down to the ones that can honour an
|
|
8
|
+
* intent under a set of constraints, then picks one deterministically.
|
|
9
|
+
*
|
|
10
|
+
* Four rail kinds are modelled, none of which require a protocol codec here:
|
|
11
|
+
*
|
|
12
|
+
* - `x402` / `mpp` — HTTP payment handshakes whose codecs live in their own
|
|
13
|
+
* modules; routing only needs to know the rail can settle the terms.
|
|
14
|
+
* - `chain` — a direct on-chain stablecoin transfer.
|
|
15
|
+
* - `fiat` — a hosted checkout. The rail carries an **opaque** `checkout`
|
|
16
|
+
* handle; Keel never inspects it, and the hosted-checkout specifics (session
|
|
17
|
+
* creation, redirect, capture) are entirely host-side. No PSP is integrated
|
|
18
|
+
* and no dependency is added.
|
|
19
|
+
*
|
|
20
|
+
* Selection is pure and deterministic: same inputs, same rail. Ties are broken
|
|
21
|
+
* by cost, then priority hint, then a fixed kind order, then rail id.
|
|
22
|
+
*/
|
|
23
|
+
/** The kinds of rail this layer can route across. */
|
|
24
|
+
export type RailKind = 'x402' | 'mpp' | 'chain' | 'fiat';
|
|
25
|
+
/** The stable order used as the final, documented kind tie-break. */
|
|
26
|
+
export declare const RAIL_KIND_ORDER: readonly RailKind[];
|
|
27
|
+
/** An asset a rail can settle, in its own denomination. */
|
|
28
|
+
export interface RailAsset {
|
|
29
|
+
/**
|
|
30
|
+
* Canonical reference: a token contract address (or `native`) for on-chain
|
|
31
|
+
* rails, an ISO-4217 code (e.g. `usd`) for fiat rails.
|
|
32
|
+
*/
|
|
33
|
+
address: string;
|
|
34
|
+
/** Display ticker, e.g. `USDC` or `USD`. */
|
|
35
|
+
symbol: string;
|
|
36
|
+
/** Minor units per whole unit: 6 for USDC, 2 for USD. */
|
|
37
|
+
decimals: number;
|
|
38
|
+
/** Networks this asset is accepted on; defaults to the rail's `networks`. */
|
|
39
|
+
networks?: readonly Caip2[];
|
|
40
|
+
}
|
|
41
|
+
/** A way an intent could be settled. Descriptors are data — no live client. */
|
|
42
|
+
export interface PaymentRail {
|
|
43
|
+
kind: RailKind;
|
|
44
|
+
/** Stable identifier, unique within a catalogue. */
|
|
45
|
+
id: string;
|
|
46
|
+
/** CAIP-2 networks this rail can settle on. */
|
|
47
|
+
networks: readonly Caip2[];
|
|
48
|
+
/** Assets this rail accepts. */
|
|
49
|
+
assets: readonly RailAsset[];
|
|
50
|
+
/** Opaque destination/payTo (a chain address, a merchant id, …). */
|
|
51
|
+
payTo: string;
|
|
52
|
+
/**
|
|
53
|
+
* Opaque hosted-checkout handle. Required for `fiat` rails and ignored for
|
|
54
|
+
* every other kind; its meaning is owned by the host's checkout integration.
|
|
55
|
+
*/
|
|
56
|
+
checkout?: string;
|
|
57
|
+
/** Extra fee the rail charges, in atomic units of the settled asset. */
|
|
58
|
+
cost?: bigint;
|
|
59
|
+
/** Tie-break hint after cost; lower wins. */
|
|
60
|
+
priority?: number;
|
|
61
|
+
}
|
|
62
|
+
/** Constraints a payer applies on top of the intent. */
|
|
63
|
+
export interface RailConstraints {
|
|
64
|
+
/** Only these rail kinds are acceptable. */
|
|
65
|
+
kinds?: readonly RailKind[];
|
|
66
|
+
/**
|
|
67
|
+
* Only these networks are acceptable. The intent's own `network` must be one
|
|
68
|
+
* of them, since a plan settles on the intent's network.
|
|
69
|
+
*/
|
|
70
|
+
networks?: readonly Caip2[];
|
|
71
|
+
/** Only these assets (address or symbol, case-insensitive) are acceptable. */
|
|
72
|
+
assets?: readonly string[];
|
|
73
|
+
/** Cap on `amount + cost`, in atomic units. */
|
|
74
|
+
maxAmount?: bigint;
|
|
75
|
+
}
|
|
76
|
+
/** The concrete terms of a selected rail: what the host would actually pay. */
|
|
77
|
+
export interface RoutePlan {
|
|
78
|
+
railId: string;
|
|
79
|
+
kind: RailKind;
|
|
80
|
+
network: Caip2;
|
|
81
|
+
asset: RailAsset;
|
|
82
|
+
/** Payment amount in atomic units. */
|
|
83
|
+
amount: bigint;
|
|
84
|
+
/** `amount` rendered against `asset.decimals`. */
|
|
85
|
+
amountDecimal: string;
|
|
86
|
+
/** Rail fee in atomic units. */
|
|
87
|
+
cost: bigint;
|
|
88
|
+
/** `amount + cost`. */
|
|
89
|
+
total: bigint;
|
|
90
|
+
payTo: string;
|
|
91
|
+
/** Present only for `fiat` rails; opaque, host-defined. */
|
|
92
|
+
checkoutHandle?: string;
|
|
93
|
+
}
|
|
94
|
+
export interface RailSelection {
|
|
95
|
+
rail: PaymentRail;
|
|
96
|
+
plan: RoutePlan;
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Parses a human decimal amount into atomic units for `decimals` places, e.g.
|
|
100
|
+
* `parseUnits('0.01', 6) === 10000n`.
|
|
101
|
+
*
|
|
102
|
+
* Hand-rolled on purpose rather than delegated to `viem`'s `parseUnits`. viem
|
|
103
|
+
* rounds when the input carries more fractional digits than the asset supports
|
|
104
|
+
* (`1.234` at 2dp yields `123n`), and its parsing rules have changed between
|
|
105
|
+
* releases — inheriting either is a poor foundation for settlement arithmetic.
|
|
106
|
+
* More fractional digits than the asset supports is an error here, not a silent
|
|
107
|
+
* truncation: rounding someone's money is a bug, not a convenience.
|
|
108
|
+
*
|
|
109
|
+
* `rails.units.viem.test.ts` pins both the agreement on the accepted domain and
|
|
110
|
+
* this intentional divergence, so neither can drift unnoticed.
|
|
111
|
+
*/
|
|
112
|
+
export declare function parseUnits(value: string, decimals: number): bigint;
|
|
113
|
+
/**
|
|
114
|
+
* Renders atomic units as a human decimal amount, trimming trailing zeros so
|
|
115
|
+
* that `parseUnits(formatUnits(n, d), d) === n` round-trips exactly.
|
|
116
|
+
*/
|
|
117
|
+
export declare function formatUnits(value: bigint, decimals: number): string;
|
|
118
|
+
/**
|
|
119
|
+
* Resolves the asset descriptor for `assetRef` on `network`, proving the rail
|
|
120
|
+
* accepts it there. Throws a `requirement-mismatch` rather than routing a
|
|
121
|
+
* payment the rail cannot actually settle.
|
|
122
|
+
*/
|
|
123
|
+
export declare function resolveRailAsset(rail: PaymentRail, network: Caip2, assetRef: string): RailAsset;
|
|
124
|
+
/**
|
|
125
|
+
* Picks the cheapest eligible rail for an intent and materialises the plan.
|
|
126
|
+
*
|
|
127
|
+
* Throws `no-acceptable-requirement` when nothing can satisfy the intent and
|
|
128
|
+
* constraints — never returns a "best effort" pick, because silently routing a
|
|
129
|
+
* payment on terms the payer did not accept is how an agent overpays.
|
|
130
|
+
*/
|
|
131
|
+
export declare function selectRail(rails: readonly PaymentRail[], intent: PaymentIntent, constraints?: RailConstraints): RailSelection;
|
|
132
|
+
//# sourceMappingURL=rails.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rails.d.ts","sourceRoot":"","sources":["../src/rails.ts"],"names":[],"mappings":"AAAA,OAAO,EAAmB,KAAK,KAAK,EAAE,KAAK,aAAa,EAAE,MAAM,YAAY,CAAC;AAE7E;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,qDAAqD;AACrD,MAAM,MAAM,QAAQ,GAAG,MAAM,GAAG,KAAK,GAAG,OAAO,GAAG,MAAM,CAAC;AAEzD,qEAAqE;AACrE,eAAO,MAAM,eAAe,EAAE,SAAS,QAAQ,EAAqC,CAAC;AAErF,2DAA2D;AAC3D,MAAM,WAAW,SAAS;IACxB;;;OAGG;IACH,OAAO,EAAE,MAAM,CAAC;IAChB,4CAA4C;IAC5C,MAAM,EAAE,MAAM,CAAC;IACf,yDAAyD;IACzD,QAAQ,EAAE,MAAM,CAAC;IACjB,6EAA6E;IAC7E,QAAQ,CAAC,EAAE,SAAS,KAAK,EAAE,CAAC;CAC7B;AAED,+EAA+E;AAC/E,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,QAAQ,CAAC;IACf,oDAAoD;IACpD,EAAE,EAAE,MAAM,CAAC;IACX,+CAA+C;IAC/C,QAAQ,EAAE,SAAS,KAAK,EAAE,CAAC;IAC3B,gCAAgC;IAChC,MAAM,EAAE,SAAS,SAAS,EAAE,CAAC;IAC7B,oEAAoE;IACpE,KAAK,EAAE,MAAM,CAAC;IACd;;;OAGG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,wEAAwE;IACxE,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,6CAA6C;IAC7C,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,wDAAwD;AACxD,MAAM,WAAW,eAAe;IAC9B,4CAA4C;IAC5C,KAAK,CAAC,EAAE,SAAS,QAAQ,EAAE,CAAC;IAC5B;;;OAGG;IACH,QAAQ,CAAC,EAAE,SAAS,KAAK,EAAE,CAAC;IAC5B,8EAA8E;IAC9E,MAAM,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAC3B,+CAA+C;IAC/C,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,+EAA+E;AAC/E,MAAM,WAAW,SAAS;IACxB,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,QAAQ,CAAC;IACf,OAAO,EAAE,KAAK,CAAC;IACf,KAAK,EAAE,SAAS,CAAC;IACjB,sCAAsC;IACtC,MAAM,EAAE,MAAM,CAAC;IACf,kDAAkD;IAClD,aAAa,EAAE,MAAM,CAAC;IACtB,gCAAgC;IAChC,IAAI,EAAE,MAAM,CAAC;IACb,uBAAuB;IACvB,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,2DAA2D;IAC3D,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAED,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,WAAW,CAAC;IAClB,IAAI,EAAE,SAAS,CAAC;CACjB;AAaD;;;;;;;;;;;;;GAaG;AACH,wBAAgB,UAAU,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,CAalE;AAED;;;GAGG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,CASnE;AAgBD;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,WAAW,EAAE,OAAO,EAAE,KAAK,EAAE,QAAQ,EAAE,MAAM,GAAG,SAAS,CAS/F;AAoDD;;;;;;GAMG;AACH,wBAAgB,UAAU,CACxB,KAAK,EAAE,SAAS,WAAW,EAAE,EAC7B,MAAM,EAAE,aAAa,EACrB,WAAW,GAAE,eAAoB,GAChC,aAAa,CA6Bf"}
|