@swapkit/toolboxes 1.0.0-beta.22 → 1.0.0-beta.24
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-12xtvbsp.js → chunk-6f98phv2.js} +2 -2
- package/dist/{chunk-12xtvbsp.js.map → chunk-6f98phv2.js.map} +1 -1
- package/dist/{chunk-kbnwrc5b.js → chunk-zcdeg6h9.js} +2 -2
- package/dist/{chunk-kbnwrc5b.js.map → chunk-zcdeg6h9.js.map} +1 -1
- package/dist/src/cosmos/index.cjs +2 -2
- package/dist/src/cosmos/index.cjs.map +6 -6
- package/dist/src/cosmos/index.js +2 -2
- package/dist/src/cosmos/index.js.map +6 -6
- package/dist/src/evm/index.js +2 -2
- package/dist/src/evm/index.js.map +1 -1
- package/dist/src/index.cjs +2 -2
- package/dist/src/index.cjs.map +3 -3
- package/dist/src/index.js +2 -2
- package/dist/src/index.js.map +3 -3
- package/dist/src/near/index.cjs +2 -2
- package/dist/src/near/index.cjs.map +4 -4
- package/dist/src/near/index.js +2 -2
- package/dist/src/near/index.js.map +4 -4
- package/dist/src/substrate/index.cjs +2 -2
- package/dist/src/substrate/index.cjs.map +5 -4
- package/dist/src/substrate/index.js +2 -2
- package/dist/src/substrate/index.js.map +5 -4
- package/dist/src/tron/index.cjs +2 -2
- package/dist/src/tron/index.cjs.map +3 -3
- package/dist/src/tron/index.js +2 -2
- package/dist/src/tron/index.js.map +3 -3
- package/dist/src/utxo/index.cjs +4 -2
- package/dist/src/utxo/index.cjs.map +8 -7
- package/dist/src/utxo/index.js +4 -2
- package/dist/src/utxo/index.js.map +8 -7
- package/package.json +2 -2
- package/src/cosmos/thorchainUtils/registry.ts +3 -3
- package/src/cosmos/toolbox/cosmos.ts +21 -7
- package/src/cosmos/toolbox/thorchain.ts +6 -6
- package/src/cosmos/util.ts +66 -3
- package/src/evm/__tests__/address-validation.test.ts +86 -0
- package/src/index.ts +1 -0
- package/src/near/__tests__/core.test.ts +80 -0
- package/src/near/helpers/core.ts +4 -1
- package/src/near/toolbox.ts +26 -25
- package/src/substrate/balance.ts +92 -0
- package/src/substrate/substrate.ts +6 -4
- package/src/tron/__tests__/address-validation.test.ts +123 -0
- package/src/tron/toolbox.ts +10 -10
- package/src/utxo/__tests__/zcash-integration.test.ts +114 -0
- package/src/utxo/helpers/api.ts +36 -0
- package/src/utxo/helpers/coinselect.ts +2 -0
- package/src/utxo/index.ts +1 -0
- package/src/utxo/toolbox/index.ts +14 -3
- package/src/utxo/toolbox/utxo.ts +7 -0
- package/src/utxo/toolbox/zcash.ts +208 -0
|
@@ -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
|
@@ -59,6 +59,8 @@ function getDefaultTxFeeByChain(chain: Chain) {
|
|
|
59
59
|
return 10000;
|
|
60
60
|
case Chain.Litecoin:
|
|
61
61
|
return 1;
|
|
62
|
+
case Chain.Zcash:
|
|
63
|
+
return 1;
|
|
62
64
|
default:
|
|
63
65
|
return 2;
|
|
64
66
|
}
|
|
@@ -74,6 +76,8 @@ function mapChainToBlockchairChain(chain: Chain) {
|
|
|
74
76
|
return "dash";
|
|
75
77
|
case Chain.Dogecoin:
|
|
76
78
|
return "dogecoin";
|
|
79
|
+
case Chain.Zcash:
|
|
80
|
+
return "zcash";
|
|
77
81
|
case Chain.Polkadot:
|
|
78
82
|
return "polkadot";
|
|
79
83
|
default:
|
|
@@ -277,6 +281,31 @@ export function getUtxoApi(chain: UTXOChain) {
|
|
|
277
281
|
return utxoApi(chain);
|
|
278
282
|
}
|
|
279
283
|
|
|
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
|
+
|
|
280
309
|
export function getUtxoNetwork() {
|
|
281
310
|
return function getNetwork(chain: Chain) {
|
|
282
311
|
switch (chain) {
|
|
@@ -295,6 +324,13 @@ export function getUtxoNetwork() {
|
|
|
295
324
|
test.versions.bip32 = bip32;
|
|
296
325
|
return coininfo.dogecoin.main.toBitcoinJS();
|
|
297
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
|
+
|
|
298
334
|
default:
|
|
299
335
|
throw new SwapKitError("toolbox_utxo_not_supported", { chain });
|
|
300
336
|
}
|
package/src/utxo/index.ts
CHANGED
|
@@ -10,11 +10,13 @@ import type { Psbt } from "bitcoinjs-lib";
|
|
|
10
10
|
import type { TransactionBuilderType, TransactionType, UTXOType } from "../types";
|
|
11
11
|
import { createBCHToolbox } from "./bitcoinCash";
|
|
12
12
|
import { createUTXOToolbox } from "./utxo";
|
|
13
|
+
import { createZcashToolbox } from "./zcash";
|
|
13
14
|
|
|
14
15
|
type BCHToolbox = Awaited<ReturnType<typeof createBCHToolbox>>;
|
|
15
16
|
type CommonUTXOToolbox = Awaited<
|
|
16
|
-
ReturnType<typeof createUTXOToolbox<Exclude<UTXOChain, Chain.BitcoinCash>>>
|
|
17
|
+
ReturnType<typeof createUTXOToolbox<Exclude<UTXOChain, Chain.BitcoinCash | Chain.Zcash>>>
|
|
17
18
|
>;
|
|
19
|
+
type ZcashToolbox = Awaited<ReturnType<typeof createZcashToolbox>>;
|
|
18
20
|
|
|
19
21
|
export type UTXOToolboxes = {
|
|
20
22
|
[Chain.BitcoinCash]: BCHToolbox;
|
|
@@ -22,6 +24,7 @@ export type UTXOToolboxes = {
|
|
|
22
24
|
[Chain.Dogecoin]: CommonUTXOToolbox;
|
|
23
25
|
[Chain.Litecoin]: CommonUTXOToolbox;
|
|
24
26
|
[Chain.Dash]: CommonUTXOToolbox;
|
|
27
|
+
[Chain.Zcash]: ZcashToolbox;
|
|
25
28
|
};
|
|
26
29
|
|
|
27
30
|
export type UTXOWallets = {
|
|
@@ -36,6 +39,9 @@ export type UtxoToolboxParams = {
|
|
|
36
39
|
[Chain.Dogecoin]: { signer: ChainSigner<Psbt, Psbt> };
|
|
37
40
|
[Chain.Litecoin]: { signer: ChainSigner<Psbt, Psbt> };
|
|
38
41
|
[Chain.Dash]: { signer: ChainSigner<Psbt, Psbt> };
|
|
42
|
+
[Chain.Zcash]: {
|
|
43
|
+
signer?: ChainSigner<Psbt, Psbt>;
|
|
44
|
+
};
|
|
39
45
|
};
|
|
40
46
|
|
|
41
47
|
export async function getUtxoToolbox<T extends keyof UTXOToolboxes>(
|
|
@@ -54,15 +60,20 @@ export async function getUtxoToolbox<T extends keyof UTXOToolboxes>(
|
|
|
54
60
|
return toolbox as UTXOToolboxes[T];
|
|
55
61
|
}
|
|
56
62
|
|
|
63
|
+
case Chain.Zcash: {
|
|
64
|
+
const toolbox = await createZcashToolbox(params as UtxoToolboxParams[Chain.Zcash]);
|
|
65
|
+
return toolbox as UTXOToolboxes[T];
|
|
66
|
+
}
|
|
67
|
+
|
|
57
68
|
case Chain.Bitcoin:
|
|
58
69
|
case Chain.Dogecoin:
|
|
59
70
|
case Chain.Litecoin:
|
|
60
71
|
case Chain.Dash: {
|
|
61
72
|
const toolbox = await createUTXOToolbox({
|
|
62
73
|
chain,
|
|
63
|
-
...(params as UtxoToolboxParams[Exclude<T, Chain.BitcoinCash>]),
|
|
74
|
+
...(params as UtxoToolboxParams[Exclude<T, Chain.BitcoinCash | Chain.Zcash>]),
|
|
64
75
|
});
|
|
65
|
-
return toolbox as UTXOToolboxes[Exclude<T, Chain.BitcoinCash>];
|
|
76
|
+
return toolbox as UTXOToolboxes[Exclude<T, Chain.BitcoinCash | Chain.Zcash>];
|
|
66
77
|
}
|
|
67
78
|
|
|
68
79
|
default:
|
package/src/utxo/toolbox/utxo.ts
CHANGED
|
@@ -42,6 +42,7 @@ import secp256k1 from "@bitcoinerlab/secp256k1";
|
|
|
42
42
|
import { ECPair, HDNode } from "@psf/bitcoincashjs-lib";
|
|
43
43
|
import { HDKey } from "@scure/bip32";
|
|
44
44
|
import { mnemonicToSeedSync } from "@scure/bip39";
|
|
45
|
+
import { validateZcashAddress } from "./zcash";
|
|
45
46
|
|
|
46
47
|
export const nonSegwitChains = [Chain.Dash, Chain.Dogecoin];
|
|
47
48
|
|
|
@@ -153,6 +154,10 @@ export async function getUTXOAddressValidator() {
|
|
|
153
154
|
return bchValidateAddress(address);
|
|
154
155
|
}
|
|
155
156
|
|
|
157
|
+
if (chain === Chain.Zcash) {
|
|
158
|
+
return validateZcashAddress(address);
|
|
159
|
+
}
|
|
160
|
+
|
|
156
161
|
try {
|
|
157
162
|
initEccLib(secp256k1);
|
|
158
163
|
btcLibAddress.toOutputScript(address, getNetwork(chain));
|
|
@@ -351,6 +356,7 @@ type CreateKeysForPathReturnType = {
|
|
|
351
356
|
[Chain.Dash]: ECPairInterface;
|
|
352
357
|
[Chain.Dogecoin]: ECPairInterface;
|
|
353
358
|
[Chain.Litecoin]: ECPairInterface;
|
|
359
|
+
[Chain.Zcash]: ECPairInterface;
|
|
354
360
|
};
|
|
355
361
|
|
|
356
362
|
export async function getCreateKeysForPath<T extends keyof CreateKeysForPathReturnType>(
|
|
@@ -395,6 +401,7 @@ export async function getCreateKeysForPath<T extends keyof CreateKeysForPathRetu
|
|
|
395
401
|
case Chain.Bitcoin:
|
|
396
402
|
case Chain.Dogecoin:
|
|
397
403
|
case Chain.Litecoin:
|
|
404
|
+
case Chain.Zcash:
|
|
398
405
|
case Chain.Dash: {
|
|
399
406
|
return function createKeysForPath({
|
|
400
407
|
phrase,
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
import secp256k1 from "@bitcoinerlab/secp256k1";
|
|
2
|
+
import { HDKey } from "@scure/bip32";
|
|
3
|
+
import { mnemonicToSeedSync } from "@scure/bip39";
|
|
4
|
+
import {
|
|
5
|
+
Chain,
|
|
6
|
+
type ChainSigner,
|
|
7
|
+
type DerivationPathArray,
|
|
8
|
+
FeeOption,
|
|
9
|
+
NetworkDerivationPath,
|
|
10
|
+
SKConfig,
|
|
11
|
+
SwapKitError,
|
|
12
|
+
derivationPathToString,
|
|
13
|
+
updateDerivationPath,
|
|
14
|
+
} from "@swapkit/helpers";
|
|
15
|
+
import type { Psbt } from "bitcoinjs-lib";
|
|
16
|
+
import { hash160 } from "bitcoinjs-lib/src/crypto";
|
|
17
|
+
import bs58check from "bs58check";
|
|
18
|
+
import ECPairFactory from "ecpair";
|
|
19
|
+
import { P, match } from "ts-pattern";
|
|
20
|
+
import { getUtxoNetwork } from "../helpers";
|
|
21
|
+
import type { UTXOBuildTxParams, UTXOTransferParams } from "../types";
|
|
22
|
+
import { createUTXOToolbox } from "./utxo";
|
|
23
|
+
|
|
24
|
+
const chain = Chain.Zcash;
|
|
25
|
+
const network = getUtxoNetwork()(chain);
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Custom Zcash address generation that handles 2-byte prefixes
|
|
29
|
+
*
|
|
30
|
+
* Zcash transparent addresses use 2-byte version prefixes, unlike Bitcoin's single byte.
|
|
31
|
+
* This is incompatible with bitcoinjs-lib's payment methods which expect UInt8 values.
|
|
32
|
+
*
|
|
33
|
+
* @param publicKey - The public key buffer to generate address from
|
|
34
|
+
* @param isTestnet - Whether to generate testnet or mainnet address
|
|
35
|
+
* @returns A valid Zcash transparent address (t1... for mainnet, tm... for testnet)
|
|
36
|
+
*/
|
|
37
|
+
function generateZcashAddress(publicKey: Buffer, isTestnet = false): string {
|
|
38
|
+
// Hash the public key using RIPEMD160(SHA256(pubkey))
|
|
39
|
+
const publicKeyHash = hash160(publicKey);
|
|
40
|
+
|
|
41
|
+
// Zcash uses 2-byte prefixes for transparent addresses
|
|
42
|
+
// These prefixes ensure addresses start with expected characters when base58 encoded
|
|
43
|
+
const prefix = isTestnet
|
|
44
|
+
? Buffer.from([0x1c, 0xba]) // testnet prefix (results in tm... addresses)
|
|
45
|
+
: Buffer.from([0x1c, 0xb8]); // mainnet prefix (results in t1... addresses)
|
|
46
|
+
|
|
47
|
+
// Combine prefix + hash (22 bytes total: 2 byte prefix + 20 byte hash)
|
|
48
|
+
const payload = Buffer.concat([prefix, publicKeyHash]);
|
|
49
|
+
|
|
50
|
+
// Encode with base58check for final address
|
|
51
|
+
return bs58check.encode(payload);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export function validateZcashAddress(address: string) {
|
|
55
|
+
try {
|
|
56
|
+
// Shielded addresses are not supported
|
|
57
|
+
if (address.startsWith("z")) {
|
|
58
|
+
console.warn(
|
|
59
|
+
"Shielded Zcash addresses (z-addresses) are not supported. Use transparent addresses (t1/t3) only.",
|
|
60
|
+
);
|
|
61
|
+
return false;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const isMainnet = address.startsWith("t1");
|
|
65
|
+
const isTestnet = address.startsWith("t3");
|
|
66
|
+
|
|
67
|
+
if (!(isMainnet || isTestnet)) {
|
|
68
|
+
return false;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Verify network matches address type
|
|
72
|
+
const { isStagenet } = SKConfig.get("envs");
|
|
73
|
+
if ((isMainnet && isStagenet) || (isTestnet && !isStagenet)) {
|
|
74
|
+
return false;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
return validateBase58Check(address, network);
|
|
78
|
+
} catch {
|
|
79
|
+
return false;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function validateBase58Check(
|
|
84
|
+
address: string,
|
|
85
|
+
network: ReturnType<ReturnType<typeof getUtxoNetwork>>,
|
|
86
|
+
) {
|
|
87
|
+
try {
|
|
88
|
+
const decoded = bs58check.decode(address);
|
|
89
|
+
|
|
90
|
+
if (decoded.length < 21) {
|
|
91
|
+
return false;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const version = decoded[0];
|
|
95
|
+
return version === network.pubKeyHash || version === network.scriptHash;
|
|
96
|
+
} catch {
|
|
97
|
+
return false;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const ECPair = ECPairFactory(secp256k1);
|
|
102
|
+
|
|
103
|
+
type ZcashSigner = ChainSigner<Psbt, Psbt>;
|
|
104
|
+
|
|
105
|
+
async function createZcashSignerFromPhrase({
|
|
106
|
+
phrase,
|
|
107
|
+
derivationPathString,
|
|
108
|
+
}: {
|
|
109
|
+
phrase: string;
|
|
110
|
+
derivationPathString: string;
|
|
111
|
+
}) {
|
|
112
|
+
const seed = mnemonicToSeedSync(phrase);
|
|
113
|
+
const root = HDKey.fromMasterSeed(seed);
|
|
114
|
+
|
|
115
|
+
const node = root.derive(derivationPathString);
|
|
116
|
+
|
|
117
|
+
if (!node.privateKey) {
|
|
118
|
+
throw new Error("Unable to derive private key");
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const keyPair = ECPair.fromPrivateKey(Buffer.from(node.privateKey), { network });
|
|
122
|
+
|
|
123
|
+
const { isStagenet } = SKConfig.get("envs");
|
|
124
|
+
const address = generateZcashAddress(keyPair.publicKey, isStagenet);
|
|
125
|
+
|
|
126
|
+
return {
|
|
127
|
+
getAddress() {
|
|
128
|
+
return Promise.resolve(address);
|
|
129
|
+
},
|
|
130
|
+
|
|
131
|
+
signTransaction(psbt: Psbt) {
|
|
132
|
+
for (let i = 0; i < psbt.inputCount; i++) {
|
|
133
|
+
psbt.signInput(i, keyPair);
|
|
134
|
+
}
|
|
135
|
+
return Promise.resolve(psbt);
|
|
136
|
+
},
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
export async function createZcashToolbox(
|
|
141
|
+
toolboxParams:
|
|
142
|
+
| {
|
|
143
|
+
signer?: ZcashSigner;
|
|
144
|
+
}
|
|
145
|
+
| {
|
|
146
|
+
phrase?: string;
|
|
147
|
+
derivationPath?: DerivationPathArray;
|
|
148
|
+
index?: number;
|
|
149
|
+
},
|
|
150
|
+
) {
|
|
151
|
+
const signer = await match(toolboxParams)
|
|
152
|
+
.with({ signer: P.not(P.nullish) }, ({ signer }) => Promise.resolve(signer))
|
|
153
|
+
.with({ phrase: P.string }, ({ phrase, derivationPath, index = 0 }) => {
|
|
154
|
+
// Handle derivation path processing at toolbox level
|
|
155
|
+
const baseDerivationPath = derivationPath ||
|
|
156
|
+
NetworkDerivationPath[Chain.Zcash] || [44, 133, 0, 0, 0];
|
|
157
|
+
const updatedDerivationPath = updateDerivationPath(baseDerivationPath, { index });
|
|
158
|
+
const derivationPathString = derivationPathToString(updatedDerivationPath);
|
|
159
|
+
|
|
160
|
+
return createZcashSignerFromPhrase({ phrase, derivationPathString });
|
|
161
|
+
})
|
|
162
|
+
.otherwise(() => Promise.resolve(undefined));
|
|
163
|
+
|
|
164
|
+
const { getFeeRates, broadcastTx, ...toolbox } = await createUTXOToolbox({
|
|
165
|
+
chain: Chain.Zcash,
|
|
166
|
+
signer,
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
function getAddressFromKeys(keys: { getAddress: () => string }) {
|
|
170
|
+
return keys.getAddress();
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
async function transfer({
|
|
174
|
+
recipient,
|
|
175
|
+
assetValue,
|
|
176
|
+
feeOptionKey = FeeOption.Fast,
|
|
177
|
+
...rest
|
|
178
|
+
}: UTXOTransferParams) {
|
|
179
|
+
const from = await signer?.getAddress();
|
|
180
|
+
if (!(signer && from)) throw new SwapKitError("toolbox_utxo_no_signer");
|
|
181
|
+
|
|
182
|
+
const feeRate = rest.feeRate || (await getFeeRates())[feeOptionKey];
|
|
183
|
+
|
|
184
|
+
const buildTxParams: UTXOBuildTxParams = {
|
|
185
|
+
...rest,
|
|
186
|
+
assetValue,
|
|
187
|
+
feeRate,
|
|
188
|
+
recipient,
|
|
189
|
+
sender: from,
|
|
190
|
+
};
|
|
191
|
+
|
|
192
|
+
const { psbt } = await toolbox.createTransaction(buildTxParams);
|
|
193
|
+
const signedPsbt = await signer.signTransaction(psbt);
|
|
194
|
+
signedPsbt.finalizeAllInputs();
|
|
195
|
+
const txHex = signedPsbt.extractTransaction().toHex();
|
|
196
|
+
|
|
197
|
+
return broadcastTx(txHex);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
return {
|
|
201
|
+
...toolbox,
|
|
202
|
+
broadcastTx,
|
|
203
|
+
getFeeRates,
|
|
204
|
+
transfer,
|
|
205
|
+
getAddressFromKeys,
|
|
206
|
+
validateAddress: validateZcashAddress,
|
|
207
|
+
};
|
|
208
|
+
}
|