Skip to content

SDK Reference

TokenboundClient

The TokenboundClient class provides an interface for interacting with tokenbound accounts, enabling operations like account creation, transaction execution, token transfers (including ERC-721, ERC-1155, and ERC-20 tokens), and message signing.

The client is instantiated with an object containing two parameters:

Parameter
One of signer or walletClientmandatory
One of chainId or chainmandatory

Use either a viem walletClient (see walletClient docs) or an Ethers signer (see signer docs) for transactions that require a user to sign. Note that viem is an SDK dependency, so walletClient is preferable for most use cases. Use of Ethers signer is recommended only for legacy projects.

The TokenboundClient is configured to use the Version 3.1 ERC-6551 contract deployments → by default.

For instructions about using a Custom Account Implementation and/or a Legacy V2 Tokenbound Account Implementation, see the Advanced Usage section at the bottom of this document.

Standard configuration

If you're using one of the standard V2/V3 ERC-6551 contract deployments →, you can simply pass thechainId. This will set Chain internally using imports from viem/chains. To keep the bundle size to a minimum, only standard chains are included in the SDK package.

import { useAccount, WalletClient } from 'wagmi'
import { TokenboundClient } from '@tokenbound/sdk'
 
const { address } = useAccount()
const walletClient: WalletClient = createWalletClient({
  chainId: goerli,
  account: address,
  transport: http(),
})
 
const tokenboundClient = new TokenboundClient({ walletClient, chainId: 5 })

Custom chain

If your chain isn't listed on the deployments page →, you'll need to pass the full Chain object from the viem/chains package using the chain parameter.

import { zora } from 'viem/chains'
const tokenboundClient = new TokenboundClient({ walletClient, chain: zora })

Using Ethers.js

Ethers 5 / 6 are supported as an alternative to viem.

const { data: signer } = useSigner()
const tokenboundClient = new TokenboundClient({ signer, chainId: 1 })

Making your first call

Now you can use the TokenboundClient to interact with the Tokenbound contracts:

const tokenboundClient = new TokenboundClient({ walletClient, chainId: 1 })
 
const tokenboundAccount = tokenboundClient.getAccount({
  tokenContract: '<token_contract_address>',
  tokenId: '<token_id>',
})
 
console.log(tokenboundAccount) //0x1a2...3b4cd

For easy reference, we've prepared code examples for a few simple SDK interactions.

TokenboundClient SDK Methods

The TokenboundClient enables creation of and interaction with Tokenbound accounts:

prepareCreateAccount

Prepares an account creation transaction to be submitted via sendTransaction

Returns a promise resolving to a prepared transaction that can be used to create a Tokenbound account for a given token contract and token ID.

When using the standard V3 implementation, this will be a MultiCallTx that will create and initialize the account in one pass. If using a custom account implementation with V3, a basic prepared transaction will be returned in the form {to, value, data}, and the created account will need to be initialized in a second step.

If using the legacy V2 implementation, the return will be a standard object of the form {to, value, data}, and account initialization is handled for you.

const preparedAccount = await tokenboundClient.prepareCreateAccount({
  tokenContract: '<token_contract_address>',
  tokenId: '<token_id>',
})
 
console.log(preparedAccount) //0x1a2...3b4cd
ParameterDescriptionType
tokenContractThe address of the token contract.string
tokenIdThe token ID.string
saltThe salt used to create a unique account address (optional)number
chainIdThe id of the chain on which the account will exist (optional)number
appendedCallsAn array of calls to execute via Multicall3 (optional)Call3[]

See Appending Calls To Account Creation for appendedCalls documentation.


createAccount

Creates a tokenbound account for an NFT. The deterministic address is calculated using the create2 opcode using the listed parameters along with chainId and implementation address. createAccount adds the account to the registry and initializes it for use. Prior to account creation, the address can already receive assets. Deploying the account allows the NFT's owner to interact with the account.

Returns an object containing the account address of the tokenbound account created and the hash of the transaction. If an account already exists, the existing account is returned.

const { account, txHash } = await tokenboundClient.createAccount({
  tokenContract: '<token_contract_address>',
  tokenId: '<token_id>',
})
 
console.log(account) //0x1a2...3b4cd
ParameterDescriptionType
tokenContractThe address of the token contract.string
tokenIdThe token ID.string
saltThe salt used to create a unique account address (optional)number
chainIdThe id of the chain on which the account will exist (optional)number
appendedCallsAn array of calls to execute via Multicall3 (optional)Call3[]

See Appending Calls To Account Creation for appendedCalls documentation.


getAccount

Gets the tokenbound account address for an NFT.

Returns the tokenbound account address for a given token contract and token ID.

const tokenboundAccount = tokenboundClient.getAccount({
  tokenContract: '<token_contract_address>',
  tokenId: '<token_id>',
})
 
console.log(tokenboundAccount) //0x1a2...3b4cd
ParameterDescriptionType
tokenContractThe address of the token contract.string
tokenIdThe token ID.string
saltThe salt used when the account was created (optional)number

checkAccountDeployment

Check if the tokenbound account address has been activated using createAccount.

Returns a boolean indicating if a tokenbound account has been deployed (created) at the accountAddress

const SAPIENZ_GOERLI_TOKEN_TBA_TOKENID_0 =
  '0x33D622b211C399912eC0feaaf1caFD01AFA53980' as `0x${string}`
 
const isAccountDeployed = await tokenboundClient.checkAccountDeployment({
  accountAddress: SAPIENZ_GOERLI_TOKEN_TBA_TOKENID_0,
})
 
console.log('IS SAPIENZ 0 DEPLOYED?', isAccountDeployed) //...
ParameterDescriptionType
accountAddressThe Tokenbound account address.string

getNFT

Extracts information about the origin NFT that is paired with the tokenbound account.

Returns a Promise that resolves to a TokenboundAccountNFT object. The TokenboundAccountNFT object contains the following properties:

  • tokenContract: The token contract address
  • tokenId: The token ID
  • chainId: The chain ID
const nft = await tokenboundClient.getNFT({
  accountAddress: '<account_address>',
})
 
const { tokenContract, tokenId, chainId } = nft
 
console.log({ tokenContract, tokenId, chainId })
ParameterDescriptionType
accountAddressThe Tokenbound account address.string

prepareExecution

Prepares an arbitrary contract call for execution against any contract.

Note: this method replaces the deprecated V2 method prepareExecuteCall.

Returns A Promise with prepared transaction to execute a call on a Tokenbound account. Can be sent via sendTransaction on an Ethers signer or via WalletClient.

const preparedExecution = await tokenboundClient.prepareExecution({
  account: '<account_address>',
  to: '<contract_address>',
  value: '<wei_value>',
  data: '<encoded_call_data>',
})
 
console.log(preparedExecution) //...
ParameterDescriptionType
accountThe Tokenbound account address.string
toThe contract address.string
valueThe value to send, in wei.bigint
data (optional)The ABI-encoded call datastring
chainId (optional)The ID of the chain the transaction should be executed on (optional). If supplied, this will send a cross-chain transaction from the TBA via a bridgestring

execute

Performs an arbitrary contract call against any contract. This means any onchain action you can perform with your EOA wallet can be done with your NFT's Tokenbound account. You can mint or transfer NFTs, approve contracts, make and vote on DAO proposals, and much more.

Note: this method replaces the deprecated V2 method executeCall.

Returns a hash of the transaction that executed a call using a Tokenbound account.

const executedCall = await tokenboundClient.execute({
  account: '<account_address>',
  to: '<contract_address>',
  value: '<wei_value>',
  data: '<encoded_call_data>',
})
 
console.log(executedCall)
ParameterDescriptionType
accountThe Tokenbound account address.string
toThe contract address.string
valueThe value to send, in wei.bigint
data (optional)The ABI-encoded call data.0x{string}
chainId (optional)The ID of the chain the transaction should be executed on (optional). If supplied, this will send a cross-chain transaction from the TBA via a bridgestring

Here's a more robust example, where we see how to use your TBA to mint an NFT using Zora's ERC721Drop contract by calling the contract's purchase function.

// Webb's First Deep Field (unlimited mint drop):
// https://zora.co/collect/eth:0x28ee638f2fcb66b4106acab7efd225aeb2bd7e8d
 
const zora721 = {
  abi: zora721DropABI,
  proxyContractAddress: getAddress('0x28ee638f2fcb66b4106acab7efd225aeb2bd7e8d'),
  mintPrice: BigInt(0),
  quantity: 2,
  tbaAddress: getAddress('0xc33f0A7FcD69Ba00b4e980463199CD38E30d0E5c'),
}
 
const encodedMintFunctionData = encodeFunctionData({
  abi: zora721.abi,
  functionName: 'purchase',
  args: [BigInt(zora721.quantity)],
})
 
const mintToTBATxHash = await tokenboundClient.execute({
  account: zora721.tbaAddress,
  to: zora721.proxyContractAddress,
  value: zora721.mintPrice * BigInt(zora721.quantity),
  data: encodedMintFunctionData,
})

isValidSigner

Checks if a tokenbound account has signing authorization. This determines whether the active WalletClient or Signer can be used to sign transactions on behalf of the TBA.

Returns a Promise that resolves to true if the account is a valid signer, otherwise false

NOTE: This method is not available to V2-based implementations

const isValidSigner = await tokenboundClient.isValidSigner({
  account: ZORA721_TBA_ADDRESS,
})
 
console.log('isValidSigner?', isValidSigner)
ParameterDescriptionType
accountThe Tokenbound account address.string

transferNFT

Transfer an NFT to a recipient from a Tokenbound account

Returns a Promise that resolves to the transaction hash of the transfer

const transferNFT = await tokenboundClient.transferNFT({
  account: '<account_address>',
  tokenType: 'ERC721',
  tokenContract: '<nft_contract_address>',
  tokenId: '<nft_token_id>',
  recipientAddress: '<recipient_address>',
})
 
console.log(transferNFT) //...
ParameterDescriptionType
accountThe Tokenbound account address.string
tokenTypeToken type: 'ERC721' or 'ERC1155'string
tokenContractThe address of the token contract.string
tokenIdThe tokenId of the NFT.string
recipientAddressThe recipient address or ENS.string
amountThe number of tokens to send (1155 only).number
chainId (optional)The ID of the chain the transaction should be executed on (optional). If supplied, this will send a cross-chain transaction from the TBA via a bridgestring

transferETH

Transfer ETH to a recipient from a Tokenbound account

Returns a Promise that resolves to the transaction hash of the transfer

const transferETH = await tokenboundClient.transferETH({
  account: '<tokenbound_account_address>',
  amount: 0.01,
  recipientAddress: '<recipient_address>',
})
 
console.log(transferERC20) //...
ParameterDescriptionType
accountThe Tokenbound account address.string
amountAmount, in decimal form (eg. 0.01 ETH).number
recipientAddressThe recipient address or ENS.string
chainId (optional)The ID of the chain the transaction should be executed on (optional). If supplied, this will send a cross-chain transaction from the TBA via a bridgestring

transferERC20

Transfer ERC-20 tokens to a recipient from a Tokenbound account

Returns a Promise that resolves to the transaction hash of the transfer

const transferERC20 = await tokenboundClient.transferERC20({
  account: '<tokenbound_account_address>',
  amount: 0.1,
  recipientAddress: '<recipient_address>',
  erc20tokenAddress: '<erc20_token_address>',
  erc20tokenDecimals: '<erc20_token_decimals>',
})
 
console.log(transferERC20) //...
ParameterDescriptionType
accountThe Tokenbound account address.string
amountAmount, in decimal form (eg. 0.1 USDC).number
recipientAddressThe recipient address or ENS.string
erc20tokenAddressThe ERC-20 token address.string
erc20tokenDecimalsThe ERC-20 token decimal specification (1-18).number
chainId (optional)The ID of the chain the transaction should be executed on (optional). If supplied, this will send a cross-chain transaction from the TBA via a bridgestring

deconstructBytecode

Deconstructs the bytecode of a Tokenbound account into its constituent parts.

Returns a Promise that resolves to a SegmentedERC6551Bytecode object, or null if the account is not deployed. The SegmentedERC6551Bytecode object contains the following properties:

  • erc1167Header: ERC-1167 Header
  • implementationAddress: The ERC-6551 implementation address
  • erc1167Footer: ERC-1167 Footer
  • salt: The salt value
  • tokenId: The token ID
  • tokenContract: The token contract address
  • chainId: The chain ID
const segmentedBytecode = await tokenboundClient.deconstructBytecode({
  accountAddress: '<account_address>',
})
 
console.log(segmentedBytecode)

| Parameter | Description | Type |

| ------------------ | ------------------------------- | ------ | | accountAddress | The Tokenbound account address. | string |


signMessage

Gets an EIP-191 formatted signature for a message.

Returns a Promise that resolves to a signed Hex string

The message to be signed is typed as UniversalSignableMessage so that it can elegantly handle Ethers 5, Ethers 6, and viem's expected types for all signable formats. Check the types associated with signMessage for viem, Ethers 5, and Ethers 6 as needed.

// Ethers 5
const arrayMessage: ArrayLike<number> = [72, 101, 108, 108, 111] // "Hello" in ASCII
 
// Ethers 5 or Ethers 6
const uint8ArrayMessage: Uint8Array = new Uint8Array([72, 101, 108, 108, 111]) // "Hello" in ASCII

Note that this method is just for convenience. Since your EOA wallet is responsible for signing, messages can also be signed explicitly using your EOA wallet address in viem or Ethers.

const signedMessage = await tokenboundClient.signMessage({
  message: 'Ice cream so good',
})
 
console.log(signedMessage)
 
// Works in Ethers 5 or 6, throws in viem
const signedUint8Message = await tokenboundClient.signMessage({
  message: uint8ArrayMessage,
})
 
console.log(signedUint8Message)
 
// Works in viem
const signedRawUint8Message = await tokenboundClient.signMessage({
  message: { raw: uint8ArrayMessage },
})
 
console.log(signedUint8Message)
ParameterDescriptionType
messageThe message to be signed.UniversalSignableMessage

Advanced Usage

Custom Account Implementation

If your team has deployed a custom account implementation contract, you'll need to point the SDK to your custom implementation instead of the default implementation.

If your custom implementation uses the legacy V2 account logic, you'll also need to supply a version parameter to instruct the TokenboundClient to make use of the V2 methods.

import { TokenboundClient } from '@tokenbound/sdk'
 
const tokenboundClient = new TokenboundClient({
  walletClient: '<walletClient>',
  chainId: '<chainId>',
  implementationAddress: '<custom_implementation_address>',
})
 
// Custom implementation AND custom registry (uncommon for most implementations)
const tokenboundClientWithCustomRegistry = new TokenboundClient({
  walletClient: '<walletClient>',
  chainId: '<chainId>',
  implementationAddress: '<custom_implementation_address>',
  registryAddress: '<custom_registry_address>',
})

Legacy V2 Tokenbound Account Implementation

If your application was created using the standard legacy V2 account implementation (see 0.2.0 →), you'll need to instruct the TokenboundClient to use it by specifying the TBVersion

import { TokenboundClient, TBVersion } from '@tokenbound/sdk'
const tokenboundClient = new TokenboundClient({
  walletClient,
  chainId: 1,
  version: TBVersion.V2,
})

Appending Calls To Account Creation

You can make your first transaction using a newly-created TBA by appending a call to createAccount's internal multicall sequence for execution after the account creation and initialization steps. To determine your account address before it has been created, use the getAccount method. You can then use the account as the Call3 target.

The Tokenbound SDK uses a fork of Multicall3 with support for authenticated calls. The value of msg.sender is appended to the calldata of each call in the style of ERC-2771, allowing contract recipients to verify the multicall sender.

See the Multicall3 docs for detailed info regarding this approach. Pay special attention to the notices re: contract writes.

Here's an example that uses your just-deployed token bound account to make a transaction.

import { Call3 } from '@tokenbound/sdk'
import { encodeFunctionData } from 'viem'
 
const tokenboundAccount = tokenboundClient.getAccount({
  tokenContract: "<token_contract_address>",
  tokenId: "<token_id>",
})
 
// Let's claim an ERC-1155 token from one of ThirdWeb's DropERC1115 contract deployments
// https://thirdweb.com/thirdweb.eth/DropERC1155
 
const maxClaimablePerWallet = 1
const pricePerToken = 0
const quantity = 1
const currencyAddress = '0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE' // ETH
 
// Configure the arguments for the claim
const claimConfig = {
  receivingTBA: tokenboundAccount,
  pricePerToken,
  quantity,
  tokenId: 0,
  currencyAddress,
  allowListProof: {
    proof: [],
    quantityLimitPerWallet: maxClaimablePerWallet ?? 1,
    pricePerToken,
    currency: currencyAddress,
  },
  data: '0x',
}
 
// Encode function data for use in prepareExecution call
const encodedClaimFunctionData = encodeFunctionData({
  abi: rewardContractABI,
  functionName: 'claim',
  args: [
    claimConfig.receivingTBA,
    claimConfig.tokenId,
    claimConfig.quantity,
    claimConfig.currencyAddress,
    claimConfig.pricePerToken,
    claimConfig.allowListProof,
    claimConfig.data,
  ],
})
 
// Prepare execution call via Tokenbound account
const preparedExecution = await tokenboundClient.prepareExecution({
  account: tokenboundAccount,
  to: CLAIM_CONTRACT_ADDRESS,
  value: 0n,
  data: encodedClaimFunctionData,
})
 
// Assemble a Call3 call that can be used by createAccount's internal Multicall3 invocation
const appendedCall: Call3 = {
  target: tokenboundAccount, // <-- Execute with TBA contract
  allowFailure: false,
  callData: preparedExecution.data, // <-- Encoded TBA 'execute' function data
}
 
const { account, txHash } = await tokenboundClient.createAccount({
  tokenContract: "<token_contract_address>",
  tokenId: "<token_id>",
  appendedCalls: [appendedCall] // <-- Call(s) to be executed sequentially after account creation
})
 
console.log(account) //0x1a2...3b4cd

Custom PublicClient or RPC URL

If using viem, you can specify a custom PublicClient RPC URL for use by the TokenboundClient's internal PublicClient.

Alternately, you can simply configure and pass your own publicClient. This option was added to enable internal testing on local chains.

Parameter
publicClientRPCUrloptional
publicClientoptional
import { TokenboundClient } from '@tokenbound/sdk'
 
const tokenboundClient = new TokenboundClient({
  walletClient: '<walletClient>',
  chainId: '<chainId>',
  publicClientRPCUrl: '<custom_rpc_url>',
})