Skip to content

Commit 97dd6f1

Browse files
CopilotblueoginL03TJ3
authored
[WIP] Prepare GoodWallet for GoodWidget integration (#57)
* Initial plan * feat: integrate AI Credits widget into GoodWallet - Add eth_getBalance and eth_call to WIDGET_PROVIDER_METHODS in policy.ts - Create AiCreditsIcon.tsx (inline SVG spark/lightning icon) - Add aiCreditsWidget defineWidget entry to registry.ts - Include aiCreditsWidget in WIDGETS array (production build) - Add AI Credits registry tests (presence, validation, dashboard actions) - Add vitest alias mock for @goodwidget/ai-credits-widget/register - Add aiCreditsWidgetRegisterMock fixture for unit tests - Add @goodwidget/ai-credits-widget file: dep in package.json - Document local tarball workflow in README - Add local-packages/ to .gitignore Co-authored-by: blueogin <43612769+blueogin@users.noreply.github.com> * feat: enhance AI Credits widget integration and configuration - Added environment variables for AI Credits backend URL and funding vault address in .env.example. - Updated .gitignore to exclude specific local packages. - Modified next.config.mjs to include new packages for AI Credits widget and set up webpack configuration for React Native compatibility. - Enhanced AuthenticatedWidgetRoute to pass backend URL and funding vault address to the widget. - Updated WalletConnectDialog styles for improved layout and responsiveness. - Refactored widget properties to include backend URL and funding vault address in host properties. - Added tests for new properties in hostProperties.test.ts. Co-authored-by: blueogin <43612769+blueogin@users.noreply.github.com> * refactor: streamline widget properties and enhance integration - Removed backendUrl and fundingVaultAddress from AuthenticatedWidgetRoute and MountedWidget components. - Introduced elementProps to encapsulate backend URL and funding vault address for better property management. - Updated hostProperties to handle elementProps, improving the assignment and clearing of widget properties. - Adjusted tests to reflect changes in property handling and ensure proper functionality. Co-authored-by: blueogin <43612769+blueogin@users.noreply.github.com> * feat: integrate shared provider method allowlist for AI Credits widget * fix: restore eth_getBalance and eth_call to WIDGET_PROVIDER_METHOD_LIST Co-authored-by: blueogin <43612769+blueogin@users.noreply.github.com> * fix: forward eth_getBalance and eth_call to rpcRequest in RestrictedEip1193Provider Co-authored-by: blueogin <43612769+blueogin@users.noreply.github.com> * add: published goodwidgets package * fix: tests * fix: playwright test timeout, add: use env feature-flags for widgets * replace vercel deployments with github action prebuilt deployments * refactor: update WalletConnectDialog styles and class usage - Removed the dialogOverlay class and replaced it with aboveWidgets for improved z-index management. - Updated the WalletConnectDialog component to reflect the new class structure, ensuring proper overlay behavior. * refactor: simplify WalletConnectDialog overflow management - Removed the dependency on status for managing document body overflow. - Ensured overflow is set to "hidden" when the dialog is mounted and reset to "auto" on unmount. - Streamlined class name handling in the dialog container for improved readability. --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: blueogin <43612769+blueogin@users.noreply.github.com> Co-authored-by: blueogin <grilucosy@gmail.com> Co-authored-by: LewisB <lewis@ikigaistudios.eu>
1 parent 3847ba2 commit 97dd6f1

25 files changed

Lines changed: 5837 additions & 322 deletions

.env.example

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,3 +110,9 @@ POSTGRES_TOKENS_DATABASE_URL=pglite://./data/local.db
110110

111111
# Server-to-server API key for the airdrop registration endpoint.
112112
AIRDROP_API_KEY=
113+
114+
# AI Credits widget backend (PUBLIC — inlined into the client bundle).
115+
NEXT_PUBLIC_AI_CREDITS_BACKEND_URL=
116+
NEXT_PUBLIC_AI_CREDITS_FUNDING_VAULT_ADDRESS=
117+
# Hide the dashboard button while keeping /{locale}/ai-credits accessible.
118+
NEXT_PUBLIC_AI_CREDITS_WIDGET_DASHBOARD_ENABLED=true

.github/workflows/goodwallet-pr.yml

Lines changed: 31 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,17 +26,36 @@ jobs:
2626
with:
2727
node-version: 24
2828

29+
- name: Enable Corepack
30+
run: corepack enable
31+
2932
- name: Run install
30-
uses: borales/actions-yarn@v5
31-
with:
32-
cmd: install
33+
run: yarn install --immutable
3334

3435
- name: Run Biome Check
3536
uses: borales/actions-yarn@v5
3637
with:
3738
cmd: biome:verify:all
3839

40+
- name: Pull Vercel Preview environment
41+
if: github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository
42+
env:
43+
VERCEL_TOKEN: ${{ secrets.VERCEL_TOKEN }}
44+
VERCEL_ORG_ID: ${{ secrets.VERCEL_ORG_ID }}
45+
VERCEL_PROJECT_ID: ${{ secrets.VERCEL_PROJECT_ID }}
46+
run: npx --yes vercel@latest pull --yes --environment=preview --token="$VERCEL_TOKEN"
47+
48+
- name: Build Vercel prebuilt artifact
49+
if: github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository
50+
env:
51+
NODE_OPTIONS: --max_old_space_size=8192
52+
VERCEL_TOKEN: ${{ secrets.VERCEL_TOKEN }}
53+
VERCEL_ORG_ID: ${{ secrets.VERCEL_ORG_ID }}
54+
VERCEL_PROJECT_ID: ${{ secrets.VERCEL_PROJECT_ID }}
55+
run: npx --yes vercel@latest build --token="$VERCEL_TOKEN"
56+
3957
- name: Run Build
58+
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.repository
4059
env:
4160
NODE_OPTIONS: --max_old_space_size=4096
4261
run: yarn build
@@ -53,6 +72,15 @@ jobs:
5372
uses: borales/actions-yarn@v5
5473
with:
5574
cmd: test:e2e
75+
76+
- name: Deploy Vercel prebuilt Preview
77+
if: github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository
78+
env:
79+
VERCEL_TOKEN: ${{ secrets.VERCEL_TOKEN }}
80+
VERCEL_ORG_ID: ${{ secrets.VERCEL_ORG_ID }}
81+
VERCEL_PROJECT_ID: ${{ secrets.VERCEL_PROJECT_ID }}
82+
run: npx --yes vercel@latest deploy --prebuilt --token="$VERCEL_TOKEN"
83+
5684
- name: Upload Playwright screenshots and failure artifacts
5785
if: always()
5886
uses: actions/upload-artifact@v4

.gitignore

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,4 +65,7 @@ tsconfig.tsbuildinfo
6565

6666
# Codex
6767
codex-resume
68-
.agents/skills
68+
.agents/skills
69+
local-packages/
70+
.bounties
71+
.env*

README.md

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,3 +92,38 @@ yarn dev
9292
Other scripts: `yarn build`, `yarn start`, `yarn test`, `yarn lint`.
9393

9494
Then open http://localhost:3000 in your browser
95+
96+
## Preview deployments
97+
98+
Vercel Git deployments are disabled in `vercel.json`. Pull requests targeting
99+
`main` are built in GitHub Actions and deployed to Vercel as prebuilt Preview
100+
artifacts after the checks pass.
101+
102+
The repository needs these GitHub Actions secrets:
103+
104+
- `VERCEL_TOKEN`
105+
- `VERCEL_ORG_ID`
106+
- `VERCEL_PROJECT_ID`
107+
108+
## GoodWidget visibility
109+
110+
Widget routes are registered independently from dashboard buttons. For example,
111+
setting `NEXT_PUBLIC_AI_CREDITS_WIDGET_DASHBOARD_ENABLED=false` hides the AI
112+
Credits button while keeping the authenticated `/{locale}/ai-credits` path
113+
available. `NEXT_PUBLIC_*` values are read during the Next.js build, so a Vercel
114+
environment change requires a new deployment/redeploy.
115+
116+
## GoodWidget local-tarball workflow
117+
118+
Widget packages that are not yet published to npm (e.g. `@goodwidget/ai-credits-widget`)
119+
must be installed from a local tarball built inside the
120+
[GoodDollar/GoodWidget](https://github.com/GoodDollar/GoodWidget) monorepo.
121+
122+
1. Clone `GoodDollar/GoodWidget` alongside this repo.
123+
2. Inside the GoodWidget monorepo, pack the widget:
124+
```sh
125+
pnpm --filter @goodwidget/ai-credits-widget pack --pack-destination /path/to/GoodWallet/local-packages
126+
```
127+
3. The tarball lands at `local-packages/ai-credits-widget.tgz` (already referenced in `package.json`).
128+
4. Run `yarn install` in this repo to link the tarball.
129+
5. The path `local-packages/*.tgz` is git-ignored; do **not** commit tarballs.

next.config.mjs

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,42 @@
1-
// Injected content via Sentry wizard below
21
import { withSentryConfig } from "@sentry/nextjs"
32

43
/** @type {import('next').NextConfig} */
54
const nextConfig = {
65
reactCompiler: true,
76
reactStrictMode: true,
87

8+
transpilePackages: [
9+
"@goodwidget/ai-credits-widget",
10+
"@goodwidget/core",
11+
"@goodwidget/embed",
12+
"@goodwidget/ui",
13+
"react-native-web",
14+
],
15+
16+
turbopack: {
17+
resolveAlias: {
18+
"react-native": "react-native-web",
19+
},
20+
},
21+
22+
webpack: (config) => {
23+
config.resolve.alias = {
24+
...config.resolve.alias,
25+
"react-native$": "react-native-web",
26+
}
27+
config.resolve.extensions = [
28+
".web.js",
29+
".web.jsx",
30+
".web.ts",
31+
".web.tsx",
32+
...(config.resolve.extensions ?? []),
33+
]
34+
return config
35+
},
36+
937
env: {
1038
NEXT_PUBLIC_VERCEL_GIT_COMMIT_SHA: process.env.VERCEL_GIT_COMMIT_SHA,
39+
TAMAGUI_TARGET: "web",
1140
},
1241
images: {
1342
minimumCacheTTL: 31536000,

package.json

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,10 @@
2424
"@bitcoinerlab/secp256k1": "^1.2.0",
2525
"@electric-sql/pglite": "^0.5.3",
2626
"@gooddollar/goodprotocol": "^2.2.0",
27+
"@goodwidget/ai-credits-widget": "0.1.5",
28+
"@goodwidget/core": "0.1.4",
29+
"@goodwidget/embed": "0.1.4",
30+
"@goodwidget/ui": "0.1.4",
2731
"@hcaptcha/react-hcaptcha": "^2.0.2",
2832
"@json-rpc-tools/utils": "^1.7.6",
2933
"@lifi/sdk": "^4.1.0",
@@ -41,6 +45,10 @@
4145
"@solana/webcrypto-ed25519-polyfill": "^6.9.0",
4246
"@toruslabs/customauth": "^20.4.0",
4347
"@walletconnect/utils": "^2.23.10",
48+
"@x402/core": "^2.19.0",
49+
"@x402/evm": "^2.19.0",
50+
"@x402/extensions": "^2.19.0",
51+
"@x402/svm": "^2.19.0",
4452
"async-await-mutex-lock": "^1.0.12",
4553
"async-retry": "^1.3.3",
4654
"bitcoinjs-lib": "^7.0.1",
@@ -63,6 +71,7 @@
6371
"react-dom": "^19.2.7",
6472
"react-icons": "^5.6.0",
6573
"react-jazzicon": "^1.0.4",
74+
"react-native-web": "^0.19.13",
6675
"swr": "^2.4.2",
6776
"valtio": "^2.3.2",
6877
"viem": "^2.54.0",
@@ -77,6 +86,7 @@
7786
"@types/node": "26.1.1",
7887
"@types/react": "^19.2.17",
7988
"@types/react-dom": "^19.2.3",
89+
"@types/react-native-web": "^0",
8090
"@vitest/coverage-v8": "4.1.9",
8191
"autoprefixer": "^10.5.0",
8292
"babel-plugin-react-compiler": "^1.0.0",

playwright.config.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,9 @@ export default defineConfig({
1010
snapshotPathTemplate: "{testDir}/screenshots/{projectName}/{arg}{ext}",
1111
fullyParallel: false,
1212
workers: 1,
13+
timeout: 90_000,
14+
expect: { timeout: 30_000 },
15+
retries: process.env.CI ? 1 : 0,
1316
reporter: "list",
1417
use: {
1518
baseURL: "http://localhost:3000",

src/sections/WalletConnect/components/WalletConnectDialog/WalletConnectDialog.module.css

Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,5 @@
1-
.dialogOverlay {
2-
width: 100%;
3-
height: 100%;
4-
background-color: var(--token-bg-overlay);
5-
position: fixed;
6-
top: 0;
7-
z-index: 100;
8-
display: flex;
9-
align-items: center;
10-
max-width: 768px;
1+
.aboveWidgets {
2+
z-index: 1100;
113
}
124

135
.dialogContainer {

src/sections/WalletConnect/components/WalletConnectDialog/WalletConnectDialog.tsx

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
"use client"
22

3-
import { useEffect } from "react"
3+
import { useEffect, useState } from "react"
4+
import { createPortal } from "react-dom"
45
import { Button } from "ui"
56
import { useSnapshot } from "valtio"
67

@@ -15,6 +16,7 @@ import { ErrorDialog } from "./ErrorDialog"
1516
import { GenericDialog } from "./GenericDialog"
1617
import { SessionProposalDialog } from "./SessionProposalDialog"
1718
import { SessionRequestDialog } from "./SessionRequestDialog"
19+
import styles from "./WalletConnectDialog.module.css"
1820

1921
const renderDialog = (dialog: WalletConnectDialogType) => {
2022
switch (dialog.type) {
@@ -31,6 +33,12 @@ const renderDialog = (dialog: WalletConnectDialogType) => {
3133

3234
export const WalletConnectDialog = () => {
3335
const { dialog, status, exiting } = useSnapshot(walletConnectDialogStore)
36+
const [mounted, setMounted] = useState(false)
37+
38+
useEffect(() => {
39+
setMounted(true)
40+
return () => setMounted(false)
41+
}, [])
3442

3543
useEffect(() => {
3644
document.body.style.overflow = "hidden"
@@ -39,11 +47,11 @@ export const WalletConnectDialog = () => {
3947
}
4048
}, [])
4149

42-
if (status !== "pending") return null
50+
if (!mounted || status !== "pending") return null
4351

44-
return (
52+
return createPortal(
4553
<div
46-
className={dialogStyles.dialogOverlay}
54+
className={`${dialogStyles.dialogOverlay} ${styles.aboveWidgets}`}
4755
onClick={(e) => {
4856
updateWalletConnectDialogStatus("rejected")
4957
e.stopPropagation()
@@ -75,6 +83,7 @@ export const WalletConnectDialog = () => {
7583
) : null}
7684
</div>
7785
</dialog>
78-
</div>
86+
</div>,
87+
document.body,
7988
)
8089
}

src/widgets/AuthenticatedWidgetRoute.tsx

Lines changed: 3 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -6,19 +6,14 @@ import { setBottomSheetProps } from "@/components/Snippet/BottomSheet/bottomShee
66
import { LoadingSpinner } from "@/components/Snippet/LoadingSpinner"
77
import { useSessionContext } from "@/login/hooks/context"
88

9-
import type { WidgetHostProps } from "./hostTypes"
109
import { useWidgetProvider, WidgetProvider } from "./provider/WidgetProvider"
1110
import { type RegisteredWidget, widgetRegistry } from "./registry"
1211
import { WidgetRenderer } from "./WidgetRenderer"
1312

1413
export const AuthenticatedWidgetRoute = ({
1514
widgetId,
16-
themeOverrides,
17-
config,
1815
}: {
1916
widgetId: string
20-
themeOverrides?: WidgetHostProps["themeOverrides"]
21-
config?: WidgetHostProps["config"]
2217
}) => {
2318
const { signer, isLoading } = useSessionContext()
2419
const widget = widgetRegistry.get(widgetId)
@@ -42,32 +37,19 @@ export const AuthenticatedWidgetRoute = ({
4237
chainIds={widget.providerPolicy.chainIds}
4338
requiredMethods={widget.providerPolicy.requiredMethods}
4439
>
45-
<MountedWidget
46-
widget={widget}
47-
themeOverrides={themeOverrides}
48-
config={config}
49-
/>
40+
<MountedWidget widget={widget} />
5041
</WidgetProvider>
5142
)
5243
}
5344

54-
const MountedWidget = ({
55-
widget,
56-
themeOverrides,
57-
config,
58-
}: {
59-
widget: RegisteredWidget
60-
themeOverrides?: WidgetHostProps["themeOverrides"]
61-
config?: WidgetHostProps["config"]
62-
}) => {
45+
const MountedWidget = ({ widget }: { widget: RegisteredWidget }) => {
6346
const provider = useWidgetProvider()
6447

6548
return (
6649
<WidgetRenderer
6750
widget={widget}
6851
provider={provider}
69-
themeOverrides={themeOverrides}
70-
config={config}
52+
elementProps={widget.elementProps}
7153
/>
7254
)
7355
}

0 commit comments

Comments
 (0)