urbit/lib/btc-wallet-store.hoon

294 lines
7.1 KiB
Plaintext
Raw Normal View History

2020-10-31 14:25:32 +03:00
::
::
2020-10-30 14:45:38 +03:00
/- *btc-wallet-store
2020-12-06 14:46:32 +03:00
/+ bip32, btc, bp=btc-provider
2020-10-30 14:45:38 +03:00
=, secp:crypto
=+ ecc=secp256k1
|%
2020-11-13 15:47:11 +03:00
++ defaults
|%
++ max-gap 20
++ confs 6
--
2020-11-02 10:21:48 +03:00
::
2020-12-07 16:27:26 +03:00
++ num-confs
|= [last-block=@ud =utxo:btc]
?: =(0 height.utxo) 0
(add 1 (sub last-block height.utxo))
::
2020-11-11 15:30:22 +03:00
++ from-xpub
2020-12-08 20:31:21 +03:00
|= [=xpub:btc =fprint:btc scan-to=(unit scon) max-gap=(unit @ud) confs=(unit @ud)]
2020-11-11 15:30:22 +03:00
^- walt
2020-12-06 20:53:02 +03:00
:* xpub
2020-12-08 20:31:21 +03:00
fprint
2020-12-06 20:53:02 +03:00
(from-extended:bip32 (trip xpub))
2020-11-11 15:30:22 +03:00
(xpub-type:btc xpub)
*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
==
2020-12-08 11:33:24 +03:00
:: txb: transaction builder helpers
::
++ txb
|_ t=txbu
++ value
^- [in=sats out=sats]
:- %+ roll
%+ turn txis.t
|=(=txi value.utxo.txi)
add
%+ roll
(turn txos.t |=(=txo value.txo))
add
::
++ fee
=/ [in=sats out=sats] value
(sub in out)
::
++ add-output
2020-12-08 20:31:21 +03:00
|= =txo
2020-12-08 11:33:24 +03:00
^- txbu
2020-12-08 20:31:21 +03:00
t(txos [txo txos.t])
2020-12-10 18:54:39 +03:00
:: +to-psbt: returns a based 64 PSBT if
:: - txinfo is present
:: - all inputs have an associated rawtx
2020-12-08 20:31:21 +03:00
::
++ to-psbt
2020-12-10 19:15:25 +03:00
^- (unit base64:psbt:btc)
2020-12-10 18:54:39 +03:00
?~ txinfo.t ~
=/ ins=(list in:psbt:btc)
%+ murn txis.t
|= =txi
?~ ur.txi ~
`[utxo.txi u.ur.txi hdkey.txi]
?: (lth (lent ins) (lent txis.t))
~
=/ outs=(list out:psbt:btc)
%+ turn txos.t
|=(=txo [address.txo hk.txo])
:- ~
%: encode:pbt:btc
rawtx.u.txinfo.t txid.u.txinfo.t
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
2020-12-08 20:31:21 +03:00
|= =idx:btc
^- bytc:btc
2020-12-08 20:51:14 +03:00
=/ pk=@ux
2020-10-30 14:45:38 +03:00
%- compress-point:ecc
2020-11-11 15:30:22 +03:00
pub:(derive-public:(derive-public:wilt.w (@ chyg)) idx)
2020-12-08 20:51:14 +03:00
[(met 3 pk) pk]
::
2020-12-10 16:22:48 +03:00
++ hdkey
|= =idx:btc
^- hdkey:btc
[fprint.w (~(pubkey wad w chyg) idx) bipt.w chyg idx]
::
2020-12-08 20:51:14 +03:00
++ mk-address
|= =idx:btc
^- address:btc
?: ?=(%84 bipt.w)
2020-12-08 20:51:14 +03:00
(need (encode-pubkey:bech32:btc %main dat:(pubkey idx)))
~|("legacy addresses not supported yet " !!)
:: +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
^- (trel address:btc idx:btc 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
2020-12-08 20:31:21 +03:00
^- (trel address:btc idx:btc walt)
=/ addr (mk-address nixt-idx)
2020-12-04 21:54:59 +03:00
:* addr
nixt-idx
%+ update-address addr
[%.n chyg nixt-idx *(set utxo:btc)]
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
2020-10-31 14:25:32 +03:00
|= [a=address:btc =addi]
:: TODO: check whether addi is used or not before bumping nixt
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
2020-12-08 20:31:21 +03:00
=/ new-idx=idx:btc +(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
[%.n chyg new-idx *(set utxo:btc)]
?. 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
2020-12-08 20:31:21 +03:00
|= =idx:btc ^- 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
--
2020-11-18 14:39:57 +03:00
:: sut: door to select utxos
::
++ sut
2020-12-07 16:27:26 +03:00
|_ [w=walt eny=@uvJ last-block=@ud payee=(unit ship) =feyb txos=(list txo)]
2020-11-18 14:39:57 +03:00
++ meta-weight 10
++ output-weight 31
2020-12-08 11:33:24 +03:00
++ n-txos (lent txos)
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
2020-12-08 11:33:24 +03:00
|= num-txos=@ud
2020-11-18 14:39:57 +03:00
^- vbytes
%+ add meta-weight
2020-12-08 11:33:24 +03:00
(mul num-txos output-weight)
2020-11-19 14:44:12 +03:00
::
2020-11-18 14:39:57 +03:00
++ input-weight
^- vbytes
?. ?=(%84 bipt.w)
2020-11-18 14:39:57 +03:00
~|("Only bech32 wallets supported" !!)
102
::
2020-12-08 11:33:24 +03:00
++ min-tx-fee
^- sats
%+ mul feyb
(add (base-weight 1) input-weight)
::
2020-11-18 14:39:57 +03:00
++ total-vbytes
|= selected=(list input)
^- vbytes
2020-12-08 11:33:24 +03:00
%+ add (base-weight n-txos)
2020-11-18 14:39:57 +03:00
(mul input-weight (lent selected))
:: 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
|= val=sats ^- sats
=/ cost (mul input-weight feyb)
?: (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
|= =utxo:btc ^- ?
(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)]
=+ tb=select-utxos
?~ tb [~ ~]
=+ fee=~(fee txb u.tb)
=/ costs=sats :: cost of this tx + sending another
%+ add min-tx-fee
(mul feyb vbytes.u.tb)
?. (gth fee costs)
[tb ~]
:- tb
`(sub fee costs)
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)
2020-11-26 12:38:30 +03:00
=/ is=(unit (list input))
2020-11-19 14:44:12 +03:00
%- single-random-draw
%- zing
(turn ~(val by wach.w) to-inputs)
2020-11-26 12:38:30 +03:00
?~(is ~ `(inputs-to-txbu u.is))
2020-11-19 18:54:01 +03:00
::
2020-11-18 14:49:09 +03:00
++ to-inputs
|= =addi ^- (list input)
2020-11-18 14:39:57 +03:00
%+ turn ~(tap in utxos.addi)
2020-11-18 14:49:09 +03:00
|=(=utxo:btc [utxo chyg.addi idx.addi])
2020-12-06 14:46:32 +03:00
::
2020-11-19 18:54:01 +03:00
++ inputs-to-txbu
2020-12-06 20:53:02 +03:00
|= is=(list input)
^- txbu
2020-12-06 14:46:32 +03:00
:* (gen-req-id:bp eny)
2020-12-08 14:47:23 +03:00
~
2020-12-06 20:53:02 +03:00
xpub.w
2020-12-06 14:46:32 +03:00
payee
2020-11-22 15:57:25 +03:00
(total-vbytes is)
2020-11-19 18:54:01 +03:00
%+ turn is
2020-12-10 16:22:48 +03:00
|= i=input
[utxo.i ~ (~(hdkey wad w chyg.i) idx.i)]
2020-11-19 18:54:01 +03:00
txos
==
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
:: only use an input if its net-value > 0
::
++ single-random-draw
|= is=(list input)
2020-11-19 14:44:12 +03:00
^- (unit (list input))
2020-11-18 14:39:57 +03:00
=/ rng ~(. og eny)
2020-12-08 11:33:24 +03:00
=/ target (add target-value (mul feyb (base-weight n-txos))) :: add base fees to target
2020-11-18 14:39:57 +03:00
=| [select=(list input) total=sats:btc]
2020-11-19 12:11:02 +03:00
|- ?: =(~ is) ~
2020-11-18 14:39:57 +03:00
=^ n rng (rads:rng (lent is))
2020-11-19 12:11:02 +03:00
=/ i=input (snag n is)
2020-11-18 14:39:57 +03:00
=/ net-val (net-value value.utxo.i)
2020-12-07 16:27:26 +03:00
=? select ?&((spendable utxo.i) (gth net-val 0))
[i select] :: select if net-value > 0
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
==
::
--
2020-10-30 14:45:38 +03:00
--