frxUSD
Quickstarts: Mint & Redeem frxUSD on Ethereum
Mint and redeem frxUSD with Bridge.xyz's USDB

Mint & Redeem frxUSD with Bridge.xyz's USDB

This guide demonstrates how to use the viem framework in a simple script that enables a user to mint and redeem frxUSD with USDB on Ethereum mainnet.

Prerequisites

Before you start building the sample app to perform a frxUSD transfer, ensure you have met the following prerequisites:

  1. Install Node.js and npm

    • Download and install Node.js directly or use a version manager like nvm.
    • npm is included with Node.js.
  2. 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.
  3. Fund your wallet with the gas token and USDB on the source chain

    • For this guide, we will be minting and redeeming frxUSD with USDB on Ethereum mainnet. Therefore, you will need to fund your wallet with USDB 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-usdb
cd frxusd-mint-and-redeem-with-usdb
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-usdb",
  "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 USDB 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_USDB_CUSTODIAN =
  '0x5fbAa3A3B489199338fbD85F7E3D444dc0504F33'; // Mint & Redeem contract address using USDB
 
// frxUSD and USDB Addresses
const ETHEREUM_MAINNET_FRXUSD_ADDRESS = '0xCAcd6fd266aF91b8AeD52aCCc382b4e165586E29'; // frxUSD address on Ethereum mainnet
const ETHEREUM_MAINNET_USDB_ADDRESS = '0x43415eb6ff9db7e26a15b704e7a3edce97d31c4e'; // USDB address on Ethereum mainnet
 
// Mint & Redeem Parameters
const MINT_AMOUNT = 10_000_000n; // 10 USDB
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 USDB 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 USDB on Ethereum mainnet. Follow the steps below to perform the mint & redeem:

1. Approve USDB & frxUSD

Before minting, approve USDB for the Mint & Redeem contract on Ethereum mainnet so it can pull USDB 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 approveUsdbForMint() {
  console.log('Approving USDB for mint...');
  const approveTx = await ethereumClient.sendTransaction({
    to: ETHEREUM_MAINNET_USDB_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_USDB_CUSTODIAN, MINT_AMOUNT],
    }),
  });
  console.log(`USDB 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_USDB_CUSTODIAN, REDEEM_AMOUNT],
    }),
  });
  console.log(`frxUSD Approval Tx: ${approveTx}`);
}
  • Both approvals are now explicit: approve USDB for mint, approve frxUSD for redeem, both to ETHEREUM_MAINNET_FRXUSD_MINT_AND_REDEEM_USDB_CUSTODIAN.

2. Mint frxUSD with USDB

After approving USDB, mint by calling the Mint & Redeem contract's deposit to receive frxUSD.

async function mintFrxusdWithUsdb() {
  console.log('Minting frxUSD with USDB...');
  const txHash = await ethereumClient.sendTransaction({
    to: ETHEREUM_MAINNET_FRXUSD_MINT_AND_REDEEM_USDB_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 approveUsdbForMint() first, then await mintFrxusdWithUsdb().

3. Redeem frxUSD with USDB

After approving frxUSD, redeem by calling the Mint & Redeem contract's redeem to receive USDB.

async function redeemFrxusdForUsdb() {
  console.log('Redeeming frxUSD for USDB...');
  const txHash = await ethereumClient.sendTransaction({
    to: ETHEREUM_MAINNET_FRXUSD_MINT_AND_REDEEM_USDB_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, then await redeemFrxusdForUsdb().

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 USDB 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_USDB_CUSTODIAN =
  '0xFE2Ea8dE262d956e852F12DE108fda57171a0a29'; // Mint & Redeem contract address using USDB
 
// frxUSD and USDB Addresses
const ETHEREUM_MAINNET_FRXUSD_ADDRESS = '0xCAcd6fd266aF91b8AeD52aCCc382b4e165586E29'; // frxUSD address on Ethereum mainnet
const ETHEREUM_MAINNET_USDB_ADDRESS = '0xeac4269c9a01190b1400c4dc728864e61895fdf3'; // USDB address on Ethereum mainnet
 
// Mint & Redeem Parameters
const MINT_AMOUNT = 10_000_000n; // 10 USDB (6 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 USDB for mint
async function approveUsdbForMint() {
  console.log('Approving USDB for mint...');
  const approveTx = await ethereumClient.sendTransaction({
    to: ETHEREUM_MAINNET_USDB_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_USDB_CUSTODIAN, MINT_AMOUNT],
    }),
  });
  console.log(`USDB Approval Tx: ${approveTx}`);
}
 
// 2) Mint frxUSD with USDB
async function mintFrxusdWithUsdb() {
  console.log('Minting frxUSD with USDB...');
  const txHash = await ethereumClient.sendTransaction({
    to: ETHEREUM_MAINNET_FRXUSD_MINT_AND_REDEEM_USDB_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_USDB_CUSTODIAN, REDEEM_AMOUNT],
    }),
  });
  console.log(`frxUSD Approval Tx: ${approveTx}`);
}
 
// 4) Redeem frxUSD for USDB
async function redeemFrxusdForUsdb() {
  console.log('Redeeming frxUSD for USDB...');
  const txHash = await ethereumClient.sendTransaction({
    to: ETHEREUM_MAINNET_FRXUSD_MINT_AND_REDEEM_USDB_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-usdb':
      console.log('Running mint with USDB...');
      await approveUsdbForMint();
      await mintFrxusdWithUsdb();
      break;
    case 'redeem-for-usdb':
      console.log('Running redeem with USDB...');
      await approveFrxusdForRedeem();
      await redeemFrxusdForUsdb();
      break;
    default:
      console.error('Invalid command. Please use "mint-with-usdb" or "redeem-for-usdb".');
      process.exit(1);
  }
  console.log('Mint and redeem completed!');
}
 
main().catch(console.error);

Test the script

To test minting frxUSD with USDB, run the following command:

node mint-and-redeem.js mint-with-usdb

To test redeeming frxUSD with USDB, run the following command:

node mint-and-redeem.js redeem-for-usdb

Once the script runs and the mint & redeem operations are finalized, the confirmation receipts are logged in the console.

What's next