Waffle: Deploy a Smart Contract with Waffle That’s Stored on IPFS

Learn how to deploy a smart contract with Waffle that's stored on IPFS.

What is Waffle?

Waffle is is a library comparable to Truffle that is used to write and test smart contracts. Waffle is compatible for use with ethers-js.

Read below to deploy a smart contract with Waffle that's stored on IPFS.

Prerequisites:

1. First, we need a Filebase IPFS bucket.

To do this, navigate to console.filebase.com. If you don’t have an account already, sign up, then log in.

2. Select ‘Buckets’ from the left side bar menu, or navigate to console.filebase.com/buckets.

Select ‘Create Bucket’ in the top right corner to create a new bucket.

3. Enter a bucket name and choose the IPFS storage network to create the bucket.

Bucket names must be unique across all Filebase users, be between 3 and 63 characters long, and can contain only lowercase characters, numbers, and dashes.

4. Next, download and install S3FS-FUSE on a Linux or macOS system.

5. Open a terminal window. Then, set up an Access Key file for use with S3FS-FUSE.

Set up a credentials file for S3FS at ${HOME}/.passwd-s3fs. You will need to save your Filebase Access and Secret keys to this file and give it owner permissions. You can do so with the following commands:

echo ACCESS_KEY_ID:SECRET_ACCESS_KEY > ${HOME}/.passwd-s3fs

chmod 600 ${HOME}/.passwd-s3fs

ACCESS_KEY_ID is your Filebase Access key, and SECRET_ACCESS_KEY is your Filebase Secret key. For more information on Filebase access keys, see here.

6. Mount your bucket.

You can mount a Filebase IPFS bucket with the command:

s3fs mybucket /path/to/mountpoint -o passwd_file=${HOME}/.passwd-s3fs -o url=https://s3.filebase.com

  • mybucket: name of your Filebase bucket

  • /path/to/mountpoint

7. Now, navigate into the mounted Filebase bucket.

cd /path/to/mounted/bucket

8. Create a new folder for your project, then navigate inside that folder:

mkdir waffle-tutorial

cd waffle-tutorial

9. Next, install the ethereum-waffle package:

npm install --save-dev ethereum-waffle

10. Install the OpenZeppelin package, we’ll be using this for our smart contract functionality:

npm install @openzeppelin/contracts@^4.6.0 -D

11. Make a new directory called src, then create a new file called BasicToken.sol. Open this file in your IDE, and insert the following content:

// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";

// Example class - a mock class using delivering from ERC20

contract BasicToken is ERC20 {
constructor(uint256 initialBalance) ERC20("Basic", "BSC") {
_mint(msg.sender, initialBalance);
}
}

12. Then, at the root of your project, create a new file called package.json. Insert the following content:

{
"scripts": {
"build": "waffle"
}
}

13. Create another new file called waffle.json that contains the following content:

{
"compilerType": "solcjs",
"compilerVersion": "0.8.13",
"sourceDirectory": "./src",
"outputDirectory": "./build"
}

14. Create new directories called build and contracts at the root of your project, since they are used in the waffle.json file.

mkdir build

mkdir contracts

15. Then build the project with the command:

npm run build

16. Now it’s time to test the smart contract. Run the following command to install Chai to be used for a test environment:

npm install --save-dev mocha chai

17. Make another new directory in the root of your project called test, then create a new file called BasicToken.test.ts in this directory. Open this new file in your IDE, and insert the following content:

import {expect, use} from 'chai';
import {Contract} from 'ethers';
import {deployContract, MockProvider, solidity} from 'ethereum-waffle';
import BasicToken from '../build/BasicToken.json';

use(solidity);
describe('BasicToken', () => {
const [wallet, walletTo] = new MockProvider().getWallets();
let token: Contract;
beforeEach(async () => {
token = await deployContract(wallet, BasicToken, [1000]);
});

it('Assigns initial balance', async () => {
expect(await token.balanceOf(wallet.address)).to.equal(1000);
});

it('Transfer adds amount to destination account', async () => {
await token.transfer(walletTo.address, 7);
expect(await token.balanceOf(walletTo.address)).to.equal(7);
});

it('Transfer emits event', async () => {
await expect(token.transfer(walletTo.address, 7))
.to.emit(token, 'Transfer')
.withArgs(wallet.address, walletTo.address, 7);
});

it('Can not transfer above the amount', async () => {
await expect(token.transfer(walletTo.address, 1007)).to.be.reverted;
});

it('Can not transfer from empty account', async () => {
const tokenFromOtherWallet = token.connect(walletTo);
await expect(tokenFromOtherWallet.transfer(wallet.address, 1))
.to.be.reverted;
});

it('Calls totalSupply on BasicToken contract', async () => {
await token.totalSupply();
expect('totalSupply').to.be.calledOnContract(token);
});

it('Calls balanceOf with sender address on BasicToken contract', async () => {
await token.balanceOf(wallet.address);
expect('balanceOf').to.be.calledOnContractWith(token, [wallet.address]);
});

});

18. Open your package.json file and insert the following content:

{
"scripts": {
"build": "waffle",
"test": "NODE_ENV=test mocha",
}
}

19. Then, add a new file called .mocharc.json in the root of your project:

{
"spec": "test/**/*.test.{js,ts}"
}

20. Then, run your test with:

npm test

This test should return the following output:

BasicToken
✓ Assigns initial balance (84ms)
✓ Transfer adds amount to destination account (404ms)
✓ Transfer emits event (219ms)
✓ Can not transfer above the amount (32ms)
✓ Can not transfer from empty account (95ms)
✓ Calls totalSupply on BasicToken contract (67ms)
✓ Calls balanceOf with sender address on BasicToken contract (75ms)
7 passing (8s)

If you have any questions, please join our Discord server, or send us an email at hello@filebase.com

Last updated