@mantle-rwa/react 0.1.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 (66) hide show
  1. package/dist/cjs/components/InvestorDashboard/index.js +156 -0
  2. package/dist/cjs/components/InvestorDashboard/index.js.map +1 -0
  3. package/dist/cjs/components/KYCFlow/index.js +231 -0
  4. package/dist/cjs/components/KYCFlow/index.js.map +1 -0
  5. package/dist/cjs/components/TokenMintForm/index.js +286 -0
  6. package/dist/cjs/components/TokenMintForm/index.js.map +1 -0
  7. package/dist/cjs/components/YieldCalculator/index.js +245 -0
  8. package/dist/cjs/components/YieldCalculator/index.js.map +1 -0
  9. package/dist/cjs/components/index.js +15 -0
  10. package/dist/cjs/components/index.js.map +1 -0
  11. package/dist/cjs/hooks/index.js +9 -0
  12. package/dist/cjs/hooks/index.js.map +1 -0
  13. package/dist/cjs/hooks/useRWA.js +54 -0
  14. package/dist/cjs/hooks/useRWA.js.map +1 -0
  15. package/dist/cjs/index.js +20 -0
  16. package/dist/cjs/index.js.map +1 -0
  17. package/dist/cjs/types/index.js +10 -0
  18. package/dist/cjs/types/index.js.map +1 -0
  19. package/dist/esm/components/InvestorDashboard/index.js +153 -0
  20. package/dist/esm/components/InvestorDashboard/index.js.map +1 -0
  21. package/dist/esm/components/KYCFlow/index.js +228 -0
  22. package/dist/esm/components/KYCFlow/index.js.map +1 -0
  23. package/dist/esm/components/TokenMintForm/index.js +279 -0
  24. package/dist/esm/components/TokenMintForm/index.js.map +1 -0
  25. package/dist/esm/components/YieldCalculator/index.js +236 -0
  26. package/dist/esm/components/YieldCalculator/index.js.map +1 -0
  27. package/dist/esm/components/index.js +8 -0
  28. package/dist/esm/components/index.js.map +1 -0
  29. package/dist/esm/hooks/index.js +5 -0
  30. package/dist/esm/hooks/index.js.map +1 -0
  31. package/dist/esm/hooks/useRWA.js +51 -0
  32. package/dist/esm/hooks/useRWA.js.map +1 -0
  33. package/dist/esm/index.js +12 -0
  34. package/dist/esm/index.js.map +1 -0
  35. package/dist/esm/types/index.js +6 -0
  36. package/dist/esm/types/index.js.map +1 -0
  37. package/dist/styles.css +1 -0
  38. package/dist/types/components/InvestorDashboard/index.d.ts +25 -0
  39. package/dist/types/components/InvestorDashboard/index.d.ts.map +1 -0
  40. package/dist/types/components/KYCFlow/index.d.ts +25 -0
  41. package/dist/types/components/KYCFlow/index.d.ts.map +1 -0
  42. package/dist/types/components/TokenMintForm/index.d.ts +60 -0
  43. package/dist/types/components/TokenMintForm/index.d.ts.map +1 -0
  44. package/dist/types/components/YieldCalculator/index.d.ts +59 -0
  45. package/dist/types/components/YieldCalculator/index.d.ts.map +1 -0
  46. package/dist/types/components/index.d.ts +8 -0
  47. package/dist/types/components/index.d.ts.map +1 -0
  48. package/dist/types/hooks/index.d.ts +5 -0
  49. package/dist/types/hooks/index.d.ts.map +1 -0
  50. package/dist/types/hooks/useRWA.d.ts +22 -0
  51. package/dist/types/hooks/useRWA.d.ts.map +1 -0
  52. package/dist/types/index.d.ts +11 -0
  53. package/dist/types/index.d.ts.map +1 -0
  54. package/dist/types/types/index.d.ts +177 -0
  55. package/dist/types/types/index.d.ts.map +1 -0
  56. package/package.json +66 -0
  57. package/src/components/InvestorDashboard/index.tsx +359 -0
  58. package/src/components/KYCFlow/index.tsx +434 -0
  59. package/src/components/TokenMintForm/index.tsx +590 -0
  60. package/src/components/YieldCalculator/index.tsx +541 -0
  61. package/src/components/index.ts +8 -0
  62. package/src/hooks/index.ts +5 -0
  63. package/src/hooks/useRWA.ts +70 -0
  64. package/src/index.ts +32 -0
  65. package/src/styles/index.css +197 -0
  66. package/src/types/index.ts +193 -0
@@ -0,0 +1,434 @@
1
+ /**
2
+ * KYCFlow Component
3
+ * Multi-step KYC verification flow with provider integration
4
+ *
5
+ * @see Requirements 10.1, 10.2, 10.3, 10.4, 10.5
6
+ */
7
+
8
+ import React, { useState, useCallback, useMemo, useEffect } from 'react';
9
+ import type { KYCFlowProps, KYCResult, KYCRequiredField } from '../../types';
10
+
11
+ /**
12
+ * Verification step status
13
+ */
14
+ type StepStatus = 'pending' | 'in_progress' | 'completed' | 'failed';
15
+
16
+ /**
17
+ * Verification step definition
18
+ */
19
+ interface VerificationStep {
20
+ id: KYCRequiredField;
21
+ label: string;
22
+ description: string;
23
+ status: StepStatus;
24
+ }
25
+
26
+ /**
27
+ * Provider configuration
28
+ */
29
+ interface ProviderConfig {
30
+ name: string;
31
+ displayName: string;
32
+ logo: string;
33
+ description: string;
34
+ }
35
+
36
+ const PROVIDER_CONFIGS: Record<string, ProviderConfig> = {
37
+ persona: {
38
+ name: 'persona',
39
+ displayName: 'Persona',
40
+ logo: '🔐',
41
+ description: 'Identity verification powered by Persona',
42
+ },
43
+ synaps: {
44
+ name: 'synaps',
45
+ displayName: 'Synaps',
46
+ logo: '🛡️',
47
+ description: 'KYC verification powered by Synaps',
48
+ },
49
+ jumio: {
50
+ name: 'jumio',
51
+ displayName: 'Jumio',
52
+ logo: '✓',
53
+ description: 'Identity verification powered by Jumio',
54
+ },
55
+ };
56
+
57
+ const FIELD_LABELS: Record<KYCRequiredField, { label: string; description: string }> = {
58
+ identity: {
59
+ label: 'Identity Verification',
60
+ description: 'Verify your identity with a government-issued ID',
61
+ },
62
+ accreditation: {
63
+ label: 'Accreditation Status',
64
+ description: 'Confirm your investor accreditation status',
65
+ },
66
+ address: {
67
+ label: 'Address Verification',
68
+ description: 'Verify your residential address',
69
+ },
70
+ tax: {
71
+ label: 'Tax Information',
72
+ description: 'Provide tax identification information',
73
+ },
74
+ };
75
+
76
+ /**
77
+ * KYCFlow component for investor verification
78
+ * Supports Persona, Synaps, and Jumio providers
79
+ *
80
+ * @example
81
+ * ```tsx
82
+ * <KYCFlow
83
+ * provider="persona"
84
+ * requiredFields={['identity', 'accreditation']}
85
+ * onComplete={(result) => console.log('KYC complete:', result)}
86
+ * theme="light"
87
+ * />
88
+ * ```
89
+ */
90
+ export function KYCFlow({
91
+ provider,
92
+ onComplete,
93
+ onError,
94
+ requiredFields,
95
+ theme = 'light',
96
+ customStyles,
97
+ registryAddress,
98
+ autoUpdateRegistry = false,
99
+ }: KYCFlowProps): React.ReactElement {
100
+ const [currentStepIndex, setCurrentStepIndex] = useState(0);
101
+ const [isLoading, setIsLoading] = useState(false);
102
+ const [error, setError] = useState<string | null>(null);
103
+ const [sessionId, setSessionId] = useState<string | null>(null);
104
+ const [verificationStarted, setVerificationStarted] = useState(false);
105
+
106
+ const isDark = theme === 'dark';
107
+ const providerConfig = PROVIDER_CONFIGS[provider];
108
+
109
+ // Initialize steps based on required fields
110
+ const [steps, setSteps] = useState<VerificationStep[]>(() =>
111
+ requiredFields.map((field) => ({
112
+ id: field,
113
+ label: FIELD_LABELS[field].label,
114
+ description: FIELD_LABELS[field].description,
115
+ status: 'pending' as StepStatus,
116
+ }))
117
+ );
118
+
119
+ // Reset steps when requiredFields change
120
+ useEffect(() => {
121
+ setSteps(
122
+ requiredFields.map((field) => ({
123
+ id: field,
124
+ label: FIELD_LABELS[field].label,
125
+ description: FIELD_LABELS[field].description,
126
+ status: 'pending' as StepStatus,
127
+ }))
128
+ );
129
+ setCurrentStepIndex(0);
130
+ setVerificationStarted(false);
131
+ setSessionId(null);
132
+ setError(null);
133
+ }, [requiredFields]);
134
+
135
+ const currentStep = steps[currentStepIndex];
136
+ const isLastStep = currentStepIndex === steps.length - 1;
137
+ const allStepsCompleted = steps.every((step) => step.status === 'completed');
138
+
139
+ // Update step status
140
+ const updateStepStatus = useCallback((stepIndex: number, status: StepStatus) => {
141
+ setSteps((prev) =>
142
+ prev.map((step, idx) => (idx === stepIndex ? { ...step, status } : step))
143
+ );
144
+ }, []);
145
+
146
+ // Start verification flow
147
+ const handleStartVerification = useCallback(async () => {
148
+ setIsLoading(true);
149
+ setError(null);
150
+ setVerificationStarted(true);
151
+
152
+ try {
153
+ // Generate session ID for this verification flow
154
+ const newSessionId = `${provider}-${Date.now()}-${Math.random().toString(36).slice(2, 10)}`;
155
+ setSessionId(newSessionId);
156
+
157
+ // Mark first step as in progress
158
+ updateStepStatus(0, 'in_progress');
159
+
160
+ // Simulate provider initialization
161
+ await new Promise((resolve) => setTimeout(resolve, 500));
162
+ } catch (err) {
163
+ const errorMessage = err instanceof Error ? err.message : 'Failed to start verification';
164
+ setError(errorMessage);
165
+ onError?.(err instanceof Error ? err : new Error(errorMessage));
166
+ setVerificationStarted(false);
167
+ } finally {
168
+ setIsLoading(false);
169
+ }
170
+ }, [provider, onError, updateStepStatus]);
171
+
172
+ // Complete current step and move to next
173
+ const handleCompleteStep = useCallback(async () => {
174
+ setIsLoading(true);
175
+ setError(null);
176
+
177
+ try {
178
+ // Simulate step completion
179
+ await new Promise((resolve) => setTimeout(resolve, 800));
180
+
181
+ // Mark current step as completed
182
+ updateStepStatus(currentStepIndex, 'completed');
183
+
184
+ if (!isLastStep) {
185
+ // Move to next step
186
+ const nextIndex = currentStepIndex + 1;
187
+ setCurrentStepIndex(nextIndex);
188
+ updateStepStatus(nextIndex, 'in_progress');
189
+ }
190
+ } catch (err) {
191
+ const errorMessage = err instanceof Error ? err.message : 'Step verification failed';
192
+ setError(errorMessage);
193
+ updateStepStatus(currentStepIndex, 'failed');
194
+ onError?.(err instanceof Error ? err : new Error(errorMessage));
195
+ } finally {
196
+ setIsLoading(false);
197
+ }
198
+ }, [currentStepIndex, isLastStep, onError, updateStepStatus]);
199
+
200
+ // Complete entire verification flow
201
+ const handleCompleteVerification = useCallback(async () => {
202
+ setIsLoading(true);
203
+ setError(null);
204
+
205
+ try {
206
+ // Complete the last step first
207
+ await new Promise((resolve) => setTimeout(resolve, 500));
208
+ updateStepStatus(currentStepIndex, 'completed');
209
+
210
+ // Determine tier based on accreditation field
211
+ const hasAccreditation = requiredFields.includes('accreditation');
212
+ const tier = hasAccreditation ? 2 : 1; // Accredited (2) or Retail (1)
213
+
214
+ // Generate identity hash
215
+ const identityHash = '0x' + Array.from({ length: 64 }, () =>
216
+ Math.floor(Math.random() * 16).toString(16)
217
+ ).join('');
218
+
219
+ const result: KYCResult = {
220
+ verified: true,
221
+ tier,
222
+ identityHash,
223
+ sessionId: sessionId || `${provider}-${Date.now()}`,
224
+ };
225
+
226
+ // Auto-update registry if configured
227
+ if (autoUpdateRegistry && registryAddress) {
228
+ // In production, this would call the SDK to update the on-chain registry
229
+ await new Promise((resolve) => setTimeout(resolve, 500));
230
+ }
231
+
232
+ onComplete(result);
233
+ } catch (err) {
234
+ const errorMessage = err instanceof Error ? err.message : 'Verification completion failed';
235
+ setError(errorMessage);
236
+ updateStepStatus(currentStepIndex, 'failed');
237
+ onError?.(err instanceof Error ? err : new Error(errorMessage));
238
+ } finally {
239
+ setIsLoading(false);
240
+ }
241
+ }, [
242
+ currentStepIndex,
243
+ requiredFields,
244
+ sessionId,
245
+ provider,
246
+ autoUpdateRegistry,
247
+ registryAddress,
248
+ onComplete,
249
+ onError,
250
+ updateStepStatus,
251
+ ]);
252
+
253
+ // Retry failed step
254
+ const handleRetry = useCallback(() => {
255
+ setError(null);
256
+ updateStepStatus(currentStepIndex, 'in_progress');
257
+ }, [currentStepIndex, updateStepStatus]);
258
+
259
+ // Style classes
260
+ const containerClass = useMemo(
261
+ () => customStyles?.container ?? `rwa-kyc-flow ${isDark ? 'dark' : ''}`,
262
+ [customStyles?.container, isDark]
263
+ );
264
+
265
+ const headerClass = useMemo(
266
+ () => customStyles?.header ?? 'rwa-kyc-header',
267
+ [customStyles?.header]
268
+ );
269
+
270
+ const buttonClass = useMemo(
271
+ () => customStyles?.button ?? 'rwa-button-primary',
272
+ [customStyles?.button]
273
+ );
274
+
275
+ // Render step indicator
276
+ const renderStepIndicator = () => (
277
+ <div className="rwa-kyc-steps" role="list" aria-label="Verification steps">
278
+ {steps.map((step, index) => (
279
+ <div
280
+ key={step.id}
281
+ className={`rwa-kyc-step ${step.status}`}
282
+ role="listitem"
283
+ aria-current={index === currentStepIndex ? 'step' : undefined}
284
+ >
285
+ <div className="rwa-kyc-step-indicator">
286
+ {step.status === 'completed' ? (
287
+ <span aria-hidden="true">✓</span>
288
+ ) : step.status === 'failed' ? (
289
+ <span aria-hidden="true">✗</span>
290
+ ) : (
291
+ <span aria-hidden="true">{index + 1}</span>
292
+ )}
293
+ </div>
294
+ <div className="rwa-kyc-step-content">
295
+ <span className="rwa-kyc-step-label">{step.label}</span>
296
+ {index === currentStepIndex && verificationStarted && (
297
+ <span className="rwa-kyc-step-description">{step.description}</span>
298
+ )}
299
+ </div>
300
+ </div>
301
+ ))}
302
+ </div>
303
+ );
304
+
305
+ // Render provider info
306
+ const renderProviderInfo = () => (
307
+ <div className="rwa-kyc-provider" data-testid="kyc-provider-info">
308
+ <span className="rwa-kyc-provider-logo" aria-hidden="true">
309
+ {providerConfig.logo}
310
+ </span>
311
+ <div className="rwa-kyc-provider-details">
312
+ <span className="rwa-kyc-provider-name">{providerConfig.displayName}</span>
313
+ <span className="rwa-kyc-provider-description">{providerConfig.description}</span>
314
+ </div>
315
+ </div>
316
+ );
317
+
318
+ // Render action buttons
319
+ const renderActions = () => {
320
+ if (!verificationStarted) {
321
+ return (
322
+ <button
323
+ type="button"
324
+ className={buttonClass}
325
+ onClick={handleStartVerification}
326
+ disabled={isLoading}
327
+ aria-busy={isLoading}
328
+ data-testid="start-verification-btn"
329
+ >
330
+ {isLoading ? 'Starting...' : 'Start Verification'}
331
+ </button>
332
+ );
333
+ }
334
+
335
+ if (currentStep?.status === 'failed') {
336
+ return (
337
+ <button
338
+ type="button"
339
+ className={buttonClass}
340
+ onClick={handleRetry}
341
+ disabled={isLoading}
342
+ data-testid="retry-btn"
343
+ >
344
+ Retry Step
345
+ </button>
346
+ );
347
+ }
348
+
349
+ if (allStepsCompleted) {
350
+ return (
351
+ <div className="rwa-kyc-success" role="status">
352
+ <span className="rwa-kyc-success-icon" aria-hidden="true">✓</span>
353
+ <span>Verification Complete</span>
354
+ </div>
355
+ );
356
+ }
357
+
358
+ if (isLastStep) {
359
+ return (
360
+ <button
361
+ type="button"
362
+ className={buttonClass}
363
+ onClick={handleCompleteVerification}
364
+ disabled={isLoading}
365
+ aria-busy={isLoading}
366
+ data-testid="complete-verification-btn"
367
+ >
368
+ {isLoading ? 'Completing...' : 'Complete Verification'}
369
+ </button>
370
+ );
371
+ }
372
+
373
+ return (
374
+ <button
375
+ type="button"
376
+ className={buttonClass}
377
+ onClick={handleCompleteStep}
378
+ disabled={isLoading}
379
+ aria-busy={isLoading}
380
+ data-testid="next-step-btn"
381
+ >
382
+ {isLoading ? 'Verifying...' : 'Continue'}
383
+ </button>
384
+ );
385
+ };
386
+
387
+ return (
388
+ <div
389
+ className={containerClass}
390
+ data-testid="kyc-flow"
391
+ data-provider={provider}
392
+ data-theme={theme}
393
+ role="form"
394
+ aria-label="KYC Verification Flow"
395
+ >
396
+ <h2 className={headerClass}>
397
+ {allStepsCompleted
398
+ ? 'Verification Complete'
399
+ : verificationStarted
400
+ ? `Step ${currentStepIndex + 1} of ${steps.length}: ${currentStep?.label}`
401
+ : 'Identity Verification'}
402
+ </h2>
403
+
404
+ {error && (
405
+ <div
406
+ className="rwa-kyc-error"
407
+ role="alert"
408
+ aria-live="polite"
409
+ data-testid="kyc-error"
410
+ >
411
+ <span className="rwa-kyc-error-icon" aria-hidden="true">⚠</span>
412
+ <span>{error}</span>
413
+ </div>
414
+ )}
415
+
416
+ {renderProviderInfo()}
417
+ {renderStepIndicator()}
418
+
419
+ <div className="rwa-kyc-actions">
420
+ {renderActions()}
421
+ </div>
422
+
423
+ {sessionId && (
424
+ <div className="rwa-kyc-session" data-testid="session-info">
425
+ <span className="text-xs text-gray-500 dark:text-gray-400">
426
+ Session: {sessionId.slice(0, 20)}...
427
+ </span>
428
+ </div>
429
+ )}
430
+ </div>
431
+ );
432
+ }
433
+
434
+ export default KYCFlow;