Transaction Handling
Managing blockchain transactions and confirmations
Transaction management forms core blockchain application interaction. Selendra fast finality enables quick confirmations. Low costs (~$0.00025) simplify fee management.
Transaction Lifecycle
| Stage | Time on Selendra | User Visibility |
|---|---|---|
| Created | Instant | Show confirmation dialog |
| Signed | 1-5 seconds | Waiting for signature |
| Broadcast | <1 second | Transaction submitted |
| Pending | 1-2 seconds | Processing |
| Included | ~1 second | Transaction confirmed |
| Finalized | 1-2 seconds | Fully confirmed |
Basic Transaction Flow
Send Transaction
import { Web3 } from "web3";
const web3 = new Web3(window.ethereum);
const tx = {
from: userAddress,
to: recipientAddress,
value: web3.utils.toWei("1", "ether"), // 1 SEL
gas: 21000,
gasPrice: await web3.eth.getGasPrice(),
};
try {
const txHash = await web3.eth.sendTransaction(tx);
const receipt = await web3.eth.getTransactionReceipt(txHash);
if (receipt.status) {
console.log("Transaction successful");
} else {
console.log("Transaction failed");
}
} catch (error) {
console.error("Transaction error:", error);
}
With Status Updates
async function sendWithUpdates(tx, onUpdate) {
try {
onUpdate({ status: "signing", message: "Awaiting signature..." });
const txHash = await web3.eth.sendTransaction(tx);
onUpdate({
status: "pending",
message: "Transaction submitted",
hash: txHash,
});
// Poll for receipt
let receipt = null;
while (!receipt) {
await new Promise((resolve) => setTimeout(resolve, 1000));
receipt = await web3.eth.getTransactionReceipt(txHash);
}
onUpdate({
status: receipt.status ? "confirmed" : "failed",
message: receipt.status
? "Transaction confirmed"
: "Transaction reverted",
receipt,
});
return receipt;
} catch (error) {
onUpdate({ status: "error", message: error.message });
throw error;
}
}
Gas Management
Estimation with Buffer
// Estimate gas
const gasEstimate = await web3.eth.estimateGas(tx);
// Add 10% buffer
const gasLimit = Math.floor(gasEstimate * 1.1);
const txWithGas = {
...tx,
gas: gasLimit,
};
Contract Gas Estimation
const contract = new web3.eth.Contract(abi, contractAddress);
const gasEstimate = await contract.methods
.transfer(recipient, amount)
.estimateGas({ from: userAddress });
const tx = contract.methods.transfer(recipient, amount).send({
from: userAddress,
gas: Math.floor(gasEstimate * 1.1),
});
Cost Calculation
async function estimateCost(gasLimit) {
const gasPrice = await web3.eth.getGasPrice();
const costWei = BigInt(gasLimit) * gasPrice;
const costSel = web3.utils.fromWei(costWei.toString(), "ether");
return {
wei: costWei,
sel: costSel,
usd: parseFloat(costSel) * selPriceInUSD,
};
}
Confirmation Waiting
Wait for Confirmation
async function waitForConfirmation(txHash, confirmations = 1) {
let receipt = null;
// Wait for mining
while (!receipt) {
await new Promise((resolve) => setTimeout(resolve, 1000));
receipt = await web3.eth.getTransactionReceipt(txHash);
}
if (confirmations === 1) {
return receipt;
}
// Wait for additional confirmations
const targetBlock = receipt.blockNumber + confirmations - 1;
let currentBlock = await web3.eth.getBlockNumber();
while (currentBlock < targetBlock) {
await new Promise((resolve) => setTimeout(resolve, 1000));
currentBlock = await web3.eth.getBlockNumber();
}
return receipt;
}
With Progress Callback
async function waitWithProgress(txHash, confirmations, onProgress) {
const receipt = await waitForReceipt(txHash);
const targetBlock = receipt.blockNumber + confirmations - 1;
onProgress({
current: 0,
required: confirmations,
receipt,
});
const interval = setInterval(async () => {
const currentBlock = await web3.eth.getBlockNumber();
const confirmed = Math.min(
currentBlock - receipt.blockNumber + 1,
confirmations
);
onProgress({
current: confirmed,
required: confirmations,
receipt,
});
if (confirmed >= confirmations) {
clearInterval(interval);
}
}, 1000);
}
Error Handling
Common Errors
async function handleTransaction(tx) {
try {
return await web3.eth.sendTransaction(tx);
} catch (error) {
// User rejected
if (error.code === 4001) {
throw new Error("Transaction rejected by user");
}
// Insufficient funds
if (error.message.includes("insufficient funds")) {
throw new Error("Insufficient SEL balance");
}
// Gas estimation failed
if (error.message.includes("gas required exceeds allowance")) {
throw new Error("Transaction would fail - check parameters");
}
// Network error
if (error.message.includes("network")) {
throw new Error("Network connection failed - please retry");
}
throw new Error(`Transaction failed: ${error.message}`);
}
}
Retry Logic
Automatic Retry
async function sendWithRetry(tx, maxAttempts = 3) {
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
try {
return await web3.eth.sendTransaction(tx);
} catch (error) {
// Don't retry user rejection
if (error.code === 4001) throw error;
// Don't retry if would fail anyway
if (error.message.includes("gas required exceeds")) throw error;
// Last attempt
if (attempt === maxAttempts) throw error;
// Exponential backoff
const delay = Math.pow(2, attempt) * 1000;
await new Promise((resolve) => setTimeout(resolve, delay));
console.log(`Retry attempt ${attempt}/${maxAttempts}`);
}
}
}
Batch Transactions
Sequential Execution
async function executeBatch(transactions) {
const results = [];
for (const tx of transactions) {
try {
const receipt = await web3.eth.sendTransaction(tx);
await waitForConfirmation(receipt.transactionHash);
results.push({ success: true, receipt });
} catch (error) {
results.push({ success: false, error: error.message });
break; // Stop on first failure
}
}
return results;
}
Parallel Execution
async function executeParallel(transactions) {
const promises = transactions.map((tx) =>
web3.eth
.sendTransaction(tx)
.then((hash) => ({ success: true, hash }))
.catch((error) => ({ success: false, error: error.message }))
);
return await Promise.all(promises);
}
Gas Sponsorship
Backend pays fees for users. Enables free experience.
Frontend Request
async function sponsoredTransaction(action, params) {
const response = await fetch("/api/sponsor-tx", {
method: "POST",
headers: {
Authorization: `Bearer ${authToken}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
userAddress,
action,
params,
}),
});
const { transactionHash } = await response.json();
const receipt = await waitForConfirmation(transactionHash);
return receipt;
}
Backend Implementation
async function sponsorTransaction(req, res) {
const { userAddress, action, params } = req.body;
await checkRateLimit(userAddress);
const relayer = new Web3.eth.accounts.Wallet(process.env.RELAYER_PRIVATE_KEY);
const tx = await contract.methods[action](...params).send({
from: relayer[0].address,
});
await logSponsoredTransaction({
userAddress,
txHash: tx.transactionHash,
cost: tx.gasUsed * tx.effectiveGasPrice,
});
res.json({ transactionHash: tx.transactionHash });
}
Transaction Queue
class TransactionQueue {
constructor() {
this.queue = [];
this.processing = false;
}
async add(tx) {
return new Promise((resolve, reject) => {
this.queue.push({ tx, resolve, reject });
this.process();
});
}
async process() {
if (this.processing || this.queue.length === 0) return;
this.processing = true;
const { tx, resolve, reject } = this.queue.shift();
try {
const receipt = await web3.eth.sendTransaction(tx);
await waitForConfirmation(receipt.transactionHash);
resolve(receipt);
} catch (error) {
reject(error);
}
this.processing = false;
this.process(); // Process next
}
}
UI Component
function TransactionStatus({ status, hash }) {
const statusConfig = {
signing: {
icon: "✍️",
color: "blue",
message: "Awaiting signature...",
},
pending: {
icon: "⏳",
color: "yellow",
message: "Processing transaction...",
},
confirmed: {
icon: "✅",
color: "green",
message: "Transaction confirmed!",
},
failed: {
icon: "❌",
color: "red",
message: "Transaction failed",
},
};
const config = statusConfig[status];
return (
<div className={`status-${config.color}`}>
<span>{config.icon}</span>
<p>{config.message}</p>
{hash && (
<a href={`https://explorer.selendra.org/tx/${hash}`}>
View on Explorer
</a>
)}
</div>
);
}
Related Documentation
Event Monitoring | Wallet Integration | EVM Smart Contracts
Community
Telegram: t.me/selendranetwork | X: @selendranetwork | GitHub: github.com/selendra/selendra
Contribute
Found an issue or want to contribute?
Help us improve this documentation by editing this page on GitHub.
