@embarkai/ui-kit 0.4.13 → 0.4.17

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.
@@ -15,7 +15,7 @@
15
15
  <meta http-equiv="X-Content-Type-Options" content="nosniff" />
16
16
  <meta http-equiv="Referrer-Policy" content="strict-origin-when-cross-origin" />
17
17
 
18
- <title>EmbarkAI Secure Wallet - iframe version 0.4.13</title>
18
+ <title>EmbarkAI Secure Wallet - iframe version 0.4.17</title>
19
19
 
20
20
  <!-- Styles will be injected by build process -->
21
21
  <style>
@@ -4592,7 +4592,7 @@ var BinanceOnrampAPI = class {
4592
4592
  this.disconnectSse();
4593
4593
  const controller = new AbortController();
4594
4594
  this.sseAbortController = controller;
4595
- const url = `${API_DOMAIN}/sse/onramp/events?walletAddress=${encodeURIComponent(walletAddress)}`;
4595
+ const url = `${API_DOMAIN}/sse/events?walletAddress=${encodeURIComponent(walletAddress)}`;
4596
4596
  const start = async () => {
4597
4597
  try {
4598
4598
  const response = await fetch(url, {
@@ -4780,7 +4780,7 @@ var FinchPayRampAPI = class {
4780
4780
  this.disconnectSse();
4781
4781
  const controller = new AbortController();
4782
4782
  this.sseAbortController = controller;
4783
- const url = `${API_DOMAIN2}/sse/onramp/events?walletAddress=${encodeURIComponent(walletAddress)}`;
4783
+ const url = `${API_DOMAIN2}/sse/events?walletAddress=${encodeURIComponent(walletAddress)}`;
4784
4784
  const start = async () => {
4785
4785
  try {
4786
4786
  const response = await fetch(url, {
@@ -6262,7 +6262,7 @@ var SigningManager = class extends TokenRefreshApiClient {
6262
6262
  };
6263
6263
 
6264
6264
  // src/iframe/main.ts
6265
- var IFRAME_VERSION = "0.4.13";
6265
+ var IFRAME_VERSION = "0.4.17";
6266
6266
  var IframeWallet = class {
6267
6267
  constructor() {
6268
6268
  console.log("=".repeat(60));
package/dist/index.cjs CHANGED
@@ -15170,6 +15170,7 @@ function useWatchTokens(options) {
15170
15170
  // 30 seconds
15171
15171
  gcTime: 5 * 60 * 1e3,
15172
15172
  // 5 minutes
15173
+ refetchOnMount: "always",
15173
15174
  retry: 2,
15174
15175
  retryDelay: 1e3
15175
15176
  });
@@ -15235,6 +15236,7 @@ function mapBlockscoutToAsset(balance) {
15235
15236
  balance: value,
15236
15237
  formattedBalance,
15237
15238
  decimals,
15239
+ exchangeRateUsd: token.exchange_rate || void 0,
15238
15240
  logo: token.icon_url || void 0
15239
15241
  };
15240
15242
  if (type === "erc721" || type === "erc1155") {
@@ -15320,6 +15322,9 @@ function useBlockscoutAssets(options) {
15320
15322
  const publicClient = useSession((st) => st.publicClient);
15321
15323
  const activeChainId = useSession((st) => st.activeChainId);
15322
15324
  const chainConfig = useSession((st) => st.chainConfig);
15325
+ const portfolioConfig = useProviderConfig().config.current.portfolio;
15326
+ const customTokenBalancesQuery = portfolioConfig?.tokenBalancesQuery;
15327
+ const customTokenBalancesQueryKey = portfolioConfig?.tokenBalancesQueryKey;
15323
15328
  const blockscoutApiUrl = getBlockscoutApiUrl(chainConfig);
15324
15329
  const blockscoutClient = (0, import_react57.useMemo)(
15325
15330
  () => blockscoutApiUrl ? (0, import_clients2.createBlockscoutClient)({ baseUrl: blockscoutApiUrl }) : null,
@@ -15350,8 +15355,9 @@ function useBlockscoutAssets(options) {
15350
15355
  enabled: !!publicClient && !!address && enabled,
15351
15356
  staleTime: 30 * 1e3,
15352
15357
  // 30 seconds
15353
- gcTime: 5 * 60 * 1e3
15358
+ gcTime: 5 * 60 * 1e3,
15354
15359
  // 5 minutes
15360
+ refetchOnMount: "always"
15355
15361
  });
15356
15362
  const {
15357
15363
  data: tokenBalances,
@@ -15360,11 +15366,22 @@ function useBlockscoutAssets(options) {
15360
15366
  refetch: refetchTokens,
15361
15367
  dataUpdatedAt
15362
15368
  } = (0, import_react_query39.useQuery)({
15363
- queryKey: [BLOCKSCOUT_QUERY_KEY, address, blockscoutApiUrl],
15364
- queryFn: () => blockscoutClient.getTokenBalances(address),
15365
- enabled: !!address && enabled && !!blockscoutClient,
15366
- // staleTime: BLOCKSCOUT_QUERY_STALE_TIME,
15369
+ queryKey: [BLOCKSCOUT_QUERY_KEY, address, blockscoutApiUrl, customTokenBalancesQueryKey],
15370
+ queryFn: async () => {
15371
+ if (customTokenBalancesQuery) {
15372
+ try {
15373
+ return await customTokenBalancesQuery(address);
15374
+ } catch (error2) {
15375
+ console.warn("[useBlockscoutAssets] Custom token balances query failed:", error2);
15376
+ if (!blockscoutClient) throw error2;
15377
+ }
15378
+ }
15379
+ return await blockscoutClient.getTokenBalances(address);
15380
+ },
15381
+ enabled: !!address && enabled && (!!customTokenBalancesQuery || !!blockscoutClient),
15382
+ staleTime: 0,
15367
15383
  gcTime: BLOCKSCOUT_QUERY_GC_TIME,
15384
+ refetchOnMount: "always",
15368
15385
  retry: 2,
15369
15386
  retryDelay: 1e3
15370
15387
  });
@@ -15378,6 +15395,7 @@ function useBlockscoutAssets(options) {
15378
15395
  enabled: !!address && enabled && !!blockscoutClient,
15379
15396
  staleTime: BLOCKSCOUT_QUERY_STALE_TIME,
15380
15397
  gcTime: BLOCKSCOUT_QUERY_GC_TIME,
15398
+ refetchOnMount: "always",
15381
15399
  retry: 2,
15382
15400
  retryDelay: 1e3
15383
15401
  });
@@ -15512,6 +15530,7 @@ function useBlockscoutAssets(options) {
15512
15530
  var import_clients2, import_react_query39, import_react57, import_viem7, BLOCKSCOUT_QUERY_KEY, ERC3643_DETECTION_QUERY_KEY, ERC3643_ABI;
15513
15531
  var init_useBlockscoutAssets = __esm({
15514
15532
  "src/internal/hooks/useBlockscoutAssets.ts"() {
15533
+ init_ProviderContext();
15515
15534
  init_SessionContext();
15516
15535
  import_clients2 = require("@embarkai/core/clients");
15517
15536
  import_react_query39 = require("@tanstack/react-query");
@@ -15614,6 +15633,21 @@ async function getAssetRate2(symbol) {
15614
15633
  const response = await fetch(assetURL);
15615
15634
  return await response.json();
15616
15635
  }
15636
+ async function resolveAssetRate(symbol, address, customAssetRateQuery) {
15637
+ if (customAssetRateQuery) {
15638
+ try {
15639
+ const customRate = await customAssetRateQuery({ symbol, address });
15640
+ const numeric = Number(customRate?.price || 0);
15641
+ if (customRate && Number.isFinite(numeric) && numeric > 0) {
15642
+ return customRate;
15643
+ }
15644
+ } catch (error) {
15645
+ console.warn("[PortfolioItem] Custom asset rate query failed:", error);
15646
+ }
15647
+ }
15648
+ if (!isAssetRateAvailable(symbol)) return null;
15649
+ return getAssetRate2(symbol);
15650
+ }
15617
15651
  function PortfolioItemImage(props) {
15618
15652
  const { asset } = props;
15619
15653
  const [nftImageError, setNftImageError] = (0, import_react58.useState)(false);
@@ -15660,22 +15694,29 @@ function PortfolioItemImage(props) {
15660
15694
  }
15661
15695
  function PortfolioItem(props) {
15662
15696
  const { address, asset, isProjectAsset } = props;
15663
- const { assets: projectAssets, showBalanceAs: showBalanceAsSymbol } = useProviderConfig().config.current.projectAssets || {};
15697
+ const providerConfig = useProviderConfig().config.current;
15698
+ const { assets: projectAssets, showBalanceAs: showBalanceAsSymbol } = providerConfig.projectAssets || {};
15699
+ const customAssetRateQuery = providerConfig.portfolio?.assetRateQuery;
15700
+ const customAssetRateQueryKey = providerConfig.portfolio?.assetRateQueryKey;
15664
15701
  const qc = (0, import_react_query40.useQueryClient)();
15665
15702
  const { balanceQueryKey } = projectAssets?.find((a) => a.symbol === showBalanceAsSymbol) || {};
15666
15703
  const projectAssetBalance = !!balanceQueryKey ? qc.getQueryData(balanceQueryKey) : null;
15667
15704
  const isNft = isNftAsset(asset);
15668
15705
  const isSecurity = isSecurityToken(asset);
15706
+ const embeddedRate = Number(asset.exchangeRateUsd || 0);
15707
+ const hasEmbeddedRate = Number.isFinite(embeddedRate) && embeddedRate > 0;
15669
15708
  const { data: assetRate, isLoading: isRateLoading } = (0, import_react_query40.useQuery)({
15670
15709
  retry: false,
15671
15710
  staleTime: 4 * 60 * 1e3,
15672
- enabled: !!address && !!isAssetRateAvailable(asset.symbol) && !isProjectAsset && !isNft && !isSecurity,
15673
- queryKey: [ASSETS_RATES_QUERY_KEY, address, asset.symbol],
15674
- queryFn: async () => await getAssetRate2(asset.symbol)
15711
+ enabled: !!address && !hasEmbeddedRate && !isProjectAsset && !isNft && !isSecurity && (!!customAssetRateQuery || !!isAssetRateAvailable(asset.symbol)),
15712
+ queryKey: [ASSETS_RATES_QUERY_KEY, address, asset.address, asset.symbol, customAssetRateQueryKey],
15713
+ queryFn: async () => await resolveAssetRate(asset.symbol, asset.address, customAssetRateQuery)
15675
15714
  });
15676
15715
  const showProjectFiatBalance = isProjectAsset && !!projectAssetBalance?.fiatFormatted;
15677
15716
  const renderBalance = showProjectFiatBalance ? projectAssetBalance?.fiatFormatted || 0 : Number((0, import_viem9.formatUnits)(BigInt(asset?.balance || "0"), asset?.decimals || 18));
15678
- const usdRenderBalance = !!assetRate?.price ? renderBalance * Number(assetRate.price) : 0;
15717
+ const resolvedRate = hasEmbeddedRate ? embeddedRate : Number(assetRate?.price || 0);
15718
+ const usdRenderBalance = resolvedRate > 0 ? renderBalance * resolvedRate : 0;
15719
+ const shortContractAddress = asset.address ? truncateMiddle(asset.address, 6, 4) : null;
15679
15720
  return /* @__PURE__ */ (0, import_jsx_runtime80.jsxs)(
15680
15721
  "div",
15681
15722
  {
@@ -15689,15 +15730,22 @@ function PortfolioItem(props) {
15689
15730
  children: [
15690
15731
  /* @__PURE__ */ (0, import_jsx_runtime80.jsx)(PortfolioItemImage, { asset }),
15691
15732
  /* @__PURE__ */ (0, import_jsx_runtime80.jsxs)("div", { className: "w-full flex-1", children: [
15692
- /* @__PURE__ */ (0, import_jsx_runtime80.jsxs)("div", { className: "w-full flex items-center justify-between text-xs", children: [
15693
- /* @__PURE__ */ (0, import_jsx_runtime80.jsx)(
15694
- "span",
15695
- {
15696
- className: "truncate max-w-[150px]",
15697
- title: isNft ? asset.nftMetadata?.collectionName || asset.name : asset.name,
15698
- children: isNft ? asset.nftMetadata?.collectionName || asset.name || (asset.address ? `${asset.address.slice(0, 6)}...${asset.address.slice(-4)}` : "Unknown") : asset.name || (asset.address ? `${asset.address.slice(0, 6)}...${asset.address.slice(-4)}` : "Unknown")
15699
- }
15700
- ),
15733
+ /* @__PURE__ */ (0, import_jsx_runtime80.jsxs)("div", { className: "w-full flex items-center justify-between text-xs gap-2", children: [
15734
+ /* @__PURE__ */ (0, import_jsx_runtime80.jsxs)("div", { className: "min-w-0 flex flex-col", children: [
15735
+ /* @__PURE__ */ (0, import_jsx_runtime80.jsx)(
15736
+ "span",
15737
+ {
15738
+ className: "truncate max-w-[150px]",
15739
+ title: isNft ? asset.nftMetadata?.collectionName || asset.name : asset.name,
15740
+ children: isNft ? asset.nftMetadata?.collectionName || asset.name || shortContractAddress || "Unknown" : asset.name || shortContractAddress || "Unknown"
15741
+ }
15742
+ ),
15743
+ !isNft && shortContractAddress && /* @__PURE__ */ (0, import_jsx_runtime80.jsxs)("span", { className: "text-[10px] text-[var(--ui-kit-fg-muted)] font-mono truncate", title: asset.address, children: [
15744
+ asset.symbol,
15745
+ " \xB7 ",
15746
+ shortContractAddress
15747
+ ] })
15748
+ ] }),
15701
15749
  !!usdRenderBalance && !isProjectAsset && !isNft && !isSecurity && /* @__PURE__ */ (0, import_jsx_runtime80.jsx)("span", { className: "text-[var(--ui-kit-fg-muted)]", children: "USD" }),
15702
15750
  !isSecurity && isNft && "type" in asset && /* @__PURE__ */ (0, import_jsx_runtime80.jsx)("span", { className: "uppercase text-[10px] px-1 bg-[var(--ui-kit-bg-info)] rounded-full", children: asset.type === "erc721" ? "ERC-721" : "ERC-1155" }),
15703
15751
  isSecurity && /* @__PURE__ */ (0, import_jsx_runtime80.jsx)("span", { className: "uppercase text-[10px] px-1 bg-[var(--ui-kit-bg-warning)] rounded-full", children: "Security Token" })
@@ -15741,6 +15789,7 @@ var init_PortfolioItem = __esm({
15741
15789
  init_base();
15742
15790
  init_priceFormatter();
15743
15791
  init_utils2();
15792
+ init_truncateMiddle();
15744
15793
  init_BalanceView2();
15745
15794
  import_jsx_runtime80 = require("react/jsx-runtime");
15746
15795
  SKIP_SYMBOLS2 = ["TEST"];
@@ -16982,6 +17031,11 @@ function AssetListItem(props) {
16982
17031
  /* @__PURE__ */ (0, import_jsx_runtime91.jsx)(AssetInputIcon, { asset, size: "large" }),
16983
17032
  /* @__PURE__ */ (0, import_jsx_runtime91.jsxs)("div", { className: "flex-1 flex flex-col items-start", children: [
16984
17033
  /* @__PURE__ */ (0, import_jsx_runtime91.jsx)("div", { className: "text-[10px] truncate leading-4", children: isNft ? asset.nftMetadata?.name || asset.name : asset.name }),
17034
+ !isNft && asset.address && /* @__PURE__ */ (0, import_jsx_runtime91.jsxs)("div", { className: "text-[10px] text-[var(--ui-kit-fg-muted)] font-mono truncate leading-4", title: asset.address, children: [
17035
+ asset.symbol,
17036
+ " \xB7 ",
17037
+ truncateMiddle(asset.address, 6, 4)
17038
+ ] }),
16985
17039
  /* @__PURE__ */ (0, import_jsx_runtime91.jsx)("span", { className: "block text-lg font-bold", children: isNft ? /* @__PURE__ */ (0, import_jsx_runtime91.jsxs)(import_jsx_runtime91.Fragment, { children: [
16986
17040
  asset.symbol,
16987
17041
  asset.tokenId && /* @__PURE__ */ (0, import_jsx_runtime91.jsxs)("span", { className: "ml-1", children: [
@@ -17039,6 +17093,7 @@ var init_AssetList = __esm({
17039
17093
  import_viem13 = require("viem");
17040
17094
  init_priceFormatter();
17041
17095
  init_utils2();
17096
+ init_truncateMiddle();
17042
17097
  init_BalanceView2();
17043
17098
  init_useSendStore();
17044
17099
  init_utils8();
@@ -19296,7 +19351,7 @@ var init_package = __esm({
19296
19351
  "package.json"() {
19297
19352
  package_default = {
19298
19353
  name: "@embarkai/ui-kit",
19299
- version: "0.4.13",
19354
+ version: "0.4.17",
19300
19355
  description: "React UI components and hooks for EmbarkAI authentication and Account Abstraction",
19301
19356
  type: "module",
19302
19357
  main: "./dist/index.cjs",
@@ -19312,8 +19367,9 @@ var init_package = __esm({
19312
19367
  clean: "rm -rf dist",
19313
19368
  "build:css": "tailwindcss -c tailwind.config.cjs -i src/styles/index.css -o src/styles/built.css --minify",
19314
19369
  "build:iframe-html": "node scripts/copy-iframe-html.js",
19315
- "build:copy-css": "cp src/styles/built.css dist/styles.css",
19370
+ "build:copy-css": "node scripts/copy-styles-css.js",
19316
19371
  build: "pnpm build:css && tsup && pnpm build:iframe-html && pnpm build:copy-css",
19372
+ prepack: "pnpm build",
19317
19373
  dev: "pnpm build:css && tsup --watch",
19318
19374
  "dev:iframe": "vite --config iframe/vite.config.ts",
19319
19375
  csv: "npx tsx src/i18n/utils/prepareCsvProofread.ts --origin en --target zh",
@@ -23285,6 +23341,7 @@ function Provider(props) {
23285
23341
  if (updates.warnings) next.warnings = { ...next.warnings, ...updates.warnings };
23286
23342
  if (updates.kyc) next.kyc = { ...next.kyc, ...updates.kyc };
23287
23343
  if (updates.watchTokens) next.watchTokens = { ...next.watchTokens, ...updates.watchTokens };
23344
+ if (updates.portfolio) next.portfolio = { ...next.portfolio, ...updates.portfolio };
23288
23345
  if (updates.services && typeof window !== "undefined") {
23289
23346
  try {
23290
23347
  window.__EMBARK_SERVICES__ = next.services;
package/dist/index.d.cts CHANGED
@@ -1,6 +1,7 @@
1
1
  import * as react_jsx_runtime from 'react/jsx-runtime';
2
2
  import * as React$1 from 'react';
3
3
  import React__default, { ReactNode, MutableRefObject, FC, HTMLAttributes, ButtonHTMLAttributes } from 'react';
4
+ import { BlockscoutTokenBalance } from '@embarkai/core/clients';
4
5
  import * as viem from 'viem';
5
6
  import { Address as Address$1, Hex, PublicClient, Chain, Hash as Hash$1, TransactionReceipt } from 'viem';
6
7
  import * as _tanstack_react_query from '@tanstack/react-query';
@@ -89,6 +90,8 @@ interface Asset {
89
90
  formattedBalance: string;
90
91
  /** Decimal precision (undefined for NFTs) */
91
92
  decimals?: number;
93
+ /** USD rate reported by explorer when available */
94
+ exchangeRateUsd?: string;
92
95
  /** NFT token ID (only for ERC721/ERC1155) */
93
96
  tokenId?: string;
94
97
  /** Token logo URL */
@@ -178,6 +181,14 @@ interface ProjectAsset extends Omit<Asset, 'type'> {
178
181
  }>;
179
182
  hideInPortfolio?: boolean;
180
183
  }
184
+ interface AssetRateQueryInput {
185
+ symbol: string;
186
+ address?: `0x${string}`;
187
+ }
188
+ interface AssetRateQueryResult {
189
+ symbol: string;
190
+ price: string;
191
+ }
181
192
  /**
182
193
  * Token configuration for custom token watching.
183
194
  * Used when chain doesn't have Blockscout API or for project-specific tokens.
@@ -228,6 +239,17 @@ interface ProviderConfig {
228
239
  assets: ProjectAsset[];
229
240
  showBalanceAs?: ProjectAsset['symbol'];
230
241
  };
242
+ /**
243
+ * Host-provided portfolio data overrides.
244
+ * If tokenBalancesQuery is provided, SDK uses it instead of Blockscout
245
+ * for ERC-20 portfolio balances and keeps the legacy Blockscout path as fallback.
246
+ */
247
+ portfolio?: {
248
+ tokenBalancesQuery?: (address: `0x${string}`) => Promise<BlockscoutTokenBalance[]>;
249
+ tokenBalancesQueryKey?: readonly unknown[];
250
+ assetRateQuery?: (input: AssetRateQueryInput) => Promise<AssetRateQueryResult | null>;
251
+ assetRateQueryKey?: readonly unknown[];
252
+ };
231
253
  /**
232
254
  * Custom tokens to monitor per chain.
233
255
  * SDK will fetch balances via RPC multicall.
package/dist/index.d.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  import * as react_jsx_runtime from 'react/jsx-runtime';
2
2
  import * as React$1 from 'react';
3
3
  import React__default, { ReactNode, MutableRefObject, FC, HTMLAttributes, ButtonHTMLAttributes } from 'react';
4
+ import { BlockscoutTokenBalance } from '@embarkai/core/clients';
4
5
  import * as viem from 'viem';
5
6
  import { Address as Address$1, Hex, PublicClient, Chain, Hash as Hash$1, TransactionReceipt } from 'viem';
6
7
  import * as _tanstack_react_query from '@tanstack/react-query';
@@ -89,6 +90,8 @@ interface Asset {
89
90
  formattedBalance: string;
90
91
  /** Decimal precision (undefined for NFTs) */
91
92
  decimals?: number;
93
+ /** USD rate reported by explorer when available */
94
+ exchangeRateUsd?: string;
92
95
  /** NFT token ID (only for ERC721/ERC1155) */
93
96
  tokenId?: string;
94
97
  /** Token logo URL */
@@ -178,6 +181,14 @@ interface ProjectAsset extends Omit<Asset, 'type'> {
178
181
  }>;
179
182
  hideInPortfolio?: boolean;
180
183
  }
184
+ interface AssetRateQueryInput {
185
+ symbol: string;
186
+ address?: `0x${string}`;
187
+ }
188
+ interface AssetRateQueryResult {
189
+ symbol: string;
190
+ price: string;
191
+ }
181
192
  /**
182
193
  * Token configuration for custom token watching.
183
194
  * Used when chain doesn't have Blockscout API or for project-specific tokens.
@@ -228,6 +239,17 @@ interface ProviderConfig {
228
239
  assets: ProjectAsset[];
229
240
  showBalanceAs?: ProjectAsset['symbol'];
230
241
  };
242
+ /**
243
+ * Host-provided portfolio data overrides.
244
+ * If tokenBalancesQuery is provided, SDK uses it instead of Blockscout
245
+ * for ERC-20 portfolio balances and keeps the legacy Blockscout path as fallback.
246
+ */
247
+ portfolio?: {
248
+ tokenBalancesQuery?: (address: `0x${string}`) => Promise<BlockscoutTokenBalance[]>;
249
+ tokenBalancesQueryKey?: readonly unknown[];
250
+ assetRateQuery?: (input: AssetRateQueryInput) => Promise<AssetRateQueryResult | null>;
251
+ assetRateQueryKey?: readonly unknown[];
252
+ };
231
253
  /**
232
254
  * Custom tokens to monitor per chain.
233
255
  * SDK will fetch balances via RPC multicall.
package/dist/index.js CHANGED
@@ -15106,6 +15106,7 @@ function useWatchTokens(options) {
15106
15106
  // 30 seconds
15107
15107
  gcTime: 5 * 60 * 1e3,
15108
15108
  // 5 minutes
15109
+ refetchOnMount: "always",
15109
15110
  retry: 2,
15110
15111
  retryDelay: 1e3
15111
15112
  });
@@ -15172,6 +15173,7 @@ function mapBlockscoutToAsset(balance) {
15172
15173
  balance: value,
15173
15174
  formattedBalance,
15174
15175
  decimals,
15176
+ exchangeRateUsd: token.exchange_rate || void 0,
15175
15177
  logo: token.icon_url || void 0
15176
15178
  };
15177
15179
  if (type === "erc721" || type === "erc1155") {
@@ -15257,6 +15259,9 @@ function useBlockscoutAssets(options) {
15257
15259
  const publicClient = useSession((st) => st.publicClient);
15258
15260
  const activeChainId = useSession((st) => st.activeChainId);
15259
15261
  const chainConfig = useSession((st) => st.chainConfig);
15262
+ const portfolioConfig = useProviderConfig().config.current.portfolio;
15263
+ const customTokenBalancesQuery = portfolioConfig?.tokenBalancesQuery;
15264
+ const customTokenBalancesQueryKey = portfolioConfig?.tokenBalancesQueryKey;
15260
15265
  const blockscoutApiUrl = getBlockscoutApiUrl(chainConfig);
15261
15266
  const blockscoutClient = useMemo5(
15262
15267
  () => blockscoutApiUrl ? createBlockscoutClient({ baseUrl: blockscoutApiUrl }) : null,
@@ -15287,8 +15292,9 @@ function useBlockscoutAssets(options) {
15287
15292
  enabled: !!publicClient && !!address && enabled,
15288
15293
  staleTime: 30 * 1e3,
15289
15294
  // 30 seconds
15290
- gcTime: 5 * 60 * 1e3
15295
+ gcTime: 5 * 60 * 1e3,
15291
15296
  // 5 minutes
15297
+ refetchOnMount: "always"
15292
15298
  });
15293
15299
  const {
15294
15300
  data: tokenBalances,
@@ -15297,11 +15303,22 @@ function useBlockscoutAssets(options) {
15297
15303
  refetch: refetchTokens,
15298
15304
  dataUpdatedAt
15299
15305
  } = useQuery16({
15300
- queryKey: [BLOCKSCOUT_QUERY_KEY, address, blockscoutApiUrl],
15301
- queryFn: () => blockscoutClient.getTokenBalances(address),
15302
- enabled: !!address && enabled && !!blockscoutClient,
15303
- // staleTime: BLOCKSCOUT_QUERY_STALE_TIME,
15306
+ queryKey: [BLOCKSCOUT_QUERY_KEY, address, blockscoutApiUrl, customTokenBalancesQueryKey],
15307
+ queryFn: async () => {
15308
+ if (customTokenBalancesQuery) {
15309
+ try {
15310
+ return await customTokenBalancesQuery(address);
15311
+ } catch (error2) {
15312
+ console.warn("[useBlockscoutAssets] Custom token balances query failed:", error2);
15313
+ if (!blockscoutClient) throw error2;
15314
+ }
15315
+ }
15316
+ return await blockscoutClient.getTokenBalances(address);
15317
+ },
15318
+ enabled: !!address && enabled && (!!customTokenBalancesQuery || !!blockscoutClient),
15319
+ staleTime: 0,
15304
15320
  gcTime: BLOCKSCOUT_QUERY_GC_TIME,
15321
+ refetchOnMount: "always",
15305
15322
  retry: 2,
15306
15323
  retryDelay: 1e3
15307
15324
  });
@@ -15315,6 +15332,7 @@ function useBlockscoutAssets(options) {
15315
15332
  enabled: !!address && enabled && !!blockscoutClient,
15316
15333
  staleTime: BLOCKSCOUT_QUERY_STALE_TIME,
15317
15334
  gcTime: BLOCKSCOUT_QUERY_GC_TIME,
15335
+ refetchOnMount: "always",
15318
15336
  retry: 2,
15319
15337
  retryDelay: 1e3
15320
15338
  });
@@ -15449,6 +15467,7 @@ function useBlockscoutAssets(options) {
15449
15467
  var BLOCKSCOUT_QUERY_KEY, ERC3643_DETECTION_QUERY_KEY, ERC3643_ABI;
15450
15468
  var init_useBlockscoutAssets = __esm({
15451
15469
  "src/internal/hooks/useBlockscoutAssets.ts"() {
15470
+ init_ProviderContext();
15452
15471
  init_SessionContext();
15453
15472
  init_initial();
15454
15473
  init_constants();
@@ -15552,6 +15571,21 @@ async function getAssetRate2(symbol) {
15552
15571
  const response = await fetch(assetURL);
15553
15572
  return await response.json();
15554
15573
  }
15574
+ async function resolveAssetRate(symbol, address, customAssetRateQuery) {
15575
+ if (customAssetRateQuery) {
15576
+ try {
15577
+ const customRate = await customAssetRateQuery({ symbol, address });
15578
+ const numeric = Number(customRate?.price || 0);
15579
+ if (customRate && Number.isFinite(numeric) && numeric > 0) {
15580
+ return customRate;
15581
+ }
15582
+ } catch (error) {
15583
+ console.warn("[PortfolioItem] Custom asset rate query failed:", error);
15584
+ }
15585
+ }
15586
+ if (!isAssetRateAvailable(symbol)) return null;
15587
+ return getAssetRate2(symbol);
15588
+ }
15555
15589
  function PortfolioItemImage(props) {
15556
15590
  const { asset } = props;
15557
15591
  const [nftImageError, setNftImageError] = useState22(false);
@@ -15598,22 +15632,29 @@ function PortfolioItemImage(props) {
15598
15632
  }
15599
15633
  function PortfolioItem(props) {
15600
15634
  const { address, asset, isProjectAsset } = props;
15601
- const { assets: projectAssets, showBalanceAs: showBalanceAsSymbol } = useProviderConfig().config.current.projectAssets || {};
15635
+ const providerConfig = useProviderConfig().config.current;
15636
+ const { assets: projectAssets, showBalanceAs: showBalanceAsSymbol } = providerConfig.projectAssets || {};
15637
+ const customAssetRateQuery = providerConfig.portfolio?.assetRateQuery;
15638
+ const customAssetRateQueryKey = providerConfig.portfolio?.assetRateQueryKey;
15602
15639
  const qc = useQueryClient20();
15603
15640
  const { balanceQueryKey } = projectAssets?.find((a) => a.symbol === showBalanceAsSymbol) || {};
15604
15641
  const projectAssetBalance = !!balanceQueryKey ? qc.getQueryData(balanceQueryKey) : null;
15605
15642
  const isNft = isNftAsset(asset);
15606
15643
  const isSecurity = isSecurityToken(asset);
15644
+ const embeddedRate = Number(asset.exchangeRateUsd || 0);
15645
+ const hasEmbeddedRate = Number.isFinite(embeddedRate) && embeddedRate > 0;
15607
15646
  const { data: assetRate, isLoading: isRateLoading } = useQuery17({
15608
15647
  retry: false,
15609
15648
  staleTime: 4 * 60 * 1e3,
15610
- enabled: !!address && !!isAssetRateAvailable(asset.symbol) && !isProjectAsset && !isNft && !isSecurity,
15611
- queryKey: [ASSETS_RATES_QUERY_KEY, address, asset.symbol],
15612
- queryFn: async () => await getAssetRate2(asset.symbol)
15649
+ enabled: !!address && !hasEmbeddedRate && !isProjectAsset && !isNft && !isSecurity && (!!customAssetRateQuery || !!isAssetRateAvailable(asset.symbol)),
15650
+ queryKey: [ASSETS_RATES_QUERY_KEY, address, asset.address, asset.symbol, customAssetRateQueryKey],
15651
+ queryFn: async () => await resolveAssetRate(asset.symbol, asset.address, customAssetRateQuery)
15613
15652
  });
15614
15653
  const showProjectFiatBalance = isProjectAsset && !!projectAssetBalance?.fiatFormatted;
15615
15654
  const renderBalance = showProjectFiatBalance ? projectAssetBalance?.fiatFormatted || 0 : Number(formatUnits5(BigInt(asset?.balance || "0"), asset?.decimals || 18));
15616
- const usdRenderBalance = !!assetRate?.price ? renderBalance * Number(assetRate.price) : 0;
15655
+ const resolvedRate = hasEmbeddedRate ? embeddedRate : Number(assetRate?.price || 0);
15656
+ const usdRenderBalance = resolvedRate > 0 ? renderBalance * resolvedRate : 0;
15657
+ const shortContractAddress = asset.address ? truncateMiddle(asset.address, 6, 4) : null;
15617
15658
  return /* @__PURE__ */ jsxs73(
15618
15659
  "div",
15619
15660
  {
@@ -15627,15 +15668,22 @@ function PortfolioItem(props) {
15627
15668
  children: [
15628
15669
  /* @__PURE__ */ jsx80(PortfolioItemImage, { asset }),
15629
15670
  /* @__PURE__ */ jsxs73("div", { className: "w-full flex-1", children: [
15630
- /* @__PURE__ */ jsxs73("div", { className: "w-full flex items-center justify-between text-xs", children: [
15631
- /* @__PURE__ */ jsx80(
15632
- "span",
15633
- {
15634
- className: "truncate max-w-[150px]",
15635
- title: isNft ? asset.nftMetadata?.collectionName || asset.name : asset.name,
15636
- children: isNft ? asset.nftMetadata?.collectionName || asset.name || (asset.address ? `${asset.address.slice(0, 6)}...${asset.address.slice(-4)}` : "Unknown") : asset.name || (asset.address ? `${asset.address.slice(0, 6)}...${asset.address.slice(-4)}` : "Unknown")
15637
- }
15638
- ),
15671
+ /* @__PURE__ */ jsxs73("div", { className: "w-full flex items-center justify-between text-xs gap-2", children: [
15672
+ /* @__PURE__ */ jsxs73("div", { className: "min-w-0 flex flex-col", children: [
15673
+ /* @__PURE__ */ jsx80(
15674
+ "span",
15675
+ {
15676
+ className: "truncate max-w-[150px]",
15677
+ title: isNft ? asset.nftMetadata?.collectionName || asset.name : asset.name,
15678
+ children: isNft ? asset.nftMetadata?.collectionName || asset.name || shortContractAddress || "Unknown" : asset.name || shortContractAddress || "Unknown"
15679
+ }
15680
+ ),
15681
+ !isNft && shortContractAddress && /* @__PURE__ */ jsxs73("span", { className: "text-[10px] text-[var(--ui-kit-fg-muted)] font-mono truncate", title: asset.address, children: [
15682
+ asset.symbol,
15683
+ " \xB7 ",
15684
+ shortContractAddress
15685
+ ] })
15686
+ ] }),
15639
15687
  !!usdRenderBalance && !isProjectAsset && !isNft && !isSecurity && /* @__PURE__ */ jsx80("span", { className: "text-[var(--ui-kit-fg-muted)]", children: "USD" }),
15640
15688
  !isSecurity && isNft && "type" in asset && /* @__PURE__ */ jsx80("span", { className: "uppercase text-[10px] px-1 bg-[var(--ui-kit-bg-info)] rounded-full", children: asset.type === "erc721" ? "ERC-721" : "ERC-1155" }),
15641
15689
  isSecurity && /* @__PURE__ */ jsx80("span", { className: "uppercase text-[10px] px-1 bg-[var(--ui-kit-bg-warning)] rounded-full", children: "Security Token" })
@@ -15675,6 +15723,7 @@ var init_PortfolioItem = __esm({
15675
15723
  init_base();
15676
15724
  init_priceFormatter();
15677
15725
  init_utils2();
15726
+ init_truncateMiddle();
15678
15727
  init_BalanceView2();
15679
15728
  SKIP_SYMBOLS2 = ["TEST"];
15680
15729
  ASSETS_RATES_QUERY_KEY = "lumia-passport-assets-rates-query-key";
@@ -16907,6 +16956,11 @@ function AssetListItem(props) {
16907
16956
  /* @__PURE__ */ jsx91(AssetInputIcon, { asset, size: "large" }),
16908
16957
  /* @__PURE__ */ jsxs84("div", { className: "flex-1 flex flex-col items-start", children: [
16909
16958
  /* @__PURE__ */ jsx91("div", { className: "text-[10px] truncate leading-4", children: isNft ? asset.nftMetadata?.name || asset.name : asset.name }),
16959
+ !isNft && asset.address && /* @__PURE__ */ jsxs84("div", { className: "text-[10px] text-[var(--ui-kit-fg-muted)] font-mono truncate leading-4", title: asset.address, children: [
16960
+ asset.symbol,
16961
+ " \xB7 ",
16962
+ truncateMiddle(asset.address, 6, 4)
16963
+ ] }),
16910
16964
  /* @__PURE__ */ jsx91("span", { className: "block text-lg font-bold", children: isNft ? /* @__PURE__ */ jsxs84(Fragment35, { children: [
16911
16965
  asset.symbol,
16912
16966
  asset.tokenId && /* @__PURE__ */ jsxs84("span", { className: "ml-1", children: [
@@ -16962,6 +17016,7 @@ var init_AssetList = __esm({
16962
17016
  "src/internal/components/SendRecieveMenu/components/AssetList.tsx"() {
16963
17017
  init_priceFormatter();
16964
17018
  init_utils2();
17019
+ init_truncateMiddle();
16965
17020
  init_BalanceView2();
16966
17021
  init_useSendStore();
16967
17022
  init_utils8();
@@ -19214,7 +19269,7 @@ var init_package = __esm({
19214
19269
  "package.json"() {
19215
19270
  package_default = {
19216
19271
  name: "@embarkai/ui-kit",
19217
- version: "0.4.13",
19272
+ version: "0.4.17",
19218
19273
  description: "React UI components and hooks for EmbarkAI authentication and Account Abstraction",
19219
19274
  type: "module",
19220
19275
  main: "./dist/index.cjs",
@@ -19230,8 +19285,9 @@ var init_package = __esm({
19230
19285
  clean: "rm -rf dist",
19231
19286
  "build:css": "tailwindcss -c tailwind.config.cjs -i src/styles/index.css -o src/styles/built.css --minify",
19232
19287
  "build:iframe-html": "node scripts/copy-iframe-html.js",
19233
- "build:copy-css": "cp src/styles/built.css dist/styles.css",
19288
+ "build:copy-css": "node scripts/copy-styles-css.js",
19234
19289
  build: "pnpm build:css && tsup && pnpm build:iframe-html && pnpm build:copy-css",
19290
+ prepack: "pnpm build",
19235
19291
  dev: "pnpm build:css && tsup --watch",
19236
19292
  "dev:iframe": "vite --config iframe/vite.config.ts",
19237
19293
  csv: "npx tsx src/i18n/utils/prepareCsvProofread.ts --origin en --target zh",
@@ -23228,6 +23284,7 @@ function Provider(props) {
23228
23284
  if (updates.warnings) next.warnings = { ...next.warnings, ...updates.warnings };
23229
23285
  if (updates.kyc) next.kyc = { ...next.kyc, ...updates.kyc };
23230
23286
  if (updates.watchTokens) next.watchTokens = { ...next.watchTokens, ...updates.watchTokens };
23287
+ if (updates.portfolio) next.portfolio = { ...next.portfolio, ...updates.portfolio };
23231
23288
  if (updates.services && typeof window !== "undefined") {
23232
23289
  try {
23233
23290
  window.__EMBARK_SERVICES__ = next.services;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@embarkai/ui-kit",
3
- "version": "0.4.13",
3
+ "version": "0.4.17",
4
4
  "description": "React UI components and hooks for EmbarkAI authentication and Account Abstraction",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",
@@ -87,7 +87,7 @@
87
87
  "clean": "rm -rf dist",
88
88
  "build:css": "tailwindcss -c tailwind.config.cjs -i src/styles/index.css -o src/styles/built.css --minify",
89
89
  "build:iframe-html": "node scripts/copy-iframe-html.js",
90
- "build:copy-css": "cp src/styles/built.css dist/styles.css",
90
+ "build:copy-css": "node scripts/copy-styles-css.js",
91
91
  "build": "pnpm build:css && tsup && pnpm build:iframe-html && pnpm build:copy-css",
92
92
  "dev": "pnpm build:css && tsup --watch",
93
93
  "dev:iframe": "vite --config iframe/vite.config.ts",