Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions apps/iframe/src/constants/requestLabels.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ export const requestLabels = {
personal_sign: "Signature Request",
wallet_addEthereumChain: "Add Network",
wallet_requestPermissions: "Permission Request",
wallet_sendCalls: "Send Calls",
wallet_switchEthereumChain: "Switch Network",
wallet_watchAsset: "Watch Asset",
[HappyMethodNames.USE_ABI]: "Record ABI",
Expand Down
15 changes: 4 additions & 11 deletions apps/iframe/src/requests/userOps.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ export type SendUserOpArgs = {
tx: RpcTransactionRequest
validator: Address
signer: UserOpSigner
paymaster?: Address // developers may specify a particular deployed paymaster when they use `wallet_sendCalls`
}

export type UserOpWrappedCall = {
Expand All @@ -57,11 +58,10 @@ export enum VALIDATOR_TYPE {
PERMISSION = "0x02",
}

export async function sendUserOp({ user, tx, validator, signer }: SendUserOpArgs, retry = 2) {
export async function sendUserOp({ user, tx, validator, signer, paymaster }: SendUserOpArgs, retry = 2) {
const smartAccountClient = (await getSmartAccountClient())!
const account = smartAccountClient.account.address

// [DEBUGLOG] // let debugNonce = 0n
try {
// We need the separate nonce lookup because:
// - we do local nonce management to be able to have multiple userOps in flight
Expand All @@ -70,7 +70,7 @@ export async function sendUserOp({ user, tx, validator, signer }: SendUserOpArgs
getNextNonce(account, validator),
smartAccountClient.prepareUserOperation({
account: smartAccountClient.account,
paymaster: contractAddresses.HappyPaymaster,
paymaster: paymaster ?? contractAddresses.HappyPaymaster,
// Specify this array to avoid fetching the nonce from here too.
// We don't really need the dummy signature, but it does not incur an extra network
// call and it makes the type system happy.
Expand All @@ -85,8 +85,6 @@ export async function sendUserOp({ user, tx, validator, signer }: SendUserOpArgs
} satisfies PrepareUserOperationParameters), // TS too dumb without this
])

// [DEBUGLOG] // debugNonce = nonce

// sendUserOperationNow does not want account included
const { account: _, ...preparedUserOp } = { ..._preparedUserOp, nonce }
preparedUserOp.signature = await signer(preparedUserOp, smartAccountClient)
Expand All @@ -105,7 +103,6 @@ export async function sendUserOp({ user, tx, validator, signer }: SendUserOpArgs

addPendingUserOp(user.address, pendingUserOpDetails)

// [DEBUGLOG] // console.log("sending", userOpHash, retry)
const userOpReceipt = await submitUserOp(smartAccountClient, validator, preparedUserOp)

receiptCache.put(userOpHash, [
Expand All @@ -119,18 +116,14 @@ export async function sendUserOp({ user, tx, validator, signer }: SendUserOpArgs
} as GetUserOperationReturnType,
])

// [DEBUGLOG] // console.log("receipt", userOpHash, retry)

markUserOpAsConfirmed(user.address, pendingUserOpDetails, userOpReceipt)

return userOpReceipt.userOpHash
} catch (error) {
// https://docs.stackup.sh/docs/entrypoint-errors
// https://docs.pimlico.io/infra/bundler/entrypoint-errors

// [DEBUGLOG] // console.log("error", nonceB, error.details || error, retry)

// Most likely the transaction didn't land, so need to resynchronize the nonce.
// (most likely) the transaction didn't land, so need to resynchronize the nonce.
deleteNonce(account, validator)

if (retry > 0) return sendUserOp({ user, tx, validator, signer }, retry - 1)
Expand Down
3 changes: 2 additions & 1 deletion apps/iframe/src/state/injectedClient.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { accessorsFromAtom } from "@happy.tech/common"
import { type Atom, atom } from "jotai"
import { createWalletClient, custom } from "viem"
import { eip5792Actions } from "viem/experimental"
import { InjectedProviderProxy } from "#src/connections/InjectedProviderProxy.ts"
import { userAtom } from "./user"
import type { AccountWalletClient } from "./walletClient"
Expand All @@ -10,7 +11,7 @@ export const injectedClientAtom: Atom<AccountWalletClient | undefined> = atom<Ac
const user = get(userAtom)
if (!user?.address) return
const provider = InjectedProviderProxy.getInstance()
return createWalletClient({ account: user.address, transport: custom(provider) })
return createWalletClient({ account: user.address, transport: custom(provider) }).extend(eip5792Actions())
},
)

Expand Down
6 changes: 4 additions & 2 deletions apps/iframe/src/state/walletClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { accessorsFromAtom } from "@happy.tech/common"
import { type Atom, atom } from "jotai"
import type { CustomTransport, ParseAccount, WalletClient } from "viem"
import { type PublicRpcSchema, type WalletRpcSchema, createWalletClient } from "viem"
import { type Eip5792Actions, eip5792Actions } from "viem/experimental"
import { providerAtom } from "./provider"
import { transportAtom } from "./transport"
import { userAtom } from "./user"
Expand All @@ -11,15 +12,16 @@ export type AccountWalletClient = WalletClient<
undefined,
ParseAccount<`0x${string}`>,
[...WalletRpcSchema, ...PublicRpcSchema]
>
> &
Eip5792Actions

export const walletClientAtom: Atom<AccountWalletClient | undefined> = atom<AccountWalletClient | undefined>((get) => {
const user = get(userAtom)
const provider = get(providerAtom)
const transport = get(transportAtom)
if (!user?.controllingAddress || !provider || !transport) return

return createWalletClient({ account: user.controllingAddress, transport })
return createWalletClient({ account: user.controllingAddress, transport }).extend(eip5792Actions())
Copy link
Contributor Author

Choose a reason for hiding this comment

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

this will be needed in the popup for when the user calls wallet_showCallsStatus

discussion context: https://happychaindevs.slack.com/archives/C084LLRJF5M/p1739780751376089?thread_ts=1739542573.757479&cid=C084LLRJF5M

})

export const { getValue: getWalletClient } = accessorsFromAtom(walletClientAtom)
5 changes: 3 additions & 2 deletions demos/react/src/useClients.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,14 @@ import {
createWalletClient,
custom,
} from "viem"
import { type Eip5792Actions, eip5792Actions } from "viem/experimental"

/**
* Creates custom public + wallet clients using the HappyProvider.
*/
export default function useClients(): {
publicClient: PublicClient
walletClient: WalletClient<CustomTransport, undefined, Account> | null
walletClient: (WalletClient<CustomTransport, undefined, Account> & Eip5792Actions) | null
} {
const { provider, user } = useHappyChain()

Expand All @@ -27,7 +28,7 @@ export default function useClients(): {
? createWalletClient({
account: user.address,
transport: custom(provider),
})
}).extend(eip5792Actions())
: null,
[user, provider],
)
Expand Down
2 changes: 2 additions & 0 deletions support/wallet-common/lib/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@ export type {
} from "./interfaces/eip1193"
export type { EIP6963ProviderInfo, EIP6963ProviderDetail, EIP6963AnnounceProviderEvent } from "./interfaces/eip6963"

export { HappyWalletCapability } from "./interfaces/eip5792"

export type {
MsgsFromApp,
MsgsFromIframe,
Expand Down
4 changes: 4 additions & 0 deletions support/wallet-common/lib/interfaces/eip5792.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export enum HappyWalletCapability {
// AtomicBatch = "atomicBatch", // coming soon!
BoopPaymaster = "boopPaymaster",
}
Loading