zopassport 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 (110) hide show
  1. package/LICENSE +22 -0
  2. package/README.md +407 -0
  3. package/app/.env.example +15 -0
  4. package/app/README.md +28 -0
  5. package/app/package.json +24 -0
  6. package/app/reanimated-mock.js +102 -0
  7. package/app/reanimated-mock.jsx +97 -0
  8. package/app/src/App.tsx +331 -0
  9. package/app/src/components/FounderBadge.tsx +26 -0
  10. package/app/src/components/OTPInput.tsx +149 -0
  11. package/app/src/components/PhoneInput.tsx +109 -0
  12. package/app/src/components/ZoAuth.tsx +320 -0
  13. package/app/src/components/ZoAvatar.tsx +87 -0
  14. package/app/src/components/ZoLanding.tsx +231 -0
  15. package/app/src/components/ZoOnboarding.tsx +524 -0
  16. package/app/src/components/ZoPassportCard.tsx +183 -0
  17. package/app/src/components/ZoProgressRing.tsx +57 -0
  18. package/app/src/components/index.ts +16 -0
  19. package/app/src/components/wallet/MovingShine.tsx +43 -0
  20. package/app/src/components/wallet/TransactionItem.tsx +84 -0
  21. package/app/src/components/wallet/TransactionList.tsx +65 -0
  22. package/app/src/components/wallet/WalletCard.tsx +152 -0
  23. package/app/src/components/wallet/WalletScreen.tsx +190 -0
  24. package/app/src/components/wallet/ZoToken.tsx +69 -0
  25. package/app/src/components/wallet/index.ts +8 -0
  26. package/app/src/components/wallet/styles/index.ts +4 -0
  27. package/app/src/components/wallet/styles/walletStyles.ts +210 -0
  28. package/app/src/sdk/ZoPassportSDK.ts +277 -0
  29. package/app/src/sdk/lib/api/auth.ts +223 -0
  30. package/app/src/sdk/lib/api/avatar.ts +155 -0
  31. package/app/src/sdk/lib/api/client.ts +135 -0
  32. package/app/src/sdk/lib/api/index.ts +8 -0
  33. package/app/src/sdk/lib/api/profile.ts +80 -0
  34. package/app/src/sdk/lib/api/wallet.ts +59 -0
  35. package/app/src/sdk/lib/types/auth.ts +78 -0
  36. package/app/src/sdk/lib/types/avatar.ts +22 -0
  37. package/app/src/sdk/lib/types/index.ts +8 -0
  38. package/app/src/sdk/lib/types/profile.ts +18 -0
  39. package/app/src/sdk/lib/types/wallet.ts +103 -0
  40. package/app/src/sdk/lib/types.ts +205 -0
  41. package/app/src/sdk/lib/utils/index.ts +6 -0
  42. package/app/src/sdk/lib/utils/phone.ts +71 -0
  43. package/app/src/sdk/lib/utils/storage.ts +116 -0
  44. package/app/src/sdk/lib/utils/wallet.ts +73 -0
  45. package/app/src/sdk/types.ts +205 -0
  46. package/app/src/styles.css +154 -0
  47. package/app/svg-mock.js +125 -0
  48. package/app/svg-mock.jsx +120 -0
  49. package/app/vite.config.ts +70 -0
  50. package/assets/ASSETS_MANIFEST.md +124 -0
  51. package/assets/bae.png +0 -0
  52. package/assets/bro.png +0 -0
  53. package/assets/cultural-stickers/Business.png +0 -0
  54. package/assets/cultural-stickers/Default (2).jpg +0 -0
  55. package/assets/cultural-stickers/Design.png +0 -0
  56. package/assets/cultural-stickers/FollowYourHeart.png +0 -0
  57. package/assets/cultural-stickers/Food.png +0 -0
  58. package/assets/cultural-stickers/Game.png +0 -0
  59. package/assets/cultural-stickers/Health&Fitness.png +0 -0
  60. package/assets/cultural-stickers/Home&Lifestyle.png +0 -0
  61. package/assets/cultural-stickers/Law.png +0 -0
  62. package/assets/cultural-stickers/Literature&Stories.png +0 -0
  63. package/assets/cultural-stickers/Music&Entertainment.png +0 -0
  64. package/assets/cultural-stickers/Nature&Wildlife.png +0 -0
  65. package/assets/cultural-stickers/Photography.png +0 -0
  66. package/assets/cultural-stickers/Science&Technology.png +0 -0
  67. package/assets/cultural-stickers/Spiritual.png +0 -0
  68. package/assets/cultural-stickers/Sport.png +0 -0
  69. package/assets/cultural-stickers/Stories&Journal.png +0 -0
  70. package/assets/cultural-stickers/Television&Cinema.png +0 -0
  71. package/assets/cultural-stickers/Travel&Adventure.png +0 -0
  72. package/assets/cultural-stickers/z.jpg (1).jpg +0 -0
  73. package/assets/figma-assets/landing-zo-logo.png +0 -0
  74. package/assets/images/rank1.jpeg +0 -0
  75. package/assets/index.ts +76 -0
  76. package/assets/lotties/loader.json +1216 -0
  77. package/assets/lotties/spinner.json +1 -0
  78. package/assets/videos/loading-screen-background.mp4 +0 -0
  79. package/assets/videos/opening-disks.mp4 +0 -0
  80. package/assets/wallet/constants.ts +38 -0
  81. package/assets/zo-coin.gif +0 -0
  82. package/assets/zo-fallback.png +0 -0
  83. package/dist/assets/index.d.mts +136 -0
  84. package/dist/assets/index.d.ts +136 -0
  85. package/dist/assets/index.js +133 -0
  86. package/dist/assets/index.js.map +1 -0
  87. package/dist/assets/index.mjs +100 -0
  88. package/dist/assets/index.mjs.map +1 -0
  89. package/dist/index.d.mts +789 -0
  90. package/dist/index.d.ts +789 -0
  91. package/dist/index.js +1118 -0
  92. package/dist/index.js.map +1 -0
  93. package/dist/index.mjs +1060 -0
  94. package/dist/index.mjs.map +1 -0
  95. package/dist/react-native.d.mts +537 -0
  96. package/dist/react-native.d.ts +537 -0
  97. package/dist/react-native.js +1617 -0
  98. package/dist/react-native.js.map +1 -0
  99. package/dist/react-native.mjs +1588 -0
  100. package/dist/react-native.mjs.map +1 -0
  101. package/dist/react.d.mts +824 -0
  102. package/dist/react.d.ts +824 -0
  103. package/dist/react.js +3856 -0
  104. package/dist/react.js.map +1 -0
  105. package/dist/react.mjs +3801 -0
  106. package/dist/react.mjs.map +1 -0
  107. package/package.json +112 -0
  108. package/scripts/init.js +196 -0
  109. package/scripts/postinstall.js +174 -0
  110. package/scripts/verify-build.js +121 -0
package/dist/index.mjs ADDED
@@ -0,0 +1,1060 @@
1
+ // src/lib/api/client.ts
2
+ import axios from "axios";
3
+
4
+ // src/lib/utils/logger.ts
5
+ var LOG_LEVELS = {
6
+ debug: 0,
7
+ info: 1,
8
+ warn: 2,
9
+ error: 3,
10
+ none: 4
11
+ };
12
+ var Logger = class {
13
+ constructor() {
14
+ this.config = {
15
+ enabled: false,
16
+ level: "warn",
17
+ prefix: "[ZoPassport]"
18
+ };
19
+ }
20
+ /**
21
+ * Configure the logger
22
+ * @param options - Logger configuration
23
+ */
24
+ configure(options) {
25
+ this.config = { ...this.config, ...options };
26
+ }
27
+ /**
28
+ * Enable debug logging
29
+ */
30
+ enable() {
31
+ this.config.enabled = true;
32
+ }
33
+ /**
34
+ * Disable all logging
35
+ */
36
+ disable() {
37
+ this.config.enabled = false;
38
+ }
39
+ /**
40
+ * Set log level
41
+ */
42
+ setLevel(level) {
43
+ this.config.level = level;
44
+ }
45
+ shouldLog(level) {
46
+ if (!this.config.enabled) return false;
47
+ return LOG_LEVELS[level] >= LOG_LEVELS[this.config.level];
48
+ }
49
+ debug(...args) {
50
+ if (this.shouldLog("debug")) {
51
+ console.log(this.config.prefix, ...args);
52
+ }
53
+ }
54
+ info(...args) {
55
+ if (this.shouldLog("info")) {
56
+ console.info(this.config.prefix, ...args);
57
+ }
58
+ }
59
+ warn(...args) {
60
+ if (this.shouldLog("warn")) {
61
+ console.warn(this.config.prefix, ...args);
62
+ }
63
+ }
64
+ error(...args) {
65
+ if (this.shouldLog("error")) {
66
+ console.error(this.config.prefix, ...args);
67
+ }
68
+ }
69
+ };
70
+ var logger = new Logger();
71
+
72
+ // src/lib/utils/storage.ts
73
+ var STORAGE_KEYS = {
74
+ ACCESS_TOKEN: "zo_access_token",
75
+ REFRESH_TOKEN: "zo_refresh_token",
76
+ TOKEN_EXPIRY: "zo_token_expiry",
77
+ REFRESH_EXPIRY: "zo_refresh_expiry",
78
+ USER: "zo_user",
79
+ CLIENT_DEVICE_ID: "zo_device_id",
80
+ CLIENT_DEVICE_SECRET: "zo_device_secret",
81
+ AVATAR_URL: "zo_avatar_url",
82
+ NICKNAME: "zo_nickname",
83
+ CITY: "zo_city",
84
+ BODY_TYPE: "zo_body_type"
85
+ };
86
+ var LocalStorageAdapter = class {
87
+ async getItem(key) {
88
+ if (typeof window === "undefined") return null;
89
+ try {
90
+ return localStorage.getItem(key);
91
+ } catch (error) {
92
+ logger.warn(`LocalStorage.getItem failed for key "${key}":`, error);
93
+ return null;
94
+ }
95
+ }
96
+ async setItem(key, value) {
97
+ if (typeof window === "undefined") return;
98
+ try {
99
+ localStorage.setItem(key, value);
100
+ } catch (error) {
101
+ logger.warn(`LocalStorage.setItem failed for key "${key}":`, error);
102
+ }
103
+ }
104
+ async removeItem(key) {
105
+ if (typeof window === "undefined") return;
106
+ try {
107
+ localStorage.removeItem(key);
108
+ } catch (error) {
109
+ logger.warn(`LocalStorage.removeItem failed for key "${key}":`, error);
110
+ }
111
+ }
112
+ };
113
+ var AsyncStorageAdapter = class {
114
+ constructor(asyncStorage) {
115
+ this.storage = asyncStorage;
116
+ }
117
+ async getItem(key) {
118
+ try {
119
+ return await this.storage.getItem(key);
120
+ } catch (error) {
121
+ logger.warn(`AsyncStorage.getItem failed for key "${key}":`, error);
122
+ return null;
123
+ }
124
+ }
125
+ async setItem(key, value) {
126
+ try {
127
+ await this.storage.setItem(key, value);
128
+ } catch (error) {
129
+ logger.warn(`AsyncStorage.setItem failed for key "${key}":`, error);
130
+ }
131
+ }
132
+ async removeItem(key) {
133
+ try {
134
+ await this.storage.removeItem(key);
135
+ } catch (error) {
136
+ logger.warn(`AsyncStorage.removeItem failed for key "${key}":`, error);
137
+ }
138
+ }
139
+ };
140
+
141
+ // src/lib/api/client.ts
142
+ function generateDeviceCredentials() {
143
+ const deviceId = `web-${Date.now()}-${Math.random().toString(36).substring(7)}`;
144
+ const deviceSecret = Math.random().toString(36).substring(2, 15) + Math.random().toString(36).substring(2, 15);
145
+ return { deviceId, deviceSecret };
146
+ }
147
+ var ZoApiClient = class {
148
+ constructor(config) {
149
+ this.config = config;
150
+ this.storage = config.storageAdapter || new LocalStorageAdapter();
151
+ this.client = axios.create({
152
+ baseURL: config.baseUrl || "https://api.io.zo.xyz",
153
+ timeout: config.timeout || 1e4,
154
+ headers: {
155
+ "Content-Type": "application/json",
156
+ "Accept": "application/json"
157
+ }
158
+ });
159
+ this.setupInterceptors();
160
+ }
161
+ async setupInterceptors() {
162
+ this.client.interceptors.request.use(async (config) => {
163
+ config.headers["client-key"] = this.config.clientKey;
164
+ const credentials = await this.getOrCreateDeviceCredentials();
165
+ config.headers["client-device-id"] = credentials.deviceId;
166
+ config.headers["client-device-secret"] = credentials.deviceSecret;
167
+ const token = await this.storage.getItem(STORAGE_KEYS.ACCESS_TOKEN);
168
+ if (token) {
169
+ config.headers["Authorization"] = `Bearer ${token}`;
170
+ }
171
+ return config;
172
+ });
173
+ this.client.interceptors.response.use(
174
+ (response) => response,
175
+ async (error) => {
176
+ const originalRequest = error.config;
177
+ if (error.response?.status === 401 && !originalRequest._retry) {
178
+ originalRequest._retry = true;
179
+ const refreshToken = await this.storage.getItem(STORAGE_KEYS.REFRESH_TOKEN);
180
+ if (refreshToken) {
181
+ try {
182
+ const response = await this.client.post("/api/v1/auth/token/refresh/", {
183
+ refresh_token: refreshToken
184
+ });
185
+ if (response.data?.access) {
186
+ await this.storage.setItem(STORAGE_KEYS.ACCESS_TOKEN, response.data.access);
187
+ if (response.data.refresh) {
188
+ await this.storage.setItem(STORAGE_KEYS.REFRESH_TOKEN, response.data.refresh);
189
+ }
190
+ originalRequest.headers["Authorization"] = `Bearer ${response.data.access}`;
191
+ return this.client(originalRequest);
192
+ }
193
+ } catch (refreshError) {
194
+ await this.storage.removeItem(STORAGE_KEYS.ACCESS_TOKEN);
195
+ await this.storage.removeItem(STORAGE_KEYS.REFRESH_TOKEN);
196
+ }
197
+ }
198
+ }
199
+ return Promise.reject(error);
200
+ }
201
+ );
202
+ }
203
+ async getOrCreateDeviceCredentials() {
204
+ const storedId = await this.storage.getItem(STORAGE_KEYS.CLIENT_DEVICE_ID);
205
+ const storedSecret = await this.storage.getItem(STORAGE_KEYS.CLIENT_DEVICE_SECRET);
206
+ if (storedId && storedSecret) {
207
+ return { deviceId: storedId, deviceSecret: storedSecret };
208
+ }
209
+ const credentials = generateDeviceCredentials();
210
+ await this.storage.setItem(STORAGE_KEYS.CLIENT_DEVICE_ID, credentials.deviceId);
211
+ await this.storage.setItem(STORAGE_KEYS.CLIENT_DEVICE_SECRET, credentials.deviceSecret);
212
+ return credentials;
213
+ }
214
+ get axiosInstance() {
215
+ return this.client;
216
+ }
217
+ getStorage() {
218
+ return this.storage;
219
+ }
220
+ };
221
+
222
+ // src/lib/api/auth.ts
223
+ var ZoAuth = class {
224
+ constructor(client) {
225
+ this.client = client;
226
+ }
227
+ /**
228
+ * Send OTP to phone number
229
+ * Step 1 of ZO phone authentication
230
+ */
231
+ async sendOTP(countryCode, phoneNumber) {
232
+ try {
233
+ const payload = {
234
+ mobile_country_code: countryCode,
235
+ mobile_number: phoneNumber,
236
+ message_channel: ""
237
+ // Empty string as per ZO API spec
238
+ };
239
+ const response = await this.client.axiosInstance.post(
240
+ "/api/v1/auth/login/mobile/otp/",
241
+ payload
242
+ );
243
+ if (response.status >= 200 && response.status < 300) {
244
+ return {
245
+ success: true,
246
+ message: response.data?.message || "OTP sent successfully"
247
+ };
248
+ }
249
+ return {
250
+ success: false,
251
+ message: response.data?.message || `Unexpected status: ${response.status}`
252
+ };
253
+ } catch (error) {
254
+ const errorData = error.response?.data;
255
+ const errorMessage = errorData?.detail || errorData?.message || errorData?.error || error.message || "Failed to send OTP";
256
+ return {
257
+ success: false,
258
+ message: errorMessage
259
+ };
260
+ }
261
+ }
262
+ /**
263
+ * Verify OTP and authenticate user
264
+ * Step 2 of ZO phone authentication
265
+ * Returns full auth response with tokens and user profile
266
+ */
267
+ async verifyOTP(countryCode, phoneNumber, otp) {
268
+ try {
269
+ const payload = {
270
+ mobile_country_code: countryCode,
271
+ mobile_number: phoneNumber,
272
+ otp
273
+ };
274
+ const response = await this.client.axiosInstance.post(
275
+ "/api/v1/auth/login/mobile/",
276
+ payload
277
+ );
278
+ let responseData;
279
+ if (typeof response.data === "string") {
280
+ try {
281
+ responseData = JSON.parse(response.data);
282
+ } catch {
283
+ return {
284
+ success: false,
285
+ error: "Invalid response format from authentication service"
286
+ };
287
+ }
288
+ } else {
289
+ responseData = response.data;
290
+ }
291
+ if (!responseData || !responseData.user || !responseData.access_token) {
292
+ return {
293
+ success: false,
294
+ error: "Invalid response structure from authentication service"
295
+ };
296
+ }
297
+ return {
298
+ success: true,
299
+ data: responseData
300
+ };
301
+ } catch (error) {
302
+ const errorMessage = this.extractErrorMessage(error);
303
+ return {
304
+ success: false,
305
+ error: errorMessage
306
+ };
307
+ }
308
+ }
309
+ /**
310
+ * Refresh access token using refresh token
311
+ */
312
+ async refreshAccessToken(refreshToken) {
313
+ try {
314
+ const response = await this.client.axiosInstance.post("/api/v1/auth/token/refresh/", {
315
+ refresh_token: refreshToken
316
+ });
317
+ return {
318
+ success: true,
319
+ tokens: response.data
320
+ };
321
+ } catch (error) {
322
+ return {
323
+ success: false,
324
+ error: "Failed to refresh authentication"
325
+ };
326
+ }
327
+ }
328
+ /**
329
+ * Check if user is authenticated
330
+ */
331
+ async checkLoginStatus(accessToken) {
332
+ try {
333
+ const response = await this.client.axiosInstance.get("/api/v1/auth/login/check/", {
334
+ headers: {
335
+ Authorization: `Bearer ${accessToken}`
336
+ }
337
+ });
338
+ return {
339
+ success: true,
340
+ isAuthenticated: response.data.authenticated === true
341
+ };
342
+ } catch {
343
+ return {
344
+ success: false,
345
+ isAuthenticated: false
346
+ };
347
+ }
348
+ }
349
+ /**
350
+ * Extract error message from various ZO API error formats
351
+ */
352
+ extractErrorMessage(error) {
353
+ const errorData = error.response?.data;
354
+ if (errorData) {
355
+ if (errorData.errors && Array.isArray(errorData.errors)) {
356
+ return errorData.errors[0] || "Invalid OTP";
357
+ }
358
+ if (errorData.detail) return errorData.detail;
359
+ if (errorData.message) return errorData.message;
360
+ if (errorData.error) return errorData.error;
361
+ }
362
+ return "Authentication failed";
363
+ }
364
+ };
365
+
366
+ // src/lib/api/profile.ts
367
+ var ZoProfile = class {
368
+ constructor(client) {
369
+ this.client = client;
370
+ }
371
+ /**
372
+ * Get user profile
373
+ */
374
+ async getProfile(accessToken) {
375
+ try {
376
+ const response = await this.client.axiosInstance.get(
377
+ "/api/v1/profile/me/",
378
+ {
379
+ headers: {
380
+ Authorization: `Bearer ${accessToken}`
381
+ }
382
+ }
383
+ );
384
+ return {
385
+ success: true,
386
+ profile: response.data
387
+ };
388
+ } catch (error) {
389
+ const errorData = error.response?.data;
390
+ return {
391
+ success: false,
392
+ error: errorData?.detail || errorData?.message || "Failed to fetch profile"
393
+ };
394
+ }
395
+ }
396
+ /**
397
+ * Update user profile (partial updates supported)
398
+ */
399
+ async updateProfile(accessToken, updates) {
400
+ try {
401
+ const response = await this.client.axiosInstance.post(
402
+ "/api/v1/profile/me/",
403
+ updates,
404
+ {
405
+ headers: {
406
+ Authorization: `Bearer ${accessToken}`
407
+ }
408
+ }
409
+ );
410
+ return {
411
+ success: true,
412
+ profile: response.data
413
+ };
414
+ } catch (error) {
415
+ const errorData = error.response?.data;
416
+ return {
417
+ success: false,
418
+ error: errorData?.detail || errorData?.message || "Failed to update profile"
419
+ };
420
+ }
421
+ }
422
+ };
423
+
424
+ // src/lib/api/avatar.ts
425
+ var ZoAvatar = class {
426
+ constructor(client) {
427
+ this.client = client;
428
+ }
429
+ /**
430
+ * Generate avatar for user
431
+ */
432
+ async generateAvatar(accessToken, bodyType) {
433
+ try {
434
+ const payload = {
435
+ body_type: bodyType
436
+ };
437
+ const response = await this.client.axiosInstance.post(
438
+ "/api/v1/avatar/generate/",
439
+ payload,
440
+ {
441
+ headers: {
442
+ Authorization: `Bearer ${accessToken}`
443
+ }
444
+ }
445
+ );
446
+ return {
447
+ success: true,
448
+ task_id: response.data.task_id,
449
+ status: response.data.status
450
+ };
451
+ } catch (error) {
452
+ const errorData = error.response?.data;
453
+ return {
454
+ success: false,
455
+ error: errorData?.detail || errorData?.message || "Failed to generate avatar"
456
+ };
457
+ }
458
+ }
459
+ /**
460
+ * Check avatar generation status
461
+ */
462
+ async getAvatarStatus(accessToken, taskId) {
463
+ try {
464
+ const response = await this.client.axiosInstance.get(
465
+ `/api/v1/avatar/status/${taskId}/`,
466
+ {
467
+ headers: {
468
+ Authorization: `Bearer ${accessToken}`
469
+ }
470
+ }
471
+ );
472
+ return {
473
+ success: true,
474
+ status: response.data.status,
475
+ avatarUrl: response.data.result?.avatar_url
476
+ };
477
+ } catch (error) {
478
+ const errorData = error.response?.data;
479
+ return {
480
+ success: false,
481
+ error: errorData?.detail || errorData?.message || "Failed to get avatar status"
482
+ };
483
+ }
484
+ }
485
+ /**
486
+ * Poll avatar status until completion
487
+ */
488
+ async pollAvatarStatus(accessToken, taskId, options = {}) {
489
+ const {
490
+ onProgress,
491
+ onComplete,
492
+ onError,
493
+ maxAttempts = 30,
494
+ interval = 2e3
495
+ } = options;
496
+ let attempts = 0;
497
+ const poll = async () => {
498
+ attempts++;
499
+ if (attempts > maxAttempts) {
500
+ const timeoutError = "Avatar generation timed out";
501
+ onError?.(timeoutError);
502
+ return;
503
+ }
504
+ const result = await this.getAvatarStatus(accessToken, taskId);
505
+ if (!result.success) {
506
+ onError?.(result.error || "Unknown error");
507
+ return;
508
+ }
509
+ onProgress?.(result.status || "unknown");
510
+ if (result.status === "completed" && result.avatarUrl) {
511
+ onComplete?.(result.avatarUrl);
512
+ return;
513
+ }
514
+ if (result.status === "failed") {
515
+ onError?.("Avatar generation failed");
516
+ return;
517
+ }
518
+ setTimeout(poll, interval);
519
+ };
520
+ poll();
521
+ }
522
+ };
523
+
524
+ // src/lib/api/wallet.ts
525
+ var ZO_TOKEN_CONFIG = {
526
+ base: {
527
+ rpc: "https://mainnet.base.org",
528
+ contractAddress: "0x111142c7ecaf39797b7865b82034269962142069",
529
+ // $Zo token on Base
530
+ decimals: 18
531
+ },
532
+ avalanche: {
533
+ rpc: "https://api.avax.network/ext/bc/C/rpc",
534
+ contractAddress: "0x111142c7ecaf39797b7865b82034269962142069",
535
+ // $Zo token on Avalanche (update if different)
536
+ decimals: 18
537
+ }
538
+ };
539
+ var ERC20_BALANCE_ABI = "0x70a08231";
540
+ var ZoWallet = class {
541
+ constructor(client) {
542
+ this.cachedBalance = 0;
543
+ this.userWalletAddress = null;
544
+ this.network = "base";
545
+ this.client = client;
546
+ }
547
+ /**
548
+ * Set the user's wallet address for on-chain queries
549
+ */
550
+ setWalletAddress(address, network = "base") {
551
+ this.userWalletAddress = address;
552
+ this.network = network;
553
+ logger.debug(`Wallet address set: ${address} on ${network}`);
554
+ }
555
+ /**
556
+ * Get wallet balance - tries on-chain first, then API fallback
557
+ * @returns Wallet balance amount
558
+ */
559
+ async getBalance() {
560
+ if (this.userWalletAddress) {
561
+ try {
562
+ const onChainBalance = await this.getOnChainBalance();
563
+ if (onChainBalance !== null) {
564
+ this.cachedBalance = onChainBalance;
565
+ return onChainBalance;
566
+ }
567
+ } catch (error) {
568
+ logger.warn("On-chain balance check failed, falling back to API:", error);
569
+ }
570
+ }
571
+ const apiBalance = await this.getBalanceFromAPI();
572
+ if (apiBalance !== null) {
573
+ return apiBalance;
574
+ }
575
+ logger.debug("Returning cached/default balance:", this.cachedBalance);
576
+ return this.cachedBalance;
577
+ }
578
+ /**
579
+ * Fetch balance directly from blockchain via JSON-RPC
580
+ */
581
+ async getOnChainBalance() {
582
+ if (!this.userWalletAddress) {
583
+ logger.warn("No wallet address set for on-chain query");
584
+ return null;
585
+ }
586
+ const config = ZO_TOKEN_CONFIG[this.network];
587
+ try {
588
+ const paddedAddress = this.userWalletAddress.toLowerCase().replace("0x", "").padStart(64, "0");
589
+ const data = ERC20_BALANCE_ABI + paddedAddress;
590
+ const response = await fetch(config.rpc, {
591
+ method: "POST",
592
+ headers: { "Content-Type": "application/json" },
593
+ body: JSON.stringify({
594
+ jsonrpc: "2.0",
595
+ id: 1,
596
+ method: "eth_call",
597
+ params: [
598
+ {
599
+ to: config.contractAddress,
600
+ data
601
+ },
602
+ "latest"
603
+ ]
604
+ })
605
+ });
606
+ const result = await response.json();
607
+ if (result.error) {
608
+ logger.warn("RPC error:", result.error);
609
+ return null;
610
+ }
611
+ const rawBalance = BigInt(result.result || "0x0");
612
+ const balance = Number(rawBalance) / Math.pow(10, config.decimals);
613
+ logger.debug(`On-chain balance fetched: ${balance} $Zo`);
614
+ return balance;
615
+ } catch (error) {
616
+ logger.warn("Failed to fetch on-chain balance:", error);
617
+ return null;
618
+ }
619
+ }
620
+ /**
621
+ * Fetch balance from Zo API endpoints (tries multiple endpoints with fallback)
622
+ */
623
+ async getBalanceFromAPI() {
624
+ const endpoints = [
625
+ "/api/v1/web3/token/airdrops/summary",
626
+ "/api/v1/wallet/balance",
627
+ "/api/v1/profile/wallet"
628
+ ];
629
+ const errors = [];
630
+ for (const endpoint of endpoints) {
631
+ try {
632
+ const response = await this.client.axiosInstance.get(endpoint);
633
+ const balance = response.data?.data?.total_amount ?? response.data?.balance ?? response.data?.total_amount;
634
+ if (typeof balance === "number") {
635
+ logger.debug(`Balance fetched from API ${endpoint}:`, balance);
636
+ this.cachedBalance = balance;
637
+ return balance;
638
+ }
639
+ } catch (error) {
640
+ if (error?.response?.status !== 404) {
641
+ errors.push({
642
+ endpoint,
643
+ status: error?.response?.status,
644
+ message: error?.message || "Unknown error"
645
+ });
646
+ }
647
+ }
648
+ }
649
+ if (errors.length > 0) {
650
+ logger.warn("All balance API endpoints failed:", errors);
651
+ }
652
+ return null;
653
+ }
654
+ /**
655
+ * Get transaction history
656
+ * @param page - Optional page number for pagination
657
+ * @returns Array of transactions
658
+ */
659
+ async getTransactions(page) {
660
+ const endpoints = [
661
+ page ? `/api/v1/profile/completion-grants/claims?page=${page}` : "/api/v1/profile/completion-grants/claims",
662
+ page ? `/api/v1/wallet/transactions?page=${page}` : "/api/v1/wallet/transactions"
663
+ ];
664
+ const errors = [];
665
+ for (const url of endpoints) {
666
+ try {
667
+ const response = await this.client.axiosInstance.get(url);
668
+ const data = response.data?.data || response.data;
669
+ return {
670
+ transactions: data?.results ?? data?.transactions ?? [],
671
+ next: data?.next,
672
+ previous: data?.previous,
673
+ count: data?.count ?? 0
674
+ };
675
+ } catch (error) {
676
+ if (error?.response?.status !== 404) {
677
+ errors.push({
678
+ endpoint: url,
679
+ status: error?.response?.status,
680
+ message: error?.message || "Unknown error"
681
+ });
682
+ }
683
+ }
684
+ }
685
+ if (errors.length > 0) {
686
+ logger.warn("All transaction API endpoints failed:", errors);
687
+ }
688
+ return {
689
+ transactions: [],
690
+ next: void 0,
691
+ previous: void 0,
692
+ count: 0
693
+ };
694
+ }
695
+ };
696
+
697
+ // src/lib/utils/phone.ts
698
+ var COUNTRY_CODES = [
699
+ { code: "1", country: "US", flag: "\u{1F1FA}\u{1F1F8}", name: "United States" },
700
+ { code: "91", country: "IN", flag: "\u{1F1EE}\u{1F1F3}", name: "India" },
701
+ { code: "44", country: "GB", flag: "\u{1F1EC}\u{1F1E7}", name: "United Kingdom" },
702
+ { code: "86", country: "CN", flag: "\u{1F1E8}\u{1F1F3}", name: "China" },
703
+ { code: "81", country: "JP", flag: "\u{1F1EF}\u{1F1F5}", name: "Japan" },
704
+ { code: "82", country: "KR", flag: "\u{1F1F0}\u{1F1F7}", name: "South Korea" },
705
+ { code: "33", country: "FR", flag: "\u{1F1EB}\u{1F1F7}", name: "France" },
706
+ { code: "49", country: "DE", flag: "\u{1F1E9}\u{1F1EA}", name: "Germany" },
707
+ { code: "7", country: "RU", flag: "\u{1F1F7}\u{1F1FA}", name: "Russia" },
708
+ { code: "55", country: "BR", flag: "\u{1F1E7}\u{1F1F7}", name: "Brazil" },
709
+ { code: "61", country: "AU", flag: "\u{1F1E6}\u{1F1FA}", name: "Australia" },
710
+ { code: "65", country: "SG", flag: "\u{1F1F8}\u{1F1EC}", name: "Singapore" },
711
+ { code: "971", country: "AE", flag: "\u{1F1E6}\u{1F1EA}", name: "UAE" },
712
+ { code: "966", country: "SA", flag: "\u{1F1F8}\u{1F1E6}", name: "Saudi Arabia" },
713
+ { code: "62", country: "ID", flag: "\u{1F1EE}\u{1F1E9}", name: "Indonesia" },
714
+ { code: "60", country: "MY", flag: "\u{1F1F2}\u{1F1FE}", name: "Malaysia" },
715
+ { code: "66", country: "TH", flag: "\u{1F1F9}\u{1F1ED}", name: "Thailand" },
716
+ { code: "84", country: "VN", flag: "\u{1F1FB}\u{1F1F3}", name: "Vietnam" },
717
+ { code: "63", country: "PH", flag: "\u{1F1F5}\u{1F1ED}", name: "Philippines" },
718
+ { code: "31", country: "NL", flag: "\u{1F1F3}\u{1F1F1}", name: "Netherlands" }
719
+ ];
720
+ function formatPhoneNumber(phone) {
721
+ const cleaned = phone.replace(/\D/g, "");
722
+ if (cleaned.length === 10) {
723
+ return `${cleaned.slice(0, 3)}-${cleaned.slice(3, 6)}-${cleaned.slice(6)}`;
724
+ }
725
+ return cleaned;
726
+ }
727
+ function parsePhoneNumber(phone) {
728
+ return phone.replace(/\D/g, "");
729
+ }
730
+
731
+ // src/lib/utils/wallet.ts
732
+ var formatBalance = (balance) => {
733
+ if (balance === 0) return "0";
734
+ const formatted = balance.toLocaleString("en-US", {
735
+ minimumFractionDigits: 0,
736
+ maximumFractionDigits: 2
737
+ });
738
+ return formatted;
739
+ };
740
+ var formatBalanceShort = (balance) => {
741
+ if (balance === 0) return "0";
742
+ if (balance < 1e3) return formatBalance(balance);
743
+ if (balance < 1e6) return `${(balance / 1e3).toFixed(1)}K`;
744
+ return `${(balance / 1e6).toFixed(1)}M`;
745
+ };
746
+ var formatWalletAddress = (address) => {
747
+ if (!address || address.length < 8) return address;
748
+ return `${address.slice(0, 4)}...${address.slice(-4)}`;
749
+ };
750
+ var formatNickname = (nickname) => {
751
+ if (!nickname) return "";
752
+ return nickname.startsWith("@") ? nickname : `@${nickname}`;
753
+ };
754
+ var formatTransactionAmount = (amount, action) => {
755
+ const formatted = formatBalance(amount);
756
+ return action === "spend" ? `- ${formatted}` : `+ ${formatted}`;
757
+ };
758
+ var getTransactionColor = (action) => {
759
+ return action === "spend" ? "#FF4444" : "#00C853";
760
+ };
761
+
762
+ // src/ZoPassportSDK.ts
763
+ var ZoPassportSDK = class {
764
+ constructor(config) {
765
+ this.refreshTimer = null;
766
+ this._user = null;
767
+ this._isAuthenticated = false;
768
+ if (config.debug) {
769
+ logger.enable();
770
+ logger.setLevel("debug");
771
+ }
772
+ this.storage = config.storageAdapter || new LocalStorageAdapter();
773
+ this.client = new ZoApiClient({
774
+ ...config,
775
+ storageAdapter: this.storage
776
+ });
777
+ this.auth = new ZoAuth(this.client);
778
+ this.profile = new ZoProfile(this.client);
779
+ this.avatar = new ZoAvatar(this.client);
780
+ this.wallet = new ZoWallet(this.client);
781
+ if (config.autoRefresh !== false) {
782
+ this.startAutoRefresh(config.refreshInterval || 6e4);
783
+ }
784
+ this._readyPromise = this.loadSession();
785
+ logger.debug("SDK initialized with config:", {
786
+ baseUrl: config.baseUrl,
787
+ autoRefresh: config.autoRefresh !== false
788
+ });
789
+ }
790
+ /**
791
+ * Wait for the SDK to be ready (session loaded from storage)
792
+ * Use this if you need to check isAuthenticated immediately after construction
793
+ */
794
+ async ready() {
795
+ return this._readyPromise;
796
+ }
797
+ // =====================
798
+ // Session Management
799
+ // =====================
800
+ async loadSession() {
801
+ try {
802
+ const userJson = await this.storage.getItem(STORAGE_KEYS.USER);
803
+ const accessToken = await this.storage.getItem(STORAGE_KEYS.ACCESS_TOKEN);
804
+ if (userJson && accessToken) {
805
+ this._user = JSON.parse(userJson);
806
+ this._isAuthenticated = true;
807
+ }
808
+ } catch (error) {
809
+ logger.warn("Failed to load session:", error);
810
+ }
811
+ }
812
+ async saveSession(authResponse) {
813
+ await this.storage.setItem(STORAGE_KEYS.ACCESS_TOKEN, authResponse.access_token);
814
+ await this.storage.setItem(STORAGE_KEYS.REFRESH_TOKEN, authResponse.refresh_token);
815
+ await this.storage.setItem(STORAGE_KEYS.TOKEN_EXPIRY, authResponse.access_token_expiry);
816
+ await this.storage.setItem(STORAGE_KEYS.REFRESH_EXPIRY, authResponse.refresh_token_expiry);
817
+ await this.storage.setItem(STORAGE_KEYS.USER, JSON.stringify(authResponse.user));
818
+ await this.storage.setItem(STORAGE_KEYS.CLIENT_DEVICE_ID, authResponse.device_id || "");
819
+ await this.storage.setItem(STORAGE_KEYS.CLIENT_DEVICE_SECRET, authResponse.device_secret || "");
820
+ this._user = authResponse.user;
821
+ this._isAuthenticated = true;
822
+ }
823
+ async clearSession() {
824
+ await this.storage.removeItem(STORAGE_KEYS.ACCESS_TOKEN);
825
+ await this.storage.removeItem(STORAGE_KEYS.REFRESH_TOKEN);
826
+ await this.storage.removeItem(STORAGE_KEYS.TOKEN_EXPIRY);
827
+ await this.storage.removeItem(STORAGE_KEYS.REFRESH_EXPIRY);
828
+ await this.storage.removeItem(STORAGE_KEYS.USER);
829
+ await this.storage.removeItem(STORAGE_KEYS.CLIENT_DEVICE_ID);
830
+ await this.storage.removeItem(STORAGE_KEYS.CLIENT_DEVICE_SECRET);
831
+ this._user = null;
832
+ this._isAuthenticated = false;
833
+ }
834
+ // =====================
835
+ // Auto Token Refresh
836
+ // =====================
837
+ startAutoRefresh(interval) {
838
+ this.refreshTimer = setInterval(async () => {
839
+ await this.refreshTokenIfNeeded();
840
+ }, interval);
841
+ }
842
+ stopAutoRefresh() {
843
+ if (this.refreshTimer) {
844
+ clearInterval(this.refreshTimer);
845
+ this.refreshTimer = null;
846
+ }
847
+ }
848
+ async refreshTokenIfNeeded() {
849
+ const tokenExpiry = await this.storage.getItem(STORAGE_KEYS.TOKEN_EXPIRY);
850
+ const refreshToken = await this.storage.getItem(STORAGE_KEYS.REFRESH_TOKEN);
851
+ if (!tokenExpiry || !refreshToken) return;
852
+ const expiryDate = new Date(tokenExpiry);
853
+ const now = /* @__PURE__ */ new Date();
854
+ const twoMinutes = 2 * 60 * 1e3;
855
+ if (expiryDate.getTime() - now.getTime() < twoMinutes) {
856
+ const result = await this.auth.refreshAccessToken(refreshToken);
857
+ if (result.success && result.tokens) {
858
+ await this.storage.setItem(STORAGE_KEYS.ACCESS_TOKEN, result.tokens.access);
859
+ await this.storage.setItem(STORAGE_KEYS.REFRESH_TOKEN, result.tokens.refresh);
860
+ await this.storage.setItem(STORAGE_KEYS.TOKEN_EXPIRY, result.tokens.access_expiry);
861
+ await this.storage.setItem(STORAGE_KEYS.REFRESH_EXPIRY, result.tokens.refresh_expiry);
862
+ }
863
+ }
864
+ }
865
+ // =====================
866
+ // Public API
867
+ // =====================
868
+ get user() {
869
+ return this._user;
870
+ }
871
+ get isAuthenticated() {
872
+ return this._isAuthenticated;
873
+ }
874
+ /**
875
+ * Complete phone authentication flow
876
+ */
877
+ async loginWithPhone(countryCode, phoneNumber, otp) {
878
+ const result = await this.auth.verifyOTP(countryCode, phoneNumber, otp);
879
+ if (result.success && result.data) {
880
+ await this.saveSession(result.data);
881
+ if (result.data.user?.wallet_address) {
882
+ this.wallet.setWalletAddress(result.data.user.wallet_address, "base");
883
+ }
884
+ return { success: true, user: result.data.user };
885
+ }
886
+ return { success: false, error: result.error };
887
+ }
888
+ /**
889
+ * Logout and clear session
890
+ */
891
+ async logout() {
892
+ await this.clearSession();
893
+ this.stopAutoRefresh();
894
+ }
895
+ /**
896
+ * Get current user profile
897
+ */
898
+ async getProfile() {
899
+ const accessToken = await this.storage.getItem(STORAGE_KEYS.ACCESS_TOKEN);
900
+ if (!accessToken) return null;
901
+ const result = await this.profile.getProfile(accessToken);
902
+ if (result.success && result.profile) {
903
+ this._user = result.profile;
904
+ await this.storage.setItem(STORAGE_KEYS.USER, JSON.stringify(result.profile));
905
+ if (result.profile.wallet_address) {
906
+ this.wallet.setWalletAddress(result.profile.wallet_address, "base");
907
+ }
908
+ return result.profile;
909
+ }
910
+ return null;
911
+ }
912
+ /**
913
+ * Update user profile
914
+ */
915
+ async updateProfile(updates) {
916
+ const accessToken = await this.storage.getItem(STORAGE_KEYS.ACCESS_TOKEN);
917
+ if (!accessToken) return { success: false, error: "Not authenticated" };
918
+ const result = await this.profile.updateProfile(accessToken, updates);
919
+ if (result.success && result.profile) {
920
+ this._user = result.profile;
921
+ await this.storage.setItem(STORAGE_KEYS.USER, JSON.stringify(result.profile));
922
+ return { success: true, profile: result.profile };
923
+ }
924
+ return { success: false, error: result.error };
925
+ }
926
+ /**
927
+ * Generate avatar
928
+ */
929
+ async generateAvatar(bodyType) {
930
+ const accessToken = await this.storage.getItem(STORAGE_KEYS.ACCESS_TOKEN);
931
+ if (!accessToken) return { success: false, error: "Not authenticated" };
932
+ const startResult = await this.avatar.generateAvatar(accessToken, bodyType);
933
+ if (!startResult.success || !startResult.task_id) {
934
+ return { success: false, error: startResult.error };
935
+ }
936
+ return new Promise((resolve) => {
937
+ this.avatar.pollAvatarStatus(accessToken, startResult.task_id, {
938
+ onComplete: (avatarUrl) => {
939
+ resolve({ success: true, avatarUrl });
940
+ },
941
+ onError: (error) => {
942
+ resolve({ success: false, error });
943
+ }
944
+ });
945
+ });
946
+ }
947
+ /**
948
+ * Get wallet balance
949
+ */
950
+ async getWalletBalance() {
951
+ return this.wallet.getBalance();
952
+ }
953
+ /**
954
+ * Get wallet transactions
955
+ */
956
+ async getWalletTransactions(page) {
957
+ return this.wallet.getTransactions(page);
958
+ }
959
+ /**
960
+ * Cleanup
961
+ */
962
+ destroy() {
963
+ this.stopAutoRefresh();
964
+ }
965
+ };
966
+
967
+ // assets/wallet/constants.ts
968
+ var WALLET_DIMENSIONS = {
969
+ cardAspectRatio: 312 / 200,
970
+ coverAspectRatio: 312 / 120,
971
+ cardBorderRadius: 16,
972
+ innerBorderRadius: 12,
973
+ avatarSize: 32,
974
+ tokenSize: 16,
975
+ tokenVideoSize: 24
976
+ };
977
+
978
+ // assets/index.ts
979
+ var ASSETS = {
980
+ // Core avatars
981
+ BRO_AVATAR: "/bro.png",
982
+ BAE_AVATAR: "/bae.png",
983
+ FALLBACK_AVATAR: "/zo-fallback.png",
984
+ DEFAULT_AVATAR: "/images/rank1.jpeg",
985
+ // Branding
986
+ ZO_LOGO: "/figma-assets/landing-zo-logo.png",
987
+ ZO_COIN: "/zo-coin.gif",
988
+ // Videos
989
+ LANDING_VIDEO: "/videos/loading-screen-background.mp4",
990
+ PORTAL_VIDEO: "/videos/opening-disks.mp4",
991
+ // Passport backgrounds (CDN)
992
+ FOUNDER_BG: "https://proxy.cdn.zo.xyz/gallery/media/images/a1659b07-94f0-4490-9b3c-3366715d9717_20250515053726.png",
993
+ CITIZEN_BG: "https://proxy.cdn.zo.xyz/gallery/media/images/bda9da5a-eefe-411d-8d90-667c80024463_20250515053805.png",
994
+ // Lotties
995
+ LOADER: "/lotties/loader.json",
996
+ SPINNER: "/lotties/spinner.json"
997
+ };
998
+ var CULTURE_STICKERS = {
999
+ travel: "/cultural-stickers/Travel&Adventure.png",
1000
+ design: "/cultural-stickers/Design.png",
1001
+ tech: "/cultural-stickers/Science&Technology.png",
1002
+ food: "/cultural-stickers/Food.png",
1003
+ music: "/cultural-stickers/Music&Entertainment.png",
1004
+ photography: "/cultural-stickers/Photography.png",
1005
+ fitness: "/cultural-stickers/Health&Fitness.png",
1006
+ sports: "/cultural-stickers/Sport.png",
1007
+ literature: "/cultural-stickers/Literature&Stories.png",
1008
+ cinema: "/cultural-stickers/Television&Cinema.png",
1009
+ spiritual: "/cultural-stickers/Spiritual.png",
1010
+ nature: "/cultural-stickers/Nature&Wildlife.png",
1011
+ business: "/cultural-stickers/Business.png",
1012
+ law: "/cultural-stickers/Law.png",
1013
+ lifestyle: "/cultural-stickers/Home&Lifestyle.png",
1014
+ gaming: "/cultural-stickers/Game.png",
1015
+ stories: "/cultural-stickers/Stories&Journal.png"
1016
+ };
1017
+ var CULTURES = [
1018
+ { id: "travel", name: "Travel & Adventure", icon: CULTURE_STICKERS.travel },
1019
+ { id: "design", name: "Design", icon: CULTURE_STICKERS.design },
1020
+ { id: "tech", name: "Science & Technology", icon: CULTURE_STICKERS.tech },
1021
+ { id: "food", name: "Food", icon: CULTURE_STICKERS.food },
1022
+ { id: "music", name: "Music & Entertainment", icon: CULTURE_STICKERS.music },
1023
+ { id: "photography", name: "Photography", icon: CULTURE_STICKERS.photography },
1024
+ { id: "fitness", name: "Health & Fitness", icon: CULTURE_STICKERS.fitness },
1025
+ { id: "sports", name: "Sport", icon: CULTURE_STICKERS.sports },
1026
+ { id: "literature", name: "Literature & Stories", icon: CULTURE_STICKERS.literature },
1027
+ { id: "cinema", name: "Television & Cinema", icon: CULTURE_STICKERS.cinema },
1028
+ { id: "spiritual", name: "Spiritual", icon: CULTURE_STICKERS.spiritual },
1029
+ { id: "nature", name: "Nature & Wildlife", icon: CULTURE_STICKERS.nature },
1030
+ { id: "business", name: "Business", icon: CULTURE_STICKERS.business },
1031
+ { id: "law", name: "Law", icon: CULTURE_STICKERS.law },
1032
+ { id: "lifestyle", name: "Home & Lifestyle", icon: CULTURE_STICKERS.lifestyle },
1033
+ { id: "gaming", name: "Game", icon: CULTURE_STICKERS.gaming },
1034
+ { id: "stories", name: "Stories & Journal", icon: CULTURE_STICKERS.stories }
1035
+ ];
1036
+ export {
1037
+ ASSETS,
1038
+ AsyncStorageAdapter,
1039
+ COUNTRY_CODES,
1040
+ CULTURES,
1041
+ CULTURE_STICKERS,
1042
+ LocalStorageAdapter,
1043
+ STORAGE_KEYS,
1044
+ ZoApiClient,
1045
+ ZoAuth,
1046
+ ZoAvatar,
1047
+ ZoPassportSDK,
1048
+ ZoProfile,
1049
+ ZoWallet,
1050
+ formatBalance,
1051
+ formatBalanceShort,
1052
+ formatNickname,
1053
+ formatPhoneNumber,
1054
+ formatTransactionAmount,
1055
+ formatWalletAddress,
1056
+ getTransactionColor,
1057
+ logger,
1058
+ parsePhoneNumber
1059
+ };
1060
+ //# sourceMappingURL=index.mjs.map