toss-expo-sdk 1.0.1 → 1.0.4
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 +68 -65
- package/lib/module/client/NonceAccountManager.js +2 -2
- package/lib/module/client/NonceAccountManager.js.map +1 -1
- package/lib/module/examples/enhancedFeaturesFlow.js +233 -0
- package/lib/module/examples/enhancedFeaturesFlow.js.map +1 -0
- package/lib/module/examples/offlinePaymentFlow.js +27 -27
- package/lib/module/examples/offlinePaymentFlow.js.map +1 -1
- package/lib/module/index.js +13 -1
- package/lib/module/index.js.map +1 -1
- package/lib/module/qr.js +2 -2
- package/lib/module/reconciliation.js +4 -4
- package/lib/module/reconciliation.js.map +1 -1
- package/lib/module/services/authService.js +3 -3
- package/lib/module/services/authService.js.map +1 -1
- package/lib/module/utils/compression.js +210 -0
- package/lib/module/utils/compression.js.map +1 -0
- package/lib/module/wifi.js +311 -0
- package/lib/module/wifi.js.map +1 -0
- package/lib/typescript/src/examples/enhancedFeaturesFlow.d.ts +45 -0
- package/lib/typescript/src/examples/enhancedFeaturesFlow.d.ts.map +1 -0
- package/lib/typescript/src/index.d.ts +7 -1
- package/lib/typescript/src/index.d.ts.map +1 -1
- package/lib/typescript/src/utils/compression.d.ts +52 -0
- package/lib/typescript/src/utils/compression.d.ts.map +1 -0
- package/lib/typescript/src/wifi.d.ts +116 -0
- package/lib/typescript/src/wifi.d.ts.map +1 -0
- package/package.json +1 -1
- package/src/client/NonceAccountManager.ts +2 -2
- package/src/examples/enhancedFeaturesFlow.ts +272 -0
- package/src/examples/offlinePaymentFlow.ts +27 -27
- package/src/index.tsx +26 -1
- package/src/qr.tsx +2 -2
- package/src/reconciliation.ts +4 -4
- package/src/services/authService.ts +3 -3
- package/src/utils/compression.ts +247 -0
- package/src/wifi.ts +401 -0
package/src/index.tsx
CHANGED
|
@@ -45,6 +45,7 @@ export {
|
|
|
45
45
|
processIntentsForSync,
|
|
46
46
|
filterExpiredIntents,
|
|
47
47
|
} from './intentManager';
|
|
48
|
+
export { verifyIntent } from './intent';
|
|
48
49
|
|
|
49
50
|
// Storage
|
|
50
51
|
export {
|
|
@@ -52,6 +53,15 @@ export {
|
|
|
52
53
|
getPendingIntents,
|
|
53
54
|
clearPendingIntents,
|
|
54
55
|
} from './storage';
|
|
56
|
+
// Secure storage (hardware-backed)
|
|
57
|
+
export {
|
|
58
|
+
secureStoreIntent,
|
|
59
|
+
getSecureIntent,
|
|
60
|
+
getAllSecureIntents,
|
|
61
|
+
removeSecureIntent,
|
|
62
|
+
clearAllSecureIntents,
|
|
63
|
+
cleanupExpiredIntents,
|
|
64
|
+
} from './storage/secureStorage';
|
|
55
65
|
|
|
56
66
|
// Transport methods (enhanced with fragmentation)
|
|
57
67
|
export {
|
|
@@ -66,7 +76,10 @@ export { initNFC, readNFCUser, writeUserToNFC, writeIntentToNFC } from './nfc';
|
|
|
66
76
|
export { QRScanner } from './qr';
|
|
67
77
|
|
|
68
78
|
// Client
|
|
69
|
-
|
|
79
|
+
import { TossClient, type TossConfig } from './client/TossClient';
|
|
80
|
+
export { TossClient, type TossConfig };
|
|
81
|
+
// Convenience helper for createClient (matches README)
|
|
82
|
+
export const createClient = TossClient.createClient;
|
|
70
83
|
export type { TossUser } from './types/tossUser';
|
|
71
84
|
export { WalletProvider, useWallet } from './contexts/WalletContext';
|
|
72
85
|
|
|
@@ -101,3 +114,15 @@ export {
|
|
|
101
114
|
type IntentExchangeRequest,
|
|
102
115
|
type IntentExchangeResponse,
|
|
103
116
|
} from './discovery';
|
|
117
|
+
|
|
118
|
+
// Compression utilities
|
|
119
|
+
export {
|
|
120
|
+
compressMetadata,
|
|
121
|
+
decompressMetadata,
|
|
122
|
+
compressIntentMetadata,
|
|
123
|
+
decompressIntentMetadata,
|
|
124
|
+
estimateCompressionSavings,
|
|
125
|
+
} from './utils/compression';
|
|
126
|
+
|
|
127
|
+
// WiFi Direct transport
|
|
128
|
+
export { WiFiDirectTransport, SmartTransportSelector } from './wifi';
|
package/src/qr.tsx
CHANGED
|
@@ -16,10 +16,10 @@ export function QRScanner({ onScan }: QRScannerProps) {
|
|
|
16
16
|
const permission = useCameraPermission();
|
|
17
17
|
|
|
18
18
|
const codeScanner = useCodeScanner({
|
|
19
|
-
codeTypes: ['qr'], //
|
|
19
|
+
codeTypes: ['qr'], // correct CodeType
|
|
20
20
|
onCodeScanned: (codes: Code[]) => {
|
|
21
21
|
const code = codes[0];
|
|
22
|
-
if (!code?.value) return; //
|
|
22
|
+
if (!code?.value) return; // undefined-safe
|
|
23
23
|
|
|
24
24
|
onScan(code.value);
|
|
25
25
|
},
|
package/src/reconciliation.ts
CHANGED
|
@@ -343,7 +343,7 @@ export async function submitTransactionToArciumMXE(
|
|
|
343
343
|
provider
|
|
344
344
|
);
|
|
345
345
|
|
|
346
|
-
msg?.('
|
|
346
|
+
msg?.(' Intent parameters encrypted with Arcium MXE');
|
|
347
347
|
|
|
348
348
|
// PRODUCTION: Build MXE submission instruction
|
|
349
349
|
// Per TOSS Paper Section 7: "Arcium operates strictly before onchain execution"
|
|
@@ -371,7 +371,7 @@ export async function submitTransactionToArciumMXE(
|
|
|
371
371
|
]);
|
|
372
372
|
|
|
373
373
|
msg?.(
|
|
374
|
-
'
|
|
374
|
+
' Encrypted data prepared for MXE program (size: ' +
|
|
375
375
|
encryptedDataBuffer.length +
|
|
376
376
|
' bytes)'
|
|
377
377
|
);
|
|
@@ -393,7 +393,7 @@ export async function submitTransactionToArciumMXE(
|
|
|
393
393
|
};
|
|
394
394
|
|
|
395
395
|
msg?.(
|
|
396
|
-
'
|
|
396
|
+
' Submitting encrypted intent to MXE program for confidential execution'
|
|
397
397
|
);
|
|
398
398
|
|
|
399
399
|
// PRODUCTION: Build transaction with MXE instruction
|
|
@@ -421,7 +421,7 @@ export async function submitTransactionToArciumMXE(
|
|
|
421
421
|
maxRetries
|
|
422
422
|
);
|
|
423
423
|
|
|
424
|
-
msg?.('
|
|
424
|
+
msg?.(' MXE transaction submitted - encrypted execution in progress');
|
|
425
425
|
msg?.(' Signature: ' + mxeSignature);
|
|
426
426
|
msg?.(' Intent details remain confidential until settlement');
|
|
427
427
|
|
|
@@ -166,7 +166,7 @@ export class AuthService {
|
|
|
166
166
|
): Promise<void> {
|
|
167
167
|
if (!useBiometrics) {
|
|
168
168
|
throw new Error(
|
|
169
|
-
'
|
|
169
|
+
' SECURITY ERROR: Biometric protection is mandatory for wallet security'
|
|
170
170
|
);
|
|
171
171
|
}
|
|
172
172
|
|
|
@@ -176,7 +176,7 @@ export class AuthService {
|
|
|
176
176
|
|
|
177
177
|
if (!hasHardware || !isEnrolled) {
|
|
178
178
|
throw new Error(
|
|
179
|
-
'
|
|
179
|
+
' Biometric authentication required but not configured on device'
|
|
180
180
|
);
|
|
181
181
|
}
|
|
182
182
|
|
|
@@ -264,7 +264,7 @@ export class AuthService {
|
|
|
264
264
|
|
|
265
265
|
if (!hasHardware || !isEnrolled) {
|
|
266
266
|
throw new Error(
|
|
267
|
-
'
|
|
267
|
+
' Biometric authentication required but not configured on this device'
|
|
268
268
|
);
|
|
269
269
|
}
|
|
270
270
|
|
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Compression Utilities for TOSS
|
|
3
|
+
*
|
|
4
|
+
* Implements safe, deterministic compression for metadata only.
|
|
5
|
+
* Transaction bytes are NEVER compressed to preserve determinism.
|
|
6
|
+
*
|
|
7
|
+
* Production-ready with official APIs.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { Buffer } from 'buffer';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Simple DEFLATE-based compression using native zlib
|
|
14
|
+
* (Available in Node.js and via polyfills in React Native)
|
|
15
|
+
*
|
|
16
|
+
* For metadata compression: memos, recipient names, etc.
|
|
17
|
+
* NOT for transaction signatures or amounts!
|
|
18
|
+
*/
|
|
19
|
+
export interface CompressionResult {
|
|
20
|
+
compressed: boolean;
|
|
21
|
+
data: Uint8Array;
|
|
22
|
+
originalSize: number;
|
|
23
|
+
compressedSize: number;
|
|
24
|
+
compressionRatio: number;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Compress metadata safely
|
|
29
|
+
* Only compresses if compression saves >10% and data >200 bytes
|
|
30
|
+
*
|
|
31
|
+
* Safe for: memo text, user names, descriptions
|
|
32
|
+
* UNSAFE for: signatures, amounts, transaction data
|
|
33
|
+
*/
|
|
34
|
+
export async function compressMetadata(
|
|
35
|
+
data: string
|
|
36
|
+
): Promise<CompressionResult> {
|
|
37
|
+
try {
|
|
38
|
+
const originalBuffer = Buffer.from(data, 'utf-8');
|
|
39
|
+
const originalSize = originalBuffer.length;
|
|
40
|
+
|
|
41
|
+
// Only compress if it's worth it
|
|
42
|
+
if (originalSize < 200) {
|
|
43
|
+
return {
|
|
44
|
+
compressed: false,
|
|
45
|
+
data: new Uint8Array(originalBuffer),
|
|
46
|
+
originalSize,
|
|
47
|
+
compressedSize: originalSize,
|
|
48
|
+
compressionRatio: 1.0,
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Use native compression if available (Node.js)
|
|
53
|
+
// For React Native, this will use a polyfill
|
|
54
|
+
const zlib = await importZlib();
|
|
55
|
+
|
|
56
|
+
const compressed = await new Promise<Buffer>((resolve, reject) => {
|
|
57
|
+
zlib.deflate(originalBuffer, (err: any, result: Buffer) => {
|
|
58
|
+
if (err) reject(err);
|
|
59
|
+
else resolve(result);
|
|
60
|
+
});
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
const compressedSize = compressed.length;
|
|
64
|
+
const compressionRatio = compressedSize / originalSize;
|
|
65
|
+
|
|
66
|
+
// Only use compression if it saves >10%
|
|
67
|
+
if (compressionRatio > 0.9) {
|
|
68
|
+
return {
|
|
69
|
+
compressed: false,
|
|
70
|
+
data: new Uint8Array(originalBuffer),
|
|
71
|
+
originalSize,
|
|
72
|
+
compressedSize: originalSize,
|
|
73
|
+
compressionRatio: 1.0,
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
return {
|
|
78
|
+
compressed: true,
|
|
79
|
+
data: new Uint8Array(compressed),
|
|
80
|
+
originalSize,
|
|
81
|
+
compressedSize,
|
|
82
|
+
compressionRatio,
|
|
83
|
+
};
|
|
84
|
+
} catch (error) {
|
|
85
|
+
// Graceful fallback: if compression fails, return uncompressed
|
|
86
|
+
console.warn('Compression failed, returning uncompressed:', error);
|
|
87
|
+
const buffer = Buffer.from(data, 'utf-8');
|
|
88
|
+
return {
|
|
89
|
+
compressed: false,
|
|
90
|
+
data: new Uint8Array(buffer),
|
|
91
|
+
originalSize: buffer.length,
|
|
92
|
+
compressedSize: buffer.length,
|
|
93
|
+
compressionRatio: 1.0,
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Decompress metadata
|
|
100
|
+
*/
|
|
101
|
+
export async function decompressMetadata(data: Uint8Array): Promise<string> {
|
|
102
|
+
try {
|
|
103
|
+
const zlib = await importZlib();
|
|
104
|
+
|
|
105
|
+
const decompressed = await new Promise<Buffer>((resolve, reject) => {
|
|
106
|
+
zlib.inflate(Buffer.from(data), (err: any, result: Buffer) => {
|
|
107
|
+
if (err) reject(err);
|
|
108
|
+
else resolve(result);
|
|
109
|
+
});
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
return decompressed.toString('utf-8');
|
|
113
|
+
} catch (error) {
|
|
114
|
+
console.warn('Decompression failed:', error);
|
|
115
|
+
// Assume data was not compressed
|
|
116
|
+
return Buffer.from(data).toString('utf-8');
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Compress intent metadata for efficient transmission
|
|
122
|
+
* Returns the original intent with compressed metadata
|
|
123
|
+
*/
|
|
124
|
+
export async function compressIntentMetadata(
|
|
125
|
+
intentMetadata: Record<string, any>
|
|
126
|
+
): Promise<{
|
|
127
|
+
original: Record<string, any>;
|
|
128
|
+
compressed: Record<string, any>;
|
|
129
|
+
savings: number;
|
|
130
|
+
}> {
|
|
131
|
+
const compressed: Record<string, any> = {};
|
|
132
|
+
let totalOriginal = 0;
|
|
133
|
+
let totalCompressed = 0;
|
|
134
|
+
|
|
135
|
+
for (const [key, value] of Object.entries(intentMetadata)) {
|
|
136
|
+
if (typeof value === 'string') {
|
|
137
|
+
const result = await compressMetadata(value);
|
|
138
|
+
totalOriginal += result.originalSize;
|
|
139
|
+
totalCompressed += result.compressedSize;
|
|
140
|
+
|
|
141
|
+
if (result.compressed) {
|
|
142
|
+
compressed[key] = {
|
|
143
|
+
__compressed: true,
|
|
144
|
+
data: Array.from(result.data),
|
|
145
|
+
};
|
|
146
|
+
} else {
|
|
147
|
+
compressed[key] = value;
|
|
148
|
+
}
|
|
149
|
+
} else {
|
|
150
|
+
compressed[key] = value;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
const savings =
|
|
155
|
+
totalOriginal > 0 ? ((totalOriginal - totalCompressed) / totalOriginal) * 100 : 0;
|
|
156
|
+
|
|
157
|
+
return {
|
|
158
|
+
original: intentMetadata,
|
|
159
|
+
compressed,
|
|
160
|
+
savings: Math.round(savings),
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Decompress intent metadata
|
|
166
|
+
*/
|
|
167
|
+
export async function decompressIntentMetadata(
|
|
168
|
+
compressedMetadata: Record<string, any>
|
|
169
|
+
): Promise<Record<string, any>> {
|
|
170
|
+
const decompressed: Record<string, any> = {};
|
|
171
|
+
|
|
172
|
+
for (const [key, value] of Object.entries(compressedMetadata)) {
|
|
173
|
+
if (
|
|
174
|
+
typeof value === 'object' &&
|
|
175
|
+
value !== null &&
|
|
176
|
+
value.__compressed === true
|
|
177
|
+
) {
|
|
178
|
+
const buffer = new Uint8Array(value.data);
|
|
179
|
+
decompressed[key] = await decompressMetadata(buffer);
|
|
180
|
+
} else {
|
|
181
|
+
decompressed[key] = value;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
return decompressed;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Lazy-load zlib to avoid breaking in environments without it
|
|
190
|
+
*/
|
|
191
|
+
async function importZlib(): Promise<any> {
|
|
192
|
+
// In Node.js, use native zlib
|
|
193
|
+
if (typeof require !== 'undefined') {
|
|
194
|
+
try {
|
|
195
|
+
return require('zlib');
|
|
196
|
+
} catch {
|
|
197
|
+
// Fall through to polyfill
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// For React Native, try to use a polyfill
|
|
202
|
+
// This can be pako (pako-zlib) or similar
|
|
203
|
+
try {
|
|
204
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
205
|
+
const pako = await import('pako' as string) as any;
|
|
206
|
+
return {
|
|
207
|
+
deflate: (data: Buffer, callback: any) => {
|
|
208
|
+
try {
|
|
209
|
+
const compressed = pako.deflate(data);
|
|
210
|
+
callback(null, Buffer.from(compressed));
|
|
211
|
+
} catch (err) {
|
|
212
|
+
callback(err);
|
|
213
|
+
}
|
|
214
|
+
},
|
|
215
|
+
inflate: (data: Buffer, callback: any) => {
|
|
216
|
+
try {
|
|
217
|
+
const decompressed = pako.inflate(data);
|
|
218
|
+
callback(null, Buffer.from(decompressed));
|
|
219
|
+
} catch (err) {
|
|
220
|
+
callback(err);
|
|
221
|
+
}
|
|
222
|
+
},
|
|
223
|
+
};
|
|
224
|
+
} catch {
|
|
225
|
+
throw new Error(
|
|
226
|
+
'Compression not available in this environment. Install pako or use Node.js.'
|
|
227
|
+
);
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* Calculate size savings for a metadata object
|
|
233
|
+
*/
|
|
234
|
+
export function estimateCompressionSavings(
|
|
235
|
+
metadata: Record<string, any>
|
|
236
|
+
): number {
|
|
237
|
+
let totalSize = 0;
|
|
238
|
+
|
|
239
|
+
for (const value of Object.values(metadata)) {
|
|
240
|
+
if (typeof value === 'string') {
|
|
241
|
+
totalSize += value.length;
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
// Conservative estimate: ~35% savings on text (typical for DEFLATE)
|
|
246
|
+
return Math.round(totalSize * 0.35);
|
|
247
|
+
}
|