@myzonerocks/pact 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.
Files changed (88) hide show
  1. package/dist/src/adapter.d.ts +18 -0
  2. package/dist/src/adapter.js +11 -0
  3. package/dist/src/adapters/erc20.d.ts +43 -0
  4. package/dist/src/adapters/erc20.js +126 -0
  5. package/dist/src/adapters/mpesa.d.ts +61 -0
  6. package/dist/src/adapters/mpesa.js +272 -0
  7. package/dist/src/adapters/paypal.d.ts +68 -0
  8. package/dist/src/adapters/paypal.js +279 -0
  9. package/dist/src/adapters/stripe.d.ts +61 -0
  10. package/dist/src/adapters/stripe.js +336 -0
  11. package/dist/src/bridge.d.ts +40 -0
  12. package/dist/src/bridge.js +99 -0
  13. package/dist/src/canonical.d.ts +14 -0
  14. package/dist/src/canonical.js +82 -0
  15. package/dist/src/client.d.ts +84 -0
  16. package/dist/src/client.js +510 -0
  17. package/dist/src/compliance.d.ts +23 -0
  18. package/dist/src/compliance.js +26 -0
  19. package/dist/src/crypto.d.ts +12 -0
  20. package/dist/src/crypto.js +50 -0
  21. package/dist/src/fake.d.ts +29 -0
  22. package/dist/src/fake.js +79 -0
  23. package/dist/src/index.d.ts +16 -0
  24. package/dist/src/index.js +16 -0
  25. package/dist/src/ledger.d.ts +47 -0
  26. package/dist/src/ledger.js +84 -0
  27. package/dist/src/leg.d.ts +34 -0
  28. package/dist/src/leg.js +4 -0
  29. package/dist/src/message.d.ts +55 -0
  30. package/dist/src/message.js +88 -0
  31. package/dist/src/money.d.ts +16 -0
  32. package/dist/src/money.js +81 -0
  33. package/dist/src/payload.d.ts +11 -0
  34. package/dist/src/payload.js +52 -0
  35. package/dist/src/policy.d.ts +26 -0
  36. package/dist/src/policy.js +83 -0
  37. package/dist/src/protocol.d.ts +4 -0
  38. package/dist/src/protocol.js +15 -0
  39. package/dist/src/router.d.ts +16 -0
  40. package/dist/src/router.js +87 -0
  41. package/dist/src/signing.d.ts +27 -0
  42. package/dist/src/signing.js +72 -0
  43. package/dist/src/state.d.ts +23 -0
  44. package/dist/src/state.js +76 -0
  45. package/dist/src/wire.d.ts +30 -0
  46. package/dist/src/wire.js +221 -0
  47. package/dist/test/erc20.test.d.ts +1 -0
  48. package/dist/test/erc20.test.js +131 -0
  49. package/dist/test/lifecycle.test.d.ts +1 -0
  50. package/dist/test/lifecycle.test.js +212 -0
  51. package/dist/test/mpesa.test.d.ts +1 -0
  52. package/dist/test/mpesa.test.js +180 -0
  53. package/dist/test/payload.test.d.ts +1 -0
  54. package/dist/test/payload.test.js +32 -0
  55. package/dist/test/paypal.test.d.ts +1 -0
  56. package/dist/test/paypal.test.js +140 -0
  57. package/dist/test/policy.test.d.ts +1 -0
  58. package/dist/test/policy.test.js +131 -0
  59. package/dist/test/stripe.test.d.ts +1 -0
  60. package/dist/test/stripe.test.js +176 -0
  61. package/dist/test/vectors.test.d.ts +1 -0
  62. package/dist/test/vectors.test.js +91 -0
  63. package/dist/test/wire.test.d.ts +1 -0
  64. package/dist/test/wire.test.js +104 -0
  65. package/package.json +50 -0
  66. package/src/adapter.ts +32 -0
  67. package/src/adapters/erc20.ts +181 -0
  68. package/src/adapters/mpesa.ts +408 -0
  69. package/src/adapters/paypal.ts +409 -0
  70. package/src/adapters/stripe.ts +478 -0
  71. package/src/bridge.ts +148 -0
  72. package/src/canonical.ts +94 -0
  73. package/src/client.ts +605 -0
  74. package/src/compliance.ts +65 -0
  75. package/src/crypto.ts +68 -0
  76. package/src/fake.ts +96 -0
  77. package/src/index.ts +106 -0
  78. package/src/ledger.ts +145 -0
  79. package/src/leg.ts +65 -0
  80. package/src/message.ts +178 -0
  81. package/src/money.ts +87 -0
  82. package/src/payload.ts +58 -0
  83. package/src/policy.ts +110 -0
  84. package/src/protocol.ts +19 -0
  85. package/src/router.ts +97 -0
  86. package/src/signing.ts +92 -0
  87. package/src/state.ts +76 -0
  88. package/src/wire.ts +248 -0
@@ -0,0 +1,79 @@
1
+ import { State } from "./state.js";
2
+ import { RefundKind } from "./adapter.js";
3
+ // FakeLeg is a deterministic, in-memory pay-in and pay-out leg. It settles
4
+ // instantly with no network and implements both sides so a test can build a
5
+ // direct corridor from one leg or a bridged corridor from two. failPayout makes
6
+ // its disburse fail so a test can exercise the escrow-unwind path.
7
+ export class FakeLeg {
8
+ id;
9
+ rail;
10
+ currency;
11
+ ids;
12
+ failPayout = false;
13
+ constructor(id, rail, currency, ids) {
14
+ this.id = id;
15
+ this.rail = rail;
16
+ this.currency = currency;
17
+ this.ids = ids;
18
+ }
19
+ payInCapabilities() {
20
+ return { rails: [this.rail], currencies: [this.currency], refunds: RefundKind.Full };
21
+ }
22
+ // collect takes the payer's funds. received is the net the bridge converts —
23
+ // the source the payer paid less the corridor fees.
24
+ async collect(_intentId, quote, _auth, _deliverTo) {
25
+ return { providerRef: this.ids(), received: quote.srcAmount.sub(quote.fees) };
26
+ }
27
+ async refundIn(intentId, _kind, reason) {
28
+ return this.terminal(intentId, reason);
29
+ }
30
+ payOutCapabilities() {
31
+ return { rails: [this.rail], currencies: [this.currency], reversible: false };
32
+ }
33
+ async disburse(_intentId, _quote, _recipientRef) {
34
+ if (this.failPayout) {
35
+ throw new Error("fake: payout failed");
36
+ }
37
+ return { providerRef: this.ids() };
38
+ }
39
+ async reverseOut(intentId, reason) {
40
+ return this.terminal(intentId, reason);
41
+ }
42
+ terminal(intentId, reason) {
43
+ return {
44
+ intentId,
45
+ state: State.Refunded,
46
+ adapterId: this.id,
47
+ providerTxRef: this.ids(),
48
+ onchainTxHash: "",
49
+ receiptHash: new Uint8Array(0),
50
+ reason,
51
+ settledAt: 0,
52
+ };
53
+ }
54
+ }
55
+ // FakeRates is an in-memory rate source keyed by "FROM:TO", standing in for a
56
+ // price feed so a bridge can be tested without a live market.
57
+ export class FakeRates {
58
+ table;
59
+ constructor(table) {
60
+ this.table = table;
61
+ }
62
+ async rate(from, to) {
63
+ const rate = this.table[`${from}:${to}`];
64
+ if (rate === undefined) {
65
+ throw new Error(`fake: no rate for ${from}:${to}`);
66
+ }
67
+ return rate;
68
+ }
69
+ }
70
+ // FakeVault is an in-memory escrow that records holds and releases, standing in
71
+ // for the custody a real bridge relies on.
72
+ export class FakeVault {
73
+ async hold(intentId, _amount) {
74
+ return `escrow-${intentId}`;
75
+ }
76
+ async release(_intentId) {
77
+ // Nothing to release in memory.
78
+ }
79
+ }
@@ -0,0 +1,16 @@
1
+ export { ID, VERSION, domain, DEFAULT_SKEW_MILLIS } from "./protocol.js";
2
+ export { Money } from "./money.js";
3
+ export { State, stateName, canTransition, isTerminal, TransitionError } from "./state.js";
4
+ export { CanonicalWriter, hashPreimage } from "./canonical.js";
5
+ export { type Intent, type Quote, type Authorization, type Settlement, BRIDGE_PASSTHROUGH, intentPreimage, intentHash, quotePreimage, quoteHash, isDirect, signingPreimage, authorizationHash, receiptHash, corridorReceipt, } from "./message.js";
6
+ export { type Signer, type Verifier, authorize, verifyAuthorization, Ed25519Signer, Ed25519Verifier, BadSignatureError, } from "./signing.js";
7
+ export { RefundKind, type AdapterEvent, type WebhookParser } from "./adapter.js";
8
+ export { railInList, type PayInLeg, type PayOutLeg, type PayInCapabilities, type PayOutCapabilities, type CollectResult, type DisburseResult, } from "./leg.js";
9
+ export { BRIDGE_USDC, PassThroughBridge, UsdcBridge, applyRate, applyInverseRate, parseRate, type Bridge, type BridgeQuote, type ConvertResult, type RateSource, type EscrowVault, } from "./bridge.js";
10
+ export { type KycProvider, type KycStatus, type KycLimits, type RiskHook, type RiskDecision, permissiveKyc, allowRisk, withinLimits, } from "./compliance.js";
11
+ export { PolicyKind, type Policy, route, isExpired, NoQuoteError } from "./router.js";
12
+ export { MemoryLedger, eventReceipt, chainLeaf, IdempotencyConflictError, type Ledger, type LedgerEvent, type Transition, } from "./ledger.js";
13
+ export { Client, type ClientConfig, type IntentSpec, type Funding, type Clock, type IdGen, systemClock, randomId, recipientDestination, } from "./client.js";
14
+ export { FakeLeg, FakeRates, FakeVault } from "./fake.js";
15
+ export { WireKind, type WireMessage, encodeIntent, encodeQuote, encodeAuthorization, encodeSettlement, encodeMessage, decodeMessage, decodeIntent, decodeQuote, decodeAuthorization, decodeSettlement, } from "./wire.js";
16
+ export { PAYLOAD_VERSION, encodePayload, decodePayload, isPayload, NotPayloadError, UnsupportedPayloadVersionError, } from "./payload.js";
@@ -0,0 +1,16 @@
1
+ export { ID, VERSION, domain, DEFAULT_SKEW_MILLIS } from "./protocol.js";
2
+ export { Money } from "./money.js";
3
+ export { State, stateName, canTransition, isTerminal, TransitionError } from "./state.js";
4
+ export { CanonicalWriter, hashPreimage } from "./canonical.js";
5
+ export { BRIDGE_PASSTHROUGH, intentPreimage, intentHash, quotePreimage, quoteHash, isDirect, signingPreimage, authorizationHash, receiptHash, corridorReceipt, } from "./message.js";
6
+ export { authorize, verifyAuthorization, Ed25519Signer, Ed25519Verifier, BadSignatureError, } from "./signing.js";
7
+ export { RefundKind } from "./adapter.js";
8
+ export { railInList, } from "./leg.js";
9
+ export { BRIDGE_USDC, PassThroughBridge, UsdcBridge, applyRate, applyInverseRate, parseRate, } from "./bridge.js";
10
+ export { permissiveKyc, allowRisk, withinLimits, } from "./compliance.js";
11
+ export { PolicyKind, route, isExpired, NoQuoteError } from "./router.js";
12
+ export { MemoryLedger, eventReceipt, chainLeaf, IdempotencyConflictError, } from "./ledger.js";
13
+ export { Client, systemClock, randomId, recipientDestination, } from "./client.js";
14
+ export { FakeLeg, FakeRates, FakeVault } from "./fake.js";
15
+ export { WireKind, encodeIntent, encodeQuote, encodeAuthorization, encodeSettlement, encodeMessage, decodeMessage, decodeIntent, decodeQuote, decodeAuthorization, decodeSettlement, } from "./wire.js";
16
+ export { PAYLOAD_VERSION, encodePayload, decodePayload, isPayload, NotPayloadError, UnsupportedPayloadVersionError, } from "./payload.js";
@@ -0,0 +1,47 @@
1
+ import { State } from "./state.js";
2
+ import type { Intent, Quote, Authorization, Settlement } from "./message.js";
3
+ import type { CollectResult } from "./leg.js";
4
+ import type { ConvertResult } from "./bridge.js";
5
+ export interface LedgerEvent {
6
+ seq: number;
7
+ intentId: string;
8
+ state: State;
9
+ payloadHash: Uint8Array;
10
+ receipt: Uint8Array;
11
+ leaf: Uint8Array;
12
+ intent?: Intent;
13
+ quote?: Quote;
14
+ authorization?: Authorization;
15
+ settlement?: Settlement;
16
+ collect?: CollectResult;
17
+ bridge?: ConvertResult;
18
+ }
19
+ export interface Transition {
20
+ intentId: string;
21
+ to: State;
22
+ payloadHash: Uint8Array;
23
+ intent?: Intent;
24
+ quote?: Quote;
25
+ authorization?: Authorization;
26
+ settlement?: Settlement;
27
+ collect?: CollectResult;
28
+ bridge?: ConvertResult;
29
+ }
30
+ export declare class IdempotencyConflictError extends Error {
31
+ constructor();
32
+ }
33
+ export interface Ledger {
34
+ apply(t: Transition): LedgerEvent;
35
+ state(intentId: string): State;
36
+ events(intentId: string): LedgerEvent[];
37
+ head(intentId: string): Uint8Array | undefined;
38
+ }
39
+ export declare class MemoryLedger implements Ledger {
40
+ private readonly byIntent;
41
+ apply(t: Transition): LedgerEvent;
42
+ state(intentId: string): State;
43
+ events(intentId: string): LedgerEvent[];
44
+ head(intentId: string): Uint8Array | undefined;
45
+ }
46
+ export declare function eventReceipt(seq: number, intentId: string, state: State, payloadHash: Uint8Array): Uint8Array;
47
+ export declare function chainLeaf(prevLeaf: Uint8Array, receipt: Uint8Array): Uint8Array;
@@ -0,0 +1,84 @@
1
+ import { State, stateName, canTransition, TransitionError } from "./state.js";
2
+ import { CanonicalWriter, hashPreimage } from "./canonical.js";
3
+ import { domain } from "./protocol.js";
4
+ import { bytesEqual } from "./crypto.js";
5
+ export class IdempotencyConflictError extends Error {
6
+ constructor() {
7
+ super("pact: idempotency conflict: step already recorded with a different payload");
8
+ this.name = "IdempotencyConflictError";
9
+ }
10
+ }
11
+ const zeroLeaf = new Uint8Array(32);
12
+ // MemoryLedger is the in-memory reference ledger.
13
+ export class MemoryLedger {
14
+ byIntent = new Map();
15
+ apply(t) {
16
+ const history = this.byIntent.get(t.intentId) ?? [];
17
+ // A repeat of the same step is a no-op returning the first result; the same
18
+ // step with a different payload is a conflict.
19
+ for (const e of history) {
20
+ if (e.state === t.to) {
21
+ if (bytesEqual(e.payloadHash, t.payloadHash))
22
+ return e;
23
+ throw new IdempotencyConflictError();
24
+ }
25
+ }
26
+ const current = history.length > 0 ? history[history.length - 1].state : State.Unspecified;
27
+ if (!canTransition(current, t.to)) {
28
+ throw new TransitionError(current, t.to);
29
+ }
30
+ const prevLeaf = history.length > 0 ? history[history.length - 1].leaf : zeroLeaf;
31
+ const seq = history.length + 1;
32
+ const receipt = eventReceipt(seq, t.intentId, t.to, t.payloadHash);
33
+ const leaf = chainLeaf(prevLeaf, receipt);
34
+ const event = {
35
+ seq,
36
+ intentId: t.intentId,
37
+ state: t.to,
38
+ payloadHash: t.payloadHash.slice(),
39
+ receipt,
40
+ leaf,
41
+ ...(t.intent ? { intent: t.intent } : {}),
42
+ ...(t.quote ? { quote: t.quote } : {}),
43
+ ...(t.authorization ? { authorization: t.authorization } : {}),
44
+ ...(t.settlement ? { settlement: t.settlement } : {}),
45
+ ...(t.collect ? { collect: t.collect } : {}),
46
+ ...(t.bridge ? { bridge: t.bridge } : {}),
47
+ };
48
+ this.byIntent.set(t.intentId, [...history, event]);
49
+ return event;
50
+ }
51
+ state(intentId) {
52
+ const history = this.byIntent.get(intentId);
53
+ if (!history || history.length === 0)
54
+ return State.Unspecified;
55
+ return history[history.length - 1].state;
56
+ }
57
+ events(intentId) {
58
+ return [...(this.byIntent.get(intentId) ?? [])];
59
+ }
60
+ head(intentId) {
61
+ const history = this.byIntent.get(intentId);
62
+ if (!history || history.length === 0)
63
+ return undefined;
64
+ return history[history.length - 1].leaf;
65
+ }
66
+ }
67
+ // eventReceipt commits to an event's identity independent of the Merkle chaining,
68
+ // so a single event can be shown and checked on its own.
69
+ export function eventReceipt(seq, intentId, state, payloadHash) {
70
+ return hashPreimage(new CanonicalWriter()
71
+ .str(domain("event"))
72
+ .u64(seq)
73
+ .str(intentId)
74
+ .str(stateName[state])
75
+ .bytes(payloadHash)
76
+ .preimage());
77
+ }
78
+ // chainLeaf folds one event's receipt into the running Merkle head.
79
+ export function chainLeaf(prevLeaf, receipt) {
80
+ const joined = new Uint8Array(prevLeaf.length + receipt.length);
81
+ joined.set(prevLeaf, 0);
82
+ joined.set(receipt, prevLeaf.length);
83
+ return hashPreimage(joined);
84
+ }
@@ -0,0 +1,34 @@
1
+ import type { Money } from "./money.js";
2
+ import type { Quote, Authorization, Settlement } from "./message.js";
3
+ import type { RefundKind } from "./adapter.js";
4
+ export interface PayInCapabilities {
5
+ rails: string[];
6
+ currencies: string[];
7
+ methods?: string[];
8
+ refunds: RefundKind;
9
+ }
10
+ export interface CollectResult {
11
+ providerRef: string;
12
+ received: Money;
13
+ }
14
+ export interface PayInLeg {
15
+ id: string;
16
+ payInCapabilities(): PayInCapabilities;
17
+ collect(intentId: string, quote: Quote, auth: Authorization, deliverTo: string): Promise<CollectResult>;
18
+ refundIn(intentId: string, kind: RefundKind, reason: string): Promise<Settlement>;
19
+ }
20
+ export interface PayOutCapabilities {
21
+ rails: string[];
22
+ currencies: string[];
23
+ reversible: boolean;
24
+ }
25
+ export interface DisburseResult {
26
+ providerRef: string;
27
+ }
28
+ export interface PayOutLeg {
29
+ id: string;
30
+ payOutCapabilities(): PayOutCapabilities;
31
+ disburse(intentId: string, quote: Quote, recipientRef: string): Promise<DisburseResult>;
32
+ reverseOut(intentId: string, reason: string): Promise<Settlement>;
33
+ }
34
+ export declare function railInList(rails: string[], rail: string): boolean;
@@ -0,0 +1,4 @@
1
+ // railInList reports whether a set of rails contains the requested rail.
2
+ export function railInList(rails, rail) {
3
+ return rails.includes(rail);
4
+ }
@@ -0,0 +1,55 @@
1
+ import { Money } from "./money.js";
2
+ import { State } from "./state.js";
3
+ export interface Intent {
4
+ id: string;
5
+ senderRef: string;
6
+ recipientRef: string;
7
+ amount: Money;
8
+ memo: string;
9
+ expiresAt: number;
10
+ allowedRails: string[];
11
+ metadata: Record<string, string>;
12
+ }
13
+ export declare function intentPreimage(i: Intent): Uint8Array;
14
+ export declare function intentHash(i: Intent): Uint8Array;
15
+ export declare const BRIDGE_PASSTHROUGH = "passthrough";
16
+ export interface Quote {
17
+ id: string;
18
+ intentId: string;
19
+ payInAdapterId: string;
20
+ payInRail: string;
21
+ payOutAdapterId: string;
22
+ payOutRail: string;
23
+ bridgeId: string;
24
+ srcAmount: Money;
25
+ dstAmount: Money;
26
+ fees: Money;
27
+ fxRate: string;
28
+ expiresAt: number;
29
+ providerQuoteRef: string;
30
+ latencyEstimateMs: number;
31
+ }
32
+ export declare function quotePreimage(q: Quote): Uint8Array;
33
+ export declare function quoteHash(q: Quote): Uint8Array;
34
+ export declare function isDirect(q: Quote): boolean;
35
+ export interface Authorization {
36
+ intentId: string;
37
+ quoteId: string;
38
+ signerIdentity: string;
39
+ signedAt: number;
40
+ signature: Uint8Array;
41
+ }
42
+ export declare function signingPreimage(intentHashBytes: Uint8Array, quoteHashBytes: Uint8Array, signerIdentity: string, signedAt: number): Uint8Array;
43
+ export declare function authorizationHash(intentHashBytes: Uint8Array, quoteHashBytes: Uint8Array, signerIdentity: string, signedAt: number): Uint8Array;
44
+ export interface Settlement {
45
+ intentId: string;
46
+ state: State;
47
+ adapterId: string;
48
+ providerTxRef: string;
49
+ onchainTxHash: string;
50
+ receiptHash: Uint8Array;
51
+ reason: string;
52
+ settledAt: number;
53
+ }
54
+ export declare function receiptHash(authHash: Uint8Array, providerTxRef: string, state: State): Uint8Array;
55
+ export declare function corridorReceipt(authHash: Uint8Array, payInProviderRef: string, payOutProviderRef: string, fxRate: string, bridgeReceiptRef: string, state: State): Uint8Array;
@@ -0,0 +1,88 @@
1
+ import { stateName } from "./state.js";
2
+ import { CanonicalWriter, hashPreimage } from "./canonical.js";
3
+ import { domain } from "./protocol.js";
4
+ export function intentPreimage(i) {
5
+ return new CanonicalWriter()
6
+ .str(domain("intent"))
7
+ .str(i.id)
8
+ .str(i.senderRef)
9
+ .str(i.recipientRef)
10
+ .money(i.amount)
11
+ .str(i.memo)
12
+ .u64(i.expiresAt)
13
+ .list(i.allowedRails)
14
+ .stringMap(i.metadata)
15
+ .preimage();
16
+ }
17
+ export function intentHash(i) {
18
+ return hashPreimage(intentPreimage(i));
19
+ }
20
+ // The bridge that moves value unchanged when the payer and recipient already
21
+ // share a currency. Its identifier is a stable wire value carried in a quote.
22
+ export const BRIDGE_PASSTHROUGH = "passthrough";
23
+ export function quotePreimage(q) {
24
+ return new CanonicalWriter()
25
+ .str(domain("quote"))
26
+ .str(q.id)
27
+ .str(q.intentId)
28
+ .str(q.payInAdapterId)
29
+ .str(q.payInRail)
30
+ .str(q.payOutAdapterId)
31
+ .str(q.payOutRail)
32
+ .str(q.bridgeId)
33
+ .money(q.srcAmount)
34
+ .money(q.dstAmount)
35
+ .money(q.fees)
36
+ .str(q.fxRate)
37
+ .u64(q.expiresAt)
38
+ .str(q.providerQuoteRef)
39
+ .u64(q.latencyEstimateMs)
40
+ .preimage();
41
+ }
42
+ export function quoteHash(q) {
43
+ return hashPreimage(quotePreimage(q));
44
+ }
45
+ // isDirect reports whether a corridor settles in one phase: the same provider
46
+ // serves both legs and the bridge is pass-through.
47
+ export function isDirect(q) {
48
+ return q.payInAdapterId === q.payOutAdapterId && q.bridgeId === BRIDGE_PASSTHROUGH;
49
+ }
50
+ // signingPreimage returns the bytes an authorization signs over.
51
+ export function signingPreimage(intentHashBytes, quoteHashBytes, signerIdentity, signedAt) {
52
+ return new CanonicalWriter()
53
+ .str(domain("authorize"))
54
+ .bytes(intentHashBytes)
55
+ .bytes(quoteHashBytes)
56
+ .str(signerIdentity)
57
+ .u64(signedAt)
58
+ .preimage();
59
+ }
60
+ export function authorizationHash(intentHashBytes, quoteHashBytes, signerIdentity, signedAt) {
61
+ return hashPreimage(signingPreimage(intentHashBytes, quoteHashBytes, signerIdentity, signedAt));
62
+ }
63
+ // receiptHash derives a settlement's receipt commitment from the authorization
64
+ // hash it settles, the provider's transaction reference, and the resulting
65
+ // state. Revealing it plus a ledger inclusion proof lets a recipient prove a
66
+ // settlement without exposing any surrounding conversation.
67
+ export function receiptHash(authHash, providerTxRef, state) {
68
+ return hashPreimage(new CanonicalWriter()
69
+ .str(domain("receipt"))
70
+ .bytes(authHash)
71
+ .str(providerTxRef)
72
+ .str(stateName[state])
73
+ .preimage());
74
+ }
75
+ // corridorReceipt binds both legs of a bridged corridor and the FX rate into one
76
+ // commitment, so the payer can prove they funded X and the recipient can prove
77
+ // they received Y as a single transaction, without revealing anything else.
78
+ export function corridorReceipt(authHash, payInProviderRef, payOutProviderRef, fxRate, bridgeReceiptRef, state) {
79
+ return hashPreimage(new CanonicalWriter()
80
+ .str(domain("corridor"))
81
+ .bytes(authHash)
82
+ .str(payInProviderRef)
83
+ .str(payOutProviderRef)
84
+ .str(fxRate)
85
+ .str(bridgeReceiptRef)
86
+ .str(stateName[state])
87
+ .preimage());
88
+ }
@@ -0,0 +1,16 @@
1
+ export declare class Money {
2
+ readonly currency: string;
3
+ readonly exponent: number;
4
+ private readonly amount;
5
+ private constructor();
6
+ static create(currency: string, exponent: number, amount: bigint): Money;
7
+ static parse(currency: string, exponent: number, minor: string): Money;
8
+ minor(): string;
9
+ value(): bigint;
10
+ private sameKind;
11
+ add(other: Money): Money;
12
+ sub(other: Money): Money;
13
+ cmp(other: Money): number;
14
+ isZero(): boolean;
15
+ toString(): string;
16
+ }
@@ -0,0 +1,81 @@
1
+ // Money is an exact count of minor units in a single currency. The amount is a
2
+ // bigint, never a number: an 18-decimal token counted in its smallest unit
3
+ // overflows a 64-bit integer at roughly nine whole tokens, and floating point
4
+ // has no place near money.
5
+ const currencyPattern = /^[A-Z0-9]{1,16}$/;
6
+ const maxExponent = 30;
7
+ export class Money {
8
+ currency;
9
+ exponent;
10
+ amount;
11
+ constructor(currency, exponent, amount) {
12
+ this.currency = currency;
13
+ this.exponent = exponent;
14
+ this.amount = amount;
15
+ }
16
+ static create(currency, exponent, amount) {
17
+ if (!currencyPattern.test(currency)) {
18
+ throw new Error("pact: currency must match [A-Z0-9]{1,16}");
19
+ }
20
+ if (!Number.isInteger(exponent) || exponent < 0 || exponent > maxExponent) {
21
+ throw new Error(`pact: exponent must be 0..${maxExponent}`);
22
+ }
23
+ if (amount < 0n) {
24
+ throw new Error("pact: amount must be non-negative");
25
+ }
26
+ return new Money(currency, exponent, amount);
27
+ }
28
+ // parse builds Money from a decimal-ASCII minor-unit string, the form used on
29
+ // the wire.
30
+ static parse(currency, exponent, minor) {
31
+ if (!/^\d+$/.test(minor)) {
32
+ throw new Error(`pact: ${minor} is not a base-10 integer`);
33
+ }
34
+ return Money.create(currency, exponent, BigInt(minor));
35
+ }
36
+ // minor renders the amount as canonical decimal ASCII: no leading zeros, "0"
37
+ // for zero. This is the exact string the canonical writer and the wire use.
38
+ minor() {
39
+ return this.amount.toString();
40
+ }
41
+ value() {
42
+ return this.amount;
43
+ }
44
+ sameKind(other) {
45
+ return this.currency === other.currency && this.exponent === other.exponent;
46
+ }
47
+ add(other) {
48
+ if (!this.sameKind(other)) {
49
+ throw new Error("pact: money operands differ in currency or exponent");
50
+ }
51
+ return new Money(this.currency, this.exponent, this.amount + other.amount);
52
+ }
53
+ // sub errors when the result would be negative: Money has no negative range,
54
+ // and a refund is a settlement, not a negative sum.
55
+ sub(other) {
56
+ if (!this.sameKind(other)) {
57
+ throw new Error("pact: money operands differ in currency or exponent");
58
+ }
59
+ const diff = this.amount - other.amount;
60
+ if (diff < 0n) {
61
+ throw new Error("pact: amount must be non-negative");
62
+ }
63
+ return new Money(this.currency, this.exponent, diff);
64
+ }
65
+ cmp(other) {
66
+ if (!this.sameKind(other)) {
67
+ throw new Error("pact: money operands differ in currency or exponent");
68
+ }
69
+ if (this.amount < other.amount)
70
+ return -1;
71
+ if (this.amount > other.amount)
72
+ return 1;
73
+ return 0;
74
+ }
75
+ isZero() {
76
+ return this.amount === 0n;
77
+ }
78
+ toString() {
79
+ return `${this.minor()} ${this.currency}/${this.exponent}`;
80
+ }
81
+ }
@@ -0,0 +1,11 @@
1
+ import { type WireMessage } from "./wire.js";
2
+ export declare const PAYLOAD_VERSION = 1;
3
+ export declare class NotPayloadError extends Error {
4
+ constructor();
5
+ }
6
+ export declare class UnsupportedPayloadVersionError extends Error {
7
+ constructor(version: number);
8
+ }
9
+ export declare function encodePayload(msg: WireMessage): Uint8Array;
10
+ export declare function isPayload(bytes: Uint8Array): boolean;
11
+ export declare function decodePayload(bytes: Uint8Array): WireMessage;
@@ -0,0 +1,52 @@
1
+ import { ID } from "./protocol.js";
2
+ import { encodeMessage, decodeMessage } from "./wire.js";
3
+ // A payload is how a payment message rides inside a host's own envelope — a chat
4
+ // message, a queue entry, any transport. It wraps a wire frame with a small
5
+ // self-identifying header so a receiver can tell a PACT payload apart from other
6
+ // envelope content and reject a version it does not understand. The host carries
7
+ // opaque bytes and stays oblivious to the message inside.
8
+ const payloadMagic = new TextEncoder().encode(ID);
9
+ // PAYLOAD_VERSION is the framing version, independent of the wire version, so the
10
+ // envelope format can evolve without breaking a decoder.
11
+ export const PAYLOAD_VERSION = 1;
12
+ export class NotPayloadError extends Error {
13
+ constructor() {
14
+ super("pact: not a pact payload");
15
+ this.name = "NotPayloadError";
16
+ }
17
+ }
18
+ export class UnsupportedPayloadVersionError extends Error {
19
+ constructor(version) {
20
+ super(`pact: unsupported payload version ${version}`);
21
+ this.name = "UnsupportedPayloadVersionError";
22
+ }
23
+ }
24
+ export function encodePayload(msg) {
25
+ const frame = encodeMessage(msg);
26
+ const out = new Uint8Array(payloadMagic.length + 1 + frame.length);
27
+ out.set(payloadMagic, 0);
28
+ out[payloadMagic.length] = PAYLOAD_VERSION;
29
+ out.set(frame, payloadMagic.length + 1);
30
+ return out;
31
+ }
32
+ // isPayload reports whether bytes carry the PACT payload header, so an envelope
33
+ // decoder can route them to decodePayload without attempting a full parse.
34
+ export function isPayload(bytes) {
35
+ if (bytes.length <= payloadMagic.length)
36
+ return false;
37
+ for (let i = 0; i < payloadMagic.length; i++) {
38
+ if (bytes[i] !== payloadMagic[i])
39
+ return false;
40
+ }
41
+ return true;
42
+ }
43
+ export function decodePayload(bytes) {
44
+ if (!isPayload(bytes)) {
45
+ throw new NotPayloadError();
46
+ }
47
+ const version = bytes[payloadMagic.length];
48
+ if (version !== PAYLOAD_VERSION) {
49
+ throw new UnsupportedPayloadVersionError(version);
50
+ }
51
+ return decodeMessage(bytes.subarray(payloadMagic.length + 1));
52
+ }
@@ -0,0 +1,26 @@
1
+ import { Money } from "./money.js";
2
+ import type { Intent, Quote } from "./message.js";
3
+ import type { KycProvider, KycStatus, RiskHook, RiskDecision } from "./compliance.js";
4
+ export interface Tier {
5
+ name: string;
6
+ jurisdiction: string;
7
+ perPayment?: Money;
8
+ windowSpend?: Money;
9
+ allowedRails?: string[];
10
+ }
11
+ export declare class TieredKyc implements KycProvider {
12
+ private readonly assigned;
13
+ private readonly fallback;
14
+ private readonly tiers;
15
+ constructor(tiers: Tier[], assigned: Record<string, string>, fallback: Tier);
16
+ tierFor(identity: string): Tier;
17
+ status(identity: string): Promise<KycStatus>;
18
+ }
19
+ export declare class WindowRisk implements RiskHook {
20
+ private readonly kyc;
21
+ private readonly windowMillis;
22
+ private readonly now;
23
+ private readonly spend;
24
+ constructor(kyc: TieredKyc, windowMillis: number, now: () => number);
25
+ evaluate(identity: string, _intent: Intent, quote: Quote): RiskDecision;
26
+ }