re-frame Effectful Handlers to work with Ethereum blockchain Web3 API, using cljs-web3
Add into your project.clj
Include [district0x.re-frame.web3-fx]
in your CLJS file
(ns my.app
(:require [district0x.re-frame.web3-fx]))
This library was completely rewritten on version upgrade 0.2.3 -> 1.0.0. API was greatly changed (simplified). Reasons for breakage were also unerlying changes in Ethereum (fork to Byzantium) and preparations for web3 1.0.0. I deeply appologize, but this was absolutely necessary.
- :web3/call
- :web3/get-balances
- :web3/watch-events
- :web3/watch-transactions
- :web3/watch-blocks
- :web3/stop-watching-balances
- :web3/stop-watching
- :web3/stop-watching-all
Following effect handlers are available:
Use it to call any function from cljs-web3 or any smart contract function. Calling cljs-web3 function:
(reg-event-fx
::load-accounts
(fn [{:keys [:db]} []]
{:web3/call {:web3 (:web3 db)
:fns [{:fn cljs-web3.eth/accounts
:args []
:on-success [::accounts-loaded]
:on-error [::error]}]}}))
Calling constant smart-contract function. In this case getting total supply of a ERC20 Token:
(reg-event-fx
::get-token-total-supply
(fn [{:keys [:db]}]
{:web3/call {:web3 (:web3 db)
:fns [{:instance (:token-contract-instance db)
:fn :total-supply
:on-success [::token-total-supply-result]
:on-error [::error]}]}}))
Calling state changing smart-contract function, aka sending a transaction to the network. In this case calling mint
function of MintableToken. Notice there's no on-success
, on-error
. Given callbacks are executed at following situations:
:on-tx-hash
When tx is successfully sent to the network. Receives tx-hash in parameters.:on-tx-hash-error
When tx wasn't send to the network. Usually user rejected to sign.:on-tx-success
When tx was processed without error. Receives receipt in parameters.:on-tx-error
When there was an error during processing a transaction. Receives receipt in parameters.:on-tx-receipt
General callback when tx was processed. Either with error or not. Receives receipt in parameters. (You don't need to use all of them, only ones you need)
All of these callbacks have their respective multi-event callbacks i.e.:
:on-tx-hash-n
:on-tx-hash-error-n
:on-tx-success-n
:on-tx-error-n
:on-tx-receipt-n
(reg-event-fx
::mint-token
(fn [{:keys [:db]} [_ {:keys [:to :amount :from]}]]
{:web3/call {:web3 (:web3 db)
:fns [{:instance (:token-contract-instance db)
:fn :mint
:args [to amount]
:tx-opts {:from from
:gas 4500000}
:on-tx-hash-n [[::tx-send-success] [::extra]]
:on-tx-hash-error [::tx-send-failed]
:on-tx-success [::token-minted]
:on-tx-error [::tx-failed]
:on-tx-receipt [::tx-receipt-loaded]}]}}))
Gets balance of Ether or ERC20 token. Optionally you can pass :watch? true
, so the callback will be fired everytime
the balance changes.
Getting and watching balance or Ether:
(reg-event-fx
::load-ether-balances
(fn [{:keys [:db]} [_ addresses]]
{:web3/get-balances {:web3 (:web3 db)
:addresses (for [address addresses]
{:address address
:watch? true
:on-success [::ether-balance-loaded address]
:on-error [::error]})}}))
Getting and watching balance of a ERC20 Token. Notice you need to pass :instance
of a ERC20 contract
(reg-event-fx
::load-token-balances
(fn [{:keys [:db]} [_ addresses]]
{:web3/get-balances {:web3 (:web3 db)
:addresses (for [address addresses]
{:address address
:instance (:token-contract-instance db)
:watch? true
:on-success [::token-balance-loaded address]
:on-error [::error]})}}))
Listens to smart-contract events. Callback receives event :args
as first param and complete event data as a second.
In this example we watch Mint event of MintableToken
(reg-event-fx
::watch-mint
(fn [{:keys [:db]} [_ {:keys [:to]}]]
{:web3/watch-events {:events [{:id :mint-watcher
:event :Mint
:instance (:token-contract-instance db)
:block-filter-opts {:from-block 0}
:event-filter-opts {:to to}
:on-success [::token-mint-event]
:on-error [::error]}]}}))
Sets up listener until transaction receipt is available. Callbacks are fired same way as in :web3/call
for
state-changing contract functions.
(reg-event-fx
::watch-transaction
interceptors
(fn [{:keys [:db]} [tx-hash]]
{:web3/watch-transactions {:web3 (:web3 db)
:transactions [{:id :my-watcher
:tx-hash tx-hash
:on-tx-success [::tx-success]
:on-tx-error [::error]
:on-tx-receipt [::tx-receipt]}]}}))
Sets up listener with callback fired on each new Ethereum block.
(reg-event-fx
::watch-blocks
(fn [{:keys [:db]}]
{:web3/watch-blocks {:id :my-watcher
:web3 (:web3 db)
:on-success [::new-block]
:on-error [::error]}}))
Stops listeners previously set up with :web3/get-balances
(reg-event-fx
::stop-watching-balances
(fn [{:keys [:db]} [_ addresses]]
{:web3/stop-watching-balances {:addresses (for [address addresses]
{:address address
:instance (:token-contract-instance db)})}}))
In any effect handler above, where you could provide :id
, you can use this effect handler to stop that listener.
(reg-event-fx
::stop-watching
(fn [{:keys [:db]}]
{:web3/stop-watching {:ids [:my-watcher]}}))
Stops all listeners set up by all effect handlers.
(reg-event-fx
::stop-watching
(fn [{:keys [:db]}]
{:web3/stop-watching-all true}))
- On development machine
npx shadow-cljs watch test-browser
- Open http://d0x-vm:6502
- On CI (or headless env)
npx shadow-cljs compile test-ci
-
CHROME_BIN=`which chromium-browser` npx karma start --single-run`