@diviswap/sdk 1.7.6

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 (66) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +510 -0
  3. package/bin/create-diviswap-app.js +25 -0
  4. package/bin/diviswap-sdk.js +4 -0
  5. package/dist/cli/index.js +1888 -0
  6. package/dist/cli/templates/nextjs-app/actions.ts.hbs +259 -0
  7. package/dist/cli/templates/nextjs-app/api-hooks.ts.hbs +439 -0
  8. package/dist/cli/templates/nextjs-app/api-route.ts.hbs +502 -0
  9. package/dist/cli/templates/nextjs-app/auth-context.tsx.hbs +59 -0
  10. package/dist/cli/templates/nextjs-app/client.ts.hbs +116 -0
  11. package/dist/cli/templates/nextjs-app/dashboard-hooks.ts.hbs +180 -0
  12. package/dist/cli/templates/nextjs-app/example-page.tsx.hbs +276 -0
  13. package/dist/cli/templates/nextjs-app/hooks.ts.hbs +252 -0
  14. package/dist/cli/templates/nextjs-app/kyc-hooks.ts.hbs +87 -0
  15. package/dist/cli/templates/nextjs-app/kyc-wizard.css.hbs +433 -0
  16. package/dist/cli/templates/nextjs-app/kyc-wizard.tsx.hbs +711 -0
  17. package/dist/cli/templates/nextjs-app/layout-wrapper.tsx.hbs +13 -0
  18. package/dist/cli/templates/nextjs-app/layout.tsx.hbs +13 -0
  19. package/dist/cli/templates/nextjs-app/middleware.ts.hbs +49 -0
  20. package/dist/cli/templates/nextjs-app/provider-wrapper.tsx.hbs +8 -0
  21. package/dist/cli/templates/nextjs-app/provider.tsx.hbs +408 -0
  22. package/dist/cli/templates/nextjs-app/setup-provider.tsx.hbs +25 -0
  23. package/dist/cli/templates/nextjs-app/types.ts.hbs +159 -0
  24. package/dist/cli/templates/react/api-client-wrapper.ts.hbs +89 -0
  25. package/dist/cli/templates/react/example.tsx.hbs +69 -0
  26. package/dist/cli/templates/react/tanstack-hooks.ts.hbs +185 -0
  27. package/dist/cli/templates/webhooks/nextjs.hbs +98 -0
  28. package/dist/index.d.mts +91 -0
  29. package/dist/index.d.ts +91 -0
  30. package/dist/index.js +2339 -0
  31. package/dist/index.js.map +1 -0
  32. package/dist/index.mjs +2313 -0
  33. package/dist/index.mjs.map +1 -0
  34. package/dist/react/index.d.mts +192 -0
  35. package/dist/react/index.d.ts +192 -0
  36. package/dist/react/index.js +1083 -0
  37. package/dist/react/index.js.map +1 -0
  38. package/dist/react/index.mjs +1064 -0
  39. package/dist/react/index.mjs.map +1 -0
  40. package/dist/wallet-BEGvzNtB.d.mts +1614 -0
  41. package/dist/wallet-BEGvzNtB.d.ts +1614 -0
  42. package/package.json +102 -0
  43. package/src/cli/templates/index.ts +65 -0
  44. package/src/cli/templates/nextjs-app/actions.ts.hbs +259 -0
  45. package/src/cli/templates/nextjs-app/api-hooks.ts.hbs +439 -0
  46. package/src/cli/templates/nextjs-app/api-route.ts.hbs +502 -0
  47. package/src/cli/templates/nextjs-app/auth-context.tsx.hbs +59 -0
  48. package/src/cli/templates/nextjs-app/client.ts.hbs +116 -0
  49. package/src/cli/templates/nextjs-app/dashboard-hooks.ts.hbs +180 -0
  50. package/src/cli/templates/nextjs-app/example-page.tsx.hbs +276 -0
  51. package/src/cli/templates/nextjs-app/hooks.ts.hbs +252 -0
  52. package/src/cli/templates/nextjs-app/kyc-hooks.ts.hbs +87 -0
  53. package/src/cli/templates/nextjs-app/kyc-wizard.css.hbs +433 -0
  54. package/src/cli/templates/nextjs-app/kyc-wizard.tsx.hbs +711 -0
  55. package/src/cli/templates/nextjs-app/layout-wrapper.tsx.hbs +13 -0
  56. package/src/cli/templates/nextjs-app/layout.tsx.hbs +13 -0
  57. package/src/cli/templates/nextjs-app/middleware.ts.hbs +49 -0
  58. package/src/cli/templates/nextjs-app/provider-wrapper.tsx.hbs +8 -0
  59. package/src/cli/templates/nextjs-app/provider.tsx.hbs +408 -0
  60. package/src/cli/templates/nextjs-app/setup-provider.tsx.hbs +25 -0
  61. package/src/cli/templates/nextjs-app/types.ts.hbs +159 -0
  62. package/src/cli/templates/react/api-client-wrapper.ts.hbs +89 -0
  63. package/src/cli/templates/react/example.tsx.hbs +69 -0
  64. package/src/cli/templates/react/tanstack-hooks.ts.hbs +185 -0
  65. package/src/cli/templates/shared/client.ts +78 -0
  66. package/src/cli/templates/webhooks/nextjs.hbs +98 -0
@@ -0,0 +1,259 @@
1
+ 'use server';
2
+
3
+ import { Diviswap } from '@diviswap/sdk';
4
+ import type { Transaction, Payee } from '@diviswap/sdk';
5
+ import { revalidatePath } from 'next/cache';
6
+ import { cookies } from 'next/headers';
7
+
8
+ // Initialize Diviswap SDK
9
+ function getDiviswap() {
10
+ return Diviswap.init({
11
+ apiKey: process.env.DIVISWAP_API_KEY!,
12
+ clientId: process.env.DIVISWAP_CLIENT_ID!,
13
+ environment: (process.env.NEXT_PUBLIC_DIVISWAP_ENV as 'production' | 'sandbox') || 'production',
14
+ debug: true // Enable debug logging
15
+ });
16
+ }
17
+
18
+ // Helper to get session token from cookies
19
+ async function getSessionToken() {
20
+ const cookieStore = await cookies();
21
+ const session = cookieStore.get('diviswap_session');
22
+ return session?.value;
23
+ }
24
+
25
+ // Transaction Server Actions
26
+ export async function createTransactionAction(data: {
27
+ type: 'onramp' | 'offramp';
28
+ amount: number;
29
+ currency?: string;
30
+ payeeId?: string;
31
+ paymentMethodId?: string;
32
+ // Required for offramp
33
+ fromAddress?: string;
34
+ chain?: string;
35
+ }) {
36
+ try {
37
+ const sessionToken = await getSessionToken();
38
+ if (!sessionToken) {
39
+ return { success: false, error: 'Not authenticated' };
40
+ }
41
+
42
+ const diviswap = getDiviswap();
43
+
44
+ // Use the appropriate method based on transaction type
45
+ let transaction;
46
+ if (data.type === 'onramp') {
47
+ // Note: Onramp is not yet available in v1 API
48
+ return { success: false, error: 'Onramp transactions are not yet available' };
49
+ } else {
50
+ transaction = await diviswap.transactions.offramp({
51
+ amount: data.amount,
52
+ currency: data.currency || 'USD',
53
+ payeeId: data.payeeId!,
54
+ fromAddress: data.fromAddress!, // Required for offramp
55
+ chain: data.chain || 'ethereum'
56
+ });
57
+ }
58
+
59
+ revalidatePath('/diviswap');
60
+ return { success: true, transaction };
61
+ } catch (error) {
62
+ return {
63
+ success: false,
64
+ error: error instanceof Error ? error.message : 'Transaction creation failed'
65
+ };
66
+ }
67
+ }
68
+
69
+ export async function getTransactionsAction(filters?: {
70
+ limit?: number;
71
+ offset?: number;
72
+ status?: 'pending' | 'processing' | 'completed' | 'failed';
73
+ type?: 'onramp' | 'offramp';
74
+ }) {
75
+ try {
76
+ const sessionToken = await getSessionToken();
77
+ if (!sessionToken) {
78
+ return { success: false, error: 'Not authenticated' };
79
+ }
80
+
81
+ const diviswap = getDiviswap();
82
+ const transactions = await diviswap.transactions.list(filters);
83
+
84
+ return { success: true, transactions };
85
+ } catch (error) {
86
+ return {
87
+ success: false,
88
+ error: error instanceof Error ? error.message : 'Failed to fetch transactions'
89
+ };
90
+ }
91
+ }
92
+
93
+ // Payee Server Actions
94
+ export async function getPayeesAction() {
95
+ try {
96
+ const sessionToken = await getSessionToken();
97
+ if (!sessionToken) {
98
+ return { success: false, error: 'Not authenticated' };
99
+ }
100
+
101
+ const diviswap = getDiviswap();
102
+ const payees = await diviswap.payees.list();
103
+
104
+ return { success: true, payees };
105
+ } catch (error) {
106
+ return {
107
+ success: false,
108
+ error: error instanceof Error ? error.message : 'Failed to fetch payees'
109
+ };
110
+ }
111
+ }
112
+
113
+ export async function createPayeeAction(data: {
114
+ nickname: string;
115
+ accountNumber: string;
116
+ routingNumber: string;
117
+ accountType: 'checking' | 'savings';
118
+ }) {
119
+ try {
120
+ const sessionToken = await getSessionToken();
121
+ if (!sessionToken) {
122
+ return { success: false, error: 'Not authenticated' };
123
+ }
124
+
125
+ const diviswap = getDiviswap();
126
+ const payee = await diviswap.payees.create({
127
+ nickname: data.nickname,
128
+ accountNumber: data.accountNumber,
129
+ routingNumber: data.routingNumber,
130
+ accountType: data.accountType,
131
+ setAsDefault: false
132
+ });
133
+
134
+ revalidatePath('/diviswap');
135
+ return { success: true, payee };
136
+ } catch (error) {
137
+ return {
138
+ success: false,
139
+ error: error instanceof Error ? error.message : 'Payee creation failed'
140
+ };
141
+ }
142
+ }
143
+
144
+ export async function deletePayeeAction(payeeId: string) {
145
+ try {
146
+ const sessionToken = await getSessionToken();
147
+ if (!sessionToken) {
148
+ return { success: false, error: 'Not authenticated' };
149
+ }
150
+
151
+ const diviswap = getDiviswap();
152
+ await diviswap.payees.delete(payeeId);
153
+
154
+ revalidatePath('/diviswap');
155
+ return { success: true };
156
+ } catch (error) {
157
+ return {
158
+ success: false,
159
+ error: error instanceof Error ? error.message : 'Failed to delete payee'
160
+ };
161
+ }
162
+ }
163
+
164
+ {{#if (includes features "fees")}}
165
+ // Fee Calculation Server Actions
166
+ export async function calculateFeesAction(data: {
167
+ amount: number;
168
+ type: 'onramp' | 'offramp';
169
+ currency?: string;
170
+ }) {
171
+ try {
172
+ const diviswap = getDiviswap();
173
+ const fees = await diviswap.fees.calculateFees({
174
+ amount: data.amount,
175
+ userId: undefined // Optional: for per-user fees
176
+ });
177
+
178
+ return { success: true, fees };
179
+ } catch (error) {
180
+ return {
181
+ success: false,
182
+ error: error instanceof Error ? error.message : 'Fee calculation failed'
183
+ };
184
+ }
185
+ }
186
+
187
+ export async function getIntegratorFeesAction() {
188
+ try {
189
+ const sessionToken = await getSessionToken();
190
+ if (!sessionToken) {
191
+ return { success: false, error: 'Not authenticated' };
192
+ }
193
+
194
+ const diviswap = getDiviswap();
195
+ const fees = await diviswap.fees.getFees();
196
+
197
+ return { success: true, fees };
198
+ } catch (error) {
199
+ return {
200
+ success: false,
201
+ error: error instanceof Error ? error.message : 'Failed to fetch integrator fees'
202
+ };
203
+ }
204
+ }
205
+
206
+ export async function updateIntegratorFeeAction(data: {
207
+ percentage: number;
208
+ userId?: string;
209
+ }) {
210
+ try {
211
+ const sessionToken = await getSessionToken();
212
+ if (!sessionToken) {
213
+ return { success: false, error: 'Not authenticated' };
214
+ }
215
+
216
+ const diviswap = getDiviswap();
217
+ const fee = await diviswap.fees.setFee(data);
218
+
219
+ revalidatePath('/diviswap');
220
+ return { success: true, fee };
221
+ } catch (error) {
222
+ return {
223
+ success: false,
224
+ error: error instanceof Error ? error.message : 'Failed to update fee'
225
+ };
226
+ }
227
+ }
228
+ {{/if}}
229
+
230
+ // Authentication Server Actions (for server components that need auth status)
231
+ export async function getCurrentUserAction() {
232
+ try {
233
+ const sessionToken = await getSessionToken();
234
+ if (!sessionToken) {
235
+ return { success: false, user: null };
236
+ }
237
+
238
+ const diviswap = getDiviswap();
239
+ const user = await diviswap.auth.getProfile();
240
+
241
+ return { success: true, user };
242
+ } catch (error) {
243
+ return { success: false, user: null };
244
+ }
245
+ }
246
+
247
+ // Utility action for checking API status
248
+ export async function checkApiStatusAction() {
249
+ try {
250
+ const diviswap = getDiviswap();
251
+ // Note: Health check endpoint not available in SDK
252
+ return { success: true, status: 'OK' };
253
+ } catch (error) {
254
+ return {
255
+ success: false,
256
+ error: error instanceof Error ? error.message : 'API health check failed'
257
+ };
258
+ }
259
+ }
@@ -0,0 +1,439 @@
1
+ import { useState, useEffect, useCallback } from 'react';
2
+
3
+ // Authentication context hook (implement based on your auth system)
4
+ export function useAuth() {
5
+ // Replace this with your actual authentication logic
6
+ // This could be from a context provider, session storage, etc.
7
+ const [customerId, setCustomerId] = useState<string | null>(null);
8
+ const [customerEmail, setCustomerEmail] = useState<string | null>(null);
9
+ const [isAuthenticated, setIsAuthenticated] = useState(false);
10
+ const [loading, setLoading] = useState(true);
11
+
12
+ useEffect(() => {
13
+ // Initialize auth state from your auth system
14
+ // Example: get from session, context, or other auth provider
15
+ const initAuth = async () => {
16
+ try {
17
+ // Your auth initialization logic here
18
+ // Example:
19
+ // const session = await getSession();
20
+ // if (session) {
21
+ // setCustomerId(session.customerId);
22
+ // setCustomerEmail(session.customerEmail);
23
+ // setIsAuthenticated(true);
24
+ // }
25
+ } catch (error) {
26
+ console.error('Auth initialization failed:', error);
27
+ } finally {
28
+ setLoading(false);
29
+ }
30
+ };
31
+
32
+ initAuth();
33
+ }, []);
34
+
35
+ const setCustomer = useCallback(async (customerId: string, customerEmail: string) => {
36
+ try {
37
+ const response = await fetch('/api/diviswap', {
38
+ method: 'POST',
39
+ headers: { 'Content-Type': 'application/json' },
40
+ body: JSON.stringify({
41
+ action: 'setCustomer',
42
+ customerId,
43
+ customerEmail
44
+ })
45
+ });
46
+
47
+ if (response.ok) {
48
+ setCustomerId(customerId);
49
+ setCustomerEmail(customerEmail);
50
+ setIsAuthenticated(true);
51
+ }
52
+ } catch (error) {
53
+ console.error('Failed to set customer context:', error);
54
+ }
55
+ }, []);
56
+
57
+ const clearCustomer = useCallback(async () => {
58
+ try {
59
+ await fetch('/api/diviswap', {
60
+ method: 'POST',
61
+ headers: { 'Content-Type': 'application/json' },
62
+ body: JSON.stringify({ action: 'clearCustomer' })
63
+ });
64
+
65
+ setCustomerId(null);
66
+ setCustomerEmail(null);
67
+ setIsAuthenticated(false);
68
+ } catch (error) {
69
+ console.error('Failed to clear customer context:', error);
70
+ }
71
+ }, []);
72
+
73
+ return {
74
+ customerId,
75
+ customerEmail,
76
+ isAuthenticated,
77
+ loading,
78
+ setCustomer,
79
+ clearCustomer
80
+ };
81
+ }
82
+
83
+ // Hook for managing transactions
84
+ export function useTransactions() {
85
+ const { customerId, customerEmail, isAuthenticated } = useAuth();
86
+ const [transactions, setTransactions] = useState<any[]>([]);
87
+ const [loading, setLoading] = useState(false);
88
+ const [error, setError] = useState<Error | null>(null);
89
+
90
+ const fetchTransactions = useCallback(async (filters?: {
91
+ limit?: number;
92
+ offset?: number;
93
+ status?: string;
94
+ type?: 'onramp' | 'offramp';
95
+ }) => {
96
+ if (!isAuthenticated || !customerId) return [];
97
+
98
+ setLoading(true);
99
+ setError(null);
100
+
101
+ try {
102
+ const queryParams = new URLSearchParams({
103
+ resource: 'transactions',
104
+ customerId,
105
+ customerEmail,
106
+ ...filters
107
+ });
108
+
109
+ const response = await fetch(`/api/diviswap?${queryParams}`);
110
+ if (!response.ok) throw new Error('Failed to fetch transactions');
111
+
112
+ const data = await response.json();
113
+ setTransactions(data);
114
+ return data;
115
+ } catch (err) {
116
+ const error = err as Error;
117
+ setError(error);
118
+ console.error('Failed to fetch transactions:', error);
119
+ throw error;
120
+ } finally {
121
+ setLoading(false);
122
+ }
123
+ }, [customerId, customerEmail, isAuthenticated]);
124
+
125
+ const createTransaction = useCallback(async (data: any) => {
126
+ if (!isAuthenticated || !customerId) throw new Error('Not authenticated');
127
+
128
+ setLoading(true);
129
+ setError(null);
130
+
131
+ try {
132
+ const response = await fetch('/api/diviswap', {
133
+ method: 'POST',
134
+ headers: { 'Content-Type': 'application/json' },
135
+ body: JSON.stringify({
136
+ action: 'createTransaction',
137
+ customerId,
138
+ customerEmail,
139
+ ...data
140
+ })
141
+ });
142
+
143
+ if (!response.ok) throw new Error('Failed to create transaction');
144
+
145
+ const transaction = await response.json();
146
+ // Refresh transactions list
147
+ await fetchTransactions();
148
+ return transaction;
149
+ } catch (err) {
150
+ const error = err as Error;
151
+ setError(error);
152
+ console.error('Failed to create transaction:', error);
153
+ throw error;
154
+ } finally {
155
+ setLoading(false);
156
+ }
157
+ }, [customerId, customerEmail, isAuthenticated, fetchTransactions]);
158
+
159
+ return {
160
+ transactions,
161
+ loading,
162
+ error,
163
+ fetchTransactions,
164
+ createTransaction,
165
+ refresh: fetchTransactions,
166
+ };
167
+ }
168
+
169
+ // Hook for managing payees
170
+ export function usePayees() {
171
+ const { customerId, customerEmail, isAuthenticated } = useAuth();
172
+ const [payees, setPayees] = useState<any[]>([]);
173
+ const [loading, setLoading] = useState(false);
174
+ const [error, setError] = useState<Error | null>(null);
175
+
176
+ const fetchPayees = useCallback(async () => {
177
+ if (!isAuthenticated || !customerId) return [];
178
+
179
+ setLoading(true);
180
+ setError(null);
181
+
182
+ try {
183
+ const response = await fetch(`/api/diviswap?resource=payees&customerId=${customerId}&customerEmail=${customerEmail}`);
184
+ if (!response.ok) throw new Error('Failed to fetch payees');
185
+
186
+ const data = await response.json();
187
+ setPayees(data);
188
+ return data;
189
+ } catch (err) {
190
+ const error = err as Error;
191
+ setError(error);
192
+ console.error('Failed to fetch payees:', error);
193
+ throw error;
194
+ } finally {
195
+ setLoading(false);
196
+ }
197
+ }, [customerId, customerEmail, isAuthenticated]);
198
+
199
+ const createPayee = useCallback(async (data: any) => {
200
+ if (!isAuthenticated || !customerId) throw new Error('Not authenticated');
201
+
202
+ setLoading(true);
203
+ setError(null);
204
+
205
+ try {
206
+ const response = await fetch('/api/diviswap', {
207
+ method: 'POST',
208
+ headers: { 'Content-Type': 'application/json' },
209
+ body: JSON.stringify({
210
+ action: 'createPayee',
211
+ customerId,
212
+ customerEmail,
213
+ ...data
214
+ })
215
+ });
216
+
217
+ if (!response.ok) throw new Error('Failed to create payee');
218
+
219
+ const payee = await response.json();
220
+ // Refresh payees list
221
+ await fetchPayees();
222
+ return payee;
223
+ } catch (err) {
224
+ const error = err as Error;
225
+ setError(error);
226
+ console.error('Failed to create payee:', error);
227
+ throw error;
228
+ } finally {
229
+ setLoading(false);
230
+ }
231
+ }, [customerId, customerEmail, isAuthenticated, fetchPayees]);
232
+
233
+ const deletePayee = useCallback(async (id: string) => {
234
+ if (!isAuthenticated || !customerId) throw new Error('Not authenticated');
235
+
236
+ setLoading(true);
237
+ setError(null);
238
+
239
+ try {
240
+ const response = await fetch(`/api/diviswap?resource=payee&id=${id}&customerId=${customerId}&customerEmail=${customerEmail}`, {
241
+ method: 'DELETE'
242
+ });
243
+
244
+ if (!response.ok) throw new Error('Failed to delete payee');
245
+
246
+ // Refresh payees list
247
+ await fetchPayees();
248
+ } catch (err) {
249
+ const error = err as Error;
250
+ setError(error);
251
+ console.error('Failed to delete payee:', error);
252
+ throw error;
253
+ } finally {
254
+ setLoading(false);
255
+ }
256
+ }, [customerId, customerEmail, isAuthenticated, fetchPayees]);
257
+
258
+ // Auto-load payees when authenticated
259
+ useEffect(() => {
260
+ if (isAuthenticated && customerId) {
261
+ fetchPayees();
262
+ }
263
+ }, [isAuthenticated, customerId, fetchPayees]);
264
+
265
+ return {
266
+ payees,
267
+ loading,
268
+ error,
269
+ createPayee,
270
+ deletePayee,
271
+ refresh: fetchPayees,
272
+ };
273
+ }
274
+
275
+ // Hook for KYC status and submission
276
+ export function useKYC() {
277
+ const { customerId, customerEmail, isAuthenticated } = useAuth();
278
+ const [kycStatus, setKycStatus] = useState<any>(null);
279
+ const [loading, setLoading] = useState(false);
280
+ const [error, setError] = useState<Error | null>(null);
281
+
282
+ const fetchKYCStatus = useCallback(async () => {
283
+ if (!isAuthenticated || !customerId) return null;
284
+
285
+ setLoading(true);
286
+ setError(null);
287
+
288
+ try {
289
+ const response = await fetch(`/api/diviswap?resource=kycStatus&customerId=${customerId}&customerEmail=${customerEmail}`);
290
+ if (!response.ok) throw new Error('Failed to fetch KYC status');
291
+
292
+ const data = await response.json();
293
+ setKycStatus(data);
294
+ return data;
295
+ } catch (err) {
296
+ const error = err as Error;
297
+ setError(error);
298
+ console.error('Failed to fetch KYC status:', error);
299
+ throw error;
300
+ } finally {
301
+ setLoading(false);
302
+ }
303
+ }, [customerId, customerEmail, isAuthenticated]);
304
+
305
+ const submitKYC = useCallback(async (kycData: any) => {
306
+ if (!isAuthenticated || !customerId) throw new Error('Not authenticated');
307
+
308
+ setLoading(true);
309
+ setError(null);
310
+
311
+ try {
312
+ const response = await fetch('/api/diviswap', {
313
+ method: 'POST',
314
+ headers: { 'Content-Type': 'application/json' },
315
+ body: JSON.stringify({
316
+ action: 'submitKYC',
317
+ customerId,
318
+ customerEmail,
319
+ ...kycData
320
+ })
321
+ });
322
+
323
+ if (!response.ok) throw new Error('Failed to submit KYC');
324
+
325
+ const result = await response.json();
326
+ // Refresh KYC status
327
+ await fetchKYCStatus();
328
+ return result;
329
+ } catch (err) {
330
+ const error = err as Error;
331
+ setError(error);
332
+ console.error('Failed to submit KYC:', error);
333
+ throw error;
334
+ } finally {
335
+ setLoading(false);
336
+ }
337
+ }, [customerId, customerEmail, isAuthenticated, fetchKYCStatus]);
338
+
339
+ // Auto-load KYC status when authenticated
340
+ useEffect(() => {
341
+ if (isAuthenticated && customerId) {
342
+ fetchKYCStatus();
343
+ }
344
+ }, [isAuthenticated, customerId, fetchKYCStatus]);
345
+
346
+ return {
347
+ kycStatus,
348
+ loading,
349
+ error,
350
+ submitKYC,
351
+ refresh: fetchKYCStatus,
352
+ isKycApproved: kycStatus?.kycStatus === 'APPROVED',
353
+ needsKyc: kycStatus?.requiresAction && kycStatus?.nextStep === 'COMPLETE_KYC'
354
+ };
355
+ }
356
+
357
+ {{#if (includes features "fees")}}
358
+ // Hook for fee calculations
359
+ export function useFeeCalculator() {
360
+ const { customerId, customerEmail, isAuthenticated } = useAuth();
361
+ const [fees, setFees] = useState<any>(null);
362
+ const [loading, setLoading] = useState(false);
363
+ const [error, setError] = useState<Error | null>(null);
364
+
365
+ const calculateFees = useCallback(async (amount: number) => {
366
+ if (!isAuthenticated || !customerId || amount <= 0) {
367
+ setFees(null);
368
+ return null;
369
+ }
370
+
371
+ setLoading(true);
372
+ setError(null);
373
+
374
+ try {
375
+ const response = await fetch('/api/diviswap', {
376
+ method: 'POST',
377
+ headers: { 'Content-Type': 'application/json' },
378
+ body: JSON.stringify({
379
+ action: 'calculateFees',
380
+ amount,
381
+ customerId,
382
+ customerEmail
383
+ })
384
+ });
385
+
386
+ if (!response.ok) throw new Error('Failed to calculate fees');
387
+
388
+ const calculatedFees = await response.json();
389
+ setFees(calculatedFees);
390
+ return calculatedFees;
391
+ } catch (err) {
392
+ const error = err as Error;
393
+ setError(error);
394
+ setFees(null);
395
+ console.error('Failed to calculate fees:', error);
396
+ throw error;
397
+ } finally {
398
+ setLoading(false);
399
+ }
400
+ }, [customerId, customerEmail, isAuthenticated]);
401
+
402
+ return {
403
+ fees,
404
+ loading,
405
+ error,
406
+ calculateFees,
407
+ };
408
+ }
409
+ {{/if}}
410
+
411
+ // Hook for handling async operations with loading states
412
+ export function useAsyncOperation<T extends (...args: any[]) => Promise<any>>() {
413
+ const [loading, setLoading] = useState(false);
414
+ const [error, setError] = useState<Error | null>(null);
415
+
416
+ const execute = useCallback(async <TResult>(
417
+ operation: T,
418
+ ...args: Parameters<T>
419
+ ): Promise<TResult | undefined> => {
420
+ setLoading(true);
421
+ setError(null);
422
+
423
+ try {
424
+ const result = await operation(...args);
425
+ return result as TResult;
426
+ } catch (err) {
427
+ setError(err as Error);
428
+ throw err;
429
+ } finally {
430
+ setLoading(false);
431
+ }
432
+ }, []);
433
+
434
+ return {
435
+ loading,
436
+ error,
437
+ execute,
438
+ };
439
+ }