@zubari/sdk 0.2.8 → 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{TransactionService-8xSEGoWA.d.mts → TransactionService-DURp3bRL.d.ts} +34 -10
- package/dist/{TransactionService-CaIcCoqY.d.ts → TransactionService-DuMJmrG3.d.mts} +34 -10
- package/dist/{WalletManager-CCs4Jsv7.d.ts → WalletManager-CEjN-YBF.d.ts} +12 -12
- package/dist/{WalletManager-B1qvFF4K.d.mts → WalletManager-bo35aHOJ.d.mts} +12 -12
- package/dist/{index-BOc9U2TK.d.mts → index-BpjMiC3n.d.ts} +22 -11
- package/dist/{index-Cx389p_j.d.mts → index-DF0Gf8NK.d.mts} +7 -1
- package/dist/{index-Cx389p_j.d.ts → index-DF0Gf8NK.d.ts} +7 -1
- package/dist/{index-Cqrpp3XA.d.ts → index-DJnsirV5.d.mts} +22 -11
- package/dist/index.d.mts +4 -4
- package/dist/index.d.ts +4 -4
- package/dist/index.js +3064 -1770
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +3064 -1770
- package/dist/index.mjs.map +1 -1
- package/dist/protocols/index.d.mts +54 -22
- package/dist/protocols/index.d.ts +54 -22
- package/dist/protocols/index.js +1008 -76
- package/dist/protocols/index.js.map +1 -1
- package/dist/protocols/index.mjs +1008 -76
- package/dist/protocols/index.mjs.map +1 -1
- package/dist/react/index.d.mts +3 -3
- package/dist/react/index.d.ts +3 -3
- package/dist/react/index.js +884 -884
- package/dist/react/index.js.map +1 -1
- package/dist/react/index.mjs +884 -884
- package/dist/react/index.mjs.map +1 -1
- package/dist/services/index.d.mts +2 -2
- package/dist/services/index.d.ts +2 -2
- package/dist/services/index.js +152 -75
- package/dist/services/index.js.map +1 -1
- package/dist/services/index.mjs +152 -75
- package/dist/services/index.mjs.map +1 -1
- package/dist/wallet/index.d.mts +3 -3
- package/dist/wallet/index.d.ts +3 -3
- package/dist/wallet/index.js +1352 -1105
- package/dist/wallet/index.js.map +1 -1
- package/dist/wallet/index.mjs +1352 -1105
- package/dist/wallet/index.mjs.map +1 -1
- package/package.json +13 -12
package/dist/react/index.mjs
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import { useMemo, useState, useCallback, useEffect } from 'react';
|
|
2
2
|
import { Wallet, HDNodeWallet } from 'ethers';
|
|
3
|
-
import { createPublicClient, http, formatEther } from 'viem';
|
|
4
|
-
import { mainnet, sepolia } from 'viem/chains';
|
|
5
3
|
import { generateMnemonic, validateMnemonic, mnemonicToSeedSync } from '@scure/bip39';
|
|
6
4
|
import { wordlist } from '@scure/bip39/wordlists/english';
|
|
7
5
|
import { HDKey } from '@scure/bip32';
|
|
8
6
|
import { bech32, base58check } from '@scure/base';
|
|
9
7
|
import { sha256 } from '@noble/hashes/sha256';
|
|
10
8
|
import { ripemd160 } from '@noble/hashes/ripemd160';
|
|
9
|
+
import { createPublicClient, http, formatEther } from 'viem';
|
|
10
|
+
import { mainnet, sepolia } from 'viem/chains';
|
|
11
11
|
|
|
12
12
|
// src/react/useWalletManager.ts
|
|
13
13
|
|
|
@@ -131,1038 +131,1038 @@ function getNetworkConfig(network, isTestnet = false) {
|
|
|
131
131
|
};
|
|
132
132
|
}
|
|
133
133
|
|
|
134
|
-
// src/
|
|
135
|
-
var
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
* Encrypt a seed phrase with a password
|
|
143
|
-
*/
|
|
144
|
-
static async encryptSeed(seed, password) {
|
|
145
|
-
const encoder = new TextEncoder();
|
|
146
|
-
const seedData = encoder.encode(seed);
|
|
147
|
-
const salt = crypto.getRandomValues(new Uint8Array(this.SALT_LENGTH));
|
|
148
|
-
const iv = crypto.getRandomValues(new Uint8Array(this.IV_LENGTH));
|
|
149
|
-
const key = await this.deriveKey(password, salt);
|
|
150
|
-
const encrypted = await crypto.subtle.encrypt(
|
|
151
|
-
{ name: this.ALGORITHM, iv },
|
|
152
|
-
key,
|
|
153
|
-
seedData
|
|
154
|
-
);
|
|
155
|
-
const combined = new Uint8Array(salt.length + iv.length + encrypted.byteLength);
|
|
156
|
-
combined.set(salt, 0);
|
|
157
|
-
combined.set(iv, salt.length);
|
|
158
|
-
combined.set(new Uint8Array(encrypted), salt.length + iv.length);
|
|
159
|
-
return btoa(String.fromCharCode(...combined));
|
|
134
|
+
// src/services/WdkApiClient.ts
|
|
135
|
+
var WdkApiClient = class {
|
|
136
|
+
config;
|
|
137
|
+
constructor(config) {
|
|
138
|
+
this.config = {
|
|
139
|
+
baseUrl: config.baseUrl,
|
|
140
|
+
timeout: config.timeout || 3e4
|
|
141
|
+
};
|
|
160
142
|
}
|
|
161
143
|
/**
|
|
162
|
-
*
|
|
144
|
+
* Generate a new BIP-39 seed phrase using Tether WDK
|
|
163
145
|
*/
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
146
|
+
async generateSeed() {
|
|
147
|
+
try {
|
|
148
|
+
const response = await fetch(`${this.config.baseUrl}/api/wallets/wdk/generate-seed`, {
|
|
149
|
+
method: "POST",
|
|
150
|
+
headers: {
|
|
151
|
+
"Content-Type": "application/json"
|
|
152
|
+
}
|
|
153
|
+
});
|
|
154
|
+
return await response.json();
|
|
155
|
+
} catch (error) {
|
|
156
|
+
return {
|
|
157
|
+
success: false,
|
|
158
|
+
error: error instanceof Error ? error.message : "Failed to generate seed"
|
|
159
|
+
};
|
|
160
|
+
}
|
|
179
161
|
}
|
|
180
162
|
/**
|
|
181
|
-
*
|
|
163
|
+
* Validate a BIP-39 seed phrase
|
|
182
164
|
*/
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
{
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
},
|
|
200
|
-
keyMaterial,
|
|
201
|
-
{ name: this.ALGORITHM, length: this.KEY_LENGTH },
|
|
202
|
-
false,
|
|
203
|
-
["encrypt", "decrypt"]
|
|
204
|
-
);
|
|
165
|
+
async validateSeed(seed) {
|
|
166
|
+
try {
|
|
167
|
+
const response = await fetch(`${this.config.baseUrl}/api/wallets/wdk/validate-seed`, {
|
|
168
|
+
method: "POST",
|
|
169
|
+
headers: {
|
|
170
|
+
"Content-Type": "application/json"
|
|
171
|
+
},
|
|
172
|
+
body: JSON.stringify({ seed })
|
|
173
|
+
});
|
|
174
|
+
return await response.json();
|
|
175
|
+
} catch (error) {
|
|
176
|
+
return {
|
|
177
|
+
success: false,
|
|
178
|
+
error: error instanceof Error ? error.message : "Failed to validate seed"
|
|
179
|
+
};
|
|
180
|
+
}
|
|
205
181
|
}
|
|
206
182
|
/**
|
|
207
|
-
*
|
|
183
|
+
* Derive address for a specific chain using Tether WDK
|
|
208
184
|
*/
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
185
|
+
async deriveAddress(seed, chain, network = "testnet") {
|
|
186
|
+
try {
|
|
187
|
+
const response = await fetch(`${this.config.baseUrl}/api/wallets/wdk/derive-address`, {
|
|
188
|
+
method: "POST",
|
|
189
|
+
headers: {
|
|
190
|
+
"Content-Type": "application/json"
|
|
191
|
+
},
|
|
192
|
+
body: JSON.stringify({ seed, chain, network })
|
|
193
|
+
});
|
|
194
|
+
return await response.json();
|
|
195
|
+
} catch (error) {
|
|
196
|
+
return {
|
|
197
|
+
success: false,
|
|
198
|
+
error: error instanceof Error ? error.message : "Failed to derive address"
|
|
199
|
+
};
|
|
200
|
+
}
|
|
213
201
|
}
|
|
214
202
|
/**
|
|
215
|
-
*
|
|
203
|
+
* Derive addresses for all chains using Tether WDK
|
|
216
204
|
*/
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
} else {
|
|
233
|
-
throw new Error("Keychain not available on this platform");
|
|
205
|
+
async deriveAllAddresses(seed, network = "testnet") {
|
|
206
|
+
try {
|
|
207
|
+
const response = await fetch(`${this.config.baseUrl}/api/wallets/wdk/derive-all`, {
|
|
208
|
+
method: "POST",
|
|
209
|
+
headers: {
|
|
210
|
+
"Content-Type": "application/json"
|
|
211
|
+
},
|
|
212
|
+
body: JSON.stringify({ seed, network })
|
|
213
|
+
});
|
|
214
|
+
return await response.json();
|
|
215
|
+
} catch (error) {
|
|
216
|
+
return {
|
|
217
|
+
success: false,
|
|
218
|
+
error: error instanceof Error ? error.message : "Failed to derive addresses"
|
|
219
|
+
};
|
|
234
220
|
}
|
|
235
221
|
}
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
222
|
+
/**
|
|
223
|
+
* Send a transaction on a specific chain using Tether WDK
|
|
224
|
+
*/
|
|
225
|
+
async sendTransaction(seed, chain, to, amount, network = "testnet") {
|
|
226
|
+
try {
|
|
227
|
+
const response = await fetch(`${this.config.baseUrl}/api/wallets/wdk/send`, {
|
|
228
|
+
method: "POST",
|
|
229
|
+
headers: {
|
|
230
|
+
"Content-Type": "application/json"
|
|
231
|
+
},
|
|
232
|
+
body: JSON.stringify({ seed, chain, to, amount, network })
|
|
233
|
+
});
|
|
234
|
+
return await response.json();
|
|
235
|
+
} catch (error) {
|
|
236
|
+
return {
|
|
237
|
+
success: false,
|
|
238
|
+
error: error instanceof Error ? error.message : "Failed to send transaction"
|
|
239
|
+
};
|
|
239
240
|
}
|
|
240
|
-
throw new Error("Keychain not available on this platform");
|
|
241
241
|
}
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
242
|
+
/**
|
|
243
|
+
* Get transaction history for an address on a specific chain
|
|
244
|
+
* Fetches from blockchain explorers (Etherscan, mempool.space, etc.)
|
|
245
|
+
*/
|
|
246
|
+
async getTransactionHistory(seed, chain, network = "testnet", limit = 10) {
|
|
247
|
+
try {
|
|
248
|
+
const response = await fetch(`${this.config.baseUrl}/api/wallets/wdk/history`, {
|
|
249
|
+
method: "POST",
|
|
250
|
+
headers: {
|
|
251
|
+
"Content-Type": "application/json"
|
|
252
|
+
},
|
|
253
|
+
body: JSON.stringify({ seed, chain, network, limit })
|
|
254
|
+
});
|
|
255
|
+
return await response.json();
|
|
256
|
+
} catch (error) {
|
|
257
|
+
return {
|
|
258
|
+
success: false,
|
|
259
|
+
error: error instanceof Error ? error.message : "Failed to get transaction history"
|
|
260
|
+
};
|
|
247
261
|
}
|
|
248
262
|
}
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
async
|
|
254
|
-
|
|
255
|
-
await
|
|
256
|
-
|
|
257
|
-
|
|
263
|
+
/**
|
|
264
|
+
* Get transaction status by hash
|
|
265
|
+
* Fetches from blockchain explorers to check confirmation status
|
|
266
|
+
*/
|
|
267
|
+
async getTransactionStatus(txHash, chain, network = "testnet") {
|
|
268
|
+
try {
|
|
269
|
+
const response = await fetch(`${this.config.baseUrl}/api/wallets/wdk/tx-status`, {
|
|
270
|
+
method: "POST",
|
|
271
|
+
headers: {
|
|
272
|
+
"Content-Type": "application/json"
|
|
273
|
+
},
|
|
274
|
+
body: JSON.stringify({ txHash, chain, network })
|
|
275
|
+
});
|
|
276
|
+
return await response.json();
|
|
277
|
+
} catch (error) {
|
|
278
|
+
return {
|
|
279
|
+
success: false,
|
|
280
|
+
error: error instanceof Error ? error.message : "Failed to get transaction status"
|
|
281
|
+
};
|
|
258
282
|
}
|
|
259
283
|
}
|
|
260
284
|
};
|
|
261
|
-
var
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
285
|
+
var DEFAULT_API_URL = process.env.NEXT_PUBLIC_API_URL || "https://ckgwifsxka.us-east-2.awsapprunner.com";
|
|
286
|
+
var wdkApiClient = null;
|
|
287
|
+
function getWdkApiClient(baseUrl) {
|
|
288
|
+
if (!wdkApiClient || baseUrl && wdkApiClient["config"].baseUrl !== baseUrl) {
|
|
289
|
+
wdkApiClient = new WdkApiClient({
|
|
290
|
+
baseUrl: baseUrl || DEFAULT_API_URL
|
|
291
|
+
});
|
|
265
292
|
}
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
}
|
|
290
|
-
async clear() {
|
|
291
|
-
if (typeof global !== "undefined" && global.KeystoreModule) {
|
|
292
|
-
await global.KeystoreModule.clear(this.alias);
|
|
293
|
-
} else {
|
|
294
|
-
throw new Error("Keystore not available on this platform");
|
|
293
|
+
return wdkApiClient;
|
|
294
|
+
}
|
|
295
|
+
var DERIVATION_PATHS2 = {
|
|
296
|
+
ethereum: "m/44'/60'/0'/0/0",
|
|
297
|
+
bitcoin_mainnet: "m/84'/0'/0'/0/0",
|
|
298
|
+
bitcoin_testnet: "m/84'/1'/0'/0/0",
|
|
299
|
+
ton: "m/44'/607'/0'/0'/0'",
|
|
300
|
+
tron: "m/44'/195'/0'/0/0",
|
|
301
|
+
solana: "m/44'/501'/0'/0'",
|
|
302
|
+
spark: "m/44'/998'/0'/0/0"
|
|
303
|
+
};
|
|
304
|
+
function deriveEthereumAddress(seed) {
|
|
305
|
+
const hdNode = HDNodeWallet.fromPhrase(seed, void 0, DERIVATION_PATHS2.ethereum);
|
|
306
|
+
return hdNode.address;
|
|
307
|
+
}
|
|
308
|
+
function deriveBitcoinAddress(seed, network = "testnet") {
|
|
309
|
+
try {
|
|
310
|
+
const seedBytes = mnemonicToSeedSync(seed);
|
|
311
|
+
const hdKey = HDKey.fromMasterSeed(seedBytes);
|
|
312
|
+
const path = network === "testnet" ? DERIVATION_PATHS2.bitcoin_testnet : DERIVATION_PATHS2.bitcoin_mainnet;
|
|
313
|
+
const child = hdKey.derive(path);
|
|
314
|
+
if (!child.publicKey) {
|
|
315
|
+
throw new Error("Failed to derive public key");
|
|
295
316
|
}
|
|
317
|
+
const pubKeyHash = ripemd160(sha256(child.publicKey));
|
|
318
|
+
const witnessVersion = 0;
|
|
319
|
+
const words = bech32.toWords(pubKeyHash);
|
|
320
|
+
words.unshift(witnessVersion);
|
|
321
|
+
const hrp = network === "testnet" ? "tb" : "bc";
|
|
322
|
+
const address = bech32.encode(hrp, words);
|
|
323
|
+
return address;
|
|
324
|
+
} catch (error) {
|
|
325
|
+
console.error("Bitcoin address derivation failed:", error);
|
|
326
|
+
throw error;
|
|
296
327
|
}
|
|
297
|
-
}
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
328
|
+
}
|
|
329
|
+
async function deriveSolanaAddress(seed) {
|
|
330
|
+
try {
|
|
331
|
+
const [ed25519, nacl, bs58Module] = await Promise.all([
|
|
332
|
+
import('ed25519-hd-key'),
|
|
333
|
+
import('tweetnacl'),
|
|
334
|
+
import('bs58')
|
|
335
|
+
]);
|
|
336
|
+
const bs58 = bs58Module.default || bs58Module;
|
|
337
|
+
const seedBytes = mnemonicToSeedSync(seed);
|
|
338
|
+
const derived = ed25519.derivePath(DERIVATION_PATHS2.solana, Buffer.from(seedBytes).toString("hex"));
|
|
339
|
+
const keypair = nacl.sign.keyPair.fromSeed(new Uint8Array(derived.key));
|
|
340
|
+
return bs58.encode(keypair.publicKey);
|
|
341
|
+
} catch (error) {
|
|
342
|
+
console.error("Solana address derivation failed:", error);
|
|
343
|
+
throw error;
|
|
303
344
|
}
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
345
|
+
}
|
|
346
|
+
async function deriveTonAddress(seed) {
|
|
347
|
+
try {
|
|
348
|
+
const [ed25519, nacl] = await Promise.all([
|
|
349
|
+
import('ed25519-hd-key'),
|
|
350
|
+
import('tweetnacl')
|
|
351
|
+
]);
|
|
352
|
+
const seedBytes = mnemonicToSeedSync(seed);
|
|
353
|
+
const derived = ed25519.derivePath(DERIVATION_PATHS2.ton, Buffer.from(seedBytes).toString("hex"));
|
|
354
|
+
const keypair = nacl.sign.keyPair.fromSeed(new Uint8Array(derived.key));
|
|
355
|
+
const publicKey = keypair.publicKey;
|
|
356
|
+
const workchain = 0;
|
|
357
|
+
const flags = 17;
|
|
358
|
+
const hash = sha256(publicKey);
|
|
359
|
+
const addressData = new Uint8Array(34);
|
|
360
|
+
addressData[0] = flags;
|
|
361
|
+
addressData[1] = workchain;
|
|
362
|
+
addressData.set(hash, 2);
|
|
363
|
+
const crc = crc16(addressData);
|
|
364
|
+
const fullAddress = new Uint8Array(36);
|
|
365
|
+
fullAddress.set(addressData);
|
|
366
|
+
fullAddress[34] = crc >> 8 & 255;
|
|
367
|
+
fullAddress[35] = crc & 255;
|
|
368
|
+
const base64 = btoa(String.fromCharCode(...fullAddress)).replace(/\+/g, "-").replace(/\//g, "_");
|
|
369
|
+
return base64;
|
|
370
|
+
} catch (error) {
|
|
371
|
+
console.error("TON address derivation failed:", error);
|
|
372
|
+
throw error;
|
|
329
373
|
}
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
374
|
+
}
|
|
375
|
+
function crc16(data) {
|
|
376
|
+
let crc = 0;
|
|
377
|
+
for (const byte of data) {
|
|
378
|
+
crc ^= byte << 8;
|
|
379
|
+
for (let i = 0; i < 8; i++) {
|
|
380
|
+
crc = crc & 32768 ? crc << 1 ^ 4129 : crc << 1;
|
|
381
|
+
crc &= 65535;
|
|
337
382
|
}
|
|
338
|
-
return new Uint8Array(
|
|
339
|
-
saltHex.match(/.{1,2}/g).map((byte) => parseInt(byte, 16))
|
|
340
|
-
);
|
|
341
383
|
}
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
384
|
+
return crc;
|
|
385
|
+
}
|
|
386
|
+
function deriveTronAddress(seed) {
|
|
387
|
+
try {
|
|
388
|
+
const hdNode = HDNodeWallet.fromPhrase(seed, void 0, DERIVATION_PATHS2.tron);
|
|
389
|
+
const ethAddressHex = hdNode.address.slice(2).toLowerCase();
|
|
390
|
+
const addressBytes = new Uint8Array(21);
|
|
391
|
+
addressBytes[0] = 65;
|
|
392
|
+
for (let i = 0; i < 20; i++) {
|
|
393
|
+
addressBytes[i + 1] = parseInt(ethAddressHex.slice(i * 2, i * 2 + 2), 16);
|
|
345
394
|
}
|
|
346
|
-
const
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
encoder.encode(value)
|
|
352
|
-
);
|
|
353
|
-
const combined = new Uint8Array(iv.length + encrypted.byteLength);
|
|
354
|
-
combined.set(iv);
|
|
355
|
-
combined.set(new Uint8Array(encrypted), iv.length);
|
|
356
|
-
const base64 = btoa(String.fromCharCode(...combined));
|
|
357
|
-
localStorage.setItem(`${this.storagePrefix}${key}`, base64);
|
|
395
|
+
const tronBase58check = base58check(sha256);
|
|
396
|
+
return tronBase58check.encode(addressBytes);
|
|
397
|
+
} catch (error) {
|
|
398
|
+
console.error("TRON address derivation failed:", error);
|
|
399
|
+
throw error;
|
|
358
400
|
}
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
const
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
atob(base64).split("").map((c) => c.charCodeAt(0))
|
|
368
|
-
);
|
|
369
|
-
const iv = combined.slice(0, 12);
|
|
370
|
-
const encrypted = combined.slice(12);
|
|
371
|
-
const decrypted = await crypto.subtle.decrypt(
|
|
372
|
-
{ name: "AES-GCM", iv },
|
|
373
|
-
this.encryptionKey,
|
|
374
|
-
encrypted
|
|
375
|
-
);
|
|
376
|
-
const decoder = new TextDecoder();
|
|
377
|
-
return decoder.decode(decrypted);
|
|
378
|
-
} catch {
|
|
379
|
-
return null;
|
|
401
|
+
}
|
|
402
|
+
function deriveSparkAddress(seed, network = "testnet") {
|
|
403
|
+
try {
|
|
404
|
+
const seedBytes = mnemonicToSeedSync(seed);
|
|
405
|
+
const hdKey = HDKey.fromMasterSeed(seedBytes);
|
|
406
|
+
const child = hdKey.derive(DERIVATION_PATHS2.spark);
|
|
407
|
+
if (!child.publicKey) {
|
|
408
|
+
throw new Error("Failed to derive public key");
|
|
380
409
|
}
|
|
410
|
+
const pubKeyHash = ripemd160(sha256(child.publicKey));
|
|
411
|
+
const witnessVersion = 0;
|
|
412
|
+
const words = bech32.toWords(pubKeyHash);
|
|
413
|
+
words.unshift(witnessVersion);
|
|
414
|
+
const hrp = network === "testnet" ? "tsp" : "sp";
|
|
415
|
+
const address = bech32.encode(hrp, words);
|
|
416
|
+
return address;
|
|
417
|
+
} catch (error) {
|
|
418
|
+
console.error("Spark address derivation failed:", error);
|
|
419
|
+
throw error;
|
|
381
420
|
}
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
keysToRemove.forEach((key) => localStorage.removeItem(key));
|
|
421
|
+
}
|
|
422
|
+
async function deriveAllAddresses(seed, network = "testnet") {
|
|
423
|
+
const addresses = {
|
|
424
|
+
ethereum: null,
|
|
425
|
+
bitcoin: null,
|
|
426
|
+
ton: null,
|
|
427
|
+
tron: null,
|
|
428
|
+
solana: null,
|
|
429
|
+
spark: null
|
|
430
|
+
};
|
|
431
|
+
try {
|
|
432
|
+
addresses.ethereum = deriveEthereumAddress(seed);
|
|
433
|
+
} catch (e) {
|
|
434
|
+
console.error("ETH derivation failed:", e);
|
|
397
435
|
}
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
this.storage.set(key, value);
|
|
436
|
+
try {
|
|
437
|
+
addresses.bitcoin = deriveBitcoinAddress(seed, network);
|
|
438
|
+
} catch (e) {
|
|
439
|
+
console.error("BTC derivation failed:", e);
|
|
403
440
|
}
|
|
404
|
-
|
|
405
|
-
|
|
441
|
+
try {
|
|
442
|
+
addresses.spark = deriveSparkAddress(seed, network);
|
|
443
|
+
} catch (e) {
|
|
444
|
+
console.error("Spark derivation failed:", e);
|
|
406
445
|
}
|
|
407
|
-
|
|
408
|
-
|
|
446
|
+
try {
|
|
447
|
+
addresses.tron = deriveTronAddress(seed);
|
|
448
|
+
} catch (e) {
|
|
449
|
+
console.error("TRON derivation failed:", e);
|
|
409
450
|
}
|
|
410
|
-
|
|
411
|
-
|
|
451
|
+
const [solResult, tonResult] = await Promise.allSettled([
|
|
452
|
+
deriveSolanaAddress(seed),
|
|
453
|
+
deriveTonAddress(seed)
|
|
454
|
+
]);
|
|
455
|
+
if (solResult.status === "fulfilled") {
|
|
456
|
+
addresses.solana = solResult.value;
|
|
457
|
+
} else {
|
|
458
|
+
console.error("SOL derivation failed:", solResult.reason);
|
|
412
459
|
}
|
|
413
|
-
|
|
414
|
-
|
|
460
|
+
if (tonResult.status === "fulfilled") {
|
|
461
|
+
addresses.ton = tonResult.value;
|
|
462
|
+
} else {
|
|
463
|
+
console.error("TON derivation failed:", tonResult.reason);
|
|
415
464
|
}
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
465
|
+
return addresses;
|
|
466
|
+
}
|
|
467
|
+
function isValidSeed(seed) {
|
|
468
|
+
return validateMnemonic(seed, wordlist);
|
|
469
|
+
}
|
|
470
|
+
function generateSeedPhrase() {
|
|
471
|
+
return generateMnemonic(wordlist);
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
// src/services/ZubariWdkService.ts
|
|
475
|
+
var DEFAULT_API_URL2 = "https://ckgwifsxka.us-east-2.awsapprunner.com";
|
|
476
|
+
function isBrowser() {
|
|
477
|
+
return typeof window !== "undefined" && typeof window.document !== "undefined";
|
|
478
|
+
}
|
|
479
|
+
var dynamicImport = new Function("specifier", "return import(specifier)");
|
|
480
|
+
async function canUseNativeWdk() {
|
|
481
|
+
if (isBrowser()) {
|
|
482
|
+
return false;
|
|
425
483
|
}
|
|
426
|
-
|
|
427
|
-
|
|
484
|
+
try {
|
|
485
|
+
await dynamicImport("@tetherto/wdk");
|
|
486
|
+
return true;
|
|
487
|
+
} catch {
|
|
488
|
+
return false;
|
|
428
489
|
}
|
|
429
|
-
return new MemoryStorageAdapter();
|
|
430
490
|
}
|
|
431
|
-
|
|
432
|
-
// src/services/WdkApiClient.ts
|
|
433
|
-
var WdkApiClient = class {
|
|
491
|
+
var ZubariWdkService = class {
|
|
434
492
|
config;
|
|
435
|
-
|
|
493
|
+
apiClient;
|
|
494
|
+
nativeWdkService = null;
|
|
495
|
+
initialized = false;
|
|
496
|
+
useNativeWdk = false;
|
|
497
|
+
constructor(config = {}) {
|
|
436
498
|
this.config = {
|
|
437
|
-
|
|
499
|
+
network: config.network || "testnet",
|
|
500
|
+
apiUrl: config.apiUrl || process.env.NEXT_PUBLIC_API_URL || DEFAULT_API_URL2,
|
|
501
|
+
forceApi: config.forceApi ?? false,
|
|
438
502
|
timeout: config.timeout || 3e4
|
|
439
503
|
};
|
|
504
|
+
this.apiClient = getWdkApiClient(this.config.apiUrl);
|
|
440
505
|
}
|
|
441
506
|
/**
|
|
442
|
-
*
|
|
507
|
+
* Initialize the service and determine the best strategy
|
|
508
|
+
*/
|
|
509
|
+
async initialize() {
|
|
510
|
+
if (this.initialized) return;
|
|
511
|
+
if (isBrowser() || this.config.forceApi) {
|
|
512
|
+
this.useNativeWdk = false;
|
|
513
|
+
this.initialized = true;
|
|
514
|
+
return;
|
|
515
|
+
}
|
|
516
|
+
if (await canUseNativeWdk()) {
|
|
517
|
+
try {
|
|
518
|
+
const WdkServiceModule = await dynamicImport("./WdkService");
|
|
519
|
+
const WdkService = WdkServiceModule.WdkService || WdkServiceModule.default;
|
|
520
|
+
this.nativeWdkService = new WdkService({
|
|
521
|
+
network: this.config.network
|
|
522
|
+
});
|
|
523
|
+
this.useNativeWdk = true;
|
|
524
|
+
} catch (error) {
|
|
525
|
+
console.warn("Failed to initialize native WDK, falling back to API:", error);
|
|
526
|
+
this.useNativeWdk = false;
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
this.initialized = true;
|
|
530
|
+
}
|
|
531
|
+
/**
|
|
532
|
+
* Get the current execution mode
|
|
533
|
+
*/
|
|
534
|
+
getMode() {
|
|
535
|
+
if (this.useNativeWdk) return "native";
|
|
536
|
+
if (isBrowser()) return "api";
|
|
537
|
+
return "api";
|
|
538
|
+
}
|
|
539
|
+
/**
|
|
540
|
+
* Check if running in browser
|
|
541
|
+
*/
|
|
542
|
+
isBrowserEnvironment() {
|
|
543
|
+
return isBrowser();
|
|
544
|
+
}
|
|
545
|
+
/**
|
|
546
|
+
* Generate a new BIP-39 seed phrase (12 words)
|
|
443
547
|
*/
|
|
444
548
|
async generateSeed() {
|
|
549
|
+
await this.initialize();
|
|
445
550
|
try {
|
|
446
|
-
const response = await
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
}
|
|
451
|
-
});
|
|
452
|
-
return await response.json();
|
|
551
|
+
const response = await this.apiClient.generateSeed();
|
|
552
|
+
if (response.success && response.seed) {
|
|
553
|
+
return response.seed;
|
|
554
|
+
}
|
|
453
555
|
} catch (error) {
|
|
454
|
-
|
|
455
|
-
success: false,
|
|
456
|
-
error: error instanceof Error ? error.message : "Failed to generate seed"
|
|
457
|
-
};
|
|
556
|
+
console.warn("API seed generation failed:", error);
|
|
458
557
|
}
|
|
558
|
+
if (this.useNativeWdk && this.nativeWdkService) {
|
|
559
|
+
try {
|
|
560
|
+
const wdk = this.nativeWdkService;
|
|
561
|
+
return await wdk.generateSeedPhrase();
|
|
562
|
+
} catch (error) {
|
|
563
|
+
console.warn("Native WDK seed generation failed:", error);
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
return generateSeedPhrase();
|
|
459
567
|
}
|
|
460
568
|
/**
|
|
461
569
|
* Validate a BIP-39 seed phrase
|
|
462
570
|
*/
|
|
463
571
|
async validateSeed(seed) {
|
|
572
|
+
await this.initialize();
|
|
464
573
|
try {
|
|
465
|
-
const response = await
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
},
|
|
470
|
-
body: JSON.stringify({ seed })
|
|
471
|
-
});
|
|
472
|
-
return await response.json();
|
|
574
|
+
const response = await this.apiClient.validateSeed(seed);
|
|
575
|
+
if (response.success) {
|
|
576
|
+
return response.isValid ?? false;
|
|
577
|
+
}
|
|
473
578
|
} catch (error) {
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
579
|
+
console.warn("API seed validation failed:", error);
|
|
580
|
+
}
|
|
581
|
+
if (this.useNativeWdk && this.nativeWdkService) {
|
|
582
|
+
try {
|
|
583
|
+
const wdk = this.nativeWdkService;
|
|
584
|
+
return await wdk.isValidSeed(seed);
|
|
585
|
+
} catch (error) {
|
|
586
|
+
console.warn("Native WDK seed validation failed:", error);
|
|
587
|
+
}
|
|
478
588
|
}
|
|
589
|
+
return isValidSeed(seed);
|
|
479
590
|
}
|
|
480
591
|
/**
|
|
481
|
-
* Derive address for a specific chain using
|
|
592
|
+
* Derive address for a specific chain using WDK API
|
|
593
|
+
*
|
|
594
|
+
* For Ethereum, falls back to local derivation if API fails.
|
|
595
|
+
* For other chains, WDK API is required - no placeholder fallback.
|
|
482
596
|
*/
|
|
483
|
-
async deriveAddress(seed, chain
|
|
597
|
+
async deriveAddress(seed, chain) {
|
|
598
|
+
await this.initialize();
|
|
599
|
+
const path = this.getDerivationPath(chain);
|
|
484
600
|
try {
|
|
485
|
-
const response = await
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
601
|
+
const response = await this.apiClient.deriveAddress(seed, chain, this.config.network);
|
|
602
|
+
if (response.success && response.address) {
|
|
603
|
+
return {
|
|
604
|
+
chain,
|
|
605
|
+
address: response.address,
|
|
606
|
+
path: response.path || path
|
|
607
|
+
};
|
|
608
|
+
}
|
|
609
|
+
} catch (error) {
|
|
610
|
+
console.warn(`API address derivation failed for ${chain}:`, error);
|
|
611
|
+
if (chain === "ethereum") {
|
|
612
|
+
return this.deriveBrowserAddress(seed, chain);
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
if (this.useNativeWdk && this.nativeWdkService) {
|
|
616
|
+
try {
|
|
617
|
+
const wdk = this.nativeWdkService;
|
|
618
|
+
await wdk.initialize(seed);
|
|
619
|
+
return await wdk.deriveAddress(chain);
|
|
620
|
+
} catch (error) {
|
|
621
|
+
console.warn(`Native WDK address derivation failed for ${chain}:`, error);
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
if (chain === "ethereum") {
|
|
625
|
+
return this.deriveBrowserAddress(seed, chain);
|
|
626
|
+
}
|
|
627
|
+
throw new Error(
|
|
628
|
+
`WDK API required for ${chain} address derivation. Ensure the backend is running.`
|
|
629
|
+
);
|
|
630
|
+
}
|
|
631
|
+
/**
|
|
632
|
+
* Derive addresses for all supported chains using WDK API
|
|
633
|
+
*
|
|
634
|
+
* Uses the backend WDK API for real cryptographically valid addresses.
|
|
635
|
+
* No placeholder fallback - WDK API is required for multi-chain addresses.
|
|
636
|
+
*/
|
|
637
|
+
async deriveAllAddresses(seed) {
|
|
638
|
+
await this.initialize();
|
|
639
|
+
try {
|
|
640
|
+
const response = await this.apiClient.deriveAllAddresses(seed, this.config.network);
|
|
641
|
+
if (response.success && response.addresses) {
|
|
642
|
+
const extractAddress = (value) => {
|
|
643
|
+
if (!value) return null;
|
|
644
|
+
if (typeof value === "string") return value;
|
|
645
|
+
if (typeof value === "object" && value !== null && "address" in value) {
|
|
646
|
+
return value.address;
|
|
647
|
+
}
|
|
648
|
+
return null;
|
|
649
|
+
};
|
|
650
|
+
return {
|
|
651
|
+
ethereum: extractAddress(response.addresses.ethereum),
|
|
652
|
+
bitcoin: extractAddress(response.addresses.bitcoin),
|
|
653
|
+
ton: extractAddress(response.addresses.ton),
|
|
654
|
+
tron: extractAddress(response.addresses.tron),
|
|
655
|
+
solana: extractAddress(response.addresses.solana),
|
|
656
|
+
spark: extractAddress(response.addresses.spark)
|
|
657
|
+
};
|
|
658
|
+
}
|
|
493
659
|
} catch (error) {
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
660
|
+
console.warn("API address derivation failed:", error);
|
|
661
|
+
}
|
|
662
|
+
if (this.useNativeWdk && this.nativeWdkService) {
|
|
663
|
+
try {
|
|
664
|
+
const wdk = this.nativeWdkService;
|
|
665
|
+
await wdk.initialize(seed);
|
|
666
|
+
return await wdk.deriveAllAddresses();
|
|
667
|
+
} catch (error) {
|
|
668
|
+
console.warn("Native WDK multi-chain derivation failed:", error);
|
|
669
|
+
}
|
|
498
670
|
}
|
|
671
|
+
throw new Error(
|
|
672
|
+
"Tether WDK API required for multi-chain address derivation. Service temporarily unavailable."
|
|
673
|
+
);
|
|
499
674
|
}
|
|
500
675
|
/**
|
|
501
|
-
*
|
|
676
|
+
* Get balances for all chains
|
|
502
677
|
*/
|
|
503
|
-
async
|
|
678
|
+
async getAllBalances(seed) {
|
|
679
|
+
await this.initialize();
|
|
504
680
|
try {
|
|
505
|
-
const response = await fetch(`${this.config.
|
|
681
|
+
const response = await fetch(`${this.config.apiUrl}/api/wallets/wdk/balances`, {
|
|
506
682
|
method: "POST",
|
|
507
|
-
headers: {
|
|
508
|
-
|
|
509
|
-
},
|
|
510
|
-
body: JSON.stringify({ seed, network })
|
|
683
|
+
headers: { "Content-Type": "application/json" },
|
|
684
|
+
body: JSON.stringify({ seed, network: this.config.network })
|
|
511
685
|
});
|
|
512
|
-
|
|
686
|
+
if (response.ok) {
|
|
687
|
+
const data = await response.json();
|
|
688
|
+
if (data.success) {
|
|
689
|
+
return data.balances;
|
|
690
|
+
}
|
|
691
|
+
}
|
|
513
692
|
} catch (error) {
|
|
514
|
-
|
|
515
|
-
success: false,
|
|
516
|
-
error: error instanceof Error ? error.message : "Failed to derive addresses"
|
|
517
|
-
};
|
|
693
|
+
console.warn("Failed to fetch balances:", error);
|
|
518
694
|
}
|
|
695
|
+
return {};
|
|
519
696
|
}
|
|
520
697
|
/**
|
|
521
|
-
*
|
|
698
|
+
* Get fee rates for a chain
|
|
522
699
|
*/
|
|
523
|
-
async
|
|
700
|
+
async getFeeRates(seed, chain) {
|
|
701
|
+
await this.initialize();
|
|
524
702
|
try {
|
|
525
|
-
const response = await fetch(`${this.config.
|
|
703
|
+
const response = await fetch(`${this.config.apiUrl}/api/wallets/wdk/fee-rates`, {
|
|
526
704
|
method: "POST",
|
|
527
|
-
headers: {
|
|
528
|
-
|
|
529
|
-
},
|
|
530
|
-
body: JSON.stringify({ seed, chain, to, amount, network })
|
|
705
|
+
headers: { "Content-Type": "application/json" },
|
|
706
|
+
body: JSON.stringify({ seed, chain, network: this.config.network })
|
|
531
707
|
});
|
|
532
|
-
|
|
708
|
+
if (response.ok) {
|
|
709
|
+
const data = await response.json();
|
|
710
|
+
if (data.success && data.feeRates) {
|
|
711
|
+
return data.feeRates;
|
|
712
|
+
}
|
|
713
|
+
}
|
|
533
714
|
} catch (error) {
|
|
534
|
-
|
|
535
|
-
success: false,
|
|
536
|
-
error: error instanceof Error ? error.message : "Failed to send transaction"
|
|
537
|
-
};
|
|
715
|
+
console.warn(`Failed to fetch fee rates for ${chain}:`, error);
|
|
538
716
|
}
|
|
717
|
+
return { slow: "0", normal: "0", fast: "0" };
|
|
539
718
|
}
|
|
540
719
|
/**
|
|
541
|
-
*
|
|
542
|
-
* Fetches from blockchain explorers (Etherscan, mempool.space, etc.)
|
|
720
|
+
* Estimate transaction fee
|
|
543
721
|
*/
|
|
544
|
-
async
|
|
722
|
+
async estimateFee(seed, chain, to, amount) {
|
|
723
|
+
await this.initialize();
|
|
545
724
|
try {
|
|
546
|
-
const response = await fetch(`${this.config.
|
|
725
|
+
const response = await fetch(`${this.config.apiUrl}/api/wallets/wdk/estimate-fee`, {
|
|
547
726
|
method: "POST",
|
|
548
|
-
headers: {
|
|
549
|
-
|
|
550
|
-
},
|
|
551
|
-
body: JSON.stringify({ seed, chain, network, limit })
|
|
727
|
+
headers: { "Content-Type": "application/json" },
|
|
728
|
+
body: JSON.stringify({ seed, chain, to, amount, network: this.config.network })
|
|
552
729
|
});
|
|
553
|
-
|
|
730
|
+
if (response.ok) {
|
|
731
|
+
const data = await response.json();
|
|
732
|
+
if (data.success) {
|
|
733
|
+
return { fee: data.fee, symbol: data.symbol };
|
|
734
|
+
}
|
|
735
|
+
}
|
|
554
736
|
} catch (error) {
|
|
555
|
-
|
|
556
|
-
success: false,
|
|
557
|
-
error: error instanceof Error ? error.message : "Failed to get transaction history"
|
|
558
|
-
};
|
|
737
|
+
console.warn(`Failed to estimate fee for ${chain}:`, error);
|
|
559
738
|
}
|
|
739
|
+
return { fee: "0", symbol: this.getChainSymbol(chain) };
|
|
560
740
|
}
|
|
561
741
|
/**
|
|
562
|
-
*
|
|
563
|
-
* Fetches from blockchain explorers to check confirmation status
|
|
742
|
+
* Send a transaction
|
|
564
743
|
*/
|
|
565
|
-
async
|
|
744
|
+
async sendTransaction(seed, chain, to, amount) {
|
|
745
|
+
await this.initialize();
|
|
566
746
|
try {
|
|
567
|
-
const response = await fetch(`${this.config.
|
|
747
|
+
const response = await fetch(`${this.config.apiUrl}/api/wallets/wdk/send`, {
|
|
568
748
|
method: "POST",
|
|
569
|
-
headers: {
|
|
570
|
-
|
|
571
|
-
},
|
|
572
|
-
body: JSON.stringify({ txHash, chain, network })
|
|
749
|
+
headers: { "Content-Type": "application/json" },
|
|
750
|
+
body: JSON.stringify({ seed, chain, to, amount, network: this.config.network })
|
|
573
751
|
});
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
}
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
}
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
const hdNode = HDNodeWallet.fromPhrase(seed, void 0, DERIVATION_PATHS2.ethereum);
|
|
604
|
-
return hdNode.address;
|
|
605
|
-
}
|
|
606
|
-
function deriveBitcoinAddress(seed, network = "testnet") {
|
|
607
|
-
try {
|
|
608
|
-
const seedBytes = mnemonicToSeedSync(seed);
|
|
609
|
-
const hdKey = HDKey.fromMasterSeed(seedBytes);
|
|
610
|
-
const path = network === "testnet" ? DERIVATION_PATHS2.bitcoin_testnet : DERIVATION_PATHS2.bitcoin_mainnet;
|
|
611
|
-
const child = hdKey.derive(path);
|
|
612
|
-
if (!child.publicKey) {
|
|
613
|
-
throw new Error("Failed to derive public key");
|
|
614
|
-
}
|
|
615
|
-
const pubKeyHash = ripemd160(sha256(child.publicKey));
|
|
616
|
-
const witnessVersion = 0;
|
|
617
|
-
const words = bech32.toWords(pubKeyHash);
|
|
618
|
-
words.unshift(witnessVersion);
|
|
619
|
-
const hrp = network === "testnet" ? "tb" : "bc";
|
|
620
|
-
const address = bech32.encode(hrp, words);
|
|
621
|
-
return address;
|
|
622
|
-
} catch (error) {
|
|
623
|
-
console.error("Bitcoin address derivation failed:", error);
|
|
624
|
-
throw error;
|
|
625
|
-
}
|
|
626
|
-
}
|
|
627
|
-
async function deriveSolanaAddress(seed) {
|
|
628
|
-
try {
|
|
629
|
-
const [ed25519, nacl, bs58Module] = await Promise.all([
|
|
630
|
-
import('ed25519-hd-key'),
|
|
631
|
-
import('tweetnacl'),
|
|
632
|
-
import('bs58')
|
|
633
|
-
]);
|
|
634
|
-
const bs58 = bs58Module.default || bs58Module;
|
|
635
|
-
const seedBytes = mnemonicToSeedSync(seed);
|
|
636
|
-
const derived = ed25519.derivePath(DERIVATION_PATHS2.solana, Buffer.from(seedBytes).toString("hex"));
|
|
637
|
-
const keypair = nacl.sign.keyPair.fromSeed(new Uint8Array(derived.key));
|
|
638
|
-
return bs58.encode(keypair.publicKey);
|
|
639
|
-
} catch (error) {
|
|
640
|
-
console.error("Solana address derivation failed:", error);
|
|
641
|
-
throw error;
|
|
642
|
-
}
|
|
643
|
-
}
|
|
644
|
-
async function deriveTonAddress(seed) {
|
|
645
|
-
try {
|
|
646
|
-
const [ed25519, nacl] = await Promise.all([
|
|
647
|
-
import('ed25519-hd-key'),
|
|
648
|
-
import('tweetnacl')
|
|
649
|
-
]);
|
|
650
|
-
const seedBytes = mnemonicToSeedSync(seed);
|
|
651
|
-
const derived = ed25519.derivePath(DERIVATION_PATHS2.ton, Buffer.from(seedBytes).toString("hex"));
|
|
652
|
-
const keypair = nacl.sign.keyPair.fromSeed(new Uint8Array(derived.key));
|
|
653
|
-
const publicKey = keypair.publicKey;
|
|
654
|
-
const workchain = 0;
|
|
655
|
-
const flags = 17;
|
|
656
|
-
const hash = sha256(publicKey);
|
|
657
|
-
const addressData = new Uint8Array(34);
|
|
658
|
-
addressData[0] = flags;
|
|
659
|
-
addressData[1] = workchain;
|
|
660
|
-
addressData.set(hash, 2);
|
|
661
|
-
const crc = crc16(addressData);
|
|
662
|
-
const fullAddress = new Uint8Array(36);
|
|
663
|
-
fullAddress.set(addressData);
|
|
664
|
-
fullAddress[34] = crc >> 8 & 255;
|
|
665
|
-
fullAddress[35] = crc & 255;
|
|
666
|
-
const base64 = btoa(String.fromCharCode(...fullAddress)).replace(/\+/g, "-").replace(/\//g, "_");
|
|
667
|
-
return base64;
|
|
668
|
-
} catch (error) {
|
|
669
|
-
console.error("TON address derivation failed:", error);
|
|
670
|
-
throw error;
|
|
671
|
-
}
|
|
672
|
-
}
|
|
673
|
-
function crc16(data) {
|
|
674
|
-
let crc = 0;
|
|
675
|
-
for (const byte of data) {
|
|
676
|
-
crc ^= byte << 8;
|
|
677
|
-
for (let i = 0; i < 8; i++) {
|
|
678
|
-
crc = crc & 32768 ? crc << 1 ^ 4129 : crc << 1;
|
|
679
|
-
crc &= 65535;
|
|
680
|
-
}
|
|
681
|
-
}
|
|
682
|
-
return crc;
|
|
683
|
-
}
|
|
684
|
-
function deriveTronAddress(seed) {
|
|
685
|
-
try {
|
|
686
|
-
const hdNode = HDNodeWallet.fromPhrase(seed, void 0, DERIVATION_PATHS2.tron);
|
|
687
|
-
const ethAddressHex = hdNode.address.slice(2).toLowerCase();
|
|
688
|
-
const addressBytes = new Uint8Array(21);
|
|
689
|
-
addressBytes[0] = 65;
|
|
690
|
-
for (let i = 0; i < 20; i++) {
|
|
691
|
-
addressBytes[i + 1] = parseInt(ethAddressHex.slice(i * 2, i * 2 + 2), 16);
|
|
752
|
+
if (response.ok) {
|
|
753
|
+
const data = await response.json();
|
|
754
|
+
let txHash = data.txHash || data.transactionHash || data.hash;
|
|
755
|
+
if (txHash && typeof txHash === "object" && "hash" in txHash) {
|
|
756
|
+
txHash = txHash.hash;
|
|
757
|
+
}
|
|
758
|
+
if (chain === "ethereum" && txHash && (typeof txHash !== "string" || !txHash.startsWith("0x") || txHash.length !== 66)) {
|
|
759
|
+
console.warn(`Invalid Ethereum tx hash format: ${txHash} (length: ${txHash?.length}, expected: 66)`);
|
|
760
|
+
}
|
|
761
|
+
return {
|
|
762
|
+
success: data.success,
|
|
763
|
+
txHash,
|
|
764
|
+
from: data.from,
|
|
765
|
+
to: data.to,
|
|
766
|
+
amount: data.amount,
|
|
767
|
+
chain: data.chain,
|
|
768
|
+
network: data.network
|
|
769
|
+
};
|
|
770
|
+
}
|
|
771
|
+
const errorData = await response.json().catch(() => ({}));
|
|
772
|
+
return {
|
|
773
|
+
success: false,
|
|
774
|
+
error: errorData.error || `HTTP ${response.status}`
|
|
775
|
+
};
|
|
776
|
+
} catch (error) {
|
|
777
|
+
return {
|
|
778
|
+
success: false,
|
|
779
|
+
error: error instanceof Error ? error.message : "Transaction failed"
|
|
780
|
+
};
|
|
692
781
|
}
|
|
693
|
-
const tronBase58check = base58check(sha256);
|
|
694
|
-
return tronBase58check.encode(addressBytes);
|
|
695
|
-
} catch (error) {
|
|
696
|
-
console.error("TRON address derivation failed:", error);
|
|
697
|
-
throw error;
|
|
698
782
|
}
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
const child = hdKey.derive(DERIVATION_PATHS2.spark);
|
|
705
|
-
if (!child.publicKey) {
|
|
706
|
-
throw new Error("Failed to derive public key");
|
|
707
|
-
}
|
|
708
|
-
const pubKeyHash = ripemd160(sha256(child.publicKey));
|
|
709
|
-
const witnessVersion = 0;
|
|
710
|
-
const words = bech32.toWords(pubKeyHash);
|
|
711
|
-
words.unshift(witnessVersion);
|
|
712
|
-
const hrp = network === "testnet" ? "tsp" : "sp";
|
|
713
|
-
const address = bech32.encode(hrp, words);
|
|
714
|
-
return address;
|
|
715
|
-
} catch (error) {
|
|
716
|
-
console.error("Spark address derivation failed:", error);
|
|
717
|
-
throw error;
|
|
783
|
+
/**
|
|
784
|
+
* Get the network configuration
|
|
785
|
+
*/
|
|
786
|
+
getNetwork() {
|
|
787
|
+
return this.config.network;
|
|
718
788
|
}
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
ton: null,
|
|
725
|
-
tron: null,
|
|
726
|
-
solana: null,
|
|
727
|
-
spark: null
|
|
728
|
-
};
|
|
729
|
-
try {
|
|
730
|
-
addresses.ethereum = deriveEthereumAddress(seed);
|
|
731
|
-
} catch (e) {
|
|
732
|
-
console.error("ETH derivation failed:", e);
|
|
789
|
+
/**
|
|
790
|
+
* Get API URL
|
|
791
|
+
*/
|
|
792
|
+
getApiUrl() {
|
|
793
|
+
return this.config.apiUrl;
|
|
733
794
|
}
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
795
|
+
// ==========================================
|
|
796
|
+
// Private Helper Methods
|
|
797
|
+
// ==========================================
|
|
798
|
+
getDerivationPath(chain) {
|
|
799
|
+
const paths = {
|
|
800
|
+
bitcoin: this.config.network === "testnet" ? "m/84'/1'/0'/0/0" : "m/84'/0'/0'/0/0",
|
|
801
|
+
ethereum: "m/44'/60'/0'/0/0",
|
|
802
|
+
ton: "m/44'/607'/0'/0'/0'",
|
|
803
|
+
tron: "m/44'/195'/0'/0/0",
|
|
804
|
+
solana: "m/44'/501'/0'/0'",
|
|
805
|
+
spark: "m/44'/998'/0'/0/0"
|
|
806
|
+
};
|
|
807
|
+
return paths[chain];
|
|
738
808
|
}
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
809
|
+
getChainSymbol(chain) {
|
|
810
|
+
const symbols = {
|
|
811
|
+
ethereum: "ETH",
|
|
812
|
+
bitcoin: "BTC",
|
|
813
|
+
ton: "TON",
|
|
814
|
+
tron: "TRX",
|
|
815
|
+
solana: "SOL",
|
|
816
|
+
spark: "SAT"
|
|
817
|
+
};
|
|
818
|
+
return symbols[chain];
|
|
743
819
|
}
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
820
|
+
/**
|
|
821
|
+
* Derive address using browser-compatible libraries
|
|
822
|
+
*/
|
|
823
|
+
async deriveBrowserAddress(seed, chain) {
|
|
824
|
+
const path = this.getDerivationPath(chain);
|
|
825
|
+
try {
|
|
826
|
+
let address;
|
|
827
|
+
switch (chain) {
|
|
828
|
+
case "ethereum":
|
|
829
|
+
address = deriveEthereumAddress(seed);
|
|
830
|
+
break;
|
|
831
|
+
case "bitcoin":
|
|
832
|
+
address = deriveBitcoinAddress(seed, this.config.network);
|
|
833
|
+
break;
|
|
834
|
+
case "tron":
|
|
835
|
+
address = deriveTronAddress(seed);
|
|
836
|
+
break;
|
|
837
|
+
case "spark":
|
|
838
|
+
address = deriveSparkAddress(seed, this.config.network);
|
|
839
|
+
break;
|
|
840
|
+
case "solana":
|
|
841
|
+
address = await deriveSolanaAddress(seed);
|
|
842
|
+
break;
|
|
843
|
+
case "ton":
|
|
844
|
+
address = await deriveTonAddress(seed);
|
|
845
|
+
break;
|
|
846
|
+
default:
|
|
847
|
+
throw new Error(`Unsupported chain: ${chain}`);
|
|
848
|
+
}
|
|
849
|
+
return { chain, address, path };
|
|
850
|
+
} catch (error) {
|
|
851
|
+
console.error(`Browser derivation failed for ${chain}:`, error);
|
|
852
|
+
throw error;
|
|
853
|
+
}
|
|
748
854
|
}
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
addresses.solana = solResult.value;
|
|
755
|
-
} else {
|
|
756
|
-
console.error("SOL derivation failed:", solResult.reason);
|
|
855
|
+
/**
|
|
856
|
+
* Derive all addresses using browser-compatible libraries
|
|
857
|
+
*/
|
|
858
|
+
async deriveAllBrowserAddresses(seed) {
|
|
859
|
+
return deriveAllAddresses(seed, this.config.network);
|
|
757
860
|
}
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
861
|
+
};
|
|
862
|
+
var defaultService = null;
|
|
863
|
+
function getZubariWdkService(config) {
|
|
864
|
+
if (!defaultService || config && config.network !== defaultService.getNetwork()) {
|
|
865
|
+
defaultService = new ZubariWdkService(config);
|
|
762
866
|
}
|
|
763
|
-
return
|
|
764
|
-
}
|
|
765
|
-
function isValidSeed(seed) {
|
|
766
|
-
return validateMnemonic(seed, wordlist);
|
|
767
|
-
}
|
|
768
|
-
function generateSeedPhrase() {
|
|
769
|
-
return generateMnemonic(wordlist);
|
|
867
|
+
return defaultService;
|
|
770
868
|
}
|
|
771
869
|
|
|
772
|
-
// src/
|
|
773
|
-
var
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
apiUrl: config.apiUrl || process.env.NEXT_PUBLIC_API_URL || DEFAULT_API_URL2,
|
|
799
|
-
forceApi: config.forceApi ?? false,
|
|
800
|
-
timeout: config.timeout || 3e4
|
|
801
|
-
};
|
|
802
|
-
this.apiClient = getWdkApiClient(this.config.apiUrl);
|
|
870
|
+
// src/security/KeyManager.ts
|
|
871
|
+
var KeyManager = class {
|
|
872
|
+
static ALGORITHM = "AES-GCM";
|
|
873
|
+
static KEY_LENGTH = 256;
|
|
874
|
+
static IV_LENGTH = 12;
|
|
875
|
+
static SALT_LENGTH = 16;
|
|
876
|
+
static PBKDF2_ITERATIONS = 1e5;
|
|
877
|
+
/**
|
|
878
|
+
* Encrypt a seed phrase with a password
|
|
879
|
+
*/
|
|
880
|
+
static async encryptSeed(seed, password) {
|
|
881
|
+
const encoder = new TextEncoder();
|
|
882
|
+
const seedData = encoder.encode(seed);
|
|
883
|
+
const salt = crypto.getRandomValues(new Uint8Array(this.SALT_LENGTH));
|
|
884
|
+
const iv = crypto.getRandomValues(new Uint8Array(this.IV_LENGTH));
|
|
885
|
+
const key = await this.deriveKey(password, salt);
|
|
886
|
+
const encrypted = await crypto.subtle.encrypt(
|
|
887
|
+
{ name: this.ALGORITHM, iv },
|
|
888
|
+
key,
|
|
889
|
+
seedData
|
|
890
|
+
);
|
|
891
|
+
const combined = new Uint8Array(salt.length + iv.length + encrypted.byteLength);
|
|
892
|
+
combined.set(salt, 0);
|
|
893
|
+
combined.set(iv, salt.length);
|
|
894
|
+
combined.set(new Uint8Array(encrypted), salt.length + iv.length);
|
|
895
|
+
return btoa(String.fromCharCode(...combined));
|
|
803
896
|
}
|
|
804
897
|
/**
|
|
805
|
-
*
|
|
898
|
+
* Decrypt a seed phrase with a password
|
|
806
899
|
*/
|
|
807
|
-
async
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
} catch (error) {
|
|
823
|
-
console.warn("Failed to initialize native WDK, falling back to API:", error);
|
|
824
|
-
this.useNativeWdk = false;
|
|
825
|
-
}
|
|
826
|
-
}
|
|
827
|
-
this.initialized = true;
|
|
900
|
+
static async decryptSeed(encryptedData, password) {
|
|
901
|
+
const combined = new Uint8Array(
|
|
902
|
+
atob(encryptedData).split("").map((c) => c.charCodeAt(0))
|
|
903
|
+
);
|
|
904
|
+
const salt = combined.slice(0, this.SALT_LENGTH);
|
|
905
|
+
const iv = combined.slice(this.SALT_LENGTH, this.SALT_LENGTH + this.IV_LENGTH);
|
|
906
|
+
const encrypted = combined.slice(this.SALT_LENGTH + this.IV_LENGTH);
|
|
907
|
+
const key = await this.deriveKey(password, salt);
|
|
908
|
+
const decrypted = await crypto.subtle.decrypt(
|
|
909
|
+
{ name: this.ALGORITHM, iv },
|
|
910
|
+
key,
|
|
911
|
+
encrypted
|
|
912
|
+
);
|
|
913
|
+
const decoder = new TextDecoder();
|
|
914
|
+
return decoder.decode(decrypted);
|
|
828
915
|
}
|
|
829
916
|
/**
|
|
830
|
-
*
|
|
917
|
+
* Derive encryption key from password using PBKDF2
|
|
831
918
|
*/
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
919
|
+
static async deriveKey(password, salt) {
|
|
920
|
+
const encoder = new TextEncoder();
|
|
921
|
+
const passwordData = encoder.encode(password);
|
|
922
|
+
const keyMaterial = await crypto.subtle.importKey(
|
|
923
|
+
"raw",
|
|
924
|
+
passwordData,
|
|
925
|
+
"PBKDF2",
|
|
926
|
+
false,
|
|
927
|
+
["deriveKey"]
|
|
928
|
+
);
|
|
929
|
+
return crypto.subtle.deriveKey(
|
|
930
|
+
{
|
|
931
|
+
name: "PBKDF2",
|
|
932
|
+
salt: salt.buffer.slice(salt.byteOffset, salt.byteOffset + salt.byteLength),
|
|
933
|
+
iterations: this.PBKDF2_ITERATIONS,
|
|
934
|
+
hash: "SHA-256"
|
|
935
|
+
},
|
|
936
|
+
keyMaterial,
|
|
937
|
+
{ name: this.ALGORITHM, length: this.KEY_LENGTH },
|
|
938
|
+
false,
|
|
939
|
+
["encrypt", "decrypt"]
|
|
940
|
+
);
|
|
836
941
|
}
|
|
837
942
|
/**
|
|
838
|
-
*
|
|
943
|
+
* Validate a BIP-39 seed phrase (basic validation)
|
|
839
944
|
*/
|
|
840
|
-
|
|
841
|
-
|
|
945
|
+
static validateSeedPhrase(seed) {
|
|
946
|
+
const words = seed.trim().split(/\s+/);
|
|
947
|
+
const validWordCounts = [12, 15, 18, 21, 24];
|
|
948
|
+
return validWordCounts.includes(words.length);
|
|
842
949
|
}
|
|
843
950
|
/**
|
|
844
|
-
* Generate a
|
|
951
|
+
* Generate a random encryption key (for backup purposes)
|
|
845
952
|
*/
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
953
|
+
static generateBackupKey() {
|
|
954
|
+
const bytes = crypto.getRandomValues(new Uint8Array(32));
|
|
955
|
+
return Array.from(bytes).map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
956
|
+
}
|
|
957
|
+
};
|
|
958
|
+
|
|
959
|
+
// src/storage/SecureStorage.ts
|
|
960
|
+
var KeychainStorageAdapter = class {
|
|
961
|
+
serviceName;
|
|
962
|
+
constructor(serviceName = "com.zubari.wallet") {
|
|
963
|
+
this.serviceName = serviceName;
|
|
964
|
+
}
|
|
965
|
+
async setItem(key, value) {
|
|
966
|
+
if (typeof global !== "undefined" && global.KeychainModule) {
|
|
967
|
+
await global.KeychainModule.setItem(this.serviceName, key, value);
|
|
968
|
+
} else {
|
|
969
|
+
throw new Error("Keychain not available on this platform");
|
|
855
970
|
}
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
} catch (error) {
|
|
861
|
-
console.warn("Native WDK seed generation failed:", error);
|
|
862
|
-
}
|
|
971
|
+
}
|
|
972
|
+
async getItem(key) {
|
|
973
|
+
if (typeof global !== "undefined" && global.KeychainModule) {
|
|
974
|
+
return global.KeychainModule.getItem(this.serviceName, key);
|
|
863
975
|
}
|
|
864
|
-
|
|
976
|
+
throw new Error("Keychain not available on this platform");
|
|
865
977
|
}
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
try {
|
|
872
|
-
const response = await this.apiClient.validateSeed(seed);
|
|
873
|
-
if (response.success) {
|
|
874
|
-
return response.isValid ?? false;
|
|
875
|
-
}
|
|
876
|
-
} catch (error) {
|
|
877
|
-
console.warn("API seed validation failed:", error);
|
|
978
|
+
async removeItem(key) {
|
|
979
|
+
if (typeof global !== "undefined" && global.KeychainModule) {
|
|
980
|
+
await global.KeychainModule.removeItem(this.serviceName, key);
|
|
981
|
+
} else {
|
|
982
|
+
throw new Error("Keychain not available on this platform");
|
|
878
983
|
}
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
984
|
+
}
|
|
985
|
+
async hasItem(key) {
|
|
986
|
+
const value = await this.getItem(key);
|
|
987
|
+
return value !== null;
|
|
988
|
+
}
|
|
989
|
+
async clear() {
|
|
990
|
+
if (typeof global !== "undefined" && global.KeychainModule) {
|
|
991
|
+
await global.KeychainModule.clear(this.serviceName);
|
|
992
|
+
} else {
|
|
993
|
+
throw new Error("Keychain not available on this platform");
|
|
886
994
|
}
|
|
887
|
-
return isValidSeed(seed);
|
|
888
995
|
}
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
async
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
if (response.success && response.address) {
|
|
901
|
-
return {
|
|
902
|
-
chain,
|
|
903
|
-
address: response.address,
|
|
904
|
-
path: response.path || path
|
|
905
|
-
};
|
|
906
|
-
}
|
|
907
|
-
} catch (error) {
|
|
908
|
-
console.warn(`API address derivation failed for ${chain}:`, error);
|
|
909
|
-
if (chain === "ethereum") {
|
|
910
|
-
return this.deriveBrowserAddress(seed, chain);
|
|
911
|
-
}
|
|
996
|
+
};
|
|
997
|
+
var KeystoreStorageAdapter = class {
|
|
998
|
+
alias;
|
|
999
|
+
constructor(alias = "zubari_wallet_keys") {
|
|
1000
|
+
this.alias = alias;
|
|
1001
|
+
}
|
|
1002
|
+
async setItem(key, value) {
|
|
1003
|
+
if (typeof global !== "undefined" && global.KeystoreModule) {
|
|
1004
|
+
await global.KeystoreModule.setItem(this.alias, key, value);
|
|
1005
|
+
} else {
|
|
1006
|
+
throw new Error("Keystore not available on this platform");
|
|
912
1007
|
}
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
return await wdk.deriveAddress(chain);
|
|
918
|
-
} catch (error) {
|
|
919
|
-
console.warn(`Native WDK address derivation failed for ${chain}:`, error);
|
|
920
|
-
}
|
|
1008
|
+
}
|
|
1009
|
+
async getItem(key) {
|
|
1010
|
+
if (typeof global !== "undefined" && global.KeystoreModule) {
|
|
1011
|
+
return global.KeystoreModule.getItem(this.alias, key);
|
|
921
1012
|
}
|
|
922
|
-
|
|
923
|
-
|
|
1013
|
+
throw new Error("Keystore not available on this platform");
|
|
1014
|
+
}
|
|
1015
|
+
async removeItem(key) {
|
|
1016
|
+
if (typeof global !== "undefined" && global.KeystoreModule) {
|
|
1017
|
+
await global.KeystoreModule.removeItem(this.alias, key);
|
|
1018
|
+
} else {
|
|
1019
|
+
throw new Error("Keystore not available on this platform");
|
|
1020
|
+
}
|
|
1021
|
+
}
|
|
1022
|
+
async hasItem(key) {
|
|
1023
|
+
const value = await this.getItem(key);
|
|
1024
|
+
return value !== null;
|
|
1025
|
+
}
|
|
1026
|
+
async clear() {
|
|
1027
|
+
if (typeof global !== "undefined" && global.KeystoreModule) {
|
|
1028
|
+
await global.KeystoreModule.clear(this.alias);
|
|
1029
|
+
} else {
|
|
1030
|
+
throw new Error("Keystore not available on this platform");
|
|
924
1031
|
}
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
1032
|
+
}
|
|
1033
|
+
};
|
|
1034
|
+
var WebEncryptedStorageAdapter = class {
|
|
1035
|
+
encryptionKey = null;
|
|
1036
|
+
storagePrefix;
|
|
1037
|
+
constructor(storagePrefix = "zubari_") {
|
|
1038
|
+
this.storagePrefix = storagePrefix;
|
|
928
1039
|
}
|
|
929
1040
|
/**
|
|
930
|
-
*
|
|
931
|
-
*
|
|
932
|
-
* Uses the backend WDK API for real cryptographically valid addresses.
|
|
933
|
-
* No placeholder fallback - WDK API is required for multi-chain addresses.
|
|
1041
|
+
* Initialize with a password-derived key
|
|
934
1042
|
*/
|
|
935
|
-
async
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
}
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
} catch (error) {
|
|
966
|
-
console.warn("Native WDK multi-chain derivation failed:", error);
|
|
967
|
-
}
|
|
1043
|
+
async initialize(password) {
|
|
1044
|
+
const encoder = new TextEncoder();
|
|
1045
|
+
const salt = this.getSalt();
|
|
1046
|
+
const keyMaterial = await crypto.subtle.importKey(
|
|
1047
|
+
"raw",
|
|
1048
|
+
encoder.encode(password),
|
|
1049
|
+
"PBKDF2",
|
|
1050
|
+
false,
|
|
1051
|
+
["deriveKey"]
|
|
1052
|
+
);
|
|
1053
|
+
this.encryptionKey = await crypto.subtle.deriveKey(
|
|
1054
|
+
{
|
|
1055
|
+
name: "PBKDF2",
|
|
1056
|
+
salt: salt.buffer,
|
|
1057
|
+
iterations: 1e5,
|
|
1058
|
+
hash: "SHA-256"
|
|
1059
|
+
},
|
|
1060
|
+
keyMaterial,
|
|
1061
|
+
{ name: "AES-GCM", length: 256 },
|
|
1062
|
+
false,
|
|
1063
|
+
["encrypt", "decrypt"]
|
|
1064
|
+
);
|
|
1065
|
+
}
|
|
1066
|
+
getSalt() {
|
|
1067
|
+
const saltKey = `${this.storagePrefix}salt`;
|
|
1068
|
+
let saltHex = localStorage.getItem(saltKey);
|
|
1069
|
+
if (!saltHex) {
|
|
1070
|
+
const salt = crypto.getRandomValues(new Uint8Array(16));
|
|
1071
|
+
saltHex = Array.from(salt).map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
1072
|
+
localStorage.setItem(saltKey, saltHex);
|
|
968
1073
|
}
|
|
969
|
-
|
|
970
|
-
|
|
1074
|
+
return new Uint8Array(
|
|
1075
|
+
saltHex.match(/.{1,2}/g).map((byte) => parseInt(byte, 16))
|
|
971
1076
|
);
|
|
972
1077
|
}
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
async getAllBalances(seed) {
|
|
977
|
-
await this.initialize();
|
|
978
|
-
try {
|
|
979
|
-
const response = await fetch(`${this.config.apiUrl}/api/wallets/wdk/balances`, {
|
|
980
|
-
method: "POST",
|
|
981
|
-
headers: { "Content-Type": "application/json" },
|
|
982
|
-
body: JSON.stringify({ seed, network: this.config.network })
|
|
983
|
-
});
|
|
984
|
-
if (response.ok) {
|
|
985
|
-
const data = await response.json();
|
|
986
|
-
if (data.success) {
|
|
987
|
-
return data.balances;
|
|
988
|
-
}
|
|
989
|
-
}
|
|
990
|
-
} catch (error) {
|
|
991
|
-
console.warn("Failed to fetch balances:", error);
|
|
1078
|
+
async setItem(key, value) {
|
|
1079
|
+
if (!this.encryptionKey) {
|
|
1080
|
+
throw new Error("Storage not initialized. Call initialize() first.");
|
|
992
1081
|
}
|
|
993
|
-
|
|
1082
|
+
const encoder = new TextEncoder();
|
|
1083
|
+
const iv = crypto.getRandomValues(new Uint8Array(12));
|
|
1084
|
+
const encrypted = await crypto.subtle.encrypt(
|
|
1085
|
+
{ name: "AES-GCM", iv },
|
|
1086
|
+
this.encryptionKey,
|
|
1087
|
+
encoder.encode(value)
|
|
1088
|
+
);
|
|
1089
|
+
const combined = new Uint8Array(iv.length + encrypted.byteLength);
|
|
1090
|
+
combined.set(iv);
|
|
1091
|
+
combined.set(new Uint8Array(encrypted), iv.length);
|
|
1092
|
+
const base64 = btoa(String.fromCharCode(...combined));
|
|
1093
|
+
localStorage.setItem(`${this.storagePrefix}${key}`, base64);
|
|
994
1094
|
}
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
async getFeeRates(seed, chain) {
|
|
999
|
-
await this.initialize();
|
|
1000
|
-
try {
|
|
1001
|
-
const response = await fetch(`${this.config.apiUrl}/api/wallets/wdk/fee-rates`, {
|
|
1002
|
-
method: "POST",
|
|
1003
|
-
headers: { "Content-Type": "application/json" },
|
|
1004
|
-
body: JSON.stringify({ seed, chain, network: this.config.network })
|
|
1005
|
-
});
|
|
1006
|
-
if (response.ok) {
|
|
1007
|
-
const data = await response.json();
|
|
1008
|
-
if (data.success && data.feeRates) {
|
|
1009
|
-
return data.feeRates;
|
|
1010
|
-
}
|
|
1011
|
-
}
|
|
1012
|
-
} catch (error) {
|
|
1013
|
-
console.warn(`Failed to fetch fee rates for ${chain}:`, error);
|
|
1095
|
+
async getItem(key) {
|
|
1096
|
+
if (!this.encryptionKey) {
|
|
1097
|
+
throw new Error("Storage not initialized. Call initialize() first.");
|
|
1014
1098
|
}
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
/**
|
|
1018
|
-
* Estimate transaction fee
|
|
1019
|
-
*/
|
|
1020
|
-
async estimateFee(seed, chain, to, amount) {
|
|
1021
|
-
await this.initialize();
|
|
1099
|
+
const base64 = localStorage.getItem(`${this.storagePrefix}${key}`);
|
|
1100
|
+
if (!base64) return null;
|
|
1022
1101
|
try {
|
|
1023
|
-
const
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1102
|
+
const combined = new Uint8Array(
|
|
1103
|
+
atob(base64).split("").map((c) => c.charCodeAt(0))
|
|
1104
|
+
);
|
|
1105
|
+
const iv = combined.slice(0, 12);
|
|
1106
|
+
const encrypted = combined.slice(12);
|
|
1107
|
+
const decrypted = await crypto.subtle.decrypt(
|
|
1108
|
+
{ name: "AES-GCM", iv },
|
|
1109
|
+
this.encryptionKey,
|
|
1110
|
+
encrypted
|
|
1111
|
+
);
|
|
1112
|
+
const decoder = new TextDecoder();
|
|
1113
|
+
return decoder.decode(decrypted);
|
|
1114
|
+
} catch {
|
|
1115
|
+
return null;
|
|
1036
1116
|
}
|
|
1037
|
-
return { fee: "0", symbol: this.getChainSymbol(chain) };
|
|
1038
1117
|
}
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
async
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
const data = await response.json();
|
|
1052
|
-
let txHash = data.txHash || data.transactionHash || data.hash;
|
|
1053
|
-
if (txHash && typeof txHash === "object" && "hash" in txHash) {
|
|
1054
|
-
txHash = txHash.hash;
|
|
1055
|
-
}
|
|
1056
|
-
if (chain === "ethereum" && txHash && (typeof txHash !== "string" || !txHash.startsWith("0x") || txHash.length !== 66)) {
|
|
1057
|
-
console.warn(`Invalid Ethereum tx hash format: ${txHash} (length: ${txHash?.length}, expected: 66)`);
|
|
1058
|
-
}
|
|
1059
|
-
return {
|
|
1060
|
-
success: data.success,
|
|
1061
|
-
txHash,
|
|
1062
|
-
from: data.from,
|
|
1063
|
-
to: data.to,
|
|
1064
|
-
amount: data.amount,
|
|
1065
|
-
chain: data.chain,
|
|
1066
|
-
network: data.network
|
|
1067
|
-
};
|
|
1118
|
+
async removeItem(key) {
|
|
1119
|
+
localStorage.removeItem(`${this.storagePrefix}${key}`);
|
|
1120
|
+
}
|
|
1121
|
+
async hasItem(key) {
|
|
1122
|
+
return localStorage.getItem(`${this.storagePrefix}${key}`) !== null;
|
|
1123
|
+
}
|
|
1124
|
+
async clear() {
|
|
1125
|
+
const keysToRemove = [];
|
|
1126
|
+
for (let i = 0; i < localStorage.length; i++) {
|
|
1127
|
+
const key = localStorage.key(i);
|
|
1128
|
+
if (key?.startsWith(this.storagePrefix)) {
|
|
1129
|
+
keysToRemove.push(key);
|
|
1068
1130
|
}
|
|
1069
|
-
const errorData = await response.json().catch(() => ({}));
|
|
1070
|
-
return {
|
|
1071
|
-
success: false,
|
|
1072
|
-
error: errorData.error || `HTTP ${response.status}`
|
|
1073
|
-
};
|
|
1074
|
-
} catch (error) {
|
|
1075
|
-
return {
|
|
1076
|
-
success: false,
|
|
1077
|
-
error: error instanceof Error ? error.message : "Transaction failed"
|
|
1078
|
-
};
|
|
1079
1131
|
}
|
|
1132
|
+
keysToRemove.forEach((key) => localStorage.removeItem(key));
|
|
1080
1133
|
}
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
}
|
|
1087
|
-
/**
|
|
1088
|
-
* Get API URL
|
|
1089
|
-
*/
|
|
1090
|
-
getApiUrl() {
|
|
1091
|
-
return this.config.apiUrl;
|
|
1134
|
+
};
|
|
1135
|
+
var MemoryStorageAdapter = class {
|
|
1136
|
+
storage = /* @__PURE__ */ new Map();
|
|
1137
|
+
async setItem(key, value) {
|
|
1138
|
+
this.storage.set(key, value);
|
|
1092
1139
|
}
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
// ==========================================
|
|
1096
|
-
getDerivationPath(chain) {
|
|
1097
|
-
const paths = {
|
|
1098
|
-
bitcoin: this.config.network === "testnet" ? "m/84'/1'/0'/0/0" : "m/84'/0'/0'/0/0",
|
|
1099
|
-
ethereum: "m/44'/60'/0'/0/0",
|
|
1100
|
-
ton: "m/44'/607'/0'/0'/0'",
|
|
1101
|
-
tron: "m/44'/195'/0'/0/0",
|
|
1102
|
-
solana: "m/44'/501'/0'/0'",
|
|
1103
|
-
spark: "m/44'/998'/0'/0/0"
|
|
1104
|
-
};
|
|
1105
|
-
return paths[chain];
|
|
1140
|
+
async getItem(key) {
|
|
1141
|
+
return this.storage.get(key) || null;
|
|
1106
1142
|
}
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
ethereum: "ETH",
|
|
1110
|
-
bitcoin: "BTC",
|
|
1111
|
-
ton: "TON",
|
|
1112
|
-
tron: "TRX",
|
|
1113
|
-
solana: "SOL",
|
|
1114
|
-
spark: "SAT"
|
|
1115
|
-
};
|
|
1116
|
-
return symbols[chain];
|
|
1143
|
+
async removeItem(key) {
|
|
1144
|
+
this.storage.delete(key);
|
|
1117
1145
|
}
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
*/
|
|
1121
|
-
async deriveBrowserAddress(seed, chain) {
|
|
1122
|
-
const path = this.getDerivationPath(chain);
|
|
1123
|
-
try {
|
|
1124
|
-
let address;
|
|
1125
|
-
switch (chain) {
|
|
1126
|
-
case "ethereum":
|
|
1127
|
-
address = deriveEthereumAddress(seed);
|
|
1128
|
-
break;
|
|
1129
|
-
case "bitcoin":
|
|
1130
|
-
address = deriveBitcoinAddress(seed, this.config.network);
|
|
1131
|
-
break;
|
|
1132
|
-
case "tron":
|
|
1133
|
-
address = deriveTronAddress(seed);
|
|
1134
|
-
break;
|
|
1135
|
-
case "spark":
|
|
1136
|
-
address = deriveSparkAddress(seed, this.config.network);
|
|
1137
|
-
break;
|
|
1138
|
-
case "solana":
|
|
1139
|
-
address = await deriveSolanaAddress(seed);
|
|
1140
|
-
break;
|
|
1141
|
-
case "ton":
|
|
1142
|
-
address = await deriveTonAddress(seed);
|
|
1143
|
-
break;
|
|
1144
|
-
default:
|
|
1145
|
-
throw new Error(`Unsupported chain: ${chain}`);
|
|
1146
|
-
}
|
|
1147
|
-
return { chain, address, path };
|
|
1148
|
-
} catch (error) {
|
|
1149
|
-
console.error(`Browser derivation failed for ${chain}:`, error);
|
|
1150
|
-
throw error;
|
|
1151
|
-
}
|
|
1146
|
+
async hasItem(key) {
|
|
1147
|
+
return this.storage.has(key);
|
|
1152
1148
|
}
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
*/
|
|
1156
|
-
async deriveAllBrowserAddresses(seed) {
|
|
1157
|
-
return deriveAllAddresses(seed, this.config.network);
|
|
1149
|
+
async clear() {
|
|
1150
|
+
this.storage.clear();
|
|
1158
1151
|
}
|
|
1159
1152
|
};
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1153
|
+
function createSecureStorage() {
|
|
1154
|
+
if (typeof global !== "undefined" && global.nativeModuleProxy !== void 0) {
|
|
1155
|
+
const Platform = global.Platform;
|
|
1156
|
+
if (Platform?.OS === "ios") {
|
|
1157
|
+
return new KeychainStorageAdapter();
|
|
1158
|
+
} else if (Platform?.OS === "android") {
|
|
1159
|
+
return new KeystoreStorageAdapter();
|
|
1160
|
+
}
|
|
1164
1161
|
}
|
|
1165
|
-
|
|
1162
|
+
if (typeof window !== "undefined" && typeof localStorage !== "undefined") {
|
|
1163
|
+
return new WebEncryptedStorageAdapter();
|
|
1164
|
+
}
|
|
1165
|
+
return new MemoryStorageAdapter();
|
|
1166
1166
|
}
|
|
1167
1167
|
|
|
1168
1168
|
// src/wallet/WalletManager.ts
|