@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.
- package/dist/src/adapter.d.ts +18 -0
- package/dist/src/adapter.js +11 -0
- package/dist/src/adapters/erc20.d.ts +43 -0
- package/dist/src/adapters/erc20.js +126 -0
- package/dist/src/adapters/mpesa.d.ts +61 -0
- package/dist/src/adapters/mpesa.js +272 -0
- package/dist/src/adapters/paypal.d.ts +68 -0
- package/dist/src/adapters/paypal.js +279 -0
- package/dist/src/adapters/stripe.d.ts +61 -0
- package/dist/src/adapters/stripe.js +336 -0
- package/dist/src/bridge.d.ts +40 -0
- package/dist/src/bridge.js +99 -0
- package/dist/src/canonical.d.ts +14 -0
- package/dist/src/canonical.js +82 -0
- package/dist/src/client.d.ts +84 -0
- package/dist/src/client.js +510 -0
- package/dist/src/compliance.d.ts +23 -0
- package/dist/src/compliance.js +26 -0
- package/dist/src/crypto.d.ts +12 -0
- package/dist/src/crypto.js +50 -0
- package/dist/src/fake.d.ts +29 -0
- package/dist/src/fake.js +79 -0
- package/dist/src/index.d.ts +16 -0
- package/dist/src/index.js +16 -0
- package/dist/src/ledger.d.ts +47 -0
- package/dist/src/ledger.js +84 -0
- package/dist/src/leg.d.ts +34 -0
- package/dist/src/leg.js +4 -0
- package/dist/src/message.d.ts +55 -0
- package/dist/src/message.js +88 -0
- package/dist/src/money.d.ts +16 -0
- package/dist/src/money.js +81 -0
- package/dist/src/payload.d.ts +11 -0
- package/dist/src/payload.js +52 -0
- package/dist/src/policy.d.ts +26 -0
- package/dist/src/policy.js +83 -0
- package/dist/src/protocol.d.ts +4 -0
- package/dist/src/protocol.js +15 -0
- package/dist/src/router.d.ts +16 -0
- package/dist/src/router.js +87 -0
- package/dist/src/signing.d.ts +27 -0
- package/dist/src/signing.js +72 -0
- package/dist/src/state.d.ts +23 -0
- package/dist/src/state.js +76 -0
- package/dist/src/wire.d.ts +30 -0
- package/dist/src/wire.js +221 -0
- package/dist/test/erc20.test.d.ts +1 -0
- package/dist/test/erc20.test.js +131 -0
- package/dist/test/lifecycle.test.d.ts +1 -0
- package/dist/test/lifecycle.test.js +212 -0
- package/dist/test/mpesa.test.d.ts +1 -0
- package/dist/test/mpesa.test.js +180 -0
- package/dist/test/payload.test.d.ts +1 -0
- package/dist/test/payload.test.js +32 -0
- package/dist/test/paypal.test.d.ts +1 -0
- package/dist/test/paypal.test.js +140 -0
- package/dist/test/policy.test.d.ts +1 -0
- package/dist/test/policy.test.js +131 -0
- package/dist/test/stripe.test.d.ts +1 -0
- package/dist/test/stripe.test.js +176 -0
- package/dist/test/vectors.test.d.ts +1 -0
- package/dist/test/vectors.test.js +91 -0
- package/dist/test/wire.test.d.ts +1 -0
- package/dist/test/wire.test.js +104 -0
- package/package.json +50 -0
- package/src/adapter.ts +32 -0
- package/src/adapters/erc20.ts +181 -0
- package/src/adapters/mpesa.ts +408 -0
- package/src/adapters/paypal.ts +409 -0
- package/src/adapters/stripe.ts +478 -0
- package/src/bridge.ts +148 -0
- package/src/canonical.ts +94 -0
- package/src/client.ts +605 -0
- package/src/compliance.ts +65 -0
- package/src/crypto.ts +68 -0
- package/src/fake.ts +96 -0
- package/src/index.ts +106 -0
- package/src/ledger.ts +145 -0
- package/src/leg.ts +65 -0
- package/src/message.ts +178 -0
- package/src/money.ts +87 -0
- package/src/payload.ts +58 -0
- package/src/policy.ts +110 -0
- package/src/protocol.ts +19 -0
- package/src/router.ts +97 -0
- package/src/signing.ts +92 -0
- package/src/state.ts +76 -0
- package/src/wire.ts +248 -0
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { Money } from "./money.js";
|
|
2
|
+
export declare const BRIDGE_USDC = "usdc";
|
|
3
|
+
export interface BridgeQuote {
|
|
4
|
+
srcAmount: Money;
|
|
5
|
+
fxRate: string;
|
|
6
|
+
fee: Money;
|
|
7
|
+
}
|
|
8
|
+
export interface ConvertResult {
|
|
9
|
+
delivered: Money;
|
|
10
|
+
receiptRef: string;
|
|
11
|
+
}
|
|
12
|
+
export interface Bridge {
|
|
13
|
+
id: string;
|
|
14
|
+
quote(dstTarget: Money, srcCurrency: string, srcExponent: number): Promise<BridgeQuote>;
|
|
15
|
+
convert(intentId: string, held: Money, dstCurrency: string, dstExponent: number): Promise<ConvertResult>;
|
|
16
|
+
}
|
|
17
|
+
export interface RateSource {
|
|
18
|
+
rate(from: string, to: string): Promise<string>;
|
|
19
|
+
}
|
|
20
|
+
export interface EscrowVault {
|
|
21
|
+
hold(intentId: string, amount: Money): Promise<string>;
|
|
22
|
+
release(intentId: string): Promise<void>;
|
|
23
|
+
}
|
|
24
|
+
export declare class PassThroughBridge implements Bridge {
|
|
25
|
+
readonly id = "passthrough";
|
|
26
|
+
quote(dstTarget: Money, srcCurrency: string, srcExponent: number): Promise<BridgeQuote>;
|
|
27
|
+
convert(_intentId: string, held: Money, dstCurrency: string, dstExponent: number): Promise<ConvertResult>;
|
|
28
|
+
}
|
|
29
|
+
export declare class UsdcBridge implements Bridge {
|
|
30
|
+
private readonly rates;
|
|
31
|
+
private readonly vault;
|
|
32
|
+
private readonly feeBps;
|
|
33
|
+
readonly id = "usdc";
|
|
34
|
+
constructor(rates: RateSource, vault: EscrowVault, feeBps: bigint);
|
|
35
|
+
quote(dstTarget: Money, srcCurrency: string, srcExponent: number): Promise<BridgeQuote>;
|
|
36
|
+
convert(intentId: string, held: Money, dstCurrency: string, dstExponent: number): Promise<ConvertResult>;
|
|
37
|
+
}
|
|
38
|
+
export declare function applyRate(src: Money, rate: string, dstCurrency: string, dstExponent: number): Money;
|
|
39
|
+
export declare function applyInverseRate(dst: Money, rate: string, srcCurrency: string, srcExponent: number): Money;
|
|
40
|
+
export declare function parseRate(rate: string): [bigint, bigint];
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { Money } from "./money.js";
|
|
2
|
+
import { BRIDGE_PASSTHROUGH } from "./message.js";
|
|
3
|
+
// The USDC bridge identifier. A stable wire value carried in a corridor quote.
|
|
4
|
+
export const BRIDGE_USDC = "usdc";
|
|
5
|
+
// PassThroughBridge is the direct-corridor bridge: it moves value unchanged when
|
|
6
|
+
// the payer and recipient are already in the same currency.
|
|
7
|
+
export class PassThroughBridge {
|
|
8
|
+
id = BRIDGE_PASSTHROUGH;
|
|
9
|
+
async quote(dstTarget, srcCurrency, srcExponent) {
|
|
10
|
+
if (dstTarget.currency !== srcCurrency || dstTarget.exponent !== srcExponent) {
|
|
11
|
+
throw new Error(`pact: pass-through bridge cannot convert ${srcCurrency} to ${dstTarget.currency}`);
|
|
12
|
+
}
|
|
13
|
+
return { srcAmount: dstTarget, fxRate: "1", fee: Money.create(srcCurrency, srcExponent, 0n) };
|
|
14
|
+
}
|
|
15
|
+
async convert(_intentId, held, dstCurrency, dstExponent) {
|
|
16
|
+
if (held.currency !== dstCurrency || held.exponent !== dstExponent) {
|
|
17
|
+
throw new Error("pact: pass-through bridge cannot convert across currencies");
|
|
18
|
+
}
|
|
19
|
+
return { delivered: held, receiptRef: "" };
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
// UsdcBridge converts the payer's currency to the recipient's through a USDC
|
|
23
|
+
// escrow. It applies a quoted rate from the injected RateSource, charges a fee in
|
|
24
|
+
// basis points, and parks the value in the injected EscrowVault between legs.
|
|
25
|
+
export class UsdcBridge {
|
|
26
|
+
rates;
|
|
27
|
+
vault;
|
|
28
|
+
feeBps;
|
|
29
|
+
id = BRIDGE_USDC;
|
|
30
|
+
constructor(rates, vault, feeBps) {
|
|
31
|
+
this.rates = rates;
|
|
32
|
+
this.vault = vault;
|
|
33
|
+
this.feeBps = feeBps;
|
|
34
|
+
}
|
|
35
|
+
async quote(dstTarget, srcCurrency, srcExponent) {
|
|
36
|
+
const rate = await this.rates.rate(srcCurrency, dstTarget.currency);
|
|
37
|
+
// Plan backwards: the source needed to deliver the target is the target at
|
|
38
|
+
// the inverse rate, rounded up so a rounding remainder never short-changes
|
|
39
|
+
// the recipient.
|
|
40
|
+
const src = applyInverseRate(dstTarget, rate, srcCurrency, srcExponent);
|
|
41
|
+
const feeMinor = (src.value() * this.feeBps) / 10000n;
|
|
42
|
+
return { srcAmount: src, fxRate: rate, fee: Money.create(srcCurrency, srcExponent, feeMinor) };
|
|
43
|
+
}
|
|
44
|
+
async convert(intentId, held, dstCurrency, dstExponent) {
|
|
45
|
+
const ref = await this.vault.hold(intentId, held);
|
|
46
|
+
const rate = await this.rates.rate(held.currency, dstCurrency);
|
|
47
|
+
const delivered = applyRate(held, rate, dstCurrency, dstExponent);
|
|
48
|
+
await this.vault.release(intentId);
|
|
49
|
+
return { delivered, receiptRef: ref };
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
// applyRate converts a source amount to a destination currency at a decimal rate.
|
|
53
|
+
// The rate is Y per X given as an integer or a fixed-point decimal; the result is
|
|
54
|
+
// floored to the destination currency's minor unit. Rounding down is deliberate:
|
|
55
|
+
// the recipient is never credited more than the rate permits.
|
|
56
|
+
export function applyRate(src, rate, dstCurrency, dstExponent) {
|
|
57
|
+
const [num, den] = parseRate(rate);
|
|
58
|
+
// dstMinor = srcMinor * rate * 10^(dstExponent - srcExponent)
|
|
59
|
+
let value = src.value() * num;
|
|
60
|
+
let denom = den;
|
|
61
|
+
if (dstExponent >= src.exponent) {
|
|
62
|
+
value = value * pow10(dstExponent - src.exponent);
|
|
63
|
+
}
|
|
64
|
+
else {
|
|
65
|
+
denom = denom * pow10(src.exponent - dstExponent);
|
|
66
|
+
}
|
|
67
|
+
return Money.create(dstCurrency, dstExponent, value / denom);
|
|
68
|
+
}
|
|
69
|
+
// applyInverseRate is the planning direction: given a target destination amount
|
|
70
|
+
// and a rate of Y per X, it returns the source amount needed. It rounds up so a
|
|
71
|
+
// rounding remainder is covered by the payer, never taken from the recipient.
|
|
72
|
+
export function applyInverseRate(dst, rate, srcCurrency, srcExponent) {
|
|
73
|
+
const [num, den] = parseRate(rate);
|
|
74
|
+
// srcMinor = ceil( dstMinor * den * 10^srcExp / (num * 10^dstExp) )
|
|
75
|
+
const numerator = dst.value() * den * pow10(srcExponent);
|
|
76
|
+
const denominator = num * pow10(dst.exponent);
|
|
77
|
+
let value = numerator / denominator;
|
|
78
|
+
if (numerator % denominator !== 0n) {
|
|
79
|
+
value = value + 1n;
|
|
80
|
+
}
|
|
81
|
+
return Money.create(srcCurrency, srcExponent, value);
|
|
82
|
+
}
|
|
83
|
+
// parseRate reads a decimal rate like "129.45" into a numerator and denominator,
|
|
84
|
+
// so the conversion stays exact integer arithmetic with no floating point.
|
|
85
|
+
export function parseRate(rate) {
|
|
86
|
+
const dot = rate.indexOf(".");
|
|
87
|
+
const whole = dot < 0 ? rate : rate.slice(0, dot);
|
|
88
|
+
const frac = dot < 0 ? "" : rate.slice(dot + 1);
|
|
89
|
+
const digits = whole + frac;
|
|
90
|
+
if (!/^\d+$/.test(digits)) {
|
|
91
|
+
throw new Error(`pact: ${JSON.stringify(rate)} is not a decimal rate`);
|
|
92
|
+
}
|
|
93
|
+
const num = BigInt(digits);
|
|
94
|
+
const den = frac.length > 0 ? pow10(frac.length) : 1n;
|
|
95
|
+
return [num, den];
|
|
96
|
+
}
|
|
97
|
+
function pow10(n) {
|
|
98
|
+
return 10n ** BigInt(n);
|
|
99
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { Money } from "./money.js";
|
|
2
|
+
export declare class CanonicalWriter {
|
|
3
|
+
private readonly parts;
|
|
4
|
+
private length;
|
|
5
|
+
private push;
|
|
6
|
+
bytes(b: Uint8Array): this;
|
|
7
|
+
str(s: string): this;
|
|
8
|
+
u64(n: number | bigint): this;
|
|
9
|
+
money(m: Money): this;
|
|
10
|
+
list(xs: readonly string[]): this;
|
|
11
|
+
stringMap(kv: Readonly<Record<string, string>>): this;
|
|
12
|
+
preimage(): Uint8Array;
|
|
13
|
+
}
|
|
14
|
+
export declare function hashPreimage(preimage: Uint8Array): Uint8Array;
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { sha256 } from "./crypto.js";
|
|
2
|
+
const encoder = new TextEncoder();
|
|
3
|
+
// CanonicalWriter builds a deterministic, length-delimited byte string. It is the
|
|
4
|
+
// one encoder every signed and hashed preimage is built with, mirrored
|
|
5
|
+
// field-for-field across the SDKs. Protobuf is deliberately not used here: its
|
|
6
|
+
// serialization is not canonical across runtimes, so a signature over protobuf
|
|
7
|
+
// bytes would not verify between implementations.
|
|
8
|
+
export class CanonicalWriter {
|
|
9
|
+
parts = [];
|
|
10
|
+
length = 0;
|
|
11
|
+
push(part) {
|
|
12
|
+
this.parts.push(part);
|
|
13
|
+
this.length += part.length;
|
|
14
|
+
}
|
|
15
|
+
// bytes writes a 4-byte big-endian length followed by the raw bytes.
|
|
16
|
+
bytes(b) {
|
|
17
|
+
const header = new Uint8Array(4);
|
|
18
|
+
new DataView(header.buffer).setUint32(0, b.length, false);
|
|
19
|
+
this.push(header);
|
|
20
|
+
this.push(b);
|
|
21
|
+
return this;
|
|
22
|
+
}
|
|
23
|
+
str(s) {
|
|
24
|
+
return this.bytes(encoder.encode(s));
|
|
25
|
+
}
|
|
26
|
+
u64(n) {
|
|
27
|
+
const b = new Uint8Array(8);
|
|
28
|
+
new DataView(b.buffer).setBigUint64(0, BigInt(n), false);
|
|
29
|
+
this.push(b);
|
|
30
|
+
return this;
|
|
31
|
+
}
|
|
32
|
+
money(m) {
|
|
33
|
+
this.str(m.minor());
|
|
34
|
+
this.str(m.currency);
|
|
35
|
+
this.u64(m.exponent);
|
|
36
|
+
return this;
|
|
37
|
+
}
|
|
38
|
+
list(xs) {
|
|
39
|
+
this.u64(xs.length);
|
|
40
|
+
for (const x of xs) {
|
|
41
|
+
this.str(x);
|
|
42
|
+
}
|
|
43
|
+
return this;
|
|
44
|
+
}
|
|
45
|
+
// stringMap writes a length then each entry, keys sorted ascending by UTF-8
|
|
46
|
+
// byte value so the encoding never depends on insertion order. The sort is on
|
|
47
|
+
// encoded bytes, not on UTF-16 code units, to match the other SDKs exactly.
|
|
48
|
+
stringMap(kv) {
|
|
49
|
+
const keys = Object.keys(kv).sort(compareUtf8);
|
|
50
|
+
this.u64(keys.length);
|
|
51
|
+
for (const k of keys) {
|
|
52
|
+
this.str(k);
|
|
53
|
+
this.str(kv[k]);
|
|
54
|
+
}
|
|
55
|
+
return this;
|
|
56
|
+
}
|
|
57
|
+
preimage() {
|
|
58
|
+
const out = new Uint8Array(this.length);
|
|
59
|
+
let offset = 0;
|
|
60
|
+
for (const part of this.parts) {
|
|
61
|
+
out.set(part, offset);
|
|
62
|
+
offset += part.length;
|
|
63
|
+
}
|
|
64
|
+
return out;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
function compareUtf8(a, b) {
|
|
68
|
+
const ab = encoder.encode(a);
|
|
69
|
+
const bb = encoder.encode(b);
|
|
70
|
+
const n = Math.min(ab.length, bb.length);
|
|
71
|
+
for (let i = 0; i < n; i++) {
|
|
72
|
+
const diff = ab[i] - bb[i];
|
|
73
|
+
if (diff !== 0)
|
|
74
|
+
return diff;
|
|
75
|
+
}
|
|
76
|
+
return ab.length - bb.length;
|
|
77
|
+
}
|
|
78
|
+
// hashPreimage is the SHA-256 of a preimage. Every message hash and receipt is
|
|
79
|
+
// derived through it.
|
|
80
|
+
export function hashPreimage(preimage) {
|
|
81
|
+
return sha256(preimage);
|
|
82
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { State } from "./state.js";
|
|
2
|
+
import { Money } from "./money.js";
|
|
3
|
+
import { type Intent, type Quote, type Authorization, type Settlement } from "./message.js";
|
|
4
|
+
import { type Signer, type Verifier } from "./signing.js";
|
|
5
|
+
import { type Bridge } from "./bridge.js";
|
|
6
|
+
import { type PayInLeg, type PayOutLeg } from "./leg.js";
|
|
7
|
+
import type { AdapterEvent } from "./adapter.js";
|
|
8
|
+
import { type Policy } from "./router.js";
|
|
9
|
+
import { type KycProvider, type RiskHook } from "./compliance.js";
|
|
10
|
+
import { type Ledger, type LedgerEvent } from "./ledger.js";
|
|
11
|
+
export type Clock = () => number;
|
|
12
|
+
export type IdGen = () => string;
|
|
13
|
+
export declare function systemClock(): number;
|
|
14
|
+
export declare function randomId(): string;
|
|
15
|
+
export interface ClientConfig {
|
|
16
|
+
payIn?: PayInLeg[];
|
|
17
|
+
payOut?: PayOutLeg[];
|
|
18
|
+
bridges?: Bridge[];
|
|
19
|
+
ledger?: Ledger;
|
|
20
|
+
policy?: Policy;
|
|
21
|
+
verifier: Verifier;
|
|
22
|
+
kyc?: KycProvider;
|
|
23
|
+
risk?: RiskHook;
|
|
24
|
+
clock?: Clock;
|
|
25
|
+
idGen?: IdGen;
|
|
26
|
+
skew?: number;
|
|
27
|
+
escrowRef?: string;
|
|
28
|
+
}
|
|
29
|
+
export interface IntentSpec {
|
|
30
|
+
senderRef: string;
|
|
31
|
+
recipientRef: string;
|
|
32
|
+
amount: Money;
|
|
33
|
+
memo?: string;
|
|
34
|
+
expiresAt: number;
|
|
35
|
+
allowedRails?: string[];
|
|
36
|
+
metadata?: Record<string, string>;
|
|
37
|
+
}
|
|
38
|
+
export interface Funding {
|
|
39
|
+
payInAdapterId: string;
|
|
40
|
+
currency: string;
|
|
41
|
+
exponent: number;
|
|
42
|
+
}
|
|
43
|
+
export declare class Client {
|
|
44
|
+
private readonly payIn;
|
|
45
|
+
private readonly payOut;
|
|
46
|
+
private readonly bridges;
|
|
47
|
+
private readonly ledger;
|
|
48
|
+
private readonly policy;
|
|
49
|
+
private readonly verifier;
|
|
50
|
+
private readonly kyc;
|
|
51
|
+
private readonly risk;
|
|
52
|
+
private readonly clock;
|
|
53
|
+
private readonly idGen;
|
|
54
|
+
private readonly skew;
|
|
55
|
+
private readonly escrowRef;
|
|
56
|
+
private readonly subscribers;
|
|
57
|
+
constructor(cfg: ClientConfig);
|
|
58
|
+
createIntent(spec: IntentSpec): Intent;
|
|
59
|
+
quoteOptions(intent: Intent, funding: Funding[]): Promise<Quote[]>;
|
|
60
|
+
private composeQuote;
|
|
61
|
+
private pickPayOut;
|
|
62
|
+
private payOutRail;
|
|
63
|
+
private pickBridge;
|
|
64
|
+
select(quotes: Quote[], identity: string): Promise<Quote>;
|
|
65
|
+
authorize(intent: Intent, quote: Quote, signer: Signer): Promise<Authorization>;
|
|
66
|
+
initiate(intent: Intent, quote: Quote, auth: Authorization): Promise<State>;
|
|
67
|
+
advance(intentId: string, adapterId: string, event: AdapterEvent): Promise<{
|
|
68
|
+
settlement: Settlement;
|
|
69
|
+
state: State;
|
|
70
|
+
}>;
|
|
71
|
+
private holdAndDisburse;
|
|
72
|
+
private settleBridged;
|
|
73
|
+
private unwind;
|
|
74
|
+
private recordFailure;
|
|
75
|
+
private finishAdvance;
|
|
76
|
+
private finish;
|
|
77
|
+
expire(intentId: string): void;
|
|
78
|
+
subscribe(handler: (event: LedgerEvent) => void): void;
|
|
79
|
+
history(intentId: string): LedgerEvent[];
|
|
80
|
+
state(intentId: string): State;
|
|
81
|
+
head(intentId: string): Uint8Array | undefined;
|
|
82
|
+
private notify;
|
|
83
|
+
}
|
|
84
|
+
export declare function recipientDestination(intent: Intent): string;
|