Skip to content

[Plan]: Build GoodReserve Widget — Reserve swap UI backed by GoodReserve SDK #15

@L03TJ3

Description

@L03TJ3

Human summary

Build the first packages/goodreserve-widget package in GoodWidget: a reserve swap UI that uses @goodsdks/good-reserve as the protocol/runtime base, GoodWalletV2's swap flow as the UI and interaction reference, and GoodSDKs apps/demo-reserve-swap as the SDK behavior reference.


Why this matters

The GoodReserve Widget is the first swap-oriented widget in GoodWidget, proving that the framework can power protocol-backed financial interactions beyond simple claim flows. It establishes a reusable execution contract for quote-driven transaction flows, reserve-specific error handling, and buy/sell direction UIs. Completing this widget creates the second SDK-backed widget pattern alongside the Citizen Claim Widget, making the framework's multi-widget capability concrete and demonstrable.


Bounty tier

Legendary


Bounty type

Widget feature / SDK integration


Target package

packages/goodreserve-widget


Required base branch

copilot/sub-pr-6 (current GoodWidget branch with agent-ready bounty setup)


Agent execution instructions

Required references

The agent and contributor must inspect these before implementation:

GoodWidget (this repo):

  • AGENTS.md — always-read operating guide
  • ARCHITECTURE.md — package boundaries and data flow
  • docs/demo-environment.md — Storybook, Playwright, stories, fixtures
  • docs/architecture/theming-contract.md — Tamagui config, tokens, themes, naming rules
  • packages/ui/src/components/ — current production-aligned UI primitives
  • packages/ui/src/components-test/ — transitional primitives including TokenInput, TransactionButton, Alert, Spinner
  • packages/ui/src/createComponent.ts — named component + manifest registration wrapper
  • packages/ui/src/manifest.ts — runtime theme manifest
  • packages/ui/src/presets.ts — GoodWalletV2 token/theme preset
  • packages/citizen-claim-widget/src/widgetRuntimeContract.ts — SDK-backed widget contract pattern
  • packages/citizen-claim-widget/src/integration.ts — SDK integration descriptor pattern
  • packages/core/src/hooks.tsuseWallet(), useHost(), useGoodWidget()
  • packages/core/src/types.tsWalletState, provider props
  • examples/storybook/src/stories/ — existing story structure and fixture patterns
  • examples/storybook/src/fixtures/mockEip1193.ts — mock EIP-1193 provider

GoodSDKs repo (cross-repo, must be accessed before implementation):

  • packages/good-reserve/README.md — GoodReserve SDK overview, public API surface, supported chains
  • packages/good-reserve/ — SDK source: GoodReserveSDK class, reserve stats, quote, buy/sell
  • packages/react-hooks/README.md — React hooks layer for GoodSDKs
  • apps/demo-reserve-swap/src/components/ReserveSwap.tsx — full orchestration reference
  • apps/demo-reserve-swap/src/components/useReserveSwapQuote.ts — quote hook behavior
  • apps/demo-reserve-swap/src/components/useReserveSwapTx.ts — transaction execution hook behavior
  • apps/demo-reserve-swap/src/utils/errors.ts — reserve error classification and display

GoodWalletV2 repo (cross-repo, UI/UX reference, must be accessed before implementation):

  • src/sections/Swap/SwapView.tsx — main swap screen layout and orchestration
  • src/sections/Swap/swapStore.ts — swap state machine
  • src/sections/Swap/swapOverlayStore.ts — overlay/modal state (slippage, token selector)
  • src/sections/Swap/lifiUtils.ts — LiFi quote/route utilities (reference for SDK integration pattern, not for migration)
  • src/app/[locale]/@home/(home)/(bottomsheet)/swap/page.tsx — swap entry point and route
  • src/sections/Home/components/WalletSection.tsx — balance/wallet header
  • src/sections/Swap/components/AmountInputBox/AmountInputBox.tsx — amount input UI
  • src/sections/Swap/components/AmountInputBox/AmountInputBox.module.css — amount input styles
  • src/sections/Swap/components/AssetSelectBox/AssetSelectBox.tsx — asset selector UI
  • src/sections/Swap/components/AssetSelectBox/AssetSelectBox.module.css
  • src/sections/Swap/components/RouteSlider/RouteSlider.tsx — route display slider
  • src/sections/Swap/components/RouteSlider/RouteSlider.module.css
  • src/sections/Swap/components/RouteBox/RouteBox.tsx — route stats box
  • src/sections/Swap/components/RouteBox/RouteBox.module.css
  • src/sections/Swap/components/TokenInfoSlider/TokenInfoSlider.tsx — token info panel
  • src/sections/Swap/components/TokensDrawer/TokensDrawer.tsx — token selector drawer
  • src/sections/Swap/components/SwapDialog/ConfirmSwapDialog.tsx — confirmation dialog
  • src/sections/Swap/components/SwapDialog/swapDialogStore.ts — dialog state
  • src/sections/Swap/components/OutOfGasWarn/OutOfGasWarn.tsx — gas warning
  • src/components/Form/RoundButton/RoundButton.tsx — round action button

⚠️ If any cross-repo reference is unavailable or the SDK API surface has not been released yet, stop and comment on this issue before writing any code.


Missing references

The following files are in external repositories and could not be read during spec authoring. Their content must be verified before implementation begins:

Reference Repo What to verify
packages/good-reserve/ GoodSDKs Exact public API: GoodReserveSDK class, useGoodReserve hook, quote/buy/sell method signatures, supported chains, slippage parameters
packages/react-hooks/README.md GoodSDKs Whether useGoodReserve or useReserveSwapQuote-style hooks exist and are exported
apps/demo-reserve-swap/src/components/*.ts GoodSDKs Full orchestration pattern, quote debounce, balance loading, error boundary behavior
GoodWalletV2 swap components GoodWalletV2 Exact layout structure, CSS class names, visual hierarchy, style values to map into Tamagui tokens
SDK release status GoodSDKs Whether @goodsdks/good-reserve and @goodsdks/react-hooks are published on npm or branch-only

Package scaffold decision

packages/goodreserve-widget does not exist yet.

This bounty includes package scaffolding. The implementer must create the package structure before any UI work begins. The scaffolding must follow the exact pattern of packages/citizen-claim-widget:

  • package.json with name @goodwidget/goodreserve-widget
  • tsconfig.json and tsconfig.build.json
  • tsup.config.ts
  • src/index.ts (public exports)
  • src/widgetRuntimeContract.ts (state/action/props types)
  • src/integration.ts (integration descriptor)
  • README.md

The package must depend on @goodwidget/core and @goodwidget/ui (workspace), and declare @goodsdks/good-reserve (and optionally @goodsdks/react-hooks) as dependencies. It must be added to pnpm-workspace.yaml and the turbo.json pipeline.


Source-to-target mapping

Table 1 — GoodWalletV2 swap UI → GoodReserve Widget sections

GoodWalletV2 source GoodReserve Widget target Notes
SwapView.tsx ReserveSwapView in packages/goodreserve-widget/src/ReserveSwapView.tsx Main widget screen; orchestrates all sections
swapStore.ts (state machine) reserveSwapStore.ts in packages/goodreserve-widget/src/ Replace LiFi-backed state with GoodReserve SDK state
swapOverlayStore.ts (overlays) reserveOverlayStore.ts in packages/goodreserve-widget/src/ Slippage sheet + token selector overlay state
lifiUtils.ts (quote/route) packages/goodreserve-widget/src/utils/reserveUtils.ts Derive from useReserveSwapQuote.ts pattern, not from LiFi
AmountInputBox.tsx ReserveAmountInput in packages/goodreserve-widget/src/components/ Reserve-specific; extends TokenInput from @goodwidget/ui
AssetSelectBox.tsx ReserveAssetPair in packages/goodreserve-widget/src/components/ Shows buy/sell asset pair; uses Select from @goodwidget/ui
RouteSlider.tsx not needed Reserve has a single deterministic route; no LiFi multi-route slider needed
RouteBox.tsx ReserveRouteStats in packages/goodreserve-widget/src/components/ Reserve stats: price, spread, liquidity; uses Card from @goodwidget/ui
TokenInfoSlider.tsx ReserveTokenInfo in packages/goodreserve-widget/src/components/ Reserve token metadata panel (optional, lower priority)
TokensDrawer.tsx Inline in ReserveAssetPair using Drawer from @goodwidget/ui Token selector bottom sheet; reuse existing Drawer
ConfirmSwapDialog.tsx + swapDialogStore.ts ReserveConfirmDialog in packages/goodreserve-widget/src/components/ Confirmation modal; uses Dialog from @goodwidget/ui
OutOfGasWarn.tsx ReserveGasWarn in packages/goodreserve-widget/src/components/ Gas/low-balance warning; uses Alert from @goodwidget/ui/components-test
RoundButton.tsx Button from @goodwidget/ui Reuse directly; no new primitive needed
WalletSection.tsx (balance header) Inline balance display using useWallet() from @goodwidget/core Reads address/chainId; balance loaded via SDK
swap/page.tsx (entry + route) GoodReserveWidget root in packages/goodreserve-widget/src/GoodReserveWidget.tsx Widget entry point with provider boundary and config/themeOverrides props

Table 2 — GoodSDKs demo-reserve-swap → GoodReserve Widget behavior

GoodSDKs source GoodReserve Widget target Notes
ReserveSwap.tsx (orchestration) ReserveSwapView.tsx in widget Widget-local orchestration, not reusable across widgets
useReserveSwapQuote.ts (quote hook) useReserveSwapQuote.ts in packages/goodreserve-widget/src/hooks/ Copy/adapt the debounce and quote state pattern; wire to SDK
useReserveSwapTx.ts (tx hook) useReserveSwapTx.ts in packages/goodreserve-widget/src/hooks/ Copy/adapt tx lifecycle, pending/success/error states
errors.ts (error mapping) packages/goodreserve-widget/src/utils/errors.ts Reserve error classification, user-visible message mapping
SDK GoodReserveSDK or useGoodReserve Wire into useReserveSwapQuote and useReserveSwapTx hooks Follow demo pattern for SDK init, chain check, balance loading

Primitive extraction plan

Already in packages/ui — reuse directly, do not recreate

Existing primitive Used for
Button Primary swap action button, direction toggle, slippage confirm
TokenInput (components-test) Base for ReserveAmountInput; extend with reserve-specific balance/max
Drawer Token selector bottom sheet
Dialog Swap confirmation modal
Card Reserve route stats card
TokenAmount Quote amount display
Spinner Loading overlays
Alert (components-test) Gas warning, reserve warning, error states
Text, Heading, Separator Layout and typography
Toast Success/error notification after tx completes
TransactionButton (components-test) Final swap execute button (pending/success/error states)

New primitives that belong in packages/ui — generic, reusable beyond this widget

Only add to packages/ui if the component has no reserve-specific logic and is clearly reusable by other widgets:

Candidate Justification Decision
DirectionToggle (buy ↔ sell flip button) Generic buy/sell toggle with no SDK coupling; could be reused in any swap widget Add to packages/ui if the implementer confirms it has zero domain logic
SlippageInput (percent input with presets) Generic slippage control; could appear in any DeFi widget Add to packages/ui only if no reserve-specific preset values leak into it

⚠️ When in doubt about packages/ui candidacy, keep the component in packages/goodreserve-widget. Adding to packages/ui expands the public override surface. Ask first.

Components local to packages/goodreserve-widget — keep here, do not move to packages/ui

  • GoodReserveWidget — root widget component with provider boundary
  • ReserveSwapView — main screen orchestration
  • ReserveAmountInput — amount input with reserve-specific balance display, token label, and MAX
  • ReserveAssetPair — from/to asset pair display + direction selector
  • ReserveRouteStats — reserve price, spread, and liquidity stats card
  • ReserveTokenInfo — optional reserve token metadata panel
  • ReserveConfirmDialog — swap confirmation modal with reserve-specific fee/slippage summary
  • ReserveGasWarn — gas/low-balance warning overlay
  • reserveSwapStore.ts — widget state machine (direction, input amounts, quote, tx state)
  • reserveOverlayStore.ts — slippage sheet and token selector overlay state
  • useReserveSwapQuote.ts — debounced quote hook wrapping SDK
  • useReserveSwapTx.ts — transaction lifecycle hook wrapping SDK
  • utils/errors.ts — reserve error classification

GoodReserve SDK integration plan

Expected SDK APIs

Based on apps/demo-reserve-swap as the reference:

SDK export Usage in widget
GoodReserveSDK class or factory Initialize with provider + chainId inside widget hooks
sdk.getReserveStats() or equivalent Load reserve price, spread, liquidity on mount and on refresh
sdk.getQuote(direction, fromAmount, token) or equivalent Debounced quote loading on amount/direction change
sdk.buy(params) / sdk.sell(params) Execute swap transaction
sdk.getBalance(address, token) Load token balances for display and validation
sdk.getSupportedTokens() or reserve pair constants Populate the asset selector with valid reserve pairs
Error types from errors.ts Classify and display reserve-specific errors

Hook vs direct SDK

Preferred: Use useGoodReserve from @goodsdks/react-hooks if the hook provides quote, balance, buy/sell, and reserve stats in one stable API. If the hooks package does not yet provide these capabilities, use the GoodReserveSDK class directly via useReserveSwapQuote.ts and useReserveSwapTx.ts (following the demo pattern).

The choice between hooks and direct SDK must be confirmed by inspecting packages/react-hooks/README.md in the GoodSDKs repo before implementation.

Supported chains

Verify against packages/good-reserve/README.md. Expected: Celo mainnet (42220) and/or Fuse (122). The widget must check chainId at runtime and render the "unsupported chain" state for any chain not in the supported list.

Slippage

Default to the SDK's recommended slippage value (inspect demo-reserve-swap). Expose a slippage setting sheet that allows the user to choose from preset values (0.1%, 0.5%, 1%) or enter a custom value. Never hardcode slippage without checking the SDK's defaults.

Package version and dependency check

Required check before implementation:

  • Confirm @goodsdks/good-reserve is published on npm and at what version.
  • Confirm @goodsdks/react-hooks is published and whether it exports reserve-specific hooks.
  • If either package is branch-only or unpublished, report the version/capability gap as a blocker before working around it.
  • Confirm the mock/fixture pattern used in apps/demo-reserve-swap is usable for Storybook without requiring a live network connection.

Scope

Must change:

  • Create packages/goodreserve-widget/ with full package scaffold (package.json, tsconfig, tsup, src/index.ts)
  • Implement GoodReserveWidget root component with provider, config, themeOverrides, environment, onSwapSuccess, onSwapError props
  • Implement ReserveSwapView and all required child components listed in the source-to-target mapping
  • Implement useReserveSwapQuote and useReserveSwapTx hooks inside the widget package
  • Implement reserveSwapStore and reserveOverlayStore for widget state
  • Wire @goodsdks/good-reserve SDK into the quote and transaction hooks
  • Add widgetRuntimeContract.ts and integration.ts files following citizen-claim-widget patterns
  • Add the widget to pnpm-workspace.yaml and turbo.json
  • Add Storybook stories for GoodReserveWidget in examples/storybook/src/stories/
  • Add mock SDK fixtures for Storybook in examples/storybook/src/fixtures/
  • Add smoke test entry in tests/demo/smoke.spec.ts

May change:

  • packages/ui/src/components/ — only to add DirectionToggle or SlippageInput if confirmed generic per the primitive extraction decision above
  • packages/ui/src/presets.ts — only if new widget-agnostic theme keys are required (ask first)
  • examples/storybook/src/fixtures/mockEip1193.ts — only to extend to Celo mainnet (42220) if not already present

Must not change:

  • packages/core/ (unless a genuine gap in wallet context API is found and approved)
  • packages/embed/
  • packages/claim-widget/
  • packages/citizen-claim-widget/
  • packages/ui/src/theme.ts (unless new generic token seeds are strictly required)
  • packages/ui/src/presets.ts themes already defined for other widgets
  • Any existing public theme target names or override precedence
  • Any existing Storybook stories other than adding the new widget story
  • examples/react-web/, examples/html/, examples/expo/ (no changes unless explicitly approved)

Non-goals

Do not include:

  • Migration of the full generic LiFi swap engine from GoodWalletV2 into GoodWidget
  • A generic multi-protocol swap widget; this is specifically a GoodReserve widget
  • Recreating GoodWalletV2 swap components that are irrelevant to reserve (route sliders, multi-hop routes, bridge estimates)
  • Broad packages/ui primitive migrations unrelated to this widget
  • New wallet provider abstractions in packages/core
  • Changes to existing token/theme override precedence
  • Production deployment, ENS resolution, or real-wallet testing
  • Any onchain behavior that requires a live network connection to test
  • Implementing the bounty spec itself (this issue is the spec; implementation follows)

Architecture constraints

  • Keep SDK/protocol logic out of packages/ui.
  • Keep widget-specific flow logic inside packages/goodreserve-widget.
  • Preserve the provider-first runtime path through GoodWidgetProvider.
  • Preserve the current theming override precedence.
  • Do not introduce new public theme targets unless explicitly listed above.
  • Do not bypass the EIP-1193/provider wrapper model.
  • Do not modify demo packages except for explicit Storybook or verification deliverables.
  • Do not touch unrelated primitives, packages, or dependencies.

Acceptance criteria

  • packages/goodreserve-widget exists with a complete package scaffold following the citizen-claim-widget pattern.
  • GoodReserveWidget renders without errors when given a mock EIP-1193 provider (Celo chain ID 42220).
  • GoodReserveWidget renders the "unsupported chain" state when the provider reports a non-reserve chain.
  • GoodReserveWidget renders the "not connected" / "no provider" state when no provider is passed.
  • The buy direction state: user can enter a "from" amount, see a loading quote, and see a loaded quote result.
  • The sell direction state: user can toggle direction, enter a "from" amount, and see a sell quote result.
  • The confirm dialog renders with reserve-specific details (price, slippage, fee) before tx submission.
  • The slippage settings sheet renders and allows selecting preset values.
  • The "insufficient balance" state renders correctly and disables the swap button.
  • The "swap pending" state renders a loading indicator on the submit button and blocks interaction.
  • The "swap success" state renders a success message (or Toast) with transaction details.
  • The "swap error" state renders a user-readable error message mapped through errors.ts.
  • The "reserve warning" / "low liquidity" state renders if the SDK signals reserve stress.
  • GoodReserveWidget accepts config and themeOverrides props that apply through the standard GoodWidget theming pipeline.
  • widgetRuntimeContract.ts and integration.ts exist and accurately describe the widget's SDK dependencies, states, events, and chains.
  • All new named components in packages/goodreserve-widget use semantic theme keys from the GoodWalletV2 preset, not hardcoded colors.
  • Any new component added to packages/ui has a createComponent() name, manifest registration, and uses the standard theming decision checklist.
  • Storybook stories cover all required states listed below.
  • pnpm build passes for the full workspace.
  • pnpm test:storybook passes.
  • pnpm test:demo passes (smoke test for new story).

Required states, flows, and behaviors

State / flow Trigger Expected behavior Verification
sdk_initializing Widget mount, provider present Show full-screen spinner; no input interaction allowed Storybook SdkInitializing story
no_provider provider prop is undefined or null Show "Connect wallet" prompt; no swap UI rendered Storybook NoProvider story
unsupported_chain Provider reports chain not in reserve's supported list Show "Switch to Celo" (or supported chain) message; swap UI blocked Storybook UnsupportedChain story
reserve_stats_loading After SDK init, before first reserve stats response Show stats card skeleton/spinner Storybook StatsLoading story
idle_buy SDK ready, buy direction selected, no input Show empty amount input (buy direction), reserve stats, disabled swap button Storybook Default (ConnectedBuy) story
idle_sell User toggles direction to sell Show empty amount input (sell direction), updated stats, disabled swap button Storybook ConnectedSell story
amount_editing User types in amount input Debounce timer starts; no quote request until debounce clears Play function test or Storybook AmountEditing
balance_loaded SDK returns balance for connected address Show formatted balance next to amount input; enable MAX button Storybook controls/args
quote_loading Debounce clears after amount change Show spinner/skeleton in quote area; swap button disabled Storybook QuoteLoading story
quote_ready SDK returns a valid quote Show output amount, price impact, and estimated fee; swap button enabled Storybook QuoteReady story
quote_error SDK returns quote error Show inline error message; swap button disabled Storybook QuoteError story
insufficient_balance Input amount > wallet balance Show "Insufficient balance" message; swap button disabled Storybook InsufficientBalance story
low_liquidity_warning SDK signals reserve stress or high price impact Show warning banner above swap button; button still enabled with confirmation step Storybook LowLiquidityWarning story
slippage_selection User opens slippage settings Show slippage sheet with presets and custom input; selection persists Play function test
confirm_dialog User taps swap button with valid quote Show confirmation dialog: output amount, slippage, estimated fee, reserve address Play function test
swap_pending User confirms in dialog; tx submitted Show spinner on button; disable all inputs; show pending state Storybook SwapPending story
swap_success Tx confirmed onchain Show success message or Toast; reset input to idle state; update balance Storybook SwapSuccess story
swap_error Tx reverted or SDK error Show user-readable error mapped through errors.ts; allow retry Storybook SwapError story
refresh_quote Quote expired or user taps refresh Re-trigger quote loading from idle_buy/idle_sell state Play function test
refresh_reserve_context User taps refresh on reserve stats Re-load reserve stats from SDK; show stats loading briefly Play function test

SDK, dependency, and cross-repo checks

Required SDK/package assumptions (must be verified before implementation):

  • @goodsdks/good-reserve — reserve swap SDK. Assumed version: latest published on main or a designated branch. Must confirm: is it on npm? Which version?
  • @goodsdks/react-hooks — React hook layer. Must confirm: does it export useGoodReserve, useReserveSwapQuote, or equivalent hooks?
  • wagmi / viem — SDK peer deps may require these. Confirm whether GoodWidget's existing wagmi integration (packages/core/src/wagmi.ts) satisfies the SDK requirements or whether new peer deps are needed.

Cross-repo dependencies:

  • GoodSDKs repo — packages/good-reserve, packages/react-hooks, apps/demo-reserve-swap
  • GoodWalletV2 repo — swap components (UI reference only; no code copy-paste; adapt to Tamagui primitives)

Required checks:

  • Confirm whether @goodsdks/good-reserve and @goodsdks/react-hooks are already published, branch-only, or local-only.
  • If there is a version/capability gap, report it before implementing a workaround.
  • Confirm whether the SDK requires a specific minimum viem/wagmi version that conflicts with existing GoodWidget deps.
  • Confirm whether the SDK can be initialized and mocked in a Storybook environment without a live RPC.

Storybook requirements

  • Add examples/storybook/src/stories/GoodReserveWidget.stories.tsx.
  • Add deterministic mock SDK fixtures in examples/storybook/src/fixtures/mockReserveSDK.ts (no live network calls; all state pre-seeded).
  • Use createMockEip1193Provider from existing fixture; wire to Celo mainnet (chain ID 42220) or extend the fixture to support it.
  • Add data-testid values for all primary interactive elements following the ComponentName-variant convention.
  • Add a smoke test entry in tests/demo/smoke.spec.ts for at least the Default story.

Required stories/states:

Story export State shown data-testid required
Default (ConnectedBuy) SDK ready, buy direction, empty input GoodReserveWidget-default
ConnectedSell SDK ready, sell direction, empty input GoodReserveWidget-sell
QuoteReady Quote loaded, output amount visible, swap button enabled GoodReserveWidget-quote-ready
QuoteLoading Debounce in progress or SDK loading GoodReserveWidget-quote-loading
InsufficientBalance Input amount exceeds mock balance GoodReserveWidget-insufficient-balance
QuoteError Mock SDK returns quote error GoodReserveWidget-quote-error
SwapPending Mock tx submitted, awaiting confirmation GoodReserveWidget-swap-pending
SwapSuccess Mock tx confirmed GoodReserveWidget-swap-success
SwapError Mock tx failed GoodReserveWidget-swap-error
NoProvider No wallet provider passed GoodReserveWidget-no-provider
UnsupportedChain Mock provider on wrong chain GoodReserveWidget-unsupported-chain
LowLiquidityWarning SDK returns low-liquidity signal GoodReserveWidget-low-liquidity

Play function tests required for:

  • SlippageSelection — open slippage sheet → select a preset → verify selection is reflected
  • ConfirmDialog — enter amount with valid mock quote → press swap → verify confirm dialog appears
  • DirectionToggle — press direction toggle → verify buy/sell labels swap

Visual and UX requirements

Required visible sections:

  • Top: wallet balance header (address, token balances for both sides of the pair)
  • Main: amount input box with token label and MAX button
  • Middle: direction toggle (buy ↔ sell)
  • Second amount input: output quote display (read-only)
  • Reserve stats card: price, spread, estimated fee
  • Bottom: slippage indicator (tappable to open settings)
  • CTA: swap action button (label changes by state: "Swap", "Loading…", "Confirm in wallet", "Insufficient balance")
  • Gas warning banner (conditionally visible)
  • Confirm dialog (modal, conditionally visible)

Required responsive checks:

  • Desktop: widget renders at standard widget width (≥360px); no horizontal overflow
  • Mobile / narrow viewport: all sections stack vertically; no clipped text or controls

Screenshots or recordings required (in PR):

  • At minimum: Default, QuoteReady, SwapSuccess, SwapError, NoProvider story screenshots

Verification commands

Run and report in the PR body:

pnpm install
pnpm build
pnpm lint
pnpm storybook &
sleep 15
pnpm test:storybook
pnpm test:demo

If any command cannot run (e.g., SDK not yet published), explain exactly what was run instead and what was verified manually.


Pull request requirements

The PR description must include:

  • Reference to this issue number
  • Summary of changes made
  • Source-to-target mapping (Table 1 + Table 2 from above)
  • Primitive extraction decisions made (what went to packages/ui vs widget-local)
  • SDK dependency version used and how it was sourced (npm published, local path, etc.)
  • Acceptance criteria checklist (mirrored from this issue)
  • Commands run and their output or pass/fail summary
  • Storybook links, screenshots, or recordings for all required stories
  • Any intentional deviations from this spec with justification
  • Known risks and follow-up items

Known risks and follow-ups

  • SDK release gap: @goodsdks/good-reserve may not be published on npm at the time of implementation. If the package is branch-only, the implementer must report this as a blocker rather than using a path dependency without approval.
  • Storybook mock depth: The demo-reserve-swap uses live Celo RPC. Mocking the SDK for Storybook requires stub implementations of quote, balance, and stats methods. The implementer must produce deterministic stubs; do not skip mocking and rely on live network in CI.
  • TransactionButton maturity: packages/ui/src/components-test/TransactionButton.tsx exists but may not cover all swap tx states. The implementer should assess whether it can be used directly or whether a widget-local ReserveSwapButton is needed.
  • GoodWalletV2 CSS → Tamagui translation: GoodWalletV2 uses CSS modules; the implementer must translate layout, spacing, and color values into Tamagui token references and semantic theme keys, not into inline pixel values.
  • Slippage default: If the GoodReserve SDK has a recommended default slippage, use it. Do not pick an arbitrary default without checking.
  • Follow-up bounty candidates: token selector with search/filter, reserve stats refresh timer, multi-widget theming demo, and Expo/React Native compatibility.

Human reviewer checklist

  • I reviewed the issue before reviewing the diff.
  • I checked whether the PR solves the stated goal, not just a related problem.
  • I checked scope boundaries and unrelated file changes.
  • I checked architecture constraints (SDK in widget, not in packages/ui; no bypass of provider model).
  • I reviewed Storybook screenshots or recordings for all required stories.
  • I checked verification command output or reproduced the checks.
  • I left concrete review comments or fixed blockers directly.

Spec readiness check

  • Required references are listed and accessible (GoodWidget files verified; cross-repo files listed and flagged for pre-implementation inspection).
  • Scope and non-goals are clear.
  • Source-to-target mapping is complete (both GoodWalletV2 → widget and demo-reserve-swap → widget).
  • SDK/version assumptions are checked — implementer must verify before starting.
  • Acceptance criteria are testable.
  • Storybook/Playwright expectations are clear.
  • Verification commands are listed.
  • This issue is ready for Copilot execution — pending SDK version check and cross-repo reference access confirmation.

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type
    No fields configured for issues without a type.

    Projects

    Status

    Prepare AI Task

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions