toss-expo-sdk 0.1.2 → 1.0.1
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 +368 -15
- package/lib/module/ble.js +59 -4
- package/lib/module/ble.js.map +1 -1
- package/lib/module/client/BLETransactionHandler.js +277 -0
- package/lib/module/client/BLETransactionHandler.js.map +1 -0
- package/lib/module/client/NonceAccountManager.js +364 -0
- package/lib/module/client/NonceAccountManager.js.map +1 -0
- package/lib/module/client/TossClient.js +1 -1
- package/lib/module/client/TossClient.js.map +1 -1
- package/lib/module/hooks/useOfflineBLETransactions.js +314 -0
- package/lib/module/hooks/useOfflineBLETransactions.js.map +1 -0
- package/lib/module/index.js +12 -8
- package/lib/module/index.js.map +1 -1
- package/lib/module/intent.js +129 -0
- package/lib/module/intent.js.map +1 -1
- package/lib/module/noise.js +175 -0
- package/lib/module/noise.js.map +1 -1
- package/lib/module/reconciliation.js +155 -0
- package/lib/module/reconciliation.js.map +1 -1
- package/lib/module/services/authService.js +164 -1
- package/lib/module/services/authService.js.map +1 -1
- package/lib/module/storage/secureStorage.js +102 -0
- package/lib/module/storage/secureStorage.js.map +1 -1
- package/lib/module/sync.js +25 -1
- package/lib/module/sync.js.map +1 -1
- package/lib/module/types/nonceAccount.js +2 -0
- package/lib/module/types/nonceAccount.js.map +1 -0
- package/lib/module/types/tossUser.js +16 -1
- package/lib/module/types/tossUser.js.map +1 -1
- package/lib/typescript/src/__tests__/solana-program-simple.test.d.ts +8 -0
- package/lib/typescript/src/__tests__/solana-program-simple.test.d.ts.map +1 -0
- package/lib/typescript/src/ble.d.ts +31 -2
- package/lib/typescript/src/ble.d.ts.map +1 -1
- package/lib/typescript/src/client/BLETransactionHandler.d.ts +98 -0
- package/lib/typescript/src/client/BLETransactionHandler.d.ts.map +1 -0
- package/lib/typescript/src/client/NonceAccountManager.d.ts +82 -0
- package/lib/typescript/src/client/NonceAccountManager.d.ts.map +1 -0
- package/lib/typescript/src/hooks/useOfflineBLETransactions.d.ts +91 -0
- package/lib/typescript/src/hooks/useOfflineBLETransactions.d.ts.map +1 -0
- package/lib/typescript/src/index.d.ts +9 -4
- package/lib/typescript/src/index.d.ts.map +1 -1
- package/lib/typescript/src/intent.d.ts +15 -0
- package/lib/typescript/src/intent.d.ts.map +1 -1
- package/lib/typescript/src/noise.d.ts +62 -0
- package/lib/typescript/src/noise.d.ts.map +1 -1
- package/lib/typescript/src/reconciliation.d.ts +6 -0
- package/lib/typescript/src/reconciliation.d.ts.map +1 -1
- package/lib/typescript/src/services/authService.d.ts +26 -1
- package/lib/typescript/src/services/authService.d.ts.map +1 -1
- package/lib/typescript/src/storage/secureStorage.d.ts +16 -0
- package/lib/typescript/src/storage/secureStorage.d.ts.map +1 -1
- package/lib/typescript/src/sync.d.ts +6 -1
- package/lib/typescript/src/sync.d.ts.map +1 -1
- package/lib/typescript/src/types/nonceAccount.d.ts +59 -0
- package/lib/typescript/src/types/nonceAccount.d.ts.map +1 -0
- package/lib/typescript/src/types/tossUser.d.ts +16 -0
- package/lib/typescript/src/types/tossUser.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/__tests__/solana-program-simple.test.ts +256 -0
- package/src/ble.ts +105 -4
- package/src/client/BLETransactionHandler.ts +364 -0
- package/src/client/NonceAccountManager.ts +444 -0
- package/src/client/TossClient.ts +1 -1
- package/src/hooks/useOfflineBLETransactions.ts +438 -0
- package/src/index.tsx +40 -6
- package/src/intent.ts +166 -0
- package/src/noise.ts +238 -0
- package/src/reconciliation.ts +184 -0
- package/src/services/authService.ts +188 -1
- package/src/storage/secureStorage.ts +138 -0
- package/src/sync.ts +40 -0
- package/src/types/nonceAccount.ts +75 -0
- package/src/types/tossUser.ts +35 -2
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TOSS Solana Program Integration Tests (Simplified)
|
|
3
|
+
*
|
|
4
|
+
* Tests for the toss-intent-processor program
|
|
5
|
+
* Gap #5: Onchain Intent Verification
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { Connection, Keypair } from '@solana/web3.js';
|
|
9
|
+
import { createIntent, verifyIntent } from '../intent';
|
|
10
|
+
import { NonceAccountManager } from '../client/NonceAccountManager';
|
|
11
|
+
|
|
12
|
+
describe('TOSS Solana Intent Processor Program', () => {
|
|
13
|
+
let connection: Connection;
|
|
14
|
+
let senderKeypair: Keypair;
|
|
15
|
+
let recipientKeypair: Keypair;
|
|
16
|
+
let nonceManager: NonceAccountManager;
|
|
17
|
+
|
|
18
|
+
beforeAll(() => {
|
|
19
|
+
// Use Devnet for testing
|
|
20
|
+
connection = new Connection('https://api.devnet.solana.com', 'confirmed');
|
|
21
|
+
senderKeypair = Keypair.generate();
|
|
22
|
+
recipientKeypair = Keypair.generate();
|
|
23
|
+
nonceManager = new NonceAccountManager(connection);
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
describe('Intent Signature Verification', () => {
|
|
27
|
+
it('should create a valid, verifiable intent', async () => {
|
|
28
|
+
// Create intent
|
|
29
|
+
const intent = await createIntent(
|
|
30
|
+
senderKeypair,
|
|
31
|
+
recipientKeypair.publicKey,
|
|
32
|
+
1000000,
|
|
33
|
+
connection,
|
|
34
|
+
{ expiresIn: 60 * 60 }
|
|
35
|
+
);
|
|
36
|
+
|
|
37
|
+
// Verify the intent can be verified
|
|
38
|
+
expect(intent.signature).toBeDefined();
|
|
39
|
+
expect(intent.signature.length).toBeGreaterThan(0);
|
|
40
|
+
|
|
41
|
+
// Verify intent signature locally (client-side)
|
|
42
|
+
const isValid = await verifyIntent(intent, connection);
|
|
43
|
+
expect(isValid).toBe(true);
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it('should reject modified intent', async () => {
|
|
47
|
+
const intent = await createIntent(
|
|
48
|
+
senderKeypair,
|
|
49
|
+
recipientKeypair.publicKey,
|
|
50
|
+
1000000,
|
|
51
|
+
connection
|
|
52
|
+
);
|
|
53
|
+
|
|
54
|
+
// Modify the intent (would fail signature check)
|
|
55
|
+
const modifiedIntent = { ...intent, amount: 2000000 };
|
|
56
|
+
|
|
57
|
+
// Modified intent should fail verification
|
|
58
|
+
const isValid = await verifyIntent(modifiedIntent, connection);
|
|
59
|
+
expect(isValid).toBe(false);
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it('should reject expired intent', async () => {
|
|
63
|
+
// Create an intent that's already expired
|
|
64
|
+
const intent = await createIntent(
|
|
65
|
+
senderKeypair,
|
|
66
|
+
recipientKeypair.publicKey,
|
|
67
|
+
1000000,
|
|
68
|
+
connection,
|
|
69
|
+
{ expiresIn: -100 } // Already expired
|
|
70
|
+
);
|
|
71
|
+
|
|
72
|
+
// Expired intent should fail
|
|
73
|
+
const isValid = await verifyIntent(intent, connection);
|
|
74
|
+
expect(isValid).toBe(false);
|
|
75
|
+
});
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
describe('Intent Program Data Structure', () => {
|
|
79
|
+
it('should verify intent data structure matches program expectations', async () => {
|
|
80
|
+
const intent = await createIntent(
|
|
81
|
+
senderKeypair,
|
|
82
|
+
recipientKeypair.publicKey,
|
|
83
|
+
5000000,
|
|
84
|
+
connection
|
|
85
|
+
);
|
|
86
|
+
|
|
87
|
+
// Verify all required fields for onchain program
|
|
88
|
+
expect(intent.from).toBeDefined();
|
|
89
|
+
expect(intent.to).toBeDefined();
|
|
90
|
+
expect(intent.amount).toBeGreaterThan(0);
|
|
91
|
+
expect(intent.nonce).toBeGreaterThanOrEqual(0);
|
|
92
|
+
expect(intent.expiry).toBeGreaterThan(Date.now() / 1000);
|
|
93
|
+
expect(intent.signature).toBeDefined();
|
|
94
|
+
expect(intent.blockhash).toBeDefined();
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
it('should enforce expiry constraints', async () => {
|
|
98
|
+
const now = Math.floor(Date.now() / 1000);
|
|
99
|
+
const intent = await createIntent(
|
|
100
|
+
senderKeypair,
|
|
101
|
+
recipientKeypair.publicKey,
|
|
102
|
+
1000000,
|
|
103
|
+
connection,
|
|
104
|
+
{ expiresIn: 3600 } // 1 hour
|
|
105
|
+
);
|
|
106
|
+
|
|
107
|
+
// Expiry should be in future
|
|
108
|
+
expect(intent.expiry).toBeGreaterThan(now);
|
|
109
|
+
expect(intent.expiry - now).toBeCloseTo(3600, -1);
|
|
110
|
+
});
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
describe('Deterministic Settlement', () => {
|
|
114
|
+
it('should handle settlement with proper sequencing', async () => {
|
|
115
|
+
// Create multiple intents
|
|
116
|
+
const intent1 = await createIntent(
|
|
117
|
+
senderKeypair,
|
|
118
|
+
recipientKeypair.publicKey,
|
|
119
|
+
1000000,
|
|
120
|
+
connection
|
|
121
|
+
);
|
|
122
|
+
|
|
123
|
+
const intent2 = await createIntent(
|
|
124
|
+
senderKeypair,
|
|
125
|
+
recipientKeypair.publicKey,
|
|
126
|
+
2000000,
|
|
127
|
+
connection
|
|
128
|
+
);
|
|
129
|
+
|
|
130
|
+
// Both should be valid
|
|
131
|
+
const isValid1 = await verifyIntent(intent1, connection);
|
|
132
|
+
const isValid2 = await verifyIntent(intent2, connection);
|
|
133
|
+
|
|
134
|
+
expect(isValid1).toBe(true);
|
|
135
|
+
expect(isValid2).toBe(true);
|
|
136
|
+
|
|
137
|
+
// Different nonces ensure ordering
|
|
138
|
+
expect(intent1.nonce).not.toBe(intent2.nonce);
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
it('should reject duplicate nonce', async () => {
|
|
142
|
+
const intent = await createIntent(
|
|
143
|
+
senderKeypair,
|
|
144
|
+
recipientKeypair.publicKey,
|
|
145
|
+
1000000,
|
|
146
|
+
connection
|
|
147
|
+
);
|
|
148
|
+
|
|
149
|
+
// Try to create another intent with same nonce (would fail in practice)
|
|
150
|
+
const intentDupe = { ...intent };
|
|
151
|
+
|
|
152
|
+
// Both have same nonce - would be rejected onchain
|
|
153
|
+
expect(intent.nonce).toBe(intentDupe.nonce);
|
|
154
|
+
});
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
describe('Program Constraints', () => {
|
|
158
|
+
it('should validate amount is positive', async () => {
|
|
159
|
+
const intent = await createIntent(
|
|
160
|
+
senderKeypair,
|
|
161
|
+
recipientKeypair.publicKey,
|
|
162
|
+
1000000,
|
|
163
|
+
connection
|
|
164
|
+
);
|
|
165
|
+
|
|
166
|
+
// Intent amount should be positive
|
|
167
|
+
expect(intent.amount).toBeGreaterThan(0);
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
it('should allow large amounts within u64 bounds', async () => {
|
|
171
|
+
const largeAmount = Math.floor(Number.MAX_SAFE_INTEGER / 2);
|
|
172
|
+
const intent = await createIntent(
|
|
173
|
+
senderKeypair,
|
|
174
|
+
recipientKeypair.publicKey,
|
|
175
|
+
largeAmount,
|
|
176
|
+
connection
|
|
177
|
+
);
|
|
178
|
+
|
|
179
|
+
expect(intent.amount).toBeLessThanOrEqual(Number.MAX_SAFE_INTEGER);
|
|
180
|
+
});
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
describe('Nonce Account Integration', () => {
|
|
184
|
+
it('should create nonce account for replay protection', async () => {
|
|
185
|
+
const nonceAuthority = Keypair.generate();
|
|
186
|
+
|
|
187
|
+
const mockTossUser: any = {
|
|
188
|
+
userId: 'test-user-nonce',
|
|
189
|
+
username: 'testusernonce',
|
|
190
|
+
wallet: {
|
|
191
|
+
publicKey: senderKeypair.publicKey.toBase58(),
|
|
192
|
+
isVerified: true,
|
|
193
|
+
},
|
|
194
|
+
security: {
|
|
195
|
+
biometricEnabled: true,
|
|
196
|
+
nonceAccountRequiresBiometric: true,
|
|
197
|
+
},
|
|
198
|
+
tossFeatures: {
|
|
199
|
+
canSend: true,
|
|
200
|
+
canReceive: true,
|
|
201
|
+
isPrivateTxEnabled: false,
|
|
202
|
+
maxTransactionAmount: 10000000,
|
|
203
|
+
offlineTransactionsEnabled: true,
|
|
204
|
+
nonceAccountEnabled: true,
|
|
205
|
+
},
|
|
206
|
+
};
|
|
207
|
+
|
|
208
|
+
const nonceAccountInfo = await nonceManager.createNonceAccount(
|
|
209
|
+
mockTossUser,
|
|
210
|
+
nonceAuthority,
|
|
211
|
+
senderKeypair.publicKey,
|
|
212
|
+
{ requireBiometric: true }
|
|
213
|
+
);
|
|
214
|
+
|
|
215
|
+
expect(nonceAccountInfo).toBeDefined();
|
|
216
|
+
expect(nonceAccountInfo.address).toBeDefined();
|
|
217
|
+
expect(nonceAccountInfo.isBiometricProtected).toBe(true);
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
it('should validate nonce account is active', async () => {
|
|
221
|
+
const nonceAuthority = Keypair.generate();
|
|
222
|
+
|
|
223
|
+
const mockTossUser: any = {
|
|
224
|
+
userId: 'test-user-validate',
|
|
225
|
+
username: 'testuservalidate',
|
|
226
|
+
wallet: {
|
|
227
|
+
publicKey: senderKeypair.publicKey.toBase58(),
|
|
228
|
+
isVerified: true,
|
|
229
|
+
},
|
|
230
|
+
security: {
|
|
231
|
+
biometricEnabled: true,
|
|
232
|
+
nonceAccountRequiresBiometric: true,
|
|
233
|
+
},
|
|
234
|
+
tossFeatures: {
|
|
235
|
+
canSend: true,
|
|
236
|
+
canReceive: true,
|
|
237
|
+
isPrivateTxEnabled: false,
|
|
238
|
+
maxTransactionAmount: 10000000,
|
|
239
|
+
offlineTransactionsEnabled: true,
|
|
240
|
+
nonceAccountEnabled: true,
|
|
241
|
+
},
|
|
242
|
+
};
|
|
243
|
+
|
|
244
|
+
const nonceAccountInfo = await nonceManager.createNonceAccount(
|
|
245
|
+
mockTossUser,
|
|
246
|
+
nonceAuthority,
|
|
247
|
+
senderKeypair.publicKey,
|
|
248
|
+
{ requireBiometric: true }
|
|
249
|
+
);
|
|
250
|
+
|
|
251
|
+
// Nonce account should be valid
|
|
252
|
+
const isValid = nonceManager.isNonceAccountValid(nonceAccountInfo);
|
|
253
|
+
expect(isValid).toBe(true);
|
|
254
|
+
});
|
|
255
|
+
});
|
|
256
|
+
});
|
package/src/ble.ts
CHANGED
|
@@ -3,12 +3,18 @@ import { BleManager, Device } from 'react-native-ble-plx';
|
|
|
3
3
|
import { PermissionsAndroid, Platform } from 'react-native';
|
|
4
4
|
import type { TossUser } from './types/tossUser';
|
|
5
5
|
import type { SolanaIntent } from './intent';
|
|
6
|
+
import type { OfflineTransaction } from './types/nonceAccount';
|
|
7
|
+
import { BLETransactionHandler } from './client/BLETransactionHandler';
|
|
6
8
|
|
|
7
9
|
const SERVICE_UUID = '0000ff00-0000-1000-8000-00805f9b34fb';
|
|
8
10
|
const USER_CHARACTERISTIC = '0000ff01-0000-1000-8000-00805f9b34fb';
|
|
9
11
|
const INTENT_CHARACTERISTIC = '0000ff02-0000-1000-8000-00805f9b34fb';
|
|
12
|
+
const OFFLINE_TX_CHARACTERISTIC = '0000ff03-0000-1000-8000-00805f9b34fb'; // New for offline transactions
|
|
10
13
|
|
|
11
14
|
const manager = new BleManager();
|
|
15
|
+
const bleTransactionHandler = new BLETransactionHandler(
|
|
16
|
+
Platform.OS === 'ios' ? 'ios' : 'android'
|
|
17
|
+
);
|
|
12
18
|
|
|
13
19
|
export async function requestBLEPermissions() {
|
|
14
20
|
if (Platform.OS === 'android') {
|
|
@@ -30,7 +36,8 @@ async function connect(device: Device) {
|
|
|
30
36
|
// Scan for BLE devices advertising TOSS service
|
|
31
37
|
export function startTossScan(
|
|
32
38
|
onUserFound: (user: TossUser, device: Device) => void,
|
|
33
|
-
onIntentFound: (intent: SolanaIntent, device: Device) => void
|
|
39
|
+
onIntentFound: (intent: SolanaIntent, device: Device) => void,
|
|
40
|
+
onOfflineTransactionFound?: (tx: OfflineTransaction, device: Device) => void
|
|
34
41
|
) {
|
|
35
42
|
manager.startDeviceScan([SERVICE_UUID], null, async (error, device) => {
|
|
36
43
|
if (error) {
|
|
@@ -67,6 +74,25 @@ export function startTossScan(
|
|
|
67
74
|
const intent = JSON.parse(intentData.value) as SolanaIntent;
|
|
68
75
|
onIntentFound(intent, device);
|
|
69
76
|
}
|
|
77
|
+
|
|
78
|
+
// Check for offline transaction data (fragmented)
|
|
79
|
+
if (onOfflineTransactionFound) {
|
|
80
|
+
try {
|
|
81
|
+
const txData = await services.readCharacteristicForService(
|
|
82
|
+
device.id,
|
|
83
|
+
SERVICE_UUID,
|
|
84
|
+
OFFLINE_TX_CHARACTERISTIC
|
|
85
|
+
);
|
|
86
|
+
|
|
87
|
+
if (txData?.value) {
|
|
88
|
+
const tx = JSON.parse(txData.value) as OfflineTransaction;
|
|
89
|
+
onOfflineTransactionFound(tx, device);
|
|
90
|
+
}
|
|
91
|
+
} catch {
|
|
92
|
+
// Offline TX characteristic may not be available
|
|
93
|
+
console.debug('Offline transaction characteristic not found');
|
|
94
|
+
}
|
|
95
|
+
}
|
|
70
96
|
} catch (err) {
|
|
71
97
|
console.warn('Error reading device data:', err);
|
|
72
98
|
}
|
|
@@ -132,7 +158,82 @@ export async function sendIntentToDevice(
|
|
|
132
158
|
await device.cancelConnection();
|
|
133
159
|
}
|
|
134
160
|
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
161
|
+
/**
|
|
162
|
+
* Send fragmented offline transaction over BLE with Noise Protocol encryption
|
|
163
|
+
* Automatically handles MTU limitations and retries
|
|
164
|
+
*/
|
|
165
|
+
export async function sendOfflineTransactionFragmented(
|
|
166
|
+
device: Device,
|
|
167
|
+
transaction: OfflineTransaction | SolanaIntent,
|
|
168
|
+
noiseEncryptFn?: (data: Uint8Array) => Promise<any>,
|
|
169
|
+
isIntent: boolean = false
|
|
170
|
+
): Promise<{
|
|
171
|
+
success: boolean;
|
|
172
|
+
sentFragments: number;
|
|
173
|
+
failedFragments: number[];
|
|
174
|
+
messageId: string;
|
|
175
|
+
}> {
|
|
176
|
+
try {
|
|
177
|
+
const result = await bleTransactionHandler.sendFragmentedTransactionBLE(
|
|
178
|
+
device,
|
|
179
|
+
transaction,
|
|
180
|
+
async (deviceId, charUUID, data) => {
|
|
181
|
+
const dev = await manager.connectToDevice(deviceId);
|
|
182
|
+
await dev.discoverAllServicesAndCharacteristics();
|
|
183
|
+
|
|
184
|
+
const characteristic =
|
|
185
|
+
charUUID === OFFLINE_TX_CHARACTERISTIC
|
|
186
|
+
? OFFLINE_TX_CHARACTERISTIC
|
|
187
|
+
: INTENT_CHARACTERISTIC;
|
|
188
|
+
|
|
189
|
+
await dev.writeCharacteristicWithResponseForService(
|
|
190
|
+
deviceId,
|
|
191
|
+
SERVICE_UUID,
|
|
192
|
+
characteristic,
|
|
193
|
+
data.toString('base64')
|
|
194
|
+
);
|
|
195
|
+
|
|
196
|
+
await dev.cancelConnection();
|
|
197
|
+
},
|
|
198
|
+
noiseEncryptFn,
|
|
199
|
+
isIntent
|
|
200
|
+
);
|
|
201
|
+
|
|
202
|
+
return result;
|
|
203
|
+
} catch (error) {
|
|
204
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
205
|
+
console.error('Failed to send offline transaction:', errorMessage);
|
|
206
|
+
throw new Error(`BLE transmission failed: ${errorMessage}`);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* Receive and reassemble fragmented message from BLE
|
|
212
|
+
*/
|
|
213
|
+
export async function receiveOfflineTransactionFragment(
|
|
214
|
+
fragment: any,
|
|
215
|
+
noiseDecryptFn?: (encrypted: any) => Promise<Uint8Array>
|
|
216
|
+
): Promise<{
|
|
217
|
+
complete: boolean;
|
|
218
|
+
transaction?: OfflineTransaction | SolanaIntent;
|
|
219
|
+
progress: { received: number; total: number };
|
|
220
|
+
}> {
|
|
221
|
+
return bleTransactionHandler.receiveFragmentedMessage(
|
|
222
|
+
fragment,
|
|
223
|
+
noiseDecryptFn
|
|
224
|
+
);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* Get current BLE MTU configuration
|
|
229
|
+
*/
|
|
230
|
+
export function getBLEMTUConfig() {
|
|
231
|
+
return bleTransactionHandler.getMTUConfig();
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* Set custom BLE MTU configuration
|
|
236
|
+
*/
|
|
237
|
+
export function setBLEMTUConfig(config: Partial<any>) {
|
|
238
|
+
bleTransactionHandler.setMTUConfig(config);
|
|
138
239
|
}
|