Skip to content
This repository has been archived by the owner on Dec 12, 2022. It is now read-only.

Latest commit

 

History

History
576 lines (408 loc) · 19.6 KB

README.md

File metadata and controls

576 lines (408 loc) · 19.6 KB

cljs-web3-next

CircleCI

This ClojureScript library provides a API for interacting with Ethereum nodes.

Installation

Latest released version of this library:
Clojars Project

API Overview

cljs-web3.core

Core functions which deal with creating and checking the status of Web3 connections.

This function is the gateway to using the library. Most other functions will take the map it returns as their first argument, unless specified otherwise.

It creates a Web3 instance over a websocket connection. Takes an the url and a map of options as parameters. Returns a the websocket (the provider)

Example:

(ns my-district
  (:require [cljs-web3.core :as web3-core]
            [web3.impl.web3js :as web3js]))

(def web3 (web3-core/websocket-provider "ws://127.0.0.1:8545" {:client-config {:max-received-frame-size 100000000
                                                                               :max-received-message-size 100000000}}))

Similar to websocket-provider, but creates a Web3 instance from a URL address.

NOTE some functions, notably subscriptions (see e.g. subscribe-events), will work differently with than http-provider, which is why it's generally recommended to use the websocket connections for your subscriptions.

Allows for extending the Web3 object with any supported JSON RPC method, which is otherwise not a part of this library. Takes as arguments:

  • a map returned by the websocket-provider or http-provider function.
  • the module name (a keyword)
  • a colection of method maps with following keys:
    • name : Name of the method to add
    • call : The RPC method name
    • params : The number of parameters for that call (optional)

Example:

(extend web3
 :evm
 [{:name "increaseTime"
   :call "evm_increaseTime"
   :params 1})])

Returns the same web3 map as passed, but now the provider is extended with the increaseTime method in the evm module, which you can invoke like this:

(.increaseTime (aget web3 :provider "evm") 1000)

Takes as arguments a map returned by the provider function and returns the URL of the node it is connected to.

(connection-url web3)
;; "ws://127.0.0.1:8545"

Takes as arguments a map returned by the provider function and returns the connection status as a boolean value. This function is synchronous, for an asynchronous method see is-listening?.

Immediately disconnects the provider, returns nil.

(disconnect web3)

Takes a provider map and a callback function as arguments, callback is executed when the connection is established.

(web3-core/on-connect web3 (fn [event] (prn "just connected")))

Takes a provider map and a callback as arguments, callback is executed when the connection is dropped.

(web3-core/on-disconnect web3 (fn [event] (prn "your web3 socket has lost its connection")))

Similar as on-connect and on-disconnect but executes the callback when connection throws an error.

This namespace contains functions for interacting with the Ethereum blockchain and Ethereum smart contracts.

Asynchronous version of the connected? function, takes the provider map and a callback function. Returns a JS/Promise which returns a boolean.

You can use it to set a periodically executing connection healthcheck:

(js/setInterval (fn []
                  (is-listening? web3
                                 (fn [_ connected?]
                                   (when-not connected?
                                     (reset-connection)))))
                3000)

Takes a provider map, contract abi interface as returned by the solc compiler (in a JSON format). Returns a Contract instance.

(def abi (aget (.readFileSync js/fs MyContract.json) "abi))

(contract-at web3 abi "0x98f93ed24052ceed35741beee1d75287cb297137")

Takes a provider map, transaction hash (a String) and an optional callback function. Returns a JS/Promise which evaluates to the receipt of that transaction.

(get-transaction-receipt web3 "0xd5000d0de00e50d6ac47fe48d11d9dc8258e74fc69b57b5c804e7ddc424af8d0" (fn [tx] (prn "I got the receipt" tx))

Returns the availiable ethereum accounts in the wallet on the node it is connected to.

(accounts web3)

Takes a web3 map and an optional callback as arguments. Returns a JS/Promise which evaluates to the current block number.

(get-block-number web3)

Takes as arguments:

  • a web3 map
  • a block number or hash
  • a boolean parameter specifying whether to include the transactions
  • an optional callback

Returns a JS/Promise which evaluates to the representation of the last mined block.

(get-block web3 block-number false)

This function returns the ABI bytecode of a transaction.

It takes as arguments:

  • the provider map
  • smart contract instance, as returned by the contract-at function
  • the kebab-cased keyword with the name of the smart contracts function
  • a vector with the arguments of the function

Returns bytecode as a string.

(encode-abi web3 my-contract :set-counter [3])

This function executes a statless (read only) method of a smart contract.

It takes as arguments:

  • the provider map
  • smart contract instance, as returned by the contract-at function
  • the kebab-cased keyword with the name of the smart contracts function to execute
  • a vector with the arguments of the function
  • map of options:
  • :from : the account calling the contract (see accounts)

Returns a JS/Promise which evaluates to the return value of that function.

(web3-eth/contract-call web3
                        my-contract
                        :my-plus
                        [3 4]
                        {:from (first accounts)})

This function executes a state-altering (payable) method of a smart contract.

It takes as arguments:

  • the provider map
  • smart contract instance, as returned by the contract-at function
  • the kebab-cased keyword with the name of the smart contracts function to execute
  • a vector with the arguments of the function
  • map of options:
  • :from : the account calling the contract (see accounts)
  • :gas : max amount of gas you are willing to spend

Returns a JS/Promise which evaluates to the tx-hash once the transaction is mined.

(<! (web3-eth/contract-send web3
                            my-contract
                            :set-counter
                            [3]
                            {:from (first accounts)
                             :gas 4000000}))

Creates a subscription listening to a specific contracts event.

It takes as arguments:

  • the provider map
  • smart contract instance, as returned by the contract-at function
  • the CamelCased keyword with the name of the smart contracts event to listen to
  • map of options:
    • :from-block : the block number (greater than or equal to) from which to get events on.
  • a nodejs-style callback function (error is a first parameters and response the second), executed each time the event is seen (optional)

Returns a EventEmitter object which can be subsequently used with on to react to an even finer-grained control.

(web3-eth/subscribe-events web3
                           my-contract
                           :SetCounterEvent
                           {:from-block block-number}
                           (fn [error event]
                             (prn "new event" event})))

This function lets you create subscriptions listening to specific logs of things happening on the blockchain, filtered by a given list of topics.

It takes as arguments:

  • the provider map
  • map of options:
    • :from-block : the block number (greater than or equal to) from which to get events on.
    • :address : a string or a vector of strings with addresses to listen to
    • :topics : An vector of values which must each appear in the log entries. Order needs to match the order in the :address vector.
  • a nodejs-style callback function (error is a first parameters and response the second), executed each time the log is seen (optional)
(subscribe-logs web3
                {:address [address]
                 :topics [event-signature]
                 :from-block block-number}
                (fn [_ event] (prn event))

Similar to the subscribe-events function, it returns an EventEmitter which can be augmented using on.

Use this function to decodes an ABI encoded log data and indexed topic data, such as returned by the subscribe-logs subscription.

Arguments:

  • the provider map abi : a map of interface inputs array data : the abi bytecode of the data field in the log (a string) topics : a vector of the topics of the log (see subscribe-logs)

This function can be used with the EventEmitter returned by the subscribe-events and subscribe-logs. It will add callbacks executed on specific events:

  • :connected: fires the callback when the subscription is created, returns the id of that subscription as the first argument of the callback
  • :data : fired on each incoming log with the log object as argument. If the subscription happens ot be listening to a smart event contract the event passed as th ecallback argument is the same as for the subscribe-events.
  • :changed : fires when the log is removed from the blockchain
  • :error : fires when an error in the subscription occurs.
(-> event-emitter
    (#(on web3 % :connected (fn [sub-id]
                              (prn "subscribed to SetCounterEvents. Subscription id :" sub-id))))
    (#(on web3 % :data (fn [event]
                         (prn "new SetCounterEvents :" event))))
    (#(on web3 % :error (fn [error]
                          (prn "Error :" error)))))

Clears a subscription, takes a web3 provider an an event emitter (returned by the subscribe-events or subscribe-logs) as arguments.

(web3-eth/unsubscribe web3 event-emitter)

Clears all created subscription.

Returns all past events for the specified contract.

It takes as arguments:

  • the provider map
  • smart contract instance, as returned by the contract-at function
  • the CamelCased keyword with the name of the smart contracts event to replay
  • map of options:
    • :from-block : the block number (greater than or equal to) from which to return the events
    • :to-block : the block number (less than or equal to) up to which the events are returned
  • a nodejs-style callback function (error is a first parameters and response the second), executed each time the event is seen (optional)
(web3-eth/get-past-events web3
                          my-contract
                          :SetCounterEvent
                          {:from-block 0
                           :to-block "latest"}
                          (fn [events]))

A subscribe-logs equivalent of get-past-events.

(web3-eth/get-past-logs web3
                        {:address [address]
                         :topics [event-signature]
                         :from-block 0
                         :to-block "latest"}
                        (fn [logs]))

This namespaces provides various utility functions.

Returns a sha3 of the input.

Implementation of Solidity sha3 function. Takes a web3 provider and a variable number of arguments, returns a hash value (a string).

(solidity-sha3 web3 "0x7d10b16dd1f9e0df45976d402879fb496c114936" 6 "abc")

(from-ascii web3 args)

(to-ascii web3 arg)

(number-to-hex web3 number)

(from-wei web3 number <unit>)

(to-wei web3 number <unit>)

(address? web3 address)

NOTE The functions in this namespaces are not a part of the API unless you extend the evm module with these RPC calls. They will only to work with a testrpc such as ganache

Increases the blockchain time by the specified number of seconds.

(increase-time! web3 [seconds] callback)

Instantly mines a block.

(mine-block web3 callback)

Snapshot the state of the blockchain at the current block.

(snapshot! web3 callback)

Revert the state of the blockchain to a previous snapshot.

(revert! web3 [snapshot-id] callback)

Functions in this namespace are not part of the API, rather help in turning the JS objects returned by the API funcions to the corresponding Clojure data structures. As such they are independent of the currently used implementation and do not take the web3 provider as their first argument.

From JavaScript Object to Clojure map with kebab-cased keywords, e.g. :

#js {:fromBlock 0, :toBlock "latest"}
;; =>
{:from-block 0 :to-block "latest"}

From Clojure with kebab-cased keywords to JavaScript e.g.

{:from-block 0 :to-block "latest"}
;; =>
#js {:fromBlock 0, :toBlock "latest"}

Given a contract instance returned by the contract-at function and a :CamelCase key of the event, returns the abi interface of that event, which can be used e.g. with subscribe-logs.

(event-interface my-contract :SetCounterEvent)

Given a returnValues field (part of the data return by subscriptions, such as subscribe-events) and an event-interface returns a edn (aka Clojure) representation of this events return values.

(return-values->clj return-values event-interface)

Running tests

lein npm install

spin up a testnet instance in a separate shell

npx truffle develop

migrate contracts

npx truffle migrate --network ganache

New build, test and release commands

As this library is meant to work both in browsers and on Node.js (server), it must be tested on both as well. Additionally CI runs the tests slightly different way, so that's the 3rd test environment.

  • it's still in browser, but CI gets success vs failure depending on the
  • browser process exit code so a bit of extra work is needed to get it out from JS (Karma is used for that)

Node.js

  1. Build: npx shadow-cljs compile test-node
  2. Tests: node out/node-tests.js

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
    

inspect on headless chrome on another chrome instance

  1. Run headless chrome: chromium-browser --headless --remote-debugging-port=9222 --remote-debugging-address=0.0.0.0 --allowed-origins="*" https://chromium.org
  2. Open chrome://inspect/#devices and configure remote target with IP ADDRESS (hostname doesn't work)

Build & release with deps.edn and tools.build

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