@zubari/sdk 0.2.8 → 0.3.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/dist/{TransactionService-8xSEGoWA.d.mts → TransactionService-DURp3bRL.d.ts} +34 -10
- package/dist/{TransactionService-CaIcCoqY.d.ts → TransactionService-DuMJmrG3.d.mts} +34 -10
- package/dist/{WalletManager-CCs4Jsv7.d.ts → WalletManager-CEjN-YBF.d.ts} +12 -12
- package/dist/{WalletManager-B1qvFF4K.d.mts → WalletManager-bo35aHOJ.d.mts} +12 -12
- package/dist/{index-BOc9U2TK.d.mts → index-BpjMiC3n.d.ts} +22 -11
- package/dist/{index-Cx389p_j.d.mts → index-DF0Gf8NK.d.mts} +7 -1
- package/dist/{index-Cx389p_j.d.ts → index-DF0Gf8NK.d.ts} +7 -1
- package/dist/{index-Cqrpp3XA.d.ts → index-DJnsirV5.d.mts} +22 -11
- package/dist/index.d.mts +4 -4
- package/dist/index.d.ts +4 -4
- package/dist/index.js +3064 -1770
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +3064 -1770
- package/dist/index.mjs.map +1 -1
- package/dist/protocols/index.d.mts +54 -22
- package/dist/protocols/index.d.ts +54 -22
- package/dist/protocols/index.js +1008 -76
- package/dist/protocols/index.js.map +1 -1
- package/dist/protocols/index.mjs +1008 -76
- package/dist/protocols/index.mjs.map +1 -1
- package/dist/react/index.d.mts +3 -3
- package/dist/react/index.d.ts +3 -3
- package/dist/react/index.js +884 -884
- package/dist/react/index.js.map +1 -1
- package/dist/react/index.mjs +884 -884
- package/dist/react/index.mjs.map +1 -1
- package/dist/services/index.d.mts +2 -2
- package/dist/services/index.d.ts +2 -2
- package/dist/services/index.js +152 -75
- package/dist/services/index.js.map +1 -1
- package/dist/services/index.mjs +152 -75
- package/dist/services/index.mjs.map +1 -1
- package/dist/wallet/index.d.mts +3 -3
- package/dist/wallet/index.d.ts +3 -3
- package/dist/wallet/index.js +1352 -1105
- package/dist/wallet/index.js.map +1 -1
- package/dist/wallet/index.mjs +1352 -1105
- package/dist/wallet/index.mjs.map +1 -1
- package/package.json +13 -12
package/dist/wallet/index.mjs
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import { Wallet, HDNodeWallet } from 'ethers';
|
|
2
|
-
import { createPublicClient, http, formatEther } from 'viem';
|
|
3
|
-
import { mainnet, sepolia } from 'viem/chains';
|
|
4
2
|
import { generateMnemonic, validateMnemonic, mnemonicToSeedSync } from '@scure/bip39';
|
|
5
3
|
import { wordlist } from '@scure/bip39/wordlists/english';
|
|
6
4
|
import { HDKey } from '@scure/bip32';
|
|
7
5
|
import { bech32, base58check } from '@scure/base';
|
|
8
6
|
import { sha256 } from '@noble/hashes/sha256';
|
|
9
7
|
import { ripemd160 } from '@noble/hashes/ripemd160';
|
|
8
|
+
import { createPublicClient, http, formatEther } from 'viem';
|
|
9
|
+
import { mainnet, sepolia } from 'viem/chains';
|
|
10
10
|
|
|
11
11
|
// src/config/networks.ts
|
|
12
12
|
var NETWORKS = {
|
|
@@ -178,1292 +178,1539 @@ function getContractAddresses(network) {
|
|
|
178
178
|
return ZUBARI_CONTRACTS[network];
|
|
179
179
|
}
|
|
180
180
|
|
|
181
|
-
// src/
|
|
182
|
-
var
|
|
183
|
-
seed;
|
|
181
|
+
// src/services/WdkApiClient.ts
|
|
182
|
+
var WdkApiClient = class {
|
|
184
183
|
config;
|
|
185
|
-
|
|
186
|
-
initialized = false;
|
|
187
|
-
constructor(seed, config) {
|
|
188
|
-
this.seed = seed;
|
|
184
|
+
constructor(config) {
|
|
189
185
|
this.config = {
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
gasless: config.gasless ?? false,
|
|
193
|
-
paymasterUrl: config.paymasterUrl,
|
|
194
|
-
bundlerUrl: config.bundlerUrl,
|
|
195
|
-
rpcUrls: config.rpcUrls
|
|
196
|
-
};
|
|
197
|
-
}
|
|
198
|
-
/**
|
|
199
|
-
* Initialize the wallet by deriving accounts for all enabled networks
|
|
200
|
-
*/
|
|
201
|
-
async initialize() {
|
|
202
|
-
if (this.initialized) return;
|
|
203
|
-
for (const network of this.config.enabledNetworks) {
|
|
204
|
-
await this.deriveAccount(network);
|
|
205
|
-
}
|
|
206
|
-
this.initialized = true;
|
|
207
|
-
}
|
|
208
|
-
/**
|
|
209
|
-
* Derive account for a specific network using BIP-44
|
|
210
|
-
*/
|
|
211
|
-
async deriveAccount(network, index = 0) {
|
|
212
|
-
getNetworkConfig(network, this.config.network === "testnet");
|
|
213
|
-
const basePath = DERIVATION_PATHS[network];
|
|
214
|
-
const derivationPath = `${basePath}/${index}`;
|
|
215
|
-
const account = {
|
|
216
|
-
network,
|
|
217
|
-
address: "",
|
|
218
|
-
// Will be derived using WDK
|
|
219
|
-
publicKey: "",
|
|
220
|
-
derivationPath
|
|
186
|
+
baseUrl: config.baseUrl,
|
|
187
|
+
timeout: config.timeout || 3e4
|
|
221
188
|
};
|
|
222
|
-
this.accounts.set(network, account);
|
|
223
|
-
return account;
|
|
224
189
|
}
|
|
225
190
|
/**
|
|
226
|
-
*
|
|
191
|
+
* Generate a new BIP-39 seed phrase using Tether WDK
|
|
227
192
|
*/
|
|
228
|
-
async
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
193
|
+
async generateSeed() {
|
|
194
|
+
try {
|
|
195
|
+
const response = await fetch(`${this.config.baseUrl}/api/wallets/wdk/generate-seed`, {
|
|
196
|
+
method: "POST",
|
|
197
|
+
headers: {
|
|
198
|
+
"Content-Type": "application/json"
|
|
199
|
+
}
|
|
200
|
+
});
|
|
201
|
+
return await response.json();
|
|
202
|
+
} catch (error) {
|
|
203
|
+
return {
|
|
204
|
+
success: false,
|
|
205
|
+
error: error instanceof Error ? error.message : "Failed to generate seed"
|
|
206
|
+
};
|
|
232
207
|
}
|
|
233
|
-
return this.deriveAccount(network, index);
|
|
234
|
-
}
|
|
235
|
-
/**
|
|
236
|
-
* Get address for a specific network
|
|
237
|
-
*/
|
|
238
|
-
async getAddress(network) {
|
|
239
|
-
const account = await this.getAccount(network);
|
|
240
|
-
return account.address;
|
|
241
208
|
}
|
|
242
209
|
/**
|
|
243
|
-
*
|
|
210
|
+
* Validate a BIP-39 seed phrase
|
|
244
211
|
*/
|
|
245
|
-
async
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
212
|
+
async validateSeed(seed) {
|
|
213
|
+
try {
|
|
214
|
+
const response = await fetch(`${this.config.baseUrl}/api/wallets/wdk/validate-seed`, {
|
|
215
|
+
method: "POST",
|
|
216
|
+
headers: {
|
|
217
|
+
"Content-Type": "application/json"
|
|
218
|
+
},
|
|
219
|
+
body: JSON.stringify({ seed })
|
|
220
|
+
});
|
|
221
|
+
return await response.json();
|
|
222
|
+
} catch (error) {
|
|
223
|
+
return {
|
|
224
|
+
success: false,
|
|
225
|
+
error: error instanceof Error ? error.message : "Failed to validate seed"
|
|
226
|
+
};
|
|
249
227
|
}
|
|
250
|
-
return addresses;
|
|
251
|
-
}
|
|
252
|
-
/**
|
|
253
|
-
* Get balance for a specific network
|
|
254
|
-
*/
|
|
255
|
-
async getBalance(network) {
|
|
256
|
-
const networkConfig = getNetworkConfig(network, this.config.network === "testnet");
|
|
257
|
-
return {
|
|
258
|
-
network,
|
|
259
|
-
native: {
|
|
260
|
-
symbol: networkConfig.nativeCurrency.symbol,
|
|
261
|
-
balance: BigInt(0),
|
|
262
|
-
balanceFormatted: "0",
|
|
263
|
-
balanceUsd: 0
|
|
264
|
-
},
|
|
265
|
-
tokens: []
|
|
266
|
-
};
|
|
267
228
|
}
|
|
268
229
|
/**
|
|
269
|
-
*
|
|
230
|
+
* Derive address for a specific chain using Tether WDK
|
|
270
231
|
*/
|
|
271
|
-
async
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
232
|
+
async deriveAddress(seed, chain, network = "testnet") {
|
|
233
|
+
try {
|
|
234
|
+
const response = await fetch(`${this.config.baseUrl}/api/wallets/wdk/derive-address`, {
|
|
235
|
+
method: "POST",
|
|
236
|
+
headers: {
|
|
237
|
+
"Content-Type": "application/json"
|
|
238
|
+
},
|
|
239
|
+
body: JSON.stringify({ seed, chain, network })
|
|
240
|
+
});
|
|
241
|
+
return await response.json();
|
|
242
|
+
} catch (error) {
|
|
243
|
+
return {
|
|
244
|
+
success: false,
|
|
245
|
+
error: error instanceof Error ? error.message : "Failed to derive address"
|
|
246
|
+
};
|
|
275
247
|
}
|
|
276
|
-
return balances;
|
|
277
248
|
}
|
|
278
249
|
/**
|
|
279
|
-
*
|
|
250
|
+
* Derive addresses for all chains using Tether WDK
|
|
280
251
|
*/
|
|
281
|
-
async
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
252
|
+
async deriveAllAddresses(seed, network = "testnet") {
|
|
253
|
+
try {
|
|
254
|
+
const response = await fetch(`${this.config.baseUrl}/api/wallets/wdk/derive-all`, {
|
|
255
|
+
method: "POST",
|
|
256
|
+
headers: {
|
|
257
|
+
"Content-Type": "application/json"
|
|
258
|
+
},
|
|
259
|
+
body: JSON.stringify({ seed, network })
|
|
260
|
+
});
|
|
261
|
+
return await response.json();
|
|
262
|
+
} catch (error) {
|
|
263
|
+
return {
|
|
264
|
+
success: false,
|
|
265
|
+
error: error instanceof Error ? error.message : "Failed to derive addresses"
|
|
266
|
+
};
|
|
289
267
|
}
|
|
290
|
-
return total;
|
|
291
|
-
}
|
|
292
|
-
/**
|
|
293
|
-
* Send native currency on a specific network
|
|
294
|
-
*/
|
|
295
|
-
async send(network, params) {
|
|
296
|
-
const { to, amount, gasless } = params;
|
|
297
|
-
gasless ?? (this.config.gasless && network === "ethereum");
|
|
298
|
-
return {
|
|
299
|
-
hash: "",
|
|
300
|
-
network,
|
|
301
|
-
status: "pending"
|
|
302
|
-
};
|
|
303
268
|
}
|
|
304
269
|
/**
|
|
305
|
-
* Send
|
|
270
|
+
* Send a transaction on a specific chain using Tether WDK
|
|
306
271
|
*/
|
|
307
|
-
async
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
272
|
+
async sendTransaction(seed, chain, to, amount, network = "testnet") {
|
|
273
|
+
try {
|
|
274
|
+
const response = await fetch(`${this.config.baseUrl}/api/wallets/wdk/send`, {
|
|
275
|
+
method: "POST",
|
|
276
|
+
headers: {
|
|
277
|
+
"Content-Type": "application/json"
|
|
278
|
+
},
|
|
279
|
+
body: JSON.stringify({ seed, chain, to, amount, network })
|
|
280
|
+
});
|
|
281
|
+
return await response.json();
|
|
282
|
+
} catch (error) {
|
|
283
|
+
return {
|
|
284
|
+
success: false,
|
|
285
|
+
error: error instanceof Error ? error.message : "Failed to send transaction"
|
|
286
|
+
};
|
|
311
287
|
}
|
|
312
|
-
return {
|
|
313
|
-
hash: "",
|
|
314
|
-
network,
|
|
315
|
-
status: "pending"
|
|
316
|
-
};
|
|
317
288
|
}
|
|
318
289
|
/**
|
|
319
|
-
*
|
|
320
|
-
*
|
|
321
|
-
* @param amount - Amount in satoshis
|
|
290
|
+
* Get transaction history for an address on a specific chain
|
|
291
|
+
* Fetches from blockchain explorers (Etherscan, mempool.space, etc.)
|
|
322
292
|
*/
|
|
323
|
-
async
|
|
324
|
-
|
|
325
|
-
|
|
293
|
+
async getTransactionHistory(seed, chain, network = "testnet", limit = 10) {
|
|
294
|
+
try {
|
|
295
|
+
const response = await fetch(`${this.config.baseUrl}/api/wallets/wdk/history`, {
|
|
296
|
+
method: "POST",
|
|
297
|
+
headers: {
|
|
298
|
+
"Content-Type": "application/json"
|
|
299
|
+
},
|
|
300
|
+
body: JSON.stringify({ seed, chain, network, limit })
|
|
301
|
+
});
|
|
302
|
+
return await response.json();
|
|
303
|
+
} catch (error) {
|
|
304
|
+
return {
|
|
305
|
+
success: false,
|
|
306
|
+
error: error instanceof Error ? error.message : "Failed to get transaction history"
|
|
307
|
+
};
|
|
326
308
|
}
|
|
327
|
-
return this.send("bitcoin", { to, amount });
|
|
328
309
|
}
|
|
329
310
|
/**
|
|
330
|
-
*
|
|
311
|
+
* Get transaction status by hash
|
|
312
|
+
* Fetches from blockchain explorers to check confirmation status
|
|
331
313
|
*/
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
314
|
+
async getTransactionStatus(txHash, chain, network = "testnet") {
|
|
315
|
+
try {
|
|
316
|
+
const response = await fetch(`${this.config.baseUrl}/api/wallets/wdk/tx-status`, {
|
|
317
|
+
method: "POST",
|
|
318
|
+
headers: {
|
|
319
|
+
"Content-Type": "application/json"
|
|
320
|
+
},
|
|
321
|
+
body: JSON.stringify({ txHash, chain, network })
|
|
322
|
+
});
|
|
323
|
+
return await response.json();
|
|
324
|
+
} catch (error) {
|
|
325
|
+
return {
|
|
326
|
+
success: false,
|
|
327
|
+
error: error instanceof Error ? error.message : "Failed to get transaction status"
|
|
328
|
+
};
|
|
341
329
|
}
|
|
342
|
-
return false;
|
|
343
|
-
}
|
|
344
|
-
/**
|
|
345
|
-
* Get Bitcoin address (native segwit bc1q...)
|
|
346
|
-
*/
|
|
347
|
-
async getBitcoinAddress() {
|
|
348
|
-
const account = await this.getAccount("bitcoin");
|
|
349
|
-
return account.address;
|
|
350
330
|
}
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
}
|
|
360
|
-
const invoiceDetails = this.decodeLightningInvoice(invoice);
|
|
361
|
-
return {
|
|
362
|
-
hash: "",
|
|
363
|
-
// Will be payment hash from Spark
|
|
364
|
-
network: "spark",
|
|
365
|
-
status: "pending",
|
|
366
|
-
metadata: {
|
|
367
|
-
invoice,
|
|
368
|
-
amount: invoiceDetails.amount,
|
|
369
|
-
destination: invoiceDetails.destination
|
|
370
|
-
}
|
|
371
|
-
};
|
|
331
|
+
};
|
|
332
|
+
var DEFAULT_API_URL = process.env.NEXT_PUBLIC_API_URL || "https://ckgwifsxka.us-east-2.awsapprunner.com";
|
|
333
|
+
var wdkApiClient = null;
|
|
334
|
+
function getWdkApiClient(baseUrl) {
|
|
335
|
+
if (!wdkApiClient || baseUrl && wdkApiClient["config"].baseUrl !== baseUrl) {
|
|
336
|
+
wdkApiClient = new WdkApiClient({
|
|
337
|
+
baseUrl: baseUrl || DEFAULT_API_URL
|
|
338
|
+
});
|
|
372
339
|
}
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
340
|
+
return wdkApiClient;
|
|
341
|
+
}
|
|
342
|
+
var DERIVATION_PATHS2 = {
|
|
343
|
+
ethereum: "m/44'/60'/0'/0/0",
|
|
344
|
+
bitcoin_mainnet: "m/84'/0'/0'/0/0",
|
|
345
|
+
bitcoin_testnet: "m/84'/1'/0'/0/0",
|
|
346
|
+
ton: "m/44'/607'/0'/0'/0'",
|
|
347
|
+
tron: "m/44'/195'/0'/0/0",
|
|
348
|
+
solana: "m/44'/501'/0'/0'",
|
|
349
|
+
spark: "m/44'/998'/0'/0/0"
|
|
350
|
+
};
|
|
351
|
+
function deriveEthereumAddress(seed) {
|
|
352
|
+
const hdNode = HDNodeWallet.fromPhrase(seed, void 0, DERIVATION_PATHS2.ethereum);
|
|
353
|
+
return hdNode.address;
|
|
354
|
+
}
|
|
355
|
+
function deriveBitcoinAddress(seed, network = "testnet") {
|
|
356
|
+
try {
|
|
357
|
+
const seedBytes = mnemonicToSeedSync(seed);
|
|
358
|
+
const hdKey = HDKey.fromMasterSeed(seedBytes);
|
|
359
|
+
const path = network === "testnet" ? DERIVATION_PATHS2.bitcoin_testnet : DERIVATION_PATHS2.bitcoin_mainnet;
|
|
360
|
+
const child = hdKey.derive(path);
|
|
361
|
+
if (!child.publicKey) {
|
|
362
|
+
throw new Error("Failed to derive public key");
|
|
382
363
|
}
|
|
383
|
-
const
|
|
384
|
-
const
|
|
385
|
-
|
|
364
|
+
const pubKeyHash = ripemd160(sha256(child.publicKey));
|
|
365
|
+
const witnessVersion = 0;
|
|
366
|
+
const words = bech32.toWords(pubKeyHash);
|
|
367
|
+
words.unshift(witnessVersion);
|
|
368
|
+
const hrp = network === "testnet" ? "tb" : "bc";
|
|
369
|
+
const address = bech32.encode(hrp, words);
|
|
370
|
+
return address;
|
|
371
|
+
} catch (error) {
|
|
372
|
+
console.error("Bitcoin address derivation failed:", error);
|
|
373
|
+
throw error;
|
|
386
374
|
}
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
375
|
+
}
|
|
376
|
+
async function deriveSolanaAddress(seed) {
|
|
377
|
+
try {
|
|
378
|
+
const [ed25519, nacl, bs58Module] = await Promise.all([
|
|
379
|
+
import('ed25519-hd-key'),
|
|
380
|
+
import('tweetnacl'),
|
|
381
|
+
import('bs58')
|
|
382
|
+
]);
|
|
383
|
+
const bs58 = bs58Module.default || bs58Module;
|
|
384
|
+
const seedBytes = mnemonicToSeedSync(seed);
|
|
385
|
+
const derived = ed25519.derivePath(DERIVATION_PATHS2.solana, Buffer.from(seedBytes).toString("hex"));
|
|
386
|
+
const keypair = nacl.sign.keyPair.fromSeed(new Uint8Array(derived.key));
|
|
387
|
+
return bs58.encode(keypair.publicKey);
|
|
388
|
+
} catch (error) {
|
|
389
|
+
console.error("Solana address derivation failed:", error);
|
|
390
|
+
throw error;
|
|
395
391
|
}
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
392
|
+
}
|
|
393
|
+
async function deriveTonAddress(seed) {
|
|
394
|
+
try {
|
|
395
|
+
const [ed25519, nacl] = await Promise.all([
|
|
396
|
+
import('ed25519-hd-key'),
|
|
397
|
+
import('tweetnacl')
|
|
398
|
+
]);
|
|
399
|
+
const seedBytes = mnemonicToSeedSync(seed);
|
|
400
|
+
const derived = ed25519.derivePath(DERIVATION_PATHS2.ton, Buffer.from(seedBytes).toString("hex"));
|
|
401
|
+
const keypair = nacl.sign.keyPair.fromSeed(new Uint8Array(derived.key));
|
|
402
|
+
const publicKey = keypair.publicKey;
|
|
403
|
+
const workchain = 0;
|
|
404
|
+
const flags = 17;
|
|
405
|
+
const hash = sha256(publicKey);
|
|
406
|
+
const addressData = new Uint8Array(34);
|
|
407
|
+
addressData[0] = flags;
|
|
408
|
+
addressData[1] = workchain;
|
|
409
|
+
addressData.set(hash, 2);
|
|
410
|
+
const crc = crc16(addressData);
|
|
411
|
+
const fullAddress = new Uint8Array(36);
|
|
412
|
+
fullAddress.set(addressData);
|
|
413
|
+
fullAddress[34] = crc >> 8 & 255;
|
|
414
|
+
fullAddress[35] = crc & 255;
|
|
415
|
+
const base64 = btoa(String.fromCharCode(...fullAddress)).replace(/\+/g, "-").replace(/\//g, "_");
|
|
416
|
+
return base64;
|
|
417
|
+
} catch (error) {
|
|
418
|
+
console.error("TON address derivation failed:", error);
|
|
419
|
+
throw error;
|
|
405
420
|
}
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
421
|
+
}
|
|
422
|
+
function crc16(data) {
|
|
423
|
+
let crc = 0;
|
|
424
|
+
for (const byte of data) {
|
|
425
|
+
crc ^= byte << 8;
|
|
426
|
+
for (let i = 0; i < 8; i++) {
|
|
427
|
+
crc = crc & 32768 ? crc << 1 ^ 4129 : crc << 1;
|
|
428
|
+
crc &= 65535;
|
|
429
|
+
}
|
|
414
430
|
}
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
431
|
+
return crc;
|
|
432
|
+
}
|
|
433
|
+
function deriveTronAddress(seed) {
|
|
434
|
+
try {
|
|
435
|
+
const hdNode = HDNodeWallet.fromPhrase(seed, void 0, DERIVATION_PATHS2.tron);
|
|
436
|
+
const ethAddressHex = hdNode.address.slice(2).toLowerCase();
|
|
437
|
+
const addressBytes = new Uint8Array(21);
|
|
438
|
+
addressBytes[0] = 65;
|
|
439
|
+
for (let i = 0; i < 20; i++) {
|
|
440
|
+
addressBytes[i + 1] = parseInt(ethAddressHex.slice(i * 2, i * 2 + 2), 16);
|
|
441
|
+
}
|
|
442
|
+
const tronBase58check = base58check(sha256);
|
|
443
|
+
return tronBase58check.encode(addressBytes);
|
|
444
|
+
} catch (error) {
|
|
445
|
+
console.error("TRON address derivation failed:", error);
|
|
446
|
+
throw error;
|
|
420
447
|
}
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
448
|
+
}
|
|
449
|
+
function deriveSparkAddress(seed, network = "testnet") {
|
|
450
|
+
try {
|
|
451
|
+
const seedBytes = mnemonicToSeedSync(seed);
|
|
452
|
+
const hdKey = HDKey.fromMasterSeed(seedBytes);
|
|
453
|
+
const child = hdKey.derive(DERIVATION_PATHS2.spark);
|
|
454
|
+
if (!child.publicKey) {
|
|
455
|
+
throw new Error("Failed to derive public key");
|
|
456
|
+
}
|
|
457
|
+
const pubKeyHash = ripemd160(sha256(child.publicKey));
|
|
458
|
+
const witnessVersion = 0;
|
|
459
|
+
const words = bech32.toWords(pubKeyHash);
|
|
460
|
+
words.unshift(witnessVersion);
|
|
461
|
+
const hrp = network === "testnet" ? "tsp" : "sp";
|
|
462
|
+
const address = bech32.encode(hrp, words);
|
|
463
|
+
return address;
|
|
464
|
+
} catch (error) {
|
|
465
|
+
console.error("Spark address derivation failed:", error);
|
|
466
|
+
throw error;
|
|
426
467
|
}
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
468
|
+
}
|
|
469
|
+
async function deriveAllAddresses(seed, network = "testnet") {
|
|
470
|
+
const addresses = {
|
|
471
|
+
ethereum: null,
|
|
472
|
+
bitcoin: null,
|
|
473
|
+
ton: null,
|
|
474
|
+
tron: null,
|
|
475
|
+
solana: null,
|
|
476
|
+
spark: null
|
|
477
|
+
};
|
|
478
|
+
try {
|
|
479
|
+
addresses.ethereum = deriveEthereumAddress(seed);
|
|
480
|
+
} catch (e) {
|
|
481
|
+
console.error("ETH derivation failed:", e);
|
|
432
482
|
}
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
static ALGORITHM = "AES-GCM";
|
|
438
|
-
static KEY_LENGTH = 256;
|
|
439
|
-
static IV_LENGTH = 12;
|
|
440
|
-
static SALT_LENGTH = 16;
|
|
441
|
-
static PBKDF2_ITERATIONS = 1e5;
|
|
442
|
-
/**
|
|
443
|
-
* Encrypt a seed phrase with a password
|
|
444
|
-
*/
|
|
445
|
-
static async encryptSeed(seed, password) {
|
|
446
|
-
const encoder = new TextEncoder();
|
|
447
|
-
const seedData = encoder.encode(seed);
|
|
448
|
-
const salt = crypto.getRandomValues(new Uint8Array(this.SALT_LENGTH));
|
|
449
|
-
const iv = crypto.getRandomValues(new Uint8Array(this.IV_LENGTH));
|
|
450
|
-
const key = await this.deriveKey(password, salt);
|
|
451
|
-
const encrypted = await crypto.subtle.encrypt(
|
|
452
|
-
{ name: this.ALGORITHM, iv },
|
|
453
|
-
key,
|
|
454
|
-
seedData
|
|
455
|
-
);
|
|
456
|
-
const combined = new Uint8Array(salt.length + iv.length + encrypted.byteLength);
|
|
457
|
-
combined.set(salt, 0);
|
|
458
|
-
combined.set(iv, salt.length);
|
|
459
|
-
combined.set(new Uint8Array(encrypted), salt.length + iv.length);
|
|
460
|
-
return btoa(String.fromCharCode(...combined));
|
|
483
|
+
try {
|
|
484
|
+
addresses.bitcoin = deriveBitcoinAddress(seed, network);
|
|
485
|
+
} catch (e) {
|
|
486
|
+
console.error("BTC derivation failed:", e);
|
|
461
487
|
}
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
const combined = new Uint8Array(
|
|
467
|
-
atob(encryptedData).split("").map((c) => c.charCodeAt(0))
|
|
468
|
-
);
|
|
469
|
-
const salt = combined.slice(0, this.SALT_LENGTH);
|
|
470
|
-
const iv = combined.slice(this.SALT_LENGTH, this.SALT_LENGTH + this.IV_LENGTH);
|
|
471
|
-
const encrypted = combined.slice(this.SALT_LENGTH + this.IV_LENGTH);
|
|
472
|
-
const key = await this.deriveKey(password, salt);
|
|
473
|
-
const decrypted = await crypto.subtle.decrypt(
|
|
474
|
-
{ name: this.ALGORITHM, iv },
|
|
475
|
-
key,
|
|
476
|
-
encrypted
|
|
477
|
-
);
|
|
478
|
-
const decoder = new TextDecoder();
|
|
479
|
-
return decoder.decode(decrypted);
|
|
488
|
+
try {
|
|
489
|
+
addresses.spark = deriveSparkAddress(seed, network);
|
|
490
|
+
} catch (e) {
|
|
491
|
+
console.error("Spark derivation failed:", e);
|
|
480
492
|
}
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
const encoder = new TextEncoder();
|
|
486
|
-
const passwordData = encoder.encode(password);
|
|
487
|
-
const keyMaterial = await crypto.subtle.importKey(
|
|
488
|
-
"raw",
|
|
489
|
-
passwordData,
|
|
490
|
-
"PBKDF2",
|
|
491
|
-
false,
|
|
492
|
-
["deriveKey"]
|
|
493
|
-
);
|
|
494
|
-
return crypto.subtle.deriveKey(
|
|
495
|
-
{
|
|
496
|
-
name: "PBKDF2",
|
|
497
|
-
salt: salt.buffer.slice(salt.byteOffset, salt.byteOffset + salt.byteLength),
|
|
498
|
-
iterations: this.PBKDF2_ITERATIONS,
|
|
499
|
-
hash: "SHA-256"
|
|
500
|
-
},
|
|
501
|
-
keyMaterial,
|
|
502
|
-
{ name: this.ALGORITHM, length: this.KEY_LENGTH },
|
|
503
|
-
false,
|
|
504
|
-
["encrypt", "decrypt"]
|
|
505
|
-
);
|
|
493
|
+
try {
|
|
494
|
+
addresses.tron = deriveTronAddress(seed);
|
|
495
|
+
} catch (e) {
|
|
496
|
+
console.error("TRON derivation failed:", e);
|
|
506
497
|
}
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
498
|
+
const [solResult, tonResult] = await Promise.allSettled([
|
|
499
|
+
deriveSolanaAddress(seed),
|
|
500
|
+
deriveTonAddress(seed)
|
|
501
|
+
]);
|
|
502
|
+
if (solResult.status === "fulfilled") {
|
|
503
|
+
addresses.solana = solResult.value;
|
|
504
|
+
} else {
|
|
505
|
+
console.error("SOL derivation failed:", solResult.reason);
|
|
514
506
|
}
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
const bytes = crypto.getRandomValues(new Uint8Array(32));
|
|
520
|
-
return Array.from(bytes).map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
507
|
+
if (tonResult.status === "fulfilled") {
|
|
508
|
+
addresses.ton = tonResult.value;
|
|
509
|
+
} else {
|
|
510
|
+
console.error("TON derivation failed:", tonResult.reason);
|
|
521
511
|
}
|
|
522
|
-
|
|
512
|
+
return addresses;
|
|
513
|
+
}
|
|
514
|
+
function isValidSeed(seed) {
|
|
515
|
+
return validateMnemonic(seed, wordlist);
|
|
516
|
+
}
|
|
517
|
+
function generateSeedPhrase() {
|
|
518
|
+
return generateMnemonic(wordlist);
|
|
519
|
+
}
|
|
523
520
|
|
|
524
|
-
// src/
|
|
525
|
-
var
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
521
|
+
// src/services/ZubariWdkService.ts
|
|
522
|
+
var DEFAULT_API_URL2 = "https://ckgwifsxka.us-east-2.awsapprunner.com";
|
|
523
|
+
function isBrowser() {
|
|
524
|
+
return typeof window !== "undefined" && typeof window.document !== "undefined";
|
|
525
|
+
}
|
|
526
|
+
var dynamicImport = new Function("specifier", "return import(specifier)");
|
|
527
|
+
async function canUseNativeWdk() {
|
|
528
|
+
if (isBrowser()) {
|
|
529
|
+
return false;
|
|
529
530
|
}
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
}
|
|
531
|
+
try {
|
|
532
|
+
await dynamicImport("@tetherto/wdk");
|
|
533
|
+
return true;
|
|
534
|
+
} catch {
|
|
535
|
+
return false;
|
|
536
536
|
}
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
537
|
+
}
|
|
538
|
+
var ZubariWdkService = class {
|
|
539
|
+
config;
|
|
540
|
+
apiClient;
|
|
541
|
+
nativeWdkService = null;
|
|
542
|
+
initialized = false;
|
|
543
|
+
useNativeWdk = false;
|
|
544
|
+
constructor(config = {}) {
|
|
545
|
+
this.config = {
|
|
546
|
+
network: config.network || "testnet",
|
|
547
|
+
apiUrl: config.apiUrl || process.env.NEXT_PUBLIC_API_URL || DEFAULT_API_URL2,
|
|
548
|
+
forceApi: config.forceApi ?? false,
|
|
549
|
+
timeout: config.timeout || 3e4
|
|
550
|
+
};
|
|
551
|
+
this.apiClient = getWdkApiClient(this.config.apiUrl);
|
|
542
552
|
}
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
553
|
+
/**
|
|
554
|
+
* Initialize the service and determine the best strategy
|
|
555
|
+
*/
|
|
556
|
+
async initialize() {
|
|
557
|
+
if (this.initialized) return;
|
|
558
|
+
if (isBrowser() || this.config.forceApi) {
|
|
559
|
+
this.useNativeWdk = false;
|
|
560
|
+
this.initialized = true;
|
|
561
|
+
return;
|
|
548
562
|
}
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
563
|
+
if (await canUseNativeWdk()) {
|
|
564
|
+
try {
|
|
565
|
+
const WdkServiceModule = await dynamicImport("./WdkService");
|
|
566
|
+
const WdkService = WdkServiceModule.WdkService || WdkServiceModule.default;
|
|
567
|
+
this.nativeWdkService = new WdkService({
|
|
568
|
+
network: this.config.network
|
|
569
|
+
});
|
|
570
|
+
this.useNativeWdk = true;
|
|
571
|
+
} catch (error) {
|
|
572
|
+
console.warn("Failed to initialize native WDK, falling back to API:", error);
|
|
573
|
+
this.useNativeWdk = false;
|
|
574
|
+
}
|
|
559
575
|
}
|
|
576
|
+
this.initialized = true;
|
|
560
577
|
}
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
this.
|
|
578
|
+
/**
|
|
579
|
+
* Get the current execution mode
|
|
580
|
+
*/
|
|
581
|
+
getMode() {
|
|
582
|
+
if (this.useNativeWdk) return "native";
|
|
583
|
+
if (isBrowser()) return "api";
|
|
584
|
+
return "api";
|
|
566
585
|
}
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
}
|
|
586
|
+
/**
|
|
587
|
+
* Check if running in browser
|
|
588
|
+
*/
|
|
589
|
+
isBrowserEnvironment() {
|
|
590
|
+
return isBrowser();
|
|
573
591
|
}
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
592
|
+
/**
|
|
593
|
+
* Generate a new BIP-39 seed phrase (12 words)
|
|
594
|
+
*/
|
|
595
|
+
async generateSeed() {
|
|
596
|
+
await this.initialize();
|
|
597
|
+
try {
|
|
598
|
+
const response = await this.apiClient.generateSeed();
|
|
599
|
+
if (response.success && response.seed) {
|
|
600
|
+
return response.seed;
|
|
601
|
+
}
|
|
602
|
+
} catch (error) {
|
|
603
|
+
console.warn("API seed generation failed:", error);
|
|
604
|
+
}
|
|
605
|
+
if (this.useNativeWdk && this.nativeWdkService) {
|
|
606
|
+
try {
|
|
607
|
+
const wdk = this.nativeWdkService;
|
|
608
|
+
return await wdk.generateSeedPhrase();
|
|
609
|
+
} catch (error) {
|
|
610
|
+
console.warn("Native WDK seed generation failed:", error);
|
|
611
|
+
}
|
|
577
612
|
}
|
|
578
|
-
|
|
613
|
+
return generateSeedPhrase();
|
|
579
614
|
}
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
615
|
+
/**
|
|
616
|
+
* Validate a BIP-39 seed phrase
|
|
617
|
+
*/
|
|
618
|
+
async validateSeed(seed) {
|
|
619
|
+
await this.initialize();
|
|
620
|
+
try {
|
|
621
|
+
const response = await this.apiClient.validateSeed(seed);
|
|
622
|
+
if (response.success) {
|
|
623
|
+
return response.isValid ?? false;
|
|
624
|
+
}
|
|
625
|
+
} catch (error) {
|
|
626
|
+
console.warn("API seed validation failed:", error);
|
|
585
627
|
}
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
await global.KeystoreModule.clear(this.alias);
|
|
594
|
-
} else {
|
|
595
|
-
throw new Error("Keystore not available on this platform");
|
|
628
|
+
if (this.useNativeWdk && this.nativeWdkService) {
|
|
629
|
+
try {
|
|
630
|
+
const wdk = this.nativeWdkService;
|
|
631
|
+
return await wdk.isValidSeed(seed);
|
|
632
|
+
} catch (error) {
|
|
633
|
+
console.warn("Native WDK seed validation failed:", error);
|
|
634
|
+
}
|
|
596
635
|
}
|
|
597
|
-
|
|
598
|
-
};
|
|
599
|
-
var WebEncryptedStorageAdapter = class {
|
|
600
|
-
encryptionKey = null;
|
|
601
|
-
storagePrefix;
|
|
602
|
-
constructor(storagePrefix = "zubari_") {
|
|
603
|
-
this.storagePrefix = storagePrefix;
|
|
636
|
+
return isValidSeed(seed);
|
|
604
637
|
}
|
|
605
638
|
/**
|
|
606
|
-
*
|
|
639
|
+
* Derive address for a specific chain using WDK API
|
|
640
|
+
*
|
|
641
|
+
* For Ethereum, falls back to local derivation if API fails.
|
|
642
|
+
* For other chains, WDK API is required - no placeholder fallback.
|
|
607
643
|
*/
|
|
608
|
-
async
|
|
609
|
-
|
|
610
|
-
const
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
}
|
|
625
|
-
keyMaterial,
|
|
626
|
-
{ name: "AES-GCM", length: 256 },
|
|
627
|
-
false,
|
|
628
|
-
["encrypt", "decrypt"]
|
|
629
|
-
);
|
|
630
|
-
}
|
|
631
|
-
getSalt() {
|
|
632
|
-
const saltKey = `${this.storagePrefix}salt`;
|
|
633
|
-
let saltHex = localStorage.getItem(saltKey);
|
|
634
|
-
if (!saltHex) {
|
|
635
|
-
const salt = crypto.getRandomValues(new Uint8Array(16));
|
|
636
|
-
saltHex = Array.from(salt).map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
637
|
-
localStorage.setItem(saltKey, saltHex);
|
|
644
|
+
async deriveAddress(seed, chain) {
|
|
645
|
+
await this.initialize();
|
|
646
|
+
const path = this.getDerivationPath(chain);
|
|
647
|
+
try {
|
|
648
|
+
const response = await this.apiClient.deriveAddress(seed, chain, this.config.network);
|
|
649
|
+
if (response.success && response.address) {
|
|
650
|
+
return {
|
|
651
|
+
chain,
|
|
652
|
+
address: response.address,
|
|
653
|
+
path: response.path || path
|
|
654
|
+
};
|
|
655
|
+
}
|
|
656
|
+
} catch (error) {
|
|
657
|
+
console.warn(`API address derivation failed for ${chain}:`, error);
|
|
658
|
+
if (chain === "ethereum") {
|
|
659
|
+
return this.deriveBrowserAddress(seed, chain);
|
|
660
|
+
}
|
|
638
661
|
}
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
662
|
+
if (this.useNativeWdk && this.nativeWdkService) {
|
|
663
|
+
try {
|
|
664
|
+
const wdk = this.nativeWdkService;
|
|
665
|
+
await wdk.initialize(seed);
|
|
666
|
+
return await wdk.deriveAddress(chain);
|
|
667
|
+
} catch (error) {
|
|
668
|
+
console.warn(`Native WDK address derivation failed for ${chain}:`, error);
|
|
669
|
+
}
|
|
646
670
|
}
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
encoder.encode(value)
|
|
671
|
+
if (chain === "ethereum") {
|
|
672
|
+
return this.deriveBrowserAddress(seed, chain);
|
|
673
|
+
}
|
|
674
|
+
throw new Error(
|
|
675
|
+
`WDK API required for ${chain} address derivation. Ensure the backend is running.`
|
|
653
676
|
);
|
|
654
|
-
const combined = new Uint8Array(iv.length + encrypted.byteLength);
|
|
655
|
-
combined.set(iv);
|
|
656
|
-
combined.set(new Uint8Array(encrypted), iv.length);
|
|
657
|
-
const base64 = btoa(String.fromCharCode(...combined));
|
|
658
|
-
localStorage.setItem(`${this.storagePrefix}${key}`, base64);
|
|
659
677
|
}
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
678
|
+
/**
|
|
679
|
+
* Derive addresses for all supported chains using WDK API
|
|
680
|
+
*
|
|
681
|
+
* Uses the backend WDK API for real cryptographically valid addresses.
|
|
682
|
+
* No placeholder fallback - WDK API is required for multi-chain addresses.
|
|
683
|
+
*/
|
|
684
|
+
async deriveAllAddresses(seed) {
|
|
685
|
+
await this.initialize();
|
|
666
686
|
try {
|
|
667
|
-
const
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
}
|
|
686
|
-
async hasItem(key) {
|
|
687
|
-
return localStorage.getItem(`${this.storagePrefix}${key}`) !== null;
|
|
688
|
-
}
|
|
689
|
-
async clear() {
|
|
690
|
-
const keysToRemove = [];
|
|
691
|
-
for (let i = 0; i < localStorage.length; i++) {
|
|
692
|
-
const key = localStorage.key(i);
|
|
693
|
-
if (key?.startsWith(this.storagePrefix)) {
|
|
694
|
-
keysToRemove.push(key);
|
|
687
|
+
const response = await this.apiClient.deriveAllAddresses(seed, this.config.network);
|
|
688
|
+
if (response.success && response.addresses) {
|
|
689
|
+
const extractAddress = (value) => {
|
|
690
|
+
if (!value) return null;
|
|
691
|
+
if (typeof value === "string") return value;
|
|
692
|
+
if (typeof value === "object" && value !== null && "address" in value) {
|
|
693
|
+
return value.address;
|
|
694
|
+
}
|
|
695
|
+
return null;
|
|
696
|
+
};
|
|
697
|
+
return {
|
|
698
|
+
ethereum: extractAddress(response.addresses.ethereum),
|
|
699
|
+
bitcoin: extractAddress(response.addresses.bitcoin),
|
|
700
|
+
ton: extractAddress(response.addresses.ton),
|
|
701
|
+
tron: extractAddress(response.addresses.tron),
|
|
702
|
+
solana: extractAddress(response.addresses.solana),
|
|
703
|
+
spark: extractAddress(response.addresses.spark)
|
|
704
|
+
};
|
|
695
705
|
}
|
|
706
|
+
} catch (error) {
|
|
707
|
+
console.warn("API address derivation failed:", error);
|
|
696
708
|
}
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
async getItem(key) {
|
|
706
|
-
return this.storage.get(key) || null;
|
|
707
|
-
}
|
|
708
|
-
async removeItem(key) {
|
|
709
|
-
this.storage.delete(key);
|
|
710
|
-
}
|
|
711
|
-
async hasItem(key) {
|
|
712
|
-
return this.storage.has(key);
|
|
713
|
-
}
|
|
714
|
-
async clear() {
|
|
715
|
-
this.storage.clear();
|
|
716
|
-
}
|
|
717
|
-
};
|
|
718
|
-
function createSecureStorage() {
|
|
719
|
-
if (typeof global !== "undefined" && global.nativeModuleProxy !== void 0) {
|
|
720
|
-
const Platform = global.Platform;
|
|
721
|
-
if (Platform?.OS === "ios") {
|
|
722
|
-
return new KeychainStorageAdapter();
|
|
723
|
-
} else if (Platform?.OS === "android") {
|
|
724
|
-
return new KeystoreStorageAdapter();
|
|
709
|
+
if (this.useNativeWdk && this.nativeWdkService) {
|
|
710
|
+
try {
|
|
711
|
+
const wdk = this.nativeWdkService;
|
|
712
|
+
await wdk.initialize(seed);
|
|
713
|
+
return await wdk.deriveAllAddresses();
|
|
714
|
+
} catch (error) {
|
|
715
|
+
console.warn("Native WDK multi-chain derivation failed:", error);
|
|
716
|
+
}
|
|
725
717
|
}
|
|
718
|
+
throw new Error(
|
|
719
|
+
"Tether WDK API required for multi-chain address derivation. Service temporarily unavailable."
|
|
720
|
+
);
|
|
726
721
|
}
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
722
|
+
/**
|
|
723
|
+
* Get balances for all chains
|
|
724
|
+
*/
|
|
725
|
+
async getAllBalances(seed) {
|
|
726
|
+
await this.initialize();
|
|
727
|
+
try {
|
|
728
|
+
const response = await fetch(`${this.config.apiUrl}/api/wallets/wdk/balances`, {
|
|
729
|
+
method: "POST",
|
|
730
|
+
headers: { "Content-Type": "application/json" },
|
|
731
|
+
body: JSON.stringify({ seed, network: this.config.network })
|
|
732
|
+
});
|
|
733
|
+
if (response.ok) {
|
|
734
|
+
const data = await response.json();
|
|
735
|
+
if (data.success) {
|
|
736
|
+
return data.balances;
|
|
737
|
+
}
|
|
738
|
+
}
|
|
739
|
+
} catch (error) {
|
|
740
|
+
console.warn("Failed to fetch balances:", error);
|
|
741
|
+
}
|
|
742
|
+
return {};
|
|
741
743
|
}
|
|
742
744
|
/**
|
|
743
|
-
*
|
|
745
|
+
* Get fee rates for a chain
|
|
744
746
|
*/
|
|
745
|
-
async
|
|
747
|
+
async getFeeRates(seed, chain) {
|
|
748
|
+
await this.initialize();
|
|
746
749
|
try {
|
|
747
|
-
const response = await fetch(`${this.config.
|
|
750
|
+
const response = await fetch(`${this.config.apiUrl}/api/wallets/wdk/fee-rates`, {
|
|
748
751
|
method: "POST",
|
|
749
|
-
headers: {
|
|
750
|
-
|
|
751
|
-
}
|
|
752
|
+
headers: { "Content-Type": "application/json" },
|
|
753
|
+
body: JSON.stringify({ seed, chain, network: this.config.network })
|
|
752
754
|
});
|
|
753
|
-
|
|
755
|
+
if (response.ok) {
|
|
756
|
+
const data = await response.json();
|
|
757
|
+
if (data.success && data.feeRates) {
|
|
758
|
+
return data.feeRates;
|
|
759
|
+
}
|
|
760
|
+
}
|
|
754
761
|
} catch (error) {
|
|
755
|
-
|
|
756
|
-
success: false,
|
|
757
|
-
error: error instanceof Error ? error.message : "Failed to generate seed"
|
|
758
|
-
};
|
|
762
|
+
console.warn(`Failed to fetch fee rates for ${chain}:`, error);
|
|
759
763
|
}
|
|
764
|
+
return { slow: "0", normal: "0", fast: "0" };
|
|
760
765
|
}
|
|
761
766
|
/**
|
|
762
|
-
*
|
|
767
|
+
* Estimate transaction fee
|
|
763
768
|
*/
|
|
764
|
-
async
|
|
769
|
+
async estimateFee(seed, chain, to, amount) {
|
|
770
|
+
await this.initialize();
|
|
765
771
|
try {
|
|
766
|
-
const response = await fetch(`${this.config.
|
|
772
|
+
const response = await fetch(`${this.config.apiUrl}/api/wallets/wdk/estimate-fee`, {
|
|
767
773
|
method: "POST",
|
|
768
|
-
headers: {
|
|
769
|
-
|
|
770
|
-
},
|
|
771
|
-
body: JSON.stringify({ seed })
|
|
774
|
+
headers: { "Content-Type": "application/json" },
|
|
775
|
+
body: JSON.stringify({ seed, chain, to, amount, network: this.config.network })
|
|
772
776
|
});
|
|
773
|
-
|
|
777
|
+
if (response.ok) {
|
|
778
|
+
const data = await response.json();
|
|
779
|
+
if (data.success) {
|
|
780
|
+
return { fee: data.fee, symbol: data.symbol };
|
|
781
|
+
}
|
|
782
|
+
}
|
|
774
783
|
} catch (error) {
|
|
775
|
-
|
|
776
|
-
success: false,
|
|
777
|
-
error: error instanceof Error ? error.message : "Failed to validate seed"
|
|
778
|
-
};
|
|
784
|
+
console.warn(`Failed to estimate fee for ${chain}:`, error);
|
|
779
785
|
}
|
|
786
|
+
return { fee: "0", symbol: this.getChainSymbol(chain) };
|
|
780
787
|
}
|
|
781
788
|
/**
|
|
782
|
-
*
|
|
789
|
+
* Send a transaction
|
|
783
790
|
*/
|
|
784
|
-
async
|
|
791
|
+
async sendTransaction(seed, chain, to, amount) {
|
|
792
|
+
await this.initialize();
|
|
785
793
|
try {
|
|
786
|
-
const response = await fetch(`${this.config.
|
|
794
|
+
const response = await fetch(`${this.config.apiUrl}/api/wallets/wdk/send`, {
|
|
787
795
|
method: "POST",
|
|
788
|
-
headers: {
|
|
789
|
-
|
|
790
|
-
},
|
|
791
|
-
body: JSON.stringify({ seed, chain, network })
|
|
796
|
+
headers: { "Content-Type": "application/json" },
|
|
797
|
+
body: JSON.stringify({ seed, chain, to, amount, network: this.config.network })
|
|
792
798
|
});
|
|
793
|
-
|
|
799
|
+
if (response.ok) {
|
|
800
|
+
const data = await response.json();
|
|
801
|
+
let txHash = data.txHash || data.transactionHash || data.hash;
|
|
802
|
+
if (txHash && typeof txHash === "object" && "hash" in txHash) {
|
|
803
|
+
txHash = txHash.hash;
|
|
804
|
+
}
|
|
805
|
+
if (chain === "ethereum" && txHash && (typeof txHash !== "string" || !txHash.startsWith("0x") || txHash.length !== 66)) {
|
|
806
|
+
console.warn(`Invalid Ethereum tx hash format: ${txHash} (length: ${txHash?.length}, expected: 66)`);
|
|
807
|
+
}
|
|
808
|
+
return {
|
|
809
|
+
success: data.success,
|
|
810
|
+
txHash,
|
|
811
|
+
from: data.from,
|
|
812
|
+
to: data.to,
|
|
813
|
+
amount: data.amount,
|
|
814
|
+
chain: data.chain,
|
|
815
|
+
network: data.network
|
|
816
|
+
};
|
|
817
|
+
}
|
|
818
|
+
const errorData = await response.json().catch(() => ({}));
|
|
819
|
+
return {
|
|
820
|
+
success: false,
|
|
821
|
+
error: errorData.error || `HTTP ${response.status}`
|
|
822
|
+
};
|
|
794
823
|
} catch (error) {
|
|
795
824
|
return {
|
|
796
825
|
success: false,
|
|
797
|
-
error: error instanceof Error ? error.message : "
|
|
826
|
+
error: error instanceof Error ? error.message : "Transaction failed"
|
|
798
827
|
};
|
|
799
828
|
}
|
|
800
829
|
}
|
|
801
830
|
/**
|
|
802
|
-
*
|
|
831
|
+
* Get the network configuration
|
|
803
832
|
*/
|
|
804
|
-
|
|
833
|
+
getNetwork() {
|
|
834
|
+
return this.config.network;
|
|
835
|
+
}
|
|
836
|
+
/**
|
|
837
|
+
* Get API URL
|
|
838
|
+
*/
|
|
839
|
+
getApiUrl() {
|
|
840
|
+
return this.config.apiUrl;
|
|
841
|
+
}
|
|
842
|
+
// ==========================================
|
|
843
|
+
// Private Helper Methods
|
|
844
|
+
// ==========================================
|
|
845
|
+
getDerivationPath(chain) {
|
|
846
|
+
const paths = {
|
|
847
|
+
bitcoin: this.config.network === "testnet" ? "m/84'/1'/0'/0/0" : "m/84'/0'/0'/0/0",
|
|
848
|
+
ethereum: "m/44'/60'/0'/0/0",
|
|
849
|
+
ton: "m/44'/607'/0'/0'/0'",
|
|
850
|
+
tron: "m/44'/195'/0'/0/0",
|
|
851
|
+
solana: "m/44'/501'/0'/0'",
|
|
852
|
+
spark: "m/44'/998'/0'/0/0"
|
|
853
|
+
};
|
|
854
|
+
return paths[chain];
|
|
855
|
+
}
|
|
856
|
+
getChainSymbol(chain) {
|
|
857
|
+
const symbols = {
|
|
858
|
+
ethereum: "ETH",
|
|
859
|
+
bitcoin: "BTC",
|
|
860
|
+
ton: "TON",
|
|
861
|
+
tron: "TRX",
|
|
862
|
+
solana: "SOL",
|
|
863
|
+
spark: "SAT"
|
|
864
|
+
};
|
|
865
|
+
return symbols[chain];
|
|
866
|
+
}
|
|
867
|
+
/**
|
|
868
|
+
* Derive address using browser-compatible libraries
|
|
869
|
+
*/
|
|
870
|
+
async deriveBrowserAddress(seed, chain) {
|
|
871
|
+
const path = this.getDerivationPath(chain);
|
|
805
872
|
try {
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
873
|
+
let address;
|
|
874
|
+
switch (chain) {
|
|
875
|
+
case "ethereum":
|
|
876
|
+
address = deriveEthereumAddress(seed);
|
|
877
|
+
break;
|
|
878
|
+
case "bitcoin":
|
|
879
|
+
address = deriveBitcoinAddress(seed, this.config.network);
|
|
880
|
+
break;
|
|
881
|
+
case "tron":
|
|
882
|
+
address = deriveTronAddress(seed);
|
|
883
|
+
break;
|
|
884
|
+
case "spark":
|
|
885
|
+
address = deriveSparkAddress(seed, this.config.network);
|
|
886
|
+
break;
|
|
887
|
+
case "solana":
|
|
888
|
+
address = await deriveSolanaAddress(seed);
|
|
889
|
+
break;
|
|
890
|
+
case "ton":
|
|
891
|
+
address = await deriveTonAddress(seed);
|
|
892
|
+
break;
|
|
893
|
+
default:
|
|
894
|
+
throw new Error(`Unsupported chain: ${chain}`);
|
|
895
|
+
}
|
|
896
|
+
return { chain, address, path };
|
|
814
897
|
} catch (error) {
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
898
|
+
console.error(`Browser derivation failed for ${chain}:`, error);
|
|
899
|
+
throw error;
|
|
900
|
+
}
|
|
901
|
+
}
|
|
902
|
+
/**
|
|
903
|
+
* Derive all addresses using browser-compatible libraries
|
|
904
|
+
*/
|
|
905
|
+
async deriveAllBrowserAddresses(seed) {
|
|
906
|
+
return deriveAllAddresses(seed, this.config.network);
|
|
907
|
+
}
|
|
908
|
+
};
|
|
909
|
+
var defaultService = null;
|
|
910
|
+
function getZubariWdkService(config) {
|
|
911
|
+
if (!defaultService || config && config.network !== defaultService.getNetwork()) {
|
|
912
|
+
defaultService = new ZubariWdkService(config);
|
|
913
|
+
}
|
|
914
|
+
return defaultService;
|
|
915
|
+
}
|
|
916
|
+
|
|
917
|
+
// src/wallet/ZubariWallet.ts
|
|
918
|
+
var ZubariWallet = class {
|
|
919
|
+
seed;
|
|
920
|
+
config;
|
|
921
|
+
accounts = /* @__PURE__ */ new Map();
|
|
922
|
+
wdkService;
|
|
923
|
+
initialized = false;
|
|
924
|
+
constructor(seed, config) {
|
|
925
|
+
this.seed = seed;
|
|
926
|
+
this.config = {
|
|
927
|
+
network: config.network || "mainnet",
|
|
928
|
+
enabledNetworks: config.enabledNetworks || ["ethereum"],
|
|
929
|
+
gasless: config.gasless ?? false,
|
|
930
|
+
paymasterUrl: config.paymasterUrl,
|
|
931
|
+
bundlerUrl: config.bundlerUrl,
|
|
932
|
+
rpcUrls: config.rpcUrls
|
|
933
|
+
};
|
|
934
|
+
this.wdkService = getZubariWdkService({
|
|
935
|
+
network: this.config.network === "testnet" ? "testnet" : "mainnet",
|
|
936
|
+
forceApi: true
|
|
937
|
+
// Use backend API for all operations
|
|
938
|
+
});
|
|
939
|
+
}
|
|
940
|
+
/**
|
|
941
|
+
* Initialize the wallet by deriving accounts for all enabled networks
|
|
942
|
+
*/
|
|
943
|
+
async initialize() {
|
|
944
|
+
if (this.initialized) return;
|
|
945
|
+
for (const network of this.config.enabledNetworks) {
|
|
946
|
+
await this.deriveAccount(network);
|
|
947
|
+
}
|
|
948
|
+
this.initialized = true;
|
|
949
|
+
}
|
|
950
|
+
/**
|
|
951
|
+
* Derive account for a specific network using BIP-44 via WDK API
|
|
952
|
+
*/
|
|
953
|
+
async deriveAccount(network, index = 0) {
|
|
954
|
+
const basePath = DERIVATION_PATHS[network];
|
|
955
|
+
const derivationPath = `${basePath}/${index}`;
|
|
956
|
+
const chainMap = {
|
|
957
|
+
ethereum: "ethereum",
|
|
958
|
+
bitcoin: "bitcoin",
|
|
959
|
+
ton: "ton",
|
|
960
|
+
tron: "tron",
|
|
961
|
+
solana: "solana",
|
|
962
|
+
spark: "spark"
|
|
963
|
+
};
|
|
964
|
+
const chain = chainMap[network];
|
|
965
|
+
if (!chain) {
|
|
966
|
+
throw new Error(`Unsupported network: ${network}`);
|
|
967
|
+
}
|
|
968
|
+
try {
|
|
969
|
+
const result = await this.wdkService.deriveAddress(this.seed, chain);
|
|
970
|
+
const account = {
|
|
971
|
+
network,
|
|
972
|
+
address: result.address,
|
|
973
|
+
publicKey: "",
|
|
974
|
+
// WDK doesn't expose public key directly
|
|
975
|
+
derivationPath: result.path || derivationPath
|
|
818
976
|
};
|
|
977
|
+
this.accounts.set(network, account);
|
|
978
|
+
return account;
|
|
979
|
+
} catch (error) {
|
|
980
|
+
console.error(`Failed to derive account for ${network}:`, error);
|
|
981
|
+
throw new Error(`Failed to derive ${network} address: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
982
|
+
}
|
|
983
|
+
}
|
|
984
|
+
/**
|
|
985
|
+
* Get account for a specific network
|
|
986
|
+
*/
|
|
987
|
+
async getAccount(network, index = 0) {
|
|
988
|
+
const existing = this.accounts.get(network);
|
|
989
|
+
if (existing && existing.derivationPath.endsWith(`/${index}`)) {
|
|
990
|
+
return existing;
|
|
991
|
+
}
|
|
992
|
+
return this.deriveAccount(network, index);
|
|
993
|
+
}
|
|
994
|
+
/**
|
|
995
|
+
* Get address for a specific network
|
|
996
|
+
*/
|
|
997
|
+
async getAddress(network) {
|
|
998
|
+
const account = await this.getAccount(network);
|
|
999
|
+
return account.address;
|
|
1000
|
+
}
|
|
1001
|
+
/**
|
|
1002
|
+
* Get all addresses for enabled networks
|
|
1003
|
+
*/
|
|
1004
|
+
async getAllAddresses() {
|
|
1005
|
+
const addresses = {};
|
|
1006
|
+
for (const network of this.config.enabledNetworks) {
|
|
1007
|
+
addresses[network] = await this.getAddress(network);
|
|
819
1008
|
}
|
|
1009
|
+
return addresses;
|
|
820
1010
|
}
|
|
821
1011
|
/**
|
|
822
|
-
*
|
|
1012
|
+
* Get balance for a specific network via WDK API
|
|
823
1013
|
*/
|
|
824
|
-
async
|
|
1014
|
+
async getBalance(network) {
|
|
1015
|
+
const networkConfig = getNetworkConfig(network, this.config.network === "testnet");
|
|
1016
|
+
const chainMap = {
|
|
1017
|
+
ethereum: "ethereum",
|
|
1018
|
+
bitcoin: "bitcoin",
|
|
1019
|
+
ton: "ton",
|
|
1020
|
+
tron: "tron",
|
|
1021
|
+
solana: "solana",
|
|
1022
|
+
spark: "spark"
|
|
1023
|
+
};
|
|
1024
|
+
const chain = chainMap[network];
|
|
1025
|
+
if (!chain) {
|
|
1026
|
+
throw new Error(`Unsupported network: ${network}`);
|
|
1027
|
+
}
|
|
825
1028
|
try {
|
|
826
|
-
const
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
1029
|
+
const balances = await this.wdkService.getAllBalances(this.seed);
|
|
1030
|
+
const chainBalance = balances[chain];
|
|
1031
|
+
if (chainBalance) {
|
|
1032
|
+
const balanceValue = BigInt(chainBalance.balance || "0");
|
|
1033
|
+
const decimals = networkConfig.nativeCurrency.decimals;
|
|
1034
|
+
const balanceFormatted = this.formatBalance(balanceValue, decimals);
|
|
1035
|
+
return {
|
|
1036
|
+
network,
|
|
1037
|
+
native: {
|
|
1038
|
+
symbol: chainBalance.symbol || networkConfig.nativeCurrency.symbol,
|
|
1039
|
+
balance: balanceValue,
|
|
1040
|
+
balanceFormatted,
|
|
1041
|
+
balanceUsd: 0
|
|
1042
|
+
// TODO: Integrate price feed
|
|
1043
|
+
},
|
|
1044
|
+
tokens: []
|
|
1045
|
+
};
|
|
1046
|
+
}
|
|
1047
|
+
return {
|
|
1048
|
+
network,
|
|
1049
|
+
native: {
|
|
1050
|
+
symbol: networkConfig.nativeCurrency.symbol,
|
|
1051
|
+
balance: BigInt(0),
|
|
1052
|
+
balanceFormatted: "0",
|
|
1053
|
+
balanceUsd: 0
|
|
830
1054
|
},
|
|
831
|
-
|
|
832
|
-
}
|
|
833
|
-
return await response.json();
|
|
1055
|
+
tokens: []
|
|
1056
|
+
};
|
|
834
1057
|
} catch (error) {
|
|
1058
|
+
console.error(`Failed to get balance for ${network}:`, error);
|
|
835
1059
|
return {
|
|
836
|
-
|
|
837
|
-
|
|
1060
|
+
network,
|
|
1061
|
+
native: {
|
|
1062
|
+
symbol: networkConfig.nativeCurrency.symbol,
|
|
1063
|
+
balance: BigInt(0),
|
|
1064
|
+
balanceFormatted: "0",
|
|
1065
|
+
balanceUsd: 0
|
|
1066
|
+
},
|
|
1067
|
+
tokens: []
|
|
838
1068
|
};
|
|
839
1069
|
}
|
|
840
1070
|
}
|
|
841
1071
|
/**
|
|
842
|
-
*
|
|
843
|
-
* Fetches from blockchain explorers (Etherscan, mempool.space, etc.)
|
|
1072
|
+
* Format balance from wei/satoshi to human-readable string
|
|
844
1073
|
*/
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
body: JSON.stringify({ seed, chain, network, limit })
|
|
853
|
-
});
|
|
854
|
-
return await response.json();
|
|
855
|
-
} catch (error) {
|
|
856
|
-
return {
|
|
857
|
-
success: false,
|
|
858
|
-
error: error instanceof Error ? error.message : "Failed to get transaction history"
|
|
859
|
-
};
|
|
860
|
-
}
|
|
1074
|
+
formatBalance(balance, decimals) {
|
|
1075
|
+
if (balance === BigInt(0)) return "0";
|
|
1076
|
+
const divisor = BigInt(10 ** decimals);
|
|
1077
|
+
const integerPart = balance / divisor;
|
|
1078
|
+
const fractionalPart = balance % divisor;
|
|
1079
|
+
const fractionalStr = fractionalPart.toString().padStart(decimals, "0").slice(0, 6);
|
|
1080
|
+
return `${integerPart}.${fractionalStr}`.replace(/\.?0+$/, "") || "0";
|
|
861
1081
|
}
|
|
862
1082
|
/**
|
|
863
|
-
* Get
|
|
864
|
-
* Fetches from blockchain explorers to check confirmation status
|
|
1083
|
+
* Get balances for all enabled networks
|
|
865
1084
|
*/
|
|
866
|
-
async
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
headers: {
|
|
871
|
-
"Content-Type": "application/json"
|
|
872
|
-
},
|
|
873
|
-
body: JSON.stringify({ txHash, chain, network })
|
|
874
|
-
});
|
|
875
|
-
return await response.json();
|
|
876
|
-
} catch (error) {
|
|
877
|
-
return {
|
|
878
|
-
success: false,
|
|
879
|
-
error: error instanceof Error ? error.message : "Failed to get transaction status"
|
|
880
|
-
};
|
|
1085
|
+
async getAllBalances() {
|
|
1086
|
+
const balances = [];
|
|
1087
|
+
for (const network of this.config.enabledNetworks) {
|
|
1088
|
+
balances.push(await this.getBalance(network));
|
|
881
1089
|
}
|
|
1090
|
+
return balances;
|
|
882
1091
|
}
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
}
|
|
894
|
-
var DERIVATION_PATHS2 = {
|
|
895
|
-
ethereum: "m/44'/60'/0'/0/0",
|
|
896
|
-
bitcoin_mainnet: "m/84'/0'/0'/0/0",
|
|
897
|
-
bitcoin_testnet: "m/84'/1'/0'/0/0",
|
|
898
|
-
ton: "m/44'/607'/0'/0'/0'",
|
|
899
|
-
tron: "m/44'/195'/0'/0/0",
|
|
900
|
-
solana: "m/44'/501'/0'/0'",
|
|
901
|
-
spark: "m/44'/998'/0'/0/0"
|
|
902
|
-
};
|
|
903
|
-
function deriveEthereumAddress(seed) {
|
|
904
|
-
const hdNode = HDNodeWallet.fromPhrase(seed, void 0, DERIVATION_PATHS2.ethereum);
|
|
905
|
-
return hdNode.address;
|
|
906
|
-
}
|
|
907
|
-
function deriveBitcoinAddress(seed, network = "testnet") {
|
|
908
|
-
try {
|
|
909
|
-
const seedBytes = mnemonicToSeedSync(seed);
|
|
910
|
-
const hdKey = HDKey.fromMasterSeed(seedBytes);
|
|
911
|
-
const path = network === "testnet" ? DERIVATION_PATHS2.bitcoin_testnet : DERIVATION_PATHS2.bitcoin_mainnet;
|
|
912
|
-
const child = hdKey.derive(path);
|
|
913
|
-
if (!child.publicKey) {
|
|
914
|
-
throw new Error("Failed to derive public key");
|
|
1092
|
+
/**
|
|
1093
|
+
* Get total portfolio value in USD
|
|
1094
|
+
*/
|
|
1095
|
+
async getTotalPortfolioUsd() {
|
|
1096
|
+
const balances = await this.getAllBalances();
|
|
1097
|
+
let total = 0;
|
|
1098
|
+
for (const balance of balances) {
|
|
1099
|
+
total += balance.native.balanceUsd;
|
|
1100
|
+
for (const token of balance.tokens) {
|
|
1101
|
+
total += token.balanceUsd;
|
|
1102
|
+
}
|
|
915
1103
|
}
|
|
916
|
-
|
|
917
|
-
const witnessVersion = 0;
|
|
918
|
-
const words = bech32.toWords(pubKeyHash);
|
|
919
|
-
words.unshift(witnessVersion);
|
|
920
|
-
const hrp = network === "testnet" ? "tb" : "bc";
|
|
921
|
-
const address = bech32.encode(hrp, words);
|
|
922
|
-
return address;
|
|
923
|
-
} catch (error) {
|
|
924
|
-
console.error("Bitcoin address derivation failed:", error);
|
|
925
|
-
throw error;
|
|
926
|
-
}
|
|
927
|
-
}
|
|
928
|
-
async function deriveSolanaAddress(seed) {
|
|
929
|
-
try {
|
|
930
|
-
const [ed25519, nacl, bs58Module] = await Promise.all([
|
|
931
|
-
import('ed25519-hd-key'),
|
|
932
|
-
import('tweetnacl'),
|
|
933
|
-
import('bs58')
|
|
934
|
-
]);
|
|
935
|
-
const bs58 = bs58Module.default || bs58Module;
|
|
936
|
-
const seedBytes = mnemonicToSeedSync(seed);
|
|
937
|
-
const derived = ed25519.derivePath(DERIVATION_PATHS2.solana, Buffer.from(seedBytes).toString("hex"));
|
|
938
|
-
const keypair = nacl.sign.keyPair.fromSeed(new Uint8Array(derived.key));
|
|
939
|
-
return bs58.encode(keypair.publicKey);
|
|
940
|
-
} catch (error) {
|
|
941
|
-
console.error("Solana address derivation failed:", error);
|
|
942
|
-
throw error;
|
|
943
|
-
}
|
|
944
|
-
}
|
|
945
|
-
async function deriveTonAddress(seed) {
|
|
946
|
-
try {
|
|
947
|
-
const [ed25519, nacl] = await Promise.all([
|
|
948
|
-
import('ed25519-hd-key'),
|
|
949
|
-
import('tweetnacl')
|
|
950
|
-
]);
|
|
951
|
-
const seedBytes = mnemonicToSeedSync(seed);
|
|
952
|
-
const derived = ed25519.derivePath(DERIVATION_PATHS2.ton, Buffer.from(seedBytes).toString("hex"));
|
|
953
|
-
const keypair = nacl.sign.keyPair.fromSeed(new Uint8Array(derived.key));
|
|
954
|
-
const publicKey = keypair.publicKey;
|
|
955
|
-
const workchain = 0;
|
|
956
|
-
const flags = 17;
|
|
957
|
-
const hash = sha256(publicKey);
|
|
958
|
-
const addressData = new Uint8Array(34);
|
|
959
|
-
addressData[0] = flags;
|
|
960
|
-
addressData[1] = workchain;
|
|
961
|
-
addressData.set(hash, 2);
|
|
962
|
-
const crc = crc16(addressData);
|
|
963
|
-
const fullAddress = new Uint8Array(36);
|
|
964
|
-
fullAddress.set(addressData);
|
|
965
|
-
fullAddress[34] = crc >> 8 & 255;
|
|
966
|
-
fullAddress[35] = crc & 255;
|
|
967
|
-
const base64 = btoa(String.fromCharCode(...fullAddress)).replace(/\+/g, "-").replace(/\//g, "_");
|
|
968
|
-
return base64;
|
|
969
|
-
} catch (error) {
|
|
970
|
-
console.error("TON address derivation failed:", error);
|
|
971
|
-
throw error;
|
|
1104
|
+
return total;
|
|
972
1105
|
}
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
1106
|
+
/**
|
|
1107
|
+
* Send native currency on a specific network via WDK API
|
|
1108
|
+
*/
|
|
1109
|
+
async send(network, params) {
|
|
1110
|
+
const { to, amount } = params;
|
|
1111
|
+
const chainMap = {
|
|
1112
|
+
ethereum: "ethereum",
|
|
1113
|
+
bitcoin: "bitcoin",
|
|
1114
|
+
ton: "ton",
|
|
1115
|
+
tron: "tron",
|
|
1116
|
+
solana: "solana",
|
|
1117
|
+
spark: "spark"
|
|
1118
|
+
};
|
|
1119
|
+
const chain = chainMap[network];
|
|
1120
|
+
if (!chain) {
|
|
1121
|
+
throw new Error(`Unsupported network: ${network}`);
|
|
981
1122
|
}
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
1123
|
+
try {
|
|
1124
|
+
const result = await this.wdkService.sendTransaction(
|
|
1125
|
+
this.seed,
|
|
1126
|
+
chain,
|
|
1127
|
+
to,
|
|
1128
|
+
amount.toString()
|
|
1129
|
+
);
|
|
1130
|
+
if (result.success && result.txHash) {
|
|
1131
|
+
return {
|
|
1132
|
+
hash: result.txHash,
|
|
1133
|
+
network,
|
|
1134
|
+
status: "pending",
|
|
1135
|
+
metadata: {
|
|
1136
|
+
from: result.from,
|
|
1137
|
+
to: result.to,
|
|
1138
|
+
amount: result.amount
|
|
1139
|
+
}
|
|
1140
|
+
};
|
|
1141
|
+
}
|
|
1142
|
+
throw new Error(result.error || "Transaction failed");
|
|
1143
|
+
} catch (error) {
|
|
1144
|
+
console.error(`Failed to send on ${network}:`, error);
|
|
1145
|
+
throw new Error(`Transaction failed: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
993
1146
|
}
|
|
994
|
-
const tronBase58check = base58check(sha256);
|
|
995
|
-
return tronBase58check.encode(addressBytes);
|
|
996
|
-
} catch (error) {
|
|
997
|
-
console.error("TRON address derivation failed:", error);
|
|
998
|
-
throw error;
|
|
999
1147
|
}
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
const
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1148
|
+
/**
|
|
1149
|
+
* Send ERC-20 token on EVM networks via WDK API
|
|
1150
|
+
*/
|
|
1151
|
+
async sendToken(network, token, to, amount) {
|
|
1152
|
+
const networkConfig = getNetworkConfig(network, this.config.network === "testnet");
|
|
1153
|
+
if (!networkConfig.isEvm) {
|
|
1154
|
+
throw new Error(`sendToken is only supported on EVM networks. ${network} is not an EVM chain.`);
|
|
1155
|
+
}
|
|
1156
|
+
try {
|
|
1157
|
+
const apiUrl = this.wdkService.getApiUrl();
|
|
1158
|
+
const response = await fetch(`${apiUrl}/api/wallets/wdk/send-token`, {
|
|
1159
|
+
method: "POST",
|
|
1160
|
+
headers: { "Content-Type": "application/json" },
|
|
1161
|
+
body: JSON.stringify({
|
|
1162
|
+
seed: this.seed,
|
|
1163
|
+
chain: "ethereum",
|
|
1164
|
+
token,
|
|
1165
|
+
to,
|
|
1166
|
+
amount: amount.toString(),
|
|
1167
|
+
network: this.config.network
|
|
1168
|
+
})
|
|
1169
|
+
});
|
|
1170
|
+
const result = await response.json();
|
|
1171
|
+
if (result.success && result.txHash) {
|
|
1172
|
+
return {
|
|
1173
|
+
hash: result.txHash,
|
|
1174
|
+
network,
|
|
1175
|
+
status: "pending",
|
|
1176
|
+
metadata: {
|
|
1177
|
+
token,
|
|
1178
|
+
from: result.from,
|
|
1179
|
+
to: result.to,
|
|
1180
|
+
amount: result.amount
|
|
1181
|
+
}
|
|
1182
|
+
};
|
|
1183
|
+
}
|
|
1184
|
+
throw new Error(result.error || "Token transfer failed");
|
|
1185
|
+
} catch (error) {
|
|
1186
|
+
console.error(`Failed to send token on ${network}:`, error);
|
|
1187
|
+
throw new Error(`Token transfer failed: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
1008
1188
|
}
|
|
1009
|
-
const pubKeyHash = ripemd160(sha256(child.publicKey));
|
|
1010
|
-
const witnessVersion = 0;
|
|
1011
|
-
const words = bech32.toWords(pubKeyHash);
|
|
1012
|
-
words.unshift(witnessVersion);
|
|
1013
|
-
const hrp = network === "testnet" ? "tsp" : "sp";
|
|
1014
|
-
const address = bech32.encode(hrp, words);
|
|
1015
|
-
return address;
|
|
1016
|
-
} catch (error) {
|
|
1017
|
-
console.error("Spark address derivation failed:", error);
|
|
1018
|
-
throw error;
|
|
1019
|
-
}
|
|
1020
|
-
}
|
|
1021
|
-
async function deriveAllAddresses(seed, network = "testnet") {
|
|
1022
|
-
const addresses = {
|
|
1023
|
-
ethereum: null,
|
|
1024
|
-
bitcoin: null,
|
|
1025
|
-
ton: null,
|
|
1026
|
-
tron: null,
|
|
1027
|
-
solana: null,
|
|
1028
|
-
spark: null
|
|
1029
|
-
};
|
|
1030
|
-
try {
|
|
1031
|
-
addresses.ethereum = deriveEthereumAddress(seed);
|
|
1032
|
-
} catch (e) {
|
|
1033
|
-
console.error("ETH derivation failed:", e);
|
|
1034
1189
|
}
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1190
|
+
/**
|
|
1191
|
+
* Send Bitcoin on-chain using WalletManagerBtc
|
|
1192
|
+
* @param to - Destination address (bc1q... for native segwit)
|
|
1193
|
+
* @param amount - Amount in satoshis
|
|
1194
|
+
*/
|
|
1195
|
+
async sendBitcoin(to, amount) {
|
|
1196
|
+
if (!this.isValidBitcoinAddress(to)) {
|
|
1197
|
+
throw new Error("Invalid Bitcoin address. Expected bc1q... (native segwit) format.");
|
|
1198
|
+
}
|
|
1199
|
+
return this.send("bitcoin", { to, amount });
|
|
1039
1200
|
}
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1201
|
+
/**
|
|
1202
|
+
* Validate Bitcoin address format
|
|
1203
|
+
*/
|
|
1204
|
+
isValidBitcoinAddress(address) {
|
|
1205
|
+
if (address.startsWith("bc1q") || address.startsWith("tb1q")) {
|
|
1206
|
+
return address.length >= 42 && address.length <= 62;
|
|
1207
|
+
}
|
|
1208
|
+
if (address.startsWith("1") || address.startsWith("m") || address.startsWith("n")) {
|
|
1209
|
+
return address.length >= 25 && address.length <= 34;
|
|
1210
|
+
}
|
|
1211
|
+
if (address.startsWith("3") || address.startsWith("2")) {
|
|
1212
|
+
return address.length >= 25 && address.length <= 34;
|
|
1213
|
+
}
|
|
1214
|
+
return false;
|
|
1044
1215
|
}
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1216
|
+
/**
|
|
1217
|
+
* Get Bitcoin address (native segwit bc1q...)
|
|
1218
|
+
*/
|
|
1219
|
+
async getBitcoinAddress() {
|
|
1220
|
+
const account = await this.getAccount("bitcoin");
|
|
1221
|
+
return account.address;
|
|
1049
1222
|
}
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1223
|
+
/**
|
|
1224
|
+
* Pay Lightning invoice via Spark network using WDK API
|
|
1225
|
+
* Uses WDK WalletManagerSpark (m/44'/998')
|
|
1226
|
+
* @param invoice - Lightning invoice string (lnbc...)
|
|
1227
|
+
*/
|
|
1228
|
+
async sendLightning(invoice) {
|
|
1229
|
+
if (!this.isValidLightningInvoice(invoice)) {
|
|
1230
|
+
throw new Error("Invalid Lightning invoice format. Expected lnbc... or lntb...");
|
|
1231
|
+
}
|
|
1232
|
+
const invoiceDetails = await this.decodeLightningInvoice(invoice);
|
|
1233
|
+
try {
|
|
1234
|
+
const apiUrl = this.wdkService.getApiUrl();
|
|
1235
|
+
const response = await fetch(`${apiUrl}/api/wallets/wdk/lightning/pay`, {
|
|
1236
|
+
method: "POST",
|
|
1237
|
+
headers: { "Content-Type": "application/json" },
|
|
1238
|
+
body: JSON.stringify({
|
|
1239
|
+
seed: this.seed,
|
|
1240
|
+
invoice,
|
|
1241
|
+
network: this.config.network
|
|
1242
|
+
})
|
|
1243
|
+
});
|
|
1244
|
+
const result = await response.json();
|
|
1245
|
+
if (result.success && result.paymentHash) {
|
|
1246
|
+
return {
|
|
1247
|
+
hash: result.paymentHash,
|
|
1248
|
+
network: "spark",
|
|
1249
|
+
status: "pending",
|
|
1250
|
+
metadata: {
|
|
1251
|
+
invoice,
|
|
1252
|
+
amount: invoiceDetails.amount,
|
|
1253
|
+
destination: invoiceDetails.destination,
|
|
1254
|
+
preimage: result.preimage
|
|
1255
|
+
}
|
|
1256
|
+
};
|
|
1257
|
+
}
|
|
1258
|
+
throw new Error(result.error || "Lightning payment failed");
|
|
1259
|
+
} catch (error) {
|
|
1260
|
+
console.error("Failed to send Lightning payment:", error);
|
|
1261
|
+
throw new Error(`Lightning payment failed: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
1262
|
+
}
|
|
1058
1263
|
}
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1264
|
+
/**
|
|
1265
|
+
* Create Lightning invoice via Spark using WDK API
|
|
1266
|
+
* @param amount - Amount in millisatoshis
|
|
1267
|
+
* @param memo - Optional payment memo
|
|
1268
|
+
* @returns Lightning invoice string (lnbc...)
|
|
1269
|
+
*/
|
|
1270
|
+
async createLightningInvoice(amount, memo) {
|
|
1271
|
+
if (amount <= BigInt(0)) {
|
|
1272
|
+
throw new Error("Invoice amount must be greater than 0");
|
|
1273
|
+
}
|
|
1274
|
+
try {
|
|
1275
|
+
const apiUrl = this.wdkService.getApiUrl();
|
|
1276
|
+
const response = await fetch(`${apiUrl}/api/wallets/wdk/lightning/invoice`, {
|
|
1277
|
+
method: "POST",
|
|
1278
|
+
headers: { "Content-Type": "application/json" },
|
|
1279
|
+
body: JSON.stringify({
|
|
1280
|
+
seed: this.seed,
|
|
1281
|
+
amount: amount.toString(),
|
|
1282
|
+
memo: memo || "",
|
|
1283
|
+
network: this.config.network
|
|
1284
|
+
})
|
|
1285
|
+
});
|
|
1286
|
+
const result = await response.json();
|
|
1287
|
+
if (result.success && result.invoice) {
|
|
1288
|
+
return result.invoice;
|
|
1289
|
+
}
|
|
1290
|
+
throw new Error(result.error || "Failed to create invoice");
|
|
1291
|
+
} catch (error) {
|
|
1292
|
+
console.error("Failed to create Lightning invoice:", error);
|
|
1293
|
+
throw new Error(`Failed to create invoice: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
1294
|
+
}
|
|
1063
1295
|
}
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
// src/services/ZubariWdkService.ts
|
|
1074
|
-
var DEFAULT_API_URL2 = "https://ckgwifsxka.us-east-2.awsapprunner.com";
|
|
1075
|
-
function isBrowser() {
|
|
1076
|
-
return typeof window !== "undefined" && typeof window.document !== "undefined";
|
|
1077
|
-
}
|
|
1078
|
-
var dynamicImport = new Function("specifier", "return import(specifier)");
|
|
1079
|
-
async function canUseNativeWdk() {
|
|
1080
|
-
if (isBrowser()) {
|
|
1081
|
-
return false;
|
|
1296
|
+
/**
|
|
1297
|
+
* Validate Lightning invoice format
|
|
1298
|
+
*/
|
|
1299
|
+
isValidLightningInvoice(invoice) {
|
|
1300
|
+
const lowerInvoice = invoice.toLowerCase();
|
|
1301
|
+
return lowerInvoice.startsWith("lnbc") || // Mainnet
|
|
1302
|
+
lowerInvoice.startsWith("lntb") || // Testnet
|
|
1303
|
+
lowerInvoice.startsWith("lnbcrt");
|
|
1082
1304
|
}
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1305
|
+
/**
|
|
1306
|
+
* Decode Lightning invoice using backend API
|
|
1307
|
+
* Parses BOLT11 invoice to extract payment details
|
|
1308
|
+
*/
|
|
1309
|
+
async decodeLightningInvoice(invoice) {
|
|
1310
|
+
try {
|
|
1311
|
+
const apiUrl = this.wdkService.getApiUrl();
|
|
1312
|
+
const response = await fetch(`${apiUrl}/api/wallets/wdk/lightning/decode`, {
|
|
1313
|
+
method: "POST",
|
|
1314
|
+
headers: { "Content-Type": "application/json" },
|
|
1315
|
+
body: JSON.stringify({ invoice })
|
|
1316
|
+
});
|
|
1317
|
+
const result = await response.json();
|
|
1318
|
+
if (result.success) {
|
|
1319
|
+
return {
|
|
1320
|
+
amount: BigInt(result.amount || 0),
|
|
1321
|
+
destination: result.destination || "",
|
|
1322
|
+
expiry: result.expiry || 3600,
|
|
1323
|
+
description: result.description
|
|
1324
|
+
};
|
|
1325
|
+
}
|
|
1326
|
+
return this.parseInvoiceBasic(invoice);
|
|
1327
|
+
} catch (error) {
|
|
1328
|
+
console.warn("Failed to decode invoice via API, using basic parsing:", error);
|
|
1329
|
+
return this.parseInvoiceBasic(invoice);
|
|
1330
|
+
}
|
|
1088
1331
|
}
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1332
|
+
/**
|
|
1333
|
+
* Basic Lightning invoice parsing (fallback)
|
|
1334
|
+
* Extracts amount from invoice prefix when API is unavailable
|
|
1335
|
+
*/
|
|
1336
|
+
parseInvoiceBasic(invoice) {
|
|
1337
|
+
const lowerInvoice = invoice.toLowerCase();
|
|
1338
|
+
const amountMatch = lowerInvoice.match(/^ln(?:bc|tb|bcrt)(\d+)([munp])?/);
|
|
1339
|
+
let amount = BigInt(0);
|
|
1340
|
+
if (amountMatch && amountMatch[1]) {
|
|
1341
|
+
const value = BigInt(amountMatch[1]);
|
|
1342
|
+
const multiplier = amountMatch[2];
|
|
1343
|
+
switch (multiplier) {
|
|
1344
|
+
case "m":
|
|
1345
|
+
amount = value * BigInt(1e8);
|
|
1346
|
+
break;
|
|
1347
|
+
// milli-BTC
|
|
1348
|
+
case "u":
|
|
1349
|
+
amount = value * BigInt(1e5);
|
|
1350
|
+
break;
|
|
1351
|
+
// micro-BTC
|
|
1352
|
+
case "n":
|
|
1353
|
+
amount = value * BigInt(100);
|
|
1354
|
+
break;
|
|
1355
|
+
// nano-BTC
|
|
1356
|
+
case "p":
|
|
1357
|
+
amount = value / BigInt(10);
|
|
1358
|
+
break;
|
|
1359
|
+
// pico-BTC
|
|
1360
|
+
default:
|
|
1361
|
+
amount = value * BigInt(1e11);
|
|
1362
|
+
break;
|
|
1363
|
+
}
|
|
1364
|
+
}
|
|
1365
|
+
return {
|
|
1366
|
+
amount,
|
|
1367
|
+
destination: "",
|
|
1368
|
+
expiry: 3600
|
|
1102
1369
|
};
|
|
1103
|
-
this.apiClient = getWdkApiClient(this.config.apiUrl);
|
|
1104
1370
|
}
|
|
1105
1371
|
/**
|
|
1106
|
-
*
|
|
1372
|
+
* Get Lightning (Spark) balance via WDK API
|
|
1107
1373
|
*/
|
|
1108
|
-
async
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
try {
|
|
1117
|
-
const WdkServiceModule = await dynamicImport("./WdkService");
|
|
1118
|
-
const WdkService = WdkServiceModule.WdkService || WdkServiceModule.default;
|
|
1119
|
-
this.nativeWdkService = new WdkService({
|
|
1374
|
+
async getLightningBalance() {
|
|
1375
|
+
try {
|
|
1376
|
+
const apiUrl = this.wdkService.getApiUrl();
|
|
1377
|
+
const response = await fetch(`${apiUrl}/api/wallets/wdk/lightning/balance`, {
|
|
1378
|
+
method: "POST",
|
|
1379
|
+
headers: { "Content-Type": "application/json" },
|
|
1380
|
+
body: JSON.stringify({
|
|
1381
|
+
seed: this.seed,
|
|
1120
1382
|
network: this.config.network
|
|
1121
|
-
})
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1383
|
+
})
|
|
1384
|
+
});
|
|
1385
|
+
const result = await response.json();
|
|
1386
|
+
if (result.success) {
|
|
1387
|
+
return {
|
|
1388
|
+
available: BigInt(result.available || 0),
|
|
1389
|
+
pending: BigInt(result.pending || 0)
|
|
1390
|
+
};
|
|
1126
1391
|
}
|
|
1392
|
+
return { available: BigInt(0), pending: BigInt(0) };
|
|
1393
|
+
} catch (error) {
|
|
1394
|
+
console.error("Failed to get Lightning balance:", error);
|
|
1395
|
+
return { available: BigInt(0), pending: BigInt(0) };
|
|
1127
1396
|
}
|
|
1128
|
-
this.initialized = true;
|
|
1129
1397
|
}
|
|
1130
1398
|
/**
|
|
1131
|
-
* Get
|
|
1399
|
+
* Get configuration
|
|
1400
|
+
*/
|
|
1401
|
+
getConfig() {
|
|
1402
|
+
return { ...this.config };
|
|
1403
|
+
}
|
|
1404
|
+
/**
|
|
1405
|
+
* Check if wallet is initialized
|
|
1406
|
+
*/
|
|
1407
|
+
isInitialized() {
|
|
1408
|
+
return this.initialized;
|
|
1409
|
+
}
|
|
1410
|
+
/**
|
|
1411
|
+
* Get contract addresses for current network
|
|
1412
|
+
*/
|
|
1413
|
+
getContractAddresses() {
|
|
1414
|
+
return getContractAddresses(this.config.network);
|
|
1415
|
+
}
|
|
1416
|
+
};
|
|
1417
|
+
|
|
1418
|
+
// src/security/KeyManager.ts
|
|
1419
|
+
var KeyManager = class {
|
|
1420
|
+
static ALGORITHM = "AES-GCM";
|
|
1421
|
+
static KEY_LENGTH = 256;
|
|
1422
|
+
static IV_LENGTH = 12;
|
|
1423
|
+
static SALT_LENGTH = 16;
|
|
1424
|
+
static PBKDF2_ITERATIONS = 1e5;
|
|
1425
|
+
/**
|
|
1426
|
+
* Encrypt a seed phrase with a password
|
|
1427
|
+
*/
|
|
1428
|
+
static async encryptSeed(seed, password) {
|
|
1429
|
+
const encoder = new TextEncoder();
|
|
1430
|
+
const seedData = encoder.encode(seed);
|
|
1431
|
+
const salt = crypto.getRandomValues(new Uint8Array(this.SALT_LENGTH));
|
|
1432
|
+
const iv = crypto.getRandomValues(new Uint8Array(this.IV_LENGTH));
|
|
1433
|
+
const key = await this.deriveKey(password, salt);
|
|
1434
|
+
const encrypted = await crypto.subtle.encrypt(
|
|
1435
|
+
{ name: this.ALGORITHM, iv },
|
|
1436
|
+
key,
|
|
1437
|
+
seedData
|
|
1438
|
+
);
|
|
1439
|
+
const combined = new Uint8Array(salt.length + iv.length + encrypted.byteLength);
|
|
1440
|
+
combined.set(salt, 0);
|
|
1441
|
+
combined.set(iv, salt.length);
|
|
1442
|
+
combined.set(new Uint8Array(encrypted), salt.length + iv.length);
|
|
1443
|
+
return btoa(String.fromCharCode(...combined));
|
|
1444
|
+
}
|
|
1445
|
+
/**
|
|
1446
|
+
* Decrypt a seed phrase with a password
|
|
1447
|
+
*/
|
|
1448
|
+
static async decryptSeed(encryptedData, password) {
|
|
1449
|
+
const combined = new Uint8Array(
|
|
1450
|
+
atob(encryptedData).split("").map((c) => c.charCodeAt(0))
|
|
1451
|
+
);
|
|
1452
|
+
const salt = combined.slice(0, this.SALT_LENGTH);
|
|
1453
|
+
const iv = combined.slice(this.SALT_LENGTH, this.SALT_LENGTH + this.IV_LENGTH);
|
|
1454
|
+
const encrypted = combined.slice(this.SALT_LENGTH + this.IV_LENGTH);
|
|
1455
|
+
const key = await this.deriveKey(password, salt);
|
|
1456
|
+
const decrypted = await crypto.subtle.decrypt(
|
|
1457
|
+
{ name: this.ALGORITHM, iv },
|
|
1458
|
+
key,
|
|
1459
|
+
encrypted
|
|
1460
|
+
);
|
|
1461
|
+
const decoder = new TextDecoder();
|
|
1462
|
+
return decoder.decode(decrypted);
|
|
1463
|
+
}
|
|
1464
|
+
/**
|
|
1465
|
+
* Derive encryption key from password using PBKDF2
|
|
1132
1466
|
*/
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1467
|
+
static async deriveKey(password, salt) {
|
|
1468
|
+
const encoder = new TextEncoder();
|
|
1469
|
+
const passwordData = encoder.encode(password);
|
|
1470
|
+
const keyMaterial = await crypto.subtle.importKey(
|
|
1471
|
+
"raw",
|
|
1472
|
+
passwordData,
|
|
1473
|
+
"PBKDF2",
|
|
1474
|
+
false,
|
|
1475
|
+
["deriveKey"]
|
|
1476
|
+
);
|
|
1477
|
+
return crypto.subtle.deriveKey(
|
|
1478
|
+
{
|
|
1479
|
+
name: "PBKDF2",
|
|
1480
|
+
salt: salt.buffer.slice(salt.byteOffset, salt.byteOffset + salt.byteLength),
|
|
1481
|
+
iterations: this.PBKDF2_ITERATIONS,
|
|
1482
|
+
hash: "SHA-256"
|
|
1483
|
+
},
|
|
1484
|
+
keyMaterial,
|
|
1485
|
+
{ name: this.ALGORITHM, length: this.KEY_LENGTH },
|
|
1486
|
+
false,
|
|
1487
|
+
["encrypt", "decrypt"]
|
|
1488
|
+
);
|
|
1137
1489
|
}
|
|
1138
1490
|
/**
|
|
1139
|
-
*
|
|
1491
|
+
* Validate a BIP-39 seed phrase (basic validation)
|
|
1140
1492
|
*/
|
|
1141
|
-
|
|
1142
|
-
|
|
1493
|
+
static validateSeedPhrase(seed) {
|
|
1494
|
+
const words = seed.trim().split(/\s+/);
|
|
1495
|
+
const validWordCounts = [12, 15, 18, 21, 24];
|
|
1496
|
+
return validWordCounts.includes(words.length);
|
|
1143
1497
|
}
|
|
1144
1498
|
/**
|
|
1145
|
-
* Generate a
|
|
1499
|
+
* Generate a random encryption key (for backup purposes)
|
|
1146
1500
|
*/
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1501
|
+
static generateBackupKey() {
|
|
1502
|
+
const bytes = crypto.getRandomValues(new Uint8Array(32));
|
|
1503
|
+
return Array.from(bytes).map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
1504
|
+
}
|
|
1505
|
+
};
|
|
1506
|
+
|
|
1507
|
+
// src/storage/SecureStorage.ts
|
|
1508
|
+
var KeychainStorageAdapter = class {
|
|
1509
|
+
serviceName;
|
|
1510
|
+
constructor(serviceName = "com.zubari.wallet") {
|
|
1511
|
+
this.serviceName = serviceName;
|
|
1512
|
+
}
|
|
1513
|
+
async setItem(key, value) {
|
|
1514
|
+
if (typeof global !== "undefined" && global.KeychainModule) {
|
|
1515
|
+
await global.KeychainModule.setItem(this.serviceName, key, value);
|
|
1516
|
+
} else {
|
|
1517
|
+
throw new Error("Keychain not available on this platform");
|
|
1156
1518
|
}
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
} catch (error) {
|
|
1162
|
-
console.warn("Native WDK seed generation failed:", error);
|
|
1163
|
-
}
|
|
1519
|
+
}
|
|
1520
|
+
async getItem(key) {
|
|
1521
|
+
if (typeof global !== "undefined" && global.KeychainModule) {
|
|
1522
|
+
return global.KeychainModule.getItem(this.serviceName, key);
|
|
1164
1523
|
}
|
|
1165
|
-
|
|
1524
|
+
throw new Error("Keychain not available on this platform");
|
|
1166
1525
|
}
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
try {
|
|
1173
|
-
const response = await this.apiClient.validateSeed(seed);
|
|
1174
|
-
if (response.success) {
|
|
1175
|
-
return response.isValid ?? false;
|
|
1176
|
-
}
|
|
1177
|
-
} catch (error) {
|
|
1178
|
-
console.warn("API seed validation failed:", error);
|
|
1526
|
+
async removeItem(key) {
|
|
1527
|
+
if (typeof global !== "undefined" && global.KeychainModule) {
|
|
1528
|
+
await global.KeychainModule.removeItem(this.serviceName, key);
|
|
1529
|
+
} else {
|
|
1530
|
+
throw new Error("Keychain not available on this platform");
|
|
1179
1531
|
}
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1532
|
+
}
|
|
1533
|
+
async hasItem(key) {
|
|
1534
|
+
const value = await this.getItem(key);
|
|
1535
|
+
return value !== null;
|
|
1536
|
+
}
|
|
1537
|
+
async clear() {
|
|
1538
|
+
if (typeof global !== "undefined" && global.KeychainModule) {
|
|
1539
|
+
await global.KeychainModule.clear(this.serviceName);
|
|
1540
|
+
} else {
|
|
1541
|
+
throw new Error("Keychain not available on this platform");
|
|
1187
1542
|
}
|
|
1188
|
-
return isValidSeed(seed);
|
|
1189
1543
|
}
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
async
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
if (response.success && response.address) {
|
|
1202
|
-
return {
|
|
1203
|
-
chain,
|
|
1204
|
-
address: response.address,
|
|
1205
|
-
path: response.path || path
|
|
1206
|
-
};
|
|
1207
|
-
}
|
|
1208
|
-
} catch (error) {
|
|
1209
|
-
console.warn(`API address derivation failed for ${chain}:`, error);
|
|
1210
|
-
if (chain === "ethereum") {
|
|
1211
|
-
return this.deriveBrowserAddress(seed, chain);
|
|
1212
|
-
}
|
|
1544
|
+
};
|
|
1545
|
+
var KeystoreStorageAdapter = class {
|
|
1546
|
+
alias;
|
|
1547
|
+
constructor(alias = "zubari_wallet_keys") {
|
|
1548
|
+
this.alias = alias;
|
|
1549
|
+
}
|
|
1550
|
+
async setItem(key, value) {
|
|
1551
|
+
if (typeof global !== "undefined" && global.KeystoreModule) {
|
|
1552
|
+
await global.KeystoreModule.setItem(this.alias, key, value);
|
|
1553
|
+
} else {
|
|
1554
|
+
throw new Error("Keystore not available on this platform");
|
|
1213
1555
|
}
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
return await wdk.deriveAddress(chain);
|
|
1219
|
-
} catch (error) {
|
|
1220
|
-
console.warn(`Native WDK address derivation failed for ${chain}:`, error);
|
|
1221
|
-
}
|
|
1556
|
+
}
|
|
1557
|
+
async getItem(key) {
|
|
1558
|
+
if (typeof global !== "undefined" && global.KeystoreModule) {
|
|
1559
|
+
return global.KeystoreModule.getItem(this.alias, key);
|
|
1222
1560
|
}
|
|
1223
|
-
|
|
1224
|
-
|
|
1561
|
+
throw new Error("Keystore not available on this platform");
|
|
1562
|
+
}
|
|
1563
|
+
async removeItem(key) {
|
|
1564
|
+
if (typeof global !== "undefined" && global.KeystoreModule) {
|
|
1565
|
+
await global.KeystoreModule.removeItem(this.alias, key);
|
|
1566
|
+
} else {
|
|
1567
|
+
throw new Error("Keystore not available on this platform");
|
|
1568
|
+
}
|
|
1569
|
+
}
|
|
1570
|
+
async hasItem(key) {
|
|
1571
|
+
const value = await this.getItem(key);
|
|
1572
|
+
return value !== null;
|
|
1573
|
+
}
|
|
1574
|
+
async clear() {
|
|
1575
|
+
if (typeof global !== "undefined" && global.KeystoreModule) {
|
|
1576
|
+
await global.KeystoreModule.clear(this.alias);
|
|
1577
|
+
} else {
|
|
1578
|
+
throw new Error("Keystore not available on this platform");
|
|
1225
1579
|
}
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1580
|
+
}
|
|
1581
|
+
};
|
|
1582
|
+
var WebEncryptedStorageAdapter = class {
|
|
1583
|
+
encryptionKey = null;
|
|
1584
|
+
storagePrefix;
|
|
1585
|
+
constructor(storagePrefix = "zubari_") {
|
|
1586
|
+
this.storagePrefix = storagePrefix;
|
|
1229
1587
|
}
|
|
1230
1588
|
/**
|
|
1231
|
-
*
|
|
1232
|
-
*
|
|
1233
|
-
* Uses the backend WDK API for real cryptographically valid addresses.
|
|
1234
|
-
* No placeholder fallback - WDK API is required for multi-chain addresses.
|
|
1589
|
+
* Initialize with a password-derived key
|
|
1235
1590
|
*/
|
|
1236
|
-
async
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
}
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
} catch (error) {
|
|
1267
|
-
console.warn("Native WDK multi-chain derivation failed:", error);
|
|
1268
|
-
}
|
|
1591
|
+
async initialize(password) {
|
|
1592
|
+
const encoder = new TextEncoder();
|
|
1593
|
+
const salt = this.getSalt();
|
|
1594
|
+
const keyMaterial = await crypto.subtle.importKey(
|
|
1595
|
+
"raw",
|
|
1596
|
+
encoder.encode(password),
|
|
1597
|
+
"PBKDF2",
|
|
1598
|
+
false,
|
|
1599
|
+
["deriveKey"]
|
|
1600
|
+
);
|
|
1601
|
+
this.encryptionKey = await crypto.subtle.deriveKey(
|
|
1602
|
+
{
|
|
1603
|
+
name: "PBKDF2",
|
|
1604
|
+
salt: salt.buffer,
|
|
1605
|
+
iterations: 1e5,
|
|
1606
|
+
hash: "SHA-256"
|
|
1607
|
+
},
|
|
1608
|
+
keyMaterial,
|
|
1609
|
+
{ name: "AES-GCM", length: 256 },
|
|
1610
|
+
false,
|
|
1611
|
+
["encrypt", "decrypt"]
|
|
1612
|
+
);
|
|
1613
|
+
}
|
|
1614
|
+
getSalt() {
|
|
1615
|
+
const saltKey = `${this.storagePrefix}salt`;
|
|
1616
|
+
let saltHex = localStorage.getItem(saltKey);
|
|
1617
|
+
if (!saltHex) {
|
|
1618
|
+
const salt = crypto.getRandomValues(new Uint8Array(16));
|
|
1619
|
+
saltHex = Array.from(salt).map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
1620
|
+
localStorage.setItem(saltKey, saltHex);
|
|
1269
1621
|
}
|
|
1270
|
-
|
|
1271
|
-
|
|
1622
|
+
return new Uint8Array(
|
|
1623
|
+
saltHex.match(/.{1,2}/g).map((byte) => parseInt(byte, 16))
|
|
1272
1624
|
);
|
|
1273
1625
|
}
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
async getAllBalances(seed) {
|
|
1278
|
-
await this.initialize();
|
|
1279
|
-
try {
|
|
1280
|
-
const response = await fetch(`${this.config.apiUrl}/api/wallets/wdk/balances`, {
|
|
1281
|
-
method: "POST",
|
|
1282
|
-
headers: { "Content-Type": "application/json" },
|
|
1283
|
-
body: JSON.stringify({ seed, network: this.config.network })
|
|
1284
|
-
});
|
|
1285
|
-
if (response.ok) {
|
|
1286
|
-
const data = await response.json();
|
|
1287
|
-
if (data.success) {
|
|
1288
|
-
return data.balances;
|
|
1289
|
-
}
|
|
1290
|
-
}
|
|
1291
|
-
} catch (error) {
|
|
1292
|
-
console.warn("Failed to fetch balances:", error);
|
|
1626
|
+
async setItem(key, value) {
|
|
1627
|
+
if (!this.encryptionKey) {
|
|
1628
|
+
throw new Error("Storage not initialized. Call initialize() first.");
|
|
1293
1629
|
}
|
|
1294
|
-
|
|
1630
|
+
const encoder = new TextEncoder();
|
|
1631
|
+
const iv = crypto.getRandomValues(new Uint8Array(12));
|
|
1632
|
+
const encrypted = await crypto.subtle.encrypt(
|
|
1633
|
+
{ name: "AES-GCM", iv },
|
|
1634
|
+
this.encryptionKey,
|
|
1635
|
+
encoder.encode(value)
|
|
1636
|
+
);
|
|
1637
|
+
const combined = new Uint8Array(iv.length + encrypted.byteLength);
|
|
1638
|
+
combined.set(iv);
|
|
1639
|
+
combined.set(new Uint8Array(encrypted), iv.length);
|
|
1640
|
+
const base64 = btoa(String.fromCharCode(...combined));
|
|
1641
|
+
localStorage.setItem(`${this.storagePrefix}${key}`, base64);
|
|
1295
1642
|
}
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
async getFeeRates(seed, chain) {
|
|
1300
|
-
await this.initialize();
|
|
1301
|
-
try {
|
|
1302
|
-
const response = await fetch(`${this.config.apiUrl}/api/wallets/wdk/fee-rates`, {
|
|
1303
|
-
method: "POST",
|
|
1304
|
-
headers: { "Content-Type": "application/json" },
|
|
1305
|
-
body: JSON.stringify({ seed, chain, network: this.config.network })
|
|
1306
|
-
});
|
|
1307
|
-
if (response.ok) {
|
|
1308
|
-
const data = await response.json();
|
|
1309
|
-
if (data.success && data.feeRates) {
|
|
1310
|
-
return data.feeRates;
|
|
1311
|
-
}
|
|
1312
|
-
}
|
|
1313
|
-
} catch (error) {
|
|
1314
|
-
console.warn(`Failed to fetch fee rates for ${chain}:`, error);
|
|
1643
|
+
async getItem(key) {
|
|
1644
|
+
if (!this.encryptionKey) {
|
|
1645
|
+
throw new Error("Storage not initialized. Call initialize() first.");
|
|
1315
1646
|
}
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
/**
|
|
1319
|
-
* Estimate transaction fee
|
|
1320
|
-
*/
|
|
1321
|
-
async estimateFee(seed, chain, to, amount) {
|
|
1322
|
-
await this.initialize();
|
|
1647
|
+
const base64 = localStorage.getItem(`${this.storagePrefix}${key}`);
|
|
1648
|
+
if (!base64) return null;
|
|
1323
1649
|
try {
|
|
1324
|
-
const
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1650
|
+
const combined = new Uint8Array(
|
|
1651
|
+
atob(base64).split("").map((c) => c.charCodeAt(0))
|
|
1652
|
+
);
|
|
1653
|
+
const iv = combined.slice(0, 12);
|
|
1654
|
+
const encrypted = combined.slice(12);
|
|
1655
|
+
const decrypted = await crypto.subtle.decrypt(
|
|
1656
|
+
{ name: "AES-GCM", iv },
|
|
1657
|
+
this.encryptionKey,
|
|
1658
|
+
encrypted
|
|
1659
|
+
);
|
|
1660
|
+
const decoder = new TextDecoder();
|
|
1661
|
+
return decoder.decode(decrypted);
|
|
1662
|
+
} catch {
|
|
1663
|
+
return null;
|
|
1337
1664
|
}
|
|
1338
|
-
return { fee: "0", symbol: this.getChainSymbol(chain) };
|
|
1339
1665
|
}
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
async
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
const data = await response.json();
|
|
1353
|
-
let txHash = data.txHash || data.transactionHash || data.hash;
|
|
1354
|
-
if (txHash && typeof txHash === "object" && "hash" in txHash) {
|
|
1355
|
-
txHash = txHash.hash;
|
|
1356
|
-
}
|
|
1357
|
-
if (chain === "ethereum" && txHash && (typeof txHash !== "string" || !txHash.startsWith("0x") || txHash.length !== 66)) {
|
|
1358
|
-
console.warn(`Invalid Ethereum tx hash format: ${txHash} (length: ${txHash?.length}, expected: 66)`);
|
|
1359
|
-
}
|
|
1360
|
-
return {
|
|
1361
|
-
success: data.success,
|
|
1362
|
-
txHash,
|
|
1363
|
-
from: data.from,
|
|
1364
|
-
to: data.to,
|
|
1365
|
-
amount: data.amount,
|
|
1366
|
-
chain: data.chain,
|
|
1367
|
-
network: data.network
|
|
1368
|
-
};
|
|
1666
|
+
async removeItem(key) {
|
|
1667
|
+
localStorage.removeItem(`${this.storagePrefix}${key}`);
|
|
1668
|
+
}
|
|
1669
|
+
async hasItem(key) {
|
|
1670
|
+
return localStorage.getItem(`${this.storagePrefix}${key}`) !== null;
|
|
1671
|
+
}
|
|
1672
|
+
async clear() {
|
|
1673
|
+
const keysToRemove = [];
|
|
1674
|
+
for (let i = 0; i < localStorage.length; i++) {
|
|
1675
|
+
const key = localStorage.key(i);
|
|
1676
|
+
if (key?.startsWith(this.storagePrefix)) {
|
|
1677
|
+
keysToRemove.push(key);
|
|
1369
1678
|
}
|
|
1370
|
-
const errorData = await response.json().catch(() => ({}));
|
|
1371
|
-
return {
|
|
1372
|
-
success: false,
|
|
1373
|
-
error: errorData.error || `HTTP ${response.status}`
|
|
1374
|
-
};
|
|
1375
|
-
} catch (error) {
|
|
1376
|
-
return {
|
|
1377
|
-
success: false,
|
|
1378
|
-
error: error instanceof Error ? error.message : "Transaction failed"
|
|
1379
|
-
};
|
|
1380
1679
|
}
|
|
1680
|
+
keysToRemove.forEach((key) => localStorage.removeItem(key));
|
|
1381
1681
|
}
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
}
|
|
1388
|
-
/**
|
|
1389
|
-
* Get API URL
|
|
1390
|
-
*/
|
|
1391
|
-
getApiUrl() {
|
|
1392
|
-
return this.config.apiUrl;
|
|
1682
|
+
};
|
|
1683
|
+
var MemoryStorageAdapter = class {
|
|
1684
|
+
storage = /* @__PURE__ */ new Map();
|
|
1685
|
+
async setItem(key, value) {
|
|
1686
|
+
this.storage.set(key, value);
|
|
1393
1687
|
}
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
// ==========================================
|
|
1397
|
-
getDerivationPath(chain) {
|
|
1398
|
-
const paths = {
|
|
1399
|
-
bitcoin: this.config.network === "testnet" ? "m/84'/1'/0'/0/0" : "m/84'/0'/0'/0/0",
|
|
1400
|
-
ethereum: "m/44'/60'/0'/0/0",
|
|
1401
|
-
ton: "m/44'/607'/0'/0'/0'",
|
|
1402
|
-
tron: "m/44'/195'/0'/0/0",
|
|
1403
|
-
solana: "m/44'/501'/0'/0'",
|
|
1404
|
-
spark: "m/44'/998'/0'/0/0"
|
|
1405
|
-
};
|
|
1406
|
-
return paths[chain];
|
|
1688
|
+
async getItem(key) {
|
|
1689
|
+
return this.storage.get(key) || null;
|
|
1407
1690
|
}
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
ethereum: "ETH",
|
|
1411
|
-
bitcoin: "BTC",
|
|
1412
|
-
ton: "TON",
|
|
1413
|
-
tron: "TRX",
|
|
1414
|
-
solana: "SOL",
|
|
1415
|
-
spark: "SAT"
|
|
1416
|
-
};
|
|
1417
|
-
return symbols[chain];
|
|
1691
|
+
async removeItem(key) {
|
|
1692
|
+
this.storage.delete(key);
|
|
1418
1693
|
}
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
*/
|
|
1422
|
-
async deriveBrowserAddress(seed, chain) {
|
|
1423
|
-
const path = this.getDerivationPath(chain);
|
|
1424
|
-
try {
|
|
1425
|
-
let address;
|
|
1426
|
-
switch (chain) {
|
|
1427
|
-
case "ethereum":
|
|
1428
|
-
address = deriveEthereumAddress(seed);
|
|
1429
|
-
break;
|
|
1430
|
-
case "bitcoin":
|
|
1431
|
-
address = deriveBitcoinAddress(seed, this.config.network);
|
|
1432
|
-
break;
|
|
1433
|
-
case "tron":
|
|
1434
|
-
address = deriveTronAddress(seed);
|
|
1435
|
-
break;
|
|
1436
|
-
case "spark":
|
|
1437
|
-
address = deriveSparkAddress(seed, this.config.network);
|
|
1438
|
-
break;
|
|
1439
|
-
case "solana":
|
|
1440
|
-
address = await deriveSolanaAddress(seed);
|
|
1441
|
-
break;
|
|
1442
|
-
case "ton":
|
|
1443
|
-
address = await deriveTonAddress(seed);
|
|
1444
|
-
break;
|
|
1445
|
-
default:
|
|
1446
|
-
throw new Error(`Unsupported chain: ${chain}`);
|
|
1447
|
-
}
|
|
1448
|
-
return { chain, address, path };
|
|
1449
|
-
} catch (error) {
|
|
1450
|
-
console.error(`Browser derivation failed for ${chain}:`, error);
|
|
1451
|
-
throw error;
|
|
1452
|
-
}
|
|
1694
|
+
async hasItem(key) {
|
|
1695
|
+
return this.storage.has(key);
|
|
1453
1696
|
}
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
*/
|
|
1457
|
-
async deriveAllBrowserAddresses(seed) {
|
|
1458
|
-
return deriveAllAddresses(seed, this.config.network);
|
|
1697
|
+
async clear() {
|
|
1698
|
+
this.storage.clear();
|
|
1459
1699
|
}
|
|
1460
1700
|
};
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1701
|
+
function createSecureStorage() {
|
|
1702
|
+
if (typeof global !== "undefined" && global.nativeModuleProxy !== void 0) {
|
|
1703
|
+
const Platform = global.Platform;
|
|
1704
|
+
if (Platform?.OS === "ios") {
|
|
1705
|
+
return new KeychainStorageAdapter();
|
|
1706
|
+
} else if (Platform?.OS === "android") {
|
|
1707
|
+
return new KeystoreStorageAdapter();
|
|
1708
|
+
}
|
|
1465
1709
|
}
|
|
1466
|
-
|
|
1710
|
+
if (typeof window !== "undefined" && typeof localStorage !== "undefined") {
|
|
1711
|
+
return new WebEncryptedStorageAdapter();
|
|
1712
|
+
}
|
|
1713
|
+
return new MemoryStorageAdapter();
|
|
1467
1714
|
}
|
|
1468
1715
|
|
|
1469
1716
|
// src/wallet/WalletManager.ts
|