toss-expo-sdk 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 (116) hide show
  1. package/LICENSE +20 -0
  2. package/README.md +292 -0
  3. package/lib/module/ble.js +103 -0
  4. package/lib/module/ble.js.map +1 -0
  5. package/lib/module/client/TossClient.js +324 -0
  6. package/lib/module/client/TossClient.js.map +1 -0
  7. package/lib/module/client/index.js +4 -0
  8. package/lib/module/client/index.js.map +1 -0
  9. package/lib/module/contexts/WalletContext.js +99 -0
  10. package/lib/module/contexts/WalletContext.js.map +1 -0
  11. package/lib/module/discovery.js +434 -0
  12. package/lib/module/discovery.js.map +1 -0
  13. package/lib/module/errors.js +47 -0
  14. package/lib/module/errors.js.map +1 -0
  15. package/lib/module/examples/offlinePaymentFlow.js +234 -0
  16. package/lib/module/examples/offlinePaymentFlow.js.map +1 -0
  17. package/lib/module/index.js +32 -0
  18. package/lib/module/index.js.map +1 -0
  19. package/lib/module/intent.js +223 -0
  20. package/lib/module/intent.js.map +1 -0
  21. package/lib/module/intentManager.js +145 -0
  22. package/lib/module/intentManager.js.map +1 -0
  23. package/lib/module/internal/arciumHelper.js +50 -0
  24. package/lib/module/internal/arciumHelper.js.map +1 -0
  25. package/lib/module/nfc.js +54 -0
  26. package/lib/module/nfc.js.map +1 -0
  27. package/lib/module/noise.js +14 -0
  28. package/lib/module/noise.js.map +1 -0
  29. package/lib/module/package.json +1 -0
  30. package/lib/module/qr.js +57 -0
  31. package/lib/module/qr.js.map +1 -0
  32. package/lib/module/reconciliation.js +329 -0
  33. package/lib/module/reconciliation.js.map +1 -0
  34. package/lib/module/services/authService.js +205 -0
  35. package/lib/module/services/authService.js.map +1 -0
  36. package/lib/module/storage/secureStorage.js +89 -0
  37. package/lib/module/storage/secureStorage.js.map +1 -0
  38. package/lib/module/storage.js +16 -0
  39. package/lib/module/storage.js.map +1 -0
  40. package/lib/module/sync.js +64 -0
  41. package/lib/module/sync.js.map +1 -0
  42. package/lib/module/types/tossUser.js +41 -0
  43. package/lib/module/types/tossUser.js.map +1 -0
  44. package/lib/module/utils/nonceUtils.js +38 -0
  45. package/lib/module/utils/nonceUtils.js.map +1 -0
  46. package/lib/typescript/package.json +1 -0
  47. package/lib/typescript/src/__tests__/index.test.d.ts +1 -0
  48. package/lib/typescript/src/__tests__/index.test.d.ts.map +1 -0
  49. package/lib/typescript/src/__tests__/reconciliation.test.d.ts +6 -0
  50. package/lib/typescript/src/__tests__/reconciliation.test.d.ts.map +1 -0
  51. package/lib/typescript/src/ble.d.ts +10 -0
  52. package/lib/typescript/src/ble.d.ts.map +1 -0
  53. package/lib/typescript/src/client/TossClient.d.ts +110 -0
  54. package/lib/typescript/src/client/TossClient.d.ts.map +1 -0
  55. package/lib/typescript/src/client/index.d.ts +3 -0
  56. package/lib/typescript/src/client/index.d.ts.map +1 -0
  57. package/lib/typescript/src/contexts/WalletContext.d.ts +20 -0
  58. package/lib/typescript/src/contexts/WalletContext.d.ts.map +1 -0
  59. package/lib/typescript/src/discovery.d.ts +188 -0
  60. package/lib/typescript/src/discovery.d.ts.map +1 -0
  61. package/lib/typescript/src/errors.d.ts +27 -0
  62. package/lib/typescript/src/errors.d.ts.map +1 -0
  63. package/lib/typescript/src/examples/offlinePaymentFlow.d.ts +48 -0
  64. package/lib/typescript/src/examples/offlinePaymentFlow.d.ts.map +1 -0
  65. package/lib/typescript/src/index.d.ts +13 -0
  66. package/lib/typescript/src/index.d.ts.map +1 -0
  67. package/lib/typescript/src/intent.d.ts +84 -0
  68. package/lib/typescript/src/intent.d.ts.map +1 -0
  69. package/lib/typescript/src/intentManager.d.ts +46 -0
  70. package/lib/typescript/src/intentManager.d.ts.map +1 -0
  71. package/lib/typescript/src/internal/arciumHelper.d.ts +19 -0
  72. package/lib/typescript/src/internal/arciumHelper.d.ts.map +1 -0
  73. package/lib/typescript/src/nfc.d.ts +7 -0
  74. package/lib/typescript/src/nfc.d.ts.map +1 -0
  75. package/lib/typescript/src/noise.d.ts +5 -0
  76. package/lib/typescript/src/noise.d.ts.map +1 -0
  77. package/lib/typescript/src/qr.d.ts +6 -0
  78. package/lib/typescript/src/qr.d.ts.map +1 -0
  79. package/lib/typescript/src/reconciliation.d.ts +65 -0
  80. package/lib/typescript/src/reconciliation.d.ts.map +1 -0
  81. package/lib/typescript/src/services/authService.d.ts +55 -0
  82. package/lib/typescript/src/services/authService.d.ts.map +1 -0
  83. package/lib/typescript/src/storage/secureStorage.d.ts +7 -0
  84. package/lib/typescript/src/storage/secureStorage.d.ts.map +1 -0
  85. package/lib/typescript/src/storage.d.ts +4 -0
  86. package/lib/typescript/src/storage.d.ts.map +1 -0
  87. package/lib/typescript/src/sync.d.ts +40 -0
  88. package/lib/typescript/src/sync.d.ts.map +1 -0
  89. package/lib/typescript/src/types/tossUser.d.ts +39 -0
  90. package/lib/typescript/src/types/tossUser.d.ts.map +1 -0
  91. package/lib/typescript/src/utils/nonceUtils.d.ts +8 -0
  92. package/lib/typescript/src/utils/nonceUtils.d.ts.map +1 -0
  93. package/package.json +176 -0
  94. package/src/__tests__/index.test.tsx +1 -0
  95. package/src/__tests__/reconciliation.test.tsx +361 -0
  96. package/src/ble.ts +138 -0
  97. package/src/client/TossClient.ts +435 -0
  98. package/src/client/index.ts +2 -0
  99. package/src/contexts/WalletContext.tsx +127 -0
  100. package/src/discovery.ts +542 -0
  101. package/src/errors.ts +51 -0
  102. package/src/examples/offlinePaymentFlow.ts +331 -0
  103. package/src/index.tsx +61 -0
  104. package/src/intent.ts +328 -0
  105. package/src/intentManager.ts +164 -0
  106. package/src/internal/arciumHelper.ts +58 -0
  107. package/src/nfc.ts +57 -0
  108. package/src/noise.ts +9 -0
  109. package/src/qr.tsx +65 -0
  110. package/src/reconciliation.ts +421 -0
  111. package/src/services/authService.ts +238 -0
  112. package/src/storage/secureStorage.ts +100 -0
  113. package/src/storage.ts +17 -0
  114. package/src/sync.ts +101 -0
  115. package/src/types/tossUser.ts +81 -0
  116. package/src/utils/nonceUtils.ts +56 -0
@@ -0,0 +1,100 @@
1
+ import * as SecureStore from 'expo-secure-store';
2
+ import { StorageError } from '../errors';
3
+ import type { SolanaIntent } from '../intent';
4
+
5
+ const STORAGE_PREFIX = 'toss_intent_';
6
+
7
+ // Helper function to get all keys
8
+ async function getAllKeys(): Promise<string[]> {
9
+ // expo-secure-store doesn't have a direct way to get all keys,
10
+ // so we'll need to track them manually
11
+ const keys = await SecureStore.getItemAsync(`${STORAGE_PREFIX}_keys`);
12
+ return keys ? JSON.parse(keys) : [];
13
+ }
14
+
15
+ // Helper function to save all keys
16
+ async function saveKeys(keys: string[]): Promise<void> {
17
+ await SecureStore.setItemAsync(
18
+ `${STORAGE_PREFIX}_keys`,
19
+ JSON.stringify(keys)
20
+ );
21
+ }
22
+
23
+ export async function secureStoreIntent(intent: SolanaIntent): Promise<void> {
24
+ try {
25
+ const key = `${STORAGE_PREFIX}${intent.id}`;
26
+ await SecureStore.setItemAsync(key, JSON.stringify(intent));
27
+
28
+ // Update the keys list
29
+ const keys = await getAllKeys();
30
+ if (!keys.includes(key)) {
31
+ keys.push(key);
32
+ await saveKeys(keys);
33
+ }
34
+ } catch (error) {
35
+ throw new StorageError('Failed to store intent securely', {
36
+ cause: error,
37
+ intentId: intent.id,
38
+ });
39
+ }
40
+ }
41
+
42
+ export async function getSecureIntent(
43
+ intentId: string
44
+ ): Promise<SolanaIntent | null> {
45
+ try {
46
+ const value = await SecureStore.getItemAsync(
47
+ `${STORAGE_PREFIX}${intentId}`
48
+ );
49
+ return value ? JSON.parse(value) : null;
50
+ } catch (error) {
51
+ throw new StorageError('Failed to retrieve intent', {
52
+ cause: error,
53
+ intentId,
54
+ });
55
+ }
56
+ }
57
+
58
+ export async function getAllSecureIntents(): Promise<SolanaIntent[]> {
59
+ try {
60
+ const keys = await getAllKeys();
61
+ const intents = await Promise.all(
62
+ keys.map(async (key: string) => {
63
+ const value = await SecureStore.getItemAsync(key);
64
+ return value ? JSON.parse(value) : null;
65
+ })
66
+ );
67
+ return intents.filter(Boolean);
68
+ } catch (error) {
69
+ throw new StorageError('Failed to retrieve all intents', { cause: error });
70
+ }
71
+ }
72
+
73
+ export async function removeSecureIntent(intentId: string): Promise<void> {
74
+ try {
75
+ const key = `${STORAGE_PREFIX}${intentId}`;
76
+ await SecureStore.deleteItemAsync(key);
77
+
78
+ // Update the keys list
79
+ const keys = await getAllKeys();
80
+ const updatedKeys = keys.filter(k => k !== key);
81
+ await saveKeys(updatedKeys);
82
+ } catch (error) {
83
+ throw new StorageError('Failed to remove intent', {
84
+ cause: error,
85
+ intentId,
86
+ });
87
+ }
88
+ }
89
+
90
+ export async function clearAllSecureIntents(): Promise<void> {
91
+ try {
92
+ const keys = await getAllKeys();
93
+ await Promise.all(
94
+ keys.map((key: string) => SecureStore.deleteItemAsync(key))
95
+ );
96
+ await saveKeys([]);
97
+ } catch (error) {
98
+ throw new StorageError('Failed to clear all intents', { cause: error });
99
+ }
100
+ }
package/src/storage.ts ADDED
@@ -0,0 +1,17 @@
1
+ import AsyncStorage from "@react-native-async-storage/async-storage";
2
+
3
+ const INTENTS_KEY = "TOSS_PENDING_INTENTS";
4
+
5
+ export async function storePendingIntent(intent: any) {
6
+ const current = JSON.parse((await AsyncStorage.getItem(INTENTS_KEY)) || "[]");
7
+ current.push(intent);
8
+ await AsyncStorage.setItem(INTENTS_KEY, JSON.stringify(current));
9
+ }
10
+
11
+ export async function getPendingIntents() {
12
+ return JSON.parse((await AsyncStorage.getItem(INTENTS_KEY)) || "[]");
13
+ }
14
+
15
+ export async function clearPendingIntents() {
16
+ await AsyncStorage.removeItem(INTENTS_KEY);
17
+ }
package/src/sync.ts ADDED
@@ -0,0 +1,101 @@
1
+ /**
2
+ * Synchronisation with Solana Blockchain
3
+ *
4
+ * Implements Section 9 of the TOSS Technical Paper:
5
+ * Upon regaining connectivity, devices initiate reconciliation with onchain state.
6
+ * All offline artifacts are verified onchain and settled with deterministic outcomes.
7
+ */
8
+
9
+ import { Connection, PublicKey } from '@solana/web3.js';
10
+ import {
11
+ reconcilePendingIntents,
12
+ detectConflicts,
13
+ getReconciliationState,
14
+ type SettlementResult,
15
+ type ReconciliationState,
16
+ } from './reconciliation';
17
+ import { NetworkError } from './errors';
18
+
19
+ export interface SyncResult {
20
+ // Intents that were successfully settled onchain
21
+ successfulSettlements: SettlementResult[];
22
+ // Intents that were rejected or failed
23
+ failedSettlements: SettlementResult[];
24
+ // Conflicts detected during reconciliation
25
+ detectedConflicts: { intentId: string; conflict: string }[];
26
+ // Overall reconciliation state
27
+ reconciliationState: ReconciliationState;
28
+ // Timestamp of sync
29
+ syncTimestamp: number;
30
+ // Whether sync was successful (all intents processed)
31
+ isComplete: boolean;
32
+ }
33
+
34
+ /**
35
+ * Full sync and reconciliation with the Solana blockchain
36
+ *
37
+ * This is the primary function for TOSS settlement. When a device regains
38
+ * connectivity, it calls this to:
39
+ * 1. Detect any conflicts with onchain state
40
+ * 2. Settle all pending intents
41
+ * 3. Update local state with results
42
+ *
43
+ * @param connection Connection to Solana RPC
44
+ * @param feePayer Optional fee payer keypair public key
45
+ * @returns Detailed sync results including conflicts and settlements
46
+ */
47
+ export async function syncToChain(
48
+ connection: Connection,
49
+ feePayer?: PublicKey
50
+ ): Promise<SyncResult> {
51
+ const syncTimestamp = Math.floor(Date.now() / 1000);
52
+
53
+ try {
54
+ // Step 1: Detect any conflicts with onchain state
55
+ const detectedConflicts = await detectConflicts(connection);
56
+
57
+ // Step 2: Reconcile and settle all pending intents
58
+ const allSettlementResults = await reconcilePendingIntents(
59
+ connection,
60
+ feePayer
61
+ );
62
+
63
+ // Step 3: Separate successful and failed settlements
64
+ const successfulSettlements = allSettlementResults.filter(
65
+ (r) => r.status === 'success'
66
+ );
67
+ const failedSettlements = allSettlementResults.filter(
68
+ (r) => r.status !== 'success'
69
+ );
70
+
71
+ // Step 4: Get final reconciliation state
72
+ const reconciliationState = await getReconciliationState(connection);
73
+
74
+ const isComplete =
75
+ failedSettlements.length === 0 && detectedConflicts.length === 0;
76
+
77
+ return {
78
+ successfulSettlements,
79
+ failedSettlements,
80
+ detectedConflicts,
81
+ reconciliationState,
82
+ syncTimestamp,
83
+ isComplete,
84
+ };
85
+ } catch (error) {
86
+ throw new NetworkError(
87
+ `Sync to chain failed: ${error instanceof Error ? error.message : String(error)}`,
88
+ { cause: error }
89
+ );
90
+ }
91
+ }
92
+
93
+ /**
94
+ * Lightweight sync to check status without settling
95
+ * Useful for monitoring or UI updates without committing to settlements
96
+ */
97
+ export async function checkSyncStatus(
98
+ connection: Connection
99
+ ): Promise<ReconciliationState> {
100
+ return getReconciliationState(connection);
101
+ }
@@ -0,0 +1,81 @@
1
+ import { PublicKey } from '@solana/web3.js';
2
+
3
+ /**
4
+ * Represents a TOSS wallet user in the ecosystem
5
+ */
6
+ export type TossUser = {
7
+ // Core Identity
8
+ userId: string; // TOSS internal ID (e.g., 'toss_123')
9
+ username: string; // @handle format (e.g., '@alice')
10
+ displayName?: string; // Optional display name
11
+
12
+ // TOSS Wallet
13
+ wallet: {
14
+ publicKey: PublicKey; // Main wallet address
15
+ isVerified: boolean; // KYC/verification status
16
+ createdAt: string; // ISO timestamp
17
+ };
18
+
19
+ // Device & Session
20
+ device: {
21
+ id: string; // Unique device ID
22
+ name?: string; // User-defined device name
23
+ lastActive: string; // ISO timestamp
24
+ client: 'mobile' | 'web' | 'desktop';
25
+ };
26
+
27
+ // Status
28
+ status: 'active' | 'inactive' | 'restricted';
29
+ lastSeen: string; // ISO timestamp
30
+
31
+ // TOSS-specific
32
+ tossFeatures: {
33
+ canSend: boolean;
34
+ canReceive: boolean;
35
+ isPrivateTxEnabled: boolean;
36
+ maxTransactionAmount: number; // In lamports
37
+ };
38
+
39
+ // Timestamps
40
+ createdAt: string;
41
+ updatedAt: string;
42
+ };
43
+
44
+ /**
45
+ * Minimal user info for transaction context
46
+ */
47
+ export type TossUserContext = Pick<
48
+ TossUser,
49
+ 'userId' | 'username' | 'wallet' | 'status'
50
+ > & {
51
+ deviceId: string;
52
+ sessionId: string;
53
+ };
54
+
55
+ // Example usage
56
+ export const exampleTossUser: TossUser = {
57
+ userId: 'toss_abc123',
58
+ username: '@alice',
59
+ displayName: 'Alice',
60
+ wallet: {
61
+ publicKey: new PublicKey('11111111111111111111111111111111'), // Example key
62
+ isVerified: true,
63
+ createdAt: '2023-01-01T00:00:00Z',
64
+ },
65
+ device: {
66
+ id: 'dev_xyz789',
67
+ name: 'Alice iPhone',
68
+ lastActive: new Date().toISOString(),
69
+ client: 'mobile',
70
+ },
71
+ status: 'active',
72
+ lastSeen: new Date().toISOString(),
73
+ tossFeatures: {
74
+ canSend: true,
75
+ canReceive: true,
76
+ isPrivateTxEnabled: true,
77
+ maxTransactionAmount: 1000000000, // 1 SOL in lamports
78
+ },
79
+ createdAt: '2023-01-01T00:00:00Z',
80
+ updatedAt: new Date().toISOString(),
81
+ };
@@ -0,0 +1,56 @@
1
+ import {
2
+ SystemProgram,
3
+ PublicKey,
4
+ Transaction,
5
+ Connection,
6
+ Keypair,
7
+ LAMPORTS_PER_SOL,
8
+ } from '@solana/web3.js';
9
+
10
+ export async function createNonceAccount(
11
+ connection: Connection,
12
+ feePayer: Keypair,
13
+ nonceAccount: Keypair = Keypair.generate(),
14
+ amount = 1 * LAMPORTS_PER_SOL // 1 SOL should be enough for many transactions
15
+ ): Promise<{ nonceAccount: Keypair; nonceAuth: PublicKey }> {
16
+ const nonceAuth = feePayer.publicKey;
17
+ const tx = new Transaction().add(
18
+ SystemProgram.createAccount({
19
+ fromPubkey: feePayer.publicKey,
20
+ newAccountPubkey: nonceAccount.publicKey,
21
+ lamports: amount,
22
+ space: 80, // Size of nonce account
23
+ programId: SystemProgram.programId,
24
+ }),
25
+ SystemProgram.nonceInitialize({
26
+ noncePubkey: nonceAccount.publicKey,
27
+ authorizedPubkey: nonceAuth,
28
+ })
29
+ );
30
+
31
+ tx.recentBlockhash = (await connection.getLatestBlockhash()).blockhash;
32
+ tx.feePayer = feePayer.publicKey;
33
+ tx.sign(feePayer, nonceAccount);
34
+
35
+ await connection.sendRawTransaction(tx.serialize());
36
+ return { nonceAccount, nonceAuth };
37
+ }
38
+
39
+ export async function getNonce(
40
+ connection: Connection,
41
+ nonceAccount: PublicKey
42
+ ): Promise<string> {
43
+ const accountInfo = await connection.getAccountInfo(nonceAccount);
44
+ if (!accountInfo) throw new Error('Nonce account not found');
45
+ return accountInfo.data.slice(32, 64).toString('hex');
46
+ }
47
+
48
+ export function createNonceAdvanceInstruction(
49
+ noncePubkey: PublicKey,
50
+ authorizedPubkey: PublicKey
51
+ ) {
52
+ return SystemProgram.nonceAdvance({
53
+ noncePubkey,
54
+ authorizedPubkey,
55
+ });
56
+ }