Skip to content

Feat Integrate Ubeswaps saving widget.#634

Merged
L03TJ3 merged 35 commits into
GoodDollar:masterfrom
Oluwatomilola:feat-saving-widget-integrat
May 8, 2026
Merged

Feat Integrate Ubeswaps saving widget.#634
L03TJ3 merged 35 commits into
GoodDollar:masterfrom
Oluwatomilola:feat-saving-widget-integrat

Conversation

@Oluwatomilola
Copy link
Copy Markdown
Contributor

@Oluwatomilola Oluwatomilola commented Apr 30, 2026

Description

This PR introduces the GoodDollar Savings Widget integration into the GoodDollar UI, allowing users to access the savings/staking interface directly within the app.

The implementation follows the recommended approach using the published package (@goodsdks/savings-widget) instead of script injection, ensuring a production-safe and maintainable integration.

Key Changes

Added a new **Savings page that renders the <gooddollar-savings-widget />
Integrated wallet connection using existing hooks (useAppKit, useAppKitAccount, useWeb3Context)
Passed web3Provider to the widget after wallet connection
Added Savings FAQ entries for better user context (rewards, network, withdrawals)
Extended FaqType to include "savings"
Added global TypeScript declaration for the custom web component
Ensured minimal and scoped changes aligned with contribution guidelines

Stability Fixes (Local Dev)

Added defensive guards to prevent crashes when analytics/feature flags (PostHog) are unavailable in local environments
Ensures the app renders without runtime errors during development

Dependencies

@goodsdks/savings-widget

Fixes #619
Closes #625


How Has This Been Tested?

Ran the app locally using yarn start
Navigated to /savings and verified:

Widget renders correctly
No blank screen or runtime crashes
Tested wallet connection flow:

"Connect Wallet” triggers AppKit modal
web3Provider is correctly passed to the widget after connection
Provider resets on disconnect
Verified:

No console errors related to the widget
No TypeScript errors (yarn tsc --noEmit)
No lint errors
Confirmed no regressions in other parts of the app


Checklist:

  • PR title matches follow: (Feature) Savings Widget Integration
  • My code follows the style guidelines of this project
  • I have followed all the instructions described in the initial task (check Definitions of Done)
  • I have performed a self-review of my own code
  • My changes generate no new warnings
  • I have added tests that prove my feature works (manual UI + integration testing)
  • New and existing unit tests pass locally with my changes
  • I have added reference to a related issue in the repository
  • I have added a detailed description of the changes proposed in the pull request
  • I have added screenshots related to my pull request (Savings page UI)
  • I have pasted a screenshot showing the feature (wallet connect + widget interaction)
  • @mentions of the person or team responsible for reviewing proposed changes

@pheobeayo

Screenshots / Demo

Screenshot 2026-04-29 at 21 31 48

Summary by Sourcery

Integrate a new savings experience and bridge flow into the GoodDollar app while improving wallet connection behavior, RPC handling, and error reporting.

New Features:

  • Add a Savings page that embeds the GoodDollar savings widget and wires it to the existing wallet connection flow.
  • Expose the Savings page and GoodBridge page via new routes and sidebar navigation entries.
  • Introduce a GoodBridge page that uses the GoodDollar bridge controller behind a chain-switching modal.
  • Add dedicated FAQ content and support for a new "savings" FAQ type.

Bug Fixes:

  • Enable and display a claim-disabled modal based on feature flags to prevent attempts when claiming is turned off.
  • Fix G$ price sourcing by always using the Celo chain to avoid mispriced values on unsupported networks.
  • Normalize and decode transaction error reasons for swaps to present clearer, user-friendly messages.
  • Filter out blocked RPC endpoints using an environment-configurable list to avoid problematic providers.
  • Adjust gas parameter handling on EIP-1559 networks to consistently apply custom gas settings where configured.
  • Control wallet reconnect behavior via a persisted flag so sessions only auto-reconnect after a successful connection.
  • Remove usage of PostHog-based wallet chat flags to avoid local development crashes when analytics are unavailable.

Enhancements:

  • Redesign the disabled-claim modal as an in-place overlay within the claim card and wire it to the feature flag state.
  • Add a GoodBridge navigation option separate from the existing micro bridge, with visibility controlled by environment and feature flags.
  • Include a TypeScript declaration for the custom savings widget web component and widen tsconfig includes to cover the new types file.

Build:

  • Upgrade GoodDollar UI, protocol, and web3 SDK dependencies and add the @goodsdks/savings-widget package while removing unused web3-onboard packages.
  • Update the Vercel CLI version in devDependencies.

Oluwatomilola and others added 30 commits March 8, 2026 15:54
Feat: Integrate ubeswap savings widget GoodDollar#619
…oodDollar#626)

* fix: replace useModal with absolute overlay for disabled claim state

* fix: gate modal display with claimEnabled flag and remove comments

* fix: use standard img tag to ensure maintenance asset renders

* chore: restore valid developer comments and remove temporary ones
* fix: stop forcing celo gas price

* chore: bump gooddollar sdks

---------

Co-authored-by: LewisB <lewis@ikigaistudios.eu>
* add GoodBridge page to src/pages/gd

* update sideBar.tsx

* feat: implement GoodBridge route and update connect wallet copy

* Delete src/pages/gd/GoodBridge/index.tsx~

* Add goodBridgeEnabled to payload destructuring

* Fix typo in goodBridgeEnabled variable

* Update bridge names in SideBar component
@Oluwatomilola Oluwatomilola marked this pull request as draft April 30, 2026 04:29
@Oluwatomilola Oluwatomilola marked this pull request as ready for review April 30, 2026 12:57
Copy link
Copy Markdown

@sourcery-ai sourcery-ai Bot left a comment

Choose a reason for hiding this comment

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

Hey - I've found 1 issue, and left some high level feedback:

  • In the new Savings page, widgetRef is typed as any and you’re assigning connectWallet/web3Provider dynamically on the custom element; consider defining a dedicated TypeScript interface for the widget element (or extending HTMLElementTagNameMap) and using a typed useRef to make these assignments type-safe and to catch property name mismatches at compile time.
  • The new decodeTransactionErrorReason helper assumes string-like error shapes but is called both with errorMessage strings and raw error objects; it may be safer to normalize common error types (e.g., Error, EthersError with data payload) explicitly before regex parsing so that unexpected shapes don’t end up as confusing generic messages.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- In the new Savings page, `widgetRef` is typed as `any` and you’re assigning `connectWallet`/`web3Provider` dynamically on the custom element; consider defining a dedicated TypeScript interface for the widget element (or extending `HTMLElementTagNameMap`) and using a typed `useRef` to make these assignments type-safe and to catch property name mismatches at compile time.
- The new `decodeTransactionErrorReason` helper assumes string-like error shapes but is called both with `errorMessage` strings and raw error objects; it may be safer to normalize common error types (e.g., `Error`, `EthersError` with `data` payload) explicitly before regex parsing so that unexpected shapes don’t end up as confusing generic messages.

## Individual Comments

### Comment 1
<location path="src/hooks/useWeb3.tsx" line_range="231-235" />
<code_context>
+        []
+    )
+
+    const filterBlockedRpcs = (rpcs: Record<string, string[]>) =>
+        Object.fromEntries(
+            Object.entries(rpcs).map(([chainId, urls]) => [
+                chainId,
+                urls.filter((url) => !excludedRpcs.some((blocked) => url.toLowerCase().includes(blocked))),
+            ])
+        )
</code_context>
<issue_to_address>
**issue:** Handle cases where all RPC URLs for a chain are filtered out by excludedRpcs.

If `excludedRpcs` matches all URLs for a chain, `filterBlockedRpcs` will return an empty array for that chain, and any code assuming at least one URL (e.g. `testedRpcs[chainId][0]`) may fail at runtime.

Consider either:
- falling back to the original URLs when filtering yields an empty array, or
- omitting those chains entirely and ensuring the rest of the app tolerates missing `testedRpcs[chainId]`.

This avoids hard failures when `REACT_APP_EXCLUDED_RPCS` is misconfigured.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment thread src/hooks/useWeb3.tsx
Comment on lines +231 to +235
const filterBlockedRpcs = (rpcs: Record<string, string[]>) =>
Object.fromEntries(
Object.entries(rpcs).map(([chainId, urls]) => [
chainId,
urls.filter((url) => !excludedRpcs.some((blocked) => url.toLowerCase().includes(blocked))),
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

issue: Handle cases where all RPC URLs for a chain are filtered out by excludedRpcs.

If excludedRpcs matches all URLs for a chain, filterBlockedRpcs will return an empty array for that chain, and any code assuming at least one URL (e.g. testedRpcs[chainId][0]) may fail at runtime.

Consider either:

  • falling back to the original URLs when filtering yields an empty array, or
  • omitting those chains entirely and ensuring the rest of the app tolerates missing testedRpcs[chainId].

This avoids hard failures when REACT_APP_EXCLUDED_RPCS is misconfigured.

@pheobeayo
Copy link
Copy Markdown

@Oluwatomilola Still finding it difficult to resolve the conflicts?

…t-integrat

# Conflicts:
#	package.json
#	yarn.lock
@Oluwatomilola
Copy link
Copy Markdown
Contributor Author

@pheobeayo
Conflicts resolved.
Thanks.

Copy link
Copy Markdown

@pheobeayo pheobeayo left a comment

Choose a reason for hiding this comment

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

Good progress — the npm import is correctly in place, loadWidgetScript is gone, the FAQ has been expanded, and the duplicate type file is resolved. The main blockers from #625 are addressed.

A few things still need attention:

1. ref prop missing from type declaration (types/gooddollar-savings-widget.d.ts)
The current declaration doesn't include ref, which means TypeScript won't properly type the widgetRef assignment. Update the intrinsic element declaration to:

'gooddollar-savings-widget': React.DetailedHTMLProps<React.HTMLAttributes<HTMLElement>, HTMLElement> & {
  ref?: React.Ref<HTMLElement & { connectWallet?: () => void; web3Provider?: unknown | null }>
}

2. useRef<any> — avoid any
Use a proper type consistent with the declaration above:

const widgetRef = useRef<HTMLElement & {
  connectWallet?: () => void
  web3Provider?: unknown | null
}>(null)

3. Merged useEffect causes unnecessary re-assignment of connectWallet
The first useEffect now assigns connectWallet on every change to walletProvider or address, which is unnecessary. Split them back into two separate effects:

useEffect(() => {
  const widget = widgetRef.current
  if (!widget) return
  widget.connectWallet = handleConnectWallet
}, [handleConnectWallet])

useEffect(() => {
  const widget = widgetRef.current
  if (!widget) return
  widget.web3Provider = (address && walletProvider) ? walletProvider : null
}, [address, walletProvider])

With this, the second disconnect useEffect can be removed entirely.

4. Verify walletProvider type compatibility with the widget
The switch from useWeb3Context (@gooddollar/web3sdk-v2) to useAppKitProvider<Provider>('eip155') changes what gets passed as web3Provider. Please confirm that the gooddollar-savings-widget accepts a raw AppKit Provider and not specifically an ethers Web3Provider otherwise this will fail silently at runtime.

Once these are addressed this should be good to merge.

@github-project-automation github-project-automation Bot moved this from Ready-For-Assignment to In Progress in GoodBounties May 2, 2026
Copy link
Copy Markdown

@pheobeayo pheobeayo left a comment

Choose a reason for hiding this comment

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

Approved, Good job LGTM
@L03TJ3 Please kindly review

@L03TJ3 L03TJ3 moved this from In Progress to In Review in GoodBounties May 8, 2026
Copy link
Copy Markdown
Collaborator

@L03TJ3 L03TJ3 left a comment

Choose a reason for hiding this comment

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

Looks good, just included a suggestion so that we can more cleanly test it on our development environment.
Not an action item! I can commit through the PR

Comment thread src/components/SideBar.tsx Outdated
@L03TJ3
Copy link
Copy Markdown
Collaborator

L03TJ3 commented May 8, 2026

okay, it look good on the surface but after QA'in there are issues with the widget.
however, that is part of the underlying sdk and not this pull-request.

can you explain if and how you tested this? have you actually deposited something into savings using the widget?

@L03TJ3
Copy link
Copy Markdown
Collaborator

L03TJ3 commented May 8, 2026

@Oluwatomilola

@Oluwatomilola
Copy link
Copy Markdown
Contributor Author

Oluwatomilola commented May 8, 2026

@L03TJ3

Yes, I tested the widget end-to-end locally using my connected MetaMask wallet with Celo.

Test flow:
I ran yarn start on my local
Connected the app to MetaMask with my wallet funded with CELO
Opened the integrated savings widget
Successfully performed:
deposit into savings via the stake button
withdraw with the unstake button
claim rewards also

Then I verified that transactions completed successfully in the UI and on-chain via Celoscan

Transaction proofs:
https://celoscan.io/tx/0xdad9ec1283ee27bf37917ad958367852a1507db3f1848f33387b11803a29bcc8 https://celoscan.io/tx/0x577ff5f84a02882e2f7e8dd1394702e59902e794fef6add69c2343e84c2fca8d https://celoscan.io/tx/0x76b742df640b2e9cdf9d03b0e0243a4f6d353da1538db7488b9b25862aefe5ce

From my testing, the integration itself is functioning correctly in the frontend. The issues mentioned during QA appear to originate from the underlying SDK/widget behavior rather than this PR integration layer.

@L03TJ3
Copy link
Copy Markdown
Collaborator

L03TJ3 commented May 8, 2026

The question I have is: why not tell or raise issues you encountered?
We could have async handled the fixes so that we have a testable-ready widget in the development environment?

Silently ignoring weird things/bugs.. they will never be handled or solved.

The issues were quite clear when it came to UX: technical error stacks in ui, approve > stake flow throwing errors.
weekly rewards not showing.

That is bugs. even if the core functionality works, its UX bugs.
and going forward it would be expected to raise them so that we don't deliver broken features to users :)
Also, if you tested it like you share the transaction proofs, is also nice to see a demo-video of the ux flow

What I encountered is described here: GoodDollar/GoodSDKs#46
(does not have to be solved now, will be picked up later)

@L03TJ3 L03TJ3 merged commit 0582c75 into GoodDollar:master May 8, 2026
4 checks passed
@github-project-automation github-project-automation Bot moved this from In Review to Deploy and Verify in GoodBounties May 8, 2026
@Oluwatomilola
Copy link
Copy Markdown
Contributor Author

My bad : (

I was focused primarily on validating the integration layer and core transaction flow, so once staking and unstaking succeeded on-chain I treated the PR as functionally complete. I should have explicitly documented the UX/runtime issues I encountered during testing instead of assuming they were already known SDK limitations.

I mistakenly separated those from the scope of the PR because the underlying transactions still executed successfully, but I agree those are still user-facing bugs and should have been raised clearly for visibility and follow-up.

Going forward I'll make sure to document all observed issues during QA — even when they originate from upstream SDK behavior — so they can be tracked, discussed, and resolved instead of silently passing through review.

Thanks for calling that out.

@L03TJ3
Copy link
Copy Markdown
Collaborator

L03TJ3 commented May 8, 2026

We are going forward with a new bounty/contributions structure.

Since you were assigned before the new structure make sure you follow the rewards as defined here: https://github.com/GoodDollar/.github/blob/1479d6c5350ea063788eb8052407b6a6054b3c8a/CONTRIBUTING.md#bounty-tiers-explained

I mention the new structure because it's also your first time contributing and having a PR merged.
You can follow the steps at the bottom of this page: payout request
but don't get confused by the different payout structure on the same page 🤓

Make sure you use a GoodDollar verified wallet!

@Oluwatomilola
Copy link
Copy Markdown
Contributor Author

Acknowledged.
Thanks.

@Oluwatomilola
Copy link
Copy Markdown
Contributor Author

Oluwatomilola commented May 11, 2026

@L03TJ3
The demo video for the saving feature
https://www.loom.com/share/63deb49a583e45c785dd65f3fc92f6eb

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.

Feat: Integrate ubeswap savings widget

7 participants