@swapkit/helpers 4.4.5 → 4.5.3
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/api/index.cjs +2 -2
- package/dist/api/index.cjs.map +4 -4
- package/dist/api/index.js +2 -2
- package/dist/api/index.js.map +4 -4
- package/dist/index.cjs +3 -3
- package/dist/index.cjs.map +10 -10
- package/dist/index.js +3 -3
- package/dist/index.js.map +10 -10
- package/dist/types/api/index.d.ts +247 -0
- package/dist/types/api/index.d.ts.map +1 -1
- package/dist/types/api/swapkitApi/endpoints.d.ts +247 -0
- package/dist/types/api/swapkitApi/endpoints.d.ts.map +1 -1
- package/dist/types/api/swapkitApi/types.d.ts +3 -1
- package/dist/types/api/swapkitApi/types.d.ts.map +1 -1
- package/dist/types/modules/assetValue.d.ts +6 -0
- package/dist/types/modules/assetValue.d.ts.map +1 -1
- package/dist/types/modules/bigIntArithmetics.d.ts.map +1 -1
- package/dist/types/modules/swapKitConfig.d.ts +63 -36
- package/dist/types/modules/swapKitConfig.d.ts.map +1 -1
- package/dist/types/modules/swapKitError.d.ts +20 -24
- package/dist/types/modules/swapKitError.d.ts.map +1 -1
- package/dist/types/types/wallet.d.ts +5 -1
- package/dist/types/types/wallet.d.ts.map +1 -1
- package/dist/types/utils/asset.d.ts +1 -1
- package/dist/types/utils/asset.d.ts.map +1 -1
- package/package.json +24 -12
- package/src/api/index.ts +9 -0
- package/src/api/midgard/endpoints.ts +348 -0
- package/src/api/midgard/types.ts +515 -0
- package/src/api/swapkitApi/endpoints.ts +242 -0
- package/src/api/swapkitApi/types.ts +624 -0
- package/src/api/thornode/endpoints.ts +105 -0
- package/src/api/thornode/types.ts +247 -0
- package/src/contracts.ts +1 -0
- package/src/index.ts +28 -0
- package/src/modules/__tests__/assetValue.test.ts +1637 -0
- package/src/modules/__tests__/bigIntArithmetics.test.ts +383 -0
- package/src/modules/__tests__/swapKitConfig.test.ts +425 -0
- package/src/modules/__tests__/swapKitNumber.test.ts +535 -0
- package/src/modules/assetValue.ts +532 -0
- package/src/modules/bigIntArithmetics.ts +363 -0
- package/src/modules/feeMultiplier.ts +80 -0
- package/src/modules/requestClient.ts +110 -0
- package/src/modules/swapKitConfig.ts +174 -0
- package/src/modules/swapKitError.ts +470 -0
- package/src/modules/swapKitNumber.ts +13 -0
- package/src/tokens.ts +1 -0
- package/src/types/commonTypes.ts +10 -0
- package/src/types/derivationPath.ts +11 -0
- package/src/types/errors/apiV1.ts +0 -0
- package/src/types/index.ts +5 -0
- package/src/types/quotes.ts +174 -0
- package/src/types/sdk.ts +38 -0
- package/src/types/wallet.ts +124 -0
- package/src/utils/__tests__/asset.test.ts +185 -0
- package/src/utils/__tests__/derivationPath.test.ts +16 -0
- package/src/utils/__tests__/explorerUrls.test.ts +59 -0
- package/src/utils/__tests__/memo.test.ts +99 -0
- package/src/utils/__tests__/others.test.ts +83 -0
- package/src/utils/__tests__/validators.test.ts +24 -0
- package/src/utils/asset.ts +395 -0
- package/src/utils/chains.ts +100 -0
- package/src/utils/derivationPath.ts +101 -0
- package/src/utils/explorerUrls.ts +32 -0
- package/src/utils/liquidity.ts +150 -0
- package/src/utils/memo.ts +102 -0
- package/src/utils/others.ts +62 -0
- package/src/utils/validators.ts +32 -0
- package/src/utils/wallets.ts +237 -0
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test";
|
|
2
|
+
import { Chain } from "@swapkit/types";
|
|
3
|
+
import { MemoType } from "../../types";
|
|
4
|
+
|
|
5
|
+
import {
|
|
6
|
+
getMemoForDeposit,
|
|
7
|
+
getMemoForLeaveAndBond,
|
|
8
|
+
getMemoForNamePreferredAssetRegister,
|
|
9
|
+
getMemoForNameRegister,
|
|
10
|
+
getMemoForRunePoolDeposit,
|
|
11
|
+
getMemoForRunePoolWithdraw,
|
|
12
|
+
getMemoForWithdraw,
|
|
13
|
+
} from "../memo";
|
|
14
|
+
|
|
15
|
+
describe("getMemoForLeaveAndBond", () => {
|
|
16
|
+
test("returns correct memo for Leave", () => {
|
|
17
|
+
const result = getMemoForLeaveAndBond({ address: "ABC123", type: MemoType.LEAVE });
|
|
18
|
+
expect(result).toBe("LEAVE:ABC123");
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
test("returns correct memo for Bond", () => {
|
|
22
|
+
const result = getMemoForLeaveAndBond({ address: "ABC123", type: MemoType.BOND });
|
|
23
|
+
expect(result).toBe("BOND:ABC123");
|
|
24
|
+
});
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
describe("getMemoForNameRegister", () => {
|
|
28
|
+
test("returns correct memo for single side", () => {
|
|
29
|
+
const result = getMemoForNameRegister({
|
|
30
|
+
address: "0xaasd123",
|
|
31
|
+
chain: Chain.Ethereum,
|
|
32
|
+
name: "asdfg",
|
|
33
|
+
owner: "thor1234",
|
|
34
|
+
});
|
|
35
|
+
expect(result).toBe("~:asdfg:ETH:0xaasd123:thor1234");
|
|
36
|
+
});
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
describe("getMemoForNamePreferredAssetRegister", () => {
|
|
40
|
+
test("returns correct memo for single side", () => {
|
|
41
|
+
const result = getMemoForNamePreferredAssetRegister({
|
|
42
|
+
asset: "ETH.USDC-0XA0B86991C6218B36C1D19D4A2E9EB0CE3606EB48",
|
|
43
|
+
chain: Chain.Ethereum,
|
|
44
|
+
name: "asdfg",
|
|
45
|
+
owner: "thor1234",
|
|
46
|
+
payout: "0x6621d872f17109d6601c49edba526ebcfd332d5d",
|
|
47
|
+
});
|
|
48
|
+
expect(result).toBe(
|
|
49
|
+
"~:asdfg:ETH:0x6621d872f17109d6601c49edba526ebcfd332d5d:thor1234:ETH.USDC-0XA0B86991C6218B36C1D19D4A2E9EB0CE3606EB48",
|
|
50
|
+
);
|
|
51
|
+
});
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
describe("getMemoForDeposit", () => {
|
|
55
|
+
test("returns correct memo for single side", () => {
|
|
56
|
+
const result = getMemoForDeposit({ chain: Chain.Ethereum, symbol: "ETH" });
|
|
57
|
+
expect(result).toBe("+:ETH.ETH");
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
test("returns correct memo when paired address is not available but affiliate info is present", () => {
|
|
61
|
+
const result = getMemoForDeposit({
|
|
62
|
+
affiliateAddress: "thor1abc123",
|
|
63
|
+
affiliateBasisPoints: 500,
|
|
64
|
+
chain: Chain.Ethereum,
|
|
65
|
+
symbol: "ETH",
|
|
66
|
+
});
|
|
67
|
+
expect(result).toBe("+:ETH.ETH::thor1abc123:500");
|
|
68
|
+
});
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
describe("getMemoForWithdraw", () => {
|
|
72
|
+
test("returns correct memo for single side", () => {
|
|
73
|
+
const result = getMemoForWithdraw({ basisPoints: 100, chain: Chain.Ethereum, symbol: "ETH", ticker: "ETH" });
|
|
74
|
+
expect(result).toBe("-:ETH.ETH:100");
|
|
75
|
+
});
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
describe("getMemoForRunePoolDeposit", () => {
|
|
79
|
+
test("returns correct memo for single side", () => {
|
|
80
|
+
const result = getMemoForRunePoolDeposit();
|
|
81
|
+
expect(result).toBe("POOL+");
|
|
82
|
+
});
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
describe("getMemoForRunePoolWithdraw", () => {
|
|
86
|
+
test("returns correct memo for single side", () => {
|
|
87
|
+
const result = getMemoForRunePoolWithdraw({ basisPoints: 500 });
|
|
88
|
+
expect(result).toBe("POOL-:500");
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
test("returns correct memo when affiliate info is present", () => {
|
|
92
|
+
const result = getMemoForRunePoolWithdraw({
|
|
93
|
+
affiliateAddress: "thor1abc123",
|
|
94
|
+
affiliateBasisPoints: 500,
|
|
95
|
+
basisPoints: 500,
|
|
96
|
+
});
|
|
97
|
+
expect(result).toBe("POOL-:500:thor1abc123:500");
|
|
98
|
+
});
|
|
99
|
+
});
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test";
|
|
2
|
+
import { Chain } from "@swapkit/types";
|
|
3
|
+
|
|
4
|
+
import { findAssetBy } from "../asset";
|
|
5
|
+
import { getTHORNameCost } from "../others";
|
|
6
|
+
|
|
7
|
+
describe("getTHORNameCost", () => {
|
|
8
|
+
describe("for correct values", () => {
|
|
9
|
+
const costCases = [
|
|
10
|
+
[1, 11],
|
|
11
|
+
[2, 12],
|
|
12
|
+
[3, 13],
|
|
13
|
+
[10, 20],
|
|
14
|
+
];
|
|
15
|
+
|
|
16
|
+
for (const [years = 0, expected = 10] of costCases) {
|
|
17
|
+
test(`returns correct ${expected} cost for ${years} years`, () => {
|
|
18
|
+
const result = getTHORNameCost(years);
|
|
19
|
+
expect(result).toBe(expected);
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
test("throws an error for negative years", () => {
|
|
25
|
+
expect(() => getTHORNameCost(-1)).toThrow("helpers_invalid_number_of_years");
|
|
26
|
+
});
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
describe("getAssetBy", () => {
|
|
30
|
+
test("find asset by identifier", async () => {
|
|
31
|
+
const assetByIdentifier = await findAssetBy({ identifier: "ETH.ETH" });
|
|
32
|
+
expect(assetByIdentifier).toBe("ETH.ETH");
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
test("find asset by chain and contract", async () => {
|
|
36
|
+
const assetByChainAndContract = await findAssetBy({
|
|
37
|
+
chain: Chain.Ethereum,
|
|
38
|
+
contract: "0XA0B86991C6218B36C1D19D4A2E9EB0CE3606EB48",
|
|
39
|
+
});
|
|
40
|
+
expect(assetByChainAndContract?.toUpperCase()).toBe("ETH.USDC-0XA0B86991C6218B36C1D19D4A2E9EB0CE3606EB48");
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
test("return undefined if asset can't be found", async () => {
|
|
44
|
+
const assetByIdentifier = await findAssetBy({ identifier: "ARB.NOTEXISTINGTOKEN" });
|
|
45
|
+
const assetByChainAndContract = await findAssetBy({ chain: Chain.Ethereum, contract: "NOTFOUND" });
|
|
46
|
+
expect(assetByIdentifier).toBeUndefined();
|
|
47
|
+
expect(assetByChainAndContract).toBeUndefined();
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
describe(Chain.Radix, () => {
|
|
51
|
+
test("find asset by identifier", async () => {
|
|
52
|
+
const assetByChainAndContract = await findAssetBy({ identifier: "XRD.XRD" });
|
|
53
|
+
expect(assetByChainAndContract?.toUpperCase()).toBe("XRD.XRD".toUpperCase());
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
test("find asset by chain and contract", async () => {
|
|
57
|
+
const assetByChainAndContract = await findAssetBy({
|
|
58
|
+
chain: Chain.Radix,
|
|
59
|
+
contract: "resource_rdx1t580qxc7upat7lww4l2c4jckacafjeudxj5wpjrrct0p3e82sq4y75",
|
|
60
|
+
});
|
|
61
|
+
expect(assetByChainAndContract?.toUpperCase()).toBe(
|
|
62
|
+
"XRD.XWBTC-resource_rdx1t580qxc7upat7lww4l2c4jckacafjeudxj5wpjrrct0p3e82sq4y75".toUpperCase(),
|
|
63
|
+
);
|
|
64
|
+
});
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
describe(Chain.Solana, () => {
|
|
68
|
+
test("find asset by identifier", async () => {
|
|
69
|
+
const assetByChainAndContract = await findAssetBy({ identifier: "SOL.SOL" });
|
|
70
|
+
expect(assetByChainAndContract?.toUpperCase()).toBe("SOL.SOL".toUpperCase());
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
test("find asset by chain and contract", async () => {
|
|
74
|
+
const assetByChainAndContract = await findAssetBy({
|
|
75
|
+
chain: Chain.Solana,
|
|
76
|
+
contract: "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v",
|
|
77
|
+
});
|
|
78
|
+
expect(assetByChainAndContract?.toUpperCase()).toBe(
|
|
79
|
+
"SOL.USDC-EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v".toUpperCase(),
|
|
80
|
+
);
|
|
81
|
+
});
|
|
82
|
+
});
|
|
83
|
+
});
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test";
|
|
2
|
+
import { validateTNS } from "../validators";
|
|
3
|
+
|
|
4
|
+
describe("validateTNS", () => {
|
|
5
|
+
const casesWithExpectation: [string, boolean][] = [
|
|
6
|
+
["validname", true],
|
|
7
|
+
["valid-name", true],
|
|
8
|
+
["valid_name", true],
|
|
9
|
+
["valid+name", true],
|
|
10
|
+
["name_with_numbers123", true],
|
|
11
|
+
["UPPER_CASE", true],
|
|
12
|
+
["toolongname123456789012345678901", false],
|
|
13
|
+
["invalid@name", false],
|
|
14
|
+
["invalid!name", false],
|
|
15
|
+
["invalid#name", false],
|
|
16
|
+
];
|
|
17
|
+
|
|
18
|
+
for (const [name, expected] of casesWithExpectation) {
|
|
19
|
+
test(`returns ${expected} for THORName "${name}"`, () => {
|
|
20
|
+
const result = validateTNS(name);
|
|
21
|
+
expect(result).toBe(expected);
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
});
|
|
@@ -0,0 +1,395 @@
|
|
|
1
|
+
import type { TokenNames } from "@swapkit/tokens";
|
|
2
|
+
import { Chain, type EVMChain, EVMChains, getChainConfig, UTXOChains } from "@swapkit/types";
|
|
3
|
+
import { match } from "ts-pattern";
|
|
4
|
+
import type { AssetValue } from "../modules/assetValue";
|
|
5
|
+
import { RequestClient } from "../modules/requestClient";
|
|
6
|
+
import { getRPCUrl } from "./chains";
|
|
7
|
+
|
|
8
|
+
export type CommonAssetString = (typeof CommonAssetStrings)[number] | Chain;
|
|
9
|
+
|
|
10
|
+
export type ConditionalAssetValueReturn<T extends boolean> = T extends true ? Promise<AssetValue[]> : AssetValue[];
|
|
11
|
+
|
|
12
|
+
export const CommonAssetStrings = [
|
|
13
|
+
`${Chain.Maya}.MAYA`,
|
|
14
|
+
`${Chain.Maya}.CACAO`,
|
|
15
|
+
`${Chain.Ethereum}.THOR`,
|
|
16
|
+
`${Chain.Ethereum}.vTHOR`,
|
|
17
|
+
`${Chain.Kujira}.USK`,
|
|
18
|
+
`${Chain.Ethereum}.FLIP`,
|
|
19
|
+
`${Chain.Radix}.XRD`,
|
|
20
|
+
] as const;
|
|
21
|
+
|
|
22
|
+
type RadixResourceResponse = {
|
|
23
|
+
at_ledger_state?: any;
|
|
24
|
+
manager: {
|
|
25
|
+
resource_type: string;
|
|
26
|
+
divisibility: { substate_type: string; is_locked: boolean; value: { divisibility: number } };
|
|
27
|
+
};
|
|
28
|
+
owner_role?: any;
|
|
29
|
+
};
|
|
30
|
+
const ethGasChains = [Chain.Arbitrum, Chain.Aurora, Chain.Base, Chain.Ethereum, Chain.Optimism] as const;
|
|
31
|
+
|
|
32
|
+
async function getRadixAssetDecimals(address: string) {
|
|
33
|
+
const { baseDecimal } = getChainConfig(Chain.Radix);
|
|
34
|
+
|
|
35
|
+
try {
|
|
36
|
+
const rpcUrl = await getRPCUrl(Chain.Radix);
|
|
37
|
+
|
|
38
|
+
const { manager } = await RequestClient.post<RadixResourceResponse>(`${rpcUrl}/state/resource`, {
|
|
39
|
+
body: JSON.stringify({ network: "mainnet", resource_address: address }),
|
|
40
|
+
headers: { Accept: "*/*", "Content-Type": "application/json" },
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
return manager?.divisibility?.value?.divisibility;
|
|
44
|
+
} catch (error) {
|
|
45
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
46
|
+
console.warn(`Failed to fetch Radix asset decimals for ${address}: ${errorMessage}`);
|
|
47
|
+
return baseDecimal;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
async function getRadixAssetTicker(address: string) {
|
|
52
|
+
try {
|
|
53
|
+
const rpcUrl = await getRPCUrl(Chain.Radix);
|
|
54
|
+
|
|
55
|
+
const response = await RequestClient.post<{
|
|
56
|
+
items: Array<{
|
|
57
|
+
address: string;
|
|
58
|
+
explicit_metadata?: { items: Array<{ key: string; value: { typed: { value: string; type: string } } }> };
|
|
59
|
+
}>;
|
|
60
|
+
}>(`${rpcUrl}/state/entity/details`, {
|
|
61
|
+
body: JSON.stringify({ addresses: [address], opt_ins: { explicit_metadata: ["symbol"] } }),
|
|
62
|
+
headers: { Accept: "*/*", "Content-Type": "application/json" },
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
const symbolMetadata = response.items[0]?.explicit_metadata?.items.find((item) => item.key === "symbol");
|
|
66
|
+
return symbolMetadata?.value.typed.value || undefined;
|
|
67
|
+
} catch (error) {
|
|
68
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
69
|
+
console.warn(`Failed to fetch Radix asset symbol for ${address}: ${errorMessage}`);
|
|
70
|
+
return "";
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
async function callEVMContract({
|
|
75
|
+
chain,
|
|
76
|
+
address,
|
|
77
|
+
methodHex,
|
|
78
|
+
id,
|
|
79
|
+
}: {
|
|
80
|
+
chain: EVMChain;
|
|
81
|
+
address: string;
|
|
82
|
+
methodHex: string;
|
|
83
|
+
id: number;
|
|
84
|
+
}) {
|
|
85
|
+
const rpcUrl = await getRPCUrl(chain);
|
|
86
|
+
return RequestClient.post<{ result: string }>(rpcUrl, {
|
|
87
|
+
body: JSON.stringify({
|
|
88
|
+
id,
|
|
89
|
+
jsonrpc: "2.0",
|
|
90
|
+
method: "eth_call",
|
|
91
|
+
params: [{ data: methodHex, to: address.toLowerCase() }, "latest"],
|
|
92
|
+
}),
|
|
93
|
+
headers: { accept: "*/*", "cache-control": "no-cache", "content-type": "application/json" },
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
async function decodeABIString(hexResult: string) {
|
|
98
|
+
if (!hexResult || hexResult === "0x") return "UNKNOWN";
|
|
99
|
+
|
|
100
|
+
try {
|
|
101
|
+
const { AbiCoder } = await import("ethers");
|
|
102
|
+
const abiCoder = AbiCoder.defaultAbiCoder();
|
|
103
|
+
const decoded = abiCoder.decode(["string"], hexResult);
|
|
104
|
+
return decoded[0].trim();
|
|
105
|
+
} catch (error) {
|
|
106
|
+
console.warn(`Failed to decode ABI string from ${hexResult}: ${error}`);
|
|
107
|
+
return "UNKNOWN";
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function decodeABIUint8(hexResult: string, fallback: number) {
|
|
112
|
+
if (!hexResult || hexResult === "0x") return fallback;
|
|
113
|
+
|
|
114
|
+
try {
|
|
115
|
+
return Number(hexResult);
|
|
116
|
+
} catch (error) {
|
|
117
|
+
console.warn(`Failed to decode ABI uint8 from ${hexResult}: ${error}`);
|
|
118
|
+
return fallback;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
async function getEVMAssetDecimals({ chain, address }: { chain: EVMChain; address: string }) {
|
|
123
|
+
const { baseDecimal } = getChainConfig(chain);
|
|
124
|
+
|
|
125
|
+
const formattedAddress = address.toLowerCase();
|
|
126
|
+
|
|
127
|
+
if (address === "" || !formattedAddress.startsWith("0x")) return baseDecimal;
|
|
128
|
+
|
|
129
|
+
const decimalResponse = await callEVMContract({ address, chain, id: 2, methodHex: "0x313ce567" }).catch(
|
|
130
|
+
(error: Error) => {
|
|
131
|
+
console.warn(`Could not fetch decimals for ${address} on ${chain}: ${error.message}`);
|
|
132
|
+
return { result: "" };
|
|
133
|
+
},
|
|
134
|
+
);
|
|
135
|
+
|
|
136
|
+
const decimals = decodeABIUint8(decimalResponse.result, baseDecimal);
|
|
137
|
+
return decimals;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
async function getEVMAssetTicker({ chain, address }: { chain: EVMChain; address: string }) {
|
|
141
|
+
const formattedAddress = address.toLowerCase();
|
|
142
|
+
|
|
143
|
+
if (formattedAddress === "" || !formattedAddress.startsWith("0x")) return undefined;
|
|
144
|
+
|
|
145
|
+
const tickerResponse = await callEVMContract({ address, chain, id: 1, methodHex: "0x95d89b41" }).catch(
|
|
146
|
+
(error: Error) => {
|
|
147
|
+
console.warn(`Could not fetch symbol for ${address} on ${chain}: ${error.message}`);
|
|
148
|
+
return { result: "" };
|
|
149
|
+
},
|
|
150
|
+
);
|
|
151
|
+
|
|
152
|
+
const ticker = await decodeABIString(tickerResponse.result);
|
|
153
|
+
|
|
154
|
+
return ticker;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
export function fetchTokenInfo({ chain, address }: { chain: Chain; address: string }) {
|
|
158
|
+
const { baseDecimal } = getChainConfig(chain);
|
|
159
|
+
const defaultResult = { decimals: baseDecimal, ticker: undefined };
|
|
160
|
+
|
|
161
|
+
return match(chain)
|
|
162
|
+
.with(...EVMChains, async () => {
|
|
163
|
+
try {
|
|
164
|
+
const { isAddress, getAddress } = await import("ethers");
|
|
165
|
+
|
|
166
|
+
if (!isAddress(getAddress(address.replace(/^0X/, "0x")))) return defaultResult;
|
|
167
|
+
|
|
168
|
+
const [ticker, decimals] = await Promise.all([
|
|
169
|
+
getEVMAssetTicker({ address, chain: chain as EVMChain }),
|
|
170
|
+
getEVMAssetDecimals({ address, chain: chain as EVMChain }),
|
|
171
|
+
]);
|
|
172
|
+
|
|
173
|
+
return { decimals, ticker };
|
|
174
|
+
} catch (error: any) {
|
|
175
|
+
console.warn(`Failed to fetch token info for ${address} on ${chain}: ${error?.code} ${error?.message}`);
|
|
176
|
+
return defaultResult;
|
|
177
|
+
}
|
|
178
|
+
})
|
|
179
|
+
.with(Chain.Solana, async () => {
|
|
180
|
+
if (!address) return defaultResult;
|
|
181
|
+
|
|
182
|
+
try {
|
|
183
|
+
const response = await fetch(`https://lite-api.jup.ag/tokens/v2/search?query=${address}`);
|
|
184
|
+
if (response.ok) {
|
|
185
|
+
const data = await response.json();
|
|
186
|
+
const token = Array.isArray(data) ? data[0] : data;
|
|
187
|
+
if (token) {
|
|
188
|
+
return { decimals: token.decimals ?? baseDecimal, ticker: token.symbol || undefined };
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
} catch (error: any) {
|
|
192
|
+
console.warn(`Failed to fetch Solana token info for ${address}: ${error?.code} ${error?.message}`);
|
|
193
|
+
}
|
|
194
|
+
return defaultResult;
|
|
195
|
+
})
|
|
196
|
+
.with(Chain.Tron, async () => {
|
|
197
|
+
if (!address) return defaultResult;
|
|
198
|
+
|
|
199
|
+
try {
|
|
200
|
+
const { TronWeb } = await import("tronweb");
|
|
201
|
+
const rpcUrl = await getRPCUrl(Chain.Tron);
|
|
202
|
+
const tronWeb = new TronWeb({
|
|
203
|
+
fullHost: rpcUrl,
|
|
204
|
+
// Set a default address for read-only calls (required by TronWeb)
|
|
205
|
+
privateKey: "0000000000000000000000000000000000000000000000000000000000000001",
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
const contract = await tronWeb.contract().at(address);
|
|
209
|
+
|
|
210
|
+
const [symbolResult, decimalsResult] = await Promise.all([
|
|
211
|
+
contract
|
|
212
|
+
.symbol()
|
|
213
|
+
.call()
|
|
214
|
+
.catch((error: Error) => {
|
|
215
|
+
console.warn(`Could not fetch symbol for ${address} on Tron:`, error);
|
|
216
|
+
return undefined;
|
|
217
|
+
}),
|
|
218
|
+
contract
|
|
219
|
+
.decimals()
|
|
220
|
+
.call()
|
|
221
|
+
.catch((error: Error) => {
|
|
222
|
+
console.warn(`Could not fetch decimals for ${address} on Tron:`, error);
|
|
223
|
+
return baseDecimal;
|
|
224
|
+
}),
|
|
225
|
+
]);
|
|
226
|
+
|
|
227
|
+
return {
|
|
228
|
+
decimals: typeof decimalsResult === "number" ? decimalsResult : Number(decimalsResult || baseDecimal),
|
|
229
|
+
ticker: symbolResult || undefined,
|
|
230
|
+
};
|
|
231
|
+
} catch (error) {
|
|
232
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
233
|
+
console.warn(`Failed to fetch Tron token info for ${address}: ${errorMessage}`);
|
|
234
|
+
return defaultResult;
|
|
235
|
+
}
|
|
236
|
+
})
|
|
237
|
+
.with(Chain.Near, async () => {
|
|
238
|
+
if (!address) return defaultResult;
|
|
239
|
+
|
|
240
|
+
try {
|
|
241
|
+
const { JsonRpcProvider } = await import("@near-js/providers");
|
|
242
|
+
const rpcUrl = await getRPCUrl(Chain.Near);
|
|
243
|
+
const provider = new JsonRpcProvider({ url: rpcUrl });
|
|
244
|
+
|
|
245
|
+
const metadata = await provider.query({
|
|
246
|
+
account_id: address,
|
|
247
|
+
args_base64: Buffer.from("{}").toString("base64"),
|
|
248
|
+
finality: "final",
|
|
249
|
+
method_name: "ft_metadata",
|
|
250
|
+
request_type: "call_function",
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
const result = JSON.parse(Buffer.from((metadata as any).result).toString());
|
|
254
|
+
|
|
255
|
+
return { decimals: result?.decimals || baseDecimal, ticker: result?.symbol };
|
|
256
|
+
} catch (error) {
|
|
257
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
258
|
+
console.warn(`Failed to fetch Near token info for ${address}: ${errorMessage}`);
|
|
259
|
+
return defaultResult;
|
|
260
|
+
}
|
|
261
|
+
})
|
|
262
|
+
.with(Chain.Radix, async () => {
|
|
263
|
+
if (!address) return defaultResult;
|
|
264
|
+
|
|
265
|
+
try {
|
|
266
|
+
const [ticker, decimals] = await Promise.all([getRadixAssetTicker(address), getRadixAssetDecimals(address)]);
|
|
267
|
+
|
|
268
|
+
return { decimals, ticker };
|
|
269
|
+
} catch (error) {
|
|
270
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
271
|
+
console.warn(`Failed to fetch Radix token info for ${address}: ${errorMessage}`);
|
|
272
|
+
return defaultResult;
|
|
273
|
+
}
|
|
274
|
+
})
|
|
275
|
+
.otherwise(async () => defaultResult);
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
export function isGasAsset({ chain, symbol }: { chain: Chain; symbol: string }) {
|
|
279
|
+
return match(chain)
|
|
280
|
+
.with(...ethGasChains, () => symbol === "ETH")
|
|
281
|
+
.with(Chain.Avalanche, () => symbol === "AVAX")
|
|
282
|
+
.with(Chain.Berachain, () => symbol === "BERA")
|
|
283
|
+
.with(Chain.BinanceSmartChain, () => symbol === "BNB")
|
|
284
|
+
.with(Chain.Gnosis, () => symbol === "xDAI" || symbol === "XDAI")
|
|
285
|
+
.with(Chain.XLayer, () => symbol === "OKB")
|
|
286
|
+
.with(Chain.Maya, () => symbol === "CACAO")
|
|
287
|
+
.with(Chain.Cosmos, () => symbol === "ATOM")
|
|
288
|
+
.with(Chain.THORChain, () => symbol === "RUNE")
|
|
289
|
+
.with(Chain.Tron, () => symbol === "TRX")
|
|
290
|
+
.with(Chain.Radix, () => `${chain}.${symbol}` === getCommonAssetInfo(chain).identifier)
|
|
291
|
+
.otherwise(() => symbol === chain);
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
export const getCommonAssetInfo = (assetString: CommonAssetString) => {
|
|
295
|
+
const { baseDecimal: decimal } = getChainConfig(assetString as Chain);
|
|
296
|
+
|
|
297
|
+
const commonAssetInfo = match(assetString.toUpperCase())
|
|
298
|
+
.with(...ethGasChains, (asset) => ({ decimal, identifier: `${asset}.ETH` }))
|
|
299
|
+
.with(Chain.THORChain, (asset) => ({ decimal, identifier: `${asset}.RUNE` }))
|
|
300
|
+
.with(Chain.Cosmos, (asset) => ({ decimal, identifier: `${asset}.ATOM` }))
|
|
301
|
+
.with(Chain.Maya, (asset) => ({ decimal: 10, identifier: `${asset}.CACAO` }))
|
|
302
|
+
.with(Chain.BinanceSmartChain, (asset) => ({ decimal, identifier: `${asset}.BNB` }))
|
|
303
|
+
.with(Chain.Avalanche, (asset) => ({ decimal, identifier: `${asset}.AVAX` }))
|
|
304
|
+
.with(Chain.Gnosis, (asset) => ({ decimal, identifier: `${asset}.xDAI` }))
|
|
305
|
+
.with(Chain.XLayer, (asset) => ({ decimal, identifier: `${asset}.OKB` }))
|
|
306
|
+
.with(Chain.Berachain, (asset) => ({ decimal, identifier: `${asset}.BERA` }))
|
|
307
|
+
.with(Chain.Tron, (asset) => ({ decimal, identifier: `${asset}.TRX` }))
|
|
308
|
+
.with(
|
|
309
|
+
Chain.Solana,
|
|
310
|
+
Chain.Chainflip,
|
|
311
|
+
Chain.Kujira,
|
|
312
|
+
Chain.Ripple,
|
|
313
|
+
Chain.Polkadot,
|
|
314
|
+
Chain.Near,
|
|
315
|
+
...UTXOChains,
|
|
316
|
+
(asset) => ({ decimal, identifier: `${asset}.${asset}` }),
|
|
317
|
+
)
|
|
318
|
+
.with(Chain.Radix, "XRD.XRD", () => ({ decimal, identifier: "XRD.XRD" }))
|
|
319
|
+
.with(Chain.Polygon, "POL.POL", () => ({ decimal, identifier: "POL.POL" }))
|
|
320
|
+
.with("KUJI.USK", (asset) => ({ decimal: 6, identifier: asset }))
|
|
321
|
+
.with("ETH.FLIP", () => ({
|
|
322
|
+
decimal: getChainConfig(Chain.Ethereum).baseDecimal,
|
|
323
|
+
identifier: "ETH.FLIP-0x826180541412D574cf1336d22c0C0a287822678A",
|
|
324
|
+
}))
|
|
325
|
+
.with("ETH.THOR", () => ({
|
|
326
|
+
decimal: getChainConfig(Chain.Ethereum).baseDecimal,
|
|
327
|
+
identifier: "ETH.THOR-0xa5f2211b9b8170f694421f2046281775e8468044",
|
|
328
|
+
}))
|
|
329
|
+
.with("ETH.vTHOR", () => ({
|
|
330
|
+
decimal: getChainConfig(Chain.Ethereum).baseDecimal,
|
|
331
|
+
identifier: "ETH.vTHOR-0x815c23eca83261b6ec689b60cc4a58b54bc24d8d",
|
|
332
|
+
}))
|
|
333
|
+
.with("MAYA.CACAO", (identifier) => ({ decimal: 10, identifier }))
|
|
334
|
+
.with("MAYA.MAYA", (identifier) => ({ decimal: 4, identifier }))
|
|
335
|
+
// Just to be sure that we are not missing any chain
|
|
336
|
+
.otherwise(() => ({ decimal, identifier: assetString }));
|
|
337
|
+
|
|
338
|
+
return commonAssetInfo;
|
|
339
|
+
};
|
|
340
|
+
|
|
341
|
+
export function getAssetType({ chain, symbol }: { chain: Chain; symbol: string }) {
|
|
342
|
+
if (symbol.includes("/")) return "Synth";
|
|
343
|
+
if (symbol.includes("~")) return "Trade";
|
|
344
|
+
|
|
345
|
+
const isNative = match(chain)
|
|
346
|
+
.with(Chain.Radix, () => symbol === Chain.Radix || `${chain}.${symbol}` === getCommonAssetInfo(chain).identifier)
|
|
347
|
+
.with(Chain.Arbitrum, Chain.Optimism, Chain.Base, Chain.Aurora, () => symbol === Chain.Ethereum)
|
|
348
|
+
.with(Chain.Cosmos, () => symbol === "ATOM")
|
|
349
|
+
.with(Chain.BinanceSmartChain, () => symbol === "BNB")
|
|
350
|
+
.with(Chain.Maya, () => symbol === "CACAO")
|
|
351
|
+
.with(Chain.THORChain, () => symbol === "RUNE")
|
|
352
|
+
.with(Chain.Tron, () => symbol === "TRX")
|
|
353
|
+
.with(Chain.XLayer, () => symbol === "OKB")
|
|
354
|
+
.otherwise(() => symbol === chain);
|
|
355
|
+
|
|
356
|
+
return isNative ? "Native" : chain;
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
export const assetFromString = (assetString: string) => {
|
|
360
|
+
const [chain, ...symbolArray] = assetString.split(".") as [Chain, ...(string | undefined)[]];
|
|
361
|
+
const synth = assetString.includes("/");
|
|
362
|
+
const symbol = symbolArray.join(".");
|
|
363
|
+
const splitSymbol = symbol?.split("-");
|
|
364
|
+
const ticker = splitSymbol?.length
|
|
365
|
+
? splitSymbol.length === 1
|
|
366
|
+
? splitSymbol[0]
|
|
367
|
+
: splitSymbol.slice(0, -1).join("-")
|
|
368
|
+
: undefined;
|
|
369
|
+
|
|
370
|
+
return { chain, symbol, synth, ticker };
|
|
371
|
+
};
|
|
372
|
+
|
|
373
|
+
export async function findAssetBy(params: { chain: Chain; contract: string } | { identifier: `${Chain}.${string}` }) {
|
|
374
|
+
const { loadTokenLists } = await import("../tokens");
|
|
375
|
+
const tokenLists = await loadTokenLists();
|
|
376
|
+
|
|
377
|
+
for (const tokenList of Object.values(tokenLists)) {
|
|
378
|
+
for (const { identifier, chain: tokenChain, ...rest } of tokenList.tokens) {
|
|
379
|
+
if ("identifier" in params && identifier === params.identifier) {
|
|
380
|
+
return identifier as TokenNames;
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
if (
|
|
384
|
+
"address" in rest &&
|
|
385
|
+
"chain" in params &&
|
|
386
|
+
tokenChain === params.chain &&
|
|
387
|
+
rest.address &&
|
|
388
|
+
rest.address.toLowerCase() === params.contract.toLowerCase()
|
|
389
|
+
)
|
|
390
|
+
return identifier as TokenNames;
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
return;
|
|
395
|
+
}
|