@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,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(
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { beforeAll, beforeEach, describe, expect, test } from "bun:test";
|
|
2
|
+
import { AssetValue, Chain, SKConfig } from "@swapkit/helpers";
|
|
3
|
+
import { providers } from "near-api-js";
|
|
4
|
+
import { getFullAccessPublicKey } from "../helpers/core";
|
|
5
|
+
import { getNearToolbox } from "../toolbox";
|
|
6
|
+
|
|
7
|
+
const accountId = "ea03292d08136cca439513a33c76af083e5204eceb4ce720320fff84071a447f";
|
|
8
|
+
|
|
9
|
+
const context: {
|
|
10
|
+
provider: providers.JsonRpcProvider;
|
|
11
|
+
toolbox: Awaited<ReturnType<typeof getNearToolbox>>;
|
|
12
|
+
} = {} as any;
|
|
13
|
+
|
|
14
|
+
beforeAll(() => {
|
|
15
|
+
context.provider = new providers.JsonRpcProvider({
|
|
16
|
+
url: SKConfig.get("rpcUrls")[Chain.Near],
|
|
17
|
+
});
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
beforeEach(async () => {
|
|
21
|
+
context.toolbox = await getNearToolbox();
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
describe("NEAR createTransaction", () => {
|
|
25
|
+
test("should retrieve full access public key for valid account", async () => {
|
|
26
|
+
const toolbox = context.toolbox;
|
|
27
|
+
|
|
28
|
+
const transaction = await toolbox.createTransaction({
|
|
29
|
+
recipient: accountId, // Self transfer
|
|
30
|
+
assetValue: AssetValue.from({
|
|
31
|
+
chain: Chain.Near,
|
|
32
|
+
value: "0.001", // Small amount
|
|
33
|
+
}),
|
|
34
|
+
sender: accountId,
|
|
35
|
+
feeRate: 300000000000000, // 300 TGas
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
expect(transaction).toBeDefined();
|
|
39
|
+
expect(transaction.publicKey).toBeDefined();
|
|
40
|
+
expect(transaction.serialized).toBeDefined();
|
|
41
|
+
}, 30000);
|
|
42
|
+
|
|
43
|
+
test("should throw error for invalid account", async () => {
|
|
44
|
+
const provider = context.provider;
|
|
45
|
+
const invalidAccountId = "non-existent-account-12345.testnet";
|
|
46
|
+
|
|
47
|
+
await expect(async () => {
|
|
48
|
+
await getFullAccessPublicKey(provider, invalidAccountId);
|
|
49
|
+
}).toThrow();
|
|
50
|
+
}, 10000);
|
|
51
|
+
|
|
52
|
+
test("should handle network errors gracefully", async () => {
|
|
53
|
+
const invalidProvider = new providers.JsonRpcProvider({ url: "https://invalid-rpc-url.test" });
|
|
54
|
+
|
|
55
|
+
await expect(async () => {
|
|
56
|
+
await getFullAccessPublicKey(invalidProvider, "any-account.testnet");
|
|
57
|
+
}).toThrow();
|
|
58
|
+
}, 10000);
|
|
59
|
+
|
|
60
|
+
test("should work with contract function call transaction", async () => {
|
|
61
|
+
const toolbox = context.toolbox;
|
|
62
|
+
const provider = context.provider;
|
|
63
|
+
|
|
64
|
+
const contractTransaction = await toolbox.createContractFunctionCall({
|
|
65
|
+
sender: accountId,
|
|
66
|
+
contractId: "wrap.testnet", // Known testnet contract
|
|
67
|
+
methodName: "storage_deposit",
|
|
68
|
+
args: { account_id: accountId },
|
|
69
|
+
gas: "300000000000000", // 300 TGas
|
|
70
|
+
attachedDeposit: "1250000000000000000000", // 0.00125 NEAR for storage
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
expect(contractTransaction).toBeDefined();
|
|
74
|
+
expect(contractTransaction.publicKey).toBeDefined();
|
|
75
|
+
|
|
76
|
+
// Verify the public key can be retrieved directly
|
|
77
|
+
const { publicKey } = await getFullAccessPublicKey(provider, accountId);
|
|
78
|
+
expect(publicKey.toString()).toBe(contractTransaction.publicKey);
|
|
79
|
+
}, 30000);
|
|
80
|
+
});
|
package/src/near/helpers/core.ts
CHANGED
|
@@ -82,5 +82,8 @@ export async function getFullAccessPublicKey(provider: Provider, accountId: stri
|
|
|
82
82
|
}
|
|
83
83
|
|
|
84
84
|
const { utils } = await import("near-api-js");
|
|
85
|
-
|
|
85
|
+
const publicKey = utils.PublicKey.fromString(fullAccessKey.public_key);
|
|
86
|
+
const nonce = (fullAccessKey.access_key.nonce as number) || 0;
|
|
87
|
+
|
|
88
|
+
return { publicKey, nonce };
|
|
86
89
|
}
|
package/src/near/toolbox.ts
CHANGED
|
@@ -50,9 +50,6 @@ export async function getNearToolbox(toolboxParams?: NearToolboxParams) {
|
|
|
50
50
|
const provider = new providers.JsonRpcProvider({ url });
|
|
51
51
|
|
|
52
52
|
async function getAccount(address?: string) {
|
|
53
|
-
if (!signer) {
|
|
54
|
-
throw new SwapKitError("toolbox_near_no_signer");
|
|
55
|
-
}
|
|
56
53
|
const { Account } = await import("near-api-js");
|
|
57
54
|
|
|
58
55
|
const _address = address || (await getAddress());
|
|
@@ -118,19 +115,31 @@ export async function getNearToolbox(toolboxParams?: NearToolboxParams) {
|
|
|
118
115
|
}
|
|
119
116
|
|
|
120
117
|
async function createTransaction(params: NearCreateTransactionParams) {
|
|
121
|
-
const { recipient, assetValue, memo, feeRate: gas, attachedDeposit, sender } = params;
|
|
118
|
+
const { recipient, assetValue, memo, feeRate: gas, attachedDeposit, sender: signerId } = params;
|
|
122
119
|
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
account_id: signerId,
|
|
130
|
-
});
|
|
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
|
+
}
|
|
131
126
|
|
|
132
|
-
|
|
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
|
+
}
|
|
133
140
|
|
|
141
|
+
// Native NEAR transfer
|
|
142
|
+
const { publicKey, nonce } = await getFullAccessPublicKey(provider, signerId);
|
|
134
143
|
const baseAmount = assetValue.getBaseValue("bigint");
|
|
135
144
|
|
|
136
145
|
const { SCHEMA } = await import("near-api-js/lib/transaction");
|
|
@@ -163,7 +172,7 @@ export async function getNearToolbox(toolboxParams?: NearToolboxParams) {
|
|
|
163
172
|
serialized: serializedBase64,
|
|
164
173
|
publicKey: publicKey.toString(),
|
|
165
174
|
details: {
|
|
166
|
-
signerId
|
|
175
|
+
signerId,
|
|
167
176
|
nonce: nonce,
|
|
168
177
|
blockHash: utils.serialize.base_encode(blockHash),
|
|
169
178
|
},
|
|
@@ -171,24 +180,16 @@ export async function getNearToolbox(toolboxParams?: NearToolboxParams) {
|
|
|
171
180
|
}
|
|
172
181
|
|
|
173
182
|
async function createContractFunctionCall(params: {
|
|
174
|
-
|
|
183
|
+
sender: string;
|
|
175
184
|
contractId: string;
|
|
176
185
|
methodName: string;
|
|
177
186
|
args: any;
|
|
178
187
|
gas: string;
|
|
179
188
|
attachedDeposit: string;
|
|
180
189
|
}) {
|
|
181
|
-
const { accountId } = params;
|
|
190
|
+
const { sender: accountId } = params;
|
|
182
191
|
|
|
183
|
-
const publicKey = await getFullAccessPublicKey(provider, accountId);
|
|
184
|
-
|
|
185
|
-
const accessKey = await provider.query({
|
|
186
|
-
request_type: "view_access_key",
|
|
187
|
-
finality: "final",
|
|
188
|
-
account_id: accountId,
|
|
189
|
-
public_key: publicKey.toString(),
|
|
190
|
-
});
|
|
191
|
-
const nonce = (accessKey as any).nonce + 1;
|
|
192
|
+
const { publicKey, nonce } = await getFullAccessPublicKey(provider, accountId);
|
|
192
193
|
|
|
193
194
|
const { SCHEMA } = await import("near-api-js/lib/transaction");
|
|
194
195
|
const { transactions, utils } = await import("near-api-js");
|
|
@@ -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 = {
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import { beforeAll, beforeEach, describe, expect, test } from "bun:test";
|
|
2
|
+
import { AssetValue, Chain, SKConfig } from "@swapkit/helpers";
|
|
3
|
+
import { createTronToolbox, getTronAddressValidator } from "../toolbox";
|
|
4
|
+
|
|
5
|
+
// Test mnemonic for consistent testing
|
|
6
|
+
const TEST_PHRASE =
|
|
7
|
+
"abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about";
|
|
8
|
+
|
|
9
|
+
const context: {
|
|
10
|
+
toolbox: Awaited<ReturnType<typeof createTronToolbox>>;
|
|
11
|
+
validateAddress: (address: string) => boolean;
|
|
12
|
+
} = {} as any;
|
|
13
|
+
|
|
14
|
+
beforeAll(async () => {
|
|
15
|
+
// Set up TRON mainnet configuration
|
|
16
|
+
SKConfig.set({
|
|
17
|
+
rpcUrls: {
|
|
18
|
+
[Chain.Tron]: "https://api.trongrid.io",
|
|
19
|
+
},
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
// Get the address validator
|
|
23
|
+
context.validateAddress = await getTronAddressValidator();
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
beforeEach(async () => {
|
|
27
|
+
context.toolbox = await createTronToolbox({ phrase: TEST_PHRASE });
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
describe("TRON Address Validation", () => {
|
|
31
|
+
test("should validate valid TRON addresses", () => {
|
|
32
|
+
const validAddresses = [
|
|
33
|
+
"TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t", // USDT contract
|
|
34
|
+
"TLa2f6VPqDgRE67v1736s7bJ8Ray5wYjU7", // Mainnet address
|
|
35
|
+
"TQn9Y2khEsLJW1ChVWFMSMeRDow5KcbLSE", // Another valid address
|
|
36
|
+
];
|
|
37
|
+
|
|
38
|
+
for (const address of validAddresses) {
|
|
39
|
+
expect(context.validateAddress(address)).toBe(true);
|
|
40
|
+
expect(context.toolbox.validateAddress(address)).toBe(true);
|
|
41
|
+
}
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
test("should reject invalid TRON addresses", () => {
|
|
45
|
+
const invalidAddresses = [
|
|
46
|
+
"", // Empty string
|
|
47
|
+
"invalid", // Random string
|
|
48
|
+
"0x742d35Cc6648C532F5e7c3d2a7a8E1e1e5b7c8D3", // Ethereum address
|
|
49
|
+
"1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa", // Bitcoin address
|
|
50
|
+
"cosmos1abc123", // Cosmos address
|
|
51
|
+
"TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6", // Too short
|
|
52
|
+
"TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6tt", // Too long
|
|
53
|
+
"XR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t", // Wrong prefix
|
|
54
|
+
"TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6O", // Invalid checksum
|
|
55
|
+
];
|
|
56
|
+
|
|
57
|
+
for (const address of invalidAddresses) {
|
|
58
|
+
expect(context.validateAddress(address)).toBe(false);
|
|
59
|
+
expect(context.toolbox.validateAddress(address)).toBe(false);
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
test("should validate address from generated account", async () => {
|
|
64
|
+
const toolbox = context.toolbox;
|
|
65
|
+
|
|
66
|
+
// Get address from the toolbox
|
|
67
|
+
const address = await toolbox.getAddress();
|
|
68
|
+
|
|
69
|
+
expect(address).toBeDefined();
|
|
70
|
+
expect(typeof address).toBe("string");
|
|
71
|
+
expect(address.length).toBeGreaterThan(0);
|
|
72
|
+
|
|
73
|
+
// The generated address should be valid
|
|
74
|
+
expect(context.validateAddress(address)).toBe(true);
|
|
75
|
+
expect(toolbox.validateAddress(address)).toBe(true);
|
|
76
|
+
|
|
77
|
+
// Address should start with 'T'
|
|
78
|
+
expect(address.startsWith("T")).toBe(true);
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
test("should create transaction with valid addresses", async () => {
|
|
82
|
+
const toolbox = context.toolbox;
|
|
83
|
+
const fromAddress = await toolbox.getAddress();
|
|
84
|
+
const toAddress = "TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t"; // Valid TRON address
|
|
85
|
+
|
|
86
|
+
// Both addresses should be valid
|
|
87
|
+
expect(toolbox.validateAddress(fromAddress)).toBe(true);
|
|
88
|
+
expect(toolbox.validateAddress(toAddress)).toBe(true);
|
|
89
|
+
|
|
90
|
+
// Create a transaction
|
|
91
|
+
const transaction = await toolbox.createTransaction({
|
|
92
|
+
recipient: toAddress,
|
|
93
|
+
assetValue: AssetValue.from({
|
|
94
|
+
chain: Chain.Tron,
|
|
95
|
+
value: "1", // 1 TRX
|
|
96
|
+
}),
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
expect(transaction).toBeDefined();
|
|
100
|
+
expect(transaction.raw_data_hex).toBeDefined();
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
test("should handle case sensitivity in addresses", () => {
|
|
104
|
+
const address = "TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t";
|
|
105
|
+
const lowerCase = address.toLowerCase();
|
|
106
|
+
const upperCase = address.toUpperCase();
|
|
107
|
+
|
|
108
|
+
// TRON addresses are case sensitive - only the original should be valid
|
|
109
|
+
expect(context.validateAddress(address)).toBe(true);
|
|
110
|
+
expect(context.validateAddress(lowerCase)).toBe(false);
|
|
111
|
+
expect(context.validateAddress(upperCase)).toBe(false);
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
test("should handle edge cases", () => {
|
|
115
|
+
const edgeCases = [null, undefined, 123, {}, [], true, false];
|
|
116
|
+
|
|
117
|
+
for (const testCase of edgeCases) {
|
|
118
|
+
// Should not throw but return false for invalid inputs
|
|
119
|
+
expect(context.validateAddress(testCase as any)).toBe(false);
|
|
120
|
+
expect(context.toolbox.validateAddress(testCase as any)).toBe(false);
|
|
121
|
+
}
|
|
122
|
+
});
|
|
123
|
+
});
|
package/src/tron/toolbox.ts
CHANGED
|
@@ -8,7 +8,6 @@ import {
|
|
|
8
8
|
updateDerivationPath,
|
|
9
9
|
warnOnce,
|
|
10
10
|
} from "@swapkit/helpers";
|
|
11
|
-
import { TronWeb } from "tronweb";
|
|
12
11
|
import { P, match } from "ts-pattern";
|
|
13
12
|
|
|
14
13
|
import { trc20ABI } from "./helpers/trc20.abi.js";
|
|
@@ -21,8 +20,11 @@ import type {
|
|
|
21
20
|
} from "./types.js";
|
|
22
21
|
|
|
23
22
|
export async function getTronAddressValidator() {
|
|
24
|
-
const { TronWeb } =
|
|
25
|
-
|
|
23
|
+
const { TronWeb } = require("tronweb");
|
|
24
|
+
|
|
25
|
+
return (address: string) => {
|
|
26
|
+
return TronWeb.isAddress(address);
|
|
27
|
+
};
|
|
26
28
|
}
|
|
27
29
|
|
|
28
30
|
export async function getTronPrivateKeyFromMnemonic({
|
|
@@ -63,6 +65,7 @@ async function createKeysForPath({
|
|
|
63
65
|
}) {
|
|
64
66
|
const { HDKey } = await import("@scure/bip32");
|
|
65
67
|
const { mnemonicToSeedSync } = await import("@scure/bip39");
|
|
68
|
+
const { TronWeb } = require("tronweb");
|
|
66
69
|
|
|
67
70
|
const seed = mnemonicToSeedSync(phrase);
|
|
68
71
|
const hdKey = HDKey.fromMasterSeed(seed);
|
|
@@ -93,6 +96,7 @@ async function createKeysForPath({
|
|
|
93
96
|
}
|
|
94
97
|
|
|
95
98
|
export const createTronToolbox = async (options: TronToolboxOptions = {}) => {
|
|
99
|
+
const { TronWeb } = await import("tronweb");
|
|
96
100
|
// Always get configuration from SKConfig
|
|
97
101
|
const rpcUrl = SKConfig.get("rpcUrls")[Chain.Tron];
|
|
98
102
|
// Note: TRON API key support can be added to SKConfig apiKeys when needed
|
|
@@ -122,10 +126,6 @@ export const createTronToolbox = async (options: TronToolboxOptions = {}) => {
|
|
|
122
126
|
return await signer.getAddress();
|
|
123
127
|
};
|
|
124
128
|
|
|
125
|
-
const validateAddress = (address: string) => {
|
|
126
|
-
return tronWeb.isAddress(address);
|
|
127
|
-
};
|
|
128
|
-
|
|
129
129
|
const calculateFeeLimit = () => {
|
|
130
130
|
return 100_000_000; // 100 TRX in SUN
|
|
131
131
|
};
|
|
@@ -267,7 +267,7 @@ export const createTronToolbox = async (options: TronToolboxOptions = {}) => {
|
|
|
267
267
|
return tronWeb.transactionBuilder.addUpdateData(transaction, memo, "utf8");
|
|
268
268
|
}
|
|
269
269
|
|
|
270
|
-
return transaction
|
|
270
|
+
return transaction;
|
|
271
271
|
}
|
|
272
272
|
|
|
273
273
|
// For TRC20, we would need to build the transaction manually
|
|
@@ -294,7 +294,7 @@ export const createTronToolbox = async (options: TronToolboxOptions = {}) => {
|
|
|
294
294
|
from,
|
|
295
295
|
);
|
|
296
296
|
|
|
297
|
-
return result.transaction
|
|
297
|
+
return result.transaction;
|
|
298
298
|
};
|
|
299
299
|
|
|
300
300
|
const signTransaction = async (transaction: TronTransaction) => {
|
|
@@ -310,7 +310,7 @@ export const createTronToolbox = async (options: TronToolboxOptions = {}) => {
|
|
|
310
310
|
return {
|
|
311
311
|
tronWeb,
|
|
312
312
|
getAddress,
|
|
313
|
-
validateAddress,
|
|
313
|
+
validateAddress: await getTronAddressValidator(),
|
|
314
314
|
getBalance,
|
|
315
315
|
transfer,
|
|
316
316
|
estimateTransactionFee,
|