@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.
- package/README.md +453 -0
- package/dist/catalog.d.ts +112 -0
- package/dist/catalog.d.ts.map +1 -0
- package/dist/catalog.js +210 -0
- package/dist/catalog.js.map +1 -0
- package/dist/components/CheckoutForm.d.ts +86 -0
- package/dist/components/CheckoutForm.d.ts.map +1 -0
- package/dist/components/CheckoutForm.js +332 -0
- package/dist/components/CheckoutForm.js.map +1 -0
- package/dist/components/HostedCheckout.d.ts +57 -0
- package/dist/components/HostedCheckout.d.ts.map +1 -0
- package/dist/components/HostedCheckout.js +414 -0
- package/dist/components/HostedCheckout.js.map +1 -0
- package/dist/components/PaymentButton.d.ts +80 -0
- package/dist/components/PaymentButton.d.ts.map +1 -0
- package/dist/components/PaymentButton.js +210 -0
- package/dist/components/PaymentButton.js.map +1 -0
- package/dist/components/RotateProvider.d.ts +115 -0
- package/dist/components/RotateProvider.d.ts.map +1 -0
- package/dist/components/RotateProvider.js +264 -0
- package/dist/components/RotateProvider.js.map +1 -0
- package/dist/components/index.d.ts +17 -0
- package/dist/components/index.d.ts.map +1 -0
- package/dist/components/index.js +27 -0
- package/dist/components/index.js.map +1 -0
- package/dist/embed.d.ts +85 -0
- package/dist/embed.d.ts.map +1 -0
- package/dist/embed.js +313 -0
- package/dist/embed.js.map +1 -0
- package/dist/hooks.d.ts +156 -0
- package/dist/hooks.d.ts.map +1 -0
- package/dist/hooks.js +280 -0
- package/dist/hooks.js.map +1 -0
- package/dist/idl/rotate_connect.json +2572 -0
- package/dist/index.d.ts +505 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +1197 -0
- package/dist/index.js.map +1 -0
- package/dist/marketplace.d.ts +257 -0
- package/dist/marketplace.d.ts.map +1 -0
- package/dist/marketplace.js +433 -0
- package/dist/marketplace.js.map +1 -0
- package/dist/platform.d.ts +234 -0
- package/dist/platform.d.ts.map +1 -0
- package/dist/platform.js +268 -0
- package/dist/platform.js.map +1 -0
- package/dist/react.d.ts +140 -0
- package/dist/react.d.ts.map +1 -0
- package/dist/react.js +429 -0
- package/dist/react.js.map +1 -0
- package/dist/store.d.ts +213 -0
- package/dist/store.d.ts.map +1 -0
- package/dist/store.js +404 -0
- package/dist/store.js.map +1 -0
- package/dist/webhooks.d.ts +149 -0
- package/dist/webhooks.d.ts.map +1 -0
- package/dist/webhooks.js +371 -0
- package/dist/webhooks.js.map +1 -0
- package/package.json +114 -0
- package/src/catalog.ts +299 -0
- package/src/components/CheckoutForm.tsx +608 -0
- package/src/components/HostedCheckout.tsx +675 -0
- package/src/components/PaymentButton.tsx +348 -0
- package/src/components/RotateProvider.tsx +370 -0
- package/src/components/index.ts +26 -0
- package/src/embed.ts +408 -0
- package/src/hooks.ts +518 -0
- package/src/idl/rotate_connect.json +2572 -0
- package/src/index.ts +1538 -0
- package/src/marketplace.ts +642 -0
- package/src/platform.ts +403 -0
- package/src/react.ts +459 -0
- package/src/store.ts +577 -0
- 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
|