Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 37 additions & 0 deletions erc20-wrapper-registry/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# ERC20 Wrapper - Violet ID example

This project aims to demonstrate a simple example of how to wrap ERC20 tokens to a compliant version of it using VioletID. It comes with a sample Factory contract, and a CompliantERC20 contract on Optimism Goerli.

You can check a frontend with a live version of this demo [here](https://erc20-wrapper.violet.co/).
The frontend is located on the `frontend` folder, all relevant code can be found on the `frontend/pages/index.tsx` file

Find all VioletID live addresses in our [docs](https://docs.violet.co).


## Setup

#### Install the project by running the command

```shell
yarn install
```

#### Compile the test contract

```shell
yarn hardhat compile
```

#### Deploy the test contract on optimismGoerli:

```shell
yarn hardhat run scripts/deploy.ts --network optimismGoerli
```


#### Run the frontend locally

```shell
cd frontend
yarn dev
```
71 changes: 71 additions & 0 deletions erc20-wrapper-registry/contracts/CompliantERC20.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.20;

import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import {IVioletID} from "@violetprotocol/violetid/contracts/IVioletID.sol";

error AccountWithoutVioletIDRequiredStatus();

/**
* @dev ERC20 token contract that only allows wrapping and unwraping to valid VioletID holders
*
* Currently uses the VioletID status 1 representing enrollment, which includes initial screening and KYC/KYB.
*/
contract CompliantERC20 is ERC20 {
IERC20 permissionlessERC20;
uint256 public tokensWrapped;
IVioletID violetID;

constructor(
string memory name_,
string memory symbol_,
address violetID_,
address _nonCompliantERC20
) ERC20(name_, symbol_) {
violetID = IVioletID(violetID_);
permissionlessERC20 = IERC20(_nonCompliantERC20);
}

modifier onlyVioletIDHolders(address account) {
uint8 isEnrolledStatus = 1;
require(
violetID.hasStatus(account, isEnrolledStatus),
"account does not have a VioletID"
);
if (!violetID.hasStatus(account, isEnrolledStatus))
revert AccountWithoutVioletIDRequiredStatus();
_;
}

// All customizations to transfers, mints, and burns should be done by overriding this function.
// https://docs.openzeppelin.com/contracts/5.x/api/token/erc20#ERC20-_transfer-address-address-uint256-
// Adding the onlyVioletIDHolders modifier ensures only addresses with VioletID status receive tokens
function _update(
address from,
address to,
uint256 amount
) internal virtual override onlyVioletIDHolders(to) {
super._update(from, to, amount);
}

function wrap(
uint256 amount
) public virtual onlyVioletIDHolders(msg.sender) {
permissionlessERC20.transferFrom(msg.sender, address(this), amount);
tokensWrapped += amount;
super._mint(msg.sender, amount);
}

function unwrap(
uint256 amount
) public virtual onlyVioletIDHolders(msg.sender) {
require(
tokensWrapped >= amount,
"cERC20_unwrap: amount to unwrap exceeds total wrapped"
);
tokensWrapped -= amount;
_burn(msg.sender, amount);
permissionlessERC20.transfer(msg.sender, amount);
}
}
33 changes: 33 additions & 0 deletions erc20-wrapper-registry/contracts/CompliantFactory.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.20;

import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import {IVioletID} from "@violetprotocol/violetid/contracts/IVioletID.sol";
import "./CompliantERC20.sol";

/**
* @dev ERC20 token factory that builds a new ERC20 using the previous token address
*
* Saves the new wrapped token address on the erc20ToCompliantWrapped mapping
*/
contract CompliantFactory {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
contract CompliantFactory {
contract CompliantERC20Factory {

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How strong do you feel about this renaming? Asking bc with that, I will need to re-do all the fixtures and mocks + redeploy and change the frontend code to comply with the renaming

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok nevermind! 😅

mapping(address => address) public erc20ToCompliantWrapped;

function deployCompliantErc(
address nonCompliantERC20,
address violetId_
) public payable {
address compliantErc20 = address(
new CompliantERC20{
salt: keccak256(abi.encodePacked(nonCompliantERC20))
}(
string.concat("c", ERC20(nonCompliantERC20).name()),
string.concat("c", ERC20(nonCompliantERC20).symbol()),
violetId_,
nonCompliantERC20
)
);
erc20ToCompliantWrapped[nonCompliantERC20] = compliantErc20;
}
}
15 changes: 15 additions & 0 deletions erc20-wrapper-registry/contracts/MockERC20.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
//SPDX-License-Identifier: MIT
pragma solidity ^0.8.14;

import "@openzeppelin/contracts/token/ERC20/ERC20.sol";

contract MockERC20 is ERC20 {
constructor(
string memory name_,
string memory symbol_
) ERC20(name_, symbol_) {}

function mint(address account, uint256 amount) public virtual {
_mint(account, amount);
}
}
3 changes: 3 additions & 0 deletions erc20-wrapper-registry/frontend/.eslintrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"extends": "next/core-web-vitals"
}
39 changes: 39 additions & 0 deletions erc20-wrapper-registry/frontend/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.

# dependencies
/node_modules
/.pnp
.pnp.js

# testing
/coverage

# next.js
/.next/
/out/

# production
/build

# misc
.DS_Store
*.pem

# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*
.pnpm-debug.log*

# local env files
.env
.env.local
.env.development.local
.env.test.local
.env.production.local

# vercel
.vercel

# typescript
*.tsbuildinfo
5 changes: 5 additions & 0 deletions erc20-wrapper-registry/frontend/.prettierrc.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
module.exports = {
semi: false,
singleQuote: true,
trailingComma: 'all',
}
Loading