Scripts that generate Safe Transaction Builder JSON files for managing Wormhole NTT (Native Token Transfers) deployments from a Safe multisig.
No private keys required - the scripts generate JSON files that signers import into the Safe UI and sign with their own wallets.
Uses the Wormhole ts-sdk for calldata generation, ensuring ABI compatibility across NTT versions.
- Copy an example config and fill in your contract addresses and parameters
- Run the script to generate a Safe Transaction Builder JSON file
- Import the JSON into the Safe Transaction Builder (see Importing into Safe)
- Safe owners review the decoded transactions, sign, and execute
git clone https://github.com/wormhole-foundation/demo-ntt-evm-multisig-tools.git
cd demo-ntt-evm-multisig-tools
npm install| Command | Description |
|---|---|
npm run extend-to-evm-chain |
Register NTT peers between two EVM chains (bidirectional) |
npm run extend-to-svm-chain |
Register a Solana/SVM chain as NTT peer on an EVM chain |
npm run set-rate-limits |
Update outbound and/or inbound rate limits |
npm run pause |
Pause or unpause the NTT Manager |
npm run transfer-ownership |
Transfer NTT Manager ownership to a new address |
All scripts output JSON files to the output/ directory. To execute them:
- Open your Safe at app.safe.global
- Make sure you are connected to the correct network (e.g., Sepolia, Base, Ethereum Mainnet)
- Click New Transaction in the left sidebar
- Select Transaction Builder
- Drag and drop the generated JSON file (or click the upload area to browse)
- The Transaction Builder will decode and display each transaction with its function name and parameters - review them carefully
- Click Create Batch then Send Batch
- Other Safe owners will see the pending transaction in the Safe UI and can confirm it
- Once the signature threshold is met, any owner can execute the transaction on-chain
All configs require the following common fields:
| Field | Description |
|---|---|
network |
"Mainnet" or "Testnet" |
rpcUrl |
RPC endpoint for the EVM chain |
wormholeChain |
Wormhole ts-sdk chain name (e.g., "Sepolia", "BaseSepolia", "Ethereum", "Base", "Arbitrum") |
evmChainId |
The EVM chain ID (e.g., "11155111" for Sepolia) |
safeAddress |
Your Safe multisig address |
nttManager |
The NTT Manager proxy contract address |
wormholeTransceiver |
The Wormhole Transceiver proxy contract address |
See the example configs in src/config/ for the full structure of each script.
Generates Safe transaction batches for registering NTT peers between two chains. Two JSON files are generated - one for each chain - so both sides can be configured via their respective Safe multisigs.
Each batch contains:
NttManager.setPeer- Register the remote chain's NTT Manager and set the inbound rate limitWormholeTransceiver.setWormholePeer- Register the remote chain's Wormhole transceiver
Note:
setWormholePeeris a one-shot call - it reverts if a peer is already registered for that chain ID. To change a peer, you must deploy a new transceiver.
cp src/config/extendToEvmChain.example.json src/config/extendToEvmChain.json
# Edit src/config/extendToEvmChain.json with your values
npm run extend-to-evm-chainGenerates a Safe transaction batch for registering an SVM-based NTT deployment (Solana, Fogo, etc.) as a peer on an EVM chain. Only one JSON file is generated (EVM side) - the SVM side should be configured using the Solana multisig tools.
Each batch contains:
NttManager.setPeer- Register the SVM NTT Manager and set the inbound rate limitWormholeTransceiver.setWormholePeer- Register the SVM Wormhole transceiver
cp src/config/extendToSvmChain.example.json src/config/extendToSvmChain.json
# Edit src/config/extendToSvmChain.json with your values
npm run extend-to-svm-chainUpdate outbound and/or per-chain inbound rate limits on the NTT Manager.
For inbound limits, chainName must be a canonical Wormhole ts-sdk chain name.
cp src/config/setRateLimits.example.json src/config/setRateLimits.json
# Edit src/config/setRateLimits.json with your values
npm run set-rate-limitsPause or unpause the NTT Manager. When paused, no transfers can be initiated or completed.
cp src/config/pause.example.json src/config/pause.json
# Edit src/config/pause.json - set "action" to "pause" or "unpause"
npm run pauseNote: Only the owner can unpause. Both the owner and the designated pauser can pause.
Transfer ownership of the NTT Manager (and all registered transceivers) to a new address.
cp src/config/transferOwnership.example.json src/config/transferOwnership.json
# Edit src/config/transferOwnership.json with the new owner address
npm run transfer-ownershipWarning: This transfers ownership of the NTT Manager AND all registered transceivers. Double-check the
newOwneraddress before executing.
src/
├── __tests__/ # Unit tests
│ ├── encoding.test.ts
│ └── safeBatchBuilder.test.ts
├── config/
│ ├── extendToEvmChain.example.json
│ ├── extendToSvmChain.example.json
│ ├── setRateLimits.example.json
│ ├── pause.example.json
│ └── transferOwnership.example.json
├── scripts/
│ ├── extendToEvmChain.ts # Register peers between two EVM chains
│ ├── extendToSvmChain.ts # Register SVM chain as peer on EVM chain
│ ├── setRateLimits.ts # Update rate limits
│ ├── pause.ts # Pause or unpause NTT
│ └── transferOwnership.ts # Transfer NTT ownership
└── utils/
├── encoding.ts # Input validation and string helpers
├── nttClient.ts # Wormhole SDK wrapper for calldata generation
└── safeBatchBuilder.ts # Safe Transaction Builder JSON generator
output/ # Generated JSON files (gitignored)
- No private keys are used or required by these scripts
- All signing happens in the Safe UI via connected wallets (MetaMask, Ledger, etc.)
- Config files with real addresses are gitignored - only example configs are committed
- Safety-critical config fields are validated before encoding - invalid addresses, RPC URLs, network names, chain IDs, decimals, and limits will produce clear error messages
- Always review the decoded transaction details in the Safe Transaction Builder before signing