1.1.16 • Published 2 years ago

cra-template-solidity v1.1.16

Weekly downloads
-
License
ISC
Repository
github
Last release
2 years ago

⚛️ cra-template-solidity

A Ethereum starter kit for rapid development of the proof of concept solidity app

npm

A starter kit build especially for rapid smart contract development in Remix IDE. Once you wrote your Solidity smart contract in Remix you could easely deploy it into global testnet or local Ganache blockchain by using a UI. After that simple copy and paste smart contract address into params.ts

remix-deploy

Usage

Create an app from template

yarn create react-app --template cra-template-solidity .

or

npx create-react-app . --template=solidity

Deploy smart contract to Ganache and update config file (also update ABI if you changed it). The path is ~/src/config/params.ts

export const CC_CONTRACT_ADDRESS = /*process.env.REACT_APP_CONTRACT ||*/ '0xec7e48D6Fb993d532B0aA2E0393461680D7ab83f';
export const CC_APP_NAME = 'HashApp';
export { default as CC_CONTRACT_ABI } from "../contract/ABI.json";

Contains boilerplate for

  1. MetaMask Connection request

  2. Network ID check

  3. Contract deployment status check by address

  4. Sample todo-list smart contract

  5. Simple config by variable with smart contract address in source code

What's inside

  1. ethers.js
  2. TypeScript
  3. MUI
  4. Mobx
  5. react-declarative
  6. tss-react

Code samples

Solidity

// SPDX-License-Identifier: GPL-3.0

pragma solidity ^0.8.7;

contract TodoList {

    struct Todo {
        uint id;
        string content;
        address owner;
        bool isDeleted;
    }

    uint256 public pendingTodoId;

    mapping (uint256 => Todo) public todoMap;

    function todosOfOwner() public view returns (uint256[] memory) {
        uint256 todosLength = pendingTodoId;
        uint256[] memory ownedTodoDirtyIds = new uint256[](todosLength);
        uint256 ownedTodoIdx = 0;
        for (uint id = 0; id != todosLength; id++) {
            Todo memory todo = todoMap[id];
            if (todo.owner == msg.sender && !todo.isDeleted) {
                ownedTodoDirtyIds[ownedTodoIdx] = todo.id;
                ownedTodoIdx++;
            }
        }
        uint256[] memory ownedTodoIds = new uint256[](ownedTodoIdx);
        for (uint id = 0; id != ownedTodoIdx; id++) {
            ownedTodoIds[id] = ownedTodoDirtyIds[id];
        }
        return ownedTodoIds;
    }

    function addTodo(string memory _content) public {
        uint256 currentId = pendingTodoId++;
        Todo memory todo;
        todo.id = currentId;
        todo.content = _content;
        todo.owner = msg.sender;
        todoMap[currentId] = todo;
    }

    function removeTodo(uint256 _id) public {
        Todo storage todo = todoMap[_id];
        require(todo.owner == msg.sender, 'You are not the owner of that todo');
        todo.isDeleted = true;
    }

}

TypeScript

import { makeAutoObservable, runInAction } from "mobx";
import { inject, singleshot } from "react-declarative";

import {
    ethers,
    BaseContract
} from "ethers";

import EthersService from "./EthersService";

import { CC_CONTRACT_ADDRESS } from "../../config/params";
import { CC_CONTRACT_ABI } from "../../config/params";

import TYPES from "../types";

type IContract = BaseContract & Record<string, (...args: any[]) => Promise<any>>;

export class ContractService {

    private readonly ethersService = inject<EthersService>(TYPES.ethersService);

    private _instance: IContract = null as never;

    get isContractConnected() {
        return !!this._instance;
    };

    constructor() {
        makeAutoObservable(this);
    };

    getPendingTodoId = async () => Number(await this._instance.pendingTodoId());

    getTodoById = async (id: number) => {
        const todoItem = await this._instance.todoMap(id);
        return {
            id: Number(todoItem.id),
            content: String(todoItem.content),
            owner: String(todoItem.owner),
            isDeleted: Boolean(todoItem.isDeleted),
        };
    };

    addTodo = async (content: string) => await this._instance.addTodo(content);

    removeTodoById = async (id: number) => await this._instance.removeTodo(id);

    todosOfOwner = async () => {
        const todoIds: number[] = (await this._instance.todosOfOwner()).map((bigint: any) => Number(bigint));
        return await Promise.all(todoIds.map((id) => this.getTodoById(id)));
    };

    todosOfEveryone = async () => {
        const pendingId = await this.getPendingTodoId();
        const totalIds = [...Array(pendingId).keys()];
        return await Promise.all(totalIds.map((id) => this.getTodoById(id)));
    };

    prefetch = singleshot(async () => {
        console.log("ContractService prefetch started");
        try {
            const deployedCode = await this.ethersService.getCode(CC_CONTRACT_ADDRESS);
            if (deployedCode === '0x') {
                throw new Error('ContractService contract not deployed');
            }
            const instance = new ethers.Contract(
                CC_CONTRACT_ADDRESS,
                CC_CONTRACT_ABI,
                this.ethersService.getSigner(),
            ) as IContract;
            runInAction(() => this._instance = instance);
        } catch (e) {
            console.warn('ContractService prefetch failed', e);
        }
    });

}

See also

This starter kit is build on top of react-declarative npm package. I think you are going to like the way of reactive programming in this app and you will want bring it to other projects which may not require web3 technologies. So check the github repo and seek for other guides