Mint & Redeem frxUSD with WisdomTree's WTGXX
This guide demonstrates how to use the viem framework in a simple script that enables a user to mint and redeem frxUSD with WTGXX on Ethereum mainnet.
Note: Please ensure the sender and recipient addresses are onboarded to hold WTGXX.
Prerequisites
Before you start building the sample app to perform a frxUSD transfer, ensure you have met the following prerequisites:
-
Install Node.js and npm
- Download and install Node.js directly or use a version manager like nvm.
- npm is included with Node.js.
-
Set up a non-custodial wallet (for example, MetaMask)
- You can download, install, and create a MetaMask wallet from its official website.
- During setup, create a wallet on Ethereum mainnet.
- Retrieve the private key for your wallet, as it will be required in the script below.
-
Fund your wallet with the gas token and WTGXX on the source chain
- For this guide, we will be minting and redeeming frxUSD with WTGXX on Ethereum mainnet. Therefore, you will need to fund your wallet with WTGXX and ETH on Ethereum mainnet.
Project setup
To build the script, first set up your project environment and install the required dependencies.
1. Set up a new project
Create a new directory and initialize a new Node.js
project with default settings:
mkdir frxusd-mint-and-redeem-with-wtgxx
cd frxusd-mint-and-redeem-with-wtgxx
npm init -y
This also creates a default package.json
file.
2. Install dependencies
In your project directory, install the required dependencies, including viem
:
npm install dotenv@^16.4.7 viem@^2.23.4
This sets up your development environment with the necessary libraries for building the script. It also updates the package.json
file with the dependencies.
3. Add module type
Add "type": "module"
to the package.json
file:
{
"name": "frax-mint-and-redeem-with-wtgxx",
"version": "1.0.0",
"type": "module",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"start": "node mint-and-redeem.js"
},
"keywords": [],
"author": "",
"license": "ISC",
"description": "",
"dependencies": {
"dotenv": "^16.4.7",
"viem": "^2.23.4"
}
}
4. Configure environment variables
Create a .env
file in your project directory and add your wallet private key:
echo "PRIVATE_KEY=your-private-key-here" > .env
Warning: This is strictly for testing purposes. Never share your private key.
Script setup
This section covers the necessary setup for the mint-and-redeem.js script, including defining keys and addresses, and configuring the wallet client for interacting with the source and destination chains.
1. Replace with your private key and wallet address
Ensure that this section of the file includes your private key and associated wallet address. The script also predefines the Mint & Redeem contract address, the frxUSD address, the WTGXX address, the mint amount, and the redeem amount. These definitions are critical for successfully minting and redeeming frxUSD between the intended wallets.
// ============ Configuration Constants ============
// Authentication
const PRIVATE_KEY = process.env.PRIVATE_KEY;
const account = privateKeyToAccount(`0x${PRIVATE_KEY}`, { nonceManager });
// Mint & Redeem Contract Addresses and Destination EID
const ETHEREUM_MAINNET_FRXUSD_MINT_AND_REDEEM_WTGXX_CUSTODIAN =
'0x860Cc723935FC9A15fF8b1A94237a711DFeF7857'; // Mint & Redeem contract address using WTGXX
// frxUSD and WTGXX Addresses
const ETHEREUM_MAINNET_FRXUSD_ADDRESS = '0xCAcd6fd266aF91b8AeD52aCCc382b4e165586E29'; // frxUSD address on Ethereum mainnet
const ETHEREUM_MAINNET_WTGXX_ADDRESS = '0x1fecf3d9d4fee7f2c02917a66028a48c6706c179'; // WTGXX address on Ethereum mainnet
// Mint & Redeem Parameters
const MINT_AMOUNT = 10_000_000_000_000_000_000n; // 10 WTGXX
const REDEEM_AMOUNT = 10_000_000_000_000_000_000n; // 10 frxUSD
2. Set up wallet client
The wallet client configures the appropriate network settings using viem
. In this example, the script will be minting and redeeming frxUSD with WTGXX on Ethereum mainnet.
// Set up a wallet client on the source chain
const ethereumClient = createWalletClient({
chain: mainnet,
transport: http(),
account,
});
frxUSD mint & redeem process
The following sections outline the relevant mint & redeem logic of the sample script. In this example, we are minting and redeeming frxUSD with WTGXX on Ethereum mainnet. Follow the steps below to perform the mint & redeem:
1. Approve WTGXX & frxUSD
Before minting, approve WTGXX for the Mint & Redeem contract on Ethereum mainnet so it can pull WTGXX from your wallet. Before redeeming, approve frxUSD for the same contract so it can pull frxUSD. For other assets, use the appropriate contract address from the frxUSD Mint & Redeem addresses page.
async function approveWtgxxForMint() {
console.log('Approving WTGXX for mint...');
const approveTx = await ethereumClient.sendTransaction({
to: ETHEREUM_MAINNET_WTGXX_ADDRESS,
data: encodeFunctionData({
abi: [
{
type: 'function',
name: 'approve',
stateMutability: 'nonpayable',
inputs: [
{ name: 'spender', type: 'address' },
{ name: 'amount', type: 'uint256' },
],
outputs: [{ name: '', type: 'bool' }],
},
],
functionName: 'approve',
args: [ETHEREUM_MAINNET_FRXUSD_MINT_AND_REDEEM_WTGXX_CUSTODIAN, MINT_AMOUNT],
}),
});
console.log(`WTGXX Approval Tx: ${approveTx}`);
}
async function approveFrxusdForRedeem() {
console.log('Approving frxUSD for redeem...');
const approveTx = await ethereumClient.sendTransaction({
to: ETHEREUM_MAINNET_FRXUSD_ADDRESS,
data: encodeFunctionData({
abi: [
{
type: 'function',
name: 'approve',
stateMutability: 'nonpayable',
inputs: [
{ name: 'spender', type: 'address' },
{ name: 'amount', type: 'uint256' },
],
outputs: [{ name: '', type: 'bool' }],
},
],
functionName: 'approve',
args: [ETHEREUM_MAINNET_FRXUSD_MINT_AND_REDEEM_WTGXX_CUSTODIAN, REDEEM_AMOUNT],
}),
});
console.log(`frxUSD Approval Tx: ${approveTx}`);
}
- Both approvals are now explicit: approve WTGXX for mint, approve frxUSD for redeem, both to
ETHEREUM_MAINNET_FRXUSD_MINT_AND_REDEEM_WTGXX_CUSTODIAN
.
2. Mint frxUSD with WTGXX
After approving WTGXX, mint by calling the Mint & Redeem contract's deposit
to receive frxUSD.
async function mintFrxusdWithWtgxx() {
console.log('Minting frxUSD with WTGXX...');
const txHash = await ethereumClient.sendTransaction({
to: ETHEREUM_MAINNET_FRXUSD_MINT_AND_REDEEM_WTGXX_CUSTODIAN,
data: encodeFunctionData({
abi: [
{
inputs: [
{ internalType: 'uint256', name: '_assetsIn', type: 'uint256' },
{ internalType: 'address', name: '_receiver', type: 'address' },
],
name: 'deposit',
outputs: [{ internalType: 'uint256', name: '_sharesOut', type: 'uint256' }],
stateMutability: 'nonpayable',
type: 'function',
},
],
functionName: 'deposit',
args: [MINT_AMOUNT, account.address],
}),
});
console.log(`Mint Tx: ${txHash}`);
}
- Call
await approveWtgxxForMint()
first, thenawait mintFrxusdWithWtgxx()
.
3. Redeem frxUSD with WTGXX
After approving frxUSD, redeem by calling the Mint & Redeem contract's redeem
to receive WTGXX.
async function redeemFrxusdForWtgxx() {
console.log('Redeeming frxUSD for WTGXX...');
const txHash = await ethereumClient.sendTransaction({
to: ETHEREUM_MAINNET_FRXUSD_MINT_AND_REDEEM_WTGXX_CUSTODIAN,
data: encodeFunctionData({
abi: [
{
inputs: [
{ internalType: 'uint256', name: '_sharesIn', type: 'uint256' },
{ internalType: 'address', name: '_receiver', type: 'address' },
{ internalType: 'address', name: '_owner', type: 'address' },
],
name: 'redeem',
outputs: [{ internalType: 'uint256', name: '_assetsOut', type: 'uint256' }],
stateMutability: 'nonpayable',
type: 'function',
},
],
functionName: 'redeem',
args: [REDEEM_AMOUNT, account.address, account.address],
}),
});
console.log(`Redeem Tx: ${txHash}`);
}
- Call
await approveFrxusdForRedeem()
first, thenawait redeemFrxusdForWtgxx()
.
Build the script
Create a mint-and-redeem.js
in your project directory and paste the following. It groups the script setup and helper functions together at the top, followed by the main execution flow.
Note: The wallet must contain mainnet ETH (gas) and WTGXX to mint, and frxUSD to redeem.
mint-and-redeem.js
// ============ Setup (imports, account, client) ============
import 'dotenv/config';
import { createWalletClient, http, encodeFunctionData, nonceManager } from 'viem';
import { privateKeyToAccount } from 'viem/accounts';
import { mainnet } from 'viem/chains';
// ============ Configuration Constants ============
// Authentication
const PRIVATE_KEY = process.env.PRIVATE_KEY;
const account = privateKeyToAccount(`0x${PRIVATE_KEY}`, { nonceManager });
// Mint & Redeem Contract Addresses
const ETHEREUM_MAINNET_FRXUSD_MINT_AND_REDEEM_WTGXX_CUSTODIAN =
'0xFE2Ea8dE262d956e852F12DE108fda57171a0a29'; // Mint & Redeem contract address using WTGXX
// frxUSD and WTGXX Addresses
const ETHEREUM_MAINNET_FRXUSD_ADDRESS = '0xCAcd6fd266aF91b8AeD52aCCc382b4e165586E29'; // frxUSD address on Ethereum mainnet
const ETHEREUM_MAINNET_WTGXX_ADDRESS = '0xeac4269c9a01190b1400c4dc728864e61895fdf3'; // WTGXX address on Ethereum mainnet
// Mint & Redeem Parameters
const MINT_AMOUNT = 10_000_000_000_000_000_000n; // 10 WTGXX (18 decimals)
const REDEEM_AMOUNT = 10_000_000_000_000_000_000n; // 10 frxUSD (18 decimals)
// Wallet client
const ethereumClient = createWalletClient({
chain: mainnet,
transport: http(),
account,
});
// ============ Helpers ============
// 1) Approve WTGXX for mint
async function approveWtgxxForMint() {
console.log('Approving WTGXX for mint...');
const approveTx = await ethereumClient.sendTransaction({
to: ETHEREUM_MAINNET_WTGXX_ADDRESS,
data: encodeFunctionData({
abi: [
{
type: 'function',
name: 'approve',
stateMutability: 'nonpayable',
inputs: [
{ name: 'spender', type: 'address' },
{ name: 'amount', type: 'uint256' },
],
outputs: [{ name: '', type: 'bool' }],
},
],
functionName: 'approve',
args: [ETHEREUM_MAINNET_FRXUSD_MINT_AND_REDEEM_WTGXX_CUSTODIAN, MINT_AMOUNT],
}),
});
console.log(`WTGXX Approval Tx: ${approveTx}`);
}
// 2) Mint frxUSD with WTGXX
async function mintFrxusdWithWtgxx() {
console.log('Minting frxUSD with WTGXX...');
const txHash = await ethereumClient.sendTransaction({
to: ETHEREUM_MAINNET_FRXUSD_MINT_AND_REDEEM_WTGXX_CUSTODIAN,
data: encodeFunctionData({
abi: [
{
inputs: [
{ internalType: 'uint256', name: '_assetsIn', type: 'uint256' },
{ internalType: 'address', name: '_receiver', type: 'address' },
],
name: 'deposit',
outputs: [{ internalType: 'uint256', name: '_sharesOut', type: 'uint256' }],
stateMutability: 'nonpayable',
type: 'function',
},
],
functionName: 'deposit',
args: [MINT_AMOUNT, account.address],
}),
});
console.log(`Mint Tx: ${txHash}`);
}
// 3) Approve frxUSD for redeem
async function approveFrxusdForRedeem() {
console.log('Approving frxUSD for redeem...');
const approveTx = await ethereumClient.sendTransaction({
to: ETHEREUM_MAINNET_FRXUSD_ADDRESS,
data: encodeFunctionData({
abi: [
{
type: 'function',
name: 'approve',
stateMutability: 'nonpayable',
inputs: [
{ name: 'spender', type: 'address' },
{ name: 'amount', type: 'uint256' },
],
outputs: [{ name: '', type: 'bool' }],
},
],
functionName: 'approve',
args: [ETHEREUM_MAINNET_FRXUSD_MINT_AND_REDEEM_WTGXX_CUSTODIAN, REDEEM_AMOUNT],
}),
});
console.log(`frxUSD Approval Tx: ${approveTx}`);
}
// 4) Redeem frxUSD for WTGXX
async function redeemFrxusdForWtgxx() {
console.log('Redeeming frxUSD for WTGXX...');
const txHash = await ethereumClient.sendTransaction({
to: ETHEREUM_MAINNET_FRXUSD_MINT_AND_REDEEM_WTGXX_CUSTODIAN,
data: encodeFunctionData({
abi: [
{
inputs: [
{ internalType: 'uint256', name: '_sharesIn', type: 'uint256' },
{ internalType: 'address', name: '_receiver', type: 'address' },
{ internalType: 'address', name: '_owner', type: 'address' },
],
name: 'redeem',
outputs: [{ internalType: 'uint256', name: '_assetsOut', type: 'uint256' }],
stateMutability: 'nonpayable',
type: 'function',
},
],
functionName: 'redeem',
args: [REDEEM_AMOUNT, account.address, account.address],
}),
});
console.log(`Redeem Tx: ${txHash}`);
}
// ============ Main ============
async function main() {
const command = process.argv[2];
switch (command) {
case 'mint-with-wtgxx':
console.log('Running mint with WTGXX...');
await approveWtgxxForMint();
await mintFrxusdWithWtgxx();
break;
case 'redeem-for-wtgxx':
console.log('Running redeem with WTGXX...');
await approveFrxusdForRedeem();
await redeemFrxusdForWtgxx();
break;
default:
console.error(
'Invalid command. Please use "mint-with-wtgxx" or "redeem-for-wtgxx".',
);
process.exit(1);
}
console.log('Mint and redeem completed!');
}
main().catch(console.error);
Test the script
To test minting frxUSD with WTGXX, run the following command:
node mint-and-redeem.js mint-with-wtgxx
To test redeeming frxUSD with WTGXX, run the following command:
node mint-and-redeem.js redeem-for-wtgxx
Once the script runs and the mint & redeem operations are finalized, the confirmation receipts are logged in the console.