← Articles
Wormhole NTT: native SPL tokens across chains logo
solana

Wormhole NTT: native SPL tokens across chains

· SEP 15, 2026 ·
Read
Share

Deploy a manager per token. Pick burning or locking. Rate limits sit on the manager. The CLI writes deployment.json. The SDK builds transfer_burn or transfer_lock.

devrels.xyz/a/315

Wormhole Native Token Transfers is the framework for moving an SPL or ERC-20 as itself across chains. You keep metadata, ownership, and upgradeability. Supply stays unified. There is no pool in the middle.

On Solana the live surface is an Anchor manager, a Wormhole transceiver, the NTT CLI, and @wormhole-foundation/sdk-solana-ntt. Docs last updated 7 August 2026. Wrapped Token Transfers is the other Wormhole token product, the Portal-style wrap. Use NTT when the token should exist natively on each chain.

Two modes

Pick this before you touch mint authority. Hub-and-spoke keeps total supply on one chain. Burn-and-mint spreads supply across every chain that can mint.

NTT deployment modes
ModeWhat happensWhen to use it
Hub-and-spoke (locking)Tokens lock in the manager on the hub. Spokes mint and burn against that custody.Existing SPL mint. You do not want to move mint authority. Solana as hub is the usual layout.
Burn-and-mint (burning)Source burns. Destination mints. Supply lives on every chain that has a manager.New token, or you will hand mint authority to the NTT token-authority PDA or an SPL multisig that includes it.

Pairing is strict. If the source manager is locking, destination managers must be burning. The native-token-transfers README calls that out as a hard warning. A locking hub plus a locking spoke will not redeem.

What you deploy on Solana

Each token gets its own NTT manager program. The example crate example-native-token-transfers declares nttiK1SepaQt6sZ4WGW5whvc9tEnGXGxuKeptcQPCcS and versions itself 3.0.0. Production deploys do not share that id. ntt add-chain takes --program-key, so every integrator has a distinct manager. Do not treat the example id as a global singleton.

The manager owns the token, peers, rate limits, and transceiver registry. The Wormhole transceiver posts and receives Guardian messages. One manager, one token. Several transceivers are allowed. Threshold attestation is M of N. Replay protection drops a second delivery of the same message.

SPL and Token-2022 both work. NTT v2.0.0+solana and later also accept transfer hooks. Solana's official testnet cluster is unsupported. Use Devnet tokens when ntt init Testnet points at SVM.

CLI path

The CLI is the supported way to write deployment.json and push config on-chain. Install needs Bun 1.2.23 on PATH.

bash
curl -fsSL https://raw.githubusercontent.com/wormhole-foundation/native-token-transfers/main/cli/install.sh | bash
ntt --version
ntt new my-ntt-project
cd my-ntt-project
ntt init Mainnet
# or: ntt init Testnet

Grind a program key that starts with ntt, then add Solana. --latest pulls current artifacts. --mode is burning or locking.

bash
solana-keygen grind --starts-with ntt:1 --ignore-case
ntt add-chain Solana --latest --mode burning \
  --token INSERT_TOKEN_ADDRESS \
  --payer INSERT_YOUR_KEYPAIR_JSON \
  --program-key INSERT_YOUR_NTT_PROGRAM_KEYPAIR_JSON
# locking hub instead:
# ntt add-chain Solana --latest --mode locking --token ... --payer ... --program-key ...

Optional --solana-priority-fee is microlamports. Default 50000. Mainnet must use a custom RPC through overrides.json. Docs point at Triton or Helius staked endpoints. After deploy:

bash
ntt status
ntt pull
ntt push --payer INSERT_YOUR_KEYPAIR_JSON

ntt status diffs local deployment.json against chain. ntt pull copies chain into the file. ntt push writes the file out. Inbound and outbound limits default to 0. You must set them before the deployment is usable. SVM amounts in that file use nine decimals. EVM amounts use 18. Confirm the mint's real decimals on each explorer before you type those strings.

Burning on Solana also needs mint authority on the token-authority PDA, or on an SPL multisig that includes it. Create metadata first. Then:

bash
ntt set-mint-authority --chain Solana \
  --token INSERT_TOKEN_ADDRESS \
  --manager INSERT_NTT_PROGRAM_ADDRESS \
  --payer INSERT_KEYPAIR_JSON
# or keep other minters:
ntt solana create-spl-multisig INSERT_MINTER_PUBKEY_1 INSERT_MINTER_PUBKEY_2 \
  --token INSERT_TOKEN_ADDRESS \
  --manager INSERT_NTT_PROGRAM_ADDRESS \
  --payer INSERT_KEYPAIR_JSON
ntt set-mint-authority --chain Solana --multisig INSERT_MULTISIG_ADDRESS --payer INSERT_KEYPAIR_JSON

A failed SVM deploy does not burn the SOL. Funds sit in program buffer accounts. Close those buffers with the usual Solana deploy recovery, then retry. Peers are local set_peer config, not a cross-chain handshake.

A transfer on Solana

The client calls transfer_burn or transfer_lock to match the mode set at init. Wrong instruction returns InvalidMode. Args: amount, recipient chain, recipient address, and should_queue.

Every outbound lands in an Outbox account. If the amount fits current outbound capacity, capacity drops, inbound capacity on the destination refills, and release_timestamp is now. If it does not fit and should_queue is true, the item waits RATE_LIMIT_DURATION. If queue is false, the instruction reverts with TransferExceedsRateLimit.

Then someone must call release_outbound per transceiver, passing the Outbox item. The Wormhole transceiver calls post_message on Wormhole core so Guardians can attest. revert_on_delay true fails before the release time. False succeeds but skips the send.

On the destination, a relayer (or you) delivers the VAA into the transceiver. The manager waits for the attestation threshold, checks inbound capacity, then mints or unlocks. Inbound overflow queues the same way. Alice burning 100 tokens on Ethereum and minting 100 on Solana is the docs example for burning mode both ways.

SDK

Platform package @wormhole-foundation/sdk-solana-ntt is on npm at 8.0.1 (28 July 2026). Import the EVM sibling @wormhole-foundation/sdk-evm-ntt when the other leg is EVM or the SDK will not register the protocol. Resolve the protocol with your manager, token, and Wormhole transceiver, then iterate the transfer generator.

typescript
const ntt = await s.getProtocol("Ntt", {
  ntt: {
    chain: "Solana",
    manager: managerAddress,
    token: mintAddress,
    transceiver: { wormhole: transceiverAddress },
  },
})

const txs = ntt.transfer(
  new SolanaAddress(payer.publicKey),
  1n,
  {
    chain: "Sepolia",
    address: new UniversalAddress(recipientWallet, "hex"),
  },
  { queue: false, automatic: false },
)
for await (const tx of txs) {
  // sign and send
}

That snippet is the SVM shape from the NTT with Executor guide. Executor attaches relay instructions so a redeem can land without a custom relayer. On Solana, gasLimit is compute units plus about 20 percent. msgValue must cover lamports, priority fees, and rent. The associated token account has to exist before redeem. If it does not, the relayer creates it and rent goes up.

Connect wallets use the same NTT SDK, Solana implementation in solana/ts/sdk/ntt.ts inside the repo. Ownership moves with a two-step transfer_ownership. The Squads walkthrough lives at demo-ntt-solana-multisig-tools.

Rate limits and the accountant

Limits are per manager, outbound as one cap, inbound per source chain. They exist so a bug or a drained key cannot dump the whole supply in one window. Set them in deployment.json then ntt push. The Global Accountant is the extra check that burned plus transferred never exceeds minted. Watch it after go-live.

LayerZero OFT is the other common omnichain SPL path. That walkthrough stays at LayerZero OFT. For routers and status machines around a swap, see painless cross-chain swaps.

People and links

Keep reading

Get new articles in your inbox

Technical deep-dives on Solana tooling, infrastructure, and ecosystem. No noise.

Wormhole NTT: native SPL tokens across chains | devrels.xyz