Wrap the app with our new provider
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
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: https://docs.privy.io/reference/sdk/server-auth/classes/PrivyClient#importuser
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.
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 .
You can customize the color button and size of the imported modal from the kit:
"use-client"
import { useConvertB3tr } from "@/hooks"
import { useWallet, useUpgradeRequired, useUpgradeSmartAccountModal } from "@vechain/vechain-kit"
// Example component allowing for B3TR to VOT3 conversion
export const ConvertModal = ({ isOpen, onClose }: Props) => {
const { account, connectedWallet, connection } = useWallet()
const isSmartAccountUpgradeRequired = useUpgradeRequired(
account?.address ?? "",
connectedWallet?.address ?? "",
3
)
const { open: openUpgradeModal } = useUpgradeSmartAccountModal()
// A custom convert b3tr to vot3 hook
const convertB3trMutation = useConvertB3tr({
amount,
})
const handleConvertB3tr = useCallback(() => {
if (connection.isConnectedWithPrivy && isSmartAccountUpgradeRequired) {
//Open Upgrade Modal
openUpgradeModal()
return
}
convertB3trMutation.resetStatus()
convertB3trMutation.sendTransaction(undefined)
}, [isSmartAccountUpgradeRequired, convertB3trMutation, openUpgradeModal, connection])
return (
<button onClick={handleConvertB3tr}> Convert my B3TR to VOT3 </button>
)
}
const { open: openUpgradeSmartAccountModal } = useUpgradeSmartAccountModal({
accentColor: '#000000',
modalSize: 'xl',
});
Common problems when upgrading to VeChain Kit from other solutions or managing dependencies
Clean install: Delete node_modules
and reinstall packages
Check versions: Ensure compatible peer dependency versions
Complete removal: Remove all old DApp Kit packages before installing VeChain Kit
Resolving version conflicts and dependency mismatches when installing VeChain Kit.
Complete migration guide for projects upgrading from DApp Kit, including package removal and import updates.
Can't find your issue? Search our GitHub Issues or ask on Discord.
Common issues and solutions when using VeChain Kit
If your issue is not addressed here, please reach out to us:
Discord: https://discord.com/invite/vechain
GitHub Issues: https://github.com/vechain/vechain-kit/issues
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
Check that you're using the latest version of VeChain Kit
Ensure all peer dependencies are correctly installed
Clear your build cache (rm -rf node_modules .next && npm install
)
Can't find your issue? Search our GitHub Issues or ask on Discord.
The hooks provide tools for interacting with VeDelegate token and functionality:
useGetVeDelegateBalance
: Retrieves the VeDelegate token balance for a given address
// Example usage of VeDelegate hooks
import { useGetVeDelegateBalance } from '@vechain/vechain-kit';
const ExampleComponent = () => {
const userAddress = "0x..."; // User's wallet address
// Get VeDelegate token balance
const { data: veDelegateBalance, isLoading } = useGetVeDelegateBalance(userAddress);
console.log(
'VeDelegate Balance:',
veDelegateBalance,
'Loading:',
isLoading
);
return (
// Your component JSX here
);
};
export default ExampleComponent;
/*
Note: The VeDelegate balance query will only be enabled when:
- A valid thor connection is available
- A valid address is provided
- A valid network type is configured
The balance is returned as a string representing the token amount.
*/
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.
VeChain Kit components not rendering correctly
Theme conflicts when your app uses Chakra UI
Missing styles or unexpected appearance
Even if you don't use Chakra in your app, it's required as a peer dependency:
Wrap your app with ChakraProvider
and include ColorModeScript
:
The essential setup:
You can keep using whatever frontend framework you prefer. The important part is defining the ChakraProvider
wrapper for VeChain Kit components to function properly.
One common issue is missing the <ColorModeScript />
component, which can cause styling inconsistencies. Always include it within your ChakraProvider.
Ensure that your project's peer dependencies align with VeChain Kit's specifications. Mismatched versions can cause installation failures, runtime errors, or unexpected behaviour.
Package installation fails with peer dependency warnings
Runtime errors about missing dependencies
Version conflicts between VeChain Kit and your existing packages
Often resolves dependency caching issues:
VeChain Kit requires specific versions of:
Chakra UI React: ^2.8.2
TanStack React Query: ^5.64.2
VeChain DApp Kit React: 2.0.1
Update or Downgrade Packages
You may need to adjust package versions to maintain compatibility:
Runtime and functionality problems that occur when integrating VeChain Kit into your application.
Fee delegation conflicts with existing setup
Browser popup blocking for social login users
Transaction failures due to configuration issues
Signing delays causing security restrictions
Remove existing fee delegation before using VeChain Kit's delegation
Pre-fetch data before triggering transactions to avoid popup blocking
Check configuration for proper delegation settings
Resolving conflicts when migrating from existing fee delegation setups and configuring VeChain Kit's delegation properly.
Preventing browser popup blocking for social login users by properly structuring transaction flows.
Can't find your issue? Search our or ask on .
# Remove existing installations
rm -rf node_modules
rm package-lock.json # or yarn.lock
# Reinstall packages
npm install # or yarn install
# Check what VeChain Kit expects
npm info @vechain/vechain-kit peerDependencies
# Install required peer dependencies
npm install @chakra-ui/react@^2.8.2 @tanstack/react-query@^5.64.2 @vechain/[email protected]
yarn add @chakra-ui/[email protected]
import React from "react";
import ReactDOM from "react-dom/client";
import App from "./App.tsx";
import { VeChainKitProvider } from "@vechain/vechain-kit";
import {
ChakraProvider,
ColorModeScript,
useColorMode,
} from "@chakra-ui/react";
import { PersistQueryClientProvider } from "@tanstack/react-query-persist-client";
import { persister, queryClient } from "./utils/queryClient.ts";
export const AppWrapper = () => {
const { colorMode } = useColorMode();
return (
<VeChainKitProvider
// ... your config
>
<App />
</VeChainKitProvider>
);
};
ReactDOM.createRoot(document.getElementById("root")!).render(
<React.StrictMode>
<PersistQueryClientProvider
client={queryClient}
persistOptions={{ persister }}
>
<ChakraProvider theme={stargateStakingTheme}>
<ColorModeScript initialColorMode="dark" />
<AppWrapper />
</ChakraProvider>
</PersistQueryClientProvider>
</React.StrictMode>
);
<ChakraProvider theme={stargateStakingTheme}>
<ColorModeScript initialColorMode="dark" />
<AppWrapper />
</ChakraProvider>
In 2.0 we replaced connex with sdk.
useConnex()
is not exported anymore, in favour of useThor() and the signer object can be used to sign transactions following the SDK patterns.
It might require to rm -rf node_modules yarn.lock && yarn
useCallClause
hook now provides type safe contract call clause with return types.
The following hooks were also removed in order to improve permeances.
Where you using any of those hooks? Please reach us on Github to add them back
If you're coming from DApp Kit or SDK, you may have version conflicts from different versions installed across your project.
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
Completely Remove DApp Kit Packages
Remove all existing DApp Kit packages from your project:
npm uninstall @vechain/dapp-kit @vechain/dapp-kit-react @vechain/dapp-kit-ui
Completely Remove DApp Kit Packages
VeChain Kit provides similar functionality with updated component names:
// X Old DApp Kit
import { ConnectWallet } from '@vechain/dapp-kit-react';
// ✅ New VeChain Kit
import { WalletButton } from '@vechain/vechain-kit';
Clean Installation
After removing old packages:
rm -rf node_modules package-lock.json
npm install
Component Mapping
ConnectWallet
WalletButton
<other dapp-kit components>
<available through VeChain Kit exports>
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
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
Only supported on React and Next.js
React query, chakra and dapp-kit are peer dependencies.
'use client';
import {VeChainKitProvider} from "@vechain/vechain-kit";
export function VeChainKitProviderWrapper({children}: any) {
return (
<VeChainKitProvider
feeDelegation={{
delegatorUrl: "https://sponsor-testnet.vechain.energy/by/441",
// set to false if you want to delegate ONLY social login transactions
// social login transactions sponsorship is currently mandatory
delegateAllTransactions: false,
}}
loginMethods={[
{method: "vechain", gridColumn: 4},
{method: "dappkit", gridColumn: 4},
]}
dappKit={{
allowedWallets: ["veworld", "wallet-connect", "sync2"],
walletConnectOptions: {
projectId:
// Get this on https://cloud.reown.com/sign-in
process.env.NEXT_PUBLIC_WALLET_CONNECT_PROJECT_ID!,
metadata: {
name: "React Dapp Template",
description: "This is the description of your app visible in VeWorld upon connection request.",
url: typeof window !== "undefined" ? window.location.origin : "",
icons: ["https://path-to-logo.png"],
},
},
}}
darkMode={false}
language="en"
network={{
type: "test",
}}
>
{children}
</VeChainKitProvider>
);
}
On Next.js you will need to dynamically load the import
import dynamic from 'next/dynamic';
const VeChainKitProvider = dynamic(
async () =>
(await import('@vechain/vechain-kit')).VeChainKitProvider,
{
ssr: false,
},
);
The modal implements a dynamic grid layout system that can be customized through the loginMethods
configuration.
The modal can be configured through the VeChainKitProvider
props.
<VeChainKitProvider
loginModalUI={{
logo: '/your-logo.png',
description: 'Custom login description',
}}
loginMethods={[
{ method: 'vechain', gridColumn: 4 },
{ method: 'dappkit', gridColumn: 4 }, // VeChain wallets, always available
{ method: 'ecosystem', gridColumn: 4 }, // Mugshot, Cleanify, Greencart, ...
{ method: 'email', gridColumn: 2 }, // only available with your own Privy
{ method: 'passkey', gridColumn: 2 }, // only available with your own Privy
{ method: 'google', gridColumn: 4 }, // only available with your own Privy
{ method: 'more', gridColumn: 2 }, // will open your own Privy login, only available with your own Privy
]}
allowCustomTokens={false} // allow the user to manage custom tokens
>
{children}
</VeChainKitProvider>
Login methods selection:
vechain, dappkit, and ecosystem are always available options
The Privy-dependent methods (email, google, passkey, more) are only available when the privy prop is defined
TypeScript will show an error if someone tries to use a Privy-dependent method when privy is not configured
// This will show a type error
const invalidConfig: VechainKitProviderProps = {
// no privy prop specified
loginMethods: [
{ method: 'email' } // ❌ Type error: 'email' is not assignable to type 'never'
],
// ... other required props
};
// This is valid
const validConfig1: VechainKitProviderProps = {
// no privy prop
loginMethods: [
{ method: 'vechain' }, // ✅ OK
{ method: 'dappkit' }, // ✅ OK
{ method: 'ecosystem' } // ✅ OK
],
// ... other required props
};
// This is also valid
const validConfig2: VechainKitProviderProps = {
privy: {
appId: 'xxx',
clientId: 'yyy',
// ... other privy props
},
loginMethods: [
{ method: 'email' }, // ✅ OK because privy is configured
{ method: 'google' }, // ✅ OK because privy is configured
{ method: 'vechain' }, // ✅ OK (always allowed)
{ method: 'ecosystem' } // ✅ OK (always allowed)
],
// ... other required props
};
Fee delegation is mandatory if you want to use this kit with social login. Learn how to setup fee delegation in the following guide:
If you have your own Privy app, you can pass an additional prop with your settings.
import { VechainKitProvider } from '@vechain/vechain-kit';
export default function App({ Component, pageProps }: AppProps) {
return (
<VechainKitProvider
privy={{
appId: process.env.NEXT_PUBLIC_PRIVY_APP_ID!,
clientId: process.env.NEXT_PUBLIC_PRIVY_CLIENT_ID!,
loginMethods: ['google', 'twitter', 'sms', 'email'],
appearance: {
walletList: ['metamask', 'rainbow'],
accentColor: '#696FFD',
loginMessage: 'Select a social media profile',
logo: 'https://i.ibb.co/ZHGmq3y/image-21.png',
},
embeddedWallets: {
createOnLogin: 'all-users',
},
allowPasskeyLinking: true,
}}
...
//other props
>
{children}
</VechainKitProvider>
);
}
Go to privy.io and create an app. You will find the APP ID and the CLIENT ID in the App Settings tab.
For further information on how to implement Privy SDK please refer to their docs: https://docs.privy.io/
This project uses:
@privy-io/react-auth for Privy connection type
@privy-io/cross-app-connect for ecosystem cross app connection
You can import privy from the kit as
import { usePrivy } from "@vechain/vechain-kit";
const { user } = usePrivy();
If you setup your own Privy be sure to go over the recommended security settings provided by Privy: https://docs.privy.io/guide/security/implementation/ and https://docs.privy.io/guide/security/implementation/csp
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.
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
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.
Once you set up the kit provider, you are good to go, and you can allow your users to login, customizing the login experience based on your needs. You can choose between many options, leaving you complete freedom on your design.
Only available for apps with self hosted Privy .
This is an example of doing login with Google custom button, for more in depth details read here.
// Example usage of Login hooks
import {
useLoginWithOAuth,
} from '@vechain/vechain-kit';
const ExampleComponent = () => {
// OAuth authentication
const {
initOAuth,
} = useLoginWithOAuth();
const handleOAuthLogin = async (provider: OAuthProvider) => {
try {
await initOAuth({ provider });
console.log(`${provider} OAuth login initiated`);
} catch (error) {
console.error("OAuth login failed:", error);
}
};
return (
<div>
{/* OAuth Login Options */}
<button onClick={() => handleOAuthLogin('google')}>
Login with Google
</button>
<button onClick={() => handleOAuthLogin('twitter')}>
Login with Twitter
</button>
<button onClick={() => handleOAuthLogin('apple')}>
Login with Apple
</button>
<button onClick={() => handleOAuthLogin('discord')}>
Login with Discord
</button>
</div>
);
};
export default ExampleComponent;
You can allow users connect to your app only with wallet by using the dapp-kit connect modal, as follows:
import { useDAppKitWalletModal } from '@vechain/vechain-kit';
export const LoginComponent = () => {
const { open: openWalletModal } = useDAppKitWalletModal();
return (
<Button onClick={openWalletModal}>
Open only "Connect Wallet"
</Button>
)}
When your app is opened inside VeWorld mobile wallet, VeWorld is always enforced as a login choice.
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 Troubleshooting section.
Contact us on Discord: https://discord.gg/wGkQnPpRVq
Open an issue on Github: https://github.com/vechain/vechain-kit/issues
The hooks provide utility functions for interacting with the VeChain network and tokens:
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
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.
useLegalDocuments
: Retrieves the user's agreement status for required and optional legal documents, including terms of service, privacy policy, and cookie policy.
useGetTokenUsdPrice
A React hook that fetches the current USD price for supported tokens (B3TR, VET, VTHO) from the VeChain oracle contract.
Fetch the price for the following token symbols: 'B3TR', 'VET', 'VTHO'.
The hook returns a TanStack Query result object with the following properties:
The hook internally:
Uses the VeChain oracle contract to fetch real-time price data
Automatically handles network configuration (mainnet/testnet)
Caches results using TanStack Query
Only fetches when both Thor connection and network type are available
The hooks offer tools for efficient smart account management:
useGetAccountAddress
: Retrieves the smart account's address linked to the wallet.
useGetAccountVersion
: Retrieces the smart account's version even if the account is not deployed yet
useSmartAccountVersion
: Retrieves the smart account's version, but only if it is already deployed, and it will revert for v1 smart accounts (because function was not implemented in that smart contract)
useSmartAccount
: Retrieves the the account of an owner, returning additional information
useAccountImplementationAddress
: Returns the implementation address of a specific version; this hook is useful when upgrading the smart account of the user
useCurrentAccountImplementationVersion
: Returns the latest (and currently used) implementation version of the smart accounts used by the factory
useUpgradeRequired
: Returns if a smart account needs an upgrade (even if it's not yet deployed)
useUpgradeRequiredForAccount
: As above but only if the account is not yet deployed
useIsSmartAccountDeployed
: Verifies the deployment status of the smart account.
useHasV1SmartAccount
: Checks for a legacy version's existence.
useUpgradeSmartAccount
: Hook to create a transaction to upgrade the smart account to a specific version
useCurrentBlock()
Fetches the current block from the blockchain with automatic updates.
Features:
Auto-refreshes every 10 seconds
Caches data for 1 minute
Returns the latest block information as Connex.Thor.Block
Example:
useTxReceipt()
Polls the blockchain for a transaction receipt until it is found or times out.
Parameters:
txId: (string) The transaction ID to monitor
blockTimeout: (optional number) Number of blocks to wait before timing out (default: 5)
Returns:
data: Transaction receipt (Connex.Thor.Transaction.Receipt)
isLoading: Boolean indicating if the receipt is being fetched
error: Error object if the operation fails
Example:
getEvents()
Fetches events from the blockchain based on specified criteria.
getAllEvents()
Iteratively fetches all events matching the criteria, handling pagination automatically.
Parameters for getEvents/getAllEvents:
nodeUrl: VeChain node URL
thor: Thor client instance
filterCriteria: Array of event filter criteria
order: Sort order ('asc' or 'desc')
from: Starting block number
to: Ending block number
offset: (getEvents only) Number of events to skip
limit: (getEvents only) Maximum number of events to return
These hooks and functions are designed to work with the VeChain blockchain and require the VeChain Kit context to be properly set up in your application.
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
1) Install and overwrite the Provider.
2) Replace all imports of @vechain/dapp-kit-react
@vechain/dapp-kit
and @vechain/dapp-kit-ui
with @vechain/vechain-kit.
3) Wherever you use the account
property from the useWallet()
hook you need to access the user address differently:
4) Use the useSendTransaction()
hook from @vechain/vechain-kit
to send your transactions to the network.
Read how to use the hook .
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 .
8) Double-check your yarn.lock
to see that all the @vechain/dapp-kit-react
@vechain/dapp-kit
and @vechain/dapp-kit-ui
installs 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.
// Example usage of Utility hooks
import {
useGetChainId,
useGetNodeUrl,
useGetCustomTokenBalances,
useGetCustomTokenInfo,
useLegalDocuments
} from '@vechain/vechain-kit';
const ExampleComponent = () => {
const address = "0x..."; // User's wallet address
const tokenAddress = "0x..."; // Custom token address
// Get network information
const { data: chainId } = useGetChainId();
const nodeUrl = useGetNodeUrl();
// Get custom token information
const { data: tokenInfo } = useGetCustomTokenInfo(tokenAddress);
// Get token balances
const {
data: tokenBalances,
isLoading,
error
} = useGetCustomTokenBalances(address);
// Get legal documents data
const {
documentsNotAgreed,
documents,
agreements,
hasAgreedToRequiredDocuments,
} = useLegalDocuments();
console.log(
'Chain ID:', chainId,
'Node URL:', nodeUrl,
'Token Info:', tokenInfo,
'Token Balances:', tokenBalances,
'Has Agreed to Required Documents:', hasAgreedToRequiredDocuments,
'User Document Agreements:', agreements,
'Documents Not Agreed:', documentsNotAgreed,
'All Legal Documents:', documents
);
return (
// Your component JSX here
);
};
export default ExampleComponent;
/*
Note: These hooks require:
- A valid thor connection
- Appropriate network configuration
- Valid input parameters where applicable
The token-related hooks work with ERC20 tokens and return:
- Original values (raw from contract)
- Scaled values (adjusted for decimals)
- Formatted values (human-readable)
*/
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>;
}
data
number
The current token price in USD.
isLoading
boolean
Indicates if the query is currently loading.
error
Error | null
Describes any error that occurred during query.
isError
boolean
Indicates if there was an error in the query.
function TokenPrices() {
const { data: vetPrice } = useGetTokenUsdPrice('VET');
const { data: vthoPrice } = useGetTokenUsdPrice('VTHO');
const { data: b3trPrice } = useGetTokenUsdPrice('B3TR');
return (
<div>
<div>VET: ${vetPrice}</div>
<div>VTHO: ${vthoPrice}</div>
<div>B3TR: ${b3trPrice}</div>
</div>
);
}
import {
useSmartAccountVersion,
useGetSmartAccountAddress,
useIsSmartAccountDeployed,
useSmartAccountNeedsUpgrade,
useWallet,
useSmartAccountImplementationAddress,
useUpgradeSmartAccountVersion,
useHasV1SmartAccount,
} from '@vechain/vechain-kit';
// connectedWallet is the owner of the smart account, can be the Privy embedded wallet or VeWorld
const { connectedWallet } = useWallet();
const { data: smartAccountAddress } = useGetSmartAccountAddress(
connectedWallet?.address,
);
const { data: upgradeRequired } = useUpgradeRequired(
smartAccountAddress,
ownerAddress: connectedWallet?.address ?? "",
version: 3
);
const { data: smartAccountDeployed } =
useIsSmartAccountDeployed(smartAccountAddress);
const { data: currentSmartAccountVersion } =
useSmartAccountVersion(smartAccountAddress);
const { data: smartAccountNeedsUpgrade } = useSmartAccountNeedsUpgrade(
smartAccountAddress,
3,
);
const { data: smartAccountImplementationVersion } =
useCurrentAccountImplementationVersion();
const { data: smartAccountImplementationAddress } =
useSmartAccountImplementationAddress(3);
// Use the new hook for upgrading
const {
sendTransaction: upgradeSmartAccount,
isTransactionPending,
error: upgradeError,
} = useUpgradeSmartAccount({
smartAccountAddress: smartAccountAddress ?? '',
targetVersion: 3,
onSuccess: () => {
console.log('Smart Account upgraded successfully!');
},
onError: () => {
console.log('Error upgrading Smart Account.');
},
});
console.log(
'Smart Account',
smartAccountAddress,
'deployed',
smartAccountDeployed,
'version',
currentSmartAccountVersion,
'upgradeRequired',
upgradeRequired,
'current implementation version',
smartAccountImplementationVersion,
'v3 implementation address',
smartAccountImplementationAddress,
);
const { data, isLoading, error } = useCurrentBlock();
function BlockInfo() {
const { data: block } = useCurrentBlock();
return <div>Current Block: {block?.number}</div>;
}
const { data, isLoading, error } = useTxReceipt(txId, blockTimeout);
function TransactionStatus({ txId }) {
const { data: receipt, isLoading } = useTxReceipt(txId);
if (isLoading) return <div>Loading...</div>;
return <div>Transaction Status: {receipt?.reverted ? 'Failed' : 'Success'}</div>;
}
const events = await getEvents({
nodeUrl,
thor,
filterCriteria,
order: 'asc',
offset: 0,
limit: 1000,
from: 0,
to: undefined
});
const allEvents = await getAllEvents({
nodeUrl,
thor,
filterCriteria,
order: 'asc',
from: 0,
to: undefined
});
// dapp-kit
const { account } = useWallet()
console.log(account) // 0x000000dsadsa
// vechain-kit
const { account } = useWallet()
console.log(account.address, account.domain, account.image)
'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
}`
/>
</>
);
}
The hooks provide tools for interacting with NFTs (Non-Fungible Tokens) on VeChain:
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
// NFT Types
interface NFTMetadata {
name: string;
description: string;
image: string;
attributes: {
trait_type: string;
value: string | number;
}[];
}
interface NFTImageHookParams {
address: string;
contractAddress: string;
}
interface NFTMetadataUriParams {
tokenId: string;
contractAddress: string;
}
interface NFTImageHookResult {
imageData: string | null;
imageMetadata: NFTMetadata | null;
tokenID: string | null;
isLoading: boolean;
error: Error | null;
}
interface NFTMetadataUriResult {
data: string | null;
isLoading: boolean;
error: Error | null;
}
// Example usage of NFT hooks
import { useNFTImage, useNFTMetadataUri } from '@vechain/vechain-kit';
const ExampleComponent = () => {
const walletAddress = "0x...";
const tokenId = "1";
const contractAddress = "0x...";
// Get complete NFT data including image
const {
imageData,
imageMetadata,
tokenID,
isLoading: isLoadingNFT,
error: nftError
} = useNFTImage({
address: walletAddress,
contractAddress: contractAddress
});
// Get just the metadata URI
const {
data: metadataUri,
isLoading: isLoadingUri,
error: uriError
} = useNFTMetadataUri({
tokenId,
contractAddress
});
// Handle loading states
if (isLoadingNFT || isLoadingUri) {
return <div>Loading NFT data...</div>;
}
// Handle errors
if (nftError || uriError) {
return <div>Error: {nftError?.message || uriError?.message}</div>;
}
console.log(
'NFT Image:', imageData,
'NFT Metadata:', imageMetadata,
'Token ID:', tokenID,
'Metadata URI:', metadataUri
);
// Example of using the NFT data
return (
<div>
{imageData && (
<img
src={imageData}
alt={imageMetadata?.name || 'NFT'}
/>
)}
{imageMetadata && (
<div>
<h2>{imageMetadata.name}</h2>
<p>{imageMetadata.description}</p>
{imageMetadata.attributes?.map((attr, index) => (
<div key={index}>
{attr.trait_type}: {attr.value}
</div>
))}
</div>
)}
</div>
);
};
export default ExampleComponent;
/*
Note: These hooks require:
- Valid thor connection
- Network type configuration
- Valid IPFS gateway for image fetching
- The hooks handle the complete flow:
1. Get token ID from address
2. Get metadata URI from token ID
3. Fetch metadata from IPFS
4. Fetch image from IPFS
Types:
interface NFTMetadata {
name: string;
description: string;
image: string;
attributes: {
trait_type: string;
value: string;
}[];
}
*/
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.
'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,
}}
/>
</>
);
}
The hooks provide tools for managing various modals in the VeChain application:
useAccountModal
: Core account modal management
useProfileModal
: Show the user only his profile, with customize and logout option
useAccountCustomizationModal
: Account customization settings
useAccessAndSecurityModal
: Security settings and access management
useChooseNameModal
: Account name selection
useUpgradeSmartAccountModal
: Smart account upgrade management
useConnectModal
: Wallet connection modal
useWalletModal
: Combined wallet management modal
useLoginModalContent
: Login modal content configuration
useTransactionModal
: Transaction confirmation and status
useTransactionToast
: Transaction notifications
useSendTokenModal
: Token transfer interface
useReceiveModal
: Token receiving interface
useExploreEcosystemModal
: VeChain ecosystem explorer
useNotificationsModal
: Notification center
useFAQModal
: Frequently asked questions
Common issues and solutions during migration from 1.x to 2.0
Migration from manual ABI lookup:
// Modal Types
interface ModalHookReturn {
open: () => void;
close: () => void;
isOpen: boolean;
}
type LoginMethod = 'ecosystem' | 'vechain' | 'dappkit' | 'passkey' | 'email' | 'google' | 'more';
interface LoginModalContentConfig {
showGoogleLogin: boolean;
showEmailLogin: boolean;
showPasskey: boolean;
showVeChainLogin: boolean;
showDappKit: boolean;
showEcosystem: boolean;
showMoreLogin: boolean;
isOfficialVeChainApp: boolean;
}
interface AccountModalContent {
type: string;
props?: Record<string, any>;
}
// Example usage of Modal hooks
import {
useWalletModal,
useAccountModal,
useSendTokenModal,
useTransactionModal,
useLoginModalContent
} from '@modals';
const ExampleComponent = () => {
// Wallet modal management
const {
open: openWallet,
close: closeWallet,
isOpen: isWalletOpen
} = useWalletModal();
// Account modal management
const {
open: openAccount,
close: closeAccount,
isOpen: isAccountOpen
} = useAccountModal();
// Send token modal
const {
open: openSendToken,
close: closeSendToken,
isOpen: isSendTokenOpen
} = useSendTokenModal();
// Transaction modal
const {
open: openTransaction,
close: closeTransaction,
isOpen: isTransactionOpen
} = useTransactionModal();
// Login modal content configuration
const loginConfig = useLoginModalContent();
// Example handlers
const handleWalletClick = () => {
openWallet();
};
const handleSendToken = () => {
openSendToken();
};
const handleAccountSettings = () => {
openAccount();
};
return (
<div>
<button
onClick={handleWalletClick}
disabled={isWalletOpen}
>
Open Wallet
</button>
<button
onClick={handleSendToken}
disabled={isSendTokenOpen}
>
Send Tokens
</button>
<button
onClick={handleAccountSettings}
disabled={isAccountOpen}
>
Account Settings
</button>
{/* Login configuration display */}
{loginConfig.showGoogleLogin && (
<button>Login with Google</button>
)}
{loginConfig.showVeChainLogin && (
<button>Login with VeChain</button>
)}
{loginConfig.showPasskey && (
<button>Login with Passkey</button>
)}
{/* Transaction modal state */}
{isTransactionOpen && (
<div>Transaction in Progress...</div>
)}
</div>
);
};
export default ExampleComponent;
/*
Note: These hooks require:
- ModalProvider context
- Valid wallet connection for some features
- Proper configuration for login methods
- Each modal manages its own state and content
*/
// 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
},
},
})
// ✅ Good: Consistent query key structure
const queryKey = getCallClauseQueryKeyWithArgs({
abi: VOT3__factory.abi,
address: contractAddress,
method: 'balanceOf' as const,
args: [userAddress],
});
// ✅ Proper invalidation
queryClient.invalidateQueries({ queryKey });
// Old pattern
const functionAbi = vot3Abi.find((e) => e.name === "convertedB3trOf");
const res = await thor.account(contractAddress).method(functionAbi).call(address);
// ✅ New pattern
import { VOT3__factory } from '@vechain/vechain-kit/contracts';
return useCallClause({
abi: VOT3__factory.abi,
address: contractAddress,
method: 'convertedB3trOf' as const,
args: [address ?? ''],
queryOptions: { enabled: !!address },
});
// Old manual transaction building
const buildConvertTx = (thor: Connex.Thor, amount: string) => {
const functionAbi = abi.find((e) => e.name === "convertToVOT3");
const clause = thor.account(contractAddress).method(functionAbi).asClause(amount);
return { ...clause, comment: `Convert ${amount}`, abi: functionAbi };
};
// ✅ New clause building
import { VOT3__factory } from '@vechain/vechain-kit/contracts';
const buildConvertTx = (thor: ThorClient, amount: string): EnhancedClause => {
const { clause } = thor.contracts
.load(contractAddress, VOT3__factory.abi)
.clause.convertToVOT3(ethers.parseEther(amount));
return {
...clause,
comment: `Convert ${humanNumber(amount)} B3TR to VOT3`,
};
};
// ✅ Batch multiple calls
const useTokenData = (tokenAddress: string) => {
return useQuery({
queryKey: ['TOKEN_DATA', tokenAddress],
queryFn: async () => {
const results = await executeMultipleClausesCall({
thor,
calls: [
{ abi: ERC20__factory.abi, functionName: 'symbol', address: tokenAddress, args: [] },
{ abi: ERC20__factory.abi, functionName: 'decimals', address: tokenAddress, args: [] },
{ abi: ERC20__factory.abi, functionName: 'totalSupply', address: tokenAddress, args: [] },
],
});
return {
symbol: results[0][0],
decimals: results[1][0],
totalSupply: results[2][0],
};
},
enabled: !!tokenAddress,
staleTime: 60000, // Cache for 1 minute
});
};
// ✅ retry configuration
const { data, error } = useCallClause({
queryOptions: {
retry: (failureCount, error) => {
if (error.message.includes('reverted')) return false;
if (error.message.includes('invalid')) return false;
return failureCount < 3;
},
retryDelay: (attemptIndex) => Math.min(1000 * 2 ** attemptIndex, 30000),
},
});
// ✅ Mock VeChain Kit hooks
jest.mock('@vechain/vechain-kit', () => ({
useWallet: () => ({
account: { address: '0x123...' },
isConnected: true,
}),
useCallClause: () => ({
data: [BigInt('1000000000000000000')],
isLoading: false,
error: null,
}),
}));
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.
import { useWallet } from "@vechain/vechain-kit";
function MyComponent = () => {
const {
account,
connectedWallet,
smartAccount,
privyUser,
connection,
disconnect
} = useWallet();
return <></>
}
export type Wallet = {
address: string;
domain?: string;
image: string;
} | null;
export type SmartAccount = Wallet & {
isDeployed: boolean;
isActive: boolean;
version: string | null;
};
export type ConnectionSource = {
type: 'privy' | 'wallet' | 'privy-cross-app';
displayName: string;
};
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;
}
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 here.
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.
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
The hook dispatches a wallet_disconnected
event when the wallet is disconnected, which can be used to trigger UI updates in dependent components.
Essential patterns for optimal performance, type safety, and maintainability
// ✅ Good: Proper typing
const args: [string, bigint, boolean] = [address, amount, isEnabled];
const contractAddress = config.contractAddress as `0x${string}`;
const method = 'balanceOf' as const;
// ✅ Use contract factories
import { VOT3__factory } from '@vechain/vechain-kit/contracts';
const abi = VOT3__factory.abi;
// Avoid: Manual ABI definitions
const functionAbi = contractAbi.find((e) => e.name === "delegates");
// ✅ Good: Conditional enablement
return useCallClause({
abi,
address: contractAddress,
method: 'getData',
args: [userAddress],
queryOptions: {
enabled: !!contractAddress && !!userAddress && isConnected,
},
});
// ✅ Good: Configure caching
queryOptions: {
staleTime: 30000, // 30 seconds for price data
refetchInterval: 60000, // Refetch every minute
}
// ✅ Good: Transform in select
return useCallClause({
abi: VOT3__factory.abi,
address: contractAddress,
method: 'convertedB3trOf' as const,
args: [address ?? ''],
queryOptions: {
enabled: !!address,
select: (data) => ({
balance: ethers.formatEther(data[0]),
formatted: humanNumber(ethers.formatEther(data[0])),
}),
},
});
// Avoid: Transform in component
const transformedData = useMemo(() => ({
balance: data?.[0]?.toString(),
}), [data]); // Causes re-renders
// ✅ Good: Comprehensive error handling
if (error) {
if (error.message.includes('reverted')) {
return <div>Contract call failed. Check parameters.</div>;
}
if (error.message.includes('network')) {
return <div>Network error. <button onClick={refetch}>Retry</button></div>;
}
return <div>Error: {error.message}</div>;
}
// ✅ Good: Retry logic
queryOptions: {
retry: (failureCount, error) => {
if (error.message.includes('reverted')) return false;
return failureCount < 3;
},
retryDelay: (attemptIndex) => Math.min(1000 * 2 ** attemptIndex, 30000),
}
// ✅ Good: Use getCallClauseQueryKey (without args)
export const getCurrentAllocationsRoundIdQueryKey = (
address: string,
networkType: NETWORK_TYPE
) =>
getCallClauseQueryKey({
abi: XAllocationVoting__factory.abi,
address: getConfig(networkType).contractAddress as `0x${string}`,
method: 'currentRoundId' as const,
});
// ✅ Good: Use getCallClauseQueryKeyWithArgs
export const getTokenBalanceQueryKey = (
address: string,
networkType: NETWORK_TYPE
) =>
getCallClauseQueryKeyWithArgs({
abi: VOT3__factory.abi,
address: getConfig(networkType).contractAddress as `0x${string}`,
method: 'balanceOf' as const,
args: [address],
});
// ✅ Good: Query invalidation after transactions
const mutation = useBuildTransaction({
clauseBuilder: buildClauses,
onTxConfirmed: () => {
queryClient.invalidateQueries({ queryKey: getTokenBalanceQueryKey(userAddress, networkType) });
},
});
// ✅ Good: Memoize expensive calculations
const queryKey = useMemo(() =>
getTokenBalanceQueryKey(userAddress, tokenAddress),
[userAddress, tokenAddress]
);
// ✅ Good: Batch multiple calls
const results = await executeMultipleClausesCall({
thor,
calls: addresses.map((address) => ({
abi: ERC20__factory.abi,
functionName: 'balanceOf',
address: address as `0x${string}`,
args: [userAddress],
})),
});
// ✅ Good: Input validation
if (!isAddress(recipient)) {
throw new Error('Invalid recipient address');
}
const amountBN = BigInt(amount);
if (amountBN <= 0n) {
throw new Error('Amount must be positive');
}
// ✅ Good: Safe BigInt handling
const formatTokenAmount = (amount: bigint, decimals: number): string => {
try {
return ethers.formatUnits(amount, decimals);
} catch (error) {
return '0';
}
};
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.
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.
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': '*'
}
})
}
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.
You can read more about it here: https://docs.vechain.org/thor/learn/fee-delegation (opens in a new tab)
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.
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.
You can learn more about the feature from docs.vechain.org on How to Integrate VIP-191 (opens in a new tab).
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.
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.
VeChain Kit Demo: https://vechain-kit.vechain.org/
Smart Account Factory: https://vechain.github.io/smart-accounts-factory/
NPM: https://www.npmjs.com/package/@vechain/vechain-kit
Check our Troubleshooting section.
Contact us on Discord: https://discord.com/invite/vechain
Open an issue on Github: https://github.com/vechain/vechain-kit/issues
@vechain/vechain-kit
@vechain/vechain-kit/utils
@vechain/vechain-kit/contracts
@vechain/vechain-kit/assets
Core API changes when migrating from VeChain Kit 1.x to 2.0
Review for detailed interaction examples
Apply for optimal performance
Verify all functionality works as expected
Consult for common issues
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 (formerly React Query), which provides powerful data-fetching and caching capabilities.
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
All hooks use consistent query key patterns, making it easy to invalidate related queries. For example:
By default, most queries are configured with:
staleTime
: How long the data remains "fresh"
cacheTime
: How long inactive data remains in cache
refetchInterval
: For automatic background updates (if applicable)
These can be customized using TanStack Query's global configuration or per-hook options.
npm install @vechain/vechain-kit@^2.0.0
npm uninstall @thor-devkit
// Before (1.x)
import { useConnex } from '@vechain/vechain-kit';
const { thor } = useConnex();
// After (2.x)
import { useThor } from '@vechain/vechain-kit';
const thor = useThor();
// Before (1.x)
import { useConnex, useWallet, useTransaction } from '@vechain/vechain-kit';
// After (2.x)
import { useThor, useWallet, useBuildTransaction, useCallClause } from '@vechain/vechain-kit';
// Before (1.x) - Manual pattern
const functionAbi = contractAbi.find((e) => e.name === "balanceOf");
const res = await thor.account(contractAddress).method(functionAbi).call(address);
const balance = ethers.formatEther(res.decoded[0]);
// After (2.x) - useCallClause
const { data } = useCallClause({
abi: TokenContract__factory.abi,
address: contractAddress as `0x${string}`,
method: 'balanceOf' as const,
args: [address],
queryOptions: {
enabled: !!address,
select: (data) => ethers.formatEther(data[0]),
},
});
// Before (1.x) - Manual clause building
const functionAbi = contractAbi.find((e) => e.name === "approve");
const clause = thor.account(contractAddress).method(functionAbi).asClause(spender, amount);
// After (2.x) - Enhanced clauses
const { sendTransaction } = useBuildTransaction({
clauseBuilder: () => {
const { clause } = thor.contracts
.load(contractAddress, ERC20__factory.abi)
.clause.approve(spender, amount);
return [{ ...clause, comment: 'Approve tokens' }];
},
});
const contractAddress = config.contractAddress as `0x${string}`;
const method = 'balanceOf' as const;
queryOptions: {
enabled: !!address && !!tokenAddress,
}
if (error?.message.includes('reverted')) {
// Handle contract revert
} else if (error?.message.includes('network')) {
// Handle network error
}
const queryClient = useQueryClient();
// Invalidate all blockchain queries
queryClient.invalidateQueries({ queryKey: ['VECHAIN_KIT'] });
// Invalidate specific query
queryClient.invalidateQueries({ queryKey: ['VECHAIN_KIT', 'CURRENT_BLOCK'] });
This button acts both as a login button and as an account button (when the user is already logged).
VeChain Kit provides multiple ways to customize the UI components. Here are some examples of different button styles and variants.
Usage example
'use client';
import { VStack, Text, Button, Box, HStack, Grid } from '@chakra-ui/react';
import {
WalletButton,
useAccountModal,
ProfileCard,
useWallet,
} from '@vechain/vechain-kit';
import { MdBrush } from 'react-icons/md';
import { CollapsibleCard } from '../../ui/CollapsibleCard';
export function UIControls() {
const { open } = useAccountModal();
const { account } = useWallet();
return (
<>
<VStack spacing={6} align="stretch" w={'full'}>
<Text textAlign="center">
VeChain Kit provides multiple ways to customize the UI
components. Here are some examples of different button
styles and variants.
</Text>
<HStack w={'full'} justifyContent={'space-between'}>
{/* Mobile Variants */}
<HStack w={'full'} justifyContent={'center'}>
<VStack
w={'fit-content'}
spacing={6}
p={6}
borderRadius="md"
bg="whiteAlpha.50"
>
<Text fontWeight="bold">
Account Button Variants
</Text>
<Text
fontSize="sm"
textAlign="center"
color="gray.400"
>
Note: Some variants might look different based
on connection state and available data. Eg:
"iconDomainAndAssets" will show the assets only
if the user has assets. And same for domain
name.
</Text>
<Grid
templateColumns={{
base: '1fr',
md: 'repeat(2, 1fr)',
}}
gap={8}
w="full"
justifyContent="space-between"
>
{/* First Column Items */}
<VStack alignItems="flex-start" spacing={8}>
<VStack alignItems="flex-start" spacing={2}>
<Box w={'fit-content'}>
<WalletButton
mobileVariant="icon"
desktopVariant="icon"
/>
</Box>
<Text
fontSize="sm"
fontWeight="medium"
color="blue.300"
bg="whiteAlpha.100"
px={3}
py={1}
borderRadius="full"
>
variant: "icon"
</Text>
</VStack>
<VStack alignItems="flex-start" spacing={2}>
<Box w={'fit-content'}>
<WalletButton
mobileVariant="iconAndDomain"
desktopVariant="iconAndDomain"
/>
</Box>
<Text
fontSize="sm"
fontWeight="medium"
color="blue.300"
bg="whiteAlpha.100"
px={3}
py={1}
borderRadius="full"
>
variant: "iconAndDomain"
</Text>
</VStack>
<VStack alignItems="flex-start" spacing={2}>
<Box w={'fit-content'}>
<WalletButton
mobileVariant="iconDomainAndAddress"
desktopVariant="iconDomainAndAddress"
/>
</Box>
<Text
fontSize="sm"
fontWeight="medium"
color="blue.300"
bg="whiteAlpha.100"
px={3}
py={1}
borderRadius="full"
>
variant: "iconDomainAndAddress"
</Text>
</VStack>
</VStack>
{/* Second Column Items */}
<VStack alignItems={'flex-start'} spacing={8}>
<VStack alignItems="flex-start" spacing={2}>
<Box w={'fit-content'}>
<WalletButton
mobileVariant="iconDomainAndAssets"
desktopVariant="iconDomainAndAssets"
/>
</Box>
<Text
fontSize="sm"
fontWeight="medium"
color="blue.300"
bg="whiteAlpha.100"
px={3}
py={1}
borderRadius="full"
>
variant: "iconDomainAndAssets"
</Text>
</VStack>
<VStack alignItems="flex-start" spacing={2}>
<Box w={'fit-content'}>
<WalletButton
mobileVariant="iconDomainAndAssets"
desktopVariant="iconDomainAndAssets"
buttonStyle={{
border: '2px solid #000000',
boxShadow:
'-2px 2px 3px 1px #00000038',
background: '#f08098',
color: 'white',
_hover: {
background: '#db607a',
border: '1px solid #000000',
boxShadow:
'-3px 2px 3px 1px #00000038',
},
transition: 'all 0.2s ease',
}}
/>
</Box>
<Text
fontSize="sm"
fontWeight="medium"
color="blue.300"
bg="whiteAlpha.100"
px={3}
py={1}
borderRadius="full"
>
variant: "iconDomainAndAssets"
(styled)
</Text>
</VStack>
<VStack alignItems="flex-start" spacing={2}>
<Button onClick={open}>
<Text>This is a custom button</Text>
</Button>
<Text
fontSize="sm"
fontWeight="medium"
color="blue.300"
bg="whiteAlpha.100"
px={3}
py={1}
borderRadius="full"
>
no variant, custom button
</Text>
</VStack>
</VStack>
</Grid>
</VStack>
</HStack>
</HStack>
</VStack>
</>
);
}
VeChain Kit supports 3 types of connections:
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.
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.
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.
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:
import { useDAppKitWalletModal } from '@vechain/vechain-kit';
export const LoginComponent = () => {
const { open: openWalletModal } = useDAppKitWalletModal();
return (
<Button onClick={openWalletModal}>
Open only "Connect Wallet"
</Button>
)}
When your app is opened inside VeWorld mobile wallet, VeWorld is always enforced as a login choice.
Our are a simplified version of the Account Abstraction pattern, made for the VeChain blockchain, and are required in order to enable social login.
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.
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
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.
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.
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).
Developers can efficiently manage smart accounts using a 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 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
:
Currently VeChain Kit does not support the handling of a smart account by a VeWorld wallet.
The hooks provide tools for interacting with VeBetterDAO's smart contracts and features:
useB3trDonated
: Fetches the amount of B3TR tokens donated for a given token ID
useB3trToUpgrade
: Retrieves the amount of B3TR tokens required to upgrade a specific token
useB3trToUpgradeToLevel
: Gets the amount of B3TR needed to upgrade to a specific level
useGMBaseUri
: Retrieves the base URI for the Galaxy Member NFT metadata
useGMMaxLevel
: Fetches the maximum level possible for Galaxy Member NFTs
useGMbalance
: Gets the number of GM NFTs owned by an address
useGetNodeIdAttached
: Retrieves the Vechain Node Token ID attached to a given GM Token ID
useGetTokenIdAttachedToNode
: Gets the GM Token ID attached to a given Vechain Node ID
useGetTokensInfoByOwner
: Fetches token information for a specific owner with infinite scrolling support
useIsGMclaimable
: Determines if a user can claim a GM NFT
useLevelMultiplier
: Gets the reward multiplier for a specific GM level
useLevelOfToken
: Retrieves the level of a specified token
useParticipatedInGovernance
: Checks if an address has participated in governance
useSelectedGmNft
: Comprehensive hook for retrieving data related to a Galaxy Member NFT
useSelectedTokenId
: Gets the selected token ID for the selected galaxy member
useTokenIdByAccount
: Retrieves the token ID for an address given an index
useGetNodeManager
: Gets the address of the user managing a node ID (endorsement) either through ownership or delegation
useVotingRewards
: Manages voting rewards functionality
useVotingRoundReward
: Handles rewards for specific voting rounds
Browser popup blocking can affect users using social login (Privy) when operations delay the signing popup.
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.
Fetching data after button click
API calls before signing
Any async operations between click and popup
Ensure all required data is loaded before the user clicks the button:
Load Data Early
Show Loading States
Avoid Async in Click Handlers
To test if your implementation avoids popup blocking:
Use social login (Privy)
Click transaction buttons
Popup should appear immediately
No browser blocking warnings
Form submissions: Validate and prepare data before submit
button is enabled
Token approvals: Pre-fetch allowance amounts
Multi-step transactions: Load all data for subsequent steps upfront
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.
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.
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.
v2 introduces significant improvements including the replacement of `Connex` with improved developer experience
Connex Removal: useConnex
is replaced with useThor
New Contract Interaction Patterns: Introduction of useCallClause
and executeMultipleClausesCall
Enhanced Transaction Building: New useBuildTransaction
hook with improved type safety
Improved Type Safety: Better TypeScript support with stricter typing
Create a git branch for migration
Identify all places where useConnex
is used (This is the main breaking change and easiest to find)
Run tsc
compiler to see all broken references
Fix the compiler errors and migrate incrementally. The new methods provide type-safe returns that will guide you
Verify functionality as you fix each error
Ensure you have adequate test coverage before starting
Documentation: Refer to individual migration guide sections
GitHub Issues:
Start with the guide to update your core dependencies and imports
Review to understand new interaction methods
Apply for optimal performance
Consult if you encounter issues
// Example usage of VeBetterDAO hooks
import {
useGMbalance,
useIsGMclaimable,
useSelectedGmNft,
useGetNodeManager,
useParticipatedInGovernance,
} from '@vechain/vechain-kit';
const ExampleComponent = () => {
const userAddress = "0x..."; // User's wallet address
// Check GM NFT balance
const { data: gmBalance } = useGMbalance(userAddress);
// Check if user can claim a GM NFT
const { isClaimable, isOwned } = useIsGMclaimable(userAddress);
// Get comprehensive GM NFT data
const {
gmId,
gmImage,
gmName,
gmLevel,
gmRewardMultiplier,
nextLevelGMRewardMultiplier,
isGMLoading,
isGMOwned,
b3trToUpgradeGMToNextLevel,
isEnoughBalanceToUpgradeGM,
missingB3trToUpgrade,
attachedNodeId,
isXNodeAttachedToGM,
maxGmLevel,
isMaxGmLevelReached,
} = useSelectedGmNft(userAddress);
// Get node manager
const { data: nodeManager } = useGetNodeManager(nodeId);
// Check governance participation
const { data: hasParticipated } = useParticipatedInGovernance(userAddress);
console.log(
'User GM NFTs:',
gmBalance,
'Can claim:',
isClaimable,
'Current GM Level:',
gmLevel,
'Node Manager:',
nodeManager,
'Has participated:',
hasParticipated
);
return (
// Your component JSX here
);
};
export default ExampleComponent;
// ✅ 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);
};
// 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
});
};
const MyComponent = () => {
const { data, isLoading } = useQuery(['requiredData'], fetchData);
return (
<button
onClick={() => sendTransaction(data)}
disabled={isLoading}
>
{isLoading ? 'Preparing...' : 'Send Transaction'}
</button>
);
};
// ❌ Avoid
onClick={async () => {
const result = await someAsyncOperation();
sendTransaction(result);
}}
// ✅ Better
onClick={() => {
sendTransaction(preLoadedData);
}}
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
// ...
};
New contract interaction patterns in VeChain Kit 2.0
import { VOT3__factory } from '@vechain/vechain-kit/contracts';
import { useCallClause, getCallClauseQueryKeyWithArgs } from '@vechain/vechain-kit';
const abi = VOT3__factory.abi;
const method = 'convertedB3trOf' as const;
export const useTokenBalance = (address?: string) => {
const { network } = useVeChainKitConfig();
const contractAddress = getConfig(network.type).contractAddress as `0x${string}`;
return useCallClause({
abi,
address: contractAddress,
method,
args: [address ?? ''],
queryOptions: {
enabled: !!address,
select: (data) => ({
balance: ethers.formatEther(data[0]),
formatted: humanNumber(ethers.formatEther(data[0])),
}),
},
});
};
import { useQuery } from '@tanstack/react-query';
import { executeMultipleClausesCall } from '@vechain/vechain-kit';
export const useMultipleTokenData = (addresses: string[]) => {
const thor = useThor();
return useQuery({
queryKey: ['MULTIPLE_TOKENS', addresses],
queryFn: async () => {
const results = await executeMultipleClausesCall({
thor,
calls: addresses.map((address) => ({
abi: ERC20__factory.abi,
functionName: 'balanceOf',
address: address as `0x${string}`,
args: [userAddress],
})),
});
return addresses.map((address, index) => ({
address,
balance: ethers.formatEther(results[index][0]),
}));
},
enabled: !!addresses.length,
});
};
import { useBuildTransaction, useWallet } from '@vechain/vechain-kit';
export const useTokenTransfer = () => {
const { account } = useWallet();
const thor = useThor();
return useBuildTransaction({
clauseBuilder: (recipient: string, amount: string) => {
if (!account?.address) return [];
const { clause } = thor.contracts
.load(tokenAddress, ERC20__factory.abi)
.clause.transfer(recipient, ethers.parseEther(amount));
return [{
...clause,
comment: `Transfer ${amount} tokens to ${recipient}`,
}];
},
onTxConfirmed: () => {
queryClient.invalidateQueries({ queryKey: ['TOKEN_BALANCE'] });
},
});
};
const useApproveAndSwap = () => {
const { account } = useWallet();
const thor = useThor();
return useBuildTransaction({
clauseBuilder: (tokenAddress: string, amount: string) => {
if (!account?.address) return [];
return [
// Approve
{
...thor.contracts
.load(tokenAddress, ERC20__factory.abi)
.clause.approve(swapAddress, ethers.parseEther(amount)).clause,
comment: 'Approve token spending',
},
// Swap
{
...thor.contracts
.load(swapAddress, SwapContract__factory.abi)
.clause.swap(tokenAddress, ethers.parseEther(amount)).clause,
comment: 'Execute swap',
},
];
},
});
};
const useContractCall = (address: string) => {
return useCallClause({
abi: ContractABI,
address: address as `0x${string}`,
method: 'getData',
args: [],
queryOptions: {
enabled: !!address,
retry: (failureCount, error) => {
if (error.message.includes('reverted')) return false;
return failureCount < 3;
},
},
});
};
The configuration system in VeChain Kit provides a comprehensive way to set up different network environments and contract addresses for VeChain applications.
Three network types are supported:
main
- VeChain mainnet
test
- VeChain testnet
solo
- Local development network
export type AppConfig = {
ipfsFetchingService: string;
ipfsPinningService: string;
vthoContractAddress: string;
b3trContractAddress: string;
vot3ContractAddress: string;
b3trGovernorAddress: string;
timelockContractAddress: string;
xAllocationPoolContractAddress: string;
xAllocationVotingContractAddress: string;
emissionsContractAddress: string;
voterRewardsContractAddress: string;
galaxyMemberContractAddress: string;
treasuryContractAddress: string;
x2EarnAppsContractAddress: string;
x2EarnCreatorContractAddress: string;
x2EarnRewardsPoolContractAddress: string;
nodeManagementContractAddress: string;
veBetterPassportContractAddress: string;
veDelegate: string;
veDelegateVotes: string;
veDelegateTokenContractAddress: string;
oracleContractAddress: string;
accountFactoryAddress: string;
cleanifyCampaignsContractAddress: string;
cleanifyChallengesContractAddress: string;
veWorldSubdomainClaimerContractAddress: string;
vetDomainsContractAddress: string;
vetDomainsPublicResolverAddress: string;
vetDomainsReverseRegistrarAddress: string;
vnsResolverAddress: string;
vetDomainAvatarUrl: string;
nodeUrl: string;
indexerUrl: string;
b3trIndexerUrl: string;
graphQlIndexerUrl: string;
network: Network;
explorerUrl: string;
};
// Basic Configuration Usage
import { getConfig, NETWORK_TYPE } from '@vechain-kit/core';
// Get config for specific network
const mainnetConfig = getConfig('main');
const testnetConfig = getConfig('test');
const soloConfig = getConfig('solo');
// Access network properties
console.log('Mainnet Node URL:', mainnetConfig.nodeUrl);
console.log('Testnet Explorer:', testnetConfig.explorerUrl);
// Access contract addresses
const {
vthoContractAddress,
accountFactoryAddress,
oracleContractAddress,
vetDomainsContractAddress
} = mainnetConfig;
// Access IPFS configuration
const {
ipfsFetchingService,
ipfsPinningService
} = mainnetConfig;
// Example: Create explorer link
const getExplorerUrl = (txId: string) =>
`${mainnetConfig.explorerUrl}/${txId}`;
Network configurations are immutable after initialization
Solo network is intended for local development
Contract addresses vary between networks
Explorer URLs are network-specific
Genesis blocks define network identity
Block time is standardized at 10 seconds
IPFS services may have rate limits
Some features may be testnet-only
Sign a string, eg "Hello VeChain", with the useSignMessage()
hook.
Use the useSignTypedData()
hook to sign structured data.
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.
Create a provider that will handle the signature verification.
Wrap the app with our new provider
Handle the signature within a custom hook
On your backend validate the signature as follows:
You should deprecate this:
VeChain Kit uses Chakra UI internally, which can cause style conflicts with Tailwind CSS, Bootstrap, and other CSS frameworks.
Your Tailwind utilities stop working
Unexpected borders on images
Wrong background colors or fonts
Button/form styles change unexpectedly
Use CSS layers to control which styles take precedence:
Framework-Specific Examples
Tailwind CSS
Tailwind v4
Bootstrap
Custom CSS
Quick Fixes for Common Issues
Verification Checklist
CSS layers defined: @layer vechain-kit, host-app
Framework styles wrapped in @layer host-app
VeChain Kit imported after layer definitions
Components render without style conflicts
No excessive !important
declarations needed
Debugging Steps
Check layer order in DevTools - Styles
panel shows which layer is applied
Verify import
order - CSS with layers must load before VeChain Kit
Test in isolation - Create a minimal component to identify conflicts
Browser Support
CSS layers are supported in all modern browsers:
Chrome 99+
Firefox 97+
Safari 15.4+
Edge 99+
The hooks provide authentication methods for VeChain applications:
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
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.
Here are some of the most commonly used 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.
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.
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.
'use client';
import { ReactElement, useCallback } from 'react';
import {
useSignMessage,
} from '@vechain/vechain-kit';
export function SigningExample(): ReactElement {
const {
signMessage,
isSigningPending: isMessageSignPending,
signature: messageSignature,
} = useSignMessage();
const handleSignMessage = useCallback(async () => {
try {
const signature = await signMessage('Hello VeChain!');
toast({
title: 'Message signed!',
description: `Signature: ${signature.slice(0, 20)}...`,
status: 'success',
duration: 1000,
isClosable: true,
});
} catch (error) {
toast({
title: 'Signing failed',
description:
error instanceof Error ? error.message : String(error),
status: 'error',
duration: 1000,
isClosable: true,
});
}
}, [signMessage, toast]);
return (
<>
<button
onClick={handleSignTypedData}
isLoading={isTypedDataSignPending}
>
Sign Typed Data
</button>
{typedDataSignature && (
<h4>
{typedDataSignature}
</h4>
)}
</>
);
}
'use client';
import { ReactElement, useCallback } from 'react';
import {
useWallet,
useSignTypedData,
} from '@vechain/vechain-kit';
// Example EIP-712 typed data
const exampleTypedData = {
domain: {
name: 'VeChain Example',
version: '1',
chainId: 1,
},
types: {
Person: [
{ name: 'name', type: 'string' },
{ name: 'wallet', type: 'address' },
],
},
message: {
name: 'Alice',
wallet: '0x0000000000000000000000000000000000000000',
},
primaryType: 'Person',
};
export function SigningTypedDataExample(): ReactElement {
const {
signTypedData,
isSigningPending: isTypedDataSignPending,
signature: typedDataSignature,
} = useSignTypedData();
const { account } = useWallet()
const handleSignTypedData = useCallback(async () => {
try {
const signature = await signTypedData(exampleTypedData, {
signer: account?.address
});
alert({
title: 'Typed data signed!',
description: `Signature: ${signature.slice(0, 20)}...`,
status: 'success',
duration: 1000,
isClosable: true,
});
} catch (error) {
alert({
title: 'Signing failed',
description:
error instanceof Error ? error.message : String(error),
status: 'error',
duration: 1000,
isClosable: true,
});
}
}, [signTypedData, toast, account]);
return (
<>
<button
onClick={handleSignTypedData}
isLoading={isTypedDataSignPending}
>
Sign Typed Data
</button>
{typedDataSignature && (
<h4>
{typedDataSignature}
</h4>
)}
</>
);
}
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>
</>
);
};
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 { useWallet, useSignTypedData } from "@vechain/vechain-kit";
import { useState, useCallback } from "react";
import { ethers } from "ethers";
// EIP-712 typed data structure
const domain = {
name: "MyAppName",
version: "1",
};
const types = {
Authentication: [
{ name: "user", type: "address" },
{ name: "timestamp", type: "string" },
],
};
export type SignatureVerificationValue = {
user: string;
timestamp: string;
};
export const useSignatureVerification = () => {
const { account } = useWallet();
const { signTypedData } = useSignTypedData();
const [isVerifying, setIsVerifying] = useState(false);
const [value, setValue] = useState<SignatureVerificationValue>({
user: "",
timestamp: "",
});
const signature = localStorage.getItem(`login_signature`);
const verifySignature = useCallback(async () => {
if (!account?.address || isVerifying) return;
setValue({
user: account.address,
timestamp: new Date().toISOString(),
});
const existingSignature = localStorage.getItem(`login_signature`);
if (existingSignature) return;
setIsVerifying(true);
try {
const signature = await signTypedData({
domain,
types,
message: value,
primaryType: "Authentication",
}, {
signer: account?.address
});
const isValid = ethers.verifyTypedData(domain, types, value, signature);
if (!isValid) {
throw new Error("Signature verification failed");
}
localStorage.setItem(`login_signature`, signature);
} catch (error) {
console.error("Signature verification failed:", error);
} finally {
setIsVerifying(false);
}
}, [account?.address, isVerifying, signTypedData, value]);
const hasVerified = account?.address ? !!localStorage.getItem(`login_signature`) : false;
return { hasVerified, isVerifying, signature, verifySignature, value };
};
import {ethers} from "ethers";
static async verifySignature(signature: string, value: { user: string , timestamp: string }): Promise<string> {
const domain = {
name: "My App Name",
version: "1",
};
const types = {
Authentication: [
{name: "user", type: "address"},
{name: "timestamp", type: "string"},
],
};
return ethers.verifyTypedData(domain, types, value, signature);
}
const signerAddress = await verifySignature(
signature,
value
);
if (signerAddress.toLowerCase() != address.toLowerCase()) {
return {
statusCode: 403,
body: JSON.stringify({
error: "Invalid signature",
}),
};
}
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>
}
/* In your global CSS file (e.g., globals.css) */
/* 1. Define layers with explicit priority */
@layer vechain-kit, host-app;
/* 2. Wrap your framework in host-app layer */
@layer host-app {
@tailwind base;
@tailwind components;
@tailwind utilities;
/* Your custom styles here */
}
@layer vechain-kit, host-app;
@layer host-app {
@tailwind base;
@tailwind components;
@tailwind utilities;
}
@layer vechain-kit, host-app;
/* Import Tailwind v4 */
@import "tailwindcss";
/* Your custom styles */
@layer host-app {
/* Custom overrides */
}
@layer vechain-kit, host-app;
@layer host-app {
@import 'bootstrap/dist/css/bootstrap.css';
}
@layer vechain-kit, host-app;
@layer host-app {
body {
/* Your body styles */
}
.your-components {
/* Component styles */
}
}
@layer host-app {
/* Fix image borders */
img {
border-style: solid !important;
}
/* Ensure body styles persist */
html body {
background: var(--your-bg);
font-family: var(--your-font);
}
}
// OAuth Types
type OAuthProvider = 'google' | 'twitter' | 'apple' | 'discord';
interface OAuthOptions {
provider: OAuthProvider;
}
interface UseLoginWithOAuthReturn {
initOAuth: (options: OAuthOptions) => Promise<void>;
}
interface UseLoginWithPasskeyReturn {
loginWithPasskey: () => Promise<void>;
}
interface UseLoginWithVeChainReturn {
login: () => Promise<void>;
}
// Example usage of Login hooks
import {
useLoginWithPasskey,
useLoginWithOAuth,
useLoginWithVeChain
} from '@vechain/vechain-kit';
const ExampleComponent = () => {
// Passkey authentication
const {
loginWithPasskey,
} = useLoginWithPasskey();
// OAuth authentication
const {
initOAuth,
} = useLoginWithOAuth();
// VeChain wallet authentication
const {
login: loginWithVeChain,
} = useLoginWithVeChain();
const handlePasskeyLogin = async () => {
try {
await loginWithPasskey();
console.log("Passkey login successful");
} catch (error) {
console.error("Passkey login failed:", error);
}
};
const handleOAuthLogin = async (provider: OAuthProvider) => {
try {
await initOAuth({ provider });
console.log(`${provider} OAuth login initiated`);
} catch (error) {
console.error("OAuth login failed:", error);
}
};
const handleVeChainLogin = async () => {
try {
await loginWithVeChain();
console.log("VeChain login successful");
} catch (error) {
console.error("VeChain login failed:", error);
}
};
return (
<div>
{/* Passkey Login */}
<button onClick={handlePasskeyLogin}>
Login with Passkey
</button>
{/* OAuth Login Options */}
<button onClick={() => handleOAuthLogin('google')}>
Login with Google
</button>
<button onClick={() => handleOAuthLogin('twitter')}>
Login with Twitter
</button>
<button onClick={() => handleOAuthLogin('apple')}>
Login with Apple
</button>
<button onClick={() => handleOAuthLogin('discord')}>
Login with Discord
</button>
{/* VeChain Wallet Login */}
<button onClick={handleVeChainLogin}>
Login with VeChain Wallet
</button>
</div>
);
};
export default ExampleComponent;
/*
Note: These hooks require:
- Privy configuration for OAuth and Passkey
- Valid VeChain network configuration
- For VeChain login:
- Valid VECHAIN_PRIVY_APP_ID
- Proper error handling for popup blockers
- Mobile browser compatibility handling
*/
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.
'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,
}}
/>
</>
);
}
You can build clauses with some of our available build functions, with our SDK or with connex.
If you want to interact directly with the user's smart account read the Smart Accounts 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.
// ✅ 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();
return sendTransaction(data);
};
display
Preferred capitalization
Luc.eth
avatar
Avatar or logo (see Avatars)
ipfs://dQw4w9WgXcQ
description
Description of the name
DevRel @ ENS Labs
keywords
List of comma-separated keywords
person, ens
Email address
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
The hooks provide tools for signing messages and typed data on VeChain:
useSignMessage
: Hook for signing plain text messages, supporting both Privy and VeChain wallets
useSignTypedData
: Hook for signing typed data (EIP-712), supporting both Privy and VeChain wallets
// Signing Types
interface SignTypedDataParams {
domain: {
name?: string;
version?: string;
chainId?: number;
verifyingContract?: string;
salt?: string;
};
types: Record<string, Array<{ name: string; type: string }>>;
message: Record<string, any>;
}
interface UseSignMessageReturnValue {
signMessage: (message: string) => Promise<string>;
isSigningPending: boolean;
signature: string | null;
error: Error | null;
reset: () => void;
}
interface UseSignTypedDataReturnValue {
signTypedData: (data: SignTypedDataParams) => Promise<string>;
isSigningPending: boolean;
signature: string | null;
error: Error | null;
reset: () => void;
}
// Example usage of Signing hooks
import { useSignMessage, useSignTypedData } from '@vechain/vechain-kit';
const ExampleComponent = () => {
// Example of signing a message
const {
signMessage,
isSigningPending: isMessagePending,
signature: messageSignature,
error: messageError,
reset: resetMessage
} = useSignMessage();
// Example of signing typed data
const {
signTypedData,
isSigningPending: isTypedDataPending,
signature: typedDataSignature,
error: typedDataError,
reset: resetTypedData
} = useSignTypedData();
const handleMessageSign = async () => {
try {
const signature = await signMessage("Hello VeChain!");
console.log("Message signature:", signature);
} catch (error) {
console.error("Message signing error:", error);
}
};
const handleTypedDataSign = async () => {
try {
const typedData = {
domain: {
name: 'MyDApp',
version: '1',
chainId: 1,
verifyingContract: '0x...'
},
types: {
Person: [
{ name: 'name', type: 'string' },
{ name: 'age', type: 'uint256' }
]
},
message: {
name: 'John Doe',
age: 30
}
};
const signature = await signTypedData(typedData);
console.log("Typed data signature:", signature);
} catch (error) {
console.error("Typed data signing error:", error);
}
};
return (
<div>
<button
onClick={handleMessageSign}
disabled={isMessagePending}
>
Sign Message
</button>
<button
onClick={handleTypedDataSign}
disabled={isTypedDataPending}
>
Sign Typed Data
</button>
{/* Display signatures */}
{messageSignature && (
<div>Message Signature: {messageSignature}</div>
)}
{typedDataSignature && (
<div>Typed Data Signature: {typedDataSignature}</div>
)}
{/* Display errors */}
{messageError && (
<div>Message Error: {messageError.message}</div>
)}
{typedDataError && (
<div>Typed Data Error: {typedDataError.message}</div>
)}
{/* Reset buttons */}
<button onClick={resetMessage}>Reset Message Signing</button>
<button onClick={resetTypedData}>Reset Typed Data Signing</button>
</div>
);
};
export default ExampleComponent;
/*
Note: These hooks require:
- Valid wallet connection (Privy or VeChain)
- For typed data signing:
- Valid EIP-712 typed data structure
- Proper domain specification
- Correct types definition
*/
VeChain Kit uses Chakra UI internally, which can cause styling conflicts with other CSS frameworks and custom styles.
CSS framework styles being overridden by VeChain Kit
VeChain Kit components not rendering correctly due to missing Chakra setup
Theme conflicts when your app also uses Chakra UI
Unexpected styling on images, buttons, or form elements
Install Chakra UI as a peer dependency even if you don't use it directly
Use CSS layers to control style precedence
Wrap your app in ChakraProvider with ColorModeScript
Setting up Chakra UI properly and resolving conflicts when your app also uses Chakra.
Using CSS layers to resolve conflicts with Tailwind, Bootstrap, and other CSS frameworks.
Can't find your issue? Search our GitHub Issues or ask on Discord.
The ProfileCard component provides a comprehensive display of user profile information, including avatar, domain, social links, and customization options.
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
// ProfileCard Types
interface ProfileCardProps {
// Required wallet address to display
address: string;
// Optional callback for edit button click
onEditClick?: () => void;
// Optional callback for logout button click
onLogout?: () => void;
// Toggle header visibility (default: true)
showHeader?: boolean;
// Toggle social links visibility (default: true)
showLinks?: boolean;
// Toggle description visibility (default: true)
showDescription?: boolean;
// Toggle display name visibility (default: true)
showDisplayName?: boolean;
// Toggle edit/logout buttons visibility (default: true)
showEdit?: boolean;
}
// Internal types used by the component
interface TextRecords {
display?: string;
description?: string;
email?: string;
url?: string;
'com.x'?: string;
}
interface WalletInfo {
address: string;
domain?: string;
image?: string;
isLoadingMetadata?: boolean;
metadata?: TextRecords;
}
// Example usage of ProfileCard component
import { ProfileCard } from '@vechain/vechain-kit';
const MyComponent = () => {
// Example wallet address
const walletAddress = "0x123...abc";
return (
<div style={{ maxWidth: '400px', margin: '0 auto' }}>
{/* Basic usage */}
<ProfileCard
address={walletAddress}
/>
{/* Full featured usage */}
<ProfileCard
address={walletAddress}
showHeader={true}
showLinks={true}
showDescription={true}
showDisplayName={true}
showEdit={true}
/>
</div>
);
};
export default MyComponent;
/*
Note: The component will automatically:
- Resolve VET domains for the address
- Fetch and display avatar
- Load text records for social links
- Handle dark/light mode
- Manage connected account state
*/
VeChain Kit includes built-in fee delegation handling. If you already have fee delegation in your app, you must remove it to avoid conflicts.
Using both your own fee delegation and VeChain Kit's delegation causes:
Transaction failures
Double delegation attempts
Multiple signatures on transactions
Incorrect transaction format
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:
<VeChainKitProvider
feeDelegation={{
delegatorUrl: process.env.NEXT_PUBLIC_DELEGATOR_URL,
delegateAllTransactions: false
}}
// ... other config
>
<App />
</VeChainKitProvider>
Delegation Options
delegatorUrl: Your fee delegation service URL
delegateAllTransactions: Set to true to delegate fees for all transactions, including VeWorld users
VeChain Kit handles all delegation logic internally
No additional delegation setup required
Works automatically with all supported wallets
To verify that delegation is working correctly:
Check that transactions only have one delegation signature
Monitor your delegation service logs
Ensure transactions complete successfully
"Transaction has multiple delegations": You still have custom delegation code active. Remove all custom delegation logic.
"Delegation failed": Check that your delegation URL is correct and the service is running.
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 VeChain Kit homepage to see all the components in action.
The hooks provide tools for interacting with the B3TR indexer service, offering sustainability and voting data:
useSustainabilityActions
: Fetches sustainability actions with pagination for a user or app, returning detailed impact data including:
Environmental metrics (carbon, water, energy)
Waste metrics (mass, items, reduction)
Social impact (biodiversity, people)
Resource conservation (timber, plastic)
Educational impact (learning time)
Activity metrics (trees planted, calories burned)
Energy metrics (clean energy production)
Health metrics (sleep quality)
useRoundAppVotes
: Retrieves voting results for a specific round, including:
Number of voters
Total votes cast
App-specific voting data
Round statistics
// Example usage of Indexer hooks
import {
useSustainabilityActions,
useRoundAppVotes
} from '@vechain/vechain-kit';
const ExampleComponent = () => {
// Get sustainability actions with pagination
const {
data: sustainabilityData,
hasNextPage,
fetchNextPage,
isLoading: isLoadingActions
} = useSustainabilityActions({
wallet: "0x...", // Optional: filter by wallet
appId: "app_id", // Optional: filter by app
direction: "desc", // Optional: sort direction
limit: 10 // Optional: items per page
});
// Get voting results for a specific round
const {
data: roundVotes,
isLoading: isLoadingVotes
} = useRoundAppVotes({
roundId: 1
});
// Handle loading more sustainability actions
const handleLoadMore = () => {
if (hasNextPage) {
fetchNextPage();
}
};
console.log(
'Sustainability Actions:', sustainabilityData?.pages,
'Has More Pages:', hasNextPage,
'Round Votes:', roundVotes
);
return (
// Your component JSX here
);
};
export default ExampleComponent;
/*
Note: These hooks require:
- A valid indexer URL configuration
- Appropriate network settings
- Valid input parameters (wallet address, appId, or roundId)
Sustainability data includes:
- Environmental metrics (carbon, water, energy)
- Waste metrics (mass, items, reduction)
- Social impact (biodiversity, people)
- Resource conservation (timber, plastic)
- Educational impact (learning time)
- Activity metrics (trees planted, calories burned)
- Energy metrics (clean energy production)
- Health metrics (sleep quality)
Round votes data includes:
- Number of voters
- Total votes cast
- App-specific voting data
- Round statistics
*/
The hooks provide tools for handling transactions on VeChain:
useSendTransaction
: Core hook for sending any transaction, supporting both Privy and VeChain wallets
useTransferERC20
: Hook for sending ERC20 token transfers
useTransferVET
: Hook for sending native VET token transfers
// Transaction Types
type TransactionStatus = 'ready' | 'pending' | 'waitingConfirmation' | 'success' | 'error';
type TransactionStatusErrorType = {
type: 'UserRejectedError' | 'RevertReasonError';
reason: string;
};
type EnhancedClause = {
to: string;
value?: string;
data?: string;
comment?: string;
abi?: any;
};
interface TransactionHookReturnValue {
sendTransaction: () => Promise<void>;
isTransactionPending: boolean;
isWaitingForWalletConfirmation: boolean;
txReceipt: Connex.Thor.Transaction.Receipt | null;
status: TransactionStatus;
resetStatus: () => void;
error?: TransactionStatusErrorType;
}
interface TransferVETParams {
fromAddress: string;
receiverAddress: string;
amount: string;
onSuccess?: () => void;
onError?: (error: Error) => void;
}
interface TransferERC20Params {
fromAddress: string;
receiverAddress: string;
amount: string;
tokenAddress: string;
tokenName: string;
onSuccess?: () => void;
onError?: (error: Error) => void;
}
interface SendTransactionParams {
signerAccountAddress: string;
clauses: EnhancedClause[];
onTxConfirmed?: () => void;
onTxFailedOrCancelled?: () => void;
}
// Example usage of Transaction hooks
import {
useSendTransaction,
useTransferERC20,
useTransferVET
} from '@vechain/vechain-kit';
const ExampleComponent = () => {
// Example of sending VET
const {
sendTransaction: sendVET,
isTransactionPending: isVETPending,
status: vetStatus,
error: vetError
} = useTransferVET({
fromAddress: "0x...",
receiverAddress: "0x...",
amount: "1.5",
onSuccess: () => console.log("VET transfer successful"),
onError: () => console.log("VET transfer failed")
});
// Example of sending ERC20 tokens
const {
sendTransaction: sendToken,
isTransactionPending: isTokenPending,
status: tokenStatus,
error: tokenError
} = useTransferERC20({
fromAddress: "0x...",
receiverAddress: "0x...",
amount: "100",
tokenAddress: "0x...",
tokenName: "TOKEN",
onSuccess: () => console.log("Token transfer successful"),
onError: () => console.log("Token transfer failed")
});
// Example of custom transaction
const {
sendTransaction: sendCustomTx,
isTransactionPending: isCustomPending,
status: customStatus,
error: customError
} = useSendTransaction({
signerAccountAddress: "0x...",
clauses: [
{
to: "0x...",
value: "0x0",
data: "0x...",
comment: "Custom transaction"
}
],
onTxConfirmed: () => console.log("Custom transaction successful"),
onTxFailedOrCancelled: () => console.log("Custom transaction failed")
});
const handleVETTransfer = async () => {
try {
await sendVET();
} catch (error) {
console.error("VET transfer error:", error);
}
};
const handleTokenTransfer = async () => {
try {
await sendToken();
} catch (error) {
console.error("Token transfer error:", error);
}
};
const handleCustomTransaction = async () => {
try {
await sendCustomTx();
} catch (error) {
console.error("Custom transaction error:", error);
}
};
return (
<div>
<button
onClick={handleVETTransfer}
disabled={isVETPending}
>
Send VET
</button>
<button
onClick={handleTokenTransfer}
disabled={isTokenPending}
>
Send Tokens
</button>
<button
onClick={handleCustomTransaction}
disabled={isCustomPending}
>
Send Custom Transaction
</button>
{/* Status displays */}
{vetStatus === 'pending' && <div>VET Transfer Pending...</div>}
{tokenStatus === 'pending' && <div>Token Transfer Pending...</div>}
{customStatus === 'pending' && <div>Custom Transaction Pending...</div>}
{/* Error displays */}
{vetError && <div>VET Error: {vetError.reason}</div>}
{tokenError && <div>Token Error: {tokenError.reason}</div>}
{customError && <div>Custom Error: {customError.reason}</div>}
</div>
);
};
export default ExampleComponent;
/*
Note: These hooks require:
- Valid thor connection
- Network configuration
- Valid wallet connection
- For ERC20 transfers: valid token contract address
- All amounts should be in their base units (e.g., wei for VET)
*/
A comprehensive collection of utility functions for VeChain development.
import {
compareAddresses,
compareListOfAddresses,
isValidAddress,
leftPadWithZeros
} from '@vechain-kit/utils';
// Compare two addresses (case-insensitive)
compareAddresses('0x123...', '0x123...'); // true
// Compare arrays of addresses
compareListOfAddresses(
['0x123...', '0x456...'],
['0x456...', '0x123...']
); // true
// Validate address format
isValidAddress('0x123...'); // true/false
// Pad address with leading zeros
leftPadWithZeros('0x123', 40); // '0x0000...0123'
import {
humanAddress,
humanDomain,
humanNumber,
getPicassoImage
} from '@vechain-kit/utils';
// Format address for display
humanAddress('0x123456789...', 6, 4); // '0x1234••••89ab'
// Format domain for display
humanDomain('very.long.domain.vet', 8, 6); // 'very.lon••••in.vet'
// Format numbers with optional symbol
humanNumber('1000000', null, 'VET'); // '1,000,000 VET'
humanNumber('1000000', 2); // '1,000,000.00'
// Generate avatar image from address
getPicassoImage('0x123...', false); // SVG data URL
// Hex Utilities
import {
removePrefix,
addPrefix,
isValid,
normalize,
compare,
generateRandom
} from '@vechain-kit/utils';
// Remove '0x' prefix
removePrefix('0x123'); // '123'
// Add '0x' prefix
addPrefix('123'); // '0x123'
// Validate hex string
isValid('0x123abc'); // true
// Normalize hex string (lowercase with prefix)
normalize('0X123ABC'); // '0x123abc'
// Compare hex strings
compare('0x123', '0X123'); // true
// Generate random hex
generateRandom(10); // '0x1234567890'
// IPFS Utilities
import {
validateIpfsUri,
toIPFSURL,
uploadBlobToIPFS,
ipfsHashToUrl
} from '@vechain-kit/utils';
// Validate IPFS URI
validateIpfsUri('ipfs://QmfSTia...'); // true
// Convert CID to IPFS URL
toIPFSURL('QmfSTia...', 'image.png'); // 'ipfs://QmfSTia.../image.png'
// Upload to IPFS
const hash = await uploadBlobToIPFS(blob, 'file.jpg', 'main');
// Convert IPFS hash to gateway URL
ipfsHashToUrl('QmfSTia...', 'main'); // https://gateway.ipfs.io/ipfs/QmfSTia...
// Media Type Resolver
import {
resolveMediaTypeFromMimeType,
NFTMediaType
} from '@vechain-kit/utils';
// Determine NFT media type
const imageType = resolveMediaTypeFromMimeType('image/jpeg'); // NFTMediaType.IMAGE
const videoType = resolveMediaTypeFromMimeType('video/mp4'); // NFTMediaType.VIDEO
const audioType = resolveMediaTypeFromMimeType('audio/mp3'); // NFTMediaType.AUDIO
const modelType = resolveMediaTypeFromMimeType('model/gltf-binary'); // NFTMediaType.MODEL
// Available media types
console.log(NFTMediaType.IMAGE); // 'image'
console.log(NFTMediaType.VIDEO); // 'video'
console.log(NFTMediaType.AUDIO); // 'audio'
console.log(NFTMediaType.MODEL); // 'model'
The hooks provide tools for interacting with IPFS (InterPlanetary File System):
useIpfsImage
: Fetches NFT media from IPFS, supporting various image formats (JPEG, PNG, GIF, etc.)
useIpfsImageList
: Fetches multiple IPFS images in parallel
useSingleImageUpload
: Handles single image upload with optional compression
useUploadImages
: Manages multiple image uploads with compression support
useIpfsMetadata
: Fetches and optionally parses JSON metadata from IPFS
useIpfsMetadatas
: Fetches multiple IPFS metadata files in parallel
// Example usage of IPFS hooks
import {
useIpfsImage,
useIpfsMetadata,
useSingleImageUpload,
useUploadImages
} from '@vechain/vechain-kit';
const ExampleComponent = () => {
// Fetch an NFT image from IPFS
const {
data: imageData,
isLoading: isImageLoading,
error: imageError
} = useIpfsImage("ipfs://...");
// Fetch metadata from IPFS
const {
data: metadata,
isLoading: isMetadataLoading,
error: metadataError
} = useIpfsMetadata<{
name: string;
description: string
}>("ipfs://...", true); // parse as JSON
// Handle single image upload
const {
uploadedImage,
onUpload: handleSingleUpload,
onRemove: handleSingleRemove,
isUploading: isSingleUploading
} = useSingleImageUpload({
compressImage: true,
defaultImage: undefined,
maxSizeMB: 0.4,
maxWidthOrHeight: 1920
});
// Handle multiple image uploads
const {
uploadedImages,
onUpload: handleMultipleUpload,
onRemove: handleMultipleRemove,
isUploading: isMultipleUploading
} = useUploadImages({
compressImages: true,
defaultImages: [],
maxSizeMB: 0.4,
maxWidthOrHeight: 1920
});
// Example upload handler
const handleFileSelect = async (event: React.ChangeEvent<HTMLInputElement>) => {
const file = event.target.files?.[0];
if (file) {
await handleSingleUpload(file);
}
};
// Example multiple files upload handler
const handleMultipleFileSelect = async (event: React.ChangeEvent<HTMLInputElement>) => {
const files = Array.from(event.target.files || []);
await handleMultipleUpload(files);
};
console.log(
'IPFS Image:', imageData,
'Loading Image:', isImageLoading,
'Metadata:', metadata,
'Loading Metadata:', isMetadataLoading,
'Uploaded Single Image:', uploadedImage,
'Single Upload in Progress:', isSingleUploading,
'Uploaded Multiple Images:', uploadedImages,
'Multiple Upload in Progress:', isMultipleUploading
);
return (
// Your component JSX here
);
};
export default ExampleComponent;
/*
Note: These hooks require:
- Valid IPFS gateway configuration
- Network type configuration
- For image hooks:
- Supported formats: JPEG, PNG, GIF, BMP, TIFF, WebP, SVG
- Maximum file size: 10MB
- For upload hooks:
- Default compression to 0.4MB
- Max width/height: 1920px
- Web Worker support for compression
*/
The hooks provide tools for interacting with VET domains and their functionality:
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
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
useClaimVeWorldSubdomain
: Claims a VeWorld subdomain with transaction handling and success/error callbacks
/**
* 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
*/