@swapkit/toolboxes 1.0.0-beta.3 → 1.0.0-beta.31
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-fjfxga2v.js → chunk-5yxc1e69.js} +1 -1
- package/dist/{chunk-fjfxga2v.js.map → chunk-5yxc1e69.js.map} +1 -1
- package/dist/chunk-6f98phv2.js +4 -0
- package/dist/{chunk-0h4xdrwz.js.map → chunk-6f98phv2.js.map} +2 -2
- package/dist/{chunk-0f0249b1.js → chunk-9bqegm61.js} +1 -1
- package/dist/{chunk-p1kdg37m.js → chunk-s47y8512.js} +2 -2
- package/dist/{chunk-p1kdg37m.js.map → chunk-s47y8512.js.map} +1 -1
- package/dist/chunk-vtd17cje.js +3 -0
- package/dist/chunk-vtd17cje.js.map +10 -0
- package/dist/chunk-zcdeg6h9.js +4 -0
- package/dist/chunk-zcdeg6h9.js.map +10 -0
- package/dist/src/cosmos/index.cjs +3 -0
- package/dist/src/cosmos/index.cjs.map +16 -0
- package/dist/src/cosmos/index.js +3 -0
- package/dist/src/cosmos/index.js.map +16 -0
- package/dist/src/evm/index.cjs +3 -0
- package/dist/src/evm/index.cjs.map +18 -0
- package/dist/src/evm/index.js +3 -0
- package/dist/src/evm/index.js.map +18 -0
- package/dist/src/index.cjs +3 -0
- package/dist/src/index.cjs.map +10 -0
- package/dist/src/index.js +3 -0
- package/dist/src/index.js.map +10 -0
- package/dist/src/near/index.cjs +3 -0
- package/dist/src/near/index.cjs.map +13 -0
- package/dist/src/near/index.js +3 -0
- package/dist/src/near/index.js.map +13 -0
- package/dist/{radix → src/radix}/index.cjs +2 -2
- package/dist/src/radix/index.cjs.map +10 -0
- package/dist/src/radix/index.js +3 -0
- package/dist/src/radix/index.js.map +10 -0
- package/dist/src/ripple/index.cjs +3 -0
- package/dist/src/ripple/index.cjs.map +10 -0
- package/dist/src/ripple/index.js +3 -0
- package/dist/src/ripple/index.js.map +10 -0
- package/dist/src/solana/index.cjs +3 -0
- package/dist/src/solana/index.cjs.map +10 -0
- package/dist/src/solana/index.js +3 -0
- package/dist/src/solana/index.js.map +10 -0
- package/dist/src/substrate/index.cjs +3 -0
- package/dist/src/substrate/index.cjs.map +12 -0
- package/dist/src/substrate/index.js +3 -0
- package/dist/src/substrate/index.js.map +12 -0
- package/dist/src/tron/index.cjs +3 -0
- package/dist/src/tron/index.cjs.map +11 -0
- package/dist/src/tron/index.js +3 -0
- package/dist/src/tron/index.js.map +11 -0
- package/dist/src/utxo/index.cjs +5 -0
- package/dist/src/utxo/index.cjs.map +17 -0
- package/dist/src/utxo/index.js +5 -0
- package/dist/src/utxo/index.js.map +17 -0
- package/package.json +49 -37
- package/src/cosmos/thorchainUtils/addressFormat.ts +4 -1
- package/src/cosmos/thorchainUtils/messages.ts +2 -2
- package/src/cosmos/thorchainUtils/registry.ts +3 -3
- package/src/cosmos/toolbox/cosmos.ts +35 -16
- package/src/cosmos/toolbox/index.ts +2 -2
- package/src/cosmos/toolbox/thorchain.ts +11 -9
- package/src/cosmos/util.ts +76 -6
- package/src/evm/__tests__/address-validation.test.ts +86 -0
- package/src/evm/__tests__/ethereum.test.ts +1 -1
- package/src/evm/helpers.ts +4 -3
- package/src/evm/toolbox/baseEVMToolbox.ts +37 -25
- package/src/evm/toolbox/index.ts +2 -2
- package/src/evm/toolbox/op.ts +21 -7
- package/src/index.ts +118 -100
- package/src/near/__tests__/core.test.ts +80 -0
- package/src/near/helpers/contractFactory.ts +22 -0
- package/src/near/helpers/core.ts +89 -0
- package/src/near/helpers/gasEstimation.ts +110 -0
- package/src/near/helpers/index.ts +5 -0
- package/src/near/helpers/nep141.ts +110 -0
- package/src/near/index.ts +24 -0
- package/src/near/toolbox.ts +498 -0
- package/src/near/types/contract.ts +48 -0
- package/src/near/types/nep141.ts +66 -0
- package/src/near/types.ts +57 -0
- package/src/radix/index.ts +8 -2
- package/src/ripple/index.ts +15 -26
- package/src/solana/toolbox.ts +73 -2
- package/src/substrate/balance.ts +92 -0
- package/src/substrate/substrate.ts +7 -5
- package/src/tron/__tests__/toolbox.test.ts +147 -0
- package/src/tron/helpers/trc20.abi.ts +40 -0
- package/src/tron/index.ts +16 -0
- package/src/tron/toolbox.ts +336 -0
- package/src/tron/types.ts +31 -0
- package/src/utxo/__tests__/zcash-integration.test.ts +114 -0
- package/src/utxo/helpers/api.ts +66 -16
- package/src/utxo/helpers/bchaddrjs.ts +8 -8
- package/src/utxo/helpers/coinselect.ts +4 -2
- package/src/utxo/helpers/txSize.ts +4 -3
- package/src/utxo/index.ts +1 -0
- package/src/utxo/toolbox/bitcoinCash.ts +22 -14
- package/src/utxo/toolbox/index.ts +16 -4
- package/src/utxo/toolbox/utxo.ts +42 -27
- package/src/utxo/toolbox/zcash.ts +208 -0
- package/src/utxo/types.ts +2 -0
- package/dist/chunk-0h4xdrwz.js +0 -4
- package/dist/cosmos/index.cjs +0 -3
- package/dist/cosmos/index.cjs.map +0 -16
- package/dist/cosmos/index.js +0 -3
- package/dist/cosmos/index.js.map +0 -16
- package/dist/evm/index.cjs +0 -3
- package/dist/evm/index.cjs.map +0 -18
- package/dist/evm/index.js +0 -3
- package/dist/evm/index.js.map +0 -18
- package/dist/index.cjs +0 -3
- package/dist/index.cjs.map +0 -10
- package/dist/index.js +0 -3
- package/dist/index.js.map +0 -10
- package/dist/radix/index.cjs.map +0 -10
- package/dist/radix/index.js +0 -3
- package/dist/radix/index.js.map +0 -10
- package/dist/ripple/index.cjs +0 -3
- package/dist/ripple/index.cjs.map +0 -10
- package/dist/ripple/index.js +0 -3
- package/dist/ripple/index.js.map +0 -10
- package/dist/solana/index.cjs +0 -3
- package/dist/solana/index.cjs.map +0 -10
- package/dist/solana/index.js +0 -3
- package/dist/solana/index.js.map +0 -10
- package/dist/substrate/index.cjs +0 -3
- package/dist/substrate/index.cjs.map +0 -11
- package/dist/substrate/index.js +0 -3
- package/dist/substrate/index.js.map +0 -11
- package/dist/utxo/index.cjs +0 -3
- package/dist/utxo/index.cjs.map +0 -16
- package/dist/utxo/index.js +0 -3
- package/dist/utxo/index.js.map +0 -16
- /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
|
+
});
|
package/src/utxo/helpers/api.ts
CHANGED
|
@@ -1,6 +1,17 @@
|
|
|
1
|
-
import {
|
|
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
|
|
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
|
|
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)
|
|
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)
|
|
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)
|
|
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)
|
|
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)
|
|
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
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
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
|
|
335
|
+
throw new SwapKitError("toolbox_utxo_not_supported", { chain });
|
|
286
336
|
}
|
|
287
337
|
};
|
|
288
338
|
}
|