@pollar/react 0.11.1 → 0.11.2

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 CHANGED
@@ -3,14 +3,20 @@
3
3
  React bindings for [Pollar](https://pollar.xyz) — drop-in authentication UI, transaction modals, and hooks for
4
4
  Stellar and Solana applications.
5
5
 
6
- > **0.11.1** requires `@pollar/core@^0.11.1`. Headline feature: a **network
7
- > picker** (`<ChainSelect>`) across the wallet-balance, enabled-assets, send and
8
- > receive modals - each scopes its rows to the selected chain instead of tagging
9
- > every row, and the filter is local (the backend returns every chain in one
10
- > payload, so switching networks costs no request). Balances now format against
11
- > each token's own `decimals`, and a `null` balance renders as a dash rather than
12
- > as `0`, because `null` means the chain could not be read. The login modal shows
13
- > loading / error state when the app config fails to load. See the
6
+ > **0.11.2** requires `@pollar/core@^0.11.2`. The **Transaction History modal
7
+ > goes multichain**: the same network picker and address chip as the Balance /
8
+ > Send modals, a server-side chain filter (pagination is server-side, so
9
+ > switching networks refetches and resets to page 1), per-chain explorer links
10
+ > (stellar.expert for Stellar, explorer.solana.com for Solana), and the unified
11
+ > `{ amount, unit }` fee, so a Solana row reads "5000 lamports". The picker
12
+ > hides itself on a single-chain app. Non-breaking.
13
+ >
14
+ > Earlier: **0.11.1** added the network picker (`<ChainSelect>`) across the
15
+ > wallet-balance, enabled-assets, send and receive modals - each scopes its rows
16
+ > to the selected chain instead of tagging every row, with a local filter (the
17
+ > backend returns every chain in one payload). Balances format against each
18
+ > token's own `decimals`, and a `null` balance renders as a dash rather than as
19
+ > `0`, because `null` means the chain could not be read. See the
14
20
  > [CHANGELOG](../../CHANGELOG.md) and [UPGRADE.md](../../UPGRADE.md) for the full
15
21
  > version history before upgrading.
16
22
 
@@ -97,11 +103,11 @@ Context provider that initialises the Pollar client and makes it available to ch
97
103
  </PollarProvider>
98
104
  ```
99
105
 
100
- | Prop | Type | Required | Description |
101
- | ----------- | ------------------------------------ | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
102
- | `client` | `PollarClient \| PollarClientConfig` | Yes | Either a pre-built `PollarClient` (testing, reuse outside React) or a `PollarClientConfig` the provider will construct one from. **Locked at first render** — swapping after mount is ignored |
103
- | `appConfig` | `PollarConfig` | No | Local override of `/applications/config`. **Presence is the opt-out switch**: pass it (even `{}`) and the remote fetch is skipped. Omit it to keep the existing remote-fetch-on-mount behaviour |
104
- | `adapters` | `PollarAdapters` | No | Named set of `PollarAdapter` objects (e.g. Trustless Work). See below |
106
+ | Prop | Type | Required | Description |
107
+ | ----------- | ------------------------------------ | -------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
108
+ | `client` | `PollarClient \| PollarClientConfig` | Yes | Either a pre-built `PollarClient` (testing, reuse outside React) or a `PollarClientConfig` the provider will construct one from. **Locked at first render** — swapping after mount is ignored |
109
+ | `appConfig` | `PollarConfig` | No | Local override of `/applications/config`. **Presence is the opt-out switch**: pass it (even `{}`) and the remote fetch is skipped. Omit it to keep the existing remote-fetch-on-mount behaviour. Unlike `client`, it can be added or swapped after mount — the context re-applies it (compared by value, so an inline object literal is fine) |
110
+ | `adapters` | `PollarAdapters` | No | Named set of `PollarAdapter` objects (e.g. Trustless Work). See below |
105
111
 
106
112
  ---
107
113
 
@@ -296,7 +302,7 @@ already wired inside `<PollarProvider>` — and most are exported in case you wa
296
302
  | `<SwapModal>` | On-chain asset-to-asset swap: pick from/to assets and amount, quote across venues, execute (auto-trustline on the buy asset when needed); paste a custom buy token (code + issuer) |
297
303
  | `<EarnModal>` | Deposit/withdraw across DeFindex vaults and Blend pools: provider + opportunity selection with live APY, wallet balance, over-spend guards, and auto-trustline on deposit |
298
304
  | `<ReceiveModal>` | Wallet address as QR code with copy-to-clipboard (no external QR dependency required) |
299
- | `<TxHistoryModal>` | Paginated transaction history with auto-fetch on open and stellar.expert explorer links |
305
+ | `<TxHistoryModal>` | Paginated multichain transaction history with auto-fetch on open. A `<ChainSelect>` in the header filters server-side (switching networks refetches and resets to page 1); rows link to their own chain's explorer (stellar.expert for Stellar, explorer.solana.com for Solana) and show the unified `{ amount, unit }` fee |
300
306
  | `<WalletBalanceModal>` | Multichain wallet balances (Stellar, Polygon, Solana). A `<ChainSelect>` in the header picks the network and the rows are filtered to it; shows that chain's address plus a refresh button. An unreadable chain's balance renders as a dash, never as `0`. On Solana testnet each row offers a faucet hint — a devnet SOL faucet on the native row, Circle's USDC faucet on the USDC row |
301
307
  | `<EnabledAssetsModal>` | The application's dashboard-enabled assets for the network picked in the header, with per-asset trustline state; establish/remove trustlines (Stellar only - other chains are informational) |
302
308
  | `<DistributionRulesModal>` | Manage the wallet's distribution rules |
package/dist/index.d.mts CHANGED
@@ -535,12 +535,18 @@ interface TxHistoryModalTemplateProps {
535
535
  accentColor: string;
536
536
  txHistory: TxHistoryState;
537
537
  offset: number;
538
+ /** Networks the user holds a wallet on; the first one is the default. */
539
+ chains: WalletChain[];
540
+ selectedChain: WalletChain | null;
541
+ /** Address of the wallet on {@link selectedChain}. */
542
+ walletAddress: string;
543
+ onSelectChain: (chain: WalletChain) => void;
538
544
  onRefresh: () => void;
539
545
  onPrev: () => void;
540
546
  onNext: () => void;
541
547
  onClose: () => void;
542
548
  }
543
- declare function TxHistoryModalTemplate({ theme, accentColor, txHistory, offset, onRefresh, onPrev, onNext, onClose, }: TxHistoryModalTemplateProps): react_jsx_runtime.JSX.Element;
549
+ declare function TxHistoryModalTemplate({ theme, accentColor, txHistory, offset, chains, selectedChain, walletAddress, onSelectChain, onRefresh, onPrev, onNext, onClose, }: TxHistoryModalTemplateProps): react_jsx_runtime.JSX.Element;
544
550
 
545
551
  interface WalletBalanceModalTemplateProps {
546
552
  theme: string;
package/dist/index.d.ts CHANGED
@@ -535,12 +535,18 @@ interface TxHistoryModalTemplateProps {
535
535
  accentColor: string;
536
536
  txHistory: TxHistoryState;
537
537
  offset: number;
538
+ /** Networks the user holds a wallet on; the first one is the default. */
539
+ chains: WalletChain[];
540
+ selectedChain: WalletChain | null;
541
+ /** Address of the wallet on {@link selectedChain}. */
542
+ walletAddress: string;
543
+ onSelectChain: (chain: WalletChain) => void;
538
544
  onRefresh: () => void;
539
545
  onPrev: () => void;
540
546
  onNext: () => void;
541
547
  onClose: () => void;
542
548
  }
543
- declare function TxHistoryModalTemplate({ theme, accentColor, txHistory, offset, onRefresh, onPrev, onNext, onClose, }: TxHistoryModalTemplateProps): react_jsx_runtime.JSX.Element;
549
+ declare function TxHistoryModalTemplate({ theme, accentColor, txHistory, offset, chains, selectedChain, walletAddress, onSelectChain, onRefresh, onPrev, onNext, onClose, }: TxHistoryModalTemplateProps): react_jsx_runtime.JSX.Element;
544
550
 
545
551
  interface WalletBalanceModalTemplateProps {
546
552
  theme: string;
package/dist/index.js CHANGED
@@ -1035,7 +1035,7 @@ var PollarModalFooter = () => {
1035
1035
  /* @__PURE__ */ jsxRuntime.jsx("span", { className: "pollar-footer-name", children: "Pollar" }),
1036
1036
  /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "pollar-footer-version", children: [
1037
1037
  "v",
1038
- "0.11.1"
1038
+ "0.11.2"
1039
1039
  ] })
1040
1040
  ] })
1041
1041
  ] });
@@ -4151,6 +4151,11 @@ function SendModal({ onClose }) {
4151
4151
  setFormError("Enter a destination address");
4152
4152
  return;
4153
4153
  }
4154
+ const isNativeAsset = selectedAsset.type === "native";
4155
+ if (isBaseUnitChain && !isNativeAsset && (!selectedAsset.issuer || selectedAsset.decimals === void 0)) {
4156
+ setFormError("This token is missing its mint or decimals and cannot be sent.");
4157
+ return;
4158
+ }
4154
4159
  let sendAmount = amount;
4155
4160
  if (isBaseUnitChain) {
4156
4161
  try {
@@ -4166,8 +4171,9 @@ function SendModal({ onClose }) {
4166
4171
  chain: "SOLANA",
4167
4172
  destination: destination.trim(),
4168
4173
  amount: sendAmount,
4169
- // Native SOL carries no mint; an SPL token's mint rides in `issuer`.
4170
- ...selectedAsset.type === "native" ? {} : { mint: selectedAsset.issuer ?? null }
4174
+ // Native SOL carries no mint; an SPL token's mint rides in `issuer`,
4175
+ // which the guard above proved is present.
4176
+ ...isNativeAsset ? {} : { mint: selectedAsset.issuer }
4171
4177
  });
4172
4178
  return;
4173
4179
  }
@@ -4639,7 +4645,7 @@ function SwapModal({ onClose }) {
4639
4645
  const assetRecords = enabledAssets.step === "loaded" ? enabledAssets.data.assets : [];
4640
4646
  const isLoadingData = walletBalance.step === "loading" || enabledAssets.step === "loading";
4641
4647
  const sellOptions = balances.filter(
4642
- (b) => b.type != null && (b.type === "native" || !b.trustlineRemoved)
4648
+ (b) => (b.chain === void 0 || b.chain === "STELLAR") && (b.type === "native" || (b.type === "credit_alphanum4" || b.type === "credit_alphanum12") && !b.trustlineRemoved)
4643
4649
  ).map((b) => ({
4644
4650
  ref: toRef(b),
4645
4651
  code: b.code,
@@ -4952,9 +4958,7 @@ function EarnModal({ onClose }) {
4952
4958
  if (!selectedOpportunity) return null;
4953
4959
  const balances = walletBalance.step === "loaded" ? walletBalance.data.balances : [];
4954
4960
  const issuer = selectedOpportunity.asset.issuer;
4955
- const rec = balances.find(
4956
- (b) => issuer === null ? b.type === "native" : b.code === assetCode && b.issuer === issuer
4957
- );
4961
+ const rec = balances.find((b) => issuer === null ? b.type === "native" : b.code === assetCode && b.issuer === issuer);
4958
4962
  return rec ? rec.available : null;
4959
4963
  })();
4960
4964
  const depositAssetRecord = (() => {
@@ -5631,6 +5635,12 @@ function TransactionModal({ onClose }) {
5631
5635
  ) });
5632
5636
  }
5633
5637
  var PAGE_SIZE = 10;
5638
+ function explorerUrlFor(chain, hash, network) {
5639
+ if (chain === "SOLANA") {
5640
+ return `https://explorer.solana.com/tx/${hash}${network === "testnet" ? "?cluster=devnet" : ""}`;
5641
+ }
5642
+ return `https://stellar.expert/explorer/${network === "testnet" ? "testnet" : "public"}/tx/${hash}`;
5643
+ }
5634
5644
  function StatusBadge({ status }) {
5635
5645
  return /* @__PURE__ */ jsxRuntime.jsx("span", { className: "pollar-hist-item-badge", "data-status": status, children: status });
5636
5646
  }
@@ -5669,6 +5679,10 @@ function TxHistoryModalTemplate({
5669
5679
  accentColor,
5670
5680
  txHistory,
5671
5681
  offset,
5682
+ chains,
5683
+ selectedChain,
5684
+ walletAddress,
5685
+ onSelectChain,
5672
5686
  onRefresh,
5673
5687
  onPrev,
5674
5688
  onNext,
@@ -5744,6 +5758,11 @@ function TxHistoryModalTemplate({
5744
5758
  /* @__PURE__ */ jsxRuntime.jsx("button", { className: "pollar-modal-close", onClick: onClose, "aria-label": "Close", children: /* @__PURE__ */ jsxRuntime.jsx("svg", { width: "16", height: "16", viewBox: "0 0 16 16", fill: "none", "aria-hidden": true, children: /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M2 2l12 12M14 2L2 14", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round" }) }) })
5745
5759
  ] })
5746
5760
  ] }),
5761
+ /* @__PURE__ */ jsxRuntime.jsx(ChainSelect, { value: selectedChain, options: chains, onChange: onSelectChain, disabled: isLoading }),
5762
+ walletAddress && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "pollar-address-row", children: [
5763
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "pollar-address", children: cropAddress(walletAddress) }),
5764
+ /* @__PURE__ */ jsxRuntime.jsx(CopyButton, { value: walletAddress, label: "Copy wallet address" })
5765
+ ] }),
5747
5766
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "pollar-hist-list", children: [
5748
5767
  txHistory.step === "idle" && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "pollar-modal-empty", children: "Click Refresh to load transactions." }),
5749
5768
  isLoading && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "pollar-loading-block", children: [
@@ -5754,7 +5773,7 @@ function TxHistoryModalTemplate({
5754
5773
  txHistory.step === "loaded" && records.length === 0 && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "pollar-modal-empty", children: "No transactions yet." }),
5755
5774
  records.map((record) => {
5756
5775
  const hash = typeof record.hash === "string" && record.hash.length > 0 ? record.hash : void 0;
5757
- const explorerUrl = hash ? `https://stellar.expert/explorer/${record.network === "testnet" ? "testnet" : "public"}/tx/${hash}` : void 0;
5776
+ const explorerUrl = hash ? explorerUrlFor(record.chain, hash, record.network) : void 0;
5758
5777
  const asset = typeof record.details?.asset === "string" ? record.details.asset : void 0;
5759
5778
  const colon = asset ? asset.indexOf(":") : -1;
5760
5779
  const trustline = record.operation === "change_trust" && asset && colon > 0 ? {
@@ -5788,10 +5807,11 @@ function TxHistoryModalTemplate({
5788
5807
  "\xB7 ",
5789
5808
  record.details.sponsored ? "Sponsored" : "Self-paid"
5790
5809
  ] }),
5791
- record.feeXlm && /* @__PURE__ */ jsxRuntime.jsxs("span", { children: [
5810
+ record.fee && /* @__PURE__ */ jsxRuntime.jsxs("span", { children: [
5792
5811
  "\xB7 ",
5793
- record.feeXlm,
5794
- " XLM"
5812
+ record.fee.amount,
5813
+ " ",
5814
+ record.fee.unit
5795
5815
  ] })
5796
5816
  ] }),
5797
5817
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "pollar-hist-item-footer", children: [
@@ -5807,7 +5827,7 @@ function TxHistoryModalTemplate({
5807
5827
  href: explorerUrl,
5808
5828
  target: "_blank",
5809
5829
  rel: "noopener noreferrer",
5810
- "aria-label": "View on Stellar Explorer",
5830
+ "aria-label": "View on block explorer",
5811
5831
  children: /* @__PURE__ */ jsxRuntime.jsxs("svg", { width: "12", height: "12", viewBox: "0 0 13 13", fill: "none", "aria-hidden": true, children: [
5812
5832
  /* @__PURE__ */ jsxRuntime.jsx(
5813
5833
  "path",
@@ -5856,19 +5876,29 @@ function TxHistoryModalTemplate({
5856
5876
  }
5857
5877
  var PAGE_SIZE2 = 10;
5858
5878
  function TxHistoryModal({ onClose }) {
5859
- const { getClient, styles, txHistory } = usePollar();
5879
+ const { getClient, styles, txHistory, wallets } = usePollar();
5860
5880
  const { theme = "light", accentColor = "#005DB4" } = styles;
5861
5881
  const [offset, setOffset] = react.useState(0);
5882
+ const { chains } = useChains();
5883
+ const [selectedChain, setSelectedChain] = react.useState(null);
5884
+ react.useEffect(() => {
5885
+ if (selectedChain === null && chains.length > 0) setSelectedChain(chains[0]);
5886
+ }, [chains, selectedChain]);
5887
+ const walletAddress = addressForChain(wallets, selectedChain);
5862
5888
  const load = react.useCallback(
5863
- (nextOffset) => {
5889
+ (nextOffset, chain) => {
5864
5890
  setOffset(nextOffset);
5865
- void getClient().fetchTxHistory({ limit: PAGE_SIZE2, offset: nextOffset });
5891
+ void getClient().fetchTxHistory({ limit: PAGE_SIZE2, offset: nextOffset, chain });
5866
5892
  },
5867
5893
  [getClient]
5868
5894
  );
5869
5895
  react.useEffect(() => {
5870
- load(0);
5871
- }, [load]);
5896
+ if (selectedChain) load(0, selectedChain);
5897
+ }, [selectedChain, load]);
5898
+ const onSelectChain = (chain) => setSelectedChain(chain);
5899
+ const paged = (nextOffset) => {
5900
+ if (selectedChain) load(nextOffset, selectedChain);
5901
+ };
5872
5902
  return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "pollar-overlay", onClick: onClose, children: /* @__PURE__ */ jsxRuntime.jsx(
5873
5903
  TxHistoryModalTemplate,
5874
5904
  {
@@ -5876,9 +5906,13 @@ function TxHistoryModal({ onClose }) {
5876
5906
  accentColor,
5877
5907
  txHistory,
5878
5908
  offset,
5879
- onRefresh: () => load(offset),
5880
- onPrev: () => load(Math.max(0, offset - PAGE_SIZE2)),
5881
- onNext: () => load(offset + PAGE_SIZE2),
5909
+ chains,
5910
+ selectedChain,
5911
+ walletAddress,
5912
+ onSelectChain,
5913
+ onRefresh: () => paged(offset),
5914
+ onPrev: () => paged(Math.max(0, offset - PAGE_SIZE2)),
5915
+ onNext: () => paged(offset + PAGE_SIZE2),
5882
5916
  onClose
5883
5917
  }
5884
5918
  ) });
@@ -6147,6 +6181,9 @@ function PollarProvider({
6147
6181
  );
6148
6182
  const [configRetry, setConfigRetry] = react.useState(0);
6149
6183
  const retryConfig = react.useCallback(() => setConfigRetry((n) => n + 1), []);
6184
+ const appConfigKey = appConfigProp === void 0 ? "" : JSON.stringify(appConfigProp);
6185
+ const appConfigPropRef = react.useRef(appConfigProp);
6186
+ appConfigPropRef.current = appConfigProp;
6150
6187
  react.useEffect(() => {
6151
6188
  return pollarClient.onTransactionStateChange(setTransaction);
6152
6189
  }, [pollarClient]);
@@ -6210,7 +6247,9 @@ function PollarProvider({
6210
6247
  setModalErrorLogger(pollarClient.getLogger());
6211
6248
  }, [pollarClient]);
6212
6249
  react.useEffect(() => {
6213
- if (appConfigProp !== void 0) {
6250
+ const localConfig = appConfigPropRef.current;
6251
+ if (localConfig !== void 0) {
6252
+ setResolvedConfig(localConfig ?? DEFAULT_APP_CONFIG);
6214
6253
  setConfigStatus("ready");
6215
6254
  return;
6216
6255
  }
@@ -6232,7 +6271,7 @@ function PollarProvider({
6232
6271
  return () => {
6233
6272
  cancelled = true;
6234
6273
  };
6235
- }, [pollarClient, appConfigProp, configRetry]);
6274
+ }, [pollarClient, appConfigKey, configRetry]);
6236
6275
  const [loginModalOpen, setLoginModalOpen] = react.useState(false);
6237
6276
  const [transactionModalOpen, setTransactionModalOpen] = react.useState(false);
6238
6277
  const [kycModalOpen, setKycModalOpen] = react.useState(false);