-
Notifications
You must be signed in to change notification settings - Fork 2
feat: add visual indicator for user's gas remaining gas budget fetched from the paymaster #786
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
e33a3dd
32f6589
c969094
056ab28
9f97b0a
5878a81
39a8757
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| --- | ||
| "@happy.tech/iframe": minor | ||
| --- | ||
|
|
||
| Adds a visual indicator in the wallet for user's paymaster gas budget. |
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. on a second look with increasing my screen brightness, the warning states need some more brightness in light mode, it kinda disappears there 🔅 |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,57 @@ | ||
| import { | ||
| BatteryFullIcon, | ||
| BatteryHighIcon, | ||
| BatteryLowIcon, | ||
| BatteryMediumIcon, | ||
| BatteryWarningIcon, | ||
| type Icon, | ||
| SpinnerIcon, | ||
| } from "@phosphor-icons/react" | ||
| import { cx } from "class-variance-authority" | ||
| import type { ClassValue } from "class-variance-authority/types" | ||
| import { useReadUserGasBudget } from "#src/hooks/useReadUserGasBudget.ts" | ||
| import { getUser } from "#src/state/user.ts" | ||
|
|
||
| const colorMap: Record<number, ClassValue> = { | ||
| 0: "text-error animate-pulse", | ||
| 1: "text-warning/60", | ||
| 2: "text-warning", | ||
| 3: "text-success/80", | ||
| 4: "text-success", | ||
| } | ||
|
|
||
| /** | ||
| * Maps each battery health level to the corresponding Phosphor Icon component. | ||
| */ | ||
| const batteryIconStates: Record<number, Icon> = { | ||
| 0: BatteryWarningIcon, | ||
| 1: BatteryLowIcon, | ||
| 2: BatteryMediumIcon, | ||
| 3: BatteryHighIcon, | ||
| 4: BatteryFullIcon, | ||
| } | ||
|
|
||
| export const UserGasBudgetIndicator = () => { | ||
| const user = getUser() | ||
| const { | ||
| data: { batteryHealth, batteryPct } = {}, | ||
| isLoading, | ||
| } = useReadUserGasBudget(user?.address) | ||
|
|
||
| if (isLoading || batteryHealth === undefined) | ||
| return ( | ||
| <SpinnerIcon | ||
| weight="bold" | ||
| className="text-lg mr-1 dark:opacity-60 motion-safe:animate-[spin_2s_linear_infinite]" | ||
| /> | ||
| ) | ||
|
|
||
| const BatteryIcon = batteryIconStates[batteryHealth] | ||
| const colorClass = colorMap[batteryHealth] | ||
|
|
||
| return ( | ||
| <button title={`${batteryPct}%`} type="button" aria-label={"gas budget"} className="dark:opacity-60"> | ||
| <BatteryIcon weight="bold" className={cx(colorClass, "text-lg", "mr-1")} /> | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this gets fixed by the same changes (comment above)
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this is in ref to the |
||
| </button> | ||
| ) | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,70 @@ | ||
| import type { Address, UInt32 } from "@happy.tech/common" | ||
| import { type UseReadContractReturnType, useReadContract } from "wagmi" | ||
| import { happyPaymaster, happyPaymasterAbi } from "#src/constants/contracts" | ||
|
|
||
| /** | ||
| * Maximum cumulative gas budget per user as defined in the HappyPaymaster contract. | ||
| */ | ||
| const MAX_GAS_BUDGET: UInt32 = 1_000_000_000 | ||
|
|
||
| /** | ||
| * Structured gas budget info for UI display: | ||
| * - `batteryHealth`: discrete level 0–4 | ||
| * - `batteryPct`: percentage of MAX_GAS_BUDGET (0–100). | ||
| */ | ||
| export type UserGasBudgetInfo = { | ||
| batteryHealth: number | ||
| batteryPct: number | ||
| } | ||
|
|
||
| /** | ||
| * Wagmi return type for the `getBudget` read call, mapped to `UserGasBudgetInfo`. | ||
| */ | ||
| export type UseReadUserGasBudgetReturnType = UseReadContractReturnType< | ||
| typeof happyPaymasterAbi, | ||
| "getBudget", | ||
| [Address], | ||
| UserGasBudgetInfo | ||
| > | ||
|
|
||
| /** | ||
| * Hook to fetch and map a user's gas budget from the HappyPaymaster contract. | ||
| * | ||
| * @param userAddress - address of the user whose budget to read | ||
| * @returns Wagmi query object with: | ||
| * - `data`: `{ batteryHealth, batteryPct }` | ||
| * - `isLoading`, `isError`, `refetch`, etc. (cf. {@link UseReadUserGasBudgetReturnType}) | ||
| * @throws `Error` if no userAddress is provided. | ||
| */ | ||
| export const useReadUserGasBudget = (userAddress?: Address): UseReadUserGasBudgetReturnType => { | ||
| if (!userAddress) throw new Error("No user found!") | ||
|
|
||
| const result = useReadContract({ | ||
| address: happyPaymaster, | ||
| abi: happyPaymasterAbi, | ||
| functionName: "getBudget", | ||
| args: [userAddress], | ||
| query: { | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. todo: query key invalidation plan?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. notes from convo w/ @norswap:
|
||
| enabled: Boolean(!!userAddress), | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The |
||
| refetchInterval: 2000, | ||
| /** | ||
| * Maps the raw onchain `userGasBudget` to UI-friendly battery info: | ||
| * 1. Calculate `pct` as the ratio of `userGasBudget` to `MAX_GAS_BUDGET` (0.0 – 1.0). | ||
| * 2. Derive `batteryHealth` (0–4) by flooring `pct * 4` into four equal buckets and | ||
| * clamping between 0 and 4 to match discrete battery levels. | ||
| * 3. Compute `batteryPct` as a percentage string with two decimals (0 – 100). | ||
| * | ||
| * @param userGasBudget - raw uint32 gas budget from HappyPaymaster | ||
| */ | ||
| select(userGasBudget) { | ||
| const pct = Number(userGasBudget) / MAX_GAS_BUDGET | ||
| return { | ||
| batteryHealth: Math.min(4, Math.max(0, Math.floor(pct * 4))), | ||
| batteryPct: Number((pct * 100).toFixed(2)), | ||
| } | ||
| }, | ||
| }, | ||
| }) | ||
|
|
||
| return result | ||
| } | ||










There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
there's a few changes in @not-reed's PR #781 that I tested with locally (didn't push here) that fix any alignment issues (not there are any observable ones)