Prerequisites
Setup 7702 Client
Send Transactions
- Alchemy
- ZeroDev
- Gelato
- Porto
The sending API is identical to 4337 - the
mode: "7702" was set during client creation.useSendTransaction.ts
Copy
Ask AI
import { useState, useCallback } from "react";
import type { ModularAccountV2Client } from "@account-kit/smart-contracts";
import type { Address, Hash, Hex } from "viem";
interface TransactionCall {
target: Address;
data?: Hex;
value?: bigint;
}
export function useSendTransaction(client: ModularAccountV2Client | null) {
const [isPending, setIsPending] = useState(false);
const [txHash, setTxHash] = useState<Hash | null>(null);
const [error, setError] = useState<Error | null>(null);
const sendTransaction = useCallback(
async (calls: TransactionCall | TransactionCall[]): Promise<Hash> => {
if (!client) throw new Error("Client not initialized");
setIsPending(true);
setError(null);
try {
const callsArray = Array.isArray(calls) ? calls : [calls];
const formattedCalls = callsArray.map((call) => ({
target: call.target,
data: call.data ?? "0x",
value: call.value ?? BigInt(0),
}));
const userOpHash = await client.sendUserOperation({
uo: formattedCalls.length === 1 ? formattedCalls[0] : formattedCalls,
});
const hash = await client.waitForUserOperationTransaction(userOpHash);
setTxHash(hash);
return hash;
} catch (err) {
const error = err instanceof Error ? err : new Error("Failed to send");
setError(error);
throw error;
} finally {
setIsPending(false);
}
},
[client]
);
return { sendTransaction, isPending, txHash, error };
}
Usage
Copy
Ask AI
import { useSmartAccountClient } from "./useSmartAccountClient";
import { useSendTransaction } from "./useSendTransaction";
import { parseEther } from "viem";
function SendTransaction() {
const { client, address } = useSmartAccountClient();
const { sendTransaction, isPending, txHash } = useSendTransaction(client);
const handleSend = async () => {
const hash = await sendTransaction({
target: "0x...",
value: parseEther("0.001"),
});
console.log("Transaction:", hash);
};
return (
<div>
<p>Address (same as EOA): {address}</p>
<button onClick={handleSend} disabled={isPending}>
{isPending ? "Sending..." : "Send 0.001 ETH"}
</button>
{txHash && <p>Tx: {txHash}</p>}
</div>
);
}
useSendTransaction.ts
Copy
Ask AI
import { useState, useCallback } from "react";
import type { KernelAccountClient } from "@zerodev/sdk";
import type { Address, Hash, Hex } from "viem";
interface TransactionCall {
to: Address;
data?: Hex;
value?: bigint;
}
export function useSendTransaction(client: KernelAccountClient | null) {
const [isPending, setIsPending] = useState(false);
const [txHash, setTxHash] = useState<Hash | null>(null);
const [error, setError] = useState<Error | null>(null);
const sendTransaction = useCallback(
async (calls: TransactionCall | TransactionCall[]): Promise<Hash> => {
if (!client) throw new Error("Client not initialized");
setIsPending(true);
setError(null);
try {
const callsArray = Array.isArray(calls) ? calls : [calls];
const hash = await client.sendTransaction({
calls: callsArray.map((call) => ({
to: call.to,
data: call.data ?? "0x",
value: call.value ?? BigInt(0),
})),
});
setTxHash(hash);
return hash;
} catch (err) {
const error = err instanceof Error ? err : new Error("Failed to send");
setError(error);
throw error;
} finally {
setIsPending(false);
}
},
[client]
);
return { sendTransaction, isPending, txHash, error };
}
useSendTransaction.ts
Copy
Ask AI
import { useState, useCallback } from "react";
import type { GelatoSmartWalletClient } from "@gelatonetwork/smartwallet";
import type { Address, Hash, Hex } from "viem";
interface TransactionCall {
to: Address;
data?: Hex;
value?: bigint;
}
export function useSendTransaction(client: GelatoSmartWalletClient | null) {
const [isPending, setIsPending] = useState(false);
const [txHash, setTxHash] = useState<Hash | null>(null);
const [error, setError] = useState<Error | null>(null);
const sendTransaction = useCallback(
async (call: TransactionCall): Promise<Hash> => {
if (!client) throw new Error("Client not initialized");
setIsPending(true);
setError(null);
try {
const hash = await client.sendTransaction({
to: call.to,
value: call.value ?? BigInt(0),
data: call.data ?? "0x",
});
const receipt = await client.waitForTransactionReceipt({ hash });
setTxHash(receipt.transactionHash);
return receipt.transactionHash;
} catch (err) {
const error = err instanceof Error ? err : new Error("Failed to send");
setError(error);
throw error;
} finally {
setIsPending(false);
}
},
[client]
);
return { sendTransaction, isPending, txHash, error };
}
Porto uses
RelayActions to send sponsored transactions.useSendTransaction.ts
Copy
Ask AI
import { useState, useCallback, useMemo } from "react";
import { Chains } from "porto";
import { RelayActions } from "porto/viem";
import { createClient, http, type Address, type Hash, type Hex, parseEther } from "viem";
interface TransactionCall {
to: Address;
data?: Hex;
value?: bigint;
}
export function useSendTransaction(portoAccount: any) {
const [isPending, setIsPending] = useState(false);
const [txHash, setTxHash] = useState<Hash | null>(null);
const [error, setError] = useState<Error | null>(null);
const portoClient = useMemo(
() =>
createClient({
chain: Chains.baseSepolia,
transport: http("https://rpc.porto.sh"),
}),
[]
);
const sendTransaction = useCallback(
async (call: TransactionCall): Promise<Hash> => {
if (!portoAccount) throw new Error("Porto account not initialized");
setIsPending(true);
setError(null);
try {
const receipt = await RelayActions.sendCalls(portoClient, {
account: portoAccount,
calls: [
{
to: call.to,
value: call.value ?? BigInt(0),
data: call.data ?? "0x",
},
],
});
setTxHash(receipt.id as Hash);
return receipt.id as Hash;
} catch (err) {
const error = err instanceof Error ? err : new Error("Failed to send");
setError(error);
throw error;
} finally {
setIsPending(false);
}
},
[portoAccount, portoClient]
);
return { sendTransaction, isPending, txHash, error };
}
Porto Usage
Copy
Ask AI
import { usePortoAccount } from "./usePortoAccount";
import { useSendTransaction } from "./useSendTransaction";
import { parseEther } from "viem";
function PortoDemo() {
const { portoAccount, address, upgradeToPorto, isUpgrading } = usePortoAccount();
const { sendTransaction, isPending, txHash } = useSendTransaction(portoAccount);
if (!portoAccount) {
return (
<button onClick={upgradeToPorto} disabled={isUpgrading}>
{isUpgrading ? "Upgrading..." : "Upgrade to Porto"}
</button>
);
}
return (
<div>
<p>Address: {address}</p>
<button
onClick={() => sendTransaction({ to: "0x...", value: parseEther("0.001") })}
disabled={isPending}
>
{isPending ? "Sending..." : "Send 0.001 ETH"}
</button>
{txHash && <p>Tx: {txHash}</p>}
</div>
);
}
Key Differences from 4337
| Aspect | EIP-7702 | EIP-4337 |
|---|---|---|
| From Address | Your EOA address | Smart contract address |
| Transaction Type | Standard transaction with delegation | UserOperation |
| Explorer View | Shows your address as sender | Shows smart wallet as sender |