Lending and borrowing using Ethers.js

This guide is created to help a potential investor to directly use the API of Minterest contract on Ethereum blockchain. The described actions will allow the user to lend and borrow assets without usage of Minterest UI.

Preconditions

In order to do a lend (as well as borrow) operation directly on the contract level using Ethers.js, one needs:

  1. A private key of a wallet with enough ETH and target asset (USDC in the example)

  2. Infura API key (can be easily substituted with Alchemy or any other provider)

  3. Address and raw ABI of the target contract (Minterest USDC market or mUSDC in the example)

The example below is using node.js as the basis to execute the JavaScript code. One needs to set it up, as well as set up the Ethers.js library itself. The setup of node.js and Ethers.js is out of scope of the guide, as it may differ for different operating systems. Detailed instructions can be found on respective resources.

Considering the node.js and Ethers.js are installed and configured, two files should be created:

  1. A JavaScript file, that will contain the code to execute transactions

  2. A JSON file to store the ABI of the target contract

JSON file content

The ABI of the contract can be retrieved using Etherscan. Open the contract page and look for the ABI section. The result of 'RAW/Text Format" export should be copied to the JSON file. In the example below the JSON file is called mUSDC_abi.json and it is placed next to the JS file with the code - this is important to simplify the import of the data.

JavaScript file

The next step is to write the code in a JavaScript file. The first block of the code is the setup of RPC Provider. The provider is responsible for sending messages to the blockchain through Infura or similar services.

// 1. Setup of provider and 
const { ethers, JsonRpcProvider } = require('ethers'); 
const ABI = require("./mUSDC_abi.json"); // Import ABI file
const INFURA_API_KEY = "YOUR_INFURA_API_KEY";

const provider = new JsonRpcProvider(
    `https://mainnet.infura.io/v3/${INFURA_API_KEY}`
);

Next is a section with constants needed for the transactions - address of the contract, private key of a wallet that will be used to sign transactions, lend and borrow values.


// 2. Create variables
const PRIVATE_KEY = "YOUR_WALLET_PRIVATE_KEY";
const account_from = { privateKey: PRIVATE_KEY,};
const mUsdcAddress = "0x960E440Ff43d7BC5fb51B103FC667103234F949D"; ///mUSDC Contract
const lendValue = 10;
const borrowValue = 2;

The next line of code creates a wallet abstraction in the code. This abstraction will be used to sign transactions.

// 3. Create wallet
let wallet = new ethers.Wallet(account_from.privateKey, provider);

A contract abstraction (created in this line of code) will be used to call functions like 'lend' and 'borrow'. Note how the instance is already linked to the wallet that will be used to communicate with the chain.

// 4. Create contract instance with signer
const mUsdc = new ethers.Contract(mUsdcAddress, ABI, wallet);

The following blocks define two functions, which call respective contract API endpoints for lending and borrowing. The functions are very similar, they send a signed transaction to the chain and wait for a success or error message.

// 6. Define functions to lend and borrow
const makeLend = async (value) => {
    console.log(
        `Calling Lend with value ${value} in contract at address: ${mUsdcAddress}`
    );
    const createReceipt = await mUsdc.lend(value);    
    await createReceipt.wait();
    console.log(`Tx successful with hash: ${createReceipt.hash}`);
};

const makeBorrow = async (value) => {
    console.log(
        `Calling Borrow with value ${value} in contract at address: ${mUsdcAddress}`
    );
    const createReceipt = await mUsdc.lend(value);    
    await createReceipt.wait();
    console.log(`Tx successful with hash: ${createReceipt.hash}`);
};

Finally, the code that invokes the lend and borrow functions:

// 7. Call the functions
makeLend(lendValue);
makeBorrow(borrowValue);

The full code of the example

// 1. Setup of provider and 
const { ethers, JsonRpcProvider } = require('ethers');
const ABI = require("./mUSDC_abi.json");
const INFURA_API_KEY = "2dd49fdb426a42dd9702e218034b0f2c";

const provider = new JsonRpcProvider(
    `https://mainnet.infura.io/v3/${INFURA_API_KEY}`
);

// 2. Create variables
const PRIVATE_KEY = "a23d1bf17acc1937db942b6f05a7a87db23bb618be83748f455a7acb420e87ae";
const account_from = { privateKey: PRIVATE_KEY,};
const mUsdcAddress = "0x960E440Ff43d7BC5fb51B103FC667103234F949D"; ///mUSDC Contract
const lendValue = 10;
const borrowValue = 2;

// 3. Create wallet
let wallet = new ethers.Wallet(account_from.privateKey, provider);

// 4. Create contract instance with signer
const mUsdc = new ethers.Contract(mUsdcAddress, ABI, wallet);

// 6. Define functions to lend and borrow
const makeLend = async (value) => {
    console.log(
        `Calling Lend with value ${value} in contract at address: ${mUsdcAddress}`
    );
    const createReceipt = await mUsdc.lend(value);    
    await createReceipt.wait();
    console.log(`Tx successful with hash: ${createReceipt.hash}`);
};

const makeBorrow = async (value) => {
    console.log(
        `Calling borrow with value ${value} in contract at address: ${mUsdcAddress}`
    );
    const createReceipt = await mUsdc.lend(value);    
    await createReceipt.wait();
    console.log(`Tx successful with hash: ${createReceipt.hash}`);
};

// 7. Call the functions
makeLend(lendValue);
makeBorrow(borrowValue);

Last updated