diff --git a/DEMO.md b/DEMO.md index d9444965b..5c4adb588 100644 --- a/DEMO.md +++ b/DEMO.md @@ -13,12 +13,11 @@ On `~zod`. Uses "abandon abandon..." mnemonic ``` |commit %home |start %btc-provider -|start %btc-wallet-hook -|start %btc-wallet-store +|start %btc-wallet :btc-provider|command [%set-credentials api-url='http://localhost:50002' %main] -:btc-wallet-hook|command [%set-provider ~zod %main] -:btc-provider|command [%whitelist-clients `(set ship)`(sy ~[~dopzod])] +:btc-wallet|command [%set-provider ~zod %main] +:btc-provider|command [%add-whitelist %users `(set ship)`(sy ~[~dopzod])] =fprint [%4 0xbeef.dead] =xpubmain 'zpub6rFR7y4Q2AijBEqTUquhVz398htDFrtymD9xYYfG1m4wAcvPhXNfE3EfH1r1ADqtfSdVCToUG868RvUUkgDKf31mGDtKsAYz2oz2AGutZYs' @@ -28,10 +27,9 @@ On `~zod`. Uses "abandon abandon..." mnemonic On `~dopzod`. Uses "absurd sick..." mnemonic from PRIVATE.scratch.md ``` |commit %home -|start %btc-wallet-hook -|start %btc-wallet-store +|start %btc-wallet -:btc-wallet-hook|command [%set-provider ~zod %main] +:btc-wallet|command [%set-provider ~zod %main] =fprint [%4 0xdead.beef] =xpubmain 'zpub6r8dKyWJ31XF6n69KKeEwLjVC5ruqAbiJ4QCqLsrV36Mvx9WEjUaiPNPGFLHNCCqgCdy6iZC8ZgHsm6a1AUTVBMVbKGemNcWFcwBGSjJKbD' @@ -41,17 +39,17 @@ On `~dopzod`. Uses "absurd sick..." mnemonic from PRIVATE.scratch.md ### Add Wallets On both `~zod`/`dopzod`, choose depending on whether you're on test or main ``` -:btc-wallet-store|action [%add-wallet xpubmain fprint ~ [~ 8] [~ 6]] +:btc-wallet|command [%add-wallet xpubmain fprint ~ [~ 8] [~ 6]] -:btc-wallet-store|action [%add-wallet xpubtest fprint ~ [~ 8] [~ 6]] +:btc-wallet|command [%add-wallet xpubtest fprint ~ [~ 8] [~ 6]] ``` ## Check Balance `~dopzod` ``` -.^(@ud %gx /=btc-wallet-store=/balance/[xpubmain]/noun) +.^(@ud %gx /=btc-wallet=/balance/[xpubmain]/noun) -.^(@ud %gx /=btc-wallet-store=/balance/[xpubtest]/noun) +.^(@ud %gx /=btc-wallet=/balance/[xpubtest]/noun) ``` ## Pay a Ship @@ -93,7 +91,7 @@ Or can change amount: ``` =realxpub 'zpub6qvniDfrk9sRxz7H9Cbr8fccuGNd4RGMmifPVvbQtqtsG7VwCUrNsnNt8DiCH8kxh3vsDuJkfNqZQspVq2xEbE64fgXT5hVJiD8WkRhvuJc' =fprint [%4 0xc93d.865c] -:btc-wallet-store|action [%add-wallet realxpub fprint ~ [~ 6] [~ 6]] +:btc-wallet|command [%add-wallet realxpub fprint ~ [~ 6] [~ 6]] -.^(@ud %gx /=btc-wallet-store=/balance/[realxpub]/noun) +.^(@ud %gx /=btc-wallet=/balance/[realxpub]/noun) ``` diff --git a/app/btc-wallet-hook.hoon b/app/btc-wallet-hook.hoon deleted file mode 100644 index 435f62603..000000000 --- a/app/btc-wallet-hook.hoon +++ /dev/null @@ -1,655 +0,0 @@ -:: btc-wallet-hook.hoon -:: -:: Subscribes to: -:: btc-provider: -:: - connection status -:: - RPC call results/errors -:: -:: btc-wallet-store -:: - requests for address info -:: - updates to existing address info -:: -:: Sends updates to: -:: /updates -:: -/- *btc-wallet-hook, bws=btc-wallet-store -/+ dbug, default-agent, bp=btc-provider, bwsl=btc-wallet-store, *btc -|% -++ defaults - |% - ++ fam-limit 10 - ++ piym-limit 3 - -- -+$ versioned-state - $% state-0 - == -:: prov: maybe ship if provider is set -:: fam-limit: how many addresses a ship and its moons can request in piym -:: piym-limit: how many entries a given ship can have in pend-piym -:: A ship can only broadcast X payments to us until we see one of them in the mempool -:: feybs: fee/byte in sats used for a given ship payee -:: -+$ state-0 - $: %0 - prov=(unit provider) - =reqs - =btc-state - def-wallet=(unit xpub) - fam-limit=@ud - piym-limit=@ud - feybs=(map ship sats) - =piym - =poym - =pend-piym - == -:: -+$ card card:agent:gall --- -=| state-0 -=* state - -%- agent:dbug -^- agent:gall -=< -|_ =bowl:gall -+* this . - def ~(. (default-agent this %|) bowl) - hc ~(. +> bowl) -:: -++ on-init - ^- (quip card _this) - ~& > '%btc-wallet-hook initialized' - :_ this(fam-limit.state fam-limit:defaults) - :~ [%pass /r/[(scot %da now.bowl)] %agent [our.bowl %btc-wallet-store] %watch /requests] - [%pass /u/[(scot %da now.bowl)] %agent [our.bowl %btc-wallet-store] %watch /updates] - == -++ on-save - ^- vase - !>(state) -++ on-load - |= old-state=vase - ^- (quip card _this) - ~& > '%btc-wallet-hook recompiled' - `this(state !<(versioned-state old-state)) -++ on-poke - |= [=mark =vase] - ^- (quip card _this) - =^ cards state - ?+ mark (on-poke:def mark vase) - %btc-wallet-hook-action - (handle-action:hc !<(action vase)) - :: - %btc-wallet-hook-command - (handle-command:hc !<(command vase)) - == - [cards this] -:: -++ on-watch - |= pax=path - ^- (quip card _this) - ?+ pax (on-watch:def pax) - [%sign-me ~] - `this - == -++ on-leave on-leave:def -++ on-peek on-peek:def -++ on-agent - |= [=wire =sign:agent:gall] - ^- (quip card _this) - ?+ -.sign (on-agent:def wire sign) - %kick - ~& >>> "kicked from prov {}" - ?~ prov `this - ?: ?& ?=(%set-provider -.wire) - =(host.u.prov src.bowl) - == - `this(prov ~) - `this - :: - %fact - =^ cards state - ?+ p.cage.sign `state - %btc-provider-status - (handle-provider-status:hc !<(status:bp q.cage.sign)) - :: - %btc-provider-update - (handle-provider-update:hc !<(update:bp q.cage.sign)) - :: - %btc-wallet-store-request - (handle-wallet-store-request:hc !<(request:bws q.cage.sign)) - :: - %btc-wallet-store-update - (handle-wallet-store-update:hc wire !<(update:bws q.cage.sign)) - == - [cards this] - == -++ on-arvo on-arvo:def -++ on-fail on-fail:def --- -|_ =bowl:gall -++ handle-command - |= comm=command - ^- (quip card _state) - ?- -.comm - %set-provider - =* sub-card - [%pass /set-provider %agent [provider.comm %btc-provider] %watch /clients] - :_ state(prov [~ provider.comm %.n]) - ?~ prov ~[sub-card] - :~ [%pass /set-provider %agent [host.u.prov %btc-provider] %leave ~] - sub-card - == - :: - %set-default-wallet - =/ xs=(list xpub) scry-scanned - =/ i=(unit @) (find ~[xpub.comm] xs) - ?~ i `state - `state(def-wallet `(snag u.i xs)) - :: - %delete-wallet - :- ~[(poke-store [%delete-wallet xpub.comm])] - ?~ def-wallet state - ?. =(u.def-wallet xpub.comm) state - state(def-wallet ~) - :: - :: %req-pay-address - :: overwrites any payment being built currently - :: can't pay if there's an outstanding payment being broadcast - :: can't pay yourself; comets can't pay (could spam requests) - :: forwards poke to payee if payee isn't us - :: deletes poym since we'll be making a new outgoing payment - :: lets us set fee per byte and recall it once we get a payment address back - :: wire is /payer/value/timestamp - :: - %req-pay-address - ?: broadcasting ~|("Broadcasting a transaction" !!) - ~| "Can't pay ourselves; no comets; can't do while tx is being signed" - ?< =(src.bowl payee.comm) - ?< ?=(%pawn (clan:title payee.comm)) - ?< broadcasting - => .(poym ~, feybs (~(put by feybs) payee.comm feyb.comm)) - :_ state - ~[(poke-hook payee.comm [%gen-pay-address value.comm])] - :: - :: %broadcast-tx - :: - poym txid must match incoming txid - :: - update sitx in poym - :: - send to provider - :: - %broadcast-tx - ?> =(src.bowl our.bowl) - ?~ prov ~|("Provider not connected" !!) - =+ signed=(to-hexb txhex.comm) - =/ tx-match=? - ?~ poym %.n - =((get-id:txu (decode:txu signed)) ~(get-txid txb:bwsl u.poym)) - :_ ?. tx-match state - ?~ poym state - state(sitx.u.poym `signed) - ?. tx-match - ~[(send-update [%broadcast-tx-mismatch-poym signed])] - ~[(poke-provider host.u.prov [%broadcast-tx signed])] - :: - %clear-poym - `state(poym ~) - :: - %force-retry - [(retry-reqs block.btc-state) state] - == -:: -++ handle-action - |= act=action - ^- (quip card _state) - ?- -.act - %add-piym - ?> =(src.bowl our.bowl) - :_ state(ps.piym (~(put by ps.piym) payer.act [~ +.act])) - ~[(poke-hook payer.act [%ret-pay-address +>.act])] - :: - %add-poym - ?> =(src.bowl our.bowl) - :_ state(poym `txbu.act) - ?~ prov ~&(>>> "provider not set" ~) - %+ turn txis.txbu.act - |=(=txi:bws (get-raw-tx host.u.prov txid.utxo.txi)) - :: - %add-poym-txi - ?> =(src.bowl our.bowl) - ?~ poym `state - =. txis.u.poym - (update-poym-txis txis.u.poym +.act) - :_ state - =+ pb=~(to-psbt txb:bwsl u.poym) - ?~ pb ~ - =+ vb=~(vbytes txb:bwsl u.poym) - =+ fee=~(fee txb:bwsl u.poym) - ~& >> "{} vbytes, {<(div fee vb)>} sats/byte, {} sats fee" - %- (slog [%leaf "PSBT: {}"]~) - ~[(send-update [%sign-tx u.poym])] - :: - %close-pym - ?> =(src.bowl our.bowl) - =^ cards state - ?. included.ti.act - `state - ?: (~(has by pend-piym) txid.ti.act) - (piym-to-history ti.act) - ?: (poym-has-txid txid.ti.act) - (poym-to-history ti.act) - `state - :_ state - [(poke-store [%tx-info ti.act block.btc-state]) cards] - :: - %fail-broadcast-tx - ?> =(src.bowl our.bowl) - ~& > "%fail-broadcast-tx" - :_ state(poym ~) - ~[(send-update [%broadcast-tx-spent-utxos txid.act])] - :: - %succeed-broadcast-tx - ?> =(src.bowl our.bowl) - ~& > "%succeed-broadcast-tx" - :_ %_ state - reqs (~(put by reqs) txid.act [%tx-info 0 txid.act]) - == - ?~ prov ~ - :- (poke-provider host.u.prov [%tx-info txid.act]) - ?~ poym ~ - ?~ payee.u.poym ~ - :_ ~ - %- poke-hook - :* u.payee.u.poym - %expect-payment - txid.act - value:(snag 0 txos.u.poym) - == - :: can't pay yourself; comets can't pay (could spam requests) - :: must have default wallet set - :: reuses payment address for ship if exists in piym - :: - %gen-pay-address - ~| "Can't pay ourselves; no comets" - ?< =(src.bowl our.bowl) - ?< ?=(%pawn (clan:title src.bowl)) - =^ cards state - (reuse-address src.bowl value.act) - ?^ cards [cards state] - :: if no reuseable address, call store to generate - :: - =+ f=(fam src.bowl) - =+ n=(~(gut by num-fam.piym) f 0) - ?~ def-wallet ~|("btc-wallet-hook: no def-wallet set" !!) - ?: (gte n fam-limit) - ~|("More than {} addresses for moons + planet" !!) - :_ state(num-fam.piym (~(put by num-fam.piym) f +(n))) - :~ %- poke-store - [%generate-address u.def-wallet %0 `[src.bowl value.act]] - == - :: - %ret-pay-address - ?: =(src.bowl our.bowl) ~|("Can't pay ourselves" !!) - ?: is-broadcasting ~|("Broadcasting a transaction" !!) - ?~ def-wallet ~|("btc-wallet-hook: no def(ault)-wallet set" !!) - =+ feyb=(~(gut by feybs) src.bowl ?~(fee.btc-state 100 u.fee.btc-state)) - ?> =(payer.act our.bowl) - :_ state - :~ %- poke-store - [%generate-txbu u.def-wallet `src.bowl feyb ~[[address.act value.act ~]]] - == - :: %expect-payment - :: - check that payment is in piym - :: - replace pend.payment with incoming txid (lock) - :: - add txid to pend-piym - :: - request tx-info from provider - :: - %expect-payment - |^ - =+ pay=(~(get by ps.piym) src.bowl) - ~| "%expect-payment: matching payment not in piym" - ?~ pay !! - ?> (piym-matches u.pay) - :_ (update-pend-piym txid.act u.pay(pend `txid.act)) - ?~ prov ~ - ~[(poke-provider host.u.prov [%tx-info txid.act])] - :: - ++ piym-matches - |= p=payment - ?& =(payer.p src.bowl) - =(value.p value.act) - == - -- - == -:: +handle-provider-status: handle connectivity updates from provider -:: - retry pend-piym on any %connected event, since we're checking mempool -:: - if status is %connected, retry all pending address lookups -:: - only retry all if previously disconnected -:: - if block is updated, retry all address reqs -:: -++ handle-provider-status - |= s=status:bp - ^- (quip card _state) - |^ - ?~ prov `state - ?. =(host.u.prov src.bowl) `state - ?- -.s - %new-block - (connected u.prov block.s fee.s `blockhash.s `blockfilter.s) - :: - %connected - (connected u.prov block.s fee.s ~ ~) - :: - %disconnected - `state(prov `[host.u.prov %.n]) - == - :: - ++ connected - |= $: p=provider - block=@ud - fee=(unit sats) - blockhash=(unit hexb) - blockfilter=(unit hexb) - == - ^- (quip card _state) - :_ %_ state - prov `[host.p %.y] - btc-state [block fee now.bowl] - == - ?: ?!(connected.p) - %- zing - :~ (retry-reqs block) - retry-poym - retry-pend-piym - == - ?. (lth block.btc-state block) - retry-pend-piym - (weld retry-pend-piym (retry-reqs block)) - -- -:: -++ handle-provider-update - |= upd=update:bp - ^- (quip card _state) - ?. ?=(%.y -.upd) `state - ?- -.p.upd - %address-info - =+ r=(~(get by reqs) address.p.upd) - :_ state(reqs (~(del by reqs) address.p.upd)) - ?~ r ~ - ?> ?=(%address-info -.u.r) - ~[(poke-store [%address-info xpub.u.r chyg.u.r idx.u.r +>.p.upd])] - :: - %tx-info - :_ state(reqs (~(del by reqs) txid.info.p.upd)) - ~[(poke-hook our.bowl [%close-pym info.p.upd])] - :: - %raw-tx - :_ state - ~[(poke-hook our.bowl [%add-poym-txi +.p.upd])] - :: - %broadcast-tx - ?~ poym `state - ?. =(~(get-txid txb:bwsl u.poym) txid.p.upd) - `state - :_ state - ?: ?|(broadcast.p.upd included.p.upd) - ~[(poke-hook our.bowl [%succeed-broadcast-tx txid.p.upd])] - ~[(poke-hook our.bowl [%fail-broadcast-tx txid.p.upd])] - == -:: -++ handle-wallet-store-request - |= req=request:bws - ^- (quip card _state) - ?~ prov `state - =/ should-send=? - ?& provider-connected - (lth last-block.req block.btc-state) - == - ?- -.req - %address-info - :_ state(reqs (~(put by reqs) a.req req)) - ?. should-send ~ - ~[(poke-provider host.u.prov [%address-info a.req])] - :: - %tx-info - :_ state(reqs (~(put by reqs) txid.req req)) - ?. should-send ~ - ~[(poke-provider host.u.prov [%tx-info txid.req])] - == -:: -++ handle-wallet-store-update - |= [=wire upd=update:bws] - ^- (quip card _state) - ?- -.upd - %generate-address - ?~ pmet.upd ~&(> "%generate-address: {}" `state) - :_ state - :~ %+ poke-hook our.bowl - [%add-piym xpub.upd address.upd payer.u.pmet.upd value.u.pmet.upd] - == - :: - %generate-txbu - :_ state - ~[(poke-hook our.bowl [%add-poym txbu.upd])] - :: - %saw-piym - `state - :: - %scan-done - ~& > "scanned wallet: {}" - ?~ def-wallet - `state(def-wallet `xpub.upd) - `state - == -:: +reuse-address: if piym already has address for payer, -:: replace address and return to payer -:: - if payment is pending, crash. Shouldn't be getting an address request -:: -++ reuse-address - |= [payer=ship value=sats] - ^- (quip card _state) - =+ p=(~(get by ps.piym) payer) - ?~ p `state - ?^ pend.u.p ~|("%gen-address: {} already has pending payment to us" !!) - =+ newp=u.p(value value) - :_ state(ps.piym (~(put by ps.piym) payer newp)) - :~ %+ poke-hook payer - [%ret-pay-address address.newp payer value] - == -:: -++ poym-has-txid - |= txid=hexb - ^- ? - ?~ poym %.n - ?~ sitx.u.poym %.n - =(txid (get-id:txu (decode:txu u.sitx.u.poym))) -:: +poym-to-history: -:: - checks whether poym has a signed tx -:: - checks whether the txid matches that signed tx -:: - if not, skip -:: - clears poym -:: - returns card that adds hest to wallet-store history -:: -++ poym-to-history - |= ti=info:tx - ^- (quip card _state) - |^ - ?~ poym `state - ?~ sitx.u.poym `state - ?. (poym-has-txid txid.ti) - `state - =+ vout=(get-vout txos.u.poym) - ?~ vout ~|("poym-to-history: poym should always have an output" !!) - :_ state(poym ~) - ~[(add-history-entry ti xpub.u.poym our.bowl payee.u.poym u.vout)] - :: - ++ get-vout - |= txos=(list txo:bws) - ^- (unit @ud) - =| idx=@ud - |- ?~ txos ~ - ?~ hk.i.txos `idx - $(idx +(idx), txos t.txos) - -- -:: +piym-to-history -:: - checks whether txid in pend-piym -:: - checks whether ti has a matching value output to piym -:: - if no match found, just deletes pend-piym with this tx -:: stops peer from spamming txids -:: - returns card that adds hest to wallet-store history -:: -++ piym-to-history - |= ti=info:tx - |^ ^- (quip card _state) - =+ pay=(~(get by pend-piym) txid.ti) - ?~ pay `state - :: if no matching output in piym, delete from pend-piym to stop DDOS of txids - :: - =+ vout=(get-vout value.u.pay) - ?~ vout - `(del-pend-piym txid.ti) - :_ (del-all-piym txid.ti payer.u.pay) - :~ %- add-history-entry - [ti xpub.u.pay payer.u.pay `our.bowl u.vout] - == - :: - ++ get-vout - |= value=sats - ^- (unit @ud) - =| idx=@ud - =+ os=outputs.ti - |- ?~ os ~ - ?: =(value.i.os value) - `idx - $(os t.os, idx +(idx)) - :: - :: - ++ del-pend-piym - |= txid=hexb - ^- _state - state(pend-piym (~(del by pend-piym) txid.ti)) - :: - ++ del-all-piym - |= [txid=hexb payer=ship] - ^- _state - =+ nf=(~(gut by num-fam.piym) payer 1) - %= state - pend-piym (~(del by pend-piym) txid) - ps.piym (~(del by ps.piym) payer) - num-fam.piym (~(put by num-fam.piym) payer (dec nf)) - == - -- -:: -++ add-history-entry - |= [ti=info:tx =xpub payer=ship payee=(unit ship) vout=@ud] - ^- card - %- poke-store - :* %add-history-entry - xpub txid.ti confs.ti recvd.ti - (turn inputs.ti |=(i=val:tx [i `payer])) - %+ turn outputs.ti - |= o=val:tx - ?: =(pos.o vout) - [o payee] - [o `payer] - == -:: +fam: planet parent if s is a moon -:: -++ fam - |= s=ship - ^- ship - ?. =(%earl (clan:title s)) s - (sein:title our.bowl now.bowl s) -:: +update-pend-piym -:: - set pend.payment to txid (lock) -:: - add txid to pend-piym -:: -++ update-pend-piym - |= [txid=hexb p=payment] - ^- _state - ?~ pend.p ~|("update-pend-piym: empty pend.payment" !!) - %= state - ps.piym (~(put by ps.piym) payer.p p) - pend-piym (~(put by pend-piym) txid p) - == -:: -:: +update-poym-txis: -:: update outgoing payment with a rawtx, if the txid is in poym's txis -:: -++ update-poym-txis - |= [txis=(list txi:bws) txid=hexb rt=hexb] - ^- (list txi:bws) - =| i=@ - |- ?: (gte i (lent txis)) txis - =/ ith=txi:bws (snag i txis) - =? txis =(txid txid.utxo.ith) - (snap txis i `txi:bws`ith(ur `rt)) - $(i +(i)) -:: +retry-reqs: get-address-info for any reqs with old last-block -:: -++ retry-reqs - |= [latest-block=@ud] - ^- (list card) - ?~ prov ~|("provider not set" !!) - %+ murn ~(val by reqs) - |= [req=request:bws] - ?: (gte last-block.req latest-block) ~ - :- ~ - %+ poke-provider host.u.prov - ?- -.req - %address-info [%address-info a.req] - %tx-info [%tx-info txid.req] - == -:: -++ retry-poym - ^- (list card) - ?~ poym ~ - %+ weld - ?~ sitx.u.poym ~ - ~[(poke-provider [%broadcast-tx u.sitx.u.poym])] - %+ turn txis.u.poym - |= =txi - (poke-provider [%raw-tx txid]) -:: +retry-pend-piym: check whether txids in pend-piym are in mempool -:: -++ retry-pend-piym - ^- (list card) - %+ turn ~(tap in ~(key by pend-piym)) - |=(=txid (poke-provider [%tx-info txid])) -:: -++ get-raw-tx - |= [host=ship txid=hexb] - ^- card - (poke-provider host [%raw-tx txid]) - -:: - -:: -++ poke-hook - |= [target=ship act=action] - ^- card - :* %pass /[(scot %da now.bowl)] %agent - [target %btc-wallet-hook] %poke - %btc-wallet-hook-action !>(act) - == -:: -++ send-update - |= =update - ^- card - [%give %fact ~[/updates] %btc-wallet-hook-update !>(update)] -:: -++ poke-store - |= act=action:bws - ^- card - :* %pass /[(scot %da now.bowl)] - %agent [our.bowl %btc-wallet-store] %poke - %btc-wallet-store-action !>(act) - == -:: -++ scry-scanned - .^ (list xpub) - %gx - (scot %p our.bowl) - %btc-wallet-store - (scot %da now.bowl) - %scanned - %noun - == --- diff --git a/app/btc-wallet-store.hoon b/app/btc-wallet-store.hoon deleted file mode 100644 index bfe1534b4..000000000 --- a/app/btc-wallet-store.hoon +++ /dev/null @@ -1,431 +0,0 @@ -:: btc-wallet-store.hoon -:: Manages wallet pubkeys -:: -:: Subscribes to: none -:: -:: Sends updates on: -:: - /requests: to request data about addresses -:: - /updates: new data about one of our addresses -:: - -:: -/- *btc-wallet-store -/+ dbug, default-agent, *btc-wallet-store, btc, bip32 -|% -++ req-pax /requests -+$ versioned-state - $% state-0 - == -:: walts: all wallets, keyed by their xpubs -:: scans: batch info for wallets being scanned -:: gena: generated addresses that haven't had activity yet -:: batch-size: how many addresses to send out at once for checking -:: last-block: most recent block seen by the store -:: -+$ state-0 - $: %0 - walts=(map xpub:btc walt) - =scans - batch-size=@ud - last-block=@ud - =history - == -:: -+$ card card:agent:gall -:: --- -=| state-0 -=* state - -%- agent:dbug -^- agent:gall -=< -|_ =bowl:gall -+* this . - def ~(. (default-agent this %|) bowl) - hc ~(. +> bowl) -:: -++ on-init - ^- (quip card _this) - ~& > '%btc-wallet-store initialized' - `this(state [%0 *(map xpub:btc walt) *^scans max-gap:defaults 0 *^history]) -++ on-save - ^- vase - !>(state) -++ on-load - |= old-state=vase - ^- (quip card _this) - ~& > '%btc-wallet-store recompiled' - `this(state !<(versioned-state old-state)) -++ on-poke - |= [=mark =vase] - ^- (quip card _this) - ?> (team:title our.bowl src.bowl) - =^ cards state - ?+ mark (on-poke:def mark vase) - %btc-wallet-store-action - (handle-action:hc !<(action vase)) - == - [cards this] -:: -++ on-watch - |= pax=path - ^- (quip card _this) - ?> (team:title our.bowl src.bowl) - ?+ pax (on-watch:def pax) - [%requests *] - :_ this - %- zing - %~ val by - %- ~(urn by scans) - |* [k=[=xpub:btc =chyg] b=batch] - ^- (list card) - (req-scan ~ b xpub.k chyg.k) - :: - [%updates *] - `this - == -++ on-peek - |= pax=path - ^- (unit (unit cage)) - ?+ pax (on-peek:def pax) - [%x %scanned ~] - ``noun+!>(scanned-wallets) - :: - [%x %balance @ ~] - ``noun+!>((balance:hc (xpub:btc +>-.pax))) - == -++ on-leave on-leave:def -++ on-agent on-agent:def -++ on-arvo on-arvo:def -++ on-fail on-fail:def --- -:: -|_ =bowl:gall -++ handle-action - |= act=action - ^- (quip card _state) - ?- -.act - %add-wallet - =/ w=walt (from-xpub +.act) - =. walts (~(put by walts) xpub.act w) - (init-batches xpub.act (dec max-gap.w)) - :: - %delete-wallet - `state(walts (~(del by walts) xpub.act)) - :: - %address-info - (update-address +.act) - :: - %tx-info - (handle-tx-info +.act) - :: - %generate-address - (generate-address +.act) - :: %generate-txbu - :: - get txbu and change amount - :: - if txbu is blank, fail - :: - if change is blank, send txbu as update - :: - if change: - :: - generate new change address - :: - add that address+change value to the txbu - :: - send txbu update - :: - send a request for info on the address (watch it) - :: - DON'T send an address update for the address, since it's change - :: - %generate-txbu - =+ uw=(~(get by walts) xpub.act) - ?~ uw - ~|("btc-wallet-store: non-existent xpub" !!) - ?. scanned.u.uw - ~|("btc-wallet-store: wallet not scanned yet" !!) - =/ [tb=(unit txbu) chng=(unit sats)] - %~ with-change sut - [u.uw eny.bowl last-block payee.act feyb.act txos.act] - ?~ tb ~&(>>> "btc-wallet-store: insufficient balance" `state) - :: if no change, just return txbu - :: - ?~ chng - [~[(send-update [%generate-txbu xpub.act u.tb])] state] - =/ [addr=address:btc =idx w=walt] - ~(nixt-address wad u.uw %1) - =/ new-txbu=txbu - (~(add-output txb u.tb) addr u.chng `(~(hdkey wad w %1) idx)) - :_ state(walts (~(put by walts) xpub.act w)) - :~ (send-update [%generate-txbu xpub.act new-txbu]) - %+ send-req ~[req-pax] - :* %address-info last-block - addr xpub.act %1 idx - == - == - :: - %add-history-entry - :_ state(history (~(put by history) txid.hest.act hest.act)) - ~[(send-req ~[req-pax] [%tx-info last-block txid.hest.act])] - :: - %del-history-entry - :_ state(history (~(del by history) txid.act)) - ~[(send-req ~[req-pax] [%tx-info last-block txid.act])] - == -:: wallet scan algorithm: -:: Initiate a batch for each chyg, with max-gap idxs in it -:: Send that to /requests subscribers to call out to providers and get the info -:: Whenever a %watch-address result comes back -:: - remove that idx from todo.batch -:: - do run-scan to check whether that chyg is done -:: - if it isn't, refill it with idxs to scan -:: -++ req-scan - |= [pax=(list path) b=batch =xpub =chyg] - ^- (list card) - =/ w=walt (~(got by walts) xpub) - %+ turn ~(tap in todo.b) - |= =idx - =/ req=request - :* %address-info last-block=1 - (~(mk-address wad w chyg) idx) - xpub chyg idx - == - (send-req pax req) -:: -++ scan-status - |= [=xpub =chyg] - ^- [empty=? done=?] - =/ b=batch (~(got by scans) [xpub chyg]) - =/ empty=? =(0 ~(wyt in todo.b)) - :- empty - ?&(empty ?!(has-used.b)) -:: -++ insert-batches - |= [=xpub b0=batch b1=batch] - ^- ^scans - =. scans (~(put by scans) [xpub %0] b0) - (~(put by scans) [xpub %1] b1) -:: -++ init-batches - |= [=xpub endpoint=idx] - ^- (quip card _state) - =/ b=batch - [(sy (gulf 0 endpoint)) endpoint %.n] - :- %+ weld - (req-scan ~[req-pax] b xpub %0) - (req-scan ~[req-pax] b xpub %1) - state(scans (insert-batches xpub b b)) -:: if the batch is done but the wallet isn't done scanning, -:: returns new address requests and updated batch -:: -++ bump-batch - |= [=xpub =chyg] - ^- (quip card batch) - =/ b=batch (~(got by scans) xpub chyg) - =/ s (scan-status xpub chyg) - ?. ?&(empty.s ?!(done.s)) - `b - =/ w=walt (~(got by walts) xpub) - =/ newb=batch - :* (sy (gulf +(endpoint.b) (add endpoint.b max-gap.w))) - (add endpoint.b max-gap.w) - %.n - == - :- (req-scan ~[req-pax] newb xpub chyg) - newb -:: +del-scanned: delete scanned idxs -:: -++ del-scanned - |= [b=batch =xpub =chyg to-delete=idx] - ^- ^scans - %+ ~(put by scans) [xpub chyg] - b(todo (~(del in todo.b) to-delete)) -:: delete the xpub from scans and set wallet to scanned -:: -++ end-scan - |= [=xpub] - ^- (quip card _state) - =/ w=walt (~(got by walts) xpub) - =. scans (~(del by scans) [xpub %0]) - =. scans (~(del by scans) [xpub %1]) - :- ~[[%give %fact ~[/updates] %btc-wallet-store-update !>([%scan-done xpub])]] - state(walts (~(put by walts) xpub w(scanned %.y))) -:: initiate a scan if one hasn't started -:: check status of scan if one is running -:: -++ run-scan - |= =xpub - ^- (quip card _state) - =/ s0 (scan-status xpub %0) - =/ s1 (scan-status xpub %1) - ?: ?&(empty.s0 done.s0 empty.s1 done.s1) - (end-scan xpub) - =/ [cards0=(list card) batch0=batch] - (bump-batch xpub %0) - =/ [cards1=(list card) batch1=batch] - (bump-batch xpub %1) - :- (weld cards0 cards1) - state(scans (insert-batches xpub batch0 batch1)) -:: +update-address: watch the address passed; -:: - update wallet with the address -:: - if address is unused, send %address-info request -:: - if address doesn't have enough confs, send %address-info request -:: - if this idx was the last in todo.scans, do run-scan to see whether scan is done -:: - updates wallet-store state to have last-block -:: -++ update-address - |= [=xpub:btc =chyg =idx utxos=(set utxo) used=? last-block=@ud] - |^ ^- (quip card _state) - =? state (gth last-block last-block.state) - state(last-block last-block) - =/ w=(unit walt) (~(get by walts) xpub) - ?~ w `state - =. walts - %+ ~(put by walts) xpub - %+ ~(update-address wad u.w chyg) - (~(mk-address wad u.w chyg) idx) - [used chyg idx utxos] - :: if the wallet is being scanned, update the scan batch - :: if not, just get more-info for the address if still being scanned - :: - =+ b=(~(get by scans) [xpub chyg]) - ?~ b [(more-info u.w) state] - =. scans - (del-scanned u.b(has-used ?|(used has-used.u.b)) xpub chyg idx) - ?: empty:(scan-status xpub chyg) - =^ cards state (run-scan xpub) - [(weld (more-info u.w) cards) state] - [(more-info u.w) state] - :: - ++ more-info - |= w=walt - ^- (list card) - ?: (is-done w) ~ - :~ - %+ send-req ~[req-pax] - :* %address-info last-block - (~(mk-address wad w chyg) idx) - xpub chyg idx - == - == - :: - ++ is-done - |= w=walt - ?& used - %+ levy (turn ~(tap in utxos) (cury num-confs last-block)) - |=(nc=@ud (gte nc confs:w)) - == - -- -:: -:: -if txid not in history but has one of our wallet addresses -:: - add it to history and request info on the addresses+tx -:: - if txid not "included" in blockchain AND was in history -:: - ("included" = in mempool or blockchain) -:: - delete from history -:: - send txinfo request again -:: - check whether this txid is in history -:: - if yes, update its confs and received -:: - request info on all its addresses -:: - request info on the tx again if not enough confs -:: -++ handle-tx-info - |= [ti=info:tx:btc block=@ud] - |^ - =. state state(last-block block) - =+ h=(~(get by history) txid.ti) - =/ rs=(list request) (address-reqs ti) - =/ cards=(list card) (turn rs to-card) - :: when addresses in wallets, but tx not in history - :: - ?~ h - ?~ rs `state - :- [(send-req ~[req-pax] [%tx-info block txid.ti]) cards] - state(history (~(put by history) txid.ti (mk-hest rs))) - ?. included.ti - :_ state(history (~(del by history) txid.ti)) - ~[(send-req ~[req-pax] [%tx-info block txid.ti])] - =+ w=(~(get by walts) xpub.u.h) - ?~ w `state - =. history - %+ ~(put by history) txid.ti - u.h(confs confs.ti, recvd recvd.ti) - :_ state - ?: (gte confs.ti confs.u.w) cards - [(send-req ~[req-pax] [%tx-info block txid.ti]) cards] - :: - ++ address-reqs - |= ti=info:tx:btc - ^- (list request) - =| rs=(list request) - =/ ws=(list walt) ~(val by walts) - |- ?~ ws rs - %= $ - ws t.ws - rs - %- zing - :~ rs - (murn inputs.ti (cury to-req i.ws)) - (murn outputs.ti (cury to-req i.ws)) - == - == - :: - ++ to-req - |= [w=walt v=val:tx:btc] - ^- (unit request) - =+ addi=(~(get by wach.w) address.v) - ?~ addi ~ - `[%address-info last-block address.v xpub.w chyg.u.addi idx.u.addi] - :: - ++ to-card - |= r=request ^- card - (send-req ~[req-pax] r) - :: - ++ mk-hest - |= rs=(lest request) - ^- hest - =/ as=(set address:btc) - %- sy - %+ turn rs - |=(r=request ?>(?=(%address-info -.r) a.r)) - :* ?>(?=(%address-info -.i.rs) xpub.i.rs) - txid.ti - confs.ti - recvd.ti - (turn inputs.ti |=(v=val:tx:btc (our-ship as v))) - (turn outputs.ti |=(v=val:tx:btc (our-ship as v))) - == - ++ our-ship - |= [as=(set address:btc) v=val:tx:btc] - ^- [=val:tx s=(unit ship)] - [v ?:((~(has in as) address.v) `our.bowl ~)] - -- -:: +generate-address: generate and return address -:: sends a request for info on the new address -:: -++ generate-address - |= [=xpub =chyg =pmet] - ^- (quip card _state) - =+ uw=(~(get by walts) xpub) - ?~ uw - ~|("btc-wallet-store: non-existent xpub" !!) - ?. scanned.u.uw - ~|("btc-wallet-store: wallet not scanned yet" !!) - =/ [addr=address:btc =idx w=walt] - ~(gen-address wad u.uw chyg) - :_ state(walts (~(put by walts) xpub w)) - :~ (send-update [%generate-address xpub addr pmet]) - %+ send-req ~[req-pax] - :* %address-info last-block - addr xpub chyg idx - == - == -:: -:: REMOVED -:: scanned-wallets -:: balance -++ send-req - |= [pax=(list path) req=request] ^- card -:: ~& >> "send-req: {}, {}" - :* %give %fact pax - %btc-wallet-store-request !>(req) - == -:: -++ send-update - |= upd=update ^- card - [%give %fact ~[/updates] %btc-wallet-store-update !>(upd)] --- diff --git a/app/btc-wallet-view.hoon b/app/btc-wallet-view.hoon deleted file mode 100644 index 598f464bf..000000000 --- a/app/btc-wallet-view.hoon +++ /dev/null @@ -1,65 +0,0 @@ -:: btc-wallet-view.hoon -:: receive signing requests from btc-wallet-hook -:: -/- *btc-wallet-view -/+ dbug, default-agent -|% -+$ versioned-state - $% state-0 - == -:: -+$ state-0 [%0 counter=@] -:: -+$ card card:agent:gall -:: --- -%- agent:dbug -=| state-0 -=* state - -^- agent:gall -|_ =bowl:gall -+* this . - def ~(. (default-agent this %|) bowl) -:: -++ on-init - ^- (quip card _this) - ~& > '%btc-wallet-view initialized successfully' - =/ filea [%file-server-action !>([%serve-dir /'~btc-wallet' /app/btc-wallet %.n %.y])] - :_ this - :~ [%pass /srv %agent [our.bowl %file-server] %poke filea] - [%pass /u/[(scot %da now.bowl)] %agent [our.bowl %btc-wallet-hook] %watch /sign-me] - == -++ on-save - ^- vase - !>(state) -++ on-load - |= old-state=vase - ^- (quip card _this) - ~& > '%btc-wallet-view recompiled successfully' - `this(state !<(versioned-state old-state)) -++ on-poke - |= [=mark =vase] - |^ ^- (quip card _this) - ?+ mark (on-poke:def mark vase) - %btc-wallet-view-action - (handle-action !<(action vase)) - == - ++ handle-action - |= =action - ~& >>> action - `this - -- -:: -++ on-watch - |= =path - ^- (quip card _this) - ?+ path (on-watch:def path) - [%primary ~] - `this - == -++ on-leave on-leave:def -++ on-peek on-peek:def -++ on-agent on-agent:def -++ on-arvo on-arvo:def -++ on-fail on-fail:def --- diff --git a/app/btc-wallet.hoon b/app/btc-wallet.hoon index f4fc4330f..0f232ff71 100644 --- a/app/btc-wallet.hoon +++ b/app/btc-wallet.hoon @@ -14,6 +14,7 @@ piym-limit=3 == ++ confs 6 + ++ fee 100 -- :: +$ versioned-state @@ -63,11 +64,11 @@ *^history ~ *^scans - defaults:params + params:defaults *(map ship sats) *^piym *^poym - *^pend + *^pend-piym == == ++ on-save @@ -87,7 +88,7 @@ %btc-wallet-action (handle-action:hc !<(action vase)) %btc-wallet-command - (handle-command:hc !<(action vase)) + (handle-command:hc !<(command vase)) == [cards this] ++ on-peek @@ -150,6 +151,8 @@ `state(curr-xpub `xpub.comm) :: %add-wallet + ?~ (~(has by walts) xpub.comm) + ((slog ~[leaf+"xpub already in wallet"]) `state) =/ w=walt (from-xpub +.comm) =. walts (~(put by walts) xpub.comm w) (init-batches xpub.comm (dec max-gap.w)) @@ -182,7 +185,7 @@ ~[(poke-provider [%broadcast-tx signed])] ?. tx-match state ?~ poym state - state(sitx.u.poym `signed) + state(signed-tx.u.poym `signed) == :: ++ handle-action @@ -200,8 +203,9 @@ =+ vb=~(vbytes txb u.poym) =+ fee=~(fee txb u.poym) ~& >> "{} vbytes, {<(div fee vb)>} sats/byte, {} sats fee" - %- (slog [%leaf "PSBT: {}"]~) + %- (slog [%leaf "PSBT: {}"]~) ~ + :: delete an incoming/outgoing payment when we see it included in a tx :: %close-pym ?> =(src.bowl our.bowl) @@ -236,9 +240,9 @@ txid.act value:(snag 0 txos.u.poym) == - :: can't pay yourself; comets can't pay (could spam requests) - :: must have default wallet set - :: reuses payment address for ship if exists in piym + :: can't pay yourself; comets can't pay (could spam address requests) + :: must have curr-wallet set + :: reuses payment address for ship if ship in piym already :: %gen-pay-address ~| "Can't pay ourselves; no comets" @@ -253,22 +257,53 @@ ?: (gte n fam-limit.params) ~|("More than {} addresses for moons + planet" !!) =. state state(num-fam.piym (~(put by num-fam.piym) f +(n))) - =^ addr state - (generate-address u.curr-xpub %0 `[src.bowl value.act]) - :- ~[(poke-us payer.act [%recv-pay-address addr value.act])] - state(ps.piym (~(put by ps.piym) src.bowl [~ u.curr-xpub addr src.bowl value.act])) + |^ + =^ a=address state + (generate-address u.curr-xpub %0) + :- ~[(poke-us src.bowl [%recv-pay-address a value.act])] + state(ps.piym (~(put by ps.piym) src.bowl [~ u.curr-xpub a src.bowl value.act])) + :: + ++ generate-address + |= [=xpub =chyg] + =/ uw=(unit walt) (~(get by walts) xpub) + ?: ?|(?=(~ uw) ?!(scanned.u.uw)) + ~|("no wallet with xpub or wallet not scanned yet" !!) + =/ [addr=address =idx w=walt] + ~(gen-address wad u.uw chyg) + [addr state(walts (~(put by walts) xpub w))] + -- :: %recv-pay-address ?: =(src.bowl our.bowl) ~|("Can't pay ourselves" !!) ?: is-broadcasting ~|("Broadcasting a transaction" !!) ?~ curr-xpub ~|("btc-wallet-hook: no curr-xpub set" !!) - =+ feyb=(~(gut by feybs) src.bowl ?~(fee.btc-state 100 u.fee.btc-state)) + =+ feyb=(~(gut by feybs) src.bowl ?~(fee.btc-state fee:defaults u.fee.btc-state)) + |^ =^ tb=(unit txbu) state - (generate-txbu u.curr-xpub `src.bowl feyb tx ~[[address.act value.act ~]]) - :_ state(poym `tb) + (generate-txbu u.curr-xpub `src.bowl feyb ~[[address.act value.act ~]]) + :_ state(poym tb) ?~ tb ~ %+ turn txis.u.tb - |=(=txi (poke-provider txid.utxo.txi)) + |=(=txi (poke-provider [%tx-info txid.utxo.txi])) + :: + ++ generate-txbu + |= [=xpub payee=(unit ship) feyb=sats txos=(list txo)] + ^- [(unit txbu) _state] + =/ uw (~(get by walts) xpub) + ?: ?|(?=(~ uw) ?!(scanned.u.uw)) + ~|("no wallet with xpub or wallet not scanned yet" !!) + =/ [tb=(unit txbu) chng=(unit sats)] + %~ with-change sut + [u.uw eny.bowl block.btc-state payee feyb txos] + ?~ tb ((slog leaf+"insufficient balance") [tb state]) + :: if no change, return txbu; else add change output to txbu + :: + ?~ chng [tb state] + =/ [addr=address:btc =idx w=walt] + ~(nixt-address wad u.uw %1) + :- `(~(add-output txb u.tb) addr u.chng `(~(hdkey wad w %1) idx)) + state(walts (~(put by walts) xpub w)) + -- :: :: %expect-payment :: - check that payment is in piym @@ -330,7 +365,7 @@ btc-state [block fee now.bowl] == ?: ?|(?!(connected.p) (lth block.btc-state block)) - ;:(weld retry-pend-piym retry-addrs retry-txs) + ;:(weld retry-pend-piym retry-addrs retry-txs retry-scans) retry-pend-piym -- :: @@ -405,28 +440,37 @@ |= addrs=(list address) ^- (unit walt) |- ?~ addrs ~ - =/ am (address-meta i.addrs ~(val by walts)) - ?^ am `w.u.am + =/ ac (address-coords i.addrs ~(val by walts)) + ?^ ac `w.u.ac $(addrs t.addrs) -- :: +:: :: Scan Logic :: +:: Algorithm +:: Initiate a batch for each chyg, with max-gap idxs in it +:: Watch all of the addresses made from idxs +:: Request info on all addresses from provider +:: When an %address-info comes back: +:: - remove that idx from todo.batch +:: - run check-scan to check whether that chyg is done +:: - if it isn't, refill it with max-gap idxs to scan +:: :: +handle-address-info: updates scans and wallet with address info :: ++ handle-address-info |= [=address utxos=(set utxo) used=?] ^- (quip card _state) - =/ am (address-meta address ~(val by walts)) - ?~ am `state - =/ [w=walt =chyg =idx] u.am + =/ ac (address-coords address ~(val by walts)) + ?~ ac `state + =/ [w=walt =chyg =idx] u.ac =. walts %+ ~(put by walts) xpub.w %+ ~(update-address wad w chyg) address [used chyg idx utxos] :: if the wallet+chyg is being scanned, update the scan batch - :: if not, just get more-info for the address if still being scanned :: =/ b (~(get by scans) [xpub.w chyg]) ?~ b `state @@ -435,15 +479,27 @@ ?: empty:(scan-status xpub.w chyg) (check-scan xpub.w) `state +:: +req-scan +:: - adds addresses in batch to wallet's watch map as un-used addresses +:: - returns provider %address-info request cards :: ++ req-scan |= [b=batch =xpub =chyg] - ^- (list card) + ^- (quip card _state) =/ w=walt (~(got by walts) xpub) - %+ turn ~(tap in todo.b) - |= =idx - %- poke-provider - [%address-info (~(mk-address wad w chyg) idx)] + =/ as=(list [address [? ^chyg idx (set utxo)]]) + %+ turn ~(tap in todo.b) + |=(=idx [(~(mk-address wad w chyg) idx) [%.n chyg idx *(set utxo)]]) + =. w + |- ?~ as w + $(as t.as, w (~(update-address wad w chyg) -.i.as +.i.as)) + :- (turn as |=([a=address *] (poke-provider [%address-info a]))) + %= state + scans + (~(put by scans) [xpub chyg] b) + walts + (~(put by walts) xpub w) + == :: ++ scan-status |= [=xpub =chyg] @@ -453,40 +509,32 @@ :- empty ?&(empty ?!(has-used.b)) :: -++ insert-batches - |= [=xpub b0=batch b1=batch] - ^- ^scans - =. scans (~(put by scans) [xpub %0] b0) - (~(put by scans) [xpub %1] b1) -:: ++ init-batches |= [=xpub endpoint=idx] ^- (quip card _state) =/ b=batch [(sy (gulf 0 endpoint)) endpoint %.n] - :- %+ weld - (req-scan b xpub %0) - (req-scan b xpub %1) - state(scans (insert-batches xpub b b)) + =^ cards0 state (req-scan b xpub %0) + =^ cards1 state (req-scan b xpub %1) + [(weld cards0 cards1) state] :: +bump-batch :: if the batch is done but the wallet isn't done scanning, :: returns new address requests and updated batch :: ++ bump-batch |= [=xpub =chyg] - ^- (quip card batch) + ^- (quip card _state) =/ b=batch (~(got by scans) xpub chyg) =/ s (scan-status xpub chyg) ?. ?&(empty.s ?!(done.s)) - `b + `state =/ w=walt (~(got by walts) xpub) =/ newb=batch :* (sy (gulf +(endpoint.b) (add endpoint.b max-gap.w))) (add endpoint.b max-gap.w) %.n == - :- (req-scan newb xpub chyg) - newb + (req-scan newb xpub chyg) :: +del-scanned: delete scanned idxs :: ++ del-scanned @@ -502,7 +550,7 @@ =/ w=walt (~(got by walts) xpub) =. scans (~(del by scans) [xpub %0]) =. scans (~(del by scans) [xpub %1]) - %- (slog leaf+"Scanned xpub {}") + %- (slog ~[leaf+"Scanned xpub {}"]) `state(walts (~(put by walts) xpub w(scanned %.y))) :: +check-scan: initiate a scan if one hasn't started :: check status of scan if one is running @@ -514,52 +562,19 @@ =/ s1 (scan-status xpub %1) ?: ?&(empty.s0 done.s0 empty.s1 done.s1) (end-scan xpub) - =/ [cards0=(list card) batch0=batch] + =^ cards0=(list card) state (bump-batch xpub %0) - =/ [cards1=(list card) batch1=batch] + =^ cards1=(list card) state (bump-batch xpub %1) - :- (weld cards0 cards1) - state(scans (insert-batches xpub batch0 batch1)) + [(weld cards0 cards1) state] :: -:: TX and Address Generation -:: -++ generate-txbu - |= [=xpub payee=(unit ship) feyb=sats txos=(list txo)] - ^- [(unit txbu) _state] - =/ uw (~(get by walts) xpub) - ?~ uw - ~|("btc-wallet: non-existent xpub" !!) - ?. scanned.u.uw - ~|("btc-wallet: wallet not scanned yet" !!) - =/ [tb=(unit txbu) chng=(unit sats)] - %~ with-change sut - [u.uw eny.bowl block.btc-state payee feyb txos] - ?~ tb ~&(>>> "btc-wallet: insufficient balance" `state) - :: if no change, return txbu; else add change to txbu - :: - ?~ chng [tb state] - =/ [addr=address:btc =idx w=walt] - ~(nixt-address wad u.uw %1) - :- `(~(add-output txb u.tb) addr u.chng `(~(hdkey wad w %1) idx)) - state(walts (~(put by walts) xpub w)) -:: -++ generate-address - |= [=xpub =chyg =pmet] - ^- [address _state] - =/ uw=(unit walt) (~(get by walts) xpub) - ?~ uw - ~|("btc-wallet: non-existent xpub" !!) - ?. scanned.u.uw - ~|("btc-wallet: wallet not scanned yet" !!) - =/ [addr=address:btc =idx w=walt] - ~(gen-address wad u.uw chyg) - [addr state(walts (~(put by walts) xpub w))] :: +:: piym/poym :: Utilities for Incoming/Outgoing Payments :: +:: :: +reuse-address -:: - if piym already has address for payer, -:: replace address and return to payer +:: - if piym already has address for payer, replace address and return to payer :: - if payment is pending, crash. Shouldn't be getting an address request :: ++ reuse-address @@ -570,16 +585,14 @@ ?^ pend.u.p ~|("%gen-address: {} already has pending payment to us" !!) =+ newp=u.p(value value) :_ state(ps.piym (~(put by ps.piym) payer newp)) - :~ %+ poke-hook payer - [%ret-pay-address address.newp payer value] - == + ~[(poke-us payer [%recv-pay-address address.newp value])] :: ++ poym-has-txid |= txid=hexb ^- ? ?~ poym %.n - ?~ sitx.u.poym %.n - =(txid (get-id:txu (decode:txu u.sitx.u.poym))) + ?~ signed-tx.u.poym %.n + =(txid (get-id:txu (decode:txu u.signed-tx.u.poym))) :: +poym-to-history: :: - checks whether poym has a signed tx :: - checks whether the txid matches that signed tx @@ -592,7 +605,7 @@ ^- (quip card _state) |^ ?~ poym `state - ?~ sitx.u.poym `state + ?~ signed-tx.u.poym `state ?. (poym-has-txid txid.ti) `state =+ vout=(get-vout txos.u.poym) @@ -711,6 +724,13 @@ :: :: Card Builders and Pokers :: +:: +++ retry-scans + ^- (list card) + %- zing + %+ turn ~(tap by scans) + |= [[=xpub =chyg] =batch] + -:(req-scan batch xpub chyg) :: +retry-addrs: get info on addresses with unconfirmed UTXOs :: ++ retry-addrs @@ -721,7 +741,7 @@ ^- (list card) %+ murn ~(tap by wach.w) |= [a=address ad=addi] - ?: %+ levy utxos.ad + ?: %+ levy ~(tap in utxos.ad) |=(u=utxo (gth height.u (sub block.btc-state confs.w))) ~ `(poke-provider [%address-info a]) @@ -740,8 +760,8 @@ ^- (list card) ?~ poym ~ %+ weld - ?~ sitx.u.poym ~ - ~[(poke-provider [%broadcast-tx u.sitx.u.poym])] + ?~ signed-tx.u.poym ~ + ~[(poke-provider [%broadcast-tx u.signed-tx.u.poym])] %+ turn txis.u.poym |= =txi (poke-provider [%raw-tx ~(get-txid txb u.poym)]) @@ -772,7 +792,7 @@ ++ is-broadcasting ^- ? ?~ poym %.n - ?=(^ sitx.u.poym) + ?=(^ signed-tx.u.poym) :: :: Scry Helpers :: diff --git a/gen/btc-wallet/command.hoon b/gen/btc-wallet/command.hoon index 4ad648c2a..7f5e0c185 100644 --- a/gen/btc-wallet/command.hoon +++ b/gen/btc-wallet/command.hoon @@ -1,6 +1,6 @@ :: Sends a command to btc-wallet :: -/- *btc-wallet-hook +/- *btc-wallet :: :- %say |= $: [now=@da eny=@uvJ =beak] diff --git a/lib/btc-wallet.hoon b/lib/btc-wallet.hoon index af508ca0e..bcf5294f8 100644 --- a/lib/btc-wallet.hoon +++ b/lib/btc-wallet.hoon @@ -37,9 +37,9 @@ (fall max-gap max-gap:defaults) (fall confs confs:defaults) == -:: +address-meta: find wallet info for the address, if any +:: +address-coords: find wallet info for the address, if any :: -++ address-meta +++ address-coords |= [a=address ws=(list walt)] ^- (unit [w=walt =chyg =idx]) |^ diff --git a/mar/btc-wallet/hook-action.hoon b/mar/btc-wallet/hook-action.hoon deleted file mode 100644 index 69eaeb6c0..000000000 --- a/mar/btc-wallet/hook-action.hoon +++ /dev/null @@ -1,12 +0,0 @@ -/- *btc-wallet-hook -|_ act=action -++ grad %noun -++ grow - |% - ++ noun act - -- -++ grab - |% - ++ noun action - -- --- diff --git a/sur/btc-wallet-hook.hoon b/sur/btc-wallet-hook.hoon deleted file mode 100644 index 0c6b8d82e..000000000 --- a/sur/btc-wallet-hook.hoon +++ /dev/null @@ -1,78 +0,0 @@ -/- *btc, bws=btc-wallet-store, bp=btc-provider -|% -:: btc-state: state from the provider; t is last update time -:: reqs: last block checked for an address/tx request to provider. -:: Used to determine whether to retry request -:: -:: payment: a payment expected from another ship -:: - address: address generated for this payment -:: piym: incoming payments. Stores all ship moons under their planet. -:: - num-fam: total payments (addresses) outstanding for ship and its moons -:: pend-piym: incoming payment txs that peer says they have broadcast -:: poym: outgoing payments. One at a time: new replaces old -:: -+$ txid hexb -+$ provider [host=ship connected=?] -+$ block @ud -+$ btc-state [=block fee=(unit sats) t=@da] -+$ reqs (map $?(address txid) request:bws) -:: -+$ payment [pend=(unit txid) =xpub =address payer=ship value=sats] -+$ piym [ps=(map ship payment) num-fam=(map ship @ud)] -+$ pend-piym (map txid payment) -+$ poym (unit txbu:bws) -:: -:: req-pay-address: request a payment address from another ship -:: - target of action is local ship -:: broadcast-tx: broadcast a signed-psbt, must be current poym -:: -+$ command - $% [%set-provider provider=ship =network] - [%set-default-wallet =xpub] - [%delete-wallet =xpub] - [%clear-poym ~] - [%force-retry ~] - [%req-pay-address payee=ship value=sats feyb=sats] - [%broadcast-tx txhex=cord] - == -:: -:: gen-pay-address: generate a payment address from our ship to another -:: ret-pay-address: give an address to a payer who requested it -:: expect-payment: tell another ship that we're paying a previously requested address -:: - vout-n is the index of the output that has value -:: - -:: local and peer pokes are initiated by the agent itself -:: they exist to make the state machine explicit -:: they are not part of the API -:: -+$ local - $% [%add-piym =xpub =address payer=ship value=sats] - [%add-poym =txbu:bws] - [%add-poym-txi txid=hexb rawtx=hexb] - [%close-pym ti=info:tx] - [%fail-broadcast-tx txid=hexb] - [%succeed-broadcast-tx txid=hexb] - == -:: -+$ peer - $% [%gen-pay-address value=sats] - [%ret-pay-address =address payer=ship value=sats] - [%expect-payment txid=hexb value=sats] - == -:: -:: -+$ update - $% request - error - == -:: -+$ request - $% [%sign-tx txbu:bws] - == -:: -+$ error - $% [%broadcast-tx-mismatch-poym signed=hexb] - [%broadcast-tx-spent-utxos txid=hexb] - == --- diff --git a/sur/btc-wallet-store.hoon b/sur/btc-wallet-store.hoon deleted file mode 100644 index f4c94a77c..000000000 --- a/sur/btc-wallet-store.hoon +++ /dev/null @@ -1,113 +0,0 @@ -:: wallets are compatible with BIPs 44, 49, and 84 -:: m / purpose' / coin_type' / account' / change / address_index -:: change can be 0 or 1 -:: -/- *btc, bp=btc-provider -/+ bip32 -|% -+$ txid hexb -++ max-index (dec (pow 2 32)) -:: idx: an address_index -:: nixt: next indices to generate addresses from (non-change/change) -:: addi: HD path along with UTXOs -:: - used: whether the address has been used -:: wach: map for watched addresses. -:: Membership implies the address is known by outside parties or had prior activity -:: scon: indices to initially scan to in (non-)change accounts -:: defaults to 2^32-1 (i.e. all the addresses, ~4B) -:: wilt: stores xpub; copulates with thousands of indices to form addresses -:: -+$ nixt (pair idx idx) -+$ addi [used=? =chyg =idx utxos=(set utxo)] -+$ wach (map address addi) -+$ scon $~([max-index max-index] (pair idx idx)) -+$ wilt _bip32 -:: walt: wallet datastructure -:: scanned: whether the wallet's addresses have been checked for prior activity -:: scan-to -:: max-gap: maximum number of consec blank addresses before wallet stops scanning -:: confs: confirmations required (after this is hit for an address, wallet stops refreshing it) -:: -+$ walt - $: =xpub - =network - =fprint - =wilt - =bipt - =wach - =nixt - scanned=? - scan-to=scon - max-gap=@ud - confs=@ud - == -:: insel: a selected utxo for input to a transaction -:: pmet: optional payment metadata -:: feyb: fee per byte in sats -:: txi/txo: input/output for a transaction being built -:: - txo has an hdkey if it's a change account -:: - by convention, first output of txo is to the payee, if one is present -:: txbu: tx builder -- all information needed to make a transaction for signing -:: - sitx: signed hex transaction -:: -+$ insel [=utxo =chyg =idx] -+$ pmet (unit [payer=ship value=sats]) -+$ feyb sats -+$ txi [=utxo ur=(unit hexb) =hdkey] -+$ txo [=address value=sats hk=(unit hdkey)] -+$ txbu - $: =xpub - payee=(unit ship) - =vbytes - txis=(list txi) - txos=(list txo) - sitx=(unit hexb) - == -:: hest: an entry in the history log -:: -+$ hest - $: =xpub - txid=hexb - confs=@ud - recvd=(unit @da) - inputs=(list [=val:tx s=(unit ship)]) - outputs=(list [=val:tx s=(unit ship)]) - == -+$ history (map hexb hest) -:: state/watch variables: -:: batch: indexes to scan for a given chyg -:: scans: all scans underway (batches) -:: -+$ batch [todo=(set idx) endpoint=idx has-used=?] -+$ scans (map [xpub chyg] batch) -:: -:: %add-wallet: add wallet to state and initiate a scan -:: %address-info: give new data about an address. -:: - used: address has been seen on the BTC blockchain? -:: - block: the most recent block at the time of this information being retrieved -:: -+$ action - $% [%add-wallet =xpub =fprint scan-to=(unit scon) max-gap=(unit @ud) confs=(unit @ud)] - [%delete-wallet =xpub] - :: TODO: can eliminate basically everything below here - [%address-info =xpub =chyg =idx utxos=(set utxo) used=? block=@ud] - [%tx-info =info:tx block=@ud] - [%generate-address =xpub =chyg =pmet] - [%generate-txbu =xpub payee=(unit ship) feyb=sats txos=(list txo)] - [%add-history-entry =hest] - [%del-history-entry txid=hexb] - == -:: -+$ update - $% [%generate-address =xpub =address =pmet] - [%generate-txbu =xpub =txbu] - [%saw-piym s=ship txid=hexb] - [%scan-done =xpub] - == -:: last-block: most recent block this address was checked -:: -+$ request - $% [%address-info last-block=@ud a=address =xpub =chyg =idx] - [%tx-info last-block=@ud txid=hexb] - == --- diff --git a/sur/btc-wallet-view.hoon b/sur/btc-wallet-view.hoon deleted file mode 100644 index 2b1a3996b..000000000 --- a/sur/btc-wallet-view.hoon +++ /dev/null @@ -1,5 +0,0 @@ -|% -+$ action - $% [%blank ~] - == --- diff --git a/sur/btc-wallet.hoon b/sur/btc-wallet.hoon index 952a62b43..2383e9c3e 100644 --- a/sur/btc-wallet.hoon +++ b/sur/btc-wallet.hoon @@ -82,10 +82,8 @@ :: - txo has an hdkey if it's a change account :: - by convention, first output of txo is to the payee, if one is present :: txbu: tx builder -- all information needed to make a transaction for signing -:: - sitx: signed hex transaction :: +$ insel [=utxo =chyg =idx] -+$ pmet (unit [payer=ship value=sats]) +$ feyb sats +$ txi [=utxo ur=(unit hexb) =hdkey] +$ txo [=address value=sats hk=(unit hdkey)] @@ -95,7 +93,7 @@ =vbytes txis=(list txi) txos=(list txo) - sitx=(unit hexb) + signed-tx=(unit hexb) == :: hest: an entry in the history log :: diff --git a/tests/lib/btc-wallet-store.hoon b/tests/lib/btc-wallet.hoon similarity index 84% rename from tests/lib/btc-wallet-store.hoon rename to tests/lib/btc-wallet.hoon index c8a7486ec..31013de67 100644 --- a/tests/lib/btc-wallet-store.hoon +++ b/tests/lib/btc-wallet.hoon @@ -1,5 +1,11 @@ -/+ *test, *btc-wallet-store, btc +/+ *test, *btc-wallet, btc |% ++$ wallet-vector + $: =xpub:btc + =chyg + =idx:btc + =address:btc + == +$ vector $: =xpub:btc eny=@uv @@ -21,6 +27,15 @@ :: ++ fprint 4^0xdead.beef :: +++ wallet-vectors + ^- (list wallet-vector) + :~ :* 'zpub6rFR7y4Q2AijBEqTUquhVz398htDFrtymD9xYYfG1m4wAcvPhXNfE3EfH1r1ADqtfSdVCToUG868RvUUkgDKf31mGDtKsAYz2oz2AGutZYs' + %0 + 0 + [%bech32 'bc1qcr8te4kr609gcawutmrza0j4xv80jy8z306fyu'] + == + == +:: ++ vectors =| w=walt ^- (list vector) @@ -105,6 +120,8 @@ ++ test-all-vectors ^- tang |^ ;: weld + %+ category "address generation/lookup" + (zing (turn wallet-vectors address-gen-lookup)) %+ category "single-random-draw" (zing (turn vectors check-single-random-draw)) :: @@ -115,6 +132,21 @@ (zing (turn dust-output-vectors check-dust-output)) == :: + ++ address-gen-lookup + |= v=wallet-vector + =/ w=walt (from-xpub xpub.v fprint ~ ~ ~) + =/ =address (~(mk-address wad w chyg.v) idx.v) + =. w (~(update-address wad w chyg.v) address [%.n %0 0 *(set utxo:btc)]) + =/ [w2=walt c=chyg i=idx] (need (address-coords address ~[w])) + ;: weld + %+ expect-eq + !>(address) + !>(address.v) + %+ expect-eq + !>([w2 c i]) + !>([w chyg.v idx.v]) + == + :: ++ check-single-random-draw |= v=vector =/ w=walt (from-xpub xpub.v fprint ~ ~ ~)