@swapkit/toolboxes 1.0.0-beta.9 → 4.0.0-beta.35

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.
Files changed (130) hide show
  1. package/dist/{chunk-fjfxga2v.js → chunk-5yxc1e69.js} +1 -1
  2. package/dist/{chunk-fjfxga2v.js.map → chunk-5yxc1e69.js.map} +1 -1
  3. package/dist/chunk-6f98phv2.js +4 -0
  4. package/dist/{chunk-0h4xdrwz.js.map → chunk-6f98phv2.js.map} +2 -2
  5. package/dist/{chunk-0f0249b1.js → chunk-9bqegm61.js} +1 -1
  6. package/dist/{chunk-p1kdg37m.js → chunk-s47y8512.js} +2 -2
  7. package/dist/{chunk-p1kdg37m.js.map → chunk-s47y8512.js.map} +1 -1
  8. package/dist/chunk-vtd17cje.js +3 -0
  9. package/dist/chunk-vtd17cje.js.map +10 -0
  10. package/dist/chunk-zcdeg6h9.js +4 -0
  11. package/dist/chunk-zcdeg6h9.js.map +10 -0
  12. package/dist/src/cosmos/index.cjs +3 -0
  13. package/dist/src/cosmos/index.cjs.map +16 -0
  14. package/dist/src/cosmos/index.js +3 -0
  15. package/dist/src/cosmos/index.js.map +16 -0
  16. package/dist/src/evm/index.cjs +3 -0
  17. package/dist/src/evm/index.cjs.map +18 -0
  18. package/dist/src/evm/index.js +3 -0
  19. package/dist/src/evm/index.js.map +18 -0
  20. package/dist/src/index.cjs +3 -0
  21. package/dist/src/index.cjs.map +10 -0
  22. package/dist/src/index.js +3 -0
  23. package/dist/src/index.js.map +10 -0
  24. package/dist/src/near/index.cjs +3 -0
  25. package/dist/src/near/index.cjs.map +13 -0
  26. package/dist/src/near/index.js +3 -0
  27. package/dist/src/near/index.js.map +13 -0
  28. package/dist/{radix → src/radix}/index.cjs +2 -2
  29. package/dist/src/radix/index.cjs.map +10 -0
  30. package/dist/src/radix/index.js +3 -0
  31. package/dist/src/radix/index.js.map +10 -0
  32. package/dist/src/ripple/index.cjs +3 -0
  33. package/dist/src/ripple/index.cjs.map +10 -0
  34. package/dist/src/ripple/index.js +3 -0
  35. package/dist/src/ripple/index.js.map +10 -0
  36. package/dist/src/solana/index.cjs +3 -0
  37. package/dist/src/solana/index.cjs.map +10 -0
  38. package/dist/src/solana/index.js +3 -0
  39. package/dist/src/solana/index.js.map +10 -0
  40. package/dist/src/substrate/index.cjs +3 -0
  41. package/dist/src/substrate/index.cjs.map +12 -0
  42. package/dist/src/substrate/index.js +3 -0
  43. package/dist/src/substrate/index.js.map +12 -0
  44. package/dist/src/tron/index.cjs +3 -0
  45. package/dist/src/tron/index.cjs.map +11 -0
  46. package/dist/src/tron/index.js +3 -0
  47. package/dist/src/tron/index.js.map +11 -0
  48. package/dist/src/utxo/index.cjs +5 -0
  49. package/dist/src/utxo/index.cjs.map +17 -0
  50. package/dist/src/utxo/index.js +5 -0
  51. package/dist/src/utxo/index.js.map +17 -0
  52. package/package.json +92 -43
  53. package/src/cosmos/thorchainUtils/addressFormat.ts +4 -1
  54. package/src/cosmos/thorchainUtils/messages.ts +2 -2
  55. package/src/cosmos/thorchainUtils/registry.ts +3 -3
  56. package/src/cosmos/toolbox/cosmos.ts +35 -16
  57. package/src/cosmos/toolbox/index.ts +2 -2
  58. package/src/cosmos/toolbox/thorchain.ts +11 -9
  59. package/src/cosmos/util.ts +87 -7
  60. package/src/evm/__tests__/address-validation.test.ts +86 -0
  61. package/src/evm/__tests__/ethereum.test.ts +1 -1
  62. package/src/evm/helpers.ts +4 -3
  63. package/src/evm/toolbox/baseEVMToolbox.ts +34 -24
  64. package/src/evm/toolbox/index.ts +2 -2
  65. package/src/evm/toolbox/op.ts +21 -7
  66. package/src/index.ts +118 -100
  67. package/src/near/__tests__/core.test.ts +80 -0
  68. package/src/near/helpers/contractFactory.ts +22 -0
  69. package/src/near/helpers/core.ts +89 -0
  70. package/src/near/helpers/gasEstimation.ts +110 -0
  71. package/src/near/helpers/index.ts +5 -0
  72. package/src/near/helpers/nep141.ts +110 -0
  73. package/src/near/index.ts +24 -0
  74. package/src/near/toolbox.ts +498 -0
  75. package/src/near/types/contract.ts +48 -0
  76. package/src/near/types/nep141.ts +66 -0
  77. package/src/near/types.ts +57 -0
  78. package/src/radix/index.ts +8 -2
  79. package/src/ripple/index.ts +14 -21
  80. package/src/solana/toolbox.ts +73 -2
  81. package/src/substrate/balance.ts +92 -0
  82. package/src/substrate/substrate.ts +7 -5
  83. package/src/tron/__tests__/toolbox.test.ts +147 -0
  84. package/src/tron/helpers/trc20.abi.ts +40 -0
  85. package/src/tron/index.ts +16 -0
  86. package/src/tron/toolbox.ts +336 -0
  87. package/src/tron/types.ts +31 -0
  88. package/src/utxo/__tests__/zcash-integration.test.ts +114 -0
  89. package/src/utxo/helpers/api.ts +66 -16
  90. package/src/utxo/helpers/bchaddrjs.ts +8 -8
  91. package/src/utxo/helpers/coinselect.ts +4 -2
  92. package/src/utxo/helpers/txSize.ts +4 -3
  93. package/src/utxo/index.ts +1 -0
  94. package/src/utxo/toolbox/bitcoinCash.ts +21 -13
  95. package/src/utxo/toolbox/index.ts +16 -4
  96. package/src/utxo/toolbox/utxo.ts +42 -27
  97. package/src/utxo/toolbox/zcash.ts +208 -0
  98. package/dist/chunk-0h4xdrwz.js +0 -4
  99. package/dist/cosmos/index.cjs +0 -3
  100. package/dist/cosmos/index.cjs.map +0 -16
  101. package/dist/cosmos/index.js +0 -3
  102. package/dist/cosmos/index.js.map +0 -16
  103. package/dist/evm/index.cjs +0 -3
  104. package/dist/evm/index.cjs.map +0 -18
  105. package/dist/evm/index.js +0 -3
  106. package/dist/evm/index.js.map +0 -18
  107. package/dist/index.cjs +0 -3
  108. package/dist/index.cjs.map +0 -10
  109. package/dist/index.js +0 -3
  110. package/dist/index.js.map +0 -10
  111. package/dist/radix/index.cjs.map +0 -10
  112. package/dist/radix/index.js +0 -3
  113. package/dist/radix/index.js.map +0 -10
  114. package/dist/ripple/index.cjs +0 -3
  115. package/dist/ripple/index.cjs.map +0 -10
  116. package/dist/ripple/index.js +0 -3
  117. package/dist/ripple/index.js.map +0 -10
  118. package/dist/solana/index.cjs +0 -3
  119. package/dist/solana/index.cjs.map +0 -10
  120. package/dist/solana/index.js +0 -3
  121. package/dist/solana/index.js.map +0 -10
  122. package/dist/substrate/index.cjs +0 -3
  123. package/dist/substrate/index.cjs.map +0 -11
  124. package/dist/substrate/index.js +0 -3
  125. package/dist/substrate/index.js.map +0 -11
  126. package/dist/utxo/index.cjs +0 -3
  127. package/dist/utxo/index.cjs.map +0 -16
  128. package/dist/utxo/index.js +0 -3
  129. package/dist/utxo/index.js.map +0 -16
  130. /package/dist/{chunk-0f0249b1.js.map → chunk-9bqegm61.js.map} +0 -0
@@ -0,0 +1,336 @@
1
+ import {
2
+ AssetValue,
3
+ Chain,
4
+ NetworkDerivationPath,
5
+ SKConfig,
6
+ SwapKitError,
7
+ derivationPathToString,
8
+ updateDerivationPath,
9
+ warnOnce,
10
+ } from "@swapkit/helpers";
11
+ import { P, match } from "ts-pattern";
12
+
13
+ import { trc20ABI } from "./helpers/trc20.abi.js";
14
+ import type {
15
+ TronCreateTransactionParams,
16
+ TronSignedTransaction,
17
+ TronSigner,
18
+ TronToolboxOptions,
19
+ TronTransaction,
20
+ TronTransferParams,
21
+ } from "./types.js";
22
+
23
+ import { TronWeb } from "tronweb";
24
+
25
+ export async function getTronAddressValidator() {
26
+ return (address: string) => {
27
+ return TronWeb.isAddress(address);
28
+ };
29
+ }
30
+
31
+ export async function getTronPrivateKeyFromMnemonic({
32
+ phrase,
33
+ derivationPath: customPath,
34
+ index,
35
+ }: {
36
+ phrase: string;
37
+ derivationPath?: string;
38
+ index?: number;
39
+ }) {
40
+ const derivationPathToUse =
41
+ customPath ||
42
+ derivationPathToString(
43
+ updateDerivationPath(NetworkDerivationPath[Chain.Tron], {
44
+ index: index || 0,
45
+ }),
46
+ );
47
+
48
+ const { HDKey } = await import("@scure/bip32");
49
+ const { mnemonicToSeedSync } = await import("@scure/bip39");
50
+
51
+ const seed = mnemonicToSeedSync(phrase);
52
+ const hdKey = HDKey.fromMasterSeed(seed);
53
+ const derived = hdKey.derive(derivationPathToUse);
54
+
55
+ if (!derived.privateKey) {
56
+ throw new SwapKitError("toolbox_tron_no_signer");
57
+ }
58
+
59
+ return Buffer.from(derived.privateKey).toString("hex");
60
+ }
61
+
62
+ async function createKeysForPath({
63
+ phrase,
64
+ derivationPath,
65
+ }: {
66
+ phrase: string;
67
+ derivationPath: string;
68
+ }) {
69
+ const { HDKey } = await import("@scure/bip32");
70
+ const { mnemonicToSeedSync } = await import("@scure/bip39");
71
+
72
+ const seed = mnemonicToSeedSync(phrase);
73
+ const hdKey = HDKey.fromMasterSeed(seed);
74
+ const derived = hdKey.derive(derivationPath);
75
+
76
+ if (!derived.privateKey) {
77
+ throw new SwapKitError("toolbox_tron_no_signer");
78
+ }
79
+
80
+ // Convert private key to hex string for TronWeb
81
+ const privateKeyHex = Buffer.from(derived.privateKey).toString("hex");
82
+
83
+ // Create TronWeb instance with the derived private key
84
+ const tronWebWithKey = new TronWeb({
85
+ fullHost: SKConfig.get("rpcUrls")[Chain.Tron],
86
+ privateKey: privateKeyHex,
87
+ });
88
+
89
+ const address = tronWebWithKey.address.fromPrivateKey(privateKeyHex);
90
+
91
+ return {
92
+ getAddress: () => Promise.resolve(typeof address === "string" ? address : ""),
93
+ signTransaction: async (transaction: TronTransaction) => {
94
+ const signedTx = await tronWebWithKey.trx.sign(transaction, privateKeyHex);
95
+ return signedTx;
96
+ },
97
+ };
98
+ }
99
+
100
+ export const createTronToolbox = async (options: TronToolboxOptions = {}) => {
101
+ // Always get configuration from SKConfig
102
+ const rpcUrl = SKConfig.get("rpcUrls")[Chain.Tron];
103
+ // Note: TRON API key support can be added to SKConfig apiKeys when needed
104
+ const headers = undefined; // No API key needed for basic TronGrid access
105
+
106
+ const tronWeb = new TronWeb({
107
+ fullHost: rpcUrl,
108
+ headers,
109
+ });
110
+
111
+ // Handle derivation path and index
112
+ const index = "index" in options ? options.index || 0 : 0;
113
+ const derivationPath = derivationPathToString(
114
+ "derivationPath" in options && options.derivationPath
115
+ ? options.derivationPath
116
+ : updateDerivationPath(NetworkDerivationPath[Chain.Tron], { index }),
117
+ );
118
+
119
+ // Create signer based on options using pattern matching
120
+ const signer: TronSigner | undefined = await match(options)
121
+ .with({ phrase: P.string }, async ({ phrase }) => createKeysForPath({ phrase, derivationPath }))
122
+ .with({ signer: P.any }, ({ signer }) => Promise.resolve(signer as TronSigner))
123
+ .otherwise(() => Promise.resolve(undefined));
124
+
125
+ const getAddress = async () => {
126
+ if (!signer) throw new SwapKitError("toolbox_tron_no_signer");
127
+ return await signer.getAddress();
128
+ };
129
+
130
+ const calculateFeeLimit = () => {
131
+ return 100_000_000; // 100 TRX in SUN
132
+ };
133
+
134
+ const getBalance = async (address: string, scamFilter = true) => {
135
+ const { getBalance: getBalanceFromApi } = await import("../utils.js");
136
+
137
+ try {
138
+ // Use SwapKit API for comprehensive balance fetching (includes TRX + TRC20 tokens)
139
+ const apiBalances = await getBalanceFromApi(Chain.Tron)(address, scamFilter);
140
+
141
+ // If API returns balances, use those
142
+ if (apiBalances.length > 0) {
143
+ return apiBalances;
144
+ }
145
+
146
+ // Fallback to on-chain TRX balance if API fails or returns empty
147
+ const trxBalanceInSun = await tronWeb.trx.getBalance(address);
148
+ return [
149
+ AssetValue.from({
150
+ chain: Chain.Tron,
151
+ value: trxBalanceInSun,
152
+ fromBaseDecimal: 6, // TRX has 6 decimals
153
+ }),
154
+ ];
155
+ } catch (error) {
156
+ warnOnce(
157
+ true,
158
+ `Failed to get Tron balance for ${address}: ${error instanceof Error ? error.message : error}`,
159
+ );
160
+
161
+ // Final fallback: try to get just the native TRX balance
162
+ try {
163
+ const trxBalanceInSun = await tronWeb.trx.getBalance(address);
164
+ return [
165
+ AssetValue.from({
166
+ chain: Chain.Tron,
167
+ value: trxBalanceInSun,
168
+ fromBaseDecimal: 6,
169
+ }),
170
+ ];
171
+ } catch (fallbackError) {
172
+ warnOnce(
173
+ true,
174
+ `Failed to get native TRX balance for ${address}: ${fallbackError instanceof Error ? fallbackError.message : fallbackError}`,
175
+ );
176
+ return [];
177
+ }
178
+ }
179
+ };
180
+
181
+ const transfer = async ({ recipient, assetValue, memo }: TronTransferParams) => {
182
+ if (!signer) throw new SwapKitError("toolbox_tron_no_signer");
183
+
184
+ const from = await getAddress();
185
+ const isNative = assetValue.isGasAsset;
186
+
187
+ if (isNative) {
188
+ // Native TRX Transfer (amount in SUN - base units)
189
+ const transaction = await tronWeb.transactionBuilder.sendTrx(
190
+ recipient,
191
+ assetValue.getBaseValue("number"),
192
+ from,
193
+ );
194
+
195
+ // Add memo if provided
196
+ if (memo) {
197
+ const transactionWithMemo = await tronWeb.transactionBuilder.addUpdateData(
198
+ transaction,
199
+ memo,
200
+ "utf8",
201
+ );
202
+ const signedTx = await signer.signTransaction(transactionWithMemo);
203
+ const { txid } = await tronWeb.trx.sendRawTransaction(signedTx);
204
+ return txid;
205
+ }
206
+
207
+ const signedTx = await signer.signTransaction(transaction);
208
+ const { txid } = await tronWeb.trx.sendRawTransaction(signedTx);
209
+ return txid;
210
+ }
211
+
212
+ // TRC20 Token Transfer
213
+ const contractAddress = assetValue.address;
214
+ if (!contractAddress) {
215
+ throw new SwapKitError("toolbox_tron_invalid_token_identifier", {
216
+ identifier: assetValue.toString(),
217
+ });
218
+ }
219
+
220
+ const feeLimit = calculateFeeLimit();
221
+ const contract = await tronWeb.contract(trc20ABI, contractAddress);
222
+
223
+ if (!contract.methods?.transfer) {
224
+ throw new SwapKitError("toolbox_tron_token_transfer_failed");
225
+ }
226
+
227
+ const txid = await contract.methods
228
+ .transfer(recipient, assetValue.getBaseValue("string"))
229
+ .send({
230
+ from,
231
+ feeLimit,
232
+ callValue: 0,
233
+ });
234
+
235
+ if (!txid) {
236
+ throw new SwapKitError("toolbox_tron_token_transfer_failed");
237
+ }
238
+
239
+ return txid;
240
+ };
241
+
242
+ const estimateTransactionFee = ({ assetValue }: TronTransferParams) => {
243
+ const isNative = assetValue.isGasAsset;
244
+
245
+ if (isNative) {
246
+ // Native TRX transfers typically consume bandwidth, which is free up to daily limit
247
+ // Return a minimal fee estimation for bandwidth cost
248
+ return AssetValue.from({ chain: Chain.Tron, value: 1 }); // 1 TRX
249
+ }
250
+
251
+ // TRC20 transfers consume energy, estimate higher fee
252
+ return AssetValue.from({ chain: Chain.Tron, value: 10 }); // 10 TRX
253
+ };
254
+
255
+ const createTransaction = async (params: TronCreateTransactionParams) => {
256
+ const { recipient, assetValue, memo, sender } = params;
257
+ const isNative = assetValue.isGasAsset;
258
+
259
+ if (isNative) {
260
+ const transaction = await tronWeb.transactionBuilder.sendTrx(
261
+ recipient,
262
+ assetValue.getBaseValue("number"),
263
+ sender,
264
+ );
265
+
266
+ if (memo) {
267
+ return tronWeb.transactionBuilder.addUpdateData(transaction, memo, "utf8");
268
+ }
269
+
270
+ return transaction;
271
+ }
272
+
273
+ // For TRC20, we would need to build the transaction manually
274
+ // This is a simplified version - in practice, you'd build the contract call transaction
275
+ const contractAddress = assetValue.address;
276
+ if (!contractAddress) {
277
+ throw new SwapKitError("toolbox_tron_invalid_token_identifier", {
278
+ identifier: assetValue.toString(),
279
+ });
280
+ }
281
+
282
+ // Build TRC20 transfer transaction
283
+ // First, try using triggerSmartContract (might work despite the known bug)
284
+ try {
285
+ const functionSelector = "transfer(address,uint256)";
286
+ const parameter = [
287
+ { type: "address", value: recipient },
288
+ { type: "uint256", value: assetValue.getBaseValue("string") },
289
+ ];
290
+
291
+ const options = {
292
+ feeLimit: calculateFeeLimit(),
293
+ callValue: 0,
294
+ };
295
+
296
+ const result = await tronWeb.transactionBuilder.triggerSmartContract(
297
+ contractAddress,
298
+ functionSelector,
299
+ options,
300
+ parameter,
301
+ sender,
302
+ );
303
+
304
+ return result.transaction;
305
+ } catch (error) {
306
+ // If both methods fail, throw a descriptive error
307
+ throw new SwapKitError("toolbox_tron_transaction_creation_failed", {
308
+ message:
309
+ "Failed to create TRC20 transaction. This might be due to TronWeb 6.0.3 bug. Use the transfer method directly instead.",
310
+ originalError: error instanceof Error ? error.message : String(error),
311
+ });
312
+ }
313
+ };
314
+
315
+ const signTransaction = async (transaction: TronTransaction) => {
316
+ if (!signer) throw new SwapKitError("toolbox_tron_no_signer");
317
+ return await signer.signTransaction(transaction);
318
+ };
319
+
320
+ const broadcastTransaction = async (signedTx: TronSignedTransaction) => {
321
+ const { txid } = await tronWeb.trx.sendRawTransaction(signedTx);
322
+ return txid;
323
+ };
324
+
325
+ return {
326
+ tronWeb,
327
+ getAddress,
328
+ validateAddress: await getTronAddressValidator(),
329
+ getBalance,
330
+ transfer,
331
+ estimateTransactionFee,
332
+ createTransaction,
333
+ signTransaction,
334
+ broadcastTransaction,
335
+ };
336
+ };
@@ -0,0 +1,31 @@
1
+ import type {
2
+ DerivationPathArray,
3
+ GenericCreateTransactionParams,
4
+ GenericTransferParams,
5
+ } from "@swapkit/helpers";
6
+ import type { Contract, Types } from "tronweb";
7
+
8
+ // Re-export TronWeb types for convenience
9
+ export type TronTransaction = Types.Transaction;
10
+ export type TronContract = Contract;
11
+ export type TronSignedTransaction = Types.SignedTransaction;
12
+
13
+ // Signer interface compatible with TronWeb and wallet implementations
14
+ export interface TronSigner {
15
+ getAddress(): Promise<string>;
16
+ signTransaction(transaction: TronTransaction): Promise<TronSignedTransaction>;
17
+ }
18
+
19
+ export type TronToolboxOptions =
20
+ | { signer?: TronSigner }
21
+ | { phrase?: string; derivationPath?: DerivationPathArray; index?: number }
22
+ | {};
23
+
24
+ export interface TronTransferParams extends GenericTransferParams {
25
+ // No additional fields needed - all inherited from GenericTransferParams
26
+ }
27
+
28
+ export interface TronCreateTransactionParams
29
+ extends Omit<GenericCreateTransactionParams, "feeRate"> {
30
+ // No additional fields needed - all inherited from GenericCreateTransactionParams
31
+ }
@@ -0,0 +1,114 @@
1
+ import { describe, expect, it } from "bun:test";
2
+ import { Chain, DerivationPath } from "@swapkit/helpers";
3
+ import { getUtxoToolbox } from "../toolbox";
4
+
5
+ describe("UTXO Toolbox Zcash Integration", () => {
6
+ const testPhrase =
7
+ "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about";
8
+
9
+ it("should create Zcash toolbox through main UTXO toolbox factory", async () => {
10
+ const toolbox = await getUtxoToolbox(Chain.Zcash);
11
+
12
+ expect(toolbox).toBeDefined();
13
+ expect(typeof toolbox.validateAddress).toBe("function");
14
+ expect(typeof toolbox.getBalance).toBe("function");
15
+ expect(typeof toolbox.getFeeRates).toBe("function");
16
+ expect(typeof toolbox.broadcastTx).toBe("function");
17
+ expect(typeof toolbox.createTransaction).toBe("function");
18
+ expect(typeof toolbox.transfer).toBe("function");
19
+ });
20
+
21
+ it("should create Zcash toolbox with phrase", async () => {
22
+ const toolbox = await getUtxoToolbox(Chain.Zcash, {
23
+ phrase: testPhrase,
24
+ });
25
+
26
+ expect(toolbox).toBeDefined();
27
+ expect(() => toolbox.getAddress()).not.toThrow();
28
+ });
29
+
30
+ it("should generate valid Zcash addresses", async () => {
31
+ const toolbox = await getUtxoToolbox(Chain.Zcash, {
32
+ phrase: testPhrase,
33
+ });
34
+
35
+ const address = await toolbox.getAddress();
36
+ expect(address).toBeDefined();
37
+ expect(typeof address).toBe("string");
38
+ expect(address?.startsWith("t1")).toBe(true); // Zcash mainnet addresses start with t1
39
+ expect(toolbox.validateAddress(address || "")).toBe(true);
40
+ });
41
+
42
+ it("should validate Zcash addresses correctly", async () => {
43
+ const toolbox = await getUtxoToolbox(Chain.Zcash);
44
+
45
+ // Valid Zcash mainnet address format
46
+ expect(toolbox.validateAddress("t1XVXWCvpMgBvUaed4XDqWtgQgJSu1Ghz7F")).toBe(true);
47
+
48
+ // Invalid addresses
49
+ expect(toolbox.validateAddress("1BvBMSEYstWetqTFn5Au4m4GFg7xJaNVN2")).toBe(false); // Bitcoin address
50
+ expect(toolbox.validateAddress("zcash:qr5agtachyxvrwxu76vzszan5pnvuzy8dm")).toBe(false); // Wrong format
51
+ expect(toolbox.validateAddress("")).toBe(false); // Empty string
52
+ expect(toolbox.validateAddress("invalid")).toBe(false); // Invalid string
53
+ });
54
+
55
+ it("should reject shielded addresses", async () => {
56
+ const toolbox = await getUtxoToolbox(Chain.Zcash);
57
+
58
+ // Test z-address (shielded) - should be rejected with warning
59
+ const originalWarn = console.warn;
60
+ let warnCalled = false;
61
+ let warnMessage = "";
62
+
63
+ console.warn = (message: string) => {
64
+ warnCalled = true;
65
+ warnMessage = message;
66
+ };
67
+
68
+ const isValid = toolbox.validateAddress(
69
+ "zs1z7rejlpsa98s2rrrfkwmaxu2xldqmfq5nj2m3hq6s7r8qjq8eqqqq9p4e7x",
70
+ );
71
+
72
+ expect(isValid).toBe(false);
73
+ expect(warnCalled).toBe(true);
74
+ expect(warnMessage).toBe(
75
+ "Shielded Zcash addresses (z-addresses) are not supported. Use transparent addresses (t1/t3) only.",
76
+ );
77
+
78
+ console.warn = originalWarn;
79
+ });
80
+
81
+ it("should create keys for derivation path", async () => {
82
+ const toolbox = await getUtxoToolbox(Chain.Zcash, {
83
+ phrase: testPhrase,
84
+ });
85
+
86
+ const keys = await toolbox.createKeysForPath({
87
+ phrase: testPhrase,
88
+ derivationPath: DerivationPath.ZEC,
89
+ });
90
+
91
+ expect(keys).toBeDefined();
92
+ expect(keys.publicKey).toBeDefined();
93
+ expect(keys.privateKey).toBeDefined();
94
+ expect(typeof keys.toWIF).toBe("function");
95
+
96
+ const address = await toolbox.getAddress();
97
+ expect(address).toBeDefined();
98
+ expect(address?.startsWith("t1")).toBe(true);
99
+ });
100
+
101
+ it("should get WIF private key from mnemonic", async () => {
102
+ const toolbox = await getUtxoToolbox(Chain.Zcash, {
103
+ phrase: testPhrase,
104
+ });
105
+
106
+ const wif = await toolbox.getPrivateKeyFromMnemonic({
107
+ phrase: testPhrase,
108
+ derivationPath: DerivationPath.ZEC,
109
+ });
110
+
111
+ expect(typeof wif).toBe("string");
112
+ expect(wif.length).toBeGreaterThan(50); // WIF keys are typically 51-52 characters
113
+ });
114
+ });
@@ -1,6 +1,17 @@
1
- import { Chain, RequestClient, SKConfig, type UTXOChain, warnOnce } from "@swapkit/helpers";
1
+ import {
2
+ Chain,
3
+ RequestClient,
4
+ SKConfig,
5
+ SwapKitError,
6
+ type UTXOChain,
7
+ warnOnce,
8
+ } from "@swapkit/helpers";
9
+ import { networks } from "bitcoinjs-lib";
2
10
  import { uniqid } from "../../utils";
3
11
 
12
+ // @ts-ignore
13
+ import coininfo from "coininfo";
14
+
4
15
  type BlockchairParams<T> = T & { chain: Chain; apiKey?: string };
5
16
  type BlockchairFetchUnspentUtxoParams = BlockchairParams<{
6
17
  offset?: number;
@@ -24,11 +35,13 @@ async function broadcastUTXOTx({ chain, txHash }: { chain: Chain; txHash: string
24
35
  }>(rpcUrl, { headers: { "Content-Type": "application/json" }, body });
25
36
 
26
37
  if (response.error) {
27
- throw new Error(`failed to broadcast a transaction: ${response.error?.message}`);
38
+ throw new SwapKitError("toolbox_utxo_broadcast_failed", { error: response.error?.message });
28
39
  }
29
40
 
30
41
  if (response.result.includes('"code":-26')) {
31
- throw new Error("Invalid transaction: the transaction amount was too low");
42
+ throw new SwapKitError("toolbox_utxo_invalid_transaction", {
43
+ error: "Transaction amount was too low",
44
+ });
32
45
  }
33
46
 
34
47
  return response.result;
@@ -46,6 +59,8 @@ function getDefaultTxFeeByChain(chain: Chain) {
46
59
  return 10000;
47
60
  case Chain.Litecoin:
48
61
  return 1;
62
+ case Chain.Zcash:
63
+ return 1;
49
64
  default:
50
65
  return 2;
51
66
  }
@@ -61,6 +76,8 @@ function mapChainToBlockchairChain(chain: Chain) {
61
76
  return "dash";
62
77
  case Chain.Dogecoin:
63
78
  return "dogecoin";
79
+ case Chain.Zcash:
80
+ return "zcash";
64
81
  case Chain.Polkadot:
65
82
  return "polkadot";
66
83
  default:
@@ -89,7 +106,8 @@ async function getSuggestedTxFee(chain: Chain) {
89
106
  async function blockchairRequest<T>(url: string, apiKey?: string): Promise<T> {
90
107
  try {
91
108
  const response = await RequestClient.get<BlockchairResponse<T>>(url);
92
- if (!response || response.context.code !== 200) throw new Error(`failed to query ${url}`);
109
+ if (!response || response.context.code !== 200)
110
+ throw new SwapKitError("toolbox_utxo_api_error", { error: `Failed to query ${url}` });
93
111
 
94
112
  return response.data as T;
95
113
  } catch (error) {
@@ -98,14 +116,16 @@ async function blockchairRequest<T>(url: string, apiKey?: string): Promise<T> {
98
116
  `${url}${apiKey ? `&key=${apiKey}` : ""}`,
99
117
  );
100
118
 
101
- if (!response || response.context.code !== 200) throw new Error(`failed to query ${url}`);
119
+ if (!response || response.context.code !== 200)
120
+ throw new SwapKitError("toolbox_utxo_api_error", { error: `Failed to query ${url}` });
102
121
 
103
122
  return response.data as T;
104
123
  }
105
124
  }
106
125
 
107
126
  async function getAddressData({ address, chain, apiKey }: BlockchairParams<{ address?: string }>) {
108
- if (!address) throw new Error("address is required");
127
+ if (!address)
128
+ throw new SwapKitError("toolbox_utxo_invalid_params", { error: "Address is required" });
109
129
 
110
130
  try {
111
131
  const response = await blockchairRequest<BlockchairAddressResponse>(
@@ -130,7 +150,8 @@ async function getUnconfirmedBalance({
130
150
  }
131
151
 
132
152
  async function getRawTx({ chain, apiKey, txHash }: BlockchairParams<{ txHash?: string }>) {
133
- if (!txHash) throw new Error("txHash is required");
153
+ if (!txHash)
154
+ throw new SwapKitError("toolbox_utxo_invalid_params", { error: "TxHash is required" });
134
155
 
135
156
  try {
136
157
  const rawTxResponse = await blockchairRequest<BlockchairRawTransactionResponse>(
@@ -139,7 +160,7 @@ async function getRawTx({ chain, apiKey, txHash }: BlockchairParams<{ txHash?: s
139
160
  );
140
161
  return rawTxResponse?.[txHash]?.raw_transaction || "";
141
162
  } catch (error) {
142
- console.error(error);
163
+ console.error("Failed to fetch raw transaction:", error);
143
164
  return "";
144
165
  }
145
166
  }
@@ -177,7 +198,8 @@ async function getUnspentUtxos({
177
198
  offset = 0,
178
199
  limit = 100,
179
200
  }: BlockchairFetchUnspentUtxoParams): Promise<Awaited<ReturnType<typeof fetchUnspentUtxoBatch>>> {
180
- if (!address) throw new Error("address is required");
201
+ if (!address)
202
+ throw new SwapKitError("toolbox_utxo_invalid_params", { error: "Address is required" });
181
203
 
182
204
  try {
183
205
  const txs = await fetchUnspentUtxoBatch({ chain, address, apiKey, offset, limit });
@@ -194,7 +216,7 @@ async function getUnspentUtxos({
194
216
 
195
217
  return [...txs, ...nextBatch];
196
218
  } catch (error) {
197
- console.error(error);
219
+ console.error("Failed to fetch unspent UTXOs:", error);
198
220
  return [];
199
221
  }
200
222
  }
@@ -259,11 +281,32 @@ export function getUtxoApi(chain: UTXOChain) {
259
281
  return utxoApi(chain);
260
282
  }
261
283
 
262
- export async function getUtxoNetwork() {
263
- // @ts-ignore
264
- const coininfo = await import("coininfo");
265
- const { networks } = await import("bitcoinjs-lib");
266
-
284
+ // Define Zcash network objects that match ECPair's expected interface
285
+ const ZCASH_MAINNET = {
286
+ messagePrefix: "\x19Zcash Signed Message:\n",
287
+ bech32: "zc",
288
+ bip32: {
289
+ public: 0x0488b21e,
290
+ private: 0x0488ade4,
291
+ },
292
+ pubKeyHash: 0x1c, // 28 in decimal - correct for Zcash mainnet
293
+ scriptHash: 0x1c, // 28 in decimal
294
+ wif: 0x80, // 128 in decimal
295
+ };
296
+
297
+ const ZCASH_TESTNET = {
298
+ messagePrefix: "\x19Zcash Signed Message:\n",
299
+ bech32: "ztestsapling",
300
+ bip32: {
301
+ public: 0x043587cf,
302
+ private: 0x04358394,
303
+ },
304
+ pubKeyHash: 0x1d, // 29 in decimal - correct for Zcash testnet
305
+ scriptHash: 0x1c, // 28 in decimal
306
+ wif: 0xef, // 239 in decimal
307
+ };
308
+
309
+ export function getUtxoNetwork() {
267
310
  return function getNetwork(chain: Chain) {
268
311
  switch (chain) {
269
312
  case Chain.Bitcoin:
@@ -281,8 +324,15 @@ export async function getUtxoNetwork() {
281
324
  test.versions.bip32 = bip32;
282
325
  return coininfo.dogecoin.main.toBitcoinJS();
283
326
  }
327
+
328
+ case Chain.Zcash: {
329
+ // Get Zcash network configuration using our custom objects
330
+ const { isStagenet } = SKConfig.get("envs");
331
+ return isStagenet ? ZCASH_TESTNET : ZCASH_MAINNET;
332
+ }
333
+
284
334
  default:
285
- throw new Error("Invalid chain");
335
+ throw new SwapKitError("toolbox_utxo_not_supported", { chain });
286
336
  }
287
337
  };
288
338
  }