@ffaerber/swarm-connect 0.3.0 → 0.5.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
@@ -1,16 +1,25 @@
1
1
  # swarm-connect
2
2
 
3
+ [![npm version](https://img.shields.io/npm/v/@ffaerber/swarm-connect.svg)](https://www.npmjs.com/package/@ffaerber/swarm-connect)
4
+ [![npm downloads](https://img.shields.io/npm/dm/@ffaerber/swarm-connect.svg)](https://www.npmjs.com/package/@ffaerber/swarm-connect)
5
+ [![license](https://img.shields.io/npm/l/@ffaerber/swarm-connect.svg)](./LICENSE)
6
+
3
7
  A React connect-button and wizard for [Ethereum Swarm](https://www.ethswarm.org/) on the [Gnosis](https://www.gnosis.io/) chain. Drop in a single `<SwarmConnectButton />` and let users connect their wallet, verify a running Bee node, and pick a postage stamp — all from one modal.
4
8
 
9
+ 📦 **npm:** [`@ffaerber/swarm-connect`](https://www.npmjs.com/package/@ffaerber/swarm-connect)
10
+
5
11
  ## Features
6
12
 
13
+ - 🪜 **Gated sequential flow** — steps unlock in order: wallet → Gnosis network → xDAI gas → Bee node → *(node wallet)* → postage stamp.
14
+ - 🦊 **Wallet connect** — wallet connection via [wagmi](https://wagmi.sh/) connectors, pinned to the Gnosis chain (ID `100`), with an xDAI balance/gas check.
7
15
  - 🐝 **Bee node detection** — checks a Bee node's `/health` endpoint and surfaces its version.
8
- - 🔧 **Editable node URL** — users can change the Bee node hostname from the modal and reconnect (defaults to `http://localhost:1633`).
16
+ - 🔧 **Editable node URL** — users can change the Bee node hostname from the modal and reconnect (defaults to `http://localhost:1633`); the chosen URL is persisted in `localStorage`.
9
17
  - 🎟️ **Postage stamp selection** — fetches available stamps from `/stamps` and lets the user pick one.
10
- - 🦊 **Wallet connect** — injected-wallet connection via [wagmi](https://wagmi.sh/), pinned to Gnosis chain (ID `100`).
11
- - **At-a-glance status** — the button shows status dots for node, stamp, wallet, and network.
12
- - 🧩 **Headless hooks** — use the `useSwarmConnect` / `useBeeNode` / `usePostageStamps` hooks to build your own UI.
13
- - 🎨 **Zero-dependency styling** — inline styles, no CSS imports required.
18
+ - 🎛️ **Per-dApp requirements** (`requirements: { xdai, xbzz, postageStamp }`) each dApp declares what "connected" means for it. Disabled requirements drop their step from the modal and from `isFullyConnected`. E.g. a dApp that manages stamps itself uses `{ xdai: true, xbzz: false, postageStamp: false }`.
19
+ - 💸 **Node-wallet funding** (`xbzz: true`) for dApps that buy stamps themselves: shows the Bee node's own wallet (`/wallet`) and lets the user top it up with xDAI + xBZZ from their connected wallet (one-time setup). The dApp then buys stamps programmatically via `stamps.createStamp()` — the modal itself never purchases.
20
+ - **At-a-glance status** — the button shows status dots for every gated step.
21
+ - 🧩 **Headless hooks** — use the `useSwarmConnect` / `useBeeNode` / `usePostageStamps` / `useNodeWallet` hooks to build your own UI.
22
+ - 🎨 **Self-contained dark theme** — scoped CSS variables and inline styles, no CSS import required.
14
23
 
15
24
  ## Installation
16
25
 
@@ -65,11 +74,12 @@ Provides the wagmi and React Query context. Configured for the Gnosis chain with
65
74
 
66
75
  ### `<SwarmConnectButton>`
67
76
 
68
- The connect button. Opens a two-tab modal (**Swarm** for the Bee node + postage stamp, **Wallet** for connecting your wallet).
77
+ The connect button. Opens a dark-themed modal with sequential, gated steps — wallet → network (Gnosis) xDAI balance → Bee node node wallet → postage stamp — where each step unlocks only once the previous one is satisfied. Which steps appear depends on `requirements`: wallet, network, and Bee node are always present; the xDAI balance, node-wallet funding, and stamp-selection steps are included only when their requirement is enabled, and the numbering adapts. The widget ships its own scoped styles (no CSS import required); the `Space Grotesk` / `Inter` / `JetBrains Mono` fonts are used when present and fall back to system fonts otherwise.
69
78
 
70
79
  | Prop | Type | Default | Description |
71
80
  | --- | --- | --- | --- |
72
81
  | `beeApiUrl` | `string` | `http://localhost:1633` | Base URL of the Bee node API. |
82
+ | `requirements` | `SwarmConnectRequirements` | `{ xdai: true, xbzz: false, postageStamp: true }` | Which requirements this dApp needs; disabled ones drop their step. See [Requirements](#requirements). |
73
83
  | `label` | `string` | auto | Overrides the button label. Defaults to `Connect to Swarm`, or the truncated address once fully connected. |
74
84
 
75
85
  ### `<SwarmConnectModal>`
@@ -80,7 +90,7 @@ The modal rendered by `SwarmConnectButton`. Exported for advanced use if you wan
80
90
 
81
91
  ### `useSwarmConnect(config?)`
82
92
 
83
- The top-level hook combining node, stamp, wallet, and network state.
93
+ The top-level hook combining wallet, network, balance, node, node-wallet, and stamp state.
84
94
 
85
95
  ```tsx
86
96
  import { useSwarmConnect } from '@ffaerber/swarm-connect'
@@ -88,15 +98,22 @@ import { useSwarmConnect } from '@ffaerber/swarm-connect'
88
98
  function Status() {
89
99
  const {
90
100
  beeNode, // { isRunning, isChecking, version?, error?, check() }
91
- stamps, // { stamps, isLoading, error?, fetchStamps(), selectedStampId?, selectStamp() }
101
+ stamps, // { stamps, isLoading, error?, fetchStamps(), selectedStampId?, selectStamp(), createStamp(), isCreating, createError? }
102
+ nodeWallet, // { address?, xdai?, xbzz?, isLoading, error?, isFunded, refresh() } — the node's own wallet
92
103
  beeApiUrl, // current Bee node URL
93
104
  setBeeApiUrl, // change the Bee node URL at runtime, then re-check
105
+ requirements, // resolved { xdai, xbzz, postageStamp } booleans
94
106
  isWalletConnected,
95
107
  address,
96
108
  isOnGnosis,
97
109
  chainId,
98
- isFullyConnected, // node running + stamp selected + wallet connected + on Gnosis
99
- } = useSwarmConnect({ beeApiUrl: 'http://localhost:1633' })
110
+ balance, // { xdai?, isLoading, hasGas } native xDAI on Gnosis
111
+ isFullyConnected, // wallet + Gnosis + node + every enabled requirement
112
+ } = useSwarmConnect({
113
+ beeApiUrl: 'http://localhost:1633',
114
+ // this dApp needs user gas, but manages stamps itself:
115
+ requirements: { xdai: true, xbzz: false, postageStamp: false },
116
+ })
100
117
 
101
118
  return <span>{isFullyConnected ? 'Ready' : 'Not connected'}</span>
102
119
  }
@@ -112,39 +129,93 @@ const { isRunning, isChecking, version, error, check } = useBeeNode('http://loca
112
129
 
113
130
  ### `usePostageStamps(beeApiUrl?)`
114
131
 
115
- Fetches and selects postage stamps.
132
+ Fetches, selects, and (for dApps that buy stamps themselves) creates postage stamps.
116
133
 
117
134
  ```tsx
118
- const { stamps, isLoading, error, fetchStamps, selectedStampId, selectStamp } =
135
+ const { stamps, isLoading, error, fetchStamps, selectedStampId, selectStamp,
136
+ createStamp, isCreating, createError } =
119
137
  usePostageStamps('http://localhost:1633')
138
+
139
+ // Buy a batch via the node (cost = 2^depth × amount PLUR, paid by the node wallet):
140
+ const batchID = await createStamp({ amount: '1000000000', depth: 20, label: 'my-app' })
120
141
  ```
121
142
 
143
+ ### `useNodeWallet(beeApiUrl?)`
144
+
145
+ Reads the Bee node's **own** wallet — the one that pays for postage stamps — from `GET /wallet` and `GET /addresses`.
146
+
147
+ ```tsx
148
+ const { address, xdai, xbzz, isLoading, error, isFunded, refresh } =
149
+ useNodeWallet('http://localhost:1633')
150
+ ```
151
+
152
+ `isFunded` is true once the node wallet holds both xDAI (gas) and xBZZ (storage payment) — funding it is a **one-time setup**; returning users with a funded node skip the step automatically.
153
+
122
154
  ## Configuration
123
155
 
124
156
  ```ts
125
157
  interface SwarmConnectConfig {
126
- beeApiUrl?: string // initial Bee node URL; defaults to http://localhost:1633
158
+ beeApiUrl?: string // initial Bee node URL; defaults to http://localhost:1633
159
+ requirements?: SwarmConnectRequirements // which steps this dApp needs; see below
127
160
  }
128
161
  ```
129
162
 
130
- `beeApiUrl` is only the **initial** value. Users can edit the node URL from the modal's Swarm tab (or programmatically via `setBeeApiUrl` from `useSwarmConnect`), which re-checks the node at the new address. This is useful when the Bee node runs on a non-default host or port.
163
+ `beeApiUrl` is only the **initial** value. Users can edit the node URL from the modal's Bee node step (or programmatically via `setBeeApiUrl` from `useSwarmConnect`), which re-checks the node at the new address and persists the choice in `localStorage` so it survives sign-out / sign-in. This is useful when the Bee node runs on a non-default host or port.
164
+
165
+ ### Requirements
131
166
 
132
- "Fully connected" requires all of the following:
167
+ Every dApp needs a connected wallet, the Gnosis chain, and a reachable Bee node — those steps are always present. The rest is per-dApp via `requirements`; a disabled requirement drops its step from the modal and is ignored by `isFullyConnected`:
133
168
 
134
- 1. A reachable Bee node (`/health` responds OK).
135
- 2. A selected postage stamp.
136
- 3. A connected wallet.
137
- 4. The wallet on the Gnosis chain (chain ID `100`).
169
+ ```ts
170
+ interface SwarmConnectRequirements {
171
+ xdai?: boolean // default true — user wallet must hold xDAI for gas
172
+ xbzz?: boolean // default false node wallet funded (xDAI + xBZZ) so the dApp can buy stamps
173
+ postageStamp?: boolean // default true — user must select a postage stamp in the modal
174
+ }
175
+ ```
176
+
177
+ - **`xdai`** — adds the *Balance* step: shows the connected wallet's native xDAI on Gnosis and links a faucet while it's empty. Disable for read-only dApps that never transact from the user's wallet.
178
+ - **`xbzz`** — adds the *Node wallet* step: shows the Bee node's own xDAI/xBZZ balances (`GET /wallet`) and, while they're empty, offers a one-time top-up (a native xDAI transfer plus an ERC-20 xBZZ transfer to the node's address). Enable when your dApp buys stamps itself — purchasing is your dApp's job via `stamps.createStamp({ amount, depth, label })` (`POST /stamps/{amount}/{depth}`); the modal never buys.
179
+ - **`postageStamp`** — adds the *Postage stamp* step where the user picks an existing stamp. Disable when the dApp manages stamps itself (e.g. it creates and tracks its own batches).
180
+
181
+ Example — a dApp that needs user gas but manages stamps itself:
182
+
183
+ ```tsx
184
+ <SwarmConnectButton requirements={{ xdai: true, xbzz: false, postageStamp: false }} />
185
+ ```
186
+
187
+ "Fully connected" requires all of the following (skipping disabled requirements):
188
+
189
+ 1. A connected wallet.
190
+ 2. The wallet on the Gnosis chain (chain ID `100`).
191
+ 3. *(`xdai`)* A non-zero xDAI balance on that wallet.
192
+ 4. A reachable Bee node (`/health` responds OK).
193
+ 5. *(`xbzz`)* The node's wallet funded with xDAI + xBZZ.
194
+ 6. *(`postageStamp`)* A selected postage stamp.
138
195
 
139
196
  ## Development
140
197
 
141
198
  ```bash
142
199
  npm install
143
- npm run dev # start Vite dev server
200
+ npm run dev # start the demo app (example/) on the Vite dev server
144
201
  npm run type-check # type-check without emitting
145
202
  npm run build # build the library + type declarations to dist/
146
203
  ```
147
204
 
205
+ ### Demo app
206
+
207
+ `npm run dev` serves a playground in [`example/`](./example) for testing sign-in end to end. It shows **four connection scenarios side by side** — classic stamp selection, dApp-managed stamps (`postageStamp: false`), dApp-buys-stamps with node funding (`xbzz: true`), and a minimal node-only flow — each with its own `useSwarmConnect` instance and modal, so you can see how the gated steps adapt to `requirements`. Once a scenario is fully connected its card shows the live state: wallet address, chain, xDAI balance, Bee node URL + version, the node's overlay address, node-wallet balances, and the selected postage stamp (as applicable). Point it at a running Bee node (defaults to `http://localhost:1633`, editable in each modal).
208
+
209
+ **`ENOSPC: System limit for number of file watchers reached`?** Your machine's inotify watch limit is exhausted. Either:
210
+
211
+ - **Quick fix** — run the dev server in polling mode: `npm run dev:poll`.
212
+ - **Permanent fix** — raise the system limit:
213
+
214
+ ```bash
215
+ echo 'fs.inotify.max_user_watches=524288' | sudo tee /etc/sysctl.d/99-inotify.conf
216
+ sudo sysctl -p /etc/sysctl.d/99-inotify.conf
217
+ ```
218
+
148
219
  The library is built with Vite in library mode and ships ESM (`swarm-connect.js`), CommonJS (`swarm-connect.umd.cjs`), and TypeScript declarations. `react`, `react-dom`, `wagmi`, `viem`, and `@tanstack/react-query` are externalized.
149
220
 
150
221
  ## License
@@ -2,5 +2,5 @@ import { SwarmConnectConfig } from '../types';
2
2
  interface SwarmConnectButtonProps extends SwarmConnectConfig {
3
3
  label?: string;
4
4
  }
5
- export declare function SwarmConnectButton({ beeApiUrl, label }: SwarmConnectButtonProps): import("react").JSX.Element;
5
+ export declare function SwarmConnectButton({ beeApiUrl, requirements, label }: SwarmConnectButtonProps): import("react").JSX.Element;
6
6
  export {};
@@ -1,12 +1,17 @@
1
- import { BeeNodeStatus, PostageStampsState } from '../types';
1
+ import { BeeNodeStatus, NodeWalletState, PostageStampsState, SwarmConnectRequirements } from '../types';
2
2
  interface SwarmConnectModalProps {
3
3
  onClose: () => void;
4
4
  beeNode: BeeNodeStatus & {
5
5
  check: () => void;
6
+ disconnect: () => void;
6
7
  };
7
8
  stamps: PostageStampsState;
8
9
  beeApiUrl: string;
9
10
  setBeeApiUrl: (url: string) => void;
11
+ /** Per-dApp requirements; disabled ones drop their step. See SwarmConnectRequirements. */
12
+ requirements?: SwarmConnectRequirements;
13
+ /** Pass the instance from useSwarmConnect to share state; created internally otherwise. */
14
+ nodeWallet?: NodeWalletState;
10
15
  }
11
- export declare function SwarmConnectModal({ onClose, beeNode, stamps, beeApiUrl, setBeeApiUrl }: SwarmConnectModalProps): import("react").JSX.Element;
16
+ export declare function SwarmConnectModal({ onClose, beeNode, stamps, beeApiUrl, setBeeApiUrl, requirements, nodeWallet: nodeWalletProp, }: SwarmConnectModalProps): import("react").JSX.Element;
12
17
  export {};
@@ -0,0 +1,53 @@
1
+ import { CSSProperties, ReactNode } from 'react';
2
+ type Tone = 'ok' | 'bad' | 'warn';
3
+ type StepState = 'locked' | 'active' | 'done';
4
+ export declare function HexIcon({ size, stroke, glow, children }: {
5
+ size?: number;
6
+ stroke?: string;
7
+ glow?: boolean;
8
+ children: ReactNode;
9
+ }): import("react").JSX.Element;
10
+ export declare function Spinner({ size }: {
11
+ size?: number;
12
+ }): import("react").JSX.Element;
13
+ export declare function Badge({ children, tone }: {
14
+ children: ReactNode;
15
+ tone: Tone;
16
+ }): import("react").JSX.Element;
17
+ export declare function StatusRow({ tone, title, sub, children }: {
18
+ tone: Tone | 'flat';
19
+ title?: string;
20
+ sub?: string;
21
+ children?: ReactNode;
22
+ }): import("react").JSX.Element;
23
+ export declare function GhostBtn({ children, onClick, disabled }: {
24
+ children: ReactNode;
25
+ onClick?: () => void;
26
+ disabled?: boolean;
27
+ }): import("react").JSX.Element;
28
+ export declare function PrimaryBtn({ children, onClick, pending, block, style }: {
29
+ children: ReactNode;
30
+ onClick?: () => void;
31
+ pending?: boolean;
32
+ block?: boolean;
33
+ style?: CSSProperties;
34
+ }): import("react").JSX.Element;
35
+ export declare function LockedNotice({ children }: {
36
+ children: ReactNode;
37
+ }): import("react").JSX.Element;
38
+ /** Numbered, gated step header with a hex badge. */
39
+ export declare function StepLabel({ step, state, children, hint }: {
40
+ step: number;
41
+ state: StepState;
42
+ children: ReactNode;
43
+ hint?: ReactNode;
44
+ }): import("react").JSX.Element;
45
+ /** The bee / hex brand mark. */
46
+ export declare function BeeMark({ size }: {
47
+ size?: number;
48
+ }): import("react").JSX.Element;
49
+ /** Row of progress dots, one per gated step. */
50
+ export declare function StepDots({ states }: {
51
+ states: boolean[];
52
+ }): import("react").JSX.Element;
53
+ export {};
@@ -0,0 +1,34 @@
1
+ import { BalanceState, BeeNodeStatus, NodeWalletState, PostageStampsState } from '../types';
2
+ export declare function WalletStep({ isConnected, address }: {
3
+ isConnected: boolean;
4
+ address?: string;
5
+ }): import("react").JSX.Element;
6
+ export declare function NetworkStep({ locked, isOnGnosis, chainId }: {
7
+ locked: boolean;
8
+ isOnGnosis: boolean;
9
+ chainId?: number;
10
+ }): import("react").JSX.Element;
11
+ export declare function BalanceStep({ locked, balance }: {
12
+ locked: boolean;
13
+ balance: BalanceState;
14
+ }): import("react").JSX.Element;
15
+ export declare function NodeUrlInput({ value, disabled, onSubmit }: {
16
+ value: string;
17
+ disabled: boolean;
18
+ onSubmit: (url: string) => void;
19
+ }): import("react").JSX.Element;
20
+ export declare function BeeNodeStep({ node, beeApiUrl }: {
21
+ node: BeeNodeStatus & {
22
+ check: () => void;
23
+ };
24
+ beeApiUrl: string;
25
+ }): import("react").JSX.Element;
26
+ export declare function NodeWalletStep({ locked, nodeWallet }: {
27
+ locked: boolean;
28
+ nodeWallet: NodeWalletState;
29
+ }): import("react").JSX.Element;
30
+ export declare function StampStep({ stamps, locked, lockedHint }: {
31
+ stamps: PostageStampsState;
32
+ locked: boolean;
33
+ lockedHint: string;
34
+ }): import("react").JSX.Element;
@@ -1,23 +1,16 @@
1
1
  export declare const GNOSIS_CHAIN_ID = 100;
2
2
  export declare const DEFAULT_BEE_API_URL = "http://localhost:1633";
3
3
  export declare const BEE_API_URL_STORAGE_KEY = "swarm-connect:bee-api-url";
4
- export declare const STYLES: {
5
- readonly colors: {
6
- readonly primary: "#1a56db";
7
- readonly primaryHover: "#1e429f";
8
- readonly success: "#0e9f6e";
9
- readonly successLight: "#f0fdf4";
10
- readonly error: "#f05252";
11
- readonly errorLight: "#fff5f5";
12
- readonly warning: "#ff8a4c";
13
- readonly warningLight: "#fffbf5";
14
- readonly border: "#e5e7eb";
15
- readonly text: "#111827";
16
- readonly muted: "#6b7280";
17
- readonly bg: "#ffffff";
18
- readonly overlay: "rgba(0,0,0,0.5)";
19
- readonly stepInactive: "#d1d5db";
20
- readonly stepActive: "#1a56db";
21
- readonly stepDone: "#0e9f6e";
22
- };
4
+ /** xBZZ (bridged BZZ) ERC-20 on Gnosis chain. */
5
+ export declare const BZZ_TOKEN_ADDRESS: "0xdBF3Ea6F5beE45c02255B2c26a16F300502F68da";
6
+ /** BZZ uses 16 decimals (1 xBZZ = 1e16 PLUR), unlike the usual 18. */
7
+ export declare const BZZ_DECIMALS = 16;
8
+ /** Suggested one-time top-up amounts for the Bee node's wallet. */
9
+ export declare const DEFAULT_FUND_XDAI = "0.1";
10
+ export declare const DEFAULT_FUND_XBZZ = "0.5";
11
+ /** Default per-dApp requirements; see SwarmConnectRequirements. */
12
+ export declare const DEFAULT_REQUIREMENTS: {
13
+ readonly xdai: true;
14
+ readonly xbzz: false;
15
+ readonly postageStamp: true;
23
16
  };
@@ -1,5 +1,6 @@
1
1
  export declare function useBeeNode(beeApiUrl?: string): {
2
2
  check: () => Promise<void>;
3
+ disconnect: () => void;
3
4
  isRunning: boolean;
4
5
  isChecking: boolean;
5
6
  version?: string;
@@ -0,0 +1,7 @@
1
+ import { NodeWalletState } from '../types';
2
+ /**
3
+ * Reads the Bee node's OWN wallet (GET /wallet) and Ethereum address
4
+ * (GET /addresses). This is the wallet that pays for postage stamps —
5
+ * separate from the user's browser wallet.
6
+ */
7
+ export declare function useNodeWallet(beeApiUrl?: string): NodeWalletState;
package/dist/index.d.ts CHANGED
@@ -4,4 +4,5 @@ export { SwarmConnectProvider } from './context/SwarmConnectProvider';
4
4
  export { useSwarmConnect } from './hooks/useSwarmConnect';
5
5
  export { useBeeNode } from './hooks/useBeeNode';
6
6
  export { usePostageStamps } from './hooks/usePostageStamps';
7
- export type { SwarmConnectConfig, SwarmConnectState, BeeNodeStatus, PostageStamp, PostageStampsState, } from './types';
7
+ export { useNodeWallet } from './hooks/useNodeWallet';
8
+ export type { SwarmConnectConfig, SwarmConnectState, SwarmConnectRequirements, BeeNodeStatus, PostageStamp, PostageStampsState, CreateStampOptions, BalanceState, NodeWalletState, } from './types';