shrub/pkg/arvo/lib/btc.hoon

572 lines
13 KiB
Plaintext
Raw Normal View History

2021-02-21 13:13:50 +03:00
:: lib/btc.hoon
2020-10-31 14:25:32 +03:00
::
2021-02-21 13:13:50 +03:00
/- *btc-wallet, json-rpc, bp=btc-provider
/+ bip32, bc=bitcoin
2020-10-30 14:45:38 +03:00
=, secp:crypto
=+ ecc=secp256k1
|%
2021-02-21 13:13:50 +03:00
::
:: Formerly lib/btc-wallet.hoon
::
::
2020-11-13 15:47:11 +03:00
++ defaults
|%
++ max-gap 20
++ confs 6
--
2021-02-18 15:13:06 +03:00
:: +fam: planet parent if s is a moon
::
++ fam
|= [our=ship now=@da s=ship]
^- ship
?. =(%earl (clan:title s)) s
(sein:title our now s)
2020-11-02 10:21:48 +03:00
::
2020-12-07 16:27:26 +03:00
++ num-confs
2021-02-18 15:13:06 +03:00
|= [last-block=@ud =utxo:bc]
2020-12-07 16:27:26 +03:00
?: =(0 height.utxo) 0
(add 1 (sub last-block height.utxo))
::
2020-11-11 15:30:22 +03:00
++ from-xpub
2021-02-18 15:13:06 +03:00
|= $: =xpub:bc
=fprint:bc
2021-01-28 12:54:06 +03:00
scan-to=(unit scon)
max-gap=(unit @ud)
confs=(unit @ud)
==
2020-11-11 15:30:22 +03:00
^- walt
2021-02-20 14:49:28 +03:00
=/ [=bipt =network] (xpub-type:bc xpub)
2020-12-06 20:53:02 +03:00
:* xpub
2021-01-28 12:54:06 +03:00
network
2020-12-08 20:31:21 +03:00
fprint
+6:(from-extended:bip32 (trip xpub))
2021-01-28 14:03:53 +03:00
bipt
2020-11-11 15:30:22 +03:00
*wach
[0 0]
%.n
(fall scan-to *scon)
2020-11-13 15:47:11 +03:00
(fall max-gap max-gap:defaults)
(fall confs confs:defaults)
2020-11-11 15:30:22 +03:00
==
:: +address-coords: find wallet info for the address, if any
2021-02-12 17:37:21 +03:00
::
++ address-coords
2021-02-12 17:37:21 +03:00
|= [a=address ws=(list walt)]
^- (unit [w=walt =chyg =idx])
|^
|- ?~ ws ~
=/ res=(unit [=chyg =idx])
(lookup i.ws)
?^ res `[i.ws chyg.u.res idx.u.res]
$(ws t.ws)
::
++ lookup
|= w=walt
^- (unit [=chyg =idx])
=/ ad=(unit addi) (~(get by wach.w) a)
?~(ad ~ `[chyg.u.ad idx.u.ad])
--
2020-12-15 10:36:46 +03:00
::
++ new-txbu
|= $: w=walt
payee=(unit ship)
2021-02-18 15:13:06 +03:00
=vbytes:bc
2020-12-15 10:36:46 +03:00
is=(list insel)
txos=(list txo)
==
^- txbu
:* xpub.w
payee
vbytes
%+ turn is
|= i=insel
[utxo.i ~ (~(hdkey wad w chyg.i) idx.i)]
txos
2020-12-15 19:05:50 +03:00
~
2020-12-15 10:36:46 +03:00
==
2021-01-28 12:54:06 +03:00
:: txb: transaction builder helpers
2020-12-08 11:33:24 +03:00
::
++ txb
|_ t=txbu
++ value
^- [in=sats out=sats]
:- %+ roll
%+ turn txis.t
|=(=txi value.utxo.txi)
add
2021-01-13 12:51:50 +03:00
(roll (turn txos.t |=(=txo value.txo)) add)
2020-12-08 11:33:24 +03:00
::
2021-01-30 20:29:29 +03:00
++ fee
2021-02-18 15:13:06 +03:00
^- sats:bc
2021-01-30 20:29:29 +03:00
=/ [in=sats out=sats] value
(sub in out)
::
++ vbytes
2021-02-18 15:13:06 +03:00
^- vbytes:bc
2021-02-20 14:49:28 +03:00
%+ add overhead-weight:bc
%+ add
%+ roll
2021-02-20 14:49:28 +03:00
(turn txis.t |=(t=txi (input-weight:bc bipt.hdkey.t)))
add
%+ roll
2021-02-20 14:49:28 +03:00
(turn txos.t |=(t=txo (output-weight:bc (get-bipt:adr:bc address.t))))
add
2020-12-15 10:36:46 +03:00
++ tx-data
|^
2021-02-18 15:13:06 +03:00
^- data:tx:bc
2020-12-15 10:36:46 +03:00
:* (turn txis.t txi-data)
(turn txos.t txo-data)
0 1 `1
==
::
++ txi-data
|= =txi
:* txid.utxo.txi pos.utxo.txi
4^0xffff.ffff ~ ~ value.utxo.txi
==
++ txo-data
|= =txo
2021-02-20 14:49:28 +03:00
:- (to-script-pubkey:adr:bc address.txo)
2021-01-26 14:27:32 +03:00
value.txo
2020-12-15 10:36:46 +03:00
--
::
++ get-txid
^- txid
2021-02-20 14:49:28 +03:00
(get-id:txu:bc tx-data)
2020-12-15 10:36:46 +03:00
::
++ get-rawtx
2021-02-20 14:49:28 +03:00
(basic-encode:txu:bc tx-data)
2020-12-15 19:05:50 +03:00
:: +add-output: append output (usually change) to txos
2020-12-15 10:36:46 +03:00
::
2020-12-08 11:33:24 +03:00
++ add-output
2020-12-08 20:31:21 +03:00
|= =txo
2020-12-08 11:33:24 +03:00
^- txbu
:: todo update vbytes
2020-12-15 19:05:50 +03:00
t(txos (snoc [txos.t] txo))
2020-12-10 18:54:39 +03:00
:: +to-psbt: returns a based 64 PSBT if
:: - all inputs have an associated rawtx
2020-12-08 20:31:21 +03:00
::
++ to-psbt
2021-02-18 15:13:06 +03:00
^- (unit base64:psbt:bc)
=/ ins=(list in:psbt:bc)
2020-12-10 18:54:39 +03:00
%+ murn txis.t
|= =txi
2021-03-01 12:13:07 +03:00
?~ rawtx.txi ~
`[utxo.txi u.rawtx.txi hdkey.txi]
2020-12-10 18:54:39 +03:00
?: (lth (lent ins) (lent txis.t))
~
2021-02-18 15:13:06 +03:00
=/ outs=(list out:psbt:bc)
2020-12-10 18:54:39 +03:00
%+ turn txos.t
|=(=txo [address.txo hk.txo])
2021-02-20 14:49:28 +03:00
`(encode:pbt:bc %.y get-rawtx get-txid ins outs)
2020-12-08 11:33:24 +03:00
--
2020-11-11 15:30:22 +03:00
:: wad: door for processing walts (wallets)
2020-12-08 20:51:14 +03:00
:: parameterized on a walt and it's chyg account
2020-11-11 15:30:22 +03:00
::
++ wad
|_ [w=walt =chyg]
2020-12-08 20:51:14 +03:00
++ pubkey
2021-02-18 15:13:06 +03:00
|= =idx:bc
^- hexb:bc
2020-12-08 20:51:14 +03:00
=/ pk=@ux
2020-10-30 14:45:38 +03:00
%- compress-point:ecc
pub:(derive-public:(~(derive-public bip32 wamp.w) chyg) idx)
2020-12-08 20:51:14 +03:00
[(met 3 pk) pk]
::
2020-12-10 16:22:48 +03:00
++ hdkey
2021-02-18 15:13:06 +03:00
|= =idx:bc
^- hdkey:bc
[fprint.w (~(pubkey wad w chyg) idx) network.w bipt.w chyg idx]
2020-12-10 16:22:48 +03:00
::
2020-12-08 20:51:14 +03:00
++ mk-address
2021-02-18 15:13:06 +03:00
|= =idx:bc
^- address:bc
2021-02-20 14:49:28 +03:00
(from-pubkey:adr:bc bipt.w network.w (pubkey idx))
:: +nixt-address: used to get change addresses
:: - gets the current next available address
:: - doesn't bump nixt-address if it's unused
:: - if used, fall back to gen-address and make a new one
::
++ nixt-address
2021-02-18 15:13:06 +03:00
^- (trel address:bc idx:bc walt)
=/ addr (mk-address nixt-idx)
~| "lib/btc-wallet-store: get-next-address: nixt shouldn't be blank"
=/ =addi (~(got by wach.w) addr)
?. used.addi
[addr nixt-idx w]
gen-address
::
:: +gen-address:
:: - generates the next available address
:: - watches it (using update address)
::
++ gen-address
2021-02-18 15:13:06 +03:00
^- (trel address:bc idx:bc walt)
=/ addr (mk-address nixt-idx)
2020-12-04 21:54:59 +03:00
:* addr
nixt-idx
%+ update-address addr
2021-02-18 15:13:06 +03:00
[%.n chyg nixt-idx *(set utxo:bc)]
2020-12-04 21:54:59 +03:00
==
2020-12-09 14:50:39 +03:00
:: +update-address
:: - insert a new address
:: - if it's used, move "nixt" to the next free address
:: - watch address
2020-10-31 14:25:32 +03:00
::
++ update-address
2021-02-18 15:13:06 +03:00
|= [a=address:bc =addi]
2020-11-11 15:30:22 +03:00
^- walt
2020-11-02 10:21:48 +03:00
?> =(chyg chyg.addi)
?> =(a (mk-address idx.addi))
=? w ?&(used.addi (is-nixt addi))
2020-12-09 14:50:39 +03:00
bump-nixt
2020-11-11 15:30:22 +03:00
w(wach (~(put by wach.w) a addi))
2020-10-31 14:25:32 +03:00
::
++ is-nixt
2020-11-02 10:21:48 +03:00
|= =addi ^- ?
?: ?=(%0 chyg.addi)
2020-11-11 15:30:22 +03:00
=(idx.addi p.nixt.w)
=(idx.addi q.nixt.w)
2020-11-02 13:37:55 +03:00
++ nixt-idx
2020-11-11 15:30:22 +03:00
?:(?=(%0 chyg) p.nixt.w q.nixt.w)
2020-12-09 14:50:39 +03:00
:: +bump-nixt: return wallet with bumped nixt
:: - find next unused address
:: - watches that address
2020-12-09 14:50:39 +03:00
:: - crashes if max-index is passed
2020-10-31 12:41:00 +03:00
::
2020-10-31 14:25:32 +03:00
++ bump-nixt
2020-12-09 14:50:39 +03:00
|^ ^- walt
2021-02-18 15:13:06 +03:00
=/ new-idx=idx:bc +(nixt-idx)
2020-10-31 14:25:32 +03:00
|- ?> (lte new-idx max-index)
2020-12-09 14:50:39 +03:00
=+ addr=(mk-address new-idx)
=/ =addi
%+ ~(gut by wach.w) addr
2021-02-18 15:13:06 +03:00
[%.n chyg new-idx *(set utxo:bc)]
2020-12-09 14:50:39 +03:00
?. used.addi
%= w
nixt (set-nixt new-idx)
wach (~(put by wach.w) addr addi)
==
2020-10-31 14:25:32 +03:00
$(new-idx +(new-idx))
2020-11-02 10:21:48 +03:00
::
++ set-nixt
2021-02-18 15:13:06 +03:00
|= =idx:bc ^- nixt
2020-11-11 15:30:22 +03:00
?:(?=(%0 chyg) [idx q.nixt.w] [p.nixt.w idx])
2020-11-02 10:21:48 +03:00
--
2020-10-30 14:45:38 +03:00
--
2021-02-12 17:37:21 +03:00
:: sut: select utxos
2020-11-18 14:39:57 +03:00
::
++ sut
2020-12-07 16:27:26 +03:00
|_ [w=walt eny=@uvJ last-block=@ud payee=(unit ship) =feyb txos=(list txo)]
2021-01-29 15:04:03 +03:00
++ dust-sats 3
++ dust-threshold
2021-02-18 15:13:06 +03:00
|= output-bipt=bipt:bc
2021-01-29 15:04:03 +03:00
^- vbytes
2021-02-20 14:49:28 +03:00
(mul dust-sats (input-weight:bc output-bipt))
2021-01-29 15:04:03 +03:00
::
2020-11-19 14:44:12 +03:00
++ target-value
^- sats
%+ roll (turn txos |=(=txo value.txo))
|=([a=sats b=sats] (add a b))
::
2020-11-18 14:39:57 +03:00
++ base-weight
^- vbytes
2021-02-20 14:49:28 +03:00
%+ add overhead-weight:bc
2021-01-29 14:09:54 +03:00
%+ roll
%+ turn txos
2021-02-20 14:49:28 +03:00
|=(=txo (output-weight:bc (get-bipt:adr:bc address.txo)))
2021-01-29 14:09:54 +03:00
add
2020-11-19 14:44:12 +03:00
::
2020-11-18 14:39:57 +03:00
++ total-vbytes
2020-12-15 10:36:46 +03:00
|= selected=(list insel)
2020-11-18 14:39:57 +03:00
^- vbytes
2021-01-29 14:09:54 +03:00
%+ add base-weight
2021-02-20 14:49:28 +03:00
(mul (input-weight:bc bipt.w) (lent selected))
2020-11-18 14:39:57 +03:00
:: value of an input after fee
2020-11-19 12:11:02 +03:00
:: 0 if net is <= 0
2020-11-18 14:39:57 +03:00
::
++ net-value
2021-01-29 15:04:03 +03:00
|= val=sats
^- sats
2021-02-20 14:49:28 +03:00
=/ cost (mul (input-weight:bc bipt.w) feyb)
2020-11-18 14:39:57 +03:00
?: (lte val cost) 0
(sub val cost)
2020-12-08 11:33:24 +03:00
::
2020-12-07 16:27:26 +03:00
:: +spendable: whether utxo has enough confs to spend
::
++ spendable
2021-02-18 15:13:06 +03:00
|= =utxo:bc ^- ?
2020-12-07 16:27:26 +03:00
(gte (num-confs last-block utxo) confs.w)
2020-12-08 11:33:24 +03:00
:: +with-change:
:: - choose UTXOs, if there are enough
:: - return txbu and amount of change (if any)
::
++ with-change
^- [tb=(unit txbu) chng=(unit sats)]
2021-02-14 13:17:58 +03:00
=/ tb=(unit txbu) select-utxos
2020-12-08 11:33:24 +03:00
?~ tb [~ ~]
2021-01-28 17:43:24 +03:00
=+ excess=~(fee txb u.tb) :: (inputs - outputs)
2021-01-29 14:09:54 +03:00
=/ new-fee=sats :: cost of this tx + one more output
2021-02-20 14:49:28 +03:00
(mul feyb (add (output-weight:bc bipt.w) vbytes.u.tb))
2021-01-28 17:43:24 +03:00
?. (gth excess new-fee)
2020-12-08 11:33:24 +03:00
[tb ~]
2021-01-29 15:04:03 +03:00
?. (gth (sub excess new-fee) (dust-threshold bipt.w))
[tb ~]
2020-12-08 11:33:24 +03:00
:- tb
2021-01-28 17:43:24 +03:00
`(sub excess new-fee)
2020-11-22 15:57:25 +03:00
:: Uses naive random selection. Should switch to branch-and-bound later.
2020-11-19 14:44:12 +03:00
::
2020-11-18 14:39:57 +03:00
++ select-utxos
2020-12-08 11:33:24 +03:00
|^ ^- (unit txbu)
2021-01-29 15:04:03 +03:00
?. %+ levy txos
|= =txo
%+ gth value.txo
2021-02-20 14:49:28 +03:00
(dust-threshold (get-bipt:adr:bc address.txo))
2021-01-29 15:04:03 +03:00
~|("One or more suggested outputs is dust." !!)
2020-12-15 10:36:46 +03:00
=/ is=(unit (list insel))
2020-11-19 14:44:12 +03:00
%- single-random-draw
%- zing
2020-12-15 10:36:46 +03:00
(turn ~(val by wach.w) to-insels)
?~ is ~
`(new-txbu w payee (total-vbytes u.is) u.is txos)
2020-11-19 18:54:01 +03:00
::
2020-12-15 10:36:46 +03:00
++ to-insels
|= =addi
^- (list insel)
2020-11-18 14:39:57 +03:00
%+ turn ~(tap in utxos.addi)
2021-02-18 15:13:06 +03:00
|=(=utxo:bc [utxo chyg.addi idx.addi])
2020-11-18 14:49:09 +03:00
--
2020-11-18 14:39:57 +03:00
:: single-random-draw
:: randomly choose utxos until target is hit
2020-12-15 10:36:46 +03:00
:: only use an insel if its net-value > 0
2020-11-18 14:39:57 +03:00
::
++ single-random-draw
2020-12-15 10:36:46 +03:00
|= is=(list insel)
^- (unit (list insel))
2020-11-18 14:39:57 +03:00
=/ rng ~(. og eny)
2021-01-29 14:09:54 +03:00
=/ target (add target-value (mul feyb base-weight)) :: add base fees to target
2021-02-18 15:13:06 +03:00
=| [select=(list insel) total=sats:bc]
2021-01-13 12:51:50 +03:00
|-
?: =(~ is) ~
2020-11-18 14:39:57 +03:00
=^ n rng (rads:rng (lent is))
2020-12-15 10:36:46 +03:00
=/ i=insel (snag n is)
2021-01-13 12:51:50 +03:00
?. (spendable utxo.i)
$(is (oust [n 1] is))
2020-11-18 14:39:57 +03:00
=/ net-val (net-value value.utxo.i)
2021-01-13 12:51:50 +03:00
=? select (gth net-val 0)
[i select]
2020-11-18 14:39:57 +03:00
=/ new-total (add total net-val)
2020-11-19 14:44:12 +03:00
?: (gte new-total target) `select
2020-11-18 14:39:57 +03:00
%= $
2020-11-19 12:11:02 +03:00
is (oust [n 1] is)
2020-11-18 14:39:57 +03:00
total new-total
==
::
--
2021-02-21 13:13:50 +03:00
::
::
:: Formerly lib/btc-provider
::
::
++ from-epoch
|= secs=@ud
^- (unit @da)
?: =(0 secs) ~
[~ (add ~1970.1.1 `@dr`(mul secs ~s1))]
::
++ get-request
|= url=@t
^- request:http
[%'GET' url ~ ~]
::
++ post-request
|= [url=@t body=json]
^- request:http
:* %'POST'
url
~[['Content-Type' 'application/json']]
=, html
%- some
%- as-octt:mimes
(en-json body)
==
::
++ gen-request
|= [=host-info:bp ract=action:rpc-types:bp]
^- request:http
%+ rpc-action-to-http
api-url.host-info ract
::
++ rpc
=, dejs:format
|%
++ parse-result
|= res=response:json-rpc
|^ ^- result:rpc-types:bp
~| -.res
?> ?=(%result -.res)
?+ id.res ~|([%unsupported-result id.res] !!)
%get-address-info
[id.res (address-info res.res)]
::
%get-tx-vals
[id.res (tx-vals res.res)]
::
%get-raw-tx
[id.res (raw-tx res.res)]
::
%broadcast-tx
[%broadcast-tx (broadcast-tx res.res)]
::
%get-block-count
[id.res (ni res.res)]
::
%get-block-info
[id.res (block-info res.res)]
==
++ address-info
%- ot
:~ [%address (cu from-cord:adr:bc so)]
[%utxos (as utxo)]
[%used bo]
[%block ni]
==
++ utxo
%- ot
:~ ['tx_pos' ni]
['tx_hash' (cu from-cord:hxb:bc so)]
[%height ni]
[%value ni]
[%recvd (cu from-epoch ni)]
==
++ tx-vals
%- ot
:~ [%included bo]
[%txid (cu from-cord:hxb:bc so)]
[%confs ni]
[%recvd (cu from-epoch ni)]
[%inputs (ar tx-val)]
[%outputs (ar tx-val)]
==
++ tx-val
%- ot
:~ [%txid (cu from-cord:hxb:bc so)]
[%pos ni]
[%address (cu from-cord:adr:bc so)]
[%value ni]
==
++ raw-tx
%- ot
:~ [%txid (cu from-cord:hxb:bc so)]
[%rawtx (cu from-cord:hxb:bc so)]
==
++ broadcast-tx
%- ot
:~ [%txid (cu from-cord:hxb:bc so)]
[%broadcast bo]
[%included bo]
==
++ block-info
%- ot
:~ [%block ni]
[%fee (mu ni)]
[%blockhash (cu from-cord:hxb:bc so)]
[%blockfilter (cu from-cord:hxb:bc so)]
==
--
--
::
++ rpc-action-to-http
|= [endpoint=@t ract=action:rpc-types:bp]
|^ ^- request:http
?- -.ract
%get-address-info
%- get-request
%+ mk-url '/addresses/info/'
(to-cord:adr:bc address.ract)
::
%get-tx-vals
%- get-request
%+ mk-url '/gettxvals/'
(to-cord:hxb:bc txid.ract)
::
%get-raw-tx
%- get-request
%+ mk-url '/getrawtx/'
(to-cord:hxb:bc txid.ract)
::
%broadcast-tx
%- get-request
%+ mk-url '/broadcasttx/'
(to-cord:hxb:bc rawtx.ract)
::
%get-block-count
%- get-request
(mk-url '/getblockcount' '')
::
%get-block-info
%- get-request
(mk-url '/getblockinfo' '')
==
++ mk-url
|= [base=@t params=@t]
%^ cat 3
(cat 3 endpoint base) params
--
:: RPC/HTTP Utilities
::
++ httr-to-rpc-response
|= hit=httr:eyre
^- response:json-rpc
~| hit
=/ jon=json (need (de-json:html q:(need r.hit)))
?. =(%2 (div p.hit 100))
(parse-rpc-error jon)
=, dejs-soft:format
^- response:json-rpc
=; dere
=+ res=((ar dere) jon)
?~ res (need (dere jon))
[%batch u.res]
|= jon=json
^- (unit response:json-rpc)
=/ res=[id=(unit @t) res=(unit json) err=(unit json)]
%. jon
=, dejs:format
=- (ou -)
:~ ['id' (uf ~ (mu so))]
['result' (uf ~ (mu same))]
['error' (uf ~ (mu same))]
==
?: ?=([^ * ~] res)
`[%result [u.id.res ?~(res.res ~ u.res.res)]]
~| jon
`(parse-rpc-error jon)
::
++ get-rpc-response
|= response=client-response:iris
^- response:json-rpc
?> ?=(%finished -.response)
%- httr-to-rpc-response
%+ to-httr:iris
response-header.response
full-file.response
::
++ parse-rpc-error
|= =json
^- response:json-rpc
:- %error
?~ json ['' '' '']
%. json
=, dejs:format
=- (ou -)
:~ =- ['id' (uf '' (cu - (mu so)))]
|*(a=(unit) ?~(a '' u.a))
:- 'error'
=- (uf ['' ''] -)
=- (cu |*(a=(unit) ?~(a ['' ''] u.a)) (mu (ou -)))
:~ ['code' (uf '' no)]
['message' (uf '' so)]
== ==
2020-10-30 14:45:38 +03:00
--