@bujius/checkout-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/dist/index.cjs ADDED
@@ -0,0 +1,913 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ BujiusCheckoutError: () => BujiusCheckoutError,
24
+ ErrorCode: () => ErrorCode,
25
+ PROGRAM_ID: () => PROGRAM_ID,
26
+ buildCreateSplit: () => buildCreateSplit,
27
+ buildSplitPayment: () => buildSplitPayment,
28
+ computeSplit: () => computeSplit,
29
+ createTransactionRequestHandler: () => createTransactionRequestHandler,
30
+ fetchSplitConfig: () => fetchSplitConfig,
31
+ findPayment: () => findPayment,
32
+ findSplitConfig: () => findSplitConfig,
33
+ fromBaseUnits: () => fromBaseUnits,
34
+ isBujiusCheckoutError: () => isBujiusCheckoutError,
35
+ newReference: () => newReference,
36
+ paymentUrl: () => paymentUrl,
37
+ toBaseUnits: () => toBaseUnits,
38
+ verifySplitPayment: () => verifySplitPayment
39
+ });
40
+ module.exports = __toCommonJS(index_exports);
41
+
42
+ // src/errors.ts
43
+ var ErrorCode = {
44
+ ORDER_NOT_FOUND: "ORDER_NOT_FOUND",
45
+ ORDER_EXPIRED: "ORDER_EXPIRED",
46
+ ORDER_PAID: "ORDER_PAID",
47
+ ORDER_HELD: "ORDER_HELD",
48
+ INVALID_ACCOUNT: "INVALID_ACCOUNT",
49
+ INVALID_AMOUNT: "INVALID_AMOUNT",
50
+ CONFIG_NOT_FOUND: "CONFIG_NOT_FOUND",
51
+ RPC_UNAVAILABLE: "RPC_UNAVAILABLE"
52
+ };
53
+ var BujiusCheckoutError = class extends Error {
54
+ code;
55
+ constructor(code, options) {
56
+ super(code, options);
57
+ this.name = "BujiusCheckoutError";
58
+ this.code = code;
59
+ }
60
+ };
61
+ function isBujiusCheckoutError(value) {
62
+ return value instanceof BujiusCheckoutError;
63
+ }
64
+
65
+ // src/program/pda.ts
66
+ var import_web3 = require("@solana/web3.js");
67
+
68
+ // src/program/idl.json
69
+ var idl_default = {
70
+ address: "LNv2EvYZFN1fBJeQPDGffRxVYPMCGpa99yCcWDSDdjN",
71
+ metadata: {
72
+ name: "bujius_checkout",
73
+ version: "0.1.0",
74
+ spec: "0.1.0",
75
+ description: "Created with Anchor"
76
+ },
77
+ instructions: [
78
+ {
79
+ name: "create_split",
80
+ discriminator: [
81
+ 196,
82
+ 195,
83
+ 250,
84
+ 188,
85
+ 17,
86
+ 255,
87
+ 10,
88
+ 151
89
+ ],
90
+ accounts: [
91
+ {
92
+ name: "authority",
93
+ writable: true,
94
+ signer: true
95
+ },
96
+ {
97
+ name: "config",
98
+ writable: true,
99
+ pda: {
100
+ seeds: [
101
+ {
102
+ kind: "const",
103
+ value: [
104
+ 115,
105
+ 112,
106
+ 108,
107
+ 105,
108
+ 116
109
+ ]
110
+ },
111
+ {
112
+ kind: "account",
113
+ path: "authority"
114
+ },
115
+ {
116
+ kind: "arg",
117
+ path: "id"
118
+ }
119
+ ]
120
+ }
121
+ },
122
+ {
123
+ name: "mint",
124
+ docs: [
125
+ "Classic SPL Token mint only (Token-2022 is out of scope)."
126
+ ]
127
+ },
128
+ {
129
+ name: "system_program",
130
+ address: "11111111111111111111111111111111"
131
+ }
132
+ ],
133
+ args: [
134
+ {
135
+ name: "id",
136
+ type: "u16"
137
+ },
138
+ {
139
+ name: "recipients",
140
+ type: {
141
+ vec: {
142
+ defined: {
143
+ name: "Recipient"
144
+ }
145
+ }
146
+ }
147
+ }
148
+ ]
149
+ },
150
+ {
151
+ name: "pay",
152
+ discriminator: [
153
+ 119,
154
+ 18,
155
+ 216,
156
+ 65,
157
+ 192,
158
+ 117,
159
+ 122,
160
+ 220
161
+ ],
162
+ accounts: [
163
+ {
164
+ name: "payer",
165
+ signer: true
166
+ },
167
+ {
168
+ name: "payer_token",
169
+ writable: true
170
+ },
171
+ {
172
+ name: "config"
173
+ },
174
+ {
175
+ name: "mint"
176
+ },
177
+ {
178
+ name: "merchant_token",
179
+ writable: true
180
+ },
181
+ {
182
+ name: "reference"
183
+ },
184
+ {
185
+ name: "token_program",
186
+ address: "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA"
187
+ }
188
+ ],
189
+ args: [
190
+ {
191
+ name: "amount",
192
+ type: "u64"
193
+ }
194
+ ]
195
+ }
196
+ ],
197
+ accounts: [
198
+ {
199
+ name: "SplitConfig",
200
+ discriminator: [
201
+ 49,
202
+ 201,
203
+ 50,
204
+ 228,
205
+ 22,
206
+ 142,
207
+ 12,
208
+ 222
209
+ ]
210
+ }
211
+ ],
212
+ events: [
213
+ {
214
+ name: "SplitCreated",
215
+ discriminator: [
216
+ 87,
217
+ 61,
218
+ 160,
219
+ 84,
220
+ 73,
221
+ 204,
222
+ 93,
223
+ 140
224
+ ]
225
+ },
226
+ {
227
+ name: "SplitPaid",
228
+ discriminator: [
229
+ 223,
230
+ 17,
231
+ 5,
232
+ 250,
233
+ 228,
234
+ 37,
235
+ 72,
236
+ 80
237
+ ]
238
+ }
239
+ ],
240
+ errors: [
241
+ {
242
+ code: 6e3,
243
+ name: "InvalidRecipientCount",
244
+ msg: "A split config needs between 1 and 4 recipients"
245
+ },
246
+ {
247
+ code: 6001,
248
+ name: "ZeroBps",
249
+ msg: "Every recipient needs at least 1 bps"
250
+ },
251
+ {
252
+ code: 6002,
253
+ name: "SplitOverflow",
254
+ msg: "The recipients' bps add up to more than 10 000"
255
+ },
256
+ {
257
+ code: 6003,
258
+ name: "DuplicateRecipient",
259
+ msg: "Recipient wallets must be distinct and different from the merchant"
260
+ },
261
+ {
262
+ code: 6004,
263
+ name: "ZeroAmount",
264
+ msg: "The amount must be greater than zero"
265
+ },
266
+ {
267
+ code: 6005,
268
+ name: "MintMismatch",
269
+ msg: "The mint does not match the split config's mint"
270
+ },
271
+ {
272
+ code: 6006,
273
+ name: "BadTokenAccount",
274
+ msg: "A token account is not the expected associated token account"
275
+ },
276
+ {
277
+ code: 6007,
278
+ name: "RecipientCountMismatch",
279
+ msg: "The number of remaining accounts does not match the recipients"
280
+ },
281
+ {
282
+ code: 6008,
283
+ name: "MathOverflow",
284
+ msg: "Arithmetic overflow"
285
+ }
286
+ ],
287
+ types: [
288
+ {
289
+ name: "Recipient",
290
+ type: {
291
+ kind: "struct",
292
+ fields: [
293
+ {
294
+ name: "wallet",
295
+ type: "pubkey"
296
+ },
297
+ {
298
+ name: "bps",
299
+ type: "u16"
300
+ }
301
+ ]
302
+ }
303
+ },
304
+ {
305
+ name: "SplitConfig",
306
+ docs: [
307
+ "One per merchant per `id`; immutable after creation."
308
+ ],
309
+ type: {
310
+ kind: "struct",
311
+ fields: [
312
+ {
313
+ name: "authority",
314
+ type: "pubkey"
315
+ },
316
+ {
317
+ name: "id",
318
+ type: "u16"
319
+ },
320
+ {
321
+ name: "mint",
322
+ type: "pubkey"
323
+ },
324
+ {
325
+ name: "recipients",
326
+ type: {
327
+ vec: {
328
+ defined: {
329
+ name: "Recipient"
330
+ }
331
+ }
332
+ }
333
+ },
334
+ {
335
+ name: "bump",
336
+ type: "u8"
337
+ }
338
+ ]
339
+ }
340
+ },
341
+ {
342
+ name: "SplitCreated",
343
+ type: {
344
+ kind: "struct",
345
+ fields: [
346
+ {
347
+ name: "config",
348
+ type: "pubkey"
349
+ },
350
+ {
351
+ name: "authority",
352
+ type: "pubkey"
353
+ },
354
+ {
355
+ name: "mint",
356
+ type: "pubkey"
357
+ },
358
+ {
359
+ name: "recipients",
360
+ type: {
361
+ vec: {
362
+ defined: {
363
+ name: "Recipient"
364
+ }
365
+ }
366
+ }
367
+ }
368
+ ]
369
+ }
370
+ },
371
+ {
372
+ name: "SplitPaid",
373
+ type: {
374
+ kind: "struct",
375
+ fields: [
376
+ {
377
+ name: "config",
378
+ type: "pubkey"
379
+ },
380
+ {
381
+ name: "payer",
382
+ type: "pubkey"
383
+ },
384
+ {
385
+ name: "mint",
386
+ type: "pubkey"
387
+ },
388
+ {
389
+ name: "reference",
390
+ type: "pubkey"
391
+ },
392
+ {
393
+ name: "amount",
394
+ type: "u64"
395
+ },
396
+ {
397
+ name: "merchant_amount",
398
+ type: "u64"
399
+ },
400
+ {
401
+ name: "shares",
402
+ type: {
403
+ vec: "u64"
404
+ }
405
+ },
406
+ {
407
+ name: "ts",
408
+ type: "i64"
409
+ }
410
+ ]
411
+ }
412
+ }
413
+ ]
414
+ };
415
+
416
+ // src/program/pda.ts
417
+ var PROGRAM_ID = new import_web3.PublicKey(idl_default.address);
418
+ function findSplitConfig(authority, id) {
419
+ const idBytes = new Uint8Array(2);
420
+ new DataView(idBytes.buffer).setUint16(0, id, true);
421
+ return import_web3.PublicKey.findProgramAddressSync(
422
+ [new TextEncoder().encode("split"), authority.toBytes(), idBytes],
423
+ PROGRAM_ID
424
+ )[0];
425
+ }
426
+
427
+ // src/program/config.ts
428
+ var import_anchor = require("@coral-xyz/anchor");
429
+ var import_web32 = require("@solana/web3.js");
430
+ var coder = new import_anchor.BorshAccountsCoder(idl_default);
431
+ async function fetchSplitConfig(connection, address) {
432
+ let info;
433
+ try {
434
+ info = await connection.getAccountInfo(address, "confirmed");
435
+ } catch {
436
+ throw new BujiusCheckoutError(ErrorCode.RPC_UNAVAILABLE);
437
+ }
438
+ if (!info || !info.owner.equals(PROGRAM_ID)) {
439
+ throw new BujiusCheckoutError(ErrorCode.CONFIG_NOT_FOUND);
440
+ }
441
+ const decoded = coder.decode("SplitConfig", info.data);
442
+ return {
443
+ address,
444
+ authority: decoded.authority,
445
+ id: decoded.id,
446
+ mint: decoded.mint,
447
+ recipients: decoded.recipients.map((r) => ({
448
+ wallet: r.wallet,
449
+ bps: r.bps
450
+ }))
451
+ };
452
+ }
453
+
454
+ // src/program/create-split.ts
455
+ var import_anchor2 = require("@coral-xyz/anchor");
456
+ var import_spl_token = require("@solana/spl-token");
457
+ var import_web33 = require("@solana/web3.js");
458
+ var coder2 = new import_anchor2.BorshInstructionCoder(idl_default);
459
+ async function buildCreateSplit(connection, authority, id, mint, recipients) {
460
+ const tx = new import_web33.Transaction();
461
+ for (const owner of [authority, ...recipients.map((r) => r.wallet)]) {
462
+ tx.add(
463
+ (0, import_spl_token.createAssociatedTokenAccountIdempotentInstruction)(
464
+ authority,
465
+ (0, import_spl_token.getAssociatedTokenAddressSync)(mint, owner),
466
+ owner,
467
+ mint
468
+ )
469
+ );
470
+ }
471
+ tx.add(
472
+ new import_web33.TransactionInstruction({
473
+ programId: PROGRAM_ID,
474
+ keys: [
475
+ { pubkey: authority, isSigner: true, isWritable: true },
476
+ { pubkey: findSplitConfig(authority, id), isSigner: false, isWritable: true },
477
+ { pubkey: mint, isSigner: false, isWritable: false },
478
+ { pubkey: import_web33.SystemProgram.programId, isSigner: false, isWritable: false }
479
+ ],
480
+ data: coder2.encode("create_split", { id, recipients })
481
+ })
482
+ );
483
+ const { blockhash, lastValidBlockHeight } = await connection.getLatestBlockhash("confirmed");
484
+ tx.feePayer = authority;
485
+ tx.recentBlockhash = blockhash;
486
+ tx.lastValidBlockHeight = lastValidBlockHeight;
487
+ return tx;
488
+ }
489
+
490
+ // src/core/amounts.ts
491
+ function toBaseUnits(decimal, decimals) {
492
+ if (typeof decimal !== "string" || decimal.length === 0) {
493
+ throw new BujiusCheckoutError(ErrorCode.INVALID_AMOUNT);
494
+ }
495
+ if (!Number.isInteger(decimals) || decimals < 0) {
496
+ throw new BujiusCheckoutError(ErrorCode.INVALID_AMOUNT);
497
+ }
498
+ if (!/^\d+(\.\d+)?$/.test(decimal)) {
499
+ throw new BujiusCheckoutError(ErrorCode.INVALID_AMOUNT);
500
+ }
501
+ const [wholePart, fractionPart = ""] = decimal.split(".");
502
+ if (fractionPart.length > decimals) {
503
+ throw new BujiusCheckoutError(ErrorCode.INVALID_AMOUNT);
504
+ }
505
+ return BigInt(`${wholePart}${fractionPart.padEnd(decimals, "0")}`);
506
+ }
507
+ function fromBaseUnits(amount, decimals) {
508
+ if (typeof amount !== "bigint" || amount < 0n) {
509
+ throw new BujiusCheckoutError(ErrorCode.INVALID_AMOUNT);
510
+ }
511
+ if (!Number.isInteger(decimals) || decimals < 0) {
512
+ throw new BujiusCheckoutError(ErrorCode.INVALID_AMOUNT);
513
+ }
514
+ const raw = amount.toString();
515
+ if (decimals === 0) {
516
+ return raw;
517
+ }
518
+ const padded = raw.padStart(decimals + 1, "0");
519
+ const whole = padded.slice(0, -decimals);
520
+ const fraction = padded.slice(-decimals);
521
+ const trimmedFraction = fraction.replace(/0+$/, "");
522
+ return trimmedFraction.length === 0 ? whole : `${whole}.${trimmedFraction}`;
523
+ }
524
+
525
+ // src/core/reference.ts
526
+ var import_web34 = require("@solana/web3.js");
527
+ function newReference() {
528
+ return import_web34.Keypair.generate().publicKey;
529
+ }
530
+
531
+ // src/core/url.ts
532
+ var import_pay = require("@solana/pay");
533
+ function paymentUrl(handlerHttpsUrl) {
534
+ return (0, import_pay.encodeURL)({ link: new URL(handlerHttpsUrl) }).toString();
535
+ }
536
+
537
+ // src/core/split-math.ts
538
+ function computeSplit(amount, bps) {
539
+ if (amount < 0n) {
540
+ throw new BujiusCheckoutError(ErrorCode.INVALID_AMOUNT);
541
+ }
542
+ const validBps = bps.every((b) => Number.isInteger(b) && b >= 0);
543
+ if (!validBps || bps.reduce((total, b) => total + b, 0) > 1e4) {
544
+ throw new BujiusCheckoutError(ErrorCode.CONFIG_NOT_FOUND);
545
+ }
546
+ const shares = bps.map((b) => amount * BigInt(b) / 10000n);
547
+ let sum = 0n;
548
+ for (const share of shares) {
549
+ sum += share;
550
+ }
551
+ return { shares, merchant: amount - sum };
552
+ }
553
+
554
+ // src/strategies/transfers.ts
555
+ var import_spl_token3 = require("@solana/spl-token");
556
+ var import_web36 = require("@solana/web3.js");
557
+
558
+ // src/strategies/program.ts
559
+ var import_anchor3 = require("@coral-xyz/anchor");
560
+ var import_spl_token2 = require("@solana/spl-token");
561
+ var import_web35 = require("@solana/web3.js");
562
+ var coder3 = new import_anchor3.BorshInstructionCoder(idl_default);
563
+ async function buildProgramPayment(connection, config, payer, amount, reference) {
564
+ const { shares, merchant } = computeSplit(
565
+ amount,
566
+ config.recipients.map((r) => r.bps)
567
+ );
568
+ const ataOf = (wallet) => (0, import_spl_token2.getAssociatedTokenAddressSync)(config.mint, wallet, true);
569
+ const merchantToken = ataOf(config.authority);
570
+ const recipientAtas = config.recipients.map((r) => ataOf(r.wallet));
571
+ const source = ataOf(payer);
572
+ const funded = [
573
+ ...config.recipients.map((r, i) => ({ wallet: r.wallet, ata: recipientAtas[i], amount: shares[i] })).filter((leg) => leg.amount > 0n),
574
+ ...merchant > 0n ? [{ wallet: config.authority, ata: merchantToken, amount: merchant }] : []
575
+ ];
576
+ let infos;
577
+ try {
578
+ infos = await connection.getMultipleAccountsInfo(
579
+ [...funded.map((leg) => leg.ata), source],
580
+ "confirmed"
581
+ );
582
+ } catch {
583
+ throw new BujiusCheckoutError(ErrorCode.RPC_UNAVAILABLE);
584
+ }
585
+ if (!infos[funded.length]) {
586
+ throw new BujiusCheckoutError(ErrorCode.INVALID_ACCOUNT);
587
+ }
588
+ const tx = new import_web35.Transaction();
589
+ for (let i = 0; i < funded.length; i++) {
590
+ if (infos[i] == null) {
591
+ const leg = funded[i];
592
+ tx.add(
593
+ (0, import_spl_token2.createAssociatedTokenAccountIdempotentInstruction)(payer, leg.ata, leg.wallet, config.mint)
594
+ );
595
+ }
596
+ }
597
+ tx.add(
598
+ new import_web35.TransactionInstruction({
599
+ programId: PROGRAM_ID,
600
+ keys: [
601
+ { pubkey: payer, isSigner: true, isWritable: false },
602
+ { pubkey: source, isSigner: false, isWritable: true },
603
+ { pubkey: config.address, isSigner: false, isWritable: false },
604
+ { pubkey: config.mint, isSigner: false, isWritable: false },
605
+ { pubkey: merchantToken, isSigner: false, isWritable: true },
606
+ { pubkey: reference, isSigner: false, isWritable: false },
607
+ { pubkey: import_spl_token2.TOKEN_PROGRAM_ID, isSigner: false, isWritable: false },
608
+ // remaining accounts, in config.recipients order
609
+ ...recipientAtas.map((pubkey) => ({ pubkey, isSigner: false, isWritable: true }))
610
+ ],
611
+ data: coder3.encode("pay", { amount: new import_anchor3.BN(amount.toString()) })
612
+ })
613
+ );
614
+ let blockhash;
615
+ let lastValidBlockHeight;
616
+ try {
617
+ ({ blockhash, lastValidBlockHeight } = await connection.getLatestBlockhash("confirmed"));
618
+ } catch {
619
+ throw new BujiusCheckoutError(ErrorCode.RPC_UNAVAILABLE);
620
+ }
621
+ tx.feePayer = payer;
622
+ tx.recentBlockhash = blockhash;
623
+ tx.lastValidBlockHeight = lastValidBlockHeight;
624
+ return tx;
625
+ }
626
+
627
+ // src/strategies/transfers.ts
628
+ var U64_MAX = 18446744073709551615n;
629
+ async function buildSplitPayment(connection, config, payer, amount, reference, strategy) {
630
+ if (amount <= 0n || amount > U64_MAX) {
631
+ throw new BujiusCheckoutError(ErrorCode.INVALID_AMOUNT);
632
+ }
633
+ if (!import_web36.PublicKey.isOnCurve(payer) || payer.equals(config.authority) || config.recipients.some((r) => r.wallet.equals(payer))) {
634
+ throw new BujiusCheckoutError(ErrorCode.INVALID_ACCOUNT);
635
+ }
636
+ if (strategy === "program") {
637
+ return buildProgramPayment(connection, config, payer, amount, reference);
638
+ }
639
+ if (strategy !== "transfers") {
640
+ throw new Error(`unknown payment strategy: ${String(strategy)}`);
641
+ }
642
+ return buildTransfersPayment(connection, config, payer, amount, reference);
643
+ }
644
+ async function buildTransfersPayment(connection, config, payer, amount, reference) {
645
+ const { shares, merchant } = computeSplit(
646
+ amount,
647
+ config.recipients.map((r) => r.bps)
648
+ );
649
+ const legs = [];
650
+ for (let i = 0; i < config.recipients.length; i++) {
651
+ const share = shares[i];
652
+ if (share > 0n) {
653
+ legs.push({ wallet: config.recipients[i].wallet, amount: share });
654
+ }
655
+ }
656
+ if (merchant > 0n) {
657
+ legs.push({ wallet: config.authority, amount: merchant });
658
+ }
659
+ const ataOf = (wallet) => (0, import_spl_token3.getAssociatedTokenAddressSync)(config.mint, wallet, true);
660
+ const destAtas = legs.map((leg) => ataOf(leg.wallet));
661
+ const source = ataOf(payer);
662
+ let infos;
663
+ try {
664
+ infos = await connection.getMultipleAccountsInfo(
665
+ [...destAtas, config.mint, source],
666
+ "confirmed"
667
+ );
668
+ } catch {
669
+ throw new BujiusCheckoutError(ErrorCode.RPC_UNAVAILABLE);
670
+ }
671
+ const mintInfo = infos[destAtas.length];
672
+ const sourceInfo = infos[destAtas.length + 1];
673
+ if (!mintInfo || !sourceInfo) {
674
+ throw new BujiusCheckoutError(ErrorCode.INVALID_ACCOUNT);
675
+ }
676
+ let decimals;
677
+ try {
678
+ decimals = (0, import_spl_token3.unpackMint)(config.mint, mintInfo).decimals;
679
+ } catch (cause) {
680
+ throw new BujiusCheckoutError(ErrorCode.INVALID_ACCOUNT, { cause });
681
+ }
682
+ const tx = new import_web36.Transaction();
683
+ for (let i = 0; i < destAtas.length; i++) {
684
+ if (infos[i] == null) {
685
+ const leg = legs[i];
686
+ const ata = destAtas[i];
687
+ tx.add(
688
+ (0, import_spl_token3.createAssociatedTokenAccountIdempotentInstruction)(
689
+ payer,
690
+ ata,
691
+ leg.wallet,
692
+ config.mint
693
+ )
694
+ );
695
+ }
696
+ }
697
+ let firstTransfer = true;
698
+ for (let i = 0; i < legs.length; i++) {
699
+ const leg = legs[i];
700
+ const destination = destAtas[i];
701
+ const ix = (0, import_spl_token3.createTransferCheckedInstruction)(
702
+ source,
703
+ config.mint,
704
+ destination,
705
+ payer,
706
+ leg.amount,
707
+ decimals
708
+ );
709
+ if (firstTransfer) {
710
+ ix.keys.push({ pubkey: reference, isSigner: false, isWritable: false });
711
+ firstTransfer = false;
712
+ }
713
+ tx.add(ix);
714
+ }
715
+ let blockhash;
716
+ let lastValidBlockHeight;
717
+ try {
718
+ ({ blockhash, lastValidBlockHeight } = await connection.getLatestBlockhash("confirmed"));
719
+ } catch {
720
+ throw new BujiusCheckoutError(ErrorCode.RPC_UNAVAILABLE);
721
+ }
722
+ tx.feePayer = payer;
723
+ tx.recentBlockhash = blockhash;
724
+ tx.lastValidBlockHeight = lastValidBlockHeight;
725
+ return tx;
726
+ }
727
+
728
+ // src/verify/find-payment.ts
729
+ async function findPayment(connection, reference) {
730
+ let signatures;
731
+ try {
732
+ signatures = await connection.getSignaturesForAddress(reference, void 0, "confirmed");
733
+ } catch {
734
+ throw new BujiusCheckoutError(ErrorCode.RPC_UNAVAILABLE);
735
+ }
736
+ const successful = signatures.filter((s) => s.err === null);
737
+ return successful.length > 0 ? successful[successful.length - 1].signature : null;
738
+ }
739
+
740
+ // src/verify/verify-payment.ts
741
+ function totals(balances, mint) {
742
+ const byOwner = /* @__PURE__ */ new Map();
743
+ for (const b of balances ?? []) {
744
+ if (b.mint !== mint || !b.owner) continue;
745
+ byOwner.set(b.owner, (byOwner.get(b.owner) ?? 0n) + BigInt(b.uiTokenAmount.amount));
746
+ }
747
+ return byOwner;
748
+ }
749
+ async function verifySplitPayment(connection, signature, config, amount, reference, expiresAt) {
750
+ const expected = computeSplit(
751
+ amount,
752
+ config.recipients.map((r) => r.bps)
753
+ );
754
+ const wallets = [...config.recipients.map((r) => r.wallet), config.authority];
755
+ const expectedAmounts = [...expected.shares, expected.merchant];
756
+ const invalid = (reason, legs2 = []) => ({
757
+ status: "invalid",
758
+ reason,
759
+ legs: legs2
760
+ });
761
+ let tx;
762
+ try {
763
+ tx = await connection.getParsedTransaction(signature, {
764
+ commitment: "confirmed",
765
+ maxSupportedTransactionVersion: 0
766
+ });
767
+ } catch {
768
+ throw new BujiusCheckoutError(ErrorCode.RPC_UNAVAILABLE);
769
+ }
770
+ if (!tx || !tx.meta) return invalid("transaction not found");
771
+ if (tx.meta.err !== null) return invalid("transaction failed");
772
+ const referenceKey = reference.toBase58();
773
+ if (!tx.transaction.message.accountKeys.some((k) => String(k.pubkey) === referenceKey)) {
774
+ return invalid("reference not in the transaction");
775
+ }
776
+ const mint = config.mint.toBase58();
777
+ const pre = totals(tx.meta.preTokenBalances, mint);
778
+ const post = totals(tx.meta.postTokenBalances, mint);
779
+ const legs = wallets.map((wallet) => {
780
+ const key = wallet.toBase58();
781
+ return { wallet, amount: (post.get(key) ?? 0n) - (pre.get(key) ?? 0n) };
782
+ });
783
+ for (let i = 0; i < legs.length; i++) {
784
+ if (legs[i].amount !== expectedAmounts[i]) {
785
+ return invalid(
786
+ `${legs[i].wallet.toBase58()} received ${legs[i].amount}, expected ${expectedAmounts[i]}`,
787
+ legs
788
+ );
789
+ }
790
+ }
791
+ const late = expiresAt !== void 0 && tx.blockTime != null && tx.blockTime * 1e3 > expiresAt.getTime();
792
+ return { status: late ? "paid_late" : "paid", legs };
793
+ }
794
+
795
+ // src/server/handler.ts
796
+ var import_web37 = require("@solana/web3.js");
797
+ var CORS_HEADERS = {
798
+ "Access-Control-Allow-Origin": "*",
799
+ "Access-Control-Allow-Methods": "GET, POST, OPTIONS",
800
+ "Access-Control-Allow-Headers": "Content-Type"
801
+ };
802
+ var ERROR_RESPONSES = {
803
+ [ErrorCode.INVALID_ACCOUNT]: { status: 400, sentence: "We could not read your account. Try again." },
804
+ [ErrorCode.ORDER_NOT_FOUND]: { status: 404, sentence: "This order does not exist." },
805
+ [ErrorCode.ORDER_HELD]: { status: 409, sentence: "Someone else is already paying this order." },
806
+ [ErrorCode.ORDER_PAID]: { status: 409, sentence: "This order is already paid." },
807
+ [ErrorCode.ORDER_EXPIRED]: {
808
+ status: 410,
809
+ sentence: "This order expired. Go back to the store and start again."
810
+ },
811
+ [ErrorCode.RPC_UNAVAILABLE]: { status: 503, sentence: "The network is busy. Try again in a moment." }
812
+ };
813
+ var UNPAYABLE = { status: 500, sentence: "This order cannot be paid. Go back to the store." };
814
+ function respond(status, body) {
815
+ const headers = new Headers(CORS_HEADERS);
816
+ if (body === void 0) {
817
+ return new Response(null, { status, headers });
818
+ }
819
+ headers.set("Content-Type", "application/json");
820
+ return new Response(JSON.stringify(body), { status, headers });
821
+ }
822
+ function describeFailure(error) {
823
+ if (isBujiusCheckoutError(error)) {
824
+ return error.code;
825
+ }
826
+ if (error instanceof Error) {
827
+ return `${error.name}: ${error.message.replace(/[a-z][a-z0-9+.-]*:\/\/\S+/gi, "<url>")}`;
828
+ }
829
+ return typeof error;
830
+ }
831
+ function respondToError(error) {
832
+ const known = isBujiusCheckoutError(error) ? ERROR_RESPONSES[error.code] : void 0;
833
+ const { status, sentence } = known ?? UNPAYABLE;
834
+ if (status >= 500) {
835
+ console.error(`Transaction request failed: ${describeFailure(error)}`);
836
+ }
837
+ return respond(status, { error: sentence });
838
+ }
839
+ async function readAccount(request) {
840
+ let body;
841
+ try {
842
+ body = await request.json();
843
+ } catch (cause) {
844
+ throw new BujiusCheckoutError(ErrorCode.INVALID_ACCOUNT, { cause });
845
+ }
846
+ const account = body?.account;
847
+ if (typeof account !== "string") {
848
+ throw new BujiusCheckoutError(ErrorCode.INVALID_ACCOUNT);
849
+ }
850
+ try {
851
+ return new import_web37.PublicKey(account);
852
+ } catch (cause) {
853
+ throw new BujiusCheckoutError(ErrorCode.INVALID_ACCOUNT, { cause });
854
+ }
855
+ }
856
+ function createTransactionRequestHandler(options) {
857
+ const { connection, label, iconUrl, strategy, resolve } = options;
858
+ return async (request) => {
859
+ if (request.method === "OPTIONS") {
860
+ return respond(204);
861
+ }
862
+ if (request.method === "GET") {
863
+ return respond(200, { label, icon: iconUrl });
864
+ }
865
+ if (request.method !== "POST") {
866
+ return respond(405, { error: "This request is not supported." });
867
+ }
868
+ try {
869
+ const account = await readAccount(request);
870
+ const key = new URL(request.url).pathname.split("/").filter(Boolean).at(-1) ?? "";
871
+ const resolved = await resolve(key, account);
872
+ if ("error" in resolved) {
873
+ throw new BujiusCheckoutError(resolved.error);
874
+ }
875
+ const { config, amount, reference, message } = resolved;
876
+ const transaction = await buildSplitPayment(
877
+ connection,
878
+ config,
879
+ account,
880
+ amount,
881
+ reference,
882
+ strategy
883
+ );
884
+ const serialized = transaction.serialize({
885
+ requireAllSignatures: false,
886
+ verifySignatures: false
887
+ });
888
+ return respond(200, { transaction: serialized.toString("base64"), message });
889
+ } catch (error) {
890
+ return respondToError(error);
891
+ }
892
+ };
893
+ }
894
+ // Annotate the CommonJS export names for ESM import in node:
895
+ 0 && (module.exports = {
896
+ BujiusCheckoutError,
897
+ ErrorCode,
898
+ PROGRAM_ID,
899
+ buildCreateSplit,
900
+ buildSplitPayment,
901
+ computeSplit,
902
+ createTransactionRequestHandler,
903
+ fetchSplitConfig,
904
+ findPayment,
905
+ findSplitConfig,
906
+ fromBaseUnits,
907
+ isBujiusCheckoutError,
908
+ newReference,
909
+ paymentUrl,
910
+ toBaseUnits,
911
+ verifySplitPayment
912
+ });
913
+ //# sourceMappingURL=index.cjs.map