@b3dotfun/sdk 0.1.70-alpha.18 → 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.
Files changed (155) hide show
  1. package/README.md +3 -3
  2. package/dist/cjs/anyspend/constants/rpc.d.ts +1 -0
  3. package/dist/cjs/anyspend/constants/rpc.js +3 -1
  4. package/dist/cjs/anyspend/react/components/AnySpendDeposit.d.ts +1 -3
  5. package/dist/cjs/anyspend/react/components/AnySpendDeposit.js +14 -12
  6. package/dist/cjs/anyspend/react/components/AnySpendStakeB3.js +32 -13
  7. package/dist/cjs/anyspend/react/components/AnySpendStakeB3ExactIn.js +32 -10
  8. package/dist/cjs/anyspend/react/components/checkout/CryptoPayPanel.js +16 -8
  9. package/dist/cjs/anyspend/react/components/common/TokenBalance.js +4 -5
  10. package/dist/cjs/anyspend/react/hooks/useSigMint.d.ts +1 -1
  11. package/dist/cjs/anyspend/utils/chain.d.ts +45 -0
  12. package/dist/cjs/anyspend/utils/chain.js +51 -2
  13. package/dist/cjs/global-account/react/components/B3Provider/B3Provider.d.ts +1 -2
  14. package/dist/cjs/global-account/react/components/B3Provider/B3Provider.js +2 -2
  15. package/dist/cjs/global-account/react/components/B3Provider/RelayKitProviderWrapper.d.ts +1 -2
  16. package/dist/cjs/global-account/react/components/B3Provider/RelayKitProviderWrapper.js +4 -5
  17. package/dist/cjs/global-account/react/components/ManageAccount/ContentTokens.js +23 -15
  18. package/dist/cjs/global-account/react/components/ManageAccount/NFTContent.js +2 -2
  19. package/dist/cjs/global-account/react/components/ManageAccount/ProfileSection.js +8 -6
  20. package/dist/cjs/global-account/react/components/ManageAccount/TokenContent.js +17 -8
  21. package/dist/cjs/global-account/react/components/Send/Send.js +15 -11
  22. package/dist/cjs/global-account/react/hooks/index.d.ts +2 -2
  23. package/dist/cjs/global-account/react/hooks/index.js +7 -8
  24. package/dist/cjs/global-account/react/hooks/useAuth.d.ts +2 -1
  25. package/dist/cjs/global-account/react/hooks/useAuth.js +1 -0
  26. package/dist/cjs/global-account/react/hooks/useAuthentication.d.ts +2 -1
  27. package/dist/cjs/global-account/react/hooks/useAuthentication.js +1 -0
  28. package/dist/cjs/global-account/react/hooks/useFirstEOA.d.ts +4 -7666
  29. package/dist/cjs/global-account/react/hooks/useFirstEOA.js +1 -0
  30. package/dist/cjs/global-account/react/hooks/usePortfolioBalance.d.ts +15 -0
  31. package/dist/cjs/global-account/react/hooks/usePortfolioBalance.js +76 -0
  32. package/dist/cjs/global-account/react/hooks/usePortfolioCollectibles.d.ts +12 -0
  33. package/dist/cjs/global-account/react/hooks/usePortfolioCollectibles.js +178 -0
  34. package/dist/cjs/global-account/react/stores/useModalStore.d.ts +0 -2
  35. package/dist/cjs/global-account/react/utils/createWagmiConfig.d.ts +2 -2
  36. package/dist/cjs/shared/constants/chains/b3Chain.d.ts +24 -0
  37. package/dist/cjs/shared/constants/chains/supported.d.ts +4 -4
  38. package/dist/cjs/shared/constants/index.d.ts +4 -0
  39. package/dist/cjs/shared/utils/zerionPositions.d.ts +96 -0
  40. package/dist/cjs/shared/utils/zerionPositions.js +186 -0
  41. package/dist/cjs/wallet/utils/createWalletConfig.d.ts +2 -2
  42. package/dist/esm/anyspend/constants/rpc.d.ts +1 -0
  43. package/dist/esm/anyspend/constants/rpc.js +2 -0
  44. package/dist/esm/anyspend/react/components/AnySpendDeposit.d.ts +1 -3
  45. package/dist/esm/anyspend/react/components/AnySpendDeposit.js +15 -13
  46. package/dist/esm/anyspend/react/components/AnySpendStakeB3.js +33 -14
  47. package/dist/esm/anyspend/react/components/AnySpendStakeB3ExactIn.js +33 -11
  48. package/dist/esm/anyspend/react/components/checkout/CryptoPayPanel.js +18 -10
  49. package/dist/esm/anyspend/react/components/common/TokenBalance.js +6 -7
  50. package/dist/esm/anyspend/react/hooks/useSigMint.d.ts +1 -1
  51. package/dist/esm/anyspend/utils/chain.d.ts +45 -0
  52. package/dist/esm/anyspend/utils/chain.js +50 -3
  53. package/dist/esm/global-account/react/components/B3Provider/B3Provider.d.ts +1 -2
  54. package/dist/esm/global-account/react/components/B3Provider/B3Provider.js +2 -2
  55. package/dist/esm/global-account/react/components/B3Provider/RelayKitProviderWrapper.d.ts +1 -2
  56. package/dist/esm/global-account/react/components/B3Provider/RelayKitProviderWrapper.js +4 -5
  57. package/dist/esm/global-account/react/components/ManageAccount/ContentTokens.js +24 -16
  58. package/dist/esm/global-account/react/components/ManageAccount/NFTContent.js +3 -3
  59. package/dist/esm/global-account/react/components/ManageAccount/ProfileSection.js +9 -7
  60. package/dist/esm/global-account/react/components/ManageAccount/TokenContent.js +18 -9
  61. package/dist/esm/global-account/react/components/Send/Send.js +16 -12
  62. package/dist/esm/global-account/react/hooks/index.d.ts +2 -2
  63. package/dist/esm/global-account/react/hooks/index.js +2 -2
  64. package/dist/esm/global-account/react/hooks/useAuth.d.ts +2 -1
  65. package/dist/esm/global-account/react/hooks/useAuth.js +1 -0
  66. package/dist/esm/global-account/react/hooks/useAuthentication.d.ts +2 -1
  67. package/dist/esm/global-account/react/hooks/useAuthentication.js +1 -0
  68. package/dist/esm/global-account/react/hooks/useFirstEOA.d.ts +4 -7666
  69. package/dist/esm/global-account/react/hooks/useFirstEOA.js +1 -0
  70. package/dist/esm/global-account/react/hooks/usePortfolioBalance.d.ts +15 -0
  71. package/dist/esm/global-account/react/hooks/usePortfolioBalance.js +72 -0
  72. package/dist/esm/global-account/react/hooks/usePortfolioCollectibles.d.ts +12 -0
  73. package/dist/esm/global-account/react/hooks/usePortfolioCollectibles.js +175 -0
  74. package/dist/esm/global-account/react/stores/useModalStore.d.ts +0 -2
  75. package/dist/esm/global-account/react/utils/createWagmiConfig.d.ts +2 -2
  76. package/dist/esm/shared/constants/chains/b3Chain.d.ts +24 -0
  77. package/dist/esm/shared/constants/chains/supported.d.ts +4 -4
  78. package/dist/esm/shared/constants/index.d.ts +4 -0
  79. package/dist/esm/shared/utils/zerionPositions.d.ts +96 -0
  80. package/dist/esm/shared/utils/zerionPositions.js +178 -0
  81. package/dist/esm/wallet/utils/createWalletConfig.d.ts +2 -2
  82. package/dist/types/anyspend/constants/rpc.d.ts +1 -0
  83. package/dist/types/anyspend/react/components/AnySpendDeposit.d.ts +1 -3
  84. package/dist/types/anyspend/react/hooks/useSigMint.d.ts +1 -1
  85. package/dist/types/anyspend/utils/chain.d.ts +45 -0
  86. package/dist/types/global-account/react/components/B3Provider/B3Provider.d.ts +1 -2
  87. package/dist/types/global-account/react/components/B3Provider/RelayKitProviderWrapper.d.ts +1 -2
  88. package/dist/types/global-account/react/hooks/index.d.ts +2 -2
  89. package/dist/types/global-account/react/hooks/useAuth.d.ts +2 -1
  90. package/dist/types/global-account/react/hooks/useAuthentication.d.ts +2 -1
  91. package/dist/types/global-account/react/hooks/useFirstEOA.d.ts +4 -7666
  92. package/dist/types/global-account/react/hooks/usePortfolioBalance.d.ts +15 -0
  93. package/dist/types/global-account/react/hooks/usePortfolioCollectibles.d.ts +12 -0
  94. package/dist/types/global-account/react/stores/useModalStore.d.ts +0 -2
  95. package/dist/types/global-account/react/utils/createWagmiConfig.d.ts +2 -2
  96. package/dist/types/shared/constants/chains/b3Chain.d.ts +24 -0
  97. package/dist/types/shared/constants/chains/supported.d.ts +4 -4
  98. package/dist/types/shared/constants/index.d.ts +4 -0
  99. package/dist/types/shared/utils/zerionPositions.d.ts +96 -0
  100. package/dist/types/wallet/utils/createWalletConfig.d.ts +2 -2
  101. package/node_modules/@b3/chain-registry/chains.json +29 -1
  102. package/node_modules/@b3/chain-registry/package.json +18 -2
  103. package/package.json +2 -2
  104. package/src/anyspend/README.md +2 -2
  105. package/src/anyspend/constants/rpc.ts +2 -0
  106. package/src/anyspend/docs/components.md +6 -6
  107. package/src/anyspend/docs/contributing.md +1 -1
  108. package/src/anyspend/docs/hooks.md +3 -3
  109. package/src/anyspend/docs/installation.md +2 -2
  110. package/src/anyspend/react/components/AnySpendDeposit.tsx +26 -19
  111. package/src/anyspend/react/components/AnySpendStakeB3.tsx +55 -14
  112. package/src/anyspend/react/components/AnySpendStakeB3ExactIn.tsx +55 -11
  113. package/src/anyspend/react/components/__tests__/QRDeposit.test.tsx +3 -4
  114. package/src/anyspend/react/components/checkout/CryptoPayPanel.tsx +25 -12
  115. package/src/anyspend/react/components/common/TokenBalance.tsx +10 -7
  116. package/src/anyspend/utils/__tests__/deprecatedChains.test.ts +44 -0
  117. package/src/anyspend/utils/__tests__/toRegistryChainId.test.ts +17 -0
  118. package/src/anyspend/utils/chain.ts +53 -2
  119. package/src/global-account/react/components/B3Provider/B3Provider.tsx +1 -3
  120. package/src/global-account/react/components/B3Provider/RelayKitProviderWrapper.tsx +4 -11
  121. package/src/global-account/react/components/ManageAccount/ContentTokens.tsx +50 -21
  122. package/src/global-account/react/components/ManageAccount/NFTContent.tsx +5 -3
  123. package/src/global-account/react/components/ManageAccount/ProfileSection.tsx +10 -6
  124. package/src/global-account/react/components/ManageAccount/TokenContent.tsx +22 -9
  125. package/src/global-account/react/components/Send/Send.tsx +39 -15
  126. package/src/global-account/react/hooks/index.ts +2 -2
  127. package/src/global-account/react/hooks/useAuth.ts +3 -2
  128. package/src/global-account/react/hooks/useAuthentication.ts +3 -2
  129. package/src/global-account/react/hooks/useFirstEOA.tsx +9 -2
  130. package/src/global-account/react/hooks/usePortfolioBalance.ts +89 -0
  131. package/src/global-account/react/hooks/usePortfolioCollectibles.ts +212 -0
  132. package/src/global-account/react/stores/useModalStore.ts +0 -2
  133. package/src/shared/constants/index.ts +8 -0
  134. package/src/shared/utils/__tests__/zerionPositions.test.ts +226 -0
  135. package/src/shared/utils/zerionPositions.ts +244 -0
  136. package/dist/cjs/global-account/react/hooks/useSimBalance.d.ts +0 -59
  137. package/dist/cjs/global-account/react/hooks/useSimBalance.js +0 -98
  138. package/dist/cjs/global-account/react/hooks/useSimCollectibles.d.ts +0 -45
  139. package/dist/cjs/global-account/react/hooks/useSimCollectibles.js +0 -190
  140. package/dist/cjs/global-account/react/utils/simdune.d.ts +0 -7
  141. package/dist/cjs/global-account/react/utils/simdune.js +0 -21
  142. package/dist/esm/global-account/react/hooks/useSimBalance.d.ts +0 -59
  143. package/dist/esm/global-account/react/hooks/useSimBalance.js +0 -93
  144. package/dist/esm/global-account/react/hooks/useSimCollectibles.d.ts +0 -45
  145. package/dist/esm/global-account/react/hooks/useSimCollectibles.js +0 -187
  146. package/dist/esm/global-account/react/utils/simdune.d.ts +0 -7
  147. package/dist/esm/global-account/react/utils/simdune.js +0 -17
  148. package/dist/types/global-account/react/hooks/useSimBalance.d.ts +0 -59
  149. package/dist/types/global-account/react/hooks/useSimCollectibles.d.ts +0 -45
  150. package/dist/types/global-account/react/utils/simdune.d.ts +0 -7
  151. package/node_modules/@b3/chain-registry/src/index.ts +0 -169
  152. package/node_modules/@b3/chain-registry/tsconfig.json +0 -16
  153. package/src/global-account/react/hooks/useSimBalance.ts +0 -164
  154. package/src/global-account/react/hooks/useSimCollectibles.ts +0 -238
  155. package/src/global-account/react/utils/simdune.ts +0 -20
@@ -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
+ }
@@ -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
+ });