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,164 @@
1
+ import { Transaction } from '@solana/web3.js';
2
+ import bs58 from 'bs58';
3
+ import type { SolanaIntent, IntentStatus } from './intent';
4
+
5
+ /**
6
+ * Verifies the signature of a Solana intent
7
+ * @param intent The intent to verify
8
+ * @returns boolean indicating if the signature is valid
9
+ */
10
+ export function verifyIntentSignature(intent: SolanaIntent): boolean {
11
+ try {
12
+ // Check if serialized transaction exists
13
+ if (!intent.serialized) {
14
+ return false;
15
+ }
16
+
17
+ // Reconstruct the transaction to verify signatures
18
+ const tx = Transaction.from(Buffer.from(bs58.decode(intent.serialized)));
19
+
20
+ // Verify all signatures in the transaction
21
+ return tx.verifySignatures();
22
+ } catch (error) {
23
+ console.error('Error verifying intent signature:', error);
24
+ return false;
25
+ }
26
+ }
27
+
28
+ /**
29
+ * Checks if an intent has expired
30
+ * @param intent The intent to check
31
+ * @returns boolean indicating if the intent has expired
32
+ */
33
+ export function isIntentExpired(intent: SolanaIntent): boolean {
34
+ return Date.now() / 1000 > intent.expiry;
35
+ }
36
+
37
+ /**
38
+ * Updates the status of an intent
39
+ * @param intent The intent to update
40
+ * @param status The new status
41
+ * @param error Optional error message if status is 'failed'
42
+ * @returns A new intent with updated status
43
+ */
44
+ export function updateIntentStatus(
45
+ intent: SolanaIntent,
46
+ status: IntentStatus,
47
+ error?: string
48
+ ): SolanaIntent {
49
+ return {
50
+ ...intent,
51
+ status,
52
+ updatedAt: Math.floor(Date.now() / 1000),
53
+ ...(error && { error }),
54
+ };
55
+ }
56
+
57
+ /**
58
+ * Validates an intent against current blockchain state
59
+ * Note: This is a placeholder - actual implementation would check against a Solana node
60
+ * @param intent The intent to validate
61
+ * @param currentBlockhash Current network blockhash
62
+ * @returns Validation result with success status and optional error message
63
+ */
64
+ export async function validateIntent(
65
+ intent: SolanaIntent,
66
+ currentBlockhash: string
67
+ ): Promise<{ valid: boolean; error?: string }> {
68
+ // Check if intent has expired
69
+ if (isIntentExpired(intent)) {
70
+ return {
71
+ valid: false,
72
+ error: 'Intent has expired',
73
+ };
74
+ }
75
+
76
+ // Verify signatures
77
+ if (!verifyIntentSignature(intent)) {
78
+ return {
79
+ valid: false,
80
+ error: 'Invalid transaction signature',
81
+ };
82
+ }
83
+
84
+ // Check if blockhash is still valid
85
+ // In a real implementation, we'd check against the Solana cluster
86
+ // This is a simplified check
87
+ if (intent.blockhash !== currentBlockhash) {
88
+ return {
89
+ valid: false,
90
+ error: 'Stale blockhash',
91
+ };
92
+ }
93
+
94
+ // Additional validation logic would go here
95
+ // - Check account balances
96
+ // - Verify program-specific logic
97
+ // - Check for double-spend attempts
98
+
99
+ return { valid: true };
100
+ }
101
+
102
+ /**
103
+ * Processes a batch of intents for synchronization
104
+ * @param intents Array of intents to process
105
+ * @param connection Connection to Solana network
106
+ * @returns Processed intents with updated statuses
107
+ */
108
+ export async function processIntentsForSync(
109
+ intents: SolanaIntent[],
110
+ connection: any // Should be Connection from @solana/web3.js in real implementation
111
+ ): Promise<SolanaIntent[]> {
112
+ const currentBlockhash = (await connection.getRecentBlockhash()).blockhash;
113
+
114
+ return Promise.all(
115
+ intents.map(async (intent) => {
116
+ // Skip already processed intents
117
+ if (intent.status !== 'pending') return intent;
118
+
119
+ const validation = await validateIntent(intent, currentBlockhash);
120
+
121
+ if (!validation.valid) {
122
+ return updateIntentStatus(intent, 'failed', validation.error);
123
+ }
124
+
125
+ try {
126
+ // In a real implementation, we would submit the transaction here
127
+ // const signature = await connection.sendRawTransaction(
128
+ // Buffer.from(bs58.decode(intent.serialized))
129
+ // );
130
+ // await connection.confirmTransaction(signature);
131
+
132
+ return updateIntentStatus(intent, 'settled');
133
+ } catch (error) {
134
+ return updateIntentStatus(
135
+ intent,
136
+ 'failed',
137
+ error instanceof Error ? error.message : 'Unknown error'
138
+ );
139
+ }
140
+ })
141
+ );
142
+ }
143
+
144
+ /**
145
+ * Filters out expired intents and updates their status
146
+ * @param intents Array of intents to check
147
+ * @returns Tuple of [validIntents, expiredIntents]
148
+ */
149
+ export function filterExpiredIntents(
150
+ intents: SolanaIntent[]
151
+ ): [SolanaIntent[], SolanaIntent[]] {
152
+ const valid: SolanaIntent[] = [];
153
+ const expired: SolanaIntent[] = [];
154
+
155
+ for (const intent of intents) {
156
+ if (isIntentExpired(intent)) {
157
+ expired.push(updateIntentStatus(intent, 'expired'));
158
+ } else {
159
+ valid.push(intent);
160
+ }
161
+ }
162
+
163
+ return [valid, expired];
164
+ }
@@ -0,0 +1,58 @@
1
+ // import {
2
+ // getArciumEnv,
3
+ // x25519,
4
+ // getMXEPublicKey,
5
+ // RescueCipher,
6
+ // } from "@arcium-hq/client";
7
+ import * as Arcium from '@arcium-hq/client';
8
+ import type { PublicKey } from '@solana/web3.js';
9
+
10
+ /**
11
+ * Output from Arcium encryption (internal only)
12
+ */
13
+ export type ArciumEncryptedOutput = {
14
+ ciphertext: number[][];
15
+ publicKey: Uint8Array;
16
+ nonce: Uint8Array;
17
+ };
18
+ /**
19
+ * Internal helper to encrypt a set of numeric values with Arcium.
20
+ * Does not leak anything about Arcium to the SDK consumer.
21
+ *
22
+ * @param mxeProgramId PublicKey of the MXE
23
+ * @param plaintextValues numeric values for encryption
24
+ * @param provider Solana provider (e.g., AnchorProvider)
25
+ */
26
+ export async function encryptForArciumInternal(
27
+ mxeProgramId: PublicKey,
28
+ plaintextValues: bigint[],
29
+ provider: any // AnchorProvider or similar
30
+ ): Promise<ArciumEncryptedOutput> {
31
+ // Required by the Arcium client before encryption
32
+ Arcium.getArciumEnv();
33
+
34
+ // 1) Generate a random x25519 keypair
35
+ const privateKey = Arcium.x25519.utils.randomSecretKey();
36
+ const publicKey = Arcium.x25519.getPublicKey(privateKey);
37
+
38
+ // 2) Fetch the MXE's public encryption key using the provided provider
39
+ const mxePubKey = await Arcium.getMXEPublicKey(provider, mxeProgramId);
40
+
41
+ if (!mxePubKey) {
42
+ throw new Error('MXE public key not found for Arcium encryption');
43
+ }
44
+
45
+ // 3) Derive DH shared secret
46
+ const sharedSecret = Arcium.x25519.getSharedSecret(privateKey, mxePubKey);
47
+
48
+ // 4) Build the cipher and encrypt the data
49
+ const cipher = new Arcium.RescueCipher(sharedSecret);
50
+ const nonce = crypto.getRandomValues(new Uint8Array(16));
51
+ const ciphertext = cipher.encrypt(plaintextValues, nonce);
52
+
53
+ return {
54
+ ciphertext,
55
+ publicKey,
56
+ nonce,
57
+ };
58
+ }
package/src/nfc.ts ADDED
@@ -0,0 +1,57 @@
1
+ // src/nfc.ts
2
+ import NfcManager, { NfcTech, Ndef } from "react-native-nfc-manager";
3
+ import type { TossUser } from './types/tossUser';
4
+ import type { SolanaIntent } from './intent';
5
+
6
+ // Start the manager
7
+ export function initNFC() {
8
+ return NfcManager.start();
9
+ }
10
+
11
+ // Read NFC tag containing a TossUser
12
+ export async function readNFCUser(): Promise<TossUser> {
13
+ try {
14
+ await NfcManager.requestTechnology(NfcTech.Ndef);
15
+ const tag = await NfcManager.getTag();
16
+ await NfcManager.cancelTechnologyRequest();
17
+
18
+ if (!tag?.ndefMessage?.[0]?.payload) {
19
+ throw new Error('No NDEF message found');
20
+ }
21
+
22
+ const message = Ndef.uri.decodePayload(tag.ndefMessage[0].payload as any);
23
+ return JSON.parse(message) as TossUser;
24
+ } catch (ex: unknown) {
25
+ await NfcManager.cancelTechnologyRequest();
26
+ throw new Error(`Failed to read user from NFC: ${String(ex)}`);
27
+ }
28
+ }
29
+
30
+ export async function writeUserToNFC(user: TossUser): Promise<boolean> {
31
+ try {
32
+ await NfcManager.requestTechnology(NfcTech.Ndef);
33
+ const jsonUser = JSON.stringify(user);
34
+ const bytes = Ndef.encodeMessage([Ndef.uriRecord(jsonUser)]);
35
+ await NfcManager.ndefHandler.writeNdefMessage(bytes);
36
+ await NfcManager.cancelTechnologyRequest();
37
+ return true;
38
+ } catch (ex: unknown) {
39
+ await NfcManager.cancelTechnologyRequest();
40
+ throw new Error(`Failed to write user to NFC: ${String(ex)}`);
41
+ }
42
+ }
43
+
44
+ // Write SolanaIntent to NFC tag
45
+ export async function writeIntentToNFC(intent: SolanaIntent): Promise<boolean> {
46
+ try {
47
+ await NfcManager.requestTechnology(NfcTech.Ndef);
48
+ const jsonIntent = JSON.stringify(intent);
49
+ const bytes = Ndef.encodeMessage([Ndef.uriRecord(jsonIntent)]);
50
+ await NfcManager.ndefHandler.writeNdefMessage(bytes);
51
+ await NfcManager.cancelTechnologyRequest();
52
+ return true;
53
+ } catch (ex: unknown) {
54
+ await NfcManager.cancelTechnologyRequest();
55
+ throw new Error(`Failed to write intent to NFC: ${String(ex)}`);
56
+ }
57
+ }
package/src/noise.ts ADDED
@@ -0,0 +1,9 @@
1
+ import { noise } from "@chainsafe/libp2p-noise";
2
+
3
+ /**
4
+ * Initialize Noise secure session with a static key.
5
+ */
6
+ export function initNoiseSession(staticKey: Uint8Array) {
7
+ const ns = noise({ staticNoiseKey: staticKey });
8
+ return ns;
9
+ }
package/src/qr.tsx ADDED
@@ -0,0 +1,65 @@
1
+ import { View, StyleSheet, Text } from 'react-native';
2
+ import {
3
+ Camera,
4
+ useCameraDevice,
5
+ useCameraPermission,
6
+ useCodeScanner,
7
+ type Code,
8
+ } from 'react-native-vision-camera';
9
+
10
+ type QRScannerProps = {
11
+ onScan: (data: string) => void;
12
+ };
13
+
14
+ export function QRScanner({ onScan }: QRScannerProps) {
15
+ const device = useCameraDevice('back');
16
+ const permission = useCameraPermission();
17
+
18
+ const codeScanner = useCodeScanner({
19
+ codeTypes: ['qr'], // ✅ correct CodeType
20
+ onCodeScanned: (codes: Code[]) => {
21
+ const code = codes[0];
22
+ if (!code?.value) return; // ✅ undefined-safe
23
+
24
+ onScan(code.value);
25
+ },
26
+ });
27
+
28
+ if (!permission.hasPermission) {
29
+ return (
30
+ <View style={styles.center}>
31
+ <Text>Camera permission not granted</Text>
32
+ </View>
33
+ );
34
+ }
35
+
36
+ if (!device) {
37
+ return (
38
+ <View style={styles.center}>
39
+ <Text>Camera not available</Text>
40
+ </View>
41
+ );
42
+ }
43
+
44
+ return (
45
+ <View style={styles.container}>
46
+ <Camera
47
+ style={StyleSheet.absoluteFill}
48
+ device={device}
49
+ isActive
50
+ codeScanner={codeScanner}
51
+ />
52
+ </View>
53
+ );
54
+ }
55
+
56
+ const styles = StyleSheet.create({
57
+ container: {
58
+ flex: 1,
59
+ },
60
+ center: {
61
+ flex: 1,
62
+ alignItems: 'center',
63
+ justifyContent: 'center',
64
+ },
65
+ });