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