@chainpayhq/sdk 0.0.0-stage → 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 +21 -0
- package/README.md +42 -2
- package/dist/accounts.d.ts +23 -0
- package/dist/accounts.js +148 -0
- package/dist/cards/accounts.d.ts +156 -0
- package/dist/cards/accounts.js +346 -0
- package/dist/cards/api.d.ts +379 -0
- package/dist/cards/api.js +241 -0
- package/dist/cards/commitment.d.ts +86 -0
- package/dist/cards/commitment.js +288 -0
- package/dist/cards/constants.d.ts +160 -0
- package/dist/cards/constants.js +163 -0
- package/dist/cards/draft.d.ts +57 -0
- package/dist/cards/draft.js +131 -0
- package/dist/cards/evidence.d.ts +69 -0
- package/dist/cards/evidence.js +41 -0
- package/dist/cards/hash.d.ts +9 -0
- package/dist/cards/hash.js +47 -0
- package/dist/cards/index.d.ts +14 -0
- package/dist/cards/index.js +14 -0
- package/dist/cards/instructions.d.ts +302 -0
- package/dist/cards/instructions.js +474 -0
- package/dist/cards/layout.d.ts +33 -0
- package/dist/cards/layout.js +137 -0
- package/dist/cards/math.d.ts +67 -0
- package/dist/cards/math.js +138 -0
- package/dist/cards/merchants.d.ts +23 -0
- package/dist/cards/merchants.js +38 -0
- package/dist/cards/pda.d.ts +42 -0
- package/dist/cards/pda.js +93 -0
- package/dist/cards/private-repayment.d.ts +198 -0
- package/dist/cards/private-repayment.js +485 -0
- package/dist/cards/redact.d.ts +18 -0
- package/dist/cards/redact.js +103 -0
- package/dist/cards/tee.d.ts +253 -0
- package/dist/cards/tee.js +605 -0
- package/dist/cli.d.ts +31 -0
- package/dist/cli.js +351 -0
- package/dist/client.d.ts +80 -0
- package/dist/client.js +493 -0
- package/dist/constants.d.ts +44 -0
- package/dist/constants.js +43 -0
- package/dist/crossmint-adapt.d.ts +43 -0
- package/dist/crossmint-adapt.js +68 -0
- package/dist/crossmint-order.d.ts +220 -0
- package/dist/crossmint-order.js +638 -0
- package/dist/delivery.d.ts +66 -0
- package/dist/delivery.js +232 -0
- package/dist/encoding.d.ts +51 -0
- package/dist/encoding.js +128 -0
- package/dist/index.d.ts +27 -0
- package/dist/index.js +26 -0
- package/dist/known-assets.d.ts +30 -0
- package/dist/known-assets.js +49 -0
- package/dist/mandate-request.d.ts +123 -0
- package/dist/mandate-request.js +401 -0
- package/dist/mandate.d.ts +44 -0
- package/dist/mandate.js +157 -0
- package/dist/ops-snapshot.d.ts +157 -0
- package/dist/ops-snapshot.js +356 -0
- package/dist/payment-request.d.ts +37 -0
- package/dist/payment-request.js +218 -0
- package/dist/payment.d.ts +40 -0
- package/dist/payment.js +214 -0
- package/dist/pda.d.ts +13 -0
- package/dist/pda.js +36 -0
- package/dist/receipt-export.d.ts +55 -0
- package/dist/receipt-export.js +149 -0
- package/dist/receipt.d.ts +108 -0
- package/dist/receipt.js +213 -0
- package/dist/solana.d.ts +5 -0
- package/dist/solana.js +20 -0
- package/dist/token-capabilities.d.ts +9 -0
- package/dist/token-capabilities.js +139 -0
- package/dist/token.d.ts +18 -0
- package/dist/token.js +55 -0
- package/dist/transaction-reader.d.ts +10 -0
- package/dist/transaction-reader.js +18 -0
- package/dist/transaction-v1.d.ts +18 -0
- package/dist/transaction-v1.js +73 -0
- package/dist/types.d.ts +267 -0
- package/dist/types.js +1 -0
- package/dist/x402-adapt.d.ts +20 -0
- package/dist/x402-adapt.js +62 -0
- package/dist/x402-challenge.d.ts +116 -0
- package/dist/x402-challenge.js +385 -0
- package/dist/x402.d.ts +18 -0
- package/dist/x402.js +35 -0
- package/package.json +54 -4
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
export type CentsString = string;
|
|
2
|
+
/** Parse a wire amount (`"50250"`). Rejects signs, decimals, leading zeros and more than 16 digits. */
|
|
3
|
+
export declare function parseCents(value: unknown, name?: string): bigint;
|
|
4
|
+
/** Signed cents for statement totals and refund fees, where negative means credit (`"-1005"`). */
|
|
5
|
+
export declare function parseSignedCents(value: unknown, name?: string): bigint;
|
|
6
|
+
export declare function centsToString(value: bigint): CentsString;
|
|
7
|
+
/**
|
|
8
|
+
* Platform fee on one line: floor(x · bps / 10 000), integer only. The one
|
|
9
|
+
* rule for debits and credits alike, the same in card_policy and Axum
|
|
10
|
+
* statements (contracts.md §1.5, review fixes 2026-10-04). Rounding down means
|
|
11
|
+
* split refunds never credit more than one refund of the same total, and a
|
|
12
|
+
* period's fees never pass fee(budget). Vectors: shared/cards/fee-vectors.json.
|
|
13
|
+
*/
|
|
14
|
+
export declare function feeCents(amountCents: bigint, feeBps: number): bigint;
|
|
15
|
+
/**
|
|
16
|
+
* A refund on a hold returns at most what it captured and hasn't refunded:
|
|
17
|
+
* `refunded + amount <= captured`. Returns the amount when it fits, `null`
|
|
18
|
+
* when card_policy (or Axum, for a closed hold) sends it to review instead.
|
|
19
|
+
*/
|
|
20
|
+
export declare function refundableCents(capturedCents: bigint, refundedCents: bigint, amountCents: bigint): bigint | null;
|
|
21
|
+
/**
|
|
22
|
+
* The most the owner can owe for one period: budget + fee(budget). $500 @ 50 bps → $502.50.
|
|
23
|
+
* A hard limit: card_policy bills no debit (purchase, forced post, late capture)
|
|
24
|
+
* past the period budget, and floor fees sum to at most fee(budget). Issuer
|
|
25
|
+
* charges past it are booked for review, never onto the owner's statement.
|
|
26
|
+
*/
|
|
27
|
+
export declare function maxObligationCents(budgetCents: bigint, feeBps: number): bigint;
|
|
28
|
+
/** available = budget − (captured + reserved), floored at zero. */
|
|
29
|
+
export declare function availableCents(budgetCents: bigint, capturedCents: bigint, reservedCents: bigint): bigint;
|
|
30
|
+
/** Outstanding after a capture: += amount + fee(amount). */
|
|
31
|
+
export declare function outstandingAfterCapture(outstandingCents: bigint, capturedCents: bigint, feeBps: number): bigint;
|
|
32
|
+
/** Outstanding after a refund: −= min(refund + fee(refund), outstanding). */
|
|
33
|
+
export declare function outstandingAfterRefund(outstandingCents: bigint, refundCents: bigint, feeBps: number): bigint;
|
|
34
|
+
export type StatementLineInput = {
|
|
35
|
+
kind: "purchase" | "refund" | "adjustment_debit" | "adjustment_credit";
|
|
36
|
+
amountCents: bigint;
|
|
37
|
+
};
|
|
38
|
+
export type StatementTotals = {
|
|
39
|
+
purchasesCents: bigint;
|
|
40
|
+
refundsCents: bigint;
|
|
41
|
+
/** Signed: refund fees are negative, using the same floor formula. */
|
|
42
|
+
feeCents: bigint;
|
|
43
|
+
/** Signed: negative means the period ended in credit. */
|
|
44
|
+
totalCents: bigint;
|
|
45
|
+
};
|
|
46
|
+
/** Statement totals (contracts.md §7.1): total = purchases − refunds + Σ per-line fees. */
|
|
47
|
+
export declare function statementTotals(lines: readonly StatementLineInput[], feeBps: number): StatementTotals;
|
|
48
|
+
/** Exact dollars for people: 50250n → "$502.50". No rounding is ever applied. */
|
|
49
|
+
export declare function formatUsdCents(value: bigint | CentsString): string;
|
|
50
|
+
/** 50 → "0.5%", 125 → "1.25%", 0 → "0%". */
|
|
51
|
+
export declare function formatFeeBps(feeBps: number): string;
|
|
52
|
+
/** USD stablecoin base units for a statement repayment: cents × 10^(decimals−2). USDC (6) → ×10 000. */
|
|
53
|
+
export declare function centsToTokenBaseUnits(cents: bigint, decimals: number): bigint;
|
|
54
|
+
/** Owner-facing review numbers before signing `set_policy` (PLAN F). */
|
|
55
|
+
export declare function policyReviewSummary(budgetCents: bigint, maxPurchaseCents: bigint, feeBps: number): {
|
|
56
|
+
budgetCents: string;
|
|
57
|
+
maxPurchaseCents: string;
|
|
58
|
+
feeBps: number;
|
|
59
|
+
feeCentsAtFullBudget: string;
|
|
60
|
+
maxObligationCents: string;
|
|
61
|
+
display: {
|
|
62
|
+
budget: string;
|
|
63
|
+
maxPurchase: string;
|
|
64
|
+
fee: string;
|
|
65
|
+
maxObligation: string;
|
|
66
|
+
};
|
|
67
|
+
};
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Card money math. Every value is an integer number of US cents (contracts.md
|
|
3
|
+
* preamble): bigint in code, decimal-integer strings on the wire, never a
|
|
4
|
+
* float and never a JS number for anything persisted.
|
|
5
|
+
*/
|
|
6
|
+
const CENTS_PATTERN = /^(0|[1-9][0-9]{0,15})$/;
|
|
7
|
+
const BPS_DENOMINATOR = 10000n;
|
|
8
|
+
/** Parse a wire amount (`"50250"`). Rejects signs, decimals, leading zeros and more than 16 digits. */
|
|
9
|
+
export function parseCents(value, name = "amountCents") {
|
|
10
|
+
if (typeof value === "bigint") {
|
|
11
|
+
if (value < 0n)
|
|
12
|
+
throw new Error(`${name} can't be negative`);
|
|
13
|
+
return value;
|
|
14
|
+
}
|
|
15
|
+
if (typeof value !== "string" || !CENTS_PATTERN.test(value)) {
|
|
16
|
+
throw new Error(`${name} must be a whole number of cents written as a string, like "2000"`);
|
|
17
|
+
}
|
|
18
|
+
return BigInt(value);
|
|
19
|
+
}
|
|
20
|
+
/** Signed cents for statement totals and refund fees, where negative means credit (`"-1005"`). */
|
|
21
|
+
export function parseSignedCents(value, name = "amountCents") {
|
|
22
|
+
if (typeof value === "string" && value.startsWith("-") && value !== "-0")
|
|
23
|
+
return -parseCents(value.slice(1), name);
|
|
24
|
+
return parseCents(value, name);
|
|
25
|
+
}
|
|
26
|
+
export function centsToString(value) {
|
|
27
|
+
if (value < 0n)
|
|
28
|
+
throw new Error("cents can't be negative on the wire");
|
|
29
|
+
return value.toString();
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Platform fee on one line: floor(x · bps / 10 000), integer only. The one
|
|
33
|
+
* rule for debits and credits alike, the same in card_policy and Axum
|
|
34
|
+
* statements (contracts.md §1.5, review fixes 2026-10-04). Rounding down means
|
|
35
|
+
* split refunds never credit more than one refund of the same total, and a
|
|
36
|
+
* period's fees never pass fee(budget). Vectors: shared/cards/fee-vectors.json.
|
|
37
|
+
*/
|
|
38
|
+
export function feeCents(amountCents, feeBps) {
|
|
39
|
+
assertBps(feeBps);
|
|
40
|
+
if (amountCents < 0n)
|
|
41
|
+
throw new Error("amount can't be negative");
|
|
42
|
+
return (amountCents * BigInt(feeBps)) / BPS_DENOMINATOR;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* A refund on a hold returns at most what it captured and hasn't refunded:
|
|
46
|
+
* `refunded + amount <= captured`. Returns the amount when it fits, `null`
|
|
47
|
+
* when card_policy (or Axum, for a closed hold) sends it to review instead.
|
|
48
|
+
*/
|
|
49
|
+
export function refundableCents(capturedCents, refundedCents, amountCents) {
|
|
50
|
+
return refundedCents + amountCents <= capturedCents ? amountCents : null;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* The most the owner can owe for one period: budget + fee(budget). $500 @ 50 bps → $502.50.
|
|
54
|
+
* A hard limit: card_policy bills no debit (purchase, forced post, late capture)
|
|
55
|
+
* past the period budget, and floor fees sum to at most fee(budget). Issuer
|
|
56
|
+
* charges past it are booked for review, never onto the owner's statement.
|
|
57
|
+
*/
|
|
58
|
+
export function maxObligationCents(budgetCents, feeBps) {
|
|
59
|
+
return budgetCents + feeCents(budgetCents, feeBps);
|
|
60
|
+
}
|
|
61
|
+
/** available = budget − (captured + reserved), floored at zero. */
|
|
62
|
+
export function availableCents(budgetCents, capturedCents, reservedCents) {
|
|
63
|
+
const used = capturedCents + reservedCents;
|
|
64
|
+
return used >= budgetCents ? 0n : budgetCents - used;
|
|
65
|
+
}
|
|
66
|
+
/** Outstanding after a capture: += amount + fee(amount). */
|
|
67
|
+
export function outstandingAfterCapture(outstandingCents, capturedCents, feeBps) {
|
|
68
|
+
return outstandingCents + capturedCents + feeCents(capturedCents, feeBps);
|
|
69
|
+
}
|
|
70
|
+
/** Outstanding after a refund: −= min(refund + fee(refund), outstanding). */
|
|
71
|
+
export function outstandingAfterRefund(outstandingCents, refundCents, feeBps) {
|
|
72
|
+
const credit = refundCents + feeCents(refundCents, feeBps);
|
|
73
|
+
return credit >= outstandingCents ? 0n : outstandingCents - credit;
|
|
74
|
+
}
|
|
75
|
+
/** Statement totals (contracts.md §7.1): total = purchases − refunds + Σ per-line fees. */
|
|
76
|
+
export function statementTotals(lines, feeBps) {
|
|
77
|
+
let purchasesCents = 0n;
|
|
78
|
+
let refundsCents = 0n;
|
|
79
|
+
let fees = 0n;
|
|
80
|
+
for (const line of lines) {
|
|
81
|
+
if (line.amountCents < 0n)
|
|
82
|
+
throw new Error("statement line amounts are unsigned; use the line kind for direction");
|
|
83
|
+
const debit = line.kind === "purchase" || line.kind === "adjustment_debit";
|
|
84
|
+
if (debit) {
|
|
85
|
+
purchasesCents += line.amountCents;
|
|
86
|
+
fees += feeCents(line.amountCents, feeBps);
|
|
87
|
+
}
|
|
88
|
+
else {
|
|
89
|
+
refundsCents += line.amountCents;
|
|
90
|
+
fees -= feeCents(line.amountCents, feeBps);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
// Never clamp: a negative total is a credit the owner is owed, not zero.
|
|
94
|
+
return { purchasesCents, refundsCents, feeCents: fees, totalCents: purchasesCents - refundsCents + fees };
|
|
95
|
+
}
|
|
96
|
+
/** Exact dollars for people: 50250n → "$502.50". No rounding is ever applied. */
|
|
97
|
+
export function formatUsdCents(value) {
|
|
98
|
+
const cents = typeof value === "bigint" ? value : parseSignedCents(value);
|
|
99
|
+
const negative = cents < 0n;
|
|
100
|
+
const abs = negative ? -cents : cents;
|
|
101
|
+
const dollars = (abs / 100n).toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",");
|
|
102
|
+
const rest = (abs % 100n).toString().padStart(2, "0");
|
|
103
|
+
return `${negative ? "-" : ""}$${dollars}.${rest}`;
|
|
104
|
+
}
|
|
105
|
+
/** 50 → "0.5%", 125 → "1.25%", 0 → "0%". */
|
|
106
|
+
export function formatFeeBps(feeBps) {
|
|
107
|
+
assertBps(feeBps);
|
|
108
|
+
const whole = Math.trunc(feeBps / 100);
|
|
109
|
+
const frac = (feeBps % 100).toString().padStart(2, "0").replace(/0+$/, "");
|
|
110
|
+
return frac ? `${whole}.${frac}%` : `${whole}%`;
|
|
111
|
+
}
|
|
112
|
+
/** USD stablecoin base units for a statement repayment: cents × 10^(decimals−2). USDC (6) → ×10 000. */
|
|
113
|
+
export function centsToTokenBaseUnits(cents, decimals) {
|
|
114
|
+
if (!Number.isInteger(decimals) || decimals < 2)
|
|
115
|
+
throw new Error("token must have at least 2 decimals");
|
|
116
|
+
return cents * 10n ** BigInt(decimals - 2);
|
|
117
|
+
}
|
|
118
|
+
/** Owner-facing review numbers before signing `set_policy` (PLAN F). */
|
|
119
|
+
export function policyReviewSummary(budgetCents, maxPurchaseCents, feeBps) {
|
|
120
|
+
const obligation = maxObligationCents(budgetCents, feeBps);
|
|
121
|
+
return {
|
|
122
|
+
budgetCents: centsToString(budgetCents),
|
|
123
|
+
maxPurchaseCents: centsToString(maxPurchaseCents),
|
|
124
|
+
feeBps,
|
|
125
|
+
feeCentsAtFullBudget: centsToString(obligation - budgetCents),
|
|
126
|
+
maxObligationCents: centsToString(obligation),
|
|
127
|
+
display: {
|
|
128
|
+
budget: formatUsdCents(budgetCents),
|
|
129
|
+
maxPurchase: formatUsdCents(maxPurchaseCents),
|
|
130
|
+
fee: formatFeeBps(feeBps),
|
|
131
|
+
maxObligation: formatUsdCents(obligation),
|
|
132
|
+
},
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
function assertBps(feeBps) {
|
|
136
|
+
if (!Number.isInteger(feeBps) || feeBps < 0 || feeBps > 1_000)
|
|
137
|
+
throw new Error("feeBps must be a whole number from 0 to 1000");
|
|
138
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export type CardSandboxMerchant = {
|
|
2
|
+
ref: string;
|
|
3
|
+
displayName: string;
|
|
4
|
+
acceptorId: string;
|
|
5
|
+
/** Statement descriptor the issuer shows (Lithic `merchant.descriptor`). */
|
|
6
|
+
descriptor: string;
|
|
7
|
+
/** What the shop sells, in plain words. */
|
|
8
|
+
sells: string;
|
|
9
|
+
mcc: number;
|
|
10
|
+
/** Fixture role: the approved shop and the one a card should decline. */
|
|
11
|
+
fixture: "approved" | "unapproved";
|
|
12
|
+
};
|
|
13
|
+
export declare const CARD_SANDBOX_MERCHANTS: readonly CardSandboxMerchant[];
|
|
14
|
+
/** Lithic's limit on `merchant_acceptor_id`. */
|
|
15
|
+
export declare const MAX_ACCEPTOR_ID_LENGTH = 15;
|
|
16
|
+
/** Plain names for the merchant categories the dashboard offers. */
|
|
17
|
+
export declare const CARD_MCC_NAMES: Readonly<Record<number, string>>;
|
|
18
|
+
export declare function cardMerchantByRef(ref: string): CardSandboxMerchant | undefined;
|
|
19
|
+
/** Matches the connector's `merchant_by_acceptor` (trimmed, case-insensitive). */
|
|
20
|
+
export declare function cardMerchantByAcceptorId(acceptorId: string): CardSandboxMerchant | undefined;
|
|
21
|
+
/** Allowlist hashes for `set_policy`. Throws on an unknown shop so nothing is silently dropped. */
|
|
22
|
+
export declare function merchantIdHashesForRefs(refs: readonly string[]): Promise<Uint8Array[]>;
|
|
23
|
+
export declare function mccLabel(mcc: number): string;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { merchantIdHash } from "./hash.js";
|
|
2
|
+
export const CARD_SANDBOX_MERCHANTS = [
|
|
3
|
+
{ ref: "demo-approved", displayName: "ChainPay demo shop", acceptorId: "DEMO-DATAAPI", descriptor: "DATA API CREDITS", sells: "Data API credits", mcc: 5734, fixture: "approved" },
|
|
4
|
+
{ ref: "demo-unapproved", displayName: "Unlisted test shop", acceptorId: "DEMO-OTHERSHOP", descriptor: "UNAPPROVED SHOP", sells: "Anything else", mcc: 5999, fixture: "unapproved" },
|
|
5
|
+
];
|
|
6
|
+
/** Lithic's limit on `merchant_acceptor_id`. */
|
|
7
|
+
export const MAX_ACCEPTOR_ID_LENGTH = 15;
|
|
8
|
+
/** Plain names for the merchant categories the dashboard offers. */
|
|
9
|
+
export const CARD_MCC_NAMES = {
|
|
10
|
+
4816: "Online services",
|
|
11
|
+
5045: "Computers and electronics",
|
|
12
|
+
5734: "Software",
|
|
13
|
+
5817: "Digital goods",
|
|
14
|
+
5942: "Books",
|
|
15
|
+
5999: "Other retail",
|
|
16
|
+
7372: "Data and computer services",
|
|
17
|
+
4121: "Rides",
|
|
18
|
+
};
|
|
19
|
+
export function cardMerchantByRef(ref) {
|
|
20
|
+
return CARD_SANDBOX_MERCHANTS.find((merchant) => merchant.ref === ref);
|
|
21
|
+
}
|
|
22
|
+
/** Matches the connector's `merchant_by_acceptor` (trimmed, case-insensitive). */
|
|
23
|
+
export function cardMerchantByAcceptorId(acceptorId) {
|
|
24
|
+
const wanted = acceptorId.trim().toUpperCase();
|
|
25
|
+
return CARD_SANDBOX_MERCHANTS.find((merchant) => merchant.acceptorId === wanted);
|
|
26
|
+
}
|
|
27
|
+
/** Allowlist hashes for `set_policy`. Throws on an unknown shop so nothing is silently dropped. */
|
|
28
|
+
export async function merchantIdHashesForRefs(refs) {
|
|
29
|
+
return Promise.all(refs.map((ref) => {
|
|
30
|
+
const merchant = cardMerchantByRef(ref);
|
|
31
|
+
if (!merchant)
|
|
32
|
+
throw new Error(`"${ref}" isn't a registered shop`);
|
|
33
|
+
return merchantIdHash(merchant.acceptorId);
|
|
34
|
+
}));
|
|
35
|
+
}
|
|
36
|
+
export function mccLabel(mcc) {
|
|
37
|
+
return CARD_MCC_NAMES[mcc] ? `${CARD_MCC_NAMES[mcc]} (${String(mcc).padStart(4, "0")})` : `Category ${String(mcc).padStart(4, "0")}`;
|
|
38
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import type { Address } from "../types.js";
|
|
2
|
+
/** `["card_binding", owner, card_id]` (base, public). */
|
|
3
|
+
export declare function deriveCardBindingAddress(owner: Address, cardId: Uint8Array, programId?: Address): Address;
|
|
4
|
+
/** `["card_policy", binding]` (delegated to the TEE, private). */
|
|
5
|
+
export declare function deriveCardPolicyAddress(binding: Address, programId?: Address): Address;
|
|
6
|
+
/** `["card_period", binding]` (delegated to the TEE, private). */
|
|
7
|
+
export declare function deriveCardPeriodAddress(binding: Address, programId?: Address): Address;
|
|
8
|
+
/** `["res", policy, auth_id_hash]` (PER-only). Its existence is the replay guard. */
|
|
9
|
+
export declare function deriveReservationAddress(policy: Address, authIdHash: Uint8Array, programId?: Address): Address;
|
|
10
|
+
/** `["auth_guard", policy]` (PER-only): replay guard for closed reservations. */
|
|
11
|
+
export declare function deriveAuthGuardAddress(policy: Address, programId?: Address): Address;
|
|
12
|
+
/** `["intent", policy, intent_id]` (PER-only); intent_id is 16 bytes. */
|
|
13
|
+
export declare function deriveCheckoutIntentAddress(policy: Address, intentId: Uint8Array, programId?: Address): Address;
|
|
14
|
+
/** `["card_commit", binding]` (base, public, never delegated). */
|
|
15
|
+
export declare function deriveCardCommitmentAddress(binding: Address, programId?: Address): Address;
|
|
16
|
+
/**
|
|
17
|
+
* `["repay_agent", binding]` (base, system-owned, lamport-only). The owner's ChainPay
|
|
18
|
+
* repayment mandate names it as `approved_agent`; it signs only inside `repay_statement`.
|
|
19
|
+
*/
|
|
20
|
+
export declare function deriveRepayAgentAddress(binding: Address, programId?: Address): Address;
|
|
21
|
+
/** MagicBlock permission PDA for a private account: `["permission:", account]` under the permission program. */
|
|
22
|
+
export declare function derivePermissionAddress(account: Address): Address;
|
|
23
|
+
/** Delegation-program escrow that pays for the card's Magic Actions: `["balance", policy, 255]`. */
|
|
24
|
+
export declare function deriveCardEscrowAddress(escrowAuthority: Address, index?: number): Address;
|
|
25
|
+
export declare function deriveDelegationBufferAddress(account: Address, programId?: Address): Address;
|
|
26
|
+
export declare function deriveDelegationRecordAddress(account: Address): Address;
|
|
27
|
+
export declare function deriveDelegationMetadataAddress(account: Address): Address;
|
|
28
|
+
export declare function deriveMagicFeeVaultAddress(validator: Address): Address;
|
|
29
|
+
export type CardAccounts = {
|
|
30
|
+
binding: Address;
|
|
31
|
+
policy: Address;
|
|
32
|
+
period: Address;
|
|
33
|
+
commitment: Address;
|
|
34
|
+
escrow: Address;
|
|
35
|
+
policyPermission: Address;
|
|
36
|
+
periodPermission: Address;
|
|
37
|
+
};
|
|
38
|
+
/** Every address one card owns, from the owner wallet and the 32-byte card id. */
|
|
39
|
+
export declare function deriveCardAccounts(owner: Address, cardId: Uint8Array, programId?: Address): CardAccounts;
|
|
40
|
+
/** Card ids travel as 64-char lowercase hex (Convex key `card:<cardId hex>`). */
|
|
41
|
+
export declare function cardIdFromHex(value: string): Uint8Array;
|
|
42
|
+
export declare function cardIdToHex(cardId: Uint8Array): string;
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { PublicKey } from "@solana/web3.js";
|
|
2
|
+
import { bytes32, publicKey } from "../encoding.js";
|
|
3
|
+
import { CARD_SEEDS, DELEGATION_PROGRAM_ID, PERMISSION_PROGRAM_ID, resolveCardPolicyProgramId, } from "./constants.js";
|
|
4
|
+
function pda(seeds, programId) {
|
|
5
|
+
return PublicKey.findProgramAddressSync(seeds, publicKey(programId))[0].toBase58();
|
|
6
|
+
}
|
|
7
|
+
function utf8(value) {
|
|
8
|
+
return new TextEncoder().encode(value);
|
|
9
|
+
}
|
|
10
|
+
/** `["card_binding", owner, card_id]` (base, public). */
|
|
11
|
+
export function deriveCardBindingAddress(owner, cardId, programId) {
|
|
12
|
+
return pda([utf8(CARD_SEEDS.binding), publicKey(owner).toBytes(), bytes32(cardId, "cardId")], resolveCardPolicyProgramId(programId));
|
|
13
|
+
}
|
|
14
|
+
/** `["card_policy", binding]` (delegated to the TEE, private). */
|
|
15
|
+
export function deriveCardPolicyAddress(binding, programId) {
|
|
16
|
+
return pda([utf8(CARD_SEEDS.policy), publicKey(binding).toBytes()], resolveCardPolicyProgramId(programId));
|
|
17
|
+
}
|
|
18
|
+
/** `["card_period", binding]` (delegated to the TEE, private). */
|
|
19
|
+
export function deriveCardPeriodAddress(binding, programId) {
|
|
20
|
+
return pda([utf8(CARD_SEEDS.period), publicKey(binding).toBytes()], resolveCardPolicyProgramId(programId));
|
|
21
|
+
}
|
|
22
|
+
/** `["res", policy, auth_id_hash]` (PER-only). Its existence is the replay guard. */
|
|
23
|
+
export function deriveReservationAddress(policy, authIdHash, programId) {
|
|
24
|
+
return pda([utf8(CARD_SEEDS.reservation), publicKey(policy).toBytes(), bytes32(authIdHash, "authIdHash")], resolveCardPolicyProgramId(programId));
|
|
25
|
+
}
|
|
26
|
+
/** `["auth_guard", policy]` (PER-only): replay guard for closed reservations. */
|
|
27
|
+
export function deriveAuthGuardAddress(policy, programId) {
|
|
28
|
+
return pda([utf8(CARD_SEEDS.authGuard), publicKey(policy).toBytes()], resolveCardPolicyProgramId(programId));
|
|
29
|
+
}
|
|
30
|
+
/** `["intent", policy, intent_id]` (PER-only); intent_id is 16 bytes. */
|
|
31
|
+
export function deriveCheckoutIntentAddress(policy, intentId, programId) {
|
|
32
|
+
if (intentId.length !== 16)
|
|
33
|
+
throw new Error("intentId must be exactly 16 bytes");
|
|
34
|
+
return pda([utf8(CARD_SEEDS.intent), publicKey(policy).toBytes(), new Uint8Array(intentId)], resolveCardPolicyProgramId(programId));
|
|
35
|
+
}
|
|
36
|
+
/** `["card_commit", binding]` (base, public, never delegated). */
|
|
37
|
+
export function deriveCardCommitmentAddress(binding, programId) {
|
|
38
|
+
return pda([utf8(CARD_SEEDS.commitment), publicKey(binding).toBytes()], resolveCardPolicyProgramId(programId));
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* `["repay_agent", binding]` (base, system-owned, lamport-only). The owner's ChainPay
|
|
42
|
+
* repayment mandate names it as `approved_agent`; it signs only inside `repay_statement`.
|
|
43
|
+
*/
|
|
44
|
+
export function deriveRepayAgentAddress(binding, programId) {
|
|
45
|
+
return pda([utf8(CARD_SEEDS.repayAgent), publicKey(binding).toBytes()], resolveCardPolicyProgramId(programId));
|
|
46
|
+
}
|
|
47
|
+
/** MagicBlock permission PDA for a private account: `["permission:", account]` under the permission program. */
|
|
48
|
+
export function derivePermissionAddress(account) {
|
|
49
|
+
return pda([utf8("permission:"), publicKey(account).toBytes()], PERMISSION_PROGRAM_ID);
|
|
50
|
+
}
|
|
51
|
+
/** Delegation-program escrow that pays for the card's Magic Actions: `["balance", policy, 255]`. */
|
|
52
|
+
export function deriveCardEscrowAddress(escrowAuthority, index = 255) {
|
|
53
|
+
if (!Number.isInteger(index) || index < 0 || index > 255)
|
|
54
|
+
throw new Error("escrow index must be 0..255");
|
|
55
|
+
return pda([utf8("balance"), publicKey(escrowAuthority).toBytes(), Uint8Array.of(index)], DELEGATION_PROGRAM_ID);
|
|
56
|
+
}
|
|
57
|
+
export function deriveDelegationBufferAddress(account, programId) {
|
|
58
|
+
return pda([utf8("buffer"), publicKey(account).toBytes()], resolveCardPolicyProgramId(programId));
|
|
59
|
+
}
|
|
60
|
+
export function deriveDelegationRecordAddress(account) {
|
|
61
|
+
return pda([utf8("delegation"), publicKey(account).toBytes()], DELEGATION_PROGRAM_ID);
|
|
62
|
+
}
|
|
63
|
+
export function deriveDelegationMetadataAddress(account) {
|
|
64
|
+
return pda([utf8("delegation-metadata"), publicKey(account).toBytes()], DELEGATION_PROGRAM_ID);
|
|
65
|
+
}
|
|
66
|
+
export function deriveMagicFeeVaultAddress(validator) {
|
|
67
|
+
return pda([utf8("magic-fee-vault"), publicKey(validator).toBytes()], DELEGATION_PROGRAM_ID);
|
|
68
|
+
}
|
|
69
|
+
/** Every address one card owns, from the owner wallet and the 32-byte card id. */
|
|
70
|
+
export function deriveCardAccounts(owner, cardId, programId) {
|
|
71
|
+
const binding = deriveCardBindingAddress(owner, cardId, programId);
|
|
72
|
+
const policy = deriveCardPolicyAddress(binding, programId);
|
|
73
|
+
const period = deriveCardPeriodAddress(binding, programId);
|
|
74
|
+
return {
|
|
75
|
+
binding,
|
|
76
|
+
policy,
|
|
77
|
+
period,
|
|
78
|
+
commitment: deriveCardCommitmentAddress(binding, programId),
|
|
79
|
+
escrow: deriveCardEscrowAddress(policy),
|
|
80
|
+
policyPermission: derivePermissionAddress(policy),
|
|
81
|
+
periodPermission: derivePermissionAddress(period),
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
/** Card ids travel as 64-char lowercase hex (Convex key `card:<cardId hex>`). */
|
|
85
|
+
export function cardIdFromHex(value) {
|
|
86
|
+
const hex = value.trim().toLowerCase().replace(/^0x/, "");
|
|
87
|
+
if (!/^[0-9a-f]{64}$/.test(hex))
|
|
88
|
+
throw new Error("cardId must be 32 bytes of hex");
|
|
89
|
+
return Uint8Array.from({ length: 32 }, (_, i) => Number.parseInt(hex.slice(i * 2, i * 2 + 2), 16));
|
|
90
|
+
}
|
|
91
|
+
export function cardIdToHex(cardId) {
|
|
92
|
+
return Array.from(bytes32(cardId, "cardId"), (byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
93
|
+
}
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
export declare const MAGICBLOCK_PAYMENTS_API = "https://payments.magicblock.app";
|
|
2
|
+
export declare const PRIVATE_REPAYMENT_METHOD = "magicblock_private_payments";
|
|
3
|
+
export declare const PRIVATE_REPAYMENT_DEVNET_USDC = "4zMMC9srt5Ri5X14GAgXhaHii3GnPAEERYPJgZJDncDU";
|
|
4
|
+
export declare const EPHEMERAL_SPL_PROGRAM = "SPLxh1LVZzEkX99H6rqYizhytLWPZVV296zyYDPagv2";
|
|
5
|
+
export type PrivateRepaymentAttempt = {
|
|
6
|
+
attemptId: string;
|
|
7
|
+
state: "awaiting_settlement" | "verified" | "mismatch";
|
|
8
|
+
method: typeof PRIVATE_REPAYMENT_METHOD;
|
|
9
|
+
statementId: string;
|
|
10
|
+
cluster: "devnet";
|
|
11
|
+
apiCluster: "devnet-private";
|
|
12
|
+
api: string;
|
|
13
|
+
mint: string;
|
|
14
|
+
amountCents: string;
|
|
15
|
+
amountBaseUnits: string;
|
|
16
|
+
recipientWallet: string;
|
|
17
|
+
recipientTokenAccount: string;
|
|
18
|
+
clientRefId: string;
|
|
19
|
+
transfer: {
|
|
20
|
+
visibility: "private";
|
|
21
|
+
fromBalance: "ephemeral";
|
|
22
|
+
toBalance: "base";
|
|
23
|
+
split: 1;
|
|
24
|
+
exactOut: true;
|
|
25
|
+
minDelayMs: string;
|
|
26
|
+
maxDelayMs: string;
|
|
27
|
+
memo: null;
|
|
28
|
+
};
|
|
29
|
+
vault: {
|
|
30
|
+
program: string;
|
|
31
|
+
vault: string;
|
|
32
|
+
vaultTokenAccount: string;
|
|
33
|
+
custody: string;
|
|
34
|
+
};
|
|
35
|
+
verification: {
|
|
36
|
+
kind: "magicblock_queue_settlement";
|
|
37
|
+
commitment: "finalized";
|
|
38
|
+
checks: string[];
|
|
39
|
+
notVerifiable: string[];
|
|
40
|
+
public: string[];
|
|
41
|
+
hidden: string[];
|
|
42
|
+
};
|
|
43
|
+
label: string;
|
|
44
|
+
};
|
|
45
|
+
export type PrivateRepaymentResult = {
|
|
46
|
+
state: "discharged" | "partner_confirmed" | "repayment_observed";
|
|
47
|
+
statement: unknown;
|
|
48
|
+
} | {
|
|
49
|
+
state: "repayment_mismatch";
|
|
50
|
+
mismatch: string[];
|
|
51
|
+
statement: unknown;
|
|
52
|
+
};
|
|
53
|
+
export declare class PrivateRepaymentError extends Error {
|
|
54
|
+
readonly status: number;
|
|
55
|
+
readonly code: string;
|
|
56
|
+
readonly retryable: boolean;
|
|
57
|
+
constructor(status: number, code: string, message: string, retryable?: boolean);
|
|
58
|
+
}
|
|
59
|
+
type Fetch = typeof fetch;
|
|
60
|
+
/** Refuse anything but the Devnet USDC private route ChainPay verifies. */
|
|
61
|
+
export declare function assertPayableAttempt(attempt: PrivateRepaymentAttempt): void;
|
|
62
|
+
/**
|
|
63
|
+
* The vault and deposit model in plain words, for the opt-in step. Every line
|
|
64
|
+
* is true of the route this module pays through; none claims more than
|
|
65
|
+
* ChainPay verifies.
|
|
66
|
+
*/
|
|
67
|
+
export declare function privateRepaymentDisclosure(attempt: Pick<PrivateRepaymentAttempt, "amountBaseUnits" | "amountCents">): string[];
|
|
68
|
+
export type ChainPayRoutesOptions = {
|
|
69
|
+
/** Axum base URL, e.g. https://relay.example. */
|
|
70
|
+
baseUrl: string;
|
|
71
|
+
/** Owner session bearer. Never an MCP connection: agents can't repay. */
|
|
72
|
+
ownerSession: string;
|
|
73
|
+
fetch?: Fetch;
|
|
74
|
+
};
|
|
75
|
+
/** Get or create the open attempt (idempotent). */
|
|
76
|
+
export declare function preparePrivateRepayment(opts: ChainPayRoutesOptions, cardId: string, statementId: string, clientOperationId: string): Promise<PrivateRepaymentAttempt>;
|
|
77
|
+
/**
|
|
78
|
+
* Ask ChainPay to verify the settlement. A `settlement_pending` error is
|
|
79
|
+
* retryable: MagicBlock's queue pays out after the transfer, usually within
|
|
80
|
+
* seconds.
|
|
81
|
+
*/
|
|
82
|
+
export declare function submitPrivateRepayment(opts: ChainPayRoutesOptions, cardId: string, statementId: string, attemptId: string): Promise<PrivateRepaymentResult>;
|
|
83
|
+
/** Poll `submit` until the settlement is found (or `timeoutMs` passes). */
|
|
84
|
+
export declare function waitForPrivateRepayment(opts: ChainPayRoutesOptions, cardId: string, statementId: string, attemptId: string, { timeoutMs, intervalMs, sleep }?: {
|
|
85
|
+
timeoutMs?: number | undefined;
|
|
86
|
+
intervalMs?: number | undefined;
|
|
87
|
+
sleep?: ((ms: number) => Promise<void>) | undefined;
|
|
88
|
+
}): Promise<PrivateRepaymentResult>;
|
|
89
|
+
/** The owner's wallet. Transactions travel as base64 wire bytes. */
|
|
90
|
+
export type PrivateRepaymentSigner = {
|
|
91
|
+
publicKey: string;
|
|
92
|
+
signMessage(message: Uint8Array): Promise<Uint8Array>;
|
|
93
|
+
/** Sign (not send) a serialized transaction; return the signed base64. */
|
|
94
|
+
signTransaction(transactionBase64: string): Promise<string>;
|
|
95
|
+
};
|
|
96
|
+
export type MagicBlockOptions = {
|
|
97
|
+
api?: string;
|
|
98
|
+
fetch?: Fetch;
|
|
99
|
+
};
|
|
100
|
+
type Built = {
|
|
101
|
+
kind: string;
|
|
102
|
+
transactionBase64: string;
|
|
103
|
+
sendTo: "base" | "ephemeral";
|
|
104
|
+
sendRpcEndpoint?: string;
|
|
105
|
+
recentBlockhash: string;
|
|
106
|
+
lastValidBlockHeight: number;
|
|
107
|
+
requiredSigners: string[];
|
|
108
|
+
fees?: {
|
|
109
|
+
lamports: string;
|
|
110
|
+
tokens: string;
|
|
111
|
+
};
|
|
112
|
+
};
|
|
113
|
+
/**
|
|
114
|
+
* Wallet challenge login for private reads and rollup sends. The token lives
|
|
115
|
+
* in memory only: never store it, log it or send it to ChainPay.
|
|
116
|
+
*/
|
|
117
|
+
export declare function magicblockLogin(signer: PrivateRepaymentSigner, opts?: MagicBlockOptions): Promise<string>;
|
|
118
|
+
export declare function privateBalance(owner: string, mint: string, token: string, opts?: MagicBlockOptions): Promise<bigint>;
|
|
119
|
+
type DecodedInstruction = {
|
|
120
|
+
programId: string;
|
|
121
|
+
accounts: string[];
|
|
122
|
+
data: Uint8Array;
|
|
123
|
+
};
|
|
124
|
+
type DecodedTransaction = {
|
|
125
|
+
signers: string[];
|
|
126
|
+
instructions: DecodedInstruction[];
|
|
127
|
+
};
|
|
128
|
+
/**
|
|
129
|
+
* Decode a serialized legacy (or v0 without lookup tables) transaction: its
|
|
130
|
+
* signers and every instruction with resolved account keys. Lookup tables
|
|
131
|
+
* can't be checked offline, so they are refused.
|
|
132
|
+
*/
|
|
133
|
+
export declare function decodeBuiltTransaction(base64: string): DecodedTransaction;
|
|
134
|
+
/**
|
|
135
|
+
* Before the owner signs a MagicBlock-built transaction, check it does
|
|
136
|
+
* exactly what the statement says (review F1): only the expected programs,
|
|
137
|
+
* the owner as the only signer, and the one amount-bearing instruction moving
|
|
138
|
+
* the expected amount of the attempt's mint (to the attempt's recipient, for
|
|
139
|
+
* the transfer).
|
|
140
|
+
*/
|
|
141
|
+
export declare function verifyBuiltTransaction(built: Pick<Built, "transactionBase64">, expect: {
|
|
142
|
+
kind: "deposit" | "transfer";
|
|
143
|
+
owner: string;
|
|
144
|
+
mint: string;
|
|
145
|
+
amountBaseUnits: bigint;
|
|
146
|
+
recipientWallet?: string;
|
|
147
|
+
clientRefId?: string;
|
|
148
|
+
minDelayMs?: string;
|
|
149
|
+
maxDelayMs?: string;
|
|
150
|
+
}): void;
|
|
151
|
+
export type PayStep = {
|
|
152
|
+
step: "login";
|
|
153
|
+
} | {
|
|
154
|
+
step: "balance";
|
|
155
|
+
privateBalance: string;
|
|
156
|
+
} | {
|
|
157
|
+
step: "deposit";
|
|
158
|
+
amountBaseUnits: string;
|
|
159
|
+
signature?: string;
|
|
160
|
+
} | {
|
|
161
|
+
step: "transfer";
|
|
162
|
+
signature?: string;
|
|
163
|
+
outcome: "sent" | "unknown";
|
|
164
|
+
};
|
|
165
|
+
export type PayPrivatelyInput = {
|
|
166
|
+
attempt: PrivateRepaymentAttempt;
|
|
167
|
+
signer: PrivateRepaymentSigner;
|
|
168
|
+
/**
|
|
169
|
+
* Submit a signed base-layer (Devnet) transaction and resolve with its
|
|
170
|
+
* signature once confirmed. The browser passes its wallet adapter's
|
|
171
|
+
* connection; MagicBlock's own send endpoint is the fallback.
|
|
172
|
+
*/
|
|
173
|
+
sendBase?: (signedBase64: string, built: {
|
|
174
|
+
recentBlockhash: string;
|
|
175
|
+
lastValidBlockHeight: number;
|
|
176
|
+
}) => Promise<string>;
|
|
177
|
+
onStep?: (step: PayStep) => void;
|
|
178
|
+
magicblock?: MagicBlockOptions;
|
|
179
|
+
/** Poll for the deposit to show up as private balance. */
|
|
180
|
+
waitMs?: number;
|
|
181
|
+
sleep?: (ms: number) => Promise<void>;
|
|
182
|
+
};
|
|
183
|
+
/**
|
|
184
|
+
* Pay the attempt privately: deposit any shortfall into MagicBlock's vault,
|
|
185
|
+
* then one private transfer to the partner tagged with the attempt reference.
|
|
186
|
+
* Call only after the owner opted in on `privateRepaymentDisclosure`.
|
|
187
|
+
*
|
|
188
|
+
* A rollup send can report an error even though the transfer landed (seen
|
|
189
|
+
* live on Devnet: "block height exceeded" for a transfer that settled), so a
|
|
190
|
+
* failed transfer send resolves as `outcome: "unknown"`; ask ChainPay
|
|
191
|
+
* (`waitForPrivateRepayment`) before paying again.
|
|
192
|
+
*/
|
|
193
|
+
export declare function payStatementPrivately(input: PayPrivatelyInput): Promise<{
|
|
194
|
+
depositSignature?: string;
|
|
195
|
+
transferSignature?: string;
|
|
196
|
+
transferOutcome: "sent" | "unknown";
|
|
197
|
+
}>;
|
|
198
|
+
export {};
|