@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,65 @@
1
+ import type { Money } from "./money.js";
2
+ import type { Quote } from "./message.js";
3
+
4
+ // KycLimits caps what an identity may transact. An absent perPayment means no cap
5
+ // on that dimension.
6
+ export interface KycLimits {
7
+ perPayment?: Money;
8
+ }
9
+
10
+ // KycStatus is what a KycProvider knows about an identity. The kernel uses only
11
+ // the limits to gate quotes; tier and jurisdiction are carried for the host's
12
+ // own policy.
13
+ export interface KycStatus {
14
+ tier: string;
15
+ jurisdiction: string;
16
+ limits: KycLimits;
17
+ }
18
+
19
+ // KycProvider resolves an identity's compliance standing. It is a seam: the
20
+ // kernel ships a permissive default and holds no policy of its own. A real
21
+ // deployment supplies one before any live-money adapter is enabled.
22
+ export interface KycProvider {
23
+ status(identity: string): Promise<KycStatus>;
24
+ }
25
+
26
+ // permissiveKyc imposes no limits, which is only acceptable for test-mode and
27
+ // testnet adapters.
28
+ export const permissiveKyc: KycProvider = {
29
+ async status(): Promise<KycStatus> {
30
+ return { tier: "unverified", jurisdiction: "", limits: {} };
31
+ },
32
+ };
33
+
34
+ // withinLimits reports whether a quote's source amount is inside an identity's
35
+ // per-payment cap. An absent cap or a currency mismatch is treated as no
36
+ // applicable limit.
37
+ export function withinLimits(status: KycStatus, quote: Quote): boolean {
38
+ const cap = status.limits.perPayment;
39
+ if (!cap) return true;
40
+ try {
41
+ return quote.srcAmount.cmp(cap) <= 0;
42
+ } catch {
43
+ return true;
44
+ }
45
+ }
46
+
47
+ // RiskDecision is the outcome of a RiskHook consultation.
48
+ export interface RiskDecision {
49
+ allow: boolean;
50
+ reason: string;
51
+ }
52
+
53
+ // RiskHook is consulted immediately before an authorization is produced. It
54
+ // receives the identity that will sign, so a policy can reason about who is
55
+ // paying, not just what. A veto blocks it. Like KycProvider it is a seam with a
56
+ // permissive default.
57
+ export interface RiskHook {
58
+ evaluate(identity: string, intent: import("./message.js").Intent, quote: Quote): RiskDecision;
59
+ }
60
+
61
+ export const allowRisk: RiskHook = {
62
+ evaluate(): RiskDecision {
63
+ return { allow: true, reason: "" };
64
+ },
65
+ };
package/src/crypto.ts ADDED
@@ -0,0 +1,68 @@
1
+ import {
2
+ createHash,
3
+ createPrivateKey,
4
+ createPublicKey,
5
+ sign as nodeSign,
6
+ verify as nodeVerify,
7
+ type KeyObject,
8
+ } from "node:crypto";
9
+
10
+ // SHA-256 of a byte string. Every message hash and receipt commitment flows
11
+ // through it.
12
+ export function sha256(data: Uint8Array): Uint8Array {
13
+ return new Uint8Array(createHash("sha256").update(data).digest());
14
+ }
15
+
16
+ // The fixed DER prefixes that wrap a raw 32-byte Ed25519 seed or public key so
17
+ // the platform key APIs will import them. The seed becomes a PKCS8 private key;
18
+ // the public key becomes an SPKI public key.
19
+ const pkcs8SeedPrefix = Buffer.from("302e020100300506032b657004220420", "hex");
20
+ const spkiPublicPrefix = Buffer.from("302a300506032b6570032100", "hex");
21
+
22
+ export interface Ed25519KeyPair {
23
+ privateKey: KeyObject;
24
+ publicKey: Uint8Array;
25
+ }
26
+
27
+ // ed25519KeyFromSeed derives a signing key pair from a 32-byte seed. Ed25519 is
28
+ // deterministic, so a given seed and message always yield the same signature,
29
+ // which is what lets the cross-language vectors pin an exact signature.
30
+ export function ed25519KeyFromSeed(seed: Uint8Array): Ed25519KeyPair {
31
+ if (seed.length !== 32) {
32
+ throw new Error("pact: ed25519 seed must be 32 bytes");
33
+ }
34
+ const der = Buffer.concat([pkcs8SeedPrefix, Buffer.from(seed)]);
35
+ const privateKey = createPrivateKey({ key: der, format: "der", type: "pkcs8" });
36
+ const spki = createPublicKey(privateKey).export({ format: "der", type: "spki" }) as Buffer;
37
+ return { privateKey, publicKey: new Uint8Array(spki.subarray(spki.length - 32)) };
38
+ }
39
+
40
+ export function ed25519Sign(privateKey: KeyObject, message: Uint8Array): Uint8Array {
41
+ return new Uint8Array(nodeSign(null, Buffer.from(message), privateKey));
42
+ }
43
+
44
+ export function ed25519Verify(publicKeyRaw: Uint8Array, message: Uint8Array, signature: Uint8Array): boolean {
45
+ if (publicKeyRaw.length !== 32) {
46
+ return false;
47
+ }
48
+ const spki = Buffer.concat([spkiPublicPrefix, Buffer.from(publicKeyRaw)]);
49
+ const publicKey = createPublicKey({ key: spki, format: "der", type: "spki" });
50
+ return nodeVerify(null, Buffer.from(message), publicKey, Buffer.from(signature));
51
+ }
52
+
53
+ // toHex and fromHex render byte strings for the wire and for test vectors.
54
+ export function toHex(bytes: Uint8Array): string {
55
+ return Buffer.from(bytes).toString("hex");
56
+ }
57
+
58
+ export function fromHex(hex: string): Uint8Array {
59
+ return new Uint8Array(Buffer.from(hex, "hex"));
60
+ }
61
+
62
+ export function bytesEqual(a: Uint8Array, b: Uint8Array): boolean {
63
+ if (a.length !== b.length) return false;
64
+ for (let i = 0; i < a.length; i++) {
65
+ if (a[i] !== b[i]) return false;
66
+ }
67
+ return true;
68
+ }
package/src/fake.ts ADDED
@@ -0,0 +1,96 @@
1
+ import { State } from "./state.js";
2
+ import { RefundKind } from "./adapter.js";
3
+ import type { Money } from "./money.js";
4
+ import type { Quote, Authorization, Settlement } from "./message.js";
5
+ import type { RateSource, EscrowVault } from "./bridge.js";
6
+ import type {
7
+ PayInLeg,
8
+ PayOutLeg,
9
+ PayInCapabilities,
10
+ PayOutCapabilities,
11
+ CollectResult,
12
+ DisburseResult,
13
+ } from "./leg.js";
14
+
15
+ // FakeLeg is a deterministic, in-memory pay-in and pay-out leg. It settles
16
+ // instantly with no network and implements both sides so a test can build a
17
+ // direct corridor from one leg or a bridged corridor from two. failPayout makes
18
+ // its disburse fail so a test can exercise the escrow-unwind path.
19
+ export class FakeLeg implements PayInLeg, PayOutLeg {
20
+ failPayout = false;
21
+
22
+ constructor(
23
+ readonly id: string,
24
+ private readonly rail: string,
25
+ private readonly currency: string,
26
+ private readonly ids: () => string,
27
+ ) {}
28
+
29
+ payInCapabilities(): PayInCapabilities {
30
+ return { rails: [this.rail], currencies: [this.currency], refunds: RefundKind.Full };
31
+ }
32
+
33
+ // collect takes the payer's funds. received is the net the bridge converts —
34
+ // the source the payer paid less the corridor fees.
35
+ async collect(_intentId: string, quote: Quote, _auth: Authorization, _deliverTo: string): Promise<CollectResult> {
36
+ return { providerRef: this.ids(), received: quote.srcAmount.sub(quote.fees) };
37
+ }
38
+
39
+ async refundIn(intentId: string, _kind: RefundKind, reason: string): Promise<Settlement> {
40
+ return this.terminal(intentId, reason);
41
+ }
42
+
43
+ payOutCapabilities(): PayOutCapabilities {
44
+ return { rails: [this.rail], currencies: [this.currency], reversible: false };
45
+ }
46
+
47
+ async disburse(_intentId: string, _quote: Quote, _recipientRef: string): Promise<DisburseResult> {
48
+ if (this.failPayout) {
49
+ throw new Error("fake: payout failed");
50
+ }
51
+ return { providerRef: this.ids() };
52
+ }
53
+
54
+ async reverseOut(intentId: string, reason: string): Promise<Settlement> {
55
+ return this.terminal(intentId, reason);
56
+ }
57
+
58
+ private terminal(intentId: string, reason: string): Settlement {
59
+ return {
60
+ intentId,
61
+ state: State.Refunded,
62
+ adapterId: this.id,
63
+ providerTxRef: this.ids(),
64
+ onchainTxHash: "",
65
+ receiptHash: new Uint8Array(0),
66
+ reason,
67
+ settledAt: 0,
68
+ };
69
+ }
70
+ }
71
+
72
+ // FakeRates is an in-memory rate source keyed by "FROM:TO", standing in for a
73
+ // price feed so a bridge can be tested without a live market.
74
+ export class FakeRates implements RateSource {
75
+ constructor(private readonly table: Record<string, string>) {}
76
+
77
+ async rate(from: string, to: string): Promise<string> {
78
+ const rate = this.table[`${from}:${to}`];
79
+ if (rate === undefined) {
80
+ throw new Error(`fake: no rate for ${from}:${to}`);
81
+ }
82
+ return rate;
83
+ }
84
+ }
85
+
86
+ // FakeVault is an in-memory escrow that records holds and releases, standing in
87
+ // for the custody a real bridge relies on.
88
+ export class FakeVault implements EscrowVault {
89
+ async hold(intentId: string, _amount: Money): Promise<string> {
90
+ return `escrow-${intentId}`;
91
+ }
92
+
93
+ async release(_intentId: string): Promise<void> {
94
+ // Nothing to release in memory.
95
+ }
96
+ }
package/src/index.ts ADDED
@@ -0,0 +1,106 @@
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 {
6
+ type Intent,
7
+ type Quote,
8
+ type Authorization,
9
+ type Settlement,
10
+ BRIDGE_PASSTHROUGH,
11
+ intentPreimage,
12
+ intentHash,
13
+ quotePreimage,
14
+ quoteHash,
15
+ isDirect,
16
+ signingPreimage,
17
+ authorizationHash,
18
+ receiptHash,
19
+ corridorReceipt,
20
+ } from "./message.js";
21
+ export {
22
+ type Signer,
23
+ type Verifier,
24
+ authorize,
25
+ verifyAuthorization,
26
+ Ed25519Signer,
27
+ Ed25519Verifier,
28
+ BadSignatureError,
29
+ } from "./signing.js";
30
+ export { RefundKind, type AdapterEvent, type WebhookParser } from "./adapter.js";
31
+ export {
32
+ railInList,
33
+ type PayInLeg,
34
+ type PayOutLeg,
35
+ type PayInCapabilities,
36
+ type PayOutCapabilities,
37
+ type CollectResult,
38
+ type DisburseResult,
39
+ } from "./leg.js";
40
+ export {
41
+ BRIDGE_USDC,
42
+ PassThroughBridge,
43
+ UsdcBridge,
44
+ applyRate,
45
+ applyInverseRate,
46
+ parseRate,
47
+ type Bridge,
48
+ type BridgeQuote,
49
+ type ConvertResult,
50
+ type RateSource,
51
+ type EscrowVault,
52
+ } from "./bridge.js";
53
+ export {
54
+ type KycProvider,
55
+ type KycStatus,
56
+ type KycLimits,
57
+ type RiskHook,
58
+ type RiskDecision,
59
+ permissiveKyc,
60
+ allowRisk,
61
+ withinLimits,
62
+ } from "./compliance.js";
63
+ export { PolicyKind, type Policy, route, isExpired, NoQuoteError } from "./router.js";
64
+ export {
65
+ MemoryLedger,
66
+ eventReceipt,
67
+ chainLeaf,
68
+ IdempotencyConflictError,
69
+ type Ledger,
70
+ type LedgerEvent,
71
+ type Transition,
72
+ } from "./ledger.js";
73
+ export {
74
+ Client,
75
+ type ClientConfig,
76
+ type IntentSpec,
77
+ type Funding,
78
+ type Clock,
79
+ type IdGen,
80
+ systemClock,
81
+ randomId,
82
+ recipientDestination,
83
+ } from "./client.js";
84
+ export { FakeLeg, FakeRates, FakeVault } from "./fake.js";
85
+ export {
86
+ WireKind,
87
+ type WireMessage,
88
+ encodeIntent,
89
+ encodeQuote,
90
+ encodeAuthorization,
91
+ encodeSettlement,
92
+ encodeMessage,
93
+ decodeMessage,
94
+ decodeIntent,
95
+ decodeQuote,
96
+ decodeAuthorization,
97
+ decodeSettlement,
98
+ } from "./wire.js";
99
+ export {
100
+ PAYLOAD_VERSION,
101
+ encodePayload,
102
+ decodePayload,
103
+ isPayload,
104
+ NotPayloadError,
105
+ UnsupportedPayloadVersionError,
106
+ } from "./payload.js";
package/src/ledger.ts ADDED
@@ -0,0 +1,145 @@
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
+ import type { Intent, Quote, Authorization, Settlement } from "./message.js";
6
+ import type { CollectResult } from "./leg.js";
7
+ import type { ConvertResult } from "./bridge.js";
8
+
9
+ // LedgerEvent is one immutable step in an intent's history. State is never
10
+ // mutated in place; the current state is the last event's state, and the whole
11
+ // history folds into a single Merkle head.
12
+ export interface LedgerEvent {
13
+ seq: number;
14
+ intentId: string;
15
+ state: State;
16
+ payloadHash: Uint8Array;
17
+ receipt: Uint8Array;
18
+ leaf: Uint8Array;
19
+ intent?: Intent;
20
+ quote?: Quote;
21
+ authorization?: Authorization;
22
+ settlement?: Settlement;
23
+ // collect and bridge persist the intermediate results a corridor needs to
24
+ // resume: what the pay-in leg collected, and what the bridge converted. They
25
+ // let settlement be driven forward by later provider events without holding
26
+ // anything open between them.
27
+ collect?: CollectResult;
28
+ bridge?: ConvertResult;
29
+ }
30
+
31
+ // Transition is a proposed append: the target state, the hash of the causing
32
+ // message, and the message itself for durable storage.
33
+ export interface Transition {
34
+ intentId: string;
35
+ to: State;
36
+ payloadHash: Uint8Array;
37
+ intent?: Intent;
38
+ quote?: Quote;
39
+ authorization?: Authorization;
40
+ settlement?: Settlement;
41
+ collect?: CollectResult;
42
+ bridge?: ConvertResult;
43
+ }
44
+
45
+ export class IdempotencyConflictError extends Error {
46
+ constructor() {
47
+ super("pact: idempotency conflict: step already recorded with a different payload");
48
+ this.name = "IdempotencyConflictError";
49
+ }
50
+ }
51
+
52
+ // Ledger is the append-only store of payment history. The kernel ships an
53
+ // in-memory reference; durable backends implement the same interface and must
54
+ // preserve the same event order, receipts, and Merkle head.
55
+ export interface Ledger {
56
+ apply(t: Transition): LedgerEvent;
57
+ state(intentId: string): State;
58
+ events(intentId: string): LedgerEvent[];
59
+ head(intentId: string): Uint8Array | undefined;
60
+ }
61
+
62
+ const zeroLeaf = new Uint8Array(32);
63
+
64
+ // MemoryLedger is the in-memory reference ledger.
65
+ export class MemoryLedger implements Ledger {
66
+ private readonly byIntent = new Map<string, LedgerEvent[]>();
67
+
68
+ apply(t: Transition): LedgerEvent {
69
+ const history = this.byIntent.get(t.intentId) ?? [];
70
+
71
+ // A repeat of the same step is a no-op returning the first result; the same
72
+ // step with a different payload is a conflict.
73
+ for (const e of history) {
74
+ if (e.state === t.to) {
75
+ if (bytesEqual(e.payloadHash, t.payloadHash)) return e;
76
+ throw new IdempotencyConflictError();
77
+ }
78
+ }
79
+
80
+ const current = history.length > 0 ? (history[history.length - 1] as LedgerEvent).state : State.Unspecified;
81
+ if (!canTransition(current, t.to)) {
82
+ throw new TransitionError(current, t.to);
83
+ }
84
+
85
+ const prevLeaf = history.length > 0 ? (history[history.length - 1] as LedgerEvent).leaf : zeroLeaf;
86
+ const seq = history.length + 1;
87
+ const receipt = eventReceipt(seq, t.intentId, t.to, t.payloadHash);
88
+ const leaf = chainLeaf(prevLeaf, receipt);
89
+
90
+ const event: LedgerEvent = {
91
+ seq,
92
+ intentId: t.intentId,
93
+ state: t.to,
94
+ payloadHash: t.payloadHash.slice(),
95
+ receipt,
96
+ leaf,
97
+ ...(t.intent ? { intent: t.intent } : {}),
98
+ ...(t.quote ? { quote: t.quote } : {}),
99
+ ...(t.authorization ? { authorization: t.authorization } : {}),
100
+ ...(t.settlement ? { settlement: t.settlement } : {}),
101
+ ...(t.collect ? { collect: t.collect } : {}),
102
+ ...(t.bridge ? { bridge: t.bridge } : {}),
103
+ };
104
+ this.byIntent.set(t.intentId, [...history, event]);
105
+ return event;
106
+ }
107
+
108
+ state(intentId: string): State {
109
+ const history = this.byIntent.get(intentId);
110
+ if (!history || history.length === 0) return State.Unspecified;
111
+ return (history[history.length - 1] as LedgerEvent).state;
112
+ }
113
+
114
+ events(intentId: string): LedgerEvent[] {
115
+ return [...(this.byIntent.get(intentId) ?? [])];
116
+ }
117
+
118
+ head(intentId: string): Uint8Array | undefined {
119
+ const history = this.byIntent.get(intentId);
120
+ if (!history || history.length === 0) return undefined;
121
+ return (history[history.length - 1] as LedgerEvent).leaf;
122
+ }
123
+ }
124
+
125
+ // eventReceipt commits to an event's identity independent of the Merkle chaining,
126
+ // so a single event can be shown and checked on its own.
127
+ export function eventReceipt(seq: number, intentId: string, state: State, payloadHash: Uint8Array): Uint8Array {
128
+ return hashPreimage(
129
+ new CanonicalWriter()
130
+ .str(domain("event"))
131
+ .u64(seq)
132
+ .str(intentId)
133
+ .str(stateName[state])
134
+ .bytes(payloadHash)
135
+ .preimage(),
136
+ );
137
+ }
138
+
139
+ // chainLeaf folds one event's receipt into the running Merkle head.
140
+ export function chainLeaf(prevLeaf: Uint8Array, receipt: Uint8Array): Uint8Array {
141
+ const joined = new Uint8Array(prevLeaf.length + receipt.length);
142
+ joined.set(prevLeaf, 0);
143
+ joined.set(receipt, prevLeaf.length);
144
+ return hashPreimage(joined);
145
+ }
package/src/leg.ts ADDED
@@ -0,0 +1,65 @@
1
+ import type { Money } from "./money.js";
2
+ import type { Quote, Authorization, Settlement } from "./message.js";
3
+ import type { RefundKind } from "./adapter.js";
4
+
5
+ // A corridor is built from two legs. A provider adapter implements the legs it
6
+ // can serve: a pay-in leg collects from the payer, a pay-out leg disburses to
7
+ // the recipient. Many providers serve only one; some serve both. The kernel
8
+ // composes them and never holds funds itself.
9
+
10
+ // PayInCapabilities describes what a pay-in leg can collect. Methods lists the
11
+ // funding surfaces the leg exposes without a separate adapter — a card leg via a
12
+ // processor exposes card plus the wallets that ride on it. Methods are
13
+ // informational; routing is on the rail.
14
+ export interface PayInCapabilities {
15
+ rails: string[];
16
+ currencies: string[];
17
+ methods?: string[];
18
+ refunds: RefundKind;
19
+ }
20
+
21
+ // CollectResult is what a pay-in leg returns once it has taken the payer's
22
+ // funds. received is the amount actually collected, which the bridge converts.
23
+ export interface CollectResult {
24
+ providerRef: string;
25
+ received: Money;
26
+ }
27
+
28
+ // PayInLeg collects the payer's funds in the source currency. deliverTo is where
29
+ // the collected funds should land: the recipient's handle for a direct corridor,
30
+ // or the bridge escrow for a bridged one. The kernel computes it, so the leg does
31
+ // not need to know which corridor shape it is serving.
32
+ export interface PayInLeg {
33
+ id: string;
34
+ payInCapabilities(): PayInCapabilities;
35
+ collect(intentId: string, quote: Quote, auth: Authorization, deliverTo: string): Promise<CollectResult>;
36
+ refundIn(intentId: string, kind: RefundKind, reason: string): Promise<Settlement>;
37
+ }
38
+
39
+ // PayOutCapabilities describes what a pay-out leg can deliver. reversible reports
40
+ // whether a completed payout can be pulled back; an irreversible rail answers a
41
+ // refund with a counter-transfer, never a reversal.
42
+ export interface PayOutCapabilities {
43
+ rails: string[];
44
+ currencies: string[];
45
+ reversible: boolean;
46
+ }
47
+
48
+ // DisburseResult is what a pay-out leg returns once it has sent funds to the
49
+ // recipient.
50
+ export interface DisburseResult {
51
+ providerRef: string;
52
+ }
53
+
54
+ // PayOutLeg delivers funds to the recipient in the destination currency.
55
+ export interface PayOutLeg {
56
+ id: string;
57
+ payOutCapabilities(): PayOutCapabilities;
58
+ disburse(intentId: string, quote: Quote, recipientRef: string): Promise<DisburseResult>;
59
+ reverseOut(intentId: string, reason: string): Promise<Settlement>;
60
+ }
61
+
62
+ // railInList reports whether a set of rails contains the requested rail.
63
+ export function railInList(rails: string[], rail: string): boolean {
64
+ return rails.includes(rail);
65
+ }