You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
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).
@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-sdk — IdentitySDK, 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.ts — useConnectAWalletAdapter 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/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):
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.
Implement widgetRuntimeContract.ts types for the state machine above.
Implement adapter.ts on top of IdentitySDK (direct viem client from useWallet()).
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.
Regenerate curated screenshot evidence per docs/demo-environment.md conventions.
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.
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.
[PLAN] Add Connect-a-wallet widget to GoodWidget
Parent issue: #113
Required states, flows, and behaviors
(Carried over from #113 — not re-derived.)
citizen-sdk'sSupportedChains: 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(),SupportedChainsenum,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-levelStreamingWidget.tsxstays 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 forconnect-a-wallet-widget— see the updated package-placement assessment below.packages/core/src/(provider.tsx,hooks.ts,types.ts,eip1193.ts) —GoodWidgetProvider,useWallet()/useHost(),EIP1193Providertype. 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 sameuseWallet()and calls its GoodDollar SDK directly — none use Wagmi or@goodsdks/react-hooks.packages/ui/src/components/andcomponents-test/— existing primitives to reuse (below).docs/widget-author-instructions.md,docs/architecture/theming-contract.md— styling hierarchy andcreateComponent()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
@GoodDollarpackages to import@goodwidget/core(workspace) —GoodWidgetProvider,useWallet,EIP1193Provider.@goodwidget/ui(workspace) — see component reuse list below.@goodwidget/embed(workspace) —createMiniAppElementfor the Custom Element wrapper (element.ts), same ascitizen-claim-widget.@goodsdks/citizen-sdk—IdentitySDK,SupportedChains,WalletLinkOptionstypes. Pin the same version rangecitizen-claim-widgetuses unless a wallet-link fix requires a bump.Confirmed decision (approved by Bounty Lead): do not add
@goodsdks/react-hooksorwagmias 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. CallIdentitySDKmethods directly from a local adapter, built on theviemclient GoodWidget's ownuseWallet()already exposes — same pattern ascitizen-claim-widget/adapter.ts.New components — package placement assessment
New package:
packages/connect-a-wallet-widget(@goodwidget/connect-a-wallet-widget), scaffolded likecitizen-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.ts—useConnectAWalletAdapterhook: wrapsIdentitySDK.connectAccount/disconnectAccount/checkConnectedStatus, tracks per-chain pending/connected/error state, surfaces theonSecurityMessageconfirmation andonHashcallback from the SDK'sWalletLinkOptions.ConnectAWalletWidget.tsx— top-level widget contract entry only (provider wiring), mirroringstreaming-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, mirroringstreaming-widget/src/components/StreamingWidgetView.tsx.components/ConnectPrompt.tsx— "no wallet connected" prompt state.components/WalletGate.tsx— connected/not-connected gating, mirroringstreaming-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), mirroringstreaming-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), mirroringstreaming-widget/src/components/format.ts.components/shared.tsx— small shared UI bits reused across the above (e.g. banner-to-Alertmapping), mirroringstreaming-widget/src/components/shared.tsx.integration.ts,element.ts(taggw-connect-a-wallet),register.ts,index.ts— same shape ascitizen-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):packages/uivs. 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
packages/connect-a-wallet-widgetmirroringcitizen-claim-widget's.widgetRuntimeContract.tstypes for the state machine above.adapter.tson top ofIdentitySDK(direct viem client fromuseWallet()).components/tree above (view composition, per-state, per-chain row, shared helpers) rather than one monolithic file —ConnectAWalletWidget.tsxstays a thin contract/provider-wiring entry point. See package-placement assessment above.integration.ts,element.ts,register.ts,index.ts.citizen-claim-widget's Showcase/QA/mdx split.states.spec.ts) covering: no-wallet, connecting, connected-empty-input, per-chain checking, per-chain connected, per-chain disconnected, tx-error, unsupported-network.docs/demo-environment.mdconventions.citizen-claim-widgetis wired, if applicable.Acceptance criteria
@goodwidget/connect-a-wallet-widgetpackage builds, lints, and exports a Custom Element (gw-connect-a-wallet) and a plain React component, consistent withcitizen-claim-widget's package shape.IdentitySDK.connectAccount/disconnectAccountand updates state on success.@goodwidget/core's existinguseWallet()EIP-1193 context, consistent with every other widget package in the repo.packages/uiprimitives or are added to the new widget package; nothing is added topackages/uiwithout a stated cross-widget reuse justification.ConnectAWalletWidget.tsxis a thin entry point (provider wiring only); per-state/per-section UI lives in focused files undercomponents/, mirroringstreaming-widget's decomposition — no single monolithic component/hook implements the whole widget.states.spec.tsexist for the new widget, followingcitizen-claim-widget's conventions.pnpm run build,pnpm run lint,pnpm test:storybook, andpnpm test:demopass for the new package.Human-reviewer checklist
onSecurityMessagefromWalletLinkOptions) should be a blocking modal/dialog (packages/ui'sDialog) 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.
-- Toast
-- it should show either connect/disconnect, never hide. if status is loading it should have a loading indicator
testspackages/uitokens/themes perdocs/widget-author-instructions.md.components/tree matches the decomposition above (no monolithicConnectAWalletWidget.tsx) before merge.