@swapkit/toolboxes 1.0.0-beta.23 → 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 +3 -3
- package/dist/src/near/index.js +2 -2
- package/dist/src/near/index.js.map +3 -3
- 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/toolbox.ts +22 -1
- package/src/substrate/balance.ts +92 -0
- package/src/substrate/substrate.ts +6 -4
- package/src/tron/toolbox.ts +1 -1
- 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,86 @@
|
|
|
1
|
+
import { beforeAll, describe, expect, test } from "bun:test";
|
|
2
|
+
import { Chain } from "@swapkit/helpers";
|
|
3
|
+
import { getEvmToolbox } from "../toolbox";
|
|
4
|
+
|
|
5
|
+
const context: {
|
|
6
|
+
validateAddress: (address: string) => boolean;
|
|
7
|
+
} = {} as any;
|
|
8
|
+
|
|
9
|
+
beforeAll(async () => {
|
|
10
|
+
// Get EVM toolbox for address validation
|
|
11
|
+
const toolbox = await getEvmToolbox(Chain.Ethereum);
|
|
12
|
+
context.validateAddress = toolbox.validateAddress;
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
describe("EVM Address Validation", () => {
|
|
16
|
+
test("should validate valid EVM addresses", () => {
|
|
17
|
+
const validAddresses = [
|
|
18
|
+
"0xa052Ddf1c1739419B90FB7bf722843AD3e63114B", // User provided address
|
|
19
|
+
"0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", // USDC contract
|
|
20
|
+
"0x6d6e022eE439C8aB8B7a7dBb0576f8090319CDc6", // Test address
|
|
21
|
+
"0xE29E61479420Dd1029A9946710Ac31A0d140e77F", // Another valid address
|
|
22
|
+
"0x0000000000000000000000000000000000000000", // Zero address
|
|
23
|
+
"0xFFfFfFffFFfffFFfFFfFFFFFffFFFffffFfFFFfF", // Max address
|
|
24
|
+
"0x1234567890123456789012345678901234567890", // Mixed case
|
|
25
|
+
];
|
|
26
|
+
|
|
27
|
+
for (const address of validAddresses) {
|
|
28
|
+
expect(context.validateAddress(address)).toBe(true);
|
|
29
|
+
}
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
test("should reject invalid EVM addresses", () => {
|
|
33
|
+
const invalidAddresses = [
|
|
34
|
+
"", // Empty string
|
|
35
|
+
"invalid", // Random string
|
|
36
|
+
"TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t", // TRON address
|
|
37
|
+
"1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa", // Bitcoin address
|
|
38
|
+
"cosmos1abc123", // Cosmos address
|
|
39
|
+
"0xa052Ddf1c1739419B90FB7bf722843AD3e63114", // Too short (missing 1 char)
|
|
40
|
+
"0xa052Ddf1c1739419B90FB7bf722843AD3e63114BB", // Too long (extra char)
|
|
41
|
+
"0XA052DDF1C1739419B90FB7BF722843AD3E63114B", // Uppercase 0X prefix
|
|
42
|
+
"0xG052Ddf1c1739419B90FB7bf722843AD3e63114B", // Invalid hex character
|
|
43
|
+
"0x", // Only prefix
|
|
44
|
+
];
|
|
45
|
+
|
|
46
|
+
for (const address of invalidAddresses) {
|
|
47
|
+
expect(context.validateAddress(address)).toBe(false);
|
|
48
|
+
}
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
test("should handle case normalization properly", () => {
|
|
52
|
+
const address = "0xa052Ddf1c1739419B90FB7bf722843AD3e63114B"; // Proper checksum
|
|
53
|
+
const lowerCase = address.toLowerCase();
|
|
54
|
+
|
|
55
|
+
// Valid case variations should be accepted
|
|
56
|
+
expect(context.validateAddress(address)).toBe(true);
|
|
57
|
+
expect(context.validateAddress(lowerCase)).toBe(true);
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
test("should reject invalid checksum addresses", () => {
|
|
61
|
+
const invalidChecksumAddress = "0xA052dDF1C1739419b90fb7BF722843ad3E63114b"; // Invalid checksum
|
|
62
|
+
|
|
63
|
+
// Should reject mixed case with invalid checksum
|
|
64
|
+
expect(context.validateAddress(invalidChecksumAddress)).toBe(false);
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
test("should handle edge cases", () => {
|
|
68
|
+
const edgeCases = [null, undefined, 123, {}, [], true, false];
|
|
69
|
+
|
|
70
|
+
for (const testCase of edgeCases) {
|
|
71
|
+
// Should not throw but return false for invalid inputs
|
|
72
|
+
expect(context.validateAddress(testCase as any)).toBe(false);
|
|
73
|
+
}
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
test("should validate checksummed addresses", () => {
|
|
77
|
+
const checksummedAddresses = [
|
|
78
|
+
"0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", // USDC with proper checksum
|
|
79
|
+
"0x6d6e022eE439C8aB8B7a7dBb0576f8090319CDc6", // Another checksummed address
|
|
80
|
+
];
|
|
81
|
+
|
|
82
|
+
for (const address of checksummedAddresses) {
|
|
83
|
+
expect(context.validateAddress(address)).toBe(true);
|
|
84
|
+
}
|
|
85
|
+
});
|
|
86
|
+
});
|
package/src/index.ts
CHANGED
|
@@ -192,6 +192,7 @@ export async function getToolbox<T extends keyof Toolboxes>(
|
|
|
192
192
|
Chain.Dogecoin,
|
|
193
193
|
Chain.BitcoinCash,
|
|
194
194
|
Chain.Bitcoin,
|
|
195
|
+
Chain.Zcash,
|
|
195
196
|
async () => {
|
|
196
197
|
const { getUtxoToolbox } = await import("@swapkit/toolboxes/utxo");
|
|
197
198
|
const utxoToolbox = await getUtxoToolbox(
|
package/src/near/toolbox.ts
CHANGED
|
@@ -117,8 +117,29 @@ export async function getNearToolbox(toolboxParams?: NearToolboxParams) {
|
|
|
117
117
|
async function createTransaction(params: NearCreateTransactionParams) {
|
|
118
118
|
const { recipient, assetValue, memo, feeRate: gas, attachedDeposit, sender: signerId } = params;
|
|
119
119
|
|
|
120
|
-
|
|
120
|
+
// Handle NEP-141 token transfers
|
|
121
|
+
if (!assetValue.isGasAsset) {
|
|
122
|
+
const contractId = assetValue.address;
|
|
123
|
+
if (!contractId) {
|
|
124
|
+
throw new SwapKitError("toolbox_near_missing_contract_address");
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
return createContractFunctionCall({
|
|
128
|
+
sender: signerId,
|
|
129
|
+
contractId,
|
|
130
|
+
methodName: "ft_transfer",
|
|
131
|
+
args: {
|
|
132
|
+
receiver_id: recipient,
|
|
133
|
+
amount: assetValue.getBaseValue("string"),
|
|
134
|
+
memo: memo || null,
|
|
135
|
+
},
|
|
136
|
+
gas: gas.toString() || "100000000000000", // 100 TGas default
|
|
137
|
+
attachedDeposit: "1", // 1 yoctoNEAR required for NEP-141 transfers
|
|
138
|
+
});
|
|
139
|
+
}
|
|
121
140
|
|
|
141
|
+
// Native NEAR transfer
|
|
142
|
+
const { publicKey, nonce } = await getFullAccessPublicKey(provider, signerId);
|
|
122
143
|
const baseAmount = assetValue.getBaseValue("bigint");
|
|
123
144
|
|
|
124
145
|
const { SCHEMA } = await import("near-api-js/lib/transaction");
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import type { ApiPromise } from "@polkadot/api";
|
|
2
|
+
import { AssetValue, Chain, SwapKitNumber } from "@swapkit/helpers";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Get balance for standard Substrate chains (Polkadot, etc.)
|
|
6
|
+
* Uses api.query.system.account to query free and reserved balances
|
|
7
|
+
*/
|
|
8
|
+
export async function getSubstrateBalance(
|
|
9
|
+
api: ApiPromise,
|
|
10
|
+
gasAsset: AssetValue,
|
|
11
|
+
address: string,
|
|
12
|
+
): Promise<AssetValue[]> {
|
|
13
|
+
try {
|
|
14
|
+
const account = await api.query.system?.account?.(address);
|
|
15
|
+
|
|
16
|
+
if (!account) {
|
|
17
|
+
return [gasAsset.set(0)];
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const {
|
|
21
|
+
// @ts-expect-error
|
|
22
|
+
data: { free },
|
|
23
|
+
} = account;
|
|
24
|
+
|
|
25
|
+
// Convert the free balance to string using SwapKitNumber for proper decimal handling
|
|
26
|
+
const freeBalance = SwapKitNumber.fromBigInt(
|
|
27
|
+
BigInt(free.toString()),
|
|
28
|
+
gasAsset.decimal,
|
|
29
|
+
).getValue("string");
|
|
30
|
+
|
|
31
|
+
return [gasAsset.set(freeBalance)];
|
|
32
|
+
} catch (error) {
|
|
33
|
+
console.error("Error fetching substrate balance:", error);
|
|
34
|
+
return [gasAsset.set(0)];
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Get balance for Chainflip chain
|
|
40
|
+
* Uses api.query.flip.account to query FLIP balances
|
|
41
|
+
*/
|
|
42
|
+
export async function getChainflipBalance(
|
|
43
|
+
api: ApiPromise,
|
|
44
|
+
gasAsset: AssetValue,
|
|
45
|
+
address: string,
|
|
46
|
+
): Promise<AssetValue[]> {
|
|
47
|
+
try {
|
|
48
|
+
// Chainflip uses a custom flip pallet for account balances
|
|
49
|
+
const flipAccount = await api.query.flip?.account?.(address);
|
|
50
|
+
|
|
51
|
+
if (!flipAccount) {
|
|
52
|
+
return [gasAsset.set(0)];
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Extract balance from the flip account structure
|
|
56
|
+
// The structure has a balance field directly
|
|
57
|
+
//@ts-expect-error
|
|
58
|
+
const balance = flipAccount.balance || flipAccount.data?.balance;
|
|
59
|
+
|
|
60
|
+
if (!balance || balance.isEmpty) {
|
|
61
|
+
return [gasAsset.set(0)];
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Convert balance to string using SwapKitNumber
|
|
65
|
+
const balanceStr = SwapKitNumber.fromBigInt(
|
|
66
|
+
BigInt(balance.toString()),
|
|
67
|
+
gasAsset.decimal,
|
|
68
|
+
).getValue("string");
|
|
69
|
+
|
|
70
|
+
return [gasAsset.set(balanceStr)];
|
|
71
|
+
} catch (error) {
|
|
72
|
+
console.error("Error fetching chainflip balance:", error);
|
|
73
|
+
return [gasAsset.set(0)];
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Factory function to create chain-specific balance getter
|
|
79
|
+
*/
|
|
80
|
+
export function createBalanceGetter(chain: Chain, api: ApiPromise) {
|
|
81
|
+
return function getBalance(address: string): Promise<AssetValue[]> {
|
|
82
|
+
const gasAsset = AssetValue.from({ chain });
|
|
83
|
+
|
|
84
|
+
switch (chain) {
|
|
85
|
+
case Chain.Chainflip:
|
|
86
|
+
return getChainflipBalance(api, gasAsset, address);
|
|
87
|
+
|
|
88
|
+
default:
|
|
89
|
+
return getSubstrateBalance(api, gasAsset, address);
|
|
90
|
+
}
|
|
91
|
+
};
|
|
92
|
+
}
|
|
@@ -19,7 +19,7 @@ import {
|
|
|
19
19
|
} from "@swapkit/helpers";
|
|
20
20
|
|
|
21
21
|
import { P, match } from "ts-pattern";
|
|
22
|
-
import {
|
|
22
|
+
import { createBalanceGetter } from "./balance";
|
|
23
23
|
import { SubstrateNetwork, type SubstrateTransferParams } from "./types";
|
|
24
24
|
|
|
25
25
|
export const PolkadotToolbox = ({ generic = false, ...signerParams }: ToolboxParams = {}) => {
|
|
@@ -36,7 +36,7 @@ export const ChainflipToolbox = async ({
|
|
|
36
36
|
...signerParams,
|
|
37
37
|
});
|
|
38
38
|
|
|
39
|
-
return { ...toolbox
|
|
39
|
+
return { ...toolbox };
|
|
40
40
|
};
|
|
41
41
|
|
|
42
42
|
export type SubstrateToolboxes = {
|
|
@@ -208,11 +208,13 @@ export const BaseSubstrateToolbox = ({
|
|
|
208
208
|
network,
|
|
209
209
|
gasAsset,
|
|
210
210
|
signer,
|
|
211
|
+
chain,
|
|
211
212
|
}: {
|
|
212
213
|
api: ApiPromise;
|
|
213
214
|
network: SubstrateNetwork;
|
|
214
215
|
gasAsset: AssetValue;
|
|
215
216
|
signer?: IKeyringPair | Signer;
|
|
217
|
+
chain?: SubstrateChain;
|
|
216
218
|
}) => ({
|
|
217
219
|
api,
|
|
218
220
|
network,
|
|
@@ -220,7 +222,7 @@ export const BaseSubstrateToolbox = ({
|
|
|
220
222
|
decodeAddress,
|
|
221
223
|
encodeAddress,
|
|
222
224
|
convertAddress,
|
|
223
|
-
getBalance:
|
|
225
|
+
getBalance: createBalanceGetter(chain || Chain.Polkadot, api),
|
|
224
226
|
createKeyring: (phrase: string) => createKeyring(phrase, network.prefix),
|
|
225
227
|
getAddress: (keyring?: IKeyringPair | Signer) => {
|
|
226
228
|
const keyringPair = keyring || signer;
|
|
@@ -301,7 +303,7 @@ export async function createSubstrateToolbox({
|
|
|
301
303
|
.with({ signer: P.any }, ({ signer }) => signer)
|
|
302
304
|
.otherwise(() => undefined);
|
|
303
305
|
|
|
304
|
-
return BaseSubstrateToolbox({ api, signer, gasAsset, network });
|
|
306
|
+
return BaseSubstrateToolbox({ api, signer, gasAsset, network, chain });
|
|
305
307
|
}
|
|
306
308
|
|
|
307
309
|
export type ToolboxParams = {
|
package/src/tron/toolbox.ts
CHANGED
|
@@ -96,7 +96,7 @@ async function createKeysForPath({
|
|
|
96
96
|
}
|
|
97
97
|
|
|
98
98
|
export const createTronToolbox = async (options: TronToolboxOptions = {}) => {
|
|
99
|
-
const { TronWeb } =
|
|
99
|
+
const { TronWeb } = await import("tronweb");
|
|
100
100
|
// Always get configuration from SKConfig
|
|
101
101
|
const rpcUrl = SKConfig.get("rpcUrls")[Chain.Tron];
|
|
102
102
|
// Note: TRON API key support can be added to SKConfig apiKeys when needed
|
|
@@ -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,
|