@b3dotfun/sdk 0.1.70-alpha.17 → 0.1.70-alpha.19
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/README.md +3 -3
- package/dist/cjs/anyspend/constants/rpc.d.ts +1 -0
- package/dist/cjs/anyspend/constants/rpc.js +3 -1
- package/dist/cjs/anyspend/react/components/AnySpendDeposit.d.ts +1 -3
- package/dist/cjs/anyspend/react/components/AnySpendDeposit.js +14 -12
- package/dist/cjs/anyspend/react/components/AnySpendStakeB3.js +32 -13
- package/dist/cjs/anyspend/react/components/AnySpendStakeB3ExactIn.js +32 -10
- package/dist/cjs/anyspend/react/components/checkout/CryptoPayPanel.js +16 -8
- package/dist/cjs/anyspend/react/components/common/TokenBalance.js +4 -5
- package/dist/cjs/anyspend/react/hooks/useSigMint.d.ts +1 -1
- package/dist/cjs/anyspend/utils/chain.d.ts +45 -0
- package/dist/cjs/anyspend/utils/chain.js +51 -2
- package/dist/cjs/global-account/react/components/B3Provider/B3Provider.d.ts +1 -2
- package/dist/cjs/global-account/react/components/B3Provider/B3Provider.js +2 -2
- package/dist/cjs/global-account/react/components/B3Provider/RelayKitProviderWrapper.d.ts +1 -2
- package/dist/cjs/global-account/react/components/B3Provider/RelayKitProviderWrapper.js +4 -5
- package/dist/cjs/global-account/react/components/ManageAccount/ContentTokens.js +23 -15
- package/dist/cjs/global-account/react/components/ManageAccount/NFTContent.js +2 -2
- package/dist/cjs/global-account/react/components/ManageAccount/ProfileSection.js +8 -6
- package/dist/cjs/global-account/react/components/ManageAccount/TokenContent.js +17 -8
- package/dist/cjs/global-account/react/components/Send/Send.js +15 -11
- package/dist/cjs/global-account/react/hooks/index.d.ts +2 -2
- package/dist/cjs/global-account/react/hooks/index.js +7 -8
- package/dist/cjs/global-account/react/hooks/useAuth.d.ts +2 -1
- package/dist/cjs/global-account/react/hooks/useAuth.js +1 -0
- package/dist/cjs/global-account/react/hooks/useAuthentication.d.ts +2 -1
- package/dist/cjs/global-account/react/hooks/useAuthentication.js +1 -0
- package/dist/cjs/global-account/react/hooks/useFirstEOA.d.ts +4 -7666
- package/dist/cjs/global-account/react/hooks/useFirstEOA.js +1 -0
- package/dist/cjs/global-account/react/hooks/usePortfolioBalance.d.ts +15 -0
- package/dist/cjs/global-account/react/hooks/usePortfolioBalance.js +76 -0
- package/dist/cjs/global-account/react/hooks/usePortfolioCollectibles.d.ts +12 -0
- package/dist/cjs/global-account/react/hooks/usePortfolioCollectibles.js +178 -0
- package/dist/cjs/global-account/react/hooks/useUnifiedChainSwitchAndExecute.d.ts +1 -0
- package/dist/cjs/global-account/react/hooks/useUnifiedChainSwitchAndExecute.js +18 -11
- package/dist/cjs/global-account/react/stores/useModalStore.d.ts +0 -2
- package/dist/cjs/global-account/react/utils/createWagmiConfig.d.ts +2 -2
- package/dist/cjs/shared/constants/chains/b3Chain.d.ts +24 -0
- package/dist/cjs/shared/constants/chains/supported.d.ts +4 -4
- package/dist/cjs/shared/constants/index.d.ts +4 -0
- package/dist/cjs/shared/utils/zerionPositions.d.ts +96 -0
- package/dist/cjs/shared/utils/zerionPositions.js +186 -0
- package/dist/cjs/wallet/utils/createWalletConfig.d.ts +2 -2
- package/dist/esm/anyspend/constants/rpc.d.ts +1 -0
- package/dist/esm/anyspend/constants/rpc.js +2 -0
- package/dist/esm/anyspend/react/components/AnySpendDeposit.d.ts +1 -3
- package/dist/esm/anyspend/react/components/AnySpendDeposit.js +15 -13
- package/dist/esm/anyspend/react/components/AnySpendStakeB3.js +33 -14
- package/dist/esm/anyspend/react/components/AnySpendStakeB3ExactIn.js +33 -11
- package/dist/esm/anyspend/react/components/checkout/CryptoPayPanel.js +18 -10
- package/dist/esm/anyspend/react/components/common/TokenBalance.js +6 -7
- package/dist/esm/anyspend/react/hooks/useSigMint.d.ts +1 -1
- package/dist/esm/anyspend/utils/chain.d.ts +45 -0
- package/dist/esm/anyspend/utils/chain.js +50 -3
- package/dist/esm/global-account/react/components/B3Provider/B3Provider.d.ts +1 -2
- package/dist/esm/global-account/react/components/B3Provider/B3Provider.js +2 -2
- package/dist/esm/global-account/react/components/B3Provider/RelayKitProviderWrapper.d.ts +1 -2
- package/dist/esm/global-account/react/components/B3Provider/RelayKitProviderWrapper.js +4 -5
- package/dist/esm/global-account/react/components/ManageAccount/ContentTokens.js +24 -16
- package/dist/esm/global-account/react/components/ManageAccount/NFTContent.js +3 -3
- package/dist/esm/global-account/react/components/ManageAccount/ProfileSection.js +9 -7
- package/dist/esm/global-account/react/components/ManageAccount/TokenContent.js +18 -9
- package/dist/esm/global-account/react/components/Send/Send.js +16 -12
- package/dist/esm/global-account/react/hooks/index.d.ts +2 -2
- package/dist/esm/global-account/react/hooks/index.js +2 -2
- package/dist/esm/global-account/react/hooks/useAuth.d.ts +2 -1
- package/dist/esm/global-account/react/hooks/useAuth.js +1 -0
- package/dist/esm/global-account/react/hooks/useAuthentication.d.ts +2 -1
- package/dist/esm/global-account/react/hooks/useAuthentication.js +1 -0
- package/dist/esm/global-account/react/hooks/useFirstEOA.d.ts +4 -7666
- package/dist/esm/global-account/react/hooks/useFirstEOA.js +1 -0
- package/dist/esm/global-account/react/hooks/usePortfolioBalance.d.ts +15 -0
- package/dist/esm/global-account/react/hooks/usePortfolioBalance.js +72 -0
- package/dist/esm/global-account/react/hooks/usePortfolioCollectibles.d.ts +12 -0
- package/dist/esm/global-account/react/hooks/usePortfolioCollectibles.js +175 -0
- package/dist/esm/global-account/react/hooks/useUnifiedChainSwitchAndExecute.d.ts +1 -0
- package/dist/esm/global-account/react/hooks/useUnifiedChainSwitchAndExecute.js +18 -11
- package/dist/esm/global-account/react/stores/useModalStore.d.ts +0 -2
- package/dist/esm/global-account/react/utils/createWagmiConfig.d.ts +2 -2
- package/dist/esm/shared/constants/chains/b3Chain.d.ts +24 -0
- package/dist/esm/shared/constants/chains/supported.d.ts +4 -4
- package/dist/esm/shared/constants/index.d.ts +4 -0
- package/dist/esm/shared/utils/zerionPositions.d.ts +96 -0
- package/dist/esm/shared/utils/zerionPositions.js +178 -0
- package/dist/esm/wallet/utils/createWalletConfig.d.ts +2 -2
- package/dist/types/anyspend/constants/rpc.d.ts +1 -0
- package/dist/types/anyspend/react/components/AnySpendDeposit.d.ts +1 -3
- package/dist/types/anyspend/react/hooks/useSigMint.d.ts +1 -1
- package/dist/types/anyspend/utils/chain.d.ts +45 -0
- package/dist/types/global-account/react/components/B3Provider/B3Provider.d.ts +1 -2
- package/dist/types/global-account/react/components/B3Provider/RelayKitProviderWrapper.d.ts +1 -2
- package/dist/types/global-account/react/hooks/index.d.ts +2 -2
- package/dist/types/global-account/react/hooks/useAuth.d.ts +2 -1
- package/dist/types/global-account/react/hooks/useAuthentication.d.ts +2 -1
- package/dist/types/global-account/react/hooks/useFirstEOA.d.ts +4 -7666
- package/dist/types/global-account/react/hooks/usePortfolioBalance.d.ts +15 -0
- package/dist/types/global-account/react/hooks/usePortfolioCollectibles.d.ts +12 -0
- package/dist/types/global-account/react/hooks/useUnifiedChainSwitchAndExecute.d.ts +1 -0
- package/dist/types/global-account/react/stores/useModalStore.d.ts +0 -2
- package/dist/types/global-account/react/utils/createWagmiConfig.d.ts +2 -2
- package/dist/types/shared/constants/chains/b3Chain.d.ts +24 -0
- package/dist/types/shared/constants/chains/supported.d.ts +4 -4
- package/dist/types/shared/constants/index.d.ts +4 -0
- package/dist/types/shared/utils/zerionPositions.d.ts +96 -0
- package/dist/types/wallet/utils/createWalletConfig.d.ts +2 -2
- package/node_modules/@b3/chain-registry/chains.json +29 -1
- package/node_modules/@b3/chain-registry/package.json +18 -2
- package/package.json +2 -2
- package/src/anyspend/README.md +2 -2
- package/src/anyspend/constants/rpc.ts +2 -0
- package/src/anyspend/docs/components.md +6 -6
- package/src/anyspend/docs/contributing.md +1 -1
- package/src/anyspend/docs/hooks.md +3 -3
- package/src/anyspend/docs/installation.md +2 -2
- package/src/anyspend/react/components/AnySpendDeposit.tsx +26 -19
- package/src/anyspend/react/components/AnySpendStakeB3.tsx +55 -14
- package/src/anyspend/react/components/AnySpendStakeB3ExactIn.tsx +55 -11
- package/src/anyspend/react/components/__tests__/QRDeposit.test.tsx +3 -4
- package/src/anyspend/react/components/checkout/CryptoPayPanel.tsx +25 -12
- package/src/anyspend/react/components/common/TokenBalance.tsx +10 -7
- package/src/anyspend/utils/__tests__/deprecatedChains.test.ts +44 -0
- package/src/anyspend/utils/__tests__/toRegistryChainId.test.ts +17 -0
- package/src/anyspend/utils/chain.ts +53 -2
- package/src/global-account/react/components/B3Provider/B3Provider.tsx +1 -3
- package/src/global-account/react/components/B3Provider/RelayKitProviderWrapper.tsx +4 -11
- package/src/global-account/react/components/ManageAccount/ContentTokens.tsx +50 -21
- package/src/global-account/react/components/ManageAccount/NFTContent.tsx +5 -3
- package/src/global-account/react/components/ManageAccount/ProfileSection.tsx +10 -6
- package/src/global-account/react/components/ManageAccount/TokenContent.tsx +22 -9
- package/src/global-account/react/components/Send/Send.tsx +39 -15
- package/src/global-account/react/hooks/__tests__/useUnifiedChainSwitchAndExecute.test.tsx +156 -0
- package/src/global-account/react/hooks/index.ts +2 -2
- package/src/global-account/react/hooks/useAuth.ts +3 -2
- package/src/global-account/react/hooks/useAuthentication.ts +3 -2
- package/src/global-account/react/hooks/useFirstEOA.tsx +9 -2
- package/src/global-account/react/hooks/usePortfolioBalance.ts +89 -0
- package/src/global-account/react/hooks/usePortfolioCollectibles.ts +212 -0
- package/src/global-account/react/hooks/useUnifiedChainSwitchAndExecute.ts +26 -12
- package/src/global-account/react/stores/useModalStore.ts +0 -2
- package/src/shared/constants/index.ts +8 -0
- package/src/shared/utils/__tests__/zerionPositions.test.ts +226 -0
- package/src/shared/utils/zerionPositions.ts +244 -0
- package/dist/cjs/global-account/react/hooks/useSimBalance.d.ts +0 -59
- package/dist/cjs/global-account/react/hooks/useSimBalance.js +0 -98
- package/dist/cjs/global-account/react/hooks/useSimCollectibles.d.ts +0 -45
- package/dist/cjs/global-account/react/hooks/useSimCollectibles.js +0 -190
- package/dist/cjs/global-account/react/utils/simdune.d.ts +0 -7
- package/dist/cjs/global-account/react/utils/simdune.js +0 -21
- package/dist/esm/global-account/react/hooks/useSimBalance.d.ts +0 -59
- package/dist/esm/global-account/react/hooks/useSimBalance.js +0 -93
- package/dist/esm/global-account/react/hooks/useSimCollectibles.d.ts +0 -45
- package/dist/esm/global-account/react/hooks/useSimCollectibles.js +0 -187
- package/dist/esm/global-account/react/utils/simdune.d.ts +0 -7
- package/dist/esm/global-account/react/utils/simdune.js +0 -17
- package/dist/types/global-account/react/hooks/useSimBalance.d.ts +0 -59
- package/dist/types/global-account/react/hooks/useSimCollectibles.d.ts +0 -45
- package/dist/types/global-account/react/utils/simdune.d.ts +0 -7
- package/node_modules/@b3/chain-registry/src/index.ts +0 -169
- package/node_modules/@b3/chain-registry/tsconfig.json +0 -16
- package/src/global-account/react/hooks/useSimBalance.ts +0 -164
- package/src/global-account/react/hooks/useSimCollectibles.ts +0 -238
- package/src/global-account/react/utils/simdune.ts +0 -20
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { useQuery } from "@tanstack/react-query";
|
|
2
|
+
import {
|
|
3
|
+
adaptZerionPositions,
|
|
4
|
+
balanceMatchesToken,
|
|
5
|
+
type B3TokenBalance,
|
|
6
|
+
type ZerionPositionLike,
|
|
7
|
+
} from "@b3dotfun/sdk/shared/utils/zerionPositions";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Wallet holdings, read through the b3 portfolio proxy.
|
|
11
|
+
*
|
|
12
|
+
* The proxy holds the upstream API key server-side and authorizes browsers by Origin allowlist, so
|
|
13
|
+
* nothing secret ships to the client and no key is accepted from it. A consumer served from an
|
|
14
|
+
* origin the proxy does not know will be refused — adding an origin is a proxy config change, not
|
|
15
|
+
* an SDK change.
|
|
16
|
+
*/
|
|
17
|
+
const PORTFOLIO_API_BASE_URL = "https://portfolio-api.b3.fun";
|
|
18
|
+
|
|
19
|
+
interface ZerionPositionsResponse {
|
|
20
|
+
data?: ZerionPositionLike[];
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Fetch every spendable holding for a wallet in one request.
|
|
25
|
+
*
|
|
26
|
+
* Deliberately unpaginated and unfiltered by chain. The positions endpoint returns a wallet's full
|
|
27
|
+
* simple-position list in a single response, and passing a chain filter is actively harmful: the
|
|
28
|
+
* upstream rejects a handful of otherwise-valid chain slugs and fails the WHOLE request when one
|
|
29
|
+
* appears, so a caller asking for five chains can lose all five. Callers narrow the result instead.
|
|
30
|
+
*/
|
|
31
|
+
async function fetchWalletPositions(address: string): Promise<B3TokenBalance[]> {
|
|
32
|
+
const url = `${PORTFOLIO_API_BASE_URL}/v1/wallets/${encodeURIComponent(address)}/positions/?filter[positions]=only_simple`;
|
|
33
|
+
const response = await fetch(url, { headers: { Accept: "application/json" } });
|
|
34
|
+
|
|
35
|
+
if (!response.ok) {
|
|
36
|
+
throw new Error(`Failed to fetch wallet balances: ${response.status} ${response.statusText}`);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const body = (await response.json()) as ZerionPositionsResponse;
|
|
40
|
+
return adaptZerionPositions(body.data ?? []);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Keyed by wallet alone so every consumer of the same wallet shares one fetch and one cache entry.
|
|
45
|
+
* Keying by the requested chain subset instead would refetch per caller and share nothing.
|
|
46
|
+
*/
|
|
47
|
+
function walletPositionsQueryKey(address?: string) {
|
|
48
|
+
return ["walletPositions", address] as const;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Holdings for a wallet, optionally narrowed to specific chains.
|
|
53
|
+
*
|
|
54
|
+
* Read `isError` before reading `data`: a failed read and an empty wallet are different facts, and
|
|
55
|
+
* rendering the first as the second tells the user they hold nothing when nobody knows.
|
|
56
|
+
*/
|
|
57
|
+
export function usePortfolioBalance(address?: string, chainIds?: number[]) {
|
|
58
|
+
const wantedChainIds = chainIds && chainIds.length > 0 ? chainIds : undefined;
|
|
59
|
+
|
|
60
|
+
return useQuery({
|
|
61
|
+
queryKey: walletPositionsQueryKey(address),
|
|
62
|
+
queryFn: () => {
|
|
63
|
+
if (!address) throw new Error("Address is required");
|
|
64
|
+
return fetchWalletPositions(address);
|
|
65
|
+
},
|
|
66
|
+
enabled: Boolean(address),
|
|
67
|
+
select: (balances: B3TokenBalance[]) =>
|
|
68
|
+
wantedChainIds ? balances.filter(balance => wantedChainIds.includes(balance.chain_id)) : balances,
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* One token's balance for a wallet, served from the same per-wallet fetch as {@link usePortfolioBalance}.
|
|
74
|
+
*
|
|
75
|
+
* `tokenAddress` may be a contract address or the chain's native sentinel; matching is
|
|
76
|
+
* case-insensitive because callers pass addresses in whatever casing their source used.
|
|
77
|
+
*/
|
|
78
|
+
export function usePortfolioTokenBalance(walletAddress?: string, tokenAddress?: string, chainId?: number) {
|
|
79
|
+
return useQuery({
|
|
80
|
+
queryKey: walletPositionsQueryKey(walletAddress),
|
|
81
|
+
queryFn: () => {
|
|
82
|
+
if (!walletAddress) throw new Error("Address is required");
|
|
83
|
+
return fetchWalletPositions(walletAddress);
|
|
84
|
+
},
|
|
85
|
+
enabled: Boolean(walletAddress) && Boolean(tokenAddress) && Boolean(chainId),
|
|
86
|
+
select: (balances: B3TokenBalance[]) =>
|
|
87
|
+
balances.find(balance => balance.chain_id === chainId && balanceMatchesToken(balance, tokenAddress ?? "")),
|
|
88
|
+
});
|
|
89
|
+
}
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
import type { NFT, SimpleHashNFTResponse } from "@b3dotfun/sdk/global-account/types/simplehash.types";
|
|
2
|
+
import { zerionSlugToChainId } from "@b3dotfun/sdk/shared/utils/zerionPositions";
|
|
3
|
+
import { useQuery } from "@tanstack/react-query";
|
|
4
|
+
|
|
5
|
+
const PORTFOLIO_API_BASE_URL = "https://portfolio-api.b3.fun";
|
|
6
|
+
|
|
7
|
+
interface ZerionNftPosition {
|
|
8
|
+
id?: string;
|
|
9
|
+
attributes?: {
|
|
10
|
+
amount?: string;
|
|
11
|
+
changed_at?: string;
|
|
12
|
+
nft_info?: {
|
|
13
|
+
contract_address?: string;
|
|
14
|
+
token_id?: string;
|
|
15
|
+
name?: string;
|
|
16
|
+
interface?: string;
|
|
17
|
+
content?: { preview?: { url?: string }; detail?: { url?: string } };
|
|
18
|
+
flags?: { is_spam?: boolean };
|
|
19
|
+
};
|
|
20
|
+
collection_info?: {
|
|
21
|
+
name?: string;
|
|
22
|
+
description?: string;
|
|
23
|
+
content?: { icon?: { url?: string } };
|
|
24
|
+
};
|
|
25
|
+
};
|
|
26
|
+
relationships?: { chain?: { data?: { id?: string } } };
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
interface ZerionNftPositionsResponse {
|
|
30
|
+
data?: ZerionNftPosition[];
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** Caps an ERC-1155 balance string at MAX_SAFE_INTEGER so a huge supply cannot overflow. */
|
|
34
|
+
function safeParseBalance(balance: string | undefined): number {
|
|
35
|
+
if (!balance) return 1;
|
|
36
|
+
const parsed = parseInt(balance, 10);
|
|
37
|
+
if (Number.isNaN(parsed)) return 1;
|
|
38
|
+
return Math.min(parsed, Number.MAX_SAFE_INTEGER);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Reshape one Zerion NFT position into the SimpleHash record `AccountAssets` renders.
|
|
43
|
+
*
|
|
44
|
+
* Fields Zerion does not carry — traits, rarity, royalties, sale history — stay empty rather than
|
|
45
|
+
* being synthesised from something adjacent. In particular the position's `price` is a current
|
|
46
|
+
* estimate, not a sale, so it must not be written into `last_sale`.
|
|
47
|
+
*/
|
|
48
|
+
function toSimpleHashNft(position: ZerionNftPosition, ownerAddress: string): NFT | null {
|
|
49
|
+
const attributes = position.attributes ?? {};
|
|
50
|
+
const nftInfo = attributes.nft_info;
|
|
51
|
+
const slug = position.relationships?.chain?.data?.id;
|
|
52
|
+
if (!nftInfo?.contract_address || !nftInfo.token_id || !slug) return null;
|
|
53
|
+
|
|
54
|
+
const imageUrl = nftInfo.content?.detail?.url ?? nftInfo.content?.preview?.url ?? "";
|
|
55
|
+
const collectionInfo = attributes.collection_info ?? {};
|
|
56
|
+
const acquiredAt = attributes.changed_at ?? "";
|
|
57
|
+
|
|
58
|
+
return {
|
|
59
|
+
nft_id: `${slug}.${nftInfo.contract_address}.${nftInfo.token_id}`,
|
|
60
|
+
chain: slug,
|
|
61
|
+
contract_address: nftInfo.contract_address,
|
|
62
|
+
token_id: nftInfo.token_id,
|
|
63
|
+
name: nftInfo.name || "",
|
|
64
|
+
description: collectionInfo.description || "",
|
|
65
|
+
previews: {
|
|
66
|
+
image_small_url: nftInfo.content?.preview?.url ?? imageUrl,
|
|
67
|
+
image_medium_url: imageUrl,
|
|
68
|
+
image_large_url: imageUrl,
|
|
69
|
+
image_opengraph_url: imageUrl,
|
|
70
|
+
blurhash: "",
|
|
71
|
+
predominant_color: "",
|
|
72
|
+
},
|
|
73
|
+
image_url: imageUrl,
|
|
74
|
+
image_properties: { width: 0, height: 0, size: 0, mime_type: "", exif_orientation: null },
|
|
75
|
+
video_url: null,
|
|
76
|
+
video_properties: null,
|
|
77
|
+
audio_url: null,
|
|
78
|
+
audio_properties: null,
|
|
79
|
+
model_url: null,
|
|
80
|
+
model_properties: null,
|
|
81
|
+
other_url: null,
|
|
82
|
+
other_properties: null,
|
|
83
|
+
background_color: null,
|
|
84
|
+
external_url: null,
|
|
85
|
+
created_date: "",
|
|
86
|
+
status: "minted",
|
|
87
|
+
token_count: 1,
|
|
88
|
+
owner_count: 1,
|
|
89
|
+
owners: [
|
|
90
|
+
{
|
|
91
|
+
owner_address: ownerAddress,
|
|
92
|
+
quantity: safeParseBalance(attributes.amount),
|
|
93
|
+
quantity_string: attributes.amount || "1",
|
|
94
|
+
first_acquired_date: acquiredAt,
|
|
95
|
+
last_acquired_date: acquiredAt,
|
|
96
|
+
},
|
|
97
|
+
],
|
|
98
|
+
contract: {
|
|
99
|
+
type: nftInfo.interface === "ERC1155" ? "ERC1155" : "ERC721",
|
|
100
|
+
name: collectionInfo.name || "",
|
|
101
|
+
symbol: null,
|
|
102
|
+
deployed_by: "",
|
|
103
|
+
deployed_via_contract: "",
|
|
104
|
+
owned_by: "",
|
|
105
|
+
has_multiple_collections: false,
|
|
106
|
+
},
|
|
107
|
+
collection: {
|
|
108
|
+
collection_id: nftInfo.contract_address,
|
|
109
|
+
name: collectionInfo.name || "Unknown Collection",
|
|
110
|
+
description: collectionInfo.description ?? null,
|
|
111
|
+
image_url: collectionInfo.content?.icon?.url ?? "",
|
|
112
|
+
image_properties: { width: 0, height: 0, mime_type: "" },
|
|
113
|
+
banner_image_url: null,
|
|
114
|
+
category: null,
|
|
115
|
+
is_nsfw: null,
|
|
116
|
+
external_url: null,
|
|
117
|
+
twitter_username: null,
|
|
118
|
+
discord_url: null,
|
|
119
|
+
instagram_username: null,
|
|
120
|
+
medium_username: null,
|
|
121
|
+
telegram_url: null,
|
|
122
|
+
marketplace_pages: [],
|
|
123
|
+
metaplex_mint: null,
|
|
124
|
+
metaplex_candy_machine: null,
|
|
125
|
+
metaplex_first_verified_creator: null,
|
|
126
|
+
floor_prices: [],
|
|
127
|
+
top_bids: [],
|
|
128
|
+
distinct_owner_count: 0,
|
|
129
|
+
distinct_nft_count: 0,
|
|
130
|
+
total_quantity: 0,
|
|
131
|
+
chains: [slug],
|
|
132
|
+
top_contracts: [nftInfo.contract_address],
|
|
133
|
+
collection_royalties: [],
|
|
134
|
+
},
|
|
135
|
+
last_sale: null,
|
|
136
|
+
primary_sale: null,
|
|
137
|
+
first_created: {
|
|
138
|
+
minted_to: ownerAddress,
|
|
139
|
+
quantity: 1,
|
|
140
|
+
quantity_string: "1",
|
|
141
|
+
timestamp: "",
|
|
142
|
+
block_number: 0,
|
|
143
|
+
transaction: "",
|
|
144
|
+
transaction_initiator: "",
|
|
145
|
+
},
|
|
146
|
+
rarity: { rank: null, score: null, unique_attributes: null },
|
|
147
|
+
royalty: [],
|
|
148
|
+
extra_metadata: {
|
|
149
|
+
attributes: [],
|
|
150
|
+
image_original_url: imageUrl,
|
|
151
|
+
animation_original_url: null,
|
|
152
|
+
metadata_original_url: "",
|
|
153
|
+
},
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* NFTs held by a wallet, in the SimpleHash shape `AccountAssets` already renders.
|
|
159
|
+
*
|
|
160
|
+
* Chain narrowing happens here rather than in the request: the upstream rejects some valid chain
|
|
161
|
+
* slugs outright and fails the whole request when one is present, so asking for a subset can cost
|
|
162
|
+
* you the whole answer.
|
|
163
|
+
*/
|
|
164
|
+
export function usePortfolioCollectibles(
|
|
165
|
+
address?: string,
|
|
166
|
+
chainIds?: number[],
|
|
167
|
+
options?: { limit?: number; filterSpam?: boolean },
|
|
168
|
+
) {
|
|
169
|
+
const limit = options?.limit;
|
|
170
|
+
const filterSpam = options?.filterSpam;
|
|
171
|
+
const wantedChainIds = chainIds && chainIds.length > 0 ? chainIds : undefined;
|
|
172
|
+
|
|
173
|
+
return useQuery({
|
|
174
|
+
queryKey: ["walletCollectibles", address, limit, filterSpam],
|
|
175
|
+
queryFn: async (): Promise<SimpleHashNFTResponse> => {
|
|
176
|
+
if (!address) throw new Error("Address is required");
|
|
177
|
+
|
|
178
|
+
const params = new URLSearchParams();
|
|
179
|
+
if (limit) params.set("page[size]", String(limit));
|
|
180
|
+
const query = params.toString();
|
|
181
|
+
const url = `${PORTFOLIO_API_BASE_URL}/v1/wallets/${encodeURIComponent(address)}/nft-positions/${query ? `?${query}` : ""}`;
|
|
182
|
+
|
|
183
|
+
const response = await fetch(url, { headers: { Accept: "application/json" } });
|
|
184
|
+
if (!response.ok) {
|
|
185
|
+
throw new Error(`Failed to fetch collectibles: ${response.status} ${response.statusText}`);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
const body = (await response.json()) as ZerionNftPositionsResponse;
|
|
189
|
+
// Spam is dropped here, not in `select`: the flag lives on the raw position and the
|
|
190
|
+
// SimpleHash shape has nowhere to carry it downstream.
|
|
191
|
+
const nfts = (body.data ?? [])
|
|
192
|
+
.filter(position => !(filterSpam && position.attributes?.nft_info?.flags?.is_spam === true))
|
|
193
|
+
.map(position => toSimpleHashNft(position, address))
|
|
194
|
+
.filter((nft): nft is NFT => nft !== null);
|
|
195
|
+
|
|
196
|
+
return { next_cursor: null, next: null, previous: null, nfts };
|
|
197
|
+
},
|
|
198
|
+
enabled: Boolean(address),
|
|
199
|
+
staleTime: 30 * 1000,
|
|
200
|
+
gcTime: 5 * 60 * 1000,
|
|
201
|
+
select: (data: SimpleHashNFTResponse) => {
|
|
202
|
+
if (!wantedChainIds) return data;
|
|
203
|
+
|
|
204
|
+
const all = data.nfts ?? [];
|
|
205
|
+
const nfts = all.filter(nft => {
|
|
206
|
+
const chainId = zerionSlugToChainId(nft.chain);
|
|
207
|
+
return chainId !== undefined && wantedChainIds.includes(chainId);
|
|
208
|
+
});
|
|
209
|
+
return nfts.length === all.length ? data : { ...data, nfts };
|
|
210
|
+
},
|
|
211
|
+
});
|
|
212
|
+
}
|
|
@@ -10,7 +10,6 @@ import { prepareTransaction, sendTransaction as twSendTransaction } from "thirdw
|
|
|
10
10
|
import { isAddress } from "viem";
|
|
11
11
|
import { useSwitchChain } from "wagmi";
|
|
12
12
|
import { useB3Config } from "../components/B3Provider/B3ConfigProvider";
|
|
13
|
-
import { useB3Account } from "../components/B3Provider/useB3Account";
|
|
14
13
|
import { useAccountWallet } from "./useAccountWallet";
|
|
15
14
|
|
|
16
15
|
export interface UnifiedTransactionParams {
|
|
@@ -23,8 +22,7 @@ export function useUnifiedChainSwitchAndExecute() {
|
|
|
23
22
|
const { switchChainAsync } = useSwitchChain();
|
|
24
23
|
const [isSwitchingOrExecuting, setIsSwitchingOrExecuting] = useState(false);
|
|
25
24
|
|
|
26
|
-
const { isActiveSmartWallet, isActiveEOAWallet, connectedEOAWallet } = useAccountWallet();
|
|
27
|
-
const aaAccount = useB3Account();
|
|
25
|
+
const { isActiveSmartWallet, isActiveEOAWallet, connectedSmartWallet, connectedEOAWallet } = useAccountWallet();
|
|
28
26
|
|
|
29
27
|
// From context, not process.env: partnerId is hashed into the server intent key and must match the ecosystem wallet.
|
|
30
28
|
const { partnerId } = useB3Config();
|
|
@@ -119,11 +117,26 @@ export function useUnifiedChainSwitchAndExecute() {
|
|
|
119
117
|
[connectedEOAWallet, switchChainAsync],
|
|
120
118
|
);
|
|
121
119
|
|
|
122
|
-
// Handle
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
120
|
+
// Handle Global Account transaction (no chain switch needed for AA).
|
|
121
|
+
// Resolve the signer from the connected ecosystem wallet rather than the active
|
|
122
|
+
// wallet so callers can explicitly require the Global Account.
|
|
123
|
+
const handleGlobalAccountSendTransaction = useCallback(
|
|
124
|
+
async (
|
|
125
|
+
targetChainId: number,
|
|
126
|
+
params: UnifiedTransactionParams,
|
|
127
|
+
expectedGlobalAccountAddress?: string,
|
|
128
|
+
): Promise<string | undefined> => {
|
|
129
|
+
const globalAccount = connectedSmartWallet?.getAccount();
|
|
130
|
+
if (!globalAccount) {
|
|
131
|
+
toast.error("Global Account not connected");
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
if (
|
|
136
|
+
expectedGlobalAccountAddress &&
|
|
137
|
+
globalAccount.address.toLowerCase() !== expectedGlobalAccountAddress.toLowerCase()
|
|
138
|
+
) {
|
|
139
|
+
toast.error("Connected Global Account does not match the authenticated account");
|
|
127
140
|
return;
|
|
128
141
|
}
|
|
129
142
|
|
|
@@ -162,7 +175,7 @@ export function useUnifiedChainSwitchAndExecute() {
|
|
|
162
175
|
toast.info("Sending transaction…");
|
|
163
176
|
const start = performance.now();
|
|
164
177
|
const sendTxResponse = await twSendTransaction({
|
|
165
|
-
account:
|
|
178
|
+
account: globalAccount,
|
|
166
179
|
transaction,
|
|
167
180
|
});
|
|
168
181
|
const end = performance.now();
|
|
@@ -178,7 +191,7 @@ export function useUnifiedChainSwitchAndExecute() {
|
|
|
178
191
|
setIsSwitchingOrExecuting(false);
|
|
179
192
|
}
|
|
180
193
|
},
|
|
181
|
-
[
|
|
194
|
+
[connectedSmartWallet, partnerId],
|
|
182
195
|
);
|
|
183
196
|
|
|
184
197
|
// Unified switch chain and execute function
|
|
@@ -186,15 +199,16 @@ export function useUnifiedChainSwitchAndExecute() {
|
|
|
186
199
|
async (targetChainId: number, params: UnifiedTransactionParams): Promise<string | undefined> => {
|
|
187
200
|
// Check which wallet type is active
|
|
188
201
|
if (isActiveSmartWallet) {
|
|
189
|
-
return
|
|
202
|
+
return handleGlobalAccountSendTransaction(targetChainId, params);
|
|
190
203
|
}
|
|
191
204
|
return handleEOASwitchChainAndSendTransaction(targetChainId, params);
|
|
192
205
|
},
|
|
193
|
-
[isActiveSmartWallet,
|
|
206
|
+
[isActiveSmartWallet, handleGlobalAccountSendTransaction, handleEOASwitchChainAndSendTransaction],
|
|
194
207
|
);
|
|
195
208
|
|
|
196
209
|
return {
|
|
197
210
|
switchChainAndExecute,
|
|
211
|
+
switchChainAndExecuteWithGlobalAccount: handleGlobalAccountSendTransaction,
|
|
198
212
|
switchChainAndExecuteWithEOA: handleEOASwitchChainAndSendTransaction,
|
|
199
213
|
isSwitchingOrExecuting,
|
|
200
214
|
isActiveSmartWallet,
|
|
@@ -675,8 +675,6 @@ export interface AnySpendDepositModalProps extends BaseModalProps {
|
|
|
675
675
|
showChainSelection?: boolean;
|
|
676
676
|
/** Whether to show the "Fund with Fiat" option in the deposit options list. Defaults to true */
|
|
677
677
|
showFiatOption?: boolean;
|
|
678
|
-
/** Minimum pool size for filtering tokens (default: 1,000,000) */
|
|
679
|
-
minPoolSize?: number;
|
|
680
678
|
/** Custom title for chain selection step */
|
|
681
679
|
chainSelectionTitle?: string;
|
|
682
680
|
/** Custom description for chain selection step */
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import type { Wallet } from "thirdweb/wallets";
|
|
2
|
+
|
|
1
3
|
export * from "./currency";
|
|
2
4
|
|
|
3
5
|
export const siteURL = "https://basement.fun";
|
|
@@ -8,6 +10,12 @@ export const ecosystemWalletId = (process.env.NEXT_PUBLIC_THIRDWEB_ECOSYSTEM_ID
|
|
|
8
10
|
process.env.EXPO_PUBLIC_THIRDWEB_ECOSYSTEM_ID ||
|
|
9
11
|
"ecosystem.b3-open-gaming") as `ecosystem.${string}`; // Fallback to prod key lookup
|
|
10
12
|
|
|
13
|
+
// Portable equivalent of thirdweb's `EcosystemWallet`, which thirdweb does not export
|
|
14
|
+
// from any public entry point — declaration emit cannot name the internal type (TS2742).
|
|
15
|
+
export type EcosystemWallet = Wallet<`ecosystem.${string}`> & {
|
|
16
|
+
getAuthToken: () => string | null;
|
|
17
|
+
};
|
|
18
|
+
|
|
11
19
|
export const tokenIcons = {
|
|
12
20
|
ETH: "https://cdn.b3.fun/ethereum.svg",
|
|
13
21
|
WETH: "https://cdn.b3.fun/wrapped-ethereum.svg",
|
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import {
|
|
3
|
+
adaptZerionPositions,
|
|
4
|
+
chainIdToZerionSlug,
|
|
5
|
+
EVM_NATIVE_TOKEN_ADDRESS,
|
|
6
|
+
zerionSlugToChainId,
|
|
7
|
+
type ZerionPositionLike,
|
|
8
|
+
} from "../zerionPositions";
|
|
9
|
+
|
|
10
|
+
// Slugs and ids below are shaped after live Zerion responses.
|
|
11
|
+
const SLUG_TO_CHAIN_ID: Record<string, number> = {
|
|
12
|
+
ethereum: 1,
|
|
13
|
+
base: 8453,
|
|
14
|
+
polygon: 137,
|
|
15
|
+
celo: 42220,
|
|
16
|
+
hyperevm: 999,
|
|
17
|
+
};
|
|
18
|
+
const slugToChainId = (slug: string) => SLUG_TO_CHAIN_ID[slug];
|
|
19
|
+
|
|
20
|
+
function position(overrides: {
|
|
21
|
+
id: string;
|
|
22
|
+
slug: string;
|
|
23
|
+
symbol: string;
|
|
24
|
+
implAddress?: string | null;
|
|
25
|
+
implDecimals?: number;
|
|
26
|
+
quantityDecimals?: number;
|
|
27
|
+
int?: string;
|
|
28
|
+
}): ZerionPositionLike {
|
|
29
|
+
const { id, slug, symbol, implAddress = null, implDecimals = 18, quantityDecimals = 18, int = "1000" } = overrides;
|
|
30
|
+
return {
|
|
31
|
+
id,
|
|
32
|
+
attributes: {
|
|
33
|
+
quantity: { int, decimals: quantityDecimals },
|
|
34
|
+
fungible_info: {
|
|
35
|
+
symbol,
|
|
36
|
+
implementations: [{ chain_id: slug, address: implAddress, decimals: implDecimals }],
|
|
37
|
+
},
|
|
38
|
+
},
|
|
39
|
+
relationships: { chain: { data: { id: slug } } },
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
describe("adaptZerionPositions native detection", () => {
|
|
44
|
+
it("treats a null implementation address as the chain's native coin", () => {
|
|
45
|
+
const [row] = adaptZerionPositions(
|
|
46
|
+
[position({ id: "base-base-asset-asset", slug: "base", symbol: "ETH" })],
|
|
47
|
+
slugToChainId,
|
|
48
|
+
);
|
|
49
|
+
|
|
50
|
+
expect(row.is_native).toBe(true);
|
|
51
|
+
expect(row.address).toBe(EVM_NATIVE_TOKEN_ADDRESS);
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it("treats Polygon's native coin as native despite its system-contract address", () => {
|
|
55
|
+
const [row] = adaptZerionPositions(
|
|
56
|
+
[
|
|
57
|
+
position({
|
|
58
|
+
id: "base-polygon-asset-asset",
|
|
59
|
+
slug: "polygon",
|
|
60
|
+
symbol: "POL",
|
|
61
|
+
implAddress: "0x0000000000000000000000000000000000001010",
|
|
62
|
+
}),
|
|
63
|
+
],
|
|
64
|
+
slugToChainId,
|
|
65
|
+
);
|
|
66
|
+
|
|
67
|
+
expect(row.is_native).toBe(true);
|
|
68
|
+
expect(row.address).toBe(EVM_NATIVE_TOKEN_ADDRESS);
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it("treats Celo's native coin as native despite its ERC-20 address", () => {
|
|
72
|
+
const [row] = adaptZerionPositions(
|
|
73
|
+
[
|
|
74
|
+
position({
|
|
75
|
+
id: "base-celo-asset-asset",
|
|
76
|
+
slug: "celo",
|
|
77
|
+
symbol: "celo",
|
|
78
|
+
implAddress: "0x471ece3750da237f93b8e339c536989b8978a438",
|
|
79
|
+
}),
|
|
80
|
+
],
|
|
81
|
+
slugToChainId,
|
|
82
|
+
);
|
|
83
|
+
|
|
84
|
+
expect(row.is_native).toBe(true);
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
it("keeps a same-symbol non-native fungible distinct from the native coin", () => {
|
|
88
|
+
const rows = adaptZerionPositions(
|
|
89
|
+
[
|
|
90
|
+
position({
|
|
91
|
+
id: "base-polygon-asset-asset",
|
|
92
|
+
slug: "polygon",
|
|
93
|
+
symbol: "POL",
|
|
94
|
+
implAddress: "0x0000000000000000000000000000000000001010",
|
|
95
|
+
}),
|
|
96
|
+
position({
|
|
97
|
+
id: "0x89299a4c719ebe9fe6018aed81eb1d48b4f78d56-polygon-asset-asset",
|
|
98
|
+
slug: "polygon",
|
|
99
|
+
symbol: "POL",
|
|
100
|
+
implAddress: "0x89299a4c719ebe9fe6018aed81eb1d48b4f78d56",
|
|
101
|
+
}),
|
|
102
|
+
],
|
|
103
|
+
slugToChainId,
|
|
104
|
+
);
|
|
105
|
+
|
|
106
|
+
expect(rows.map(r => r.is_native)).toEqual([true, false]);
|
|
107
|
+
expect(rows[1].address).toBe("0x89299a4c719ebe9fe6018aed81eb1d48b4f78d56");
|
|
108
|
+
});
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
describe("adaptZerionPositions row filtering", () => {
|
|
112
|
+
it("drops a row whose chain is unmapped", () => {
|
|
113
|
+
const rows = adaptZerionPositions(
|
|
114
|
+
[position({ id: "base-unknownchain-asset-asset", slug: "unknownchain", symbol: "???" })],
|
|
115
|
+
slugToChainId,
|
|
116
|
+
);
|
|
117
|
+
|
|
118
|
+
expect(rows).toEqual([]);
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
it("drops a row whose fungible has no implementation on its chain, rather than calling it native", () => {
|
|
122
|
+
const rows = adaptZerionPositions(
|
|
123
|
+
[
|
|
124
|
+
{
|
|
125
|
+
id: "0xabc-ethereum-asset-asset",
|
|
126
|
+
attributes: {
|
|
127
|
+
quantity: { int: "1000", decimals: 18 },
|
|
128
|
+
fungible_info: {
|
|
129
|
+
symbol: "ORPHAN",
|
|
130
|
+
// implementations exist, but none for the position's own chain
|
|
131
|
+
implementations: [{ chain_id: "polygon", address: "0xabc", decimals: 18 }],
|
|
132
|
+
},
|
|
133
|
+
},
|
|
134
|
+
relationships: { chain: { data: { id: "ethereum" } } },
|
|
135
|
+
},
|
|
136
|
+
],
|
|
137
|
+
slugToChainId,
|
|
138
|
+
);
|
|
139
|
+
|
|
140
|
+
expect(rows).toEqual([]);
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
it("drops a row that carries no quantity rather than reporting a zero balance", () => {
|
|
144
|
+
const rows = adaptZerionPositions(
|
|
145
|
+
[
|
|
146
|
+
{
|
|
147
|
+
id: "base-base-asset-asset",
|
|
148
|
+
attributes: {
|
|
149
|
+
quantity: { decimals: 18 },
|
|
150
|
+
fungible_info: { symbol: "ETH", implementations: [{ chain_id: "base", address: null, decimals: 18 }] },
|
|
151
|
+
},
|
|
152
|
+
relationships: { chain: { data: { id: "base" } } },
|
|
153
|
+
},
|
|
154
|
+
],
|
|
155
|
+
slugToChainId,
|
|
156
|
+
);
|
|
157
|
+
|
|
158
|
+
expect(rows).toEqual([]);
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
it("drops a row that carries no decimals rather than guessing a scale", () => {
|
|
162
|
+
const rows = adaptZerionPositions(
|
|
163
|
+
[
|
|
164
|
+
{
|
|
165
|
+
id: "0xabc-ethereum-asset-asset",
|
|
166
|
+
attributes: {
|
|
167
|
+
quantity: { int: "1000" },
|
|
168
|
+
fungible_info: { symbol: "MYSTERY", implementations: [{ chain_id: "ethereum", address: "0xabc" }] },
|
|
169
|
+
},
|
|
170
|
+
relationships: { chain: { data: { id: "ethereum" } } },
|
|
171
|
+
},
|
|
172
|
+
],
|
|
173
|
+
slugToChainId,
|
|
174
|
+
);
|
|
175
|
+
|
|
176
|
+
expect(rows).toEqual([]);
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
it("drops trash and non-displayable rows", () => {
|
|
180
|
+
const base = position({ id: "0xspam-ethereum-asset-asset", slug: "ethereum", symbol: "SPAM" });
|
|
181
|
+
const spam: ZerionPositionLike = { ...base, attributes: { ...base.attributes, flags: { is_trash: true } } };
|
|
182
|
+
const hidden: ZerionPositionLike = {
|
|
183
|
+
...base,
|
|
184
|
+
attributes: { ...base.attributes, flags: { displayable: false } },
|
|
185
|
+
};
|
|
186
|
+
|
|
187
|
+
expect(adaptZerionPositions([spam, hidden], slugToChainId)).toEqual([]);
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
it("drops any quantity that is not a base-10 integer, since all of them format to NaN", () => {
|
|
191
|
+
const rows = adaptZerionPositions(
|
|
192
|
+
[
|
|
193
|
+
position({ id: "0xa-ethereum-asset-asset", slug: "ethereum", symbol: "A", int: "" }),
|
|
194
|
+
position({ id: "0xb-ethereum-asset-asset", slug: "ethereum", symbol: "B", int: null as unknown as string }),
|
|
195
|
+
position({ id: "0xc-ethereum-asset-asset", slug: "ethereum", symbol: "C", int: "not-a-number" }),
|
|
196
|
+
position({ id: "0xd-ethereum-asset-asset", slug: "ethereum", symbol: "D", int: "0x1f" }),
|
|
197
|
+
position({ id: "0xe-ethereum-asset-asset", slug: "ethereum", symbol: "E", int: "1.5" }),
|
|
198
|
+
],
|
|
199
|
+
slugToChainId,
|
|
200
|
+
);
|
|
201
|
+
|
|
202
|
+
expect(rows).toEqual([]);
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
it("keeps a genuine zero balance", () => {
|
|
206
|
+
const [row] = adaptZerionPositions(
|
|
207
|
+
[position({ id: "base-ethereum-asset-asset", slug: "ethereum", symbol: "ETH", int: "0" })],
|
|
208
|
+
slugToChainId,
|
|
209
|
+
);
|
|
210
|
+
|
|
211
|
+
expect(row.amount).toBe("0");
|
|
212
|
+
});
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
describe("zerionSlugToChainId", () => {
|
|
216
|
+
it("reports an inherited object key as an unknown chain", () => {
|
|
217
|
+
// Slugs come off the wire; an object lookup would answer `constructor` with a function.
|
|
218
|
+
expect(zerionSlugToChainId("constructor")).toBeUndefined();
|
|
219
|
+
expect(zerionSlugToChainId("toString")).toBeUndefined();
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
it("still resolves a real slug", () => {
|
|
223
|
+
expect(zerionSlugToChainId("ethereum")).toBe(1);
|
|
224
|
+
expect(chainIdToZerionSlug(8453)).toBe("base");
|
|
225
|
+
});
|
|
226
|
+
});
|