Build on VeChain, Part 2: Fee Delegation & Front-End Integration
1
0

From deployed contract to gasless user experience — a practical walkthrough.
In Part 1 we covered the landscape: where the documentation lives, the core concepts that distinguish VeChainThor from other EVM chains, and the tooling you’ll use daily. We ended with a deployed test contract on the VeChainThor testnet.
This guide picks up exactly where that left off. We’ll add VIP-191 fee delegation so your users never touch gas, then wire the contract to a React front-end using the VeChain DApp Kit — giving you a complete, working dApp architecture that’s ready to iterate on.
What We’re Building
By the end of this guide, you’ll have three connected layers:
- A Solidity smart contract deployed to VeChainThor testnet via Hardhat
- A fee delegation service that sponsors gas for your users’ transactions
- A React front-end that connects wallets, reads contract state, and sends delegated transactions — all without users needing VTHO
This is the same architecture behind VeBetter applications serving millions of users today. The blockchain is there, doing the work — but the user never sees it.
Prerequisites
This guide assumes you’ve completed the setup from Part 1:
- Node.js 18+ installed
- A VeChainThor testnet wallet funded via the faucet
- The @vechain/sdk-hardhat-plugin installed in a Hardhat project
- A basic familiarity with React and Solidity
If you haven’t deployed a contract to testnet yet, start with Part 1 and come back here once you’ve confirmed your deployment on the Explorer.
1. Deploy Your Contract with Hardhat

If you already have a contract deployed from Part 1, skip to Section 2. Otherwise, let’s set up a minimal contract and deployment pipeline.
Project Setup
bash
mkdir vechain-dapp && cd vechain-dapp
npm init -y
npm install — save-dev hardhat @vechain/sdk-hardhat-plugin @vechain/sdk-core @openzeppelin/contracts
npx hardhat init
Select “Create a TypeScript project” when prompted.
Configure Hardhat for VeChainThor
The VeChain Hardhat plugin requires that network names include “vechain” — this signals the plugin to handle VeChainThor-specific transaction processing. Update your hardhat.config.ts:
typescript
import ‘@vechain/sdk-hardhat-plugin’;
import { VET_DERIVATION_PATH } from ‘@vechain/sdk-core’;
import { HardhatUserConfig } from ‘hardhat/config’;
const config: HardhatUserConfig = {
solidity: {
version: ‘0.8.20’,
settings: {
optimizer: { enabled: true, runs: 200 },
evmVersion: ‘shanghai’,
},
},
networks: {
vechain_testnet: {
url: ‘https://testnet.vechain.org’,
accounts: {
mnemonic: ‘your twelve word mnemonic phrase goes here …’,
count: 3,
path: VET_DERIVATION_PATH,
},
debug: false,
gas: ‘auto’,
gasPrice: ‘auto’,
},
},
};
export default config;
Important: Never commit your mnemonic to version control. Use environment variables or a .env file with dotenv in production workflows.
A Minimal Contract
For this walkthrough, we’ll use a simple greeting contract — enough to demonstrate reads, writes, and fee delegation without the complexity of a full token or marketplace.
Create contracts/Greeter.sol:
solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
contract Greeter {
string private greeting;
address public lastSetter;
event GreetingChanged(address indexed setter, string newGreeting);
constructor(string memory _greeting) {
greeting = _greeting;
lastSetter = msg.sender;
}
function greet() public view returns (string memory) {
return greeting;
}
function setGreeting(string memory _greeting) public {
greeting = _greeting;
lastSetter = msg.sender;
emit GreetingChanged(msg.sender, _greeting);
}
}
Deploy Script
Create scripts/deploy.ts:
typescript
import { ethers } from ‘hardhat’;
async function main() {
const Greeter = await ethers.getContractFactory(‘Greeter’);
const greeter = await Greeter.deploy(‘Hello, VeChain!’);
await greeter.waitForDeployment();
const address = await greeter.getAddress();
console.log(`Greeter deployed to: ${address}`);
}
main().catch((error) => {
console.error(error);
process.exitCode = 1;
});
Compile and Deploy
bash
npx hardhat compile
npx hardhat run scripts/deploy.ts — network vechain_testnet
Note the deployed contract address — you’ll need it for both the fee delegation service and the front-end.
Verify the deployment on the Testnet Explorer by searching for your contract address. You should see the contract creation transaction with its bytecode.
2. Implement VIP-191 Fee Delegation

Fee delegation is what transforms a blockchain application into something your users can actually adopt without a crypto education. The architecture is straightforward: your back-end co-signs transactions, the protocol charges your wallet instead of the user’s, and the user experience feels like any other web application.
There are three approaches to implementing delegation, each suited to different scenarios. We’ll cover all three, starting with the simplest.
Approach A: Managed Delegation Service
The fastest path. Services like vechain.energy provide hosted delegation endpoints. You configure a sponsorship, receive a URL, and pass it to your front-end. No back-end code required.
This is ideal for hackathons, prototypes, and early-stage projects where you want fee delegation working in minutes rather than hours. The trade-off is less granular control over which transactions get sponsored.
For testnet development, vechain.energy provides a public sponsor endpoint that covers gas for any transaction:
https://sponsor-testnet.vechain.energy/by/{projectId}
Approach B: Private Key Delegation (SDK-Level)
When you’re building with the VeChain SDK directly — server-side scripts, backend services, automated workflows — you can delegate by providing the gas payer’s private key to the VeChainProvider. This is the pattern you’ll use for programmatic interactions where both the sender and the gas payer are under your control.
typescript
import {
ThorClient,
VeChainProvider,
ProviderInternalBaseWallet,
} from ‘@vechain/sdk-network’;
// Initialise the client
const thorClient = ThorClient.at(‘https://testnet.vechain.org');
// Configure the provider with delegation enabled
const provider = new VeChainProvider(
thorClient,
new ProviderInternalBaseWallet(
[
{
privateKey: Buffer.from(SENDER_PRIVATE_KEY, ‘hex’),
address: SENDER_ADDRESS,
},
],
{
gasPayer: {
gasPayerPrivateKey: GAS_PAYER_PRIVATE_KEY,
},
}
),
true // enable delegation
);
// Get a signer and interact with contracts
const signer = await provider.getSigner(SENDER_ADDRESS);
With this provider, every transaction signed by the sender will automatically be co-signed by the gas payer. The gas payer’s VTHO balance covers the fees.
When to use this: Backend services, automated contract interactions, deployment scripts, and any scenario where you hold both keys. Not suitable for front-ends where the user holds their own wallet — for that, see Approach C.
Approach C: URL-Based Delegation (Production Pattern)
This is the production architecture. You deploy a delegation micro-service that implements the VIP-201 protocol — it receives unsigned transactions from your front-end, validates them against your business rules, co-signs them, and returns the sponsored transaction. The user’s wallet signs on their end, the delegation service signs on yours, and both signatures are submitted together.
The Delegation Service
A minimal Express-based delegation service looks like this:
typescript
import express from ‘express’;
import { Transaction } from ‘@vechain/sdk-core’;
const app = express();
app.use(express.json());
const GAS_PAYER_PRIVATE_KEY = process.env.GAS_PAYER_KEY;
const ALLOWED_CONTRACTS = [
‘0xYourGreeterContractAddress’, // whitelist your contracts
];
const MAX_GAS_PER_TX = 100000;
app.post(‘/delegate’, async (req, res) => {
try {
const { raw } = req.body;
// Decode and validate the transaction
const tx = Transaction.decode(Buffer.from(raw, ‘hex’));
// Validate: only sponsor transactions to your contracts
const allClausesValid = tx.body.clauses.every((clause) =>
ALLOWED_CONTRACTS.includes(clause.to?.toLowerCase())
);
if (!allClausesValid) {
return res.status(403).json({ error: ‘Contract not whitelisted’ });
}
// Validate: cap gas to prevent drain attacks
if (tx.body.gas > MAX_GAS_PER_TX) {
return res.status(403).json({ error: ‘Gas exceeds maximum’ });
}
// Co-sign: compute the delegation hash and sign with the gas payer’s key.
// See the VIP-191 integration tutorial for the full signing flow:
// docs.vechain.org/developer-resources/vip-191-designated-gas-payer
const delegationHash = tx.getDelegationHash(tx.origin);
const gasPayer = /* sign delegationHash with GAS_PAYER_PRIVATE_KEY */;
return res.json({ signature: gasPayer.signature });
} catch (err) {
return res.status(500).json({ error: ‘Delegation failed’ });
}
});
app.listen(3001, () => console.log(‘Delegation service on :3001’));
Critical safeguards (review Section 6 of Part 1 for the full list):
- Whitelist contracts — Only co-sign transactions targeting your own contract addresses
- Cap gas — Set a maximum gas per transaction to prevent abuse
- Rate limit — Throttle requests per wallet address per time window
- Monitor balance — Alert when the gas payer’s VTHO drops below a threshold
- Validate clauses — Inspect function selectors to ensure only expected methods are being called
Connecting the Service to Your Front-End
Once your delegation service is running, you pass its URL to the front-end signing flow. We’ll wire this up in Section 3.
For this walkthrough, we’ll use a managed delegation URL for testnet and note where you’d swap in your own VIP-201 service for production.
3. Build the React Front-End

VeChain provides two front-end integration paths. Choose based on your requirements:
VeChain DApp Kit (@vechain/dapp-kit-react) — Lightweight. Provides wallet connection (VeWorld, Sync2, WalletConnect) and Connex access. You build the UI yourself.
VeChain Kit (@vechain/vechain-kit) — Full-featured. Includes everything in DApp Kit plus social login (email, Google via Privy), smart accounts, pre-built UI components, and built-in fee delegation configuration. Best for consumer-facing applications where you want Web2-style onboarding.
We’ll use VeChain DApp Kit for this walkthrough — it gives you the clearest view of what’s happening at each step. If you’re building for mainstream users and want social login out of the box, the VeChain Kit documentation covers the full setup including fee delegation configuration.
Scaffold the React App
bash
cd .. # back to project root, alongside your Hardhat project
npm create vite@latest greeter-frontend — — template react-ts
cd greeter-frontend
npm install @vechain/dapp-kit-react @vechain/dapp-kit-ui @vechain/sdk-core @vechain/sdk-network
npm install
Configure the DApp Kit Provider
The DAppKitProvider wraps your application and manages the connection to VeChainThor and the user’s wallet. Create src/main.tsx:
tsx
import React from ‘react’;
import ReactDOM from ‘react-dom/client’;
import { DAppKitProvider } from ‘@vechain/dapp-kit-react’;
import App from ‘./App’;
import ‘./index.css’;
ReactDOM.createRoot(document.getElementById(‘root’)!).render(
genesis=”test”
usePersistence={true}
logLevel=”DEBUG”
>
);
The genesis prop tells the DApp Kit which network to connect to (“test” for testnet, “main” for mainnet). usePersistenceremembers the wallet connection across page reloads.
Add Wallet Connection
The DApp Kit provides a WalletButton component that handles the full connection flow — wallet selection, authentication, and address display — in a single line.
Update src/App.tsx:
tsx
import { WalletButton } from ‘@vechain/dapp-kit-react’;
import { Greeter } from ‘./components/Greeter’;
function App() {
return (
VeChain Greeter
);
}
export default App;
The WalletButton supports VeWorld, Sync2, and WalletConnect. Users select their preferred wallet, authenticate, and their address becomes available throughout your application via the useWallet() hook.
Read Contract State
Now let’s read the current greeting from the deployed contract. Create src/components/Greeter.tsx:
tsx
import { useState, useEffect } from ‘react’;
import { useConnex, useWallet } from ‘@vechain/dapp-kit-react’;
// Replace with your deployed contract address
const CONTRACT_ADDRESS = ‘0xYourGreeterContractAddress’;
const GREETER_ABI = {
greet: {
inputs: [],
name: ‘greet’,
outputs: [{ name: ‘’, type: ‘string’ }],
stateMutability: ‘view’,
type: ‘function’,
},
setGreeting: {
inputs: [{ name: ‘_greeting’, type: ‘string’ }],
name: ‘setGreeting’,
outputs: [],
stateMutability: ‘nonpayable’,
type: ‘function’,
},
};
export function Greeter() {
const connex = useConnex();
const { account } = useWallet();
const [greeting, setGreeting] = useState
const [newGreeting, setNewGreeting] = useState
const [txId, setTxId] = useState
const [loading, setLoading] = useState(false);
// Read the current greeting
useEffect(() => {
async function fetchGreeting() {
const result = await connex.thor
.account(CONTRACT_ADDRESS)
.method(GREETER_ABI.greet)
.call();
setGreeting(result.decoded[0]);
}
fetchGreeting();
}, [connex, txId]); // re-fetch after a transaction
// Write a new greeting — with fee delegation
async function handleSetGreeting() {
if (!account || !newGreeting) return;
setLoading(true);
try {
const clause = connex.thor
.account(CONTRACT_ADDRESS)
.method(GREETER_ABI.setGreeting)
.asClause(newGreeting);
const result = await connex.vendor
.sign(‘tx’, [clause])
.delegate(‘https://sponsor-testnet.vechain.energy/by/269’) // replace with your project ID or VIP-201 service URL
.comment(‘Update the greeting’)
.request();
setTxId(result.txid);
setNewGreeting(‘’);
} catch (err) {
console.error(‘Transaction failed:’, err);
} finally {
setLoading(false);
}
}
return (
Current greeting: {greeting || ‘Loading…’}
{account ? (
type=”text”
value={newGreeting}
onChange={(e) => setNewGreeting(e.target.value)}
placeholder=”Enter a new greeting”
style={{ padding: ‘0.5rem’, width: ‘100%’ }}
/>
{txId && (
Transaction:{‘ ‘}
href={`https://explore-testnet.vechain.org/transactions/${txId}`}
target=”_blank”
rel=”noopener noreferrer”
>
{txId.slice(0, 10)}…
)}
) : (
Connect your wallet to update the greeting.
)}
);
}
What’s Happening Here
Let’s walk through the key lines, because this is where VeChainThor’s architecture becomes tangible.
Reading state is done via connex.thor.account(address).method(abi).call(). This is a free, read-only operation — no transaction, no gas, no wallet signature required. The Connex interface queries the node directly and returns the decoded result.
Writing state is a two-step process:
- Build the clause — connex.thor.account(address).method(abi).asClause(args) creates a transaction clause containing the encoded function call. This is VeChain’s equivalent of building a transaction object on Ethereum.
- Sign with delegation — connex.vendor.sign(‘tx’, [clause]).delegate(url).request() prompts the user’s wallet to sign the transaction. The .delegate() call is the critical addition — it tells the Connex layer to route the unsigned transaction through the delegation URL for co-signing before submission. The user sees a signing prompt in their wallet. They approve. The delegation service co-signs. The transaction is submitted. Gas is charged to the delegation service’s wallet, not the user’s.
The user never sees VTHO. They never need to acquire it. They click a button, approve in their wallet, and the state updates. That’s the experience.
Run It
bash
npm run dev
Open the dev server URL, connect your VeWorld or Sync2 wallet, and try updating the greeting. Watch the transaction appear on the Explorer — you’ll see the gas was paid by the delegation service’s address, not your connected wallet.
4. Swap In Your Own Delegation Service

For production, replace the managed testnet URL with your own VIP-201 service. The front-end change is one line:
tsx
// Development (managed service)
.delegate(‘https://sponsor-testnet.vechain.energy/by/269')
// Production (your VIP-201 service)
.delegate(‘https://your-api.example.com/delegate')
If you’re using the VeChain Kit instead of the DApp Kit, fee delegation is configured at the provider level rather than per-transaction:
tsx
delegatorUrl: ‘https://your-api.example.com/delegate',
delegateAllTransactions: true,
}}
// … other config
>
With delegateAllTransactions: true, every transaction from your application is automatically routed through the delegation service. No per-call .delegate() needed.
5. Adding Social Login (Optional)

For consumer-facing applications, requiring users to install a browser wallet is a significant adoption barrier. The VeChain Kit solves this by integrating Privy-powered social login — users authenticate with email, Google, or other OAuth providers and receive a smart account backed by the VeChainThor blockchain.
The setup involves wrapping your application in VeChainKitProvider instead of DAppKitProvider, configuring your Privy App ID and Client ID, and specifying your desired login methods. The VeChain Kit installation guide and the Builders Academy social login tutorial provide the complete walkthrough.
When social login is enabled, fee delegation becomes mandatory — users authenticating via email don’t have VTHO (or a mental model of gas), so your application must sponsor their transactions. This is the same pattern used by VeBetter applications serving millions of users who interact with VeChainThor daily without ever knowing they’re using a blockchain.
6. Production Checklist

Before moving from testnet to mainnet, work through these considerations.
Fee Delegation Service
- Fund your gas payer wallet with sufficient VTHO on mainnet. Monitor the balance and set alerts at a threshold that gives you time to top up.
- Harden your validation logic. The whitelist-and-cap approach from Section 2 is the minimum. In production, also validate function selectors (only allow calls to specific contract methods), check caller authentication (is this user legitimate?), and log every delegation request for auditability.
- Rate limit aggressively. A compromised front-end or a bot can flood your delegation service. Per-address rate limits protect your VTHO balance.
- Deploy behind HTTPS with appropriate CORS headers. Your front-end’s origin should be the only allowed caller.
Front-End
- Switch the genesis from “test” to “main” in your DApp Kit or VeChain Kit provider configuration.
- Update the node URL from https://testnet.vechain.org to https://mainnet.vechain.org.
- Update contract addresses to your mainnet deployments.
- Handle transaction failures gracefully. If the delegation service is down, tell the user — don’t show a cryptic error. If the gas payer’s balance is insufficient, your delegation service should return a clear error that your front-end can translate into a human-readable message.
- SSR compatibility — If you’re using Next.js, dynamically import the DApp Kit provider with ssr: false. VeChain’s wallet libraries require browser APIs that aren’t available during server-side rendering.
Contract
- Audit your contract before mainnet deployment. OpenZeppelin contracts provide a solid foundation, but any custom logic should be reviewed.
- Verify your contract on the VeChain Explorer so that other developers and users can inspect the source code.
- Test multi-clause interactions if your dApp uses them. Confirm that clause-level failures are handled correctly in both your contract logic and your front-end.
7. Essential Resources for This Guide
Documentation
Fee Delegation (Concepts): docs.vechain.org/…/fee-delegation
Fee Delegation (How-To): docs.vechain.org/…/write-data/fee-delegation
VIP-191 Integration Tutorial: docs.vechain.org/…/vip-191-designated-gas-payer
SDK Transactions: docs.vechain.org/…/sdk/transactions
SDK Contracts: docs.vechain.org/…/sdk/contracts
Connex API Specification: docs.vechain.org/…/connex/api-specification
DApp Kit: docs.vechain.org/…/dapp-kit
DApp Kit React Usage: docs.vechain.org/…/dapp-kit-1/react/usage
VeChain Kit (Social Login & Advanced Features)
VeChain Kit Docs: docs.vechainkit.vechain.org
Installation: docs.vechainkit.vechain.org/quickstart/installation
Fee Delegation Setup: docs.vechainkit.vechain.org/fee-delegation/fee-delegation-setup
Social Login (Builders Academy): academy.vechain.org/…/add-social-login-to-your-front-end
Specifications & Examples
VIP-191 Specification: github.com/vechain/VIPs/…/VIP-191.md
VIP-201 Delegation Protocol: github.com/vechain/VIPs/…/VIP-201.md
Hardhat Plugin Sample: github.com/vechain/hardhat-plugin-sample-ethers
VeWorld Sample dApp: github.com/vechain/veworld-dapp
Buy Me a Coffee Tutorial: docs.vechain.org/…/buy-me-a-coffee
vechain.energy Delegation Guide: learn.vechain.energy/…/FeeDelegation/Implementation
Build with Hardhat: docs.vechain.org/…/build-with-hardhat
Testnet Explorer: explore-testnet.vechain.org
Testnet Faucet: faucet.vecha.in
What Comes Next
You now have a complete, working dApp on VeChainThor — smart contract, fee delegation, and a connected front-end. From here, the paths branch based on what you’re building:
If you’re building for VeBetter, explore the VeBetter documentation for sustainability proof patterns, B3TR token integration, and the application submission process. The same fee delegation and front-end patterns you’ve implemented here form the foundation of every VeBetter application.
If you’re building for enterprise, multi-clause transactions become your primary tool for batch operations and atomic workflows. Combine them with fee delegation to create applications where your business clients interact with the blockchain without managing wallets or tokens.
If you’re building for DeFi or token projects, the SDK’s contract factory patterns (documented in the SDK Contracts reference) support delegated deployment and interaction with complex contract systems.
Across all these paths, the principle is the same: VeChainThor’s architecture is designed to make the blockchain invisible to the end user. Fee delegation is the mechanism. Your application is the interface. The utility speaks for itself.
This is Part 2 of the Build on VeChain series. Part 1: Developer Resources, Patterns & Tools covers orientation, core concepts, tooling, and best practices, linked here.
Build on VeChain, Part 2: Fee Delegation & Front-End Integration was originally published in VeChain on Medium, where people are continuing the conversation by highlighting and responding to this story.
1
0
Securely connect the portfolio you’re using to start.