solid-drift 0.14.0 → 0.16.0

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
@@ -1094,6 +1094,37 @@ const connect = createConnectButton(() => btn, { strength: 0.35 });
1094
1094
 
1095
1095
  Returns `{ copyTick, chainPulse, status }`. `status()` is `"idle"`, `"ticking"` (check visible), or `"pulsing"` (ring expanding). Call `chainPulse()` after a successful connection or network switch. Under reduced motion there is no magnetic pull or scale; `copyTick()` and `chainPulse()` still show their overlays statically.
1096
1096
 
1097
+ ### Web3 data layer
1098
+
1099
+ A zero-dependency read layer for chain and market data as signals: public RPC and API endpoints over `fetch`, with user-swappable endpoints. Every network primitive shares the `{ data, error, status, retry, abort }` shape, is SSR-safe (nothing fetches on the server), and polls with error backoff. Defaults are conservative because public endpoints are rate-limited. This is read-only: transaction signing stays with wallet libraries.
1100
+
1101
+ ```tsx
1102
+ import {
1103
+ createPoll, createTokenPrice, createPriceChange, createPriceCompare,
1104
+ createGasPrice, createBalance, createTxReceipt, createBlockNumber,
1105
+ createChainlinkPrice, createNFTMetadata, createENS, createIdenticon,
1106
+ createChain, CHAINS, shortenAddress, isAddress, formatUnits, parseUnits,
1107
+ } from "solid-drift";
1108
+ ```
1109
+
1110
+ **Polling infra.** `createPoll(fetcher, options?)` fetches immediately (unless `immediate: false`), then on `interval` (default 30s). On error the interval multiplies by `backoff` (default 2) up to `maxInterval` (default 5min) and resets on the next success. Returns `{ data, error, status, retry, abort }`; `status()` is `"idle"`, `"loading"`, `"success"`, or `"error"`.
1111
+
1112
+ **Pure helpers.** `isAddress(value)` checks `0x` + 40 hex chars. `shortenAddress(address, chars = 4)` renders `0xd8dA…6045` and passes invalid input through. `formatUnits(value, decimals = 18)` formats wei-style bigints as decimal strings without float artifacts; `parseUnits(value, decimals = 18)` parses them back and throws on invalid input. `CHAINS` maps seven chain ids (Ethereum, Optimism, BNB Chain, Polygon, Base, Arbitrum One, Sepolia) to name, currency, decimals, explorer, and a public RPC; `createChain(id)` looks one up as a reactive accessor (`undefined` for unknown ids).
1113
+
1114
+ **Market.** `createTokenPrice(tokenId, options?)` polls CoinGecko's public API (default 60s; swap `endpoint` or `vsCurrency`) and exposes `price()` and `change24h()`. `createPriceChange(source, options?)` samples any numeric signal on change and on `sampleMs` (default 60s), keeps a rolling `windowMs` (default 1h), and reports the percent change between the first and last sample; `reset()` clears the window. `createPriceCompare(a, b)` compares two price signals with `ratio()`, `diffPercent()`, and `leader()` (`"a"`, `"b"`, or `"tie"`).
1115
+
1116
+ **Chain (JSON-RPC).** `createGasPrice(options?)` reads `eth_gasPrice` every 15s as `{ wei, gwei }`. `createBalance(address, options?)` reads the native balance every 20s, or an ERC20 `balanceOf` when `token` is set, exposing `balance()` (bigint) and `formatted()`. `createTxReceipt(hash, options?)` polls every 4s until the receipt lands, then stops on its own; `mined()` mirrors that and `receipt()` carries `transactionHash`, `blockNumber`, `success`, and `gasUsed`. `createBlockNumber(options?)` polls the latest block every 12s as a chain-health heartbeat. `createChainlinkPrice(feed, options?)` reads a Chainlink `AggregatorV3Interface` feed on-chain (`decimals()` once, then `latestRoundData()` every 30s). All take an `endpoint` option defaulting to a public mainnet RPC.
1117
+
1118
+ **Identity and NFTs.** `createNFTMetadata(contract, tokenId, options?)` fetches `tokenURI` on-chain, resolves the JSON (one-shot with `retry`), rewrites `ipfs://` through a gateway, and exposes `metadata()` (`name`, `description`, `image`, `attributes`, `raw`) plus `image()`. `createENS(address, options?)` reverse-resolves an address through the public ENS registry (one-shot with `retry`); `name()` is `undefined` when no name is set. `createIdenticon(address, options?)` renders a deterministic mirrored-grid SVG avatar as a data URI, pure computation, works on the server.
1119
+
1120
+ ```tsx
1121
+ const { price, change24h } = createTokenPrice("ethereum");
1122
+ const { change } = createPriceChange(price);
1123
+ const { formatted } = createBalance("0xd8dA…6045");
1124
+ const { mined, receipt } = createTxReceipt("0x5c50…f7b");
1125
+ const avatar = createIdenticon("0xd8dA…6045");
1126
+ ```
1127
+
1097
1128
  ### `createToast(options?)`
1098
1129
 
1099
1130
  A signal-native toast queue with choreographed lifecycle. The primitive owns timing and state; you own the rendering, so no component opinions leak into your design system. Each toast moves through `"entering"` to `"visible"` to `"leaving"` to removed on the shared animation clock: bind `state` to CSS classes or drift values for enter/exit motion without any timers of your own.
@@ -1164,6 +1195,36 @@ const machine = createSlotMachine({
1164
1195
 
1165
1196
  Options: `symbols` (required, at least 2), `reels` (default `3`), `duration` (ms for the first reel, default `1400`), `stagger` (extra ms per subsequent reel, default `500`), `minSpins` (full rotations before stopping, default `3`), `easing` (default `"easeOutQuart"`), `onTick(reel, symbol)`, `onDone(result)`. Returns `{ values, result, status, spin, stop, reset }`: `values()` is the visible symbol per reel, `result()` the final symbols of the last spin, `status()` is `"idle"`, `"spinning"`, or `"done"`. `stop()` halts at the current symbols; `reset()` returns to idle. SSR-safe and reduced-motion safe: `spin()` jumps straight to the result.
1166
1197
 
1198
+ ### `createRedPacket(options?)`
1199
+
1200
+ Crypto red packet ceremony: tap to open, coins burst out with physics, the amount counts up. The primitive owns the ceremony state machine (`"sealed"`, `"opening"`, `"bursting"`, `"revealed"`) and the coin particle physics; you render the envelope and the coins. Each coin carries position, rotation, size, opacity, and its share of the total, split randomly like a real red packet grab.
1201
+
1202
+ ```tsx
1203
+ import { createRedPacket } from "solid-drift"
1204
+
1205
+ const packet = createRedPacket({ amount: 88, coins: 14 })
1206
+
1207
+ <button onClick={() => packet.open()}>
1208
+ {packet.status() === "sealed"
1209
+ ? "🧧 Tap to open"
1210
+ : `$${packet.revealed().toFixed(2)}`}
1211
+ </button>
1212
+ <For each={packet.coins()}>
1213
+ {(coin) => (
1214
+ <div
1215
+ class="coin"
1216
+ style={{
1217
+ transform: `translate(${coin.x}px, ${coin.y}px) rotate(${coin.rotation}deg)`,
1218
+ opacity: coin.opacity,
1219
+ width: `${coin.size}px`,
1220
+ }}
1221
+ />
1222
+ )}
1223
+ </For>
1224
+ ```
1225
+
1226
+ Options: `coins` (default `12`), `amount` (total, default `88`), `spread` (burst size in px, default `160`), `gravity` (px/s^2, default `900`), `openDuration` (ms, default `500`), `burstDuration` (ms, default `1600`), `revealDuration` (ms, default `800`), `onOpen`, `onReveal(amount)`. Returns `{ status, coins, revealed, open, reset }`. SSR-safe and reduced-motion safe: `open()` jumps straight to revealed with no burst.
1227
+
1167
1228
  ### Easings
1168
1229
 
1169
1230
  Named easings: `linear`, `easeInQuad`, `easeOutQuad`, `easeInOutQuad`, `easeInCubic`, `easeOutCubic`, `easeInOutCubic`, `easeInQuart`, `easeOutQuart`, `easeInOutQuart`, `easeOutExpo`, `easeOutBack`, plus the cartoon set: `easeInBack` (anticipation dip before movement), `easeInOutBack` (wind-up, overshoot, settle), `easeOutElastic` (decaying rubber-band oscillation), `easeOutBounce` (shrinking cartoon bounces). Also `cubicBezier(x1, y1, x2, y2)` for CSS-style curves. Pass a name or a custom `(t) => number` function anywhere an easing is accepted.
package/dist/fun.d.ts CHANGED
@@ -62,3 +62,90 @@ export interface SlotMachineControls<T = string> {
62
62
  * ```
63
63
  */
64
64
  export declare function createSlotMachine<T = string>(options: SlotMachineOptions<T>): SlotMachineControls<T>;
65
+ export type RedPacketStatus = "sealed" | "opening" | "bursting" | "revealed";
66
+ export interface RedPacketCoin {
67
+ id: number;
68
+ /** Pixels right from the packet center. */
69
+ x: number;
70
+ /** Pixels down from the packet center. */
71
+ y: number;
72
+ vx: number;
73
+ vy: number;
74
+ /** Degrees. */
75
+ rotation: number;
76
+ /** Degrees per second. */
77
+ vr: number;
78
+ /** Diameter in pixels. */
79
+ size: number;
80
+ /** 0..1, fades at the end of life. */
81
+ opacity: number;
82
+ /** This coin's share of the total amount. */
83
+ amount: number;
84
+ }
85
+ export interface RedPacketOptions {
86
+ /** Coins in the burst. Default 12. */
87
+ coins?: number;
88
+ /** Total amount, split randomly across coins like a real red packet. Default 88. */
89
+ amount?: number;
90
+ /** Burst size in pixels, scales launch speed. Default 160. */
91
+ spread?: number;
92
+ /** Gravity in px/s^2. Default 900. */
93
+ gravity?: number;
94
+ /** Envelope opening ceremony in ms. Default 500. */
95
+ openDuration?: number;
96
+ /** Coin burst in ms before the reveal. Default 1600. */
97
+ burstDuration?: number;
98
+ /** Amount count-up in ms once revealed. Default 800. */
99
+ revealDuration?: number;
100
+ /** Called when the packet is tapped open. */
101
+ onOpen?: () => void;
102
+ /** Called with the total amount when the reveal lands. */
103
+ onReveal?: (amount: number) => void;
104
+ }
105
+ export interface RedPacketControls {
106
+ status: Accessor<RedPacketStatus>;
107
+ /** Live coin particles, centered on the packet. Render them absolutely. */
108
+ coins: Accessor<RedPacketCoin[]>;
109
+ /** Amount counted up so far. Reaches the total when revealed. */
110
+ revealed: Accessor<number>;
111
+ /** Tap the packet: sealed -> opening -> bursting -> revealed. */
112
+ open: () => void;
113
+ /** Back to sealed. */
114
+ reset: () => void;
115
+ }
116
+ /**
117
+ * Crypto red packet ceremony: tap to open, coins burst out with physics,
118
+ * the amount counts up. The hongbao moment, as a signal-native primitive.
119
+ *
120
+ * The primitive owns the ceremony state machine and the coin particle
121
+ * physics; you render the envelope and the coins however fits your design.
122
+ * Each coin carries position, rotation, size, opacity, and its share of
123
+ * the total amount, split randomly like a real red packet grab.
124
+ *
125
+ * SSR-safe: `open()` jumps straight to revealed on the server. Under
126
+ * reduced motion the packet opens instantly with no burst: the amount
127
+ * just appears.
128
+ *
129
+ * ```tsx
130
+ * import { createRedPacket } from "solid-drift"
131
+ *
132
+ * const packet = createRedPacket({ amount: 88, coins: 14 })
133
+ *
134
+ * <button onClick={() => packet.open()}>
135
+ * {packet.status() === "sealed" ? "🧧 Tap to open" : `$${packet.revealed().toFixed(2)}`}
136
+ * </button>
137
+ * <For each={packet.coins()}>
138
+ * {(coin) => (
139
+ * <div
140
+ * class="coin"
141
+ * style={{
142
+ * transform: `translate(${coin.x}px, ${coin.y}px) rotate(${coin.rotation}deg)`,
143
+ * opacity: coin.opacity,
144
+ * width: `${coin.size}px`,
145
+ * }}
146
+ * />
147
+ * )}
148
+ * </For>
149
+ * ```
150
+ */
151
+ export declare function createRedPacket(options?: RedPacketOptions): RedPacketControls;
package/dist/fun.js CHANGED
@@ -1,6 +1,7 @@
1
1
  import { createSignal, onCleanup, } from "solid-js";
2
2
  import { now, schedule } from "./engine.js";
3
3
  import { resolveEasing } from "./easing.js";
4
+ import { createTween } from "./tween.js";
4
5
  import { prefersReducedMotion } from "./reduced-motion.js";
5
6
  /**
6
7
  * Gacha slot machine: reels launch fast, decelerate with momentum, and
@@ -131,3 +132,145 @@ export function createSlotMachine(options) {
131
132
  onCleanup(() => cancel?.());
132
133
  return { values, result, status, spin, stop, reset };
133
134
  }
135
+ /**
136
+ * Crypto red packet ceremony: tap to open, coins burst out with physics,
137
+ * the amount counts up. The hongbao moment, as a signal-native primitive.
138
+ *
139
+ * The primitive owns the ceremony state machine and the coin particle
140
+ * physics; you render the envelope and the coins however fits your design.
141
+ * Each coin carries position, rotation, size, opacity, and its share of
142
+ * the total amount, split randomly like a real red packet grab.
143
+ *
144
+ * SSR-safe: `open()` jumps straight to revealed on the server. Under
145
+ * reduced motion the packet opens instantly with no burst: the amount
146
+ * just appears.
147
+ *
148
+ * ```tsx
149
+ * import { createRedPacket } from "solid-drift"
150
+ *
151
+ * const packet = createRedPacket({ amount: 88, coins: 14 })
152
+ *
153
+ * <button onClick={() => packet.open()}>
154
+ * {packet.status() === "sealed" ? "🧧 Tap to open" : `$${packet.revealed().toFixed(2)}`}
155
+ * </button>
156
+ * <For each={packet.coins()}>
157
+ * {(coin) => (
158
+ * <div
159
+ * class="coin"
160
+ * style={{
161
+ * transform: `translate(${coin.x}px, ${coin.y}px) rotate(${coin.rotation}deg)`,
162
+ * opacity: coin.opacity,
163
+ * width: `${coin.size}px`,
164
+ * }}
165
+ * />
166
+ * )}
167
+ * </For>
168
+ * ```
169
+ */
170
+ export function createRedPacket(options = {}) {
171
+ const { coins: coinCount = 12, amount = 88, spread = 160, gravity = 900, openDuration = 500, burstDuration = 1600, revealDuration = 800, onOpen, onReveal, } = options;
172
+ const [status, setStatus] = createSignal("sealed");
173
+ const [coinList, setCoinList] = createSignal([]);
174
+ const [target, setTarget] = createSignal(0);
175
+ const tweened = createTween(target, {
176
+ duration: revealDuration,
177
+ easing: "easeOutExpo",
178
+ });
179
+ // On the server the tween effect never runs, so read the target directly.
180
+ const revealed = () => typeof window === "undefined" ? target() : tweened();
181
+ let cancel = null;
182
+ let burstAt = 0;
183
+ let lastT = 0;
184
+ let nextCoinId = 1;
185
+ const spawnCoins = () => {
186
+ const weights = Array.from({ length: coinCount }, () => 0.5 + Math.random());
187
+ const totalWeight = weights.reduce((a, b) => a + b, 0);
188
+ const speedScale = spread / 160;
189
+ return weights.map((w, i) => {
190
+ const angle = ((Math.random() * 120 - 60) * Math.PI) / 180;
191
+ const speed = (200 + Math.random() * 220) * speedScale;
192
+ return {
193
+ id: nextCoinId++,
194
+ x: 0,
195
+ y: 0,
196
+ vx: Math.sin(angle) * speed,
197
+ vy: -Math.cos(angle) * speed,
198
+ rotation: Math.random() * 360,
199
+ vr: (Math.random() - 0.5) * 720,
200
+ size: 24 + Math.random() * 16,
201
+ opacity: 1,
202
+ amount: (amount * w) / totalWeight,
203
+ };
204
+ });
205
+ };
206
+ const stepCoins = (t) => {
207
+ // Every coin is born at burstAt, so they share one age and one fade.
208
+ const dt = Math.min(Math.max((t - lastT) / 1000, 0), 0.05);
209
+ lastT = t;
210
+ const age = t - burstAt;
211
+ if (age >= burstDuration) {
212
+ setCoinList([]);
213
+ return;
214
+ }
215
+ const fadeStart = burstDuration * 0.7;
216
+ const opacity = age < fadeStart
217
+ ? 1
218
+ : Math.max(1 - (age - fadeStart) / (burstDuration - fadeStart), 0);
219
+ setCoinList((prev) => prev.map((c) => {
220
+ const vy = c.vy + gravity * dt;
221
+ return {
222
+ ...c,
223
+ x: c.x + c.vx * dt,
224
+ y: c.y + vy * dt,
225
+ vy,
226
+ rotation: c.rotation + c.vr * dt,
227
+ opacity,
228
+ };
229
+ }));
230
+ };
231
+ const open = () => {
232
+ if (status() !== "sealed")
233
+ return;
234
+ onOpen?.();
235
+ if (typeof window === "undefined" || prefersReducedMotion()) {
236
+ // Accessibility / SSR: no ceremony, the amount just appears.
237
+ setTarget(amount);
238
+ setStatus("revealed");
239
+ onReveal?.(amount);
240
+ return;
241
+ }
242
+ setStatus("opening");
243
+ const t0 = now();
244
+ cancel?.();
245
+ cancel = schedule((t) => {
246
+ const s = status();
247
+ if (s === "opening" && t - t0 >= openDuration) {
248
+ burstAt = t;
249
+ lastT = t;
250
+ setCoinList(spawnCoins());
251
+ setStatus("bursting");
252
+ }
253
+ else if (s === "bursting") {
254
+ stepCoins(t);
255
+ if (t - burstAt >= burstDuration) {
256
+ setCoinList([]);
257
+ setTarget(amount);
258
+ setStatus("revealed");
259
+ onReveal?.(amount);
260
+ cancel = null;
261
+ return false;
262
+ }
263
+ }
264
+ return true;
265
+ });
266
+ };
267
+ const reset = () => {
268
+ cancel?.();
269
+ cancel = null;
270
+ setCoinList([]);
271
+ setTarget(0);
272
+ setStatus("sealed");
273
+ };
274
+ onCleanup(() => cancel?.());
275
+ return { status, coins: coinList, revealed, open, reset };
276
+ }
package/dist/index.d.ts CHANGED
@@ -12,7 +12,7 @@ export { createScrollProgress, type ScrollTarget } from "./scroll.js";
12
12
  export { createInView, type InViewOptions } from "./inview.js";
13
13
  export { usePrefersReducedMotion, prefersReducedMotion, } from "./reduced-motion.js";
14
14
  export { isLowPowerMode, useLowPowerMode, type LowPowerOptions, } from "./power.js";
15
- export { createSlotMachine, type SlotMachineControls, type SlotMachineOptions, type SlotMachineStatus, } from "./fun.js";
15
+ export { createSlotMachine, type SlotMachineControls, type SlotMachineOptions, type SlotMachineStatus, createRedPacket, type RedPacketCoin, type RedPacketControls, type RedPacketOptions, type RedPacketStatus, } from "./fun.js";
16
16
  export { createStagger } from "./stagger.js";
17
17
  export { createHorizontalScroll, type HorizontalScrollOptions, type HorizontalScrollResult, } from "./horizontal.js";
18
18
  export { createScrub, type ScrubKeyframe, type ScrubOptions } from "./scrub.js";
@@ -34,3 +34,5 @@ export { createStreamReveal, createAgentState, parseDriftSpec, createSpecPlayer,
34
34
  export type { StreamRevealStatus, StreamRevealOptions, StreamRevealControls, AgentState, AgentStateTransition, AgentStateOptions, AgentStateControls, DriftSpecPrimitive, DriftSpecStep, DriftSpec, SpecPlayerStatus, SpecPlayerControls, } from "./ai.js";
35
35
  export { createTxLifecycle, createTicker, createMintReveal, createConnectButton, } from "./web3.js";
36
36
  export type { TxState, TxStatusInput, TxLifecycleOptions, TxLifecycleControls, TickerOptions, TickerControls, MintRevealStatus, MintRevealOptions, MintRevealControls, ConnectButtonOptions, ConnectButtonStatus, ConnectButtonControls, } from "./web3.js";
37
+ export { createPoll, shortenAddress, isAddress, formatUnits, parseUnits, CHAINS, createChain, createTokenPrice, createPriceChange, createPriceCompare, createGasPrice, createBalance, createTxReceipt, createBlockNumber, createChainlinkPrice, createNFTMetadata, createENS, createIdenticon, } from "./web3data.js";
38
+ export type { PollStatus, PollOptions, PollControls, ChainInfo, TokenPrice, TokenPriceOptions, PriceChangeOptions, GasPriceOptions, GasPriceData, BalanceOptions, BalanceData, TxReceiptData, TxReceiptOptions, BlockNumberOptions, ChainlinkPriceOptions, NFTMetadata, NFTMetadataOptions, ENSOptions, IdenticonOptions, } from "./web3data.js";
package/dist/index.js CHANGED
@@ -12,7 +12,7 @@ export { createScrollProgress } from "./scroll.js";
12
12
  export { createInView } from "./inview.js";
13
13
  export { usePrefersReducedMotion, prefersReducedMotion, } from "./reduced-motion.js";
14
14
  export { isLowPowerMode, useLowPowerMode, } from "./power.js";
15
- export { createSlotMachine, } from "./fun.js";
15
+ export { createSlotMachine, createRedPacket, } from "./fun.js";
16
16
  export { createStagger } from "./stagger.js";
17
17
  export { createHorizontalScroll, } from "./horizontal.js";
18
18
  export { createScrub } from "./scrub.js";
@@ -31,3 +31,4 @@ export { createKineticType, createScenePlayer, createShowreel, createCamera, cre
31
31
  export { createDrag, } from "./gesture.js";
32
32
  export { createStreamReveal, createAgentState, parseDriftSpec, createSpecPlayer, DriftSpecError, } from "./ai.js";
33
33
  export { createTxLifecycle, createTicker, createMintReveal, createConnectButton, } from "./web3.js";
34
+ export { createPoll, shortenAddress, isAddress, formatUnits, parseUnits, CHAINS, createChain, createTokenPrice, createPriceChange, createPriceCompare, createGasPrice, createBalance, createTxReceipt, createBlockNumber, createChainlinkPrice, createNFTMetadata, createENS, createIdenticon, } from "./web3data.js";
@@ -0,0 +1,323 @@
1
+ import { type Accessor } from "solid-js";
2
+ /**
3
+ * Web3 data layer: zero-dependency chain and market data as signals.
4
+ *
5
+ * Public RPC and API endpoints over fetch, with user-swappable endpoints.
6
+ * Every network primitive shares the `{ data, error, status, retry, abort }`
7
+ * shape and is SSR-safe (nothing fetches on the server).
8
+ *
9
+ * Honest limits: public endpoints are rate-limited, so default polling
10
+ * intervals are conservative. This is a read-only data layer: transaction
11
+ * signing stays with wallet libraries like wagmi.
12
+ */
13
+ export type PollStatus = "idle" | "loading" | "success" | "error";
14
+ export interface PollOptions {
15
+ /** Milliseconds between fetches. Default 30000. */
16
+ interval?: number;
17
+ /** Error backoff multiplier. Default 2. */
18
+ backoff?: number;
19
+ /** Backoff cap in milliseconds. Default 300000. */
20
+ maxInterval?: number;
21
+ /** Fetch immediately on creation. Default true. */
22
+ immediate?: boolean;
23
+ }
24
+ export interface PollControls<T> {
25
+ data: Accessor<T | undefined>;
26
+ error: Accessor<Error | null>;
27
+ status: Accessor<PollStatus>;
28
+ /** Fetch now and reset the backoff. */
29
+ retry: () => void;
30
+ /** Stop polling and abort any in-flight request. */
31
+ abort: () => void;
32
+ }
33
+ /**
34
+ * Backoff polling infrastructure for the data primitives.
35
+ *
36
+ * Fetches immediately (unless `immediate: false`), then on `interval`.
37
+ * On error the interval multiplies by `backoff` up to `maxInterval` and
38
+ * resets on the next success. Uses `setTimeout`, so background tabs get
39
+ * the browser's natural timer throttling instead of a busy rAF loop.
40
+ * SSR-safe: never fetches on the server.
41
+ *
42
+ * ```ts
43
+ * const { data, status, retry, abort } = createPoll(
44
+ * async (signal) => {
45
+ * const res = await fetch("https://api.example.com/price", { signal });
46
+ * return res.json();
47
+ * },
48
+ * { interval: 30000 },
49
+ * );
50
+ * ```
51
+ */
52
+ export declare function createPoll<T>(fetcher: (signal: AbortSignal) => Promise<T>, options?: PollOptions): PollControls<T>;
53
+ /**
54
+ * Shorten an EVM address: `0x1234567890abcdef...` becomes `0x1234…abcd`.
55
+ * Returns the input unchanged when it is not a valid address.
56
+ */
57
+ export declare function shortenAddress(address: string, chars?: number): string;
58
+ /** True for `0x` + 40 hex chars. Checksum-agnostic. */
59
+ export declare function isAddress(value: string): boolean;
60
+ /**
61
+ * Format a wei-style integer as a decimal string: `formatUnits(1500000000000000000n)`
62
+ * is `"1.5"`. Accepts bigint or integer strings. BigInt-safe, no floats.
63
+ */
64
+ export declare function formatUnits(value: bigint | string, decimals?: number): string;
65
+ /**
66
+ * Parse a decimal string into wei-style bigint: `parseUnits("1.5")` is
67
+ * `1500000000000000000n`. Throws on invalid input or too many decimals.
68
+ */
69
+ export declare function parseUnits(value: string, decimals?: number): bigint;
70
+ export interface ChainInfo {
71
+ id: number;
72
+ name: string;
73
+ currency: string;
74
+ decimals: number;
75
+ explorer: string;
76
+ rpc: string;
77
+ }
78
+ /** Registry of well-known EVM chains: id to name, currency, explorer, RPC. */
79
+ export declare const CHAINS: Record<number, ChainInfo>;
80
+ /**
81
+ * Look up a chain in `CHAINS` as an accessor. Unknown ids give `undefined`.
82
+ *
83
+ * ```ts
84
+ * const chain = createChain(() => 1)
85
+ * chain()?.explorer // "https://etherscan.io"
86
+ * ```
87
+ */
88
+ export declare function createChain(source: number | Accessor<number>): Accessor<ChainInfo | undefined>;
89
+ /**
90
+ * keccak256 of a byte array. Exported for tests; not part of the public API.
91
+ */
92
+ export declare function keccak256(data: Uint8Array): Uint8Array<ArrayBuffer>;
93
+ export interface TokenPrice {
94
+ /** Price in the quote currency. */
95
+ price: number;
96
+ /** 24h change percent, when the source reports it. */
97
+ change24h?: number;
98
+ }
99
+ export interface TokenPriceOptions extends PollOptions {
100
+ /** Quote currency id. Default "usd". */
101
+ vsCurrency?: string;
102
+ /** Price API base. Default CoinGecko public API. */
103
+ endpoint?: string;
104
+ }
105
+ /**
106
+ * Live token price as a signal, via CoinGecko's public API.
107
+ *
108
+ * The free endpoint is rate-limited; the default 60s interval is
109
+ * conservative on purpose. Pass your own `endpoint` (any base that
110
+ * answers `/simple/price?ids={id}&vs_currencies={vs}&include_24hr_change=true`).
111
+ *
112
+ * ```ts
113
+ * const { price, change24h, status } = createTokenPrice("ethereum");
114
+ * <Show when={status() === "success"}>
115
+ * ${(price() ?? 0).toFixed(2)} ({(change24h() ?? 0).toFixed(1)}%)
116
+ * </Show>
117
+ * ```
118
+ */
119
+ export declare function createTokenPrice(tokenId: string | Accessor<string>, options?: TokenPriceOptions): PollControls<TokenPrice> & {
120
+ price: Accessor<number | undefined>;
121
+ change24h: Accessor<number | undefined>;
122
+ };
123
+ export interface PriceChangeOptions {
124
+ /** Rolling window in ms. Default 3600000 (1h). */
125
+ windowMs?: number;
126
+ /** How often to sample the source in ms. Default 60000. */
127
+ sampleMs?: number;
128
+ }
129
+ /**
130
+ * Percent change of any numeric signal over a rolling window.
131
+ *
132
+ * Samples the source on each change and on `sampleMs`, keeps samples
133
+ * within `windowMs`, and reports `(last - first) / first * 100`.
134
+ * `undefined` until at least two samples exist. Signal-native, no network.
135
+ *
136
+ * ```ts
137
+ * const { price } = createTokenPrice("ethereum");
138
+ * const { change, reset } = createPriceChange(price);
139
+ * ```
140
+ */
141
+ export declare function createPriceChange(source: Accessor<number | undefined>, options?: PriceChangeOptions): {
142
+ change: Accessor<number | undefined>;
143
+ reset: () => void;
144
+ };
145
+ /**
146
+ * Compare two numeric signals: their ratio, percent difference, and which
147
+ * is larger. Any side `undefined` makes everything `undefined` until both
148
+ * have values. Signal-native, no network.
149
+ *
150
+ * ```ts
151
+ * const { price: eth } = createTokenPrice("ethereum");
152
+ * const { price: btc } = createTokenPrice("bitcoin");
153
+ * const { ratio, diffPercent, leader } = createPriceCompare(eth, btc);
154
+ * ```
155
+ */
156
+ export declare function createPriceCompare(a: Accessor<number | undefined>, b: Accessor<number | undefined>): {
157
+ ratio: Accessor<number | undefined>;
158
+ diffPercent: Accessor<number | undefined>;
159
+ leader: Accessor<"a" | "b" | "tie" | undefined>;
160
+ };
161
+ export interface GasPriceOptions extends PollOptions {
162
+ /** JSON-RPC endpoint. Default a public mainnet endpoint. */
163
+ endpoint?: string;
164
+ }
165
+ export interface GasPriceData {
166
+ wei: bigint;
167
+ gwei: number;
168
+ }
169
+ /**
170
+ * Current gas price over JSON-RPC (`eth_gasPrice`), as wei bigint and gwei.
171
+ * Default 15s polling. Swap `endpoint` for any chain.
172
+ */
173
+ export declare function createGasPrice(options?: GasPriceOptions): PollControls<GasPriceData> & {
174
+ wei: Accessor<bigint | undefined>;
175
+ gwei: Accessor<number | undefined>;
176
+ };
177
+ export interface BalanceOptions extends PollOptions {
178
+ /** JSON-RPC endpoint. Default a public mainnet endpoint. */
179
+ endpoint?: string;
180
+ /** ERC20 token contract. Omit for the native balance. */
181
+ token?: string;
182
+ /** Decimals for formatting. Default 18. */
183
+ decimals?: number;
184
+ }
185
+ export interface BalanceData {
186
+ balance: bigint;
187
+ formatted: string;
188
+ }
189
+ /**
190
+ * Token balance of an address: native (`eth_getBalance`) or ERC20
191
+ * (`balanceOf` via `eth_call`). Read-only; never signs.
192
+ *
193
+ * ```ts
194
+ * const { formatted } = createBalance("0xabc…", { token: "0xdef…" });
195
+ * ```
196
+ */
197
+ export declare function createBalance(address: string, options?: BalanceOptions): PollControls<BalanceData> & {
198
+ balance: Accessor<bigint | undefined>;
199
+ formatted: Accessor<string | undefined>;
200
+ };
201
+ export interface TxReceiptData {
202
+ transactionHash: string;
203
+ blockNumber: number;
204
+ /** true when `status` is 0x1, false when 0x0 (reverted). */
205
+ success: boolean;
206
+ gasUsed: bigint;
207
+ }
208
+ export interface TxReceiptOptions extends PollOptions {
209
+ /** JSON-RPC endpoint. Default a public mainnet endpoint. */
210
+ endpoint?: string;
211
+ }
212
+ /**
213
+ * Watch a transaction hash until its receipt lands. Polls every 4s and
214
+ * stops on its own once the receipt arrives; `mined()` mirrors that.
215
+ * Read-only confirmation; pairs with `createTxLifecycle` from web3.ts.
216
+ */
217
+ export declare function createTxReceipt(hash: string, options?: TxReceiptOptions): PollControls<TxReceiptData | null> & {
218
+ receipt: Accessor<TxReceiptData | null | undefined>;
219
+ mined: Accessor<boolean>;
220
+ };
221
+ export interface BlockNumberOptions extends PollOptions {
222
+ /** JSON-RPC endpoint. Default a public mainnet endpoint. */
223
+ endpoint?: string;
224
+ }
225
+ /**
226
+ * Latest block number over JSON-RPC. Default 12s polling.
227
+ * Handy as a chain-health heartbeat and a cache-busting ticker.
228
+ */
229
+ export declare function createBlockNumber(options?: BlockNumberOptions): PollControls<number> & {
230
+ blockNumber: Accessor<number | undefined>;
231
+ };
232
+ export interface ChainlinkPriceOptions extends PollOptions {
233
+ /** JSON-RPC endpoint. Default a public mainnet endpoint. */
234
+ endpoint?: string;
235
+ }
236
+ /**
237
+ * Read a Chainlink `AggregatorV3Interface` price feed on-chain:
238
+ * `decimals()` once, then `latestRoundData()` polled every 30s.
239
+ * Feed addresses live on the
240
+ * [Chainlink docs](https://docs.chain.link/data-feeds/price-feeds/addresses).
241
+ *
242
+ * ```ts
243
+ * // ETH / USD feed on mainnet
244
+ * const { price } = createChainlinkPrice("0x5f4eC3Df9cbd43714FE2740f5E3616155c5b8419");
245
+ * ```
246
+ */
247
+ export declare function createChainlinkPrice(feed: string, options?: ChainlinkPriceOptions): PollControls<number> & {
248
+ price: Accessor<number | undefined>;
249
+ };
250
+ export interface NFTMetadata {
251
+ name?: string;
252
+ description?: string;
253
+ image?: string;
254
+ attributes?: Array<Record<string, unknown>>;
255
+ raw: unknown;
256
+ }
257
+ export interface NFTMetadataOptions {
258
+ /** JSON-RPC endpoint for `tokenURI`. Default a public mainnet endpoint. */
259
+ endpoint?: string;
260
+ /** IPFS gateway base. Default "https://ipfs.io". */
261
+ gateway?: string;
262
+ }
263
+ /**
264
+ * Fetch an NFT's `tokenURI` on-chain and resolve its JSON metadata
265
+ * (one-shot, with `retry`). `ipfs://` URIs are rewritten through the
266
+ * gateway. Returns the parsed fields plus `raw` for anything custom.
267
+ *
268
+ * ```ts
269
+ * const { metadata, image, status, retry } = createNFTMetadata(
270
+ * "0xcontract…",
271
+ * 42,
272
+ * );
273
+ * ```
274
+ */
275
+ export declare function createNFTMetadata(contract: string, tokenId: string | number | bigint, options?: NFTMetadataOptions): {
276
+ data: Accessor<NFTMetadata | undefined>;
277
+ error: Accessor<Error | null>;
278
+ status: Accessor<PollStatus>;
279
+ retry: () => void;
280
+ abort: () => void;
281
+ metadata: Accessor<NFTMetadata | undefined>;
282
+ image: Accessor<string | undefined>;
283
+ };
284
+ export interface ENSOptions {
285
+ /** JSON-RPC endpoint. Default a public mainnet endpoint. */
286
+ endpoint?: string;
287
+ }
288
+ /**
289
+ * Reverse-resolve an address to its ENS name via the public ENS registry
290
+ * (one-shot, with `retry`). `undefined` when the address has no name set;
291
+ * errors (network, RPC) surface on `error`.
292
+ *
293
+ * ```ts
294
+ * const { name, status } = createENS("0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045");
295
+ * ```
296
+ */
297
+ export declare function createENS(address: string, options?: ENSOptions): {
298
+ data: Accessor<string | undefined>;
299
+ error: Accessor<Error | null>;
300
+ status: Accessor<PollStatus>;
301
+ retry: () => void;
302
+ abort: () => void;
303
+ name: Accessor<string | undefined>;
304
+ };
305
+ export interface IdenticonOptions {
306
+ /** Pixel size of the square image. Default 64. */
307
+ size?: number;
308
+ /** Grid cells per side. Default 8. */
309
+ cells?: number;
310
+ /** Background color. Default "#f0f0f0". */
311
+ background?: string;
312
+ }
313
+ /**
314
+ * Deterministic identicon avatar for any address as a data URI: a mirrored
315
+ * random-walk grid in SVG, so the same address always renders the same
316
+ * image. Pure computation, works on the server, no network.
317
+ *
318
+ * ```tsx
319
+ * const avatar = createIdenticon("0xabc…");
320
+ * <img src={avatar()} alt="avatar" width={64} height={64} />;
321
+ * ```
322
+ */
323
+ export declare function createIdenticon(address: string | Accessor<string>, options?: IdenticonOptions): Accessor<string>;