Integrating MetaMask with NestJS and Web3.js: A Detailed Guide
In this tutorial, we’ll step-by-step understand how to integrate MetaMask with NestJS using the Web3.js library. We will be creating a NestJS application that will interact with the Ethereum blockchain.
Prerequisites
Before we start, you need to:
- Create an account on Infura. It provides an API URL that we’ll use to connect to the Ethereum network.
- Set up a MetaMask account with an Ethereum wallet and its respective private key.
Configuring the Web3 Module
In NestJS, a module is a component that bundles related providers. Here, we’ll create a Web3 module, which will initialize the Web3 instance and configure the wallet and private key. This module will be responsible for providing the necessary configuration for our application to communicate with the Ethereum blockchain.
import { Module } from '@nestjs/common';
import Web3 from 'web3';
import { ConfigModule, ConfigService } from '@nestjs/config';
import { Web3Adapter } from './web3.adapter';
@Module({
imports: [ConfigModule],
providers: [
{
provide: 'Web3',
useFactory: (configService: ConfigService) => {
return new Web3(configService.get('INFURA_URL'));
},
inject: [ConfigService],
},
{
provide: 'Config',
useFactory: (configService: ConfigService) => {
return {
wallet: configService.get('WALLET'),
privateKey: configService.get('PRIVATE_KEY'),
};
},
inject: [ConfigService],
},
Web3Adapter,
],
exports: [Web3Adapter],
})
export class Web3Module {}
In this code:
- We’re creating a new module named
Web3Module
. - We import
Web3Adapter
andWeb3
along withConfigModule
andConfigService
from@nestjs/config
to handle the application configuration. - In the
providers
section, we are providing theWeb3
instance and the wallet and private key configurations. - The
Web3
instance is created using the Infura URL (obtained from the environment variables), and the wallet and private key configurations are also obtained from the environment variables. - Finally, we export
Web3Adapter
so it can be used in other modules.
Configuring the Web3 Adapter
Next, let’s set up the Web3Adapter
service, which will be responsible for interacting with the Ethereum blockchain. It will have two functions: balance
to check the wallet balance and transfer
to transfer Ether to another wallet.
import { Inject, Injectable } from '@nestjs/common';
import Web3 from 'web3';
@Injectable()
export class Web3Adapter {
constructor(
@Inject('Web3')
private readonly web3: Web3,
@Inject('Config')
private readonly config: { wallet: string; privateKey: string },
) {}
async balance() {
const balance = await this.web3.eth.getBalance(this.config.wallet);
return this.web3.utils.fromWei(balance, 'ether');
}
async transfer(toWallet: string, value: number) {
const nonce = await this.web3.eth.getTransactionCount(
this.config.wallet,
'latest',
);
const transaction = {
to: toWallet,
value,
gas: 21000,
nonce,
};
const signedTx = await this.web3.eth.accounts.signTransaction(
transaction,
this.config.privateKey,
);
const tx = await this.web3.eth.sendSignedTransaction(
signedTx.rawTransaction,
);
return tx.transactionHash;
}
}
In this code:
- The
balance
function uses theweb3
instance to get the wallet balance and converts the balance from Wei to Ether usingweb3.utils.fromWei
. - The
transfer
function first gets the nonce, which is the number of the next transaction to be processed from the wallet. - It then creates a transaction containing the destination wallet, the value to be transferred, the gas for the transaction, and the nonce.
- The transaction is then signed using the private key of the wallet.
- Finally, the signed transaction is sent, and the transaction hash is returned.
Setting Up the Wallet Service, Controller, and Module
Now, let’s create the wallet service, controller, and module. The WalletService
will utilize the Web3Adapter
to implement its functions, the WalletController
will provide endpoints to check the balance and transfer Ether, and the WalletModule
will group everything together.
Service:
import { Injectable } from '@nestjs/common';
import { Web3Adapter } from '../adapters/web3/web3.adapter';
@Injectable()
export class WalletService {
constructor(private readonly web3Adapter: Web3Adapter) {}
async getBalance() {
return this.web3Adapter.balance();
}
async setTransfer(toWallet: string, value: number) {
return this.web3Adapter.transfer(toWallet, value);
}
}
In the WalletService
service, we're simply calling the functions from the Web3Adapter
.
Controller:
import { Body, Controller, Get, Post } from '@nestjs/common';
import { WalletService } from './wallet.service';
@Controller('wallet')
export class WalletController {
constructor(private readonly walletService: WalletService) {}
@Get()
getBalance() {
return this.walletService.getBalance();
}
@Post()
setTransfer(
@Body('toWallet') toWallet: string,
@Body('value') value: number,
) {
return this.walletService.setTransfer(toWallet, value);
}
}
In the WalletController
, we're providing two endpoints. A GET to /wallet
that returns the wallet balance, and a POST to /wallet
that transfers Ether to another wallet.
Module:
import { Module } from '@nestjs/common';
import { WalletController } from './wallet.controller';
import { WalletService } from './wallet.service';
import { Web3Module } from 'src/utils/web3/web3.module';
@Module({
imports: [Web3Module],
controllers: [WalletController],
providers: [WalletService],
})
export class WalletModule {}
Finally, in the WalletModule
module, we're importing the Web3Module
we created earlier, the WalletController
, and the WalletService
.
Now you have an API that interacts with the Ethereum blockchain using MetaMask and Web3.js. Remember to properly set up your environment variables in the .env
file.
INFURA_URL=<infura url>
WALLET=<user wallet>
PRIVATE_KEY=<metamask private key>
I hope this tutorial was helpful and informative. Integrating MetaMask with NestJS using Web3.js is a great way to interact with the Ethereum blockchain in an easy and secure way.
If you want to see the complete code, follow the link:
engcfraposo/nestjs-web3 (github.com)
If you want to listen in portuguese, follow the link too: