@rotateprotocol/sdk 1.0.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 (74) hide show
  1. package/README.md +453 -0
  2. package/dist/catalog.d.ts +112 -0
  3. package/dist/catalog.d.ts.map +1 -0
  4. package/dist/catalog.js +210 -0
  5. package/dist/catalog.js.map +1 -0
  6. package/dist/components/CheckoutForm.d.ts +86 -0
  7. package/dist/components/CheckoutForm.d.ts.map +1 -0
  8. package/dist/components/CheckoutForm.js +332 -0
  9. package/dist/components/CheckoutForm.js.map +1 -0
  10. package/dist/components/HostedCheckout.d.ts +57 -0
  11. package/dist/components/HostedCheckout.d.ts.map +1 -0
  12. package/dist/components/HostedCheckout.js +414 -0
  13. package/dist/components/HostedCheckout.js.map +1 -0
  14. package/dist/components/PaymentButton.d.ts +80 -0
  15. package/dist/components/PaymentButton.d.ts.map +1 -0
  16. package/dist/components/PaymentButton.js +210 -0
  17. package/dist/components/PaymentButton.js.map +1 -0
  18. package/dist/components/RotateProvider.d.ts +115 -0
  19. package/dist/components/RotateProvider.d.ts.map +1 -0
  20. package/dist/components/RotateProvider.js +264 -0
  21. package/dist/components/RotateProvider.js.map +1 -0
  22. package/dist/components/index.d.ts +17 -0
  23. package/dist/components/index.d.ts.map +1 -0
  24. package/dist/components/index.js +27 -0
  25. package/dist/components/index.js.map +1 -0
  26. package/dist/embed.d.ts +85 -0
  27. package/dist/embed.d.ts.map +1 -0
  28. package/dist/embed.js +313 -0
  29. package/dist/embed.js.map +1 -0
  30. package/dist/hooks.d.ts +156 -0
  31. package/dist/hooks.d.ts.map +1 -0
  32. package/dist/hooks.js +280 -0
  33. package/dist/hooks.js.map +1 -0
  34. package/dist/idl/rotate_connect.json +2572 -0
  35. package/dist/index.d.ts +505 -0
  36. package/dist/index.d.ts.map +1 -0
  37. package/dist/index.js +1197 -0
  38. package/dist/index.js.map +1 -0
  39. package/dist/marketplace.d.ts +257 -0
  40. package/dist/marketplace.d.ts.map +1 -0
  41. package/dist/marketplace.js +433 -0
  42. package/dist/marketplace.js.map +1 -0
  43. package/dist/platform.d.ts +234 -0
  44. package/dist/platform.d.ts.map +1 -0
  45. package/dist/platform.js +268 -0
  46. package/dist/platform.js.map +1 -0
  47. package/dist/react.d.ts +140 -0
  48. package/dist/react.d.ts.map +1 -0
  49. package/dist/react.js +429 -0
  50. package/dist/react.js.map +1 -0
  51. package/dist/store.d.ts +213 -0
  52. package/dist/store.d.ts.map +1 -0
  53. package/dist/store.js +404 -0
  54. package/dist/store.js.map +1 -0
  55. package/dist/webhooks.d.ts +149 -0
  56. package/dist/webhooks.d.ts.map +1 -0
  57. package/dist/webhooks.js +371 -0
  58. package/dist/webhooks.js.map +1 -0
  59. package/package.json +114 -0
  60. package/src/catalog.ts +299 -0
  61. package/src/components/CheckoutForm.tsx +608 -0
  62. package/src/components/HostedCheckout.tsx +675 -0
  63. package/src/components/PaymentButton.tsx +348 -0
  64. package/src/components/RotateProvider.tsx +370 -0
  65. package/src/components/index.ts +26 -0
  66. package/src/embed.ts +408 -0
  67. package/src/hooks.ts +518 -0
  68. package/src/idl/rotate_connect.json +2572 -0
  69. package/src/index.ts +1538 -0
  70. package/src/marketplace.ts +642 -0
  71. package/src/platform.ts +403 -0
  72. package/src/react.ts +459 -0
  73. package/src/store.ts +577 -0
  74. package/src/webhooks.ts +506 -0
package/dist/index.js ADDED
@@ -0,0 +1,1197 @@
1
+ "use strict";
2
+ /**
3
+ * Rotate Protocol SDK
4
+ *
5
+ * Non-custodial P2P payment protocol for Solana.
6
+ * Your keys, your money. We never touch your funds.
7
+ *
8
+ * @packageDocumentation
9
+ */
10
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
11
+ if (k2 === undefined) k2 = k;
12
+ var desc = Object.getOwnPropertyDescriptor(m, k);
13
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
14
+ desc = { enumerable: true, get: function() { return m[k]; } };
15
+ }
16
+ Object.defineProperty(o, k2, desc);
17
+ }) : (function(o, m, k, k2) {
18
+ if (k2 === undefined) k2 = k;
19
+ o[k2] = m[k];
20
+ }));
21
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
22
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
23
+ }) : function(o, v) {
24
+ o["default"] = v;
25
+ });
26
+ var __importStar = (this && this.__importStar) || (function () {
27
+ var ownKeys = function(o) {
28
+ ownKeys = Object.getOwnPropertyNames || function (o) {
29
+ var ar = [];
30
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
31
+ return ar;
32
+ };
33
+ return ownKeys(o);
34
+ };
35
+ return function (mod) {
36
+ if (mod && mod.__esModule) return mod;
37
+ var result = {};
38
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
39
+ __setModuleDefault(result, mod);
40
+ return result;
41
+ };
42
+ })();
43
+ var __importDefault = (this && this.__importDefault) || function (mod) {
44
+ return (mod && mod.__esModule) ? mod : { "default": mod };
45
+ };
46
+ Object.defineProperty(exports, "__esModule", { value: true });
47
+ exports.IDL = exports.RotatePlatformManager = exports.MarketplaceCart = exports.RotateMarketplace = exports.RotateCart = exports.RotateStore = exports.RotateSDK = exports.MEMO_PREFIX = exports.TOKEN_MINTS = exports.SEEDS = exports.MAX_RANDOM_ID = exports.MIN_RANDOM_ID = exports.MEMO_PROGRAM_ID = exports.BPS = exports.MIN_PAYMENT_TOKENS = exports.MIN_PAYMENT_LAMPORTS = exports.MIN_PAYMENT_USD = exports.MAX_PLATFORM_FEE_BPS = exports.PROTOCOL_FEE_BPS = exports.PROGRAM_ID = void 0;
48
+ exports.getProtocolPda = getProtocolPda;
49
+ exports.getPlatformPda = getPlatformPda;
50
+ exports.getMerchantPda = getMerchantPda;
51
+ exports.getLinkPda = getLinkPda;
52
+ exports.createMemoInstruction = createMemoInstruction;
53
+ exports.getLinkDescription = getLinkDescription;
54
+ exports.calculateFees = calculateFees;
55
+ exports.generateSampleId = generateSampleId;
56
+ exports.isValidId = isValidId;
57
+ const web3_js_1 = require("@solana/web3.js");
58
+ const spl_token_1 = require("@solana/spl-token");
59
+ const anchor = __importStar(require("@coral-xyz/anchor"));
60
+ const anchor_1 = require("@coral-xyz/anchor");
61
+ // Import IDL
62
+ const rotate_connect_json_1 = __importDefault(require("./idl/rotate_connect.json"));
63
+ exports.IDL = rotate_connect_json_1.default;
64
+ // ==================== CONSTANTS ====================
65
+ /** Rotate Protocol Program ID */
66
+ exports.PROGRAM_ID = new web3_js_1.PublicKey('ELBYdNeCGeMThC2ccckw3fyAt77SniV3gPTo1fuFcxDg');
67
+ /** Protocol fee in basis points (3%) */
68
+ exports.PROTOCOL_FEE_BPS = 300;
69
+ /** Maximum platform fee in basis points (6%) */
70
+ exports.MAX_PLATFORM_FEE_BPS = 600;
71
+ /** Minimum payment in USD (micro-USD, 6 decimals) - $5.00 */
72
+ exports.MIN_PAYMENT_USD = 5000000;
73
+ /** Minimum payment in lamports (~$5 at ~$100/SOL) */
74
+ exports.MIN_PAYMENT_LAMPORTS = 50000000;
75
+ /** Minimum payment in tokens (USDC/USDT) - $5.00 */
76
+ exports.MIN_PAYMENT_TOKENS = 5000000;
77
+ /** Basis points denominator */
78
+ exports.BPS = 10000;
79
+ /** Solana Memo Program ID */
80
+ exports.MEMO_PROGRAM_ID = new web3_js_1.PublicKey('MemoSq4gqABAXKb96qnH8TysNcWxMyWCqXgDLGmfcHr');
81
+ /** 7-digit ID range for platforms and merchants (sequential starting at 1000000) */
82
+ exports.MIN_RANDOM_ID = 1000000;
83
+ exports.MAX_RANDOM_ID = 9999999;
84
+ /** PDA Seeds */
85
+ exports.SEEDS = {
86
+ PROTOCOL: Buffer.from('protocol'),
87
+ PLATFORM: Buffer.from('platform'),
88
+ MERCHANT: Buffer.from('merchant'),
89
+ LINK: Buffer.from('link'),
90
+ };
91
+ /** Token mint addresses by network */
92
+ exports.TOKEN_MINTS = {
93
+ devnet: {
94
+ USDC: new web3_js_1.PublicKey('4zMMC9srt5Ri5X14GAgXhaHii3GnPAEERYPJgZJDncDU'),
95
+ USDT: new web3_js_1.PublicKey('EJwZgeZrdC8TXTQbQBoL6bfuAnFUUy1PVCMB4DYPzVaS'),
96
+ },
97
+ 'mainnet-beta': {
98
+ USDC: new web3_js_1.PublicKey('EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v'),
99
+ USDT: new web3_js_1.PublicKey('Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB'),
100
+ },
101
+ };
102
+ // ==================== PDA HELPERS ====================
103
+ function getProtocolPda(programId = exports.PROGRAM_ID) {
104
+ return web3_js_1.PublicKey.findProgramAddressSync([exports.SEEDS.PROTOCOL], programId);
105
+ }
106
+ function getPlatformPda(platformId, programId = exports.PROGRAM_ID) {
107
+ const idBuffer = Buffer.alloc(4);
108
+ idBuffer.writeUInt32LE(platformId);
109
+ return web3_js_1.PublicKey.findProgramAddressSync([exports.SEEDS.PLATFORM, idBuffer], programId);
110
+ }
111
+ function getMerchantPda(merchantId, programId = exports.PROGRAM_ID) {
112
+ const idBuffer = Buffer.alloc(4);
113
+ idBuffer.writeUInt32LE(merchantId);
114
+ return web3_js_1.PublicKey.findProgramAddressSync([exports.SEEDS.MERCHANT, idBuffer], programId);
115
+ }
116
+ function getLinkPda(linkId, programId = exports.PROGRAM_ID) {
117
+ const idBuffer = Buffer.alloc(4);
118
+ idBuffer.writeUInt32LE(linkId);
119
+ return web3_js_1.PublicKey.findProgramAddressSync([exports.SEEDS.LINK, idBuffer], programId);
120
+ }
121
+ // ==================== ID GENERATION ====================
122
+ /**
123
+ * Generate a sample 7-digit ID within the valid range.
124
+ * For testing and display only — on-chain IDs are assigned
125
+ * automatically and sequentially by the protocol.
126
+ */
127
+ function generateSampleId() {
128
+ return Math.floor(Math.random() * (exports.MAX_RANDOM_ID - exports.MIN_RANDOM_ID + 1)) + exports.MIN_RANDOM_ID;
129
+ }
130
+ /**
131
+ * Validate that an ID is within the valid 7-digit range
132
+ */
133
+ function isValidId(id) {
134
+ return id >= exports.MIN_RANDOM_ID && id <= exports.MAX_RANDOM_ID;
135
+ }
136
+ // ==================== MEMO HELPERS ====================
137
+ /** Memo prefix used by Rotate for on-chain descriptions */
138
+ exports.MEMO_PREFIX = 'rotate:';
139
+ /**
140
+ * Create a memo instruction with a Rotate-prefixed description.
141
+ * Used internally by createLink* methods when `description` is provided.
142
+ */
143
+ function createMemoInstruction(description, signer) {
144
+ return new web3_js_1.TransactionInstruction({
145
+ keys: [{ pubkey: signer, isSigner: true, isWritable: false }],
146
+ programId: exports.MEMO_PROGRAM_ID,
147
+ data: Buffer.from(exports.MEMO_PREFIX + description, 'utf-8'),
148
+ });
149
+ }
150
+ /**
151
+ * Recover a link description from its creation transaction memo.
152
+ * Returns null if no memo found or if the transaction doesn't have a Rotate memo.
153
+ *
154
+ * @param connection - Solana RPC connection
155
+ * @param linkId - The payment link ID
156
+ * @param programId - Optional custom program ID (defaults to PROGRAM_ID)
157
+ */
158
+ async function getLinkDescription(connection, linkId, programId = exports.PROGRAM_ID) {
159
+ try {
160
+ const [linkPda] = getLinkPda(linkId, programId);
161
+ const sigs = await connection.getSignaturesForAddress(linkPda, { limit: 5 });
162
+ if (sigs.length === 0)
163
+ return null;
164
+ // Oldest signature is the creation tx
165
+ const creationSig = sigs[sigs.length - 1].signature;
166
+ const txData = await connection.getTransaction(creationSig, { maxSupportedTransactionVersion: 0 });
167
+ if (!txData?.transaction?.message)
168
+ return null;
169
+ const msg = txData.transaction.message;
170
+ const accountKeys = msg.staticAccountKeys || msg.accountKeys || [];
171
+ const ixs = msg.compiledInstructions || msg.instructions || [];
172
+ for (const ix of ixs) {
173
+ const progKey = accountKeys[ix.programIdIndex];
174
+ if (progKey && progKey.toString() === exports.MEMO_PROGRAM_ID.toString()) {
175
+ const memoBytes = ix.data instanceof Uint8Array
176
+ ? ix.data
177
+ : typeof ix.data === 'string'
178
+ ? Buffer.from(ix.data, 'base64')
179
+ : ix.data;
180
+ const memoText = new TextDecoder().decode(memoBytes);
181
+ if (memoText.startsWith(exports.MEMO_PREFIX)) {
182
+ return memoText.slice(exports.MEMO_PREFIX.length);
183
+ }
184
+ }
185
+ }
186
+ }
187
+ catch {
188
+ // Memo recovery is best-effort
189
+ }
190
+ return null;
191
+ }
192
+ // ==================== FEE CALCULATION ====================
193
+ /**
194
+ * Calculate fees for a payment.
195
+ * Fees are split 50/50 between buyer and seller.
196
+ *
197
+ * **Rounding note:** When `totalFees` is odd, integer division means the
198
+ * seller share rounds down and the buyer pays one extra micro-unit.
199
+ * At worst this is a fraction of a cent and matches standard financial
200
+ * rounding conventions.
201
+ *
202
+ * @param amount - Payment amount in smallest unit (lamports or micro-USD/tokens).
203
+ * @param platformFeeBps - Platform fee in basis points (0-600).
204
+ */
205
+ function calculateFees(amount, platformFeeBps) {
206
+ const protocolFee = Math.floor((amount * exports.PROTOCOL_FEE_BPS) / exports.BPS);
207
+ const platformFee = Math.floor((amount * platformFeeBps) / exports.BPS);
208
+ const totalFees = protocolFee + platformFee;
209
+ // 50/50 split — seller share rounds down; buyer absorbs the extra micro-unit on odd totals
210
+ const sellerFeeShare = Math.floor(totalFees / 2);
211
+ const buyerFeeShare = totalFees - sellerFeeShare;
212
+ return {
213
+ amount,
214
+ protocolFee,
215
+ platformFee,
216
+ totalFees,
217
+ buyerFeeShare,
218
+ sellerFeeShare,
219
+ buyerPays: amount + buyerFeeShare,
220
+ merchantReceives: amount - sellerFeeShare,
221
+ };
222
+ }
223
+ // ==================== MAIN SDK CLASS ====================
224
+ class RotateSDK {
225
+ constructor(config) {
226
+ // Typed as `any` because the IDL is imported as JSON — Anchor can't infer
227
+ // account types without generated Program<RotateConnect> bindings.
228
+ // Generate types with `anchor idl type` for full type safety.
229
+ this.program = null;
230
+ this.network = config.network;
231
+ this.programId = config.programId || exports.PROGRAM_ID;
232
+ this.paymentBaseUrl = config.paymentBaseUrl || 'https://rotate.app';
233
+ this.priceApiUrl = config.priceApiUrl || 'https://api.coingecko.com/api/v3/simple/price?ids=solana&vs_currencies=usd';
234
+ this.qrApiUrl = config.qrApiUrl || 'https://api.qrserver.com/v1/create-qr-code/?size={size}x{size}&data={url}';
235
+ this.linkCreationRetries = config.linkCreationRetries ?? 3;
236
+ const endpoint = config.rpcEndpoint ||
237
+ (config.network === 'mainnet-beta'
238
+ ? 'https://api.mainnet-beta.solana.com'
239
+ : 'https://api.devnet.solana.com');
240
+ this.connection = new web3_js_1.Connection(endpoint, 'confirmed');
241
+ }
242
+ /**
243
+ * Initialize with a wallet (required for transactions)
244
+ */
245
+ initWithWallet(wallet) {
246
+ const provider = new anchor.AnchorProvider(this.connection, wallet, { commitment: 'confirmed' });
247
+ this.program = new anchor.Program(rotate_connect_json_1.default, provider);
248
+ }
249
+ /**
250
+ * Get the Anchor program instance.
251
+ * Throws if `initWithWallet()` has not been called.
252
+ *
253
+ * Returns `any` because the IDL is loaded from JSON at runtime.
254
+ * For full type safety, generate types with `anchor idl type` and
255
+ * cast the return value to `Program<RotateConnect>`.
256
+ */
257
+ getProgram() {
258
+ if (!this.program) {
259
+ throw new Error('SDK not initialized with wallet. Call initWithWallet() first.');
260
+ }
261
+ return this.program;
262
+ }
263
+ /**
264
+ * Get connection
265
+ */
266
+ getConnection() {
267
+ return this.connection;
268
+ }
269
+ // ==================== READ METHODS ====================
270
+ /**
271
+ * Get protocol data
272
+ */
273
+ async getProtocol() {
274
+ try {
275
+ const [pda] = getProtocolPda(this.programId);
276
+ const account = await this.connection.getAccountInfo(pda);
277
+ if (!account)
278
+ return null;
279
+ return this.decodeProtocol(account.data);
280
+ }
281
+ catch {
282
+ return null;
283
+ }
284
+ }
285
+ /**
286
+ * Get platform data
287
+ */
288
+ async getPlatform(platformId) {
289
+ try {
290
+ const [pda] = getPlatformPda(platformId, this.programId);
291
+ const account = await this.connection.getAccountInfo(pda);
292
+ if (!account)
293
+ return null;
294
+ return this.decodePlatform(account.data);
295
+ }
296
+ catch {
297
+ return null;
298
+ }
299
+ }
300
+ /**
301
+ * Get merchant data
302
+ */
303
+ async getMerchant(merchantId) {
304
+ try {
305
+ const [pda] = getMerchantPda(merchantId, this.programId);
306
+ const account = await this.connection.getAccountInfo(pda);
307
+ if (!account)
308
+ return null;
309
+ return this.decodeMerchant(account.data);
310
+ }
311
+ catch {
312
+ return null;
313
+ }
314
+ }
315
+ /**
316
+ * Get payment link data
317
+ */
318
+ async getPaymentLink(linkId) {
319
+ try {
320
+ const [pda] = getLinkPda(linkId, this.programId);
321
+ const account = await this.connection.getAccountInfo(pda);
322
+ if (!account)
323
+ return null;
324
+ return this.decodePaymentLink(account.data);
325
+ }
326
+ catch {
327
+ return null;
328
+ }
329
+ }
330
+ /**
331
+ * Check if a payment link is paid
332
+ */
333
+ async isLinkPaid(linkId) {
334
+ const link = await this.getPaymentLink(linkId);
335
+ return link?.status === 'Paid';
336
+ }
337
+ /**
338
+ * Poll for payment status
339
+ */
340
+ async waitForPayment(linkId, timeoutMs = 300000) {
341
+ const startTime = Date.now();
342
+ while (Date.now() - startTime < timeoutMs) {
343
+ const link = await this.getPaymentLink(linkId);
344
+ if (link?.status === 'Paid') {
345
+ return link;
346
+ }
347
+ await new Promise(resolve => setTimeout(resolve, 2000));
348
+ }
349
+ return null;
350
+ }
351
+ // ==================== WRITE METHODS ====================
352
+ /**
353
+ * Internal helper: retry a link-creation callback when a PDA collision
354
+ * occurs (e.g. two concurrent `createLink*` calls compute the same
355
+ * `nextLinkId`). On each retry the protocol is re-fetched to pick up
356
+ * the incremented `link_count`.
357
+ */
358
+ async _retryLinkCreation(fn) {
359
+ let lastError;
360
+ for (let attempt = 0; attempt <= this.linkCreationRetries; attempt++) {
361
+ try {
362
+ const protocol = await this.getProtocol();
363
+ if (!protocol)
364
+ throw new Error('Protocol not initialized');
365
+ const nextLinkId = protocol.linkCount + 1;
366
+ return await fn(nextLinkId);
367
+ }
368
+ catch (err) {
369
+ lastError = err;
370
+ if (!RotateSDK._isPdaCollision(err) || attempt === this.linkCreationRetries)
371
+ throw err;
372
+ // Exponential back-off before retry to let the previous tx confirm
373
+ await new Promise(r => setTimeout(r, 500 * 2 ** attempt));
374
+ }
375
+ }
376
+ throw lastError;
377
+ }
378
+ /**
379
+ * Detect whether an error is a PDA-collision ("account already in use").
380
+ *
381
+ * Checks multiple signals so the heuristic survives Anchor / runtime
382
+ * message changes:
383
+ * 1. Error message substrings (Anchor & system program phrasing).
384
+ * 2. Anchor structured error codes (`err.error.errorCode`).
385
+ * 3. Transaction logs emitted by the runtime.
386
+ *
387
+ * @internal
388
+ */
389
+ static _isPdaCollision(err) {
390
+ // 1. Message-based detection (covers most Anchor versions)
391
+ const msg = (err?.message || '') + ' ' + (err?.error?.errorMessage || '');
392
+ if (msg.includes('already in use') ||
393
+ msg.includes('AccountAlreadyExists') ||
394
+ // 0x0 is the system-program error code for "account already exists"
395
+ // when surfaced through Anchor's error wrapper
396
+ msg.includes('0x0')) {
397
+ return true;
398
+ }
399
+ // 2. Anchor structured error code (numeric)
400
+ // System program "AccountAlreadyExists" is code 0 in Anchor's mapping.
401
+ const code = err?.error?.errorCode?.number ?? err?.code;
402
+ if (code === 0)
403
+ return true;
404
+ // 3. Transaction log inspection (runtime logs the system-program error)
405
+ const logs = err?.logs ?? err?.error?.logs;
406
+ if (Array.isArray(logs) && logs.some((l) => l.includes('already in use'))) {
407
+ return true;
408
+ }
409
+ return false;
410
+ }
411
+ /**
412
+ * Create a new platform with a random 7-digit ID
413
+ * @returns Transaction signature and the generated platform ID
414
+ */
415
+ async createPlatform(params) {
416
+ const program = this.getProgram();
417
+ const protocol = await this.getProtocol();
418
+ if (!protocol)
419
+ throw new Error('Protocol not initialized');
420
+ // 7-digit sequential ID: 1000000 + platform_count
421
+ const platformId = exports.MIN_RANDOM_ID + protocol.platformCount;
422
+ const [platformPda] = getPlatformPda(platformId, this.programId);
423
+ const [protocolPda] = getProtocolPda(this.programId);
424
+ const tx = await program.methods
425
+ .createPlatform(params.feeBps)
426
+ .accounts({
427
+ protocol: protocolPda,
428
+ platform: platformPda,
429
+ wallet: params.wallet,
430
+ admin: program.provider.publicKey,
431
+ systemProgram: web3_js_1.SystemProgram.programId,
432
+ })
433
+ .rpc();
434
+ return { tx, platformId };
435
+ }
436
+ /**
437
+ * Create a new merchant with a random 7-digit ID
438
+ * @returns Transaction signature and the generated merchant ID
439
+ */
440
+ async createMerchant(params) {
441
+ const program = this.getProgram();
442
+ const protocol = await this.getProtocol();
443
+ if (!protocol)
444
+ throw new Error('Protocol not initialized');
445
+ // 7-digit sequential ID: 1000000 + merchant_count
446
+ const merchantId = exports.MIN_RANDOM_ID + protocol.merchantCount;
447
+ const [merchantPda] = getMerchantPda(merchantId, this.programId);
448
+ const [platformPda] = getPlatformPda(params.platformId, this.programId);
449
+ const [protocolPda] = getProtocolPda(this.programId);
450
+ const tx = await program.methods
451
+ .createMerchant()
452
+ .accounts({
453
+ protocol: protocolPda,
454
+ platform: platformPda,
455
+ merchant: merchantPda,
456
+ wallet: params.wallet,
457
+ payer: program.provider.publicKey,
458
+ systemProgram: web3_js_1.SystemProgram.programId,
459
+ })
460
+ .rpc();
461
+ return { tx, merchantId };
462
+ }
463
+ /**
464
+ * Create a USD-denominated payment link.
465
+ *
466
+ * Automatically retries on PDA collision (concurrent link creation).
467
+ */
468
+ async createLinkUsd(params) {
469
+ return this._retryLinkCreation(async (nextLinkId) => {
470
+ const program = this.getProgram();
471
+ const [linkPda] = getLinkPda(nextLinkId, this.programId);
472
+ const [platformPda] = getPlatformPda(params.platformId, this.programId);
473
+ const [merchantPda] = getMerchantPda(params.merchantId, this.programId);
474
+ const [protocolPda] = getProtocolPda(this.programId);
475
+ const builder = program.methods
476
+ .createLinkUsd(new anchor_1.BN(params.amount.toString()), new anchor_1.BN(params.expiresAt || 0), params.allowTips ?? false, params.allowPartial ?? false, new anchor_1.BN(params.orderRef || Date.now().toString()))
477
+ .accounts({
478
+ protocol: protocolPda,
479
+ platform: platformPda,
480
+ merchant: merchantPda,
481
+ link: linkPda,
482
+ creator: program.provider.publicKey,
483
+ systemProgram: web3_js_1.SystemProgram.programId,
484
+ });
485
+ // Attach description as on-chain memo (recoverable after cache clear)
486
+ if (params.description) {
487
+ builder.postInstructions([createMemoInstruction(params.description, program.provider.publicKey)]);
488
+ }
489
+ const tx = await builder.rpc();
490
+ return { tx, linkId: nextLinkId };
491
+ });
492
+ }
493
+ /**
494
+ * Create a SOL payment link.
495
+ *
496
+ * Automatically retries on PDA collision (concurrent link creation).
497
+ */
498
+ async createLinkSol(params) {
499
+ return this._retryLinkCreation(async (nextLinkId) => {
500
+ const program = this.getProgram();
501
+ const [linkPda] = getLinkPda(nextLinkId, this.programId);
502
+ const [platformPda] = getPlatformPda(params.platformId, this.programId);
503
+ const [merchantPda] = getMerchantPda(params.merchantId, this.programId);
504
+ const [protocolPda] = getProtocolPda(this.programId);
505
+ const builder = program.methods
506
+ .createLinkSol(new anchor_1.BN(params.amount.toString()), new anchor_1.BN(params.expiresAt || 0), params.allowTips ?? false, params.allowPartial ?? false, new anchor_1.BN(params.orderRef || Date.now().toString()))
507
+ .accounts({
508
+ protocol: protocolPda,
509
+ platform: platformPda,
510
+ merchant: merchantPda,
511
+ link: linkPda,
512
+ creator: program.provider.publicKey,
513
+ systemProgram: web3_js_1.SystemProgram.programId,
514
+ });
515
+ if (params.description) {
516
+ builder.postInstructions([createMemoInstruction(params.description, program.provider.publicKey)]);
517
+ }
518
+ const tx = await builder.rpc();
519
+ return { tx, linkId: nextLinkId };
520
+ });
521
+ }
522
+ /**
523
+ * Pay a SOL link
524
+ */
525
+ async payLinkSol(params) {
526
+ const program = this.getProgram();
527
+ const protocol = await this.getProtocol();
528
+ if (!protocol)
529
+ throw new Error('Protocol not initialized');
530
+ const merchant = await this.getMerchant(params.merchantId);
531
+ if (!merchant)
532
+ throw new Error('Merchant not found');
533
+ const platform = await this.getPlatform(params.platformId);
534
+ if (!platform)
535
+ throw new Error('Platform not found');
536
+ const [linkPda] = getLinkPda(params.linkId, this.programId);
537
+ const [platformPda] = getPlatformPda(params.platformId, this.programId);
538
+ const [merchantPda] = getMerchantPda(params.merchantId, this.programId);
539
+ const [protocolPda] = getProtocolPda(this.programId);
540
+ const tx = await program.methods
541
+ .payLinkSol(new anchor_1.BN(params.amount.toString()), new anchor_1.BN((params.tip || 0n).toString()))
542
+ .accounts({
543
+ protocol: protocolPda,
544
+ platform: platformPda,
545
+ merchant: merchantPda,
546
+ link: linkPda,
547
+ merchantWallet: merchant.wallet,
548
+ platformWallet: platform.wallet,
549
+ treasury: protocol.treasury,
550
+ payer: program.provider.publicKey,
551
+ systemProgram: web3_js_1.SystemProgram.programId,
552
+ })
553
+ .rpc();
554
+ return tx;
555
+ }
556
+ /**
557
+ * Pay a USD link with SOL
558
+ *
559
+ * Includes on-chain price oracle slippage protection. If `expectedLamports` is provided,
560
+ * the contract validates that `lamportsAmount` is within 5% of the expected value.
561
+ * Pass 0n or omit to skip on-chain slippage validation (backwards-compatible).
562
+ */
563
+ async payLinkUsdSol(params) {
564
+ const program = this.getProgram();
565
+ const protocol = await this.getProtocol();
566
+ if (!protocol)
567
+ throw new Error('Protocol not initialized');
568
+ const merchant = await this.getMerchant(params.merchantId);
569
+ if (!merchant)
570
+ throw new Error('Merchant not found');
571
+ const platform = await this.getPlatform(params.platformId);
572
+ if (!platform)
573
+ throw new Error('Platform not found');
574
+ // If no expectedLamports provided, auto-fetch from oracle for slippage protection.
575
+ // If the price API is unavailable, we throw rather than silently skipping the
576
+ // on-chain slippage check — paying without price validation risks loss of funds.
577
+ let expectedLamports = params.expectedLamports || 0n;
578
+ if (expectedLamports === 0n) {
579
+ expectedLamports = await this.microUsdToLamports(params.amount);
580
+ }
581
+ const [linkPda] = getLinkPda(params.linkId, this.programId);
582
+ const [platformPda] = getPlatformPda(params.platformId, this.programId);
583
+ const [merchantPda] = getMerchantPda(params.merchantId, this.programId);
584
+ const [protocolPda] = getProtocolPda(this.programId);
585
+ const tx = await program.methods
586
+ .payLinkUsdSol(new anchor_1.BN(params.amount.toString()), // usd_amount
587
+ new anchor_1.BN((params.tipUsd || 0n).toString()), // tip_usd
588
+ new anchor_1.BN(params.lamportsAmount.toString()), // lamports_amount
589
+ new anchor_1.BN((params.tipLamports || 0n).toString()), // tip_lamports
590
+ new anchor_1.BN(expectedLamports.toString()) // expected_lamports (oracle slippage check)
591
+ )
592
+ .accounts({
593
+ protocol: protocolPda,
594
+ platform: platformPda,
595
+ merchant: merchantPda,
596
+ link: linkPda,
597
+ merchantWallet: merchant.wallet,
598
+ platformWallet: platform.wallet,
599
+ treasury: protocol.treasury,
600
+ payer: program.provider.publicKey,
601
+ systemProgram: web3_js_1.SystemProgram.programId,
602
+ })
603
+ .rpc();
604
+ return tx;
605
+ }
606
+ /**
607
+ * Pay a USD link with token (USDC/USDT)
608
+ */
609
+ async payLinkUsdToken(params) {
610
+ const program = this.getProgram();
611
+ const protocol = await this.getProtocol();
612
+ if (!protocol)
613
+ throw new Error('Protocol not initialized');
614
+ const merchant = await this.getMerchant(params.merchantId);
615
+ if (!merchant)
616
+ throw new Error('Merchant not found');
617
+ const platform = await this.getPlatform(params.platformId);
618
+ if (!platform)
619
+ throw new Error('Platform not found');
620
+ const mint = this.getTokenMint(params.currency);
621
+ const payerAta = await (0, spl_token_1.getAssociatedTokenAddress)(mint, program.provider.publicKey);
622
+ const merchantAta = await (0, spl_token_1.getAssociatedTokenAddress)(mint, merchant.wallet);
623
+ const platformAta = await (0, spl_token_1.getAssociatedTokenAddress)(mint, platform.wallet);
624
+ const treasuryAta = await (0, spl_token_1.getAssociatedTokenAddress)(mint, protocol.treasury);
625
+ const [linkPda] = getLinkPda(params.linkId, this.programId);
626
+ const [platformPda] = getPlatformPda(params.platformId, this.programId);
627
+ const [merchantPda] = getMerchantPda(params.merchantId, this.programId);
628
+ const [protocolPda] = getProtocolPda(this.programId);
629
+ const tx = await program.methods
630
+ .payLinkUsdToken(new anchor_1.BN(params.amount.toString()), // usd_amount
631
+ new anchor_1.BN((params.tipUsd || 0n).toString()) // tip_usd
632
+ )
633
+ .accounts({
634
+ protocol: protocolPda,
635
+ platform: platformPda,
636
+ merchant: merchantPda,
637
+ link: linkPda,
638
+ payerTokenAccount: payerAta,
639
+ merchantTokenAccount: merchantAta,
640
+ platformTokenAccount: platformAta,
641
+ treasuryTokenAccount: treasuryAta,
642
+ payer: program.provider.publicKey,
643
+ tokenProgram: spl_token_1.TOKEN_PROGRAM_ID,
644
+ })
645
+ .rpc();
646
+ return tx;
647
+ }
648
+ /**
649
+ * Cancel a payment link
650
+ */
651
+ async cancelLink(linkId, merchantId) {
652
+ const program = this.getProgram();
653
+ const merchant = await this.getMerchant(merchantId);
654
+ if (!merchant)
655
+ throw new Error('Merchant not found');
656
+ const [linkPda] = getLinkPda(linkId, this.programId);
657
+ const [merchantPda] = getMerchantPda(merchantId, this.programId);
658
+ const [platformPda] = getPlatformPda(merchant.platformId, this.programId);
659
+ const tx = await program.methods
660
+ .cancelLink()
661
+ .accounts({
662
+ merchant: merchantPda,
663
+ platform: platformPda,
664
+ link: linkPda,
665
+ authority: program.provider.publicKey,
666
+ })
667
+ .rpc();
668
+ return tx;
669
+ }
670
+ // ==================== BATCH READ METHODS ====================
671
+ /**
672
+ * Fetch all merchant accounts belonging to a specific platform using
673
+ * `getProgramAccounts` with a `memcmp` filter.
674
+ *
675
+ * This is **significantly** faster than iterating through every merchant
676
+ * ID sequentially, especially for platforms with many merchants.
677
+ *
678
+ * The filter matches the `platform_id` field at byte offset 12 inside
679
+ * the Merchant account data (8-byte discriminator + 4-byte `id`).
680
+ *
681
+ * @param platformId - The platform ID to filter by.
682
+ * @param activeOnly - If true, only return active merchants (default: false).
683
+ */
684
+ async getMerchantsByPlatform(platformId, activeOnly = false) {
685
+ const platformIdBuf = Buffer.alloc(4);
686
+ platformIdBuf.writeUInt32LE(platformId);
687
+ const accounts = await this.connection.getProgramAccounts(this.programId, {
688
+ filters: [
689
+ { memcmp: { offset: 0, bytes: RotateSDK.DISCRIMINATORS.Merchant.toString('base64') } },
690
+ { memcmp: { offset: 12, bytes: platformIdBuf.toString('base64') } },
691
+ ],
692
+ });
693
+ const merchants = [];
694
+ for (const { account } of accounts) {
695
+ try {
696
+ const merchant = this.decodeMerchant(account.data);
697
+ if (activeOnly && !merchant.active)
698
+ continue;
699
+ merchants.push(merchant);
700
+ }
701
+ catch {
702
+ // Skip accounts that fail to decode
703
+ }
704
+ }
705
+ return merchants;
706
+ }
707
+ // ==================== PROTOCOL ADMIN ====================
708
+ /**
709
+ * Update the protocol treasury address (authority only).
710
+ * Only the original protocol authority can call this.
711
+ */
712
+ async updateProtocol(newTreasury) {
713
+ const program = this.getProgram();
714
+ const [protocolPda] = getProtocolPda(this.programId);
715
+ const tx = await program.methods
716
+ .updateProtocol()
717
+ .accounts({
718
+ protocol: protocolPda,
719
+ newTreasury,
720
+ authority: program.provider.publicKey,
721
+ })
722
+ .rpc();
723
+ return tx;
724
+ }
725
+ // ==================== RENT RECLAMATION ====================
726
+ /**
727
+ * Close a completed/cancelled link and reclaim rent SOL back to the merchant wallet.
728
+ * Only works on links with status Paid or Cancelled.
729
+ */
730
+ async closeLink(linkId, merchantId) {
731
+ const program = this.getProgram();
732
+ const [linkPda] = getLinkPda(linkId, this.programId);
733
+ const [merchantPda] = getMerchantPda(merchantId, this.programId);
734
+ const tx = await program.methods
735
+ .closeLink()
736
+ .accounts({
737
+ merchant: merchantPda,
738
+ link: linkPda,
739
+ authority: program.provider.publicKey,
740
+ })
741
+ .rpc();
742
+ return tx;
743
+ }
744
+ // ==================== TOKEN LINK METHODS ====================
745
+ /**
746
+ * Create a token (USDC/USDT) payment link.
747
+ *
748
+ * Automatically retries on PDA collision (concurrent link creation).
749
+ */
750
+ async createLinkToken(params) {
751
+ return this._retryLinkCreation(async (nextLinkId) => {
752
+ const program = this.getProgram();
753
+ const mint = this.getTokenMint(params.currency);
754
+ const [linkPda] = getLinkPda(nextLinkId, this.programId);
755
+ const [platformPda] = getPlatformPda(params.platformId, this.programId);
756
+ const [merchantPda] = getMerchantPda(params.merchantId, this.programId);
757
+ const [protocolPda] = getProtocolPda(this.programId);
758
+ const builder = program.methods
759
+ .createLinkToken(new anchor_1.BN(params.amount.toString()), new anchor_1.BN(params.expiresAt || 0), params.allowTips ?? false, params.allowPartial ?? false, new anchor_1.BN(params.orderRef || Date.now().toString()))
760
+ .accounts({
761
+ protocol: protocolPda,
762
+ platform: platformPda,
763
+ merchant: merchantPda,
764
+ link: linkPda,
765
+ tokenMint: mint,
766
+ creator: program.provider.publicKey,
767
+ systemProgram: web3_js_1.SystemProgram.programId,
768
+ });
769
+ if (params.description) {
770
+ builder.postInstructions([createMemoInstruction(params.description, program.provider.publicKey)]);
771
+ }
772
+ const tx = await builder.rpc();
773
+ return { tx, linkId: nextLinkId };
774
+ });
775
+ }
776
+ /**
777
+ * Pay a token (USDC/USDT) link
778
+ */
779
+ async payLinkToken(params) {
780
+ const program = this.getProgram();
781
+ const protocol = await this.getProtocol();
782
+ if (!protocol)
783
+ throw new Error('Protocol not initialized');
784
+ const merchant = await this.getMerchant(params.merchantId);
785
+ if (!merchant)
786
+ throw new Error('Merchant not found');
787
+ const platform = await this.getPlatform(params.platformId);
788
+ if (!platform)
789
+ throw new Error('Platform not found');
790
+ const mint = this.getTokenMint(params.currency);
791
+ const payerAta = await (0, spl_token_1.getAssociatedTokenAddress)(mint, program.provider.publicKey);
792
+ const merchantAta = await (0, spl_token_1.getAssociatedTokenAddress)(mint, merchant.wallet);
793
+ const platformAta = await (0, spl_token_1.getAssociatedTokenAddress)(mint, platform.wallet);
794
+ const treasuryAta = await (0, spl_token_1.getAssociatedTokenAddress)(mint, protocol.treasury);
795
+ const [linkPda] = getLinkPda(params.linkId, this.programId);
796
+ const [platformPda] = getPlatformPda(params.platformId, this.programId);
797
+ const [merchantPda] = getMerchantPda(params.merchantId, this.programId);
798
+ const [protocolPda] = getProtocolPda(this.programId);
799
+ const tx = await program.methods
800
+ .payLinkToken(new anchor_1.BN(params.amount.toString()), new anchor_1.BN((params.tip || 0n).toString()))
801
+ .accounts({
802
+ protocol: protocolPda,
803
+ platform: platformPda,
804
+ merchant: merchantPda,
805
+ link: linkPda,
806
+ payerTokenAccount: payerAta,
807
+ merchantTokenAccount: merchantAta,
808
+ platformTokenAccount: platformAta,
809
+ treasuryTokenAccount: treasuryAta,
810
+ payer: program.provider.publicKey,
811
+ tokenProgram: spl_token_1.TOKEN_PROGRAM_ID,
812
+ })
813
+ .rpc();
814
+ return tx;
815
+ }
816
+ // ==================== DIRECT PAYMENT METHODS ====================
817
+ /**
818
+ * Pay with SOL directly (no payment link)
819
+ */
820
+ async paySol(params) {
821
+ const program = this.getProgram();
822
+ const protocol = await this.getProtocol();
823
+ if (!protocol)
824
+ throw new Error('Protocol not initialized');
825
+ const merchant = await this.getMerchant(params.merchantId);
826
+ if (!merchant)
827
+ throw new Error('Merchant not found');
828
+ const platform = await this.getPlatform(params.platformId);
829
+ if (!platform)
830
+ throw new Error('Platform not found');
831
+ const [platformPda] = getPlatformPda(params.platformId, this.programId);
832
+ const [merchantPda] = getMerchantPda(params.merchantId, this.programId);
833
+ const [protocolPda] = getProtocolPda(this.programId);
834
+ const tx = await program.methods
835
+ .paySol(new anchor_1.BN(params.amount.toString()), new anchor_1.BN(params.orderRef || Date.now().toString()))
836
+ .accounts({
837
+ protocol: protocolPda,
838
+ platform: platformPda,
839
+ merchant: merchantPda,
840
+ merchantWallet: merchant.wallet,
841
+ platformWallet: platform.wallet,
842
+ treasury: protocol.treasury,
843
+ payer: program.provider.publicKey,
844
+ systemProgram: web3_js_1.SystemProgram.programId,
845
+ })
846
+ .rpc();
847
+ return tx;
848
+ }
849
+ /**
850
+ * Pay with token (USDC/USDT) directly (no payment link)
851
+ */
852
+ async payToken(params) {
853
+ const program = this.getProgram();
854
+ const protocol = await this.getProtocol();
855
+ if (!protocol)
856
+ throw new Error('Protocol not initialized');
857
+ const merchant = await this.getMerchant(params.merchantId);
858
+ if (!merchant)
859
+ throw new Error('Merchant not found');
860
+ const platform = await this.getPlatform(params.platformId);
861
+ if (!platform)
862
+ throw new Error('Platform not found');
863
+ const mint = this.getTokenMint(params.currency);
864
+ const payerAta = await (0, spl_token_1.getAssociatedTokenAddress)(mint, program.provider.publicKey);
865
+ const merchantAta = await (0, spl_token_1.getAssociatedTokenAddress)(mint, merchant.wallet);
866
+ const platformAta = await (0, spl_token_1.getAssociatedTokenAddress)(mint, platform.wallet);
867
+ const treasuryAta = await (0, spl_token_1.getAssociatedTokenAddress)(mint, protocol.treasury);
868
+ const [platformPda] = getPlatformPda(params.platformId, this.programId);
869
+ const [merchantPda] = getMerchantPda(params.merchantId, this.programId);
870
+ const [protocolPda] = getProtocolPda(this.programId);
871
+ const tx = await program.methods
872
+ .payToken(new anchor_1.BN(params.amount.toString()), new anchor_1.BN(params.orderRef || Date.now().toString()))
873
+ .accounts({
874
+ protocol: protocolPda,
875
+ platform: platformPda,
876
+ merchant: merchantPda,
877
+ payerTokenAccount: payerAta,
878
+ merchantTokenAccount: merchantAta,
879
+ platformTokenAccount: platformAta,
880
+ treasuryTokenAccount: treasuryAta,
881
+ payer: program.provider.publicKey,
882
+ tokenProgram: spl_token_1.TOKEN_PROGRAM_ID,
883
+ })
884
+ .rpc();
885
+ return tx;
886
+ }
887
+ // ==================== UPDATE METHODS ====================
888
+ /**
889
+ * Update platform settings (admin only)
890
+ */
891
+ async updatePlatform(params) {
892
+ const program = this.getProgram();
893
+ const [platformPda] = getPlatformPda(params.platformId, this.programId);
894
+ const tx = await program.methods
895
+ .updatePlatform(params.feeBps, params.active)
896
+ .accounts({
897
+ platform: platformPda,
898
+ newWallet: params.newWallet,
899
+ admin: program.provider.publicKey,
900
+ })
901
+ .rpc();
902
+ return tx;
903
+ }
904
+ /**
905
+ * Update merchant settings (wallet owner only)
906
+ */
907
+ async updateMerchant(params) {
908
+ const program = this.getProgram();
909
+ const [merchantPda] = getMerchantPda(params.merchantId, this.programId);
910
+ const [platformPda] = getPlatformPda(params.platformId, this.programId);
911
+ const tx = await program.methods
912
+ .updateMerchant(params.active)
913
+ .accounts({
914
+ platform: platformPda,
915
+ merchant: merchantPda,
916
+ newWallet: params.newWallet,
917
+ authority: program.provider.publicKey,
918
+ })
919
+ .rpc();
920
+ return tx;
921
+ }
922
+ // ==================== PAYMENT URL GENERATION ====================
923
+ /**
924
+ * Generate payment URL for a link.
925
+ *
926
+ * URL format: `{base}/checkout/?link={id}&network={network}`
927
+ *
928
+ * The checkout page reads query parameters to load payment data from on-chain.
929
+ * Merchant and platform IDs are resolved from the on-chain link automatically,
930
+ * but can be provided explicitly for faster loading.
931
+ *
932
+ * @param linkId - The payment link ID.
933
+ * @param options - Optional overrides for base URL, merchant/platform IDs, and brand.
934
+ */
935
+ getPaymentUrl(linkId, baseUrl) {
936
+ const url = baseUrl || this.paymentBaseUrl;
937
+ const params = new URLSearchParams({ link: String(linkId), network: this.network });
938
+ return `${url}/checkout/?${params.toString()}`;
939
+ }
940
+ /**
941
+ * Generate QR code URL for a payment link.
942
+ * Uses the configurable `qrApiUrl` template (default: api.qrserver.com).
943
+ */
944
+ getQRCodeUrl(linkId, baseUrl, size = 300) {
945
+ const paymentUrl = this.getPaymentUrl(linkId, baseUrl);
946
+ return this.qrApiUrl
947
+ .replace('{url}', encodeURIComponent(paymentUrl))
948
+ .replace(/\{size\}/g, String(size));
949
+ }
950
+ // ==================== PRICE CONVERSION ====================
951
+ /**
952
+ * Get current SOL price in USD.
953
+ * Uses the configurable `priceApiUrl` (defaults to CoinGecko).
954
+ *
955
+ * **Throws** if the price API is unreachable or returns an unexpected
956
+ * format, rather than silently falling back to a stale hardcoded value.
957
+ * Callers that need a fallback should catch the error themselves.
958
+ *
959
+ * @throws {Error} If the price API request fails or returns no price.
960
+ */
961
+ async getSolPrice() {
962
+ const response = await fetch(this.priceApiUrl);
963
+ if (!response.ok) {
964
+ throw new Error(`SOL price API returned HTTP ${response.status}`);
965
+ }
966
+ const data = await response.json();
967
+ const price = data.solana?.usd;
968
+ if (typeof price !== 'number' || price <= 0) {
969
+ throw new Error('SOL price API returned an invalid or missing price. ' +
970
+ 'Ensure the priceApiUrl returns { solana: { usd: <number> } }.');
971
+ }
972
+ return price;
973
+ }
974
+ /**
975
+ * Get current SOL price in USD, with a fallback value on failure.
976
+ *
977
+ * Use this when a best-effort price is acceptable (e.g. UI display).
978
+ * For payment-critical paths, prefer `getSolPrice()` which throws on
979
+ * failure so you can surface the error to the user.
980
+ *
981
+ * @param fallback - Price to return if the API call fails (default: 100).
982
+ */
983
+ async getSolPriceSafe(fallback = 100) {
984
+ try {
985
+ return await this.getSolPrice();
986
+ }
987
+ catch {
988
+ return fallback;
989
+ }
990
+ }
991
+ /**
992
+ * Convert USD to lamports.
993
+ *
994
+ * @throws {Error} If the SOL price cannot be fetched (propagated from `getSolPrice`).
995
+ */
996
+ async usdToLamports(usdAmount) {
997
+ const solPrice = await this.getSolPrice();
998
+ const solAmount = usdAmount / solPrice;
999
+ return BigInt(Math.floor(solAmount * web3_js_1.LAMPORTS_PER_SOL));
1000
+ }
1001
+ /**
1002
+ * Convert micro-USD to lamports.
1003
+ *
1004
+ * @throws {Error} If the SOL price cannot be fetched (propagated from `getSolPrice`).
1005
+ */
1006
+ async microUsdToLamports(microUsd) {
1007
+ const usdAmount = Number(microUsd) / 1000000;
1008
+ return this.usdToLamports(usdAmount);
1009
+ }
1010
+ // ==================== TOKEN HELPERS ====================
1011
+ /**
1012
+ * Get token mint address for currency
1013
+ */
1014
+ getTokenMint(currency) {
1015
+ return exports.TOKEN_MINTS[this.network][currency];
1016
+ }
1017
+ /**
1018
+ * Get token balance
1019
+ */
1020
+ async getTokenBalance(walletAddress, currency) {
1021
+ try {
1022
+ const mint = this.getTokenMint(currency);
1023
+ const ata = await (0, spl_token_1.getAssociatedTokenAddress)(mint, walletAddress);
1024
+ const balance = await this.connection.getTokenAccountBalance(ata);
1025
+ return Number(balance.value.uiAmount);
1026
+ }
1027
+ catch {
1028
+ return 0;
1029
+ }
1030
+ }
1031
+ /** Validate buffer length and (optionally) discriminator before decoding. */
1032
+ static assertAccountData(data, accountName) {
1033
+ const minSize = RotateSDK.ACCOUNT_SIZES[accountName];
1034
+ if (data.length < minSize) {
1035
+ throw new Error(`${accountName} account data too short: expected >= ${minSize} bytes, got ${data.length}`);
1036
+ }
1037
+ const expected = RotateSDK.DISCRIMINATORS[accountName];
1038
+ const actual = data.slice(0, 8);
1039
+ if (!actual.equals(expected)) {
1040
+ throw new Error(`${accountName} discriminator mismatch: expected ${expected.toString('hex')}, got ${actual.toString('hex')}`);
1041
+ }
1042
+ }
1043
+ decodeProtocol(data) {
1044
+ RotateSDK.assertAccountData(data, 'Protocol');
1045
+ let offset = 8;
1046
+ const authority = new web3_js_1.PublicKey(data.slice(offset, offset + 32));
1047
+ offset += 32;
1048
+ const treasury = new web3_js_1.PublicKey(data.slice(offset, offset + 32));
1049
+ offset += 32;
1050
+ const usdcMint = new web3_js_1.PublicKey(data.slice(offset, offset + 32));
1051
+ offset += 32;
1052
+ const usdtMint = new web3_js_1.PublicKey(data.slice(offset, offset + 32));
1053
+ offset += 32;
1054
+ const platformCount = data.readUInt32LE(offset);
1055
+ offset += 4;
1056
+ const merchantCount = data.readUInt32LE(offset);
1057
+ offset += 4;
1058
+ const linkCount = data.readUInt32LE(offset);
1059
+ offset += 4;
1060
+ const bump = data.readUInt8(offset);
1061
+ return {
1062
+ authority,
1063
+ treasury,
1064
+ usdcMint,
1065
+ usdtMint,
1066
+ platformCount,
1067
+ merchantCount,
1068
+ linkCount,
1069
+ bump,
1070
+ };
1071
+ }
1072
+ decodePlatform(data) {
1073
+ RotateSDK.assertAccountData(data, 'Platform');
1074
+ let offset = 8;
1075
+ const id = data.readUInt32LE(offset);
1076
+ offset += 4;
1077
+ const admin = new web3_js_1.PublicKey(data.slice(offset, offset + 32));
1078
+ offset += 32;
1079
+ const wallet = new web3_js_1.PublicKey(data.slice(offset, offset + 32));
1080
+ offset += 32;
1081
+ const feeBps = data.readUInt16LE(offset);
1082
+ offset += 2;
1083
+ const active = data.readUInt8(offset) === 1;
1084
+ offset += 1;
1085
+ const bump = data.readUInt8(offset);
1086
+ return { id, admin, wallet, feeBps, active, bump };
1087
+ }
1088
+ decodeMerchant(data) {
1089
+ RotateSDK.assertAccountData(data, 'Merchant');
1090
+ let offset = 8;
1091
+ const id = data.readUInt32LE(offset);
1092
+ offset += 4;
1093
+ const platformId = data.readUInt32LE(offset);
1094
+ offset += 4;
1095
+ const wallet = new web3_js_1.PublicKey(data.slice(offset, offset + 32));
1096
+ offset += 32;
1097
+ const active = data.readUInt8(offset) === 1;
1098
+ offset += 1;
1099
+ const bump = data.readUInt8(offset);
1100
+ return { id, platformId, wallet, active, bump };
1101
+ }
1102
+ decodePaymentLink(data) {
1103
+ RotateSDK.assertAccountData(data, 'PaymentLink');
1104
+ let offset = 8;
1105
+ const id = data.readUInt32LE(offset);
1106
+ offset += 4;
1107
+ const merchantId = data.readUInt32LE(offset);
1108
+ offset += 4;
1109
+ const tokenTypeValue = data.readUInt8(offset);
1110
+ offset += 1;
1111
+ const tokenType = ['Sol', 'Usdc', 'Usdt', 'Usd'][tokenTypeValue];
1112
+ const tokenMint = new web3_js_1.PublicKey(data.slice(offset, offset + 32));
1113
+ offset += 32;
1114
+ const statusValue = data.readUInt8(offset);
1115
+ offset += 1;
1116
+ const status = ['Pending', 'PartiallyPaid', 'Paid', 'Cancelled'][statusValue];
1117
+ const allowTips = data.readUInt8(offset) === 1;
1118
+ offset += 1;
1119
+ const allowPartial = data.readUInt8(offset) === 1;
1120
+ offset += 1;
1121
+ const amount = data.readBigUInt64LE(offset);
1122
+ offset += 8;
1123
+ const amountPaid = data.readBigUInt64LE(offset);
1124
+ offset += 8;
1125
+ const expiresAt = Number(data.readBigInt64LE(offset));
1126
+ offset += 8;
1127
+ const orderRef = data.readBigUInt64LE(offset).toString();
1128
+ offset += 8;
1129
+ const bump = data.readUInt8(offset);
1130
+ return {
1131
+ id,
1132
+ merchantId,
1133
+ tokenType,
1134
+ tokenMint,
1135
+ status,
1136
+ allowTips,
1137
+ allowPartial,
1138
+ amount,
1139
+ amountPaid,
1140
+ expiresAt,
1141
+ orderRef,
1142
+ bump,
1143
+ };
1144
+ }
1145
+ // ==================== MEMO / DESCRIPTION ====================
1146
+ /**
1147
+ * Recover a payment link description from its on-chain memo.
1148
+ * Returns null if no description was embedded.
1149
+ */
1150
+ async getLinkDescription(linkId) {
1151
+ return getLinkDescription(this.connection, linkId, this.programId);
1152
+ }
1153
+ }
1154
+ exports.RotateSDK = RotateSDK;
1155
+ // ==================== DECODE HELPERS ====================
1156
+ /**
1157
+ * Anchor account discriminators: `sha256("account:<Name>")[0..8]`.
1158
+ * Used by `getProgramAccounts` filters and for decode-time validation.
1159
+ *
1160
+ * To regenerate:
1161
+ * ```ts
1162
+ * import { createHash } from 'crypto';
1163
+ * createHash('sha256').update('account:Merchant').digest().slice(0, 8);
1164
+ * ```
1165
+ */
1166
+ RotateSDK.DISCRIMINATORS = {
1167
+ // sha256("account:Protocol")[0..8]
1168
+ Protocol: Buffer.from([0x2d, 0x27, 0x65, 0x2b, 0x73, 0x48, 0x83, 0x28]),
1169
+ // sha256("account:Platform")[0..8]
1170
+ Platform: Buffer.from([0x4d, 0x5c, 0xcc, 0x3a, 0xbb, 0x62, 0x5b, 0x0c]),
1171
+ // sha256("account:Merchant")[0..8]
1172
+ Merchant: Buffer.from([0x47, 0xeb, 0x1e, 0x28, 0xe7, 0x15, 0x20, 0x40]),
1173
+ // sha256("account:PaymentLink")[0..8]
1174
+ PaymentLink: Buffer.from([0xa9, 0xf7, 0x93, 0xbd, 0x27, 0xef, 0x0e, 0x26]),
1175
+ };
1176
+ /** Expected on-chain account sizes (discriminator included). */
1177
+ RotateSDK.ACCOUNT_SIZES = {
1178
+ Protocol: 149, // 8 + 32 + 32 + 32 + 32 + 4 + 4 + 4 + 1
1179
+ Platform: 80, // 8 + 4 + 32 + 32 + 2 + 1 + 1
1180
+ Merchant: 50, // 8 + 4 + 4 + 32 + 1 + 1
1181
+ PaymentLink: 85, // 8 + 4 + 4 + 1 + 32 + 1 + 1 + 1 + 8 + 8 + 8 + 8 + 1
1182
+ };
1183
+ // ==================== EXPORTS ====================
1184
+ // Re-export store & marketplace classes and types
1185
+ var store_1 = require("./store");
1186
+ Object.defineProperty(exports, "RotateStore", { enumerable: true, get: function () { return store_1.RotateStore; } });
1187
+ Object.defineProperty(exports, "RotateCart", { enumerable: true, get: function () { return store_1.RotateCart; } });
1188
+ var marketplace_1 = require("./marketplace");
1189
+ Object.defineProperty(exports, "RotateMarketplace", { enumerable: true, get: function () { return marketplace_1.RotateMarketplace; } });
1190
+ Object.defineProperty(exports, "MarketplaceCart", { enumerable: true, get: function () { return marketplace_1.MarketplaceCart; } });
1191
+ var platform_1 = require("./platform");
1192
+ Object.defineProperty(exports, "RotatePlatformManager", { enumerable: true, get: function () { return platform_1.RotatePlatformManager; } });
1193
+ exports.default = RotateSDK;
1194
+ // Note: RotateConfig, Protocol, Platform, Merchant, PaymentLink, MEMO_PROGRAM_ID, MEMO_PREFIX,
1195
+ // createMemoInstruction, getLinkDescription, and all param types are already exported
1196
+ // at their definition sites (export function / export interface / export type).
1197
+ //# sourceMappingURL=index.js.map