@x1id/resolve 0.23.0 → 0.44.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 (49) hide show
  1. package/README.md +97 -0
  2. package/dist/accounts.d.ts +6 -0
  3. package/dist/accounts.js +6 -1
  4. package/dist/agent.d.ts +184 -8
  5. package/dist/agent.js +351 -3
  6. package/dist/agentPolicy.d.ts +238 -0
  7. package/dist/agentPolicy.js +364 -0
  8. package/dist/attestation.d.ts +25 -0
  9. package/dist/attestation.js +25 -0
  10. package/dist/checkout.d.ts +216 -0
  11. package/dist/checkout.js +327 -0
  12. package/dist/credentials.d.ts +466 -0
  13. package/dist/credentials.js +669 -0
  14. package/dist/gate.d.ts +94 -4
  15. package/dist/gate.js +206 -7
  16. package/dist/gateClient.d.ts +12 -0
  17. package/dist/giftLink.d.ts +272 -0
  18. package/dist/giftLink.js +452 -0
  19. package/dist/index.d.ts +13 -0
  20. package/dist/index.js +32 -0
  21. package/dist/loginButton.d.ts +69 -0
  22. package/dist/loginButton.js +207 -0
  23. package/dist/logo.d.ts +1 -0
  24. package/dist/logo.js +8 -0
  25. package/dist/messaging.d.ts +439 -0
  26. package/dist/messaging.js +979 -0
  27. package/dist/nsGate.d.ts +98 -0
  28. package/dist/nsGate.js +172 -0
  29. package/dist/oidc.d.ts +272 -0
  30. package/dist/oidc.js +641 -0
  31. package/dist/pay.d.ts +513 -0
  32. package/dist/pay.js +756 -0
  33. package/dist/payment.d.ts +9 -2
  34. package/dist/payment.js +9 -2
  35. package/dist/recovery.d.ts +186 -0
  36. package/dist/recovery.js +276 -0
  37. package/dist/recoveryNft.d.ts +183 -0
  38. package/dist/recoveryNft.js +291 -0
  39. package/dist/shield.d.ts +373 -0
  40. package/dist/shield.js +695 -0
  41. package/dist/subscription.d.ts +439 -0
  42. package/dist/subscription.js +642 -0
  43. package/dist/textRecords.d.ts +52 -6
  44. package/dist/textRecords.js +80 -8
  45. package/dist/types.d.ts +3 -0
  46. package/dist/x402Receipt.d.ts +246 -0
  47. package/dist/x402Receipt.js +374 -0
  48. package/package.json +18 -1
  49. package/schema/agent-manifest.json +91 -8
package/dist/pay.js ADDED
@@ -0,0 +1,756 @@
1
+ /**
2
+ * X1ID Pay, phase 1: on-chain invoices (docs/specs/x1id-pay-onchain.md).
3
+ *
4
+ * The registry program (`x1-handles`) holds three Pay accounts:
5
+ *
6
+ * - `PayConfig` at `["pay_config"]`: the protocol fee (`fee_bps`, hard cap
7
+ * {@link PAY_FEE_BPS_MAX}). While it does not exist the fee is 0.
8
+ * - `Invoice` at `["invoice", payee, id]`: one payment request. Its own
9
+ * address is the Solana Pay reference, so `getSignaturesForAddress(invoice)`
10
+ * finds the payment.
11
+ * - `MerchantAuthority` at `["merchant", payee, delegate]`: lets a merchant's
12
+ * server key create and cancel invoices that always pay the merchant.
13
+ *
14
+ * Every Pay instruction is routed through the program's `fallback`
15
+ * (`extra_ix`), so none of them is in the generated IDL. They are encoded
16
+ * here by hand, like `set_namespace_pricing` in `namespacePricing.ts`:
17
+ * `sha256("global:<name>")[..8]` followed by raw Borsh arguments, with
18
+ * accounts in EXACTLY the order of the program's `Accounts` structs.
19
+ *
20
+ * Optional accounts follow Anchor's convention: a skipped optional slot that
21
+ * precedes a used one is the program id ("None"); trailing unused slots are
22
+ * simply left off.
23
+ *
24
+ * Hand-rolled like the rest of this package: no Anchor client, no
25
+ * `@solana/web3.js`. PDAs come through an injected {@link PdaFinder}.
26
+ */
27
+ import { encodeBase58, decodeBase58_32 } from "./base58.js";
28
+ import { DEFAULT_HANDLE_PROGRAM, makeAccountReader } from "./accounts.js";
29
+ import { sha256 } from "./sha256.js";
30
+ // ---------------------------------------------------------------------------
31
+ // constants (mirror programs/x1-handles/src/{lib,state}.rs)
32
+ // ---------------------------------------------------------------------------
33
+ /** Hard cap on the Pay protocol fee in basis points (1%). */
34
+ export const PAY_FEE_BPS_MAX = 100;
35
+ /** The X1 record slot a name's payment address is read from. */
36
+ export const PAY_X1_COIN_TYPE = 5_010_000;
37
+ /** `Invoice.mint` for native XNT (`Pubkey::default()`). */
38
+ export const INVOICE_NATIVE_MINT = "11111111111111111111111111111111";
39
+ /** Length of an invoice id (`[u8; 16]`). */
40
+ export const INVOICE_ID_LEN = 16;
41
+ export const PAY_CONFIG_SEED = "pay_config";
42
+ export const INVOICE_SEED = "invoice";
43
+ export const MERCHANT_AUTHORITY_SEED = "merchant";
44
+ /** Allocated size of each account (8-byte discriminator + `INIT_SPACE`). */
45
+ export const PAY_CONFIG_ACCOUNT_LEN = 8 + 12;
46
+ export const INVOICE_ACCOUNT_LEN = 8 + 268;
47
+ export const MERCHANT_AUTHORITY_ACCOUNT_LEN = 8 + 74;
48
+ /** Byte offsets inside a raw `Invoice` account, for `getProgramAccounts`
49
+ * `memcmp` filters. Only fields before the variable-width `handle` are fixed. */
50
+ export const INVOICE_OFFSETS = {
51
+ version: 8,
52
+ payee: 9,
53
+ creator: 41,
54
+ id: 73,
55
+ mint: 89,
56
+ amount: 121,
57
+ expiresAt: 129,
58
+ status: 137,
59
+ paidBy: 138,
60
+ paidAt: 170,
61
+ handle: 178,
62
+ };
63
+ const d8 = (...b) => Uint8Array.from(b);
64
+ /** `sha256("global:set_pay_config")[..8]`. */
65
+ export const SET_PAY_CONFIG_DISCRIMINATOR = d8(9, 248, 66, 110, 133, 99, 13, 55);
66
+ /** `sha256("global:create_invoice")[..8]`. */
67
+ export const CREATE_INVOICE_DISCRIMINATOR = d8(154, 170, 31, 135, 134, 100, 156, 146);
68
+ /** `sha256("global:pay_invoice")[..8]`. */
69
+ export const PAY_INVOICE_DISCRIMINATOR = d8(104, 6, 62, 239, 197, 206, 208, 220);
70
+ /** `sha256("global:cancel_invoice")[..8]`. */
71
+ export const CANCEL_INVOICE_DISCRIMINATOR = d8(88, 158, 54, 49, 53, 26, 92, 68);
72
+ /** `sha256("global:close_invoice")[..8]`. */
73
+ export const CLOSE_INVOICE_DISCRIMINATOR = d8(217, 64, 46, 247, 231, 36, 118, 252);
74
+ /** `sha256("global:create_merchant_authority")[..8]`. */
75
+ export const CREATE_MERCHANT_AUTHORITY_DISCRIMINATOR = d8(164, 168, 136, 143, 229, 233, 128, 171);
76
+ /** `sha256("global:revoke_merchant_authority")[..8]`. */
77
+ export const REVOKE_MERCHANT_AUTHORITY_DISCRIMINATOR = d8(64, 229, 132, 124, 172, 101, 15, 105);
78
+ /** `sha256("account:Invoice")[..8]`. */
79
+ export const INVOICE_ACCOUNT_DISCRIMINATOR = d8(51, 194, 250, 114, 6, 104, 18, 164);
80
+ /** `sha256("account:PayConfig")[..8]`. */
81
+ export const PAY_CONFIG_ACCOUNT_DISCRIMINATOR = d8(46, 180, 219, 72, 57, 225, 101, 83);
82
+ /** `sha256("account:MerchantAuthority")[..8]`. */
83
+ export const MERCHANT_AUTHORITY_ACCOUNT_DISCRIMINATOR = d8(223, 239, 26, 237, 235, 231, 118, 252);
84
+ /** Program error codes added by Pay phase 1 (`RegistryError`, 6121-6135),
85
+ * plus `InvoiceTermsChanged` (6136, Sentinel run-007): `pay_invoice`'s
86
+ * pinned `expected_amount`/`expected_mint` no longer match the invoice. */
87
+ export const PAY_ERRORS = {
88
+ PayFeeAboveMax: 6121,
89
+ PayConfigMismatch: 6122,
90
+ InvoiceAmountZero: 6123,
91
+ InvoiceExpiryInPast: 6124,
92
+ NotMerchantDelegate: 6125,
93
+ PayeeNotNamePaymentAddress: 6126,
94
+ PayeeNoLongerHoldsName: 6127,
95
+ InvoiceHandleMismatch: 6128,
96
+ InvoiceNotOpen: 6129,
97
+ InvoiceExpired: 6130,
98
+ InvoiceMintMismatch: 6131,
99
+ InvoicePayeeMismatch: 6132,
100
+ InvoiceTreasuryMismatch: 6133,
101
+ NotInvoiceCloser: 6134,
102
+ InvoiceStillOpen: 6135,
103
+ InvoiceTermsChanged: 6136,
104
+ };
105
+ /** Anchor's `InstructionFallbackNotFound`: the deployed program predates Pay. */
106
+ export const PAY_NOT_DEPLOYED_ERROR = 101;
107
+ /** `Invoice.status` as stored (Borsh enum index). */
108
+ export const INVOICE_STATUS = { Open: 0, Paid: 1, Cancelled: 2 };
109
+ const SYSTEM_PROGRAM = "11111111111111111111111111111111";
110
+ const PAY_ASSOCIATED_TOKEN_PROGRAM = "ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL";
111
+ const TE = new TextEncoder();
112
+ const U64_MAX = 0xffffffffffffffffn;
113
+ const I64_MAX = 0x7fffffffffffffffn;
114
+ // ---------------------------------------------------------------------------
115
+ // encoding helpers
116
+ // ---------------------------------------------------------------------------
117
+ function toBytes32(v, what) {
118
+ if (typeof v === "string") {
119
+ const b = decodeBase58_32(v);
120
+ if (!b)
121
+ throw new Error(`${what} is not a valid base58 address`);
122
+ return b;
123
+ }
124
+ if (v.length !== 32)
125
+ throw new Error(`${what} must be exactly 32 bytes`);
126
+ return v;
127
+ }
128
+ function toBase58(v, what) {
129
+ return encodeBase58(toBytes32(v, what));
130
+ }
131
+ function key(pubkey, what, isSigner, isWritable) {
132
+ return { pubkey: toBase58(pubkey, what), isSigner, isWritable };
133
+ }
134
+ function concat(...parts) {
135
+ const out = new Uint8Array(parts.reduce((a, p) => a + p.length, 0));
136
+ let o = 0;
137
+ for (const p of parts) {
138
+ out.set(p, o);
139
+ o += p.length;
140
+ }
141
+ return out;
142
+ }
143
+ function u16le(n) {
144
+ const out = new Uint8Array(2);
145
+ new DataView(out.buffer).setUint16(0, n, true);
146
+ return out;
147
+ }
148
+ function u32le(n) {
149
+ const out = new Uint8Array(4);
150
+ new DataView(out.buffer).setUint32(0, n, true);
151
+ return out;
152
+ }
153
+ function u64le(n) {
154
+ const out = new Uint8Array(8);
155
+ new DataView(out.buffer).setBigUint64(0, n, true);
156
+ return out;
157
+ }
158
+ function i64le(n) {
159
+ const out = new Uint8Array(8);
160
+ new DataView(out.buffer).setBigInt64(0, n, true);
161
+ return out;
162
+ }
163
+ function hasPrefix(data, disc) {
164
+ if (data.length < disc.length)
165
+ return false;
166
+ for (let i = 0; i < disc.length; i++)
167
+ if (data[i] !== disc[i])
168
+ return false;
169
+ return true;
170
+ }
171
+ function checkId(id) {
172
+ if (!(id instanceof Uint8Array) || id.length !== INVOICE_ID_LEN) {
173
+ throw new Error(`invoice id must be exactly ${INVOICE_ID_LEN} bytes`);
174
+ }
175
+ return id;
176
+ }
177
+ // ---------------------------------------------------------------------------
178
+ // pure helpers
179
+ // ---------------------------------------------------------------------------
180
+ /** The fee `pay_invoice` takes from `amount` at `feeBps`: `amount * feeBps /
181
+ * 10_000`, rounded down (the program's u128 `pay_fee`). The payer always pays
182
+ * exactly `amount`; the payee receives `amount - fee`. */
183
+ export function payFee(amount, feeBps) {
184
+ if (typeof amount !== "bigint" || amount < 0n || amount > U64_MAX)
185
+ throw new Error("amount must be a u64 bigint");
186
+ if (!Number.isInteger(feeBps) || feeBps < 0 || feeBps > 0xffff)
187
+ throw new Error("feeBps must be a u16");
188
+ // The program clamps the stored rate to the cap before using it.
189
+ const bps = BigInt(Math.min(feeBps, PAY_FEE_BPS_MAX));
190
+ return (amount * bps) / 10000n;
191
+ }
192
+ /** {@link payFee} as a full split. */
193
+ export function paySplit(amount, feeBps) {
194
+ const fee = payFee(amount, feeBps);
195
+ return { amount, fee, payeeReceives: amount - fee };
196
+ }
197
+ /** 16 fresh random bytes for a new invoice id (CSPRNG). */
198
+ export function newInvoiceId() {
199
+ const c = globalThis.crypto;
200
+ if (!c?.getRandomValues)
201
+ throw new Error("No CSPRNG available (globalThis.crypto.getRandomValues)");
202
+ return c.getRandomValues(new Uint8Array(INVOICE_ID_LEN));
203
+ }
204
+ /** Lowercase hex of an invoice id (the form used in links). */
205
+ export function invoiceIdToHex(id) {
206
+ return Array.from(checkId(id), (b) => b.toString(16).padStart(2, "0")).join("");
207
+ }
208
+ /** Parse a 32-character hex invoice id; null when malformed. */
209
+ export function invoiceIdFromHex(hex) {
210
+ if (typeof hex !== "string" || !/^[0-9a-fA-F]{32}$/.test(hex))
211
+ return null;
212
+ const out = new Uint8Array(INVOICE_ID_LEN);
213
+ for (let i = 0; i < INVOICE_ID_LEN; i++)
214
+ out[i] = parseInt(hex.slice(2 * i, 2 * i + 2), 16);
215
+ return out;
216
+ }
217
+ /** `memo_hash` for an off-chain description: sha256 of its UTF-8 bytes. An
218
+ * empty description hashes to all zeros (no memo). */
219
+ export function invoiceMemoHash(text) {
220
+ if (text === "")
221
+ return new Uint8Array(32);
222
+ return sha256(TE.encode(text));
223
+ }
224
+ // ---------------------------------------------------------------------------
225
+ // PDAs
226
+ // ---------------------------------------------------------------------------
227
+ /** The `["pay_config"]` singleton. */
228
+ export async function findPayConfigPda(find, programId) {
229
+ return find([TE.encode(PAY_CONFIG_SEED)], toBase58(programId, "programId"));
230
+ }
231
+ /** An invoice: `["invoice", payee, id]`. */
232
+ export async function findInvoicePda(find, programId, payee, id) {
233
+ return find([TE.encode(INVOICE_SEED), toBytes32(payee, "payee"), checkId(id)], toBase58(programId, "programId"));
234
+ }
235
+ /** A merchant grant: `["merchant", payee, delegate]`. */
236
+ export async function findMerchantAuthorityPda(find, programId, payee, delegate) {
237
+ return find([TE.encode(MERCHANT_AUTHORITY_SEED), toBytes32(payee, "payee"), toBytes32(delegate, "delegate")], toBase58(programId, "programId"));
238
+ }
239
+ /** A name's X1 payment record, `["record", handle, PAY_X1_COIN_TYPE]`, the
240
+ * `payee_record` account of a handle-bound invoice (existing or not). */
241
+ export async function findPayeeX1RecordPda(find, programId, handle) {
242
+ return find([TE.encode("record"), toBytes32(handle, "handle"), u32le(PAY_X1_COIN_TYPE)], toBase58(programId, "programId"));
243
+ }
244
+ /** Associated token account of `owner` for `mint` under `tokenProgram`. */
245
+ export async function findPayTokenAccount(find, owner, mint, tokenProgram) {
246
+ return find([toBytes32(owner, "owner"), toBytes32(tokenProgram, "tokenProgram"), toBytes32(mint, "mint")], PAY_ASSOCIATED_TOKEN_PROGRAM);
247
+ }
248
+ /** Decode a raw `PayConfig` (discriminator included); null if not one. */
249
+ export function decodePayConfig(data) {
250
+ if (data.length < PAY_CONFIG_ACCOUNT_LEN || !hasPrefix(data, PAY_CONFIG_ACCOUNT_DISCRIMINATOR))
251
+ return null;
252
+ const dv = new DataView(data.buffer, data.byteOffset, data.byteLength);
253
+ return { version: data[8], bump: data[9], feeBps: dv.getUint16(10, true), updatedAt: dv.getBigInt64(12, true) };
254
+ }
255
+ /** Decode a raw `MerchantAuthority` (discriminator included); null if not one. */
256
+ export function decodeMerchantAuthority(data) {
257
+ if (data.length < MERCHANT_AUTHORITY_ACCOUNT_LEN || !hasPrefix(data, MERCHANT_AUTHORITY_ACCOUNT_DISCRIMINATOR)) {
258
+ return null;
259
+ }
260
+ const dv = new DataView(data.buffer, data.byteOffset, data.byteLength);
261
+ return {
262
+ version: data[8],
263
+ payee: encodeBase58(data.slice(9, 41)),
264
+ delegate: encodeBase58(data.slice(41, 73)),
265
+ createdAt: dv.getBigInt64(73, true),
266
+ bump: data[81],
267
+ };
268
+ }
269
+ const STATUS_NAMES = ["open", "paid", "cancelled"];
270
+ /**
271
+ * Decode a raw `Invoice` (discriminator included) in the exact Borsh order
272
+ * of state.rs: version u8 | payee | creator | id [u8;16] | mint | amount u64 |
273
+ * expires_at i64 | status u8 | paid_by | paid_at i64 | handle Option<Pubkey>
274
+ * (1 tag byte, +32 when Some) | memo_hash [u8;32] | rent_payer | bump u8.
275
+ * `handle` is variable-width, so the fields after it move by 32 bytes when
276
+ * the invoice is unbound. Null when the bytes are not an `Invoice`.
277
+ */
278
+ export function decodeInvoice(data) {
279
+ if (!hasPrefix(data, INVOICE_ACCOUNT_DISCRIMINATOR) || data.length < INVOICE_OFFSETS.handle + 1)
280
+ return null;
281
+ const dv = new DataView(data.buffer, data.byteOffset, data.byteLength);
282
+ const pk = (o) => encodeBase58(data.slice(o, o + 32));
283
+ const statusByte = data[INVOICE_OFFSETS.status];
284
+ const status = STATUS_NAMES[statusByte];
285
+ if (status === undefined)
286
+ return null;
287
+ const tag = data[INVOICE_OFFSETS.handle];
288
+ if (tag > 1)
289
+ return null;
290
+ let o = INVOICE_OFFSETS.handle + 1;
291
+ let handle = null;
292
+ if (tag === 1) {
293
+ if (data.length < o + 32)
294
+ return null;
295
+ handle = pk(o);
296
+ o += 32;
297
+ }
298
+ if (data.length < o + 32 + 32 + 1)
299
+ return null;
300
+ const memoHash = data.slice(o, o + 32);
301
+ const rentPayer = pk(o + 32);
302
+ const bump = data[o + 64];
303
+ const mint = pk(INVOICE_OFFSETS.mint);
304
+ return {
305
+ version: data[INVOICE_OFFSETS.version],
306
+ payee: pk(INVOICE_OFFSETS.payee),
307
+ creator: pk(INVOICE_OFFSETS.creator),
308
+ id: data.slice(INVOICE_OFFSETS.id, INVOICE_OFFSETS.id + INVOICE_ID_LEN),
309
+ mint,
310
+ isNative: mint === INVOICE_NATIVE_MINT,
311
+ amount: dv.getBigUint64(INVOICE_OFFSETS.amount, true),
312
+ expiresAt: dv.getBigInt64(INVOICE_OFFSETS.expiresAt, true),
313
+ status,
314
+ paidBy: pk(INVOICE_OFFSETS.paidBy),
315
+ paidAt: dv.getBigInt64(INVOICE_OFFSETS.paidAt, true),
316
+ handle,
317
+ memoHash,
318
+ rentPayer,
319
+ bump,
320
+ };
321
+ }
322
+ /** Borsh-encode an {@link Invoice} exactly as the program stores it (the
323
+ * inverse of {@link decodeInvoice}, padded to the allocated length). Useful
324
+ * for tests, fixtures and mocks. */
325
+ export function encodeInvoice(inv) {
326
+ const status = STATUS_NAMES.indexOf(inv.status);
327
+ if (status < 0)
328
+ throw new Error("bad status");
329
+ const body = concat(INVOICE_ACCOUNT_DISCRIMINATOR, Uint8Array.of(inv.version), toBytes32(inv.payee, "payee"), toBytes32(inv.creator, "creator"), checkId(inv.id), toBytes32(inv.mint, "mint"), u64le(inv.amount), i64le(inv.expiresAt), Uint8Array.of(status), toBytes32(inv.paidBy, "paidBy"), i64le(inv.paidAt), inv.handle === null ? Uint8Array.of(0) : concat(Uint8Array.of(1), toBytes32(inv.handle, "handle")), inv.memoHash, toBytes32(inv.rentPayer, "rentPayer"), Uint8Array.of(inv.bump));
330
+ const out = new Uint8Array(Math.max(INVOICE_ACCOUNT_LEN, body.length));
331
+ out.set(body);
332
+ return out;
333
+ }
334
+ function toSecs(n) {
335
+ return typeof n === "bigint" ? n : BigInt(Math.floor(n));
336
+ }
337
+ /** Whether the invoice is past a non-zero expiry at `nowSecs` (the program's
338
+ * rule: payable while `now < expires_at`). */
339
+ export function isInvoiceExpired(invoice, nowSecs) {
340
+ return invoice.expiresAt !== 0n && toSecs(nowSecs) >= invoice.expiresAt;
341
+ }
342
+ /** The status a person should see at `nowSecs`. */
343
+ export function invoiceStatusLabel(invoice, nowSecs) {
344
+ if (invoice.status === "paid")
345
+ return "Paid";
346
+ if (invoice.status === "cancelled")
347
+ return "Cancelled";
348
+ return isInvoiceExpired(invoice, nowSecs) ? "Expired" : "Open";
349
+ }
350
+ /** Whether `pay_invoice` would accept the invoice at `nowSecs` (Open and not
351
+ * expired). Chain time can lag the local clock, so treat this as a hint. */
352
+ export function isInvoicePayable(invoice, nowSecs) {
353
+ return invoice.status === "open" && !isInvoiceExpired(invoice, nowSecs);
354
+ }
355
+ /**
356
+ * Read and decode an Invoice account. Null when the account does not exist.
357
+ * Throws when it exists but is not an Invoice, or (with `programId`) is not
358
+ * owned by the registry program.
359
+ */
360
+ export async function fetchInvoice(rpc, address, opts = {}) {
361
+ const addr = toBase58(address, "address");
362
+ const res = (await rpc("getAccountInfo", [addr, { encoding: "base64", commitment: opts.commitment ?? "confirmed" }]));
363
+ const v = res?.value;
364
+ if (!v)
365
+ return null;
366
+ if (opts.programId !== undefined && v.owner !== toBase58(opts.programId, "programId")) {
367
+ throw new Error("account is not owned by the registry program");
368
+ }
369
+ const bin = atob(v.data[0]);
370
+ const data = new Uint8Array(bin.length);
371
+ for (let i = 0; i < bin.length; i++)
372
+ data[i] = bin.charCodeAt(i);
373
+ const invoice = decodeInvoice(data);
374
+ if (!invoice)
375
+ throw new Error("account is not an X1ID invoice");
376
+ return { address: addr, invoice, lamports: BigInt(v.lamports) };
377
+ }
378
+ /** Read the live fee rate: the `PayConfig`'s `fee_bps` (clamped to the cap),
379
+ * or 0 when the account does not exist, exactly as `pay_invoice` resolves it. */
380
+ export async function fetchPayFeeBps(rpc, payConfig, programId) {
381
+ const res = (await rpc("getAccountInfo", [
382
+ toBase58(payConfig, "payConfig"),
383
+ { encoding: "base64", commitment: "confirmed" },
384
+ ]));
385
+ const v = res?.value;
386
+ if (!v || v.owner !== toBase58(programId, "programId"))
387
+ return 0;
388
+ const bin = atob(v.data[0]);
389
+ const data = new Uint8Array(bin.length);
390
+ for (let i = 0; i < bin.length; i++)
391
+ data[i] = bin.charCodeAt(i);
392
+ const c = decodePayConfig(data);
393
+ if (!c)
394
+ throw new Error("pay_config account is not a valid PayConfig");
395
+ return Math.min(c.feeBps, PAY_FEE_BPS_MAX);
396
+ }
397
+ function bindingRemaining(b) {
398
+ const out = [];
399
+ if (b.pnftTokenAccount !== undefined)
400
+ out.push(key(b.pnftTokenAccount, "pnftTokenAccount", false, false));
401
+ if (b.namespace !== undefined)
402
+ out.push(key(b.namespace, "namespace", false, false));
403
+ return out;
404
+ }
405
+ /** `set_pay_config(fee_bps)`. Accounts (`SetPayConfig`): admin(s,w), config,
406
+ * pay_config(w), system_program. */
407
+ export function buildSetPayConfigIx(p) {
408
+ if (!Number.isInteger(p.feeBps) || p.feeBps < 0 || p.feeBps > PAY_FEE_BPS_MAX) {
409
+ throw new Error(`feeBps must be an integer between 0 and ${PAY_FEE_BPS_MAX}`);
410
+ }
411
+ return {
412
+ programId: toBase58(p.programId, "programId"),
413
+ keys: [
414
+ key(p.admin, "admin", true, true),
415
+ key(p.config, "config", false, false),
416
+ key(p.payConfig, "payConfig", false, true),
417
+ key(SYSTEM_PROGRAM, "systemProgram", false, false),
418
+ ],
419
+ data: concat(SET_PAY_CONFIG_DISCRIMINATOR, u16le(p.feeBps)),
420
+ };
421
+ }
422
+ /** The Borsh arguments of `create_invoice` (after the discriminator). */
423
+ export function encodeCreateInvoiceArgs(a) {
424
+ if (a.memoHash.length !== 32)
425
+ throw new Error("memoHash must be exactly 32 bytes");
426
+ return concat(checkId(a.id), toBytes32(a.mint, "mint"), u64le(a.amount), i64le(a.expiresAt), a.memoHash, Uint8Array.of(a.bindHandle ? 1 : 0));
427
+ }
428
+ /**
429
+ * `create_invoice(id, mint, amount, expires_at, memo_hash, bind_handle)`.
430
+ * Accounts (`CreateInvoice`): creator(s), rent_payer(s,w), payee, config,
431
+ * invoice(w), system_program, then the optionals merchant_authority, handle,
432
+ * payee_record (program id = "None" where a later one is used), then the
433
+ * binding's remaining accounts.
434
+ */
435
+ export function buildCreateInvoiceIx(p) {
436
+ const programId = toBase58(p.programId, "programId");
437
+ const creator = toBase58(p.creator, "creator");
438
+ const payee = p.payee !== undefined ? toBase58(p.payee, "payee") : creator;
439
+ const rentPayer = p.rentPayer !== undefined ? toBase58(p.rentPayer, "rentPayer") : creator;
440
+ if (typeof p.amount !== "bigint" || p.amount <= 0n || p.amount > U64_MAX) {
441
+ throw new Error("amount must be a u64 bigint greater than zero");
442
+ }
443
+ const expiresAt = p.expiresAt ?? 0n;
444
+ if (typeof expiresAt !== "bigint" || expiresAt < 0n || expiresAt > I64_MAX) {
445
+ throw new Error("expiresAt must be a non-negative i64 bigint (0n = never)");
446
+ }
447
+ if (creator !== payee && p.merchantAuthority === undefined) {
448
+ throw new Error("a creator other than the payee needs merchantAuthority (the payee's grant)");
449
+ }
450
+ const none = () => ({ pubkey: programId, isSigner: false, isWritable: false });
451
+ const keys = [
452
+ key(creator, "creator", true, false),
453
+ key(rentPayer, "rentPayer", true, true),
454
+ key(payee, "payee", false, false),
455
+ key(p.config, "config", false, false),
456
+ key(p.invoice, "invoice", false, true),
457
+ key(SYSTEM_PROGRAM, "systemProgram", false, false),
458
+ ];
459
+ if (p.merchantAuthority !== undefined || p.bindHandle !== undefined) {
460
+ keys.push(p.merchantAuthority !== undefined ? key(p.merchantAuthority, "merchantAuthority", false, false) : none());
461
+ }
462
+ if (p.bindHandle !== undefined) {
463
+ keys.push(key(p.bindHandle.handle, "handle", false, false));
464
+ keys.push(key(p.bindHandle.payeeRecord, "payeeRecord", false, false));
465
+ keys.push(...bindingRemaining(p.bindHandle));
466
+ }
467
+ return {
468
+ programId,
469
+ keys,
470
+ data: concat(CREATE_INVOICE_DISCRIMINATOR, encodeCreateInvoiceArgs({
471
+ id: p.id,
472
+ mint: p.mint ?? INVOICE_NATIVE_MINT,
473
+ amount: p.amount,
474
+ expiresAt,
475
+ memoHash: p.memoHash ?? new Uint8Array(32),
476
+ bindHandle: p.bindHandle !== undefined,
477
+ })),
478
+ };
479
+ }
480
+ /** The Borsh arguments of `pay_invoice` (after the discriminator):
481
+ * `expected_amount: u64`, `expected_mint: Pubkey`. */
482
+ export function encodePayInvoiceArgs(a) {
483
+ if (typeof a.expectedAmount !== "bigint" || a.expectedAmount <= 0n || a.expectedAmount > U64_MAX) {
484
+ throw new Error("expectedAmount must be a u64 bigint greater than zero");
485
+ }
486
+ if (a.expectedMint === undefined || a.expectedMint === null) {
487
+ throw new Error(`expectedMint is required (${INVOICE_NATIVE_MINT} for native XNT)`);
488
+ }
489
+ return concat(u64le(a.expectedAmount), toBytes32(a.expectedMint, "expectedMint"));
490
+ }
491
+ /**
492
+ * `pay_invoice(expected_amount, expected_mint)`. The two arguments pin the
493
+ * terms the payer reviewed: an Open invoice's PDA can be re-initialised with
494
+ * new terms (cancel + close + create of the same id, in one transaction) after
495
+ * the payer signed, and the program then refuses with `InvoiceTermsChanged`
496
+ * instead of charging the new amount (Sentinel x1p-007-01). A
497
+ * discriminator-only payload (SDK < 0.26) fails `InstructionDidNotDeserialize`.
498
+ *
499
+ * Accounts (`PayInvoice`): payer(s,w), invoice(w), payee(w),
500
+ * config, treasury(w), pay_config, system_program, then eight optionals:
501
+ * handle, payee_record, mint, payer_token_account(w), payee_token_account(w),
502
+ * treasury_token_account(w), token_program, associated_token_program; then
503
+ * the binding's remaining accounts. Remaining accounts start after ALL eight
504
+ * optional slots, so a native payment with a binding proof fills the six SPL
505
+ * slots with the "None" placeholder.
506
+ */
507
+ export function buildPayInvoiceIx(p) {
508
+ const programId = toBase58(p.programId, "programId");
509
+ const args = encodePayInvoiceArgs({ expectedAmount: p.expectedAmount, expectedMint: p.expectedMint });
510
+ const native = toBase58(p.expectedMint, "expectedMint") === INVOICE_NATIVE_MINT;
511
+ if (native && p.spl !== undefined)
512
+ throw new Error("a native (XNT) invoice takes no SPL accounts");
513
+ if (!native) {
514
+ if (p.spl === undefined)
515
+ throw new Error("an SPL invoice needs its SPL accounts (spl)");
516
+ if (toBase58(p.spl.mint, "spl.mint") !== toBase58(p.expectedMint, "expectedMint")) {
517
+ throw new Error("spl.mint must equal expectedMint");
518
+ }
519
+ }
520
+ const none = () => ({ pubkey: programId, isSigner: false, isWritable: false });
521
+ const keys = [
522
+ key(p.payer, "payer", true, true),
523
+ key(p.invoice, "invoice", false, true),
524
+ key(p.payee, "payee", false, true),
525
+ key(p.config, "config", false, false),
526
+ key(p.treasury, "treasury", false, true),
527
+ key(p.payConfig, "payConfig", false, false),
528
+ key(SYSTEM_PROGRAM, "systemProgram", false, false),
529
+ ];
530
+ const remaining = p.binding !== undefined ? bindingRemaining(p.binding) : [];
531
+ if (p.binding !== undefined || p.spl !== undefined) {
532
+ if (p.binding !== undefined) {
533
+ keys.push(key(p.binding.handle, "handle", false, false));
534
+ keys.push(key(p.binding.payeeRecord, "payeeRecord", false, false));
535
+ }
536
+ else {
537
+ keys.push(none(), none());
538
+ }
539
+ }
540
+ if (p.spl !== undefined) {
541
+ keys.push(key(p.spl.mint, "mint", false, false), key(p.spl.payerTokenAccount, "payerTokenAccount", false, true), key(p.spl.payeeTokenAccount, "payeeTokenAccount", false, true), key(p.spl.treasuryTokenAccount, "treasuryTokenAccount", false, true), key(p.spl.tokenProgram, "tokenProgram", false, false), key(PAY_ASSOCIATED_TOKEN_PROGRAM, "associatedTokenProgram", false, false));
542
+ }
543
+ else if (remaining.length > 0) {
544
+ for (let i = 0; i < 6; i++)
545
+ keys.push(none());
546
+ }
547
+ keys.push(...remaining);
548
+ return { programId, keys, data: concat(PAY_INVOICE_DISCRIMINATOR, args) };
549
+ }
550
+ /** `cancel_invoice()`. Accounts (`CancelInvoice`): signer(s), invoice(w),
551
+ * then the optional merchant_authority. */
552
+ export function buildCancelInvoiceIx(p) {
553
+ const keys = [key(p.signer, "signer", true, false), key(p.invoice, "invoice", false, true)];
554
+ if (p.merchantAuthority !== undefined)
555
+ keys.push(key(p.merchantAuthority, "merchantAuthority", false, false));
556
+ return { programId: toBase58(p.programId, "programId"), keys, data: CANCEL_INVOICE_DISCRIMINATOR.slice() };
557
+ }
558
+ /** `close_invoice()`. Accounts (`CloseInvoice`): signer(s), invoice(w),
559
+ * rent_payer(w). */
560
+ export function buildCloseInvoiceIx(p) {
561
+ return {
562
+ programId: toBase58(p.programId, "programId"),
563
+ keys: [
564
+ key(p.signer, "signer", true, false),
565
+ key(p.invoice, "invoice", false, true),
566
+ key(p.rentPayer, "rentPayer", false, true),
567
+ ],
568
+ data: CLOSE_INVOICE_DISCRIMINATOR.slice(),
569
+ };
570
+ }
571
+ /** `create_merchant_authority(delegate)`. Accounts
572
+ * (`CreateMerchantAuthority`): payee(s,w), merchant_authority(w), system_program. */
573
+ export function buildCreateMerchantAuthorityIx(p) {
574
+ return {
575
+ programId: toBase58(p.programId, "programId"),
576
+ keys: [
577
+ key(p.payee, "payee", true, true),
578
+ key(p.merchantAuthority, "merchantAuthority", false, true),
579
+ key(SYSTEM_PROGRAM, "systemProgram", false, false),
580
+ ],
581
+ data: concat(CREATE_MERCHANT_AUTHORITY_DISCRIMINATOR, toBytes32(p.delegate, "delegate")),
582
+ };
583
+ }
584
+ /** `revoke_merchant_authority()`. Accounts (`RevokeMerchantAuthority`):
585
+ * payee(s,w), merchant_authority(w). */
586
+ export function buildRevokeMerchantAuthorityIx(p) {
587
+ return {
588
+ programId: toBase58(p.programId, "programId"),
589
+ keys: [key(p.payee, "payee", true, true), key(p.merchantAuthority, "merchantAuthority", false, true)],
590
+ data: REVOKE_MERCHANT_AUTHORITY_DISCRIMINATOR.slice(),
591
+ };
592
+ }
593
+ // ---------------------------------------------------------------------------
594
+ // error helpers
595
+ // ---------------------------------------------------------------------------
596
+ /** Plain-language sentence for a Pay program error code, or null. */
597
+ export function payErrorMessage(code) {
598
+ switch (code) {
599
+ case 6121: return "The fee is above the program's 1% cap.";
600
+ case 6122: return "The fee config account is not the canonical one.";
601
+ case 6123: return "An invoice amount must be greater than zero.";
602
+ case 6124: return "The expiry must be in the future (or none).";
603
+ case 6125: return "Only the payee or one of their authorized merchant keys can do this.";
604
+ case 6126: return "This wallet is not the payment address of that name.";
605
+ case 6127: return "The name on this invoice no longer pays to its payee, so it can't be paid.";
606
+ case 6128: return "The name accounts don't match this invoice.";
607
+ case 6129: return "This invoice is no longer open (it was paid or cancelled).";
608
+ case 6130: return "This invoice has expired.";
609
+ case 6131: return "The token or token accounts don't match this invoice.";
610
+ case 6132: return "The payee account doesn't match this invoice.";
611
+ case 6133: return "The treasury account doesn't match the registry.";
612
+ case 6134: return "Only the payee or whoever paid the rent can close this invoice.";
613
+ case 6135: return "This invoice is still open and not expired, so it can't be closed yet.";
614
+ case 6136: return "This invoice's amount or token changed after you reviewed it. Nothing was charged; reload it before paying.";
615
+ default: return null;
616
+ }
617
+ }
618
+ /**
619
+ * The instruction to SIMULATE (never send) to learn whether the deployed
620
+ * program has Pay: `pay_invoice` with no accounts and all-zero arguments. A
621
+ * Pay-enabled program recognizes the discriminator, decodes the arguments and
622
+ * fails account validation (3005 `AccountNotEnoughKeys`); an older one answers
623
+ * `InstructionFallbackNotFound` (101). Read the simulation's `err` with
624
+ * {@link classifyPayProbe}.
625
+ *
626
+ * Simulate it with `sigVerify: false`, `replaceRecentBlockhash: true` and a
627
+ * fee payer that EXISTS and is SYSTEM-owned (the connected wallet, or a fixed
628
+ * funded system account). A program-owned fee payer such as a multisig
629
+ * treasury fails the whole transaction (`InvalidAccountForFee`) before the
630
+ * probe runs, which says nothing about Pay.
631
+ */
632
+ export function buildPayProbeIx(programId) {
633
+ return {
634
+ programId: toBase58(programId, "programId"),
635
+ keys: [],
636
+ data: concat(PAY_INVOICE_DISCRIMINATOR, new Uint8Array(8 + 32)),
637
+ };
638
+ }
639
+ /**
640
+ * Classify a probe simulation's `value.err` (probe at `probeIndex`). Only an
641
+ * `InstructionError` AT the probe index is evidence: `Custom 101` =
642
+ * "not-deployed", any other custom code = "live". Everything else is
643
+ * "unknown".
644
+ */
645
+ export function classifyPayProbe(err, probeIndex) {
646
+ if (!err || typeof err !== "object")
647
+ return "unknown";
648
+ const ie = err.InstructionError;
649
+ if (!Array.isArray(ie) || ie.length !== 2 || ie[0] !== probeIndex)
650
+ return "unknown";
651
+ const detail = ie[1];
652
+ if (typeof detail === "object" && detail !== null && typeof detail.Custom === "number") {
653
+ return detail.Custom === PAY_NOT_DEPLOYED_ERROR ? "not-deployed" : "live";
654
+ }
655
+ return "unknown";
656
+ }
657
+ /**
658
+ * @deprecated Two-state view of {@link classifyPayProbe}: `true` only for
659
+ * "live". It cannot tell "not deployed" from "the simulation said nothing"
660
+ * (e.g. a program-owned fee payer); use {@link classifyPayProbe}.
661
+ */
662
+ export function interpretPayProbe(err, probeIndex) {
663
+ return classifyPayProbe(err, probeIndex) === "live";
664
+ }
665
+ /** Whether a simulation / transaction error says the program does not know
666
+ * Pay instructions yet (Anchor `InstructionFallbackNotFound`, code 101). */
667
+ export function isPayNotDeployedError(err) {
668
+ if (!err || typeof err !== "object")
669
+ return false;
670
+ const ie = err.InstructionError;
671
+ if (!Array.isArray(ie) || ie.length !== 2)
672
+ return false;
673
+ const detail = ie[1];
674
+ return typeof detail === "object" && detail !== null && detail.Custom === PAY_NOT_DEPLOYED_ERROR;
675
+ }
676
+ function toExpectedAmount(v) {
677
+ let n;
678
+ if (typeof v === "bigint")
679
+ n = v;
680
+ else if (typeof v === "number") {
681
+ if (!Number.isSafeInteger(v))
682
+ throw new Error("amount must be an integer number of base units (use a bigint for large values)");
683
+ n = BigInt(v);
684
+ }
685
+ else if (typeof v === "string" && /^[0-9]{1,20}$/.test(v))
686
+ n = BigInt(v);
687
+ else
688
+ throw new Error("amount must be base units: a bigint, a safe integer or a decimal string");
689
+ if (n <= 0n || n > U64_MAX)
690
+ throw new Error("amount must be a u64 greater than zero");
691
+ return n;
692
+ }
693
+ /**
694
+ * MERCHANT SERVER: is `invoice` paid, on chain, with exactly the terms you
695
+ * expect? This is the check that releases goods. A checkout widget result,
696
+ * a redirect or a webhook is only a hint to call this.
697
+ *
698
+ * Reads the Invoice account (one `getAccountInfo`) and returns `paid: true`
699
+ * only when ALL hold: the account is owned by the registry program, decodes
700
+ * as an Invoice, its payee, mint, amount (and `id`, when given) equal what you
701
+ * expect, and its status is Paid. The program moved the funds payer → payee in
702
+ * the same instruction that set Paid, so Paid means paid.
703
+ *
704
+ * Pass the invoice address and terms from YOUR database (the ones you used to
705
+ * create the invoice), never from the browser. Mark the order fulfilled once
706
+ * and idempotently: after payment the invoice can be closed, and a closed
707
+ * invoice reads as `not-found`.
708
+ *
709
+ * `rpc` is an RPC URL or an {@link RpcFn}. Throws on RPC/transport errors
710
+ * (retry) and on malformed arguments; every on-chain answer is a result.
711
+ */
712
+ export async function verifyInvoicePaid(rpc, invoice, expect) {
713
+ const call = typeof rpc === "string" ? makeAccountReader(rpc).rpc : rpc;
714
+ const address = toBase58(invoice, "invoice");
715
+ const programId = toBase58(expect.programId ?? DEFAULT_HANDLE_PROGRAM, "programId");
716
+ const payee = toBase58(expect.payee, "payee");
717
+ const mint = toBase58(expect.mint ?? INVOICE_NATIVE_MINT, "mint");
718
+ const amount = toExpectedAmount(expect.amount);
719
+ let id = null;
720
+ if (expect.id !== undefined) {
721
+ id = typeof expect.id === "string" ? invoiceIdFromHex(expect.id) : checkId(expect.id);
722
+ if (!id)
723
+ throw new Error("id must be 16 bytes or 32 hex characters");
724
+ }
725
+ const commitment = expect.commitment ?? "confirmed";
726
+ if (commitment !== "confirmed" && commitment !== "finalized")
727
+ throw new Error('commitment must be "confirmed" or "finalized"');
728
+ const res = (await call("getAccountInfo", [address, { encoding: "base64", commitment }]));
729
+ const v = res?.value;
730
+ const fail = (reason, status) => status === undefined ? { paid: false, invoice: address, reason } : { paid: false, invoice: address, reason, status };
731
+ if (!v)
732
+ return fail("not-found");
733
+ if (v.owner !== programId)
734
+ return fail("wrong-program");
735
+ const raw = v.data;
736
+ if (!Array.isArray(raw) || raw[1] !== "base64" || typeof raw[0] !== "string")
737
+ return fail("not-invoice");
738
+ const bin = atob(raw[0]);
739
+ const data = new Uint8Array(bin.length);
740
+ for (let i = 0; i < bin.length; i++)
741
+ data[i] = bin.charCodeAt(i);
742
+ const inv = decodeInvoice(data);
743
+ if (!inv)
744
+ return fail("not-invoice");
745
+ if (inv.payee !== payee)
746
+ return fail("wrong-payee", inv.status);
747
+ if (inv.mint !== mint)
748
+ return fail("wrong-mint", inv.status);
749
+ if (inv.amount !== amount)
750
+ return fail("wrong-amount", inv.status);
751
+ if (id && !id.every((b, i) => b === inv.id[i]))
752
+ return fail("wrong-id", inv.status);
753
+ if (inv.status !== "paid")
754
+ return fail("not-paid", inv.status);
755
+ return { paid: true, invoice: address, paidBy: inv.paidBy, paidAt: inv.paidAt, amount: inv.amount, mint: inv.mint, payee: inv.payee };
756
+ }