This version of the kit deprecated the use of connex in favour of the SDK.
It may have some breaking changes based on how you integrated the kit inside your app. Please read more in details what changed from V1 and how to migrate to version 2 in the following section.
Installation
To add the package to your React app, you need to install the kit with all its peer dependencies as following:
Only supported on React and Next.js
React query, chakra and dapp-kit are peer dependencies.
Migrate Social Login Users
If you have an app with some custom login (eg: login with email, login with google, etc.) and you want to use this kit and migrate your users you will need to:
Create your Privy app
Manually add your users or use Privy's APIs
Please refer to this documentation to import users through Privy APIs:
3) Toggle the "Pregenerate EVM Wallet" so an embedded wallet will be created and associated to the user.
4) If your users where logging in with social you may need to ask them to associate that social login method after they login first time with their email.
Connection Types
VeChain Kit supports 3 types of connections:
1) Privy
This connection type is often used by organizations like VeBetterDAO, Cleanify, and Greencart. When connected, users can back up their embedded wallets, sign transactions without confirmation prompts, and add login methods. By connecting with Privy, developers use their personal APP_ID and CLIENT_ID to create their own app on Privy.
Pros of self hosting Privy:
No UI confirmations on users transactions
Allow your users to backup their keys and update security settings directly in your app
Targetted social login methods
Cons:
Price
Responsibilities to correctly secure your Privy account, since it contains access to user's wallet settings
Your users will need to login into other apps through ecosystem mode
2) Privy Cross App
When users integrate VeChain-kit using "Login with VeChain" and "Ecosystem" logins (eg: Mugshot and Greencart), this will be the default connection type. It is easily recognizable because login and wallet activities will open a secured popup window where the owner of the wallet will approve the actions.
With this type of connection, you can have social login in your app without actually paying Privy.
3) Self-custody wallets
This connection type allows login with self custody wallets, and is using the dapp-kit package under the hood.
The available wallets are: VeWorld mobile, VeWorld extension, Sync2, and Wallet Connect for VeWorld mobile.
Other wallets are available when login in with VeChain, such as Metamask, Rabby, Phantom, Coinbase Wallet, and Ranibow.
That will use Privy under the hood though, which means the connection type will be "privy-cross-app".
If you want to use vechain-kit but do not care about social login then you can skip the first login modal and directly show the "Connect Wallet" modal like this:
When your app is opened inside VeWorld mobile wallet, VeWorld is always enforced as a login choice.
Intro
Component Overview
The hook offers a versatile suite of components for seamless integration into web applications. Below are some of the key components:
WalletButton: Dynamically triggers either a login or account modal based on the user's connection status.
ProfileCard: Displays essential details, including an avatar, description, and social links linked to a user's address.
Transaction Visibility:
TransactionModal
TransactionToast
AccountModal: Allows users to explore specific sections as needed.
DAppKitWalletButton: Provides a focused interface for selecting wallet connection options.
These components collectively enhance user interaction and streamline.
Head over the to see all the components in action.
yarn add @tanstack/react-query@"^5.64.2" @chakra-ui/react@"^2.8.2" @vechain/dapp-kit-react@"2.1.0-rc.1" @vechain/vechain-kit
// or
npm i @tanstack/react-query@"^5.64.2" @chakra-ui/react@"^2.8.2" @vechain/dapp-kit-react@"2.1.0-rc.1" @vechain/vechain-kit
The kit provides hooks for developers to interact with smart contracts like VeBetterDAO, VePassport, veDelegate, and price oracles.
The hooks in this package provide a standardized way to interact with various blockchain and web services. All hooks are built using TanStack Query (formerly React Query), which provides powerful data-fetching and caching capabilities.
Common Features
Every hook in the @api directory returns a consistent interface that includes:
data: The fetched data
isLoading: Boolean indicating if the request is in progress
isError: Boolean indicating if the request failed
error: Error object if the request failed
refetch: Function to manually trigger a new fetch
isRefetching: Boolean indicating if a refetch is in progress
Additionally, these hooks integrate with TanStack Query's global features:
Automatic background refetching
Cache invalidation
Optimistic updates
Infinite queries (for pagination)
Parallel queries
Query retrying
Query polling
Query Invalidation
All hooks use consistent query key patterns, making it easy to invalidate related queries. For example:
Your app requires only crypto wallet authentication
You want to bypass social login options
You need a streamlined wallet-focused experience
Special Considerations
VeWorld Mobile Integration
When your application is accessed through the VeWorld mobile wallet browser, VeWorld is automatically enforced as the primary authentication method. This ensures seamless integration with the mobile wallet experience.
Authentication State Management
All authentication methods automatically sync with the VeChain Kit provider, ensuring consistent state across your application. You can access the current authentication state using:
import { useWallet } from '@vechain/vechain-kit';
function Component() {
const { connection, address, source } = useWallet();
if (connection.isConnected) {
console.log('Connected wallet:', address);
console.log('Connection source:', source);
}
}
Error Handling
Always implement proper error handling for authentication flows:
This provides a high-level summary of all breaking changes in VeChain Kit v2. For detailed information on each topic, please refer to the specific guides linked below.
Major Breaking Changes
1. Connex Removal
useConnex is completely removed and replaced with useThor
This affects all blockchain interactions throughout your application
4) Use the useSendTransaction() hook from @vechain/vechain-kit to send your transactions to the network.
Read how to use the hook here.
6) If you use useConnex() by importing from dapp-kit, import it from vechain-kit.
7) If you are using certificate signing to authenticate your users with your backend to issue a jwt/session token you will need to switch to use signTypedData instead, since Privy and Smart Accounts does not support the VeChain certificate authentication signature type. Read how to do here.
8) Double-check your yarn.lock to see that all the @vechain/dapp-kit-react@vechain/dapp-kit and @vechain/dapp-kit-uiinstalls are using the the 1.5.0 version.
Remove all installations of @vechain/dapp-kit @vechain/dapp-kit-ui and @vechain/dapp-kit-react from your app. If you need some specific hooks or methods from dapp-kit you can import them directly from the @vechain/vechain-kit.
Setup Privy (optional)
If you have your own Privy app, you can pass an additional prop with your settings.
Allow your users to backup their keys and update security settings directly in your app
Targetted social login methods
Cons:
Price
Responsibilities to correctly secure your Privy account, since it contains access to user's wallet settings
Your users will need to login into other apps through ecosystem mode
Peer Dependencies
Ensure that your project's peer dependencies align with VeChain Kit's specifications. Mismatched versions can cause installation failures, runtime errors, or unexpected behaviour.
Common Issues
Package installation fails with peer dependency warnings
Runtime errors about missing dependencies
Version conflicts between VeChain Kit and your existing packages
VeChain Kit includes built-in fee delegation handling. If you already have fee delegation in your app, you must remove it to avoid conflicts.
Problem
Using both your own fee delegation and VeChain Kit's delegation causes:
Transaction failures
Double delegation attempts
Multiple signatures on transactions
Incorrect transaction format
Solution
Remove Existing Fee Delegation
Remove all custom fee delegation logic from your application:
Remove fee delegation middleware
Remove delegation signing logic
Remove any custom delegation providers
If you do not remove your own delegation then transactions could fail because your transaction will delegate 2 times, will have 2 signatures and an incorrect format.
Configure VeChain Kit Fee Delegation
Simply provide the delegation URL in the VeChainKitProvider:
VeChain Kit is a comprehensive library designed to make building VeChain applications fast and straightforward. It offers:
Seamless Wallet Integration: Support for VeWorld, Sync2, WalletConnect, VeChain Embedded Wallet, and social logins (powered by Privy).
Unified Ecosystem Accounts: Leverage Privy’s Ecosystem feature to give users a single wallet across multiple dApps, providing a consistent identity within the VeChain network.
Developer-Friendly Hooks: Easy-to-use React Hooks that let you read and write data on the VeChainThor blockchain.
Pre-Built UI Components: Ready-to-use components (e.g., TransactionModal) to simplify wallet operations and enhance your users’ experience.
Multi-Language Support: Built-in i18n for a global audience.
Token Operations: Send tokens, check balances, manage VET domains, and more—all in one place.
Easier Integration
We provide a standardized “kit” that quickly integrates social logins and VeChain Smart Accounts—without the hassle of manual contract deployment or configuration.
Only fetches when both Thor connection and network type are available
Example with Multiple Tokens
Smart Accounts v1 to v3
Upgrading to unlocks multi-clause support, enhanced security, and other essential features for transactions on VeChain. This feature is available in VeChain Kit from .
When integrating social login in your app, users might have a version 1 smart account. This version doesn’t support multiclause transactions, potentially causing issues within your app.
To address this scenario, consider wrapping your onClick handler or using another suitable method to prompt users to upgrade their smart accounts to version 3.
The kit makes available both the hooks to know if the upgrade is required and the component for upgrading:
useUpgradeRequired(smartAccountAddress, ownerAddress, targetVersion: 3) is the hook that will let you know if the user is on v1 and needs to upgrade to v3
useUpgradeSmartAccountModal() is the hook that will allow you to open the upgrade modal, that the user will use to upgrade.
You can also handle this with your own UI by using the useUpgradeSmartAccount(smartAccountAddress, targetVersion: 3) hook.
View other useful hooks .
Example usage
You can customize the color button and size of the imported modal from the kit:
Example demo
With UI from the Kit
With custom UI
Profile Card
ProfileCard Component
The ProfileCard component provides a comprehensive display of user profile information, including avatar, domain, social links, and customization options.
Features
Customizable header with generated background
Avatar display with domain integration
Social links integration (Email, Website, Twitter/X)
Address display with copy functionality
Edit and logout options for connected accounts
Dark/Light mode support
Props
Usage example
Utils
Utility Hooks
The hooks provide utility functions for interacting with the VeChain network and tokens:
Network Utility Hooks
useGetChainId: Retrieves the chain ID from the genesis block of the connected network
useGetNodeUrl: Returns the node URL being used, either custom or default for the network
Token Utility Hooks
useGetCustomTokenBalances: Fetches balances for multiple custom tokens for a given address, returning original, scaled, and formatted values
useGetCustomTokenInfo: Retrieves token information (name, symbol, decimals) for a custom token address.
Legal Documents Hook
useLegalDocuments: Retrieves the user's agreement status for required and optional legal documents, including terms of service, privacy policy, and cookie policy.
Usage Example
Dev support
Are you having issues using the kit? Join our discord server to receive support from our devs or open an issue on our Github!
Check our .
Contact us on Discord:
Open an issue on Github:
Use yarn or npm to build the packages. pnpm is known to have some issues we are trying to solve.
Quick Links
If your issue is not addressed here, please reach out to us:
Discord:
GitHub Issues:
Common Issue Categories
Problems when upgrading from DApp Kit or managing dependencies
Peer Dependencies
From DApp Kit
CSS conflicts and theming problems
Chakra UI Conflicts
CSS Framework Conflicts
Runtime and functionality problems
Fee Delegation
Privy Popup Blocking
Before You Start
Check that you're using the latest version of VeChain Kit
Ensure all peer dependencies are correctly installed
If you're coming from DApp Kit or SDK, you may have version conflicts from different versions installed across your project.
VeChain Kit includes DApp Kit functionality, but still requires @vechain/dapp-kit-react as a peer dependency for compatibility.
Problem
Multiple versions of DApp Kit packages can cause:
Version conflicts between different parts of your project
Runtime errors from competing implementations
Unexpected behaviour from mixed package versions
Solution
Completely Remove DApp Kit Packages
Remove all existing DApp Kit packages from your project:
Completely Remove DApp Kit Packages
VeChain Kit provides similar functionality with updated component names:
Clean Installation
After removing old packages:
Component Mapping
DApp Kit
VeChain Kit
Verification
Ensure clean migration:
No dapp-kit packages in package.json
All imports updated to use @vechain/vechain-kit
Component names updated to VeChain Kit equivalents
Chakra Conflicts
VeChain Kit components are wrapped in their own Chakra Provider (v2), ensuring consistent styling throughout the modals. This can cause conflicts if your app also uses Chakra UI.
Problem
VeChain Kit components not rendering correctly
Theme conflicts when your app uses Chakra UI
Missing styles or unexpected appearance
Solution
Install Chakra UI
Even if you don't use Chakra in your app, it's required as a peer dependency:
Setup ChakraProvider
Wrap your app with ChakraProvider and include ColorModeScript:
Key Requirements
The essential setup:
Framework Flexibility
You can keep using whatever frontend framework you prefer. The important part is defining the ChakraProvider wrapper for VeChain Kit components to function properly.
Common Issue
One common issue is missing the <ColorModeScript /> component, which can cause styling inconsistencies. Always include it within your ChakraProvider.
import { useGetTokenUsdPrice } from '@vechain/vechain-kit';
function TokenPrice() {
const { data: vetPrice, isLoading, error } = useGetTokenUsdPrice('VET');
if (isLoading) return <div>Loading...</div>;
if (error) return <div>Error fetching price</div>;
return <div>VET Price: ${vetPrice}</div>;
}
One common issue, solvable by installing Chakra and defining <ColorModeScript />
vetDomains
VetDomains Hooks
The hooks provide tools for interacting with VET domains and their functionality:
Domain Resolution Hooks
useVechainDomain: Resolves VET domains to addresses and vice versa, returning domain info and validation status
useIsDomainProtected: Checks if a domain is protected from claiming
useGetDomainsOfAddress: Gets all domains owned by an address, with optional parent domain filtering
Domain Record Management Hooks
useGetTextRecords: Gets all text records for a domain
useGetAvatar: Gets the avatar URL for a domain. This hook will return directly the URL of the image, removing the need for developers to convert the URI to URL manually. The response can be null if the domain name does not exist or if there is no avatar attached to this domain.
useGetAvatarOfAddress : This hook will check if the address has any primary domain name set, if yes it will fetch and return the avatar URL (again, no need to manually convert URI to URL, the hook does it). If there is no domain name attached to it or if there is no avatar attached to the domain, it will return the Picasso image.
useGetResolverAddress: Gets the resolver contract address for a domain
useUpdateTextRecord: Updates text records for a domain with transaction handling
Subdomain Management Hooks
useClaimVeWorldSubdomain: Claims a VeWorld subdomain with transaction handling and success/error callbacks
Usage Example
/**
* Domain Management Hooks
* The hooks provide tools for interacting with VET domains and their functionality
*/
// Domain Resolution Hooks
- `useVechainDomain(addressOrDomain: string)`:
Returns { address?: string; domain?: string; isValidAddressOrDomain: boolean }
// Resolves VET domains to addresses and vice versa
- `useIsDomainProtected(domain: string)`:
Returns boolean
// Checks if a domain is protected from claiming
- `useGetDomainsOfAddress(address: string, parentDomain?: string)`:
Returns { domains: Array<{ name: string }> }
// Gets all domains owned by an address
// Domain Record Management Hooks
- `useGetTextRecords(domain: string)`:
Returns TextRecords object
// Gets all text records for a domain
- `useGetAvatar(domain: string)`:
Returns string | null
// Gets the avatar URL for a domain
- `useGetResolverAddress(domain: string)`:
Returns string
// Gets the resolver contract address for a domain
- `useUpdateTextRecord({
onSuccess?: () => void,
onError?: (error: Error) => void,
signerAccountAddress?: string,
resolverAddress: string
})`:
Returns { sendTransaction, isTransactionPending, error }
// Updates text records for a domain
// Subdomain Management Hooks
- `useClaimVeWorldSubdomain({
subdomain: string,
domain: string,
onSuccess?: () => void,
onError?: (error: Error) => void,
alreadyOwned?: boolean
})`:
Returns { sendTransaction, isTransactionPending, error }
// Claims a VeWorld subdomain
// Example Usage
const ExampleComponent = () => {
const address = "0x...";
const domain = "example.vet";
// Resolve domain or address
const { data: domainInfo } = useVechainDomain(address);
// Check domain protection
const { data: isProtected } = useIsDomainProtected(domain);
// Get owned domains
const { data: ownedDomains } = useGetDomainsOfAddress(address);
// Get domain records
const { data: textRecords } = useGetTextRecords(domain);
const { data: avatar } = useGetAvatar(domain);
const { data: avatarOfAddress } = useGetAvatarOfAddress(address);
// Update domain records
const { sendTransaction: updateRecord } = useUpdateTextRecord({
onSuccess: () => console.log("Record updated"),
resolverAddress: "0x..."
});
// Claim subdomain
const { sendTransaction: claimSubdomain } = useClaimVeWorldSubdomain({
subdomain: "mysub",
domain: "veworld.vet",
onSuccess: () => console.log("Subdomain claimed")
});
console.log(
'Domain Info:', domainInfo,
'Is Protected:', isProtected,
'Owned Domains:', ownedDomains,
'Text Records:', textRecords,
'Avatar:', avatar
);
return (
// Your component JSX
);
};
/*
Requirements:
- All hooks require a valid thor connection
- Appropriate network configuration must be set
- Valid input parameters must be provided
- Most hooks are part of the @tanstack/react-query ecosystem
Additional Notes:
- Domain resolution works for both .vet domains and addresses
- Text records can include various fields like email, description, url, etc.
- Subdomain claiming is specific to veworld.vet domains
- Protection status affects whether a domain can be claimed or transferred
*/
Privy Popup Blocking
Browser popup blocking can affect users using social login (Privy) when operations delay the signing popup.
Problem
When using Privy for social login (cross-app connection), browsers may block the confirmation popup if there's a delay between user
action and popup trigger.
What Causes This
Fetching data after button click
API calls before signing
Any async operations between click and popup
Solution
Pre-fetch Data Before Transaction
Ensure all required data is loaded before the user clicks the button:
// ✅ Good: Pre-fetch data
const { data } = useQuery(['someData'], fetchSomeData);
const sendTx = () => sendTransaction(data);
// ❌ Bad: Fetching data during the transaction
const sendTx = async () => {
const data = await fetchSomeData(); // This delay causes popup blocking
return sendTransaction(data);
};
Best Practices
Load Data Early
// Load data when component mounts or when form changes
const { data: gasPrice } = useQuery(['gasPrice'], fetchGasPrice, {
staleTime: 30000 // Cache for 30 seconds
});
// Transaction handler is instant
const handleTransaction = () => {
sendTransaction({
gasPrice,
// ... other pre-loaded data
});
};
With every name come a set of records. These records are key value pairs that can be used to store information about the profile. Think of this as a user's digital backpack. Utalized for storage of preferences, public details, and more.
Types of Records
Here are some of the most commonly used records:
Name
Usage
Reference
Example
display
Preferred capitalization
Luc.eth
avatar
Avatar or logo (see )
ipfs://dQw4w9WgXcQ
description
Description of the name
DevRel @ ENS Labs
keywords
List of comma-separated keywords
person, ens
email
Email address
mail
Physical mailing address
V3X HQ
notice
Notice regarding this name
This is a notice
location
Generic location (e.g. "Toronto, Canada")
Breda, NL
phone
Phone number as an E.164 string
+1 234 567 890
url
Website URL
header
Image URL to be used as a header/banner
ipfs://dQw4w9WgXcQ
Other Records
Currently there are a few records that have been standardised. However you are welcome to store any key value pair you desire. We generally recommend to stick to a pattern, or prefix things with your app or protocol (eg. com.discord, or org.reddit), as such to avoid collisions.
Header/Banner Record
One of the newer standardised records is the "header" record. This header record, similar to the avatar record, accepts any IPFS, Arweave, EIP155, or regular URL to an image resource. The image is then displayed as a banner on the profile page and tends to be in a 1:3 aspect ratio.
Setting Records
When records are loaded they are loaded from the resolver responsible for the name. As resolvers are user controlled, we cannot guarantee a write function is available. This makes it a more in-depth process to update a users records.
This is protocol build by ENS domains and supported by veDelegate. Follow ENS documentation for more information.
Keep reading how to integrate with the VeChain Kit
Transaction Modal
Use our pre built TransactionModal or TransactionToast components to show your users the progress and outcome of the transaction, or build your own UX and UI.
Usage example
'use client';
import {
useWallet,
useSendTransaction,
useTransactionModal,
TransactionModal,
getConfig
} from '@vechain/vechain-kit';
import { IB3TR__factory } from '@vechain/vechain-kit/contracts';
import { humanAddress } from '@vechain/vechain-kit/utils';
import { useMemo, useCallback } from 'react';
export function TransactionExamples() {
const { account } = useWallet();
const b3trMainnetAddress = getConfig("main").b3trContractAddress;
const clauses = useMemo(() => {
const B3TRInterface = IB3TR__factory.createInterface();
const clausesArray: any[] = [];
clausesArray.push({
to: b3trMainnetAddress,
value: '0x0',
data: B3TRInterface.encodeFunctionData('transfer', [
"0x0, // receiver address
'0', // 0 B3TR (in wei)
]),
comment: `This is a dummy transaction to test the transaction modal. Confirm to transfer ${0} B3TR to ${humanAddress("Ox0")}`,
abi: B3TRInterface.getFunction('transfer'),
});
return clausesArray;
}, [connectedWallet?.address]);
const {
sendTransaction,
status,
txReceipt,
resetStatus,
isTransactionPending,
error,
} = useSendTransaction({
signerAccountAddress: account?.address ?? '',
});
const {
open: openTransactionModal,
close: closeTransactionModal,
isOpen: isTransactionModalOpen,
} = useTransactionModal();
// This is the function triggering the transaction and opening the modal
const handleTransaction = useCallback(async () => {
openTransactionModal();
await sendTransaction(clauses);
}, [sendTransaction, clauses, openTransactionModal]);
const handleTryAgain = useCallback(async () => {
resetStatus();
await sendTransaction(clauses);
}, [sendTransaction, clauses, resetStatus]);
return (
<>
<button
onClick={handleTransactionWithModal}
isLoading={isTransactionPending}
isDisabled={isTransactionPending}
>
Send B3TR
</button>
<TransactionModal
isOpen={isTransactionModalOpen}
onClose={closeTransactionModal}
status={status}
txReceipt={txReceipt}
txError={error}
onTryAgain={handleTryAgain}
uiConfig={{
title: 'Test Transaction',
description: `This is a dummy transaction to test the transaction modal. Confirm to transfer ${0} B3TR to ${
account?.address
}`,
showShareOnSocials: true,
showExplorerButton: true,
isClosable: true,
}}
/>
</>
);
}
Setup Legal Documents (optional)
To prompt users to review and accept your policies, like Terms and Conditions, Privacy Policy, or Cookie Policy, VeChainKit offers a simple plug-and-play solution.
You can avoid building your own if you haven't already.
By enabling also the tracking consent, you will allow VeChainKit to prompt your users to collect data to improve the kit.
When the legalDocuments option is configured, the users will see:
Left: A modal prompt when connecting their wallet, requiring them to review and accept required and optional legal documents.
Right: A summary view under Settings > General > Terms and Policies, showing which policies they’ve accepted and when.
Legal Docs Modal
Legal Docs Summary View
Important
Legal document agreements are tied to the wallet address, document type, document version, and the url.
If the version of any document is updated, users will be prompted to accept it again.
Agreements are stored in the browser’s local storage, meaning acceptance is per browser and device.
As a result, users may be prompted again if they switch browsers, devices, or clear their local storage even if they've previously agreed on another setup.
import { VechainKitProvider } from '@vechain/vechain-kit';
export default function App({ Component, pageProps }: AppProps) {
return (
<VechainKitProvider
legalDocuments={{
allowAnalytics: true, // Enables optional consent for VeChainKit tracking
cookiePolicy: [
{
displayName: 'MyApp Policy', // (Optional) Custom display label
url: 'https://www.myapp.com/cookie-policy',
version: 1, // Increment to re-prompt users
required: false, // Optional: User sees a checkbox to opt in
},
],
privacyPolicy: [
{
url: 'https://www.myapp.com/privacy-policy',
version: 1, // Increment to re-prompt users
required: false, // Optional: can be skipped or rejected
},
],
termsAndConditions: [
{
displayName: 'MyApp T&C',
url: 'https://www.myapp.com/terms-and-conditions',
version: 1, // Increment to re-prompt users
required: true, // Required: must be accepted to proceed
},
],
}}
// ... other props
>
{children}
</VechainKitProvider>
);
}
Key Options
Option
Type
Required
Description
allowAnalytics
boolean
No
If true, prompts users with an optional tracking policy.
cookiePolicy
array
No
One or more cookie policy versions.
privacyPolicy
array
No
One or more privacy policy versions.
termsAndConditions
array
No
One or more T&C versions.
Transaction Toast
Use our pre built TransactionModal or TransactionToast components to show your users the progress and outcome of the transaction, or build your own UX and UI.
Usage example
'use client';
import { useWallet, useSendTransaction, useTransactionModal, TransactionModal, getConfig } from '@vechain/vechain-kit';
import { IB3TR__factory } from '@vechain/vechain-kit/contracts';
import { humanAddress } from '@vechain/vechain-kit/utils';
import { useMemo, useCallback } from 'react';
export function TransactionExamples() { const { account } = useWallet();
const b3trMainnetAddress = getConfig("main").b3trContractAddress;
const clauses = useMemo(() => {
const B3TRInterface = IB3TR__factory.createInterface();
const clausesArray: any[] = [];
clausesArray.push({
to: b3trMainnetAddress,
value: '0x0',
data: B3TRInterface.encodeFunctionData('transfer', [
"0x0, // receiver address
'0', // 0 B3TR (in wei)
]),
comment: `This is a dummy transaction to test the transaction modal. Confirm to transfer ${0} B3TR to ${humanAddress("Ox0")}`,
abi: B3TRInterface.getFunction('transfer'),
});
return clausesArray;
}, [connectedWallet?.address]);
const {
sendTransaction,
status,
txReceipt,
resetStatus,
isTransactionPending,
error,
} = useSendTransaction({
signerAccountAddress: account?.address ?? '',
});
const {
open,
close,
isOpen
} = useTransactionToast();
// This is the function triggering the transaction and opening the toast
const handleTransaction = useCallback(async () => {
open();
await sendTransaction(clauses);
}, [sendTransaction, clauses, open]);
const handleTryAgain = useCallback(async () => {
resetStatus();
await sendTransaction(clauses);
}, [sendTransaction, clauses, resetStatus]);
return (
<>
<button
onClick={handleTransactionWithModal}
isLoading={isTransactionPending}
isDisabled={isTransactionPending}
>
Send B3TR
</button>
<TransactionToast
isOpen={isOpen}
onClose={close}
status={status}
txReceipt={txReceipt}
txError={error}
onTryAgain={handleTryAgain}
title: 'Test Transaction',
description: `This is a dummy transaction to test the transaction modal. Confirm to transfer ${0} B3TR to ${
account?.address
}`
/>
</>
);
}
General
Common issues and solutions during migration from 1.x to 2.0
TypeScript Compilation Errors
Type Mismatch Errors
BigInt Serialization Errors
React Query Issues
Cache Invalidation Not Working
Contract Interaction Issues
Migration from manual ABI lookup:
Transaction Building Fails
Performance Issues
Too Many Network Requests
Network Issues
RPC Endpoint Problems
Testing Issues
Mocking Problems
Login
Login Hooks
The hooks provide authentication methods for VeChain applications:
Authentication Hooks
useLoginWithPasskey: Hook for authenticating using passkeys (biometric/device-based authentication)
useLoginWithOAuth: Hook for authenticating using OAuth providers (Google, Twitter, Apple, Discord)
useLoginWithVeChain: Hook for authenticating using VeChain wallet
Types
Usage example
NFTs
NFT Hooks
The hooks provide tools for interacting with NFTs (Non-Fungible Tokens) on VeChain:
NFT Data Hooks
useNFTImage: Fetches NFT image and metadata from IPFS, handling the complete flow from token ID to final image
useNFTMetadataUri: Retrieves the metadata URI for an NFT using its token ID
Types
Example usage
// Error: Type 'string' is not assignable to type '`0x${string}`'
const address: `0x${string}` = userAddress;
// ✅ Solution
const contractAddress = getConfig(networkType).contractAddress as `0x${string}`;
const method = 'convertedB3trOf' as const;
// ✅ Validate addresses
import { humanAddress } from '@vechain/vechain-kit/utils';
const validatedAddress = humanAddress(address);
import { hashFn } from 'wagmi/query';
// Error: Do not know how to serialize a BigInt
// ✅ Solution: set wagmi's hashfn as default queryKeyHashFn
export const queryClient = new QueryClient({
defaultOptions: {
queries: {
queryKeyHashFn: hashFn,
retry: 0,
retryOnMount: false,
staleTime: 30000,
// other options
},
},
})
Fee Delegation is mostly meant to be implemented to support end-user-experience, removing the need to purchase/collect gas tokens and remove need to pay for the applications activities.
Currently, it is mandatory to sponsor transaction for users that use social login. You can deploy your own custom solution or use vechain.energy.
FEE_DELEGATION_URL
You need to set this parameter in your VeChainKitProvider weapper.
You need one per environmnet.
To obtain one you can deploy your own custom solution or use vechain.energy.
Option 1: create your own
You can deploy this as a microservice (through lambda, cloudflare, etc.) or as an endpoint of your backend.
import { Address, HDKey, Transaction, Secp256k1, Hex } from '@vechain/sdk-core';
// the default signer is a solo node seeded account
const DEFAULT_SIGNER = 'denial kitchen pet squirrel other broom bar gas better priority spoil cross'
export async function onRequestPost({ request, env }): Promise<Response> {
const body = await request.json()
console.log('Incoming request', body);
const signerWallet = HDKey.fromMnemonic((env.SIGNER_MNEMONIC ?? DEFAULT_SIGNER).split(' '), HDKey.VET_DERIVATION_PATH).deriveChild(0);
if (!signerWallet.publicKey || !signerWallet.privateKey) { throw new Error('Could not load signing wallet') }
const signerAddress = Address.ofPublicKey(signerWallet.publicKey)
const transactionToSign = Transaction.decode(
Buffer.from(body.raw.slice(2), 'hex'),
false
);
const transactionHash = transactionToSign.getSignatureHash(Address.of(body.origin))
const signature = Secp256k1.sign(transactionHash.bytes, signerWallet.privateKey)
return new Response(JSON.stringify({
signature: Hex.of(signature).toString(),
address: signerAddress.toString()
}), {
status: 200,
headers: {
'Content-Type': 'application/json',
'access-control-allow-origin': '*'
}
})
}
Add this address 0xD7B96cAC488fEE053daAf8dF74f306bBc237D3f5 (MAINNET) or 0x7C5114ef27a721Df187b32e4eD983BaB813B81Cb (TESTNET) in {YourProjectName/FeeDelegation/Configurations/Smart Contract to sponsor all transactions.
// Smart contract to allow delegate all requests
/
pragma solidity ^0.8.20;
contract FeeDelegation {
/**
* @dev Check if a transaction can be sponsored for a user
*/
function canSponsorTransactionFor(
address _origin,
address _to,
bytes calldata _data
) public view returns (bool) {
return true;
}
}
Enable email notifications to let you know of low VTHO balance.
What is Fee Delegation?
Fee Delegation is a simple process that creates a magical user experience using blockchains without losing decentralized benefits.
The user submitting a transaction requests a second signature from a gas payer.
When the transaction is processed, the gas costs are taken from the gas payers balance instead of the user submitting the transaction.
It creates a feeless and trustless solution for instant blockchain access.
It stops users from instantly using an application. Users that need to pay for transactions are required to obtain a token from somewhere. Regular users do not know where to go and researching a place to buy and accessing a (de)centralized exchange is too much to ask for a user that wants to use your application.
What does it allow?
Users can open an application and interact with it instantly. An Application can submit transactions in the background and can no longer be be distinguished from regular(web2) alternatives. Fee delegation solves the biggest hurdle of user on - boarding to blockchain - applications without invalidating the web3 - principles.
Fee Delegation is mostly meant to be implemented to support end-user-experience, removing the need to purchase/collect gas tokens and remove need to pay for the applications activities.
Other use-cases are instant interactions with blockchains from different backends or processes without introducing account management for gas tokens.
Removed Features in v2
This document lists all hooks, modules, and features that have been completely removed in v2. If you use any of these, you'll need to find alternatives or implement custom solutions.
Core Hooks Removed
View removed core hooks
useConnex
Removed: useConnex hook
Replacement: Use useThor
Migration:
useCall
Removed: Generic useCall hook
Replacement: Use useCallClause with proper typing
Migration:
useEvents
Status: Completely refactored with new API
Note: Not removed, but has breaking API changes
Documentation:
VeBetterDAO Module (Completely Removed)
The entire VeBetterDAO module has been removed. All hooks under api/vebetterdao/ are no longer available.
View all removed VeBetterDAO hooks
Galaxy Member Hooks
useGMbalance
useB3trToUpgrade
useB3trToUpgradeToLevel
useGetNodeIdAttached
useGetTokenIdAttachedToNode
useGMMaxLevel
useParticipatedInGovernance
useTokenIdByAccount
useNFTImage
useB3trDonated
useGMBaseUri
useSelectedTokenId
useIsGMClaimable
useSelectedGmNft
useLevelOfToken
useNFTMetadataUri
Node Management Hooks
useGetNodeManager
useIsNodeHolder
useUserXNodes
Rewards Hooks
useLevelMultiplier
VePassport Hooks
useAccountLinking
usePassportChecks
useUserDelegation
useUserStatus
useAppSecurityLevel
useGetCumulativeScoreWithDecay
useGetDelegatee
useGetDelegator
useGetEntitiesLinkedToPassport
useGetPassportForEntity
useGetPendingDelegationsDelegateePOV
useGetPendingDelegationsDelegatorPOV
useGetPendingLinkings
useIsEntity
useIsPassportCheckEnabled
useIsPassport
useParticipationScoreThreshold
useSecurityMultiplier
useThresholdParticipationScore
useThresholdParticipationScoreAtTimepoint
useIsBlacklisted
useIsWhitelisted
useUserRoundScore
X2Earn Rewards Pool Hooks
useUserVotesInAllRounds
useUserTopVotedApps
XAllocation Pool Hooks
useAllocationAmount
useXAppVotesQf
XApps Hooks
useXNode
useAppAdmin
useAppExists
useAppsEligibleInNextRound
useGetX2EarnAppAvailableFunds
useXAppsMetadataBaseUri
useXNodeCheckCooldown
XNodes Hooks
All XNodes-related functionality has been removed.
Other Removed Modules
View other removed modules
Blockchain Module
getEvents utility removed
Use new event handling patterns instead
ERC20 Module
useGetErc20Balance removed
Use useCallClause with ERC20 ABI instead
Indexer Module
All indexer hooks removed
Implement custom indexing if needed
Oracle Module
useGetTokenUsdPrice removed
Integrate with external price feeds directly
veDelegate Module
useGetVeDelegateBalance removed
Use contract calls directly
NFTs Module
All NFT-related hooks removed
Use generic contract interaction patterns
Removed Utility Hooks
View removed utility hooks
useDecodeFunctionSignature
Purpose: Decoded function signatures from transaction data
Alternative: Use ethers.js or web3.js utilities directly
useGetCustomTokenBalances
Purpose: Fetched balances for custom tokens
Alternative: Use useCallClause with token contracts
useGetCustomTokenInfo
Purpose: Retrieved token metadata
Alternative: Query token contracts directly
Migration Strategies
For VeBetterDAO Features
If you depend on VeBetterDAO functionality:
Option 1: Implement custom hooks using useCallClause
Option 2: Wait for community implementations
Option 3: Use direct contract interactions
Example custom implementation:
For Removed Utility Functions
Most removed utilities can be replaced with:
Direct contract calls using useCallClause
Thor client methods
External libraries (ethers.js, web3.js)
Send Transactions
The useSendTransaction hook is mandatory if you use social login in your app, since it handles the social login transactions (that needs to be prepared and broadcasted differently from normal transactions). If you are not interested in social login features then you can avoid using useSendTransaction and useSignMessage and use the signer exported by the kit following the SDK guides for creating transactions or signing messages.
This hook will take care of checking your connection type and handle the transaction submission between privy, cross-app and wallet connections.
When implementing VeChain Kit it is mandatory to use this hook to send transaction.
Use our pre built TransactionModal or TransactionToast components to show your users the progress and outcome of the transaction, or build your own UX and UI.
You can build clauses with some of our available build functions, with our or with .
If you want to interact directly with the user's smart account read the section.
Important
Ensuring data is pre-fetched before initiating a transaction is crucial to avoid browser pop-up blocking for users using social login, which can adversely affect user experience.
🔧 Custom Gas Configuration (optional)
When sending transactions using VechainKit, you can fine-tune the gas settings by using two optional fields: suggestedMaxGas and gasPadding. These options give you greater control over the gas behavior of your transaction.
suggestedMaxGas
The suggestedMaxGas parameter allows you to explicitly define the maximum amount of gas to be used for the transaction. When this field is set, it will override the internal gas estimation logic and also ignore any gasPadding value.
Expected format: an integer representing gas units (e.g., 40000000 for 40 million gas).
Use this option when you want full control and know in advance the maximum gas your transaction should consume.
Example:
gasPadding
The gasPadding option allows you to add a safety buffer on top of the estimated gas. This can be useful to prevent underestimations for complex transactions.
Expected format: a number between 0 and 1, representing a percentage increase (e.g., 0.1 adds 10%).
Only applied if suggestedMaxGas is not set.
Example:
Summary
Option
Type
Applies Gas Estimation
Applies Padding
Overwrites Estimation
Use suggestedMaxGas when you want to define the gas cap directly, and gasPadding when you prefer to work with auto-estimation but want a bit of headroom.
Our smart accounts are a simplified version of the Account Abstraction pattern, made for the VeChain blockchain, and are required in order to enable social login.
Every wallet on VeChain owns a smart account. The address of your smart account is deterministic, and it can be deployed at any time, and receive tokens even if it is not deployed yet.
Testnet Factory address changed from v1 to v3 from 0x7EABA81B4F3741Ac381af7e025f3B6e0428F05Fb to 0x713b908Bcf77f3E00EFEf328E50b657a1A23AeaF which will cause all your testnet smart account addresses to change when upgrading to v1.5.0.
Contracts
There are 2 contracts that work together to enable social login and account abstraction:
SimpleAccount: A smart contract wallet owned by the user that can:
Execute transactions directly from the owner or through signed messages
Handle both single and batch transactions
SimpleAccountFactory: Factory contract that creates and manages SimpleAccount contracts:
Creates new accounts with deterministic addresses using CREATE2
Get the account address of a smart account without deploying it
Supports multiple accounts per owner through custom salts
Manages different versions of the SimpleAccount implementation
How it works
Account Creation: When a user wants to create a smart account, they interact with the SimpleAccountFactory, which creates a new SimpleAccount instance with the user as the owner.
Transaction Execution: The SimpleAccount can execute transactions in several ways:
Direct execution by the owner
Batch execution of multiple transactions
Signature-based execution (useful for social login) - Deprecated, should avoid using this
Batch signature-based execution with replay protection (useful for social login + multiclause)
Batch signature-based execution with 16-bit chain ID to allow iOS and Android developers handle VeChain chain ID.
Nonce Management: For batch transactions with authorization (executeBatchWithAuthorization), a nonce is required to protect users against replay attacks:
The nonce should be generated when requesting the signature
Best practice is to use Date.now() as the nonce value
Each nonce can only be used once per account
Without proper nonce management, malicious actors could replay the same signed transaction multiple times
Nonces are only used and required for executeBatchWithAuthorization method
Social Login Integration: This system enables social login by creating deterministic account addresses for each user and allowing transactions to be signed off-chain and executed by anyone. This creates a seamless experience where users can interact with dApps using their social credentials.
Version Management
The system has evolved through multiple versions to improve functionality and security:
SimpleAccount:
V1: Basic account functionality with single transaction execution
V2: Skipped for misconfiguration during upgrade
V3: Introduced batch transactions with nonce-based replay protection, ownership transfer and version tracking
SimpleAccountFactory:
V1: Basic account creation and management
V2: Added support for multiple accounts per owner using custom salts
V3: Support for V3 SimpleAccounts, enhanced version management and backward compatibility with legacy accounts
The factory maintains compatibility with all account versions, ensuring a smooth experience across different dApps and versions.
Testnet Factory address changed from v1 to v3 from 0x7EABA81B4F3741Ac381af7e025f3B6e0428F05Fb to 0x713b908Bcf77f3E00EFEf328E50b657a1A23AeaF which will cause all your testnet smart account addresses to change.
Smart Accounts V3 upgrade
Upgrading the user's smart accounts from V1 to V3 is mandatory, in order to protect against reply attacks and to allow multiclause transactions on VeChain.
To facilitate the mandatory upgrade process, the kit now includes:
Built-in Check: Automatically displays an alert prompting users to upgrade if they possess a V1 smart account.
Hooks & Component: To avoid apps spending VTHO to upgrade accounts of users that won't do any action on the app we allow the developer to check upgradeability on demand by using the useUpgradeRequiredForAccount hook and show the UpgradeSmartAccountModal (importable from the kit).
Example of the upgrade modal
Hooks
Developers can efficiently manage smart accounts using a variety of hooks provided by the kit.
By importing these hooks, developers can:
Easily check if an upgrade is needed
Determine the current smart account version
Simplify maintaining and upgrading smart accounts
This ensures users seamlessly benefit from the latest features and protections.
Multiclause transactions
Multiclause transactions offer enhanced security and flexibility within the VeChain ecosystem. Here's a breakdown of the key points for developers:
Single Clause Transactions:
You can still use executeWithAuthorization, but keep in mind it retains a replay attack vector. This method is primarily for backward compatibility.
Recommended Adoption:
Switch to executeBatchWithAuthorization for a more secure solution. This method:
Solves replay attack issues.
Executes multiple transactions with a single signature, simulating multiclause capabilities.
Automatic Management:
Using useSendTransaction from the kit automates this process, negating the need for manual adjustments.
Understanding these components is crucial for those working with smart accounts outside of the vechain-kit. This knowledge ensures both security and operational efficiency.
How to use executeBatchWithAuthorization and the nonce :
const executeBatchWithAuthorization = (txClauses: Connex.VM.Clause[]) => {
const toArray: string[] = [];
const valueArray: string[] = [];
const dataArray: string[] = [];
txClauses.forEach((clause) => {
toArray.push(clause.to ?? '');
valueArray.push(String(clause.value));
if (typeof clause.data === 'object' && 'abi' in clause.data) {
dataArray.push(encodeFunctionData(clause.data));
} else {
dataArray.push(clause.data || '0x');
}
});
const dataToSign = {
domain: {
name: 'Wallet',
version: '1',
chainId,
verifyingContract, // the smart account of the user
},
types: {
ExecuteBatchWithAuthorization: [
{ name: 'to', type: 'address[]' },
{ name: 'value', type: 'uint256[]' },
{ name: 'data', type: 'bytes[]' },
{ name: 'validAfter', type: 'uint256' },
{ name: 'validBefore', type: 'uint256' },
{ name: 'nonce', type: 'bytes32' },
],
EIP712Domain: [
{ name: 'name', type: 'string' },
{ name: 'version', type: 'string' },
{ name: 'chainId', type: 'uint256' },
{ name: 'verifyingContract', type: 'address' },
],
},
primaryType: 'ExecuteBatchWithAuthorization',
message: {
to: toArray,
value: valueArray,
data: dataArray,
validAfter: 0,
validBefore: Math.floor(Date.now() / 1000) + 60, // e.g. 1 minute from now
nonce: ethers.hexlify(ethers.randomBytes(32)), // nonce needs to be random
},
};
// Sign the typed data with Privy
const signature = (
await signTypedDataPrivy(typedData, {
uiOptions: {
title,
description,
buttonText,
},
})
).signature;
// Rebuild the clauses to call the executeBatchWithAuthorization
const clauses = [];
clauses.push(
Clause.callFunction(
Address.of(smartAccount.address),
ABIContract.ofAbi(SimpleAccountABI).getFunction(
'executeBatchWithAuthorization',
),
[
typedData.message.to,
typedData.message.value?.map((val) => BigInt(val)) ?? 0,
typedData.message.data,
BigInt(typedData.message.validAfter),
BigInt(typedData.message.validBefore),
typedData.message.nonce, // If your contract expects bytes32
signature as `0x${string}`,
],
),
);
//Now you can broadcast the transaction to the blockchain with one of your delegator wallets
// ...
};
Currently VeChain Kit does not support the handling of a smart account by a VeWorld wallet.
Embedded Wallets
When a user initiates a connection through Privy, either directly or via cross-app integration, a secure wallet is immediately created for them. Privy implements a sophisticated key management technique known as key splitting, specifically using Shamir’s secret sharing method. This approach ensures that users retain full custody of their wallets without needing to manage any secret keys themselves. Importantly, neither Privy nor any integrated application ever accesses the user's keys; these secrets are only reconstituted directly on the user's device during the signing of messages or transactions. This process guarantees the utmost security and privacy for the user's onchain activities.
This type of wallet created by Privy is called Embedded Wallet.
Seamless Wallet Integration
Users benefit from an intuitively integrated wallet management experience that aligns seamlessly with their existing accounts, removing any unnecessary technical barriers. Applications built with Privy can generate wallets automatically for new accounts, such as those registered with an email address or phone number, even before the user logs in for the first time. Additionally, Privy provides users with the option to export their wallet keys, serving as an escape mechanism should they choose to transition away from Privy’s services at any point.
Privy embedded wallets are portable and can be used by any app––even apps not using Privy. Privy’s cross application wallet system supports interoperability with simple user experiences accessible to everyone, and seamlessly integrates with other Privy integrations and wallet connector solutions like RainbowKit and wagmi.
Sample cross app wallet flow
Embedded wallets can be backed up by the user through the VeChain Kit modal in the Settings page.
Many login methods can be attached to the Embedded Wallet.
Fee delegation allows your dApp to sponsor transaction fees for users, removing the barrier of requiring VTHO tokens. This is mandatory for social login users who don't have VTHO to pay for transactions.
What is Fee Delegation?
Fee delegation is a VeChain feature that enables applications to pay transaction fees on behalf of users. This creates a seamless user experience, especially for:
New users without VTHO tokens
Social login users (email, Google, etc.)
Improving overall user onboarding
Configuration
Add fee delegation to your VeChainKitProvider:
<VeChainKitProvider
feeDelegation={{
delegatorUrl: "YOUR_FEE_DELEGATION_URL",
delegateAllTransactions: true, // or false for social login only
}}
>
{children}
</VeChainKitProvider>
Configuration Options
delegatorUrl: The endpoint URL for your fee delegation service
delegateAllTransactions:
true: Sponsor all transactions (wallet and social users)
false: Sponsor only social login transactions (mandatory)
Setup Options
You have two options for setting up fee delegation:
Option 1: Create Your Own Service
Deploy a custom fee delegation service as a microservice or backend endpoint.
Example Implementation (Cloudflare Worker)
import {
Address,
HDKey,
Transaction,
Secp256k1,
Hex
} from '@vechain/sdk-core';
// Default signer for development (use env variable in production)
const DEFAULT_SIGNER = 'denial kitchen pet squirrel other broom bar gas better priority spoil cross';
export async function onRequestPost({ request, env }): Promise<Response> {
try {
const body = await request.json();
console.log('Incoming fee delegation request:', body);
// Load signer wallet from mnemonic
const mnemonic = (env.SIGNER_MNEMONIC ?? DEFAULT_SIGNER).split(' ');
const signerWallet = HDKey.fromMnemonic(
mnemonic,
HDKey.VET_DERIVATION_PATH
).deriveChild(0);
if (!signerWallet.publicKey || !signerWallet.privateKey) {
throw new Error('Could not load signing wallet');
}
// Get signer address
const signerAddress = Address.ofPublicKey(signerWallet.publicKey);
// Decode and sign the transaction
const transactionToSign = Transaction.decode(
Buffer.from(body.raw.slice(2), 'hex'),
false
);
const transactionHash = transactionToSign.getSignatureHash(
Address.of(body.origin)
);
const signature = Secp256k1.sign(
transactionHash.bytes,
signerWallet.privateKey
);
return new Response(JSON.stringify({
signature: Hex.of(signature).toString(),
address: signerAddress.toString()
}), {
status: 200,
headers: {
'Content-Type': 'application/json',
'Access-Control-Allow-Origin': '*'
}
});
} catch (error) {
console.error('Fee delegation error:', error);
return new Response(JSON.stringify({ error: error.message }), {
status: 500,
headers: {
'Content-Type': 'application/json',
'Access-Control-Allow-Origin': '*'
}
});
}
}
Environment Variables
# .env
SIGNER_MNEMONIC="your twelve word mnemonic phrase here"
Security Considerations
Never expose your mnemonic: Store it securely in environment variables
Implement request validation: Check origin, transaction limits, etc.
Add rate limiting: Prevent abuse of your delegation service
Monitor usage: Track delegation requests and VTHO consumption
Option 2: Use VeChain.Energy
VeChain.Energy provides a managed fee delegation service.
To authenticate users in your backend (BE) and issue them a JWT (JSON Web Token), you may need to check that the connected user actually holds the private keys of that wallet (assuring he is not pretending to be someone else).
The recommended approach is to use signTypedData to have the user sign a piece of structured data, ensuring the signed payload is unique for each authentication request.
When using Privy the user owns a smart account (which is a smart contract), and he cannot directly sign a message with the smart account but needs to do it with his Embedded Wallet (the wallet created and secured by Privy). This means that when you verify identities of users connected with social login you will need to check that the address that signed the message is actually the owner of the smart account.
Example usage
Create a provider that will handle the signature verification.
import { useSignatureVerification } from "../hooks/useSignatureVerification";
import { Modal, ModalOverlay, ModalContent, VStack, Text, Spinner, Button } from "@chakra-ui/react";
import { useTranslation } from "react-i18next";
import { ReactNode, useEffect } from "react";
import { useWallet } from "@vechain/vechain-kit";
import { clearSignature, usePostUser } from "@/hooks";
type Props = {
children: ReactNode;
};
export const SignatureVerificationWrapper = ({ children }: Props) => {
const { hasVerified, isVerifying, signature, verifySignature, value } = useSignatureVerification();
const { account } = useWallet();
const { t } = useTranslation();
const { mutate: postUser } = usePostUser();
useEffect(() => {
if (account?.address && !signature) {
verifySignature();
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [account?.address, signature]);
useEffect(() => {
// If user signed the signature we call our backend endpoint
if (signature && value.user !== "" && value.timestamp !== "" && account?.address) {
// When you want to execute the mutation:
postUser({
address: account.address,
value: value,
signature: signature,
});
}
}, [signature, value, account?.address, postUser]);
// if user disconnects we clear the signature
useEffect(() => {
if (!account) {
clearSignature();
}
}, [account]);
// Only show the modal if we have an account connected AND haven't verified yet AND are not in the process of verifying
const showModal = !!account?.address && (!hasVerified || !signature);
return (
<>
{children}
<Modal isOpen={showModal} onClose={() => {}} isCentered closeOnOverlayClick={false}>
<ModalOverlay />
<ModalContent p={6}>
<VStack spacing={4}>
{isVerifying ? (
<>
<Spinner size="xl" color="primary.500" />
<Text textAlign="center" fontSize="lg">
{t("Please sign the message to verify your wallet ownership")}
</Text>
</>
) : (
<>
<Text textAlign="center" fontSize="lg">
{t("Signature verification is mandatory to proceed. Please try again.")}
</Text>
<Button onClick={verifySignature} colorScheme="secondary">
{t("Try Again")}
</Button>
</>
)}
</VStack>
</ModalContent>
</Modal>
</>
);
};
Wrap the app with our new provider
import React from "react";
import ReactDOM from "react-dom/client";
import { VeChainKitProviderWrapper } from "./components/VeChainKitProviderWrapper.tsx";
import { ChakraProvider, ColorModeScript } from "@chakra-ui/react";
import { lightTheme } from "./theme";
import { RouterProvider } from "react-router-dom";
import { router } from "./router";
import { AppProvider } from "./components/AppProvider.tsx";
import { SignatureVerificationWrapper } from "./components/SignatureVerificationWrapper.tsx";
(async () => {
ReactDOM.createRoot(document.getElementById("root")!).render(
<React.StrictMode>
<ChakraProvider theme={lightTheme}>
<ColorModeScript initialColorMode="light" />
<VeChainKitProviderWrapper>
<SignatureVerificationWrapper>
<AppProvider>
<RouterProvider router={router} />
</AppProvider>
</SignatureVerificationWrapper>
</VeChainKitProviderWrapper>
</ChakraProvider>
</React.StrictMode>,
);
})();
import { useConnex } from '@vechain/vechain-kit';
export default function Home(): ReactElement {
const connex = useConnex();
const handleSignWithConnex = () => {
connex.vendor.sign(
'cert',
{
purpose: 'identification',
payload: {
type: 'text',
content: 'Please sign this message to connect your wallet.',
},
}
).request();
}
return <button onClick={handleSignWithConnex}> Sign </button>
}
Wallet
The useWallet hook provides a unified interface for managing wallet connections in a VeChain application, supporting multiple connection methods including social logins (via Privy), direct wallet connections (via DappKit), and cross-application connections.
This will be the hook you will use more frequently and it provides quite a few useful informations.
Usage
Types
Return values
account: Wallet
The primary account being used. This will be either:
The smart account (if connected via Privy)
The wallet address (if connected via DappKit)
smartAccount: SmartAccount
Information about the user's smart account:
address: The smart account address
domain: Associated VeChain domain name
image: Generated avatar image
isDeployed: Whether the smart account is deployed; smart accounts can be deployed on demand to avoid spending money on non active users. Learn more about smart accounts .
isActive: Whether this is the currently active account
version: Smart account contract version
When the user is connected with Privy account will always be equal to the smartAccount.
connectedWallet: Wallet
The currently connected wallet, regardless of connection method (can be both a Privy Embedded Wallet or a self custody Wallet connected trough VeWorld or Sync2):
address: Wallet address
domain: Associated VeChain domain name
image: Generated avatar image
privyUser: User | null
The Privy user object if connected via Privy, null otherwise
connection: ConnectionState
Current connection state information:
isConnected: Overall connection status
isLoading: Whether connection is in progress
isConnectedWithSocialLogin: Connected via Privy (no crossapp)
isConnectedWithDappKit: Connected via DappKit
isConnectedWithCrossApp: Connected via cross-app
isConnectedWithPrivy: Connected via Privy (social or cross-app)
isConnectedWithVeChain: Connected with VeChain cross-app
source: Connection source information
isInAppBrowser: Whether your app is running in VeWorld app browser
nodeUrl: Current node URL (if provided by you in the provider, otherwise default in use by VeChain Kit)
delegatorUrl: Fee delegation service URL setted by you in the provider
chainId: Current chain ID
network: Network type (mainnet/testnet/custom)
disconnect(): Promise<void>
This function terminates the current wallet connection, ensuring cleanup of connection details and triggering necessary event listeners for state updates.
Connection Sources
The hook supports three main connection types:
Social Login (privy): Authentication via social providers with your own APP_ID
Wallet Connection (wallet): Direct wallet connection via DappKit
Cross-App (privy-cross-app): Connection through ecosystem integration
Events
The hook dispatches a wallet_disconnected event when the wallet is disconnected, which can be used to trigger UI updates in dependent components.
interface User {
/** The Privy-issued DID for the user. If you need to store additional information
* about a user, you can use this DID to reference them. */
id: string;
/** The datetime of when the user was created. */
createdAt: Date;
/** The user's email address, if they have linked one. It cannot be linked to another user. */
email?: Email;
/** The user's phone number, if they have linked one. It cannot be linked to another user. */
phone?: Phone;
/** The user's most recently linked wallet, if they have linked at least one wallet.
* It cannot be linked to another user.
* This wallet is the wallet that will be used for transactions and signing if it is connected.
**/
wallet?: Wallet;
/**
* The user's smart wallet, if they have set up through the Privy Smart Wallet SDK.
*/
smartWallet?: SmartWallet;
/** The user's Google account, if they have linked one. It cannot be linked to another user. */
google?: Google;
/** The user's Twitter account, if they have linked one. It cannot be linked to another user. */
twitter?: Twitter;
/** The user's Discord account, if they have linked one. It cannot be linked to another user. */
discord?: Discord;
/** The user's Github account, if they have linked one. It cannot be linked to another user. */
github?: Github;
/** The user's Spotify account, if they have linked one. It cannot be linked to another user. */
spotify?: Spotify;
/** The user's Instagram account, if they have linked one. It cannot be linked to another user. */
instagram?: Instagram;
/** The user's Tiktok account, if they have linked one. It cannot be linked to another user. */
tiktok?: Tiktok;
/** The user's LinkedIn account, if they have linked one. It cannot be linked to another user. */
linkedin?: LinkedIn;
/** The user's Apple account, if they have linked one. It cannot be linked to another user. */
apple?: Apple;
/** The user's Farcaster account, if they have linked one. It cannot be linked to another user. */
farcaster?: Farcaster;
/** The user's Telegram account, if they have linked one. It cannot be linked to another user. */
telegram?: Telegram;
/** The list of accounts associated with this user. Each account contains additional metadata
* that may be helpful for advanced use cases. */
linkedAccounts: Array<LinkedAccountWithMetadata>;
/** The list of MFA Methods associated with this user. */
mfaMethods: Array<MfaMethod>;
/**
* Whether or not the user has explicitly accepted the Terms and Conditions
* and/or Privacy Policy
*/
hasAcceptedTerms: boolean;
/** Whether or not the user is a guest */
isGuest: boolean;
/** Custom metadata field for a given user account */
customMetadata?: CustomMetadataType;
}
// New events API - check documentation for updated usage
import { useEvents } from '@vechain/vechain-kit';
// The API has changed - refer to blockchain hooks documentation
Query Keys
Generating Query Keys
v2 introduces specific query key functions:
import {
getCallClauseQueryKeyWithArgs,
getCallClauseQueryKey
} from '@vechain/vechain-kit';
// With arguments
const queryKeyWithArgs = getCallClauseQueryKeyWithArgs({
abi: ContractABI,
address: contractAddress,
method: 'balanceOf',
args: [userAddress]
});
// Without arguments (for methods with no parameters)
const queryKey = getCallClauseQueryKey({
abi: ContractABI,
address: contractAddress,
method: 'totalSupply'
});
// Use with React Query for cache invalidation
queryClient.invalidateQueries({ queryKey: queryKeyWithArgs });