@pollar/react 0.11.1-rc.3 → 0.11.1

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,13 +3,16 @@
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.10.0** requires `@pollar/core@^0.10.0`. Headline features: on-chain swaps
7
- > via `<SwapModal>` + `usePollar().swap` / `getSwapQuote` / `openSwapModal`;
8
- > live SEP-24 on/off-ramps through `<RampWidget>` (now wired to
9
- > `client.createOnRamp` / `client.createOffRamp`); and a self-driving Privy
10
- > adapter (registered interactive adapters are auto-driven to completion before
11
- > `login({ provider })`). See the [CHANGELOG](../../CHANGELOG.md) and
12
- > [UPGRADE.md](../../UPGRADE.md) for the full version history before upgrading.
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
14
+ > [CHANGELOG](../../CHANGELOG.md) and [UPGRADE.md](../../UPGRADE.md) for the full
15
+ > version history before upgrading.
13
16
 
14
17
  ## Installation
15
18
 
@@ -69,11 +72,11 @@ Context provider that initialises the Pollar client and makes it available to ch
69
72
  <PollarProvider
70
73
  client={{
71
74
  apiKey: 'your-api-key',
72
- baseUrl: 'https://sdk.api.pollar.xyz', // optional
75
+ baseUrl: 'https://sdk.api.pollar.xyz', // optional, origin only — the SDK appends /v2
73
76
  stellarNetwork: 'testnet', // optional, default: 'testnet'
74
77
  storage, // optional, RN apps inject this
75
78
  keyManager, // optional, autodetects on web
76
- walletAdapter, // optional, external wallet stack
79
+ walletAdapters, // optional, WalletAdapter[] — external wallet stacks
77
80
  deviceLabel: 'iPhone — Safari', // optional, shown in SessionsModal
78
81
  onStorageDegrade, // optional, telemetry hook
79
82
  }}
@@ -112,8 +115,15 @@ const {
112
115
  // Session
113
116
  isAuthenticated, // boolean - true when a wallet address is present
114
117
  wallet, // WalletInfo | null - read wallet?.address for the on-chain address
118
+ wallets, // WalletInfo[] - every wallet the user holds, one per chain. To drive
119
+ // a network picker, prefer the useChains() hook (below) over reading
120
+ // this directly - it applies the app's configured chain order
115
121
  verified, // boolean - true once the server has confirmed the session
116
122
 
123
+ // App config (the remote /config fetch behind the login modal)
124
+ configStatus, // 'loading' | 'ready' | 'error'
125
+ retryConfig, // () => void - re-runs the fetch after an error
126
+
117
127
  // Client escape hatch
118
128
  getClient, // () => PollarClient - for getUserProfile(), listSessions(), …
119
129
 
@@ -131,6 +141,7 @@ const {
131
141
  buildTx, // (operation, params, options?) => Promise<BuildOutcome>
132
142
  signAndSubmitTx, // (unsignedXdr?: string) => Promise<SubmitOutcome> (custodial; XDR optional)
133
143
  signTx, // (unsignedXdr: string) => Promise<SignOutcome> (external-wallet only)
144
+ sendPayment, // (params: SendPaymentParams) => Promise<SubmitOutcome>
134
145
  submitTx, // (signedXdr: string) => Promise<SubmitOutcome>
135
146
  buildAndSignAndSubmitTx, // (operation, params, options?) => Promise<SubmitOutcome> (one-shot)
136
147
  runTx, // alias of buildAndSignAndSubmitTx
@@ -148,12 +159,14 @@ const {
148
159
  // Enabled assets / trustlines
149
160
  enabledAssets, // EnabledAssetsState
150
161
  refreshAssets, // () => Promise<void>
151
- setTrustline, // (asset, opts?) => Promise<TrustlineOutcome>
162
+ setTrustline, // (asset, opts?: { limit?, skipSponsorship? }) => Promise<TrustlineOutcome> (sponsorship decided server-side)
152
163
  openEnabledAssetsModal, // () => void
153
164
 
154
165
  // Swap (DEX/AMM)
155
166
  getSwapQuote, // (params: SwapQuoteParams) => Promise<SwapQuote[]>
156
167
  swap, // (quote: SwapQuote, opts?) => Promise<SubmitOutcome>
168
+ getSwapConfig, // () => Promise<SwapVenue[]> (which venues this app offers)
169
+ getSwapTokens, // () => Promise<SwapToken[]>
157
170
  openSwapModal, // () => void
158
171
 
159
172
  // Earn (yield vaults + lending — DeFindex + Blend)
@@ -213,6 +226,14 @@ login({ provider: 'xbull' });
213
226
  login({ provider: 'lobstr' });
214
227
  ```
215
228
 
229
+ #### When the app config fails to load
230
+
231
+ The login modal is gated on the remote app-config fetch. While it runs the modal shows a spinner; if it fails, it shows
232
+ "Could not load sign-in options. Check your connection and try again." plus a **Try again** button wired to
233
+ `retryConfig()`. Both states are readable from `usePollar().configStatus` (`'loading' | 'ready' | 'error'`) if you are
234
+ driving `<LoginModalTemplate>` yourself. Passing `appConfig` to `<PollarProvider>` opts out of the fetch entirely, so
235
+ neither state can occur.
236
+
216
237
  #### Self-driving interactive adapters (Privy, …)
217
238
 
218
239
  Registered adapters that opt into the interactive-auth contract
@@ -231,25 +252,57 @@ provider:
231
252
 
232
253
  ---
233
254
 
255
+ ### `useChains()`
256
+
257
+ The single source of truth for "which chains, in what order, and which address to
258
+ show". Every built-in component (`<WalletButton>`, the network pickers) reads from
259
+ it, so they can never disagree about which chain leads. Use it when you build your
260
+ own wallet button or picker instead of calling `chainsOf(wallets)` yourself —
261
+ `chainsOf` alone cannot know the app's configured order.
262
+
263
+ ```ts
264
+ import { useChains } from '@pollar/react';
265
+
266
+ const { chains, primaryChain, primaryAddress, ready } = useChains();
267
+ ```
268
+
269
+ | Field | Type | Description |
270
+ | ---------------- | --------------------- | ----------------------------------------------------------------------------------------------------------------------- |
271
+ | `chains` | `WalletChain[]` | Chains to offer, in the app's configured order: those the app serves **and** the user holds. `[]` until config resolves |
272
+ | `primaryChain` | `WalletChain \| null` | The app's first configured chain the user holds. `null` until ready |
273
+ | `primaryAddress` | `string` | The address on `primaryChain`. Falls back to the session's first wallet while config is in flight |
274
+ | `ready` | `boolean` | `false` while `/config` is loading or failed — gate any chain UI on it |
275
+
276
+ The order and membership come from the app's `/applications/config`
277
+ (`appConfig.application.chains`), not from the session's `wallets`: a chain the app
278
+ switched off stops appearing on the next page load even for a user whose stored
279
+ session still carries it.
280
+
281
+ ---
282
+
234
283
  ### Components
235
284
 
236
285
  Every modal mounts itself when its `openXModal()` action is called. You don't need to render these directly — they're
237
- already wired inside `<PollarProvider>` — but they're exported in case you want to mount them yourself.
238
-
239
- | Component | Purpose |
240
- | -------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
241
- | `<WalletButton>` | Drop-in button. Opens login when signed out; signed in, shows the wallet address with a dropdown (Send, Receive, copy address, balance, history, ramp, KYC, distribution rules, sessions, sign out, plus a "Create account" action when the external wallet has no on-chain account yet). Inline arc spinner during in-progress transactions |
242
- | `<SendModal>` | Full send flow: asset picker, amount, destination, inline build > sign > success/error |
243
- | `<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) |
244
- | `<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 |
245
- | `<ReceiveModal>` | Wallet address as QR code with copy-to-clipboard (no external QR dependency required) |
246
- | `<TxHistoryModal>` | Paginated transaction history with auto-fetch on open and stellar.expert explorer links |
247
- | `<WalletBalanceModal>` | Multichain wallet balances (Stellar + Solana); each balance carries a per-chain tag on multichain apps, with refresh button |
248
- | `<EnabledAssetsModal>` | The application's dashboard-enabled assets with per-asset trustline state; establish/remove trustlines |
249
- | `<DistributionRulesModal>` | Manage the wallet's distribution rules |
250
- | `<SessionsModal>` | Lists every active refresh-token family for the current user with device metadata, marks the local session, per-row revoke, and a "Sign out everywhere" button |
251
- | `<KycModal>` | Identity verification flow - provider selection + status polling _(UI preview - backend coming soon)_ |
252
- | `<RampWidget>` | Buy/sell crypto via SEP-24 - direction tabs, route comparison, payment instructions (wired to `client.createOnRamp` / `client.createOffRamp`) |
286
+ already wired inside `<PollarProvider>` — and most are exported in case you want to mount them yourself.
287
+
288
+ > Two wrappers are **not** exported: `<TxHistoryModal>` and `<TransactionModal>`. Only their templates
289
+ > (`TxHistoryModalTemplate` / `TransactionModalTemplate`) are public. Mount those instead if you need to drive them
290
+ > yourself.
291
+
292
+ | Component | Purpose |
293
+ | -------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
294
+ | `<WalletButton>` | Drop-in button. Opens login when signed out; signed in, shows the wallet address with a dropdown (Send, Receive, copy address, balance, history, ramp, KYC, distribution rules, sessions, sign out, plus a "Create account" action when the external wallet has no on-chain account yet). Inline arc spinner during in-progress transactions |
295
+ | `<SendModal>` | Full send flow: network picker, asset picker, amount, destination, inline build > sign > success/error. Sending is available on Stellar and Solana; Polygon can be browsed but not sent from |
296
+ | `<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
+ | `<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
+ | `<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 |
300
+ | `<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
+ | `<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
+ | `<DistributionRulesModal>` | Manage the wallet's distribution rules |
303
+ | `<SessionsModal>` | Lists every active refresh-token family for the current user with device metadata, marks the local session, per-row revoke, and a "Sign out everywhere" button |
304
+ | `<KycModal>` | Identity verification flow - provider selection + status polling _(UI preview - backend coming soon)_ |
305
+ | `<RampWidget>` | Buy/sell crypto via SEP-24 - direction tabs, route comparison, payment instructions (wired to `client.createOnRamp` / `client.createOffRamp`) |
253
306
 
254
307
  ```tsx
255
308
  import { WalletButton } from '@pollar/react';
@@ -263,8 +316,15 @@ export function Header() {
263
316
 
264
317
  ### Template components
265
318
 
266
- Every modal ships a pure presentational "template" companion — same name with a `Template` suffix. Use these when you
267
- want to swap the chrome but keep the data wiring from `usePollar()`.
319
+ Almost every modal ships a pure presentational "template" companion — same name with a `Template` suffix. Use these when
320
+ you want to swap the chrome but keep the data wiring from `usePollar()`. (`<EarnModal>` is the exception: it has no
321
+ template yet.)
322
+
323
+ > The wallet-balance, enabled-assets, send and receive templates each require `chains`, `selectedChain` and
324
+ > `onSelectChain`. Get `chains` (in the app's configured order) from `useChains()`, keep `selectedChain` in your own
325
+ > state, and render `<ChainSelect>` for the stock picker. `useChains()` is preferred over `chainsOf(wallets)` on its
326
+ > own, which cannot know the configured order; if you do call `chainsOf` directly, pass the order as its second
327
+ > argument. `addressForChain(wallets, selectedChain)` gives the address for the picked chain.
268
328
 
269
329
  | Wrapper | Template |
270
330
  | -------------------------- | ---------------------------------- |
@@ -385,6 +445,10 @@ import type {
385
445
  PollarAuthProvider,
386
446
  AuthProviderContext,
387
447
 
448
+ // Network picker
449
+ ChainSelectProps,
450
+ UseChainsResult,
451
+
388
452
  // Template props
389
453
  WalletButtonTemplateProps,
390
454
  SendModalTemplateProps,
package/dist/index.css CHANGED
@@ -343,6 +343,111 @@
343
343
  background: transparent;
344
344
  border: none;
345
345
  }
346
+ .pollar-chain-tag {
347
+ font-size: 0.625rem;
348
+ font-weight: 600;
349
+ line-height: 1;
350
+ letter-spacing: 0.02em;
351
+ text-transform: uppercase;
352
+ padding: 0.1875rem 0.375rem;
353
+ border-radius: 4px;
354
+ color: var(--pollar-chain-color, var(--pollar-muted));
355
+ background: color-mix(in srgb, var(--pollar-chain-color, #6b7280) 14%, transparent);
356
+ border: 1px solid color-mix(in srgb, var(--pollar-chain-color, #6b7280) 34%, transparent);
357
+ }
358
+ .pollar-issuer {
359
+ display: inline-flex;
360
+ align-items: center;
361
+ gap: 0.25rem;
362
+ }
363
+ .pollar-issuer-addr {
364
+ font-size: 0.6875rem;
365
+ font-family: monospace;
366
+ color: var(--pollar-muted);
367
+ }
368
+ .pollar-busy-overlay {
369
+ position: absolute;
370
+ inset: 0;
371
+ z-index: 2;
372
+ border-radius: inherit;
373
+ cursor: wait;
374
+ background: color-mix(in srgb, var(--pollar-bg) 62%, transparent);
375
+ backdrop-filter: blur(1.5px);
376
+ -webkit-backdrop-filter: blur(1.5px);
377
+ }
378
+ .pollar-busy-overlay-inner {
379
+ position: sticky;
380
+ top: 45%;
381
+ display: flex;
382
+ flex-direction: column;
383
+ align-items: center;
384
+ gap: 0.625rem;
385
+ color: var(--pollar-muted);
386
+ font-size: 0.8125rem;
387
+ }
388
+ .pollar-switch {
389
+ flex: none;
390
+ width: 38px;
391
+ height: 22px;
392
+ padding: 2px;
393
+ border-radius: 999px;
394
+ border: 1px solid var(--pollar-border);
395
+ background: var(--pollar-input-bg);
396
+ cursor: pointer;
397
+ display: flex;
398
+ align-items: center;
399
+ justify-content: flex-start;
400
+ transition: background 0.15s, border-color 0.15s;
401
+ }
402
+ .pollar-switch-on {
403
+ justify-content: flex-end;
404
+ background: var(--pollar-accent);
405
+ border-color: var(--pollar-accent);
406
+ }
407
+ .pollar-switch-knob {
408
+ width: 16px;
409
+ height: 16px;
410
+ border-radius: 50%;
411
+ background: var(--pollar-bg);
412
+ box-shadow: 0 1px 2px rgba(0, 0, 0, 0.25);
413
+ display: flex;
414
+ align-items: center;
415
+ justify-content: center;
416
+ transition: transform 0.15s;
417
+ }
418
+ .pollar-switch:disabled {
419
+ opacity: 0.5;
420
+ cursor: not-allowed;
421
+ }
422
+ .pollar-switch-busy {
423
+ cursor: wait;
424
+ }
425
+ .pollar-switch-busy .pollar-switch-knob {
426
+ color: var(--pollar-muted);
427
+ }
428
+ .pollar-chain-field {
429
+ margin-bottom: 0.75rem;
430
+ }
431
+ .pollar-address-row {
432
+ display: flex;
433
+ align-items: center;
434
+ gap: 0.5rem;
435
+ margin-bottom: 1rem;
436
+ }
437
+ .pollar-address {
438
+ flex: 1;
439
+ min-width: 0;
440
+ font-size: 0.75rem;
441
+ font-family: monospace;
442
+ color: var(--pollar-muted);
443
+ background: var(--pollar-input-bg);
444
+ border: 1px solid var(--pollar-border);
445
+ border-radius: var(--pollar-card-border-radius, 10px);
446
+ padding: 0.375rem 0.75rem;
447
+ overflow: hidden;
448
+ text-overflow: ellipsis;
449
+ white-space: nowrap;
450
+ }
346
451
 
347
452
  /* src/components/distribution-rules-modal/DistributionRulesModal.css */
348
453
  .pollar-dist-modal {
@@ -435,22 +540,60 @@
435
540
  color: var(--pollar-error-text, #dc2626);
436
541
  }
437
542
 
438
- /* src/components/enabled-assets-modal/EnabledAssetsModal.css */
439
- .pollar-asset-modal {
440
- max-width: 380px;
543
+ /* src/components/send-modal/SendModal.css */
544
+ .pollar-send-modal {
545
+ max-width: 420px;
546
+ }
547
+ .pollar-send-header-left {
548
+ display: flex;
549
+ align-items: center;
550
+ gap: 0.5rem;
551
+ }
552
+ .pollar-send-field {
553
+ display: flex;
554
+ flex-direction: column;
555
+ gap: 0.375rem;
556
+ margin-bottom: 1rem;
557
+ }
558
+ .pollar-send-label {
559
+ font-size: 0.8125rem;
560
+ font-weight: 600;
561
+ color: var(--pollar-muted);
441
562
  }
442
- .pollar-asset-address {
563
+ .pollar-send-label-row {
564
+ display: flex;
565
+ align-items: baseline;
566
+ justify-content: space-between;
567
+ }
568
+ .pollar-send-hint {
443
569
  font-size: 0.75rem;
444
- font-family: monospace;
445
570
  color: var(--pollar-muted);
446
- background: var(--pollar-input-bg);
447
- border: 1px solid var(--pollar-border);
448
- border-radius: var(--pollar-card-border-radius, 10px);
449
- padding: 0.375rem 0.75rem;
450
- margin-bottom: 1rem;
451
- overflow: hidden;
452
- text-overflow: ellipsis;
453
- white-space: nowrap;
571
+ }
572
+ .pollar-send-select {
573
+ appearance: none;
574
+ -webkit-appearance: none;
575
+ background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12' fill='none'%3E%3Cpath d='M2 4l4 4 4-4' stroke='%236b7280' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
576
+ background-repeat: no-repeat;
577
+ background-position: right 0.875rem center;
578
+ padding-right: 2.25rem;
579
+ cursor: pointer;
580
+ }
581
+ .pollar-send-skeleton {
582
+ height: var(--pollar-input-height, 44px);
583
+ border-radius: var(--pollar-input-border-radius, 0.5rem);
584
+ background:
585
+ linear-gradient(
586
+ 90deg,
587
+ var(--pollar-input-bg) 25%,
588
+ var(--pollar-border) 50%,
589
+ var(--pollar-input-bg) 75%);
590
+ background-size: 200% 100%;
591
+ animation: pollar-pulse 1.4s ease-in-out infinite;
592
+ }
593
+
594
+ /* src/components/enabled-assets-modal/EnabledAssetsModal.css */
595
+ .pollar-asset-modal {
596
+ max-width: 380px;
454
597
  }
455
598
  .pollar-asset-list {
456
599
  display: flex;
@@ -498,20 +641,12 @@
498
641
  font-size: 0.6875rem;
499
642
  color: var(--pollar-muted);
500
643
  }
501
- .pollar-asset-trustline {
644
+ .pollar-asset-trustline-label {
502
645
  font-size: 0.6875rem;
503
646
  font-weight: 600;
504
647
  color: var(--pollar-muted);
505
- background: var(--pollar-bg);
506
- border: 1px solid var(--pollar-border);
507
- border-radius: 999px;
508
- padding: 0.1875rem 0.5rem;
509
648
  white-space: nowrap;
510
649
  }
511
- .pollar-asset-trustline.pollar-established {
512
- color: var(--pollar-success-text);
513
- border-color: var(--pollar-success-text);
514
- }
515
650
  .pollar-asset-sponsor {
516
651
  font-size: 0.625rem;
517
652
  color: var(--pollar-muted);
@@ -521,37 +656,6 @@
521
656
  align-items: center;
522
657
  gap: 0.5rem;
523
658
  }
524
- .pollar-asset-btn {
525
- font-size: 0.6875rem;
526
- font-weight: 600;
527
- color: var(--pollar-accent);
528
- background: var(--pollar-bg);
529
- border: 1px solid var(--pollar-accent);
530
- border-radius: 999px;
531
- padding: 0.25rem 0.625rem;
532
- cursor: pointer;
533
- white-space: nowrap;
534
- transition:
535
- opacity 0.15s,
536
- background 0.15s,
537
- color 0.15s;
538
- }
539
- .pollar-asset-btn:hover:not(:disabled) {
540
- background: var(--pollar-accent);
541
- color: #fff;
542
- }
543
- .pollar-asset-btn.pollar-danger {
544
- color: var(--pollar-error-text);
545
- border-color: var(--pollar-error-border);
546
- }
547
- .pollar-asset-btn.pollar-danger:hover:not(:disabled) {
548
- background: var(--pollar-error-text);
549
- color: #fff;
550
- }
551
- .pollar-asset-btn:disabled {
552
- opacity: 0.5;
553
- cursor: not-allowed;
554
- }
555
659
  .pollar-asset-add-custom {
556
660
  width: 100%;
557
661
  background: none;
@@ -1689,57 +1793,6 @@
1689
1793
  overflow-y: auto;
1690
1794
  }
1691
1795
 
1692
- /* src/components/send-modal/SendModal.css */
1693
- .pollar-send-modal {
1694
- max-width: 420px;
1695
- }
1696
- .pollar-send-header-left {
1697
- display: flex;
1698
- align-items: center;
1699
- gap: 0.5rem;
1700
- }
1701
- .pollar-send-field {
1702
- display: flex;
1703
- flex-direction: column;
1704
- gap: 0.375rem;
1705
- margin-bottom: 1rem;
1706
- }
1707
- .pollar-send-label {
1708
- font-size: 0.8125rem;
1709
- font-weight: 600;
1710
- color: var(--pollar-muted);
1711
- }
1712
- .pollar-send-label-row {
1713
- display: flex;
1714
- align-items: baseline;
1715
- justify-content: space-between;
1716
- }
1717
- .pollar-send-hint {
1718
- font-size: 0.75rem;
1719
- color: var(--pollar-muted);
1720
- }
1721
- .pollar-send-select {
1722
- appearance: none;
1723
- -webkit-appearance: none;
1724
- background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12' fill='none'%3E%3Cpath d='M2 4l4 4 4-4' stroke='%236b7280' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
1725
- background-repeat: no-repeat;
1726
- background-position: right 0.875rem center;
1727
- padding-right: 2.25rem;
1728
- cursor: pointer;
1729
- }
1730
- .pollar-send-skeleton {
1731
- height: var(--pollar-input-height, 44px);
1732
- border-radius: var(--pollar-input-border-radius, 0.5rem);
1733
- background:
1734
- linear-gradient(
1735
- 90deg,
1736
- var(--pollar-input-bg) 25%,
1737
- var(--pollar-border) 50%,
1738
- var(--pollar-input-bg) 75%);
1739
- background-size: 200% 100%;
1740
- animation: pollar-pulse 1.4s ease-in-out infinite;
1741
- }
1742
-
1743
1796
  /* src/components/swap-modal/SwapModal.css */
1744
1797
  .pollar-swap-quote {
1745
1798
  display: flex;
@@ -2083,26 +2136,6 @@
2083
2136
  .pollar-bal-modal {
2084
2137
  max-width: 380px;
2085
2138
  }
2086
- .pollar-bal-address-row {
2087
- display: flex;
2088
- align-items: center;
2089
- gap: 0.5rem;
2090
- margin-bottom: 1rem;
2091
- }
2092
- .pollar-bal-address {
2093
- flex: 1;
2094
- min-width: 0;
2095
- font-size: 0.75rem;
2096
- font-family: monospace;
2097
- color: var(--pollar-muted);
2098
- background: var(--pollar-input-bg);
2099
- border: 1px solid var(--pollar-border);
2100
- border-radius: var(--pollar-card-border-radius, 10px);
2101
- padding: 0.375rem 0.75rem;
2102
- overflow: hidden;
2103
- text-overflow: ellipsis;
2104
- white-space: nowrap;
2105
- }
2106
2139
  .pollar-bal-list {
2107
2140
  display: flex;
2108
2141
  flex-direction: column;
@@ -2134,6 +2167,22 @@
2134
2167
  font-weight: 600;
2135
2168
  color: var(--pollar-text);
2136
2169
  }
2170
+ .pollar-bal-faucet-hint {
2171
+ font-size: 0.6875rem;
2172
+ color: var(--pollar-muted);
2173
+ }
2174
+ .pollar-bal-faucet-hint a {
2175
+ color: var(--pollar-accent);
2176
+ text-decoration: none;
2177
+ font-weight: 600;
2178
+ }
2179
+ .pollar-bal-faucet-hint a:hover {
2180
+ text-decoration: underline;
2181
+ }
2182
+ .pollar-bal-faucet-net {
2183
+ font-size: 0.625rem;
2184
+ opacity: 0.8;
2185
+ }
2137
2186
  .pollar-bal-chain-tag {
2138
2187
  font-size: 0.625rem;
2139
2188
  font-weight: 600;