@pollar/react 0.11.1-rc.3 → 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,13 +3,22 @@
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.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
20
+ > [CHANGELOG](../../CHANGELOG.md) and [UPGRADE.md](../../UPGRADE.md) for the full
21
+ > version history before upgrading.
13
22
 
14
23
  ## Installation
15
24
 
@@ -69,11 +78,11 @@ Context provider that initialises the Pollar client and makes it available to ch
69
78
  <PollarProvider
70
79
  client={{
71
80
  apiKey: 'your-api-key',
72
- baseUrl: 'https://sdk.api.pollar.xyz', // optional
81
+ baseUrl: 'https://sdk.api.pollar.xyz', // optional, origin only — the SDK appends /v2
73
82
  stellarNetwork: 'testnet', // optional, default: 'testnet'
74
83
  storage, // optional, RN apps inject this
75
84
  keyManager, // optional, autodetects on web
76
- walletAdapter, // optional, external wallet stack
85
+ walletAdapters, // optional, WalletAdapter[] — external wallet stacks
77
86
  deviceLabel: 'iPhone — Safari', // optional, shown in SessionsModal
78
87
  onStorageDegrade, // optional, telemetry hook
79
88
  }}
@@ -94,11 +103,11 @@ Context provider that initialises the Pollar client and makes it available to ch
94
103
  </PollarProvider>
95
104
  ```
96
105
 
97
- | Prop | Type | Required | Description |
98
- | ----------- | ------------------------------------ | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
99
- | `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 |
100
- | `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 |
101
- | `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 |
102
111
 
103
112
  ---
104
113
 
@@ -112,8 +121,15 @@ const {
112
121
  // Session
113
122
  isAuthenticated, // boolean - true when a wallet address is present
114
123
  wallet, // WalletInfo | null - read wallet?.address for the on-chain address
124
+ wallets, // WalletInfo[] - every wallet the user holds, one per chain. To drive
125
+ // a network picker, prefer the useChains() hook (below) over reading
126
+ // this directly - it applies the app's configured chain order
115
127
  verified, // boolean - true once the server has confirmed the session
116
128
 
129
+ // App config (the remote /config fetch behind the login modal)
130
+ configStatus, // 'loading' | 'ready' | 'error'
131
+ retryConfig, // () => void - re-runs the fetch after an error
132
+
117
133
  // Client escape hatch
118
134
  getClient, // () => PollarClient - for getUserProfile(), listSessions(), …
119
135
 
@@ -131,6 +147,7 @@ const {
131
147
  buildTx, // (operation, params, options?) => Promise<BuildOutcome>
132
148
  signAndSubmitTx, // (unsignedXdr?: string) => Promise<SubmitOutcome> (custodial; XDR optional)
133
149
  signTx, // (unsignedXdr: string) => Promise<SignOutcome> (external-wallet only)
150
+ sendPayment, // (params: SendPaymentParams) => Promise<SubmitOutcome>
134
151
  submitTx, // (signedXdr: string) => Promise<SubmitOutcome>
135
152
  buildAndSignAndSubmitTx, // (operation, params, options?) => Promise<SubmitOutcome> (one-shot)
136
153
  runTx, // alias of buildAndSignAndSubmitTx
@@ -148,12 +165,14 @@ const {
148
165
  // Enabled assets / trustlines
149
166
  enabledAssets, // EnabledAssetsState
150
167
  refreshAssets, // () => Promise<void>
151
- setTrustline, // (asset, opts?) => Promise<TrustlineOutcome>
168
+ setTrustline, // (asset, opts?: { limit?, skipSponsorship? }) => Promise<TrustlineOutcome> (sponsorship decided server-side)
152
169
  openEnabledAssetsModal, // () => void
153
170
 
154
171
  // Swap (DEX/AMM)
155
172
  getSwapQuote, // (params: SwapQuoteParams) => Promise<SwapQuote[]>
156
173
  swap, // (quote: SwapQuote, opts?) => Promise<SubmitOutcome>
174
+ getSwapConfig, // () => Promise<SwapVenue[]> (which venues this app offers)
175
+ getSwapTokens, // () => Promise<SwapToken[]>
157
176
  openSwapModal, // () => void
158
177
 
159
178
  // Earn (yield vaults + lending — DeFindex + Blend)
@@ -213,6 +232,14 @@ login({ provider: 'xbull' });
213
232
  login({ provider: 'lobstr' });
214
233
  ```
215
234
 
235
+ #### When the app config fails to load
236
+
237
+ The login modal is gated on the remote app-config fetch. While it runs the modal shows a spinner; if it fails, it shows
238
+ "Could not load sign-in options. Check your connection and try again." plus a **Try again** button wired to
239
+ `retryConfig()`. Both states are readable from `usePollar().configStatus` (`'loading' | 'ready' | 'error'`) if you are
240
+ driving `<LoginModalTemplate>` yourself. Passing `appConfig` to `<PollarProvider>` opts out of the fetch entirely, so
241
+ neither state can occur.
242
+
216
243
  #### Self-driving interactive adapters (Privy, …)
217
244
 
218
245
  Registered adapters that opt into the interactive-auth contract
@@ -231,25 +258,57 @@ provider:
231
258
 
232
259
  ---
233
260
 
261
+ ### `useChains()`
262
+
263
+ The single source of truth for "which chains, in what order, and which address to
264
+ show". Every built-in component (`<WalletButton>`, the network pickers) reads from
265
+ it, so they can never disagree about which chain leads. Use it when you build your
266
+ own wallet button or picker instead of calling `chainsOf(wallets)` yourself —
267
+ `chainsOf` alone cannot know the app's configured order.
268
+
269
+ ```ts
270
+ import { useChains } from '@pollar/react';
271
+
272
+ const { chains, primaryChain, primaryAddress, ready } = useChains();
273
+ ```
274
+
275
+ | Field | Type | Description |
276
+ | ---------------- | --------------------- | ----------------------------------------------------------------------------------------------------------------------- |
277
+ | `chains` | `WalletChain[]` | Chains to offer, in the app's configured order: those the app serves **and** the user holds. `[]` until config resolves |
278
+ | `primaryChain` | `WalletChain \| null` | The app's first configured chain the user holds. `null` until ready |
279
+ | `primaryAddress` | `string` | The address on `primaryChain`. Falls back to the session's first wallet while config is in flight |
280
+ | `ready` | `boolean` | `false` while `/config` is loading or failed — gate any chain UI on it |
281
+
282
+ The order and membership come from the app's `/applications/config`
283
+ (`appConfig.application.chains`), not from the session's `wallets`: a chain the app
284
+ switched off stops appearing on the next page load even for a user whose stored
285
+ session still carries it.
286
+
287
+ ---
288
+
234
289
  ### Components
235
290
 
236
291
  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`) |
292
+ already wired inside `<PollarProvider>` — and most are exported in case you want to mount them yourself.
293
+
294
+ > Two wrappers are **not** exported: `<TxHistoryModal>` and `<TransactionModal>`. Only their templates
295
+ > (`TxHistoryModalTemplate` / `TransactionModalTemplate`) are public. Mount those instead if you need to drive them
296
+ > yourself.
297
+
298
+ | Component | Purpose |
299
+ | -------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
300
+ | `<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 |
301
+ | `<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 |
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) |
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 |
304
+ | `<ReceiveModal>` | Wallet address as QR code with copy-to-clipboard (no external QR dependency required) |
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 |
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 |
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) |
308
+ | `<DistributionRulesModal>` | Manage the wallet's distribution rules |
309
+ | `<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 |
310
+ | `<KycModal>` | Identity verification flow - provider selection + status polling _(UI preview - backend coming soon)_ |
311
+ | `<RampWidget>` | Buy/sell crypto via SEP-24 - direction tabs, route comparison, payment instructions (wired to `client.createOnRamp` / `client.createOffRamp`) |
253
312
 
254
313
  ```tsx
255
314
  import { WalletButton } from '@pollar/react';
@@ -263,8 +322,15 @@ export function Header() {
263
322
 
264
323
  ### Template components
265
324
 
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()`.
325
+ Almost every modal ships a pure presentational "template" companion — same name with a `Template` suffix. Use these when
326
+ you want to swap the chrome but keep the data wiring from `usePollar()`. (`<EarnModal>` is the exception: it has no
327
+ template yet.)
328
+
329
+ > The wallet-balance, enabled-assets, send and receive templates each require `chains`, `selectedChain` and
330
+ > `onSelectChain`. Get `chains` (in the app's configured order) from `useChains()`, keep `selectedChain` in your own
331
+ > state, and render `<ChainSelect>` for the stock picker. `useChains()` is preferred over `chainsOf(wallets)` on its
332
+ > own, which cannot know the configured order; if you do call `chainsOf` directly, pass the order as its second
333
+ > argument. `addressForChain(wallets, selectedChain)` gives the address for the picked chain.
268
334
 
269
335
  | Wrapper | Template |
270
336
  | -------------------------- | ---------------------------------- |
@@ -385,6 +451,10 @@ import type {
385
451
  PollarAuthProvider,
386
452
  AuthProviderContext,
387
453
 
454
+ // Network picker
455
+ ChainSelectProps,
456
+ UseChainsResult,
457
+
388
458
  // Template props
389
459
  WalletButtonTemplateProps,
390
460
  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;