Skip to content

Open-Attestation/token-registry

Repository files navigation

Token Registry

TradeTrust Token Registry

TradeTrust Electronic Bill of Lading (eBL)

The Electronic Bill of Lading (eBL) is a digital document which you can use to prove the ownership of goods. It is a standardized document accepted by all major shipping lines and customs authorities.

The Token Registry repository contains the following:

  • The smart contract code for token registry in the /contracts folder
  • The node package for using this library in the /src folder

Table of Contents

Installation

To install Token Registry on your machine, run the command below:

npm install --save @govtechsg/token-registry

Usage

Provide one of the following depending on your purpose:

  • To use the package, provide your own Web3 provider

  • To write to the blockchain, provide the signer that exposes the Typechain (Ethers) bindings for the contracts

TradeTrustToken

The TradeTrustToken is a Soulbound Token (SBT) tied to the Title Escrow. The SBT implementation is loosely based on OpenZeppelin's implementation of the ERC721 standard.

In this case, an SBT is used because the token is largely restricted to its designated Title Escrow contracts, while it can be transferred to the registry.

See issue #108 for more details.

Connecting to an existing token registry

The following is a code example that connects to a token registry:

import { TradeTrustToken__factory } from "@govtechsg/token-registry/contracts";

const connectedRegistry = TradeTrustToken__factory.connect(tokenRegistryAddress, signer);

Issuing a document

The following is a code example that issues a document:

await connectedRegistry.mint(beneficiaryAddress, holderAddress, tokenId);

Restoring a document

The following is a code example that restores a document:

await connectedRegistry.restore(tokenId);

Accepting or burning a document

The following is a code example that burns a document:

await connectedRegistry.burn(tokenId);

Title Escrow

Using the Title Escrow contract, you can manage and represent the ownership of a token between a beneficiary and holder. During minting, the Token Registry will create and assign a Title Escrow as the owner of that token. The actual owners will use the Title Escrow contract to perform their ownership operations.

Connecting to a Title Escrow

The following is a code example that connects to a Title Escrow:

import { TitleEscrow__factory } from "@govtechsg/token-registry/contracts";

const connectedEscrow = TitleEscrow__factory.connect(existingTitleEscrowAddress, signer);

Transfer of the beneficiary or holder

To transfer the beneficiary and holder within the Title Escrow, use the following methods:

function transferBeneficiary(address beneficiaryNominee) external;

function transferHolder(address newHolder) external;

function transferOwners(address beneficiaryNominee, address newHolder) external;

function nominate(address beneficiaryNominee) external;
  • The transferBeneficiary transfers only the beneficiary and transferHolder transfers only the holder.

  • To transfer both the beneficiary and holder in a single transaction, use transferOwners.

  • Transfer of the beneficiary will require a nomination through the nominate method.

Surrendering or burning a document

To surrender or burn a document use the surrender method in the Title Escrow:

function surrender() external;

The following shows a code example:

await connectedEscrow.surrender();

Accessing the current owners

You can retrieve the addresses of the current owners from the beneficiary, holder, and nominee methods.

The following shows a code example:

const currentBeneficiary = await connectedEscrow.beneficiary();

const currentHolder = await connectedEscrow.holder();

const nominatedBeneficiary = await connectedEscrow.nominee();

Title Escrow signable (experimental)

This is similar to the Title Escrow with the additional support for off-chain nomination and the endorsement of beneficiary nominees:

  • The on-chain nominee will take precedence.
  • The current beneficiary will initiate the transfer transaction with the endorsement.

With this feature, you can save on gas fees for cases where frequent nominations and endorsements occur between the owners.

Currently, this is not the default Title Escrow. To use this version of the Title Escrow, you need to make some changes to the TitleEscrowFactory.sol file before deployment by following these steps in the code example:

// Step 1. Import the TitleEscrowSignable contract

import "./TitleEscrowSignable.sol";

contract TitleEscrowFactory is ITitleEscrowFactory {
  // ...

  constructor() {
    // Step 2. Look for this line in the constructor

    implementation = address(new TitleEscrow());

    // Step 3. Replace the line in Step 2 with the following line:

    implementation = address(new TitleEscrowSignable());
  }

  // ...
}

Note: This is currently an experimental feature. Implementers need to set up a "book-keeping" backend for the signed data.

Provider & signer

The following code examples shows different ways to get the provider or the signer:

import { Wallet, providers, getDefaultProvider } from "ethers";

// Providers
const mainnetProvider = getDefaultProvider();
const metamaskProvider = new providers.Web3Provider(web3.currentProvider); // Will change network automatically

// Signer
const signerFromPrivateKey = new Wallet("YOUR-PRIVATE-KEY-HERE", provider);
const signerFromEncryptedJson = Wallet.fromEncryptedJson(json, password);
signerFromEncryptedJson.connect(provider);
const signerFromMnemonic = Wallet.fromMnemonic("MNEMONIC-HERE");
signerFromMnemonic.connect(provider);

Roles and access

Using roles, you can grant the users to access only certain functions. Currently, here are the designated roles for the different key operations.

Role Access
DefaultAdmin Able to perform all operations
MinterRole Able to mint new tokens
AccepterRole Able to accept a surrendered token
RestorerRole Able to restore a surrendered token

The admin user can grant a trusted user multiple roles to perform different operations.

To grant roles to the users or revoke roles from them, the admin user can call the following functions:

Granting a role to a user

Only the default admin or the role admin will be able to call this function:

import { constants } from "@govtechsg/token-registry";

await tokenRegistry.grantRole(constants.roleHash.MinterRole, "0xbabe");

Revoking a role from a user

Only the default admin or the role admin will be able to call this function:

import { constants } from "@govtechsg/token-registry";

await tokenRegistry.revokeRole(constants.roleHash.AccepterRole, "0xbabe");

Setting a role admin

The standard setup does not change the user's role into a role admin, so that the user does not need to deploy or pay the gas more than what's needed.

For a more complex setup, you can add the admin roles to the designated roles.

Only the default admin will be able to call this function:

import { constants } from "@govtechsg/token-registry";
const { roleHash } = constants;

await tokenRegistry.setRoleAdmin(roleHash.MinterRole, roleHash.MinterAdminRole);
await tokenRegistry.setRoleAdmin(roleHash.RestorerRole, roleHash.RestorerAdminRole);
await tokenRegistry.setRoleAdmin(roleHash.AccepterRole, roleHash.AccepterAdminRole);

Deployment

With Hardhat, you can manage the contract development environment and deployment. This repository provides a couple of Hardhat tasks to simplify the deployment process.

Starting from V4, the token registry includes an easy and cost-effective way to deploy the contracts and meanwhile keep the options available for advanced users to set up the contracts in a preferable way.

Note: Before deployment, ensure that you have set up your configuration file. See the Configuration section for more details. The deployer (configured in your .env file) will be the default admin.

Quick start

To quickly deploy contracts with ease, you need to supply your token name and symbol to the command:

npx hardhat deploy:token --network mumbai --name "The Great Shipping Co." --symbol GSC

The above is the easiest and most cost-effective method to deploy. Currently, this is supported on Ethereum, Sepolia, Polygon, and Polygon Mumbai. The deployed contract will inherit all the standard functionalities from the Token Registry's on-chain contracts. This saves deployment costs and makes the process more convenient for users and integrators.

Note: Remember to supply the--network argument with the name of the network on which you want to deploy. See the Network configuration section for more information on the list of network names.

Advanced usage

For experienced users who want more control over their setup (or have extra fund to spend), a few other options and commands are provided.

Important: Depending on your use case, the gas costs might be higher than the method described in Quick start.

Token contract

The following command deploys the token contract.

Usage: hardhat [GLOBAL OPTIONS] deploy:token --factory <STRING> --name <STRING> [--standalone] --symbol <STRING> [--verify]

OPTIONS:

  --factory   	Address of Title Escrow factory (Optional)
  --name      	Name of the token
  --standalone	Deploy as standalone token contract
  --symbol    	Symbol of token
  --verify    	Verify on Etherscan

deploy:token: Deploys the TradeTrust token

Note:

  • The --factory argument is optional. If not provided, the task will use the default Title Escrow Factory.
  • You can also reuse a previously deployed Title Escrow factory by passing its address to the --factory argument.

Standalone contract

To deploy your own modified version or your own copy of the token contract, use the --standalone flag:

npx hardhat deploy:token --network mumbai --name "The Great Shipping Co." --symbol GSC --verify --standalone

The above command will deploy a full token contract with the name The Great Shipping Co. under the symbol GSC on the Polygon mumbai network using the default Title Escrow factory. The contract will also be verified on Etherscan.

Using an existing Title Escrow factory

To use an existing or your own version of Title Escrow factory, you can supply its address to the —factory argument. This option only works with the --standalone flag.

npx hardhat deploy:token --network polygon --name "The Great Shipping Co." --symbol GSC --factory 0xfac70

The above command will deploy a "cheap" token contract with the name The Great Shipping Co. under the symbol GSC on the Polygon Mainnet network using an existing Title Escrow factory at 0xfac70.

Title Escrow factory

The command below deploys the Title Escrow factory.

Usage: hardhat [GLOBAL OPTIONS] deploy:factory [--verify]

OPTIONS:

  --verify	Verify on Etherscan

deploy:factory: Deploys a new Title Escrow factory

Deploying a new Title Escrow factory

To deploy your own modified version or your own copy of the Title Escrow factory, use this command:

npx hardhat deploy:factory --network rinkeby

The above command will deploy a new Title Escrow factory on the Rinkeby network without verifying the contract. To verify the contract, pass in the --verify flag.

Verification

When verifying the contracts through either the Hardhat's verify plugin or passing the --verify flag to the deployment tasks, which internally uses the same plugin, you need to include your correct API key. Depending on the network, add the key into your .env configuration. See the Configuration section for more information.

  • For Ethereum, set ETHERSCAN_API_KEY
  • For Polygon, set POLYGONSCAN_API_KEY

Network configuration

The table below shows the network names that are currently pre-configured:

Network ID Name Network Type
1 Ethereum Mainnet mainnet Production
11155111 Ethereum Testnet Sepolia sepolia Test
137 Polygon Mainnet polygon Production
80001 Polygon Testnet Mumbai mumbai Test
50 XDC Network xdc Production
51 XDC Apothem Network xdcapothem Test

Note: You can configure the existing networks and add others to which you want to deploy in the hardhat.config.ts file.

Configuration

Create a .env file and add your own keys into it. You can rename it from the sample file .env.sample or copy the following code into a new file:

# Infura
INFURA_APP_ID=

# API Keys
ETHERSCAN_API_KEY=
POLYGONSCAN_API_KEY=
COINMARKETCAP_API_KEY=

# Deployer Private Key
DEPLOYER_PK=

# Mnemonic words
MNEMONIC=

Only one of the following is needed:

  • DEPLOYER_PK
  • MNEMONIC

Development

This repository's development framework uses Hardhat.

You can run the tests using npm run test and find more development tasks in the package.json scripts.

Scripts

You can install dependencies, test your project, check source code, and build your project with the commands below:

npm install
npm test
npm run lint
npm run build

For more information on the commands below, see the Deployment section:

npx hardhat deploy:token
npx hardhat deploy:factory
npx hardhat deploy:token:impl

Subgraph

Check out the Token Registry Subgraph GitHub repository for more information on using and deploying your own subgraphs for the Token Registry contracts.

Additional information

The contracts have not gone through formal audits. Use them at your own discretion.