Skip to content

Commit

Permalink
Merge pull request #4 from pelle/ethereum-url
Browse files Browse the repository at this point in the history
Introduction of Signer concept
  • Loading branch information
pelle authored Sep 21, 2016
2 parents 1816f16 + 12e592c commit 838189d
Show file tree
Hide file tree
Showing 19 changed files with 9,403 additions and 24,634 deletions.
9 changes: 9 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,14 @@
# CLOTH Changelog

### 0.3.0

- Introduces concept of `signer` BREAKING CHANGE
- Support ethereum URL's
- Supports Proxy Contracts
- Fixes issues with bytes32
- Supports array return values
- Make polling times adjustable

### 0.2.7

- Fix `bytes` and `bytesXX` encoding and decoding of solidity function call parameters
Expand Down
60 changes: 49 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@

Simple ClojureScript (soon Clojure as well) library for interacting with the Ethereum blockchain.

This is extremely WIP and really should not be used by anyone yet. API is likely to change alot in particular with regular clojure support.
Since it's still fairly early API is likely to change a lot in particular with regular clojure support.

Add the following to your project.clj file:

`[cloth "0.2.7"]`
`[cloth "0.3.0"]`

Note I have not tested any of this in production or using minified clojurescript code.

Expand Down Expand Up @@ -41,24 +41,62 @@ The `cloth.core` namespace has most of what you need for regular use for sending
:value 100000000N})
```

### Key management
### Signers

The current implementation supports KeyPair maps containing a private-key and an address:
Signing is done by maps called signers and a multimethod called `(sign-with-signer tx signer)`.

The current implementation supports signers as KeyPair maps containing a private-key and an address:

```clojure
{:private-key "0x3fa3d2b5c94e3f521d6c160e0ef97123cc6d0946c12869b949959aa0f8c333de",
:address "0x9927ff21b9bb0eee9b0ee4867ebf9102d12d6ecb"}
```

The `cloth.keys` has a function `(create-keypair)` which creates a map like above.
The `cloth.keys` namespace has a function `(create-keypair)` which creates a map like above.

#### Ethereum URL's

Or a url based signer which generates an ethereum-url primarily useful to create a link on a mobile browser or a QR code.

```clojure
{:type :url,
:address "0x9927ff21b9bb0eee9b0ee4867ebf9102d12d6ecb" ;; optional
:show-url (fn [url]
;; trigger display of url in your web page
;; return a promise that is fullfilled based on a onhashchange event
)}
```

#### Proxy Signers

Proxy signers use simple smart contracts known as Proxy's that can be controlled through one or more `device keys` or other kinds of business rules.

A proxy contract needs to implement a function with the following interface:

```solidity
function forward(address recipient, uint value, bytes data) {
// forward contract based on certain busines rules
}
```

Once you have your proxy contract deployed you can create a signer like this:

```clojure
{ :type :proxy
:address "0xbfd6f4d8016d3b2388af8a6617778a3686993a1a" ;; address of proxy contract
:device { :private-key "0x3fa3d2b5c94e3f521d6c160e0ef97123cc6d0946c12869b949959aa0f8c333de",
:address "0x9927ff21b9bb0eee9b0ee4867ebf9102d12d6ecb"}}
```

#### Creating a global signer

In regular use you can store a keypair in a global keypair atom `cloth.core/global-keypair`:
In regular use with a single signer for example in a web app set it in a global signer atom `cloth.core/global-signer`:

```clojure
(reset! cloth.core/global-keypair (cloth.keys/create-keypair))
(reset! cloth.core/global-signer (cloth.keys/create-keypair))
```

For server applications you may want to assign a keypair to a request using dynamic binding.
For server applications you may want to assign a signer to a request using dynamic binding.

This is particularly useful in a ring-middleware:

Expand All @@ -68,13 +106,13 @@ This is particularly useful in a ring-middleware:
;; App specific code
...)

(defn wrap-signing-key [app]
(defn wrap-signer [app]
(fn [request])
(binding [cloth.core/bound-keypair (extract-key-from-request request)]
(binding [cloth.core/bound-signer (extract-key-from-request request)]
(app request)))
```

All code in the `cloth.core` namespace uses the `(cloth.core/keypair)` function to return the current keypair map.
All code in the `cloth.core` namespace uses the `(cloth.core/current-signer)` function to return the current keypair map.

Instead of callbacks we use [promesa](http://funcool.github.io/promesa/latest/) which allow us to compose functions easily.

Expand Down
30 changes: 21 additions & 9 deletions project.clj
Original file line number Diff line number Diff line change
@@ -1,17 +1,19 @@
(defproject cloth "0.2.7"
(defproject cloth "0.3.0"
:description "Clojure(Script) tools for Ethereum"
:url "https://github.com/pelle/cloth"
:dependencies [[org.clojure/clojure "1.9.0-alpha8"]
[org.clojure/clojurescript "1.9.225"]
[org.clojure/core.async "0.2.385"]
[org.clojure/clojurescript "1.9.229"]
[org.clojure/core.async "0.2.391"]
[funcool/cats "2.0.0"]
[funcool/promesa "1.5.0"]
[funcool/httpurr "0.6.1"]
[funcool/httpurr "0.6.2"]
[aleph "0.4.1" :scope "provided"]
[funcool/cuerdas "0.8.0"]
[funcool/cuerdas "1.0.1"]
[org.ethereum/ethereumj-core "1.2.0-RELEASE"]
[clj-time "0.12.0"]
[com.andrewmcveigh/cljs-time "0.4.0"]
[byte-streams "0.2.2"]
[com.cemerick/url "0.1.1"]
;[cljsjs/bignumber "2.1.4-1"]

[cheshire "5.6.3"]]
Expand All @@ -23,9 +25,19 @@
[lein-codox "0.9.5"]
[lein-ancient "0.6.10"]
[lein-externs "0.1.5"]]
:npm {:dependencies [[ethereumjs-tx "1.1.1"]]}
:profiles {:dev {:plugins [ ;[lein-auto "0.1.2"]
[com.jakemccrary/lein-test-refresh "0.16.0"]]}}
:npm {:dependencies [[karma "1.3.0"]
[karma-chrome-launcher "2.0.0"]
[karma-cljs-test "0.1.0"]
[derequire "2.0.3"]
[browser-builds "pelle/browser-builds#babelify"]]}

;; NOT WORKING
:aliases {"build-ethjs" ^{:doc "Build version of ethereumjs-tx"}
["shell"
"cat" "node_modules/browser-builds/dist/ethereumjs-tx.js" "|" "derequire"
">src/ethereumjs-tx/ethereumjs-tx.js"]}
:profiles {:dev {:plugins [[lein-auto "0.1.2"]
[lein-shell "0.5.0"]]}}
:cljsbuild
{:builds {:dev {:source-paths ["src"]
:figwheel true
Expand All @@ -46,7 +58,7 @@
:doo {:build "test"}
:source-paths ["src" "target/classes"]
:clean-targets ["out" "release" "target"]
:auto {:default {:file-pattern #"\.(clj|cljs|cljx|edn|sol)$"}
:auto {:default {:file-pattern #"\.(clj|cljs|cljc|cljx|edn|sol)$"}
:paths [ "src" "test"]}
;; Home of ethereumj
:repositories [["oss.jfrog.org" "http://dl.bintray.com/ethereum/maven"]])
Expand Down
18 changes: 1 addition & 17 deletions src/cloth/chain.cljc
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@
(-> (select-keys tx [:from :to :hash :input])
(assoc :value (util/hex->uint (:value tx))
:block-hash (:blockHash tx)
:block-number (util/hex->uint (:blockNumber tx))
:block-number (and (:blockNumber tx) (util/hex->uint (:blockNumber tx)))
:nonce (util/hex->uint (:nonce tx))
:gas (util/hex->uint (:gas tx))
:gas-price (util/hex->uint (:gasPrice tx))
Expand Down Expand Up @@ -144,14 +144,6 @@
([object block-number]
(ethrpc "eth_call" object block-number)))

(defn new-filter
[object]
(ethrpc "eth_newFilter" (clj->js object)))

(defn new-block-filter
[]
(ethrpc "eth_newBlockFilter"))

(defn get-filter-changes
[id]
(ethrpc "eth_getFilterChanges" id))
Expand All @@ -160,14 +152,6 @@
[id]
(ethrpc "eth_getFilterLogs" id))

(defn get-logs
[object]
(ethrpc "eth_getLogs" (clj->js object)))

(defn uninstall-filter
[id]
(ethrpc "eth_uninstallFilter" id))

;; the following are just for local development we add signing in the browser
(defn send-transaction
"Only use this for local development not for prod"
Expand Down
18 changes: 9 additions & 9 deletions src/cloth/contracts.cljc
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@

#?(:clj
(defn abi->args [f]
(mapv #(symbol (c/dasherize (:name %))) (:inputs f))))
(mapv #(symbol (c/kebab (:name %))) (:inputs f))))


(defn deploy-contract [binary]
Expand All @@ -37,7 +37,7 @@
[fabi contract args]
(->> (cloth.chain/call
(assoc (cloth.tx/fn-tx contract fabi args)
:from (:address (cloth/keypair))))
:from (:address (cloth/current-signer))))
(p/mapcat (fn [val]
;(println "returned: " val)
(p/resolved (util/decode-return-value (:outputs fabi) val))
Expand Down Expand Up @@ -85,42 +85,42 @@
"
[contract file]
(let [compiled (compile-solidity file)
contract-key (keyword (c/capitalize (c/camelize (name contract))))
contract-key (keyword (c/capitalize (c/camel (name contract))))
binary (get-in compiled [:contracts contract-key :bin])
abi (json/parse-string (get-in compiled [:contracts contract-key :abi]) true)
functions (filter #(= (:type %) "function") abi)
events (filter #(= (:type %) "event") abi)
deploy-name (symbol (str "deploy-" (c/dasherize (name contract)) "!"))]
deploy-name (symbol (str "deploy-" (c/kebab (name contract)) "!"))]
`(do
(defn ~deploy-name []
(deploy-contract ~binary))

~@(for [f functions]
(if (:constant f)
`(defn ~(symbol (c/dasherize (:name f)))
`(defn ~(symbol (c/kebab (:name f)))
~(str
"Calls "
(fn-doc f)
" without creating a transaction\nReturns a promise will return function return value")
[contract# & args#]
(call-contract-fn ~f contract# args#))
`(do
(defn ~(symbol (str (c/dasherize (:name f)) "!"))
(defn ~(symbol (str (c/kebab (:name f)) "!"))
~(str "Calls " (fn-doc f) " and submit it as a transaction\nReturns a promise which will return the tx hash")
[contract# & args#]
(create-fn-tx ~f contract# args#))
(defn ~(symbol (str (c/dasherize (:name f)) "!!"))
(defn ~(symbol (str (c/kebab (:name f)) "!!"))
~(str "Calls " (fn-doc f) " and submit it as a transaction.\nReturns a promise which will return the tx receipt once it has mined")
[contract# & args#]
(create-fn-and-wait-for-receipt ~f contract# args#))
(defn ~(symbol (str (c/dasherize (:name f)) "?"))
(defn ~(symbol (str (c/kebab (:name f)) "?"))
~(str "Calls " (fn-doc f) " without creating a transaction. Returns a promise which will return function return value")
[contract# & args#]
(call-contract-fn ~f contract# args#)))))

~@(for [e events]
(do
`(defn ~(symbol (str (c/dasherize (:name e)) "-ch"))
`(defn ~(symbol (str (c/kebab (:name e)) "-ch"))
~(str
"Listen to event "
(fn-doc e)
Expand Down
Loading

0 comments on commit 838189d

Please sign in to comment.