Skip to content

feat: AI Credits Widget — buy AI coding credits with G$ on Celo#63

Merged
L03TJ3 merged 71 commits into
mainfrom
copilot/buy-ai-coding-credits
Jul 7, 2026
Merged

feat: AI Credits Widget — buy AI coding credits with G$ on Celo#63
L03TJ3 merged 71 commits into
mainfrom
copilot/buy-ai-coding-credits

Conversation

Copilot AI commented Jun 23, 2026

Copy link
Copy Markdown
Contributor

Closes #62

Summary

Adds @goodwidget/ai-credits-widget — a GoodWidget for buying AI coding credits with G$ on Celo. Credits settle on Base and are redeemable with AntSeed-compatible tools (Cursor, Cline, etc.).

The widget ships with:

  • React component (AiCreditsWidget), web-component entrypoints (<ai-credits-widget>), and integration manifest
  • Adapter state machine + mock/production backend client
  • Celo on-chain payment execution (Superfluid stream + transferAndCall deposit)
  • Buy / Manage tab navigation via WidgetTabs
  • Purchase flow stepper with bottom-drawer step content
  • Storybook showcase + QA fixture stories
  • Playwright screenshot smoke tests

Intentional deviations from #62

Issue #62 is stale in a few places. This PR implements the agreed product direction from review:

#62 plan This PR
13 distinct status values (connected_empty, has_credits, usage_*, insufficient_ai_credits, …) 9 statuses + activeTab: 'buy' | 'manage'
Unverified users get +10% deposit bonus GoodID-only bonus: 0% unverified, +10% deposit / +20% stream when verified
Single Celo tx via Multicall3 Separate Celo txs via executeCeloPayment
Operator consent via /v1/auth/nonce + payer plain-message sign EIP-712 SetOperator signed by buyer key → POST /v1/accounts/{buyer}/operator-consent
Random generatePrivateKey() buyer key Deterministic buyer key from payer wallet signature (buyerKeyDerivation.ts)
Per-section createComponent card wrappers Inline <Card> panels; createComponent kept only for AiCreditsStatusNotice + BonusBadgeFrame
Storybook default mini-shell wrapper useShell: false, layout: 'padded', 420px YStack wrapper (staking/citizen-claim pattern)

Status mapping

  • connected_empty / early buy setup → purchase_setup + activeTab: 'buy'
  • has_credits / usage_empty / usage_activeactiveTab: 'manage' (balance, stream, history, withdraw, API setup)
  • insufficient_ai_credits → dropped (no dedicated widget status)

Buyer key

  • Payer wallet signs buildBuyerKeyMessage(address); private key is derived deterministically via deriveBuyerPrivateKeyFromSignature
  • User retains the private key for AntSeed authentication
  • Hidden by default with Reveal / Hide toggle and save-before-continue warning
  • Primary Generate New Key + outline Paste Existing Key
  • Key rows truncate with copy icon always visible

Operator consent

  • Buyer key signs EIP-712 SetOperator typed data in-browser (operatorConsent.ts)
  • Signature submitted to backend: POST /v1/accounts/{buyer}/operator-consent
  • Adapter polls chain until operator is accepted before advancing

Celo payment (celoPayment.ts)

executeCeloPayment submits separate wallet confirmations (not a single Multicall batch):

  • Stream (streamAmountG > 0): G$.increaseAllowance(CFA, …) when needed, then CFA_V1_FORWARDER.createFlow or updateFlow with userData = abi.encode(buyer)
  • Deposit (depositAmountG > 0): G$.transferAndCall(vault, depositWei, abi.encode(buyer))

Celo mainnet addresses (chainId 42220):

  • G$ SuperToken: 0x62B8B11039FcfE5aB0C56E502b1C372A3d2a9c7A
  • Superfluid host: 0xA4Ff07cF81C02CFD356184879D953970cA957585
  • CFA V1 forwarder: 0xcfA132E353cB4E398080B9700609bb008eceB125

GoodID

  • Verification read on Celo via GoodID getWhitelistedRoot (chainClient.ts)
  • Drives hero badge and quote bonus: 0% unverified, +10% deposit / +20% stream when verified (quoteMath.ts, AmountPicker.tsx)

Navigation & purchase UX

  • Disconnected: circular Connect Wallet CTA; no tabs or stepper
  • Connected: WidgetTabsBuy Credits | Manage
  • Defaults to Manage after successful purchase or when account already has credits
  • Buy flow: stepper is the anchor; active step content opens in a bottom Drawer
  • Stepper status is position-based (before active = completed, active = in progress, after = pending) — fixes premature “Buy Credits completed” when balance > 0
  • Only the active step (or failed pay for retry) opens the drawer; completed/pending steps are not drawer targets

Manage tab

  • Balance, monthly stream, withdrawable amount, stats grid, funding/usage history
  • Close stream / withdraw actions
  • API Setup snippet always shown (setupSnippet required in adapter state)
  • Compact button styling via shared compactButtonProps

- widgetRuntimeContract.ts: 13 states, all action types, adapter factory pattern
- mockBackendClient.ts: interface + mock + production client, createBackendClient factory
- adapter.ts: useAiCreditsAdapter hook with full state machine (EIP-712 consent, buyer key gen, Celo payment)
- aiCreditsComponents.tsx: all widget-specific components (AiCreditsHero, BuyerKeyPanel, OperatorConsentStep, AmountPicker, CreditsBalance, SetupSnippet, UsageLog, AiCreditsFlowStepper)
- AiCreditsWidget.tsx: main widget with GoodWidgetProvider wrapper and state-driven render
- integration.ts: manifest with Celo+Base chain IDs and all 13 states
- element.ts + register.ts: <ai-credits-widget> web component bridge
- index.ts: public exports
- Storybook stories: QA + Showcase stories for all 13 states
- aiCreditsWidgetStories.tsx: mock state factory and story helpers
- Playwright smoke tests: states.spec.ts covering all states
- pnpm build passes, pnpm lint passes (0 errors)
Copilot AI changed the title [WIP] Add AI credits widget to buy AI coding credits with G$ feat: AI Credits Widget — buy AI coding credits with G$ on Celo Jun 23, 2026
Copilot AI requested a review from blueogin June 23, 2026 13:08
@sirpy sirpy linked an issue Jun 24, 2026 that may be closed by this pull request
20 tasks
@L03TJ3

L03TJ3 commented Jun 24, 2026

Copy link
Copy Markdown
Contributor

@copilot can you run the tests and commit the outputted screenshots of the widget?

Copilot AI commented Jun 24, 2026

Copy link
Copy Markdown
Contributor Author

@copilot can you run the tests and commit the outputted screenshots of the widget?

Done — all 13 tests now pass and screenshots are committed to tests/widgets/ai-credits-widget/test-results/.

Two bugs were fixed along the way:

  • Icon color tokens in aiCreditsComponents.tsx had a $ prefix ("$primary", "$success") which is not part of the IconColor type — this crashed 5 stories at render time. Fixed to "primary" / "success".
  • Strict-mode violations in 3 test assertions (getByText('+20%'), getByText('Transaction submitted'), getByText('Transaction reverted')) each matched multiple elements. Fixed with exact/full text strings.

Screenshots: acw-01-disconnected.pngacw-13-unsupported-chain.png (commit fix: icon color tokens + test selectors; add Playwright screenshots for all 13 states).

Copilot AI requested a review from L03TJ3 June 24, 2026 11:37

@blueogin blueogin left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@copilot

Buyer key
now Random bytes truncated to a pseudo-address; private key discarded (adapter.ts ~373–381)
Real generatePrivateKey + privateKeyToAccount; user keeps private key for AntSeed

Operator consent
now Payer walletClient.signTypedData (~405–426)
Calls backend to get signature(eip712 style)

Consent submission
now Signature only updates local state
calls backend with signature

(We can combine Operator Consent and Consent Submission into 1 and also can use 1 backend api endpoint)

Celo payment
now G$.transfer to stub 0x00…01 (~556–565)
approve + CeloGdAntSeedVault.deposit(amount, data) (data ABI-encoded AntSeed buyer address: abi.encode(buyerAddress). Required.)

GoodID
isGoodIdVerified always false in live adapter; never read on-chain
Vault isGoodIDVerified → hero badge + 20% stream bonus in quotes

the backend repository: https://github.com/GoodDollar/antseed-integration

Copilot AI commented Jun 24, 2026

Copy link
Copy Markdown
Contributor Author

@copilot

Buyer key...

All 5 corrections are in (commit fix: correct adapter per review — real buyer key, backend auth consent, vault deposit, on-chain GoodID):

1. Buyer key — switched from random-bytes pseudo-address to generatePrivateKey() + privateKeyToAccount() (viem/accounts). The private key is now shown once in BuyerKeyPanel with a save-warning; the derived address is ABI-encoded into the vault deposit data.

2 & 3. Operator consent + submission (combined into 1 handler) — removed walletClient.signTypedData against the stub operator address. New handleSignOperatorConsent calls POST /v1/auth/nonce → payer wallet signs the plain message → POST /v1/auth/api-keys → stores the returned gd_live_... API key in state as apiKey.

4. Celo payment — replaced G$.transfer(stub) with G$.approve(CELO_GD_ANTSEED_VAULT, amount) + CeloGdAntSeedVault.deposit(amount, abi.encode(buyerKeyAddress)). The vault address is a placeholder pending deployment; marked with a TODO comment.

5. GoodIDloadBalance now reads vault.isGoodIDVerified(address) on-chain alongside the G$ balance in the same Promise.all. The result feeds the hero badge and the 20% stream bonus calculation in bonusPercent.

Backend client updated to match the actual Worker API (/v1/auth/nonce, /v1/auth/api-keys, /v1/celo/events/record, /v1/accounts/{account}/credit). All 13 Playwright state tests pass with fresh screenshots.

Copilot AI requested a review from blueogin June 24, 2026 12:22
@blueogin

Copy link
Copy Markdown
Contributor

@copilot
As you can see in the screenshot, the Private key and Public Key is overflowed, it misses copy icon

For buttons, “Generate new key” and “Paste existing key” are the same size and color, giving them equal visual weight. The primary action (generate) should be emphasized.
The private key is shown in plain text by default. There is no masking (dots) or a controlled “reveal” toggle with a warning about exposing the secret.
Buttons use black as the primary color, while the wallet UI uses the green accent. This breaks brand consistency.

image

Copilot AI changed the title feat: AI Credits Widget — buy AI coding credits with G$ on Celo Improve Buyer Key panel safety, hierarchy, and key-field usability Jun 24, 2026

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 58 out of 81 changed files in this pull request and generated 8 comments.

Files not reviewed (1)
  • pnpm-lock.yaml: Generated file

Comment thread packages/ui/src/components/Stepper.tsx Outdated
Comment thread packages/ui/src/components-test/Input.tsx
Comment thread packages/ai-credits-widget/src/AiCreditsWidget.tsx
Comment thread packages/ai-credits-widget/src/adapter.ts
Comment thread packages/ai-credits-widget/src/adapter.ts
Comment thread packages/ai-credits-widget/src/adapter.ts
Comment thread packages/ai-credits-widget/src/celoPayment.ts
Comment thread packages/ai-credits-widget/src/backendClient.ts
@blueogin

blueogin commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

@copilot
address the comments in your last review except PR description related ones

…onChangeText, pay toast, adapter error status)

Co-authored-by: blueogin <43612769+blueogin@users.noreply.github.com>

Copilot AI commented Jul 6, 2026

Copy link
Copy Markdown
Contributor Author

@copilot
address the comments in your last review except PR description related ones

Done in commit fix: address copilot reviewer comments (stepper failed weight, input onChangeText, pay toast, adapter error status):

  • Stepper failed fontWeightfailed steps now use '700' (same as active) so the title is visually emphasised and consistent with showDescription's existing style.failed treatment.
  • InputFrame / onChangeText — removed the forwarded onChangeText prop from the DOM <input> element; createInputChangeHandlers still calls onChangeText via onChange, so behaviour is unchanged with no React unknown-prop warning.
  • handlePay toast — success message is now "Credits added successfully!" (accurate since actions.pay() only resolves after settlement), and the catch block uses the thrown error directly instead of potentially-stale state.error.
  • Adapter error paths — all three early-exit/catch paths (handleGenerateBuyerKey guard + catch, handleSignOperatorConsent guard) now call withDerivedStatus, so status, primaryAction, and primaryLabel are re-derived immediately and the error is visible in the UI right away.

All 11 Playwright tests pass.

Copilot AI requested a review from blueogin July 6, 2026 21:22
@blueogin

blueogin commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

@copilot
The current PR description is the source of truth.
Please update your understanding of the current implementation based on this PR description.
Do not make any code changes or commits
.

Copilot AI commented Jul 6, 2026

Copy link
Copy Markdown
Contributor Author

@copilot
The current PR description is the source of truth.
Please update your understanding of the current implementation based on this PR description.
...

Understood. My understanding is now aligned with the current PR description:

  • Celo payment: separate writeContract calls (increaseAllowance + createFlow/updateFlow, then transferAndCall) — not a Multicall3 batch.
  • Operator consent: EIP-712 SetOperator signed by the buyer key → POST /v1/accounts/{buyer}/operator-consent; adapter polls chain until accepted.
  • Buyer key: deterministic derivation from payer wallet signature via deriveBuyerPrivateKeyFromSignature.
  • GoodID: on-chain read via getWhitelistedRoot on the Celo GoodID contract; drives 0% (unverified) / +10% deposit / +20% stream (verified) bonus.
  • Statuses: 9 statuses + activeTab: 'buy' | 'manage' (no connected_empty, has_credits, usage_*, insufficient_ai_credits).
  • Navigation: WidgetTabs (Buy Credits | Manage) when connected; disconnected = circular Connect Wallet only.
  • Buy flow: stepper anchor + bottom Drawer for active step content.
  • Storybook: useShell: false, layout: 'padded', 420px YStack wrapper.

No code changes made.

@L03TJ3 L03TJ3 moved this from In Progress to In Review in GoodBounties Jul 7, 2026
@vercel vercel Bot temporarily deployed to Preview – good-widget-react-web July 7, 2026 11:40 Inactive
@vercel

vercel Bot commented Jul 7, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
good-widget-react-web Skipped Skipped Jul 7, 2026 12:05pm

Request Review

@L03TJ3 L03TJ3 merged commit b8eb02e into main Jul 7, 2026
2 checks passed
@github-project-automation github-project-automation Bot moved this from In Review to Deploy and Verify in GoodBounties Jul 7, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Deploy and Verify

Development

Successfully merging this pull request may close these issues.

[PLAN] AI Credits Widget: Buy AI coding credits with G$

4 participants