summaryrefslogtreecommitdiff
path: root/lib/ethereum.hoon
diff options
context:
space:
mode:
authorpolwex <polwex@sortug.com>2025-06-22 06:14:42 +0700
committerpolwex <polwex@sortug.com>2025-06-22 06:14:42 +0700
commit6dccba9bb5100329209ad01732f9d63f4c4fb43b (patch)
tree140b33d2e25084174fce057056de9dea0e2dcbea /lib/ethereum.hoon
metamask login getting there
Diffstat (limited to 'lib/ethereum.hoon')
-rw-r--r--lib/ethereum.hoon1006
1 files changed, 1006 insertions, 0 deletions
diff --git a/lib/ethereum.hoon b/lib/ethereum.hoon
new file mode 100644
index 0000000..0c2c635
--- /dev/null
+++ b/lib/ethereum.hoon
@@ -0,0 +1,1006 @@
+:: ethereum: utilities
+::
+=, ethereum-types
+|%
+:: deriving and using ethereum keys
+::
+++ key
+ |%
+ ++ address-from-pub
+ =, keccak:crypto
+ |= pub=@
+ %+ end [3 20]
+ %+ keccak-256 64
+ (rev 3 64 pub)
+ ::
+ ++ address-from-prv
+ (cork pub-from-prv address-from-pub)
+ ::
+ ++ pub-from-prv
+ =, secp256k1:secp:crypto
+ |= prv=@
+ %- serialize-point
+ (priv-to-pub prv)
+ ::
+ ++ sign-typed-transaction
+ |= [tx=typed-transaction:rpc pk=@]
+ ^- @ux
+ =- (cat 3 - -.tx)
+ ?- -.tx
+ %0x0 (sign-transaction +.tx pk)
+ %0x2 (sign-transaction-1559 +.tx pk)
+ ==
+ ::
+ ++ sign-transaction
+ =, crypto
+ |= [tx=transaction:rpc pk=@]
+ |^ ^- @ux
+ :: hash the raw transaction data
+ =/ hash=@
+ %- keccak-256:keccak
+ =+ dat=(encode chain-id.tx 0 0)
+ =+ wid=(met 3 dat)
+ [wid (rev 3 wid dat)]
+ :: sign transaction hash with private key
+ =+ (ecdsa-raw-sign:secp256k1:secp hash pk)
+ :: complete transaction is raw data, with r and s
+ :: taken from the signature, and v as per eip-155
+ (encode :(add (mul chain-id.tx 2) 35 v) r s)
+ ::
+ ++ encode
+ |= [v=@ r=@ s=@]
+ %+ encode:rlp %l
+ tx(to b+20^to.tx, chain-id [v r s ~])
+ --
+ ::
+ ++ sign-transaction-1559
+ =, crypto
+ |= [tx=transaction-1559:rpc pk=@]
+ |^ ^- @ux
+ =; hash=@
+ =+ (ecdsa-raw-sign:secp256k1:secp hash pk)
+ ::NOTE we retrieve y's parity from the v value
+ (encode-1559 ~ (end 0 v) r s)
+ :: hash the raw transaction data including leading 0x2
+ %- keccak-256:keccak
+ =+ dat=(cat 3 (encode-1559 ~) 0x2)
+ =+ wid=(met 3 dat)
+ [wid (rev 3 wid dat)]
+ ::
+ ++ encode-1559
+ |= sig=(unit [y=@ r=@ s=@])
+ %+ encode:rlp %l
+ =, tx
+ :* chain-id
+ nonce
+ max-priority-gas-fee
+ max-gas-fee
+ gas
+ b+20^to
+ value
+ data
+ ::
+ :- %l
+ %+ turn ~(tap by access-list)
+ |= [a=address b=(list @ux)]
+ l+~[b+20^a l+(turn b |=(c=@ux b+32^c))]
+ ::
+ ?~ sig ~
+ ~[y r s]:u.sig
+ ==
+ --
+ --
+::
+:: rlp en/decoding
+::NOTE https://eth.wiki/en/fundamentals/rlp
+::
+++ rlp
+ |%
+ ::NOTE rlp encoding doesn't really care about leading zeroes,
+ :: but because we need to disinguish between no-bytes zero
+ :: and one-byte zero (and also empty list) we end up with
+ :: this awful type...
+ +$ item
+ $@ @
+ $% [%l l=(list item)]
+ [%b b=byts]
+ ==
+ :: +encode-atoms: encode list of atoms as a %l of %b items
+ ::
+ ++ encode-atoms ::NOTE deprecated
+ |= l=(list @)
+ ^- @
+ (encode l+l)
+ ::
+ ++ encode
+ |= in=item
+ |^ ^- @
+ ?- in
+ @
+ $(in [%b (met 3 in) in])
+ ::
+ [%b *]
+ ?: &(=(1 wid.b.in) (lte dat.b.in 0x7f))
+ dat.b.in
+ =- (can 3 ~[b.in [(met 3 -) -]])
+ (encode-length wid.b.in 0x80)
+ ::
+ [%l *]
+ :: we +can because b+1^0x0 encodes to 0x00
+ ::
+ =/ l=(list byts)
+ %+ turn l.in
+ |= ni=item
+ =+ (encode ni)
+ [(max 1 (met 3 -)) -]
+ %+ can 3
+ %- flop
+ =- [(met 3 -)^- l]
+ (encode-length (roll (turn l head) add) 0xc0)
+ ==
+ ::
+ ++ encode-length
+ |= [len=@ off=@]
+ ?: (lth len 56) (add len off)
+ =- (cat 3 len -)
+ :(add (met 3 len) off 55)
+ --
+ :: +decode-atoms: decode expecting a %l of %b items, producing atoms within
+ ::
+ ++ decode-atoms
+ |= dat=@
+ ^- (list @)
+ =/ i=item (decode dat)
+ ~| [%unexpected-data i]
+ ?> ?=(%l -.i)
+ %+ turn l.i
+ |= i=item
+ ~| [%unexpected-list i]
+ ?> ?=(%b -.i)
+ dat.b.i
+ ::
+ ++ decode
+ |= dat=@
+ ^- item
+ =/ bytes=(list @) (flop (rip 3 dat))
+ =? bytes ?=(~ bytes) ~[0]
+ |^ item:decode-head
+ ::
+ ++ decode-head
+ ^- [done=@ud =item]
+ ?~ bytes
+ ~| %rlp-unexpected-end
+ !!
+ =* byt i.bytes
+ :: byte in 0x00-0x79 range encodes itself
+ ::
+ ?: (lte byt 0x79)
+ :- 1
+ [%b 1^byt]
+ :: byte in 0x80-0xb7 range encodes string length
+ ::
+ ?: (lte byt 0xb7)
+ =+ len=(sub byt 0x80)
+ :- +(len)
+ :- %b
+ len^(get-value 1 len)
+ :: byte in 0xb8-0xbf range encodes string length length
+ ::
+ ?: (lte byt 0xbf)
+ =+ led=(sub byt 0xb7)
+ =+ len=(get-value 1 led)
+ :- (add +(led) len)
+ :- %b
+ len^(get-value +(led) len)
+ :: byte in 0xc0-f7 range encodes list length
+ ::
+ ?: (lte byt 0xf7)
+ =+ len=(sub byt 0xc0)
+ :- +(len)
+ :- %l
+ %. len
+ decode-list(bytes (slag 1 `(list @)`bytes))
+ :: byte in 0xf8-ff range encodes list length length
+ ::
+ ?: (lte byt 0xff)
+ =+ led=(sub byt 0xf7)
+ =+ len=(get-value 1 led)
+ :- (add +(led) len)
+ :- %l
+ %. len
+ decode-list(bytes (slag +(led) `(list @)`bytes))
+ ~| [%rip-not-bloq-3 `@ux`byt]
+ !!
+ ::
+ ++ decode-list
+ |= rem=@ud
+ ^- (list item)
+ ?: =(0 rem) ~
+ =+ ^- [don=@ud =item] ::TODO =/
+ decode-head
+ :- item
+ %= $
+ rem (sub rem don)
+ bytes (slag don bytes)
+ ==
+ ::
+ ++ get-value
+ |= [at=@ud to=@ud]
+ ^- @
+ (rep 3 (flop (swag [at to] bytes)))
+ --
+ --
+::
+:: abi en/decoding
+::NOTE https://solidity.readthedocs.io/en/develop/abi-spec.html
+::
+++ abi
+ => |%
+ :: solidity types. integer bitsizes ignored
+ ++ etyp
+ $@ $? :: static
+ %address %bool
+ %int %uint
+ %real %ureal
+ :: dynamic
+ %bytes %string
+ ==
+ $% :: static
+ [%bytes-n n=@ud]
+ :: dynamic
+ [%array-n t=etyp n=@ud]
+ [%array t=etyp]
+ ==
+ ::
+ :: solidity-style typed data. integer bitsizes ignored
+ ++ data
+ $% [%address p=address]
+ [%string p=tape]
+ [%bool p=?]
+ [%int p=@sd]
+ [%uint p=@ud]
+ [%real p=@rs]
+ [%ureal p=@urs]
+ [%array-n p=(list data)]
+ [%array p=(list data)]
+ [%bytes-n p=octs] ::TODO just @, because context knows length?
+ [%bytes p=octs]
+ ==
+ --
+ =, mimes:html
+ |%
+ :: encoding
+ ::
+ ++ encode-args
+ :: encode list of arguments.
+ ::
+ |= das=(list data)
+ ^- tape
+ (encode-data [%array-n das])
+ ::
+ ++ encode-data
+ :: encode typed data into ABI bytestring.
+ ::
+ |= dat=data
+ ^- tape
+ ?+ -.dat
+ ~| [%unsupported-type -.dat]
+ !!
+ ::
+ %array-n
+ :: enc(X) = head(X[0]) ... head(X[k-1]) tail(X[0]) ... tail(X[k-1])
+ :: where head and tail are defined for X[i] being of a static type as
+ :: head(X[i]) = enc(X[i]) and tail(X[i]) = "" (the empty string), or as
+ :: head(X[i]) = enc(len( head(X[0])..head(X[k-1])
+ :: tail(X[0])..tail(X[i-1]) ))
+ :: and tail(X[i]) = enc(X[i]) otherwise.
+ ::
+ :: so: if it's a static type, data goes in the head. if it's a dynamic
+ :: type, a reference goes into the head and data goes into the tail.
+ ::
+ :: in the head, we first put a placeholder where references need to go.
+ =+ hol=(reap 64 'x')
+ =/ hes=(list tape)
+ %+ turn p.dat
+ |= d=data
+ ?. (is-dynamic-type d) ^$(dat d)
+ hol
+ =/ tas=(list tape)
+ %+ turn p.dat
+ |= d=data
+ ?. (is-dynamic-type d) ""
+ ^$(dat d)
+ :: once we know the head and tail, we can fill in the references in head.
+ =- (weld nes `tape`(zing tas))
+ ^- [@ud nes=tape]
+ =+ led=(lent (zing hes))
+ %+ roll hes
+ |= [t=tape i=@ud nes=tape]
+ :- +(i)
+ :: if no reference needed, just put the data.
+ ?. =(t hol) (weld nes t)
+ :: calculate byte offset of data we need to reference.
+ =/ ofs=@ud
+ =- (div - 2) :: two hex digits per byte.
+ %+ add led :: count head, and
+ %- lent %- zing :: count all tail data
+ (scag i tas) :: preceding ours.
+ =+ ref=^$(dat [%uint ofs])
+ :: shouldn't hit this unless we're sending over 2gb of data?
+ ~| [%weird-ref-lent (lent ref)]
+ ?> =((lent ref) (lent hol))
+ (weld nes ref)
+ ::
+ %array :: where X has k elements (k is assumed to be of type uint256):
+ :: enc(X) = enc(k) enc([X[1], ..., X[k]])
+ :: i.e. it is encoded as if it were an array of static size k, prefixed
+ :: with the number of elements.
+ %+ weld $(dat [%uint (lent p.dat)])
+ $(dat [%array-n p.dat])
+ ::
+ %bytes-n
+ :: enc(X) is the sequence of bytes in X padded with zero-bytes to a
+ :: length of 32.
+ :: Note that for any X, len(enc(X)) is a multiple of 32.
+ ~| [%bytes-n-too-long max=32 actual=p.p.dat]
+ ?> (lte p.p.dat 32)
+ (pad-to-multiple (render-hex-bytes p.dat) 64 %right)
+ ::
+ %bytes :: of length k (which is assumed to be of type uint256)
+ :: enc(X) = enc(k) pad_right(X), i.e. the number of bytes is encoded as a
+ :: uint256 followed by the actual value of X as a byte sequence, followed
+ :: by the minimum number of zero-bytes such that len(enc(X)) is a
+ :: multiple of 32.
+ %+ weld $(dat [%uint p.p.dat])
+ (pad-to-multiple (render-hex-bytes p.dat) 64 %right)
+ ::
+ %string
+ :: enc(X) = enc(enc_utf8(X)), i.e. X is utf-8 encoded and this value is
+ :: interpreted as of bytes type and encoded further. Note that the length
+ :: used in this subsequent encoding is the number of bytes of the utf-8
+ :: encoded string, not its number of characters.
+ $(dat [%bytes (lent p.dat) (swp 3 (crip p.dat))])
+ ::
+ %uint
+ :: enc(X) is the big-endian encoding of X, padded on the higher-order
+ :: (left) side with zero-bytes such that the length is a multiple of 32
+ :: bytes.
+ (pad-to-multiple (render-hex-bytes (as-octs p.dat)) 64 %left)
+ ::
+ %bool
+ :: as in the uint8 case, where 1 is used for true and 0 for false
+ $(dat [%uint ?:(p.dat 1 0)])
+ ::
+ %address
+ :: as in the uint160 case
+ $(dat [%uint `@ud`p.dat])
+ ==
+ ::
+ ++ is-dynamic-type
+ |= a=data
+ ?. ?=(%array-n -.a)
+ ?=(?(%string %bytes %array) -.a)
+ &(!=((lent p.a) 0) (lien p.a is-dynamic-type))
+ ::
+ :: decoding
+ ::
+ ++ decode-topics decode-arguments
+ ::
+ ++ decode-results
+ :: rex: string of hex bytes with leading 0x.
+ |* [rex=@t tys=(list etyp)]
+ =- (decode-arguments - tys)
+ %^ rut 9
+ (rsh [3 2] rex)
+ (curr rash hex)
+ ::
+ ++ decode-arguments
+ |* [wos=(list @) tys=(list etyp)]
+ =/ wos=(list @) wos :: get rid of tmi
+ =| win=@ud
+ =< (decode-from 0 tys)
+ |%
+ ++ decode-from
+ |* [win=@ud tys=(list etyp)]
+ ?~ tys !!
+ =- ?~ t.tys dat
+ [dat $(win nin, tys t.tys)]
+ (decode-one win ~[i.tys])
+ ::
+ ++ decode-one
+ ::NOTE we take (list etyp) even though we only operate on
+ :: a single etyp as a workaround for urbit/arvo#673
+ |* [win=@ud tys=(list etyp)]
+ =- [nin dat]=- ::NOTE ^= regular form broken
+ ?~ tys !!
+ =* typ i.tys
+ =+ wor=(snag win wos)
+ ?+ typ
+ ~| [%unsupported-type typ]
+ !!
+ ::
+ ?(%address %bool %uint) :: %int %real %ureal
+ :- +(win)
+ ?- typ
+ %address `@ux`wor
+ %uint `@ud`wor
+ %bool =(1 wor)
+ ==
+ ::
+ %string
+ =+ $(tys ~[%bytes])
+ [nin (trip (swp 3 q.dat))]
+ ::
+ %bytes
+ :- +(win)
+ :: find the word index of the actual data.
+ =/ lic=@ud (div wor 32)
+ :: learn the bytelength of the data.
+ =/ len=@ud (snag lic wos)
+ (decode-bytes-n +(lic) len)
+ ::
+ [%bytes-n *]
+ :- (add win +((div (dec n.typ) 32)))
+ (decode-bytes-n win n.typ)
+ ::
+ [%array *]
+ :- +(win)
+ :: find the word index of the actual data.
+ =. win (div wor 32)
+ :: read the elements from their location.
+ %- tail
+ %^ decode-array-n ~[t.typ] +(win)
+ (snag win wos)
+ ::
+ [%array-n *]
+ (decode-array-n ~[t.typ] win n.typ)
+ ==
+ ::
+ ++ decode-bytes-n
+ |= [fro=@ud bys=@ud]
+ ^- octs
+ :: parse {bys} bytes from {fro}.
+ :- bys
+ %+ rsh
+ :- 3
+ =+ (mod bys 32)
+ ?:(=(0 -) - (sub 32 -))
+ %+ rep 8
+ %- flop
+ =- (swag [fro -] wos)
+ +((div (dec bys) 32))
+ ::
+ ++ decode-array-n
+ ::NOTE we take (list etyp) even though we only operate on
+ :: a single etyp as a workaround for urbit/arvo#673
+ ::NOTE careful! produces lists without type info
+ =| res=(list)
+ |* [tys=(list etyp) fro=@ud len=@ud]
+ ^- [@ud (list)]
+ ?~ tys !!
+ ?: =(len 0) [fro (flop `(list)`res)]
+ =+ (decode-one fro ~[i.tys]) :: [nin=@ud dat=*]
+ $(res ^+(res [dat res]), fro nin, len (dec len))
+ --
+ --
+::
+:: communicating with rpc nodes
+::NOTE https://github.com/ethereum/wiki/wiki/JSON-RPC
+::
+++ rpc
+ :: types
+ ::
+ => =, abi
+ =, format
+ |%
+ :: raw call data
+ ++ call-data
+ $: function=@t
+ arguments=(list data)
+ ==
+ ::
+ :: raw transaction data
+ +$ typed-transaction
+ $% [%0x0 transaction]
+ [%0x2 transaction-1559]
+ ==
+ ::
+ +$ transaction
+ $: nonce=@ud
+ gas-price=@ud
+ gas=@ud
+ to=address
+ value=@ud
+ data=@ux
+ chain-id=@ux
+ ==
+ ::
+ +$ transaction-1559
+ $: chain-id=@ux
+ nonce=@ud
+ max-priority-gas-fee=@ud
+ max-gas-fee=@ud
+ gas=@ud
+ to=address
+ value=@ud
+ data=@ux
+ access-list=(jar address @ux)
+ ==
+ ::
+ :: ethereum json rpc api
+ ::
+ :: supported requests.
+ ++ request
+ $% [%eth-block-number ~]
+ [%eth-call cal=call deb=block]
+ $: %eth-new-filter
+ fro=(unit block)
+ tob=(unit block)
+ adr=(list address)
+ top=(list ?(@ux (list @ux)))
+ ==
+ [%eth-get-block-by-number bon=@ud txs=?]
+ [%eth-get-filter-logs fid=@ud]
+ $: %eth-get-logs
+ fro=(unit block)
+ tob=(unit block)
+ adr=(list address)
+ top=(list ?(@ux (list @ux)))
+ ==
+ $: %eth-get-logs-by-hash
+ has=@
+ adr=(list address)
+ top=(list ?(@ux (list @ux)))
+ ==
+ [%eth-get-filter-changes fid=@ud]
+ [%eth-get-transaction-by-hash txh=@ux]
+ [%eth-get-transaction-count adr=address =block]
+ [%eth-get-balance adr=address =block]
+ [%eth-get-transaction-receipt txh=@ux]
+ [%eth-send-raw-transaction dat=@ux]
+ ==
+ ::
+ ::TODO clean up & actually use
+ ++ response
+ $% ::TODO
+ [%eth-new-filter fid=@ud]
+ [%eth-get-filter-logs los=(list event-log)]
+ [%eth-get-logs los=(list event-log)]
+ [%eth-get-logs-by-hash los=(list event-log)]
+ [%eth-got-filter-changes los=(list event-log)]
+ [%eth-transaction-hash haz=@ux]
+ ==
+ ::
+ ++ transaction-result
+ $: block-hash=(unit @ux)
+ block-number=(unit @ud)
+ transaction-index=(unit @ud)
+ from=@ux
+ to=(unit @ux)
+ input=@t
+ ==
+ ::
+ ++ event-log
+ $: :: null for pending logs
+ $= mined %- unit
+ $: input=(unit @ux)
+ log-index=@ud
+ transaction-index=@ud
+ transaction-hash=@ux
+ block-number=@ud
+ block-hash=@ux
+ removed=?
+ ==
+ ::
+ address=@ux
+ data=@t
+ :: event data
+ ::
+ :: For standard events, the first topic is the event signature
+ :: hash. For anonymous events, the first topic is the first
+ :: indexed argument.
+ :: Note that this does not support the "anonymous event with
+ :: zero topics" case. This has dubious usability, and using
+ :: +lest instead of +list saves a lot of ?~ checks.
+ ::
+ topics=(lest @ux)
+ ==
+ ::
+ :: data for eth_call.
+ ++ call
+ $: from=(unit address)
+ to=address
+ gas=(unit @ud)
+ gas-price=(unit @ud)
+ value=(unit @ud)
+ data=tape
+ ==
+ ::
+ :: minimum data needed to construct a read call
+ ++ proto-read-request
+ $: id=(unit @t)
+ to=address
+ call-data
+ ==
+ ::
+ :: block to operate on.
+ ++ block
+ $% [%number n=@ud]
+ [%label l=?(%earliest %latest %pending)]
+ ==
+ --
+ ::
+ :: logic
+ ::
+ |%
+ ++ encode-call
+ |= call-data
+ ^- tape
+ ::TODO should this check to see if the data matches the function signature?
+ =- :(weld "0x" - (encode-args arguments))
+ %+ scag 8
+ %+ render-hex-bytes 32
+ %- keccak-256:keccak:crypto
+ (as-octs:mimes:html function)
+ ::
+ :: building requests
+ ::
+ ++ json-request
+ =, eyre
+ |= [url=purl jon=json]
+ ^- hiss
+ :^ url %post
+ %- ~(gas in *math)
+ ~['Content-Type'^['application/json']~]
+ (some (as-octs (en:json:html jon)))
+ :: +light-json-request: like json-request, but for %l
+ ::
+ :: TODO: Exorcising +purl from our system is a much longer term effort;
+ :: get the current output types for now.
+ ::
+ ++ light-json-request
+ |= [url=purl:eyre jon=json]
+ ^- request:http
+ ::
+ :* %'POST'
+ (crip (en-purl:html url))
+ ~[['content-type' 'application/json']]
+ (some (as-octs (en:json:html jon)))
+ ==
+ ::
+ ++ batch-read-request
+ |= req=(list proto-read-request)
+ ^- json
+ a+(turn req read-request)
+ ::
+ ++ read-request
+ |= proto-read-request
+ ^- json
+ %+ request-to-json id
+ :+ %eth-call
+ ^- call
+ [~ to ~ ~ ~ `tape`(encode-call function arguments)]
+ [%label %latest]
+ ::
+ ++ request-to-json
+ =, enjs:format
+ |= [riq=(unit @t) req=request]
+ ^- json
+ %- pairs
+ =; r=[met=@t pas=(list json)]
+ ::TODO should use request-to-json:rpc:jstd,
+ :: and probably (fall riq -.req)
+ :* jsonrpc+s+'2.0'
+ method+s+met.r
+ params+a+pas.r
+ ::TODO would just jamming the req noun for id be a bad idea?
+ ?~ riq ~
+ [id+s+u.riq]~
+ ==
+ ?- -.req
+ %eth-block-number
+ ['eth_blockNumber' ~]
+ ::
+ %eth-call
+ :- 'eth_call'
+ :~ (eth-call-to-json cal.req)
+ (block-to-json deb.req)
+ ==
+ ::
+ %eth-new-filter
+ :- 'eth_newFilter'
+ :_ ~
+ :- %o %- ~(gas by *(map @t json))
+ =- (murn - same)
+ ^- (list (unit (pair @t json)))
+ :~ ?~ fro.req ~
+ `['fromBlock' (block-to-json u.fro.req)]
+ ::
+ ?~ tob.req ~
+ `['toBlock' (block-to-json u.tob.req)]
+ ::
+ ::NOTE tmi
+ ?: =(0 (lent adr.req)) ~
+ :+ ~ 'address'
+ ?: =(1 (lent adr.req)) (tape (address-to-hex (snag 0 adr.req)))
+ :- %a
+ (turn adr.req (cork address-to-hex tape))
+ ::
+ ?~ top.req ~
+ :+ ~ 'topics'
+ (topics-to-json top.req)
+ ==
+ ::
+ %eth-get-block-by-number
+ :- 'eth_getBlockByNumber'
+ :~ (tape (num-to-hex-minimal bon.req))
+ b+txs.req
+ ==
+ ::
+ %eth-get-filter-logs
+ ['eth_getFilterLogs' (tape (num-to-hex fid.req)) ~]
+ ::
+ %eth-get-logs
+ :- 'eth_getLogs'
+ :_ ~
+ :- %o %- ~(gas by *(map @t json))
+ =- (murn - same)
+ ^- (list (unit (pair @t json)))
+ :~ ?~ fro.req ~
+ `['fromBlock' (block-to-json u.fro.req)]
+ ::
+ ?~ tob.req ~
+ `['toBlock' (block-to-json u.tob.req)]
+ ::
+ ?: =(0 (lent adr.req)) ~
+ :+ ~ 'address'
+ ?: =(1 (lent adr.req)) (tape (address-to-hex (snag 0 adr.req)))
+ :- %a
+ (turn adr.req (cork address-to-hex tape))
+ ::
+ ?~ top.req ~
+ :+ ~ 'topics'
+ (topics-to-json top.req)
+ ==
+ ::
+ %eth-get-logs-by-hash
+ :- 'eth_getLogs'
+ :_ ~ :- %o
+ %- ~(gas by *(map @t json))
+ =- (murn - same)
+ ^- (list (unit (pair @t json)))
+ :~ `['blockHash' (tape (transaction-to-hex has.req))]
+ ::
+ ?: =(0 (lent adr.req)) ~
+ :+ ~ 'address'
+ ?: =(1 (lent adr.req)) (tape (address-to-hex (snag 0 adr.req)))
+ :- %a
+ (turn adr.req (cork address-to-hex tape))
+ ::
+ ?~ top.req ~
+ :+ ~ 'topics'
+ (topics-to-json top.req)
+ ==
+ ::
+ %eth-get-filter-changes
+ ['eth_getFilterChanges' (tape (num-to-hex fid.req)) ~]
+ ::
+ %eth-get-transaction-count
+ :- 'eth_getTransactionCount'
+ :~ (tape (address-to-hex adr.req))
+ (block-to-json block.req)
+ ==
+ ::
+ %eth-get-balance
+ :- 'eth_getBalance'
+ :~ (tape (address-to-hex adr.req))
+ (block-to-json block.req)
+ ==
+ ::
+ %eth-get-transaction-by-hash
+ ['eth_getTransactionByHash' (tape (transaction-to-hex txh.req)) ~]
+ ::
+ %eth-get-transaction-receipt
+ ['eth_getTransactionReceipt' (tape (transaction-to-hex txh.req)) ~]
+ ::
+ %eth-send-raw-transaction
+ ['eth_sendRawTransaction' (tape (num-to-hex dat.req)) ~]
+ ==
+ ::
+ ++ eth-call-to-json
+ =, enjs:format
+ |= cal=call
+ ^- json
+ :- %o %- ~(gas by *(map @t json))
+ =- (murn - same)
+ ^- (list (unit (pair @t json)))
+ :~ ?~ from.cal ~
+ `['from' (tape (address-to-hex u.from.cal))]
+ ::
+ `['to' (tape (address-to-hex to.cal))]
+ ::
+ ?~ gas.cal ~
+ `['gas' (tape (num-to-hex u.gas.cal))]
+ ::
+ ?~ gas-price.cal ~
+ `['gasPrice' (tape (num-to-hex u.gas-price.cal))]
+ ::
+ ?~ value.cal ~
+ `['value' (tape (num-to-hex u.value.cal))]
+ ::
+ ?~ data.cal ~
+ `['data' (tape data.cal)]
+ ==
+ ::
+ ++ block-to-json
+ |= dob=block
+ ^- json
+ ?- -.dob
+ %number s+(crip '0' 'x' ((x-co:co 1) n.dob))
+ %label s+l.dob
+ ==
+ ::
+ ++ topics-to-json
+ |= tos=(list ?(@ux (list @ux)))
+ ^- json
+ :- %a
+ =/ ttj
+ ;: cork
+ (cury render-hex-bytes 32)
+ prefix-hex
+ tape:enjs:format
+ ==
+ %+ turn tos
+ |= t=?(@ (list @))
+ ?@ t
+ ?: =(0 t) ~
+ (ttj `@`t)
+ a+(turn t ttj)
+ ::
+ :: parsing responses
+ ::
+ ::TODO ++ parse-response |= json ^- response
+ ::
+ ++ parse-hex-result
+ |= j=json
+ ^- @
+ ?> ?=(%s -.j)
+ (hex-to-num p.j)
+ ::
+ ++ parse-eth-new-filter-res parse-hex-result
+ ::
+ ++ parse-eth-block-number parse-hex-result
+ ::
+ ++ parse-transaction-hash parse-hex-result
+ ::
+ ++ parse-eth-get-transaction-count parse-hex-result
+ ::
+ ++ parse-eth-get-balance parse-hex-result
+ ::
+ ++ parse-event-logs
+ (ar:dejs:format parse-event-log)
+ ::
+ ++ parse-event-log
+ =, dejs:format
+ |= log=json
+ ^- event-log
+ =- ((ot -) log)
+ :~ =- ['logIndex'^(cu - (mu so))]
+ |= li=(unit @t)
+ ?~ li ~
+ =- ``((ou -) log) ::TODO not sure if elegant or hacky.
+ :~ 'logIndex'^(un (cu hex-to-num so))
+ 'transactionIndex'^(un (cu hex-to-num so))
+ 'transactionHash'^(un (cu hex-to-num so))
+ 'blockNumber'^(un (cu hex-to-num so))
+ 'blockHash'^(un (cu hex-to-num so))
+ 'removed'^(uf | bo)
+ ==
+ ::
+ address+(cu hex-to-num so)
+ data+so
+ ::
+ =- topics+(cu - (ar so))
+ |= r=(list @t)
+ ^- (lest @ux)
+ ?> ?=([@t *] r)
+ :- (hex-to-num i.r)
+ (turn t.r hex-to-num)
+ ==
+ ::
+ ++ parse-transaction-result
+ =, dejs:format
+ |= jon=json
+ ~| jon=jon
+ ^- transaction-result
+ =- ((ot -) jon)
+ :~ 'blockHash'^_~ :: TODO: fails if maybe-num?
+ 'blockNumber'^maybe-num
+ 'transactionIndex'^maybe-num
+ from+(cu hex-to-num so)
+ to+maybe-num
+ input+so
+ ==
+ ::
+ ++ maybe-num
+ =, dejs:format
+ =- (cu - (mu so))
+ |= r=(unit @t)
+ ?~ r ~
+ `(hex-to-num u.r)
+ --
+::
+:: utilities
+::TODO give them better homes!
+::
+++ num-to-hex
+ |= n=@
+ ^- tape
+ %- prefix-hex
+ ?: =(0 n)
+ "0"
+ %- render-hex-bytes
+ (as-octs:mimes:html n)
+::
+++ num-to-hex-minimal
+ |= n=@
+ ^- tape
+ %- prefix-hex
+ ((x-co:co 1) n)
+::
+++ address-to-hex
+ |= a=address
+ ^- tape
+ %- prefix-hex
+ (render-hex-bytes 20 `@`a)
+::
+++ address-to-checksum
+ |= a=address
+ ^- tape
+ =/ hexed (render-hex-bytes 20 `@`a)
+ =/ hash (keccak-256:keccak:crypto (as-octs:mimes:html (crip hexed)))
+ =| ret=tape
+ =/ pos 63
+ |-
+ ?~ hexed (prefix-hex (flop ret))
+ =/ char i.hexed
+ ?: (lth char 58) $(pos (dec pos), ret [char ret], hexed t.hexed)
+ =/ nib (cut 2 [pos 1] hash)
+ ?: (lth 7 nib) $(pos (dec pos), ret [(sub char 32) ret], hexed t.hexed)
+ $(pos (dec pos), ret [char ret], hexed t.hexed)
+::
+++ transaction-to-hex
+ |= h=@
+ ^- tape
+ %- prefix-hex
+ (render-hex-bytes 32 h)
+::
+++ prefix-hex
+ |= a=tape
+ ^- tape
+ ['0' 'x' a]
+::
+++ render-hex-bytes
+ :: atom to string of hex bytes without 0x prefix and dots.
+ |= a=octs
+ ^- tape
+ ((x-co:co (mul 2 p.a)) q.a)
+::
+++ pad-to-multiple
+ |= [wat=tape mof=@ud wer=?(%left %right)]
+ ^- tape
+ =+ len=(lent wat)
+ ?: =(0 len) (reap mof '0')
+ =+ mad=(mod len mof)
+ ?: =(0 mad) wat
+ =+ tad=(reap (sub mof mad) '0')
+ %- weld
+ ?:(?=(%left wer) [tad wat] [wat tad])
+::
+++ hex-to-num
+ |= a=@t
+ ~| %non-hex-cord
+ ?> =((end [3 2] a) '0x')
+ =< ?<(=(0 p) q) %- need
+ (de:base16:mimes:html (rsh [3 2] a))
+--