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