Documentation Index Fetch the complete documentation index at: https://docs.getpara.com/llms.txt
Use this file to discover all available pages before exploring further.
This walkthrough covers sending basic Ethereum transfers using Para with both Ethers v6 and Viem v2. You’ll learn transaction construction, gas estimation, and signing patterns for each library.
Prerequisites
You need an authenticated Para client and basic knowledge of Ethereum transactions.
Core Dependencies
npm install @getpara/react-sdk @getpara/ethers-v6-integration ethers
npm install @getpara/react-sdk @getpara/viem viem
Provider Setup
RPC Configuration
import { ethers } from "ethers" ;
const RPC_URL = process . env . NEXT_PUBLIC_HOLESKY_RPC_URL ||
"https://ethereum-holesky-rpc.publicnode.com" ;
// Create JSON RPC provider
const provider = new ethers . JsonRpcProvider ( RPC_URL );
import { createPublicClient , http } from "viem" ;
import { holesky } from "viem/chains" ;
const RPC_URL = process . env . NEXT_PUBLIC_HOLESKY_RPC_URL ||
"https://ethereum-holesky-rpc.publicnode.com" ;
// Create public client for read operations
const publicClient = createPublicClient ({
chain: holesky ,
transport: http ( RPC_URL )
});
Para Signer Setup
import { createParaEthersSigner } from "@getpara/ethers-v6-integration" ;
import { useAccount , useClient } from "@getpara/react-sdk" ;
// Get Para client and account
const client = useClient ();
const { data : account } = useAccount ();
// Create Para Ethers signer
let signer : ParaEthersSigner | null = null ;
if ( account ?. isConnected && provider && client ) {
signer = createParaEthersSigner ({ para: client , provider: provider });
}
import { createParaViemAccount , createParaViemClient } from "@getpara/viem" ;
import { useAccount , useClient } from "@getpara/react-sdk" ;
// Get Para client and account
const client = useClient ();
const { data : account } = useAccount ();
// Create Para Viem account and wallet client
let walletClient : any = null ;
if ( account ?. isConnected && client ) {
const viemAccount = createParaViemAccount ( client );
walletClient = createParaViemClient ( client , {
account: viemAccount ,
chain: holesky ,
transport: http ( RPC_URL )
});
}
Transaction Construction
Basic Transaction Parameters
import { parseEther , toBigInt } from "ethers" ;
const constructTransaction = async (
toAddress : string ,
ethAmount : string ,
userAddress : string
) => {
// Get transaction parameters
const nonce = await provider . getTransactionCount ( userAddress );
const feeData = await provider . getFeeData ();
const gasLimit = toBigInt ( 21000 ); // Standard ETH transfer gas
const value = parseEther ( ethAmount );
// Construct transaction object
const transaction = {
to: toAddress ,
value: value ,
nonce: nonce ,
gasLimit: gasLimit ,
maxFeePerGas: feeData . maxFeePerGas ,
maxPriorityFeePerGas: feeData . maxPriorityFeePerGas ,
chainId: 17000 , // Holesky chain ID
};
return transaction ;
};
import { parseEther , parseGwei } from "viem" ;
const constructTransaction = (
toAddress : `0x ${ string } ` ,
ethAmount : string ,
userAddress : `0x ${ string } `
) => {
// Construct transaction object
const transaction = {
account: userAddress ,
to: toAddress ,
value: parseEther ( ethAmount ),
chain: holesky ,
maxFeePerGas: parseGwei ( "100" ), // Custom max fee
maxPriorityFeePerGas: parseGwei ( "3" ) // Custom priority fee
};
return transaction ;
};
Gas Estimation
Estimate Transaction Cost
import { formatEther } from "ethers" ;
const estimateTransactionCost = async (
userAddress : string ,
ethAmount : string
) => {
// Get current balance
const balanceWei = await provider . getBalance ( userAddress );
// Get fee data
const feeData = await provider . getFeeData ();
const gasLimit = toBigInt ( 21000 );
// Calculate max gas cost
const maxGasFee = gasLimit * ( feeData . maxFeePerGas ?? toBigInt ( 0 ));
const amountWei = parseEther ( ethAmount );
const totalCost = amountWei + maxGasFee ;
return {
balance: formatEther ( balanceWei ),
amount: formatEther ( amountWei ),
estimatedGas: formatEther ( maxGasFee ),
totalCost: formatEther ( totalCost ),
hasSufficientBalance: totalCost <= balanceWei
};
};
import { formatEther , parseEther } from "viem" ;
const estimateTransactionCost = async (
userAddress : `0x ${ string } ` ,
toAddress : `0x ${ string } ` ,
ethAmount : string
) => {
// Get current balance
const balance = await publicClient . getBalance ({ address: userAddress });
const amountWei = parseEther ( ethAmount );
// Estimate gas for the specific transaction
const estimatedGas = await publicClient . estimateGas ({
account: userAddress ,
to: toAddress ,
value: amountWei
});
// Get current gas price
const gasPrice = await publicClient . getGasPrice ();
const estimatedGasCost = estimatedGas * gasPrice ;
const totalCost = amountWei + estimatedGasCost ;
return {
balance: formatEther ( balance ),
amount: formatEther ( amountWei ),
estimatedGas: formatEther ( estimatedGasCost ),
totalCost: formatEther ( totalCost ),
hasSufficientBalance: totalCost <= balance
};
};
Transaction Validation
Balance and Parameter Checks
const validateTransaction = async (
userAddress : string ,
toAddress : string ,
ethAmount : string
) => {
// Basic parameter validation
if ( ! ethers . isAddress ( toAddress )) {
throw new Error ( "Invalid recipient address" );
}
if ( parseFloat ( ethAmount ) <= 0 ) {
throw new Error ( "Amount must be greater than 0" );
}
// Check balance and gas
const estimation = await estimateTransactionCost ( userAddress , ethAmount );
if ( ! estimation . hasSufficientBalance ) {
throw new Error (
`Insufficient balance. Need ${ estimation . totalCost } ETH, have ${ estimation . balance } ETH`
);
}
return true ;
};
import { isAddress } from "viem" ;
const validateTransaction = async (
userAddress : `0x ${ string } ` ,
toAddress : string ,
ethAmount : string
) => {
// Basic parameter validation
if ( ! isAddress ( toAddress )) {
throw new Error ( "Invalid recipient address" );
}
if ( parseFloat ( ethAmount ) <= 0 ) {
throw new Error ( "Amount must be greater than 0" );
}
// Check balance and gas
const estimation = await estimateTransactionCost (
userAddress ,
toAddress as `0x ${ string } ` ,
ethAmount
);
if ( ! estimation . hasSufficientBalance ) {
throw new Error (
`Insufficient balance. Need ${ estimation . totalCost } ETH, have ${ estimation . balance } ETH`
);
}
return true ;
};
Sending Transactions
Execute Transfer
const sendEthTransfer = async (
toAddress : string ,
ethAmount : string ,
userAddress : string
) => {
// Validate transaction
await validateTransaction ( userAddress , toAddress , ethAmount );
// Construct transaction
const transaction = await constructTransaction ( toAddress , ethAmount , userAddress );
// Send transaction using Para signer
const txResponse = await signer . sendTransaction ( transaction );
console . log ( "Transaction sent:" , txResponse . hash );
// Wait for confirmation
const receipt = await txResponse . wait ();
console . log ( "Transaction confirmed:" , receipt . hash );
console . log ( "Block number:" , receipt . blockNumber );
console . log ( "Gas used:" , receipt . gasUsed . toString ());
return {
hash: receipt . hash ,
blockNumber: receipt . blockNumber ,
gasUsed: receipt . gasUsed . toString (),
status: receipt . status === 1 ? "success" : "failed"
};
};
const sendEthTransfer = async (
toAddress : `0x ${ string } ` ,
ethAmount : string ,
userAddress : `0x ${ string } `
) => {
// Validate transaction
await validateTransaction ( userAddress , toAddress , ethAmount );
// Send transaction using Para wallet client
const hash = await walletClient . sendTransaction ({
account: userAddress ,
to: toAddress ,
value: parseEther ( ethAmount ),
chain: holesky ,
maxFeePerGas: parseGwei ( "100" ),
maxPriorityFeePerGas: parseGwei ( "3" )
});
console . log ( "Transaction sent:" , hash );
// Wait for confirmation
const receipt = await publicClient . waitForTransactionReceipt ({ hash });
console . log ( "Transaction confirmed:" , receipt . transactionHash );
console . log ( "Block number:" , receipt . blockNumber );
console . log ( "Gas used:" , receipt . gasUsed . toString ());
return {
hash: receipt . transactionHash ,
blockNumber: receipt . blockNumber ,
gasUsed: receipt . gasUsed . toString (),
status: receipt . status === "success" ? "success" : "failed"
};
};
Complete Implementation Example
Full Transfer Function
import { ethers , parseEther , formatEther , toBigInt } from "ethers" ;
import { createParaEthersSigner } from "@getpara/ethers-v6-integration" ;
class EthersTransferService {
private provider : ethers . JsonRpcProvider ;
private signer : ParaEthersSigner ;
constructor ( rpcUrl : string , paraClient : any ) {
this . provider = new ethers . JsonRpcProvider ( rpcUrl );
this . signer = createParaEthersSigner ({ para: paraClient , provider: this . provider });
}
async transfer ( toAddress : string , ethAmount : string , fromAddress : string ) {
try {
// Validate inputs
if ( ! ethers . isAddress ( toAddress )) {
throw new Error ( "Invalid recipient address" );
}
// Check balance
const balance = await this . provider . getBalance ( fromAddress );
const amount = parseEther ( ethAmount );
const feeData = await this . provider . getFeeData ();
const gasLimit = toBigInt ( 21000 );
const maxGasFee = gasLimit * ( feeData . maxFeePerGas ?? toBigInt ( 0 ));
if ( balance < amount + maxGasFee ) {
throw new Error ( "Insufficient balance for transfer and gas" );
}
// Construct and send transaction
const nonce = await this . provider . getTransactionCount ( fromAddress );
const transaction = {
to: toAddress ,
value: amount ,
nonce: nonce ,
gasLimit: gasLimit ,
maxFeePerGas: feeData . maxFeePerGas ,
maxPriorityFeePerGas: feeData . maxPriorityFeePerGas ,
chainId: 17000
};
const txResponse = await this . signer . sendTransaction ( transaction );
const receipt = await txResponse . wait ();
return {
success: true ,
hash: receipt . hash ,
blockNumber: receipt . blockNumber ,
gasUsed: receipt . gasUsed . toString ()
};
} catch ( error ) {
console . error ( "Transfer failed:" , error );
throw error ;
}
}
}
import {
createPublicClient ,
createParaViemClient ,
createParaViemAccount ,
http ,
parseEther ,
formatEther ,
parseGwei ,
isAddress
} from "viem" ;
import { holesky } from "viem/chains" ;
class ViemTransferService {
private publicClient : any ;
private walletClient : any ;
constructor ( rpcUrl : string , paraClient : any ) {
this . publicClient = createPublicClient ({
chain: holesky ,
transport: http ( rpcUrl )
});
const account = createParaViemAccount ( paraClient );
this . walletClient = createParaViemClient ( paraClient , {
account ,
chain: holesky ,
transport: http ( rpcUrl )
});
}
async transfer (
toAddress : `0x ${ string } ` ,
ethAmount : string ,
fromAddress : `0x ${ string } `
) {
try {
// Validate inputs
if ( ! isAddress ( toAddress )) {
throw new Error ( "Invalid recipient address" );
}
// Check balance and estimate gas
const balance = await this . publicClient . getBalance ({ address: fromAddress });
const amount = parseEther ( ethAmount );
const estimatedGas = await this . publicClient . estimateGas ({
account: fromAddress ,
to: toAddress ,
value: amount
});
const gasPrice = await this . publicClient . getGasPrice ();
const estimatedGasCost = estimatedGas * gasPrice ;
if ( balance < amount + estimatedGasCost ) {
throw new Error ( "Insufficient balance for transfer and gas" );
}
// Send transaction
const hash = await this . walletClient . sendTransaction ({
account: fromAddress ,
to: toAddress ,
value: amount ,
chain: holesky ,
maxFeePerGas: parseGwei ( "100" ),
maxPriorityFeePerGas: parseGwei ( "3" )
});
const receipt = await this . publicClient . waitForTransactionReceipt ({ hash });
return {
success: true ,
hash: receipt . transactionHash ,
blockNumber: receipt . blockNumber ,
gasUsed: receipt . gasUsed . toString ()
};
} catch ( error ) {
console . error ( "Transfer failed:" , error );
throw error ;
}
}
}
Key Differences
Library Comparison
Feature Ethers v6 Viem v2 Provider Setup JsonRpcProvidercreatePublicClientWallet Client ParaEthersSignercreateParaViemClientUnit Parsing parseEther()parseEther()Gas Estimation getFeeData()estimateGas() + getGasPrice()Transaction Send signer.sendTransaction()walletClient.sendTransaction()Receipt Waiting txResponse.wait()publicClient.waitForTransactionReceipt()Address Validation ethers.isAddress()isAddress()
Gas Strategy Differences
// Ethers uses provider fee data
const feeData = await provider . getFeeData ();
const transaction = {
maxFeePerGas: feeData . maxFeePerGas ,
maxPriorityFeePerGas: feeData . maxPriorityFeePerGas
};
// Viem allows manual gas price setting
const transaction = {
maxFeePerGas: parseGwei ( "100" ),
maxPriorityFeePerGas: parseGwei ( "3" )
};
Best Practices
Transaction Safety
Always validate recipient addresses before sending
Check balance including gas costs before transaction construction
Use appropriate gas limits (21000 for basic ETH transfers)
Handle network errors gracefully with proper try/catch blocks
Wait for receipt confirmation before considering transaction complete
Gas Optimization
Monitor network conditions for optimal gas pricing
Use EIP-1559 gas parameters for better fee prediction
Estimate gas dynamically rather than using static values
Consider gas limit buffers for complex operations
Error Handling
Common errors to handle:
Invalid recipient addresses
Insufficient balance
Network connectivity issues
Transaction reversion
Nonce management conflicts
Next Steps
ERC-20 Token Transfers Learn to transfer ERC-20 tokens
Transaction Monitoring Track transaction status and confirmations
Solana Transfers Intermediate · 20 min · SOL transfers and Solana programs
EIP-712 Typed Data Signing Intermediate · 20 min · Structured data signing
Aave v3 Integration Advanced · 45 min · Lending, borrowing, and yield strategies