/district-ui-smart-contracts

⚠️ This code now resides at d0x monorepo

Primary LanguageClojureEclipse Public License 1.0EPL-1.0

district-ui-smart-contracts

CircleCI

Clojurescript re-mount module, that takes care of loading Ethereum smart-contract files.

Installation

This module is available as a Maven artifact from Clojars. The latest released version is:
Clojars Project

Include [district.ui.smart-contracts] in your CLJS file, where you use mount/start

API Overview

Warning: district0x modules are still in early stages, therefore API can change in a future.

district.ui.smart-contracts

This namespace contains smart-contracts mount module. Once you start mount it'll take care of loading smart contract files.

You can pass following args to initiate this module:

  • :disable-loading-at-start? Pass true if you don't want load ABIs or BINs at start
  • :contracts A map of smart-contracts to load
  • :load-bin? Pass true if you want to load BIN files as well
  • :format The compiled contracts output format, can be one of :solc-abi-bin :truffle-json
  • :load-method How to take contracts content to the browser. Possible values :request(default), :use-loaded (see adding abis into js bundle)
  • :contracts-path Path where contracts should be loaded from. Default: "./contracts/build/"
  • :contracts-version Pass some version for bypassing browser's cache after deploying new contracts to production. Pass :no-cache if you want to invalidate browser cache on every request (useful for development)
  • :request-timeout Request timeout for loading files. Default: 10000 (10s)

Passed :contracts should have following format:

(ns my-district.smart-contracts)

(def smart-contracts
  {:my-contract {:name "MyContract"                         ;; ABI and BIN is loaded by this name
                 :address "0xfbb1b73c4f0bda4f67dca266ce6ef42f520fbb98"
                 ;; optional, if not provided, will try to load
                 :abi nil
                 ;; optional, if not provided, will try to load
                 :bin nil
                 ;; optional, path would overwrite generic :contracts-path
                 :path nil
                 ;; optional, path would overwrite generic :contracts-version
                 :version nil}})

Starting the module may look like this:

  (ns my-district.core
    (:require [mount.core :as mount]
              [district.ui.smart-contracts]
              [my-district.smart-contracts]))

  (-> (mount/with-args
        {:web3 {:url "https://mainnet.infura.io/"}
         :smart-contracts {:contracts my-district.smart-contracts/smart-contracts
                           :contracts-path "./"}})
    (mount/start))

In order to use the option :load-method :use-loaded you need to provide some info at build time so contracts abis can be included in the bundle. You provide this information via environment variables at build time.

Example:

SMART_CONTRACTS=./src/memefactory/shared/smart_contracts.cljs
SMART_CONTRACTS_BUILD_PATH=./resources/public/contracts/build/
SMART_CONTRACTS_SKIP=ds-guard,param-change-registry-db,meme-registry-db,minime-token-factory

Be aware that using this method is only supported for contracts compiled in the truffle json format.

district.ui.smart-contracts.subs

re-frame subscriptions provided by this module:

Returns all contracts.

Returns contract by contract-key

Returns address of a contract.

Returns ABI of a contract.

Returns BIN of a contract.

Returns name of a contract.

Returns web3 instance of a contract.

(ns my-district.home-page
  (:require [district.ui.smart-contracts.subs :as contracts-subs]
            [re-frame.core :refer [subscribe]]))

(defn home-page []
  (let [contract-abi (subscribe [::contracts-subs/contract-abi :my-contract])]
    (fn []
      [:div "MyContract ABI is: " @contract-abi])))

district.ui.smart-contracts.events

re-frame events provided by this module:

Loads smart contracts. Pass same args as to mount start.

Event fired when a single file was loaded. Either ABI or BIN.

Event fired when all smart contract files have been loaded. Use this event to hook into event flow from your modules. One example using re-frame-forward-events-fx may look like this:

(ns my-district.events
    (:require [district.ui.smart-contracts.events :as contracts-events]
              [re-frame.core :refer [reg-event-fx]]
              [day8.re-frame.forward-events-fx]))

(reg-event-fx
  ::my-event
  (fn []
    {:register :my-forwarder
     :events #{::contracts-events/contracts-loaded}
     :dispatch-to [::do-something]}))

Sets new contract into re-frame db

Fired when there was an error loading contract file

district.ui.smart-contracts.deploy-events

Events useful for deploying contracts. This namespace is meant to be used only in tests or very simple apps. Any larger application should be doing smart-contract deployment on server-side via district-server-smart-contracts.

Deploys a smart-contract of key contract-key and saves new address into re-frame db.

  (ns my-district.core
    (:require [mount.core :as mount]
              [district.ui.smart-contracts]
              [district.ui.smart-contracts.deploy-events :as deploy-events]
              [district.ui.smart-contracts.queries :as queries]))

  (-> (mount/with-args
        {:web3 {:url "http://localhost:8549"}
         :smart-contracts
          {:disable-loading-at-start? true
           :contracts {:deploy-test-contract {:name "DeployTestContract"
                                              :abi (js/JSON.parse "[{\"inputs\":[{\"name\":\"someNumber\",\"type\":\"uint256\"}],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"}]")
                                              :bin "0x60606040523415600e57600080fd5b604051602080607183398101604052808051915050801515602e57600080fd5b50603580603c6000396000f3006060604052600080fd00a165627a7a72305820f6c231e485f5b65831c99412cbcad5b4e41a4b69d40f3d4db8de3a38137701fb0029"}}}})
    (mount/start))

(dispatch [::deploy-events/deploy-contract :deploy-test-contract {:gas 4500000
                                                                  :arguments [1]
                                                                  :from "0xb2930b35844a230f00e51431acae96fe543a0347"
                                                                  :on-success [::optional-callback]
                                                                  :on-error [::optional-error-callback]}])

When successfully deployed, you'll be able to access contract instance and address same way as other contracts

(queries/contract-address db :deploy-test-contract)
(queries/instance db :deploy-test-contract)

Event fired when deploying a contract failed.

district.ui.smart-contracts.queries

DB queries provided by this module: You should use them in your events, instead of trying to get this module's data directly with get-in into re-frame db.

Works the same way as sub ::contracts

Works the same way as sub ::contract

Works the same way as sub ::contract-address

Works the same way as sub ::contract-abi

Works the same way as sub ::contract-bin

Works the same way as sub ::contract-name

Works the same way as sub ::instance

Merges contracts and returns new re-frame db

Merges a contract and returns new re-frame db

Associates ABI to contract and returns new re-frame db

Associates BIN to contract and returns new re-frame db

Dependency on other district UI modules

Development

yarn install
# Start ganache blockchain with 1s block time
ganache-cli -p 8549 -b 1 --noVMErrorsOnRPCResponse

Test

Browser

  1. Build: npx shadow-cljs watch test-browser
  2. Tests: http://d0x-vm:6502

CI (Headless Chrome, Karma)

  1. Build: npx shadow-cljs compile test-ci
  2. Tests: CHROME_BIN=`which chromium-browser` npx karma start karma.conf.js --single-run

Build & release with deps.edn and tools.build

  1. Build: clj -T:build jar
  2. Release: clj -T:build deploy