diff --git a/apps/iframe/src/components/requests/WalletSwitchEthereumChain.tsx b/apps/iframe/src/components/requests/WalletSwitchEthereumChain.tsx index 1b3d6553df..49dafeccec 100644 --- a/apps/iframe/src/components/requests/WalletSwitchEthereumChain.tsx +++ b/apps/iframe/src/components/requests/WalletSwitchEthereumChain.tsx @@ -19,7 +19,7 @@ export const WalletSwitchEthereumChain = ({ if (import.meta.env.PROD) return diff --git a/apps/iframe/src/requests/handlers/injected.ts b/apps/iframe/src/requests/handlers/injected.ts index d43a17bf19..52dcb1970e 100644 --- a/apps/iframe/src/requests/handlers/injected.ts +++ b/apps/iframe/src/requests/handlers/injected.ts @@ -3,12 +3,14 @@ import { EIP1193SwitchChainError, EIP1193UnauthorizedError, EIP1474InvalidInput, + HappyWalletCapability, type Msgs, type ProviderMsgsFromApp, WalletType, } from "@happy.tech/wallet-common" +import type { Capabilities } from "viem" import { privateKeyToAccount } from "viem/accounts" -import { checkAndChecksumAddress, checkedTx, checkedWatchedAsset } from "#src/requests/utils/checks" +import { checkAndChecksumAddress, checkAuthenticated, checkedTx, checkedWatchedAsset } from "#src/requests/utils/checks" import { sendToPublicClient, sendToWalletClient } from "#src/requests/utils/sendToClient" import { getSessionKey, @@ -25,7 +27,7 @@ import { getTransactionReceipt, } from "#src/requests/utils/shared" import { eoaSigner, sessionKeySigner } from "#src/requests/utils/signers" -import { getChains, setChains, setCurrentChain } from "#src/state/chains" +import { getChains, getCurrentChain, setChains, setCurrentChain } from "#src/state/chains" import { revokedSessionKeys } from "#src/state/interfaceState" import { loadAbiForUser } from "#src/state/loadedAbis" import { getPermissions, grantPermissions, revokePermissions } from "#src/state/permissions" @@ -220,6 +222,34 @@ export async function dispatchInjectedRequest(request: ProviderMsgsFromApp[Msgs. return addWatchedAsset(user.address, params) } + case "wallet_getCapabilities": { + checkAuthenticated() + if (!request.payload?.params?.[0] || !request.payload?.params?.[1]) { + throw new EIP1474InvalidInput("Missing payload parameters") + } + checkAndChecksumAddress(request.payload.params[0]) + + const currentChainId = getCurrentChain().chainId + if (request.payload.params[1].length > 1) { + const requestedChainIds = request.payload.params[1] + for (const chainId of requestedChainIds) { + if (chainId !== currentChainId) { + console.warn( + `Unsupported chain ID requested: ${chainId}. The Happy Wallet is a HappyChain exclusive 🤠!`, + ) + } + } + } + + const capabilities: Capabilities = { + [currentChainId]: Object.fromEntries( + Object.values(HappyWalletCapability).map((capability) => [capability, { supported: true }]), + ), + } + + return capabilities + } + case HappyMethodNames.LOAD_ABI: { return user ? loadAbiForUser(user.address, request.payload.params) : undefined } diff --git a/apps/iframe/src/requests/handlers/permissionless.ts b/apps/iframe/src/requests/handlers/permissionless.ts index 744e020e1f..8767c4d92a 100644 --- a/apps/iframe/src/requests/handlers/permissionless.ts +++ b/apps/iframe/src/requests/handlers/permissionless.ts @@ -1,6 +1,13 @@ import { HappyMethodNames } from "@happy.tech/common" -import { EIP1193UserRejectedRequestError, type Msgs, type ProviderMsgsFromApp } from "@happy.tech/wallet-common" -import { isAddress } from "viem" +import { isAddress } from "@happy.tech/common" +import { + EIP1193UserRejectedRequestError, + EIP1474InvalidInput, + type Msgs, + type ProviderMsgsFromApp, +} from "@happy.tech/wallet-common" +import { HappyWalletCapability } from "@happy.tech/wallet-common" +import type { Capabilities } from "viem" import { sendBoop } from "#src/requests/utils/boop" import { checkAndChecksumAddress, checkAuthenticated, checkedTx } from "#src/requests/utils/checks" import { sendToPublicClient } from "#src/requests/utils/sendToClient" @@ -93,6 +100,34 @@ export async function dispatchedPermissionlessRequest(request: ProviderMsgsFromA // If this is permissionless, we're already on the right chain so we simply succeed. return null + case "wallet_getCapabilities": { + checkAuthenticated() + if (!request.payload?.params?.[0] || !request.payload?.params?.[1]) { + throw new EIP1474InvalidInput("Missing payload parameters") + } + checkAndChecksumAddress(request.payload.params[0]) + + const currentChainId = getCurrentChain().chainId + if (request.payload.params[1].length > 1) { + const requestedChainIds = request.payload.params[1] + for (const chainId of requestedChainIds) { + if (chainId !== currentChainId) { + console.warn( + `Unsupported chain ID requested: ${chainId}. The Happy Wallet is a HappyChain exclusive 🤠!`, + ) + } + } + } + + const capabilities: Capabilities = { + [currentChainId]: Object.fromEntries( + Object.values(HappyWalletCapability).map((capability) => [capability, { supported: true }]), + ), + } + + return capabilities + } + case HappyMethodNames.REQUEST_SESSION_KEY: { getCheckedUser() const target = checkAndChecksumAddress(request.payload.params[0]) diff --git a/apps/iframe/src/requests/utils/checks.ts b/apps/iframe/src/requests/utils/checks.ts index e92764b3e6..a813b11dcb 100644 --- a/apps/iframe/src/requests/utils/checks.ts +++ b/apps/iframe/src/requests/utils/checks.ts @@ -89,8 +89,8 @@ export function checkedWatchedAsset(params: WatchAssetParameters) { * Checks that the address is valid, or throws. * @throws EIP1474InvalidInput if the address is invalid */ -export function checkAddress(address: string): asserts address is Address { - if (!isAddress(address)) throw new EIP1474InvalidInput(`invalid address: ${address}`) +export function checkAddress(address?: string): asserts address is Address { + if (!address || !isAddress(address)) throw new EIP1474InvalidInput(`invalid address: ${address}`) } /** diff --git a/apps/submitter/bin/benchmarkLatency.ts b/apps/submitter/bin/benchmarkLatency.ts index 9cd5089d70..074d94de9f 100644 --- a/apps/submitter/bin/benchmarkLatency.ts +++ b/apps/submitter/bin/benchmarkLatency.ts @@ -1,11 +1,4 @@ -import { - BoopClient, - CreateAccount, - type ExecuteSuccess, - GetNonce, - Onchain, - computeBoopHash, -} from "@happy.tech/boop-sdk" +import { BoopClient, CreateAccount, computeBoopHash } from "@happy.tech/boop-sdk" import { delayed, stringify } from "@happy.tech/common" import { type PrivateKeyAccount, generatePrivateKey, privateKeyToAccount } from "viem/accounts" import { createAndSignMintBoop } from "#lib/utils/test/helpers" diff --git a/support/wallet-common/lib/index.ts b/support/wallet-common/lib/index.ts index 3c9c27c44d..578e94ea1b 100644 --- a/support/wallet-common/lib/index.ts +++ b/support/wallet-common/lib/index.ts @@ -26,6 +26,7 @@ export { shortenAddress } from "./utils/shortenAddress" export type { Chain, ChainBlockExplorer, ChainContract, ChainRpcUrls, ChainNativeCurrency } from "./chains/viem" export { Msgs, WalletDisplayAction } from "./interfaces/events" export type { RecordAbiPayload } from "./interfaces/eip1193" +export { HappyWalletCapability } from "./interfaces/eip5792" export type { ChainParameters } from "./chains/utils" export type { ConnectionProvider } from "./interfaces/connectionProvider" export type { HappyUser } from "./interfaces/happyUser" diff --git a/support/wallet-common/lib/interfaces/eip5792.ts b/support/wallet-common/lib/interfaces/eip5792.ts new file mode 100644 index 0000000000..8b918918f8 --- /dev/null +++ b/support/wallet-common/lib/interfaces/eip5792.ts @@ -0,0 +1,3 @@ +export enum HappyWalletCapability { + BoopPaymaster = "boopPaymaster", +} diff --git a/support/wallet-common/lib/interfaces/permissions.ts b/support/wallet-common/lib/interfaces/permissions.ts index 358b98d129..a2d3aac683 100644 --- a/support/wallet-common/lib/interfaces/permissions.ts +++ b/support/wallet-common/lib/interfaces/permissions.ts @@ -66,6 +66,7 @@ const safeList = new Set([ "wallet_revokePermissions", // https://github.com/MetaMask/metamask-improvement-proposals/blob/main/MIPs/mip-2.md "web3_clientVersion", "web3_sha3", + "wallet_getCapabilities", ]) /**