Using EIP-7702
Upgrade your user’s accounts to Smart Wallets using EIP-7702. This gives users access to all of the capabilities of Smart Wallets including gas sponsorship, batching, and more.
How it works
EIP-7702 enables EOAs (Externally Owned Accounts) to delegate control to smart wallets that can execute code directly from their addresses. Users simply sign an authorization payload to delegate, then their account can function as a smart wallet with access to all of the user’s assets. Wallet APIs will prompt the user to sign this authorization when the delegation is missing on the chain they’re interacting with.
Prerequisites
- API key from your dashboard
Implementation
React
JavaScript
API
To use Smart Wallets in EIP-7702 mode, you must specify the mode when constructing the smart account client with useSmartAccountClient
.
This will allow the authorized EOA to act as a smart wallet with the same address as the EOA. The useSendCalls
hook will sign the required
delegation payload the first time that a user interacts with a new chain. This payload is then relayed on chain with the user’s first transaction.
Prerequisites:
See the useSendCalls
SDK reference for parameter descriptions.
Advanced
When to use EIP-7702
EIP-7702 is particularly useful when you have existing wallets that are being used as EOAs, but want access to smart wallet capabilities while allowing users to keep their existing address and avoid transferring their assets. With EIP-7702, you can upgrade your users’ EOAs directly to a Smart Wallet at their current address.
See a full list of recommendations here.
Usage with prepare calls
The EIP-7702 capability works with the prepare calls function in a similar way to its usage in send calls. However, you are required to handle the authorization signature request in your code’s logic.
When a delegation is required the response to prepare calls will return an array of signature requests:
- An authorization signature request
- A user operation signature request
Both of these requests need to be signed and included in the sendPreparedCalls
request. sendPreparedCalls
takes
an array of signed calls for this purpose. These signatures are combined into a single transaction that is relayed onchain.
Once the delegation is signed, as long as the user doesn’t delegate to another account, subsequent requests will only
contain the user operation signature request.
Signing an authorization signature request requires logic specific to the wallet being used. Examples:
Most external wallet implementations, including browser wallets, will block requests to sign authorizations. This is done for security reasons. To use EIP-7702 ensure that your user’s wallets support signing authorizations.
React
JavaScript
API
See the usePrepareCalls
react
hook
EIP-7702 delegations
The eip7702Auth
capability supports the interface defined in ERC-7902.
However, it currently only supports a single delegation address: 0x69007702764179f14F51cdce752f4f775d74E139
. All other addresses will be rejected.
It’s recommended to use eip7702Auth: true
to avoid the extra complexity.
Once your account is delegated, it will remain delegated until the delegation is replaced or removed. We currently only support relaying delegations for Modular Account v2. If you wish to replace or remove this delegation, you’ll need to relay the delegation yourself or use a third party service. Viem has a guide for this here.
To reset an account back to a pure EOA with no delegation, delegate the account to 0x0000000000000000000000000000000000000000
as defined in EIP-7702.
Third party signers
If you have an existing custom signer or another third-party embedded wallet provider, you can upgrade your embedded EOAs to our smart wallets by connecting your existing signer. This will allow you to use EIP-7702 to get features like gas sponsorship, batching, and more.
Requirement: Your signer or embedded wallet provider must support signing EIP-7702 authorizations in order to delegate to a smart account.
To bring your own signer, you’ll create a SmartAccountSigner
that implements signAuthorization
. See the details for the interface requirements here.
For example, you can upgrade an existing Privy embedded EOA by extending a Wallet Client to use the sign7702Authorization
action exposed by Privy.
See full example code here.
The bulk of the logic happens in use-embedded-smart-wallet.ts
.
In this react hook, a Privy embedded wallet is wrapped in a WalletClientSigner
that supports signAuthorization
.
This signer is then passed to createSmartWalletClient
to construct a client for sending transactions.
import { type Address = `0x${string}`Address, type Authorization<uint32 = number, signed extends boolean = false> = {
address: Address;
chainId: uint32;
nonce: uint32;
} & (signed extends true ? Signature<uint32> : ExactPartial<Signature<uint32>>)Authorization, function createWalletClient<transport extends Transport, chain extends Chain | undefined = undefined, accountOrAddress extends Account | Address | undefined = undefined, rpcSchema extends RpcSchema | undefined = undefined>(parameters: WalletClientConfig<transport, chain, accountOrAddress, rpcSchema>): WalletClient<transport, chain, ParseAccount<accountOrAddress>, rpcSchema>Creates a Wallet Client with a given Transport configured for a Chain.
Docs: https://viem.sh/docs/clients/wallet
A Wallet Client is an interface to interact with Ethereum Account(s) and provides the ability to retrieve accounts, execute transactions, sign messages, etc. through Wallet Actions.
The Wallet Client supports signing over:
JSON-RPC Accounts (e.g. Browser Extension Wallets, WalletConnect, etc).
Local Accounts (e.g. private key/mnemonic wallets).
createWalletClient, function custom<provider extends EthereumProvider>(provider: provider, config?: CustomTransportConfig): CustomTransportcustom } from "viem";
import { import useSign7702AuthorizationuseSign7702Authorization } from "@privy-io/react-auth";
import { type AuthorizationRequest<uint32 = number> = OneOf<{
address: Address;
} | {
contractAddress: Address;
}> & {
chainId: uint32;
nonce: uint32;
}AuthorizationRequest, class WalletClientSignerRepresents a wallet client signer for smart accounts, providing methods to get the address, sign messages, and sign typed data.
WalletClientSigner } from "@aa-sdk/core";
import { const sepolia: Chainsepolia, function alchemy(config: AlchemyTransportConfig): AlchemyTransportCreates an Alchemy transport with the specified configuration options. When sending all traffic to Alchemy, you must pass in one of rpcUrl, apiKey, or jwt. If you want to send Bundler and Paymaster traffic to Alchemy and Node traffic to a different RPC, you must pass in alchemyConnection and nodeRpcUrl.
alchemy } from "@account-kit/infra";
import { function useEffect(effect: React.EffectCallback, deps?: React.DependencyList): voidAccepts a function that contains imperative, possibly effectful code.
useEffect, function useState<S>(initialState: S | (() => S)): [S, React.Dispatch<React.SetStateAction<S>>] (+1 overload)Returns a stateful value, and a function to update it.
useState } from "react";
import {
function createSmartWalletClient<TAccount extends Address | undefined = undefined>(params: SmartWalletClientParams<TAccount>): SmartWalletClient<TAccount>Creates a smart wallet client that can be used to interact with a smart account.
createSmartWalletClient,
type SmartWalletClient<TAccount extends Address | undefined = `0x${string}` | undefined> = Client_Base<Transport, Chain, JsonRpcAccount<`0x${string}`> | undefined, WalletServerViemRpcSchema> & {
policyIds?: string[];
} & {
internal: InternalState;
} & {
extend: <const client extends {
[x: string]: unknown;
account?: undefined;
batch?: undefined;
cacheTime?: undefined;
ccipRead?: undefined;
chain?: undefined;
experimental_blockTag?: undefined;
key?: undefined;
name?: undefined;
pollingInterval?: undefined;
request?: undefined;
transport?: undefined;
type?: undefined;
uid?: undefined;
} & ExactPartial<...>>(fn: (client: Client<...>) => client) => Client<...>;
} & SmartWalletActions<...>SmartWalletClient,
} from "@account-kit/wallet-client";
import { anyConnectedWallet as import PrivyWalletPrivyWallet } from "@privy-io/react-auth";
/\*_ Creates an Alchemy Smart Wallet client for an embedded Privy wallet using EIP-7702. _/;
export const const useSmartEmbeddedWallet: ({ embeddedWallet, }: {
embeddedWallet: PrivyWallet;
}) => {
client: SmartWalletClient | undefined;
}useSmartEmbeddedWallet = ({
embeddedWallet: PrivyWalletembeddedWallet,
}: {
embeddedWallet: PrivyWalletembeddedWallet: import PrivyWalletPrivyWallet;
}) => {
const { const signAuthorization: anysignAuthorization } = import useSign7702AuthorizationuseSign7702Authorization();
const [const client: SmartWalletClient | undefinedclient, const setClient: React.Dispatch<React.SetStateAction<SmartWalletClient | undefined>>setClient] = useState<SmartWalletClient>(): [SmartWalletClient | undefined, React.Dispatch<React.SetStateAction<SmartWalletClient | undefined>>] (+1 overload)Returns a stateful value, and a function to update it.
useState<type SmartWalletClient<TAccount extends Address | undefined = `0x${string}` | undefined> = Client_Base<Transport, Chain, JsonRpcAccount<`0x${string}`> | undefined, WalletServerViemRpcSchema> & {
policyIds?: string[];
} & {
internal: InternalState;
} & {
extend: <const client extends {
[x: string]: unknown;
account?: undefined;
batch?: undefined;
cacheTime?: undefined;
ccipRead?: undefined;
chain?: undefined;
experimental_blockTag?: undefined;
key?: undefined;
name?: undefined;
pollingInterval?: undefined;
request?: undefined;
transport?: undefined;
type?: undefined;
uid?: undefined;
} & ExactPartial<...>>(fn: (client: Client<...>) => client) => Client<...>;
} & SmartWalletActions<...>SmartWalletClient>();
function useEffect(effect: React.EffectCallback, deps?: React.DependencyList): voidAccepts a function that contains imperative, possibly effectful code.
useEffect(() => {
const const apiKey: string | undefinedapiKey = var process: NodeJS.Processprocess.NodeJS.Process.env: NodeJS.ProcessEnvThe process.env
property returns an object containing the user environment. See environ(7)
.
An example of this object looks like:
jsTERM: 'xterm-256color', SHELL: '/usr/local/bin/bash', USER: 'maciej', PATH: '~/.bin/:/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin', PWD: '/Users/maciej', EDITOR: 'vim', SHLVL: '1', HOME: '/Users/maciej', LOGNAME: 'maciej', _: '/usr/local/bin/node'
It is possible to modify this object, but such modifications will not be reflected outside the Node.js process, or (unless explicitly requested) to other Worker
threads. In other words, the following example would not work:
bash node -e 'process.env.foo = "bar"' && echo $foo
While the following will:
env.foo = 'bar'; console.log(env.foo); ```
Assigning a property on `process.env` will implicitly convert the value to a string. **This behavior is deprecated.** Future versions of Node.js may throw an error when the value is not a string, number, or boolean.
```js import env from 'node:process';
env.test = null; console.log(env.test); // => 'null' env.test = undefined; console.log(env.test); // => 'undefined' ```
Use `delete` to delete a property from `process.env`.
```js import env from 'node:process';
env.TEST = 1; delete env.TEST; console.log(env.TEST); // => undefined ```
On Windows operating systems, environment variables are case-insensitive.
```js import env from 'node:process';
env.TEST = 1; console.log(env.test); // => 1 ```
Unless explicitly specified when creating a `Worker` instance, each `Worker` thread has its own copy of `process.env`, based on its parent thread's `process.env`, or whatever was specified as the `env` option to the `Worker` constructor. Changes to `process.env` will not be visible across `Worker` threads, and only the main thread can make changes that are visible to the operating system or to native add-ons. On Windows, a copy of `process.env` on a `Worker` instance operates in a case-sensitive manner unlike the main thread.
env.string | undefinedNEXT_PUBLIC_ALCHEMY_API_KEY;
if (!const apiKey: string | undefinedapiKey) {
throw new var Error: ErrorConstructor
new (message?: string) => ErrorError("Missing NEXT_PUBLIC_ALCHEMY_API_KEY");
}
(async () => {
const const provider: anyprovider = await embeddedWallet: PrivyWalletembeddedWallet.anygetEthereumProvider();
const const baseSigner: WalletClientSignerbaseSigner = new new WalletClientSigner(client: WalletClient, signerType: string): WalletClientSignerInitializes a signer with a given wallet client and signer type.
WalletClientSigner(
createWalletClient<CustomTransport, Chain, `0x${string}`, []>(parameters: {
account?: `0x${string}` | Account | undefined;
cacheTime?: number | undefined | undefined;
ccipRead?: {
request?: (parameters: CcipRequestParameters) => Promise<CcipRequestReturnType>;
} | false | undefined | undefined;
... 5 more ...;
transport: CustomTransport;
}): {
...;
}Creates a Wallet Client with a given Transport configured for a Chain.
Docs: https://viem.sh/docs/clients/wallet
A Wallet Client is an interface to interact with Ethereum Account(s) and provides the ability to retrieve accounts, execute transactions, sign messages, etc. through Wallet Actions.
The Wallet Client supports signing over:
JSON-RPC Accounts (e.g. Browser Extension Wallets, WalletConnect, etc).
Local Accounts (e.g. private key/mnemonic wallets).
createWalletClient({
account?: `0x${string}` | Account | undefinedThe Account to use for the Client. This will be used for Actions that require an account as an argument.
account: embeddedWallet: PrivyWalletembeddedWallet.anyaddress as type Address = `0x${string}`Address,
chain?: Chain | undefinedChain for the client.
chain: const sepolia: Chainsepolia,
transport: CustomTransportThe RPC transport
transport: custom<any>(provider: any, config?: CustomTransportConfig): CustomTransportcustom(const provider: anyprovider),
}),
"privy",
);
const const signer: {
signAuthorization: (unsignedAuth: AuthorizationRequest<number>) => Promise<Authorization<number, true>>;
signerType: string;
inner: WalletClient;
getAddress: () => Promise<`0x${string}`>;
signMessage: (message: SignableMessage) => Promise<`0x${string}`>;
signTypedData: <const TTypedData extends TypedData | Record<string, unknown>, TPrimaryType extends keyof TTypedData | "EIP712Domain" | string = string>(typedData: TypedDataDefinition<TTypedData, TPrimaryType>) => Promise<Hex>;
}signer = {
...const baseSigner: WalletClientSignerbaseSigner,
signAuthorization: (unsignedAuth: AuthorizationRequest<number>) => Promise<Authorization<number, true>>signAuthorization: async (
unsignedAuth: AuthorizationRequest<number>unsignedAuth: type AuthorizationRequest<uint32 = number> = OneOf<{
address: Address;
} | {
contractAddress: Address;
}> & {
chainId: uint32;
nonce: uint32;
}AuthorizationRequest<number>,
): interface Promise<T>Represents the completion of an asynchronous operation
Promise<type Authorization<uint32 = number, signed extends boolean = false> = {
address: Address;
chainId: uint32;
nonce: uint32;
} & (signed extends true ? Signature<uint32> : ExactPartial<Signature<uint32>>)Authorization<number, true>> => {
const const signature: anysignature = await const signAuthorization: anysignAuthorization({
...unsignedAuth: AuthorizationRequest<number>unsignedAuth,
contractAddress: `0x${string}`contractAddress:
unsignedAuth: AuthorizationRequest<number>unsignedAuth.address?: `0x${string}` | undefinedaddress ?? unsignedAuth: {
contractAddress: Address;
address?: undefined;
} & {
chainId: number;
nonce: number;
}unsignedAuth.contractAddress: `0x${string}`contractAddress,
});
return {
...unsignedAuth: AuthorizationRequest<number>unsignedAuth,
...const signature: anysignature,
};
},
};
const const client: SmartWalletClient<undefined>client = createSmartWalletClient<undefined>(params: SmartWalletClientParams<undefined>): SmartWalletClient<undefined>Creates a smart wallet client that can be used to interact with a smart account.
createSmartWalletClient({
chain: Chainchain: const sepolia: Chainsepolia,
transport: AlchemyTransporttransport: function alchemy(config: AlchemyTransportConfig): AlchemyTransportCreates an Alchemy transport with the specified configuration options. When sending all traffic to Alchemy, you must pass in one of rpcUrl, apiKey, or jwt. If you want to send Bundler and Paymaster traffic to Alchemy and Node traffic to a different RPC, you must pass in alchemyConnection and nodeRpcUrl.
alchemy({
apiKey: stringapiKey,
}),
signer: SmartAccountSigner<any>signer,
policyId?: string | undefinedpolicyId: var process: NodeJS.Processprocess.NodeJS.Process.env: NodeJS.ProcessEnvThe process.env
property returns an object containing the user environment. See environ(7)
.
An example of this object looks like:
jsTERM: 'xterm-256color', SHELL: '/usr/local/bin/bash', USER: 'maciej', PATH: '~/.bin/:/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin', PWD: '/Users/maciej', EDITOR: 'vim', SHLVL: '1', HOME: '/Users/maciej', LOGNAME: 'maciej', _: '/usr/local/bin/node'
It is possible to modify this object, but such modifications will not be reflected outside the Node.js process, or (unless explicitly requested) to other Worker
threads. In other words, the following example would not work:
bash node -e 'process.env.foo = "bar"' && echo $foo
While the following will:
env.foo = 'bar'; console.log(env.foo); ```
Assigning a property on `process.env` will implicitly convert the value to a string. **This behavior is deprecated.** Future versions of Node.js may throw an error when the value is not a string, number, or boolean.
```js import env from 'node:process';
env.test = null; console.log(env.test); // => 'null' env.test = undefined; console.log(env.test); // => 'undefined' ```
Use `delete` to delete a property from `process.env`.
```js import env from 'node:process';
env.TEST = 1; delete env.TEST; console.log(env.TEST); // => undefined ```
On Windows operating systems, environment variables are case-insensitive.
```js import env from 'node:process';
env.TEST = 1; console.log(env.test); // => 1 ```
Unless explicitly specified when creating a `Worker` instance, each `Worker` thread has its own copy of `process.env`, based on its parent thread's `process.env`, or whatever was specified as the `env` option to the `Worker` constructor. Changes to `process.env` will not be visible across `Worker` threads, and only the main thread can make changes that are visible to the operating system or to native add-ons. On Windows, a copy of `process.env` on a `Worker` instance operates in a case-sensitive manner unlike the main thread.
env.string | undefinedNEXT_PUBLIC_ALCHEMY_POLICY_ID,
});
const setClient: (value: React.SetStateAction<SmartWalletClient | undefined>) => voidsetClient(const client: SmartWalletClient<undefined>client);
})();
}, [embeddedWallet: PrivyWalletembeddedWallet, const signAuthorization: anysignAuthorization]);
return { client: SmartWalletClient | undefinedclient };
};
When using the SmartWalletClient
you must set the eip7702Auth
capability to true
, as shown in smart-wallet-demo.tsx
import { anyConnectedWallet as import PrivyWalletPrivyWallet } from "@privy-io/react-auth";
import { import useSmartEmbeddedWalletuseSmartEmbeddedWallet } from "../hooks/use-smart-embedded-wallet";
import { function useCallback<T extends Function>(callback: T, deps: React.DependencyList): TuseCallback
will return a memoized version of the callback that only changes if one of the inputs
has changed.
useCallback, function useState<S>(initialState: S | (() => S)): [S, React.Dispatch<React.SetStateAction<S>>] (+1 overload)Returns a stateful value, and a function to update it.
useState } from "react";
import type { type Address = `0x${string}`Address, type Hex = `0x${string}`Hex } from "viem";
export const const SmartWalletDemo: ({ embeddedWallet, }: {
embeddedWallet: PrivyWallet;
}) => JSX.ElementSmartWalletDemo = ({
embeddedWallet: PrivyWalletembeddedWallet,
}: {
embeddedWallet: PrivyWalletembeddedWallet: import PrivyWalletPrivyWallet;
}) => {
const { const client: anyclient } = import useSmartEmbeddedWalletuseSmartEmbeddedWallet({ embeddedWallet: PrivyWalletembeddedWallet });
const [const status: {
status: "idle" | "error" | "sending";
} | {
status: "success";
txHash: Hex;
}status, const setStatus: React.Dispatch<React.SetStateAction<{
status: "idle" | "error" | "sending";
} | {
status: "success";
txHash: Hex;
}>>setStatus] = useState<{
status: "idle" | "error" | "sending";
} | {
status: "success";
txHash: Hex;
}>(initialState: {
status: "idle" | "error" | "sending";
} | {
status: "success";
txHash: Hex;
} | (() => {
status: "idle" | "error" | "sending";
} | {
status: "success";
txHash: Hex;
})): [{
status: "idle" | "error" | "sending";
} | {
status: "success";
txHash: Hex;
}, React.Dispatch<React.SetStateAction<{
status: "idle" | "error" | "sending";
} | {
status: "success";
txHash: Hex;
}>>] (+1 overload)Returns a stateful value, and a function to update it.
useState<
| { status: "idle" | "error" | "sending"status: "idle" | "error" | "sending" }
| { status: "success"status: "success"; txHash: `0x${string}`txHash: type Hex = `0x${string}`Hex }
>({ status: "idle" | "error" | "sending"status: "idle" });
const const delegateAndSend: () => Promise<void>delegateAndSend = useCallback<() => Promise<void>>(callback: () => Promise<void>, deps: React.DependencyList): () => Promise<void>useCallback
will return a memoized version of the callback that only changes if one of the inputs
has changed.
useCallback(async () => {
if (!const client: anyclient) {
return;
}
const setStatus: (value: React.SetStateAction<{
status: "idle" | "error" | "sending";
} | {
status: "success";
txHash: Hex;
}>) => voidsetStatus({ status: "idle" | "error" | "sending"status: "sending" });
try {
const {
anypreparedCallIds: [const callId: anycallId],
} = await const client: anyclient.anysendCalls({
capabilities: {
eip7702Auth: boolean;
}capabilities: {
eip7702Auth: booleaneip7702Auth: true,
},
from: `0x${string}`from: embeddedWallet: PrivyWalletembeddedWallet.anyaddress as type Address = `0x${string}`Address,
calls: {
to: string;
data: string;
}[]calls: [
{
to: stringto: "0x0000000000000000000000000000000000000000",
data: stringdata: "0x",
},
],
});
if (!const callId: anycallId) {
throw new var Error: ErrorConstructor
new (message?: string) => ErrorError("Missing call id");
}
const { const receipts: anyreceipts } = await const client: anyclient.anywaitForCallsStatus({ id: anyid: const callId: anycallId });
if (!const receipts: anyreceipts?.anylength) {
throw new var Error: ErrorConstructor
new (message?: string) => ErrorError("Missing transaction receipts");
}
const [const receipt: anyreceipt] = const receipts: anyreceipts;
if (const receipt: anyreceipt?.anystatus !== "success") {
throw new var Error: ErrorConstructor
new (message?: string) => ErrorError("Transaction failed");
}
const setStatus: (value: React.SetStateAction<{
status: "idle" | "error" | "sending";
} | {
status: "success";
txHash: Hex;
}>) => voidsetStatus({ status: "success"status: "success", txHash: `0x${string}`txHash: const receipt: anyreceipt.anytransactionHash });
} catch (function (local var) err: unknownerr) {
var console: ConsoleThe console
module provides a simple debugging console that is similar to the JavaScript console mechanism provided by web browsers.
The module exports two specific components:
- A
Console
class with methods such as console.log()
, console.error()
and console.warn()
that can be used to write to any Node.js stream. * A global console
instance configured to write to process.stdout
and process.stderr
. The global console
can be used without importing the node:console
module.
Warning: The global console object's methods are neither consistently synchronous like the browser APIs they resemble, nor are they consistently asynchronous like all other Node.js streams. See the note on process I/O
for more information.
Example using the global console
:
const name = 'Will Robinson'; console.warn(`Danger $name! Danger!`); // Prints: Danger Will Robinson! Danger!, to stderr ```
Example using the `Console` class:
```js const out = getStreamSomehow(); const err = getStreamSomehow(); const myConsole = new console.Console(out, err);
myConsole.log('hello world'); // Prints: hello world, to out myConsole.log('hello %s', 'world'); // Prints: hello world, to out myConsole.error(new Error('Whoops, something bad happened')); // Prints: [Error: Whoops, something bad happened], to err
const name = 'Will Robinson'; myConsole.warn(`Danger $name! Danger!`); // Prints: Danger Will Robinson! Danger!, to err ```
console.Console.error(message?: any, ...optionalParams: any[]): void (+1 overload)Prints to stderr
with newline. Multiple arguments can be passed, with the first used as the primary message and all additional used as substitution values similar to printf(3)
(the arguments are all passed to util.format()
).
js const code = 5; console.error('error #%d', code); // Prints: error #5, to stderr console.error('error', code); // Prints: error 5, to stderr
If formatting elements (e.g. %d
) are not found in the first string then util.inspect()
is called on each argument and the resulting string values are concatenated. See util.format()
for more information.
error("Transaction failed:", function (local var) err: unknownerr);
const setStatus: (value: React.SetStateAction<{
status: "idle" | "error" | "sending";
} | {
status: "success";
txHash: Hex;
}>) => voidsetStatus({ status: "idle" | "error" | "sending"status: "error" });
}
}, [const client: anyclient, embeddedWallet: PrivyWalletembeddedWallet]);
return (
<React.JSX.IntrinsicElements.div: React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>div React.HTMLAttributes<HTMLDivElement>.className?: string | undefinedclassName="flex flex-col gap-4">
<React.JSX.IntrinsicElements.div: React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>div>
<React.JSX.IntrinsicElements.h2: React.DetailedHTMLProps<React.HTMLAttributes<HTMLHeadingElement>, HTMLHeadingElement>h2 React.HTMLAttributes<T>.className?: string | undefinedclassName="text-lg font-semibold text-gray-900">
Embedded EOA Address
</React.JSX.IntrinsicElements.h2: React.DetailedHTMLProps<React.HTMLAttributes<HTMLHeadingElement>, HTMLHeadingElement>h2>
<React.JSX.IntrinsicElements.p: React.DetailedHTMLProps<React.HTMLAttributes<HTMLParagraphElement>, HTMLParagraphElement>p React.HTMLAttributes<T>.className?: string | undefinedclassName="text-gray-600 font-mono break-all">
{embeddedWallet: PrivyWalletembeddedWallet.anyaddress}
</React.JSX.IntrinsicElements.p: React.DetailedHTMLProps<React.HTMLAttributes<HTMLParagraphElement>, HTMLParagraphElement>p>
</React.JSX.IntrinsicElements.div: React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>div>
<React.JSX.IntrinsicElements.button: React.DetailedHTMLProps<React.ButtonHTMLAttributes<HTMLButtonElement>, HTMLButtonElement>button
React.DOMAttributes<HTMLButtonElement>.onClick?: React.MouseEventHandler<HTMLButtonElement> | undefinedonClick={const delegateAndSend: () => Promise<void>delegateAndSend}
React.ButtonHTMLAttributes<HTMLButtonElement>.disabled?: boolean | undefineddisabled={!const client: anyclient || const status: {
status: "idle" | "error" | "sending";
} | {
status: "success";
txHash: Hex;
}status.status: "idle" | "error" | "sending" | "success"status === "sending"}
React.HTMLAttributes<T>.className?: string | undefinedclassName={`w-full py-3 px-4 rounded-lg font-semibold text-white transition-colors ${
const status: {
status: "idle" | "error" | "sending";
} | {
status: "success";
txHash: Hex;
}status.status: "idle" | "error" | "sending" | "success"status === "sending"
? "bg-indigo-400 cursor-not-allowed"
: "bg-indigo-600 hover:bg-indigo-700"
}`}
>
{const status: {
status: "idle" | "error" | "sending";
} | {
status: "success";
txHash: Hex;
}status.status: "idle" | "error" | "sending" | "success"status === "sending"
? "Sending..."
: "Upgrade & Send Sponsored Transaction"}
</React.JSX.IntrinsicElements.button: React.DetailedHTMLProps<React.ButtonHTMLAttributes<HTMLButtonElement>, HTMLButtonElement>button>
{const status: {
status: "idle" | "error" | "sending";
} | {
status: "success";
txHash: Hex;
}status.status: "idle" | "error" | "sending" | "success"status === "success" && (
<React.JSX.IntrinsicElements.section: React.DetailedHTMLProps<React.HTMLAttributes<HTMLElement>, HTMLElement>section React.HTMLAttributes<T>.className?: string | undefinedclassName="bg-green-50 rounded-xl shadow-lg p-6 border border-green-200">
<React.JSX.IntrinsicElements.h2: React.DetailedHTMLProps<React.HTMLAttributes<HTMLHeadingElement>, HTMLHeadingElement>h2 React.HTMLAttributes<T>.className?: string | undefinedclassName="text-lg font-semibold text-green-900 mb-4">
Congrats! Sponsored transaction successful!
</React.JSX.IntrinsicElements.h2: React.DetailedHTMLProps<React.HTMLAttributes<HTMLHeadingElement>, HTMLHeadingElement>h2>
<React.JSX.IntrinsicElements.p: React.DetailedHTMLProps<React.HTMLAttributes<HTMLParagraphElement>, HTMLParagraphElement>p React.HTMLAttributes<T>.className?: string | undefinedclassName="text-green-700 mb-4">
You've successfully upgraded your EOA to a smart account and sent
your first sponsored transaction.{" "}
<React.JSX.IntrinsicElements.a: React.DetailedHTMLProps<React.AnchorHTMLAttributes<HTMLAnchorElement>, HTMLAnchorElement>a
React.AnchorHTMLAttributes<HTMLAnchorElement>.href?: string | undefinedhref="https://www.alchemy.com/docs/wallets/transactions/using-eip-7702"
React.HTMLAttributes<T>.className?: string | undefinedclassName="text-indigo-600 hover:underline"
React.AnchorHTMLAttributes<HTMLAnchorElement>.target?: React.HTMLAttributeAnchorTarget | undefinedtarget="_blank"
React.HTMLAttributes<HTMLAnchorElement>.rel?: string | undefinedrel="noopener noreferrer"
>
Keep building
</React.JSX.IntrinsicElements.a: React.DetailedHTMLProps<React.AnchorHTMLAttributes<HTMLAnchorElement>, HTMLAnchorElement>a>
.
</React.JSX.IntrinsicElements.p: React.DetailedHTMLProps<React.HTMLAttributes<HTMLParagraphElement>, HTMLParagraphElement>p>
<React.JSX.IntrinsicElements.p: React.DetailedHTMLProps<React.HTMLAttributes<HTMLParagraphElement>, HTMLParagraphElement>p React.HTMLAttributes<T>.className?: string | undefinedclassName="text-green-700">
<React.JSX.IntrinsicElements.strong: React.DetailedHTMLProps<React.HTMLAttributes<HTMLElement>, HTMLElement>strong>Transaction Hash:</React.JSX.IntrinsicElements.strong: React.DetailedHTMLProps<React.HTMLAttributes<HTMLElement>, HTMLElement>strong>{" "}
<React.JSX.IntrinsicElements.span: React.DetailedHTMLProps<React.HTMLAttributes<HTMLSpanElement>, HTMLSpanElement>span React.HTMLAttributes<T>.className?: string | undefinedclassName="font-mono break-all">{const status: {
status: "success";
txHash: Hex;
}status.txHash: `0x${string}`txHash}</React.JSX.IntrinsicElements.span: React.DetailedHTMLProps<React.HTMLAttributes<HTMLSpanElement>, HTMLSpanElement>span>
</React.JSX.IntrinsicElements.p: React.DetailedHTMLProps<React.HTMLAttributes<HTMLParagraphElement>, HTMLParagraphElement>p>
</React.JSX.IntrinsicElements.section: React.DetailedHTMLProps<React.HTMLAttributes<HTMLElement>, HTMLElement>section>
)}
{const status: {
status: "idle" | "error" | "sending";
} | {
status: "success";
txHash: Hex;
}status.status: "idle" | "error" | "sending" | "success"status === "error" && (
<React.JSX.IntrinsicElements.section: React.DetailedHTMLProps<React.HTMLAttributes<HTMLElement>, HTMLElement>section React.HTMLAttributes<T>.className?: string | undefinedclassName="bg-red-50 rounded-xl shadow-lg p-6 border border-red-200">
<React.JSX.IntrinsicElements.h2: React.DetailedHTMLProps<React.HTMLAttributes<HTMLHeadingElement>, HTMLHeadingElement>h2 React.HTMLAttributes<T>.className?: string | undefinedclassName="text-lg font-semibold text-red-900 mb-4">
Transaction Failed
</React.JSX.IntrinsicElements.h2: React.DetailedHTMLProps<React.HTMLAttributes<HTMLHeadingElement>, HTMLHeadingElement>h2>
<React.JSX.IntrinsicElements.p: React.DetailedHTMLProps<React.HTMLAttributes<HTMLParagraphElement>, HTMLParagraphElement>p React.HTMLAttributes<T>.className?: string | undefinedclassName="text-red-700">
There was an error sending your sponsored transaction. Please try
again.
</React.JSX.IntrinsicElements.p: React.DetailedHTMLProps<React.HTMLAttributes<HTMLParagraphElement>, HTMLParagraphElement>p>
</React.JSX.IntrinsicElements.section: React.DetailedHTMLProps<React.HTMLAttributes<HTMLElement>, HTMLElement>section>
)}
</React.JSX.IntrinsicElements.div: React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>div>
);
};
Next steps
Build more: