Installation
Copy
Ask AI
npm install @gelatonetwork/smartwallet @getpara/react-sdk @getpara/viem-v2-integration @tanstack/react-query viem
Setup Instructions
1
Create Para App
- Visit the Para Dashboard
- Create a new app or select an existing one
- Copy your API Key from the dashboard
2
Get Gelato API Key
- Visit the Gelato App
- Navigate to
Paymaster & Bundler > API Keys - Create a new API Key and select your required networks
- Copy the generated API Key
3
Set Environment Variables
Create a
.env.local file in your project root:Copy
Ask AI
NEXT_PUBLIC_PARA_API_KEY=your_para_api_key
NEXT_PUBLIC_GELATO_API_KEY=your_gelato_api_key
Implementation
1
Import Dependencies
Copy
Ask AI
import {
createGelatoSmartWalletClient,
sponsored,
} from "@gelatonetwork/smartwallet";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import {
ParaProvider,
Environment,
useClient,
useModal,
useWallet,
} from "@getpara/react-sdk";
import { createParaAccount } from "@getpara/viem-v2-integration";
import { createPublicClient, createWalletClient, http } from "viem";
import { sepolia } from "viem/chains";
import { gelato } from "@gelatonetwork/smartwallet/accounts";
import "@getpara/react-sdk/styles.css";
2
Configure Providers
Copy
Ask AI
const queryClient = new QueryClient();
export default function Provider({ children }: { children: React.ReactNode }) {
return (
<QueryClientProvider client={queryClient}>
<ParaProvider
paraClientConfig={{
env: Environment.BETA,
apiKey: process.env.NEXT_PUBLIC_PARA_API_KEY!,
}}
config={{
appName: "Your App Name",
}}
>
<App />
</ParaProvider>
</QueryClientProvider>
);
}
3
Create Smart Wallet Client
- Main Component
- Custom Sign Functions
Copy
Ask AI
export default function App() {
const para = useClient();
const { openModal } = useModal();
const { data: wallet } = useWallet();
const sendTransaction = async () => {
try {
if (!para) {
throw new Error("Para client not available");
}
const viemParaAccount = createParaAccount(para);
if (!viemParaAccount) {
throw new Error("Failed to create Para account");
}
viemParaAccount.signMessage = async ({ message }) => {
return customSignMessage(para, message);
};
viemParaAccount.signAuthorization = async (authorization) => {
return customSignAuthorization(para, authorization);
};
viemParaAccount.signTypedData = async (typedData) => {
return customSignTypedData(para, typedData);
};
const publicClient = createPublicClient({
chain: sepolia,
transport: http(""),
});
const account = await gelato({
owner: viemParaAccount,
client: publicClient,
});
const walletClient = createWalletClient({
account: account,
chain: sepolia,
transport: http(),
});
const smartWalletClient = await createGelatoSmartWalletClient(
walletClient,
{
apiKey: process.env.NEXT_PUBLIC_GELATO_API_KEY,
}
);
} catch (error) {
console.error("Client creation failed:", error);
throw error;
}
};
return (
<div>
<button onClick={() => openModal()}>Connect Wallet</button>
<button onClick={() => sendTransaction()}>Send Transaction</button>
<p>Connected: {wallet?.address}</p>
</div>
);
}
Copy
Ask AI
import { SuccessfulSignatureRes } from "@getpara/react-sdk";
import type { ParaWeb } from "@getpara/react-sdk";
import { hashMessage, hashTypedData } from "viem";
import type {
Hash,
SignableMessage,
TypedDataDefinition,
TypedData,
} from "viem";
import { hashAuthorization } from "viem/utils";
const SIGNATURE_LENGTH = 130;
const V_OFFSET_FOR_ETHEREUM = 27;
function hexStringToBase64(hex: string): string {
const cleanHex = hex.startsWith("0x") ? hex.slice(2) : hex;
const bytes = Buffer.from(cleanHex, "hex");
return bytes.toString("base64");
}
function parseSignature(signature: string): {
r: string;
s: string;
v: number;
} {
const cleanSig = signature.startsWith("0x") ? signature.slice(2) : signature;
if (cleanSig.length !== SIGNATURE_LENGTH) {
throw new Error(
`Invalid signature length: expected ${SIGNATURE_LENGTH} hex chars, got ${cleanSig.length}`
);
}
const r = cleanSig.slice(0, 64);
const s = cleanSig.slice(64, 128);
const vHex = cleanSig.slice(128, 130);
const v = parseInt(vHex, 16);
if (isNaN(v)) {
throw new Error(`Invalid v value in signature: ${vHex}`);
}
return { r, s, v };
}
async function signWithPara(
para: ParaWeb,
hash: Hash,
adjustV: boolean = true
): Promise<Hash> {
const wallet = para.getWalletsByType("EVM")[0];
if (!wallet) {
throw new Error("Para wallet not available for signing");
}
const messagePayload = hash.startsWith("0x") ? hash.substring(2) : hash;
const messageBase64 = hexStringToBase64(messagePayload);
const response = await para.signMessage({
walletId: wallet.id,
messageBase64,
});
if (!("signature" in response)) {
throw new Error(`Signature failed: ${JSON.stringify(response)}`);
}
let signature = (response as SuccessfulSignatureRes).signature;
const { v } = parseSignature(signature);
if (adjustV && v < 27) {
const adjustedV = (v + V_OFFSET_FOR_ETHEREUM).toString(16).padStart(2, "0");
signature = signature.slice(0, -2) + adjustedV;
}
return `0x${signature}`;
}
export async function customSignMessage(
para: ParaWeb,
message: SignableMessage
): Promise<Hash> {
const hashedMessage = hashMessage(message);
return signWithPara(para, hashedMessage, true);
}
export async function customSignAuthorization(
para: ParaWeb,
authorization: any
): Promise<any> {
const address = (authorization.address ||
authorization.contractAddress) as `0x${string}`;
if (!address) {
throw new Error("Authorization must include address or contractAddress");
}
const authorizationHash = hashAuthorization({
address,
chainId: authorization.chainId,
nonce: authorization.nonce,
});
const fullSignature = await signWithPara(para, authorizationHash, false);
const { r, s, v } = parseSignature(fullSignature);
if (v !== 0 && v !== 1) {
throw new Error(`Invalid v value for EIP-7702: ${v}. Expected 0 or 1`);
}
return {
address,
chainId: Number(authorization.chainId),
nonce: Number(authorization.nonce),
r: `0x${r}`,
s: `0x${s}`,
yParity: v as 0 | 1,
v: BigInt(v),
};
}
export async function customSignTypedData<
const typedData extends TypedData | Record<string, unknown>,
primaryType extends keyof typedData | "EIP712Domain" = keyof typedData
>(
para: ParaWeb,
parameters: TypedDataDefinition<typedData, primaryType>
): Promise<Hash> {
const typedDataHash = hashTypedData(parameters);
return signWithPara(para, typedDataHash, true);
}
4
Execute Transactions
Execute transactions using different payment methods:
- Sponsored
- ERC-20
- Native
Copy
Ask AI
const results = await smartWalletClient?.execute({
payment: sponsored(),
calls: [
{
to: "0xa8851f5f279eD47a292f09CA2b6D40736a51788E",
data: "0xd09de08a",
value: 0n
}
]
});
Copy
Ask AI
import { erc20 } from "@gelatonetwork/smartwallet";
const token = "0x036CbD53842c5426634e7929541eC2318f3dCF7e"; // USDC (Base Sepolia)
const results = await smartWalletClient?.execute({
payment: erc20(token),
calls: [
{
to: "0xa8851f5f279eD47a292f09CA2b6D40736a51788E",
data: "0xd09de08a",
value: 0n,
},
],
});
Copy
Ask AI
import { native } from "@gelatonetwork/smartwallet";
const results = await smartWalletClient?.execute({
payment: native(),
calls: [
{
to: "0xa8851f5f279eD47a292f09CA2b6D40736a51788E",
data: "0xd09de08a",
value: 0n,
},
],
});