@kryptos_connect/mobile-sdk 1.0.0 → 1.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -77,6 +77,7 @@ const config = {
77
77
  appLogo: "https://your-logo-url.com/logo.png", // or require('./logo.png')
78
78
  clientId: "your-client-id",
79
79
  theme: "light", // or 'dark'
80
+ walletConnectProjectId: "your-walletconnect-project-id", // optional
80
81
  };
81
82
 
82
83
  export default function App() {
@@ -96,19 +97,35 @@ import { KryptosConnectButton } from "@kryptos_connect/mobile-sdk";
96
97
  function YourComponent() {
97
98
  const generateLinkToken = async () => {
98
99
  // Call your backend API to generate a link token
99
- const response = await fetch("https://your-api.com/generate-link-token");
100
+ const response = await fetch("https://your-api.com/generate-link-token", {
101
+ method: "POST",
102
+ headers: {
103
+ "Content-Type": "application/json",
104
+ },
105
+ body: JSON.stringify({
106
+ // Optional: Include access_token for authenticated users
107
+ // access_token: userAccessToken
108
+ }),
109
+ });
100
110
  const data = await response.json();
101
- return data.link_token;
111
+ // Return link_token and isAuthorized flag
112
+ return {
113
+ link_token: data.link_token,
114
+ isAuthorized: data.isAuthorized, // pass true if access_token was provided
115
+ };
102
116
  };
103
117
 
104
118
  const handleSuccess = (userConsent) => {
105
119
  console.log("Connection successful!", userConsent);
106
- // Handle the public_token
107
- const publicToken = userConsent?.public_token;
120
+ // For new users, exchange the public token for an access token
121
+ // For authorized users, userConsent might be null
122
+ if (userConsent?.public_token) {
123
+ exchangePublicToken(userConsent.public_token);
124
+ }
108
125
  };
109
126
 
110
- const handleError = () => {
111
- console.log("Connection failed");
127
+ const handleError = (error) => {
128
+ console.error("Connection failed:", error);
112
129
  };
113
130
 
114
131
  return (
@@ -121,6 +138,59 @@ function YourComponent() {
121
138
  }
122
139
  ```
123
140
 
141
+ ## User Flow Variations
142
+
143
+ The SDK automatically handles two different user flows based on the `isAuthorized` flag returned from `generateLinkToken`:
144
+
145
+ ### Flow 1: New/Anonymous User (`isAuthorized: false` or undefined)
146
+
147
+ ```
148
+ INIT → AUTH (Login) → OTP → INTEGRATION → PERMISSIONS → STATUS
149
+ ```
150
+
151
+ - User enters email and verifies OTP (or continues as guest)
152
+ - User selects integrations to connect
153
+ - User grants permissions/consent
154
+ - Returns `public_token` in `onSuccess` callback
155
+
156
+ ### Flow 2: Authenticated User (`isAuthorized: true`)
157
+
158
+ ```
159
+ INIT → INTEGRATION → STATUS
160
+ ```
161
+
162
+ - Skips login/OTP (already authenticated)
163
+ - Skips permissions (already consented)
164
+ - User directly selects integrations
165
+ - Returns `null` in `onSuccess` callback (no new token needed)
166
+
167
+ ### Implementation Example
168
+
169
+ ```tsx
170
+ const generateLinkToken = async () => {
171
+ const user = getCurrentUser(); // Your auth logic
172
+
173
+ const response = await fetch("/api/kryptos/create-link-token", {
174
+ method: "POST",
175
+ headers: {
176
+ "Content-Type": "application/json",
177
+ },
178
+ body: JSON.stringify({
179
+ // Include access_token if user is logged in
180
+ access_token: user?.kryptosAccessToken || undefined,
181
+ }),
182
+ });
183
+
184
+ const data = await response.json();
185
+
186
+ return {
187
+ link_token: data.link_token,
188
+ // isAuthorized will be true if access_token was valid
189
+ isAuthorized: !!user?.kryptosAccessToken,
190
+ };
191
+ };
192
+ ```
193
+
124
194
  ## WalletConnect / Reown AppKit configuration
125
195
 
126
196
  Passing a `walletConnectProjectId` to `KryptosConnectProvider` enables the built-in AppKit (WalletConnect v2) flow used by the `WalletConnectComponent`:
@@ -148,13 +218,47 @@ export default function App() {
148
218
 
149
219
  Behind the scenes the SDK creates an AppKit instance (see `src/wallet-connect/AppKitConfig.ts`) with Ethers adapter, common EVM chains, and persistent storage. If you need to customize chains, metadata, or features, copy that file into your app and adjust it following the Reown AppKit guide: https://docs.reown.com/appkit/overview
150
220
 
221
+ ### Environment Configuration
222
+
223
+ You can configure different environments (dev/prod) by passing the `baseUrl` option:
224
+
225
+ ```tsx
226
+ import { KryptosConnectProvider } from "@kryptos_connect/mobile-sdk";
227
+
228
+ // Development Environment
229
+ const devConfig = {
230
+ appName: "Your App",
231
+ clientId: "your-client-id",
232
+ theme: "light",
233
+ walletConnectProjectId: "your-project-id",
234
+ };
235
+
236
+ // Production Environment
237
+ const prodConfig = {
238
+ appName: "Your App",
239
+ clientId: "your-client-id",
240
+ theme: "light",
241
+ walletConnectProjectId: "your-project-id",
242
+ };
243
+
244
+ export default function App() {
245
+ const config = __DEV__ ? devConfig : prodConfig;
246
+
247
+ return (
248
+ <KryptosConnectProvider config={config}>
249
+ <YourApp />
250
+ </KryptosConnectProvider>
251
+ );
252
+ }
253
+ ```
254
+
151
255
  ### 3. Custom Button (Optional)
152
256
 
153
257
  You can also use a custom button:
154
258
 
155
259
  ```tsx
156
260
  import { KryptosConnectButton } from "@kryptos_connect/mobile-sdk";
157
- import { Text, View } from "react-native";
261
+ import { Text, View, StyleSheet } from "react-native";
158
262
 
159
263
  function CustomButton() {
160
264
  return (
@@ -164,13 +268,38 @@ function CustomButton() {
164
268
  onError={handleError}
165
269
  >
166
270
  <View style={styles.customButton}>
167
- <Text style={styles.customText}>Connect Your Wallet</Text>
271
+ <Text style={styles.customText}>🔐 Connect Your Wallet</Text>
168
272
  </View>
169
273
  </KryptosConnectButton>
170
274
  );
171
275
  }
276
+
277
+ const styles = StyleSheet.create({
278
+ customButton: {
279
+ backgroundColor: "#00C693",
280
+ paddingVertical: 12,
281
+ paddingHorizontal: 24,
282
+ borderRadius: 8,
283
+ alignItems: "center",
284
+ },
285
+ customText: {
286
+ color: "#FFFFFF",
287
+ fontSize: 16,
288
+ fontWeight: "600",
289
+ },
290
+ });
172
291
  ```
173
292
 
293
+ ## Features
294
+
295
+ ### Sandbox Mode Indicator
296
+
297
+ The SDK automatically displays a "Sandbox Mode" badge when using the development environment. This helps distinguish between development and production environments visually. The badge appears at the bottom of the authentication modal and only shows when:
298
+
299
+ - The client's project stage is not "production"
300
+
301
+ This indicator is automatically managed by the SDK and requires no additional configuration.
302
+
174
303
  ## API Reference
175
304
 
176
305
  ### KryptosConnectProvider
@@ -189,7 +318,7 @@ The provider component that wraps your app and provides the Kryptos context.
189
318
  ```typescript
190
319
  type KryptosConfig = {
191
320
  appName: string; // Your app name
192
- appLogo?: ReactNode | string; // Logo URL or React Native Image source
321
+ appLogo?: ReactNode | string | ImageSourcePropType; // Logo URL, React Native Image source, or require()
193
322
  theme?: "light" | "dark"; // Theme mode (default: 'light')
194
323
  clientId: string; // Your Kryptos client ID
195
324
  walletConnectProjectId?: string; // Optional WalletConnect project ID
@@ -202,44 +331,216 @@ The main button component that triggers the connection flow.
202
331
 
203
332
  #### Props
204
333
 
205
- | Prop | Type | Required | Description |
206
- | ------------------- | -------------------------------- | -------- | --------------------------------- |
207
- | `generateLinkToken` | `() => Promise<string>` | Yes | Function to generate a link token |
208
- | `onSuccess` | `(consent: UserConsent) => void` | No | Called on successful connection |
209
- | `onError` | `() => void` | No | Called on connection failure |
210
- | `children` | `ReactNode` | No | Custom button content |
211
- | `style` | `ViewStyle` | No | Custom button container style |
212
- | `textStyle` | `TextStyle` | No | Custom button text style |
334
+ | Prop | Type | Required | Description |
335
+ | ------------------- | --------------------------------------------------------------- | -------- | ------------------------------------------------------------------------------------------------------------------------ |
336
+ | `generateLinkToken` | `() => Promise<{ link_token: string; isAuthorized?: boolean }>` | Yes | Function that returns link token and authorization status. `isAuthorized: true` skips login flow for authenticated users |
337
+ | `onSuccess` | `(consent: UserConsent \| null) => void` | No | Callback fired when connection succeeds. Receives `public_token` for new users, `null` for authorized users |
338
+ | `onError` | `(error?: Error) => void` | No | Callback fired when connection fails |
339
+ | `children` | `ReactNode` | No | Custom button content |
340
+ | `style` | `ViewStyle` | No | Custom button container style |
341
+ | `textStyle` | `TextStyle` | No | Custom button text style |
213
342
 
214
- ### KryptosConnectModal
343
+ #### TypeScript Types
215
344
 
216
- If you need more control, you can use the modal component directly:
345
+ ```typescript
346
+ interface KryptosConnectButtonProps {
347
+ generateLinkToken: () => Promise<{
348
+ link_token: string;
349
+ isAuthorized?: boolean;
350
+ }>;
351
+ onSuccess?: (data: UserConsent | null) => void;
352
+ onError?: (error?: Error) => void;
353
+ children?: ReactNode;
354
+ style?: ViewStyle;
355
+ textStyle?: TextStyle;
356
+ }
217
357
 
218
- ```tsx
219
- import { KryptosConnectModal } from "@kryptos_connect/mobile-sdk";
220
- import { useState } from "react";
358
+ interface UserConsent {
359
+ public_token: string;
360
+ // Additional consent data
361
+ }
221
362
 
222
- function YourComponent() {
223
- const [open, setOpen] = useState(false);
363
+ // Note: onSuccess receives:
364
+ // - UserConsent with public_token for new users (isAuthorized: false)
365
+ // - null for authenticated users (isAuthorized: true)
366
+ ```
224
367
 
225
- return (
226
- <>
227
- <TouchableOpacity onPress={() => setOpen(true)}>
228
- <Text>Open Modal</Text>
229
- </TouchableOpacity>
230
-
231
- <KryptosConnectModal
232
- open={open}
233
- setOpen={setOpen}
234
- generateLinkToken={generateLinkToken}
235
- onSuccess={handleSuccess}
236
- onError={handleError}
237
- />
238
- </>
239
- );
240
- }
368
+ ## Backend Integration
369
+
370
+ You'll need to implement two backend endpoints to complete the integration.
371
+
372
+ ### Base URLs
373
+
374
+ | Environment | URL |
375
+ | -------------- | --------------------------------- |
376
+ | **Production** | `https://connect-api.kryptos.io/` |
377
+
378
+ ### 1. Create Link Token
379
+
380
+ The link token can be created in two ways, depending on whether you want to authenticate an existing user or create a new session.
381
+
382
+ #### Option A: Without Access Token (New/Anonymous User)
383
+
384
+ Use this approach for **new users** or when you want users to go through the login/OTP flow:
385
+
386
+ ```javascript
387
+ // Example: Node.js/Express
388
+ app.post("/api/kryptos/create-link-token", async (req, res) => {
389
+ try {
390
+ const response = await fetch(`${KRYPTOS_BASE_URL}/link-token`, {
391
+ method: "POST",
392
+ headers: {
393
+ "Content-Type": "application/json",
394
+ "X-Client-Id": YOUR_CLIENT_ID,
395
+ "X-Client-Secret": YOUR_CLIENT_SECRET,
396
+ },
397
+ body: JSON.stringify({
398
+ scopes:
399
+ "openid profile offline_access email portfolios:read transactions:read integrations:read tax:read accounting:read reports:read workspace:read users:read",
400
+ }),
401
+ });
402
+
403
+ const data = await response.json();
404
+ res.json({
405
+ link_token: data.data.link_token,
406
+ isAuthorized: false, // No access token provided
407
+ });
408
+ } catch (error) {
409
+ res.status(500).json({ error: "Failed to create link token" });
410
+ }
411
+ });
412
+ ```
413
+
414
+ **User Experience Flow**:
415
+
416
+ 1. User clicks "Connect to Kryptos"
417
+ 2. **Login screen appears**
418
+ 3. User enters email → Verifies OTP (OR continues as guest)
419
+ 4. User selects integrations
420
+ 5. User grants permissions
421
+ 6. `onSuccess` receives `public_token` to exchange
422
+
423
+ #### Option B: With Access Token (Authenticated User)
424
+
425
+ Use this approach for **existing authenticated users** to skip the login flow:
426
+
427
+ ```javascript
428
+ // Example: Node.js/Express
429
+ app.post("/api/kryptos/create-link-token", async (req, res) => {
430
+ try {
431
+ // Get the user's stored access token from your database
432
+ const userAccessToken = await getUserAccessToken(req.user.id);
433
+
434
+ const response = await fetch(`${KRYPTOS_BASE_URL}/link-token`, {
435
+ method: "POST",
436
+ headers: {
437
+ "Content-Type": "application/json",
438
+ "X-Client-Id": YOUR_CLIENT_ID,
439
+ "X-Client-Secret": YOUR_CLIENT_SECRET,
440
+ },
441
+ body: JSON.stringify({
442
+ scopes:
443
+ "openid profile offline_access integrations:read integrations:write",
444
+ access_token: userAccessToken, // Pass the user's access token
445
+ }),
446
+ });
447
+
448
+ const data = await response.json();
449
+ res.json({
450
+ link_token: data.data.link_token,
451
+ isAuthorized: true, // Access token provided, user is authenticated
452
+ });
453
+ } catch (error) {
454
+ res.status(500).json({ error: "Failed to create link token" });
455
+ }
456
+ });
241
457
  ```
242
458
 
459
+ **User Experience Flow**:
460
+
461
+ 1. User clicks "Connect to Kryptos"
462
+ 2. **Directly shows integration selection** (no login screen)
463
+ 3. User selects integrations
464
+ 4. `onSuccess` receives `null` (no new token needed)
465
+
466
+ #### Choosing the Right Approach
467
+
468
+ | Scenario | Use Option | isAuthorized | User Flow | Returns public_token |
469
+ | ------------------------------------------ | ----------------- | ------------ | --------------------------------------- | -------------------- |
470
+ | First-time user connecting to Kryptos | A (Without Token) | `false` | LOGIN → OTP → INTEGRATION → PERMISSIONS | ✅ Yes |
471
+ | User doesn't have an access token yet | A (Without Token) | `false` | LOGIN → OTP → INTEGRATION → PERMISSIONS | ✅ Yes |
472
+ | Returning user with stored access token | B (With Token) | `true` | INTEGRATION only (skip login) | ❌ No |
473
+ | Adding more integrations for existing user | B (With Token) | `true` | INTEGRATION only (skip login) | ❌ No |
474
+
475
+ **Important Notes**:
476
+
477
+ - After the first successful connection (using Option A), store the `access_token` you receive from the token exchange
478
+ - Use the stored `access_token` for subsequent connections (Option B) to provide a seamless experience
479
+ - For authorized users (`isAuthorized: true`), the `onSuccess` callback receives `null` instead of a `public_token`
480
+ - Authorized users skip both the login/OTP and permissions steps
481
+
482
+ ### 2. Exchange Public Token
483
+
484
+ After a successful connection, exchange the `public_token` for an `access_token`. **Important**: Store this `access_token` securely in your database - you'll use it to create link tokens for authenticated users (Option B above).
485
+
486
+ ```javascript
487
+ // Example: Node.js/Express
488
+ app.post("/api/kryptos/exchange-token", async (req, res) => {
489
+ try {
490
+ const { public_token } = req.body;
491
+
492
+ const response = await fetch(`${KRYPTOS_BASE_URL}/token/exchange`, {
493
+ method: "POST",
494
+ headers: {
495
+ "Content-Type": "application/json",
496
+ },
497
+ body: JSON.stringify({
498
+ public_token,
499
+ client_id: YOUR_CLIENT_ID,
500
+ client_secret: YOUR_CLIENT_SECRET,
501
+ }),
502
+ });
503
+
504
+ const data = await response.json();
505
+
506
+ // IMPORTANT: Store the access_token securely in your database
507
+ // You'll need this token to:
508
+ // 1. Create authenticated link tokens (skip login flow)
509
+ // 2. Make API calls on behalf of the user
510
+ await saveUserAccessToken(req.user.id, data.data.access_token);
511
+
512
+ res.json({ success: true });
513
+ } catch (error) {
514
+ res.status(500).json({ error: "Failed to exchange token" });
515
+ }
516
+ });
517
+ ```
518
+
519
+ **Complete Integration Flow:**
520
+
521
+ 1. **First Connection** (New User - `isAuthorized: false`):
522
+
523
+ ```
524
+ App → generateLinkToken() [returns { link_token, isAuthorized: false }]
525
+ → SDK Flow: INIT → AUTH → OTP → INTEGRATION → PERMISSIONS → STATUS
526
+ → User logs in with email/OTP (or as guest)
527
+ → User connects integrations
528
+ → User grants permissions
529
+ → onSuccess receives { public_token: "..." }
530
+ → Backend exchanges public_token for access_token
531
+ → Store access_token in database ← IMPORTANT
532
+ ```
533
+
534
+ 2. **Subsequent Connections** (Returning User - `isAuthorized: true`):
535
+ ```
536
+ App → generateLinkToken() [returns { link_token, isAuthorized: true }]
537
+ → SDK Flow: INIT → INTEGRATION → STATUS (skip AUTH, OTP)
538
+ → User directly sees integrations (no login)
539
+ → User connects more integrations
540
+ → onSuccess receives null (no new token needed)
541
+ → Integrations are added to user's existing account
542
+ ```
543
+
243
544
  ## Supported Platforms
244
545
 
245
546
  - ✅ iOS 12.0+
package/dist/index.d.mts CHANGED
@@ -7,27 +7,23 @@ type KryptosConfig = {
7
7
  theme?: "light" | "dark";
8
8
  clientId: string;
9
9
  walletConnectProjectId?: string;
10
+ baseUrl?: string;
11
+ };
12
+ type ClientInfo = {
13
+ name: string;
14
+ description: string | null;
15
+ scopes: string;
16
+ project_stage: string;
10
17
  };
11
18
  type KryptosUser = {
12
- message: string;
19
+ is_anonymous: boolean;
13
20
  user: {
14
- uid: string;
15
- email: string;
16
- baseCurrency: string;
17
- costbasisType: string;
18
- country: string;
19
- userType: string;
20
- isConnect: boolean;
21
- timezone: string;
22
- createdAt: number;
23
- updatedAt: number;
24
- lastUsage: number;
25
- connectMeta: {
26
- lastUsage: number;
27
- isAnonymous: boolean;
28
- clientId: string;
29
- };
21
+ email: string | null;
22
+ first_name: string | null;
30
23
  };
24
+ user_id: string;
25
+ workspace_id: string;
26
+ workspace_name: string;
31
27
  };
32
28
  type UserConsent = {
33
29
  public_token: string;
@@ -68,6 +64,9 @@ type KryptosContextType = KryptosConfig & {
68
64
  setEmail: (value: string) => void;
69
65
  userConsent: UserConsent | null;
70
66
  setUserConsent: (value: UserConsent | null) => void;
67
+ clientInfo: ClientInfo | null;
68
+ isAuthorized: boolean;
69
+ setIsAuthorized: (value: boolean) => void;
71
70
  };
72
71
  declare const KryptosConnectProvider: React.FC<{
73
72
  children: React.ReactNode;
package/dist/index.d.ts CHANGED
@@ -7,27 +7,23 @@ type KryptosConfig = {
7
7
  theme?: "light" | "dark";
8
8
  clientId: string;
9
9
  walletConnectProjectId?: string;
10
+ baseUrl?: string;
11
+ };
12
+ type ClientInfo = {
13
+ name: string;
14
+ description: string | null;
15
+ scopes: string;
16
+ project_stage: string;
10
17
  };
11
18
  type KryptosUser = {
12
- message: string;
19
+ is_anonymous: boolean;
13
20
  user: {
14
- uid: string;
15
- email: string;
16
- baseCurrency: string;
17
- costbasisType: string;
18
- country: string;
19
- userType: string;
20
- isConnect: boolean;
21
- timezone: string;
22
- createdAt: number;
23
- updatedAt: number;
24
- lastUsage: number;
25
- connectMeta: {
26
- lastUsage: number;
27
- isAnonymous: boolean;
28
- clientId: string;
29
- };
21
+ email: string | null;
22
+ first_name: string | null;
30
23
  };
24
+ user_id: string;
25
+ workspace_id: string;
26
+ workspace_name: string;
31
27
  };
32
28
  type UserConsent = {
33
29
  public_token: string;
@@ -68,6 +64,9 @@ type KryptosContextType = KryptosConfig & {
68
64
  setEmail: (value: string) => void;
69
65
  userConsent: UserConsent | null;
70
66
  setUserConsent: (value: UserConsent | null) => void;
67
+ clientInfo: ClientInfo | null;
68
+ isAuthorized: boolean;
69
+ setIsAuthorized: (value: boolean) => void;
71
70
  };
72
71
  declare const KryptosConnectProvider: React.FC<{
73
72
  children: React.ReactNode;