Skip to content

Wormhole Native Token Transfers on Algorand

This guide provides comprehensive technical documentation for implementing Wormhole’s Native Token Transfers (NTT) framework between Algorand and EVM-compatible chains. The implementation enables bidirectional token transfers across heterogeneous blockchain environments through Wormhole’s verified attestation architecture. This guide has been made considering we have an existing Algorand token. So, a new token is deployed on EVM in burning mode. You can deploy in locking/burning mode as per your requirement. The details of which mode you need is covered in this section.

Native Token Transfers (NTT) is Wormhole’s framework for transferring tokens across blockchains without requiring liquidity pools. Unlike traditional token bridges that wrap assets or rely on locked liquidity, NTT enables direct token transfers by either burning tokens on the source chain and minting them on the destination, or by locking tokens on one chain while minting representations on others.

The framework gives integrators complete control over how their tokens behave on each chain, including the token standard, metadata, and supply distribution. This level of control makes NTT particularly valuable for projects that want to maintain native token properties across multiple blockchains while ensuring security through Wormhole’s Guardian network.

The Transceiver contract serves as the foundational abstraction for all cross-chain communication within the NTT framework. It defines a standardized interface for sending and receiving messages between chains, independent of the underlying messaging protocol. They sit at the boundary between NTT and an underlying General Messaging Protocol (GMP) such as Wormhole, Axelar, LayerZero, or CCIP.

A transceiver does not understand tokens, balances, or rate limits. Its sole responsibility is to move opaque messages across chains and attest to their receipt. This abstraction allows the NTT framework to remain agnostic to the messaging layer. Wormhole is simply one concrete implementation.

The Wormhole Transceiver represents a concrete implementation of the Transceiver abstraction using the Wormhole messaging protocol. It bridges the generic Transceiver interface with Wormhole’s specific verification model, particularly the Verified Action Approval (VAA) system.

When a Wormhole Transceiver sends a message, it interacts with Wormhole Core to publish a message that will be observed and signed by Wormhole Guardians. This creates a VAA which is a cryptographically verified attestation that a specific action occurred on the source chain. The VAA serves as a decentralized notarization, with signatures from a supermajority of Guardians providing strong security guarantees.

On the receiving side, the Wormhole Transceiver must verify the VAA signatures, check for replay attacks, and validate that the message originates from a known peer contract. This verification process transforms a potentially untrustworthy cross-chain message into a verified fact that can be acted upon with confidence. Each WormholeTransceiver needs to know its counterpart on other chains. This is where we need to configure peer addresses.

The Transceiver Manager serves as the central coordination point for all cross-chain messaging within an NTT deployment. Its primary function is managing the relationship between MessageHandlers (like NttManager) and their configured Transceivers, while tracking attestations for received messages.

This design enables multi-transceiver security. A message handler can require attestations from one or many transceivers before a message is considered valid. This makes it possible to combine multiple GMPs or verification backends for stronger guarantees.

The Message Handler abstract contract provides the scaffolding for executing cross-chain messages once they’ve been sufficiently verified. It defines the critical lifecycle methods for message approval and execution, while leaving message-specific logic to concrete implementations.

It encapsulates the threshold logic, checking whether a message has received enough attestations from distinct Transceivers. It then carries out the actual business logic once approval conditions are met. This separation ensures that verification and execution remain distinct phases, preventing premature execution of unverified messages.

The NTT Manager is the central orchestrator of all token transfers and sits at the heart of the NTT system. When a user wants to transfer tokens cross-chain, they interact exclusively with the NTT Manager, which handles everything from rate limiting to message creation.

The manager’s first responsibility is enforcing rate limits, which protect against potential exploits by limiting how many tokens can move in or out within a given time period. If a transfer exceeds the current capacity, the manager can either reject it or place it in a queue, depending on configuration. This queuing system ensures that even during high-volume periods, transfers can eventually complete once capacity refills.

Beyond rate limiting, the NTT Manager creates unique message identifiers for each transfer by maintaining a sequence counter. This sequence, combined with the source chain ID and contract address, ensures every message can be uniquely identified throughout its lifecycle. The manager also maintains a registry of peer NTT Managers on other chains, knowing exactly where to send messages and from where to accept them.

When receiving messages from other chains, the NTT Manager verifies that sufficient attestations have been collected before executing the transfer. It checks with the Transceiver Manager to confirm that the required number of Transceivers have verified the incoming message. Only after this verification does it call the NTT Token contract to mint or unlock tokens for the recipient.

The NTT Token contract manages the actual token supply and is the lowest level of the NTT architecture. Its behavior differs significantly between Algorand and EVM chains due to fundamental differences in how these platforms handle tokens.

On Algorand, tokens exist as Algorand Standard Assets (ASAs) that are natively integrated into the protocol layer. The NTT Token contract doesn’t implement the token itself but rather controls the supply of an ASA. In burning mode, the ASA is created within the NTT Token contract, meaning the entire supply initially sits in the contract’s account. In locking mode, the ASA already exists externally, and the NTT Token contract receives tokens when they need to be locked.

On EVM chains, the NTT Token contract is an ERC-20 implementation. In burning mode, it includes mint and burn functions that the NTT Manager can call. In locking mode, it’s typically an existing ERC-20 contract, and the NTT Manager holds tokens on behalf of users who have transferred them to other chains.

The critical point is that after deployment, both modes function identically from the transfer perspective. Whether tokens are being burned or locked, the user experience is the same: tokens go into the NTT Token contract when leaving the chain and come out when arriving. The distinction is semantic and affects how circulating supply is calculated, but not the actual mechanics of transfers.

The lifecycle of a message can be broken down into two stages:

  1. Initiating the transfer on the source chain by locking/burning the token.
  2. Completing the transfer on the destination chain by unlocking/minting the token.
 Execute Transfer Result

The transfer process begins when a user wants to move tokens from one chain to another. On Algorand, the user creates a transaction group that includes both a call to the NTT Manager’s transfer function and an asset transfer that sends the tokens to the NTT Token contract. On EVM chains, the user must first approve the NTT Manager to spend their tokens, then call the transfer function.

The NTT Manager immediately checks its rate limit to determine if the transfer can proceed. If capacity is sufficient, the manager calls the NTT Token contract to lock or burn the tokens. The tokens are removed from circulation on the source chain before any message is sent to the destination chain. In burning mode, this reduces the total supply. In locking mode, it moves the tokens into the NTT Token contract’s custody. With the tokens secured, the NTT Manager creates a message containing all transfer details.

The message goes to Wormhole Core, which emits an event that Guardian nodes observe. The Guardians independently validate that the event came from a legitimate source and sign it. Once the threshold of signatures is reached, the VAA becomes available for anyone to query from Wormhole’s network. The transaction succeeds, the user receives a message ID for tracking, and the tokens have been removed from the source chain.

 Execute Transfer Result

Completion on the destination chain happens in two distinct phases:


The first phase is message reception and attestation. A relayer retrieves the VAA from Wormhole’s network and submits it to the Wormhole Transceiver on the destination chain. The Transceiver first calls Wormhole Core to verify the VAA’s Guardian signatures. The Transceiver also checks that the message came from a known peer Transceiver on the source chain and VAA hasn’t been processed before by checking its hash against a registry of seen messages. This replay protection ensures the same transfer can’t be executed multiple times. The Transceiver Manager records this attestation, associating it with the message digest.

The second phase is message execution. Once threshold has been met, the relayer, the recipient, or any third party, calls the NTT Manager which verifies the message hasn’t been executed already and the message came from a known peer NTT Manager on the source chain.

Rate limiting is checked on the destination chain and if it passes, the NTT Manager calls the NTT Token contract’s mint function, passing the recipient address and amount. In burning mode, this creates new tokens. In locking mode, it transfers existing tokens from the contract’s custody. The tokens appear in the recipient’s account, completing the transfer. The transfer is now complete end-to-end.

One of the most important design decisions when adopting NTT is whether you are working with a new token or an existing token. NTT is flexible enough to support both existing tokens and brand new multichain-native tokens. The main tool for this is the choice between locking and burning mode on each chain.

  1. If You Have an Existing Token on Algorand (ASA)
  • Algorand should use locking mode:
    • NttToken is configured to hold the existing ASA when users transfer cross-chain.
    • The original ASA continues to be the “canonical” representation of your token.
    • All initial supply is on Algorand, and other chains get supply only when Algorand locks some of its ASA balance and mint or unlock on the other chains.
  • All other chains should use burning mode:
    • On each destination chain, you deploy an NttToken that either wraps a newly created token or directly manages it.
    • The supply on each destination chain increases only when Algorand has locked the corresponding amount.
  1. If You Have an Existing Token on an EVM Chain (ERC-20)
  • The chain with the existing ERC-20 should use locking mode:
    • NttToken holds locked ERC-20 balances when users bridge out.
    • The original ERC-20 contract remains the canonical representation.
    • All initial supply starts and conceptually lives on that chain.
  • All other chains, including Algorand, should use burning mode:
    • Algorand gets an NttToken that manages an ASA in burning mode.
    • Supply on Algorand increases only when tokens have been locked on the origin chain.
  1. If You Are Creating a New Token
  • Every chain uses burning mode:
    • On Algorand, NttToken creates a new ASA and holds the supply.
    • On each EVM chain, NttToken deploys a new ERC-20 or equivalent managed token.
    • You decide how to distribute the initial supply across chains: mint everything on one chain and move it out via NTT, or mint portions on multiple chains according to your distribution plan.
  • Account with funds: Minimum 10 ALGO for contract deployment and testing
  • Save Account mnemonic: 25-word recovery phrase. This will be used later while bridging.
  • Wallet with ETH: Minimum 0.05 ETH for deployment and testing
  • Save Private key which is 64-character hex string. This will also be used later while bridging.
  • Basescan API key: From sepolia.basescan.org/apis
  1. ETH Faucets: Get some Base Sepolia Tokens using Coinbase Faucet (Recommended - gives 0.05-0.1 ETH). Other alternatives available are:
  1. Algorand Testnet ALGO: Get some Algorand testnet tokens using Algorand Testnet Dispenser
Terminal window
# Clone Folks Finance NTT Contracts repository
git clone https://github.com/Folks-Finance/algorand-ntt-contracts.git
cd algorand-ntt-contracts
# Create Python virtual environment
python3 -m venv venv
source venv/bin/activate # On Windows: venv\Scripts\activate
# Install Python dependencies
pip install -r requirements.txt
# Install Node.js dependencies
npm install

Generate TEAL code, ARC56 specs, and TypeScript clients

Terminal window
npm run build

This generates artifacts in the specs/ folder:

  • *.approval.teal - Contract approval programs
  • *.clear.teal - Contract clear state programs
  • *.arc56.json - Contract interface specifications
  • TypeScript client files for contract interaction

Update the .env file:

Terminal window
ALGORAND_TESTNET_ACCOUNT_MNEMONIC="your twenty five word mnemonic phrase here ..."

Option A: For a New ASA (Creating a new token)

Terminal window
npm run script deployment/deployNewToken.ts

Option B: For an Existing ASA (Using an existing Algorand token)

Terminal window
npm run script deployment/deployExistingToken.ts

Expected Output:

 Execute Transfer Result

For existing token, transfer entire non-circulating supply(apart from tokens you want to send during bridging) to NttToken contract

Terminal window
algokit goal asset send \
--from <YOUR_ADDRESS> \
--to <NTT_TOKEN_APP_ADDRESS> \
--assetid <ASA_ID> \
--amount <TOTAL_SUPPLY>

For existing token (optional), update the ASA reserve address to be the NttToken contract address

Terminal window
algokit goal asset config \
--assetid <ASA_ID> \
--manager <YOUR_ADDRESS> \
--reserve <NTT_TOKEN_APP_ADDRESS>
Terminal window
npm run script deployment/deployNttManager.ts

Expected Output:

 Execute Transfer Result
Terminal window
npm run script deployment/deployWormholeTransceiver.ts

Expected Output:

 Execute Transfer Result

After deployment, check deployment/out/contracts.json. The testnet should be populated with token, nttManager and wormholeTransceiver details.

 Execute Transfer Result
Terminal window
# Clone NTT repository
git clone --branch 'v1.6.0+cli' --single-branch --depth 1 \
https://github.com/wormhole-foundation/native-token-transfers.git
cd native-token-transfers
# Install Bun (if not already installed)
curl -fsSL https://bun.sh/install | bash -s "bun-v1.2.23"
# Install dependencies
npm ci
# Install NTT CLI globally
cd cli
./install.sh
# Verify installation
ntt --version
Terminal window
cd ~ # Or your preferred workspace
ntt new my-ntt-deployment
cd my-ntt-deployment
# Initialize for testnet
ntt init Testnet
Terminal window
git clone https://github.com/wormhole-foundation/example-ntt-token-evm.git
cd example-ntt-token-evm
forge install

Ensure token parameters match Algorand ASA and vice versa.

Terminal window
ASSET_NAME=<YOUR_ASSET_NAME>
UNIT_NAME=<YOUR_ASSET_UNIT_NAME>
DECIMALS=6 # Must match ASA decimals
DEPLOYER_ADDRESS=<YOUR_EVM_ADDRESS>
OWNER_ADDRESS=<YOUR_EVM_ADDRESS> # Can be same as deployer

Alternatively, if your token requires non-standard decimals (not 18), add to PeerToken.sol:

function decimals() public view virtual override(ERC20) returns (uint8) {
return 6; // Match your ASA decimals
}
Terminal window
forge create --rpc-url https://sepolia.base.org \
--private-key $EVM_TESTNET_PRIVATE_KEY \
src/PeerToken.sol:PeerToken \
--broadcast \
--constructor-args "$ASSET_NAME" "$UNIT_NAME" $DECIMALS $DEPLOYER_ADDRESS $OWNER_ADDRESS

Expected Output:

Deployment Result of Peer Token on EVM

Save the deployed token address!

Configure Contract Verification (Optional)

Section titled “Configure Contract Verification (Optional)”

For production deployments with verified contracts:

Terminal window
ntt config set-chain BaseSepolia verifier etherscan
ntt config set-chain BaseSepolia scan_api_key $BASESCAN_API_KEY
# Verify configuration
ntt config get-chain BaseSepolia scan_api_key

Without Verification:

Terminal window
ntt add-chain BaseSepolia \
--latest \
--mode burning \
--token $YOUR_EVM_TOKEN_ADDRESS \
--skip-verify

With Verification:

Terminal window
ntt add-chain BaseSepolia \
--latest \
--mode burning \
--token $YOUR_EVM_TOKEN_ADDRESS
Terminal window
ntt status

Expected Output:

 Execute Transfer Result

Check deployment.json in your NTT project. You should be able to see details of your deployed token.

 Execute Transfer Result

Save the addresses you get in your deployment.json file for peer configuration!

Configure Base Sepolia → Algorand Peering

Section titled “Configure Base Sepolia → Algorand Peering”

Copy the below addresses

  • From Base Sepolia (deployment.json): NttManager and WormholeTransceiver
  • From Algorand (deployment/out/contracts.json): nttManager.peerAddress and wormholeTransceiver.peerAddress
Terminal window
export PEER_ADDRESS=<YOUR_PEER_ADDRESS>
export TOKEN_DECIMALS=6 #Token decimals (MUST match ASA)
export NTTMANAGER=<YOUR_NTT_MANAGER>
cast send $NTTMANAGER \
"setPeer(uint16,bytes32,uint8,uint256)" \
8 \ # Algorand's Wormhole chain ID
$PEER_ADDRESS \
$TOKEN_DECIMALS \
1000000000000 \
--rpc-url https://sepolia.base.org \
--private-key $EVM_TESTNET_PRIVATE_KEY

Expected Output:

set peer result

Set WormholeTransceiver Peer on Base Sepolia

Section titled “Set WormholeTransceiver Peer on Base Sepolia”
Terminal window
cast send $WORMHOLE_TRANSCEIVER \
"setWormholePeer(uint16,bytes32)" \
8 \
$WORMHOLE_TRANSCEIVER_PEER_ADDRESS \
--rpc-url https://sepolia.base.org \
--private-key $EVM_TESTNET_PRIVATE_KEY

Once you set you can verify it using the below command:

Terminal window
cast call $WORMHOLE_TRANSCEIVER \
"getWormholePeer(uint16)(bytes32)" \
8 \
--rpc-url https://sepolia.base.org

Expected Output:

get womrhole peer result

Configure Algorand → Base Sepolia Peering

Section titled “Configure Algorand → Base Sepolia Peering”

Edit peers in algorand-ntt-contracts/deployment/configure.ts:

const peers: NttPeerChain[] = [
{
wormholeChainId: 10004, // Base Sepolia's Wormhole chain ID
nttManager: <PADDED_NTT_MANAGER_ADDRESS>,
wormholeTransceiver: <PADDED_WORMHOLE_TRANSCEIVER_ADDRESS>,
},
];
Terminal window
cd algorand-ntt-contracts
npm run script deployment/configure.ts

Expected Output:

get configure NTT result
Terminal window
# Clone the SDK repository
git clone https://github.com/Folks-Finance/wormhole-ntt-sdk.git
cd wormhole-ntt-sdk
# Install dependencies
npm install
# Build the SDK
npm run build

Go to examples/constants.ts file. Update your token details and contract addresses in CUSTOM_NTT_TOKEN_TESTNET_ID, CUSTOM_NTT_TOKEN_TESTNET_AVM and CUSTOM_NTT_TOKEN_TESTNET_EVM.

For transferring from avm to evm use examples/transfer-avm-to-evm-custom-token.ts. Update destChain according to the EVM testnet you are using. Since we are using base sepolia, we will update:

const destChain = TESTNET_FOLKS_CHAIN_ID.BASE_SEPOLIA;

Update YOUR_EVM_ADDRESS to the address you want to send the tokens as well as the amount(default is 1 TOKEN) to in convertToGenericAddress. This transfer function will transfer the TOKENS from Algorand to your configured EVM chain.

const prepareCall = await FolksBridge.prepare.transfer(
CUSTOM_NTT_TOKEN_TESTNET_ID,
1_000_000n,
destChain,
convertToGenericAddress(<YOUR_EVM_ADDRESS>, ChainType.EVM),
capabilities,
quote,
feePaymentToken
);

In case you don’t have FOLKS token to pay fees, you can update feePaymentToken to use native ALGO for fee payment

const feePaymentToken = {
tokenType: TokenType.GAS,
tokenSymbol: 'ALGO',
tokenDecimals: 6,
};

To transfer from evm to avm use examples/transfer-evm-to-avm-custom-token.ts. Update chain, sourceChain and folksChainId according to the EVM testnet you are using. Since we are using base sepolia, we will update:

const chain = baseSepolia;
const folksChainId = TESTNET_FOLKS_CHAIN_ID.BASE_SEPOLIA;
const sourceChain = TESTNET_FOLKS_CHAIN_ID.BASE_SEPOLIA;

Update YOUR_AVM_ADDRESS to the address you want to send the tokens as well as the amount(default is 1 TOKEN) to in convertToGenericAddress. This transfer function will transfer the TOKENS from configured EVM chain to Algorand.

const prepareCall = await FolksBridge.prepare.transfer(
CUSTOM_NTT_TOKEN_TESTNET_ID,
1_000_000n,
destChain,
convertToGenericAddress(<YOUR_AVM_ADDRESS>, ChainType.AVM),
capabilities,
quote,
feePaymentToken,
);

In case you don’t have FOLKS token to pay fees, you can update feePaymentToken to use native ETH token for fee payment

const feePaymentToken = {
tokenType: TokenType.GAS,
tokenSymbol: 'ETH',
tokenDecimals: 18,
};

copy the .env.example to .env file and update the private key for EVM Testnet and add Algorand Testnet Mnemonic:

Terminal window
EVM_TESTNET_PRIVATE_KEY=<YOUR_EVM_PRIVATE_KEY>
ALGORAND_TESTNET_ACCOUNT_MNEMONIC=<YOUR_ALGORAND_TESTNET_ACCOUNT_MNEMONIC>

Before running AVM to EVM transfer, ensure:

  1. Your Algorand Account has at least 10 ALGO
  2. You have enough custom ALGO token you want to send(excluding MBR)
  3. Set minter role to NTT Manager Address cast send $YOUR_EVM_TOKEN_ADDRESS "setMinter(address)" $NTT_MANAGER_ADDRESS --rpc-url https://sepolia.base.org --private-key $EVM_TESTNET_PRIVATE_KEY to mint tokens for your EVM_TESTNET_ADDRESS on destination chain.

Before running EVM to AVM transfer, ensure:

  1. At least 0.002 ETH (0.0015 for bridge fee + 0.0005 for gas)
  2. You have enough custom ETH token you want to send
  3. Must approve Folks Bridge contracts. If using other EVM chains, get the NTT_EXECUTOR_ADDRESS from nttWithExecutor.ts file: cast send $YOUR_EVM_TOKEN_ADDRESS "approve(address,uint256)" $BASE_SEPOLIA_NTT_EXECUTOR_ADDRESS $AMOUNT --rpc-url https://sepolia.base.org --private-key $EVM_TESTNET_PRIVATE_KEY
  4. Must approve NTTManager. Get $MANAGER address of EVM testnet from deployment.json file: cast send $YOUR_EVM_TOKEN_ADDRESS "approve(address,uint256)" $MANAGER $AMOUNT --rpc-url https://sepolia.base.org --private-key $EVM_TESTNET_PRIVATE_KEY
  5. Opt-in to ASA to receive assets on Algorand

Run the below command to transfer from evm to avm:

Terminal window
npx env-cmd tsx examples/transfer-evm-to-avm-custom-token.ts
Execute Transfer Result

Alternatively, you could use below command to transfer from avm to evm:

Terminal window
npx env-cmd tsx examples/transfer-avm-to-evm-custom-token.ts
Execute Transfer Result

Visit: https://wormholescan.io/#/tx/{YOUR_TX_HASH}?network=Testnet

Transfer Stages:

  1. Source Finalized - Transaction confirmed on Source Chain
  2. Guardian Signed - Wormhole guardians created VAA
  3. Transfer Finalized - Transfer tokens to Destination Chain
  4. Successful - Tokens transferred on Destination Chain to user account
 Execute Transfer Result

On completion the transaction status will change to successful. You should now be able to see the tokens being received in the destination account address.

 Execute Transfer Result

Figure: AVM to EVM transfer result

 Execute Transfer Result

Figure: EVM to AVM transfer result

OperationCommand
Mint Tokenscast send $TOKEN "mint(address,uint256)" $RECIPIENT $AMOUNT --rpc-url $RPC --private-key $KEY
Check token balancecast call $TOKEN "balanceOf(address)(uint256)" $ADDRESS --rpc-url $RPC
Approve spendercast send $TOKEN "approve(address,uint256)" $SPENDER $AMOUNT --rpc-url $RPC --private-key $KEY
Check allowancecast call $TOKEN "allowance(address,address)(uint256)" $OWNER $SPENDER --rpc-url $RPC
Check decimalscast call $TOKEN "decimals()(uint8)" --rpc-url $RPC
Check total supplycast call $TOKEN "totalSupply()(uint256)" --rpc-url $RPC
Check ETH balancecast balance $ADDRESS --rpc-url $RPC
Send ETHcast send $RECIPIENT --value 0.1ether --rpc-url $RPC --private-key $KEY
Get noncecast nonce $ADDRESS --rpc-url $RPC
Get transaction receiptcast receipt $TX_HASH --rpc-url $RPC
Get peer configurationcast call $WORMHOLE_TRANSCEIVER "getWormholePeer(uint16)(bytes32)" $CHAIN_ID --rpc-url $RPC
Check if peer is setcast call $NTT_MANAGER "getPeer(uint16)((bytes32,uint8,uint256))" $CHAIN_ID --rpc-url $RPC
Get rate limit infocast call $NTT_MANAGER "getCurrentOutboundCapacity()(uint256)" --rpc-url $RPC \ cast call $NTT_MANAGER "getCurrentInboundCapacity(uint16)(uint256)" $CHAIN_ID --rpc-url $RPC
ETH balancecast balance $YOUR_EVM_ADDRESS --rpc-url https://sepolia.base.org
Token balancecast call $TOKEN_ADDRESS "balanceOf(address)(uint256)" $YOUR_EVM_ADDRESS --rpc-url https://sepolia.base.org
Token allowance for FolksBridgecast call $TOKEN_ADDRESS "allowance(address,address)(uint256)" $YOUR_EVM_ADDRESS $BASE_SEPOLIA_NTT_EXECUTOR_ADDRESS --rpc-url https://sepolia.base.org
Check WormholeTransceiver peer on Base Sepolia cast call $WORMHOLE_TRANSCEIVER_ADDRESS "getWormholePeer(uint16)(bytes32)" 8 --rpc-url https://sepolia.base.org
Transfer Failed?
├─> Insufficient funds error?
| ├─> Check source chain
| ├─> Algorand: Check ALGO balance(need 10 ALGO)
| ├─> Get funds from [Lora](https://lora.algokit.io/testnet/fund)
│ ├─> ETH: Check ETH balance (need 0.002+ ETH)
│ └─> Get funds from Coinbase or any other faucet
├─> InsufficientAllowance (0xfb8f41b2)?
│ ├─> Approve FolksBridge Contract
│ └─> Approve NttManager Address
├─> InsufficientBalance (0xe450d38c)?
│ ├─> Check token balance
│ ├─> Mint/Transfer more tokens if needed
│ └─> Use ETH/ALGO for fees instead of FOLKS
├─> Invalid private key?
│ ├─> Ensure 0x prefix
│ └─> Verify 64 hex characters
├─> Decimal mismatch?
│ ├─> Check both chains have same decimals
│ └─> Override decimals() in PeerToken.sol
├─> Peer not configured?
│ ├─> Verify setPeer on both chains
│ ├─> Check 32-byte padding for EVM addresses
│ └─> Run configure.ts on Algorand
├─> VAA not delivered?
| ├─> Transaction successful on EVM chain?
| └─> Check WormholeScan for VAA Status
|
├─> Transfer Got Stuck at Finalised?
| |-> NTT Address has minter role?
| └─> Grant minter role to NTTManager

You have successfully deployed a cross-chain NTT token bridge between Base Sepolia and Algorand Testnet. Once you are confident with the bidirectional transfers on testnet you can start preparing for mainnet deployment with updated configurations