@b3dotfun/sdk 0.1.70-alpha.19 → 0.1.70-alpha.20
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/cjs/anyspend/react/components/AnySpend.js +1 -2
- package/dist/cjs/anyspend/react/components/AnySpendCustomExactIn.js +4 -9
- package/dist/cjs/anyspend/react/components/checkout/CryptoPayPanel.js +2 -3
- package/dist/cjs/anyspend/utils/address.d.ts +2 -1
- package/dist/cjs/anyspend/utils/address.js +12 -9
- package/dist/cjs/global-account/react/utils/createWagmiConfig.d.ts +2 -2
- package/dist/cjs/shared/constants/chains/supported.d.ts +4 -4
- package/dist/cjs/shared/constants/chains/supported.js +4 -4
- package/dist/cjs/shared/utils/zerionPositions.d.ts +3 -3
- package/dist/cjs/shared/utils/zerionPositions.js +12 -10
- package/dist/cjs/wallet/utils/createWalletConfig.d.ts +2 -2
- package/dist/esm/anyspend/react/components/AnySpend.js +2 -3
- package/dist/esm/anyspend/react/components/AnySpendCustomExactIn.js +5 -10
- package/dist/esm/anyspend/react/components/checkout/CryptoPayPanel.js +3 -4
- package/dist/esm/anyspend/utils/address.d.ts +2 -1
- package/dist/esm/anyspend/utils/address.js +9 -6
- package/dist/esm/global-account/react/utils/createWagmiConfig.d.ts +2 -2
- package/dist/esm/shared/constants/chains/supported.d.ts +4 -4
- package/dist/esm/shared/constants/chains/supported.js +1 -1
- package/dist/esm/shared/utils/zerionPositions.d.ts +3 -3
- package/dist/esm/shared/utils/zerionPositions.js +5 -3
- package/dist/esm/wallet/utils/createWalletConfig.d.ts +2 -2
- package/dist/types/anyspend/utils/address.d.ts +2 -1
- package/dist/types/global-account/react/utils/createWagmiConfig.d.ts +2 -2
- package/dist/types/shared/constants/chains/supported.d.ts +4 -4
- package/dist/types/shared/utils/zerionPositions.d.ts +3 -3
- package/dist/types/wallet/utils/createWalletConfig.d.ts +2 -2
- package/node_modules/@b3/chain-registry/chains.json +38 -1
- package/node_modules/@b3/chain-registry/dist/chains.json +1225 -0
- package/node_modules/@b3/chain-registry/dist/index.cjs +1676 -0
- package/node_modules/@b3/chain-registry/dist/index.d.cts +195 -0
- package/node_modules/@b3/chain-registry/dist/index.d.ts +195 -0
- package/node_modules/@b3/chain-registry/dist/index.mjs +1604 -0
- package/node_modules/@b3/chain-registry/dist/src/index.d.ts +193 -0
- package/node_modules/@b3/chain-registry/dist/src/index.js +408 -0
- package/node_modules/@b3/chain-registry/package.json +44 -8
- package/node_modules/@b3/chain-registry/src/index.test.ts +432 -0
- package/node_modules/@b3/chain-registry/src/index.ts +672 -0
- package/package.json +5 -2
- package/src/anyspend/react/components/AnySpend.tsx +2 -2
- package/src/anyspend/react/components/AnySpendCustomExactIn.tsx +5 -10
- package/src/anyspend/react/components/__tests__/QRDeposit.test.tsx +39 -1
- package/src/anyspend/react/components/checkout/CryptoPayPanel.tsx +13 -4
- package/src/anyspend/services/gas.test.ts +95 -18
- package/src/anyspend/utils/__tests__/address.test.ts +39 -0
- package/src/anyspend/utils/address.ts +8 -6
- package/src/shared/constants/chains/supported.ts +1 -1
- package/src/shared/react/hooks/__tests__/useCurrencyConversion.test.ts +54 -43
- package/src/shared/utils/__tests__/zerionPositions.test.ts +23 -1
- package/src/shared/utils/zerionPositions.ts +6 -3
|
@@ -6,6 +6,7 @@ import {
|
|
|
6
6
|
getDefaultToken,
|
|
7
7
|
getExplorerTxUrl,
|
|
8
8
|
getHyperliquidUSDCToken,
|
|
9
|
+
isSameChainAndToken,
|
|
9
10
|
HYPERLIQUID_CHAIN_ID,
|
|
10
11
|
HYPERLIQUID_USDC_ADDRESS,
|
|
11
12
|
USDC_BASE,
|
|
@@ -902,8 +903,7 @@ function AnySpendInner({
|
|
|
902
903
|
const isSameChainSameToken = useMemo(() => {
|
|
903
904
|
return (
|
|
904
905
|
activeTab === "crypto" &&
|
|
905
|
-
selectedSrcChainId
|
|
906
|
-
selectedSrcToken.address.toLowerCase() === selectedDstToken.address.toLowerCase()
|
|
906
|
+
isSameChainAndToken(selectedSrcChainId, selectedSrcToken.address, selectedDstChainId, selectedDstToken.address)
|
|
907
907
|
);
|
|
908
908
|
}, [activeTab, selectedSrcChainId, selectedDstChainId, selectedSrcToken.address, selectedDstToken.address]);
|
|
909
909
|
|
|
@@ -2,7 +2,7 @@ import { getChainName, getExplorerTxUrl } from "@b3dotfun/sdk/anyspend";
|
|
|
2
2
|
import { useGasPrice } from "@b3dotfun/sdk/anyspend/react";
|
|
3
3
|
import { components } from "@b3dotfun/sdk/anyspend/types/api";
|
|
4
4
|
import { GetQuoteResponse } from "@b3dotfun/sdk/anyspend/types/api_req_res";
|
|
5
|
-
import { normalizeAddress } from "@b3dotfun/sdk/anyspend/utils";
|
|
5
|
+
import { isSameChainAndToken, normalizeAddress } from "@b3dotfun/sdk/anyspend/utils";
|
|
6
6
|
import {
|
|
7
7
|
Button,
|
|
8
8
|
ShinyButton,
|
|
@@ -218,16 +218,11 @@ function AnySpendCustomExactInInner({
|
|
|
218
218
|
const isSameChainSameToken = useMemo(() => {
|
|
219
219
|
return (
|
|
220
220
|
effectivePaymentType === "crypto" &&
|
|
221
|
-
|
|
222
|
-
|
|
221
|
+
selectedSrcToken != null &&
|
|
222
|
+
selectedDstToken != null &&
|
|
223
|
+
isSameChainAndToken(selectedSrcChainId, selectedSrcToken.address, selectedDstChainId, selectedDstToken.address)
|
|
223
224
|
);
|
|
224
|
-
}, [
|
|
225
|
-
effectivePaymentType,
|
|
226
|
-
selectedSrcChainId,
|
|
227
|
-
selectedDstChainId,
|
|
228
|
-
selectedSrcToken?.address,
|
|
229
|
-
selectedDstToken?.address,
|
|
230
|
-
]);
|
|
225
|
+
}, [effectivePaymentType, selectedSrcChainId, selectedDstChainId, selectedSrcToken, selectedDstToken]);
|
|
231
226
|
|
|
232
227
|
// Check if this is a direct transfer
|
|
233
228
|
const isDirectTransfer = isSameChainSameToken && allowDirectTransfer;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { USDC_BASE } from "@b3dotfun/sdk/anyspend";
|
|
1
|
+
import { RELAY_SOLANA_MAINNET_CHAIN_ID, USDC_BASE } from "@b3dotfun/sdk/anyspend";
|
|
2
2
|
import { fireEvent, render, screen } from "@testing-library/react";
|
|
3
3
|
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
4
4
|
|
|
@@ -74,6 +74,10 @@ vi.mock("@relayprotocol/relay-kit-ui", () => ({
|
|
|
74
74
|
},
|
|
75
75
|
}));
|
|
76
76
|
|
|
77
|
+
vi.mock("@stripe/stripe-js", () => ({
|
|
78
|
+
loadStripe: vi.fn(() => Promise.resolve(null)),
|
|
79
|
+
}));
|
|
80
|
+
|
|
77
81
|
// Spread the real global-account/react (Button, Badge, etc. are needed by transitively
|
|
78
82
|
// imported sub-components and render fine under happy-dom). Override only `toast` so the
|
|
79
83
|
// error path doesn't depend on a toast provider.
|
|
@@ -240,6 +244,40 @@ describe("QRDeposit pureTransferOnly (Fund Wallet)", () => {
|
|
|
240
244
|
expect(container.textContent).toContain("Target ≈ 9.365547 USDC");
|
|
241
245
|
});
|
|
242
246
|
|
|
247
|
+
it("Test C3 — case-mutated Solana mints require a swap order", async () => {
|
|
248
|
+
const QRDeposit = await loadQRDeposit();
|
|
249
|
+
const wrappedSol = "So11111111111111111111111111111111111111112";
|
|
250
|
+
const sourceToken = {
|
|
251
|
+
chainId: RELAY_SOLANA_MAINNET_CHAIN_ID,
|
|
252
|
+
address: wrappedSol,
|
|
253
|
+
symbol: "wSOL",
|
|
254
|
+
name: "Wrapped SOL",
|
|
255
|
+
decimals: 9,
|
|
256
|
+
metadata: {},
|
|
257
|
+
};
|
|
258
|
+
const destinationToken = {
|
|
259
|
+
...sourceToken,
|
|
260
|
+
address: `s${wrappedSol.slice(1)}`,
|
|
261
|
+
symbol: "OTHER",
|
|
262
|
+
name: "Distinct Solana Mint",
|
|
263
|
+
};
|
|
264
|
+
|
|
265
|
+
render(
|
|
266
|
+
<QRDeposit
|
|
267
|
+
recipientAddress={RECIPIENT}
|
|
268
|
+
sourceToken={sourceToken}
|
|
269
|
+
sourceChainId={RELAY_SOLANA_MAINNET_CHAIN_ID}
|
|
270
|
+
destinationToken={destinationToken}
|
|
271
|
+
destinationChainId={RELAY_SOLANA_MAINNET_CHAIN_ID}
|
|
272
|
+
destinationAmountDisplay="1"
|
|
273
|
+
/>,
|
|
274
|
+
);
|
|
275
|
+
|
|
276
|
+
expect(createOrderSpy).toHaveBeenCalledTimes(1);
|
|
277
|
+
expect(createOrderSpy.mock.calls[0][0].dstToken.address).toBe(destinationToken.address);
|
|
278
|
+
expect(watchTransferArgs.enabled).toBe(false);
|
|
279
|
+
});
|
|
280
|
+
|
|
243
281
|
it("Test C2 — swap mode without a fixed amount shows the Receive row but no target hint", async () => {
|
|
244
282
|
const QRDeposit = await loadQRDeposit();
|
|
245
283
|
|
|
@@ -6,7 +6,12 @@ import { useAnyspendCreateOrder } from "@b3dotfun/sdk/anyspend/react/hooks/useAn
|
|
|
6
6
|
import { useAnyspendOrderAndTransactions } from "@b3dotfun/sdk/anyspend/react/hooks/useAnyspendOrderAndTransactions";
|
|
7
7
|
import { useCreateDepositFirstOrder } from "@b3dotfun/sdk/anyspend/react/hooks/useCreateDepositFirstOrder";
|
|
8
8
|
import { useOnOrderSuccess } from "@b3dotfun/sdk/anyspend/react/hooks/useOnOrderSuccess";
|
|
9
|
-
import {
|
|
9
|
+
import {
|
|
10
|
+
ALL_CHAINS,
|
|
11
|
+
RELAY_SOLANA_MAINNET_CHAIN_ID,
|
|
12
|
+
getAvailableChainIds,
|
|
13
|
+
isSameChainAndToken,
|
|
14
|
+
} from "@b3dotfun/sdk/anyspend";
|
|
10
15
|
import { getPaymentUrl, toRegistryChainId } from "@b3dotfun/sdk/anyspend/utils/chain";
|
|
11
16
|
import { isNativeToken } from "@b3dotfun/sdk/anyspend/utils/token";
|
|
12
17
|
import {
|
|
@@ -97,9 +102,13 @@ export function CryptoPayPanel({
|
|
|
97
102
|
}, [selectedSrcToken, dstTokenData, destinationTokenAddress, destinationTokenChainId]);
|
|
98
103
|
|
|
99
104
|
const isSameToken =
|
|
100
|
-
selectedSrcToken &&
|
|
101
|
-
|
|
102
|
-
|
|
105
|
+
selectedSrcToken !== null &&
|
|
106
|
+
isSameChainAndToken(
|
|
107
|
+
selectedSrcToken.chainId,
|
|
108
|
+
selectedSrcToken.address,
|
|
109
|
+
destinationTokenChainId,
|
|
110
|
+
destinationTokenAddress,
|
|
111
|
+
);
|
|
103
112
|
|
|
104
113
|
const { anyspendQuote, isLoadingAnyspendQuote } = useAnyspendQuote({
|
|
105
114
|
type: "swap",
|
|
@@ -1,31 +1,108 @@
|
|
|
1
|
-
import { describe, it,
|
|
1
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
2
|
+
import type { GasOracleResponse } from "../types/gas";
|
|
2
3
|
import { gasService, isGasOracleSupported } from "./gas";
|
|
3
4
|
|
|
5
|
+
const RESPONSES: Readonly<Record<number, GasOracleResponse>> = {
|
|
6
|
+
1: {
|
|
7
|
+
chainId: 1,
|
|
8
|
+
chainName: "Ethereum",
|
|
9
|
+
timestamp: "2026-01-01T00:00:00.000Z",
|
|
10
|
+
source: "blocknative",
|
|
11
|
+
gasPrice: { low: "7", standard: "8", fast: "9" },
|
|
12
|
+
eip1559: {
|
|
13
|
+
baseFee: "6",
|
|
14
|
+
maxPriorityFee: { low: "1", standard: "2", fast: "3" },
|
|
15
|
+
maxFee: { low: "8", standard: "10", fast: "12" },
|
|
16
|
+
},
|
|
17
|
+
analysis: {
|
|
18
|
+
level: "elevated",
|
|
19
|
+
percentile: 80,
|
|
20
|
+
vs1h: 1.5,
|
|
21
|
+
vs4h: 1.4,
|
|
22
|
+
vs24h: 1.3,
|
|
23
|
+
recommendation: "Wait if possible.",
|
|
24
|
+
},
|
|
25
|
+
cached: false,
|
|
26
|
+
},
|
|
27
|
+
8453: {
|
|
28
|
+
chainId: 8453,
|
|
29
|
+
chainName: "Base",
|
|
30
|
+
timestamp: "2026-01-01T00:00:00.000Z",
|
|
31
|
+
source: "rpc",
|
|
32
|
+
gasPrice: { low: "0.004", standard: "0.005", fast: "0.006" },
|
|
33
|
+
eip1559: {
|
|
34
|
+
baseFee: "0.003",
|
|
35
|
+
maxPriorityFee: { low: "0.001", standard: "0.002", fast: "0.003" },
|
|
36
|
+
maxFee: { low: "0.005", standard: "0.007", fast: "0.009" },
|
|
37
|
+
},
|
|
38
|
+
analysis: {
|
|
39
|
+
level: "low",
|
|
40
|
+
percentile: 20,
|
|
41
|
+
vs1h: 0.8,
|
|
42
|
+
vs4h: 0.9,
|
|
43
|
+
vs24h: 0.7,
|
|
44
|
+
recommendation: "Good time to transact.",
|
|
45
|
+
},
|
|
46
|
+
cached: true,
|
|
47
|
+
cacheAge: 10,
|
|
48
|
+
},
|
|
49
|
+
};
|
|
50
|
+
|
|
4
51
|
describe("gasService", () => {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
52
|
+
beforeEach(() => {
|
|
53
|
+
vi.stubGlobal(
|
|
54
|
+
"fetch",
|
|
55
|
+
vi.fn(async (input: string | URL | Request) => {
|
|
56
|
+
const url = new URL(input instanceof Request ? input.url : String(input));
|
|
57
|
+
const chainId = Number(url.pathname.split("/").at(-1));
|
|
58
|
+
const response = RESPONSES[chainId];
|
|
59
|
+
|
|
60
|
+
return response ? Response.json(response) : new Response(null, { status: 404 });
|
|
61
|
+
}),
|
|
62
|
+
);
|
|
9
63
|
});
|
|
10
64
|
|
|
11
|
-
|
|
12
|
-
|
|
65
|
+
afterEach(() => {
|
|
66
|
+
vi.unstubAllGlobals();
|
|
67
|
+
});
|
|
13
68
|
|
|
14
|
-
|
|
69
|
+
it("checks supported chains", () => {
|
|
70
|
+
expect(isGasOracleSupported(8453)).toBe(true);
|
|
71
|
+
expect(isGasOracleSupported(1)).toBe(true);
|
|
72
|
+
expect(isGasOracleSupported(999999)).toBe(false);
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
it("fetches and transforms Base gas data", async () => {
|
|
76
|
+
const gas = await gasService.fetch(8453);
|
|
15
77
|
|
|
16
|
-
expect(gas
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
78
|
+
expect(gas).toEqual({
|
|
79
|
+
chainId: 8453,
|
|
80
|
+
chainName: "Base",
|
|
81
|
+
gasPriceGwei: "0.005",
|
|
82
|
+
baseFeeGwei: "0.003",
|
|
83
|
+
level: "low",
|
|
84
|
+
isSpike: false,
|
|
85
|
+
recommendation: "Good time to transact.",
|
|
86
|
+
vsMedian: 0.8,
|
|
87
|
+
source: "rpc",
|
|
88
|
+
timestamp: "2026-01-01T00:00:00.000Z",
|
|
89
|
+
});
|
|
21
90
|
});
|
|
22
91
|
|
|
23
|
-
it("
|
|
92
|
+
it("fetches and transforms Ethereum gas data", async () => {
|
|
24
93
|
const gas = await gasService.fetch(1);
|
|
25
94
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
95
|
+
expect(gas).toEqual({
|
|
96
|
+
chainId: 1,
|
|
97
|
+
chainName: "Ethereum",
|
|
98
|
+
gasPriceGwei: "8",
|
|
99
|
+
baseFeeGwei: "6",
|
|
100
|
+
level: "elevated",
|
|
101
|
+
isSpike: true,
|
|
102
|
+
recommendation: "Wait if possible.",
|
|
103
|
+
vsMedian: 1.5,
|
|
104
|
+
source: "blocknative",
|
|
105
|
+
timestamp: "2026-01-01T00:00:00.000Z",
|
|
106
|
+
});
|
|
30
107
|
});
|
|
31
108
|
});
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { RELAY_SOLANA_MAINNET_CHAIN_ID } from "../../constants";
|
|
3
|
+
import { HYPERLIQUID_CHAIN_ID } from "../token";
|
|
4
|
+
import { isSameChainAndToken } from "../address";
|
|
5
|
+
|
|
6
|
+
const SOLANA_CHAIN_ID = 7565164;
|
|
7
|
+
const WRAPPED_SOL = "So11111111111111111111111111111111111111112";
|
|
8
|
+
|
|
9
|
+
describe("isSameChainAndToken", () => {
|
|
10
|
+
it("compares Solana mint addresses byte-for-byte", () => {
|
|
11
|
+
const caseMutatedMint = `s${WRAPPED_SOL.slice(1)}`;
|
|
12
|
+
|
|
13
|
+
expect(isSameChainAndToken(SOLANA_CHAIN_ID, WRAPPED_SOL, SOLANA_CHAIN_ID, caseMutatedMint)).toBe(false);
|
|
14
|
+
const invalidBase58Mutation = `SO${WRAPPED_SOL.slice(2)}`;
|
|
15
|
+
expect(
|
|
16
|
+
isSameChainAndToken(
|
|
17
|
+
RELAY_SOLANA_MAINNET_CHAIN_ID,
|
|
18
|
+
invalidBase58Mutation,
|
|
19
|
+
RELAY_SOLANA_MAINNET_CHAIN_ID,
|
|
20
|
+
invalidBase58Mutation.toLowerCase(),
|
|
21
|
+
),
|
|
22
|
+
).toBe(false);
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it("compares EVM contract addresses case-insensitively on the same chain", () => {
|
|
26
|
+
const checksumAddress = "0xdAC17F958D2ee523a2206206994597C13D831ec7";
|
|
27
|
+
|
|
28
|
+
expect(isSameChainAndToken(1, checksumAddress, 1, checksumAddress.toLowerCase())).toBe(true);
|
|
29
|
+
expect(isSameChainAndToken(1, checksumAddress, 8453, checksumAddress)).toBe(false);
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
it("preserves case-insensitive token matching on Hyperliquid", () => {
|
|
33
|
+
const mixedCaseAddress = "0xAbCdEf0123456789AbCdEf0123456789";
|
|
34
|
+
|
|
35
|
+
expect(
|
|
36
|
+
isSameChainAndToken(HYPERLIQUID_CHAIN_ID, mixedCaseAddress, HYPERLIQUID_CHAIN_ID, mixedCaseAddress.toLowerCase()),
|
|
37
|
+
).toBe(true);
|
|
38
|
+
});
|
|
39
|
+
});
|
|
@@ -1,11 +1,9 @@
|
|
|
1
|
+
import { chainTypeForIdentifier, isSolanaAddress } from "@b3/chain-registry/runtime";
|
|
1
2
|
import { isAddress } from "viem";
|
|
3
|
+
import { RELAY_SOLANA_MAINNET_CHAIN_ID } from "../constants";
|
|
2
4
|
import { HYPERLIQUID_CHAIN_ID } from "./token";
|
|
3
5
|
|
|
4
|
-
export
|
|
5
|
-
// Solana addresses are 32-byte base58 strings (usually 32-44 characters)
|
|
6
|
-
const solanaAddressRegex = /^[1-9A-HJ-NP-Za-km-z]{32,44}$/;
|
|
7
|
-
return solanaAddressRegex.test(address);
|
|
8
|
-
}
|
|
6
|
+
export { isSolanaAddress };
|
|
9
7
|
|
|
10
8
|
export function isEvmOrSolanaAddress(address: string): boolean {
|
|
11
9
|
return isAddress(address) || isSolanaAddress(address);
|
|
@@ -49,5 +47,9 @@ export function isSameChainAndToken(
|
|
|
49
47
|
destinationChainId: number,
|
|
50
48
|
destinationTokenAddress: string,
|
|
51
49
|
): boolean {
|
|
52
|
-
|
|
50
|
+
if (sourceChainId !== destinationChainId) return false;
|
|
51
|
+
if (chainTypeForIdentifier(String(sourceChainId)) === "solana" || sourceChainId === RELAY_SOLANA_MAINNET_CHAIN_ID) {
|
|
52
|
+
return sourceTokenAddress === destinationTokenAddress;
|
|
53
|
+
}
|
|
54
|
+
return sourceTokenAddress.toLowerCase() === destinationTokenAddress.toLowerCase();
|
|
53
55
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ANYSPEND_EVM_CHAINS } from "@b3/chain-registry";
|
|
1
|
+
import { ANYSPEND_EVM_CHAINS } from "@b3/chain-registry/runtime";
|
|
2
2
|
import { toThirdwebChain, toViemChain } from "@b3dotfun/sdk/shared/utils/chain-transformers";
|
|
3
3
|
import invariant from "invariant";
|
|
4
4
|
import type { Chain as ThirdwebChain } from "thirdweb";
|
|
@@ -1,25 +1,27 @@
|
|
|
1
|
-
import { describe, it, expect, vi, beforeEach } from "vitest";
|
|
2
1
|
import { renderHook } from "@testing-library/react";
|
|
2
|
+
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
3
3
|
import { useCurrencyConversion } from "../useCurrencyConversion";
|
|
4
4
|
|
|
5
5
|
// Mock the external dependencies
|
|
6
6
|
// Store mock rates for different quote currencies
|
|
7
7
|
const mockRates: Record<string, number | undefined> = {};
|
|
8
8
|
|
|
9
|
-
// Mock store state
|
|
10
9
|
const mockStoreState: any = {
|
|
11
10
|
selectedCurrency: "B3",
|
|
12
11
|
baseCurrency: "B3",
|
|
12
|
+
customCurrencies: {},
|
|
13
|
+
getExchangeRate: vi.fn(() => undefined),
|
|
13
14
|
setSelectedCurrency: vi.fn(),
|
|
14
15
|
setBaseCurrency: vi.fn(),
|
|
15
16
|
};
|
|
16
17
|
|
|
17
|
-
vi.mock("@
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
}
|
|
18
|
+
vi.mock("@tanstack/react-query", async importOriginal => {
|
|
19
|
+
const actual = await importOriginal<typeof import("@tanstack/react-query")>();
|
|
20
|
+
return {
|
|
21
|
+
...actual,
|
|
22
|
+
useQuery: vi.fn(() => ({ data: mockRates })),
|
|
23
|
+
};
|
|
24
|
+
});
|
|
23
25
|
|
|
24
26
|
vi.mock("@b3dotfun/sdk/shared/utils/number", () => ({
|
|
25
27
|
formatDisplayNumber: vi.fn((value: any) => {
|
|
@@ -29,14 +31,8 @@ vi.mock("@b3dotfun/sdk/shared/utils/number", () => ({
|
|
|
29
31
|
}),
|
|
30
32
|
}));
|
|
31
33
|
|
|
32
|
-
vi.mock("../../stores/currencyStore", () =>
|
|
33
|
-
|
|
34
|
-
if (selector) {
|
|
35
|
-
return selector(mockStoreState);
|
|
36
|
-
}
|
|
37
|
-
return mockStoreState;
|
|
38
|
-
}),
|
|
39
|
-
CURRENCY_SYMBOLS: {
|
|
34
|
+
vi.mock("../../stores/currencyStore", () => {
|
|
35
|
+
const symbols: Record<string, string> = {
|
|
40
36
|
B3: "B3",
|
|
41
37
|
USD: "$",
|
|
42
38
|
EUR: "€",
|
|
@@ -47,8 +43,23 @@ vi.mock("../../stores/currencyStore", () => ({
|
|
|
47
43
|
ETH: "ETH",
|
|
48
44
|
SOL: "SOL",
|
|
49
45
|
KRW: "₩",
|
|
50
|
-
}
|
|
51
|
-
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
return {
|
|
49
|
+
useCurrencyStore: vi.fn((selector: any) => {
|
|
50
|
+
if (selector) {
|
|
51
|
+
return selector(mockStoreState);
|
|
52
|
+
}
|
|
53
|
+
return mockStoreState;
|
|
54
|
+
}),
|
|
55
|
+
getCurrencySymbol: vi.fn((currency: string) => symbols[currency] ?? currency),
|
|
56
|
+
getCurrencyMetadata: vi.fn(() => undefined),
|
|
57
|
+
};
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
function renderCurrencyConversion() {
|
|
61
|
+
return renderHook(() => useCurrencyConversion());
|
|
62
|
+
}
|
|
52
63
|
|
|
53
64
|
describe("useCurrencyConversion", () => {
|
|
54
65
|
beforeEach(() => {
|
|
@@ -66,7 +77,7 @@ describe("useCurrencyConversion", () => {
|
|
|
66
77
|
mockStoreState.selectedCurrency = "B3";
|
|
67
78
|
mockStoreState.baseCurrency = "B3";
|
|
68
79
|
|
|
69
|
-
const { result } =
|
|
80
|
+
const { result } = renderCurrencyConversion();
|
|
70
81
|
const formatted = result.current.formatCurrencyValue(100, "B3");
|
|
71
82
|
|
|
72
83
|
expect(formatted).toContain("B3");
|
|
@@ -78,7 +89,7 @@ describe("useCurrencyConversion", () => {
|
|
|
78
89
|
mockStoreState.selectedCurrency = "USD";
|
|
79
90
|
mockStoreState.baseCurrency = "B3";
|
|
80
91
|
|
|
81
|
-
const { result } =
|
|
92
|
+
const { result } = renderCurrencyConversion();
|
|
82
93
|
const formatted = result.current.formatCurrencyValue(100, "B3");
|
|
83
94
|
|
|
84
95
|
expect(formatted).toContain("B3");
|
|
@@ -90,7 +101,7 @@ describe("useCurrencyConversion", () => {
|
|
|
90
101
|
mockStoreState.selectedCurrency = "USD";
|
|
91
102
|
mockStoreState.baseCurrency = "B3";
|
|
92
103
|
|
|
93
|
-
const { result } =
|
|
104
|
+
const { result } = renderCurrencyConversion();
|
|
94
105
|
const formatted = result.current.formatCurrencyValue(100, "B3");
|
|
95
106
|
|
|
96
107
|
expect(formatted).toMatch(/^\$/);
|
|
@@ -103,7 +114,7 @@ describe("useCurrencyConversion", () => {
|
|
|
103
114
|
mockStoreState.selectedCurrency = "EUR";
|
|
104
115
|
mockStoreState.baseCurrency = "B3";
|
|
105
116
|
|
|
106
|
-
const { result } =
|
|
117
|
+
const { result } = renderCurrencyConversion();
|
|
107
118
|
const formatted = result.current.formatCurrencyValue(100, "B3");
|
|
108
119
|
|
|
109
120
|
expect(formatted).toMatch(/^€/);
|
|
@@ -115,7 +126,7 @@ describe("useCurrencyConversion", () => {
|
|
|
115
126
|
mockStoreState.selectedCurrency = "JPY";
|
|
116
127
|
mockStoreState.baseCurrency = "B3";
|
|
117
128
|
|
|
118
|
-
const { result } =
|
|
129
|
+
const { result } = renderCurrencyConversion();
|
|
119
130
|
const formatted = result.current.formatCurrencyValue(100, "B3");
|
|
120
131
|
|
|
121
132
|
expect(formatted).toContain("¥");
|
|
@@ -128,7 +139,7 @@ describe("useCurrencyConversion", () => {
|
|
|
128
139
|
mockStoreState.selectedCurrency = "KRW";
|
|
129
140
|
mockStoreState.baseCurrency = "B3";
|
|
130
141
|
|
|
131
|
-
const { result } =
|
|
142
|
+
const { result } = renderCurrencyConversion();
|
|
132
143
|
const formatted = result.current.formatCurrencyValue(100, "B3");
|
|
133
144
|
|
|
134
145
|
expect(formatted).toContain("₩");
|
|
@@ -141,7 +152,7 @@ describe("useCurrencyConversion", () => {
|
|
|
141
152
|
mockStoreState.selectedCurrency = "ETH";
|
|
142
153
|
mockStoreState.baseCurrency = "B3";
|
|
143
154
|
|
|
144
|
-
const { result } =
|
|
155
|
+
const { result } = renderCurrencyConversion();
|
|
145
156
|
const formatted = result.current.formatCurrencyValue(100, "B3");
|
|
146
157
|
|
|
147
158
|
expect(formatted).toContain("ETH");
|
|
@@ -154,7 +165,7 @@ describe("useCurrencyConversion", () => {
|
|
|
154
165
|
mockStoreState.selectedCurrency = "SOL";
|
|
155
166
|
mockStoreState.baseCurrency = "B3";
|
|
156
167
|
|
|
157
|
-
const { result } =
|
|
168
|
+
const { result } = renderCurrencyConversion();
|
|
158
169
|
const formatted = result.current.formatCurrencyValue(100, "B3");
|
|
159
170
|
|
|
160
171
|
expect(formatted).toContain("SOL");
|
|
@@ -166,7 +177,7 @@ describe("useCurrencyConversion", () => {
|
|
|
166
177
|
mockStoreState.selectedCurrency = "USD";
|
|
167
178
|
mockStoreState.baseCurrency = "B3";
|
|
168
179
|
|
|
169
|
-
const { result } =
|
|
180
|
+
const { result } = renderCurrencyConversion();
|
|
170
181
|
const formatted = result.current.formatCurrencyValue(10, "B3");
|
|
171
182
|
|
|
172
183
|
// 10 * 1.5 = 15
|
|
@@ -180,7 +191,7 @@ describe("useCurrencyConversion", () => {
|
|
|
180
191
|
mockStoreState.selectedCurrency = "USD";
|
|
181
192
|
mockStoreState.baseCurrency = "B3";
|
|
182
193
|
|
|
183
|
-
const { result } =
|
|
194
|
+
const { result } = renderCurrencyConversion();
|
|
184
195
|
const inputValue = 100;
|
|
185
196
|
const formatted = result.current.formatCurrencyValue(inputValue, "B3");
|
|
186
197
|
|
|
@@ -193,7 +204,7 @@ describe("useCurrencyConversion", () => {
|
|
|
193
204
|
mockStoreState.selectedCurrency = "USD";
|
|
194
205
|
mockStoreState.baseCurrency = "B3";
|
|
195
206
|
|
|
196
|
-
const { result } =
|
|
207
|
+
const { result } = renderCurrencyConversion();
|
|
197
208
|
|
|
198
209
|
expect(result.current.selectedCurrency).toBe("USD");
|
|
199
210
|
});
|
|
@@ -202,7 +213,7 @@ describe("useCurrencyConversion", () => {
|
|
|
202
213
|
mockStoreState.selectedCurrency = "USD";
|
|
203
214
|
mockStoreState.baseCurrency = "B3";
|
|
204
215
|
|
|
205
|
-
const { result } =
|
|
216
|
+
const { result } = renderCurrencyConversion();
|
|
206
217
|
|
|
207
218
|
expect(result.current.baseCurrency).toBe("B3");
|
|
208
219
|
});
|
|
@@ -213,7 +224,7 @@ describe("useCurrencyConversion", () => {
|
|
|
213
224
|
mockStoreState.selectedCurrency = "USD";
|
|
214
225
|
mockStoreState.baseCurrency = "B3";
|
|
215
226
|
|
|
216
|
-
const { result } =
|
|
227
|
+
const { result } = renderCurrencyConversion();
|
|
217
228
|
|
|
218
229
|
expect(result.current.exchangeRate).toBe(testRate);
|
|
219
230
|
});
|
|
@@ -222,7 +233,7 @@ describe("useCurrencyConversion", () => {
|
|
|
222
233
|
mockStoreState.selectedCurrency = "EUR";
|
|
223
234
|
mockStoreState.baseCurrency = "B3";
|
|
224
235
|
|
|
225
|
-
const { result } =
|
|
236
|
+
const { result } = renderCurrencyConversion();
|
|
226
237
|
|
|
227
238
|
expect(result.current.selectedCurrencySymbol).toBe("€");
|
|
228
239
|
expect(result.current.baseCurrencySymbol).toBe("B3");
|
|
@@ -235,24 +246,24 @@ describe("useCurrencyConversion", () => {
|
|
|
235
246
|
mockStoreState.selectedCurrency = "B3";
|
|
236
247
|
mockStoreState.baseCurrency = "B3";
|
|
237
248
|
|
|
238
|
-
const { result } =
|
|
249
|
+
const { result } = renderCurrencyConversion();
|
|
239
250
|
const tooltip = result.current.formatTooltipValue(100, "B3");
|
|
240
251
|
|
|
241
252
|
expect(tooltip).toContain("USD");
|
|
242
253
|
expect(tooltip).toContain("150");
|
|
243
254
|
});
|
|
244
255
|
|
|
245
|
-
it("
|
|
256
|
+
it("shows the USD equivalent regardless of selected display currency", () => {
|
|
246
257
|
mockRates.EUR = 0.9;
|
|
247
258
|
mockRates.USD = 1.2;
|
|
248
259
|
mockStoreState.selectedCurrency = "EUR";
|
|
249
260
|
mockStoreState.baseCurrency = "B3";
|
|
250
261
|
|
|
251
|
-
const { result } =
|
|
262
|
+
const { result } = renderCurrencyConversion();
|
|
252
263
|
const tooltip = result.current.formatTooltipValue(100, "B3");
|
|
253
264
|
|
|
254
|
-
expect(tooltip).toContain("
|
|
255
|
-
expect(tooltip).toContain("
|
|
265
|
+
expect(tooltip).toContain("USD");
|
|
266
|
+
expect(tooltip).toContain("120");
|
|
256
267
|
});
|
|
257
268
|
|
|
258
269
|
it("should handle custom currency for base currency", () => {
|
|
@@ -261,7 +272,7 @@ describe("useCurrencyConversion", () => {
|
|
|
261
272
|
mockStoreState.selectedCurrency = "EUR";
|
|
262
273
|
mockStoreState.baseCurrency = "B3";
|
|
263
274
|
|
|
264
|
-
const { result } =
|
|
275
|
+
const { result } = renderCurrencyConversion();
|
|
265
276
|
const tooltip = result.current.formatTooltipValue(100, "B3");
|
|
266
277
|
|
|
267
278
|
expect(tooltip).toContain("USD");
|
|
@@ -273,7 +284,7 @@ describe("useCurrencyConversion", () => {
|
|
|
273
284
|
mockStoreState.selectedCurrency = "USD";
|
|
274
285
|
mockStoreState.baseCurrency = "B3";
|
|
275
286
|
|
|
276
|
-
const { result } =
|
|
287
|
+
const { result } = renderCurrencyConversion();
|
|
277
288
|
const tooltip = result.current.formatTooltipValue(50, "ETH");
|
|
278
289
|
|
|
279
290
|
expect(tooltip).toContain("ETH");
|
|
@@ -285,7 +296,7 @@ describe("useCurrencyConversion", () => {
|
|
|
285
296
|
mockStoreState.selectedCurrency = "B3";
|
|
286
297
|
mockStoreState.baseCurrency = "B3";
|
|
287
298
|
|
|
288
|
-
const { result } =
|
|
299
|
+
const { result } = renderCurrencyConversion();
|
|
289
300
|
const tooltip = result.current.formatTooltipValue(-100, "B3");
|
|
290
301
|
|
|
291
302
|
expect(tooltip).toContain("USD");
|
|
@@ -293,15 +304,15 @@ describe("useCurrencyConversion", () => {
|
|
|
293
304
|
expect(tooltip).not.toContain("-");
|
|
294
305
|
});
|
|
295
306
|
|
|
296
|
-
it("
|
|
307
|
+
it("falls back to the source currency when its USD rate is unavailable", () => {
|
|
297
308
|
mockRates.USD = undefined;
|
|
298
309
|
mockStoreState.selectedCurrency = "B3";
|
|
299
310
|
mockStoreState.baseCurrency = "B3";
|
|
300
311
|
|
|
301
|
-
const { result } =
|
|
312
|
+
const { result } = renderCurrencyConversion();
|
|
302
313
|
const tooltip = result.current.formatTooltipValue(100, "B3");
|
|
303
314
|
|
|
304
|
-
expect(tooltip).toContain("
|
|
315
|
+
expect(tooltip).toContain("B3");
|
|
305
316
|
expect(tooltip).toContain("100");
|
|
306
317
|
});
|
|
307
318
|
});
|
|
@@ -1,8 +1,13 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { isSolanaAddress as registryIsSolanaAddress, SOLANA_NATIVE_ASSET } from "@b3/chain-registry/runtime";
|
|
2
|
+
import { describe, expect, expectTypeOf, it } from "vitest";
|
|
3
|
+
import { isSolanaAddress as sdkIsSolanaAddress } from "../../../anyspend/utils/address";
|
|
2
4
|
import {
|
|
3
5
|
adaptZerionPositions,
|
|
4
6
|
chainIdToZerionSlug,
|
|
5
7
|
EVM_NATIVE_TOKEN_ADDRESS,
|
|
8
|
+
SOLANA_CHAIN_ID,
|
|
9
|
+
SOLANA_NATIVE_TOKEN_ADDRESS,
|
|
10
|
+
SOLANA_ZERION_SLUG,
|
|
6
11
|
zerionSlugToChainId,
|
|
7
12
|
type ZerionPositionLike,
|
|
8
13
|
} from "../zerionPositions";
|
|
@@ -224,3 +229,20 @@ describe("zerionSlugToChainId", () => {
|
|
|
224
229
|
expect(chainIdToZerionSlug(8453)).toBe("base");
|
|
225
230
|
});
|
|
226
231
|
});
|
|
232
|
+
|
|
233
|
+
describe("canonical Solana registry compatibility", () => {
|
|
234
|
+
it("preserves published SDK values and literal types", () => {
|
|
235
|
+
expect(SOLANA_NATIVE_TOKEN_ADDRESS).toBe(SOLANA_NATIVE_ASSET.wrapped.address);
|
|
236
|
+
expect(SOLANA_ZERION_SLUG).toBe(SOLANA_NATIVE_ASSET.chain);
|
|
237
|
+
expect(SOLANA_CHAIN_ID).toBe(SOLANA_NATIVE_ASSET.chainId);
|
|
238
|
+
expectTypeOf(SOLANA_NATIVE_TOKEN_ADDRESS).toEqualTypeOf<"So11111111111111111111111111111111111111112">();
|
|
239
|
+
expectTypeOf(SOLANA_ZERION_SLUG).toEqualTypeOf<"solana">();
|
|
240
|
+
expectTypeOf(SOLANA_CHAIN_ID).toEqualTypeOf<7565164>();
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
it("re-exports the registry Solana address guard unchanged", () => {
|
|
244
|
+
expect(sdkIsSolanaAddress).toBe(registryIsSolanaAddress);
|
|
245
|
+
expect(sdkIsSolanaAddress(SOLANA_NATIVE_TOKEN_ADDRESS)).toBe(true);
|
|
246
|
+
expect(sdkIsSolanaAddress("So1111111111111111111111111111111111111111O")).toBe(false);
|
|
247
|
+
});
|
|
248
|
+
});
|