@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/src/react.ts ADDED
@@ -0,0 +1,459 @@
1
+ /**
2
+ * Rotate Protocol React Hooks
3
+ *
4
+ * React hooks for easy integration with Solana wallet adapters.
5
+ *
6
+ * @packageDocumentation
7
+ */
8
+
9
+ import { useState, useEffect, useCallback, useMemo, useRef } from 'react';
10
+ import { useConnection, useWallet } from '@solana/wallet-adapter-react';
11
+ import { PublicKey } from '@solana/web3.js';
12
+ import RotateSDK, {
13
+ PaymentLink,
14
+ Platform,
15
+ Merchant,
16
+ Protocol,
17
+ calculateFees,
18
+ generateSampleId,
19
+ isValidId,
20
+ getLinkDescription,
21
+ MEMO_PROGRAM_ID,
22
+ Network,
23
+ MIN_RANDOM_ID,
24
+ MAX_RANDOM_ID,
25
+ } from './index';
26
+
27
+ // ==================== TYPES ====================
28
+
29
+ export interface UseRotateOptions {
30
+ network?: Network;
31
+ rpcEndpoint?: string;
32
+ }
33
+
34
+ export interface PaymentStatus {
35
+ linkId: number;
36
+ status: PaymentLink['status'];
37
+ amountPaid: bigint;
38
+ amountTotal: bigint;
39
+ isPaid: boolean;
40
+ isExpired: boolean;
41
+ }
42
+
43
+ // ==================== MAIN HOOK ====================
44
+
45
+ /**
46
+ * Main hook for Rotate Protocol
47
+ */
48
+ export function useRotate(options: UseRotateOptions = {}) {
49
+ const { connection } = useConnection();
50
+ const wallet = useWallet();
51
+
52
+ const sdk = useMemo(() => {
53
+ return new RotateSDK({
54
+ network: options.network || 'mainnet-beta',
55
+ rpcEndpoint: options.rpcEndpoint,
56
+ });
57
+ }, [options.network, options.rpcEndpoint]);
58
+
59
+ const [protocol, setProtocol] = useState<Protocol | null>(null);
60
+ const [loading, setLoading] = useState(true);
61
+ const [error, setError] = useState<string | null>(null);
62
+
63
+ // Load protocol on mount
64
+ useEffect(() => {
65
+ async function load() {
66
+ try {
67
+ const data = await sdk.getProtocol();
68
+ setProtocol(data);
69
+ } catch (e: any) {
70
+ setError(e.message);
71
+ } finally {
72
+ setLoading(false);
73
+ }
74
+ }
75
+ load();
76
+ }, [sdk]);
77
+
78
+ return {
79
+ sdk,
80
+ protocol,
81
+ loading,
82
+ error,
83
+ connected: wallet.connected,
84
+ publicKey: wallet.publicKey,
85
+ signTransaction: wallet.signTransaction,
86
+ };
87
+ }
88
+
89
+ // ==================== PAYMENT LINK HOOK ====================
90
+
91
+ /**
92
+ * Hook for working with a specific payment link
93
+ */
94
+ export function usePaymentLink(linkId: number, options: UseRotateOptions = {}) {
95
+ const { sdk } = useRotate(options);
96
+ const [link, setLink] = useState<PaymentLink | null>(null);
97
+ const [loading, setLoading] = useState(true);
98
+ const [error, setError] = useState<string | null>(null);
99
+
100
+ // Load link data
101
+ const refresh = useCallback(async () => {
102
+ setLoading(true);
103
+ try {
104
+ const data = await sdk.getPaymentLink(linkId);
105
+ setLink(data);
106
+ setError(null);
107
+ } catch (e: any) {
108
+ setError(e.message);
109
+ } finally {
110
+ setLoading(false);
111
+ }
112
+ }, [sdk, linkId]);
113
+
114
+ useEffect(() => {
115
+ refresh();
116
+ }, [refresh]);
117
+
118
+ // Poll for updates
119
+ const [polling, setPolling] = useState(false);
120
+ const pollIntervalRef = useRef(2000);
121
+
122
+ const startPolling = useCallback((intervalMs: number = 2000) => {
123
+ pollIntervalRef.current = intervalMs;
124
+ setPolling(true);
125
+ }, []);
126
+
127
+ const stopPolling = useCallback(() => {
128
+ setPolling(false);
129
+ }, []);
130
+
131
+ useEffect(() => {
132
+ if (!polling) return;
133
+
134
+ const interval = setInterval(refresh, pollIntervalRef.current);
135
+ return () => clearInterval(interval);
136
+ }, [polling, refresh]);
137
+
138
+ // Status helpers
139
+ const status: PaymentStatus | null = link ? {
140
+ linkId: link.id,
141
+ status: link.status,
142
+ amountPaid: link.amountPaid,
143
+ amountTotal: link.amount,
144
+ isPaid: link.status === 'Paid',
145
+ isExpired: link.expiresAt > 0 && Date.now() / 1000 > link.expiresAt,
146
+ } : null;
147
+
148
+ return {
149
+ link,
150
+ status,
151
+ loading,
152
+ error,
153
+ refresh,
154
+ startPolling,
155
+ stopPolling,
156
+ isPolling: polling,
157
+ paymentUrl: sdk.getPaymentUrl(linkId),
158
+ };
159
+ }
160
+
161
+ // ==================== MERCHANT HOOK ====================
162
+
163
+ /**
164
+ * Hook for merchant operations
165
+ */
166
+ export function useMerchant(merchantId: number, options: UseRotateOptions = {}) {
167
+ const { sdk } = useRotate(options);
168
+ const [merchant, setMerchant] = useState<Merchant | null>(null);
169
+ const [platform, setPlatform] = useState<Platform | null>(null);
170
+ const [loading, setLoading] = useState(true);
171
+ const [error, setError] = useState<string | null>(null);
172
+
173
+ useEffect(() => {
174
+ async function load() {
175
+ try {
176
+ const merchantData = await sdk.getMerchant(merchantId);
177
+ setMerchant(merchantData);
178
+
179
+ if (merchantData) {
180
+ const platformData = await sdk.getPlatform(merchantData.platformId);
181
+ setPlatform(platformData);
182
+ }
183
+ } catch (e: any) {
184
+ setError(e.message);
185
+ } finally {
186
+ setLoading(false);
187
+ }
188
+ }
189
+ load();
190
+ }, [sdk, merchantId]);
191
+
192
+ // Calculate fees helper
193
+ const getFees = useCallback((amountUsd: number) => {
194
+ return calculateFees(amountUsd, platform?.feeBps || 0);
195
+ }, [platform]);
196
+
197
+ return {
198
+ merchant,
199
+ platform,
200
+ loading,
201
+ error,
202
+ getFees,
203
+ };
204
+ }
205
+
206
+ // ==================== SOL PRICE HOOK ====================
207
+
208
+ /**
209
+ * Hook for SOL price.
210
+ * `priceApiUrl` can be overridden to use a custom price feed (default: CoinGecko).
211
+ */
212
+ export function useSolPrice(
213
+ refreshIntervalMs: number = 30000,
214
+ priceApiUrl: string = 'https://api.coingecko.com/api/v3/simple/price?ids=solana&vs_currencies=usd'
215
+ ) {
216
+ const [price, setPrice] = useState<number | null>(null);
217
+ const [loading, setLoading] = useState(true);
218
+
219
+ const refresh = useCallback(async () => {
220
+ try {
221
+ const response = await fetch(priceApiUrl);
222
+ const data = await response.json() as { solana?: { usd?: number } };
223
+ setPrice(data.solana?.usd || null);
224
+ } catch {
225
+ // Ignore errors
226
+ } finally {
227
+ setLoading(false);
228
+ }
229
+ }, [priceApiUrl]);
230
+
231
+ useEffect(() => {
232
+ refresh();
233
+ const interval = setInterval(refresh, refreshIntervalMs);
234
+ return () => clearInterval(interval);
235
+ }, [refresh, refreshIntervalMs]);
236
+
237
+ const usdToSol = useCallback((usd: number) => {
238
+ if (!price) return null;
239
+ return usd / price;
240
+ }, [price]);
241
+
242
+ const solToUsd = useCallback((sol: number) => {
243
+ if (!price) return null;
244
+ return sol * price;
245
+ }, [price]);
246
+
247
+ return {
248
+ price,
249
+ loading,
250
+ refresh,
251
+ usdToSol,
252
+ solToUsd,
253
+ };
254
+ }
255
+
256
+ // ==================== TOKEN BALANCE HOOK ====================
257
+
258
+ /**
259
+ * Hook for token balances
260
+ */
261
+ export function useTokenBalances(options: UseRotateOptions = {}) {
262
+ const { sdk } = useRotate(options);
263
+ const { publicKey, connected } = useWallet();
264
+ const { connection } = useConnection();
265
+
266
+ const [balances, setBalances] = useState<{
267
+ sol: number;
268
+ usdc: number;
269
+ usdt: number;
270
+ }>({ sol: 0, usdc: 0, usdt: 0 });
271
+ const [loading, setLoading] = useState(true);
272
+
273
+ const refresh = useCallback(async () => {
274
+ if (!publicKey) return;
275
+
276
+ setLoading(true);
277
+ try {
278
+ const [solBalance, usdcBalance, usdtBalance] = await Promise.all([
279
+ connection.getBalance(publicKey),
280
+ sdk.getTokenBalance(publicKey, 'USDC'),
281
+ sdk.getTokenBalance(publicKey, 'USDT'),
282
+ ]);
283
+
284
+ setBalances({
285
+ sol: solBalance / 1e9,
286
+ usdc: usdcBalance,
287
+ usdt: usdtBalance,
288
+ });
289
+ } catch {
290
+ // Ignore errors
291
+ } finally {
292
+ setLoading(false);
293
+ }
294
+ }, [connection, publicKey, sdk]);
295
+
296
+ useEffect(() => {
297
+ if (connected && publicKey) {
298
+ refresh();
299
+ }
300
+ }, [connected, publicKey, refresh]);
301
+
302
+ return {
303
+ balances,
304
+ loading,
305
+ refresh,
306
+ hasEnough: (amount: number, currency: 'SOL' | 'USDC' | 'USDT') => {
307
+ const balance = currency === 'SOL' ? balances.sol :
308
+ currency === 'USDC' ? balances.usdc :
309
+ balances.usdt;
310
+ return balance >= amount;
311
+ },
312
+ };
313
+ }
314
+
315
+ // ==================== PAYMENT FLOW HOOK ====================
316
+
317
+ /**
318
+ * Complete payment flow hook
319
+ */
320
+ export function usePaymentFlow(linkId: number, options: UseRotateOptions = {}) {
321
+ const { sdk, connected, publicKey } = useRotate(options);
322
+ const { link, status, refresh, startPolling, stopPolling } = usePaymentLink(linkId, options);
323
+ const { price: solPrice } = useSolPrice();
324
+ const { balances } = useTokenBalances(options);
325
+
326
+ const [selectedCurrency, setSelectedCurrency] = useState<'SOL' | 'USDC' | 'USDT'>('USDC');
327
+ const [paying, setPaying] = useState(false);
328
+ const [paymentError, setPaymentError] = useState<string | null>(null);
329
+
330
+ // Calculate amount in selected currency, accounting for link token type
331
+ const amountInCurrency = useMemo(() => {
332
+ if (!link) return null;
333
+
334
+ // SOL-denominated links store lamports (9 decimals)
335
+ if (link.tokenType === 'Sol') {
336
+ return Number(link.amount) / 1_000_000_000;
337
+ }
338
+
339
+ // USD / USDC / USDT links store micro-units (6 decimals)
340
+ const amountUsd = Number(link.amount) / 1_000_000;
341
+
342
+ if (selectedCurrency === 'SOL' && solPrice) {
343
+ return amountUsd / solPrice;
344
+ }
345
+
346
+ // USDC/USDT are 1:1 with USD
347
+ return amountUsd;
348
+ }, [link, selectedCurrency, solPrice]);
349
+
350
+ // Check if user can pay
351
+ const canPay = useMemo(() => {
352
+ if (!connected || !link || !amountInCurrency) return false;
353
+ if (link.status !== 'Pending' && link.status !== 'PartiallyPaid') return false;
354
+
355
+ const balance = selectedCurrency === 'SOL' ? balances.sol :
356
+ selectedCurrency === 'USDC' ? balances.usdc :
357
+ balances.usdt;
358
+
359
+ return balance >= amountInCurrency;
360
+ }, [connected, link, amountInCurrency, selectedCurrency, balances]);
361
+
362
+ return {
363
+ link,
364
+ status,
365
+ connected,
366
+ publicKey,
367
+ selectedCurrency,
368
+ setSelectedCurrency,
369
+ amountInCurrency,
370
+ canPay,
371
+ paying,
372
+ paymentError,
373
+ refresh,
374
+ startPolling,
375
+ stopPolling,
376
+ balances,
377
+ solPrice,
378
+ };
379
+ }
380
+
381
+ // ==================== PLATFORM/MERCHANT CREATION HOOK ====================
382
+
383
+ /**
384
+ * Hook for creating platforms and merchants with random IDs
385
+ */
386
+ export function useCreateEntities(options: UseRotateOptions = {}) {
387
+ const { sdk, connected, publicKey } = useRotate(options);
388
+ const [creating, setCreating] = useState(false);
389
+ const [error, setError] = useState<string | null>(null);
390
+
391
+ const createPlatform = useCallback(async (feeBps: number, wallet: PublicKey) => {
392
+ if (!connected) throw new Error('Wallet not connected');
393
+
394
+ setCreating(true);
395
+ setError(null);
396
+
397
+ try {
398
+ const result = await sdk.createPlatform({ feeBps, wallet });
399
+ return result;
400
+ } catch (e: any) {
401
+ setError(e.message);
402
+ throw e;
403
+ } finally {
404
+ setCreating(false);
405
+ }
406
+ }, [sdk, connected]);
407
+
408
+ const createMerchant = useCallback(async (platformId: number, wallet: PublicKey) => {
409
+ if (!connected) throw new Error('Wallet not connected');
410
+
411
+ setCreating(true);
412
+ setError(null);
413
+
414
+ try {
415
+ const result = await sdk.createMerchant({ platformId, wallet });
416
+ return result;
417
+ } catch (e: any) {
418
+ setError(e.message);
419
+ throw e;
420
+ } finally {
421
+ setCreating(false);
422
+ }
423
+ }, [sdk, connected]);
424
+
425
+ return {
426
+ createPlatform,
427
+ createMerchant,
428
+ creating,
429
+ error,
430
+ generateSampleId,
431
+ isValidId,
432
+ MIN_RANDOM_ID,
433
+ MAX_RANDOM_ID,
434
+ };
435
+ }
436
+
437
+ // ==================== STORE & MARKETPLACE HOOKS ====================
438
+
439
+ // Re-export from hooks module for convenience
440
+ export { useProducts, useCart, useMarketplace, useMarketplaceCart } from './hooks';
441
+ export type { UseProductsReturn, UseCartReturn, UseMarketplaceReturn, UseMarketplaceCartReturn } from './hooks';
442
+
443
+ // ==================== EXPORTS ====================
444
+
445
+ export {
446
+ RotateSDK,
447
+ calculateFees,
448
+ generateSampleId,
449
+ isValidId,
450
+ getLinkDescription,
451
+ MEMO_PROGRAM_ID,
452
+ MIN_RANDOM_ID,
453
+ MAX_RANDOM_ID,
454
+ };
455
+
456
+ // Re-export store, marketplace & platform classes
457
+ export { RotateStore, RotateCart } from './store';
458
+ export { RotateMarketplace, MarketplaceCart } from './marketplace';
459
+ export { RotatePlatformManager } from './platform';