Repository navigation
[PLAN] Integrate invite SDK into citizen-claim-widget #85
Copy link
Copy link
Description
[PLAN] Integrate invite SDK into citizen-claim-widget
Parent: #84
Short description
Plan the full @goodsdks/invite-sdk 1.0.1 integration in @goodwidget/citizen-claim-widget. The embedded Invite Rewards experience must replicate the current GoodWallet invite flow and reward behavior while preserving the widget’s provider-first, viem-based runtime and existing Claim and News behavior.
Confirmed product decisions
- Invite destination: copy a ready-to-send message that includes the invite code and the exact page URL where the widget is currently loaded (
window.location.href). The host supplies no callback, origin, route, or destination configuration. Do not encode the invite code in the URL. - Join entry points: support joining an inviter from both the Claim and Invite Rewards tabs. They use the same invite state, validation, and mutation flow; entering a valid code must never produce divergent eligibility or join behavior between tabs.
- Bounty collection: include the complete GoodWallet-equivalent flow: education/status, eligibility diagnostics, and collection (including batch collection where SDK support applies), not status-only UI.
- Invite-code algorithm: use GoodWallet’s deterministic algorithm: convert the connected EVM address (without
0x) to bytes, Base58-encode it, then resolve progressively longer prefixes from 10 through 30 characters. The first prefix whosecodeToUserresult is the zero address is the code. If a prefix resolves to the caller, treat the caller as already registered; if no unused prefix is found, show a retryable generation error. Perform each lookup throughInviteSDK.resolveCode; do not reimplement contract resolution or contract preconditions. - GoodWallet reference: current GoodWallet copy, reward wording, and visual hierarchy are the source of truth, adapted only for the embedded widget’s existing design-system and accessibility contracts.
- SDK release/environments: directly consume
@goodsdks/invite-sdk1.0.1. It supports Celo and XDC; production uses production InvitesV2 addresses, andstagingnormalizes to the SDK’s development contract environment.
Repositories, packages, and reference files
GoodWidget
packages/citizen-claim-widget/src/CitizenClaimWidget.tsx— existing Claim / Invite Rewards / News shell; replace only the Invite Rewards placeholder and add the approved Claim-tab entry point without altering claim behavior.packages/citizen-claim-widget/src/adapter.ts— reuse the EIP-1193-to-viem client factory, environment normalization, and SDK adapter pattern for invite reads/writes.packages/citizen-claim-widget/src/widgetRuntimeContract.tsandintegration.ts— extend runtime state/actions/events without changing existing public claim contracts.packages/citizen-claim-widget/package.json— add the direct@goodsdks/invite-sdk@1.0.1dependency.examples/storybook/src/stories/citizen-claim-widget/andtests/widgets/citizen-claim-widget/— widget stories, fixtures, Playwright smoke coverage, and screenshot evidence.docs/demo-environment.md— Storybook and Playwright verification conventions.
GoodSDKs
packages/invite-sdk/src/sdks/viem-invite-sdk.ts— typedInviteSDKreads,resolveCode, join prechecks/simulation, writes, eligibility diagnostics, and bounty collection.packages/invite-sdk/src/types.ts— invite user/level/stat types, eligibility results, collection results, and typed errors.packages/invite-sdk/src/constants.ts— Celo/XDC support and production/development address resolution; staging maps to development.packages/invite-sdk/package.json— published package/version and viem peer dependency.
GoodWallet UX/UI source of truth
src/sections/GoodDollar/components/Invite/InviteView.tsx— hierarchy: campaign reward headline, invitee reward, “How it works”, share/join, code entry, total rewards, and invitee status.src/sections/GoodDollar/components/Invite/components/ShareLinkBox.tsx— join/register before a code is available, pending/success/error feedback, and native share/copy behavior.src/sections/GoodDollar/components/Invite/components/InvCodeBox.tsx— debounced code validation; hide entry after joining an inviter or receiving the invitee bounty.src/gooddollar/stores/inviteStore.ts— Base58 prefix generation rule,codeToUserlookup, invite data aggregation, and collection eligibility inputs.src/sections/GoodDollar/components/Invite/components/TotalEarnedBox.tsxandInviteesListBox.tsx— cumulative-reward and pending-versus-approved invitee presentation.
Required states, flows, and behaviors
Shared invite flow
- Keep Claim and News behavior unchanged. Both approved entry points consume one shared invite runtime state and actions.
- Handle disconnected, unsupported network, initial loading, refresh, retryable read error, invalid code, join pending, join success, and retryable mutation error states.
- Limit transactions to SDK-supported Celo/XDC. On unsupported networks, show a clear non-transactional state and do not attempt writes.
- Load the caller’s invite user, bounty level, invitees, pending invitees/count, collection eligibility diagnostics, and minimum-claims/minimum-days rules.
- Validate a submitted code through
InviteSDK.resolveCodebefore join. Reject malformed/unknown codes, the caller’s own code, and codes when the caller has already joined. A code becomes usable only after its owner’sjointransaction succeeds. - Submit all joins through InviteSDK prechecks/simulation and its join transaction. Prevent duplicate submissions while pending. Present
NOT_ACTIVE,INVITE_CODE_IN_USE,SELF_INVITE,USER_ALREADY_JOINED, and unknown errors as concise actionable messages. - Refresh all invite data after successful joins and collections; preserve an explicit retry action after failed reads or mutations.
Invite Rewards tab
- Mirror GoodWallet’s visual hierarchy and current copy: reward headline, invitee reward, accessible “How it works” disclosure (share, signup/join, claim/day eligibility period, reward receipt), share/create card, code-entry card, total earned, and invitee/pending status.
- For verified callers without a personal code, offer join/register to create one using the confirmed deterministic algorithm and SDK-backed lookups/prechecks.
- Once a code exists, copy a message in this form:
Claim GoodDollar with me. Open this page and use my invite code: <code>\n<current-page-url>. Use Web Share when available and clipboard fallback with success/error feedback. - Show the code-entry control only while the recipient remains eligible to join an inviter. The same control/action is also available from the Claim tab.
- Show pending invitees separately from reward-eligible/paid invitees. Include full collection controls, using SDK eligibility diagnostics before collection and batch results where supported. Never imply rewards are collectible before whitelist, minimum-days, and minimum-claims conditions are satisfied.
Integration constraints
- Align the SDK environment with the widget’s
production,staging, anddevelopmentselection; rely on InviteSDK’s documented staging-to-development resolution. - Do not copy InvitesV2 ABI, address resolution, contract prechecks, simulation, error mapping, or join/collection logic into GoodWidget.
- Keep invite-specific state orchestration and components in
packages/citizen-claim-widget. Reuse@goodwidget/uiprimitives; move nothing topackages/uiunless it is demonstrably reusable across widgets. - Preserve existing public claim props/events and named theme targets. Treat any required invite-specific event or theme target as documented public API.
Execution plan
- Verify the current citizen-claim runtime boundaries, Claim-tab integration point, and existing SDK/client versions; add
@goodsdks/invite-sdk@1.0.0as a direct widget dependency. - Extend the adapter/runtime contract with a shared invite state machine, typed SDK error mapping, refresh semantics, and supported-chain initialization using the existing viem clients.
- Implement the deterministic GoodWallet invite-code generator using only InviteSDK code resolution, then wire InviteSDK validation, join prechecks/simulation, join writes, collection eligibility, and collection writes.
- Build widget-local components matching the GoodWallet hierarchy: reward/education, create/share, shared join-with-code, earned rewards, invitee/pending list, and collection feedback. Surface the shared join control from both Claim and Invite Rewards.
- Implement current-page message generation, Web Share support, and clipboard fallback without host URL configuration.
- Add explicit loading, empty, verification-required, unsupported-chain, invalid-code, join-pending, join-success, collection-pending/success, SDK-error, and RPC-error views.
- Add Storybook examples for injected and custodial fixtures, ready-to-share, both join entry points, eligibility/pending states, collection, empty state, and errors.
- Add Playwright smoke coverage for main, empty, error, and collection flows, with
page.screenshot()calls and curated screenshot evidence under the widget test convention. - Update widget-facing documentation for supported networks, SDK version, no-host-URL-config behavior, shared join entry points, collection lifecycle, public API, and limitations.
- Run repository/package lint, build, Storybook interaction, and Playwright checks; verify Claim/News regression coverage and document visual evidence in the PR.
Acceptance criteria
-
@goodwidget/citizen-claim-widgetdirectly consumes@goodsdks/invite-sdk@1.0.1and does not duplicate InvitesV2 protocol logic. - Invite URL sharing copies the current loaded page URL alongside the code; no integrator URL/callback configuration exists.
- Users can join an inviter from both Claim and Invite Rewards through the same validated, pending-safe flow.
- A personal invite code follows GoodWallet’s Base58, shortest-unused-prefix algorithm and is usable only after a successful join transaction.
- Invite Rewards replicates GoodWallet’s current copy, reward hierarchy, education, reward status, pending/approved invitee presentation, and full bounty-collection functionality.
- Code validation resolves on-chain through InviteSDK and rejects unknown, self, and already-joined cases before joining.
- All writes use InviteSDK prechecks/simulation and provide loading, invalid-code, pending, success, typed-error, and retry states.
- Unsupported-chain, disconnected, loading, empty, verification-required, validation, RPC-error, and transaction-error states are intentional and testable.
- No wagmi or React Query dependency is introduced; new invite UI remains widget-local unless a reusable primitive is approved.
- Storybook covers injected/custodial fixtures and all planned flow states; Playwright covers main, empty, and error flows with
page.screenshot()evidence. - Documentation records Celo/XDC support, production/staging behavior, version, no-host-URL-config behavior, host events/theme targets, and limitations.
- Existing lint/build/test commands pass and Claim/News behavior continues to pass regression coverage.
Reactions are currently unavailable
Metadata
Metadata
Labels
No labels
Type
Projects
- StatusShow more project fieldsPrepare AI Task
Activity
[-][DRAFT][PLAN] Integrate invite SDK into citizen-claim-widget[/-][+][PLAN] Integrate invite SDK into citizen-claim-widget[/+]L03TJ3 commentedon Jul 15, 2026
This issue is marked
[DRAFT][PLAN]. Per the repository workflow, I will not make implementation changes while it remains a draft. Please promote the plan by removing[DRAFT](and confirm it is ready for execution), then I can implement and validate the specified integration.L03TJ3 commentedon Jul 15, 2026
Implementation is blocked before source changes:
pnpm add --filter @goodwidget/citizen-claim-widget @goodsdks/invite-sdk@1.0.0fails because the published package declares@goodsdks/citizen-sdk: "workspace:*".pnpm view @goodsdks/invite-sdk@1.0.0 dependencies peerDependencies version --jsonconfirms that manifest. A package published with a resolvable citizen-sdk version (or an approved replacement version) is required to satisfy the acceptance criterion to directly consume@goodsdks/invite-sdk@1.0.0and keep the widget buildable. I have not added a workaround or implementation against an unresolvable SDK.