Skip to content

[PLAN] Add Connect-a-wallet widget to GoodWidget #114

Description

@goodbounties-nanoclaw-agent

[PLAN] Add Connect-a-wallet widget to GoodWidget

Parent issue: #113


Required states, flows, and behaviors

(Carried over from #113 — not re-derived.)

  • No wallet connected: only show a "Connect a wallet" prompt.
  • Connecting: handled via the primary verified identity flow; show a loading state while the connection is pending.
  • Connected (primary verified identity): unblocks a connect/disconnect address input field, where the user can paste an external address to link.
    • Pasting an address triggers a loading state for that address while its connection status is checked per chain against the primary verified address.
    • Each chain field then shows a "Connect" or "Disconnect" button, respective to that chain's current connection status.
  • Error states: surfaced via toast notification or an error block at the top of the widget.
    • Connect/disconnect transaction failure → red error block.
    • Connected to an unsupported network → orange warning block.
  • Chains in scope, per citizen-sdk's SupportedChains: Celo (42220), Fuse (122), XDC (50).

Execution plan

Reference files mapped (across repos)

GoodDollar/GoodSDKs (citizen-sdk — reference logic, not modified):

  • packages/citizen-sdk/README.md — wallet-link contract: identitySDK.connectAccount(), disconnectAccount(), checkConnectedStatus(), SupportedChains enum, WalletLinkOptions (onSecurityMessage, onHash, skipSecurityMessage).

GoodDollar/GoodWidget (target repo):

  • packages/citizen-claim-widget/ — closest precedent package; also built on @goodsdks/citizen-sdk. Use its scaffold as the structural template (widgetRuntimeContract.ts, adapter.ts, CitizenClaimWidget.tsx, integration.ts, element.ts, register.ts, index.ts).
  • packages/streaming-widget/src/ — structural precedent for component decomposition (not wallet logic): top-level StreamingWidget.tsx stays a thin contract/provider-wiring entry point; components/ holds the actual composed view (StreamingWidgetView.tsx), per-state/per-tab files (BalancesTab.tsx, HistoryTab.tsx, etc.), a per-item row component (PoolCard.tsx/StreamCard.tsx), a gating component (WalletGate.tsx), and shared helpers (format.ts, shared.tsx). Mirror this shape for connect-a-wallet-widget — see the updated package-placement assessment below.
  • packages/core/src/ (provider.tsx, hooks.ts, types.ts, eip1193.ts) — GoodWidgetProvider, useWallet()/useHost(), EIP1193Provider type. This is the only source of wallet/chain state; no new provider abstraction is needed. Confirmed: every existing widget package (ai-credits-widget, citizen-claim-widget, goodreserve-widget, staking-migration-widget, streaming-widget) consumes this same useWallet() and calls its GoodDollar SDK directly — none use Wagmi or @goodsdks/react-hooks.
  • packages/ui/src/components/ and components-test/ — existing primitives to reuse (below).
  • docs/widget-author-instructions.md, docs/architecture/theming-contract.md — styling hierarchy and createComponent() rules to follow; also documents the "Practical Component Workflow" this plan's decomposition follows.
  • docs/demo-environment.md — Storybook/Playwright conventions for the new widget's stories and tests.
  • AGENTS.md — Always/Ask-first/Never operating rules (see flags below).

Existing @GoodDollar packages to import

  • @goodwidget/core (workspace) — GoodWidgetProvider, useWallet, EIP1193Provider.
  • @goodwidget/ui (workspace) — see component reuse list below.
  • @goodwidget/embed (workspace) — createMiniAppElement for the Custom Element wrapper (element.ts), same as citizen-claim-widget.
  • @goodsdks/citizen-sdkIdentitySDK, SupportedChains, WalletLinkOptions types. Pin the same version range citizen-claim-widget uses unless a wallet-link fix requires a bump.

Confirmed decision (approved by Bounty Lead): do not add @goodsdks/react-hooks or wagmi as dependencies. react-hooks's wallet-link hooks require a Wagmi v2 provider tree, which conflicts with GoodWidget's provider-first, raw-EIP-1193 architecture — and matches how every other widget package in the repo is built. Call IdentitySDK methods directly from a local adapter, built on the viem client GoodWidget's own useWallet() already exposes — same pattern as citizen-claim-widget/adapter.ts.

New components — package placement assessment

New package: packages/connect-a-wallet-widget (@goodwidget/connect-a-wallet-widget), scaffolded like citizen-claim-widget, with the component tree following streaming-widget's decomposition pattern rather than a single file:

  • widgetRuntimeContract.ts — status union (loading | connecting | not_connected | connected_no_input | checking_address | ready | error), per-chain link state, props contract.
  • adapter.tsuseConnectAWalletAdapter hook: wraps IdentitySDK.connectAccount / disconnectAccount / checkConnectedStatus, tracks per-chain pending/connected/error state, surfaces the onSecurityMessage confirmation and onHash callback from the SDK's WalletLinkOptions.
  • ConnectAWalletWidget.tsx — top-level widget contract entry only (provider wiring), mirroring streaming-widget/src/StreamingWidget.tsx. No per-state UI logic here.
  • components/ConnectAWalletWidgetView.tsx — the composed view; switches on the adapter's status union and renders the components below, mirroring streaming-widget/src/components/StreamingWidgetView.tsx.
  • components/ConnectPrompt.tsx — "no wallet connected" prompt state.
  • components/WalletGate.tsx — connected/not-connected gating, mirroring streaming-widget/src/components/WalletGate.tsx.
  • components/AddressLinkForm.tsx — address paste input + per-address checking-loading state.
  • components/ChainLinkRow.tsx — one row per chain (badge + connect/disconnect + status), mirroring streaming-widget/src/components/PoolCard.tsx/StreamCard.tsx's per-item row pattern.
  • components/format.ts — pure formatting/status-mapping helpers (address display, chain name/status text), mirroring streaming-widget/src/components/format.ts.
  • components/shared.tsx — small shared UI bits reused across the above (e.g. banner-to-Alert mapping), mirroring streaming-widget/src/components/shared.tsx.
  • integration.ts, element.ts (tag gw-connect-a-wallet), register.ts, index.ts — same shape as citizen-claim-widget.

Reused as-is from packages/ui (no new component needed):

  • Alert, Toast/ToastContainer/createToast, Input, AddressDisplay, ChainBadge, TransactionButton, Spinner, Card, Heading, Text, YStack/XStack.

Net new to packages/ui (only if design confirms cross-widget reuse — do not build ahead of it):

  • A composite "per-chain link row" (chain badge + status + connect/disconnect button) is the one candidate worth assessing for packages/ui vs. widget-local. Default to widget-local (components/ChainLinkRow.tsx) for the first implementation; only promote if a second widget needs the same composite.

Build steps

  1. Scaffold packages/connect-a-wallet-widget mirroring citizen-claim-widget's.
  2. Implement widgetRuntimeContract.ts types for the state machine above.
  3. Implement adapter.ts on top of IdentitySDK (direct viem client from useWallet()).
  4. Implement the widget as the focused components/ tree above (view composition, per-state, per-chain row, shared helpers) rather than one monolithic file — ConnectAWalletWidget.tsx stays a thin contract/provider-wiring entry point. See package-placement assessment above.
  5. Add integration.ts, element.ts, register.ts, index.ts.
  6. Add Storybook stories mirroring citizen-claim-widget's Showcase/QA/mdx split.
  7. Add Playwright state coverage (states.spec.ts) covering: no-wallet, connecting, connected-empty-input, per-chain checking, per-chain connected, per-chain disconnected, tx-error, unsupported-network.
  8. Regenerate curated screenshot evidence per docs/demo-environment.md conventions.
  9. Wire into demo entry points the same way citizen-claim-widget is wired, if applicable.

Acceptance criteria

  • New @goodwidget/connect-a-wallet-widget package builds, lints, and exports a Custom Element (gw-connect-a-wallet) and a plain React component, consistent with citizen-claim-widget's package shape.
  • "No wallet connected" state renders only the "Connect a wallet" prompt.
  • "Connecting" state shows a loading indicator while the primary verified identity flow resolves.
  • Once connected, the address input is enabled; pasting an address triggers a per-address loading state while per-chain status is checked against the primary verified address, for Celo, Fuse, and XDC.
  • Each chain row shows a "Connect" or "Disconnect" action matching that chain's live connection status, and pressing it calls IdentitySDK.connectAccount/disconnectAccount and updates state on success.
  • Connect/disconnect transaction failures surface as a red error block (or toast, per final design).
  • Being connected to an unsupported network surfaces an orange warning block.
  • No new wallet/provider abstraction (e.g. Wagmi) is introduced; the widget consumes @goodwidget/core's existing useWallet() EIP-1193 context, consistent with every other widget package in the repo.
  • All new components either reuse existing packages/ui primitives or are added to the new widget package; nothing is added to packages/ui without a stated cross-widget reuse justification.
  • ConnectAWalletWidget.tsx is a thin entry point (provider wiring only); per-state/per-section UI lives in focused files under components/, mirroring streaming-widget's decomposition — no single monolithic component/hook implements the whole widget.
  • Storybook stories + curated screenshots and a Playwright states.spec.ts exist for the new widget, following citizen-claim-widget's conventions.
  • pnpm run build, pnpm run lint, pnpm test:storybook, and pnpm test:demo pass for the new package.

Human-reviewer checklist

  • Confirm whether the security-confirmation prompt (onSecurityMessage from WalletLinkOptions) should be a blocking modal/dialog (packages/ui's Dialog) or an inline confirmation — not specified in [Feature]: Add Connect-a-wallet widget #113.
    -- it should be a modal that gets confirmed before executing the transaction. That is also how its demoed.
  • Confirm error-block vs. toast placement for tx failures ([Feature]: Add Connect-a-wallet widget #113 allows either).
    -- Toast
  • Verify per-chain Connect/Disconnect buttons correctly disable/hide based on live on-chain status, not just optimistic local state.
    -- it should show either connect/disconnect, never hide. if status is loading it should have a loading indicator
  • Verify Storybook screenshots are committed following the pattern as has been comitted so far in tests
  • Verify no widget-local styling bypasses packages/ui tokens/themes per docs/widget-author-instructions.md.
  • Verify the components/ tree matches the decomposition above (no monolithic ConnectAWalletWidget.tsx) before merge.

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

Projects

Status
Prepare AI Task

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions