Skip to content

Latest commit

 

History

History
459 lines (342 loc) · 17.5 KB

README.md

File metadata and controls

459 lines (342 loc) · 17.5 KB

Camino Messenger Contracts

CAMINO NETWORK CHAT WITH US

CI

This repository contains the smart contracts for the Camino Messenger.

Camino (mainnet) Deployed Contracts

Below is a table of deployed contracts and their addresses on Camino mainnet.

Contract Address
CMAccountManager 0xf9FE1eaAB73a2902136FE7A83E0703338D3b9F1e
BookingToken 0xe2b8c92B6519d1A2020dA0A5fBbA99a43A2c0922
BookingTokenOperator (Library) 0x65C34Ca1FCdF46B60C2b9b8f81475f69086116dD
CMAccount (Implementation) 0x52D94b6ccDa96BE4a99ED9C8D39682D6B4EE4702

Columbus (testnet) Deployed Contracts

Below is a table of deployed contracts and their addresses on Columbus testnet.

Contract Address
CMAccountManager 0xE5B2f76C778D082b07BDd7D51FFe83E3E055B47F
BookingToken 0xe55E387F5474a012D1b048155E25ea78C7DBfBBC
BookingTokenOperator (Library) 0xac1300CDaF25fD622a0fA09302fB09e9a6400ae4
CMAccount (Implementation) 0xaA573b3942168bC5222C7aa11c6708D7f64Af374

Chain4Travel Messenger Server

Chain4Travel is running the first and currently only messenger server.

Camino Mainnet Address
Messenger URL https://messenger.chain4travel.com
Messenger CM Account 0x16DFfB3911BB0b1B53eF4d774804381f0B38B5d7
Messenger Service Bot (toBot) Addr 0xbeb027D2f439805E17EAA16Da26c1FCa68a30232
Columbus Testnet Address
Messenger URL https://dev.messenger.chain4travel.com
Messenger CM Account 0xF6bA5c68A505559c170dC7a30448Ed64D8b9Bc3B
Messenger Service Bot (toBot) Addr 0xff6BAC3d972680515cbB59fCB6Db6deB13Eb0E91

For network fee cheques, the Camino Messenger Bot should use the values above for:

  • fromCMAccount: Partner's CM Account address.
  • toCMAccount: The "Messenger CM Account" address from the tables above.
  • toBot: The "Messenger Service Bot" address from the tables above.

Quickstart

Clone the repo and change directory into

git clone [email protected]:chain4travel/camino-messenger-contracts.git
cd camino-messenger-contracts

Install packages

yarn install

Run tests. This will compile the contracts and run the tests:

yarn test

Setting Hardhat Vars

For Camino (mainnet) and Columbus (testnet) networks, we are using hardhat's vars tool to store private keys. To set these you can use the commands below:

yarn hardhat vars set COLUMBUS_DEPLOYER_PRIVATE_KEY
yarn hardhat vars set CAMINO_DEPLOYER_PRIVATE_KEY

These will also be used for yarn hardhat manager tasks. These variables are stored in the /home/$USER/.config/hardhat-nodejs/vars.json file, so they are not accidentally pushed to git.

Contracts

CMAccount

The CMAccount contract represents a Camino Messenger account. Currently, it includes functionalities for the management of bots. More features will be introduced in the future.

This contract works closely with the CMAccountManager to handle accounts.

CMAccountManager

The CMAccountManager contract acts as a manager for CMAccount contracts. It handles the creation, registration, verification, and management of accounts. It also keeps records for the developer wallet, fees, and CMAccount implementation address. Accounts can only be upgraded to the implementation address that the manager holds.

ChequeManager

The ChequeManager contract handles the processing of cheques. It verifies the signatures, checks the validity of the cheques, and transfers the funds between accounts. It also calculates the developer fee and transfers it to the developer's wallet.

This is a base contract that is inherited by the CMAccount contract.

PartnerConfiguration

The PartnerConfiguration contract is used by the CMAccount and implements features to register supported (supplier) and wanted (distributor) services, register public keys that would be used to encrypt private data, off-chain payment support, and on-chain supported payment token addresses.

ServiceRegistry

The ServiceRegistry contract is used by the CMAccountManager contract and implements a registry that is used to hash service names to keccak256 hashes and store them in a mapping as hash => service name. CMAccount use these to resolve hashes to service names and service names to hashes.

BookingToken

The BookingToken contract is an ERC-721 NFT contract that is used by the partners to mint and buy Booking Tokens. A Booking Token represents a booking done on the Camino Messenger ecosystem.

Only the CMAccount contracts are allowed to mint and buy the tokens.

KYCUtils

The KYCUtils contract provides utility functions for KYC (Know Your Customer).

Proxies

For CMAccountManager and CMAccount contracts, an ERC1967Proxy (UUPS) is used.

The hardhat-ignition module deploys the CMAccountManager contract and then deploys an ERC1967Proxy proxy, setting the implementation address to the CMAccountManager's address. We will call this proxy managerProxy or simply manager in this document.

Then a CMAccount contract is deployed, and its address is set by calling managerProxy.setAccountImplementation(CMAccount.getAddress()). After that, the manager is ready to create CM accounts.

Calling managerProxy.createCMAccount(...) with the necessary arguments creates an ERC1967Proxy and sets the implementation address to the recorded account implementation address in the manager. After it is deployed, it is immediately (same transaction) initialized with the given arguments.

Deploy Contracts Locally

Run local hardhat node

yarn hardhat node

Deploy contracts using the ignition module

yarn hardhat ignition deploy ignition/modules/0_development.js --network localhost

Output should be similar to this

yarn run v1.22.19
$ /hgst/work/github.com/chain4travel/camino-messenger-contracts/node_modules/.bin/hardhat ignition deploy ignition/modules/0_development.js --network localhost
Hardhat Ignition 🚀

Deploying [ CMAccountManagerModule ]

Batch #1
  Executed BookingTokenProxyModule#BookingToken
  Executed CMAccountManagerModule#BookingTokenOperator
  Executed ManagerProxyModule#CMAccountManager

Batch #2
  Executed BookingTokenProxyModule#ERC1967Proxy
  Executed CMAccountManagerModule#CMAccount
  Executed ManagerProxyModule#ERC1967Proxy

Batch #3
  Executed CMAccountManagerModule#BookingToken
  Executed CMAccountManagerModule#CMAccountManager

Batch #4
  Executed CMAccountManagerModule#BookingToken.initialize
  Executed CMAccountManagerModule#CMAccountManager.initialize
  Executed CMAccountManagerModule#CMAccountManager.setAccountImplementation
  Executed CMAccountManagerModule#CMAccountManager.setBookingTokenAddress

[ CMAccountManagerModule ] successfully deployed 🚀

Deployed Addresses

BookingTokenProxyModule#BookingToken - 0x5FbDB2315678afecb367f032d93F642f64180aa3
CMAccountManagerModule#BookingTokenOperator - 0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512
ManagerProxyModule#CMAccountManager - 0x9fE46736679d2D9a65F0992F2272dE9f3c7fa6e0
BookingTokenProxyModule#ERC1967Proxy - 0xCf7Ed3AccA5a467e9e704C703E8D87F634fB0Fc9
CMAccountManagerModule#CMAccount - 0xDc64a140Aa3E981100a9becA4E685f962f0cF6C9
ManagerProxyModule#ERC1967Proxy - 0x5FC8d32690cc91D4c39d9d3abcBD16989F875707
CMAccountManagerModule#BookingToken - 0xCf7Ed3AccA5a467e9e704C703E8D87F634fB0Fc9
CMAccountManagerModule#CMAccountManager - 0x5FC8d32690cc91D4c39d9d3abcBD16989F875707
Done in 3.78s.

You can also see your deployed contract addresses in the ignition/deployments/<chainid>/deployed_addresses.json file.

Visualize the deployment

yarn hardhat ignition visualize ignition/modules/0_development.js

This will open a browswer tab with the deployment flow visualized.

Cheques: Create & Sign

This document provides a detailed guide on creating, signing, and verifying cheques using Camino Messenger smart contracts. The guide includes information on the cheque structure, type hashes, domain separator, and EIP-712 standard.

EIP-712 Overview

EIP-712 is a standard for hashing and signing of typed structured data. It is used to improve the usability of off-chain message signing for on-chain verification. The standard defines a structured format for messages, allowing them to be easily parsed and verified. For more info see: https://eips.ethereum.org/EIPS/eip-712

Cheque Typehash, Domain Typehash, and Domain Separator

  • Cheque Typehash: This is the keccak256 hash of the MessengerCheque struct type.

    function calculateMessengerChequeTypeHash() {
        const typeHash = ethers.keccak256(
            ethers.toUtf8Bytes(
                "MessengerCheque(address fromCMAccount,address toCMAccount,address toBot,uint256 counter,uint256 amount,uint256 createdAt,uint256 expiresAt)",
            ),
        );
        return typeHash;
    }
  • Domain Typehash: This is the keccak256 hash of the EIP-712 domain type.

    function calculateDomainTypeHash() {
        const domainTypeHash = ethers.keccak256(
            ethers.toUtf8Bytes("EIP712Domain(string name,string version,uint256 chainId)"),
        );
        return domainTypeHash;
    }
  • Domain Separator: This is a hash that combines the domain typehash with the name, version, and chain ID of the domain.

    function calculateDomainSeparator(domainName, domainVersion, chainId) {
        const coder = AbiCoder.defaultAbiCoder();
        const domainSeparator = ethers.keccak256(
            coder.encode(
                ["bytes32", "bytes32", "bytes32", "uint256"],
                [
                    calculateDomainTypeHash(),
                    ethers.keccak256(ethers.toUtf8Bytes(domainName)),
                    ethers.keccak256(ethers.toUtf8Bytes(domainVersion)),
                    ethers.toBigInt(chainId),
                ],
            ),
        );
        return domainSeparator;
    }
    
    function calculateDomainSeparatorForChain(_chainId) {
        const domainName = "CaminoMessenger";
        const domainVersion = "1";
        const chainId = _chainId;
        return calculateDomainSeparator(domainName, domainVersion, chainId);
    }

Creating Cheques

Define a cheque structure

const cheque = {
    fromCMAccount: "0x...", // Address of the CM Account of the sending bot (signer)
    toCMAccount: "0x...", // Address of the CM Account of the receiving bot
    toBot: "0x...", // Address of the bot receiving the cheque
    counter: 123, // Counter, needs to be incremented for each cheque
    amount: ethers.parseUnits("1.0", "ether"), // 1 ETH, amount to pay (after substracting the last paid amount)
    createdAt: Math.floor(Date.now() / 1000), // Current Unix timestamp, as an example
    expiresAt: Math.floor(Date.now() / 1000) + 86400, // Expiration timestamp
};

Example:

const cheque = {
    fromCMAccount: "0x90F79bf6EB2c4f870365E785982E1f101E93b906",
    toCMAccount: "0x70997970C51812dc3A010C7d01b50e0d17dc79C8",
    toBot: "0x15d34AAf54267DB7D7c367839AAf71A00a2C6A65",
    counter: 123n,
    amount: 1000000000000000000n,
    createdAt: 1722282175n,
    expiresAt: 1722652561n,
};

Calculate Messenger Cheque Hash

Messenger Cheque Typehash:

function calculateMessengerChequeHash(cheque) {
    const chequeTypeHash = calculateMessengerChequeTypeHash();

    const coder = ethers.AbiCoder.defaultAbiCoder();
    const encodedCheque = coder.encode(
        ["bytes32", "address", "address", "address", "uint256", "uint256", "uint256", "uint256"],
        [
            chequeTypeHash,
            cheque.fromCMAccount,
            cheque.toCMAccount,
            cheque.toBot,
            cheque.counter,
            cheque.amount,
            cheque.createdAt,
            cheque.expiresAt,
        ],
    );
    return ethers.keccak256(encodedCheque);

Sign cheque

The function below uses ethers.signTypedData to sign the cheque, which is calculating the type hashes and domain separator from the provided data according to EIP-712 specification. So, the functions above are for when you want to calculate the hashes separately.

async function signMessengerCheque(cheque, signer) {
    const chainId = await signer.provider.getNetwork().then((n) => n.chainId);

    const types = {
        MessengerCheque: [
            { name: "fromCMAccount", type: "address" },
            { name: "toCMAccount", type: "address" },
            { name: "toBot", type: "address" },
            { name: "counter", type: "uint256" },
            { name: "amount", type: "uint256" },
            { name: "createdAt", type: "uint256" },
            { name: "expiresAt", type: "uint256" },
        ],
    };

    const DOMAIN_NAME = "CaminoMessenger";
    const DOMAIN_VERSION = "1";

    const domain = {
        name: DOMAIN_NAME,
        version: DOMAIN_VERSION,
        chainId: chainId,
    };

    const signature = await signer.signTypedData(domain, types, cheque);
    return signature;
}

If you would like to calculate the typed data hash yourself and sign it, check out the function that is generating the hash on the CMAccount contract:

function hashTypedDataV4(MessengerCheque memory cheque) public view returns (bytes32) {
    return keccak256(abi.encodePacked("\x19\x01", DOMAIN_SEPARATOR, hashMessengerCheque(cheque)));
}

And also in utils/cheques.js file (Javascript):

function calculateTypedDataHash(cheque, domainSeparator) {
    const chequeHash = calculateMessengerChequeHash(cheque);
    return ethers.keccak256(ethers.concat([ethers.toUtf8Bytes("\x19\x01"), domainSeparator, chequeHash]));
}

Tip

All the functions mentioned above can be seen from utils/cheques.js file.

Cheques: Verify

Cheque verification is normally done on-chain by the verifyCheque function on the CM Account contract of the cheque's drawer (the bot who signed the cheque).

Signature of the function is like this:

function verifyCheque(
    MessengerCheque memory cheque,
    bytes memory signature
) public returns (address signer, uint256 paymentAmount) {}

This function does not only verify that the signer of the cheque is a registered bot on the CM Account, but also other verifications like:

  • If the fromCMAccount is the contract itself
  • If the address of toCMAccount is a registered CM Account on the manager
  • If expiresAt timestamp is bigger then block.timestamp
  • Last counter and last amount recorded on the contract are lower then the cheque's
  • If the toBot address has the required role (CHEQUE_OPERATOR_ROLE)

So, to only verify if cheque's signature is valid, without doing the verifications above (which can only be done on-chain), you can use the examples below.

Verify Cheque Signature Off-Chain

JavaScript

For example code about how to verify signatures off-chain using JavaScript and Ethers.js, check out the test/ChequeManager.test.js test file.

Note

Signing without signTypedData:

If you would like to learn how you can sign a cheque without relying ethers.js's signTypedData (EIP712), there is an example script at examples/sign_primitive.js.

Go

TODO: WIP

Python

TODO: WIP