@zubari/sdk 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +324 -0
- package/dist/SecureStorage-jO783AhC.d.mts +89 -0
- package/dist/SecureStorage-jO783AhC.d.ts +89 -0
- package/dist/SwapService-C0G8IXW2.d.mts +35 -0
- package/dist/SwapService-DZD0OJI_.d.ts +35 -0
- package/dist/WalletManager-DJjdq89b.d.mts +6106 -0
- package/dist/WalletManager-TiAdzqrn.d.ts +6106 -0
- package/dist/index-BLuxEdLp.d.mts +156 -0
- package/dist/index-BLuxEdLp.d.ts +156 -0
- package/dist/index-DO3T2HVe.d.ts +135 -0
- package/dist/index-fXVD8_D0.d.mts +135 -0
- package/dist/index.d.mts +67 -0
- package/dist/index.d.ts +67 -0
- package/dist/index.js +2411 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +2386 -0
- package/dist/index.mjs.map +1 -0
- package/dist/protocols/index.d.mts +181 -0
- package/dist/protocols/index.d.ts +181 -0
- package/dist/protocols/index.js +415 -0
- package/dist/protocols/index.js.map +1 -0
- package/dist/protocols/index.mjs +410 -0
- package/dist/protocols/index.mjs.map +1 -0
- package/dist/react/index.d.mts +49 -0
- package/dist/react/index.d.ts +49 -0
- package/dist/react/index.js +1573 -0
- package/dist/react/index.js.map +1 -0
- package/dist/react/index.mjs +1570 -0
- package/dist/react/index.mjs.map +1 -0
- package/dist/services/index.d.mts +198 -0
- package/dist/services/index.d.ts +198 -0
- package/dist/services/index.js +554 -0
- package/dist/services/index.js.map +1 -0
- package/dist/services/index.mjs +547 -0
- package/dist/services/index.mjs.map +1 -0
- package/dist/storage/index.d.mts +57 -0
- package/dist/storage/index.d.ts +57 -0
- package/dist/storage/index.js +442 -0
- package/dist/storage/index.js.map +1 -0
- package/dist/storage/index.mjs +435 -0
- package/dist/storage/index.mjs.map +1 -0
- package/dist/wallet/index.d.mts +8 -0
- package/dist/wallet/index.d.ts +8 -0
- package/dist/wallet/index.js +1678 -0
- package/dist/wallet/index.js.map +1 -0
- package/dist/wallet/index.mjs +1674 -0
- package/dist/wallet/index.mjs.map +1 -0
- package/package.json +136 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,2411 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var ethers = require('ethers');
|
|
4
|
+
var viem = require('viem');
|
|
5
|
+
var chains = require('viem/chains');
|
|
6
|
+
var react = require('react');
|
|
7
|
+
|
|
8
|
+
// src/config/networks.ts
|
|
9
|
+
var NETWORKS = {
|
|
10
|
+
bitcoin: {
|
|
11
|
+
name: "Bitcoin",
|
|
12
|
+
chainId: 0,
|
|
13
|
+
coinType: 0,
|
|
14
|
+
// m/44'/0'
|
|
15
|
+
rpcUrl: "https://blockstream.info/api",
|
|
16
|
+
explorerUrl: "https://blockstream.info",
|
|
17
|
+
nativeCurrency: {
|
|
18
|
+
name: "Bitcoin",
|
|
19
|
+
symbol: "BTC",
|
|
20
|
+
decimals: 8
|
|
21
|
+
},
|
|
22
|
+
isEvm: false
|
|
23
|
+
},
|
|
24
|
+
ethereum: {
|
|
25
|
+
name: "Ethereum",
|
|
26
|
+
chainId: 1,
|
|
27
|
+
coinType: 60,
|
|
28
|
+
// m/44'/60'
|
|
29
|
+
rpcUrl: "https://eth.llamarpc.com",
|
|
30
|
+
explorerUrl: "https://etherscan.io",
|
|
31
|
+
nativeCurrency: {
|
|
32
|
+
name: "Ether",
|
|
33
|
+
symbol: "ETH",
|
|
34
|
+
decimals: 18
|
|
35
|
+
},
|
|
36
|
+
isEvm: true
|
|
37
|
+
},
|
|
38
|
+
ton: {
|
|
39
|
+
name: "TON",
|
|
40
|
+
chainId: -239,
|
|
41
|
+
coinType: 607,
|
|
42
|
+
// m/44'/607'
|
|
43
|
+
rpcUrl: "https://toncenter.com/api/v2",
|
|
44
|
+
explorerUrl: "https://tonscan.org",
|
|
45
|
+
nativeCurrency: {
|
|
46
|
+
name: "Toncoin",
|
|
47
|
+
symbol: "TON",
|
|
48
|
+
decimals: 9
|
|
49
|
+
},
|
|
50
|
+
isEvm: false
|
|
51
|
+
},
|
|
52
|
+
tron: {
|
|
53
|
+
name: "TRON",
|
|
54
|
+
chainId: 728126428,
|
|
55
|
+
coinType: 195,
|
|
56
|
+
// m/44'/195'
|
|
57
|
+
rpcUrl: "https://api.trongrid.io",
|
|
58
|
+
explorerUrl: "https://tronscan.org",
|
|
59
|
+
nativeCurrency: {
|
|
60
|
+
name: "TRON",
|
|
61
|
+
symbol: "TRX",
|
|
62
|
+
decimals: 6
|
|
63
|
+
},
|
|
64
|
+
isEvm: false
|
|
65
|
+
},
|
|
66
|
+
solana: {
|
|
67
|
+
name: "Solana",
|
|
68
|
+
chainId: 0,
|
|
69
|
+
coinType: 501,
|
|
70
|
+
// m/44'/501'
|
|
71
|
+
rpcUrl: "https://api.mainnet-beta.solana.com",
|
|
72
|
+
explorerUrl: "https://solscan.io",
|
|
73
|
+
nativeCurrency: {
|
|
74
|
+
name: "Solana",
|
|
75
|
+
symbol: "SOL",
|
|
76
|
+
decimals: 9
|
|
77
|
+
},
|
|
78
|
+
isEvm: false
|
|
79
|
+
},
|
|
80
|
+
spark: {
|
|
81
|
+
name: "Spark (Lightning)",
|
|
82
|
+
chainId: 0,
|
|
83
|
+
coinType: 998,
|
|
84
|
+
// m/44'/998'
|
|
85
|
+
rpcUrl: "",
|
|
86
|
+
explorerUrl: "",
|
|
87
|
+
nativeCurrency: {
|
|
88
|
+
name: "Bitcoin",
|
|
89
|
+
symbol: "BTC",
|
|
90
|
+
decimals: 8
|
|
91
|
+
},
|
|
92
|
+
isEvm: false
|
|
93
|
+
}
|
|
94
|
+
};
|
|
95
|
+
var TESTNET_NETWORKS = {
|
|
96
|
+
ethereum: {
|
|
97
|
+
name: "Sepolia",
|
|
98
|
+
chainId: 11155111,
|
|
99
|
+
// Using eth-sepolia.g.alchemy.com public endpoint (more reliable than rpc.sepolia.org)
|
|
100
|
+
// Fallback order: 1. Alchemy public, 2. Infura public, 3. BlockPi
|
|
101
|
+
rpcUrl: "https://ethereum-sepolia-rpc.publicnode.com",
|
|
102
|
+
explorerUrl: "https://sepolia.etherscan.io"
|
|
103
|
+
},
|
|
104
|
+
solana: {
|
|
105
|
+
name: "Solana Devnet",
|
|
106
|
+
rpcUrl: "https://api.devnet.solana.com",
|
|
107
|
+
explorerUrl: "https://solscan.io?cluster=devnet"
|
|
108
|
+
}
|
|
109
|
+
};
|
|
110
|
+
var DERIVATION_PATHS = {
|
|
111
|
+
bitcoin: "m/44'/0'/0'/0",
|
|
112
|
+
ethereum: "m/44'/60'/0'/0",
|
|
113
|
+
ton: "m/44'/607'/0'/0",
|
|
114
|
+
tron: "m/44'/195'/0'/0",
|
|
115
|
+
solana: "m/44'/501'/0'/0",
|
|
116
|
+
spark: "m/44'/998'/0'/0"
|
|
117
|
+
};
|
|
118
|
+
function getNetworkConfig(network, isTestnet = false) {
|
|
119
|
+
const mainnetConfig = NETWORKS[network];
|
|
120
|
+
if (!isTestnet) return mainnetConfig;
|
|
121
|
+
const testnetOverrides = TESTNET_NETWORKS[network];
|
|
122
|
+
if (!testnetOverrides) return mainnetConfig;
|
|
123
|
+
return {
|
|
124
|
+
...mainnetConfig,
|
|
125
|
+
...testnetOverrides
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// src/config/contracts.ts
|
|
130
|
+
var ZERO_ADDRESS = "0x0000000000000000000000000000000000000000";
|
|
131
|
+
var ZUBARI_CONTRACTS = {
|
|
132
|
+
testnet: {
|
|
133
|
+
// Ethereum Sepolia (11155111) - Deployed 2024-12-09
|
|
134
|
+
registry: "0xEdDf443D48832f23D4A0bED4C4c5eF200B38A7d3",
|
|
135
|
+
nft: "0xdc37e25650D685e4c38124aC314477Ea5f508a9e",
|
|
136
|
+
marketplace: ZERO_ADDRESS,
|
|
137
|
+
// Not yet deployed
|
|
138
|
+
tips: "0xFDc353edC63Cd3D4bba35bB43861369516a9Dc85",
|
|
139
|
+
subscriptions: "0x8C05F8aD2F295fB7f3596043a7c37C98A5F7fAB8",
|
|
140
|
+
payouts: "0x804Fe503936E8b8d3D5Dbb62AF4fB6Fe7265Fb2c",
|
|
141
|
+
entryPoint: "0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789",
|
|
142
|
+
paymaster: ZERO_ADDRESS,
|
|
143
|
+
// Not yet deployed
|
|
144
|
+
accountFactory: ZERO_ADDRESS,
|
|
145
|
+
// Not yet deployed
|
|
146
|
+
usdt: "0xaA8E23Fb1079EA71e0a56F48a2aA51851D8433D0",
|
|
147
|
+
// USDT on Sepolia
|
|
148
|
+
weth: "0xfFf9976782d46CC05630D1f6eBAb18b2324d6B14"
|
|
149
|
+
},
|
|
150
|
+
mainnet: {
|
|
151
|
+
// Ethereum Mainnet (1)
|
|
152
|
+
registry: ZERO_ADDRESS,
|
|
153
|
+
nft: ZERO_ADDRESS,
|
|
154
|
+
marketplace: ZERO_ADDRESS,
|
|
155
|
+
tips: ZERO_ADDRESS,
|
|
156
|
+
subscriptions: ZERO_ADDRESS,
|
|
157
|
+
payouts: ZERO_ADDRESS,
|
|
158
|
+
entryPoint: "0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789",
|
|
159
|
+
paymaster: ZERO_ADDRESS,
|
|
160
|
+
accountFactory: ZERO_ADDRESS,
|
|
161
|
+
usdt: "0xdAC17F958D2ee523a2206206994597C13D831ec7",
|
|
162
|
+
// USDT on Ethereum
|
|
163
|
+
weth: "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2"
|
|
164
|
+
}
|
|
165
|
+
};
|
|
166
|
+
var PLATFORM_CONFIG = {
|
|
167
|
+
// Platform fee in basis points (300 = 3%)
|
|
168
|
+
tipFeeBps: 300,
|
|
169
|
+
// Maximum royalty in basis points (1000 = 10%)
|
|
170
|
+
maxRoyaltyBps: 1e3,
|
|
171
|
+
// Default slippage tolerance for swaps (50 = 0.5%)
|
|
172
|
+
defaultSlippageBps: 50,
|
|
173
|
+
// Voucher validity in seconds (30 days)
|
|
174
|
+
voucherValiditySecs: 30 * 24 * 60 * 60,
|
|
175
|
+
// Swap deadline in seconds (30 minutes)
|
|
176
|
+
swapDeadlineSecs: 30 * 60
|
|
177
|
+
};
|
|
178
|
+
var NFT_VOUCHER_DOMAIN = {
|
|
179
|
+
name: "ZubariNFT",
|
|
180
|
+
version: "1"
|
|
181
|
+
};
|
|
182
|
+
var NFT_VOUCHER_TYPES = {
|
|
183
|
+
NFTVoucher: [
|
|
184
|
+
{ name: "tokenId", type: "bytes32" },
|
|
185
|
+
{ name: "uri", type: "string" },
|
|
186
|
+
{ name: "creator", type: "address" },
|
|
187
|
+
{ name: "royaltyBps", type: "uint256" },
|
|
188
|
+
{ name: "deadline", type: "uint256" }
|
|
189
|
+
]
|
|
190
|
+
};
|
|
191
|
+
function getContractAddresses(network) {
|
|
192
|
+
return ZUBARI_CONTRACTS[network];
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// src/wallet/ZubariWallet.ts
|
|
196
|
+
var ZubariWallet = class {
|
|
197
|
+
seed;
|
|
198
|
+
config;
|
|
199
|
+
accounts = /* @__PURE__ */ new Map();
|
|
200
|
+
initialized = false;
|
|
201
|
+
constructor(seed, config) {
|
|
202
|
+
this.seed = seed;
|
|
203
|
+
this.config = {
|
|
204
|
+
network: config.network || "mainnet",
|
|
205
|
+
enabledNetworks: config.enabledNetworks || ["ethereum"],
|
|
206
|
+
gasless: config.gasless ?? false,
|
|
207
|
+
paymasterUrl: config.paymasterUrl,
|
|
208
|
+
bundlerUrl: config.bundlerUrl,
|
|
209
|
+
rpcUrls: config.rpcUrls
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
/**
|
|
213
|
+
* Initialize the wallet by deriving accounts for all enabled networks
|
|
214
|
+
*/
|
|
215
|
+
async initialize() {
|
|
216
|
+
if (this.initialized) return;
|
|
217
|
+
for (const network of this.config.enabledNetworks) {
|
|
218
|
+
await this.deriveAccount(network);
|
|
219
|
+
}
|
|
220
|
+
this.initialized = true;
|
|
221
|
+
}
|
|
222
|
+
/**
|
|
223
|
+
* Derive account for a specific network using BIP-44
|
|
224
|
+
*/
|
|
225
|
+
async deriveAccount(network, index = 0) {
|
|
226
|
+
getNetworkConfig(network, this.config.network === "testnet");
|
|
227
|
+
const basePath = DERIVATION_PATHS[network];
|
|
228
|
+
const derivationPath = `${basePath}/${index}`;
|
|
229
|
+
const account = {
|
|
230
|
+
network,
|
|
231
|
+
address: "",
|
|
232
|
+
// Will be derived using WDK
|
|
233
|
+
publicKey: "",
|
|
234
|
+
derivationPath
|
|
235
|
+
};
|
|
236
|
+
this.accounts.set(network, account);
|
|
237
|
+
return account;
|
|
238
|
+
}
|
|
239
|
+
/**
|
|
240
|
+
* Get account for a specific network
|
|
241
|
+
*/
|
|
242
|
+
async getAccount(network, index = 0) {
|
|
243
|
+
const existing = this.accounts.get(network);
|
|
244
|
+
if (existing && existing.derivationPath.endsWith(`/${index}`)) {
|
|
245
|
+
return existing;
|
|
246
|
+
}
|
|
247
|
+
return this.deriveAccount(network, index);
|
|
248
|
+
}
|
|
249
|
+
/**
|
|
250
|
+
* Get address for a specific network
|
|
251
|
+
*/
|
|
252
|
+
async getAddress(network) {
|
|
253
|
+
const account = await this.getAccount(network);
|
|
254
|
+
return account.address;
|
|
255
|
+
}
|
|
256
|
+
/**
|
|
257
|
+
* Get all addresses for enabled networks
|
|
258
|
+
*/
|
|
259
|
+
async getAllAddresses() {
|
|
260
|
+
const addresses = {};
|
|
261
|
+
for (const network of this.config.enabledNetworks) {
|
|
262
|
+
addresses[network] = await this.getAddress(network);
|
|
263
|
+
}
|
|
264
|
+
return addresses;
|
|
265
|
+
}
|
|
266
|
+
/**
|
|
267
|
+
* Get balance for a specific network
|
|
268
|
+
*/
|
|
269
|
+
async getBalance(network) {
|
|
270
|
+
const networkConfig = getNetworkConfig(network, this.config.network === "testnet");
|
|
271
|
+
return {
|
|
272
|
+
network,
|
|
273
|
+
native: {
|
|
274
|
+
symbol: networkConfig.nativeCurrency.symbol,
|
|
275
|
+
balance: BigInt(0),
|
|
276
|
+
balanceFormatted: "0",
|
|
277
|
+
balanceUsd: 0
|
|
278
|
+
},
|
|
279
|
+
tokens: []
|
|
280
|
+
};
|
|
281
|
+
}
|
|
282
|
+
/**
|
|
283
|
+
* Get balances for all enabled networks
|
|
284
|
+
*/
|
|
285
|
+
async getAllBalances() {
|
|
286
|
+
const balances = [];
|
|
287
|
+
for (const network of this.config.enabledNetworks) {
|
|
288
|
+
balances.push(await this.getBalance(network));
|
|
289
|
+
}
|
|
290
|
+
return balances;
|
|
291
|
+
}
|
|
292
|
+
/**
|
|
293
|
+
* Get total portfolio value in USD
|
|
294
|
+
*/
|
|
295
|
+
async getTotalPortfolioUsd() {
|
|
296
|
+
const balances = await this.getAllBalances();
|
|
297
|
+
let total = 0;
|
|
298
|
+
for (const balance of balances) {
|
|
299
|
+
total += balance.native.balanceUsd;
|
|
300
|
+
for (const token of balance.tokens) {
|
|
301
|
+
total += token.balanceUsd;
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
return total;
|
|
305
|
+
}
|
|
306
|
+
/**
|
|
307
|
+
* Send native currency on a specific network
|
|
308
|
+
*/
|
|
309
|
+
async send(network, params) {
|
|
310
|
+
const { to, amount, gasless } = params;
|
|
311
|
+
gasless ?? (this.config.gasless && network === "ethereum");
|
|
312
|
+
return {
|
|
313
|
+
hash: "",
|
|
314
|
+
network,
|
|
315
|
+
status: "pending"
|
|
316
|
+
};
|
|
317
|
+
}
|
|
318
|
+
/**
|
|
319
|
+
* Send ERC-20 token on EVM networks
|
|
320
|
+
*/
|
|
321
|
+
async sendToken(network, token, to, amount) {
|
|
322
|
+
const networkConfig = getNetworkConfig(network, this.config.network === "testnet");
|
|
323
|
+
if (!networkConfig.isEvm) {
|
|
324
|
+
throw new Error(`sendToken is only supported on EVM networks. ${network} is not an EVM chain.`);
|
|
325
|
+
}
|
|
326
|
+
return {
|
|
327
|
+
hash: "",
|
|
328
|
+
network,
|
|
329
|
+
status: "pending"
|
|
330
|
+
};
|
|
331
|
+
}
|
|
332
|
+
/**
|
|
333
|
+
* Send Bitcoin on-chain using WalletManagerBtc
|
|
334
|
+
* @param to - Destination address (bc1q... for native segwit)
|
|
335
|
+
* @param amount - Amount in satoshis
|
|
336
|
+
*/
|
|
337
|
+
async sendBitcoin(to, amount) {
|
|
338
|
+
if (!this.isValidBitcoinAddress(to)) {
|
|
339
|
+
throw new Error("Invalid Bitcoin address. Expected bc1q... (native segwit) format.");
|
|
340
|
+
}
|
|
341
|
+
return this.send("bitcoin", { to, amount });
|
|
342
|
+
}
|
|
343
|
+
/**
|
|
344
|
+
* Validate Bitcoin address format
|
|
345
|
+
*/
|
|
346
|
+
isValidBitcoinAddress(address) {
|
|
347
|
+
if (address.startsWith("bc1q") || address.startsWith("tb1q")) {
|
|
348
|
+
return address.length >= 42 && address.length <= 62;
|
|
349
|
+
}
|
|
350
|
+
if (address.startsWith("1") || address.startsWith("m") || address.startsWith("n")) {
|
|
351
|
+
return address.length >= 25 && address.length <= 34;
|
|
352
|
+
}
|
|
353
|
+
if (address.startsWith("3") || address.startsWith("2")) {
|
|
354
|
+
return address.length >= 25 && address.length <= 34;
|
|
355
|
+
}
|
|
356
|
+
return false;
|
|
357
|
+
}
|
|
358
|
+
/**
|
|
359
|
+
* Get Bitcoin address (native segwit bc1q...)
|
|
360
|
+
*/
|
|
361
|
+
async getBitcoinAddress() {
|
|
362
|
+
const account = await this.getAccount("bitcoin");
|
|
363
|
+
return account.address;
|
|
364
|
+
}
|
|
365
|
+
/**
|
|
366
|
+
* Pay Lightning invoice via Spark network
|
|
367
|
+
* Uses WDK WalletManagerSpark (m/44'/998')
|
|
368
|
+
* @param invoice - Lightning invoice string (lnbc...)
|
|
369
|
+
*/
|
|
370
|
+
async sendLightning(invoice) {
|
|
371
|
+
if (!this.isValidLightningInvoice(invoice)) {
|
|
372
|
+
throw new Error("Invalid Lightning invoice format. Expected lnbc... or lntb...");
|
|
373
|
+
}
|
|
374
|
+
const invoiceDetails = this.decodeLightningInvoice(invoice);
|
|
375
|
+
return {
|
|
376
|
+
hash: "",
|
|
377
|
+
// Will be payment hash from Spark
|
|
378
|
+
network: "spark",
|
|
379
|
+
status: "pending",
|
|
380
|
+
metadata: {
|
|
381
|
+
invoice,
|
|
382
|
+
amount: invoiceDetails.amount,
|
|
383
|
+
destination: invoiceDetails.destination
|
|
384
|
+
}
|
|
385
|
+
};
|
|
386
|
+
}
|
|
387
|
+
/**
|
|
388
|
+
* Create Lightning invoice via Spark
|
|
389
|
+
* @param amount - Amount in millisatoshis
|
|
390
|
+
* @param memo - Optional payment memo
|
|
391
|
+
* @returns Lightning invoice string (lnbc...)
|
|
392
|
+
*/
|
|
393
|
+
async createLightningInvoice(amount, memo) {
|
|
394
|
+
if (amount <= 0) {
|
|
395
|
+
throw new Error("Invoice amount must be greater than 0");
|
|
396
|
+
}
|
|
397
|
+
const isTestnet = this.config.network === "testnet";
|
|
398
|
+
const prefix = isTestnet ? "lntb" : "lnbc";
|
|
399
|
+
return `${prefix}${amount}m1...`;
|
|
400
|
+
}
|
|
401
|
+
/**
|
|
402
|
+
* Validate Lightning invoice format
|
|
403
|
+
*/
|
|
404
|
+
isValidLightningInvoice(invoice) {
|
|
405
|
+
const lowerInvoice = invoice.toLowerCase();
|
|
406
|
+
return lowerInvoice.startsWith("lnbc") || // Mainnet
|
|
407
|
+
lowerInvoice.startsWith("lntb") || // Testnet
|
|
408
|
+
lowerInvoice.startsWith("lnbcrt");
|
|
409
|
+
}
|
|
410
|
+
/**
|
|
411
|
+
* Decode Lightning invoice (basic parsing)
|
|
412
|
+
*/
|
|
413
|
+
decodeLightningInvoice(invoice) {
|
|
414
|
+
return {
|
|
415
|
+
amount: BigInt(0),
|
|
416
|
+
destination: "",
|
|
417
|
+
expiry: 3600
|
|
418
|
+
};
|
|
419
|
+
}
|
|
420
|
+
/**
|
|
421
|
+
* Get Lightning (Spark) balance
|
|
422
|
+
*/
|
|
423
|
+
async getLightningBalance() {
|
|
424
|
+
return {
|
|
425
|
+
available: BigInt(0),
|
|
426
|
+
pending: BigInt(0)
|
|
427
|
+
};
|
|
428
|
+
}
|
|
429
|
+
/**
|
|
430
|
+
* Get configuration
|
|
431
|
+
*/
|
|
432
|
+
getConfig() {
|
|
433
|
+
return { ...this.config };
|
|
434
|
+
}
|
|
435
|
+
/**
|
|
436
|
+
* Check if wallet is initialized
|
|
437
|
+
*/
|
|
438
|
+
isInitialized() {
|
|
439
|
+
return this.initialized;
|
|
440
|
+
}
|
|
441
|
+
/**
|
|
442
|
+
* Get contract addresses for current network
|
|
443
|
+
*/
|
|
444
|
+
getContractAddresses() {
|
|
445
|
+
return getContractAddresses(this.config.network);
|
|
446
|
+
}
|
|
447
|
+
};
|
|
448
|
+
|
|
449
|
+
// src/security/KeyManager.ts
|
|
450
|
+
var KeyManager = class {
|
|
451
|
+
static ALGORITHM = "AES-GCM";
|
|
452
|
+
static KEY_LENGTH = 256;
|
|
453
|
+
static IV_LENGTH = 12;
|
|
454
|
+
static SALT_LENGTH = 16;
|
|
455
|
+
static PBKDF2_ITERATIONS = 1e5;
|
|
456
|
+
/**
|
|
457
|
+
* Encrypt a seed phrase with a password
|
|
458
|
+
*/
|
|
459
|
+
static async encryptSeed(seed, password) {
|
|
460
|
+
const encoder = new TextEncoder();
|
|
461
|
+
const seedData = encoder.encode(seed);
|
|
462
|
+
const salt = crypto.getRandomValues(new Uint8Array(this.SALT_LENGTH));
|
|
463
|
+
const iv = crypto.getRandomValues(new Uint8Array(this.IV_LENGTH));
|
|
464
|
+
const key = await this.deriveKey(password, salt);
|
|
465
|
+
const encrypted = await crypto.subtle.encrypt(
|
|
466
|
+
{ name: this.ALGORITHM, iv },
|
|
467
|
+
key,
|
|
468
|
+
seedData
|
|
469
|
+
);
|
|
470
|
+
const combined = new Uint8Array(salt.length + iv.length + encrypted.byteLength);
|
|
471
|
+
combined.set(salt, 0);
|
|
472
|
+
combined.set(iv, salt.length);
|
|
473
|
+
combined.set(new Uint8Array(encrypted), salt.length + iv.length);
|
|
474
|
+
return btoa(String.fromCharCode(...combined));
|
|
475
|
+
}
|
|
476
|
+
/**
|
|
477
|
+
* Decrypt a seed phrase with a password
|
|
478
|
+
*/
|
|
479
|
+
static async decryptSeed(encryptedData, password) {
|
|
480
|
+
const combined = new Uint8Array(
|
|
481
|
+
atob(encryptedData).split("").map((c) => c.charCodeAt(0))
|
|
482
|
+
);
|
|
483
|
+
const salt = combined.slice(0, this.SALT_LENGTH);
|
|
484
|
+
const iv = combined.slice(this.SALT_LENGTH, this.SALT_LENGTH + this.IV_LENGTH);
|
|
485
|
+
const encrypted = combined.slice(this.SALT_LENGTH + this.IV_LENGTH);
|
|
486
|
+
const key = await this.deriveKey(password, salt);
|
|
487
|
+
const decrypted = await crypto.subtle.decrypt(
|
|
488
|
+
{ name: this.ALGORITHM, iv },
|
|
489
|
+
key,
|
|
490
|
+
encrypted
|
|
491
|
+
);
|
|
492
|
+
const decoder = new TextDecoder();
|
|
493
|
+
return decoder.decode(decrypted);
|
|
494
|
+
}
|
|
495
|
+
/**
|
|
496
|
+
* Derive encryption key from password using PBKDF2
|
|
497
|
+
*/
|
|
498
|
+
static async deriveKey(password, salt) {
|
|
499
|
+
const encoder = new TextEncoder();
|
|
500
|
+
const passwordData = encoder.encode(password);
|
|
501
|
+
const keyMaterial = await crypto.subtle.importKey(
|
|
502
|
+
"raw",
|
|
503
|
+
passwordData,
|
|
504
|
+
"PBKDF2",
|
|
505
|
+
false,
|
|
506
|
+
["deriveKey"]
|
|
507
|
+
);
|
|
508
|
+
return crypto.subtle.deriveKey(
|
|
509
|
+
{
|
|
510
|
+
name: "PBKDF2",
|
|
511
|
+
salt: salt.buffer.slice(salt.byteOffset, salt.byteOffset + salt.byteLength),
|
|
512
|
+
iterations: this.PBKDF2_ITERATIONS,
|
|
513
|
+
hash: "SHA-256"
|
|
514
|
+
},
|
|
515
|
+
keyMaterial,
|
|
516
|
+
{ name: this.ALGORITHM, length: this.KEY_LENGTH },
|
|
517
|
+
false,
|
|
518
|
+
["encrypt", "decrypt"]
|
|
519
|
+
);
|
|
520
|
+
}
|
|
521
|
+
/**
|
|
522
|
+
* Validate a BIP-39 seed phrase (basic validation)
|
|
523
|
+
*/
|
|
524
|
+
static validateSeedPhrase(seed) {
|
|
525
|
+
const words = seed.trim().split(/\s+/);
|
|
526
|
+
const validWordCounts = [12, 15, 18, 21, 24];
|
|
527
|
+
return validWordCounts.includes(words.length);
|
|
528
|
+
}
|
|
529
|
+
/**
|
|
530
|
+
* Generate a random encryption key (for backup purposes)
|
|
531
|
+
*/
|
|
532
|
+
static generateBackupKey() {
|
|
533
|
+
const bytes = crypto.getRandomValues(new Uint8Array(32));
|
|
534
|
+
return Array.from(bytes).map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
535
|
+
}
|
|
536
|
+
};
|
|
537
|
+
|
|
538
|
+
// src/storage/SecureStorage.ts
|
|
539
|
+
var KeychainStorageAdapter = class {
|
|
540
|
+
serviceName;
|
|
541
|
+
constructor(serviceName = "com.zubari.wallet") {
|
|
542
|
+
this.serviceName = serviceName;
|
|
543
|
+
}
|
|
544
|
+
async setItem(key, value) {
|
|
545
|
+
if (typeof global !== "undefined" && global.KeychainModule) {
|
|
546
|
+
await global.KeychainModule.setItem(this.serviceName, key, value);
|
|
547
|
+
} else {
|
|
548
|
+
throw new Error("Keychain not available on this platform");
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
async getItem(key) {
|
|
552
|
+
if (typeof global !== "undefined" && global.KeychainModule) {
|
|
553
|
+
return global.KeychainModule.getItem(this.serviceName, key);
|
|
554
|
+
}
|
|
555
|
+
throw new Error("Keychain not available on this platform");
|
|
556
|
+
}
|
|
557
|
+
async removeItem(key) {
|
|
558
|
+
if (typeof global !== "undefined" && global.KeychainModule) {
|
|
559
|
+
await global.KeychainModule.removeItem(this.serviceName, key);
|
|
560
|
+
} else {
|
|
561
|
+
throw new Error("Keychain not available on this platform");
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
async hasItem(key) {
|
|
565
|
+
const value = await this.getItem(key);
|
|
566
|
+
return value !== null;
|
|
567
|
+
}
|
|
568
|
+
async clear() {
|
|
569
|
+
if (typeof global !== "undefined" && global.KeychainModule) {
|
|
570
|
+
await global.KeychainModule.clear(this.serviceName);
|
|
571
|
+
} else {
|
|
572
|
+
throw new Error("Keychain not available on this platform");
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
};
|
|
576
|
+
var KeystoreStorageAdapter = class {
|
|
577
|
+
alias;
|
|
578
|
+
constructor(alias = "zubari_wallet_keys") {
|
|
579
|
+
this.alias = alias;
|
|
580
|
+
}
|
|
581
|
+
async setItem(key, value) {
|
|
582
|
+
if (typeof global !== "undefined" && global.KeystoreModule) {
|
|
583
|
+
await global.KeystoreModule.setItem(this.alias, key, value);
|
|
584
|
+
} else {
|
|
585
|
+
throw new Error("Keystore not available on this platform");
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
async getItem(key) {
|
|
589
|
+
if (typeof global !== "undefined" && global.KeystoreModule) {
|
|
590
|
+
return global.KeystoreModule.getItem(this.alias, key);
|
|
591
|
+
}
|
|
592
|
+
throw new Error("Keystore not available on this platform");
|
|
593
|
+
}
|
|
594
|
+
async removeItem(key) {
|
|
595
|
+
if (typeof global !== "undefined" && global.KeystoreModule) {
|
|
596
|
+
await global.KeystoreModule.removeItem(this.alias, key);
|
|
597
|
+
} else {
|
|
598
|
+
throw new Error("Keystore not available on this platform");
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
async hasItem(key) {
|
|
602
|
+
const value = await this.getItem(key);
|
|
603
|
+
return value !== null;
|
|
604
|
+
}
|
|
605
|
+
async clear() {
|
|
606
|
+
if (typeof global !== "undefined" && global.KeystoreModule) {
|
|
607
|
+
await global.KeystoreModule.clear(this.alias);
|
|
608
|
+
} else {
|
|
609
|
+
throw new Error("Keystore not available on this platform");
|
|
610
|
+
}
|
|
611
|
+
}
|
|
612
|
+
};
|
|
613
|
+
var WebEncryptedStorageAdapter = class {
|
|
614
|
+
encryptionKey = null;
|
|
615
|
+
storagePrefix;
|
|
616
|
+
constructor(storagePrefix = "zubari_") {
|
|
617
|
+
this.storagePrefix = storagePrefix;
|
|
618
|
+
}
|
|
619
|
+
/**
|
|
620
|
+
* Initialize with a password-derived key
|
|
621
|
+
*/
|
|
622
|
+
async initialize(password) {
|
|
623
|
+
const encoder = new TextEncoder();
|
|
624
|
+
const salt = this.getSalt();
|
|
625
|
+
const keyMaterial = await crypto.subtle.importKey(
|
|
626
|
+
"raw",
|
|
627
|
+
encoder.encode(password),
|
|
628
|
+
"PBKDF2",
|
|
629
|
+
false,
|
|
630
|
+
["deriveKey"]
|
|
631
|
+
);
|
|
632
|
+
this.encryptionKey = await crypto.subtle.deriveKey(
|
|
633
|
+
{
|
|
634
|
+
name: "PBKDF2",
|
|
635
|
+
salt: salt.buffer,
|
|
636
|
+
iterations: 1e5,
|
|
637
|
+
hash: "SHA-256"
|
|
638
|
+
},
|
|
639
|
+
keyMaterial,
|
|
640
|
+
{ name: "AES-GCM", length: 256 },
|
|
641
|
+
false,
|
|
642
|
+
["encrypt", "decrypt"]
|
|
643
|
+
);
|
|
644
|
+
}
|
|
645
|
+
getSalt() {
|
|
646
|
+
const saltKey = `${this.storagePrefix}salt`;
|
|
647
|
+
let saltHex = localStorage.getItem(saltKey);
|
|
648
|
+
if (!saltHex) {
|
|
649
|
+
const salt = crypto.getRandomValues(new Uint8Array(16));
|
|
650
|
+
saltHex = Array.from(salt).map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
651
|
+
localStorage.setItem(saltKey, saltHex);
|
|
652
|
+
}
|
|
653
|
+
return new Uint8Array(
|
|
654
|
+
saltHex.match(/.{1,2}/g).map((byte) => parseInt(byte, 16))
|
|
655
|
+
);
|
|
656
|
+
}
|
|
657
|
+
async setItem(key, value) {
|
|
658
|
+
if (!this.encryptionKey) {
|
|
659
|
+
throw new Error("Storage not initialized. Call initialize() first.");
|
|
660
|
+
}
|
|
661
|
+
const encoder = new TextEncoder();
|
|
662
|
+
const iv = crypto.getRandomValues(new Uint8Array(12));
|
|
663
|
+
const encrypted = await crypto.subtle.encrypt(
|
|
664
|
+
{ name: "AES-GCM", iv },
|
|
665
|
+
this.encryptionKey,
|
|
666
|
+
encoder.encode(value)
|
|
667
|
+
);
|
|
668
|
+
const combined = new Uint8Array(iv.length + encrypted.byteLength);
|
|
669
|
+
combined.set(iv);
|
|
670
|
+
combined.set(new Uint8Array(encrypted), iv.length);
|
|
671
|
+
const base64 = btoa(String.fromCharCode(...combined));
|
|
672
|
+
localStorage.setItem(`${this.storagePrefix}${key}`, base64);
|
|
673
|
+
}
|
|
674
|
+
async getItem(key) {
|
|
675
|
+
if (!this.encryptionKey) {
|
|
676
|
+
throw new Error("Storage not initialized. Call initialize() first.");
|
|
677
|
+
}
|
|
678
|
+
const base64 = localStorage.getItem(`${this.storagePrefix}${key}`);
|
|
679
|
+
if (!base64) return null;
|
|
680
|
+
try {
|
|
681
|
+
const combined = new Uint8Array(
|
|
682
|
+
atob(base64).split("").map((c) => c.charCodeAt(0))
|
|
683
|
+
);
|
|
684
|
+
const iv = combined.slice(0, 12);
|
|
685
|
+
const encrypted = combined.slice(12);
|
|
686
|
+
const decrypted = await crypto.subtle.decrypt(
|
|
687
|
+
{ name: "AES-GCM", iv },
|
|
688
|
+
this.encryptionKey,
|
|
689
|
+
encrypted
|
|
690
|
+
);
|
|
691
|
+
const decoder = new TextDecoder();
|
|
692
|
+
return decoder.decode(decrypted);
|
|
693
|
+
} catch {
|
|
694
|
+
return null;
|
|
695
|
+
}
|
|
696
|
+
}
|
|
697
|
+
async removeItem(key) {
|
|
698
|
+
localStorage.removeItem(`${this.storagePrefix}${key}`);
|
|
699
|
+
}
|
|
700
|
+
async hasItem(key) {
|
|
701
|
+
return localStorage.getItem(`${this.storagePrefix}${key}`) !== null;
|
|
702
|
+
}
|
|
703
|
+
async clear() {
|
|
704
|
+
const keysToRemove = [];
|
|
705
|
+
for (let i = 0; i < localStorage.length; i++) {
|
|
706
|
+
const key = localStorage.key(i);
|
|
707
|
+
if (key?.startsWith(this.storagePrefix)) {
|
|
708
|
+
keysToRemove.push(key);
|
|
709
|
+
}
|
|
710
|
+
}
|
|
711
|
+
keysToRemove.forEach((key) => localStorage.removeItem(key));
|
|
712
|
+
}
|
|
713
|
+
};
|
|
714
|
+
var MemoryStorageAdapter = class {
|
|
715
|
+
storage = /* @__PURE__ */ new Map();
|
|
716
|
+
async setItem(key, value) {
|
|
717
|
+
this.storage.set(key, value);
|
|
718
|
+
}
|
|
719
|
+
async getItem(key) {
|
|
720
|
+
return this.storage.get(key) || null;
|
|
721
|
+
}
|
|
722
|
+
async removeItem(key) {
|
|
723
|
+
this.storage.delete(key);
|
|
724
|
+
}
|
|
725
|
+
async hasItem(key) {
|
|
726
|
+
return this.storage.has(key);
|
|
727
|
+
}
|
|
728
|
+
async clear() {
|
|
729
|
+
this.storage.clear();
|
|
730
|
+
}
|
|
731
|
+
};
|
|
732
|
+
function createSecureStorage() {
|
|
733
|
+
if (typeof global !== "undefined" && global.nativeModuleProxy !== void 0) {
|
|
734
|
+
const Platform = global.Platform;
|
|
735
|
+
if (Platform?.OS === "ios") {
|
|
736
|
+
return new KeychainStorageAdapter();
|
|
737
|
+
} else if (Platform?.OS === "android") {
|
|
738
|
+
return new KeystoreStorageAdapter();
|
|
739
|
+
}
|
|
740
|
+
}
|
|
741
|
+
if (typeof window !== "undefined" && typeof localStorage !== "undefined") {
|
|
742
|
+
return new WebEncryptedStorageAdapter();
|
|
743
|
+
}
|
|
744
|
+
return new MemoryStorageAdapter();
|
|
745
|
+
}
|
|
746
|
+
|
|
747
|
+
// src/services/WdkApiClient.ts
|
|
748
|
+
var WdkApiClient = class {
|
|
749
|
+
config;
|
|
750
|
+
constructor(config) {
|
|
751
|
+
this.config = {
|
|
752
|
+
baseUrl: config.baseUrl,
|
|
753
|
+
timeout: config.timeout || 3e4
|
|
754
|
+
};
|
|
755
|
+
}
|
|
756
|
+
/**
|
|
757
|
+
* Generate a new BIP-39 seed phrase using Tether WDK
|
|
758
|
+
*/
|
|
759
|
+
async generateSeed() {
|
|
760
|
+
try {
|
|
761
|
+
const response = await fetch(`${this.config.baseUrl}/api/wallets/wdk/generate-seed`, {
|
|
762
|
+
method: "POST",
|
|
763
|
+
headers: {
|
|
764
|
+
"Content-Type": "application/json"
|
|
765
|
+
}
|
|
766
|
+
});
|
|
767
|
+
return await response.json();
|
|
768
|
+
} catch (error) {
|
|
769
|
+
return {
|
|
770
|
+
success: false,
|
|
771
|
+
error: error instanceof Error ? error.message : "Failed to generate seed"
|
|
772
|
+
};
|
|
773
|
+
}
|
|
774
|
+
}
|
|
775
|
+
/**
|
|
776
|
+
* Validate a BIP-39 seed phrase
|
|
777
|
+
*/
|
|
778
|
+
async validateSeed(seed) {
|
|
779
|
+
try {
|
|
780
|
+
const response = await fetch(`${this.config.baseUrl}/api/wallets/wdk/validate-seed`, {
|
|
781
|
+
method: "POST",
|
|
782
|
+
headers: {
|
|
783
|
+
"Content-Type": "application/json"
|
|
784
|
+
},
|
|
785
|
+
body: JSON.stringify({ seed })
|
|
786
|
+
});
|
|
787
|
+
return await response.json();
|
|
788
|
+
} catch (error) {
|
|
789
|
+
return {
|
|
790
|
+
success: false,
|
|
791
|
+
error: error instanceof Error ? error.message : "Failed to validate seed"
|
|
792
|
+
};
|
|
793
|
+
}
|
|
794
|
+
}
|
|
795
|
+
/**
|
|
796
|
+
* Derive address for a specific chain using Tether WDK
|
|
797
|
+
*/
|
|
798
|
+
async deriveAddress(seed, chain, network = "testnet") {
|
|
799
|
+
try {
|
|
800
|
+
const response = await fetch(`${this.config.baseUrl}/api/wallets/wdk/derive-address`, {
|
|
801
|
+
method: "POST",
|
|
802
|
+
headers: {
|
|
803
|
+
"Content-Type": "application/json"
|
|
804
|
+
},
|
|
805
|
+
body: JSON.stringify({ seed, chain, network })
|
|
806
|
+
});
|
|
807
|
+
return await response.json();
|
|
808
|
+
} catch (error) {
|
|
809
|
+
return {
|
|
810
|
+
success: false,
|
|
811
|
+
error: error instanceof Error ? error.message : "Failed to derive address"
|
|
812
|
+
};
|
|
813
|
+
}
|
|
814
|
+
}
|
|
815
|
+
/**
|
|
816
|
+
* Derive addresses for all chains using Tether WDK
|
|
817
|
+
*/
|
|
818
|
+
async deriveAllAddresses(seed, network = "testnet") {
|
|
819
|
+
try {
|
|
820
|
+
const response = await fetch(`${this.config.baseUrl}/api/wallets/wdk/derive-all`, {
|
|
821
|
+
method: "POST",
|
|
822
|
+
headers: {
|
|
823
|
+
"Content-Type": "application/json"
|
|
824
|
+
},
|
|
825
|
+
body: JSON.stringify({ seed, network })
|
|
826
|
+
});
|
|
827
|
+
return await response.json();
|
|
828
|
+
} catch (error) {
|
|
829
|
+
return {
|
|
830
|
+
success: false,
|
|
831
|
+
error: error instanceof Error ? error.message : "Failed to derive addresses"
|
|
832
|
+
};
|
|
833
|
+
}
|
|
834
|
+
}
|
|
835
|
+
};
|
|
836
|
+
var DEFAULT_API_URL = process.env.NEXT_PUBLIC_API_URL || "http://localhost:3001";
|
|
837
|
+
var wdkApiClient = null;
|
|
838
|
+
function getWdkApiClient(baseUrl) {
|
|
839
|
+
if (!wdkApiClient || baseUrl && wdkApiClient["config"].baseUrl !== baseUrl) {
|
|
840
|
+
wdkApiClient = new WdkApiClient({
|
|
841
|
+
baseUrl: baseUrl || DEFAULT_API_URL
|
|
842
|
+
});
|
|
843
|
+
}
|
|
844
|
+
return wdkApiClient;
|
|
845
|
+
}
|
|
846
|
+
|
|
847
|
+
// src/services/WdkService.ts
|
|
848
|
+
var WdkManager;
|
|
849
|
+
var WalletManagerBtc;
|
|
850
|
+
var WalletManagerEvm;
|
|
851
|
+
var WalletManagerSolana;
|
|
852
|
+
var WalletManagerTon;
|
|
853
|
+
var WalletManagerTron;
|
|
854
|
+
var WalletManagerSpark;
|
|
855
|
+
var wdkLoaded = false;
|
|
856
|
+
var wdkLoadError = null;
|
|
857
|
+
var dynamicImport = new Function("specifier", "return import(specifier)");
|
|
858
|
+
async function loadWdkModules() {
|
|
859
|
+
if (wdkLoaded) return;
|
|
860
|
+
if (wdkLoadError) throw wdkLoadError;
|
|
861
|
+
try {
|
|
862
|
+
const [wdk, btc, evm, solana, ton, tron, spark] = await Promise.all([
|
|
863
|
+
dynamicImport("@tetherto/wdk"),
|
|
864
|
+
dynamicImport("@tetherto/wdk-wallet-btc"),
|
|
865
|
+
dynamicImport("@tetherto/wdk-wallet-evm"),
|
|
866
|
+
dynamicImport("@tetherto/wdk-wallet-solana"),
|
|
867
|
+
dynamicImport("@tetherto/wdk-wallet-ton"),
|
|
868
|
+
dynamicImport("@tetherto/wdk-wallet-tron"),
|
|
869
|
+
dynamicImport("@tetherto/wdk-wallet-spark")
|
|
870
|
+
]);
|
|
871
|
+
WdkManager = wdk.default;
|
|
872
|
+
WalletManagerBtc = btc.default;
|
|
873
|
+
WalletManagerEvm = evm.default;
|
|
874
|
+
WalletManagerSolana = solana.default;
|
|
875
|
+
WalletManagerTon = ton.default;
|
|
876
|
+
WalletManagerTron = tron.default;
|
|
877
|
+
WalletManagerSpark = spark.default;
|
|
878
|
+
wdkLoaded = true;
|
|
879
|
+
} catch (error) {
|
|
880
|
+
wdkLoadError = error instanceof Error ? error : new Error("Failed to load WDK modules");
|
|
881
|
+
console.error("Failed to load WDK modules:", error);
|
|
882
|
+
throw wdkLoadError;
|
|
883
|
+
}
|
|
884
|
+
}
|
|
885
|
+
var DERIVATION_PATHS2 = {
|
|
886
|
+
bitcoin: "m/84'/0'/0'/0/0",
|
|
887
|
+
// BIP-84 for native SegWit
|
|
888
|
+
ethereum: "m/44'/60'/0'/0/0",
|
|
889
|
+
ton: "m/44'/607'/0'",
|
|
890
|
+
// Updated for v1.0.0-beta.6+
|
|
891
|
+
tron: "m/44'/195'/0'/0/0",
|
|
892
|
+
solana: "m/44'/501'/0'/0'",
|
|
893
|
+
// Updated for v1.0.0-beta.4+
|
|
894
|
+
spark: "m/44'/998'/0'/0/0"
|
|
895
|
+
};
|
|
896
|
+
var DEFAULT_RPC_URLS = {
|
|
897
|
+
mainnet: {
|
|
898
|
+
ethereum: "https://eth.llamarpc.com",
|
|
899
|
+
solana: "https://api.mainnet-beta.solana.com",
|
|
900
|
+
ton: "https://toncenter.com/api/v2/jsonRPC",
|
|
901
|
+
tron: "https://api.trongrid.io"
|
|
902
|
+
},
|
|
903
|
+
testnet: {
|
|
904
|
+
ethereum: "https://ethereum-sepolia-rpc.publicnode.com",
|
|
905
|
+
solana: "https://api.devnet.solana.com",
|
|
906
|
+
ton: "https://testnet.toncenter.com/api/v2/jsonRPC",
|
|
907
|
+
tron: "https://api.shasta.trongrid.io"
|
|
908
|
+
}
|
|
909
|
+
};
|
|
910
|
+
var WdkService = class {
|
|
911
|
+
seed = null;
|
|
912
|
+
config;
|
|
913
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
914
|
+
wallets = {};
|
|
915
|
+
constructor(config = {}) {
|
|
916
|
+
this.config = {
|
|
917
|
+
network: config.network || "testnet",
|
|
918
|
+
rpcUrls: config.rpcUrls
|
|
919
|
+
};
|
|
920
|
+
}
|
|
921
|
+
/**
|
|
922
|
+
* Check if WDK modules are loaded
|
|
923
|
+
*/
|
|
924
|
+
static isLoaded() {
|
|
925
|
+
return wdkLoaded;
|
|
926
|
+
}
|
|
927
|
+
/**
|
|
928
|
+
* Ensure WDK modules are loaded
|
|
929
|
+
*/
|
|
930
|
+
async ensureLoaded() {
|
|
931
|
+
await loadWdkModules();
|
|
932
|
+
}
|
|
933
|
+
/**
|
|
934
|
+
* Load WDK modules (call this before using sync methods)
|
|
935
|
+
*/
|
|
936
|
+
async loadModules() {
|
|
937
|
+
await loadWdkModules();
|
|
938
|
+
}
|
|
939
|
+
/**
|
|
940
|
+
* Generate a random BIP-39 seed phrase (12 words)
|
|
941
|
+
*/
|
|
942
|
+
async generateSeedPhrase() {
|
|
943
|
+
await this.ensureLoaded();
|
|
944
|
+
return WdkManager.getRandomSeedPhrase();
|
|
945
|
+
}
|
|
946
|
+
/**
|
|
947
|
+
* Validate a BIP-39 seed phrase
|
|
948
|
+
*/
|
|
949
|
+
async isValidSeed(seed) {
|
|
950
|
+
await this.ensureLoaded();
|
|
951
|
+
return WdkManager.isValidSeed(seed);
|
|
952
|
+
}
|
|
953
|
+
/**
|
|
954
|
+
* Validate seed phrase (sync version - basic check)
|
|
955
|
+
*/
|
|
956
|
+
isValidSeedSync(seed) {
|
|
957
|
+
const words = seed.trim().split(/\s+/);
|
|
958
|
+
return words.length === 12 || words.length === 24;
|
|
959
|
+
}
|
|
960
|
+
/**
|
|
961
|
+
* Initialize the service with a seed phrase
|
|
962
|
+
*/
|
|
963
|
+
async initialize(seed) {
|
|
964
|
+
await this.ensureLoaded();
|
|
965
|
+
if (!WdkManager.isValidSeed(seed)) {
|
|
966
|
+
throw new Error("Invalid seed phrase");
|
|
967
|
+
}
|
|
968
|
+
this.seed = seed;
|
|
969
|
+
this.wallets = {};
|
|
970
|
+
}
|
|
971
|
+
/**
|
|
972
|
+
* Get RPC URL for a chain
|
|
973
|
+
*/
|
|
974
|
+
getRpcUrl(chain) {
|
|
975
|
+
const networkUrls = DEFAULT_RPC_URLS[this.config.network];
|
|
976
|
+
if (this.config.rpcUrls?.[chain]) {
|
|
977
|
+
return this.config.rpcUrls[chain];
|
|
978
|
+
}
|
|
979
|
+
return networkUrls[chain] || "";
|
|
980
|
+
}
|
|
981
|
+
/**
|
|
982
|
+
* Get or create wallet instance for a specific chain
|
|
983
|
+
*/
|
|
984
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
985
|
+
async getWallet(chain) {
|
|
986
|
+
if (!this.seed) {
|
|
987
|
+
throw new Error("WDK service not initialized. Call initialize() first.");
|
|
988
|
+
}
|
|
989
|
+
if (this.wallets[chain]) {
|
|
990
|
+
return this.wallets[chain];
|
|
991
|
+
}
|
|
992
|
+
const isTestnet = this.config.network === "testnet";
|
|
993
|
+
try {
|
|
994
|
+
switch (chain) {
|
|
995
|
+
case "ethereum": {
|
|
996
|
+
const rpcUrl = this.getRpcUrl("ethereum");
|
|
997
|
+
const wallet = new WalletManagerEvm(this.seed, { provider: rpcUrl });
|
|
998
|
+
this.wallets[chain] = wallet;
|
|
999
|
+
return wallet;
|
|
1000
|
+
}
|
|
1001
|
+
case "bitcoin": {
|
|
1002
|
+
const wallet = new WalletManagerBtc(this.seed, {
|
|
1003
|
+
network: isTestnet ? "testnet" : "bitcoin"
|
|
1004
|
+
});
|
|
1005
|
+
this.wallets[chain] = wallet;
|
|
1006
|
+
return wallet;
|
|
1007
|
+
}
|
|
1008
|
+
case "solana": {
|
|
1009
|
+
const rpcUrl = this.getRpcUrl("solana");
|
|
1010
|
+
const wallet = new WalletManagerSolana(this.seed, {
|
|
1011
|
+
rpcUrl
|
|
1012
|
+
});
|
|
1013
|
+
this.wallets[chain] = wallet;
|
|
1014
|
+
return wallet;
|
|
1015
|
+
}
|
|
1016
|
+
case "ton": {
|
|
1017
|
+
const url = this.getRpcUrl("ton");
|
|
1018
|
+
const wallet = new WalletManagerTon(this.seed, {
|
|
1019
|
+
tonClient: { url }
|
|
1020
|
+
});
|
|
1021
|
+
this.wallets[chain] = wallet;
|
|
1022
|
+
return wallet;
|
|
1023
|
+
}
|
|
1024
|
+
case "tron": {
|
|
1025
|
+
const fullHost = this.getRpcUrl("tron");
|
|
1026
|
+
const wallet = new WalletManagerTron(this.seed, {
|
|
1027
|
+
provider: fullHost
|
|
1028
|
+
});
|
|
1029
|
+
this.wallets[chain] = wallet;
|
|
1030
|
+
return wallet;
|
|
1031
|
+
}
|
|
1032
|
+
case "spark": {
|
|
1033
|
+
const wallet = new WalletManagerSpark(this.seed, {
|
|
1034
|
+
network: isTestnet ? "TESTNET" : "MAINNET"
|
|
1035
|
+
});
|
|
1036
|
+
this.wallets[chain] = wallet;
|
|
1037
|
+
return wallet;
|
|
1038
|
+
}
|
|
1039
|
+
default:
|
|
1040
|
+
throw new Error(`Unsupported chain: ${chain}`);
|
|
1041
|
+
}
|
|
1042
|
+
} catch (error) {
|
|
1043
|
+
console.error(`Failed to initialize ${chain} wallet:`, error);
|
|
1044
|
+
throw error;
|
|
1045
|
+
}
|
|
1046
|
+
}
|
|
1047
|
+
/**
|
|
1048
|
+
* Derive address for a specific chain
|
|
1049
|
+
*/
|
|
1050
|
+
async deriveAddress(chain) {
|
|
1051
|
+
const path = DERIVATION_PATHS2[chain];
|
|
1052
|
+
try {
|
|
1053
|
+
const wallet = await this.getWallet(chain);
|
|
1054
|
+
const account = await wallet.getAccount(0);
|
|
1055
|
+
const address = await account.getAddress();
|
|
1056
|
+
return {
|
|
1057
|
+
chain,
|
|
1058
|
+
address,
|
|
1059
|
+
path
|
|
1060
|
+
};
|
|
1061
|
+
} catch (error) {
|
|
1062
|
+
console.error(`Error deriving ${chain} address:`, error);
|
|
1063
|
+
throw error;
|
|
1064
|
+
}
|
|
1065
|
+
}
|
|
1066
|
+
/**
|
|
1067
|
+
* Derive addresses for all supported chains
|
|
1068
|
+
*/
|
|
1069
|
+
async deriveAllAddresses() {
|
|
1070
|
+
const chains = ["ethereum", "bitcoin", "ton", "tron", "solana", "spark"];
|
|
1071
|
+
const addresses = {
|
|
1072
|
+
ethereum: null,
|
|
1073
|
+
bitcoin: null,
|
|
1074
|
+
ton: null,
|
|
1075
|
+
tron: null,
|
|
1076
|
+
solana: null,
|
|
1077
|
+
spark: null
|
|
1078
|
+
};
|
|
1079
|
+
const results = await Promise.allSettled(
|
|
1080
|
+
chains.map(async (chain) => {
|
|
1081
|
+
const result = await this.deriveAddress(chain);
|
|
1082
|
+
return { chain, address: result.address };
|
|
1083
|
+
})
|
|
1084
|
+
);
|
|
1085
|
+
for (const result of results) {
|
|
1086
|
+
if (result.status === "fulfilled") {
|
|
1087
|
+
addresses[result.value.chain] = result.value.address;
|
|
1088
|
+
} else {
|
|
1089
|
+
console.error("Failed to derive address:", result.reason);
|
|
1090
|
+
}
|
|
1091
|
+
}
|
|
1092
|
+
return addresses;
|
|
1093
|
+
}
|
|
1094
|
+
/**
|
|
1095
|
+
* Derive addresses for specific chains only
|
|
1096
|
+
*/
|
|
1097
|
+
async deriveAddressesForChains(chains) {
|
|
1098
|
+
const addresses = {};
|
|
1099
|
+
const results = await Promise.allSettled(
|
|
1100
|
+
chains.map(async (chain) => {
|
|
1101
|
+
const result = await this.deriveAddress(chain);
|
|
1102
|
+
return { chain, address: result.address };
|
|
1103
|
+
})
|
|
1104
|
+
);
|
|
1105
|
+
for (const result of results) {
|
|
1106
|
+
if (result.status === "fulfilled") {
|
|
1107
|
+
addresses[result.value.chain] = result.value.address;
|
|
1108
|
+
}
|
|
1109
|
+
}
|
|
1110
|
+
return addresses;
|
|
1111
|
+
}
|
|
1112
|
+
/**
|
|
1113
|
+
* Get fee rates for a specific chain
|
|
1114
|
+
*/
|
|
1115
|
+
async getFeeRates(chain) {
|
|
1116
|
+
if (!this.seed) {
|
|
1117
|
+
throw new Error("WDK service not initialized. Call initialize() first.");
|
|
1118
|
+
}
|
|
1119
|
+
try {
|
|
1120
|
+
const wallet = await this.getWallet(chain);
|
|
1121
|
+
const feeRates = await wallet.getFeeRates();
|
|
1122
|
+
return {
|
|
1123
|
+
slow: (feeRates.slow || feeRates.low || "0").toString(),
|
|
1124
|
+
medium: (feeRates.medium || feeRates.normal || feeRates.standard || "0").toString(),
|
|
1125
|
+
fast: (feeRates.fast || feeRates.high || "0").toString()
|
|
1126
|
+
};
|
|
1127
|
+
} catch (error) {
|
|
1128
|
+
console.error(`Error fetching fee rates for ${chain}:`, error);
|
|
1129
|
+
throw error;
|
|
1130
|
+
}
|
|
1131
|
+
}
|
|
1132
|
+
/**
|
|
1133
|
+
* Get the current network configuration
|
|
1134
|
+
*/
|
|
1135
|
+
getNetwork() {
|
|
1136
|
+
return this.config.network;
|
|
1137
|
+
}
|
|
1138
|
+
/**
|
|
1139
|
+
* Check if service is initialized
|
|
1140
|
+
*/
|
|
1141
|
+
isInitialized() {
|
|
1142
|
+
return this.seed !== null;
|
|
1143
|
+
}
|
|
1144
|
+
/**
|
|
1145
|
+
* Clean up and dispose of wallet instances
|
|
1146
|
+
*/
|
|
1147
|
+
dispose() {
|
|
1148
|
+
for (const wallet of Object.values(this.wallets)) {
|
|
1149
|
+
if (wallet && typeof wallet.dispose === "function") {
|
|
1150
|
+
try {
|
|
1151
|
+
wallet.dispose();
|
|
1152
|
+
} catch {
|
|
1153
|
+
}
|
|
1154
|
+
}
|
|
1155
|
+
}
|
|
1156
|
+
this.wallets = {};
|
|
1157
|
+
this.seed = null;
|
|
1158
|
+
}
|
|
1159
|
+
};
|
|
1160
|
+
|
|
1161
|
+
// src/wallet/WalletManager.ts
|
|
1162
|
+
var STORAGE_KEYS = {
|
|
1163
|
+
ENCRYPTED_SEED: "encrypted_seed",
|
|
1164
|
+
ACTIVE_WALLET: "active_wallet"
|
|
1165
|
+
};
|
|
1166
|
+
var SUPPORTED_CHAINS = ["ethereum", "bitcoin", "ton", "tron", "solana", "spark"];
|
|
1167
|
+
var WalletManager = class _WalletManager {
|
|
1168
|
+
config;
|
|
1169
|
+
storage;
|
|
1170
|
+
currentSeed = null;
|
|
1171
|
+
derivedAddress = null;
|
|
1172
|
+
derivedAddresses = {};
|
|
1173
|
+
selectedChain = "ethereum";
|
|
1174
|
+
wdkService;
|
|
1175
|
+
constructor(config = {}) {
|
|
1176
|
+
const isTestnet = config.network !== "mainnet";
|
|
1177
|
+
const ethereumConfig = getNetworkConfig("ethereum", isTestnet);
|
|
1178
|
+
this.config = {
|
|
1179
|
+
network: config.network || "testnet",
|
|
1180
|
+
rpcUrl: config.rpcUrl || ethereumConfig.rpcUrl,
|
|
1181
|
+
storage: config.storage || createSecureStorage(),
|
|
1182
|
+
enabledChains: config.enabledChains || SUPPORTED_CHAINS,
|
|
1183
|
+
apiUrl: config.apiUrl || process.env.NEXT_PUBLIC_API_URL || "http://localhost:3001"
|
|
1184
|
+
};
|
|
1185
|
+
this.storage = this.config.storage;
|
|
1186
|
+
this.wdkService = new WdkService({
|
|
1187
|
+
network: this.config.network
|
|
1188
|
+
});
|
|
1189
|
+
}
|
|
1190
|
+
/**
|
|
1191
|
+
* Generate a new BIP-39 seed phrase (12 words) using ethers.js
|
|
1192
|
+
* For native WDK generation, use generateSeedWithWdk() instead
|
|
1193
|
+
*/
|
|
1194
|
+
static generateSeed() {
|
|
1195
|
+
const wallet = ethers.Wallet.createRandom();
|
|
1196
|
+
const mnemonic = wallet.mnemonic;
|
|
1197
|
+
if (!mnemonic) {
|
|
1198
|
+
throw new Error("Failed to generate mnemonic");
|
|
1199
|
+
}
|
|
1200
|
+
return mnemonic.phrase;
|
|
1201
|
+
}
|
|
1202
|
+
/**
|
|
1203
|
+
* Generate a new BIP-39 seed phrase using native Tether WDK
|
|
1204
|
+
* This is the recommended method for generating seed phrases
|
|
1205
|
+
*/
|
|
1206
|
+
async generateSeedWithWdk() {
|
|
1207
|
+
return await this.wdkService.generateSeedPhrase();
|
|
1208
|
+
}
|
|
1209
|
+
/**
|
|
1210
|
+
* Validate seed phrase using native WDK (async, more accurate)
|
|
1211
|
+
*/
|
|
1212
|
+
async validateSeedWithWdk(seed) {
|
|
1213
|
+
return await this.wdkService.isValidSeed(seed);
|
|
1214
|
+
}
|
|
1215
|
+
/**
|
|
1216
|
+
* Validate a BIP-39 seed phrase
|
|
1217
|
+
*/
|
|
1218
|
+
static validateSeed(seed) {
|
|
1219
|
+
return KeyManager.validateSeedPhrase(seed);
|
|
1220
|
+
}
|
|
1221
|
+
/**
|
|
1222
|
+
* Derive Ethereum address from seed phrase using BIP-44 path
|
|
1223
|
+
* Path: m/44'/60'/0'/0/0
|
|
1224
|
+
*/
|
|
1225
|
+
static deriveAddress(seed) {
|
|
1226
|
+
const hdNode = ethers.HDNodeWallet.fromPhrase(seed, void 0, "m/44'/60'/0'/0/0");
|
|
1227
|
+
return hdNode.address;
|
|
1228
|
+
}
|
|
1229
|
+
/**
|
|
1230
|
+
* Get the HDNodeWallet for signing transactions
|
|
1231
|
+
*/
|
|
1232
|
+
static getWallet(seed) {
|
|
1233
|
+
return ethers.HDNodeWallet.fromPhrase(seed, void 0, "m/44'/60'/0'/0/0");
|
|
1234
|
+
}
|
|
1235
|
+
/**
|
|
1236
|
+
* Initialize storage with password (required for web platform)
|
|
1237
|
+
*/
|
|
1238
|
+
async initializeStorage(password) {
|
|
1239
|
+
if (this.storage instanceof WebEncryptedStorageAdapter) {
|
|
1240
|
+
await this.storage.initialize(password);
|
|
1241
|
+
}
|
|
1242
|
+
}
|
|
1243
|
+
/**
|
|
1244
|
+
* Create a new wallet with generated seed
|
|
1245
|
+
*/
|
|
1246
|
+
async createWallet(password) {
|
|
1247
|
+
const seed = _WalletManager.generateSeed();
|
|
1248
|
+
const address = _WalletManager.deriveAddress(seed);
|
|
1249
|
+
const encrypted = await KeyManager.encryptSeed(seed, password);
|
|
1250
|
+
await this.storage.setItem(STORAGE_KEYS.ENCRYPTED_SEED, encrypted);
|
|
1251
|
+
this.currentSeed = seed;
|
|
1252
|
+
this.derivedAddress = address;
|
|
1253
|
+
return { seed, address };
|
|
1254
|
+
}
|
|
1255
|
+
/**
|
|
1256
|
+
* Import an existing wallet from seed phrase
|
|
1257
|
+
*/
|
|
1258
|
+
async importWallet(seed, password) {
|
|
1259
|
+
if (!_WalletManager.validateSeed(seed)) {
|
|
1260
|
+
throw new Error("Invalid seed phrase");
|
|
1261
|
+
}
|
|
1262
|
+
const address = _WalletManager.deriveAddress(seed);
|
|
1263
|
+
const encrypted = await KeyManager.encryptSeed(seed, password);
|
|
1264
|
+
await this.storage.setItem(STORAGE_KEYS.ENCRYPTED_SEED, encrypted);
|
|
1265
|
+
this.currentSeed = seed;
|
|
1266
|
+
this.derivedAddress = address;
|
|
1267
|
+
return { address };
|
|
1268
|
+
}
|
|
1269
|
+
/**
|
|
1270
|
+
* Unlock wallet with password
|
|
1271
|
+
*/
|
|
1272
|
+
async unlock(password) {
|
|
1273
|
+
const encrypted = await this.storage.getItem(STORAGE_KEYS.ENCRYPTED_SEED);
|
|
1274
|
+
if (!encrypted) {
|
|
1275
|
+
throw new Error("No wallet found");
|
|
1276
|
+
}
|
|
1277
|
+
try {
|
|
1278
|
+
const seed = await KeyManager.decryptSeed(encrypted, password);
|
|
1279
|
+
const address = _WalletManager.deriveAddress(seed);
|
|
1280
|
+
this.currentSeed = seed;
|
|
1281
|
+
this.derivedAddress = address;
|
|
1282
|
+
return { address };
|
|
1283
|
+
} catch {
|
|
1284
|
+
throw new Error("Invalid password");
|
|
1285
|
+
}
|
|
1286
|
+
}
|
|
1287
|
+
/**
|
|
1288
|
+
* Lock wallet (clear seed from memory)
|
|
1289
|
+
*/
|
|
1290
|
+
lock() {
|
|
1291
|
+
this.currentSeed = null;
|
|
1292
|
+
this.wdkService.dispose();
|
|
1293
|
+
}
|
|
1294
|
+
/**
|
|
1295
|
+
* Check if wallet exists in storage
|
|
1296
|
+
*/
|
|
1297
|
+
async hasWallet() {
|
|
1298
|
+
return this.storage.hasItem(STORAGE_KEYS.ENCRYPTED_SEED);
|
|
1299
|
+
}
|
|
1300
|
+
/**
|
|
1301
|
+
* Delete wallet from storage
|
|
1302
|
+
*/
|
|
1303
|
+
async deleteWallet() {
|
|
1304
|
+
await this.storage.removeItem(STORAGE_KEYS.ENCRYPTED_SEED);
|
|
1305
|
+
this.currentSeed = null;
|
|
1306
|
+
this.derivedAddress = null;
|
|
1307
|
+
this.derivedAddresses = {};
|
|
1308
|
+
this.wdkService.dispose();
|
|
1309
|
+
}
|
|
1310
|
+
/**
|
|
1311
|
+
* Get current wallet state
|
|
1312
|
+
*/
|
|
1313
|
+
getState() {
|
|
1314
|
+
return {
|
|
1315
|
+
isInitialized: this.derivedAddress !== null,
|
|
1316
|
+
isLocked: this.currentSeed === null && this.derivedAddress !== null,
|
|
1317
|
+
address: this.derivedAddress,
|
|
1318
|
+
balance: null
|
|
1319
|
+
// Use fetchBalance for current balance
|
|
1320
|
+
};
|
|
1321
|
+
}
|
|
1322
|
+
/**
|
|
1323
|
+
* Get current address (if unlocked)
|
|
1324
|
+
*/
|
|
1325
|
+
getAddress() {
|
|
1326
|
+
return this.derivedAddress;
|
|
1327
|
+
}
|
|
1328
|
+
/**
|
|
1329
|
+
* Check if wallet is unlocked
|
|
1330
|
+
*/
|
|
1331
|
+
isUnlocked() {
|
|
1332
|
+
return this.currentSeed !== null;
|
|
1333
|
+
}
|
|
1334
|
+
/**
|
|
1335
|
+
* Get the seed phrase (only if unlocked)
|
|
1336
|
+
*/
|
|
1337
|
+
getSeed() {
|
|
1338
|
+
return this.currentSeed;
|
|
1339
|
+
}
|
|
1340
|
+
/**
|
|
1341
|
+
* Fetch balance for current address with timeout handling
|
|
1342
|
+
*/
|
|
1343
|
+
async fetchBalance() {
|
|
1344
|
+
if (!this.derivedAddress) {
|
|
1345
|
+
throw new Error("Wallet not initialized");
|
|
1346
|
+
}
|
|
1347
|
+
const chain = this.config.network === "mainnet" ? chains.mainnet : chains.sepolia;
|
|
1348
|
+
const client = viem.createPublicClient({
|
|
1349
|
+
chain,
|
|
1350
|
+
transport: viem.http(this.config.rpcUrl, {
|
|
1351
|
+
timeout: 15e3,
|
|
1352
|
+
// 15 second timeout
|
|
1353
|
+
retryCount: 2,
|
|
1354
|
+
retryDelay: 1e3
|
|
1355
|
+
})
|
|
1356
|
+
});
|
|
1357
|
+
try {
|
|
1358
|
+
const balance = await client.getBalance({
|
|
1359
|
+
address: this.derivedAddress
|
|
1360
|
+
});
|
|
1361
|
+
return viem.formatEther(balance);
|
|
1362
|
+
} catch (error) {
|
|
1363
|
+
console.warn("Failed to fetch balance:", error);
|
|
1364
|
+
return "0";
|
|
1365
|
+
}
|
|
1366
|
+
}
|
|
1367
|
+
/**
|
|
1368
|
+
* Create viem public client for the current network
|
|
1369
|
+
*/
|
|
1370
|
+
getPublicClient() {
|
|
1371
|
+
const chain = this.config.network === "mainnet" ? chains.mainnet : chains.sepolia;
|
|
1372
|
+
return viem.createPublicClient({
|
|
1373
|
+
chain,
|
|
1374
|
+
transport: viem.http(this.config.rpcUrl, {
|
|
1375
|
+
timeout: 15e3,
|
|
1376
|
+
// 15 second timeout
|
|
1377
|
+
retryCount: 2,
|
|
1378
|
+
retryDelay: 1e3
|
|
1379
|
+
})
|
|
1380
|
+
});
|
|
1381
|
+
}
|
|
1382
|
+
/**
|
|
1383
|
+
* Get ethers wallet for signing (only if unlocked)
|
|
1384
|
+
*/
|
|
1385
|
+
getEthersWallet() {
|
|
1386
|
+
if (!this.currentSeed) return null;
|
|
1387
|
+
return _WalletManager.getWallet(this.currentSeed);
|
|
1388
|
+
}
|
|
1389
|
+
/**
|
|
1390
|
+
* Set active wallet preference
|
|
1391
|
+
*/
|
|
1392
|
+
async setActiveWalletPreference(wallet) {
|
|
1393
|
+
await this.storage.setItem(STORAGE_KEYS.ACTIVE_WALLET, wallet);
|
|
1394
|
+
}
|
|
1395
|
+
/**
|
|
1396
|
+
* Get active wallet preference
|
|
1397
|
+
*/
|
|
1398
|
+
async getActiveWalletPreference() {
|
|
1399
|
+
const stored = await this.storage.getItem(STORAGE_KEYS.ACTIVE_WALLET);
|
|
1400
|
+
return stored === "metamask" || stored === "wdk" ? stored : "metamask";
|
|
1401
|
+
}
|
|
1402
|
+
// ==========================================
|
|
1403
|
+
// Multi-Chain Support Methods (Powered by Tether WDK)
|
|
1404
|
+
// ==========================================
|
|
1405
|
+
/**
|
|
1406
|
+
* Derive address for a specific chain (async version)
|
|
1407
|
+
*
|
|
1408
|
+
* Calls the backend WDK API for proper derivation when available.
|
|
1409
|
+
* Falls back to local ethers-based derivation if API is unavailable.
|
|
1410
|
+
*/
|
|
1411
|
+
static async deriveAddressForChainAsync(seed, chain, network = "testnet", apiUrl) {
|
|
1412
|
+
try {
|
|
1413
|
+
const wdkApi = getWdkApiClient(apiUrl);
|
|
1414
|
+
const response = await wdkApi.deriveAddress(seed, chain, network);
|
|
1415
|
+
if (response.success && response.address) {
|
|
1416
|
+
return response.address;
|
|
1417
|
+
}
|
|
1418
|
+
console.warn(`WDK API failed for ${chain}, using local derivation:`, response.error);
|
|
1419
|
+
} catch (error) {
|
|
1420
|
+
console.warn(`WDK API unavailable for ${chain}, using local derivation:`, error);
|
|
1421
|
+
}
|
|
1422
|
+
return _WalletManager.deriveAddressForChain(seed, chain);
|
|
1423
|
+
}
|
|
1424
|
+
/**
|
|
1425
|
+
* Format address for non-WDK chains (fallback)
|
|
1426
|
+
*
|
|
1427
|
+
* Note: This fallback produces PLACEHOLDER addresses derived from the seed.
|
|
1428
|
+
* For real blockchain interaction, use the WDK API via deriveAddressForChainAsync().
|
|
1429
|
+
* These addresses should NOT be used for receiving funds without verification.
|
|
1430
|
+
*/
|
|
1431
|
+
static formatAddressForChain(address, chain) {
|
|
1432
|
+
if (chain === "ethereum") {
|
|
1433
|
+
return address;
|
|
1434
|
+
}
|
|
1435
|
+
const addressBytes = address.toLowerCase().replace("0x", "");
|
|
1436
|
+
switch (chain) {
|
|
1437
|
+
case "bitcoin": {
|
|
1438
|
+
const btcChars = "qpzry9x8gf2tvdw0s3jn54khce6mua7l";
|
|
1439
|
+
let btcAddr = "tb1q";
|
|
1440
|
+
for (let i = 0; i < 38; i++) {
|
|
1441
|
+
const idx = parseInt(addressBytes.charAt(i % 40) || "0", 16) % btcChars.length;
|
|
1442
|
+
btcAddr += btcChars[idx];
|
|
1443
|
+
}
|
|
1444
|
+
return btcAddr;
|
|
1445
|
+
}
|
|
1446
|
+
case "ton": {
|
|
1447
|
+
const base64Chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
|
|
1448
|
+
let tonAddr = "EQ";
|
|
1449
|
+
for (let i = 0; i < 46; i++) {
|
|
1450
|
+
const idx = parseInt(addressBytes.charAt(i % 40) || "0", 16) * 4 % base64Chars.length;
|
|
1451
|
+
tonAddr += base64Chars[idx];
|
|
1452
|
+
}
|
|
1453
|
+
return tonAddr;
|
|
1454
|
+
}
|
|
1455
|
+
case "tron": {
|
|
1456
|
+
const base58Chars = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";
|
|
1457
|
+
let tronAddr = "T";
|
|
1458
|
+
for (let i = 0; i < 33; i++) {
|
|
1459
|
+
const idx = parseInt(addressBytes.charAt(i % 40) || "0", 16) * 3 % base58Chars.length;
|
|
1460
|
+
tronAddr += base58Chars[idx];
|
|
1461
|
+
}
|
|
1462
|
+
return tronAddr;
|
|
1463
|
+
}
|
|
1464
|
+
case "solana": {
|
|
1465
|
+
const base58Chars = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";
|
|
1466
|
+
let solAddr = "";
|
|
1467
|
+
for (let i = 0; i < 44; i++) {
|
|
1468
|
+
const idx = parseInt(addressBytes.charAt(i % 40) || "0", 16) * 3 % base58Chars.length;
|
|
1469
|
+
solAddr += base58Chars[idx];
|
|
1470
|
+
}
|
|
1471
|
+
return solAddr;
|
|
1472
|
+
}
|
|
1473
|
+
case "spark": {
|
|
1474
|
+
const bech32Chars = "qpzry9x8gf2tvdw0s3jn54khce6mua7l";
|
|
1475
|
+
let sparkAddr = "sp1q";
|
|
1476
|
+
for (let i = 0; i < 58; i++) {
|
|
1477
|
+
const idx = parseInt(addressBytes.charAt(i % 40) || "0", 16) % bech32Chars.length;
|
|
1478
|
+
sparkAddr += bech32Chars[idx];
|
|
1479
|
+
}
|
|
1480
|
+
return sparkAddr;
|
|
1481
|
+
}
|
|
1482
|
+
default:
|
|
1483
|
+
return address;
|
|
1484
|
+
}
|
|
1485
|
+
}
|
|
1486
|
+
/**
|
|
1487
|
+
* Derive address for a specific chain (sync version for backwards compatibility)
|
|
1488
|
+
* Uses ethers for basic derivation, use deriveAddressForChainAsync for WDK
|
|
1489
|
+
*
|
|
1490
|
+
* Note: For non-Ethereum chains, this produces placeholder addresses that are
|
|
1491
|
+
* deterministic but not cryptographically valid. Use WDK API for real addresses.
|
|
1492
|
+
*/
|
|
1493
|
+
static deriveAddressForChain(seed, chain) {
|
|
1494
|
+
const ethPath = DERIVATION_PATHS["ethereum"];
|
|
1495
|
+
const ethNode = ethers.HDNodeWallet.fromPhrase(seed, void 0, `${ethPath}/0`);
|
|
1496
|
+
if (chain === "ethereum") {
|
|
1497
|
+
return ethNode.address;
|
|
1498
|
+
}
|
|
1499
|
+
return _WalletManager.formatAddressForChain(ethNode.address, chain);
|
|
1500
|
+
}
|
|
1501
|
+
/**
|
|
1502
|
+
* Derive addresses for all enabled chains (sync version)
|
|
1503
|
+
*/
|
|
1504
|
+
deriveAllAddresses() {
|
|
1505
|
+
if (!this.currentSeed) {
|
|
1506
|
+
throw new Error("Wallet is locked");
|
|
1507
|
+
}
|
|
1508
|
+
const addresses = {};
|
|
1509
|
+
for (const chain of this.config.enabledChains) {
|
|
1510
|
+
addresses[chain] = _WalletManager.deriveAddressForChain(this.currentSeed, chain);
|
|
1511
|
+
}
|
|
1512
|
+
this.derivedAddresses = addresses;
|
|
1513
|
+
return addresses;
|
|
1514
|
+
}
|
|
1515
|
+
/**
|
|
1516
|
+
* Derive addresses for all enabled chains using native Tether WDK (recommended)
|
|
1517
|
+
* This uses the WDK directly without needing a backend API.
|
|
1518
|
+
* Returns REAL cryptographically valid addresses for all chains.
|
|
1519
|
+
*/
|
|
1520
|
+
async deriveAllAddressesWithWdk() {
|
|
1521
|
+
if (!this.currentSeed) {
|
|
1522
|
+
throw new Error("Wallet is locked");
|
|
1523
|
+
}
|
|
1524
|
+
try {
|
|
1525
|
+
await this.wdkService.initialize(this.currentSeed);
|
|
1526
|
+
const enabledChainsSet = new Set(this.config.enabledChains);
|
|
1527
|
+
const wdkAddresses = await this.wdkService.deriveAllAddresses();
|
|
1528
|
+
const addresses = {};
|
|
1529
|
+
for (const [chain, address] of Object.entries(wdkAddresses)) {
|
|
1530
|
+
if (enabledChainsSet.has(chain) && address) {
|
|
1531
|
+
addresses[chain] = address;
|
|
1532
|
+
}
|
|
1533
|
+
}
|
|
1534
|
+
this.derivedAddresses = addresses;
|
|
1535
|
+
return addresses;
|
|
1536
|
+
} catch (error) {
|
|
1537
|
+
console.error("Native WDK derivation failed:", error);
|
|
1538
|
+
throw error;
|
|
1539
|
+
}
|
|
1540
|
+
}
|
|
1541
|
+
/**
|
|
1542
|
+
* Derive addresses for all enabled chains using Tether WDK
|
|
1543
|
+
* Tries native WDK first, then falls back to API, then to placeholders
|
|
1544
|
+
*/
|
|
1545
|
+
async deriveAllAddressesAsync() {
|
|
1546
|
+
if (!this.currentSeed) {
|
|
1547
|
+
throw new Error("Wallet is locked");
|
|
1548
|
+
}
|
|
1549
|
+
try {
|
|
1550
|
+
return await this.deriveAllAddressesWithWdk();
|
|
1551
|
+
} catch (wdkError) {
|
|
1552
|
+
console.warn("Native WDK failed, trying API:", wdkError);
|
|
1553
|
+
}
|
|
1554
|
+
try {
|
|
1555
|
+
const wdkApi = getWdkApiClient(this.config.apiUrl);
|
|
1556
|
+
const response = await wdkApi.deriveAllAddresses(this.currentSeed, this.config.network);
|
|
1557
|
+
if (response.success && response.addresses) {
|
|
1558
|
+
const addresses = {};
|
|
1559
|
+
for (const chain of this.config.enabledChains) {
|
|
1560
|
+
const address = response.addresses[chain];
|
|
1561
|
+
if (address) {
|
|
1562
|
+
addresses[chain] = address;
|
|
1563
|
+
}
|
|
1564
|
+
}
|
|
1565
|
+
this.derivedAddresses = addresses;
|
|
1566
|
+
return addresses;
|
|
1567
|
+
}
|
|
1568
|
+
console.warn("WDK API call failed:", response.error);
|
|
1569
|
+
} catch (apiError) {
|
|
1570
|
+
console.warn("WDK API unavailable:", apiError);
|
|
1571
|
+
}
|
|
1572
|
+
console.warn("WARNING: Using placeholder addresses. These are NOT valid for receiving funds!");
|
|
1573
|
+
return this.deriveAllAddresses();
|
|
1574
|
+
}
|
|
1575
|
+
/**
|
|
1576
|
+
* Get address for a specific chain
|
|
1577
|
+
*/
|
|
1578
|
+
getAddressForChain(chain) {
|
|
1579
|
+
if (!this.currentSeed) {
|
|
1580
|
+
return this.derivedAddresses[chain] || null;
|
|
1581
|
+
}
|
|
1582
|
+
if (!this.derivedAddresses[chain]) {
|
|
1583
|
+
this.derivedAddresses[chain] = _WalletManager.deriveAddressForChain(this.currentSeed, chain);
|
|
1584
|
+
}
|
|
1585
|
+
return this.derivedAddresses[chain] || null;
|
|
1586
|
+
}
|
|
1587
|
+
/**
|
|
1588
|
+
* Get all derived addresses
|
|
1589
|
+
*/
|
|
1590
|
+
getAllAddresses() {
|
|
1591
|
+
return { ...this.derivedAddresses };
|
|
1592
|
+
}
|
|
1593
|
+
/**
|
|
1594
|
+
* Set the selected chain
|
|
1595
|
+
*/
|
|
1596
|
+
setSelectedChain(chain) {
|
|
1597
|
+
if (!this.config.enabledChains.includes(chain)) {
|
|
1598
|
+
throw new Error(`Chain ${chain} is not enabled`);
|
|
1599
|
+
}
|
|
1600
|
+
this.selectedChain = chain;
|
|
1601
|
+
}
|
|
1602
|
+
/**
|
|
1603
|
+
* Get the currently selected chain
|
|
1604
|
+
*/
|
|
1605
|
+
getSelectedChain() {
|
|
1606
|
+
return this.selectedChain;
|
|
1607
|
+
}
|
|
1608
|
+
/**
|
|
1609
|
+
* Get enabled chains
|
|
1610
|
+
*/
|
|
1611
|
+
getEnabledChains() {
|
|
1612
|
+
return [...this.config.enabledChains];
|
|
1613
|
+
}
|
|
1614
|
+
/**
|
|
1615
|
+
* Get chain configuration
|
|
1616
|
+
*/
|
|
1617
|
+
getChainConfig(chain) {
|
|
1618
|
+
return getNetworkConfig(chain, this.config.network === "testnet");
|
|
1619
|
+
}
|
|
1620
|
+
/**
|
|
1621
|
+
* Fetch balance for a specific chain
|
|
1622
|
+
* Note: Currently only Ethereum is implemented
|
|
1623
|
+
*/
|
|
1624
|
+
async fetchBalanceForChain(chain) {
|
|
1625
|
+
const address = this.getAddressForChain(chain);
|
|
1626
|
+
if (!address) {
|
|
1627
|
+
throw new Error(`No address for chain ${chain}`);
|
|
1628
|
+
}
|
|
1629
|
+
const networkConfig = this.getChainConfig(chain);
|
|
1630
|
+
let balance = "0";
|
|
1631
|
+
if (chain === "ethereum") {
|
|
1632
|
+
const viemChain = this.config.network === "mainnet" ? chains.mainnet : chains.sepolia;
|
|
1633
|
+
const client = viem.createPublicClient({
|
|
1634
|
+
chain: viemChain,
|
|
1635
|
+
transport: viem.http(this.config.rpcUrl, {
|
|
1636
|
+
timeout: 15e3,
|
|
1637
|
+
// 15 second timeout
|
|
1638
|
+
retryCount: 2,
|
|
1639
|
+
retryDelay: 1e3
|
|
1640
|
+
})
|
|
1641
|
+
});
|
|
1642
|
+
try {
|
|
1643
|
+
const rawBalance = await client.getBalance({
|
|
1644
|
+
address
|
|
1645
|
+
});
|
|
1646
|
+
balance = viem.formatEther(rawBalance);
|
|
1647
|
+
} catch (error) {
|
|
1648
|
+
console.warn(`Failed to fetch ${chain} balance:`, error);
|
|
1649
|
+
}
|
|
1650
|
+
}
|
|
1651
|
+
return {
|
|
1652
|
+
chain,
|
|
1653
|
+
symbol: networkConfig.nativeCurrency.symbol,
|
|
1654
|
+
balance,
|
|
1655
|
+
balanceUsd: 0,
|
|
1656
|
+
// TODO: Implement price fetching
|
|
1657
|
+
address,
|
|
1658
|
+
decimals: networkConfig.nativeCurrency.decimals
|
|
1659
|
+
};
|
|
1660
|
+
}
|
|
1661
|
+
/**
|
|
1662
|
+
* Fetch balances for all enabled chains
|
|
1663
|
+
*/
|
|
1664
|
+
async fetchAllBalances() {
|
|
1665
|
+
const balances = [];
|
|
1666
|
+
for (const chain of this.config.enabledChains) {
|
|
1667
|
+
try {
|
|
1668
|
+
const balance = await this.fetchBalanceForChain(chain);
|
|
1669
|
+
balances.push(balance);
|
|
1670
|
+
} catch (error) {
|
|
1671
|
+
console.error(`Failed to fetch balance for ${chain}:`, error);
|
|
1672
|
+
const networkConfig = this.getChainConfig(chain);
|
|
1673
|
+
balances.push({
|
|
1674
|
+
chain,
|
|
1675
|
+
symbol: networkConfig.nativeCurrency.symbol,
|
|
1676
|
+
balance: "0",
|
|
1677
|
+
balanceUsd: 0,
|
|
1678
|
+
address: this.getAddressForChain(chain) || "",
|
|
1679
|
+
decimals: networkConfig.nativeCurrency.decimals
|
|
1680
|
+
});
|
|
1681
|
+
}
|
|
1682
|
+
}
|
|
1683
|
+
return balances;
|
|
1684
|
+
}
|
|
1685
|
+
/**
|
|
1686
|
+
* Get extended wallet state with multi-chain info
|
|
1687
|
+
*/
|
|
1688
|
+
getExtendedState() {
|
|
1689
|
+
return {
|
|
1690
|
+
isInitialized: this.derivedAddress !== null,
|
|
1691
|
+
isLocked: this.currentSeed === null && this.derivedAddress !== null,
|
|
1692
|
+
address: this.derivedAddress,
|
|
1693
|
+
balance: null,
|
|
1694
|
+
addresses: this.derivedAddresses,
|
|
1695
|
+
selectedChain: this.selectedChain
|
|
1696
|
+
};
|
|
1697
|
+
}
|
|
1698
|
+
};
|
|
1699
|
+
|
|
1700
|
+
// src/protocols/NFTProtocol.ts
|
|
1701
|
+
var ZubariNFTProtocol = class {
|
|
1702
|
+
contractAddress;
|
|
1703
|
+
_marketplaceAddress;
|
|
1704
|
+
chainId;
|
|
1705
|
+
constructor(contractAddress, marketplaceAddress, chainId) {
|
|
1706
|
+
this.contractAddress = contractAddress;
|
|
1707
|
+
this._marketplaceAddress = marketplaceAddress;
|
|
1708
|
+
this.chainId = chainId;
|
|
1709
|
+
}
|
|
1710
|
+
/**
|
|
1711
|
+
* Create a lazy mint voucher for off-chain NFT creation
|
|
1712
|
+
* The voucher can be redeemed on-chain when purchased
|
|
1713
|
+
*/
|
|
1714
|
+
async createLazyMintVoucher(metadata, creatorAddress, signer) {
|
|
1715
|
+
if (metadata.royaltyBps > PLATFORM_CONFIG.maxRoyaltyBps) {
|
|
1716
|
+
throw new Error(`Royalty cannot exceed ${PLATFORM_CONFIG.maxRoyaltyBps / 100}%`);
|
|
1717
|
+
}
|
|
1718
|
+
const tokenId = this.generateTokenId();
|
|
1719
|
+
const deadline = Math.floor(Date.now() / 1e3) + PLATFORM_CONFIG.voucherValiditySecs;
|
|
1720
|
+
const voucherData = {
|
|
1721
|
+
tokenId,
|
|
1722
|
+
uri: metadata.image,
|
|
1723
|
+
// Will be IPFS URI
|
|
1724
|
+
creator: creatorAddress,
|
|
1725
|
+
royaltyBps: metadata.royaltyBps,
|
|
1726
|
+
deadline
|
|
1727
|
+
};
|
|
1728
|
+
const domain = {
|
|
1729
|
+
...NFT_VOUCHER_DOMAIN,
|
|
1730
|
+
chainId: this.chainId,
|
|
1731
|
+
verifyingContract: this.contractAddress
|
|
1732
|
+
};
|
|
1733
|
+
const signature = await signer.signTypedData(domain, NFT_VOUCHER_TYPES, voucherData);
|
|
1734
|
+
return {
|
|
1735
|
+
...voucherData,
|
|
1736
|
+
signature
|
|
1737
|
+
};
|
|
1738
|
+
}
|
|
1739
|
+
/**
|
|
1740
|
+
* Redeem a lazy mint voucher to mint the NFT on-chain
|
|
1741
|
+
*/
|
|
1742
|
+
async redeemVoucher(voucher, _buyerAddress) {
|
|
1743
|
+
if (voucher.deadline < Math.floor(Date.now() / 1e3)) {
|
|
1744
|
+
throw new Error("Voucher has expired");
|
|
1745
|
+
}
|
|
1746
|
+
return {
|
|
1747
|
+
hash: "",
|
|
1748
|
+
network: "ethereum",
|
|
1749
|
+
status: "pending"
|
|
1750
|
+
};
|
|
1751
|
+
}
|
|
1752
|
+
/**
|
|
1753
|
+
* List an NFT for sale on the marketplace
|
|
1754
|
+
*/
|
|
1755
|
+
async listForSale(_params) {
|
|
1756
|
+
return {
|
|
1757
|
+
hash: "",
|
|
1758
|
+
network: "ethereum",
|
|
1759
|
+
status: "pending"
|
|
1760
|
+
};
|
|
1761
|
+
}
|
|
1762
|
+
/**
|
|
1763
|
+
* Buy an NFT from the marketplace
|
|
1764
|
+
*/
|
|
1765
|
+
async buyNFT(_listingId, _price) {
|
|
1766
|
+
return {
|
|
1767
|
+
hash: "",
|
|
1768
|
+
network: "ethereum",
|
|
1769
|
+
status: "pending"
|
|
1770
|
+
};
|
|
1771
|
+
}
|
|
1772
|
+
/**
|
|
1773
|
+
* Transfer an NFT to another address
|
|
1774
|
+
*/
|
|
1775
|
+
async transfer(_tokenId, _to) {
|
|
1776
|
+
return {
|
|
1777
|
+
hash: "",
|
|
1778
|
+
network: "ethereum",
|
|
1779
|
+
status: "pending"
|
|
1780
|
+
};
|
|
1781
|
+
}
|
|
1782
|
+
/**
|
|
1783
|
+
* Get NFTs owned by an address
|
|
1784
|
+
*/
|
|
1785
|
+
async getOwnedNFTs(_address) {
|
|
1786
|
+
return [];
|
|
1787
|
+
}
|
|
1788
|
+
/**
|
|
1789
|
+
* Generate a random tokenId (32 bytes hex)
|
|
1790
|
+
*/
|
|
1791
|
+
generateTokenId() {
|
|
1792
|
+
const bytes = new Uint8Array(32);
|
|
1793
|
+
crypto.getRandomValues(bytes);
|
|
1794
|
+
return "0x" + Array.from(bytes).map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
1795
|
+
}
|
|
1796
|
+
};
|
|
1797
|
+
|
|
1798
|
+
// src/protocols/TipsProtocol.ts
|
|
1799
|
+
var ZubariTipsProtocol = class {
|
|
1800
|
+
contractAddress;
|
|
1801
|
+
chainId;
|
|
1802
|
+
gaslessEnabled;
|
|
1803
|
+
constructor(contractAddress, chainId, gaslessEnabled = false) {
|
|
1804
|
+
this.contractAddress = contractAddress;
|
|
1805
|
+
this.chainId = chainId;
|
|
1806
|
+
this.gaslessEnabled = gaslessEnabled;
|
|
1807
|
+
}
|
|
1808
|
+
/**
|
|
1809
|
+
* Send a tip to a creator
|
|
1810
|
+
*/
|
|
1811
|
+
async sendTip(tip) {
|
|
1812
|
+
const { recipient, amount, token, message } = tip;
|
|
1813
|
+
const platformFee = amount * BigInt(PLATFORM_CONFIG.tipFeeBps) / BigInt(1e4);
|
|
1814
|
+
const creatorAmount = amount - platformFee;
|
|
1815
|
+
if (amount <= 0n) {
|
|
1816
|
+
throw new Error("Tip amount must be greater than 0");
|
|
1817
|
+
}
|
|
1818
|
+
const txResult = {
|
|
1819
|
+
hash: ""};
|
|
1820
|
+
return {
|
|
1821
|
+
txHash: txResult.hash,
|
|
1822
|
+
tipId: "",
|
|
1823
|
+
// Will be returned from contract event
|
|
1824
|
+
recipient,
|
|
1825
|
+
amount: creatorAmount,
|
|
1826
|
+
platformFee,
|
|
1827
|
+
timestamp: Math.floor(Date.now() / 1e3)
|
|
1828
|
+
};
|
|
1829
|
+
}
|
|
1830
|
+
/**
|
|
1831
|
+
* Send tips to multiple creators in a single transaction
|
|
1832
|
+
*/
|
|
1833
|
+
async sendBatchTips(tips) {
|
|
1834
|
+
if (tips.length === 0) {
|
|
1835
|
+
throw new Error("At least one tip is required");
|
|
1836
|
+
}
|
|
1837
|
+
tips.reduce((sum, tip) => sum + tip.amount, BigInt(0));
|
|
1838
|
+
return tips.map((tip) => ({
|
|
1839
|
+
txHash: "",
|
|
1840
|
+
tipId: "",
|
|
1841
|
+
recipient: tip.recipient,
|
|
1842
|
+
amount: tip.amount - tip.amount * BigInt(PLATFORM_CONFIG.tipFeeBps) / BigInt(1e4),
|
|
1843
|
+
platformFee: tip.amount * BigInt(PLATFORM_CONFIG.tipFeeBps) / BigInt(1e4),
|
|
1844
|
+
timestamp: Math.floor(Date.now() / 1e3)
|
|
1845
|
+
}));
|
|
1846
|
+
}
|
|
1847
|
+
/**
|
|
1848
|
+
* Get tips received by an address
|
|
1849
|
+
*/
|
|
1850
|
+
async getTipsReceived(address) {
|
|
1851
|
+
return [];
|
|
1852
|
+
}
|
|
1853
|
+
/**
|
|
1854
|
+
* Get tips sent by an address
|
|
1855
|
+
*/
|
|
1856
|
+
async getTipsSent(address) {
|
|
1857
|
+
return [];
|
|
1858
|
+
}
|
|
1859
|
+
/**
|
|
1860
|
+
* Get tip statistics for a creator
|
|
1861
|
+
*/
|
|
1862
|
+
async getCreatorTipStats(creator) {
|
|
1863
|
+
return {
|
|
1864
|
+
totalReceived: BigInt(0),
|
|
1865
|
+
tipCount: 0,
|
|
1866
|
+
uniqueTippers: 0
|
|
1867
|
+
};
|
|
1868
|
+
}
|
|
1869
|
+
/**
|
|
1870
|
+
* Get platform fee in basis points
|
|
1871
|
+
*/
|
|
1872
|
+
getPlatformFeeBps() {
|
|
1873
|
+
return PLATFORM_CONFIG.tipFeeBps;
|
|
1874
|
+
}
|
|
1875
|
+
};
|
|
1876
|
+
|
|
1877
|
+
// src/protocols/SubscriptionProtocol.ts
|
|
1878
|
+
var ZubariSubscriptionProtocol = class {
|
|
1879
|
+
contractAddress;
|
|
1880
|
+
chainId;
|
|
1881
|
+
constructor(contractAddress, chainId) {
|
|
1882
|
+
this.contractAddress = contractAddress;
|
|
1883
|
+
this.chainId = chainId;
|
|
1884
|
+
}
|
|
1885
|
+
/**
|
|
1886
|
+
* Create a new subscription plan
|
|
1887
|
+
*/
|
|
1888
|
+
async createPlan(plan) {
|
|
1889
|
+
if (!plan.name || plan.name.length === 0) {
|
|
1890
|
+
throw new Error("Plan name is required");
|
|
1891
|
+
}
|
|
1892
|
+
if (plan.price <= 0n) {
|
|
1893
|
+
throw new Error("Plan price must be greater than 0");
|
|
1894
|
+
}
|
|
1895
|
+
if (plan.duration <= 0) {
|
|
1896
|
+
throw new Error("Plan duration must be greater than 0");
|
|
1897
|
+
}
|
|
1898
|
+
const planId = this.generatePlanId(plan.name);
|
|
1899
|
+
return planId;
|
|
1900
|
+
}
|
|
1901
|
+
/**
|
|
1902
|
+
* Update an existing subscription plan
|
|
1903
|
+
*/
|
|
1904
|
+
async updatePlan(planId, updates) {
|
|
1905
|
+
return {
|
|
1906
|
+
hash: "",
|
|
1907
|
+
network: "ethereum",
|
|
1908
|
+
status: "pending"
|
|
1909
|
+
};
|
|
1910
|
+
}
|
|
1911
|
+
/**
|
|
1912
|
+
* Subscribe to a creator's plan
|
|
1913
|
+
*/
|
|
1914
|
+
async subscribe(creator, planId, months = 1) {
|
|
1915
|
+
if (months <= 0) {
|
|
1916
|
+
throw new Error("Subscription duration must be at least 1 month");
|
|
1917
|
+
}
|
|
1918
|
+
const now = Math.floor(Date.now() / 1e3);
|
|
1919
|
+
const durationSeconds = months * 30 * 24 * 60 * 60;
|
|
1920
|
+
return {
|
|
1921
|
+
subscriptionId: "",
|
|
1922
|
+
planId,
|
|
1923
|
+
creator,
|
|
1924
|
+
subscriber: "",
|
|
1925
|
+
// Current user address
|
|
1926
|
+
startTime: now,
|
|
1927
|
+
endTime: now + durationSeconds,
|
|
1928
|
+
autoRenew: false,
|
|
1929
|
+
status: "active"
|
|
1930
|
+
};
|
|
1931
|
+
}
|
|
1932
|
+
/**
|
|
1933
|
+
* Cancel an active subscription
|
|
1934
|
+
*/
|
|
1935
|
+
async cancel(subscriptionId) {
|
|
1936
|
+
return {
|
|
1937
|
+
hash: "",
|
|
1938
|
+
network: "ethereum",
|
|
1939
|
+
status: "pending"
|
|
1940
|
+
};
|
|
1941
|
+
}
|
|
1942
|
+
/**
|
|
1943
|
+
* Check if an address is subscribed to a creator
|
|
1944
|
+
*/
|
|
1945
|
+
async isSubscribed(creator, subscriber) {
|
|
1946
|
+
return false;
|
|
1947
|
+
}
|
|
1948
|
+
/**
|
|
1949
|
+
* Get active subscriptions for a subscriber
|
|
1950
|
+
*/
|
|
1951
|
+
async getActiveSubscriptions(subscriber) {
|
|
1952
|
+
return [];
|
|
1953
|
+
}
|
|
1954
|
+
/**
|
|
1955
|
+
* Get all subscribers for a creator
|
|
1956
|
+
*/
|
|
1957
|
+
async getSubscribers(creator) {
|
|
1958
|
+
return [];
|
|
1959
|
+
}
|
|
1960
|
+
/**
|
|
1961
|
+
* Get a specific plan by ID
|
|
1962
|
+
*/
|
|
1963
|
+
async getPlan(planId) {
|
|
1964
|
+
return null;
|
|
1965
|
+
}
|
|
1966
|
+
/**
|
|
1967
|
+
* Get all plans for a creator
|
|
1968
|
+
*/
|
|
1969
|
+
async getCreatorPlans(creator) {
|
|
1970
|
+
return [];
|
|
1971
|
+
}
|
|
1972
|
+
/**
|
|
1973
|
+
* Generate a unique plan ID
|
|
1974
|
+
*/
|
|
1975
|
+
generatePlanId(name) {
|
|
1976
|
+
const bytes = new Uint8Array(16);
|
|
1977
|
+
crypto.getRandomValues(bytes);
|
|
1978
|
+
return "0x" + Array.from(bytes).map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
1979
|
+
}
|
|
1980
|
+
};
|
|
1981
|
+
|
|
1982
|
+
// src/protocols/PayoutsProtocol.ts
|
|
1983
|
+
var ZubariPayoutsProtocol = class {
|
|
1984
|
+
contractAddress;
|
|
1985
|
+
chainId;
|
|
1986
|
+
constructor(contractAddress, chainId) {
|
|
1987
|
+
this.contractAddress = contractAddress;
|
|
1988
|
+
this.chainId = chainId;
|
|
1989
|
+
}
|
|
1990
|
+
/**
|
|
1991
|
+
* Get pending earnings breakdown for the current user
|
|
1992
|
+
*/
|
|
1993
|
+
async getPendingEarnings() {
|
|
1994
|
+
return {
|
|
1995
|
+
tips: BigInt(0),
|
|
1996
|
+
subscriptions: BigInt(0),
|
|
1997
|
+
nftSales: BigInt(0),
|
|
1998
|
+
royalties: BigInt(0),
|
|
1999
|
+
total: BigInt(0)
|
|
2000
|
+
};
|
|
2001
|
+
}
|
|
2002
|
+
/**
|
|
2003
|
+
* Get historical earnings for a time period
|
|
2004
|
+
*/
|
|
2005
|
+
async getEarningsHistory(period = "all") {
|
|
2006
|
+
return [];
|
|
2007
|
+
}
|
|
2008
|
+
/**
|
|
2009
|
+
* Claim all pending earnings
|
|
2010
|
+
*/
|
|
2011
|
+
async claimEarnings() {
|
|
2012
|
+
return {
|
|
2013
|
+
hash: "",
|
|
2014
|
+
network: "ethereum",
|
|
2015
|
+
status: "pending"
|
|
2016
|
+
};
|
|
2017
|
+
}
|
|
2018
|
+
/**
|
|
2019
|
+
* Claim specific amount of earnings
|
|
2020
|
+
*/
|
|
2021
|
+
async claimPartialEarnings(amount) {
|
|
2022
|
+
if (amount <= 0n) {
|
|
2023
|
+
throw new Error("Amount must be greater than 0");
|
|
2024
|
+
}
|
|
2025
|
+
return {
|
|
2026
|
+
hash: "",
|
|
2027
|
+
network: "ethereum",
|
|
2028
|
+
status: "pending"
|
|
2029
|
+
};
|
|
2030
|
+
}
|
|
2031
|
+
/**
|
|
2032
|
+
* Setup revenue split with collaborators
|
|
2033
|
+
* Basis points must sum to 10000 (100%)
|
|
2034
|
+
*/
|
|
2035
|
+
async setupRevenueSplit(splits) {
|
|
2036
|
+
const totalBps = splits.reduce((sum, split) => sum + split.basisPoints, 0);
|
|
2037
|
+
if (totalBps !== 1e4) {
|
|
2038
|
+
throw new Error("Revenue splits must sum to 100% (10000 basis points)");
|
|
2039
|
+
}
|
|
2040
|
+
for (const split of splits) {
|
|
2041
|
+
if (split.basisPoints <= 0) {
|
|
2042
|
+
throw new Error("Each split must have positive basis points");
|
|
2043
|
+
}
|
|
2044
|
+
if (!split.recipient || split.recipient.length === 0) {
|
|
2045
|
+
throw new Error("Each split must have a valid recipient address");
|
|
2046
|
+
}
|
|
2047
|
+
}
|
|
2048
|
+
return {
|
|
2049
|
+
hash: "",
|
|
2050
|
+
network: "ethereum",
|
|
2051
|
+
status: "pending"
|
|
2052
|
+
};
|
|
2053
|
+
}
|
|
2054
|
+
/**
|
|
2055
|
+
* Get current revenue split configuration
|
|
2056
|
+
*/
|
|
2057
|
+
async getRevenueSplit() {
|
|
2058
|
+
return [];
|
|
2059
|
+
}
|
|
2060
|
+
/**
|
|
2061
|
+
* Remove revenue split (creator gets 100%)
|
|
2062
|
+
*/
|
|
2063
|
+
async removeRevenueSplit() {
|
|
2064
|
+
return {
|
|
2065
|
+
hash: "",
|
|
2066
|
+
network: "ethereum",
|
|
2067
|
+
status: "pending"
|
|
2068
|
+
};
|
|
2069
|
+
}
|
|
2070
|
+
/**
|
|
2071
|
+
* Convert earnings to stablecoin (USDT)
|
|
2072
|
+
*/
|
|
2073
|
+
async convertToStable(token, amount) {
|
|
2074
|
+
if (amount <= 0n) {
|
|
2075
|
+
throw new Error("Amount must be greater than 0");
|
|
2076
|
+
}
|
|
2077
|
+
return {
|
|
2078
|
+
hash: "",
|
|
2079
|
+
network: "ethereum",
|
|
2080
|
+
status: "pending"
|
|
2081
|
+
};
|
|
2082
|
+
}
|
|
2083
|
+
};
|
|
2084
|
+
|
|
2085
|
+
// src/services/SwapService.ts
|
|
2086
|
+
var SwapService = class {
|
|
2087
|
+
_chainId;
|
|
2088
|
+
isTestnet;
|
|
2089
|
+
constructor(chainId, isTestnet = false) {
|
|
2090
|
+
this._chainId = chainId;
|
|
2091
|
+
this.isTestnet = isTestnet;
|
|
2092
|
+
}
|
|
2093
|
+
/**
|
|
2094
|
+
* Get a swap quote
|
|
2095
|
+
*/
|
|
2096
|
+
async getQuote(tokenIn, tokenOut, amountIn) {
|
|
2097
|
+
if (amountIn <= 0n) {
|
|
2098
|
+
throw new Error("Amount must be greater than 0");
|
|
2099
|
+
}
|
|
2100
|
+
return {
|
|
2101
|
+
tokenIn,
|
|
2102
|
+
tokenOut,
|
|
2103
|
+
amountIn,
|
|
2104
|
+
amountOut: BigInt(0),
|
|
2105
|
+
priceImpact: 0,
|
|
2106
|
+
route: [tokenIn, tokenOut],
|
|
2107
|
+
estimatedGas: BigInt(0)
|
|
2108
|
+
};
|
|
2109
|
+
}
|
|
2110
|
+
/**
|
|
2111
|
+
* Execute a swap with slippage protection
|
|
2112
|
+
*/
|
|
2113
|
+
async executeSwap(quote, slippageToleranceBps = PLATFORM_CONFIG.defaultSlippageBps) {
|
|
2114
|
+
quote.amountOut - quote.amountOut * BigInt(slippageToleranceBps) / BigInt(1e4);
|
|
2115
|
+
Math.floor(Date.now() / 1e3) + PLATFORM_CONFIG.swapDeadlineSecs;
|
|
2116
|
+
if (quote.priceImpact > 5) {
|
|
2117
|
+
console.warn(`High price impact: ${quote.priceImpact}%`);
|
|
2118
|
+
}
|
|
2119
|
+
return {
|
|
2120
|
+
hash: "",
|
|
2121
|
+
network: "ethereum",
|
|
2122
|
+
status: "pending"
|
|
2123
|
+
};
|
|
2124
|
+
}
|
|
2125
|
+
/**
|
|
2126
|
+
* Convert earnings to USDT, keeping some ETH for gas
|
|
2127
|
+
*/
|
|
2128
|
+
async convertEarningsToStable(ethBalance, reserveForGas = BigInt("10000000000000000")) {
|
|
2129
|
+
const amountToSwap = ethBalance - reserveForGas;
|
|
2130
|
+
if (amountToSwap <= 0n) {
|
|
2131
|
+
throw new Error("Insufficient balance after gas reserve");
|
|
2132
|
+
}
|
|
2133
|
+
const contracts = getContractAddresses(this.isTestnet ? "testnet" : "mainnet");
|
|
2134
|
+
const quote = await this.getQuote(
|
|
2135
|
+
"0x0000000000000000000000000000000000000000",
|
|
2136
|
+
// ETH
|
|
2137
|
+
contracts.usdt,
|
|
2138
|
+
// USDT on Ethereum
|
|
2139
|
+
amountToSwap
|
|
2140
|
+
);
|
|
2141
|
+
return this.executeSwap(quote);
|
|
2142
|
+
}
|
|
2143
|
+
/**
|
|
2144
|
+
* Check if a swap route exists
|
|
2145
|
+
*/
|
|
2146
|
+
async hasRoute(tokenIn, tokenOut) {
|
|
2147
|
+
try {
|
|
2148
|
+
const quote = await this.getQuote(tokenIn, tokenOut, BigInt(1));
|
|
2149
|
+
return quote.amountOut > 0n;
|
|
2150
|
+
} catch {
|
|
2151
|
+
return false;
|
|
2152
|
+
}
|
|
2153
|
+
}
|
|
2154
|
+
/**
|
|
2155
|
+
* Get supported tokens for swapping
|
|
2156
|
+
*/
|
|
2157
|
+
async getSupportedTokens() {
|
|
2158
|
+
return [];
|
|
2159
|
+
}
|
|
2160
|
+
};
|
|
2161
|
+
|
|
2162
|
+
// src/types/index.ts
|
|
2163
|
+
var ZubariError = class extends Error {
|
|
2164
|
+
constructor(code, message, details) {
|
|
2165
|
+
super(message);
|
|
2166
|
+
this.code = code;
|
|
2167
|
+
this.details = details;
|
|
2168
|
+
this.name = "ZubariError";
|
|
2169
|
+
}
|
|
2170
|
+
};
|
|
2171
|
+
function useWalletManager(options = {}) {
|
|
2172
|
+
const { autoCheckWallet = true, ...config } = options;
|
|
2173
|
+
const manager = react.useMemo(() => new WalletManager(config), [
|
|
2174
|
+
config.network,
|
|
2175
|
+
config.rpcUrl
|
|
2176
|
+
]);
|
|
2177
|
+
const [state, setState] = react.useState({
|
|
2178
|
+
isInitialized: false,
|
|
2179
|
+
isLocked: true,
|
|
2180
|
+
address: null,
|
|
2181
|
+
balance: null
|
|
2182
|
+
});
|
|
2183
|
+
const [isLoading, setIsLoading] = react.useState(false);
|
|
2184
|
+
const [error, setError] = react.useState(null);
|
|
2185
|
+
const [selectedChain, setSelectedChainState] = react.useState("ethereum");
|
|
2186
|
+
const [chainBalances, setChainBalances] = react.useState([]);
|
|
2187
|
+
const updateState = react.useCallback(() => {
|
|
2188
|
+
setState(manager.getExtendedState());
|
|
2189
|
+
}, [manager]);
|
|
2190
|
+
react.useEffect(() => {
|
|
2191
|
+
if (autoCheckWallet) {
|
|
2192
|
+
manager.hasWallet().then((exists) => {
|
|
2193
|
+
if (exists) {
|
|
2194
|
+
setState((prev) => ({
|
|
2195
|
+
...prev,
|
|
2196
|
+
isInitialized: true,
|
|
2197
|
+
isLocked: true
|
|
2198
|
+
}));
|
|
2199
|
+
}
|
|
2200
|
+
});
|
|
2201
|
+
}
|
|
2202
|
+
}, [manager, autoCheckWallet]);
|
|
2203
|
+
const createWallet = react.useCallback(
|
|
2204
|
+
async (password) => {
|
|
2205
|
+
setIsLoading(true);
|
|
2206
|
+
setError(null);
|
|
2207
|
+
try {
|
|
2208
|
+
await manager.initializeStorage(password);
|
|
2209
|
+
const result = await manager.createWallet(password);
|
|
2210
|
+
try {
|
|
2211
|
+
await manager.deriveAllAddressesAsync();
|
|
2212
|
+
} catch {
|
|
2213
|
+
manager.deriveAllAddresses();
|
|
2214
|
+
}
|
|
2215
|
+
updateState();
|
|
2216
|
+
return result;
|
|
2217
|
+
} catch (err) {
|
|
2218
|
+
const message = err instanceof Error ? err.message : "Failed to create wallet";
|
|
2219
|
+
setError(message);
|
|
2220
|
+
throw err;
|
|
2221
|
+
} finally {
|
|
2222
|
+
setIsLoading(false);
|
|
2223
|
+
}
|
|
2224
|
+
},
|
|
2225
|
+
[manager, updateState]
|
|
2226
|
+
);
|
|
2227
|
+
const importWallet = react.useCallback(
|
|
2228
|
+
async (seed, password) => {
|
|
2229
|
+
setIsLoading(true);
|
|
2230
|
+
setError(null);
|
|
2231
|
+
try {
|
|
2232
|
+
await manager.initializeStorage(password);
|
|
2233
|
+
await manager.importWallet(seed, password);
|
|
2234
|
+
try {
|
|
2235
|
+
await manager.deriveAllAddressesAsync();
|
|
2236
|
+
} catch {
|
|
2237
|
+
manager.deriveAllAddresses();
|
|
2238
|
+
}
|
|
2239
|
+
updateState();
|
|
2240
|
+
} catch (err) {
|
|
2241
|
+
const message = err instanceof Error ? err.message : "Failed to import wallet";
|
|
2242
|
+
setError(message);
|
|
2243
|
+
throw err;
|
|
2244
|
+
} finally {
|
|
2245
|
+
setIsLoading(false);
|
|
2246
|
+
}
|
|
2247
|
+
},
|
|
2248
|
+
[manager, updateState]
|
|
2249
|
+
);
|
|
2250
|
+
const unlock = react.useCallback(
|
|
2251
|
+
async (password) => {
|
|
2252
|
+
setIsLoading(true);
|
|
2253
|
+
setError(null);
|
|
2254
|
+
try {
|
|
2255
|
+
await manager.initializeStorage(password);
|
|
2256
|
+
await manager.unlock(password);
|
|
2257
|
+
try {
|
|
2258
|
+
await manager.deriveAllAddressesAsync();
|
|
2259
|
+
} catch {
|
|
2260
|
+
manager.deriveAllAddresses();
|
|
2261
|
+
}
|
|
2262
|
+
updateState();
|
|
2263
|
+
} catch (err) {
|
|
2264
|
+
const message = err instanceof Error ? err.message : "Invalid password";
|
|
2265
|
+
setError(message);
|
|
2266
|
+
throw err;
|
|
2267
|
+
} finally {
|
|
2268
|
+
setIsLoading(false);
|
|
2269
|
+
}
|
|
2270
|
+
},
|
|
2271
|
+
[manager, updateState]
|
|
2272
|
+
);
|
|
2273
|
+
const lock = react.useCallback(() => {
|
|
2274
|
+
manager.lock();
|
|
2275
|
+
setChainBalances([]);
|
|
2276
|
+
updateState();
|
|
2277
|
+
}, [manager, updateState]);
|
|
2278
|
+
const deleteWallet = react.useCallback(async () => {
|
|
2279
|
+
setIsLoading(true);
|
|
2280
|
+
setError(null);
|
|
2281
|
+
try {
|
|
2282
|
+
await manager.deleteWallet();
|
|
2283
|
+
setChainBalances([]);
|
|
2284
|
+
setState({
|
|
2285
|
+
isInitialized: false,
|
|
2286
|
+
isLocked: true,
|
|
2287
|
+
address: null,
|
|
2288
|
+
balance: null
|
|
2289
|
+
});
|
|
2290
|
+
} catch (err) {
|
|
2291
|
+
const message = err instanceof Error ? err.message : "Failed to delete wallet";
|
|
2292
|
+
setError(message);
|
|
2293
|
+
throw err;
|
|
2294
|
+
} finally {
|
|
2295
|
+
setIsLoading(false);
|
|
2296
|
+
}
|
|
2297
|
+
}, [manager]);
|
|
2298
|
+
const fetchBalance = react.useCallback(async () => {
|
|
2299
|
+
setIsLoading(true);
|
|
2300
|
+
try {
|
|
2301
|
+
const balance = await manager.fetchBalance();
|
|
2302
|
+
setState((prev) => ({ ...prev, balance }));
|
|
2303
|
+
return balance;
|
|
2304
|
+
} catch (err) {
|
|
2305
|
+
console.warn("Failed to fetch balance:", err);
|
|
2306
|
+
setState((prev) => ({ ...prev, balance: "0" }));
|
|
2307
|
+
return "0";
|
|
2308
|
+
} finally {
|
|
2309
|
+
setIsLoading(false);
|
|
2310
|
+
}
|
|
2311
|
+
}, [manager]);
|
|
2312
|
+
const fetchAllBalances = react.useCallback(async () => {
|
|
2313
|
+
setIsLoading(true);
|
|
2314
|
+
try {
|
|
2315
|
+
const balances = await manager.fetchAllBalances();
|
|
2316
|
+
setChainBalances(balances);
|
|
2317
|
+
return balances;
|
|
2318
|
+
} catch (err) {
|
|
2319
|
+
console.warn("Failed to fetch all balances:", err);
|
|
2320
|
+
return [];
|
|
2321
|
+
} finally {
|
|
2322
|
+
setIsLoading(false);
|
|
2323
|
+
}
|
|
2324
|
+
}, [manager]);
|
|
2325
|
+
const setSelectedChain = react.useCallback((chain) => {
|
|
2326
|
+
manager.setSelectedChain(chain);
|
|
2327
|
+
setSelectedChainState(chain);
|
|
2328
|
+
}, [manager]);
|
|
2329
|
+
const getAddressForChain = react.useCallback(
|
|
2330
|
+
(chain) => manager.getAddressForChain(chain),
|
|
2331
|
+
[manager]
|
|
2332
|
+
);
|
|
2333
|
+
const getAllAddresses = react.useCallback(
|
|
2334
|
+
() => manager.getAllAddresses(),
|
|
2335
|
+
[manager]
|
|
2336
|
+
);
|
|
2337
|
+
const hasWallet = react.useCallback(() => manager.hasWallet(), [manager]);
|
|
2338
|
+
const getSeed = react.useCallback(() => manager.getSeed(), [manager]);
|
|
2339
|
+
return {
|
|
2340
|
+
state,
|
|
2341
|
+
isLoading,
|
|
2342
|
+
error,
|
|
2343
|
+
createWallet,
|
|
2344
|
+
importWallet,
|
|
2345
|
+
unlock,
|
|
2346
|
+
lock,
|
|
2347
|
+
deleteWallet,
|
|
2348
|
+
fetchBalance,
|
|
2349
|
+
// Multi-chain
|
|
2350
|
+
selectedChain,
|
|
2351
|
+
setSelectedChain,
|
|
2352
|
+
chainBalances,
|
|
2353
|
+
fetchAllBalances,
|
|
2354
|
+
getAddressForChain,
|
|
2355
|
+
getAllAddresses,
|
|
2356
|
+
supportedChains: SUPPORTED_CHAINS,
|
|
2357
|
+
// Utilities
|
|
2358
|
+
hasWallet,
|
|
2359
|
+
getSeed,
|
|
2360
|
+
manager
|
|
2361
|
+
};
|
|
2362
|
+
}
|
|
2363
|
+
|
|
2364
|
+
// src/utils/index.ts
|
|
2365
|
+
function formatAddress(address, chars = 4) {
|
|
2366
|
+
if (!address || address.length < 10) return address;
|
|
2367
|
+
return `${address.slice(0, chars + 2)}...${address.slice(-chars)}`;
|
|
2368
|
+
}
|
|
2369
|
+
function formatBalance(balance, decimals = 18, precision = 4) {
|
|
2370
|
+
const divisor = BigInt(10 ** decimals);
|
|
2371
|
+
const integerPart = balance / divisor;
|
|
2372
|
+
const fractionalPart = balance % divisor;
|
|
2373
|
+
const fractionalStr = fractionalPart.toString().padStart(decimals, "0").slice(0, precision);
|
|
2374
|
+
return `${integerPart}.${fractionalStr}`;
|
|
2375
|
+
}
|
|
2376
|
+
function isValidAddress(address) {
|
|
2377
|
+
return /^0x[a-fA-F0-9]{40}$/.test(address);
|
|
2378
|
+
}
|
|
2379
|
+
function normalizeAddress(address) {
|
|
2380
|
+
if (!isValidAddress(address)) {
|
|
2381
|
+
throw new Error("Invalid Ethereum address");
|
|
2382
|
+
}
|
|
2383
|
+
return address.toLowerCase();
|
|
2384
|
+
}
|
|
2385
|
+
|
|
2386
|
+
exports.DERIVATION_PATHS = DERIVATION_PATHS;
|
|
2387
|
+
exports.KeyManager = KeyManager;
|
|
2388
|
+
exports.MemoryStorageAdapter = MemoryStorageAdapter;
|
|
2389
|
+
exports.NETWORKS = NETWORKS;
|
|
2390
|
+
exports.PLATFORM_CONFIG = PLATFORM_CONFIG;
|
|
2391
|
+
exports.SwapService = SwapService;
|
|
2392
|
+
exports.TESTNET_NETWORKS = TESTNET_NETWORKS;
|
|
2393
|
+
exports.WalletManager = WalletManager;
|
|
2394
|
+
exports.WebEncryptedStorageAdapter = WebEncryptedStorageAdapter;
|
|
2395
|
+
exports.ZUBARI_CONTRACTS = ZUBARI_CONTRACTS;
|
|
2396
|
+
exports.ZubariError = ZubariError;
|
|
2397
|
+
exports.ZubariNFTProtocol = ZubariNFTProtocol;
|
|
2398
|
+
exports.ZubariPayoutsProtocol = ZubariPayoutsProtocol;
|
|
2399
|
+
exports.ZubariSubscriptionProtocol = ZubariSubscriptionProtocol;
|
|
2400
|
+
exports.ZubariTipsProtocol = ZubariTipsProtocol;
|
|
2401
|
+
exports.ZubariWallet = ZubariWallet;
|
|
2402
|
+
exports.createSecureStorage = createSecureStorage;
|
|
2403
|
+
exports.formatAddress = formatAddress;
|
|
2404
|
+
exports.formatBalance = formatBalance;
|
|
2405
|
+
exports.getContractAddresses = getContractAddresses;
|
|
2406
|
+
exports.getNetworkConfig = getNetworkConfig;
|
|
2407
|
+
exports.isValidAddress = isValidAddress;
|
|
2408
|
+
exports.normalizeAddress = normalizeAddress;
|
|
2409
|
+
exports.useWalletManager = useWalletManager;
|
|
2410
|
+
//# sourceMappingURL=index.js.map
|
|
2411
|
+
//# sourceMappingURL=index.js.map
|