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