Only this pageAll pages
Powered by GitBook
1 of 54

V2

VeChain Kit

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...

Social Login

Loading...

Loading...

Loading...

Migrate Social Login Users

If you have an app with some custom login (eg: login with email, login with google, etc.) and you want to use this kit and migrate your users you will need to:

  • Create your Privy app

  • Manually add your users or use Privy's APIs

Privy/YourApp/Users/All Users/Add

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.

Migrations

Migrating Your App: From DAppKit or a Traditional Web2 Application

Follow these steps to seamlessly transition your application from DAppKit or a classic web2 framework.

Smart Accounts v1 to v3

Upgrading to unlocks multi-clause support, enhanced security, and other essential features for transactions on VeChain. This feature is available in VeChain Kit from .

When integrating social login in your app, users might have a version 1 smart account. This version doesn’t support multiclause transactions, potentially causing issues within your app.

To address this scenario, consider wrapping your onClick handler or using another suitable method to prompt users to upgrade their smart accounts to version 3.

The kit makes available both the hooks to know if the upgrade is required and the component for upgrading:

  • useUpgradeRequired(smartAccountAddress, ownerAddress, targetVersion: 3) is the hook that will let you know if the user is on v1 and needs to upgrade to v3

  • useUpgradeSmartAccountModal() is the hook that will allow you to open the upgrade modal, that the user will use to upgrade.

  • You can also handle this with your own UI by using the useUpgradeSmartAccount(smartAccountAddress, targetVersion: 3) hook.

View other useful hooks .

Example usage

You can customize the color button and size of the imported modal from the kit:

Example demo

With UI from the Kit

With custom UI

Migrate from DAppKit
Migrate Social Login Users
Smart Accounts v1 to v3
"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',
});
Smart Account v3
v1.5.0
here

Migration Issues

Common problems when upgrading to VeChain Kit from other solutions or managing dependencies

Quick Fixes

  • 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

Issues in This Section

Peer Dependencies

Resolving version conflicts and dependency mismatches when installing VeChain Kit.

From DApp 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.

Troubleshooting

Common issues and solutions when using VeChain Kit

Quick Links

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

Common Issue Categories

🔄 Migration Issues

Problems when upgrading from DApp Kit or managing dependencies

  • Peer Dependencies

  • From DApp Kit

🎨 Styling Issues

CSS conflicts and theming problems

  • Chakra UI Conflicts

  • CSS Framework Conflicts

⚙️ Integration Issues

Runtime and functionality problems

  • Fee Delegation

  • Privy Popup Blocking

Before You Start

  1. Check that you're using the latest version of VeChain Kit

  2. Ensure all peer dependencies are correctly installed

  3. Clear your build cache (rm -rf node_modules .next && npm install)


Can't find your issue? Search our GitHub Issues or ask on Discord.

veDelegate

VeDelegate Hooks

The hooks provide tools for interacting with VeDelegate token and functionality:

Token Hooks

  • useGetVeDelegateBalance: Retrieves the VeDelegate token balance for a given address

Usage Example

// 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.
*/

Chakra Conflicts

VeChain Kit components are wrapped in their own Chakra Provider (v2), ensuring consistent styling throughout the modals. This can cause conflicts if your app also uses Chakra UI.

Problem

  • VeChain Kit components not rendering correctly

  • Theme conflicts when your app uses Chakra UI

  • Missing styles or unexpected appearance

Solution

Install Chakra UI

Even if you don't use Chakra in your app, it's required as a peer dependency:

Setup ChakraProvider

Wrap your app with ChakraProvider and include ColorModeScript:

Key Requirements

The essential setup:

Framework Flexibility

You can keep using whatever frontend framework you prefer. The important part is defining the ChakraProvider wrapper for VeChain Kit components to function properly.

Common Issue

One common issue is missing the <ColorModeScript /> component, which can cause styling inconsistencies. Always include it within your ChakraProvider.

Peer Dependencies

Ensure that your project's peer dependencies align with VeChain Kit's specifications. Mismatched versions can cause installation failures, runtime errors, or unexpected behaviour.

Common Issues

  • Package installation fails with peer dependency warnings

  • Runtime errors about missing dependencies

  • Version conflicts between VeChain Kit and your existing packages

Solution

  1. Clean Installation

Often resolves dependency caching issues:

  1. Check Required Peer Dependencies

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

  1. Update or Downgrade Packages

You may need to adjust package versions to maintain compatibility:

Integration Issues

Runtime and functionality problems that occur when integrating VeChain Kit into your application.

Common Problems

  • Fee delegation conflicts with existing setup

  • Browser popup blocking for social login users

  • Transaction failures due to configuration issues

  • Signing delays causing security restrictions

Quick Fixes

  • 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

Issues in This Section

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]
Fee Delegation
Privy Popup Blocking
GitHub Issues
Discord
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>
One common issue, solvable by installing Chakra and defining <ColorModeScript />

Upgrade VeChain Kit from 1.x to 2.x

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.

Deprecated Hooks

Utils Hooks

  • useRoundAppVotes

  • useSustainabilityActions

Galaxy Member Hooks

  • useGMbalance

  • useB3trToUpgrade

  • useB3trToUpgradeToLevel

  • useGetNodeIdAttached

  • useGetTokenIdAttachedToNode

  • useGMMaxLevel

  • useParticipatedInGovernance

  • useTokenIdByAccount

  • useNFTImage

  • useB3trDonated

  • useGMBaseUri

  • useSelectedTokenId

  • useIsGMClaimable

  • useSelectedGmNft

  • useLevelOfToken

  • useNFTMetadataUri

NodeManagement

  • useGetNodeManager

  • useIsNodeHolder

  • useUserXNodes

VeBetterPassport

  • useAccountLinking

  • usePassportChecks

  • useUserDelegation

  • useUserStatus

  • useAppSecurityLevel

  • useGetCumulativeScoreWithDecay

  • useGetDelegatee

  • useGetDelegator

  • useGetEntitiesLinkedToPassport

  • useGetPassportForEntity

  • useGetPendingDelegationsDelegateePOV

  • useGetPendingDelegationsDelegatorPOV

  • useGetPendingLinkings

  • useIsEntity

  • useIsPassportCheckEnabled

  • useIsPassport

  • useParticipationScoreThreshold

  • useSecurityMultiplier

  • useThresholdParticipationScore

  • useThresholdParticipationScoreAtTimepoint

  • useIsBlacklisted

  • useIsWhitelisted

  • useUserRoundScore

VBD VoterRewards:

  • useLevelMultiplier

X2Earn Apps:

  • useUserVotesInAllRounds

  • useUserTopVotedApps

  • useXNode

  • useAppAdmin

  • useAppExists

  • useAppsEligibleInNextRound

  • useGetX2EarnAppAvailableFunds

  • useXAppsMetadataBaseUri

  • useXNodeCheckCooldown

XAllocation Voting

  • useAllocationAmount

  • useXAppVotesQf

Where you using any of those hooks? Please reach us on Github to add them back

From DApp Kit

If you're coming from DApp Kit or SDK, you may have version conflicts from different versions installed across your project.

VeChain Kit includes DApp Kit functionality, but still requires @vechain/dapp-kit-react as a peer dependency for compatibility.

Problem

Multiple versions of DApp Kit packages can cause:

  • Version conflicts between different parts of your project

  • Runtime errors from competing implementations

  • Unexpected behaviour from mixed package versions

Solution

  1. 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
  1. 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';
  1. Clean Installation

After removing old packages:

rm -rf node_modules package-lock.json
npm install

Component Mapping

DApp Kit
VeChain Kit

ConnectWallet

WalletButton

<other dapp-kit components>

<available through VeChain Kit exports>

Verification

Ensure clean migration:

  • No dapp-kit packages in package.json

  • All imports updated to use @vechain/vechain-kit

  • Component names updated to VeChain Kit equivalents

Quickstart

1) Install package

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.

2) Define Provider

'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,
    },
);

Login Methods

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
};

By default we have a list of default apps that will be shown as ecosystem login options. If you want to customize this list you can pass the allowedApps array prop. You can find the app ids in the Ecosystem tab in the Privy dashboard.

3) Setup Fee Delegation (mandatory if allowing social login)

Fee delegation is mandatory if you want to use this kit with social login. Learn how to setup fee delegation in the following guide:

4) Setup Privy (optional)

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

Pros of self hosting Privy:

  • No UI confirmations on users transactions

  • Allow your users to backup their keys and update security settings directly in your app

  • Targetted social login methods

Cons:

  • Price

  • Responsibilities to correctly secure your Privy account, since it contains access to user's wallet settings

  • Your users will need to login into other apps through ecosystem mode

5) Setup Legal Documents (optional)

To prompt users to review and accept your policies, like Terms and Conditions, Privacy Policy, or Cookie Policy, VeChainKit offers a simple plug-and-play solution.

You can avoid building your own if you haven't already.

By enabling also the tracking consent, you will allow VeChainKit to prompt your users to collect data to improve the kit.


When the legalDocuments option is configured, the users will see:

  • Left: A modal prompt when connecting their wallet, requiring them to review and accept required and optional legal documents.

  • Right: A summary view under Settings > General > Terms and Policies, showing which policies they’ve accepted and when.

Legal Docs Modal
Legal Docs Summary View

Important

Legal document agreements are tied to the wallet address, document type, document version, and the url.

  • If the version of any document is updated, users will be prompted to accept it again.

  • Agreements are stored in the browser’s local storage, meaning acceptance is per browser and device.

  • As a result, users may be prompted again if they switch browsers, devices, or clear their local storage even if they've previously agreed on another setup.

import { VechainKitProvider } from '@vechain/vechain-kit';

export default function App({ Component, pageProps }: AppProps) {
    return (
        <VechainKitProvider
            legalDocuments={{
                allowAnalytics: true, // Enables optional consent for VeChainKit tracking

                cookiePolicy: [
                    {
                        displayName: 'MyApp Policy', // (Optional) Custom display label
                        url: 'https://www.myapp.com/cookie-policy',
                        version: 1, // Increment to re-prompt users
                        required: false, // Optional: User sees a checkbox to opt in
                    },
                ],

                privacyPolicy: [
                    {
                        url: 'https://www.myapp.com/privacy-policy',
                        version: 1, // Increment to re-prompt users
                        required: false, // Optional: can be skipped or rejected
                    },
                ],

                termsAndConditions: [
                    {
                        displayName: 'MyApp T&C',
                        url: 'https://www.myapp.com/terms-and-conditions',
                        version: 1, // Increment to re-prompt users
                        required: true, // Required: must be accepted to proceed
                    },
                ],
            }}
            // ... other props
        >
            {children}
        </VechainKitProvider>
    );
}

Key Options

Option
Type
Required
Description

allowAnalytics

boolean

No

If true, prompts users with an optional tracking policy.

cookiePolicy

array

No

One or more cookie policy versions.

privacyPolicy

array

No

One or more privacy policy versions.

termsAndConditions

array

No

One or more T&C versions.

6) Show the login button

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.

You can use this component by importing it from the kit, it will handle for you the connection state and show a login button if the user is disconnected or the profile button when the user is connected.

'use client';

import { WalletButton } from '@vechain/vechain-kit';

export function Page() {
    return (
        <WalletButton />
    );
}

Read more here on how to customize this button.

Alternatively, you can create your own custom button and invoke the connect modal or account modal based on your needs.

'use client';

import { useConnectModal, useAccountModal, useWallet } from '@vechain/vechain-kit';

export function Page() {
    const { connection } = useWallet();
    
    const { 
        open: openConnectModal, 
        close: closeConnectModal, 
        isOpen: isConnectModalOpen 
    } = useConnectModal();
    
     const { 
        open: openAccountModal, 
        close: openAccountModal, 
        isOpen: isAccountModalOpen 
    } = useAccountModal();
    
    if (!connection.isConnected) {
        return (
            <button onClick={openConnectModal}> Connect </button>
        );
    }
    
    return (
        <button onClick={openAccountModal}> View Account </button>
    );
}

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.

Support for devs

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

Utils

Utility Hooks

The hooks provide utility functions for interacting with the VeChain network and tokens:

Network Utility Hooks

  • useGetChainId: Retrieves the chain ID from the genesis block of the connected network

  • useGetNodeUrl: Returns the node URL being used, either custom or default for the network

Token Utility Hooks

  • useGetCustomTokenBalances: Fetches balances for multiple custom tokens for a given address, returning original, scaled, and formatted values

  • useGetCustomTokenInfo: Retrieves token information (name, symbol, decimals) for a custom token address.

Legal Documents Hook

  • useLegalDocuments: Retrieves the user's agreement status for required and optional legal documents, including terms of service, privacy policy, and cookie policy.

Usage Example

Oracle

useGetTokenUsdPrice

A React hook that fetches the current USD price for supported tokens (B3TR, VET, VTHO) from the VeChain oracle contract.

Usage

Parameters

Fetch the price for the following token symbols: 'B3TR', 'VET', 'VTHO'.

Returns

The hook returns a TanStack Query result object with the following properties:

Property
Type
Description

Implementation Details

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

Example with Multiple Tokens

Smart Account

Hooks

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

Usage

Blockchain Hooks

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:

Utility Functions

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.

Transaction Toast

Use our pre built TransactionModal or TransactionToast components to show your users the progress and outcome of the transaction, or build your own UX and UI.

Usage example

Migrate from DAppKit

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)
here
here
'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
                }`
            />
        </>
    );
}
Fee Delegation

NFTs

NFT Hooks

The hooks provide tools for interacting with NFTs (Non-Fungible Tokens) on VeChain:

NFT Data Hooks

  • useNFTImage: Fetches NFT image and metadata from IPFS, handling the complete flow from token ID to final image

  • useNFTMetadataUri: Retrieves the metadata URI for an NFT using its token ID

Types

// 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

// 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;
    }[];
}
*/

Transaction Modal

Use our pre built TransactionModal or TransactionToast components to show your users the progress and outcome of the transaction, or build your own UX and UI.

Usage example

'use client';

import {
    useWallet,
    useSendTransaction,
    useTransactionModal,
    TransactionModal,
    getConfig
} from '@vechain/vechain-kit';
import { IB3TR__factory } from '@vechain/vechain-kit/contracts';
import { humanAddress } from '@vechain/vechain-kit/utils';
import { useMemo, useCallback } from 'react';

export function TransactionExamples() {
    const { account } = useWallet();
    const b3trMainnetAddress = getConfig("main").b3trContractAddress;
    
    const clauses = useMemo(() => {
        const B3TRInterface = IB3TR__factory.createInterface();

        const clausesArray: any[] = [];
        clausesArray.push({
            to: b3trMainnetAddress,
            value: '0x0',
            data: B3TRInterface.encodeFunctionData('transfer', [
                "0x0, // receiver address
                '0', // 0 B3TR (in wei)
            ]),
            comment: `This is a dummy transaction to test the transaction modal. Confirm to transfer ${0} B3TR to ${humanAddress("Ox0")}`,
            abi: B3TRInterface.getFunction('transfer'),
        });

        return clausesArray;
    }, [connectedWallet?.address]);

    const {
        sendTransaction,
        status,
        txReceipt,
        resetStatus,
        isTransactionPending,
        error,
    } = useSendTransaction({
        signerAccountAddress: account?.address ?? '',
    });

    const {
        open: openTransactionModal,
        close: closeTransactionModal,
        isOpen: isTransactionModalOpen,
    } = useTransactionModal();

    // This is the function triggering the transaction and opening the modal
    const handleTransaction = useCallback(async () => {
        openTransactionModal();
        await sendTransaction(clauses);
    }, [sendTransaction, clauses, openTransactionModal]);
    
    const handleTryAgain = useCallback(async () => {
        resetStatus();
        await sendTransaction(clauses);
    }, [sendTransaction, clauses, resetStatus]);

    return (
        <>
            <button
                onClick={handleTransactionWithModal}
                isLoading={isTransactionPending}
                isDisabled={isTransactionPending}
            >
                Send B3TR
            </button>

            <TransactionModal
                isOpen={isTransactionModalOpen}
                onClose={closeTransactionModal}
                status={status}
                txReceipt={txReceipt}
                txError={error}
                onTryAgain={handleTryAgain}
                uiConfig={{
                    title: 'Test Transaction',
                    description: `This is a dummy transaction to test the transaction modal. Confirm to transfer ${0} B3TR to ${
                        account?.address
                    }`,
                    showShareOnSocials: true,
                    showExplorerButton: true,
                    isClosable: true,
                }}
            />
        </>
    );
}

Open targeted modals

The hooks provide tools for managing various modals in the VeChain application:

Account Related Modals

  • 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

Wallet & Connection Modals

  • useConnectModal: Wallet connection modal

  • useWalletModal: Combined wallet management modal

  • useLoginModalContent: Login modal content configuration

Transaction Modals

  • useTransactionModal: Transaction confirmation and status

  • useTransactionToast: Transaction notifications

  • useSendTokenModal: Token transfer interface

  • useReceiveModal: Token receiving interface

Additional Feature Modals

  • useExploreEcosystemModal: VeChain ecosystem explorer

  • useNotificationsModal: Notification center

  • useFAQModal: Frequently asked questions

Types

Usage example

Troubleshooting

Common issues and solutions during migration from 1.x to 2.0

TypeScript Compilation Errors

Type Mismatch Errors

BigInt Serialization Errors

React Query Issues

Cache Invalidation Not Working

Contract Interaction Issues

Migration from manual ABI lookup:

Transaction Building Fails

Performance Issues

Too Many Network Requests

Network Issues

RPC Endpoint Problems

Testing Issues

Mocking Problems

// 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,
  }),
}));

Wallet

The useWallet hook provides a unified interface for managing wallet connections in a VeChain application, supporting multiple connection methods including social logins (via Privy), direct wallet connections (via DappKit), and cross-application connections.

This will be the hook you will use more frequently and it provides quite a few useful informations.

Usage

import { useWallet } from "@vechain/vechain-kit"; 

function MyComponent = () => {
    const {
        account,
        connectedWallet,
        smartAccount,
        privyUser,
        connection,
        disconnect
    } = useWallet();
    
    return <></>
}

Types

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;
}

Return values

account: Wallet

The primary account being used. This will be either:

  • The smart account (if connected via Privy)

  • The wallet address (if connected via DappKit)

smartAccount: SmartAccount

Information about the user's smart account:

  • address: The smart account address

  • domain: Associated VeChain domain name

  • image: Generated avatar image

  • isDeployed: Whether the smart account is deployed; smart accounts can be deployed on demand to avoid spending money on non active users. Learn more about smart accounts 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.

Connection Sources

The hook supports three main connection types:

  1. Social Login (privy): Authentication via social providers with your own APP_ID

  2. Wallet Connection (wallet): Direct wallet connection via DappKit

  3. Cross-App (privy-cross-app): Connection through ecosystem integration

Events

The hook dispatches a wallet_disconnected event when the wallet is disconnected, which can be used to trigger UI updates in dependent components.

Best Practices

Essential patterns for optimal performance, type safety, and maintainability

Type Safety

// ✅ 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");

Query Optimization

// ✅ 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
}

Data Transformation

// ✅ 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

Error Handling

// ✅ 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),
}

Query Key Management

// ✅ 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) });
  },
});

Performance Tips

// ✅ 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],
  })),
});

Security

// ✅ 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

Fee Delegation is mostly meant to be implemented to support end-user-experience, removing the need to purchase/collect gas tokens and remove need to pay for the applications activities.

Currently, it is mandatory to sponsor transaction for users that use social login. You can deploy your own custom solution or use vechain.energy.

FEE_DELEGATION_URL

You need to set this parameter in your VeChainKitProvider weapper.

You need one per environmnet.

To obtain one you can deploy your own custom solution or use vechain.energy.

Option 1: create your own

You can deploy this as a microservice (through lambda, cloudflare, etc.) or as an endpoint of your backend.

import { Address, HDKey, Transaction, Secp256k1, Hex } from '@vechain/sdk-core';

// the default signer is a solo node seeded account
const DEFAULT_SIGNER = 'denial kitchen pet squirrel other broom bar gas better priority spoil cross'

export async function onRequestPost({ request, env }): Promise<Response> {
    const body = await request.json()
    console.log('Incoming request', body);

    const signerWallet = HDKey.fromMnemonic((env.SIGNER_MNEMONIC ?? DEFAULT_SIGNER).split(' '), HDKey.VET_DERIVATION_PATH).deriveChild(0);
    if (!signerWallet.publicKey || !signerWallet.privateKey) { throw new Error('Could not load signing wallet') }

    const signerAddress = Address.ofPublicKey(signerWallet.publicKey)
    const transactionToSign = Transaction.decode(
        Buffer.from(body.raw.slice(2), 'hex'),
        false
    );
    const transactionHash = transactionToSign.getSignatureHash(Address.of(body.origin))
    const signature = Secp256k1.sign(transactionHash.bytes, signerWallet.privateKey)

    return new Response(JSON.stringify({
        signature: Hex.of(signature).toString(),
        address: signerAddress.toString()
    }), {
        status: 200,
        headers: {
            'Content-Type': 'application/json',
            'access-control-allow-origin': '*'
        }
    })
}

Option 2: use vechain.energy

GO TO TUTORIAL

Add this address 0xD7B96cAC488fEE053daAf8dF74f306bBc237D3f5 (MAINNET) or 0x7C5114ef27a721Df187b32e4eD983BaB813B81Cb (TESTNET) in {YourProjectName/FeeDelegation/Configurations/Smart Contract to sponsor all transactions.

// Smart contract to allow delegate all requests

/
pragma solidity ^0.8.20;

contract FeeDelegation {
    /**
     * @dev Check if a transaction can be sponsored for a user
     */
    function canSponsorTransactionFor(
        address _origin,
        address _to,
        bytes calldata _data
    ) public view returns (bool) {
        return true;
    }
}

Enable email notifications to let you know of low VTHO balance.

What is Fee Delegation?

Fee Delegation is a simple process that creates a magical user experience using blockchains without losing decentralized benefits.

The user submitting a transaction requests a second signature from a gas payer.

When the transaction is processed, the gas costs are taken from the gas payers balance instead of the user submitting the transaction.

It creates a feeless and trustless solution for instant blockchain access.

You can read more about it here: https://docs.vechain.org/thor/learn/fee-delegation (opens in a new tab)

Why is it important?

It stops users from instantly using an application. Users that need to pay for transactions are required to obtain a token from somewhere. Regular users do not know where to go and researching a place to buy and accessing a (de)centralized exchange is too much to ask for a user that wants to use your application.

What does it allow?

Users can open an application and interact with it instantly. An Application can submit transactions in the background and can no longer be be distinguished from regular(web2) alternatives. Fee delegation solves the biggest hurdle of user on - boarding to blockchain - applications without invalidating the web3 - principles.

Want to learn more?

You can learn more about the feature from docs.vechain.org on How to Integrate VIP-191 (opens in a new tab).

What are the use-cases?

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.

Intro

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.

Resources

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

Available imports

  • @vechain/vechain-kit

  • @vechain/vechain-kit/utils

  • @vechain/vechain-kit/contracts

  • @vechain/vechain-kit/assets

API Changes

Core API changes when migrating from VeChain Kit 1.x to 2.0

Update Dependencies

Deprecated/Removed Hooks

Complete list of removed hooks

Utils Hooks:

  • useRoundAppVotes

  • useSustainabilityActions

Galaxy Member Hooks:

  • useGMbalance

  • useB3trToUpgrade

  • useB3trToUpgradeToLevel

  • useGetNodeIdAttached

  • useGetTokenIdAttachedToNode

  • useGMMaxLevel

  • useParticipatedInGovernance

  • useTokenIdByAccount

  • useNFTImage

  • useB3trDonated

  • useGMBaseUri

  • useSelectedTokenId

  • useIsGMClaimable

  • useSelectedGmNft

  • useLevelOfToken

  • useNFTMetadataUri

NodeManagement:

  • useGetNodeManager

  • useIsNodeHolder

  • useUserXNodes

VeBetterPassport:

  • useAccountLinking

  • usePassportChecks

  • useUserDelegation

  • useUserStatus

  • useAppSecurityLevel

  • useGetCumulativeScoreWithDecay

  • useGetDelegatee

  • useGetDelegator

  • useGetEntitiesLinkedToPassport

  • useGetPassportForEntity

  • useGetPendingDelegationsDelegateePOV

  • useGetPendingDelegationsDelegatorPOV

  • useGetPendingLinkings

  • useIsEntity

  • useIsPassportCheckEnabled

  • useIsPassport

  • useParticipationScoreThreshold

  • useSecurityMultiplier

  • useThresholdParticipationScore

  • useThresholdParticipationScoreAtTimepoint

  • useIsBlacklisted

  • useIsWhitelisted

  • useUserRoundScore

VBD VoterRewards:

  • useLevelMultiplier

X2Earn Apps:

  • useUserVotesInAllRounds

  • useUserTopVotedApps

  • useXNode

  • useAppAdmin

  • useAppExists

  • useAppsEligibleInNextRound

  • useGetX2EarnAppAvailableFunds

  • useXAppsMetadataBaseUri

  • useXNodeCheckCooldown

XAllocation Voting:

  • useAllocationAmount

  • useXAppVotesQf

Update Imports

Update Contract Calls

Reading Data

Writing Data (Transactions)

Key Patterns

Type Safety

Query Enablement

Error Handling

Next Steps

  1. Review for detailed interaction examples

  2. Apply for optimal performance

  3. Verify all functionality works as expected

  4. Consult for common issues

Hooks

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.

Common Features

Every hook in the @api directory returns a consistent interface that includes:

  • data: The fetched data

  • isLoading: Boolean indicating if the request is in progress

  • isError: Boolean indicating if the request failed

  • error: Error object if the request failed

  • refetch: Function to manually trigger a new fetch

  • isRefetching: Boolean indicating if a refetch is in progress

Additionally, these hooks integrate with TanStack Query's global features:

  • Automatic background refetching

  • Cache invalidation

  • Optimistic updates

  • Infinite queries (for pagination)

  • Parallel queries

  • Query retrying

  • Query polling

Query Invalidation

All hooks use consistent query key patterns, making it easy to invalidate related queries. For example:

Caching Behavior

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
}
Contract Patterns
Best Practices
Troubleshooting
const queryClient = useQueryClient();
// Invalidate all blockchain queries
queryClient.invalidateQueries({ queryKey: ['VECHAIN_KIT'] });
// Invalidate specific query
queryClient.invalidateQueries({ queryKey: ['VECHAIN_KIT', 'CURRENT_BLOCK'] });
TanStack Query

WalletButton

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>
        </>
    );
}

Connection Types

VeChain Kit supports 3 types of connections:

1) Privy

This connection type is often used by organizations like VeBetterDAO, Cleanify, and Greencart. When connected, users can back up their embedded wallets, sign transactions without confirmation prompts, and add login methods. By connecting with Privy, developers use their personal APP_ID and CLIENT_ID to create their own app on Privy.

Pros of self hosting Privy:

  • No UI confirmations on users transactions

  • Allow your users to backup their keys and update security settings directly in your app

  • Targetted social login methods

Cons:

  • Price

  • Responsibilities to correctly secure your Privy account, since it contains access to user's wallet settings

  • Your users will need to login into other apps through ecosystem mode

2) Privy Cross App

When users integrate VeChain-kit using "Login with VeChain" and "Ecosystem" logins (eg: Mugshot and Greencart), this will be the default connection type. It is easily recognizable because login and wallet activities will open a secured popup window where the owner of the wallet will approve the actions.

With this type of connection, you can have social login in your app without actually paying Privy.

3) Self-custody wallets

This connection type allows login with self custody wallets, and is using the dapp-kit package under the hood.

The available wallets are: VeWorld mobile, VeWorld extension, Sync2, and Wallet Connect for VeWorld mobile.

Other wallets are available when login in with VeChain, such as Metamask, Rabby, Phantom, Coinbase Wallet, and Ranibow. That will use Privy under the hood though, which means the connection type will be "privy-cross-app".

If you want to use vechain-kit but do not care about social login then you can skip the first login modal and directly show the "Connect Wallet" modal like this:

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.

Smart Accounts

Our are a simplified version of the Account Abstraction pattern, made for the VeChain blockchain, and are required in order to enable social login.

Addresses

Mainnet

Testnet

Every wallet on VeChain owns a smart account. The address of your smart account is deterministic, and it can be deployed at any time, and receive tokens even if it is not deployed yet.

Testnet Factory address changed from v1 to v3 from 0x7EABA81B4F3741Ac381af7e025f3B6e0428F05Fb to 0x713b908Bcf77f3E00EFEf328E50b657a1A23AeaF which will cause all your testnet smart account addresses to change when upgrading to v1.5.0.

Contracts

There are 2 contracts that work together to enable social login and account abstraction:

  • SimpleAccount: A smart contract wallet owned by the user that can:

    • Execute transactions directly from the owner or through signed messages

    • Handle both single and batch transactions

  • SimpleAccountFactory: Factory contract that creates and manages SimpleAccount contracts:

    • Creates new accounts with deterministic addresses using CREATE2

    • Get the account address of a smart account without deploying it

    • Supports multiple accounts per owner through custom salts

    • Manages different versions of the SimpleAccount implementation

How it works

  1. 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.

  2. 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.

  3. 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

  4. Social Login Integration: This system enables social login by creating deterministic account addresses for each user and allowing transactions to be signed off-chain and executed by anyone. This creates a seamless experience where users can interact with dApps using their social credentials.

Version Management

The system has evolved through multiple versions to improve functionality and security:

  • SimpleAccount:

    • V1: Basic account functionality with single transaction execution

    • V2: Skipped for misconfiguration during upgrade

    • V3: Introduced batch transactions with nonce-based replay protection, ownership transfer and version tracking

  • SimpleAccountFactory:

    • V1: Basic account creation and management

    • V2: Added support for multiple accounts per owner using custom salts

    • V3: Support for V3 SimpleAccounts, enhanced version management and backward compatibility with legacy accounts

The factory maintains compatibility with all account versions, ensuring a smooth experience across different dApps and versions.

Testnet Factory address changed from v1 to v3 from 0x7EABA81B4F3741Ac381af7e025f3B6e0428F05Fb to 0x713b908Bcf77f3E00EFEf328E50b657a1A23AeaF which will cause all your testnet smart account addresses to change.

Smart Accounts V3 upgrade

Upgrading the user's smart accounts from V1 to V3 is mandatory, in order to protect against reply attacks and to allow multiclause transactions on VeChain.

To facilitate the mandatory upgrade process, the kit now includes:

  • Built-in Check: Automatically displays an alert prompting users to upgrade if they possess a V1 smart account.

  • Hooks & Component: To avoid apps spending VTHO to upgrade accounts of users that won't do any action on the app we allow the developer to check upgradeability on demand by using the useUpgradeRequiredForAccount hook and show the UpgradeSmartAccountModal (importable from the kit).

Hooks

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

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.

VeBetterDAO

VeBetterDAO Hooks

The hooks provide tools for interacting with VeBetterDAO's smart contracts and features:

Galaxy Member Hooks

  • 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

Node Management Hooks

  • useGetNodeManager: Gets the address of the user managing a node ID (endorsement) either through ownership or delegation

Rewards Hooks

  • useVotingRewards: Manages voting rewards functionality

  • useVotingRoundReward: Handles rewards for specific voting rounds

Usage Example

Privy Popup Blocking

Browser popup blocking can affect users using social login (Privy) when operations delay the signing popup.

Problem

When using Privy for social login (cross-app connection), browsers may block the confirmation popup if there's a delay between user action and popup trigger.

What Causes This

  • Fetching data after button click

  • API calls before signing

  • Any async operations between click and popup

Solution

Pre-fetch Data Before Transaction

Ensure all required data is loaded before the user clicks the button:

Best Practices

  1. Load Data Early

  1. Show Loading States

  1. Avoid Async in Click Handlers

Testing

To test if your implementation avoids popup blocking:

  1. Use social login (Privy)

  2. Click transaction buttons

  3. Popup should appear immediately

  4. No browser blocking warnings

Common Scenarios

  • 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

Embedded Wallets

When a user initiates a connection through Privy, either directly or via cross-app integration, a secure wallet is immediately created for them. Privy implements a sophisticated key management technique known as key splitting, specifically using Shamir’s secret sharing method. This approach ensures that users retain full custody of their wallets without needing to manage any secret keys themselves. Importantly, neither Privy nor any integrated application ever accesses the user's keys; these secrets are only reconstituted directly on the user's device during the signing of messages or transactions. This process guarantees the utmost security and privacy for the user's onchain activities.

This type of wallet created by Privy is called Embedded Wallet.

Seamless Wallet Integration

Users benefit from an intuitively integrated wallet management experience that aligns seamlessly with their existing accounts, removing any unnecessary technical barriers. Applications built with Privy can generate wallets automatically for new accounts, such as those registered with an email address or phone number, even before the user logs in for the first time. Additionally, Privy provides users with the option to export their wallet keys, serving as an escape mechanism should they choose to transition away from Privy’s services at any point.

Using wallets across apps

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 Migration

v2 introduces significant improvements including the replacement of `Connex` with improved developer experience

What's Changed in 2.0

Major Breaking Changes

  • 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

useCallClause is for reading data from smart contracts with automatic caching and refetching

executeMultipleClausesCall is to execute multiple contract calls in a single batch

Migration Path

Preparation Steps

  1. Create a git branch for migration

  2. Identify all places where useConnex is used (This is the main breaking change and easiest to find)

  3. Run tsc compiler to see all broken references

  4. Fix the compiler errors and migrate incrementally. The new methods provide type-safe returns that will guide you

  5. Verify functionality as you fix each error

  6. Ensure you have adequate test coverage before starting

Getting Help

  • Documentation: Refer to individual migration guide sections

  • GitHub Issues:

Next Steps

  1. Start with the guide to update your core dependencies and imports

  2. Review to understand new interaction methods

  3. Apply for optimal performance

  4. 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);
}}
Report issues
API Changes
Contract Patterns
Best Practices
Troubleshooting
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
    // ...
};
smart accounts
0xC06Ad8573022e2BE416CA89DA47E8c592971679A
0x713b908Bcf77f3E00EFEf328E50b657a1A23AeaF
variety of hooks
Example of the upgrade modal
​
Sample cross app wallet flow

Contract Patterns

New contract interaction patterns in VeChain Kit 2.0

Single Contract Call Pattern

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])),
      }),
    },
  });
};

Multiple Contract Calls Pattern

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,
  });
};

Transaction Building Pattern

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'] });
    },
  });
};

Multi-Clause Transactions

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',
        },
      ];
    },
  });
};

Error Handling

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;
      },
    },
  });
};

Configs

VeChain Kit Configuration

The configuration system in VeChain Kit provides a comprehensive way to set up different network environments and contract addresses for VeChain applications.

Network Types

Three network types are supported:

  • main - VeChain mainnet

  • test - VeChain testnet

  • solo - Local development network

Available data

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 Usage

// 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}`;

Notes

  • 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 Messages

Sign Message

Sign a string, eg "Hello VeChain", with the useSignMessage() hook.

Sign Typed Data (EIP712)

Use the useSignTypedData() hook to sign structured data.

Certificate Signing

To authenticate users in your backend (BE) and issue them a JWT (JSON Web Token), you may need to check that the connected user actually holds the private keys of that wallet (assuring he is not pretending to be someone else).

The recommended approach is to use signTypedData to have the user sign a piece of structured data, ensuring the signed payload is unique for each authentication request.

When using Privy the user owns a smart account (which is a smart contract), and he cannot directly sign a message with the smart account but needs to do it with his Embedded Wallet (the wallet created and secured by Privy). This means that when you verify identities of users connected with social login you will need to check that the address that signed the message is actually the owner of the smart account.

Example usage

Create a provider that will handle the signature verification.

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:

CSS Framework Conflicts

VeChain Kit uses Chakra UI internally, which can cause style conflicts with Tailwind CSS, Bootstrap, and other CSS frameworks.

Problem: Styles Being Overridden

Symptoms

  • Your Tailwind utilities stop working

  • Unexpected borders on images

  • Wrong background colors or fonts

  • Button/form styles change unexpectedly

Solution: CSS Layer Configuration

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

  1. Check layer order in DevTools - Styles panel shows which layer is applied

  2. Verify import order - CSS with layers must load before VeChain Kit

  3. 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+

Login

Login Hooks

The hooks provide authentication methods for VeChain applications:

Authentication Hooks

  • useLoginWithPasskey: Hook for authenticating using passkeys (biometric/device-based authentication)

  • useLoginWithOAuth: Hook for authenticating using OAuth providers (Google, Twitter, Apple, Discord)

  • useLoginWithVeChain: Hook for authenticating using VeChain wallet

Types

Usage example

Text Records (avatar & co.)

With every name come a set of records. These records are key value pairs that can be used to store information about the profile. Think of this as a user's digital backpack. Utalized for storage of preferences, public details, and more.

Types of Records

Here are some of the most commonly used records:

Name
Usage
Reference
Example

Other Records

Currently there are a few records that have been standardised. However you are welcome to store any key value pair you desire. We generally recommend to stick to a pattern, or prefix things with your app or protocol (eg. com.discord, or org.reddit), as such to avoid collisions.

Header/Banner Record

One of the newer standardised records is the "header" record. This header record, similar to the avatar record, accepts any IPFS, Arweave, EIP155, or regular URL to an image resource. The image is then displayed as a banner on the profile page and tends to be in a 1:3 aspect ratio.

Setting Records

When records are loaded they are loaded from the resolver responsible for the name. As resolvers are user controlled, we cannot guarantee a write function is available. This makes it a more in-depth process to update a users records.

This is protocol build by ENS domains and supported by veDelegate. Follow for more information.

Keep reading how to integrate with the VeChain Kit

'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
*/

Send Transactions

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

ENSIP-5

Luc.eth

avatar

Avatar or logo (see Avatars)

ENSIP-5

ipfs://dQw4w9WgXcQ

description

Description of the name

ENSIP-5

DevRel @ ENS Labs

keywords

List of comma-separated keywords

ENSIP-5

person, ens

email

Email address

ENSIP-5

[email protected]

mail

Physical mailing address

ENSIP-5

V3X HQ

notice

Notice regarding this name

ENSIP-5

This is a notice

location

Generic location (e.g. "Toronto, Canada")

ENSIP-5

Breda, NL

phone

Phone number as an E.164 string

ENSIP-5

+1 234 567 890

url

Website URL

ENSIP-5

https://ens.domains

header

Image URL to be used as a header/banner

ENSIP-18

ipfs://dQw4w9WgXcQ

ENS documentation
vetDomains
Profile Card
Setup Fee Delegation Service – docs.vechain.energy

Signing

Signing Hooks

The hooks provide tools for signing messages and typed data on VeChain:

Core Signing Hooks

  • 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

Types

Usage example

// 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
*/

Styling Issues

VeChain Kit uses Chakra UI internally, which can cause styling conflicts with other CSS frameworks and custom styles.

Common Problems

  • 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

Quick Fixes

  • 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

Issues in This Section

Chakra Conflicts

Setting up Chakra UI properly and resolving conflicts when your app also uses Chakra.

CSS Framework Conflicts

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.

Logo

Profile Card

ProfileCard Component

The ProfileCard component provides a comprehensive display of user profile information, including avatar, domain, social links, and customization options.

Features

  • Customizable header with generated background

  • Avatar display with domain integration

  • Social links integration (Email, Website, Twitter/X)

  • Address display with copy functionality

  • Edit and logout options for connected accounts

  • Dark/Light mode support

Props

Usage example

// 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
*/

Fee Delegation

VeChain Kit includes built-in fee delegation handling. If you already have fee delegation in your app, you must remove it to avoid conflicts.

Problem

Using both your own fee delegation and VeChain Kit's delegation causes:

  • Transaction failures

  • Double delegation attempts

  • Multiple signatures on transactions

  • Incorrect transaction format

Solution

  1. 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.

  1. 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>

  1. Delegation Options

  • delegatorUrl: Your fee delegation service URL

  • delegateAllTransactions: Set to true to delegate fees for all transactions, including VeWorld users

Important Notes

  • VeChain Kit handles all delegation logic internally

  • No additional delegation setup required

  • Works automatically with all supported wallets

Verification

To verify that delegation is working correctly:

  1. Check that transactions only have one delegation signature

  2. Monitor your delegation service logs

  3. Ensure transactions complete successfully

Common Errors

"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.

Components

Component Overview

The hook offers a versatile suite of components for seamless integration into web applications. Below are some of the key components:

  • WalletButton: Dynamically triggers either a login or account modal based on the user's connection status.

  • ProfileCard: Displays essential details, including an avatar, description, and social links linked to a user's address.

  • Transaction Visibility:

    • TransactionModal

    • TransactionToast

  • AccountModal: Allows users to explore specific sections as needed.

  • DAppKitWalletButton: Provides a focused interface for selecting wallet connection options.

These components collectively enhance user interaction and streamline.

Head over the VeChain Kit homepage to see all the components in action.

Indexer

Indexer Hooks

The hooks provide tools for interacting with the B3TR indexer service, offering sustainability and voting data:

Sustainability Hooks

  • 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)

Voting & Allocation Hooks

  • useRoundAppVotes: Retrieves voting results for a specific round, including:

    • Number of voters

    • Total votes cast

    • App-specific voting data

    • Round statistics

Usage Example

// 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
*/

Transactions

Transaction Hooks

The hooks provide tools for handling transactions on VeChain:

Core Transaction Hooks

  • 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

Types

// 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;
}

Usage example

// 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)
*/

Utils

VeChain Kit Utilities

A comprehensive collection of utility functions for VeChain development.

Address Utilities

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'

Formatting Utilities

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

// 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

// 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

// 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'

Ipfs

IPFS Hooks

The hooks provide tools for interacting with IPFS (InterPlanetary File System):

Image Hooks

  • 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

Metadata Hooks

  • useIpfsMetadata: Fetches and optionally parses JSON metadata from IPFS

  • useIpfsMetadatas: Fetches multiple IPFS metadata files in parallel

Usage Example

// 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
*/

vetDomains

VetDomains Hooks

The hooks provide tools for interacting with VET domains and their functionality:

Domain Resolution Hooks

  • useVechainDomain: Resolves VET domains to addresses and vice versa, returning domain info and validation status

  • useIsDomainProtected: Checks if a domain is protected from claiming

  • useGetDomainsOfAddress: Gets all domains owned by an address, with optional parent domain filtering

Domain Record Management Hooks

  • useGetTextRecords: Gets all text records for a domain

  • useGetAvatar: Gets the avatar URL for a domain. This hook will return directly the URL of the image, removing the need for developers to convert the URI to URL manually. The response can be null if the domain name does not exist or if there is no avatar attached to this domain.

  • useGetAvatarOfAddress : This hook will check if the address has any primary domain name set, if yes it will fetch and return the avatar URL (again, no need to manually convert URI to URL, the hook does it). If there is no domain name attached to it or if there is no avatar attached to the domain, it will return the Picasso image.

  • useGetResolverAddress: Gets the resolver contract address for a domain

  • useUpdateTextRecord: Updates text records for a domain with transaction handling

Subdomain Management Hooks

  • useClaimVeWorldSubdomain: Claims a VeWorld subdomain with transaction handling and success/error callbacks

Usage Example

/**
 * Domain Management Hooks
 * The hooks provide tools for interacting with VET domains and their functionality
 */

// Domain Resolution Hooks
- `useVechainDomain(addressOrDomain: string)`: 
  Returns { address?: string; domain?: string; isValidAddressOrDomain: boolean }
  // Resolves VET domains to addresses and vice versa

- `useIsDomainProtected(domain: string)`: 
  Returns boolean
  // Checks if a domain is protected from claiming

- `useGetDomainsOfAddress(address: string, parentDomain?: string)`: 
  Returns { domains: Array<{ name: string }> }
  // Gets all domains owned by an address

// Domain Record Management Hooks
- `useGetTextRecords(domain: string)`: 
  Returns TextRecords object
  // Gets all text records for a domain

- `useGetAvatar(domain: string)`: 
  Returns string | null
  // Gets the avatar URL for a domain

- `useGetResolverAddress(domain: string)`: 
  Returns string
  // Gets the resolver contract address for a domain

- `useUpdateTextRecord({
    onSuccess?: () => void,
    onError?: (error: Error) => void,
    signerAccountAddress?: string,
    resolverAddress: string
  })`: 
  Returns { sendTransaction, isTransactionPending, error }
  // Updates text records for a domain

// Subdomain Management Hooks
- `useClaimVeWorldSubdomain({
    subdomain: string,
    domain: string,
    onSuccess?: () => void,
    onError?: (error: Error) => void,
    alreadyOwned?: boolean
  })`: 
  Returns { sendTransaction, isTransactionPending, error }
  // Claims a VeWorld subdomain

// Example Usage
const ExampleComponent = () => {
    const address = "0x...";
    const domain = "example.vet";
    
    // Resolve domain or address
    const { data: domainInfo } = useVechainDomain(address);
    
    // Check domain protection
    const { data: isProtected } = useIsDomainProtected(domain);
    
    // Get owned domains
    const { data: ownedDomains } = useGetDomainsOfAddress(address);
    
    // Get domain records
    const { data: textRecords } = useGetTextRecords(domain);
    const { data: avatar } = useGetAvatar(domain);
    const { data: avatarOfAddress } = useGetAvatarOfAddress(address);
    
    // Update domain records
    const { sendTransaction: updateRecord } = useUpdateTextRecord({
        onSuccess: () => console.log("Record updated"),
        resolverAddress: "0x..."
    });
    
    // Claim subdomain
    const { sendTransaction: claimSubdomain } = useClaimVeWorldSubdomain({
        subdomain: "mysub",
        domain: "veworld.vet",
        onSuccess: () => console.log("Subdomain claimed")
    });

    console.log(
        'Domain Info:', domainInfo,
        'Is Protected:', isProtected,
        'Owned Domains:', ownedDomains,
        'Text Records:', textRecords,
        'Avatar:', avatar
    );

    return (
        // Your component JSX
    );
};

/*
Requirements:
- All hooks require a valid thor connection
- Appropriate network configuration must be set
- Valid input parameters must be provided
- Most hooks are part of the @tanstack/react-query ecosystem

Additional Notes:
- Domain resolution works for both .vet domains and addresses
- Text records can include various fields like email, description, url, etc.
- Subdomain claiming is specific to veworld.vet domains
- Protection status affects whether a domain can be claimed or transferred
*/