diff --git a/.github/actions/glob/Dockerfile b/.github/actions/glob/Dockerfile index 4071c47429..28fc457106 100644 --- a/.github/actions/glob/Dockerfile +++ b/.github/actions/glob/Dockerfile @@ -1,4 +1,4 @@ -FROM jaredtobin/janeway:v0.13.4 +FROM jaredtobin/janeway:v0.15.2 COPY entrypoint.sh /entrypoint.sh EXPOSE 22/tcp ENTRYPOINT ["/entrypoint.sh"] diff --git a/.github/actions/glob/entrypoint.sh b/.github/actions/glob/entrypoint.sh index cd9ecea225..7428213a5b 100755 --- a/.github/actions/glob/entrypoint.sh +++ b/.github/actions/glob/entrypoint.sh @@ -10,10 +10,10 @@ chmod 600 service-account chmod 600 id_ssh chmod 600 id_ssh.pub -janeway release glob --dev --no-pill \ +janeway release glob-all --dev --no-pill \ --credentials service-account \ --ssh-key id_ssh \ - --do-it-live \ + --ci \ | bash SHORTHASH=$(git rev-parse --short HEAD) @@ -21,12 +21,12 @@ SHORTHASH=$(git rev-parse --short HEAD) janeway release prepare-ota arvo-glob-"$SHORTHASH" "$1" \ --credentials service-account \ --ssh-key id_ssh \ - --do-it-live \ + --ci \ | bash janeway release perform-ota "$1" \ --credentials service-account \ --ssh-key id_ssh \ - --do-it-live \ + --ci \ | bash diff --git a/.github/workflows/chromatic.yml b/.github/workflows/chromatic.yml new file mode 100644 index 0000000000..92a8774e0f --- /dev/null +++ b/.github/workflows/chromatic.yml @@ -0,0 +1,27 @@ +name: Chromatic Deployment + +on: + pull_request: + paths: + - 'pkg/interface/**' + push: + paths: + - 'pkg/interface/**' + branches: + - 'release/next-userspace' + +jobs: + chromatic-deployment: + runs-on: ubuntu-latest + name: "Deploy Chromatic" + steps: + - uses: actions/checkout@v2 + with: + fetch-depth: 0 + - run: cd 'pkg/interface' && npm i + - name: Publish to Chromatic + uses: chromaui/action@v1 + with: + token: ${{ secrets.GITHUB_TOKEN }} + projectToken: ${{ secrets.CHROMATIC_PROJECT_TOKEN }} + workingDir: pkg/interface diff --git a/.github/workflows/glob.yml b/.github/workflows/glob.yml index 3aabb75c9f..ba67d98cb8 100644 --- a/.github/workflows/glob.yml +++ b/.github/workflows/glob.yml @@ -2,7 +2,7 @@ name: glob on: push: branches: - - 'release/next-js' + - 'release/next-userspace' jobs: glob: runs-on: ubuntu-latest diff --git a/.github/workflows/merge-master.yml b/.github/workflows/merge-master.yml index 627d9e802d..48d0abf9fb 100644 --- a/.github/workflows/merge-master.yml +++ b/.github/workflows/merge-master.yml @@ -6,13 +6,13 @@ on: jobs: merge-to-next-js: runs-on: ubuntu-latest - name: "Merge master to release/next-js" + name: "Merge master to release/next-userspace" steps: - uses: actions/checkout@v2 - uses: devmasx/merge-branch@v1.3.1 with: type: now - target_branch: release/next-js + target_branch: release/next-userspace github_token: ${{ secrets.JANEWAY_BOT_TOKEN }} merge-to-group-timer: diff --git a/.github/workflows/typescript-check.yml b/.github/workflows/typescript-check.yml new file mode 100644 index 0000000000..6ad913bba4 --- /dev/null +++ b/.github/workflows/typescript-check.yml @@ -0,0 +1,14 @@ +name: typescript-check + +on: + pull_request: + paths: + - 'pkg/interface/**' + +jobs: + typescript-check: + runs-on: ubuntu-latest + name: "Check pkg/interface types" + steps: + - uses: actions/checkout@v2 + - run: cd 'pkg/interface' && npm i && npm run tsc diff --git a/.gitignore b/.gitignore index e79759dc67..14e61e6a84 100644 --- a/.gitignore +++ b/.gitignore @@ -76,4 +76,4 @@ pkg/interface/link-webext/web-ext-artifacts *.xz # Logs -*.log \ No newline at end of file +*.log diff --git a/MAINTAINERS.md b/MAINTAINERS.md index 78b2eda4a9..eaca1e7bda 100644 --- a/MAINTAINERS.md +++ b/MAINTAINERS.md @@ -309,9 +309,9 @@ the new binary, and restarting the pier with it. #### Continuous deployment A subset of release branches are deployed continuously to the network. Thus far -this only includes `release/next-js`, which deploys livenet-compatible -JavaScript changes to select QA ships. Any push to master will automatically -merge master into `release/next-js` to keep the streams at parity. +this only includes `release/next-userspace`, which deploys livenet-compatible +changes to select QA ships. Any push to master will automatically +merge master into `release/next-userspace` to keep the streams at parity. ### Announce the update diff --git a/bin/ivory.pill b/bin/ivory.pill index dca958f7f4..9f9071cf92 100644 --- a/bin/ivory.pill +++ b/bin/ivory.pill @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:5758d6cd7f5a36b9f45e988bf032951e40711541d9edbf9d2d85efba1e959257 -size 4080881 +oid sha256:063cb7928607fd3e3882e46a369047e3304e1635ee7761e2daa1fe611eb74ca7 +size 7130416 diff --git a/bin/solid.pill b/bin/solid.pill index c6b1ca9148..afdacd3ef5 100644 --- a/bin/solid.pill +++ b/bin/solid.pill @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:04e24541db4fad200778dc4ea67e2658844d5460f244cb62779332a0079a4e32 -size 9654865 +oid sha256:6d654c8c49f9836102b1db7dec7e625d5e8100ab7db4baa31b4184751c73c009 +size 15337032 diff --git a/pkg/arvo/app/acme.hoon b/pkg/arvo/app/acme.hoon index bbb53b8eb4..7d64a69f3f 100644 --- a/pkg/arvo/app/acme.hoon +++ b/pkg/arvo/app/acme.hoon @@ -1394,8 +1394,6 @@ ^+ this ?: =(~ dom) ~|(%acme-empty-certificate-order !!) - ?: ?=(?(%earl %pawn) (clan:title our.bow)) - this =. ..emit (queue-next-order 1 | dom) =. ..emit cancel-current-order :: notify %dill diff --git a/pkg/arvo/app/aggregator-rpc.hoon b/pkg/arvo/app/aggregator-rpc.hoon new file mode 100644 index 0000000000..777c0701da --- /dev/null +++ b/pkg/arvo/app/aggregator-rpc.hoon @@ -0,0 +1,262 @@ +:: Aggregator JSON-RPC API +:: +/- rpc=json-rpc, *dice +/+ naive, + azimuth-rpc, + json-rpc, + *server, + default-agent, + verb, + dbug, + version, + agentio +|% +:: ++$ card card:agent:gall +:: ++$ state-0 [%0 ~] +-- +:: +%+ verb | +%- agent:dbug +:: +=| state-0 +=* state - +:: +^- agent:gall +=< + |_ =bowl:gall + +* this . + do ~(. +> bowl) + def ~(. (default-agent this %|) bowl) + :: + ++ on-init + ^- (quip card _this) + ~& > 'init' + :_ this + [%pass /bind %arvo %e %connect [~ /v1/roller] dap.bowl]~ + :: + ++ on-save !>(state) + ++ on-load + |= old=vase + ^- (quip card _this) + [~ this(state !<(state-0 old))] + :: + ++ on-poke + |= [=mark =vase] + ^- (quip card _this) + |^ + ?> (team:title our.bowl src.bowl) + ?+ mark (on-poke:def mark vase) + %handle-http-request + =+ !<([id=@ta req=inbound-request:eyre] vase) + :_ this + (handle-http-request id req) + :: + %azimuth-action + =+ !<([%disconnect bind=binding:eyre] vase) + ~& >>> "disconnecting at {}" + :_ this + [%pass /bind %arvo %e %disconnect bind]~ + == + :: + ++ handle-http-request + |= [id=@ta =inbound-request:eyre] + ^- (list card) + |^ + =* req request.inbound-request + =* headers header-list.req + =/ req-line (parse-request-line url.req) + ?. =(method.req %'POST') + :: TODO: method not supported + :: + (give-simple-payload:app id not-found:gen) + ?~ rpc-request=(validate-request:json-rpc body.req) + :: TODO: malformed request + :: + (give-simple-payload:app id not-found:gen) + =/ [data=(list cage) response=simple-payload:http] + (process-rpc-request:do u.rpc-request) + %+ weld + (give-simple-payload:app id response) + |- + ?~ data ~ + :_ $(data t.data) + ^- card + [%pass / %agent [our.bowl %aggregator] %poke i.data] + -- + -- + :: + ++ on-watch + |= =path + ^- (quip card _this) + ?> (team:title our.bowl src.bowl) + ?+ path (on-watch:def path) + [%http-response *] [~ this] + == + :: + ++ on-arvo + |= [=wire =sign-arvo] + ^- (quip card _this) + ?+ sign-arvo (on-arvo:def wire sign-arvo) + [%eyre %bound *] + ~? !accepted.sign-arvo + [dap.bowl 'bind rejected!' binding.sign-arvo] + [~ this] + == + :: + ++ on-leave on-leave:def + ++ on-peek on-peek:def + ++ on-agent on-agent:def + ++ on-fail on-fail:def + -- +:: +|_ =bowl:gall +++ process-rpc-request + |= req=batch-request:rpc + ^- [(list cage) simple-payload:http] + |^ + ?- -.req + %o + =/ [data=(unit cage) =response:rpc] + (process p.req) + [(drop data) (render response)] + :: + %a + =| data=(list cage) + =| resp=(list response:rpc) + |- + ?~ p.req + [(flop data) (render %batch (flop resp))] + =/ [dat=(unit cage) res=response:rpc] + (process i.p.req) + =? data ?=(^ dat) [u.dat data] + $(p.req t.p.req, resp [res resp]) + == + :: + ++ render + |= res=response:rpc + %- json-response:gen + (response-to-json:json-rpc res) + :: + ++ process + |= request:rpc + =, azimuth-rpc + ?. ?=([%map *] params) + [~ ~(parse error:json-rpc id)] + =/ method=@tas (enkebab method) + ?: ?=(l2-tx method) + (process-rpc id +.params method) + ?+ method [~ ~(method error:json-rpc id)] + %get-point `(get-point id +.params point:scry) + %get-ships `(get-ships id +.params points:scry) + %cancel-transaction (cancel-tx id +.params) + %get-spawned `(get-spawned id +.params spawned:scry) + %get-all-pending `(all:pending id +.params all:pending:scry) + %get-pending-by-ship `(ship:pending id +.params ship:pending:scry) + %get-pending-by-address `(addr:pending id +.params addr:pending:scry) + %get-transaction-status `(status id +.params tx-status:scry) + %when-next-batch `(next-batch id +.params next-batch:scry) + %get-nonce `(nonce id +.params nonce:scry) + %get-history `(history id +.params addr:history:scry) + %get-roller-config `(get-config id +.params config:scry) + %hash-transaction `(hash-transaction id +.params chain-id:scry) + == + -- +:: +++ scry + |% + ++ point + |= =ship + .^ (unit point:naive) + %gx + (~(scry agentio bowl) %aggregator /point/(scot %p ship)/noun) + == + :: + ++ points + |= =address:naive + .^ (list ship) + %gx + (~(scry agentio bowl) %aggregator /points/(scot %ux address)/noun) + == + :: + ++ spawned + |= =ship + .^ (list [@p @ux]) + %gx + (~(scry agentio bowl) %aggregator /spawned/(scot %p ship)/noun) + == + :: + ++ pending + |% + ++ all + .^ (list pend-tx) + %gx + (~(scry agentio bowl) %aggregator /pending/noun) + == + :: + ++ ship + |= =^ship + .^ (list pend-tx) + %gx + (~(scry agentio bowl) %aggregator /pending/(scot %p ship)/noun) + == + :: + ++ addr + |= =address:naive + .^ (list pend-tx) + %gx + %+ ~(scry agentio bowl) %aggregator + /pending/[(scot %ux address)]/noun + == + -- + :: + ++ history + |% + ++ addr + |= =address:naive + .^ (list roller-tx) + %gx + (~(scry agentio bowl) %aggregator /history/(scot %ux address)/noun) + == + -- + :: + ++ tx-status + |= keccak=@ux + .^ ^tx-status + %gx + (~(scry agentio bowl) %aggregator /tx/(scot %ux keccak)/status/noun) + == + :: + ++ next-batch + .^ time + %gx + (~(scry agentio bowl) %aggregator /next-batch/noun) + == + :: + ++ nonce + |= [=ship =proxy:naive] + .^ (unit @) + %gx + %+ ~(scry agentio bowl) + %aggregator + /nonce/(scot %p ship)/[proxy]/noun + == + :: + ++ config + .^ roller-config + %gx + %+ ~(scry agentio bowl) + %aggregator + /config/noun + == + :: + ++ chain-id + .^ @ + %gx + %+ ~(scry agentio bowl) + %aggregator + /chain-id/noun + == + -- +-- diff --git a/pkg/arvo/app/aggregator.hoon b/pkg/arvo/app/aggregator.hoon index 6250ef2dca..e8d45a738b 100644 --- a/pkg/arvo/app/aggregator.hoon +++ b/pkg/arvo/app/aggregator.hoon @@ -16,79 +16,90 @@ :: when retrying, only do so if l2 txs remain in the "frozen" txs group. :: on %tx diff from naive, remove the matching tx from the frozen group. :: -::TODO remaining general work: -:: - hook up subscription to azimuth for %tx diffs -:: - hook up thread updates/results -:: - hook up timer callbacks -:: - cache state, upate after every azimuth %fact -:: - properly support private key changes -:: ::TODO questions: :: - it's a bit weird how we just assume the raw and tx in raw-tx to match... :: -/+ naive, default-agent, ethereum, dbug, verb -/= ttttt /tests/lib/naive ::TODO use new lib +/- *dice +/+ azimuth, + naive, + dice, + lib=naive-transactions, + default-agent, + ethereum, + dbug, + verb :: -::TODO /sur file for public types |% +$ state-0 $: %0 :: pending: the next l2 txs to be sent :: sending: the l2 txs currently sending/awaiting l2 confirmation - ::TODO should maybe key by [address nonce] instead. same for wires :: finding: raw-tx-hash reverse lookup for sending map + :: history: status of l2 txs by ethereum address + :: transfers: index that keeps track of transfer-proxy changes :: next-nonce: next l1 nonce to use + :: next-batch: when then next l2 batch will be sent + :: pre: predicted l2 state + :: own: ownership of azimuth points + :: derive-p: flag (derive predicted state) + :: derive-o: flag (derive ownership state) :: pending=(list pend-tx) - sending=(map nonce:naive [next-gas-price=@ud txs=(list raw-tx:naive)]) - finding=(map keccak $?(%confirmed %failed l1-tx-pointer)) - next-nonce=@ud + :: + $= sending + %+ map l1-tx-pointer + [next-gas-price=@ud txs=(list raw-tx:naive)] + :: + finding=(map keccak ?(%confirmed %failed l1-tx-pointer)) + history=(jug address:ethereum roller-tx) + transfers=(map ship address:ethereum) + next-nonce=(unit @ud) + next-batch=time + pre=^state:naive + own=owners + derive-p=? + derive-o=? :: :: pk: private key to send the roll :: frequency: time to wait between sending batches (TODO fancier) :: endpoint: ethereum rpc endpoint to use + :: contract: ethereum contract address + :: chain-id: mainnet, ropsten, local (https://chainid.network/) :: pk=@ frequency=@dr - endpoint=@t + endpoint=(unit @t) + contract=@ux + chain-id=@ == :: -+$ keccak @ux ++$ init [nas=^state:naive own=owners] :: -+$ tx-status - $: status=?(%unknown %pending %sending %confirmed %failed) - pointer=(unit l1-tx-pointer) - == -:: -+$ l1-tx-pointer - $: =address:ethereum - nonce=@ud - == -:: -::TODO cache sender address? -+$ pend-tx [force=? =raw-tx:naive] -:: -+$ part-tx - $% [%raw raw=octs] - [%don =tx:naive] - [%ful raw=octs =tx:naive] ::TODO redundant? ++$ config + $% [%frequency frequency=@dr] + [%setkey pk=@] + [%endpoint endpoint=@t] + [%network net=?(%mainnet %ropsten %local)] == :: +$ action - $% [%submit force=? sig=@ tx=part-tx] - [%cancel sig=@ keccak=@] - :: + $% :: we need to include the address in submit so pending txs show up + :: in the tx history, but because users can send the wrong + :: address, in +apply-tx:predicted state, we just replace + :: the provided address, with the one used when the message was signed; + :: + :: we need to do it there to know the correct nonce that the signed + :: message should have included. + :: + [%submit force=? =address:naive sig=@ tx=part-tx] + [%cancel sig=@ keccak=@ =l2-tx =ship] [%commit ~] ::TODO maybe pk=(unit @) later - [%config frequency=@dr] - [%setkey pk=@] - ::TODO configure endpoint, contract address, chain..? + [%config config] == :: +$ card card:agent:gall :: -::TODO config? -++ contract 0xb581.01cd.3bbb.cc6f.a40b.cdb0.4bb7.1623.b5c7.d39b -++ chain-id '1' +:: TODO: add to config :: ++ resend-time ~m5 :: @@ -110,9 +121,14 @@ :: ++ on-init ^- (quip card _this) - ::TODO set default frequency and endpoint? =. frequency ~h1 - [~ this] + =. contract naive:local-contracts:azimuth + =. chain-id chain-id:local-contracts:azimuth + =^ card next-batch set-timer + :_ this + :~ card + [%pass /azimuth-events %agent [our.bowl %azimuth] %watch /event] + == :: ++ on-save !>(state) ++ on-load @@ -125,28 +141,46 @@ ^- (quip card _this) =^ cards state ?+ mark (on-poke:def mark vase) - %aggregator-action + %aggregator-action =+ !<(poke=action vase) (on-action:do poke) == [cards this] :: +on-peek: scry paths - ::TODO reevaluate wrt recent flow changes :: :: /x/pending -> %noun (list pend-tx) :: /x/pending/[~ship] -> %noun (list pend-tx) :: /x/pending/[0xadd.ress] -> %noun (list pend-tx) :: /x/tx/[0xke.ccak]/status -> %noun tx-status - :: /x/nonce/[~ship]/[0xadd.ress] -> %atom @ + :: /x/history/[0xadd.ress] -> %noun (list roller-tx) + :: /x/nonce/[~ship]/[proxy] -> %noun (unit @) + :: /x/spawned/[~ship] -> %noun (list [ship address]) + :: /x/next-batch -> %atom time + :: /x/point/[~ship] -> %noun point:naive + :: /x/points/[0xadd.ress] -> %noun (list [ship point:naive]) + :: /x/config -> %noun config + :: /x/chain-id -> %atom @ :: ++ on-peek |= =path ^- (unit (unit cage)) + |^ ?+ path ~ - [%x %pending ~] ``noun+!>(pending) + [%x %pending ~] ``noun+!>(pending) + [%x %pending @ ~] (pending-by i.t.t.path) + [%x %tx @ %status ~] (status i.t.t.path) + [%x %history @ ~] (history i.t.t.path) + [%x %nonce @ @ ~] (nonce i.t.t.path i.t.t.t.path) + [%x %spawned @ ~] (spawned i.t.t.path) + [%x %next-batch ~] ``atom+!>(next-batch) + [%x %point @ ~] (point i.t.t.path) + [%x %points @ ~] (points i.t.t.path) + [%x %config ~] config + [%x %chain-id ~] ``atom+!>(chain-id) + == :: - [%x %pending @ ~] - =* wat i.t.t.path + ++ pending-by + |= wat=@t ?~ who=(slaw %p wat) :: by-address :: @@ -156,8 +190,7 @@ ``noun+!>(pending) %+ skim pending |= pend-tx - ::TODO deduce address from sig.raw-tx ? - !! + =(u.wer (need (get-l1-address tx.raw-tx pre))) :: by-ship :: =; pending=(list pend-tx) @@ -166,8 +199,9 @@ |= pend-tx =(u.who ship.from.tx.raw-tx) :: - [%x %tx @ %status ~] - ?~ keccak=(slaw %ux i.t.t.path) + ++ status + |= wat=@t + ?~ keccak=(slaw %ux wat) [~ ~] :+ ~ ~ :- %noun @@ -179,27 +213,107 @@ =; known=? [?:(known %pending %unknown) ~] %+ lien pending - |= [* raw-tx:naive] - =(u.keccak (hash-tx raw)) + |= pend-tx + =(u.keccak (hash-tx:lib raw.raw-tx)) :: - [%x %nonce @ @ ~] - ?~ who=(slaw %p i.t.t.path) + ++ history + |= wat=@t + :+ ~ ~ + :- %noun + !> ^- (list roller-tx) + ?~ addr=(slaw %ux wat) ~ + %~ tap in + (~(get ju ^history) u.addr) + :: + ++ nonce + |= [who=@t proxy=@t] + ?~ who=(slaw %p who) [~ ~] - =+ proxy=i.t.t.t.path ?. ?=(proxy:naive proxy) [~ ~] - =/ [* nas=^state:naive] pending-state:do - ::TODO or should we ~ when !(~(has by points.nas) who) ? - =/ =point:naive (~(gut by points.nas) u.who *point:naive) - =+ (proxy-from-point:naive proxy point) - ``atom+!>(nonce) - == + :+ ~ ~ + :- %noun + !> ^- (unit @) + ?~ point=(get:orm:naive points.pre u.who) + ~ + =< `nonce + (proxy-from-point:naive proxy u.point) + :: + ++ spawned + |= wat=@t + :+ ~ ~ + :- %noun + !> ^- (list [=^ship =address:ethereum]) + ?~ star=(slaw %p wat) ~ + =/ range + %+ lot:orm:naive points.pre + :: range exclusive [star next-star-first-planet-] + :: TODO: make range inclusive ([first-planet last-planet])? + :: + [`u.star `(cat 3 +(u.star) 0x1)] + %+ turn (tap:orm:naive range) + |= [=ship =point:naive] + ^- [=^ship =address:ethereum] + :- ship + address:(proxy-from-point:naive %own point) + :: + ++ point + |= wat=@t + ?~ ship=(rush wat ;~(pfix sig fed:ag)) + ``noun+!>(*(unit point:naive)) + ``noun+!>((get:orm:naive points.pre u.ship)) + :: + ++ points + |= wat=@t + :+ ~ ~ + :- %noun + !> ^- (list ship) + ?~ addr=(slaw %ux wat) + ~ + %~ tap in + (~(get ju own) u.addr) + :: + ++ config + :+ ~ ~ + :- %noun + !> ^- roller-config + :* next-batch + frequency + resend-time + contract + chain-id + == + -- :: ++ on-arvo |= [=wire =sign-arvo] ^- (quip card _this) - ?+ +<.sign-arvo (on-arvo:def wire sign-arvo) - %wake =^(cards state on-timer:do [cards this]) + ?+ wire (on-arvo:def wire sign-arvo) + [%timer ~] + ?+ +<.sign-arvo (on-arvo:def wire sign-arvo) + %wake =^(cards state on-timer:do [cards this]) + == + :: + [%predict ~] + ?+ +<.sign-arvo (on-arvo:def wire sign-arvo) + %wake + =. state (predicted-state canonical-state):do + `this(derive-p &) + == + :: + [%owners ~] + ?+ +<.sign-arvo (on-arvo:def wire sign-arvo) + %wake + =. own.state canonical-owners:do + `this(derive-o &) + == + :: + [%resend @ @ ~] + =/ [address=@ux nonce=@ud] + [(slav %ux i.t.wire) (rash i.t.t.wire dem)] + ?+ +<.sign-arvo (on-arvo:def wire sign-arvo) + %wake [(send-roll:do address nonce) this] + == == :: ++ on-fail @@ -209,7 +323,120 @@ :: ++ on-watch on-watch:def ++ on-leave on-leave:def - ++ on-agent on-agent:def + ++ on-agent + |= [=wire =sign:agent:gall] + ^- (quip card _this) + |^ + ?+ wire (on-agent:def wire sign) + [%send @ @ *] (send-batch i.t.wire i.t.t.wire sign) + [%azimuth-events ~] (azimuth-event sign) + [%nonce ~] (nonce sign) + == + :: + ++ send-batch + |= [address=@t nonce=@t =sign:agent:gall] + ^- (quip card _this) + =/ [address=@ux nonce=@ud] + [(slav %ux address) (rash nonce dem)] + ?- -.sign + %poke-ack + ?~ p.sign + %- (slog leaf+"Send batch thread started successfully" ~) + [~ this] + %- (slog leaf+"{(trip dap.bowl)} couldn't start thread" u.p.sign) + :_ this + [(leave:spider:do wire)]~ + :: + %watch-ack + ?~ p.sign + [~ this] + =/ =tank leaf+"{(trip dap.bowl)} couldn't start listen to thread" + %- (slog tank u.p.sign) + [~ this] + :: + %kick + [~ this] + :: + %fact + ?+ p.cage.sign (on-agent:def wire sign) + %thread-fail + =+ !<([=term =tang] q.cage.sign) + %- (slog leaf+"{(trip dap.bowl)} failed" leaf+ tang) + =^ cards state + (on-batch-result:do address nonce %.n^'thread failed') + [cards this] + :: + %thread-done + =+ !<(result=(each @ud @t) q.cage.sign) + =^ cards state + (on-batch-result:do address nonce result) + [cards this] + == + == + :: + ++ azimuth-event + |= =sign:agent:gall + ^- (quip card _this) + ?+ -.sign [~ this] + %watch-ack + ?~ p.sign [~ this] + =/ =tank leaf+"{(trip dap.bowl)} couldn't start listen to %azimuth" + %- (slog tank u.p.sign) + [~ this] + :: + %fact + ?+ p.cage.sign (on-agent:def wire sign) + %naive-diffs + =+ !<(=diff:naive q.cage.sign) + =^ cards state + (on-naive-diff:do diff) + [cards this] + :: + %naive-state + ~& > %received-azimuth-state + :: cache naive and ownership state + :: + =^ nas own.state !<(init q.cage.sign) + =. state (predicted-state:do nas) + `this + == + == + :: + ++ nonce + |= =sign:agent:gall + ^- (quip card _this) + ?- -.sign + %poke-ack + ?~ p.sign + %- (slog leaf+"Nonce thread started successfully" ~) + [~ this] + %- (slog leaf+"{(trip dap.bowl)} couldn't start thread" u.p.sign) + :_ this + [(leave:spider:do wire)]~ + :: + %watch-ack + ?~ p.sign + [~ this] + =/ =tank leaf+"{(trip dap.bowl)} couldn't start listen to thread" + %- (slog tank u.p.sign) + [~ this] + :: + %kick + [~ this] + :: + %fact + ?+ p.cage.sign (on-agent:def wire sign) + %thread-fail + =+ !<([=term =tang] q.cage.sign) + %- (slog leaf+"{(trip dap.bowl)} failed" leaf+ tang) + [~ this] + :: + %thread-done + =+ !<(nonce=@ud q.cage.sign) + [~ this(next-nonce `nonce)] + == + == + -- -- :: |_ =bowl:gall @@ -231,176 +458,333 @@ |= [=wire thread=term arg=vase] ^- (list card) =/ tid=@ta (rap 3 thread '--' (scot %uv eny.bowl) ~) - :~ (poke wire %spider-start !>([~ `tid thread arg])) - (watch wire %spider-start /thread-result/[tid]) + =/ args [~ `tid thread arg] + :~ [%pass wire %agent [our.bowl %spider] %watch /thread-result/[tid]] + [%pass wire %agent [our.bowl %spider] %poke %spider-start !>(args)] == :: - ++ poke - |= [=path =cage] - ^- card - [%pass path %agent [our.bowl %spider] %poke cage] - :: - ++ watch - |= [=path =sub=path] - ^- card - [%pass path %agent [our.bowl %spider] %watch sub-path] - :: ++ leave |= =path ^- card [%pass path %agent [our.bowl %spider] %leave ~] -- :: -++ hash-tx keccak-256:keccak:crypto -:: -++ hash-raw-tx - |= =raw-tx:naive - (hash-tx raw.raw-tx) -:: ++ part-tx-to-full |= =part-tx ^- [octs tx:naive] - ?+ -.part-tx !! - :: %raw [+.part-tx (decode-tx:naive +.part-tx)] - :: %don [(encode-tx:naive +.part-tx) +.part-tx] + ?- -.part-tx + %raw + ?~ batch=(parse-raw-tx:naive q.raw.part-tx) + ~& %parse-failed + :: TODO: maybe return a unit if parsing fails? + :: + !! + [raw tx]:-.u.batch + :: + %don [(gen-tx-octs:lib +.part-tx) +.part-tx] %ful +.part-tx == -:: +pending-state +:: +canonical-state: current l2 state from /app/azimuth :: -:: derives tentative state from pending txs and canonical state, -:: discarding invalid pending txs in the process. +++ canonical-state + .^ ^state:naive + %gx + (scot %p our.bowl) + %azimuth + (scot %da now.bowl) + /nas/noun + == +:: +canonical-owners: current azimuth point ownership :: -::TODO maybe want to cache locally, refresh on %fact from azimuth? +++ canonical-owners + .^ owners + %gx + (scot %p our.bowl) + %azimuth + (scot %da now.bowl) + /own/noun + == +:: +predicted-state :: -++ pending-state - ^- (quip pend-tx ^state:naive) - :: load current, canonical state - :: - =+ .^ nas=^state:naive - %gx - (scot %p our.bowl) - %azimuth - (scot %da now.bowl) - /nas/nas - == - :: apply our pending transactions - ::TODO should also apply txs from sending map! - :: - =| valid=_pending - |- ^+ [valid nas] - ?~ pending [(flop valid) nas] - :: - =^ gud=? nas (try-apply nas i.pending) - =? valid gud [i.pending valid] - $(pending t.pending) -:: +try-apply: +:: derives predicted state from applying pending/sending txs to +:: the canonical state, discarding invalid txs in the process. :: -++ try-apply - |= [nas=^state:naive force=? =raw-tx:naive] - ^- [success=? _nas] - ?. (verify-sig-and-nonce:naive verifier:ttttt chain-id nas raw-tx) - [force nas] +++ predicted-state + |= nas=^state:naive + ^+ state + =. pre.state nas + |^ + =^ nes state apply-sending + =^ nep state apply-pending + state(sending nes, pending nep) :: - =^ out points.nas (increment-nonce:naive nas from.tx.raw-tx) + ++ apply-pending + (apply-txs pending %pending) :: - ?~ nex=(receive-tx:naive nas tx.raw-tx) - [force nas] - [& +.u.nex] + ++ apply-sending + =| valid=_sending + =+ sending=~(tap by sending) + |- ^+ [valid state] + ?~ sending [valid state] + :: + =* key p.i.sending + =* val q.i.sending + =^ new-valid state + %+ apply-txs + (turn txs.val |=(=raw-tx:naive [| 0x0 raw-tx])) + %sending + =. valid + %+ ~(put by valid) key + val(txs (turn new-valid (cork tail tail))) + $(sending t.sending) + :: + ++ apply-txs + |= [txs=(list pend-tx) type=?(%pending %sending)] + =/ valid=_txs ~ + :: =| local=(set keccak) + |- ^+ [valid state] + ?~ txs [valid state] + :: + =* tx i.txs + =* raw-tx raw-tx.i.txs + =* ship ship.from.tx.raw-tx.i.txs + =/ hash=@ux (hash-raw-tx:lib raw-tx) + :: TODO: add tests to validate if this is necessary + :: + :: ?: (~(has in local) hash) + :: :: if tx was already seen here, skip + :: :: + :: $(txs t.txs) + =/ sign-address=(unit @ux) + (extract-address:lib raw-tx pre.state chain-id) + =^ gud=? state + (try-apply pre.state force.tx raw-tx) + :: TODO: only replace address if !=(address.tx sign-address)? + :: + =? tx &(gud ?=(^ sign-address)) + tx(address u.sign-address) + =? valid gud (snoc valid tx) + =? finding.state !gud + (~(put by finding.state) [hash %failed]) + =? history.state !gud + =/ =roller-tx + [ship type hash (l2-tx +<.tx.raw-tx)] + %. [address.tx roller-tx(status %failed)] + ~(put ju (~(del ju history.state) address.tx roller-tx)) + :: $(txs t.txs, local (~(put in local) hash)) + $(txs t.txs) + :: + ++ try-apply + |= [nas=^state:naive force=? =raw-tx:naive] + ^- [? _state] + =/ [success=? predicted=_nas owners=_own] + (apply-raw-tx:dice force raw-tx nas own chain-id) + :- success + state(pre predicted, own owners) + -- +:: +++ get-l1-address + |= [=tx:naive nas=^state:naive] + ^- (unit address:ethereum) + ?~ point=(get:orm:naive points.nas ship.from.tx) ~ + =< `address + (proxy-from-point:naive proxy.from.tx u.point) :: ++ on-action |= =action ^- (quip card _state) ?- -.action - %commit !! :: TODO send-roll - %config [~ state(frequency frequency.action)] - %setkey [~ state(pk pk.action)] ::TODO what about existing sending entries? + %commit on-timer + %config (on-config +.action) + %cancel (cancel-tx +.action) :: %submit - =^ success state - ^- [? _state] - %^ take-tx - force.action - sig.action - (part-tx-to-full tx.action) - :: TODO: consider failure case - ?> success - [~ state] - :: - %cancel - !! ::TODO + %- take-tx + :^ force.action + address.action + sig.action + (part-tx-to-full tx.action) == +:: +++ on-config + |= =config + ^- (quip card _state) + ?- -.config + %frequency [~ state(frequency frequency.config)] + %endpoint [~ state(endpoint `endpoint.config)] + :: + %network + :- ~ + =/ [contract=@ux chain-id=@] + =< [naive chain-id] + =, azimuth + ?- net.config + %mainnet mainnet-contracts + %ropsten ropsten-contracts + %local local-contracts + == + state(contract contract, chain-id chain-id) + :: + %setkey + ?~ pk=(de:base16:mimes:html pk.config) + `state + [(get-nonce q.u.pk) state(pk q.u.pk)] + == +:: TODO: move address to state? +:: +++ get-address + ^- address:ethereum + (address-from-prv:key:ethereum pk) +:: +cancel-tx: cancel a pending transaction +:: +++ cancel-tx + |= [sig=@ =keccak =l2-tx =ship] + ^- (quip card _state) + ?^ status=(~(get by finding) keccak) + ~? lverb [dap.bowl %tx-not-pending status+u.status] + [~ state] + :: "cancel: 0x1234abcd" + :: + =/ message=octs + %: cad:naive 3 + 8^'cancel: ' + :: + =; hash=@t + (met 3 hash)^hash + (crip "0x{((x-co:co 20) keccak)}") + :: + ~ + == + ?~ addr=(verify-sig:lib sig message) + ~? lverb [dap.bowl %cancel-sig-fail] + [~ state] + :: TODO: mark as failed instead? add a %cancelled to tx-status? + :: + =. history + %+ ~(del ju history) u.addr + [ship %pending keccak l2-tx] + =. pending + %+ skip pending + |= pend-tx + =(keccak (hash-raw-tx:lib raw-tx)) + [~ state] :: +take-tx: accept submitted l2 tx into the :pending list -::TODO rewrite :: ++ take-tx - |= [force=? =raw-tx:naive] - ^- [success=? _state] - =/ [nep=_pending nas=^state:naive] pending-state - =| success=? - :: TODO: actually use try-apply when proper Tx signing in place + |= pend-tx + ^- (quip card _state) + =/ hash=@ux (hash-raw-tx:lib raw-tx) + :: TODO: what if this hash/tx is already in the history? + :: e.g. if previously failed, but now it will go through + :: a) check in :finding that hash doesn't exist and if so, skip ? + :: b) extract the status from :finding, use it to delete + :: the entry in :history, and then insert it as %pending ? :: - :: =^ success nas - :: (try-apply nas force raw-tx) - ::TODO want to notify about dropped pendings, or no? client prolly polls... - =? pending success (snoc nep [force raw-tx]) - ::TODO cache nas? - [success state] + :: =/ not-sent=? !(~(has by finding) hash) + :: =? pending not-sent + =. pending (snoc pending [force address raw-tx]) + :: =? history not-sent + =. history + %+ ~(put ju history) address + [ship.from.tx.raw-tx %pending hash (l2-tx +<.tx.raw-tx)] + =? transfers =(%transfer-point (l2-tx +<.tx.raw-tx)) + (~(put by transfers) ship.from.tx.raw-tx address) + :: ?. not-sent ~& "skip" [~ state] + :: toggle flush flag + :: + :_ state(derive-p ?:(derive-p | derive-p)) + ?. derive-p ~ + :: derive predicted state in 5m. + :: + [(wait:b:sys /predict (add ~m5 now.bowl))]~ :: +set-timer: %wait until next whole :frequency :: ++ set-timer - ^- card - %+ wait:b:sys /timer - (mul +((div now.bowl frequency)) frequency) + ^- [=card =time] + =+ time=(mul +((div now.bowl frequency)) frequency) + [(wait:b:sys /timer time) time] :: +on-timer: every :frequency, freeze :pending txs roll and start sending it :: ++ on-timer ^- (quip card _state) + =. state (predicted-state canonical-state) =^ cards state - ?~ pending [~ state] - =/ nonce=@ud next-nonce - =: :: FIXME: what's up with this? `pending ~` also fails - :: pending *(list pend-tx) - next-nonce +(next-nonce) + ?: =(~ pending) [~ state] + ?~ next-nonce + ~&([dap.bowl %no-nonce] [~ state]) + =/ nonce=@ud u.next-nonce + =: pending ~ + derive-p & + next-nonce `+(u.next-nonce) :: sending - %+ ~(put by sending) nonce - [0 (turn pending tail)] + %+ ~(put by sending) + [get-address nonce] + [0 (turn pending (cork tail tail))] + :: + finding + %- ~(gas by finding) + %+ turn pending + |= pend-tx + (hash-raw-tx:lib raw-tx)^[address nonce] + :: + history + %+ roll pending + |= [pend-tx hist=_history] + =/ tx=roller-tx + :^ ship.from.tx.raw-tx + %pending + (hash-raw-tx:lib raw-tx) + (l2-tx +<.tx.raw-tx) + %+ ~(put ju (~(del ju hist) address tx)) + address + tx(status %sending) == - [(send-roll nonce) state] - [[set-timer cards] state] + [(send-roll get-address nonce) state] + =^ card next-batch set-timer + [[card cards] state] +:: +get-nonce: retrieves the latest nonce +:: +++ get-nonce + |= pk=@ + ^- (list card) + ?~ endpoint ~&([dap.bowl %no-endpoint] ~) + (start-thread:spider /nonce [%aggregator-nonce !>([u.endpoint pk])]) +:: :: +send-roll: start thread to submit roll from :sending to l1 :: ++ send-roll - |= nonce=@ud + |= [=address:ethereum nonce=@ud] ^- (list card) :: if this nonce isn't in the sending queue anymore, it's done :: - ?. (~(has by sending) nonce) - ~? lverb [dap.bowl %done-sending nonce] + ?. (~(has by sending) [address nonce]) + ~? lverb [dap.bowl %done-sending [address nonce]] ~ :: start the thread, passing in the l2 txs to use :: + ?~ endpoint ~&([dap.bowl %no-endpoint] ~) ::TODO should go ahead and set resend timer in case thread hangs, or nah? %+ start-thread:spider - /send/(scot %ud nonce) + /send/(scot %ux address)/(scot %ud nonce) :- %aggregator-send - !> - :* endpoint + !> ^- rpc-send-roll + :* u.endpoint contract chain-id - 0x1234.5678 + pk nonce - (~(got by sending) nonce) + (~(got by sending) [address nonce]) == -:: +on-thread-result: await resend after thread success or failure +:: +on-batch-result: await resend after thread success or failure :: -++ on-thread-result - |= [nonce=@ud result=(each @ud term)] +++ on-batch-result + |= [=address:ethereum nonce=@ud result=(each @ud @t)] ^- (quip card _state) :: update gas price for this tx in state :: =? sending ?=(%& -.result) - %+ ~(jab by sending) nonce + %+ ~(jab by sending) [address nonce] (cork tail (lead p.result)) :: print error if there was one :: @@ -408,15 +792,24 @@ :: resend the l1 tx in five minutes :: :_ state - [(wait:b:sys /resend/(scot %ud nonce) (add resend-time now.bowl))]~ + :_ ~ + %+ wait:b:sys + /resend/(scot %ux address)/(scot %ud nonce) + (add resend-time now.bowl) :: +on-naive-diff: process l2 tx confirmations :: ++ on-naive-diff |= =diff:naive ^- (quip card _state) + ?: ?=(%point -.diff) + :_ state(derive-o ?:(derive-o | derive-o)) + ?. derive-o ~ + :: calculate ownership in 5m. + :: + [(wait:b:sys /owners (add ~m5 now.bowl))]~ ?. ?=(%tx -.diff) [~ state] - =/ =keccak (hash-raw-tx raw-tx.diff) + =/ =keccak (hash-raw-tx:lib raw-tx.diff) ?~ wer=(~(get by finding) keccak) [~ state] :: if we had already seen the tx, no-op @@ -426,10 +819,11 @@ [dap.bowl %weird-double-confirm from.tx.raw-tx.diff] [~ state] =* nonce nonce.u.wer + =* ship ship.from.tx.raw-tx.diff :: remove the tx from the sending map :: =. sending - ?~ sen=(~(get by sending) nonce) + ?~ sen=(~(get by sending) [get-address nonce]) ~& [dap.bowl %weird-double-remove] sending ?~ nin=(find [raw-tx.diff]~ txs.u.sen) @@ -437,9 +831,9 @@ sending =. txs.u.sen (oust [u.nin 1] txs.u.sen) ?~ txs.u.sen - ~? lverb [dap.bowl %done-with-nonce nonce] - (~(del by sending) nonce) - (~(put by sending) nonce u.sen) + ~? lverb [dap.bowl %done-with-nonce [get-address nonce]] + (~(del by sending) [get-address nonce]) + (~(put by sending) [get-address nonce] u.sen) :: update the finding map with the new status :: =. finding @@ -449,6 +843,27 @@ :: unexpected tx failures here. would that be useful? probably not? :: ~? !forced [dap.bowl %aggregated-tx-failed-anyway err.diff] %failed - [~ state] + :: + =. history + =/ l2-tx (l2-tx +<.tx.raw-tx.diff) + =/ tx=roller-tx [ship %sending keccak l2-tx] + ?~ addr=(get-l1-address tx.raw-tx.diff pre) + history + =/ =address:ethereum + ?. =(%transfer-point l2-tx) + u.addr + :: TODO: delete this ship from the transfer? + :: + (~(got by transfers) ship) + %+ ~(put ju (~(del ju history) address tx)) + address + %_ tx + status ?~(err.diff %confirmed %failed) + == + :_ state(derive-p ?:(derive-p | derive-p)) + ?. derive-p ~ + :: derive predicted state in 5m. + :: + [(wait:b:sys /predict (add ~m5 now.bowl))]~ :: -- diff --git a/pkg/arvo/app/azimuth-rpc.hoon b/pkg/arvo/app/azimuth-rpc.hoon index c7f7c8869b..fce3723705 100644 --- a/pkg/arvo/app/azimuth-rpc.hoon +++ b/pkg/arvo/app/azimuth-rpc.hoon @@ -11,14 +11,6 @@ version, agentio |% -:: FIXME: import tx-status, pend-tx from aggregator -:: -+$ tx-status - $: status=?(%unknown %pending %sent %confirmed %failed) - tx=(unit @ux) - == -:: -+$ pend-tx [force=? =raw-tx:naive] :: +$ card card:agent:gall :: @@ -42,7 +34,7 @@ ^- (quip card _this) ~& > 'init' :_ this - [%pass /bind %arvo %e %connect [~ [%v1 %azimuth ~]] dap.bowl]~ + [%pass /bind %arvo %e %connect [~ /v1/azimuth] dap.bowl]~ :: ++ on-save !>(state) ++ on-load @@ -65,7 +57,7 @@ =+ !<([%disconnect bind=binding:eyre] vase) ~& >>> "disconnecting at {}" :_ this - [[%pass /bind %arvo %e %disconnect bind]]~ + [%pass /bind %arvo %e %disconnect bind]~ == :: ++ handle-http-request @@ -79,22 +71,19 @@ :: TODO: method not supported :: (give-simple-payload:app id not-found:gen) - ?~ rpc-request=(validate-request:json-rpc body.req parse-method) + ?~ rpc-request=(validate-request:json-rpc body.req) :: TODO: malformed request :: (give-simple-payload:app id not-found:gen) - =/ [data=(unit cage) response=simple-payload:http] + =/ [data=(list cage) response=simple-payload:http] (process-rpc-request:do u.rpc-request) %+ weld (give-simple-payload:app id response) + |- ?~ data ~ - :_ ~ + :_ $(data t.data) ^- card - [%pass / %agent [our.bowl %aggregator] %poke u.data] - :: TODO: validate that format is e.g. 'getPoint' - :: TODO: maybe use getPoint and translate to %get-point - :: - ++ parse-method |=(t=@t `term`t) + [%pass / %agent [our.bowl %azimuth] %poke i.data] -- -- :: @@ -124,34 +113,43 @@ :: |_ =bowl:gall ++ process-rpc-request - |= request:rpc - ^- [(unit cage) simple-payload:http] - =; [data=(unit cage) =response:rpc] - :- data + |= req=batch-request:rpc + ^- [(list cage) simple-payload:http] + |^ + ?- -.req + %o + =/ [data=(unit cage) =response:rpc] + (process p.req) + [(drop data) (render response)] + :: + %a + =| data=(list cage) + =| resp=(list response:rpc) + |- + ?~ p.req + [(flop data) (render %batch (flop resp))] + =/ [dat=(unit cage) res=response:rpc] + (process i.p.req) + =? data ?=(^ dat) [u.dat data] + $(p.req t.p.req, resp [res resp]) + == + :: + ++ render + |= res=response:rpc %- json-response:gen - (response-to-json:json-rpc response) - =, azimuth-rpc - ?. ?=([%map *] params) - [~ ~(parse error id)] - ?+ method [~ ~(method error id)] - %get-point [~ (get-point id +.params point:scry)] - %transfer-point (transfer-point id +.params) - %configure-keys (configure-keys id +.params) - %spawn (spawn id +.params) - %escape (escape id +.params method) - %cancel-escape (cancel-escape id +.params method) - %adopt (adopt id +.params method) - %detach (detach id +.params method) - %reject (reject id +.params method) - %set-management-proxy (management-proxy id +.params method) - %set-spawn-proxy (spawn-proxy id +.params method) - %set-transfer-proxy (transfer-proxy id +.params method) - %pending [~ (all:pending id +.params all:pending:scry)] - %pending-by-ship [~ (ship:pending id +.params ship:pending:scry)] - %pending-by-address [~ (addr:pending id +.params addr:pending:scry)] - %status [~ (status id +.params tx-status:scry)] - :: %history [~ (history id +.params all:history:scry)] - == + (response-to-json:json-rpc res) + :: + ++ process + |= request:rpc + =, azimuth-rpc + ?. ?=([%map *] params) + [~ ~(parse error:json-rpc id)] + =/ method=@tas (enkebab method) + ?+ method [~ ~(method error:json-rpc id)] + %get-point `(get-point id +.params point:scry) + %get-dns `(get-dns id +.params dns:scry) + == + -- :: ++ scry |% @@ -159,77 +157,15 @@ |= =ship .^ (unit point:naive) %gx - (~(scry agentio bowl) %azimuth /nas/[(scot %p ship)]/noun) + (~(scry agentio bowl) %azimuth /point/(scot %p ship)/noun) == :: - ++ pending - |% - ++ all - .^ (list pend-tx) - %gx - (~(scry agentio bowl) %aggregator /pending/noun) - == - :: - ++ ship - |= =^ship - .^ (list pend-tx) - %gx - (~(scry agentio bowl) %aggregator /pending/[(scot %p ship)]/noun) - == - :: - ++ addr - |= =address:naive - .^ (list pend-tx) - %gx - %+ ~(scry agentio bowl) %aggregator - /pending/[(scot %ux address)]/noun - == - -- - :: - ++ history - |% - ++ all - :: FIXME: use proper type from aggregator/index - :: - .^ (list tx:naive) - %gx - (~(scry agentio bowl) %aggregator /history/noun) - == - :: - ++ ship - |= =^ship - :: FIXME: use proper type from aggregator/index - :: - .^ (list tx:naive) - %gx - (~(scry agentio bowl) %aggregator /history/[(scot %p ship)]/noun) - == - :: - ++ addr - |= =address:naive - :: FIXME: use proper type from aggregator/index - :: - .^ (list tx:naive) - %gx - (~(scry agentio bowl) %aggregator /history/[(scot %ux address)]/noun) - == - -- - :: - ++ tx-status - |= keccak=@ux - .^ ^tx-status - %gx - (~(scry agentio bowl) %aggregator /tx/[(scot %ux keccak)]/status/noun) - == - :: - ++ nonce - |= [=ship =address:naive] - :: FIXME: use proper type from aggregator/index - .^ @ + ++ dns + .^ (list @t) %gx %+ ~(scry agentio bowl) - %aggregator - /nonce/[(scot %p ship)]/[(scot %ux address)]/atom + %azimuth + /dns/noun == -- -- diff --git a/pkg/arvo/app/azimuth.hoon b/pkg/arvo/app/azimuth.hoon index 45e165469b..94bb90e630 100644 --- a/pkg/arvo/app/azimuth.hoon +++ b/pkg/arvo/app/azimuth.hoon @@ -1,5 +1,11 @@ -/- eth-watcher -/+ ethereum, azimuth, naive, default-agent, verb, dbug +/- eth-watcher, *dice +/+ ethereum, + azimuth, + naive, + dice, + default-agent, + verb, + dbug /* snap %eth-logs /app/azimuth/logs/eth-logs :: =/ last-snap :: maybe just use the last one? @@ -12,10 +18,11 @@ =, jael |% ++ app-state - $: %2 + $: %3 url=@ta whos=(set ship) nas=^state:naive + own=owners logs=(list =event-log:rpc:ethereum) == +$ poke-data @@ -27,9 +34,17 @@ [%watch url=@ta] == +$ tagged-diff [=id:block diff:naive] +:: ++$ network ?(%mainnet %ropsten %local) -- :: |% +++ net + ^- network + :: TODO: add poke action to allow switching? + :: eth snapshot could also be considered + :: + %local :: TODO: maybe flop the endianness here so metamask signs it in normal :: order? :: @@ -61,39 +76,42 @@ (hex-to-num:ethereum data) :: ++ run-logs - |= [nas=^state:naive logs=(list event-log:rpc:ethereum)] - ^- [(list tagged-diff) ^state:naive] + |= [state=app-state logs=(list event-log:rpc:ethereum)] + ^- (quip tagged-diff _state) + =/ [contract=@ux * chain-id=@ *] (get-network net) ?~ logs - `nas + `state ?~ mined.i.logs $(logs t.logs) - =^ raw-effects nas + =/ [raw-effects=effects:naive new-nas=_nas.state] =/ =^input:naive - ?: =(azimuth:contracts:azimuth address.i.logs) + ?: =(contract address.i.logs) =/ data (data-to-hex data.i.logs) =/ =event-log:naive [address.i.logs data topics.i.logs] [%log event-log] ?~ input.u.mined.i.logs [%bat *@] - =/ len (met 3 u.input.u.mined.i.logs) - =/ fun - (rsh [3 (sub len 4)] u.input.u.mined.i.logs) - ?. =(0x2688.7f26 fun) - [%bat *@] - [%bat (end [3 (sub len 4)] u.input.u.mined.i.logs)] + [%bat u.input.u.mined.i.logs] =/ res %- mule - |.((%*(. naive lac |) verifier chain-id:contracts:azimuth nas input)) + |.((%*(. naive lac |) verifier chain-id nas.state input)) ?- -.res %& p.res - %| ((slog 'naive-fail' p.res) `nas) + %| ((slog 'naive-fail' p.res) `nas.state) == + =. own.state + =, dice + ?. =(contract address.i.logs) + =< own + (apply-effects raw-effects nas.state own.state chain-id) + (update-ownership raw-effects nas.state new-nas own.state) + =. nas.state new-nas =/ effects-1 =/ =id:block [block-hash block-number]:u.mined.i.logs (turn raw-effects |=(=diff:naive [id diff])) - =^ effects-2 nas $(logs t.logs) - [(welp effects-1 effects-2) nas] + =^ effects-2 state $(logs t.logs) + [(welp effects-1 effects-2) state] :: ++ to-udiffs |= effects=(list tagged-diff) @@ -125,16 +143,39 @@ :- [%give %fact ~[path] %azimuth-udiffs !>(~[i.udiffs])] $(udiffs t.udiffs) :: -++ start - |= [state=app-state our=ship dap=term] +++ event-update + |= effects=(list tagged-diff) + ^- (list card:agent:gall) + %+ murn effects + |= tag=tagged-diff + ^- (unit card:agent:gall) + ?. |(?=(%tx +<.tag) ?=(%point +<.tag)) ~ + %- some ^- card:agent:gall + [%give %fact ~[/event] %naive-diffs !>(+.tag)] +:: +++ get-network + |= =network + ^- [@ux @ux @ @] + =< [azimuth naive chain-id launch] + =, azimuth + ?- network + %mainnet mainnet-contracts + %ropsten ropsten-contracts + %local local-contracts + == +:: +++ start + |= [state=app-state =network our=ship dap=term] + ^- card:agent:gall + =/ [azimuth=@ux naive=@ux * launch=@ud] (get-network network) =/ args=vase !> :+ %watch /[dap] ^- config:eth-watcher :* url.state =(%czar (clan:title our)) ~m5 ~h30 - (max launch:contracts:azimuth last-snap) - ~[azimuth:contracts:azimuth] - ~[naive:contracts:azimuth] + (max launch last-snap) + ~[azimuth] + ~[naive] (topics whos.state) == [%pass /wa %agent [our %eth-watcher] %poke %eth-watcher-poke args] @@ -172,14 +213,28 @@ - %2 nas *^state:naive == - `this(state ?>(?=(%2 -.old-state) old-state)) + =? old-state ?=(%2 -.old-state) + %= old-state + - %3 + own *owners + == + `this(state ?>(?=(%3 -.old-state) old-state)) :: - ++ app-states $%(app-state-0 app-state-1 app-state) + ++ app-states $%(app-state-0 app-state-1 app-state-2 app-state) + ++ app-state-2 + $: %2 + url=@ta + whos=(set ship) + nas=^state:naive + own=* + logs=(list =event-log:rpc:ethereum) + == ++ app-state-1 $: %1 url=@ta whos=(set ship) nas=* + own=* logs=(list =event-log:rpc:ethereum) == ++ app-state-0 @@ -187,6 +242,7 @@ url=@ta whos=(set ship) nas=* + own=* logs=(list =event-log-0) == :: @@ -212,7 +268,7 @@ ?+ q.vase !! %rerun ~& [%rerunning (lent logs.state)] - =^ effects nas.state (run-logs *^state:naive logs.state) + =^ effects state (run-logs state logs.state) `this :: %resub @@ -235,39 +291,36 @@ %listen [[%pass /lo %arvo %j %listen (silt whos.poke) source.poke]~ this] %watch =. url.state url.poke - [[(start state [our dap]:bowl) ~] this] + [[(start state net [our dap]:bowl) ~] this] == :: ++ on-watch |= =path ^- (quip card:agent:gall _this) ?< =(/sole/drum path) - ?> ?=(?(~ [@ ~]) path) + ?: =(/event path) + :_ this + [%give %fact ~ %naive-state !>([nas.state own.state])]~ =/ who=(unit ship) ?~ path ~ + ?: ?=([@ ~] path) ~ `(slav %p i.path) =. whos.state ?~ who ~ (~(put in whos.state) u.who) :_ this :_ ~ - (start state [our dap]:bowl) + (start state net [our dap]:bowl) :: ++ on-leave on-leave:def ++ on-peek |= =path ^- (unit (unit cage)) ?+ path (on-peek:def path) - [%x %logs ~] - ``logs+!>(logs.state) - :: - [%x %nas ~] - ``nas+!>(nas.state) - :: - [%x %nas @t ~] - ?~ ship=(rush i.t.t.path ;~(pfix sig fed:ag)) - ``noun+!>(*(unit point:naive)) - ``noun+!>((~(get by points.nas.state) u.ship)) + [%x %logs ~] ``noun+!>(logs.state) + [%x %nas ~] ``noun+!>(nas.state) + [%x %dns ~] ``noun+!>(dns.nas.state) + [%x %own ~] ``noun+!>(own.state) == :: ++ on-agent @@ -289,16 +342,12 @@ %logs (welp logs.state loglist.diff) == =? nas.state ?=(%history -.diff) *^state:naive - =^ effects nas.state - %+ run-logs - ?- -.diff - :: %history *^state:naive - %history nas.state - %logs nas.state - == - loglist.diff + =^ effects state (run-logs state loglist.diff) :: - [(jael-update (to-udiffs effects)) this] + :_ this + %+ weld + (event-update effects) + (jael-update (to-udiffs effects)) :: ++ on-arvo on-arvo:def ++ on-fail on-fail:def diff --git a/pkg/arvo/app/btc-provider.hoon b/pkg/arvo/app/btc-provider.hoon new file mode 100644 index 0000000000..e88fe55a4e --- /dev/null +++ b/pkg/arvo/app/btc-provider.hoon @@ -0,0 +1,355 @@ +:: btc-provider.hoon +:: Proxy that serves a BTC full node and ElectRS address indexer +:: +:: Subscriptions: none +:: To Subscribers: /clients +:: current connection state +:: results/errors of RPC calls +:: +:: Scrys +:: x/is-whitelisted/SHIP: bool, whether ship is whitelisted +:: +/- *bitcoin, json-rpc, *btc-provider +/+ dbug, default-agent, bl=btc, groupl=group, resource +|% ++$ versioned-state + $% state-0 + == +:: ++$ state-0 [%0 =host-info =whitelist] +:: ++$ card card:agent:gall +:: +-- +%- agent:dbug +=| state-0 +=* state - +^- agent:gall +=< +|_ =bowl:gall ++* this . + def ~(. (default-agent this %|) bowl) + hc ~(. +> bowl) +:: +++ on-init + ^- (quip card _this) + ~& > '%btc-provider initialized successfully' + =| wl=^whitelist + :- ~ + %_ this + host-info + ['' connected=%.n %main block=0 clients=*(set ship)] + whitelist wl(public %.n, kids %.n) + == +:: +++ on-save + ^- vase + !>(state) +:: +++ on-load + |= old-state=vase + ^- (quip card _this) + ~& > '%btc-provider recompiled successfully ' + `this(state !<(versioned-state old-state)) +:: +++ on-poke + |= [=mark =vase] + ^- (quip card _this) + ?> ?|((team:title our.bowl src.bowl) (is-client:hc src.bowl)) + =^ cards state + ?+ mark (on-poke:def mark vase) + %btc-provider-command + ?> (team:title our.bowl src.bowl) + (handle-command:hc !<(command vase)) + %btc-provider-action + (handle-action:hc !<(action vase)) + == + [cards this] +:: +++ on-watch + |= pax=path + ^- (quip card _this) + :: checking provider permissions before trying to subscribe + :: terrible hack until we have cross-ship scries + :: + ?: ?=([%permitted @ ~] pax) + :_ this + =/ jon=json + %+ frond:enjs:format + %'providerStatus' + %- pairs:enjs:format + :~ provider+s+(scot %p our.bowl) + permitted+b+(is-whitelisted:hc src.bowl) + == + [%give %fact ~ %json !>(jon)]~ + :: + ?> ?=([%clients *] pax) + ?. (is-whitelisted:hc src.bowl) + ~& >>> "btc-provider: blocked client {}" + [~[[%give %kick ~ ~]] this] + ~& > "btc-provider: accepted client {}" + :- [do-ping:hc]~ + this(clients.host-info (~(put in clients.host-info) src.bowl)) +:: +++ on-arvo + |= [=wire =sign-arvo] + ^- (quip card _this) + :: check for connectivity every 30 seconds + :: + ?: ?=([%ping-timer *] wire) + :_ this + :~ do-ping:hc + (start-ping-timer:hc ~s30) + == + =^ cards state + ?+ +<.sign-arvo (on-arvo:def wire sign-arvo) + %http-response + (handle-rpc-response:hc wire client-response.sign-arvo) + == + [cards this] +:: +++ on-peek + |= pax=path + ^- (unit (unit cage)) + ?+ pax (on-peek:def pax) + [%x %is-whitelisted @t ~] + ``noun+!>((is-whitelisted:hc (ship (slav %p +>-.pax)))) + :: + [%x %is-client @t ~] + ``noun+!>((is-client (ship (slav %p +>-.pax)))) +== +:: +++ on-leave on-leave:def +++ on-agent on-agent:def +++ on-fail on-fail:def +-- +:: helper core +|_ =bowl:gall +++ handle-command + |= comm=command + ^- (quip card _state) + ?- -.comm + %set-credentials + :- :~ do-ping + (start-ping-timer ~s30) + == + %= state + host-info + [api-url.comm connected=%.n network.comm block=0 clients=*(set ship)] + == + :: + %add-whitelist + ?- -.wt.comm + %public + `state(public.whitelist %.y) + :: + %kids + `state(kids.whitelist %.y) + :: + %users + `state(users.whitelist (~(uni in users.whitelist) users.wt.comm)) + :: + %groups + `state(groups.whitelist (~(uni in groups.whitelist) groups.wt.comm)) + == + :: + %remove-whitelist + =. state + ?- -.wt.comm + %public + state(public.whitelist %.n) + :: + %kids + state(kids.whitelist %.n) + :: + %users + state(users.whitelist (~(dif in users.whitelist) users.wt.comm)) + :: + %groups + state(groups.whitelist (~(dif in groups.whitelist) groups.wt.comm)) + == + clean-client-list + == +:: if not connected, only %ping action is allowed +:: +++ handle-action + |= act=action + ^- (quip card _state) + ?. ?|(connected.host-info ?=(%ping -.act)) + ~& >>> "Not connected to RPC" + [~[(send-update [%| %not-connected 500])] state] + :: + =/ ract=action:rpc-types + ?- -.act :: ~|("Invalid action" !!) + %address-info + [%get-address-info address.act] + :: + %tx-info + [%get-tx-vals txid.act] + :: + %raw-tx + [%get-raw-tx txid.act] + :: + %broadcast-tx + [%broadcast-tx rawtx.act] + :: + %ping + [%get-block-info ~] + == + [~[(req-card act ract)] state] +:: +++ req-card + |= [act=action ract=action:rpc-types] + =| out=outbound-config:iris + =/ req=request:http + (gen-request:bl host-info ract) + [%pass (rpc-wire act) %arvo %i %request req out] +:: wire structure: /action-tas/now +:: +++ rpc-wire + |= act=action ^- wire + /[-.act]/[(scot %ux (cut 3 [0 20] eny.bowl))] +:: +++ kick-client + |= client=ship + ^- (quip card _state) + ~& >>> "dropping client {}" + :- ~[[%give %kick ~[/clients] `client]] + state(clients.host-info (~(dif in clients.host-info) (silt ~[client]))) +:: +:: Handles HTTP responses from RPC servers. Parses for errors, then handles response. +:: For actions that require collating multiple RPC calls, uses req-card to call out +:: to RPC again if more information is required. +:: +++ handle-rpc-response + |= [=wire response=client-response:iris] + ^- (quip card _state) + ?. ?=(%finished -.response) `state + =* status status-code.response-header.response + :: handle error types: connection errors, RPC errors (in order) + :: + =^ conn-err state + (connection-error status) + ?^ conn-err + :_ state(connected.host-info %.n) + ~[(send-status [%disconnected ~]) (send-update [%| u.conn-err])] + :: + %+ handle-rpc-result wire + %- parse-result:rpc:bl + (get-rpc-response:bl response) +:: +++ connection-error + |= status=@ud + ^- [(unit error) _state] + ?+ status [`[%rpc-error ~] state] + %200 + [~ state] + %400 + [`[%bad-request status] state] + %401 + [`[%no-auth status] state(connected.host-info %.n)] + %502 + [`[%not-connected status] state(connected.host-info %.n)] + %504 + [`[%not-connected status] state(connected.host-info %.n)] + == +:: +++ handle-rpc-result + |= [=wire r=result:rpc-types] + ^- (quip card _state) + ?+ -.wire ~|("Unexpected HTTP response" !!) + %address-info + ?> ?=([%get-address-info *] r) + :_ state + ~[(send-update [%.y %address-info +.r])] + :: + %tx-info + ?> ?=([%get-tx-vals *] r) + :_ state + ~[(send-update [%.y %tx-info +.r])] + :: + %raw-tx + ?> ?=([%get-raw-tx *] r) + :_ state + ~[(send-update [%.y %raw-tx +.r])] + :: + %broadcast-tx + ?> ?=([%broadcast-tx *] r) + :_ state + ~[(send-update [%.y %broadcast-tx +.r])] + :: + %ping + ?> ?=([%get-block-info *] r) + :_ state(connected.host-info %.y, block.host-info block.r) + ?: =(block.host-info block.r) + ~[(send-status [%connected network.host-info block.r fee.r])] + ~[(send-status [%new-block network.host-info block.r fee.r blockhash.r blockfilter.r])] + == +:: +++ send-status + |= =status ^- card + %- ?: ?=(%new-block -.status) + ~&(>> "%new-block: {}" same) + same + [%give %fact ~[/clients] %btc-provider-status !>(status)] +:: +++ send-update + |= =update + ^- card + =+ c=[%give %fact ~[/clients] %btc-provider-update !>(update)] + ?: ?=(%.y -.update) +:: ~& >> "prov. update: {}" + c + ~& >> "prov. err: {}" + c +:: +++ is-whitelisted + |= user=ship ^- ? + |^ + ?| public.whitelist + =(our.bowl user) + ?&(kids.whitelist is-kid) + (~(has in users.whitelist) user) + in-group + == + ++ is-kid + =(our.bowl (sein:title our.bowl now.bowl user)) + ++ in-group + =/ gs ~(tap in groups.whitelist) + |- + ?~ gs %.n + ?: (~(is-member groupl bowl) user i.gs) + %.y + $(gs t.gs) + :: .^((unit group:g) %gx ;:(weld /=group-store=/groups p /noun)) + -- +:: +clean-client-list: remove clients who are no longer whitelisted +:: called after a whitelist change +:: +++ clean-client-list + ^- (quip card _state) + =/ to-kick=(set ship) + %- silt + %+ murn ~(tap in clients.host-info) + |= c=ship ^- (unit ship) + ?:((is-whitelisted c) ~ `c) + :_ state(clients.host-info (~(dif in clients.host-info) to-kick)) + %+ turn ~(tap in to-kick) + |=(c=ship [%give %kick ~[/clients] `c]) +:: +++ is-client + |= user=ship ^- ? + (~(has in clients.host-info) user) +:: +++ start-ping-timer + |= interval=@dr ^- card + [%pass /ping-timer %arvo %b %wait (add now.bowl interval)] +:: +++ do-ping + ^- card + =/ act=action [%ping ~] + :* %pass /ping/[(scot %da now.bowl)] %agent + [our.bowl %btc-provider] %poke + %btc-provider-action !>(act) + == +-- diff --git a/pkg/arvo/app/btc-wallet.hoon b/pkg/arvo/app/btc-wallet.hoon new file mode 100644 index 0000000000..bd46aad8d7 --- /dev/null +++ b/pkg/arvo/app/btc-wallet.hoon @@ -0,0 +1,1161 @@ +:: btc-wallet +:: +:: Scrys +:: x/scanned: (list xpub) of all scanned wallets +:: x/balance/xpub: balance (in sats) of wallet +/- *btc-wallet, bp=btc-provider, file-server, launch-store +/+ dbug, default-agent, bl=btc, bc=bitcoin, bip32 +|% +++ defaults + |% + ++ params + :* batch-size=20 + fam-limit=10 + piym-limit=3 + == + ++ confs 6 + ++ fee 100 + -- +:: ++$ versioned-state + $% state-0 + state-1 + == +:: ++$ state-0 + $: %0 + prov=(unit provider) + walts=(map xpub:bc walt-0) + =btc-state + =history + curr-xpub=(unit xpub:bc) + =scans + =params + feybs=(map ship sats) + =piym + =poym + ahistorical-txs=(set txid) + == +:: ++$ state-1 + $: %1 + prov=(unit provider) + walts=(map xpub:bc walt) + =btc-state + =history + curr-xpub=(unit xpub:bc) + =scans + =params + feybs=(map ship sats) + =piym + =poym + ahistorical-txs=(set txid) + == +:: ++$ card card:agent:gall +:: +-- +=| state-1 +=* state - +%- agent:dbug +^- agent:gall +=< +|_ =bowl:gall ++* this . + def ~(. (default-agent this %|) bowl) + hc ~(. +> bowl) +:: +++ on-init +^- (quip card _this) + ~& > '%btc-wallet initialized' + =/ file + [%file-server-action !>([%serve-dir /'~btc' /app/btc-wallet %.n %.y])] + =/ tile + :- %launch-action + !> :+ %add + %btc-wallet + [[%custom `'/~btc' `'/~btc/img/tile.svg'] %.y] + =/ warning [%settings-event !>([%put-entry %btc-wallet %warning %b %.y])] + =/ currency + [%settings-event !>([%put-entry %btc-wallet %currency %s 'USD'])] + :- :~ [%pass /btc-wallet-server %agent [our.bowl %file-server] %poke file] + [%pass /btc-wallet-tile %agent [our.bowl %launch] %poke tile] + [%pass /warn %agent [our.bowl %settings-store] %poke warning] + [%pass /warn %agent [our.bowl %settings-store] %poke currency] + == + %_ this + state + :* %1 + ~ + *(map xpub:bc walt) + *^btc-state + *^history + ~ + *^scans + params:defaults + *(map ship sats) + *^piym + *^poym + ~ + == + == +:: +++ on-save + ^- vase + !>(state) +:: +++ on-load + |= old-state=vase + ^- (quip card _this) + ~& > '%btc-wallet recompiled' + =/ ver !<(versioned-state old-state) + =| cards=(list card) + |- + ?- -.ver + %1 + [cards this(state ver)] + :: + %0 + =/ new-walts=(map xpub:bc walt) + %- ~(run by walts.ver) + |= old-walt=walt-0 + ^- walt + old-walt(wilt +6:wilt.old-walt) + $(ver [%1 +.ver(walts new-walts)]) + == +:: +++ on-poke + |= [=mark =vase] + ^- (quip card _this) + =^ cards state + ?+ mark (on-poke:def mark vase) + %btc-wallet-command + ?> =(our.bowl src.bowl) + (handle-command:hc !<(command vase)) + :: + %btc-wallet-action + ?< =(our.bowl src.bowl) + (handle-action:hc !<(action vase)) + :: + %btc-wallet-internal + ?> =(our.bowl src.bowl) + (handle-internal:hc !<(internal vase)) + == + [cards this] +++ on-peek + |= pax=path + ^- (unit (unit cage)) + ?+ pax (on-peek:def pax) + [%x %configured ~] + =/ provider=json + ?~ prov ~ + [%s (scot %p host.u.prov)] + =/ result=json + %- pairs:enjs:format + :~ [%'provider' provider] + [%'hasWallet' b+?=(^ walts)] + == + ``json+!>(result) + :: + [%x %scanned ~] + ``noun+!>(scanned-wallets) + :: + [%x %balance @ ~] + ``noun+!>((balance:hc (xpub:bc +>-.pax))) + == +++ 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)) + :: + %json + ?+ wire `state + [%check-payee @ ~] + =/ who (slav %p i.t.wire) + :_ state + :~ [%give %fact ~[/all] cage.sign] + [%pass wire %agent [who %btc-wallet] %leave ~] + == + :: + [%permitted @ ~] + =/ who (slav %p i.t.wire) + :_ state + :~ [%give %fact ~[/all] cage.sign] + [%pass wire %agent [who %btc-provider] %leave ~] + == + == + == + [cards this] + == +:: +++ on-watch + |= =path + ^- (quip card _this) + ?+ path (on-watch:def path) + [%check-payee @ ~] + =/ who (slav %p i.t.path) + ?> =(who our.bowl) + =/ response=json + %+ frond:enjs:format 'checkPayee' + %- pairs:enjs:format + :~ ['hasWallet' b+?=(^ curr-xpub)] + ['payee' (ship:enjs:format our.bowl)] + == + :_ this + [%give %fact ~ %json !>(response)]~ + :: + [%all ~] + ?> (team:title our.bowl src.bowl) + :_ this + [give-initial:hc]~ + == +:: +++ on-leave on-leave:def +++ on-arvo on-arvo:def +++ on-fail on-fail:def +-- +|_ =bowl:gall +++ handle-command + |= comm=command + ^- (quip card _state) + ?> (team:title our.bowl src.bowl) + ?- -.comm + %set-provider + |^ + ?~ provider.comm + ?~ prov `state + :_ state(prov ~) + :~ (leave-provider host.u.prov) + (give-update %change-provider ~) + == + :_ state(prov [~ u.provider.comm %.n]) + ?~ prov + [(watch-provider u.provider.comm)]~ + :~ (leave-provider host.u.prov) + (watch-provider u.provider.comm) + (give-update %change-provider `[u.provider.comm %.n]) + == + :: + ++ watch-provider + |= who=@p + ^- card + :* %pass /set-provider/[(scot %p who)] %agent [who %btc-provider] + %watch /clients + == + ++ leave-provider + |= who=@p + ^- card + :* %pass /set-provider/[(scot %p who)] %agent [who %btc-provider] + %leave ~ + == + -- + :: + %check-provider + =/ pax /permitted/(scot %p provider.comm) + :_ state + [%pass pax %agent [provider.comm %btc-provider] %watch pax]~ + :: + %check-payee + =/ pax /check-payee/(scot %p payee.comm) + :_ state + [%pass pax %agent [payee.comm %btc-wallet] %watch pax]~ + :: + %set-current-wallet + (set-curr-xpub xpub.comm) + :: + %add-wallet + ?~ (~(has by walts) xpub.comm) + ((slog ~[leaf+"xpub already in wallet"]) `state) + =/ w=walt (from-xpub:bl +.comm) + =. walts (~(put by walts) xpub.comm w) + =^ c1 state (init-batches xpub.comm (dec max-gap.w)) + =^ c2 state (set-curr-xpub xpub.comm) + [(weld c1 c2) state] + :: + %delete-wallet + =* cw curr-xpub.state + =? cw ?&(?=(^ cw) =(u.cw xpub.comm)) + ~ + =. scans (~(del by scans) [xpub.comm %0]) + =. scans (~(del by scans) [xpub.comm %1]) + =. walts (~(del by walts) xpub.comm) + =. history + %- ~(rep by history) + |= [[=txid =hest] out=_history] + ?: =(xpub.hest xpub.comm) + (~(del by out) txid) + out + :_ state + [give-initial]~ + :: + %init-payment-external + ?: is-broadcasting + %- (slog ~[leaf+"broadcasting a transaction"]) + [[(give-update %error %tx-being-signed)]~ state] + ?~ curr-xpub ~|("btc-wallet: no curr-xpub set" !!) + ?: (is-dust value.comm address.comm) + %- (slog ~[leaf+"sending dust"]) + [[(give-update %error %no-dust)]~ state] + :: + =/ uw (~(get by walts) u.curr-xpub) + ?: ?|(?=(~ uw) ?!(scanned.u.uw)) + ~|("no wallet with xpub or wallet not scanned yet" !!) + =/ [tb=(unit txbu) chng=(unit sats)] + %~ with-change sut:bl + [u.uw eny.bowl block.btc-state ~ feyb.comm ~[[address.comm value.comm ~]]] + ?~ tb + %- (slog ~[leaf+"insufficient balance or not enough confirmed balance"]) + [[(give-update %error %insufficient-balance)]~ state] + =^ tb=(unit txbu) state + ?~ chng `state + =/ [addr=address =idx w=walt] + ~(nixt-address wad:bl u.uw %1) + :- `(~(add-output txb:bl u.tb) addr u.chng `(~(hdkey wad:bl w %1) idx)) + state(walts (~(put by walts) u.curr-xpub w)) + =/ po=^poym ?~(tb [~ ~] [tb note.comm]) + :_ state(poym po) + ?~ tb ~ + %+ turn txis.u.tb + |= =txi + (poke-provider %raw-tx txid.utxo.txi) + :: + :: overwrites any payment being built in poym + :: + %init-payment + ?: =(src.bowl payee.comm) + %- (slog ~[leaf+"can't pay ourselves"]) + [[(give-update %error %cant-pay-ourselves)]~ state] + ?: ?=(%pawn (clan:title payee.comm)) + %- (slog ~[leaf+"no comets"]) + [[(give-update %error %no-comets)]~ state] + ?: is-broadcasting + %- (slog ~[leaf+"broadcasting a transaction"]) + [[(give-update %error %tx-being-signed)]~ state] + :_ state(poym [~ note.comm], feybs (~(put by feybs) payee.comm feyb.comm)) + ~[(poke-peer payee.comm [%gen-pay-address value.comm note.comm])] + :: + %broadcast-tx + ?~ prov ~|("Provider not connected" !!) + =+ signed=(from-cord:hxb:bc txhex.comm) + =/ tx-match=? + ?~ txbu.poym %.n + =((get-id:txu:bc (decode:txu:bc signed)) ~(get-txid txb:bl u.txbu.poym)) + :- ?. tx-match + %- (slog leaf+"txid didn't match txid in wallet") + [(give-update %error %broadcast-fail)]~ + ~[(poke-provider [%broadcast-tx signed])] + ?. tx-match state + ?~ txbu.poym state + state(signed-tx.u.txbu.poym `signed) + :: + %gen-new-address + ?~ curr-xpub ~|("btc-wallet: no curr-xpub set" !!) + =/ uw=(unit walt) (~(get by walts) u.curr-xpub) + ?: ?|(?=(~ uw) ?!(scanned.u.uw)) + ~|("no wallet with xpub or wallet not scanned yet" !!) + =/ [addr=address =idx w=walt] + ~(gen-address wad:bl u.uw %0) + :_ state(walts (~(put by walts) u.curr-xpub w)) + [(give-update %new-address addr)]~ + == +:: +++ handle-action + |= act=action + ^- (quip card _state) + ?- -.act + :: comets can't pay (could spam address requests) + :: reuses payment address for ship if ship in piym already + :: + %gen-pay-address + ~| "no comets" + ?< ?=(%pawn (clan:title src.bowl)) + ?~ curr-xpub ~|("btc-wallet: no curr-xpub set" !!) + |^ + =^ cards state reuse-address + ?^ cards [cards state] :: if cards returned, means we already have an address + =+ f=(fam:bl our.bowl now.bowl src.bowl) + =+ n=(~(gut by num-fam.piym) f 0) + ?: (gte n fam-limit.params) + ~|("More than {} addresses for moons + planet" !!) + =. state state(num-fam.piym (~(put by num-fam.piym) f +(n))) + =^ a=address state + (generate-address u.curr-xpub %0) + :- ~[(poke-peer src.bowl [%give-pay-address a value.act])] + %= state + ps.piym + %+ ~(put by ps.piym) src.bowl + [~ u.curr-xpub a src.bowl value.act note.act] + == + :: + ++ generate-address + |= [=xpub:bc =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:bl u.uw chyg) + [addr state(walts (~(put by walts) xpub w))] + :: + ++ reuse-address + ^- (quip card _state) + =* payer src.bowl + =+ p=(~(get by ps.piym) payer) + ?~ p `state + ?^ pend.u.p ~|("%gen-address: {} already has pending payment to us" !!) + =+ newp=u.p(value value.act) + :_ state(ps.piym (~(put by ps.piym) payer newp)) + ~[(poke-peer payer [%give-pay-address address.newp value.act])] + -- + :: + %give-pay-address + ?: =(src.bowl our.bowl) ~|("Can't pay ourselves" !!) + ?: is-broadcasting ~|("Broadcasting a transaction" !!) + ?~ curr-xpub ~|("btc-wallet-hook: no curr-xpub set" !!) + ?: (is-dust value.act address.act) + %- (slog ~[leaf+"sending dust"]) + [[(give-update %error %no-dust)]~ 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 ~[[address.act value.act ~]]) + =/ po=^poym ?~(tb [~ ~] [tb note.poym]) + :_ state(poym po) + ?~ tb [(give-update %error %insufficient-balance)]~ + %+ turn txis.u.tb + |=(=txi (poke-provider [%raw-tx txid.utxo.txi])) + :: + ++ generate-txbu + |= [=xpub:bc 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:bl + [u.uw eny.bowl block.btc-state payee feyb txos] + ?~ tb ((slog ~[leaf+"insufficient balance or not enough confirmed balance"]) [tb state]) + :: if no change, return txbu; else add change output to txbu + :: + ?~ chng [tb state] + =/ [addr=address =idx w=walt] + ~(nixt-address wad:bl u.uw %1) + :- `(~(add-output txb:bl u.tb) addr u.chng `(~(hdkey wad:bl w %1) idx)) + state(walts (~(put by walts) xpub w)) + -- + :: + :: %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 [%tx-info txid.act])] + :: + ++ piym-matches + |= p=payment + ?& =(payer.p src.bowl) + =(value.p value.act) + == + :: + ++ update-pend-piym + |= [txid=hexb p=payment] + ^- _state + ?~ pend.p ~|("update-pend-piym: no pending payment" !!) + %= state + ps.piym (~(put by ps.piym) payer.p p) + pend.piym (~(put by pend.piym) txid p) + == + -- + == +:: +++ handle-internal + |= intr=internal + ^- (quip card _state) + ?- -.intr + %add-poym-raw-txi + |^ + ?> =(src.bowl our.bowl) + ?~ txbu.poym `state + =. txis.u.txbu.poym + (update-poym-txis txis.u.txbu.poym +.intr) + :_ state + =+ pb=~(to-psbt txb:bl u.txbu.poym) + ?~ pb ~ + =+ vb=~(vbytes txb:bl u.txbu.poym) + =+ fee=~(fee txb:bl u.txbu.poym) + ~& >> "{} vbytes, {<(div fee vb)>} sats/byte, {} sats fee" + %- (slog [%leaf "PSBT: {}"]~) + [(give-update [%psbt u.pb fee])]~ + :: update outgoing payment with a rawtx, if the txid is in poym's txis + :: + ++ update-poym-txis + |= [txis=(list txi) txid=hexb rawtx=hexb] + ^- (list txi) + =| i=@ + |- ?: (gte i (lent txis)) txis + =/ ith=txi (snag i txis) + =? txis =(txid txid.utxo.ith) + (snap txis i `txi`ith(rawtx `rawtx)) + $(i +(i)) + -- + :: delete an incoming/outgoing payment when we see it included in a tx + :: + %close-pym + ?> =(src.bowl our.bowl) + |^ + =^ cards state + ?. included.ti.intr + `state + ?: (~(has by pend.piym) txid.ti.intr) + (piym-to-history ti.intr) + ?: (poym-has-txid txid.ti.intr) + (poym-to-history ti.intr) + `state + =^ cards2 state + (handle-tx-info ti.intr) + :_ state + (weld cards cards2) + :: + ++ poym-has-txid + |= txid=hexb + ^- ? + ?~ txbu.poym %.n + ?~ signed-tx.u.txbu.poym %.n + =(txid (get-id:txu:bc (decode:txu:bc u.signed-tx.u.txbu.poym))) + :: - 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 history + :: + ++ poym-to-history + |= ti=info:tx + ^- (quip card _state) + |^ + ?~ txbu.poym `state + ?~ signed-tx.u.txbu.poym `state + ?. (poym-has-txid txid.ti) + `state + =+ vout=(get-vout txos.u.txbu.poym) + ?~ vout ~|("poym-to-history: poym should always have an output" !!) + =/ new-hest=hest + (mk-hest ti xpub.u.txbu.poym our.bowl payee.u.txbu.poym u.vout note.poym) + :- [(give-update %new-tx new-hest)]~ + %= state + poym [~ ~] + history (~(put by history) txid.ti new-hest) + == + :: + ++ get-vout + |= txos=(list txo) + ^- (unit @ud) + =| idx=@ud + |- ?~ txos ~ + ?~ hk.i.txos `idx + $(idx +(idx), txos t.txos) + -- + :: - 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 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) + =/ new-hest (mk-hest ti xpub.u.pay payer.u.pay `our.bowl u.vout note.u.pay) + =. state (del-all-piym txid.ti payer.u.pay) + :- [(give-update %new-tx new-hest)]~ + %= state + history (~(put by history) txid.ti new-hest) + == + :: + ++ 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)) + == + -- + :: + ++ mk-hest + |= $: ti=info:tx + =xpub:bc + payer=ship + payee=(unit ship) + vout=@ud + note=(unit @t) + == + ^- hest + :* xpub + txid.ti + confs.ti + recvd.ti + (turn inputs.ti |=(i=val:tx [i `payer])) + %+ turn outputs.ti + |= o=val:tx + ?: =(pos.o vout) :: check whether this is the output that went to payee + [o payee] + [o `payer] + note + == + -- + :: + %fail-broadcast-tx + ?> =(src.bowl our.bowl) + ~& >>> "%fail-broadcast-tx" + :_ state(poym [~ ~]) + [(give-update %error %broadcast-fail)]~ + :: + %succeed-broadcast-tx + ?> =(src.bowl our.bowl) + ~& > "%succeed-broadcast-tx" + :_ state + :- (give-update %broadcast-success ~) + ?~ prov ~ + :- (poke-provider [%tx-info txid.intr]) + ?~ txbu.poym ~ + ?~ payee.u.txbu.poym ~ + :_ ~ + %- poke-peer + :* u.payee.u.txbu.poym + %expect-payment + txid.intr + value:(snag 0 txos.u.txbu.poym) + == + == +:: +:: +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 +:: - if provider's network doesn't match network in our state, leave +:: +++ handle-provider-status + |= s=status:bp + ^- (quip card _state) + |^ + =^ cards state + ?~ prov `state + ?. =(host.u.prov src.bowl) `state + ?- -.s + %new-block + (on-connected u.prov network.s block.s fee.s `blockhash.s `blockfilter.s) + :: + %connected + (on-connected u.prov network.s block.s fee.s ~ ~) + :: + %disconnected + `state(prov `u.prov(connected %.n)) + == + :_ state + :* (give-update %btc-state btc-state) + (give-update %change-provider prov) + cards + == + :: + ++ on-connected + |= $: p=provider + =network + block=@ud + fee=(unit sats) + blockhash=(unit hexb) + blockfilter=(unit hexb) + == + ^- (quip card _state) + :_ %_ state + prov `p(connected %.y) + btc-state [block fee now.bowl] + == + ?: ?|(?!(connected.p) (lth block.btc-state block)) + ;: weld + (retry-pend-piym network) + (retry-poym network) + (retry-addrs network) + (retry-txs network) + (retry-scans network) + retry-ahistorical-txs + == + ;: weld +:: (retry-addrs network) + retry-ahistorical-txs + (retry-pend-piym network) + == + :: + ++ retry-ahistorical-txs + ^- (list card) + %+ turn ~(tap in ahistorical-txs) + |= =txid + (poke-provider [%tx-info txid]) + + :: + ++ retry-scans + |= =network + ^- (list card) + %- zing + %+ murn ~(tap by scans) + |= [[=xpub:bc =chyg] =batch] + ?. =(network network:(~(got by walts) xpub)) ~ + `-:(req-scan batch xpub chyg) + :: +retry-addrs: get info on addresses with unconfirmed UTXOs + :: + ++ retry-addrs + |= =network + ^- (list card) + %- zing + %+ murn ~(val by walts) + |= w=walt + ?. =(network network.w) ~ + ^- (unit (list card)) + :- ~ + %+ turn ~(tap by wach.w) + |= [a=address *] + (poke-provider [%address-info a]) + :: +retry-txs: get info on txs without enough confirmations + :: + ++ retry-txs + |= =network + ^- (list card) + %+ murn ~(tap by history) + |= [=txid =hest] + =/ w (~(get by walts) xpub.hest) + ?~ w ~ + ?. =(network network.u.w) ~ + ?: (gte confs.hest confs.u.w) ~ + `(poke-provider [%tx-info txid]) + :: + ++ retry-poym + |= =network + ^- (list card) + ?~ txbu.poym ~ + =/ w (~(get by walts) xpub.u.txbu.poym) + ?~ w ~ + ?. =(network network.u.w) ~ + %+ weld + ?~ signed-tx.u.txbu.poym ~ + ~[(poke-provider [%broadcast-tx u.signed-tx.u.txbu.poym])] + %+ turn txis.u.txbu.poym + |= =txi + (poke-provider [%raw-tx ~(get-txid txb:bl u.txbu.poym)]) + :: +retry-pend-piym: check whether txids in pend-piym are in mempool + :: + ++ retry-pend-piym + |= =network + ^- (list card) + %+ murn ~(tap by pend.piym) + |= [=txid p=payment] + =/ w (~(get by walts) xpub.p) + ?~ w ~ + ?. =(network network.u.w) ~ + `(poke-provider [%tx-info txid]) + -- +:: +++ handle-provider-update + |= upd=update:bp + ^- (quip card _state) + ?~ prov `state + ?. =(host.u.prov src.bowl) `state + ?. ?=(%.y -.upd) `state + ?- -.p.upd + %address-info + :: located in the helper in Scan Logic to keep all of that unified + :: + (handle-address-info address.p.upd utxos.p.upd used.p.upd) + :: + %tx-info + =/ [cards=(list card) sty=state-1] + (handle-tx-info info.p.upd) + :_ sty + [(poke-internal [%close-pym info.p.upd]) cards] + :: + %raw-tx + :_ state + ~[(poke-internal [%add-poym-raw-txi +.p.upd])] + :: + %broadcast-tx + ?~ txbu.poym `state + ?. =(~(get-txid txb:bl u.txbu.poym) txid.p.upd) + `state + :_ state + ?: ?|(broadcast.p.upd included.p.upd) + ~[(poke-internal [%succeed-broadcast-tx txid.p.upd])] + :~ (poke-internal [%fail-broadcast-tx txid.p.upd]) + (give-update %cancel-tx txid.p.upd) + == + == +:: +++ handle-tx-info + |= ti=info:tx + ^- (quip card _state) + |^ + =/ h (~(get by history) txid.ti) + =. ahistorical-txs (~(del in ahistorical-txs) txid.ti) + =/ our-inputs=(set address) + %- silt + %+ skim + %+ turn inputs.ti + |=(=val:tx address.val) + is-our-address + =/ our-outputs=(set address) + %- silt + %+ skim + %+ turn outputs.ti + |=(=val:tx address.val) + is-our-address + =/ our-addrs=(set address) :: all our addresses in inputs/outputs of tx + (~(uni in our-inputs) our-outputs) + :: + =/ addr-info-cards=(list card) + %+ turn ~(tap in our-addrs) + |= a=address + ^- card + (poke-provider [%address-info a]) + ?: =(0 ~(wyt in our-addrs)) `state + =/ =xpub + xpub.w:(need (address-coords:bl (snag 0 ~(tap in our-addrs)) ~(val by walts))) + ?~ h :: addresses in wallets, but tx not in history + =/ new-hest=hest (mk-hest xpub our-inputs our-outputs) + =. history (~(put by history) txid.ti new-hest) + :_ state + :_ :_ addr-info-cards + (give-update %new-tx new-hest) + (give-update %balance current-balance) + ?. included.ti :: tx in history, but not in mempool/blocks + :_ state(history (~(del by history) txid.ti)) + :_ :_ addr-info-cards + (give-update %cancel-tx txid.ti) + (give-update %balance current-balance) + =/ new-hest u.h(confs confs.ti, recvd recvd.ti) + =. history (~(put by history) txid.ti new-hest) + :_ state + :_ :_ addr-info-cards + (give-update %new-tx new-hest) + (give-update %balance current-balance) + :: + ++ mk-hest + :: has tx-info + |= [=xpub:bc our-inputs=(set address) our-outputs=(set address)] + ^- hest + :* xpub + txid.ti + confs.ti + recvd.ti + (turn inputs.ti |=(v=val:tx (is-our-ship our-inputs v))) + (turn outputs.ti |=(v=val:tx (is-our-ship our-outputs v))) + ~ + == + :: + ++ is-our-ship + |= [as=(set address) v=val:tx] + ^- [=val:tx s=(unit ship)] + [v ?:((~(has in as) address.v) `our.bowl ~)] + :: + ++ is-our-address + |=(a=address ?=(^ (address-coords:bl a ~(val by walts)))) + -- +++ set-curr-xpub + |= =xpub + ^- (quip card _state) + ?~ (find ~[xpub] scanned-wallets) `state + =. curr-xpub `xpub + :_ state + [give-initial]~ +:: +:: +:: 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) + =/ ac (address-coords:bl address ~(val by walts)) + ?~ ac + `state + =/ [w=walt =chyg =idx] u.ac + =. walts + %+ ~(put by walts) xpub.w + %+ ~(update-address wad:bl w chyg) + address + [used chyg idx utxos] + :: if transactions haven't made it into history, request transaction info + :: + =^ cards=(list card) ahistorical-txs + %+ roll ~(tap in utxos) + |= [u=utxo cad=(list card) ah=(set txid)] + ^- [(list card) (set txid)] + ?: (~(has by history) txid.u) + [cad ah] + :- [(poke-provider [%tx-info txid.u]) cad] + (~(put by ah) txid.u) + :: if the wallet+chyg is being scanned, update the scan batch + :: + =/ b (~(get by scans) [xpub.w chyg]) + ?~ b + [cards state] + =. scans + (del-scanned u.b(has-used ?|(used has-used.u.b)) xpub.w chyg idx) + ?: empty:(scan-status xpub.w chyg) + =^ scan-cards=(list card) state + (check-scan xpub.w) + [(weld scan-cards cards) state] + :: + [cards 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:bc =chyg] + ^- (quip card _state) + =/ w=walt (~(got by walts) xpub) + =/ as=(list [address [? ^chyg idx (set utxo)]]) + %+ turn ~(tap in todo.b) + |=(=idx [(~(mk-address wad:bl w chyg) idx) [%.n chyg idx *(set utxo)]]) + =. w + |- ?~ as w + $(as t.as, w (~(update-address wad:bl 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:bc =chyg] + ^- [empty=? done=?] + =/ b=batch (~(got by scans) [xpub chyg]) + =/ empty=? =(0 ~(wyt in todo.b)) + :- empty + ?&(empty ?!(has-used.b)) +:: +++ init-batches + |= [=xpub:bc endpoint=idx] + ^- (quip card _state) + =/ b=batch + [(silt (gulf 0 endpoint)) endpoint %.n] + =^ 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:bc =chyg] + ^- (quip card _state) + =/ b=batch (~(got by scans) xpub chyg) + =/ s (scan-status xpub chyg) + ?. ?&(empty.s ?!(done.s)) + `state + =/ w=walt (~(got by walts) xpub) + =/ newb=batch + :* (silt (gulf +(endpoint.b) (add endpoint.b max-gap.w))) + (add endpoint.b max-gap.w) + %.n + == + (req-scan newb xpub chyg) +:: +del-scanned: delete scanned idxs +:: +++ del-scanned + |= [b=batch =xpub:bc =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:bc] + ^- (quip card _state) + =/ w=walt (~(got by walts) xpub) + =. scans (~(del by scans) [xpub %0]) + =. scans (~(del by scans) [xpub %1]) + %- (slog ~[leaf+"Scanned xpub {}"]) + =. state state(walts (~(put by walts) xpub w(scanned %.y))) + (set-curr-xpub xpub) +:: +check-scan: initiate a scan if one hasn't started +:: check status of scan if one is running +:: +++ check-scan + |= =xpub:bc + ^- (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) state + (bump-batch xpub %0) + =^ cards1=(list card) state + (bump-batch xpub %1) + [(weld cards0 cards1) state] +:: +:: +:: +++ poke-provider + |= [act=action:bp] + ^- card + ?~ prov ~|("provider not set" !!) + :* %pass /[(scot %da now.bowl)] + %agent [host.u.prov %btc-provider] + %poke %btc-provider-action !>([act]) + == +:: +++ poke-peer + |= [target=ship act=action] + ^- card + :* %pass /[(scot %da now.bowl)] %agent + [target %btc-wallet] %poke + %btc-wallet-action !>(act) + == +++ poke-internal + |= [intr=internal] + ^- card + :* %pass /[(scot %da now.bowl)] %agent + [our.bowl %btc-wallet] %poke + %btc-wallet-internal !>(intr) + == +:: +++ give-update + |= upd=update + ^- card + [%give %fact ~[/all] %btc-wallet-update !>(upd)] +:: +++ give-initial + ^- card + =^ a=(unit address) state + ?~ curr-xpub `state + =/ uw=(unit walt) (~(get by walts) u.curr-xpub) + ?: ?|(?=(~ uw) ?!(scanned.u.uw)) + ~|("no wallet with xpub or wallet not scanned yet" !!) + =/ [addr=address =idx w=walt] + ~(gen-address wad:bl u.uw %0) + [`addr state(walts (~(put by walts) u.curr-xpub w))] + =/ initial=update + :* %initial + prov + curr-xpub + current-balance + current-history + btc-state + a + == + (give-update initial) +:: +++ is-dust + |= [=sats =address] + ^- ? + %+ lth sats + (mul 3 (input-weight:bc (get-bipt:adr:bc address))) +:: +++ is-broadcasting + ^- ? + ?~ txbu.poym %.n + ?=(^ signed-tx.u.txbu.poym) +:: +:: Scry Helpers +:: +++ scanned-wallets + ^- (list xpub:bc) + %+ murn ~(tap by walts) + |= [=xpub:bc w=walt] + ^- (unit xpub:bc) + ?:(scanned.w `xpub ~) +:: +++ balance + |= =xpub:bc + ^- (unit [sats sats]) + =/ w (~(get by walts) xpub) + ?~ w ~ + =/ values=(list [confirmed=sats unconfirmed=sats]) + %+ turn ~(val by wach.u.w) + |= =addi ^- [sats sats] + %+ roll + %+ turn ~(tap by utxos.addi) + |= =utxo + ^- [sats sats] + ?: (~(spendable sut:bl [u.w eny.bowl block.btc-state ~ 0 ~]) utxo) + [value.utxo 0] + [0 value.utxo] + |= [[a=sats b=sats] out=[p=sats q=sats]] + [(add a p.out) (add b q.out)] + :- ~ + %+ roll values + |= [[a=sats b=sats] out=[p=sats q=sats]] + [(add a p.out) (add b q.out)] + :: +:: +++ current-balance + ^- (unit [sats sats]) + ?~ curr-xpub ~ + (balance u.curr-xpub) +:: +++ current-history + ^- ^history + ?~ curr-xpub ~ + %- ~(gas by *^history) + %+ skim ~(tap by history) + |= [txid =hest] + =(u.curr-xpub xpub.hest) +-- diff --git a/pkg/arvo/app/btc-wallet/img/tile.svg b/pkg/arvo/app/btc-wallet/img/tile.svg new file mode 100644 index 0000000000..ac70f15314 --- /dev/null +++ b/pkg/arvo/app/btc-wallet/img/tile.svg @@ -0,0 +1,4 @@ + + + + diff --git a/pkg/arvo/app/btc-wallet/index.html b/pkg/arvo/app/btc-wallet/index.html new file mode 100644 index 0000000000..fefd5f7b18 --- /dev/null +++ b/pkg/arvo/app/btc-wallet/index.html @@ -0,0 +1,31 @@ + + + + Wallet + + + + + + + + + +
+
+ + + + + diff --git a/pkg/arvo/app/chat-cli.hoon b/pkg/arvo/app/chat-cli.hoon index b297103502..c13141a96d 100644 --- a/pkg/arvo/app/chat-cli.hoon +++ b/pkg/arvo/app/chat-cli.hoon @@ -169,7 +169,7 @@ :: %fact ?+ p.cage.sign ~|([dap.bowl %bad-sub-mark wire p.cage.sign] !!) - %graph-update-1 + %graph-update-2 %- on-graph-update:tc !<(update:graph q.cage.sign) == @@ -401,12 +401,16 @@ :: +read-post: add envelope to state and show it to user :: ++ read-post - |= [=target =index:post =post:post] + |= [=target =index:post =maybe-post:graph] ^- (quip card _session) - :- (show-post:sh-out target post) - %_ session - history [[target index] history.session] - count +(count.session) + ?- -.maybe-post + %| [~ session] + %& + :- (show-post:sh-out target p.maybe-post) + %_ session + history [[target index] history.session] + count +(count.session) + == == :: ++ notice-remove @@ -734,7 +738,8 @@ :: ?. (is-chat-graph target) [[(note:sh-out "no such chat")]~ put-ses] - =. viewing (~(put in viewing) target) + =. audience target + =. viewing (~(put in viewing) target) =^ cards state ?: (~(has by bound) target) [~ state] @@ -758,15 +763,15 @@ ::TODO move creation into lib? %^ act %out-message %graph-push-hook - :- %graph-update-1 + :- %graph-update-2 !> ^- update:graph :- now.bowl :+ %add-nodes audience %- ~(put by *(map index:post node:graph)) :- ~[now.bowl] :_ *internal-graph:graph - ^- post:post - [our-self ~[now.bowl] now.bowl [msg]~ ~ ~] + ^- maybe-post:graph + [%& `post:post`[our-self ~[now.bowl] now.bowl [msg]~ ~ ~]] :: +eval: run hoon, send code and result as message :: :: this double-virtualizes and clams to disable .^ for security reasons @@ -890,10 +895,12 @@ =/ =uid:post (snag index history) =/ =node:graph (got-node:libgraph uid) =. audience resource.uid + ?: ?=(%| -.post.node) + [~ state] :_ put-ses ^- (list card) :~ (print:sh-out ['?' ' ' number]) - (effect:sh-out ~(render-activate mr resource.uid post.node)) + (effect:sh-out ~(render-activate mr resource.uid p.post.node)) prompt:sh-out == -- diff --git a/pkg/arvo/app/chat-hook.hoon b/pkg/arvo/app/chat-hook.hoon index 906e30b4ec..ce44a73ea5 100644 --- a/pkg/arvo/app/chat-hook.hoon +++ b/pkg/arvo/app/chat-hook.hoon @@ -154,7 +154,7 @@ ++ poke-graph-store |= =update:graph-store ^- card - (poke-our %graph-store %graph-update-1 !>(update)) + (poke-our %graph-store %graph-update-2 !>(update)) :: ++ nobody ^- @p diff --git a/pkg/arvo/app/chat-store.hoon b/pkg/arvo/app/chat-store.hoon index 782cf0422a..9406360b47 100644 --- a/pkg/arvo/app/chat-store.hoon +++ b/pkg/arvo/app/chat-store.hoon @@ -1,334 +1,28 @@ -:: chat-store [landscape]: +:: chat-store [landscape]: deprecated :: -:: data store that holds linear sequences of chat messages -:: -/- *group, store=chat-store -/+ default-agent, verb, dbug, group-store, - graph-store, resource, *migrate, grpl=group, mdl=metadata -~% %chat-store-top ..part ~ +/- store=chat-store +/+ default-agent |% +$ card card:agent:gall -+$ versioned-state - $% state-0 - state-1 - state-2 - state-3 - state-4 - == -:: -+$ state-0 [%0 =inbox:store] -+$ state-1 [%1 =inbox:store] -+$ state-2 [%2 =inbox:store] -+$ state-3 [%3 =inbox:store] -+$ state-4 [%4 =inbox:store] -+$ admin-action - $% [%trim ~] - [%migrate-graph ~] - == -- :: -=| state-4 -=* state - -:: -%- agent:dbug -%+ verb | ^- agent:gall -=< - ~% %chat-store-agent-core ..peek-x-envelopes ~ - |_ =bowl:gall - +* this . - chat-core +> - cc ~(. chat-core bowl) - def ~(. (default-agent this %|) bowl) - :: - ++ on-init on-init:def - ++ on-save !>(state) - ++ on-load - |= old-vase=vase - ^- (quip card _this) - |^ - =/ old !<(versioned-state old-vase) - =| cards=(list card) - |- - ^- (quip card _this) - ?- -.old - %4 [cards this(state old)] - :: - %3 - =. cards :_(cards (poke-admin %migrate-graph ~)) - $(old [%4 inbox.old]) - :: - %2 - =/ =inbox:store - (migrate-path-map:group-store inbox.old) - =/ kick-paths - %~ tap in - %+ roll - ~(val by sup.bowl) - |= [[=ship sub=path] subs=(set path)] - ^- (set path) - ?. ?=([@ @ *] sub) - subs - ?. &(=(%mailbox i.sub) =('~' i.t.sub)) - subs - (~(put in subs) sub) - =? cards ?=(^ kick-paths) - :_ cards - [%give %kick kick-paths ~] - $(old [%3 inbox]) - :: - ?(%0 %1) $(old (old-to-2 inbox.old)) - :: - == - ++ poke-admin - |= =admin-action - ^- card - [%pass / %agent [our dap]:bowl %poke noun+!>(admin-action)] - :: - ++ old-to-2 - |= =inbox:store - ^- state-2 - :- %2 - %- ~(run by inbox) - |= =mailbox:store - ^- mailbox:store - [config.mailbox (flop envelopes.mailbox)] - -- - :: - ++ on-poke - ~/ %chat-store-poke - |= [=mark =vase] - ^- (quip card _this) - ?> (team:title our.bowl src.bowl) - =^ cards state - ?+ mark (on-poke:def mark vase) - %noun (poke-noun:cc !<(admin-action vase)) - %import (poke-import:cc q.vase) - == - [cards this] - :: - ++ on-watch on-watch:def - ++ on-leave on-leave:def - ++ on-peek - ~/ %chat-store-peek - |= =path - ^- (unit (unit cage)) - ?+ path (on-peek:def path) - [%x %all ~] ``noun+!>(inbox) - [%x %keys ~] ``noun+!>(~(key by inbox)) - [%x %envelopes *] (peek-x-envelopes:cc t.t.path) - [%x %mailbox *] - ?~ t.t.path - ~ - ``noun+!>((~(get by inbox) t.t.path)) - :: - [%x %config *] - ?~ t.t.path - ~ - =/ mailbox (~(get by inbox) t.t.path) - ?~ mailbox - ~ - ``noun+!>(config.u.mailbox) - :: - [%x %export ~] - ``noun+!>(state) - == - :: - ++ on-agent on-agent:def - ++ on-arvo on-arvo:def - ++ on-fail on-fail:def - -- +|_ =bowl:gall ++* this . + def ~(. (default-agent this %|) bowl) :: -~% %chat-store-library ..card ~ -|_ bol=bowl:gall -++ met ~(. mdl bol) -++ grp ~(. grpl bol) +++ on-init on-init:def +++ on-save !>(~) +++ on-load + |= old-vase=vase + ^- (quip card _this) + [~ this] :: -++ peek-x-envelopes - |= pax=path - ^- (unit (unit [%noun vase])) - ?+ pax ~ - [@ @ *] - =/ mail-path t.t.pax - =/ mailbox (~(get by inbox) mail-path) - ?~ mailbox - [~ ~ %noun !>(~)] - =* envelopes envelopes.u.mailbox - =/ sign-test=[?(%neg %pos) @] - %- need - %+ rush i.pax - ;~ pose - %+ cook - |= n=@ - [%neg n] - ;~(pfix hep dem:ag) - :: - %+ cook - |= n=@ - [%pos n] - dem:ag - == - =* length length.config.u.mailbox - =* start +.sign-test - ?: =(-.sign-test %neg) - ?: (gth start length) - [~ ~ %noun !>(envelopes)] - [~ ~ %noun !>((swag [(sub length start) start] envelopes))] - :: - =/ end (slav %ud i.t.pax) - ?. (lte start end) - ~ - =. end ?:((lth end length) end length) - [~ ~ %noun !>((swag [start (sub end start)] envelopes))] - == -:: -++ poke-noun - |= nou=admin-action - ^- (quip card _state) - ?: ?=([%migrate-graph ~] nou) - :_ state - (migrate-inbox inbox) - ~& %trimming-chat-store - :- ~ - %_ state - inbox - %- ~(urn by inbox) - |= [=path mailbox:store] - ^- mailbox:store - =/ [a=* out=(list envelope:store)] - %+ roll envelopes - |= $: =envelope:store - o=[[hav=(set serial:store) curr=@] out=(list envelope:store)] - == - ?: (~(has in hav.o) uid.envelope) - [[hav.o curr.o] out.o] - :- - ^- [(set serial:store) @] - [(~(put in hav.o) uid.envelope) +(curr.o)] - ^- (list envelope:store) - [envelope(number curr.o) out.o] - =/ len (lent out) - ~? !=(len (lent envelopes)) [path [%old (lent envelopes)] [%new len]] - [[len len] (flop out)] - == -:: -++ poke-import - |= arc=* - ^- (quip card _state) - =/ sty=state-4 [%4 (remake-map ;;((tree [path mailbox:store]) +.arc))] - :_ sty - (migrate-inbox inbox.sty) -:: -++ update-subscribers - |= [pax=path =update:store] - ^- (list card) - [%give %fact ~[pax] %chat-update !>(update)]~ -:: -++ send-diff - |= [pax=path upd=update:store] - ^- (list card) - %- zing - :~ (update-subscribers /all upd) - (update-subscribers /updates upd) - (update-subscribers [%mailbox pax] upd) - ?. |(|(=(%read -.upd) =(%message -.upd)) =(%messages -.upd)) - ~ - ?. |(=(%create -.upd) =(%delete -.upd)) - ~ - (update-subscribers /keys upd) - == -:: -++ migrate-inbox - |= =inbox:store - ^- (list card) - %- zing - (turn ~(tap by inbox) mailbox-to-updates) -:: -++ add-graph - |= [rid=resource =mailbox:store] - %- poke-graph-store - :- now.bol - :+ %add-graph rid - :- (mailbox-to-graph mailbox) - [`%graph-validator-chat %.y] -:: -++ archive-graph - |= rid=resource - %- poke-graph-store - [now.bol %archive-graph rid] -:: -++ nobody - ^- @p - (bex 128) -:: -++ path-to-resource - |= =path - ^- resource - ?. ?=([@ @ ~] path) - nobody^(spat path) - =/ m-ship=(unit ship) - (slaw %p i.path) - ?~ m-ship - nobody^(spat path) - [u.m-ship i.t.path] -:: -++ mailbox-to-updates - |= [=path =mailbox:store] - ^- (list card) - =/ app-rid=resource - (path-to-resource path) - =/ group-rid=resource - (fall (peek-group:met %graph app-rid) [nobody %bad-group]) - =/ group=(unit group) - (scry-group:grp group-rid) - :- (add-graph app-rid mailbox) - ?~ group (archive-graph app-rid)^~ - ?. &(=(~ members.u.group) hidden.u.group) ~ - ~& >>> "archiving {}" - :~ (archive-graph app-rid) - (remove-group group-rid) - == -:: -++ remove-group - |= group=resource - ^- card - =- [%pass / %agent [our.bol %group-store] %poke -] - group-update-0+!>([%remove-group group ~]) -:: -++ poke-graph-store - |= =update:graph-store - ^- card - [%pass / %agent [our.bol %graph-store] %poke %graph-update-1 !>(update)] -:: -++ letter-to-contents - |= =letter:store - ^- (list content:graph-store) - :_ ~ - ?. ?=(%me -.letter) - letter - [%text narrative.letter] -:: -++ envelope-to-node - |= =envelope:store - ^- [atom:graph-store node:graph-store] - =/ contents=(list content:graph-store) - (letter-to-contents letter.envelope) - =/ =index:graph-store - [when.envelope ~] - =, envelope - :- when.envelope - :_ [%empty ~] - ^- post:graph-store - :* author - index - when - contents - ~ ~ - == -:: -++ mailbox-to-graph - |= =mailbox:store - ^- graph:graph-store - %+ gas:orm:graph-store *graph:graph-store - (turn envelopes.mailbox envelope-to-node) +++ on-poke on-poke:def +++ on-watch on-watch:def +++ 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/pkg/arvo/app/contact-push-hook.hoon b/pkg/arvo/app/contact-push-hook.hoon index ed2d4660bd..bb8a1a5262 100644 --- a/pkg/arvo/app/contact-push-hook.hoon +++ b/pkg/arvo/app/contact-push-hook.hoon @@ -70,10 +70,11 @@ :: ++ transform-proxy-update |= vas=vase - ^- (unit vase) + ^- (quip card (unit vase)) :: TODO: should check if user is allowed to %add, %remove, %edit :: contact =/ =update:store !<(update:store vas) + :- ~ ?- -.update %initial ~ %add `vas diff --git a/pkg/arvo/app/demo-push-hook.hoon b/pkg/arvo/app/demo-push-hook.hoon index 4dcd47737f..988a31b2a4 100644 --- a/pkg/arvo/app/demo-push-hook.hoon +++ b/pkg/arvo/app/demo-push-hook.hoon @@ -43,8 +43,8 @@ :: ++ transform-proxy-update |= vas=vase - ^- (unit vase) - `vas + ^- (quip card (unit vase)) + ``vas :: ++ resource-for-update |= =vase diff --git a/pkg/arvo/app/dm-hook.hoon b/pkg/arvo/app/dm-hook.hoon new file mode 100644 index 0000000000..2ceca38ea4 --- /dev/null +++ b/pkg/arvo/app/dm-hook.hoon @@ -0,0 +1,319 @@ +:: dm-hook [landscape]: receive and send DMs +:: +/+ default-agent, dbug, store=graph-store, graphlib=graph, agentio, resource +/+ sig=signatures, hook=dm-hook +:: +|% +:: ++$ base-state-0 + $: screening=? + screened=(jug ship [=index:store =node:store]) + pending=(jar ship atom) + == +:: ++$ state-0 [%0 base-state-0] ++$ state-1 [%1 base-state-0] ++$ versioned-state + $% state-0 + state-1 + == ++$ card card:agent:gall ++$ nodes (map index:store node:store) +++ orm orm:store +-- +:: +=| state-1 +=* state - +%- agent:dbug +^- agent:gall +:: +|_ =bowl:gall ++* this . + def ~(. (default-agent this %|) bowl) + gra ~(. graphlib bowl) + io ~(. agentio bowl) + pass pass:io +:: +++ on-init + :_ this + :_ ~ + =/ dms=(list resource) + ?. .^(? %gu (scry:io %graph-store ~)) + ~ + %+ skim ~(tap in get-keys:gra) + |=([ship name=term] ?=(^ (rush name ;~(pfix (jest 'dm--') fed:ag)))) + |^ + %+ poke-our:pass %graph-store + %+ update:cg:gra now.bowl + :+ %add-graph [our.bowl %dm-inbox] + [graph `%graph-validator-dm %.n] + :: + ++ dm-parser + ;~(pfix (jest 'dm--') fed:ag) + :: + ++ counterparty + |= rid=resource + =/ =ship (rash name.rid dm-parser) + ?. =(our.bowl ship) ship + entity.rid + :: + ++ update-indices + |= [pfix=index:store =graph:store] + =* loop $ + ^- graph:store + %+ gas:orm *graph:store + %+ turn (tap:orm graph) + |= [=atom =node:store] + ^- [^atom node:store] + =/ =index:store (snoc pfix atom) + :- atom + =. children.node + ?: ?=(%empty -.children.node) children.node + [%graph loop(pfix index, graph p.children.node)] + ?: ?=(%| -.post.node) node + node(index.p.post index) + :: + ++ graph + %+ roll dms + |= [rid=resource =graph:store] + ^- graph:store + =/ =ship (counterparty rid) + =| =post:store + =: author.post our.bowl + index.post [ship ~] + time-sent.post now.bowl + == + =/ dm=graph:store + (update-indices ~[ship] (get-graph-mop:gra rid)) + (put:orm:store graph `@`ship [%& post] %graph dm) + -- +:: +++ on-save !>(state) +++ on-load + |= =vase + ^- (quip card _this) + =+ !<(old=versioned-state vase) + ?: ?=(%1 -.old) `this(state old) + :_ this(state [%1 +.old]) + (poke-self:pass noun+!>(%reinit))^~ +:: +++ on-poke + |= [=mark =vase] + ^- (quip card _this) + |^ + ?+ mark (on-poke:def mark vase) + %noun + ?+ q.vase !! + %reinit + ?: (~(has in get-keys:gra) [our.bowl %dm-inbox]) + `this + on-init + == + :: + %dm-hook-action + =+ !<(=action:hook vase) + =^ cards state + ?+ -.action !! + %accept (accept-screen ship.action) + %decline (decline-screen ship.action) + %screen (set-screen screen.action) + == + [cards this] + :: + %graph-update-2 + =+ !<(=update:store vase) + ?+ -.q.update !! + %add-nodes + ?> ?=([@ %dm-inbox] resource.q.update) + =^ cards state + ?: =(our.bowl src.bowl) + (outgoing-add (hash-and-sign nodes.q.update)) + ?: &(screening !(dm-exists src.bowl)) + (screen-add nodes.q.update) + (incoming-add nodes.q.update) + [cards this] + == + == + :: + ++ hash-and-sign + |= =nodes + %- ~(gas by *^nodes) + %+ turn ~(tap by nodes) + |= [=index:store =node:store] + ^- [index:store node:store] + :- index + ?> ?=(%& -.post.node) + =* p post.node + =/ =hash:store + `@ux`(sham [~ author time-sent contents]:p.p) + %_ node + hash.p.post `hash + :: + signatures.p.post + %- ~(gas in *signatures:store) + [(sign:sig our.bowl now.bowl hash)]~ + == + :: + ++ give + |= =action:hook + ^- card + (fact:io dm-hook-action+!>(action) ~[/updates]) + :: + ++ accept-screen + |= =ship + ^- (quip card _state) + =/ unscreened=nodes + %- ~(gas by *nodes) + ~(tap in (~(get ju screened) ship)) + :_ state(screened (~(del by screened) ship)) + %+ welp (add-missing-root ship) + :~ %+ poke-our:pass %graph-store + (update:cg:gra now.bowl %add-nodes [our.bowl %dm-inbox] unscreened) + :: + (give %accept ship) + == + :: + ++ set-screen + |= screen=? + :_ state(screening screen) + (give %screen screen)^~ + :: + ++ decline-screen + |= =ship + ^- (quip card _state) + :_ state(screened (~(del by screened) ship)) + (give %decline ship)^~ + :: + ++ screen-add + |= =nodes + ?> =(1 ~(wyt by nodes)) + =/ ship-screen (~(get ju screened) src.bowl) + =. ship-screen (~(uni in ship-screen) (normalize-incoming nodes)) + `state(screened (~(put by screened) src.bowl ship-screen)) + :: + ++ dm-exists + |= =ship + =/ =index:store + [ship ~] + (check-node-existence:gra [our.bowl %dm-inbox] index) + :: + ++ add-node + |= [=index:store =node:store] + ^- update:store + :^ now.bowl %add-nodes [our.bowl %dm-inbox] + (~(gas by *nodes) [index node] ~) + :: + ++ add-missing-root + |= =ship + ^- (list card) + ?: (dm-exists ship) ~ + =/ =index:store + [ship ~] + =| =post:store + =: author.post our.bowl + index.post index + time-sent.post now.bowl + == + =/ =node:store + [%&^post %empty ~] + (poke-our:pass %graph-store (update:cg:gra (add-node index node)))^~ + :: + ++ outgoing-add + |= =nodes + ^- (quip card _state) + =/ nodes=(list [=index:store =node:store]) + ~(tap by nodes) + =| cards=(list card) + |- ^- (quip card _state) + ?~ nodes [cards state] + ?> ?=([@ @ ~] index.i.nodes) + =/ =ship i.index.i.nodes + =/ =dock [ship %dm-hook] + =/ =wire /dm/(scot %p ship) + =/ =cage + (update:cg:gra (add-node [index node]:i.nodes)) + %= $ + nodes t.nodes + pending (~(add ja pending) ship now.bowl) + :: + cards + ;: welp + cards + :: + (add-missing-root ship) + :: + :- (poke-our:pass %graph-store cage) + ?: =(our.bowl ship) ~ + (~(poke pass wire) dock cage)^~ + == + == + :: + ++ normalize-incoming + |= =nodes + ^- ^nodes + %- ~(gas by *^nodes) + %+ turn ~(tap by nodes) + |= [=index:store =node:store] + ?> ?=([@ @ ~] index) + ?> ?=(%empty -.children.node) + ?> ?=(%& -.post.node) + =/ new-index=index:store + [src.bowl now.bowl ~] + =. index.p.post.node + new-index + [new-index node] + :: + ++ incoming-add + |= =nodes + ^- (quip card _state) + :_ state + ?> =(1 ~(wyt by nodes)) + =* ship src.bowl + %+ snoc (add-missing-root ship) + %+ poke-our:pass %graph-store + %+ update:cg:gra now.bowl + [%add-nodes [our.bowl %dm-inbox] (normalize-incoming nodes)] + -- +:: +++ on-watch + |= =path + ?. ?=([%updates ~] path) + (on-watch:def path) + :_ this + :~ (fact-init:io dm-hook-action+!>([%pendings ~(key by screened)])) + (fact-init:io dm-hook-action+!>([%screen screening])) + == +:: +++ on-peek on-peek:def +++ on-leave on-leave:def +++ on-agent + |= [=wire =sign:agent:gall] + ^- (quip card _this) + |^ + ?. ?=([%dm @ ~] wire) + (on-agent:def wire sign) + ?> ?=(%poke-ack -.sign) + =/ =ship + (slav %p i.t.wire) + =^ acked=atom state + (remove-pending ship) + ?~ p.sign + `this + :_ this + :_ ~ + =+ indices=(~(gas in *(set index:store)) ~[ship acked] ~) + %+ poke-our:pass %graph-store + (update:cg:gra now.bowl %remove-posts [our.bowl %dm-inbox] indices) + :: + ++ remove-pending + |= =ship + ^- [atom _state] + =/ pend-ship=(list atom) + (flop (~(get ja pending) ship)) + ?> ?=(^ pend-ship) + [i.pend-ship state(pending (~(put by pending) ship (flop t.pend-ship)))] + -- + +++ on-arvo on-arvo:def +++ on-fail on-fail:def +-- diff --git a/pkg/arvo/app/dojo.hoon b/pkg/arvo/app/dojo.hoon index c06b86e50e..a8a9b5dd1a 100644 --- a/pkg/arvo/app/dojo.hoon +++ b/pkg/arvo/app/dojo.hoon @@ -875,7 +875,7 @@ %ge (dy-run-generator (dy-cage p.p.p.bil) q.p.bil) %sa =+ .^(=dais:clay cb+(en-beam he-beak /[p.bil])) - (dy-hand p.bil bunt:dais) + (dy-hand p.bil *vale:dais) :: %as =/ cag=cage (dy-cage p.q.bil) @@ -1162,6 +1162,7 @@ %import !! %export-all !! %import-all !! + %cancel !! %as :* %as mar.source.com $(num +(num), source.com next.source.com) diff --git a/pkg/arvo/app/file-server.hoon b/pkg/arvo/app/file-server.hoon index 8bff63cc79..f2b910f44b 100644 --- a/pkg/arvo/app/file-server.hoon +++ b/pkg/arvo/app/file-server.hoon @@ -188,8 +188,11 @@ ?: ?=([%'~landscape' %js %session ~] site.req-line) %+ require-authorization-simple:app inbound-request - %- js-response:gen - (as-octt:mimes:html "window.ship = '{+:(scow %p our.bowl)}';") + %. %- as-octs:mimes:html + (rap 3 'window.ship = "' (rsh 3 (scot %p our.bowl)) '";' ~) + %* . js-response:gen + cache %.n + == :: =/ [payload=simple-payload:http public=?] (get-file req-line is-file) ?: public payload @@ -222,6 +225,8 @@ [~ %js] (js-response:gen file) [~ %css] (css-response:gen file) [~ %png] (png-response:gen file) + [~ %svg] (svg-response:gen file) + [~ %ico] (ico-response:gen file) :: [~ %html] %. file @@ -238,11 +243,9 @@ [not-found:gen %.n] :_ public.u.content =/ mime-type=@t (rsh 3 (crip )) - :: Should maybe inspect to see how long cache should hold - :: =/ headers :~ content-type+mime-type - max-1-da:gen + max-1-wk:gen 'service-worker-allowed'^'/' == [[200 headers] `q.u.data] @@ -271,7 +274,10 @@ ++ match-content-path |= [pax=path =^serving is-file=?] ^- (unit [content path ?]) - %- ~(rep by serving) + %+ roll + %+ sort ~(tap by serving) + |= [[a=path *] [b=path *]] + (gth (lent a) (lent b)) |= $: [url-base=path =content public=? spa=?] out=(unit [content path ?]) == diff --git a/pkg/arvo/app/glob.hoon b/pkg/arvo/app/glob.hoon index 0fb9ffa4a6..0dc73742ff 100644 --- a/pkg/arvo/app/glob.hoon +++ b/pkg/arvo/app/glob.hoon @@ -2,13 +2,16 @@ :: :: prompts content delivery and Gall state storage for Landscape JS blob :: -/- glob +/- glob, *resource /+ default-agent, verb, dbug |% -++ hash 0v3.g6u13.haedt.jt4hd.61ek5.6t30q +++ landscape-hash 0v2.i41hn.un6g3.jucd7.rhrah.n0qmv +++ btc-wallet-hash 0v2.3qak4.al612.8m1ig.kg03r.mfide +$ state-0 [%0 hash=@uv glob=(unit (each glob:glob tid=@ta))] ++$ state-1 [%1 =globs:glob] +$ all-states $% state-0 + state-1 == +$ card card:agent:gall -- @@ -19,12 +22,12 @@ [%pass [%timer path] %arvo %b %wait (add now ~m30)] :: ++ wait-start - |= now=@da + |= [now=@da =path] ^- card - [%pass /start %arvo %b %wait now] + [%pass [%start path] %arvo %b %wait now] :: ++ poke-file-server - |= [our=@p =cage] + |= [our=@p hash=@uv =cage] ^- card [%pass /serving/(scot %uv hash) %agent [our %file-server] %poke cage] :: @@ -43,9 +46,12 @@ ^- card [%pass [%running path] %agent [our %spider] %leave ~] -- -=| state=state-0 -=. hash.state hash -=/ serve-path=path /'~landscape'/js/bundle +=| state=state-1 +=. globs.state + (~(put by globs.state) /'~landscape'/js/bundle landscape-hash ~) +=. globs.state + (~(put by globs.state) /'~btc'/js/bundle btc-wallet-hash ~) +:: ^- agent:gall %+ verb | %- agent:dbug @@ -56,77 +62,121 @@ ++ on-init ^- (quip card _this) :: delay through timer to make sure %spider has started - [[(wait-start now.bowl) ~] this] + :_ this + %+ turn ~(tap by ~(key by globs.state)) + |=(=path (wait-start now.bowl path)) :: ++ on-save !>(state) ++ on-load |= old-state=vase ^- (quip card _this) =+ !<(old=all-states old-state) - ?> ?=(%0 -.old) - ?~ glob.old - on-init - ?: ?=(%& -.u.glob.old) - ?: =(hash.old hash.state) - `this(state old) - on-init - =/ cancel-cards - =/ args [tid.p.u.glob.old &] - :~ (leave-spider /(scot %uv hash.old) our.bowl) - (poke-spider /(scot %uv hash.old) our.bowl %spider-stop !>(args)) + =| cards=(list card) + =/ upgrading=? %.n + |- + ?- -.old + %1 + =/ [cards-1=(list card) =globs:glob] + %- ~(rep by globs.old) + |= $: [=serve=path =glob-details:glob] + cards=(list card) + globs=_globs.state + == + ^- [(list card) globs:glob] + =/ new-glob-details (~(get by globs) serve-path) + ?~ new-glob-details + [cards globs] + ?~ glob.glob-details + :_ globs + [(wait-start now.bowl serve-path) cards] + ?: ?=(%& -.u.glob.glob-details) + ?: =(hash.u.new-glob-details hash.glob-details) + [cards (~(put by globs) serve-path glob-details)] + :_ globs + [(wait-start now.bowl serve-path) cards] + ?: upgrading + :_ globs + [(wait-start now.bowl serve-path) cards] + =/ args [tid.p.u.glob.glob-details &] + =/ spider-wire [(scot %uv hash.glob-details) serve-path] + :_ globs + :* (leave-spider spider-wire our.bowl) + (poke-spider spider-wire our.bowl %spider-stop !>(args)) + (wait-start now.bowl serve-path) + cards + == + :- (weld cards cards-1) + this(globs.state globs) + :: + %0 + =/ globs + (~(put by globs.state) /'~landscape'/js/bundle [hash.old glob.old]) + %= $ + old [%1 globs] + :: + cards + ?~ glob.old ~ + ?: =(%& -.u.glob.old) ~ + ?> ?=(%| -.u.glob.old) + =/ args [tid.p.u.glob.old &] + :~ (leave-spider /(scot %uv hash.old) our.bowl) + (poke-spider /(scot %uv hash.old) our.bowl %spider-stop !>(args)) + == + :: + upgrading %.y == - =^ init-cards this on-init - [(weld cancel-cards init-cards) this] + == :: ++ on-poke |= [=mark =vase] ^- (quip card _this) ?+ mark (on-poke:def mark vase) %glob-make + =+ !<(dir=path vase) :_ this =/ home=path /(scot %p our.bowl)/home/(scot %da now.bowl) + =+ .^(paths=(list path) %ct (weld home dir)) =+ .^(=js=tube:clay %cc (weld home /js/mime)) =+ .^(=map=tube:clay %cc (weld home /map/mime)) - =+ .^(arch %cy (weld home /app/landscape/js/bundle)) - =/ bundle-hash=@t - %- need - ^- (unit @t) - %- ~(rep by dir) - |= [[file=@t ~] out=(unit @t)] - ?^ out out - ?. ?& =((end [3 6] file) 'index.') - !=('sj.' (end [3 3] (swp 3 file))) - == - out - ``@t`(rsh [3 6] file) - =/ js-name - (cat 3 'index.' bundle-hash) - =/ map-name - (cat 3 js-name '.js') - =+ .^(js=@t %cx :(weld home /app/landscape/js/bundle /[js-name]/js)) - =+ .^(map=@t %cx :(weld home /app/landscape/js/bundle /[map-name]/map)) - =+ .^(sw=@t %cx :(weld home /app/landscape/js/bundle /serviceworker/js)) - =+ !<(=js=mime (js-tube !>(js))) - =+ !<(=sw=mime (js-tube !>(sw))) - =+ !<(=map=mime (map-tube !>(map))) =/ =glob:glob %- ~(gas by *glob:glob) - :~ /[js-name]/js^js-mime - /[map-name]/map^map-mime - /serviceworker/js^sw-mime + %+ turn paths + |= pax=path + ^- [path mime] + =+ .^(file=@t %cx (weld home pax)) + =/ mar (snag 0 (flop pax)) + :- (slag (lent dir) pax) + ?+ mar ~|(unsupported-glob-type+mar !!) + %js !<(mime (js-tube !>(file))) + %map !<(mime (map-tube !>(file))) == =/ =path /(cat 3 'glob-' (scot %uv (sham glob)))/glob + ~& globbed+`(set ^path)`~(key by glob) [%pass /make %agent [our.bowl %hood] %poke %drum-put !>([path (jam glob)])]~ :: %noun - ?: =(%kick q.vase) - (on-load !>(state(hash *@uv))) + ?: =(%kick -.q.vase) + =+ !<([%kick =path] vase) + =/ glob-details (~(get by globs.state) path) + ?~ glob-details + ~& no-such-glob+path + `this + =/ new-state + state(globs (~(put by globs.state) path *@uv glob.u.glob-details)) + (on-load !>(new-state)) (on-poke:def mark vase) == :: ++ on-watch on-watch:def ++ on-leave on-leave:def -++ on-peek on-peek:def +:: +++ on-peek + |= =path + ^- (unit (unit cage)) + ?+ path (on-peek:def path) + [%x %btc-wallet ~] ``noun+!>(btc-wallet-hash) + == +:: ++ on-agent |= [=wire =sign:agent:gall] ^- (quip card _this) @@ -134,83 +184,109 @@ (on-agent:def wire sign) ?: ?=([%make ~] wire) (on-agent:def wire sign) - ?. ?=([%running @ ~] wire) + ?. ?=([%running @ *] wire) %- (slog leaf+"glob: strange on-agent! {}" ~) (on-agent:def wire sign) + :: + =/ produced-hash (slav %uv i.t.wire) + =* serve-path t.t.wire + =/ glob-details (~(get by globs.state) serve-path) + ?~ glob-details + [~ this] + ?. =(hash.u.glob-details produced-hash) + [~ this] ?- -.sign %poke-ack ?~ p.sign [~ this] %- (slog leaf+"glob: couldn't start thread; will retry" u.p.sign) - :_ this(glob.state ~) :_ ~ - (leave-spider t.wire our.bowl) + :_ this(globs.state (~(put by globs.state) serve-path produced-hash ~)) + [(leave-spider t.wire our.bowl)]~ :: %watch-ack ?~ p.sign [~ this] %- (slog leaf+"glob: couldn't listen to thread; will retry" u.p.sign) - [~ this(glob.state ~)] + [~ this(globs.state (~(put by globs.state) serve-path produced-hash ~))] :: %kick - =? glob.state ?=([~ %| *] glob.state) - ~ - `this + ?. ?=([~ %| *] glob.u.glob-details) + `this + [~ this(globs.state (~(put by globs.state) serve-path produced-hash ~))] :: %fact - =/ produced-hash (slav %uv i.t.wire) - ?. =(hash.state produced-hash) - [~ this] ?+ p.cage.sign (on-agent:def wire sign) %thread-fail =+ !<([=term =tang] q.cage.sign) %- (slog leaf+"glob: thread failed; will retry" leaf+ tang) - [~ this(glob.state ~)] + :- ~ + this(globs.state (~(put by globs.state) serve-path produced-hash ~)) :: %thread-done =+ !<(=glob:glob q.cage.sign) - ?. =(hash.state (sham glob)) + ?. =(hash.u.glob-details (sham glob)) %: mean leaf+"glob: hash doesn't match!" - >expected=hash.state< + >expected=hash.u.glob-details< >got=(sham glob)< ~ == - :_ this(glob.state `[%& glob]) :_ ~ - %+ poke-file-server our.bowl - [%file-server-action !>([%serve-glob serve-path glob %&])] + =. globs.state + (~(put by globs.state) serve-path produced-hash `[%& glob]) + :_ this :_ ~ + %: poke-file-server + our.bowl + produced-hash + %file-server-action + !>([%serve-glob serve-path glob %&]) + == == == :: ++ on-arvo |= [=wire =sign-arvo] ^- (quip card _this) - ?: ?=([%start ~] wire) - =/ new-tid=@ta (cat 3 'glob--' (scot %uv eny.bowl)) - =/ args [~ `new-tid %glob !>([~ hash.state])] - =/ action !>([%unserve-dir serve-path]) - :_ this(glob.state `[%| new-tid]) - :~ (poke-file-server our.bowl %file-server-action action) - (wait-timeout /[new-tid] now.bowl) - (watch-spider /(scot %uv hash.state) our.bowl /thread-result/[new-tid]) - (poke-spider /(scot %uv hash.state) our.bowl %spider-start !>(args)) + ?: ?=([%start *] wire) + =* serve-path t.wire + =/ glob-details (~(get by globs.state) serve-path) + ?~ glob-details + [~ this] + =/ new-tid=@ta (cat 3 'glob--' (scot %uv (sham eny.bowl serve-path))) + =/ args [~ `new-tid %glob !>([~ hash.u.glob-details])] + =/ action=cage [%file-server-action !>([%unserve-dir serve-path])] + =/ spider-wire [(scot %uv hash.u.glob-details) serve-path] + =. globs.state + (~(put by globs.state) serve-path hash.u.glob-details `[%| new-tid]) + :_ this + :~ (poke-file-server our.bowl hash.u.glob-details action) + (wait-timeout [new-tid serve-path] now.bowl) + (watch-spider spider-wire our.bowl /thread-result/[new-tid]) + (poke-spider spider-wire our.bowl %spider-start !>(args)) == - ?. ?=([%timer @ ~] wire) + :: + ?. ?=([%timer @ *] wire) %- (slog leaf+"glob: strange on-arvo wire: {}" ~) `this ?. ?=(%wake +<.sign-arvo) %- (slog leaf+"glob: strange on-arvo sign: {}" ~) `this - ?: ?=([~ %& *] glob.state) + =* serve-path t.wire + =/ glob-details (~(get by globs.state) serve-path) + ?~ glob-details `this - ?. ?| ?=(~ glob.state) - =(i.t.wire tid.p.u.glob.state) + ?: ?=([~ %& *] glob.u.glob-details) + `this + ?. ?| ?=(~ glob.u.glob-details) + =(i.t.wire tid.p.u.glob.u.glob-details) == `this ?^ error.sign-arvo %- (slog leaf+"glob: timer handling failed; will retry" ~) [[(wait-timeout t.wire now.bowl)]~ this] %- (slog leaf+"glob: timed out; retrying" ~) - (on-load !>(state(hash *@uv))) + =/ new-details u.glob-details(hash *@uv) + =/ new-state state(globs (~(put by globs.state) serve-path new-details)) + (on-load !>(new-state)) :: ++ on-fail on-fail:def -- diff --git a/pkg/arvo/app/graph-pull-hook.hoon b/pkg/arvo/app/graph-pull-hook.hoon index 54031f9892..1f9f9fc0e4 100644 --- a/pkg/arvo/app/graph-pull-hook.hoon +++ b/pkg/arvo/app/graph-pull-hook.hoon @@ -9,7 +9,7 @@ update:store %graph-update %graph-push-hook - 1 1 + 2 2 %.n == -- @@ -41,7 +41,7 @@ %- (slog leaf+"nacked {}" tang) :_ this ?. (~(has in get-keys:gra) resource) ~ - =- [%pass /pull-nack %agent [our.bowl %graph-store] %poke %graph-update-1 -]~ + =- [%pass /pull-nack %agent [our.bowl %graph-store] %poke %graph-update-2 -]~ !> ^- update:store [now.bowl [%archive-graph resource]] :: diff --git a/pkg/arvo/app/graph-push-hook.hoon b/pkg/arvo/app/graph-push-hook.hoon index 94780bf582..853828d891 100644 --- a/pkg/arvo/app/graph-push-hook.hoon +++ b/pkg/arvo/app/graph-push-hook.hoon @@ -1,6 +1,6 @@ /- *group, metadata=metadata-store /+ store=graph-store, mdl=metadata, res=resource, graph, group, default-agent, - dbug, verb, push-hook + dbug, verb, push-hook, agentio :: ~% %graph-push-hook-top ..part ~ |% @@ -12,16 +12,44 @@ update:store %graph-update %graph-pull-hook - 1 1 + 2 2 == :: +$ agent (push-hook:push-hook config) :: +$ state-null ~ +$ state-zero [%0 marks=(set mark)] ++$ state-one [%1 ~] +$ versioned-state $@ state-null - state-zero + $% state-zero + state-one + == +:: ++$ cached-transform + $-([index:store post:store atom ?] [index:store post:store]) +:: ++$ cached-permission + $-(indexed-post:store $-(vip-metadata:metadata permissions:store)) +:: +:: TODO: come back to this and potentially use send a %t +:: to be notified of validator changes ++$ cache + $: graph-to-mark=(map resource:res (unit mark)) + perm-marks=(map [mark @tas] cached-permission) + transform-marks=(map mark cached-transform) + == +:: ++$ inflated-state + $: state-one + cache + == +:: ++$ cache-action + $% [%graph-to-mark (pair resource:res (unit mark))] + [%perm-marks (pair (pair mark @tas) cached-permission)] + [%transform-marks (pair mark cached-transform)] + == -- :: %- agent:dbug @@ -30,26 +58,48 @@ %- (agent:push-hook config) ^- agent =- -=| state-zero +~% %graph-push-hook-agent ..scry.hook-core ~ +=| inflated-state =* state - |_ =bowl:gall +* this . def ~(. (default-agent this %|) bowl) grp ~(. group bowl) gra ~(. graph bowl) - hc ~(. hook-core bowl) + met ~(. mdl bowl) + hc ~(. hook-core bowl +.state) + io ~(. agentio bowl) :: ++ on-init on-init:def -++ on-save !>(state) +++ on-save !>(-.state) ++ on-load |= =vase =+ !<(old=versioned-state vase) =? old ?=(~ old) [%0 ~] - ?> ?=(%0 -.old) - `this(state old) + =? old ?=(%0 -.old) + [%1 ~] + ?> ?=(%1 -.old) + `this(-.state old, +.state *cache) +:: +++ on-poke + |= [=mark =vase] + ^- (quip card _this) + ?. =(mark %graph-cache-hook) + [~ this] + =/ a=cache-action !<(cache-action vase) + =* c +.state + =* graph-to-mark graph-to-mark.c + =* perm-marks perm-marks.c + =* transform-marks transform-marks.c + =. c + ?- -.a + %graph-to-mark c(graph-to-mark (~(put by graph-to-mark) p.a q.a)) + %perm-marks c(perm-marks (~(put by perm-marks) p.a q.a)) + %transform-marks c(transform-marks (~(put by transform-marks) p.a q.a)) + == + [~ this(+.state c)] :: -++ on-poke on-poke:def ++ on-agent on-agent:def ++ on-watch on-watch:def ++ on-leave on-leave:def @@ -58,47 +108,77 @@ |= [=wire =sign-arvo] ^- (quip card _this) ?+ wire (on-arvo:def wire sign-arvo) + :: XX: no longer necessary :: - [%perms @ @ ~] - ?> ?=(?(%add %remove) i.t.t.wire) - =* mark i.t.wire - :_ this - (build-permissions:hc mark i.t.t.wire %next)^~ - :: - [%transform-add @ ~] - =* mark i.t.wire - :_ this - (build-transform-add:hc mark %next)^~ + [%perms @ @ ~] [~ this] + [%transform-add @ ~] [~ this] == :: ++ on-fail on-fail:def ++ transform-proxy-update + ~/ %transform-proxy-update |= vas=vase - ^- (unit vase) + ^- (quip card (unit vase)) =/ =update:store !<(update:store vas) =* rid resource.q.update =. p.update now.bowl ?- -.q.update %add-nodes - ?. (is-allowed-add:hc rid nodes.q.update) - ~ - =/ mark (get-mark:gra rid) - ?~ mark `vas - |^ - =/ transform - !< $-([index:store post:store atom ?] [index:store post:store]) - %. !>(*indexed-post:store) - .^(tube:clay (scry:hc %cc %home /[u.mark]/transform-add-nodes)) - =/ [* result=(list [index:store node:store])] - %+ roll - (flatten-node-map ~(tap by nodes.q.update)) - (transform-list transform) - =. nodes.q.update - %- ~(gas by *(map index:store node:store)) - result - [~ !>(update)] + =| cards=(list card) + ?: ?=(^ (rush name.rid ;~(pfix (jest 'dm--') fed:ag))) + :: block new DM messages + [~ ~] + =^ allowed cards (is-allowed-add:hc rid nodes.q.update) + ?. allowed + [cards ~] + =/ mark-cached (~(has by graph-to-mark) rid) + =/ mark + ?: mark-cached + (~(got by graph-to-mark) rid) + (get-mark:gra rid) + ?~ mark + [cards `vas] + =< $ + ~% %transform-add-nodes ..transform-proxy-update ~ + |% + ++ $ + ^- (quip card (unit vase)) + =/ transform-cached (~(has by transform-marks) u.mark) + =/ transform=cached-transform + ?: transform-cached + (~(got by transform-marks) u.mark) + =/ =tube:clay + .^(tube:clay (scry:hc %cc %home /[u.mark]/transform-add-nodes)) + !< cached-transform + %. !>(*indexed-post:store) + tube + =/ [* result=(list [index:store node:store])] + %+ roll + (flatten-node-map ~(tap by nodes.q.update)) + (transform-list transform) + =. nodes.q.update + %- ~(gas by *(map index:store node:store)) + result + :_ :- ~ + !> ^- update:store + update + %+ weld cards + %- zing + :~ ?: mark-cached ~ + :_ ~ + %+ poke-self:pass:io %graph-cache-hook + !> ^- cache-action + [%graph-to-mark rid mark] + :: + ?: transform-cached ~ + :_ ~ + %+ poke-self:pass:io %graph-cache-hook + !> ^- cache-action + [%transform-marks u.mark transform] + == :: ++ flatten-node-map + ~/ %flatten-node-map |= lis=(list [index:store node:store]) ^- (list [index:store node:store]) |^ @@ -130,10 +210,13 @@ -- :: ++ transform-list + ~/ %transform-list |= transform=$-([index:store post:store atom ?] [index:store post:store]) |= $: [=index:store =node:store] [indices=(set index:store) lis=(list [index:store node:store])] == + ~| "cannot put a deleted post into %add-nodes {}" + ?> ?=(%& -.post.node) =/ l (lent index) =/ parent-modified=? %- ~(rep in indices) @@ -144,36 +227,42 @@ %.n =((swag [0 k] index) i) =/ [ind=index:store =post:store] - (transform index post.node now.bowl parent-modified) + (transform index p.post.node now.bowl parent-modified) :- (~(put in indices) index) - (snoc lis [ind node(post post)]) + (snoc lis [ind node(p.post post)]) -- :: - %remove-nodes - ?. (is-allowed-remove:hc resource.q.update indices.q.update) + %remove-posts + =| cards=(list card) + =^ allowed cards + (is-allowed-remove:hc rid indices.q.update) + :- cards + ?. allowed ~ `vas :: - %add-graph ~ - %remove-graph ~ - %add-signatures ~ - %remove-signatures ~ - %archive-graph ~ - %unarchive-graph ~ - %add-tag ~ - %remove-tag ~ - %keys ~ - %tags ~ - %tag-queries ~ - %run-updates ~ + %add-graph [~ ~] + %remove-graph [~ ~] + %add-signatures [~ ~] + %remove-signatures [~ ~] + %archive-graph [~ ~] + %unarchive-graph [~ ~] + %add-tag [~ ~] + %remove-tag [~ ~] + %keys [~ ~] + %tags [~ ~] + %tag-queries [~ ~] + %run-updates [~ ~] == :: ++ resource-for-update resource-for-update:gra :: ++ initial-watch + ~/ %initial-watch |= [=path =resource:res] ^- vase - ?> (is-allowed:hc resource) + |^ + ?> (is-allowed resource) !> ^- update:store ?~ path :: new subscribe @@ -186,22 +275,19 @@ =/ =time (slav %da i.path) =/ =update-log:store (get-update-log-subset:gra resource time) [now.bowl [%run-updates resource update-log]] + :: + ++ is-allowed + |= =resource:res + =/ group-res=resource:res + (need (peek-group:met %graph resource)) + (is-member:grp src.bowl group-res) + -- :: ++ take-update |= =vase ^- [(list card) agent] =/ =update:store !<(update:store vase) ?+ -.q.update [~ this] - %add-graph - ?~ mark.q.update `this - =* mark u.mark.q.update - ?: (~(has in marks) mark) `this - :_ this(marks (~(put in marks) mark)) - :~ (build-permissions:hc mark %add %sing) - (build-permissions:hc mark %remove %sing) - (build-transform-add:hc mark %sing) - == - :: %remove-graph :_ this [%give %kick ~[resource+(en-path:res resource.q.update)] ~]~ @@ -211,11 +297,14 @@ [%give %kick ~[resource+(en-path:res resource.q.update)] ~]~ == -- -^| ^= hook-core -|_ =bowl:gall +:: +~% %graph-push-hook-helper ..card.hook-core ~ +^= hook-core +|_ [=bowl:gall =cache] +* grp ~(. group bowl) met ~(. mdl bowl) gra ~(. graph bowl) + io ~(. agentio bowl) :: ++ scry |= [care=@t desk=@t =path] @@ -223,28 +312,43 @@ /[care]/(scot %p our.bowl)/[desk]/(scot %da now.bowl) path :: -++ perm-mark-name - |= perm=@t - ^- @t - (cat 3 'graph-permissions-' perm) -:: ++ perm-mark |= [=resource:res perm=@t vip=vip-metadata:metadata =indexed-post:store] - ^- permissions:store - =- (check vip) - !< check=$-(vip-metadata:metadata permissions:store) - %. !>(indexed-post) - =/ mark (get-mark:gra resource) - ?~ mark |=(=vase !>([%no %no %no])) - .^(tube:clay (scry %cc %home /[u.mark]/(perm-mark-name perm))) -:: -++ add-mark - |= [=resource:res vip=vip-metadata:metadata =indexed-post:store] - (perm-mark resource %add vip indexed-post) -:: -++ remove-mark - |= [=resource:res vip=vip-metadata:metadata =indexed-post:store] - (perm-mark resource %remove vip indexed-post) + ^- [permissions:store (list card)] + |^ + =/ mark-cached (~(has by graph-to-mark.cache) resource) + =/ mark + ?: mark-cached + (~(got by graph-to-mark.cache) resource) + (get-mark:gra resource) + ?~ mark + [[%no %no %no] ~] + =/ key [u.mark (perm-mark-name perm)] + =/ perms-cached (~(has by perm-marks.cache) key) + =/ convert + ?: perms-cached + (~(got by perm-marks.cache) key) + .^(cached-permission (scry %cf %home /[u.mark]/(perm-mark-name perm))) + :- ((convert indexed-post) vip) + %- zing + :~ ?: mark-cached ~ + :_ ~ + %+ poke-self:pass:io %graph-cache-hook + !> ^- cache-action + [%graph-to-mark resource mark] + :: + ?: perms-cached ~ + :_ ~ + %+ poke-self:pass:io %graph-cache-hook + !> ^- cache-action + [%perm-marks [u.mark (perm-mark-name perm)] convert] + == + :: + ++ perm-mark-name + |= perm=@t + ^- @t + (cat 3 'graph-permissions-' perm) + -- :: ++ get-permission |= [=permissions:store is-admin=? writers=(set ship)] @@ -257,22 +361,23 @@ writer.permissions reader.permissions :: -++ is-allowed - |= =resource:res - =/ group-res=resource:res - (need (peek-group:met %graph resource)) - (is-member:grp src.bowl group-res) -:: ++ get-roles-writers-variation + ~/ %get-roles-writers-variation |= =resource:res ^- (unit [is-admin=? writers=(set ship) vip=vip-metadata:metadata]) =/ assoc=(unit association:metadata) - (peek-association:met %graph resource) + (peek-association:met %graph resource) ?~ assoc ~ + =/ group=(unit group:grp) + (scry-group:grp group.u.assoc) + ?~ group ~ =/ role=(unit (unit role-tag)) - (role-for-ship:grp group.u.assoc src.bowl) + (role-for-ship-with-group:grp u.group group.u.assoc src.bowl) =/ writers=(set ship) - (get-tagged-ships:grp group.u.assoc [%graph resource %writers]) + %^ get-tagged-ships-with-group:grp + u.group + group.u.assoc + [%graph resource %writers] ?~ role ~ =/ is-admin=? ?=(?([~ %admin] [~ %moderator]) u.role) @@ -281,74 +386,111 @@ ++ node-to-indexed-post |= =node:store ^- indexed-post:store - =* index index.post.node - [(snag (dec (lent index)) index) post.node] + ?> ?=(%& -.post.node) + =* index index.p.post.node + [(snag (dec (lent index)) index) p.post.node] :: ++ is-allowed-add + ~/ %is-allowed-add |= [=resource:res nodes=(map index:store node:store)] - ^- ? - %- (bond |.(%.n)) + ^- [? (list card)] + |^ + %- (bond |.([%.n ~])) %+ biff (get-roles-writers-variation resource) |= [is-admin=? writers=(set ship) vip=vip-metadata:metadata] + ^- (unit [? (list card)]) %- some - %+ levy ~(tap by nodes) - |= [=index:store =node:store] - =/ parent-index=index:store - (scag (dec (lent index)) index) - ?: (~(has by nodes) parent-index) %.y - ?. =(author.post.node src.bowl) - %.n - =/ =permissions:store - %^ add-mark resource vip - (node-to-indexed-post node) - =/ =permission-level:store - (get-permission permissions is-admin writers) - ?- permission-level - %yes %.y - %no %.n - :: - %self - =/ parent-node=node:store - (got-node:gra resource parent-index) - =(author.post.parent-node src.bowl) - == + =/ a ~(tap by nodes) + =| cards=(list card) + |- ^- [? (list card)] + ?~ a [& cards] + =/ c (check i.a is-admin writers vip) + ?. -.c + [| (weld cards +.c)] + $(a t.a, cards (weld cards +.c)) + :: + ++ check + |= $: [=index:store =node:store] + is-admin=? + writers=(set ship) + vip=vip-metadata:metadata + == + ^- [? (list card)] + =/ parent-index=index:store + (scag (dec (lent index)) index) + ?: (~(has by nodes) parent-index) + [%.y ~] + ?: ?=(%| -.post.node) + [%.n ~] + ?. =(author.p.post.node src.bowl) + [%.n ~] + =/ added + %^ add-mark resource vip + (node-to-indexed-post node) + =* permissions -.added + =* cards +.added + =/ =permission-level:store + (get-permission permissions is-admin writers) + :_ cards + ?- permission-level + %yes %.y + %no %.n + :: + %self + =/ parent-node=node:store + (got-node:gra resource parent-index) + ?: ?=(%| -.post.parent-node) + %.n + =(author.p.post.parent-node src.bowl) + == + :: + ++ add-mark + |= [=resource:res vip=vip-metadata:metadata =indexed-post:store] + (perm-mark resource %add vip indexed-post) + -- :: ++ is-allowed-remove + ~/ %is-allowed-remove |= [=resource:res indices=(set index:store)] - ^- ? - %- (bond |.(%.n)) + ^- [? (list card)] + |^ + %- (bond |.([%.n ~])) %+ biff (get-roles-writers-variation resource) |= [is-admin=? writers=(set ship) vip=vip-metadata:metadata] - %- some - %+ levy ~(tap by indices) - |= =index:store - =/ =node:store - (got-node:gra resource index) - =/ =permissions:store - %^ remove-mark resource vip - (node-to-indexed-post node) - =/ =permission-level:store - (get-permission permissions is-admin writers) - ?- permission-level - %yes %.y - %no %.n - %self =(author.post.node src.bowl) - == -:: -++ build-permissions - |= [=mark kind=?(%add %remove) mode=?(%sing %next)] - ^- card - =/ =wire /perms/[mark]/[kind] - =/ =mood:clay [%c da+now.bowl /[mark]/(perm-mark-name kind)] - =/ =rave:clay ?:(?=(%sing mode) [mode mood] [mode mood]) - [%pass wire %arvo %c %warp our.bowl %home `rave] -:: -++ build-transform-add - |= [=mark mode=?(%sing %next)] - ^- card - =/ =wire /transform-add/[mark] - =/ =mood:clay [%c da+now.bowl /[mark]/transform-add-nodes] - =/ =rave:clay ?:(?=(%sing mode) [mode mood] [mode mood]) - [%pass wire %arvo %c %warp our.bowl %home `rave] + %- some + =/ a ~(tap by indices) + =| cards=(list card) + |- ^- [? (list card)] + ?~ a [& cards] + =/ c (check i.a is-admin writers vip) + ?. -.c + [| (weld cards +.c)] + $(a t.a, cards (weld cards +.c)) + :: + ++ check + |= [=index:store is-admin=? writers=(set ship) vip=vip-metadata:metadata] + ^- [? (list card)] + =/ =node:store + (got-node:gra resource index) + ?: ?=(%| -.post.node) + [%.n ~] + =/ removed + %^ remove-mark resource vip + (node-to-indexed-post node) + =* permissions -.removed + =* cards +.removed + =/ =permission-level:store + (get-permission permissions is-admin writers) + :_ cards + ?- permission-level + %yes %.y + %no %.n + %self =(author.p.post.node src.bowl) + == + :: + ++ remove-mark + |= [=resource:res vip=vip-metadata:metadata =indexed-post:store] + (perm-mark resource %remove vip indexed-post) + -- -- diff --git a/pkg/arvo/app/graph-store.hoon b/pkg/arvo/app/graph-store.hoon index 684cfdf097..cf9e84ea47 100644 --- a/pkg/arvo/app/graph-store.hoon +++ b/pkg/arvo/app/graph-store.hoon @@ -1,29 +1,36 @@ :: graph-store [landscape] :: -:: -/+ store=graph-store, sigs=signatures, res=resource, default-agent, dbug, verb, - *migrate +/+ store=graph-store, sigs=signatures, res=resource, default-agent, dbug, verb ~% %graph-store-top ..part ~ |% +$ card card:agent:gall +$ versioned-state - $% state-0 - state-1 - state-2 - state-3 + $% [%0 network:zero:store] + [%1 network:zero:store] + [%2 network:zero:store] + [%3 network:one:store] + [%4 network:store] + state-5 == :: -+$ state-0 [%0 network:zero:store] -+$ state-1 [%1 network:zero:store] -+$ state-2 [%2 network:zero:store] -+$ state-3 [%3 network:store] -:: ++$ state-5 [%5 network:store] ++ orm orm:store ++ orm-log orm-log:store +$ debug-input [%validate-graph =resource:store] +:: ++$ cache + $: validators=(map mark $-(indexed-post:store indexed-post:store)) + == +:: +:: TODO: come back to this and potentially use ford runes or otherwise +:: send a %t to be notified of validator changes ++$ inflated-state + $: state-5 + cache + == -- :: -=| state-3 +=| inflated-state =* state - :: %- agent:dbug @@ -35,25 +42,24 @@ def ~(. (default-agent this %|) bowl) :: ++ on-init [~ this] -++ on-save !>(state) +++ on-save !>(-.state) ++ on-load |= =old=vase ^- (quip card _this) =+ !<(old=versioned-state old-vase) =| cards=(list card) - |^ + |- ?- -.old - %0 + %0 + =* zro zero-load:upgrade:store %_ $ -.old %1 - cards cards - validators.old validators.old :: graphs.old %- ~(run by graphs.old) |= [=graph:zero:store q=(unit mark)] ^- [graph:zero:store (unit mark)] - :- (convert-unix-timestamped-graph:zero-load graph) + :- (convert-unix-timestamped-graph:zro graph) ?^ q q `%graph-validator-link :: @@ -63,138 +69,57 @@ == :: %1 + =* zro zero-load:upgrade:store %_ $ -.old %2 - graphs.old (~(run by graphs.old) change-revision-graph:zero-load) + graphs.old (~(run by graphs.old) change-revision-graph:zro) :: update-logs.old %- ~(run by update-logs.old) |=(a=* *update-log:zero:store) == :: - %2 + %2 + =* upg upgrade:store %_ $ -.old %3 - update-logs.old (~(run by update-logs.old) update-log-to-one:store) - graphs.old (~(run by graphs.old) marked-graph-to-one:store) - archive.old (~(run by archive.old) marked-graph-to-one:store) + update-logs.old (~(run by update-logs.old) update-log-to-one:upg) + graphs.old (~(run by graphs.old) marked-graph-to-one:upg) + archive.old (~(run by archive.old) marked-graph-to-one:upg) == :: - %3 [cards this(state old)] - == + %3 + =* upg upgrade:store + %_ $ + -.old %4 + graphs.old (~(run by graphs.old) marked-graph-to-two:upg) + archive.old (~(run by archive.old) marked-graph-to-two:upg) + tag-queries.old *tag-queries:store + validators.old ~ + :: + update-logs.old + %- ~(run by update-logs.old) + |=(a=* *update-log:store) + == :: - ++ zero-load - :: =* infinitely recurses - =, store=zero:store - =, orm=orm:zero:store - =, orm-log=orm-log:zero:store - |% - ++ change-revision-graph - |= [=graph:store q=(unit mark)] - ^- [graph:store (unit mark)] - |^ - :_ q - ?+ q graph - [~ %graph-validator-link] convert-links - [~ %graph-validator-publish] convert-publish - == - :: - ++ convert-links - %+ gas:orm *graph:store - %+ turn (tap:orm graph) - |= [=atom =node:store] - ^- [^atom node:store] - :: top-level - :: - :+ atom post.node - ?: ?=(%empty -.children.node) - [%empty ~] - :- %graph - %+ gas:orm *graph:store - %+ turn (tap:orm p.children.node) - |= [=^atom =node:store] - ^- [^^atom node:store] - :: existing comments get turned into containers for revisions - :: - :^ atom - post.node(contents ~, hash ~) - %graph - %+ gas:orm *graph:store - :_ ~ :- %0 - :_ [%empty ~] - post.node(index (snoc index.post.node atom), hash ~) - :: - ++ convert-publish - %+ gas:orm *graph:store - %+ turn (tap:orm graph) - |= [=atom =node:store] - ^- [^atom node:store] - :: top-level - :: - :+ atom post.node - ?: ?=(%empty -.children.node) - [%empty ~] - :- %graph - %+ gas:orm *graph:store - %+ turn (tap:orm p.children.node) - |= [=^atom =node:store] - ^- [^^atom node:store] - :: existing container for publish note revisions - :: - ?+ atom !! - %1 [atom node] - %2 - :+ atom post.node - ?: ?=(%empty -.children.node) - [%empty ~] - :- %graph - %+ gas:orm *graph:store - %+ turn (tap:orm p.children.node) - |= [=^^atom =node:store] - ^- [^^^atom node:store] - :+ atom post.node(contents ~, hash ~) - :- %graph - %+ gas:orm *graph:store - :_ ~ :- %1 - :_ [%empty ~] - post.node(index (snoc index.post.node atom), hash ~) - == - -- - :: - ++ maybe-unix-to-da - |= =atom - ^- @ - :: (bex 127) is roughly 226AD - ?. (lte atom (bex 127)) - atom - (add ~1970.1.1 (div (mul ~s1 atom) 1.000)) + %4 + %_ $ + -.old %5 :: - ++ convert-unix-timestamped-node - |= =node:store - ^- node:store - =. index.post.node - (convert-unix-timestamped-index index.post.node) - ?. ?=(%graph -.children.node) - node - :+ post.node - %graph - (convert-unix-timestamped-graph p.children.node) - :: - ++ convert-unix-timestamped-index - |= =index:store - (turn index maybe-unix-to-da) - :: - ++ convert-unix-timestamped-graph - |= =graph:store - %+ gas:orm *graph:store - %+ turn - (tap:orm graph) - |= [=atom =node:store] - ^- [^atom node:store] - :- (maybe-unix-to-da atom) - (convert-unix-timestamped-node node) - -- - -- + update-logs.old + %- ~(gas by *update-logs:store) + %+ turn ~(tap by graphs.old) + |= [=resource:store =graph:store mar=(unit mark)] + :- resource + =/ log (~(got by update-logs.old) resource) + ?. =(~ log) log + =/ =logged-update:store + [now.bowl %add-graph resource graph mar %.y] + (gas:orm-log ~ [now.bowl logged-update] ~) + == + :: + %5 [cards this(-.state old, +.state *cache)] + == :: ++ on-watch ~/ %graph-store-watch @@ -213,7 +138,7 @@ ++ give |= =action:store ^- (list card) - [%give %fact ~ [%graph-update-1 !>([now.bowl action])]]~ + [%give %fact ~ [%graph-update-2 !>([now.bowl action])]]~ -- :: ++ on-poke @@ -224,7 +149,7 @@ ?> (team:title our.bowl src.bowl) =^ cards state ?+ mark (on-poke:def mark vase) - %graph-update-1 (graph-update !<(update:store vase)) + %graph-update-2 (graph-update !<(update:store vase)) %noun (debug !<(debug-input vase)) %import (poke-import q.vase) == @@ -239,7 +164,7 @@ %add-graph (add-graph p.update +.q.update) %remove-graph (remove-graph +.q.update) %add-nodes (add-nodes p.update +.q.update) - %remove-nodes (remove-nodes p.update +.q.update) + %remove-posts (remove-posts p.update +.q.update) %add-signatures (add-signatures p.update +.q.update) %remove-signatures (remove-signatures p.update +.q.update) %add-tag (add-tag +.q.update) @@ -265,7 +190,9 @@ !(~(has by graphs) resource) == == ~| "validation of graph {} failed using mark {}" - ?> (validate-graph graph mark) + =^ is-valid state + (validate-graph graph mark) + ?> is-valid =/ =logged-update:store [time %add-graph resource graph mark overwrite] =/ =update-log:store @@ -302,6 +229,10 @@ (~(got by graphs) resource) ~| "cannot add duplicate nodes to {}" ?< (check-for-duplicates graph ~(key by nodes)) + ~| "validation of nodes failed using mark {}" + =^ is-valid state + (check-validity ~(tap by nodes) mark) + ?> is-valid =/ =update-log:store (~(got by update-logs) resource) =. update-log (put:orm-log update-log time [time [%add-nodes resource nodes]]) @@ -316,30 +247,41 @@ (add-node-list resource graph mark (sort-nodes nodes)) == :: + ++ check-validity + |= [lis=(list (pair index:store node:store)) mark=(unit ^mark)] + ^- [? _state] + |- + ?~ lis [& state] + =^ is-valid state + (validate-graph (gas:orm ~ [(rear p.i.lis) q.i.lis]~) mark) + ?. is-valid + [| state] + $(lis t.lis) + :: ++ check-for-duplicates |= [=graph:store nodes=(set index:store)] ^- ? - =/ node-list ~(tap in nodes) - |- - ?~ node-list %.n - ?: (has-node graph i.node-list) %.y - $(node-list t.node-list) - :: - ++ has-node - |= [=graph:store =index:store] - ^- ? - =/ node=(unit node:store) ~ - |- - ?~ index - ?=(^ node) - ?~ t.index - ?=(^ (get:orm graph i.index)) - =. node (get:orm graph i.index) - ?~ node %.n - ?- -.children.u.node - %empty %.n - %graph $(graph p.children.u.node, index t.index) - == + |^ + %+ lien ~(tap in nodes) + |= =index:store + (has-node graph index) + :: + ++ has-node + |= [=graph:store =index:store] + ^- ? + =/ node=(unit node:store) ~ + |- + ?~ index + ?=(^ node) + ?~ t.index + ?=(^ (get:orm graph i.index)) + =. node (get:orm graph i.index) + ?~ node %.n + ?- -.children.u.node + %empty %.n + %graph $(graph p.children.u.node, index t.index) + == + -- :: ++ sort-nodes |= nodes=(map index:store node:store) @@ -359,6 +301,11 @@ ?~ node-list graph =* index -.i.node-list =* node +.i.node-list + ~| "cannot add deleted post" + ?> ?=(%& -.post.node) + =* p p.post.node + ~| "graph indexes must match" + ?> =(index index.p) %_ $ node-list t.node-list graph (add-node-at-index graph index node mark) @@ -373,8 +320,6 @@ == ^- graph:store ?< ?=(~ index) - ~| "validation of node failed using mark {}" - ?> (validate-graph (gas:orm ~ [i.index node]~) mark) =* atom i.index %^ put:orm graph @@ -382,8 +327,10 @@ :: add child :: ?~ t.index - =* p post.node - ?~ hash.p node(signatures.post *signatures:store) + ~| "cannot add deleted post" + ?> ?=(%& -.post.node) + =* p p.post.node + ?~ hash.p node(signatures.p.post *signatures:store) =/ =validated-portion:store [parent-hash author.p time-sent.p contents.p] =/ =hash:store `@ux`(sham validated-portion) @@ -402,8 +349,14 @@ ^- internal-graph:store :- %graph %_ $ - index t.index - parent-hash hash.post.parent + index t.index + :: + parent-hash + ?- -.post.parent + %| `p.post.parent + %& hash.p.post.parent + == + :: graph ?: ?=(%graph -.children.parent) p.children.parent @@ -412,7 +365,7 @@ == -- :: - ++ remove-nodes + ++ remove-posts |= [=time =resource:store indices=(set index:store)] ^- (quip card _state) |^ @@ -420,82 +373,83 @@ (~(got by graphs) resource) =/ =update-log:store (~(got by update-logs) resource) =. update-log - (put:orm-log update-log time [time [%remove-nodes resource indices]]) - =/ [affected-indices=(set index:store) new-graph=graph:store] - (remove-indices resource graph (sort ~(tap in indices) by-lent)) - :: - :- (give [/updates]~ [%remove-nodes resource (~(uni in indices) affected-indices)]) + (put:orm-log update-log time [time [%remove-posts resource indices]]) + :- (give [/updates]~ [%remove-posts resource indices]) %_ state - update-logs (~(put by update-logs) resource update-log) + update-logs (~(put by update-logs) resource update-log) + :: graphs %+ ~(put by graphs) resource - [new-graph mark] + :_ mark + %^ remove-indices + resource + graph + (sort ~(tap in indices) by-lent) == :: - :: we always want to remove the deepest node first, - :: so we don't remove parents before children ++ by-lent |* [a=(list) b=(list)] ^- ? (gth (lent a) (lent b)) :: ++ remove-indices - =| affected=(set index:store) |= [=resource:store =graph:store indices=(list index:store)] - ^- [(set index:store) graph:store] - ?~ indices [affected graph] - =^ new-affected graph - (remove-index graph i.indices) + ^- graph:store + ?~ indices graph %_ $ - indices t.indices - affected (~(uni in affected) new-affected) - == - :: - ++ get-descendants - |= =graph:store - =| indices=(list index:store) - =/ nodes (tap:orm:store graph) - %- ~(gas in *(set index:store)) - |- =* tap-nodes $ - ^+ indices - %- zing - %+ turn nodes - |= [atom =node:store] - ^- (list index:store) - %+ welp - index.post.node^~ - ?. ?=(%graph -.children.node) - ~ - %_ tap-nodes - nodes (tap:orm p.children.node) + indices t.indices + graph (remove-index graph i.indices) == :: ++ remove-index - =| indices=(set index:store) + =| parent-hash=(unit hash:store) |= [=graph:store =index:store] - ^- [(set index:store) graph:store] - ?~ index [indices graph] + ^- graph:store + ?~ index graph =* atom i.index + %^ put:orm + graph + atom :: last index in list :: ?~ t.index - =^ rm-node graph (del:orm graph atom) - ?~ rm-node `graph - ?. ?=(%graph -.children.u.rm-node) - `graph - =/ new-indices - (get-descendants p.children.u.rm-node) - [(~(uni in indices) new-indices) graph] - =/ =node:store + =/ =node:store + ~| "cannot remove index that does not exist {}" + (need (get:orm graph atom)) + %_ node + post + ~| "cannot remove post that has already been removed" + ?> ?=(%& -.post.node) + =* p p.post.node + ^- maybe-post:store + :- %| + ?~ hash.p + =/ =validated-portion:store + [parent-hash author.p time-sent.p contents.p] + `@ux`(sham validated-portion) + u.hash.p + == + :: recurse children + :: + =/ parent=node:store ~| "parent index does not exist to remove a node from!" (need (get:orm graph atom)) ~| "child index does not exist to remove a node from!" - ?> ?=(%graph -.children.node) - =^ new-indices p.children.node - $(graph p.children.node, index t.index) - :- (~(uni in indices) new-indices) - (put:orm graph atom node) + ?> ?=(%graph -.children.parent) + %_ parent + p.children + %_ $ + index t.index + graph p.children.parent + :: + parent-hash + ?- -.post.parent + %| `p.post.parent + %& hash.p.post.parent + == + == + == -- :: ++ add-signatures @@ -531,11 +485,13 @@ graph atom ?~ t.index + ~| "cannot add signatures to a deleted post" + ?> ?=(%& -.post.node) ~| "cannot add signatures to a node missing a hash" - ?> ?=(^ hash.post.node) + ?> ?=(^ hash.p.post.node) ~| "signatures did not match public keys!" - ?> (are-signatures-valid:sigs our.bowl signatures u.hash.post.node now.bowl) - node(signatures.post (~(uni in signatures) signatures.post.node)) + ?> (are-signatures-valid:sigs our.bowl signatures u.hash.p.post.node now.bowl) + node(signatures.p.post (~(uni in signatures) signatures.p.post.node)) ~| "child graph does not exist to add signatures to!" ?> ?=(%graph -.children.node) node(p.children $(graph p.children.node, index t.index)) @@ -576,28 +532,29 @@ graph atom ?~ t.index - node(signatures.post (~(dif in signatures) signatures.post.node)) + ~| "cannot add signatures to a deleted post" + ?> ?=(%& -.post.node) + node(signatures.p.post (~(dif in signatures) signatures.p.post.node)) ~| "child graph does not exist to add signatures to!" ?> ?=(%graph -.children.node) node(p.children $(graph p.children.node, index t.index)) -- :: ++ add-tag - |= [=term =resource:store] + |= [=term =uid:store] ^- (quip card _state) - ?> (~(has by graphs) resource) - :- (give [/updates /tags ~] [%add-tag term resource]) + ?> (~(has by graphs) resource.uid) + :- (give [/updates /tags ~] [%add-tag term uid]) %_ state - tag-queries (~(put ju tag-queries) term resource) + tag-queries (~(put ju tag-queries) term uid) == :: ++ remove-tag - |= [=term =resource:store] + |= [=term =uid:store] ^- (quip card _state) - ?> (~(has by graphs) resource) - :- (give [/updates /tags ~] [%remove-tag term resource]) + :- (give [/updates /tags ~] [%remove-tag term uid]) %_ state - tag-queries (~(del ju tag-queries) term resource) + tag-queries (~(del ju tag-queries) term uid) == :: ++ archive-graph @@ -610,10 +567,6 @@ archive (~(put by archive) resource (~(got by graphs) resource)) graphs (~(del by graphs) resource) update-logs (~(del by update-logs) resource) - tag-queries - %- ~(run by tag-queries) - |= =resources:store - (~(del in resources) resource) == :: ++ unarchive-graph @@ -648,7 +601,7 @@ ?- -.q.update %add-graph update(resource.q resource) %add-nodes update(resource.q resource) - %remove-nodes update(resource.q resource) + %remove-posts update(resource.q resource) %add-signatures update(resource.uid.q resource) %remove-signatures update(resource.uid.q resource) == @@ -657,7 +610,7 @@ ++ give |= [paths=(list path) update=action:store] ^- (list card) - [%give %fact paths [%graph-update-1 !>([now.bowl update])]]~ + [%give %fact paths [%graph-update-2 !>([now.bowl update])]]~ -- :: ++ debug @@ -665,24 +618,38 @@ ^- (quip card _state) =/ [=graph:store mark=(unit mark:store)] (~(got by graphs) resource.debug-input) - ?> (validate-graph graph mark) + =^ is-valid state + (validate-graph graph mark) + ?> is-valid [~ state] :: ++ validate-graph |= [=graph:store mark=(unit mark:store)] - ^- ? - ?~ mark %.y - =/ =dais:clay - .^ =dais:clay - %cb - /(scot %p our.bowl)/[q.byk.bowl]/(scot %da now.bowl)/[u.mark] + ^- [? _state] + ?~ mark [%.y state] + =/ has-validator (~(has by validators) u.mark) + =/ validate=$-(indexed-post:store indexed-post:store) + ?: has-validator + (~(got by validators) u.mark) + .^ $-(indexed-post:store indexed-post:store) + %cf + (scot %p our.bowl) + q.byk.bowl + (scot %da now.bowl) + u.mark + %graph-indexed-post + ~ == + :_ state(validators (~(put by validators) u.mark validate)) |- ^- ? ?~ graph %.y %+ roll (tap:orm graph) |= [[=atom =node:store] out=?] ^- ? - ?& ?=(^ (vale:dais [atom post.node])) + ?& ?| ?=(%| -.post.node) + ?=(^ (validate [atom p.post.node])) + == + :: ?- -.children.node %empty %.y %graph ^$(graph p.children.node) @@ -691,155 +658,9 @@ ++ poke-import |= arc=* ^- (quip card _state) - |^ - =/ sty=state-3 [%3 (remake-network ;;(tree-network +.arc))] - :_ sty - %+ turn ~(tap by graphs.sty) - |= [rid=resource:store =marked-graph:store] - ^- card - ?: =(our.bowl entity.rid) - =/ =cage [%push-hook-action !>([%add rid])] - [%pass / %agent [our.bowl %graph-push-hook] %poke cage] - (try-rejoin rid 0) - :: - +$ tree-network - $: graphs=tree-graphs - tag-queries=(tree [term (tree resource:store)]) - update-logs=tree-update-logs - archive=tree-graphs - validators=(tree ^mark) - == - +$ tree-graphs (tree [resource:store tree-marked-graph]) - +$ tree-marked-graph [p=tree-graph q=(unit ^mark)] - +$ tree-graph (tree [atom tree-node]) - +$ tree-node [post=tree-post children=tree-internal-graph] - +$ tree-internal-graph - $~ [%empty ~] - $% [%graph p=tree-graph] - [%empty ~] - == - +$ tree-update-logs (tree [resource:store tree-update-log]) - +$ tree-update-log (tree [time tree-logged-update]) - +$ tree-logged-update - $: p=time - $= q - $% [%add-graph =resource:store =tree-graph mark=(unit ^mark) ow=?] - [%add-nodes =resource:store nodes=(tree [index:store tree-node])] - [%remove-nodes =resource:store indices=(tree index:store)] - [%add-signatures =uid:store signatures=tree-signatures] - [%remove-signatures =uid:store signatures=tree-signatures] - == - == - +$ tree-signatures (tree signature:store) - +$ tree-post - $: author=ship - =index:store - time-sent=time - contents=(list content:store) - hash=(unit hash:store) - signatures=tree-signatures - == - :: - ++ remake-network - |= t=tree-network - ^- network:store - :* (remake-graphs graphs.t) - (remake-jug tag-queries.t) - (remake-update-logs update-logs.t) - (remake-graphs archive.t) - (remake-set validators.t) - == - :: - ++ remake-graphs - |= t=tree-graphs - ^- graphs:store - %- remake-map - (~(run by t) remake-marked-graph) - :: - ++ remake-marked-graph - |= t=tree-marked-graph - ^- marked-graph:store - [(remake-graph p.t) q.t] - :: - ++ remake-graph - |= t=tree-graph - ^- graph:store - %+ gas:orm *graph:store - %+ turn ~(tap by t) - |= [a=atom tn=tree-node] - ^- [atom node:store] - [a (remake-node tn)] - :: - ++ remake-internal-graph - |= t=tree-internal-graph - ^- internal-graph:store - ?: ?=(%empty -.t) - [%empty ~] - [%graph (remake-graph p.t)] - :: - ++ remake-node - |= t=tree-node - ^- node:store - :- (remake-post post.t) - (remake-internal-graph children.t) - :: - ++ remake-update-logs - |= t=tree-update-logs - ^- update-logs:store - %- remake-map - (~(run by t) remake-update-log) - :: - ++ remake-update-log - |= t=tree-update-log - ^- update-log:store - =/ ulm ((ordered-map time logged-update:store) gth) - %+ gas:ulm *update-log:store - %+ turn ~(tap by t) - |= [=time tlu=tree-logged-update] - ^- [^time logged-update:store] - [time (remake-logged-update tlu)] - :: - ++ remake-logged-update - |= t=tree-logged-update - ^- logged-update:store - :- p.t - ?- -.q.t - %add-graph - :* %add-graph - resource.q.t - (remake-graph tree-graph.q.t) - mark.q.t - ow.q.t - == - :: - %add-nodes - :- %add-nodes - :- resource.q.t - %- remake-map - (~(run by nodes.q.t) remake-node) - :: - %remove-nodes - [%remove-nodes resource.q.t (remake-set indices.q.t)] - :: - %add-signatures - [%add-signatures uid.q.t (remake-set signatures.q.t)] - :: - %remove-signatures - [%remove-signatures uid.q.t (remake-set signatures.q.t)] - == - :: - ++ remake-post - |= t=tree-post - ^- post:store - t(signatures (remake-set signatures.t)) - -- - :: - ++ try-rejoin - |= [rid=resource:store nack-count=@] - ^- card - =/ res-path (en-path:res rid) - =/ wire [%try-rejoin (scot %ud nack-count) res-path] - [%pass wire %agent [entity.rid %graph-push-hook] %watch resource+res-path] + =^ cards -.state + (import:store arc our.bowl) + [cards state] -- :: ++ on-peek @@ -858,15 +679,15 @@ ``noun+!>(q.u.result) :: [%x %keys ~] - :- ~ :- ~ :- %graph-update-1 + :- ~ :- ~ :- %graph-update-2 !>(`update:store`[now.bowl [%keys ~(key by graphs)]]) :: [%x %tags ~] - :- ~ :- ~ :- %graph-update-1 + :- ~ :- ~ :- %graph-update-2 !>(`update:store`[now.bowl [%tags ~(key by tag-queries)]]) :: [%x %tag-queries ~] - :- ~ :- ~ :- %graph-update-1 + :- ~ :- ~ :- %graph-update-2 !>(`update:store`[now.bowl [%tag-queries tag-queries]]) :: [%x %graph @ @ ~] @@ -875,7 +696,7 @@ =/ result=(unit marked-graph:store) (~(get by graphs) [ship term]) ?~ result [~ ~] - :- ~ :- ~ :- %graph-update-1 + :- ~ :- ~ :- %graph-update-2 !> ^- update:store :- now.bowl [%add-graph [ship term] `graph:store`p.u.result q.u.result %.y] @@ -890,7 +711,7 @@ ?~ result ~& no-archived-graph+[ship term] [~ ~] - :- ~ :- ~ :- %graph-update-1 + :- ~ :- ~ :- %graph-update-2 !> ^- update:store :- now.bowl [%add-graph [ship term] `graph:store`p.u.result q.u.result %.y] @@ -906,13 +727,13 @@ =/ graph=(unit marked-graph:store) (~(get by graphs) [ship term]) ?~ graph [~ ~] - :- ~ :- ~ :- %graph-update-1 + :- ~ :- ~ :- %graph-update-2 !> ^- update:store :- now.bowl :+ %add-nodes [ship term] %- ~(gas by *(map index:store node:store)) - %+ turn (tap:orm `graph:store`(subset:orm p.u.graph start end)) + %+ turn (tap:orm `graph:store`(lot:orm p.u.graph start end)) |= [=atom =node:store] ^- [index:store node:store] [~[atom] node] @@ -933,7 +754,7 @@ (turn t.t.t.t.path (cury slav %ud)) =/ node=(unit node:store) (get-node ship term index) ?~ node [~ ~] - :- ~ :- ~ :- %graph-update-1 + :- ~ :- ~ :- %graph-update-2 !> ^- update:store :- now.bowl :+ %add-nodes @@ -952,7 +773,7 @@ =/ graph (get-node-children ship term parent) ?~ graph [~ ~] - :- ~ :- ~ :- %graph-update-1 + :- ~ :- ~ :- %graph-update-2 !> ^- update:store :- now.bowl :+ %add-nodes @@ -963,13 +784,33 @@ %+ turn =- ?.(older (slag (safe-sub (lent -) count) -) (scag count -)) %- tap:orm - %+ subset:orm u.graph + %+ lot:orm u.graph =/ idx (snag (dec (lent index)) index) ?:(older [`idx ~] [~ `idx]) |= [=atom =node:store] ^- [index:store node:store] [(snoc parent atom) node] + :: + [%x %shallow-children @ @ *] + =/ newest ?=(%newest i.t.path) + =/ =ship (slav %p i.t.t.path) + =/ =term i.t.t.t.path + =/ =index:store + (turn t.t.t.t.path (cury slav %ud)) + =/ children + (get-node-children ship term index) + ?~ children [~ ~] + :- ~ :- ~ :- %graph-update-2 + !> ^- update:store + :+ now.bowl %add-nodes + :- [ship term] + %- ~(gas by *(map index:store node:store)) + %+ turn (tap:orm u.children) + |= [=atom =node:store] + ^- [index:store node:store] + :- (snoc index atom) + node(children [%empty ~]) :: [%x ?(%newest %oldest) @ @ @ *] =/ newest ?=(%newest i.t.path) @@ -982,7 +823,7 @@ =/ children (get-node-children ship term index) ?~ children [~ ~] - :- ~ :- ~ :- %graph-update-1 + :- ~ :- ~ :- %graph-update-2 !> ^- update:store :- now.bowl :+ %add-nodes @@ -1008,17 +849,121 @@ ?- -.children.u.node %empty [~ ~] %graph - :- ~ :- ~ :- %graph-update-1 + :- ~ :- ~ :- %graph-update-2 !> ^- update:store :- now.bowl :+ %add-nodes [ship term] %- ~(gas by *(map index:store node:store)) - %+ turn (tap:orm `graph:store`(subset:orm p.children.u.node end start)) + %+ turn (tap:orm `graph:store`(lot:orm p.children.u.node end start)) |= [=atom =node:store] ^- [index:store node:store] [(snoc index atom) node] == + :: + [%x %deep-nodes-older-than @ @ @ @ ~] + =/ =ship (slav %p i.t.t.path) + =/ =term i.t.t.t.path + =/ count=(unit atom) (rush i.t.t.t.t.path dem:ag) + =/ start=(unit atom) (rush i.t.t.t.t.t.path dem:ag) + ?: ?=(~ count) + [~ ~] + =/ result=(unit marked-graph:store) + (~(get by graphs) [ship term]) + ?~ result + [~ ~] + :- ~ :- ~ :- %graph-update-2 + !> ^- update:store + :- now.bowl + :+ %add-nodes + [ship term] + =* a u.count + =/ b=(list (pair atom node:store)) + (tab:orm p.u.result start u.count) + =| c=index:store + =| d=(map index:store node:store) + =| e=@ud + =- d + |- ^- [e=@ud d=(map index:store node:store)] + ?: ?|(?=(~ b) =(e a)) + [e d] + =* atom p.i.b + =* node q.i.b + =. c (snoc c atom) + ?- -.children.node + %empty + $(b t.b, e +(e), d (~(put by d) c node), c (snip c)) + :: + %graph + =/ f $(b (tab:orm p.children.node ~ (sub a e))) + ?: =(e.f a) f + %_ $ + b t.b + e +(e.f) + d (~(put by d.f) c node(children [%empty ~])) + c (snip c) + == + == + :: + [%x %firstborn @ @ @ *] + |^ + =/ =ship (slav %p i.t.t.path) + =/ =term i.t.t.t.path + =/ =index:store + (turn t.t.t.t.path (cury slav %ud)) + ?> ?=(^ index) + =/ result=(unit marked-graph:store) + (~(get by graphs) [ship term]) + ?~ result + [~ ~] + %- (bond |.(`(unit (unit cage))`[~ ~])) + %+ biff + (collect-parents p.u.result index ship term) + (corl some collect-firstborn) + :: + ++ collect-parents + |= [=graph:store =index:store =ship =term] + ^- (unit [node:store index:store (map index:store node:store) ^ship ^term]) + =| =(map index:store node:store) + =| =node:store + =| ind=index:store + =/ len (lent index) + |- + ?: (gte (lent ind) len) + `[node ind map ship term] + ?> ?=(^ index) + =* atom i.index + ?. (has:orm graph atom) + ~ + =: node (got:orm graph atom) + ind (snoc ind atom) + == + ?: ?=(%empty -.children.node) + ?. (gte (lent ind) len) + ~ + :- ~ + :* node ind + (~(put by map) ind node) + ship term + == + %_ $ + index t.index + graph p.children.node + map (~(put by map) ind node(children empty+~)) + == + :: + ++ collect-firstborn + |= [=node:store =index:store map=(map index:store node:store) =ship =term] + ^- (unit (unit cage)) + ?: ?=(%empty -.children.node) + :- ~ :- ~ :- %graph-update-2 + !> ^- update:store + [now.bowl [%add-nodes [ship term] map]] + =/ item=[k=atom v=node:store] + (need (ram:orm p.children.node)) + =. index (snoc index k.item) + $(map (~(put by map) index v.item(children empty+~)), node v.item) + -- :: [%x %update-log-subset @ @ @ @ ~] =/ =ship (slav %p i.t.t.path) @@ -1028,7 +973,7 @@ =/ update-log=(unit update-log:store) (~(get by update-logs) [ship term]) ?~ update-log [~ ~] :: orm-log is ordered backwards, so swap start and end - ``noun+!>((subset:orm-log u.update-log end start)) + ``noun+!>((lot:orm-log u.update-log end start)) :: [%x %update-log @ @ ~] =/ =ship (slav %p i.t.t.path) @@ -1046,7 +991,7 @@ %+ biff m-update-log |= =update-log:store =/ result=(unit [=time =update:store]) - (peek:orm-log:store update-log) + (pry:orm-log:store update-log) (bind result |=([=time update:store] time)) == :: diff --git a/pkg/arvo/app/group-push-hook.hoon b/pkg/arvo/app/group-push-hook.hoon index e1a2fc2376..20decf8c3a 100644 --- a/pkg/arvo/app/group-push-hook.hoon +++ b/pkg/arvo/app/group-push-hook.hoon @@ -47,8 +47,9 @@ :: ++ transform-proxy-update |= vas=vase - ^- (unit vase) + ^- (quip card (unit vase)) =/ =update:store !<(update:store vas) + :- ~ ?: ?=(%initial -.update) ~ |^ diff --git a/pkg/arvo/app/group-store.hoon b/pkg/arvo/app/group-store.hoon index 59ddc4dc94..f869a4c0a3 100644 --- a/pkg/arvo/app/group-store.hoon +++ b/pkg/arvo/app/group-store.hoon @@ -136,13 +136,13 @@ ^- (unit (unit cage)) ?+ path (on-peek:def path) [%y %groups ~] - ``noun+!>(~(key by groups)) + ``noun+!>(`(set resource)`~(key by groups)) :: [%x %groups %ship @ @ ~] =/ rid=(unit resource) (de-path-soft:resource t.t.path) ?~ rid ~ - ``noun+!>((peek-group u.rid)) + ``noun+!>(`(unit group)`(peek-group u.rid)) :: [%x %groups %ship @ @ %join @ ~] =/ rid=(unit resource) @@ -150,7 +150,7 @@ =/ =ship (slav %p i.t.t.t.t.t.t.path) ?~ rid ~ - ``noun+!>((peek-group-join u.rid ship)) + ``noun+!>(`?`(peek-group-join u.rid ship)) :: [%x %export ~] ``noun+!>(state) @@ -199,6 +199,7 @@ :: ++ peek-group-join |= [rid=resource =ship] + ^- ? =/ ugroup (~(get by groups) rid) ?~ ugroup diff --git a/pkg/arvo/app/hark-graph-hook.hoon b/pkg/arvo/app/hark-graph-hook.hoon index d05d69a973..6cc767efcc 100644 --- a/pkg/arvo/app/hark-graph-hook.hoon +++ b/pkg/arvo/app/hark-graph-hook.hoon @@ -24,7 +24,6 @@ watch-on-self=_& == :: -:: ++ scry |* [[our=@p now=@da] =mold p=path] ?> ?=(^ p) @@ -37,7 +36,6 @@ %^ scry [our now] tube:clay /cc/[desk]/[mark]/notification-kind -:: -- :: =| state-1 @@ -126,15 +124,15 @@ :: ++ poke-noun |= non=* - ?> ?=(%rewatch-dms non) - =/ graphs=(list resource) - ~(tap in get-keys:gra) - :- ~ - %_ state - watching - %- ~(gas in watching) - (murn graphs |=(rid=resource ?:((should-watch:ha rid) `[rid ~] ~))) - == + [~ state] +:: ?> ?=(%rewatch-dms non) +:: =/ graphs=(list resource) +:: ~(tap in get-keys:gra) +:: %_ state +:: watching +:: %- ~(gas in watching) +:: (murn graphs |=(rid=resource ?:((should-watch:ha rid) `[rid ~] ~))) +:: == :: ++ hark-graph-hook-action |= =action:hook @@ -182,7 +180,7 @@ ~[watch-graph:ha] :: %fact - ?. ?=(%graph-update-1 p.cage.sign) + ?. ?=(%graph-update-2 p.cage.sign) (on-agent:def wire sign) =^ cards state (graph-update !<(update:graph-store q.cage.sign)) @@ -197,18 +195,20 @@ :: ?(%remove-graph %archive-graph) (remove-graph resource.q.update) - :: - %remove-nodes - (remove-nodes resource.q.update indices.q.update) - :: + :: + %remove-posts + (remove-posts resource.q.update indices.q.update) + :: %add-nodes =* rid resource.q.update - (check-nodes ~(val by nodes.q.update) rid) + =/ assoc=(unit association:metadata) + (peek-association:met %graph rid) + (check-nodes ~(val by nodes.q.update) rid assoc) == :: this is awful, but notification kind should always switch :: on the index, so hopefully doesn't matter :: TODO: rethink this - ++ remove-nodes + ++ remove-posts |= [rid=resource indices=(set index:graph-store)] =/ to-remove %- ~(gas by *(set [resource index:graph-store])) @@ -256,32 +256,22 @@ =/ graph=graph:graph-store :: graph in subscription is bunted (get-graph-mop:gra rid) =/ node=(unit node:graph-store) - (bind (peek:orm:graph-store graph) |=([@ =node:graph-store] node)) + (bind (pry:orm:graph-store graph) |=([@ =node:graph-store] node)) + =/ assoc=(unit association:metadata) + (peek-association:met %graph rid) =^ cards state - (check-nodes (drop node) rid) - ?. (should-watch:ha rid) + (check-nodes (drop node) rid assoc) + ?. (should-watch:ha rid assoc) [cards state] :_ state(watching (~(put in watching) [rid ~])) (weld cards (give:ha ~[/updates] %listen [rid ~])) :: - :: ++ check-nodes |= $: nodes=(list node:graph-store) rid=resource + assoc=(unit association:metadata) == - =/ group=(unit resource) - (peek-group:met %graph rid) - ?~ group - ~& no-group+rid - `state - =/ metadatum=(unit metadatum:metadata) - (peek-metadatum:met %graph rid) - ?~ metadatum `state - =/ module=term - ?: ?=(%empty -.config.u.metadatum) %$ - ?: ?=(%group -.config.u.metadatum) %$ - module.config.u.metadatum - abet:check:(abed:handle-update:ha rid nodes u.group module) + abet:check:(abed:handle-update:ha rid nodes) -- :: ++ on-peek on-peek:def @@ -343,31 +333,31 @@ $(contents t.contents) :: ++ should-watch - |= rid=resource + |= [rid=resource assoc=(unit association:metadata)] ^- ? - =/ group-rid=(unit resource) - (peek-group:met %graph rid) - ?~ group-rid %.n - ?| !(is-managed:grp u.group-rid) - &(watch-on-self =(our.bowl entity.rid)) - == + ?~ assoc + %.y + &(watch-on-self =(our.bowl entity.rid)) :: ++ handle-update |_ $: rid=resource :: input updates=(list node:graph-store) - group=resource - module=term + mark=(unit mark) hark-pokes=(list action:store) :: output new-watches=(list index:graph-store) == ++ update-core . :: ++ abed - |= [r=resource upds=(list node:graph-store) grp=resource mod=term] - update-core(rid r, updates upds, group grp, module mod) + |= [r=resource upds=(list node:graph-store)] + =/ m=(unit ^mark) + (get-mark:gra r) + update-core(rid r, updates upds, mark m) :: ++ get-conversion - (^get-conversion rid) + :: LA: this tube should be cached in %hark-graph-hook state + :: instead of just trying to keep it warm, as the scry overhead is large + ~+ (^get-conversion rid) :: ++ abet ^- (quip card _state) @@ -417,30 +407,35 @@ |= =node:graph-store ^+ update-core =. update-core (check-node-children node) + ?: ?=(%| -.post.node) + update-core + =* pos p.post.node =+ !< notif-kind=(unit notif-kind:hook) - (get-conversion !>([0 post.node])) + %- get-conversion + !>(`indexed-post:graph-store`[0 pos]) ?~ notif-kind update-core =/ desc=@t - ?: (is-mention contents.post.node) + ?: (is-mention contents.pos) %mention name.u.notif-kind =* not-kind u.notif-kind =/ parent=index:post - (scag parent.index-len.not-kind index.post.node) + (scag parent.index-len.not-kind index.pos) =/ notif-index=index:store - [%graph group rid module desc parent] - ?: =(our.bowl author.post.node) + [%graph rid mark desc parent] + ?: =(our.bowl author.pos) (self-post node notif-index not-kind) =. update-core - (update-unread-count not-kind notif-index [time-sent index]:post.node) + (update-unread-count not-kind notif-index [time-sent index]:pos) =? update-core ?| =(desc %mention) (~(has in watching) [rid parent]) + =(mark `%graph-validator-dm) == =/ =contents:store - [%graph (limo post.node ~)] - (add-unread notif-index [time-sent.post.node %.n contents]) + [%graph (limo pos ~)] + (add-unread notif-index [time-sent.pos %.n contents]) update-core :: ++ update-unread-count @@ -459,19 +454,19 @@ =notif-kind:hook == ^+ update-core + ?> ?=(%& -.post.node) =/ =stats-index:store (to-stats-index:store index) =. update-core - (hark %seen-index time-sent.post.node stats-index) + (hark %seen-index time-sent.p.post.node stats-index) =? update-core ?=(%count mode.notif-kind) (hark %read-count stats-index) =? update-core watch-on-self - (new-watch index.post.node [watch-for index-len]:notif-kind) + (new-watch index.p.post.node [watch-for index-len]:notif-kind) update-core :: ++ add-unread |= [=index:store =notification:store] (hark %add-note index notification) - :: -- -- diff --git a/pkg/arvo/app/hark-store.hoon b/pkg/arvo/app/hark-store.hoon index e62e5e27f2..ab34ac6f84 100644 --- a/pkg/arvo/app/hark-store.hoon +++ b/pkg/arvo/app/hark-store.hoon @@ -25,6 +25,7 @@ state-4 state-5 state-6 + state-7 == +$ unread-stats [indices=(set index:graph-store) last=@da] @@ -32,6 +33,8 @@ +$ base-state $: unreads-each=(jug stats-index:store index:graph-store) unreads-count=(map stats-index:store @ud) + timeboxes=(map stats-index:store @da) + unread-notes=timebox:store last-seen=(map stats-index:store @da) =notifications:store archive=notifications:store @@ -52,23 +55,16 @@ [%5 state-three:store] :: +$ state-6 - [%6 base-state] + [%6 state-four:store] +:: ++$ state-7 + [%7 base-state] :: -+$ inflated-state - $: state-6 - cache - == -:: $cache: useful to have precalculated, but can be derived from state -:: albeit expensively -+$ cache - $: by-index=(jug stats-index:store [time=@da =index:store]) - ~ - == :: ++ orm ((ordered-map @da timebox:store) gth) -- :: -=| inflated-state +=| state-7 =* state - :: =< @@ -87,26 +83,43 @@ :_ this ~[autoseen-timer] :: -++ on-save !>(-.state) +++ on-save !>(state) ++ on-load |= =old=vase ^- (quip card _this) =/ old !<(versioned-state old-vase) =| cards=(list card) - |^ + |^ + ^- (quip card _this) ?- -.old - %6 + %7 :- (flop cards) - this(-.state old, +.state (inflate-cache:ha old)) + this(state old) + :: + %6 + %_ $ + -.old %7 :: - %5 + +.old + %* . *base-state + notifications (notifications:to-five:upgrade:store notifications.old) + archive ~ + unreads-each unreads-each.old + unreads-count unreads-count.old + last-seen last-seen.old + current-timebox current-timebox + dnd dnd.old + == + == + :: + %5 %_ $ -.old %6 - notifications.old (convert-notifications-4 notifications.old) - archive.old (convert-notifications-4 archive.old) + notifications.old (notifications:to-four:upgrade:store notifications.old) + archive.old *notifications:state-four:store == - :: + :: %4 %_ $ -.old %5 @@ -115,14 +128,14 @@ %- ~(run by last-seen.old) |=(old=@da (min old now.bowl)) == - :: + :: %3 %_ $ -.old %4 - notifications.old (convert-notifications-3 notifications.old) - archive.old (convert-notifications-3 archive.old) + notifications.old (notifications:to-three:upgrade:store notifications.old) + archive.old *notifications:state-three:store == - :: + :: %2 %_ $ -.old %3 @@ -131,7 +144,7 @@ :_ cards [%pass / %agent [our dap]:bowl %poke noun+!>(%fix-dangling)] == - :: + :: %1 %_ $ :: @@ -146,7 +159,7 @@ dnd dnd.old == == - :: + :: %0 %_ $ :: @@ -160,98 +173,6 @@ == == :: - ++ convert-notifications-4 - |= old=notifications:state-three:store - %+ gas:orm *notifications:store - ^- (list [@da timebox:store]) - %+ murn - (tap:orm:state-three:store old) - |= [time=@da =timebox:state-three:store] - ^- (unit [@da timebox:store]) - =/ new-timebox=timebox:store - (convert-timebox-4 timebox) - ?: =(0 ~(wyt by new-timebox)) - ~ - `[time new-timebox] - :: - ++ convert-timebox-4 - |= =timebox:state-three:store - ^- timebox:store - %- ~(gas by *timebox:store) - ^- (list [index:store notification:store]) - %+ murn - ~(tap by timebox) - |= [=index:store =notification:state-three:store] - ^- (unit [index:store notification:store]) - =/ new-notification=(unit notification:store) - (convert-notification-4 notification) - ?~ new-notification ~ - `[index u.new-notification] - :: - ++ convert-notification-4 - |= =notification:state-three:store - ^- (unit notification:store) - ?: ?=(%group -.contents.notification) - `notification - =/ con=(list post:post) - (convert-graph-contents-4 list.contents.notification) - ?: =(~ con) ~ - =, notification - `[date read %graph con] - :: - ++ convert-graph-contents-4 - |= con=(list post:post-zero:post) - ^- (list post:post) - (turn con post-to-one:graph-store) - :: - ++ convert-notifications-3 - |= old=notifications:state-two:store - %+ gas:orm:state-three:store *notifications:state-three:store - ^- (list [@da timebox:state-three:store]) - %+ murn - (tap:orm:state-two:store old) - |= [time=@da =timebox:state-two:store] - ^- (unit [@da timebox:state-three:store]) - =/ new-timebox=timebox:state-three:store - (convert-timebox-3 timebox) - ?: =(0 ~(wyt by new-timebox)) - ~ - `[time new-timebox] - :: - ++ convert-timebox-3 - |= =timebox:state-two:store - ^- timebox:state-three:store - %- ~(gas by *timebox:state-three:store) - ^- (list [index:state-three:store notification:state-three:store]) - %+ murn - ~(tap by timebox) - |= [=index:store =notification:state-two:store] - ^- (unit [index:store notification:state-three:store]) - =/ new-notification=(unit notification:state-three:store) - (convert-notification-3 notification) - ?~ new-notification ~ - `[index u.new-notification] - :: - ++ convert-notification-3 - |= =notification:state-two:store - ^- (unit notification:state-three:store) - ?: ?=(%graph -.contents.notification) - `notification - =/ con=(list group-contents:store) - (convert-group-contents-3 list.contents.notification) - ?: =(~ con) ~ - =, notification - `[date read %group con] - :: - ++ convert-group-contents-3 - |= con=(list group-contents:state-two:store) - ^- (list group-contents:store) - %+ murn con - |= =group-contents:state-two:store - ^- (unit group-contents:store) - ?. ?=(?(%add-members %remove-members) -.group-contents) ~ - `group-contents - :: ++ uni-by |= [a=(set index:graph-store) b=(set index:graph-store)] =/ merged @@ -291,12 +212,12 @@ |= =timebox:state-zero:store ^- timebox:state-two:store %- ~(gas by *timebox:state-two:store) - ^- (list [index:store notification:state-two:store]) + ^- (list [index:state-two:store notification:state-two:store]) %+ murn ~(tap by timebox) |= [=index:state-zero:store =notification:state-zero:store] - ^- (unit [index:store notification:state-two:store]) - =/ new-index=(unit index:store) + ^- (unit [index:state-two:store notification:state-two:store]) + =/ new-index=(unit index:state-two:store) (convert-index-1 index) =/ new-notification=(unit notification:state-two:store) (convert-notification-1 notification) @@ -306,13 +227,13 @@ :: ++ convert-index-1 |= =index:state-zero:store - ^- (unit index:store) + ^- (unit index:state-two:store) ?+ -.index `index %chat ~ :: %graph =, index - `[%graph group graph module description ~] + `[%graph graph *resource module description ~] == :: ++ convert-notification-1 @@ -339,8 +260,14 @@ ^- update:store :- %more ^- (list update:store) - :- give-unreads - [%set-dnd dnd]~ + :~ give-unreads + [%set-dnd dnd] + give-notifications + == + :: + ++ give-notifications + ^- update:store + [%timebox ~ ~(tap by unread-notes)] :: ++ give-since-unreads ^- (list [stats-index:store stats:store]) @@ -348,7 +275,6 @@ ~(tap by unreads-count) |= [=stats-index:store count=@ud] :* stats-index - (~(gut by by-index) stats-index ~) [%count count] (~(gut by last-seen) stats-index *time) == @@ -359,31 +285,16 @@ ~(tap by unreads-each) |= [=stats-index:store indices=(set index:graph-store)] :* stats-index - (~(gut by by-index) stats-index ~) [%each indices] (~(gut by last-seen) stats-index *time) == :: - ++ give-group-unreads - ^- (list [stats-index:store stats:store]) - %+ murn ~(tap by by-index) - |= [=stats-index:store nots=(set [time index:store])] - ?. ?=(%group -.stats-index) - ~ - :- ~ - :* stats-index - nots - [%count 0] - *time - == - :: ++ give-unreads ^- update:store :- %unreads ;: weld give-each-unreads give-since-unreads - give-group-unreads == -- :: @@ -409,8 +320,7 @@ ?:(is-archive archive notifications) |= [time=@da =timebox:store] ^- update:store - :^ %timebox time is-archive - ~(tap by timebox) + [%timebox `time ~(tap by timebox)] == :: ++ on-poke @@ -471,9 +381,7 @@ ^- (quip card _this) ?. ?=([%autoseen ~] wire) (on-arvo:def wire sign-arvo) - ?> ?=([%behn %wake *] sign-arvo) - :_ this(current-timebox now.bowl) - ~[autoseen-timer:ha] + `this :: ++ on-fail on-fail:def -- @@ -512,7 +420,6 @@ %unread-each (unread-each +.in) :: %read-note (read-note +.in) - %unread-note (unread-note +.in) :: %seen-index (seen-index +.in) %remove-graph (remove-graph +.in) @@ -525,13 +432,6 @@ :: +| %note :: :: notification tracking - ++ upd-cache - |= [read=? time=@da =index:store] - poke-core(+.state (^upd-cache read time index)) - :: - ++ rebuild-cache - poke-core(+.state (inflate-cache -.state)) - :: ++ put-notifs |= [time=@da =timebox:store] poke-core(notifications (put:orm notifications time timebox)) @@ -539,74 +439,60 @@ ++ add-note |= [=index:store =notification:store] ^+ poke-core + =/ existing-notif + (~(get by unread-notes) index) + =/ new=notification:store + (merge-notification existing-notif notification) + =. unread-notes + (~(put by unread-notes) index new) + =/ timebox=@da + (~(gut by timeboxes) (to-stats-index:store index) current-timebox) + (give %added index new) + :: + ++ do-archive + |= [time=(unit @da) =index:store] + ^+ poke-core + |^ + ?~(time archive-unread (archive-read u.time)) + :: + ++ archive-unread + =. unread-notes + (~(del by unread-notes) index) + (give %archive ~ index) + :: + ++ archive-read + |= time=@da + =/ =timebox:store + (gut-orm notifications time) + =/ =notification:store + (~(got by timebox) index) + =/ new-timebox=timebox:store + (~(del by timebox) index) + =. poke-core + (put-notifs time new-timebox) + (give %archive `time index) + -- + :: + ++ read-note + |= =index:store + =/ =notification:store + (~(got by unread-notes) index) + =. unread-notes + (~(del by unread-notes) index) + =/ =time + (~(gut by timeboxes) (to-stats-index:store index) current-timebox) =/ =timebox:store - (gut-orm notifications current-timebox) + (gut-orm notifications time) =/ existing-notif (~(get by timebox) index) =/ new=notification:store (merge-notification existing-notif notification) - =/ new-read=? - ?~ existing-notif %.y - read.u.existing-notif - =/ new-timebox=timebox:store + =. timebox (~(put by timebox) index new) - =. poke-core (put-notifs current-timebox new-timebox) - =? poke-core new-read - (upd-cache %.n current-timebox index) - (give %added current-timebox index new) - :: - ++ do-archive - |= [time=@da =index:store] - ^+ poke-core - =/ =timebox:store - (gut-orm notifications time) - =/ =notification:store - (~(got by timebox) index) - =/ new-timebox=timebox:store - (~(del by timebox) index) - =? poke-core !read.notification - (upd-cache %.y time index) - =. poke-core - (put-notifs time new-timebox) - =. archive - %^ jub-orm archive time - |= archive-box=timebox:store - (~(put by archive-box) index notification(read %.y)) - (give %archive time index) - :: - :: if we detect cache inconsistencies, wipe and rebuild - ++ change-read-status - |= [time=@da =index:store read=?] - ^+ poke-core - =. poke-core (upd-cache read time index) - =/ tib=(unit timebox:store) - (get:orm notifications time) - ?~ tib poke-core - =/ not=(unit notification:store) - (~(get by u.tib) index) - ?~ not poke-core - =? poke-core - :: cache is inconsistent iff we didn't directly - :: call this through %read-note or %unread-note - &(=(read read.u.not) !?=(?(%read-note %unread-note) -.in)) - ~& >> "Inconsistent hark cache, rebuilding" - rebuild-cache - ?< &(=(read read.u.not) ?=(?(%read-note %unread-note) -.in)) - =. u.tib - (~(put by u.tib) index u.not(read read)) =. notifications - (put:orm notifications time u.tib) - poke-core + (put:orm notifications time timebox) + (give %note-read time index) :: - ++ read-note - |= [time=@da =index:store] - %. [%read-note time index] - give:(change-read-status time index %.y) - :: - ++ unread-note - |= [time=@da =index:store] - %. [%unread-note time index] - give:(change-read-status time index %.n) :: :: +| %each :: @@ -624,18 +510,18 @@ |= [=stats-index:store ref=index:graph-store] %- read-indices %+ skim - ~(tap ^in (~(get ju by-index) stats-index)) - |= [time=@da =index:store] - =/ =timebox:store - (gut-orm notifications time) + ~(tap ^in ~(key by unread-notes)) + |= =index:store + ?. (stats-index-is-index:store stats-index index) %.n =/ not=notification:store - (~(got by timebox) index) + (~(got by unread-notes) index) ?. ?=(%graph -.index) %.n ?. ?=(%graph -.contents.not) %.n (lien list.contents.not |=(p=post:post =(index.p ref))) :: ++ read-each |= [=stats-index:store ref=index:graph-store] + =. timeboxes (~(put by timeboxes) stats-index now.bowl) =. poke-core (read-index-each stats-index ref) %+ jub-unreads-each:(give %read-each stats-index ref) stats-index @@ -659,12 +545,13 @@ ++ read-count |= =stats-index:store =. unreads-count (~(put by unreads-count) stats-index 0) - =/ times=(list [@da index:store]) - ~(tap ^in (~(get ju by-index) stats-index)) + =/ times=(list index:store) + (unread-for-stats-index stats-index) + =? timeboxes !(~(has by timeboxes) stats-index) (~(put by timeboxes) stats-index now.bowl) (give:(read-indices times) %read-count stats-index) :: ++ read-indices - |= times=(list [time=@da =index:store]) + |= times=(list =index:store) |- ?~ times poke-core =/ core @@ -694,8 +581,6 @@ unreads-each indices =. last-seen ((dif-map-by-key ,@da) last-seen indices) - =. by-index - ((dif-map-by-key ,(set [@da =index:store])) by-index indices) poke-core :: ++ get-stats-indices @@ -705,7 +590,6 @@ ~(tap ^in ~(key by unreads-count)) ~(tap ^in ~(key by last-seen)) ~(tap ^in ~(key by unreads-each)) - ~(tap ^in ~(key by by-index)) == |= =stats-index:store ?. ?=(%graph -.stats-index) %.n @@ -728,30 +612,35 @@ ~(tap ^in set) |- ?~ indices poke-core - =/ times=(list [time=@da =index:store]) - ~(tap ^in (~(get ju by-index) i.indices)) + =/ times=(list =index:store) + (unread-for-stats-index i.indices) =. poke-core (read-indices times) $(indices t.indices) -- :: ++ seen - => (emit cancel-autoseen) - => (emit autoseen-timer) - poke-core(current-timebox now.bowl) + =. poke-core + (read-indices ~(tap ^in ~(key by unread-notes))) + poke-core(current-timebox now.bowl, timeboxes ~) :: ++ read-all =: unreads-count (~(run by unreads-count) _0) unreads-each (~(run by unreads-each) _~) notifications (~(run by notifications) _~) == - (give:seen:rebuild-cache %read-all ~) + (give:seen %read-all ~) :: ++ set-dnd |= d=? (give:poke-core(dnd d) %set-dnd d) -- :: +++ unread-for-stats-index + |= =stats-index:store + %+ skim ~(tap in ~(key by unread-notes)) + (cury stats-index-is-index:store stats-index) +:: ++ merge-notification |= [existing=(unit notification:store) new=notification:store] ^- notification:store @@ -760,11 +649,11 @@ :: %graph ?> ?=(%graph -.contents.new) - u.existing(read %.n, list.contents (weld list.contents.u.existing list.contents.new)) + u.existing(list.contents (weld list.contents.u.existing list.contents.new)) :: %group ?> ?=(%group -.contents.new) - u.existing(read %.n, list.contents (weld list.contents.u.existing list.contents.new)) + u.existing(list.contents (weld list.contents.u.existing list.contents.new)) == :: :: +key-orm: +key:by for ordered maps @@ -818,38 +707,4 @@ ^- (list [@da timebox:store]) %+ skim (tap:orm notifications) |=([@da =timebox:store] !=(~(wyt by timebox) 0)) - -:: -++ upd-cache - |= [read=? time=@da =index:store] - ^+ +.state - %_ +.state - :: - by-index - %. [(to-stats-index:store index) time index] - ?: read - ~(del ju by-index) - ~(put ju by-index) - == -:: -++ inflate-cache - |= state-6 - ^+ +.state - =. +.state - *cache - =/ nots=(list [p=@da =timebox:store]) - (tap:orm notifications) - |- =* outer $ - ?~ nots +.state - =/ unreads ~(tap by timebox.i.nots) - |- =* inner $ - ?~ unreads - outer(nots t.nots) - =* notification q.i.unreads - =* index p.i.unreads - ?: read.notification - inner(unreads t.unreads) - =. +.state - (upd-cache %.n p.i.nots index) - inner(unreads t.unreads) -- diff --git a/pkg/arvo/app/hood.hoon b/pkg/arvo/app/hood.hoon index 24a9e524cc..b6567e1f5b 100644 --- a/pkg/arvo/app/hood.hoon +++ b/pkg/arvo/app/hood.hoon @@ -2,7 +2,7 @@ /+ drum=hood-drum, helm=hood-helm, kiln=hood-kiln |% +$ state - $: %12 + $: %13 drum=state:drum helm=state:helm kiln=state:kiln @@ -15,6 +15,7 @@ [%9 drum=state:drum helm=state:helm kiln=state:kiln] [%10 drum=state:drum helm=state:helm kiln=state:kiln] [%11 drum=state:drum helm=state:helm kiln=state:kiln] + [%12 drum=state:drum helm=state:helm kiln=state:kiln] == +$ any-state-tuple $: drum=any-state:drum diff --git a/pkg/arvo/app/landscape/index.html b/pkg/arvo/app/landscape/index.html index 8a76a571f8..9d5903f2cf 100644 --- a/pkg/arvo/app/landscape/index.html +++ b/pkg/arvo/app/landscape/index.html @@ -24,6 +24,6 @@
- + diff --git a/pkg/arvo/app/launch.hoon b/pkg/arvo/app/launch.hoon index 16997bfc0b..bc7bdd7f0e 100644 --- a/pkg/arvo/app/launch.hoon +++ b/pkg/arvo/app/launch.hoon @@ -13,16 +13,23 @@ [%4 state-zero] [%5 state-zero] [%6 state-zero] + [%7 state-7] == :: +$ state-zero + $: tiles=tiles-0:store + =tile-ordering:store + first-time=? + == +:: ++$ state-7 $: =tiles:store =tile-ordering:store first-time=? == -- :: -=| [%6 state-zero] +=| [%7 state-7] =* state - %- agent:dbug ^- agent:gall @@ -32,7 +39,7 @@ :: ++ on-init ^- (quip card _this) - =/ new-state *state-zero + =/ new-state *state-7 =. new-state %_ new-state tiles @@ -41,12 +48,12 @@ |= =term :- term ^- tile:store - ?+ term [[%custom ~] %.y] + ?+ term [[%custom ~ ~] %.y] %term [[%basic 'Terminal' '/~landscape/img/term.png' '/~term'] %.y] == tile-ordering [%weather %clock %term ~] == - [~ this(state [%6 new-state])] + [~ this(state [%7 new-state])] :: ++ on-save !>(state) ++ on-load @@ -55,8 +62,22 @@ =/ old-state !<(versioned-state old) =| cards=(list card) |- ^- (quip card _this) - ?: ?=(%6 -.old-state) + ?: ?=(%7 -.old-state) [cards this(state old-state)] + :: + ?: ?=(%6 -.old-state) + =/ new-tiles=tiles:store + %- ~(gas by *tiles:store) + %+ turn ~(tap by tiles.old-state) + |= [=term =tile-0:store] + :- term + :_ is-shown.tile-0 + ?- -.type.tile-0 + %basic type.tile-0 + %custom [%custom ~ ~] + == + $(old-state [%7 new-tiles tile-ordering.old-state first-time.old-state]) + :: ?: ?=(%5 -.old-state) :: replace %dojo with %term :: @@ -86,11 +107,11 @@ =. new-state %_ new-state tiles - %- ~(gas by *tiles:store) + %- ~(gas by *tiles-0:store) %+ turn `(list term)`[%weather %clock %dojo ~] |= =term :- term - ^- tile:store + ^- tile-0:store ?+ term [[%custom ~] %.y] %dojo [[%basic 'Dojo' '/~landscape/img/Dojo.png' '/~dojo'] %.y] == @@ -191,9 +212,14 @@ ^- (unit (unit cage)) ?. (team:title our.bowl src.bowl) ~ ?+ path [~ ~] - [%x %tiles ~] ``noun+!>([tiles tile-ordering]) - [%x %first-time ~] ``noun+!>(first-time) - [%x %keys ~] ``noun+!>(~(key by tiles)) + [%x %tiles ~] ``noun+!>([tiles tile-ordering]) + [%x %first-time ~] ``noun+!>(first-time) + [%x %keys ~] ``noun+!>(~(key by tiles)) + :: + [%x %runtime-lag ~] + :^ ~ ~ %json + !> ^- json + b+.^(? //(scot %p our.bowl)//(scot %da now.bowl)/zen/lag) == :: ++ on-arvo diff --git a/pkg/arvo/app/lens.hoon b/pkg/arvo/app/lens.hoon index f7290cfa9b..ed9c93328f 100644 --- a/pkg/arvo/app/lens.hoon +++ b/pkg/arvo/app/lens.hoon @@ -1,5 +1,5 @@ /- lens, *sole -/+ *server, default-agent +/+ *server, default-agent, dbug /= lens-mark /mar/lens/command :: TODO: ask clay to build a $tube =, format |% @@ -35,6 +35,8 @@ -- :: =| =state +%- agent:dbug +^- agent:gall |_ =bowl:gall +* this . def ~(. (default-agent this %|) bowl) @@ -56,8 +58,6 @@ ?. ?=(%handle-http-request mark) (on-poke:def mark vase) =+ !<([eyre-id=@ta =inbound-request:eyre] vase) - ?> ?=(~ job.state) - :: =/ request-line (parse-request-line url.request.inbound-request) =/ site (flop site.request-line) :: @@ -76,6 +76,13 @@ =/ com=command:lens (json:grab:lens-mark jon) :: + ?: ?=(%cancel -.source.com) + ~& %lens-cancel + :_ this(job.state ~) + (give-simple-payload:app eyre-id (json-response:gen [%s 'cancelled'])) + :: + ?> ?=(~ job.state) + :: ?+ -.source.com :_ this(job.state (some [eyre-id com])) [%pass /sole %agent [our.bowl %dojo] %watch /sole/[eyre-id]]~ diff --git a/pkg/arvo/app/metadata-push-hook.hoon b/pkg/arvo/app/metadata-push-hook.hoon index a7ffd37bde..dafbba4679 100644 --- a/pkg/arvo/app/metadata-push-hook.hoon +++ b/pkg/arvo/app/metadata-push-hook.hoon @@ -2,7 +2,7 @@ :: /- *group, *invite-store, store=metadata-store /+ default-agent, verb, dbug, grpl=group, push-hook, - resource, mdl=metadata, gral=graph + resource, mdl=metadata, gral=graph, agentio ~% %group-hook-top ..part ~ |% +$ card card:agent:gall @@ -18,9 +18,19 @@ == :: +$ agent (push-hook:push-hook config) +:: ++$ state-null ~ ++$ state-zero [%0 ~] +:: ++$ versioned-state + $% state-null + state-zero + == -- :: :: +=| state-zero +=* state - %- agent:dbug %+ verb | ^- agent:gall @@ -32,23 +42,45 @@ grp ~(. grpl bowl) met ~(. mdl bowl) gra ~(. gral bowl) + io ~(. agentio bowl) + pass pass:io :: ++ on-init on-init:def ++ on-save !>(~) -++ on-load on-load:def +++ on-load on-load:def +:: ++ on-poke |= [=mark =vase] - ?. ?=(%metadata-hook-update mark) - (on-poke:def mark vase) - =+ !<(=hook-update:store vase) - ?. ?=(%req-preview -.hook-update) - (on-poke:def mark vase) - ?> =(entity.group.hook-update our.bowl) - =/ =group-preview:store - (get-preview:met group.hook-update) - :_ this - =- [%pass / %agent [src.bowl %metadata-pull-hook] %poke -]~ - metadata-hook-update+!>(`hook-update:store`[%preview group-preview]) + |^ ^- (quip card _this) + ?+ mark (on-poke:def mark vase) + %metadata-hook-update metadata-hook-update + %noun noun + == + :: + ++ metadata-hook-update + =+ !<(=hook-update:store vase) + ?. ?=(%req-preview -.hook-update) + (on-poke:def mark vase) + ?> =(entity.group.hook-update our.bowl) + =/ =group-preview:store + (get-preview:met group.hook-update) + :_ this + =- [%pass / %agent [src.bowl %metadata-pull-hook] %poke -]~ + metadata-hook-update+!>(`hook-update:store`[%preview group-preview]) + :: + ++ noun + ?+ q.vase ~|("unknown noun poke" !!) + :: + %clean-dm + =+ .^(sharing=(set resource) (scry:io %gx dap.bowl /sharing/noun)) + :_ this + %+ murn ~(tap in sharing) + |= rid=resource + ^- (unit card) + ?@ (rush name.rid ;~(pfix (jest 'dm--') fed:ag)) ~ + `(poke-self:pass push-hook-action+!>([%remove rid])) + == + -- :: ++ on-agent on-agent:def ++ on-watch on-watch:def @@ -59,8 +91,9 @@ :: ++ transform-proxy-update |= vas=vase - ^- (unit vase) + ^- (quip card (unit vase)) =/ =update:store !<(update:store vas) + :- ~ ?. ?=(?(%add %remove) -.update) ~ =/ role=(unit (unit role-tag)) diff --git a/pkg/arvo/app/metadata-store.hoon b/pkg/arvo/app/metadata-store.hoon index 2f430864c5..ed2322976e 100644 --- a/pkg/arvo/app/metadata-store.hoon +++ b/pkg/arvo/app/metadata-store.hoon @@ -106,6 +106,7 @@ +$ state-8 [%8 base-state-3] +$ state-9 [%9 base-state-3] +$ state-10 [%10 base-state-3] ++$ state-11 [%11 base-state-3] +$ versioned-state $% state-0 state-1 @@ -118,10 +119,11 @@ state-8 state-9 state-10 + state-11 == :: +$ inflated-state - $: state-10 + $: state-11 cached-indices == -- @@ -198,22 +200,21 @@ [%x %associations ~] ``noun+!>(associations) [%x %app-name @ ~] =/ =app-name:store i.t.t.path - ``noun+!>((metadata-for-app:mc app-name)) + ``noun+!>(`associations:store`(metadata-for-app:mc app-name)) :: [%x %group *] =/ group=resource (de-path:resource t.t.path) - ``noun+!>((metadata-for-group:mc group)) + ``noun+!>(`associations:store`(metadata-for-group:mc group)) :: [%x %metadata @ @ @ @ ~] =/ =md-resource:store [i.t.t.path (de-path:resource t.t.t.path)] - ``noun+!>((~(get by associations) md-resource)) + ``noun+!>(`(unit association:store)`(~(get by associations) md-resource)) :: [%x %resource @ *] =/ app=term i.t.t.path =/ rid=resource (de-path:resource t.t.t.path) - ``noun+!>((~(get by resource-indices) [app rid])) - + ``noun+!>(`(unit resource)`(~(get by resource-indices) [app rid])) :: [%x %export ~] ``noun+!>(-.state) @@ -234,7 +235,7 @@ =| cards=(list card) |^ =* loop $ - ?: ?=(%10 -.old) + ?: ?=(%11 -.old) :- cards %_ state associations associations.old @@ -242,6 +243,8 @@ group-indices (rebuild-group-indices associations.old) app-indices (rebuild-app-indices associations.old) == + ?: ?=(%10 -.old) + $(-.old %11, associations.old (hide-dm-assoc associations.old)) ?: ?=(%9 -.old) =/ groups (fall (~(get by (rebuild-app-indices associations.old)) %groups) ~) @@ -282,6 +285,20 @@ :: pre-breach, can safely throw away loop(old *state-8) :: + ++ hide-dm-assoc + |= assoc=associations:store + ^- associations:store + %- ~(gas by *associations:store) + %+ turn ~(tap by assoc) + |= [m=md-resource:store [g=resource met=metadatum:store]] + ^- [md-resource:store association:store] + =? hidden.met + ?& ?=(^ (rush name.resource.m ;~(pfix (jest 'dm--') fed:ag))) + ?=(%graph app-name.m) + == + %.y + [m [g met]] + :: ++ associations-2-to-3 |= assoc=associations-2 ^- associations:store @@ -483,7 +500,7 @@ :: ++ metadata-for-app |= =app-name:store - ^+ associations + ^- associations:store %+ roll ~(tap in (~(gut by app-indices) app-name ~)) |= [[group=resource rid=resource] out=associations:store] =/ =md-resource:store @@ -494,6 +511,7 @@ :: ++ metadata-for-group |= group=resource + ^- associations:store =/ resources=(set md-resource:store) (~(get ju group-indices) group) %+ roll diff --git a/pkg/arvo/gen/btc-provider/action.hoon b/pkg/arvo/gen/btc-provider/action.hoon new file mode 100644 index 0000000000..2182dbd575 --- /dev/null +++ b/pkg/arvo/gen/btc-provider/action.hoon @@ -0,0 +1,13 @@ +:: Sends a raw RPC action to the BTC Provider +:: +:: Commands: +:: +:: +:: +/- *btc-provider +:: +:- %say +|= $: [now=@da eny=@uvJ =beak] + [[act=action ~] ~] +== +[%btc-provider-action act] diff --git a/pkg/arvo/gen/btc-provider/command.hoon b/pkg/arvo/gen/btc-provider/command.hoon new file mode 100644 index 0000000000..f7b3751452 --- /dev/null +++ b/pkg/arvo/gen/btc-provider/command.hoon @@ -0,0 +1,13 @@ +:: Sends a command to the BTC Provider +:: +:: Commands: +:: +:: +:: +/- *btc-provider +:: +:- %say +|= $: [now=@da eny=@uvJ =beak] + [[comm=command ~] ~] + == +[%btc-provider-command comm] diff --git a/pkg/arvo/gen/btc-wallet-check.hoon b/pkg/arvo/gen/btc-wallet-check.hoon new file mode 100644 index 0000000000..9c5e435550 --- /dev/null +++ b/pkg/arvo/gen/btc-wallet-check.hoon @@ -0,0 +1,5 @@ +:- %say +|= [[now=time * bec=beak] ~ ~] +:- %noun +:- %btc-wallet-hash +.^(@uv %gx (en-beam bec(q %glob) /btc-wallet/noun)) diff --git a/pkg/arvo/gen/btc-wallet/action.hoon b/pkg/arvo/gen/btc-wallet/action.hoon new file mode 100644 index 0000000000..949bbff48b --- /dev/null +++ b/pkg/arvo/gen/btc-wallet/action.hoon @@ -0,0 +1,9 @@ +:: Sends an action to btc-wallet +:: +/- *btc-wallet +:: +:- %say +|= $: [now=@da eny=@uvJ =beak] +[[act=action ~] ~] +== +[%btc-wallet-action act] diff --git a/pkg/arvo/gen/btc-wallet/command.hoon b/pkg/arvo/gen/btc-wallet/command.hoon new file mode 100644 index 0000000000..7f5e0c1850 --- /dev/null +++ b/pkg/arvo/gen/btc-wallet/command.hoon @@ -0,0 +1,9 @@ +:: Sends a command to btc-wallet +:: +/- *btc-wallet +:: +:- %say +|= $: [now=@da eny=@uvJ =beak] + [[comm=command ~] ~] + == +[%btc-wallet-command comm] diff --git a/pkg/arvo/gen/dm-hook/dm.hoon b/pkg/arvo/gen/dm-hook/dm.hoon new file mode 100644 index 0000000000..129205a55b --- /dev/null +++ b/pkg/arvo/gen/dm-hook/dm.hoon @@ -0,0 +1,21 @@ +:: dm-hook|dm: DM somebody +:: +/- *graph-store +:- %say +|= $: [now=@da eny=@uvJ =beak] + [[him=ship contents=(list content) ~] ~] + == +=* our p.beak +=/ =post *post +=: author.post our + index.post ~[him now] + time-sent.post now + contents.post contents +== +:: +:- %graph-update-2 +^- update +:- now +:+ %add-nodes [our %dm-inbox] +%- ~(gas by *(map index node)) +~[[~[him now] [%&^post [%empty ~]]]] diff --git a/pkg/arvo/gen/glob/make.hoon b/pkg/arvo/gen/glob/make.hoon index 1f2d5b4a88..ba580ef537 100644 --- a/pkg/arvo/gen/glob/make.hoon +++ b/pkg/arvo/gen/glob/make.hoon @@ -1,3 +1,3 @@ :- %say -|= * -[%glob-make ~] +|= [^ [=path ~] ~] +[%glob-make path] diff --git a/pkg/arvo/gen/graph-store/add-graph.hoon b/pkg/arvo/gen/graph-store/add-graph.hoon index 9b47be0e68..2f284f26a8 100644 --- a/pkg/arvo/gen/graph-store/add-graph.hoon +++ b/pkg/arvo/gen/graph-store/add-graph.hoon @@ -5,6 +5,6 @@ |= $: [now=@da eny=@uvJ =beak] [[=resource mark=(unit mark) overwrite=? ~] ~] == -:- %graph-update-1 +:- %graph-update-2 ^- update [now [%add-graph resource (gas:orm ~ ~) mark overwrite]] diff --git a/pkg/arvo/gen/graph-store/add-post.hoon b/pkg/arvo/gen/graph-store/add-post.hoon index 25edf239a1..2f68f2af23 100644 --- a/pkg/arvo/gen/graph-store/add-post.hoon +++ b/pkg/arvo/gen/graph-store/add-post.hoon @@ -12,9 +12,9 @@ contents.post contents == :: -:- %graph-update-1 +:- %graph-update-2 ^- update :- now :+ %add-nodes [our name] %- ~(gas by *(map index node)) -~[[[now]~ [post [%empty ~]]]] +~[[[now]~ [[%& post] [%empty ~]]]] diff --git a/pkg/arvo/gen/graph-store/add-signatures.hoon b/pkg/arvo/gen/graph-store/add-signatures.hoon index 5106908759..570fa97e70 100644 --- a/pkg/arvo/gen/graph-store/add-signatures.hoon +++ b/pkg/arvo/gen/graph-store/add-signatures.hoon @@ -5,6 +5,6 @@ |= $: [now=@da eny=@uvJ =beak] [[[=resource =index] =signatures ~] ~] == -:- %graph-update-1 +:- %graph-update-2 ^- update [now [%add-signatures [resource index] signatures]] diff --git a/pkg/arvo/gen/graph-store/add-tag.hoon b/pkg/arvo/gen/graph-store/add-tag.hoon index 6d9572d01a..08366364f9 100644 --- a/pkg/arvo/gen/graph-store/add-tag.hoon +++ b/pkg/arvo/gen/graph-store/add-tag.hoon @@ -3,8 +3,8 @@ /- *graph-store :- %say |= $: [now=@da eny=@uvJ =beak] - [[=term =resource ~] ~] + [[=term =uid ~] ~] == -:- %graph-update-1 +:- %graph-update-2 ^- update -[now [%add-tag term resource]] +[now [%add-tag term uid]] diff --git a/pkg/arvo/gen/graph-store/archive-graph.hoon b/pkg/arvo/gen/graph-store/archive-graph.hoon index 003f835728..a6f9ae2989 100644 --- a/pkg/arvo/gen/graph-store/archive-graph.hoon +++ b/pkg/arvo/gen/graph-store/archive-graph.hoon @@ -5,6 +5,6 @@ |= $: [now=@da eny=@uvJ =beak] [[=resource ~] ~] == -:- %graph-update-1 +:- %graph-update-2 ^- update [now [%archive-graph resource]] diff --git a/pkg/arvo/gen/graph-store/export-graph.hoon b/pkg/arvo/gen/graph-store/export-graph.hoon index 2f9bdbafe0..4330d01eb2 100644 --- a/pkg/arvo/gen/graph-store/export-graph.hoon +++ b/pkg/arvo/gen/graph-store/export-graph.hoon @@ -4,7 +4,7 @@ |= $: [now=@da eny=@uvJ bec=beak] [[=ship graph=term ~] ~] == -:- %graph-update-1 +:- %graph-update-2 =/ our (scot %p p.bec) =/ wen (scot %da now) =/ who (scot %p ship) diff --git a/pkg/arvo/gen/graph-store/import-graph.hoon b/pkg/arvo/gen/graph-store/import-graph.hoon index f14150c206..4d3e03aa0b 100644 --- a/pkg/arvo/gen/graph-store/import-graph.hoon +++ b/pkg/arvo/gen/graph-store/import-graph.hoon @@ -4,6 +4,6 @@ |= $: [now=@da eny=@uvJ bec=beak] [[graph=term =path ~] ~] == -:- %graph-update-1 +:- %graph-update-2 =- ~& update=- - .^(=update:graph-store %cx path) diff --git a/pkg/arvo/gen/graph-store/remove-graph.hoon b/pkg/arvo/gen/graph-store/remove-graph.hoon index fc11cfe31f..2bd979faa7 100644 --- a/pkg/arvo/gen/graph-store/remove-graph.hoon +++ b/pkg/arvo/gen/graph-store/remove-graph.hoon @@ -5,6 +5,6 @@ |= $: [now=@da eny=@uvJ =beak] [[=resource ~] ~] == -:- %graph-update-1 +:- %graph-update-2 ^- update [now [%remove-graph resource]] diff --git a/pkg/arvo/gen/graph-store/remove-nodes.hoon b/pkg/arvo/gen/graph-store/remove-posts.hoon similarity index 51% rename from pkg/arvo/gen/graph-store/remove-nodes.hoon rename to pkg/arvo/gen/graph-store/remove-posts.hoon index 13eb098571..01f2345c30 100644 --- a/pkg/arvo/gen/graph-store/remove-nodes.hoon +++ b/pkg/arvo/gen/graph-store/remove-posts.hoon @@ -1,10 +1,10 @@ -:: graph-store|remove-nodes: remove nodes from a graph at indices +:: graph-store|remove-posts: remove nodes from a graph at indices :: /- *graph-store :- %say |= $: [now=@da eny=@uvJ =beak] [[=resource indices=(set index) ~] ~] == -:- %graph-update-1 +:- %graph-update-2 ^- update -[now [%remove-nodes resource indices]] +[now [%remove-posts resource indices]] diff --git a/pkg/arvo/gen/graph-store/remove-signatures.hoon b/pkg/arvo/gen/graph-store/remove-signatures.hoon index 7fc7f90302..8bd4b86958 100644 --- a/pkg/arvo/gen/graph-store/remove-signatures.hoon +++ b/pkg/arvo/gen/graph-store/remove-signatures.hoon @@ -6,6 +6,6 @@ |= $: [now=@da eny=@uvJ =beak] [[[=resource =index] =signatures ~] ~] == -:- %graph-update-1 +:- %graph-update-2 ^- update [now [%remove-signatures [resource index] signatures]] diff --git a/pkg/arvo/gen/graph-store/remove-tag.hoon b/pkg/arvo/gen/graph-store/remove-tag.hoon index c01a10eccd..1a425a4bb2 100644 --- a/pkg/arvo/gen/graph-store/remove-tag.hoon +++ b/pkg/arvo/gen/graph-store/remove-tag.hoon @@ -3,8 +3,8 @@ /- *graph-store :- %say |= $: [now=@da eny=@uvJ =beak] - [[=term =resource ~] ~] + [[=term =uid ~] ~] == -:- %graph-update-1 +:- %graph-update-2 ^- update -[now [%remove-tag term resource]] +[now [%remove-tag term uid]] diff --git a/pkg/arvo/gen/graph-store/unarchive-graph.hoon b/pkg/arvo/gen/graph-store/unarchive-graph.hoon index 7dfd75cbf4..9402207af9 100644 --- a/pkg/arvo/gen/graph-store/unarchive-graph.hoon +++ b/pkg/arvo/gen/graph-store/unarchive-graph.hoon @@ -5,6 +5,6 @@ |= $: [now=@da eny=@uvJ =beak] [[=resource ~] ~] == -:- %graph-update-1 +:- %graph-update-2 ^- update [now [%unarchive-graph resource]] diff --git a/pkg/arvo/gen/hood/code.hoon b/pkg/arvo/gen/hood/code.hoon index 3cb86c225f..3cd4eacae3 100644 --- a/pkg/arvo/gen/hood/code.hoon +++ b/pkg/arvo/gen/hood/code.hoon @@ -3,15 +3,14 @@ :::: /hoon/code/hood/gen :: /? 310 -:: -:::: - :: -:- %say +/- *sole +/+ *generators +:- %ask |= $: [now=@da eny=@uvJ bec=beak] [arg=?(~ [%reset ~]) ~] == =* our p.bec -:- %helm-code +^- (sole-result [%helm-code ?(~ %reset)]) ?~ arg =/ code=tape %+ slag 1 @@ -20,11 +19,23 @@ =/ step=tape %+ scow %ud .^(@ud %j /(scot %p our)/step/(scot %da now)/(scot %p our)) - %- %- slog - :~ [%leaf code] - [%leaf (weld "current step=" step)] - [%leaf "use |code %reset to invalidate this and generate a new code"] - == - ~ + :: + %+ print 'use |code %reset to invalidate this and generate a new code' + %+ print leaf+(weld "current step=" step) + %+ print leaf+code + (produce [%helm-code ~]) +:: ?> =(%reset -.arg) -%reset +%+ print 'continue?' +%+ print 'warning: resetting your code closes all web sessions' +%+ prompt + [%& %project "y/n: "] +%+ parse + ;~ pose + (cold %.y (mask "yY")) + (cold %.n (mask "nN")) + == +|= reset=? +?. reset + no-product +(produce [%helm-code %reset]) diff --git a/pkg/arvo/gen/hood/fuse.hoon b/pkg/arvo/gen/hood/fuse.hoon new file mode 100644 index 0000000000..870b38d2aa --- /dev/null +++ b/pkg/arvo/gen/hood/fuse.hoon @@ -0,0 +1,15 @@ +:: Kiln: Fuse local desk from (optionally-)foreign sources +:: +:::: /hoon/fuse/hood/gen + :: +/* help-text %txt /gen/hood/fuse/help/txt +=, clay +:: +:::: + :: +:- %say +|= [[now=@da eny=@uvJ bec=beak] [arg=[?(~ [des=desk bas=beak con=(list [beak germ]) ~])]] ~] +:- %kiln-fuse +?~ arg + ((slog (turn `wain`help-text |=(=@t leaf+(trip t)))) ~) +[des bas con]:arg diff --git a/pkg/arvo/gen/hood/fuse/help.txt b/pkg/arvo/gen/hood/fuse/help.txt new file mode 100644 index 0000000000..9e18eb80e9 --- /dev/null +++ b/pkg/arvo/gen/hood/fuse/help.txt @@ -0,0 +1,8 @@ +Usage: + + |fuse %destination-desk base-beak ~[[source-beak %some-germ] [another-beak %another-germ]] + +A fuse replaces the contents of %destination-desk with the merge of the +specified beaks according to their merge strategies. This has no dependence +on the previous state of %destination-desk so any commits/work there will +be overwritten. diff --git a/pkg/arvo/gen/kick.hoon b/pkg/arvo/gen/kick.hoon new file mode 100644 index 0000000000..e5a469bddc --- /dev/null +++ b/pkg/arvo/gen/kick.hoon @@ -0,0 +1,7 @@ +:: Kick subs +:- %say +|= $: [now=@da eny=@uvJ bec=beak] + ~ + ~ + == +[%kick %kick] diff --git a/pkg/arvo/gen/tally.hoon b/pkg/arvo/gen/tally.hoon index a66710d5c5..7935400362 100644 --- a/pkg/arvo/gen/tally.hoon +++ b/pkg/arvo/gen/tally.hoon @@ -1,5 +1,5 @@ /- gr=group, md=metadata-store, ga=graph-store -/+ re=resource +/+ re=resource, graph=graph-store !: :- %say |= $: [now=@da eny=@uvJ =beak] @@ -67,7 +67,7 @@ =/ real=(set resource:re) =/ upd=update:ga %+ scry update:ga - [%x %graph-store /keys/graph-update-1] + [%x %graph-store /keys/graph-update-2] ?> ?=(%keys -.q.upd) resources.q.upd :: count activity per channel @@ -86,14 +86,17 @@ %+ scry update:ga [%x %graph-store /graph/(scot %p entity.r)/[name.r]/noun] ?> ?=(%add-graph -.q.upd) - =/ mo ((ordered-map atom node:ga) gth) + =* mo orm:graph =/ week=(list [@da node:ga]) - (tap:mo (subset:mo graph.q.upd ~ `(sub now ~d7))) + (tap:mo (lot:mo graph.q.upd ~ `(sub now ~d7))) :- (lent week) %~ wyt in %+ roll week - |= [[* [author=ship *] *] a=(set ship)] - (~(put in a) author) + |= [[* mp=maybe-post:ga *] a=(set ship)] + ?- -.mp + %| a + %& (~(put in a) author.p.mp) + == :: render results :: :- (tac 'the date is ' (scot %da now)) diff --git a/pkg/arvo/gen/trouble.hoon b/pkg/arvo/gen/trouble.hoon index 6ce73e5176..7eff7e13a6 100644 --- a/pkg/arvo/gen/trouble.hoon +++ b/pkg/arvo/gen/trouble.hoon @@ -9,6 +9,7 @@ :: children :: glob-hash: hash of the glob, which is the js for landscape :: +/- glob /+ version :- %say |= [[now=time * bec=beak] ~ ~] @@ -65,8 +66,11 @@ .^(@uv %cz /[parent]/[desk.u.ota]/(scot %ud ud.cass)) :: ++ glob-state - ^- [@uv @tas] - =< [hash ?~(glob %waiting ?:(-.u.glob %done %trying))] - !< [@ud hash=@uv glob=(unit [? *])] - .^(vase %gx (weld (pathify ~.glob ~) /dbug/state/noun)) + ^- (list [path @uv @tas]) + =+ !< [@ud =globs:glob] + .^(vase %gx (weld (pathify ~.glob ~) /dbug/state/noun)) + %+ turn ~(tap by globs) + |= [srv=path hash=@uv glob=(unit [? *])] + ^- [path @uv @tas] + [srv hash ?~(glob %waiting ?:(-.u.glob %done %trying))] -- diff --git a/pkg/arvo/lib/azimuth-rpc.hoon b/pkg/arvo/lib/azimuth-rpc.hoon index 80d4443401..cc03bd72a8 100644 --- a/pkg/arvo/lib/azimuth-rpc.hoon +++ b/pkg/arvo/lib/azimuth-rpc.hoon @@ -1,7 +1,7 @@ :: azimuth-rpc: command parsing and utilities :: -/- rpc=json-rpc -/+ naive +/- rpc=json-rpc, *dice +/+ naive, json-rpc, lib=naive-transactions :: => :: Utilities :: @@ -19,32 +19,54 @@ %set-spawn-proxy %set-transfer-proxy == - :: FIXME: import tx-status, pend-tx from aggregator :: - +$ tx-status - $: status=?(%unknown %pending %sent %confirmed %failed) - tx=(unit @ux) - == - :: - +$ pend-tx [force=? =raw-tx:naive] + ++ parse-ship + |= jon=json + ^- (unit @p) + ?: ?=([%n *] jon) + (rush p.jon dem) + ?. ?=([%s *] jon) ~ + (rush p.jon ;~(pfix sig fed:ag)) + :: TODO: from /lib/group-store (move to zuse?) + ++ enkebab + |= str=cord + ^- @tas + ~| str + =- (fall - str) + %+ rush str + =/ name + %+ cook + |= part=tape + ^- tape + ?~ part part + :- (add i.part 32) + t.part + ;~(plug hig (star low)) + %+ cook + |=(a=(list tape) (crip (zing (join "-" a)))) + ;~(plug (star low) (star name)) :: ++ from-json + =, dejs-soft:format |% - ++ keys - |= params=(map @t json) - ^- (unit [encrypt=@ auth=@ crypto-suite=@ breach=?]) - ?~ data=(~(get by params) 'data') ~ - %. u.data - =, dejs-soft:format - %- ot - :~ ['encrypt' so] - ['auth' so] - ['crypto-suite' so] - ['breach' bo] - == - :: ++ data |% + ++ keys + |= params=(map @t json) + ^- (unit [encrypt=@ auth=@ crypto-suite=@ breach=?]) + ?~ data=(~(get by params) 'data') ~ + =; ans=(unit [cryp=(unit @ux) auth=(unit @ux) suit=@ brec=?]) + ?~ ans ~ + ?: |(?=(~ cryp.u.ans) ?=(~ auth.u.ans)) ~ + (some [u.cryp.u.ans u.auth.u.ans suit.u.ans brec.u.ans]) + %. u.data + %- ot + :~ ['encrypt' (cu to-hex so)] + ['auth' (cu to-hex so)] + ['cryptoSuite' no] + ['breach' bo] + == + :: ++ address-transfer |= params=(map @t json) ^- (unit [@ux ?]) @@ -54,7 +76,6 @@ ?~ add.u.ans ~ (some [u.add.u.ans r.u.ans]) %. u.data - =, dejs-soft:format %- ot ~[['address' (cu to-hex so)] ['reset' bo]] :: @@ -67,9 +88,8 @@ ?~ add.u.ans ~ (some [ship.u.ans u.add.u.ans]) %. u.data - =, dejs-soft:format %- ot - :~ ['ship' (su ;~(pfix sig fed:ag))] + :~ ['ship' parse-ship] ['address' (cu to-hex so)] == :: @@ -79,7 +99,6 @@ ?~ data=(~(get by params) 'data') ~ =; ans=(unit (unit @ux)) ?~(ans ~ u.ans) - =, dejs-soft:format %. u.data (ot ['address' (cu to-hex so)]~) :: @@ -87,90 +106,106 @@ |= params=(map @t json) ^- (unit @p) ?~ data=(~(get by params) 'data') ~ - =, dejs-soft:format %. u.data - (ot ['ship' (su ;~(pfix sig fed:ag))]~) + (ot ['ship' parse-ship]~) + :: + ++ cancel + |= params=(map @t json) + ^- (unit [l2-tx @p]) + ?~ data=(~(get by params) 'data') ~ + %. u.data + %- ot + :~ ['type' (cu l2-tx so)] + ['ship' parse-ship] + == -- :: ++ ship |= params=(map @t json) ^- (unit @p) ?~ data=(~(get by params) 'ship') ~ - =, dejs-soft:format - %. u.data - (su ;~(pfix sig fed:ag)) + (parse-ship u.data) :: ++ address |= params=(map @t json) ^- (unit @ux) ?~ data=(~(get by params) 'address') ~ - =; ans=(unit (unit @ux)) - ?~(ans ~ u.ans) - =, dejs-soft:format - ((cu to-hex so) u.data) + ?~ ans=((cu to-hex so) u.data) ~ + u.ans :: ++ sig |= params=(map @t json) ^- (unit @) - ?~ sig=(~(get by params) 'sig') ~ - (so:dejs-soft:format u.sig) + ?~ sig=(~(get by params) 'sig') ~ + ?~ ans=((cu to-hex so) u.sig) ~ + u.ans :: ++ from |= params=(map @t json) ^- (unit [@p proxy:naive]) ?~ from=(~(get by params) 'from') ~ - =, dejs-soft:format %. u.from %- ot - :~ ['ship' (su ;~(pfix sig fed:ag))] + :~ ['ship' parse-ship] ['proxy' (cu proxy:naive so)] == :: - ++ keccak + ++ hash |= params=(map @t json) ^- (unit @ux) - ?~ keccak=(~(get by params) 'keccak') ~ - =; ans=(unit (unit @ux)) - ?~(ans ~ u.ans) - =, dejs-soft:format - ((cu to-hex so) u.keccak) + ?~ hash=(~(get by params) 'hash') ~ + ?~ ans=((cu to-hex so) u.hash) ~ + u.ans :: ++ raw |= params=(map @t json) ^- (unit octs) ?~ raw=(~(get by params) 'raw') ~ - =; ans=(unit (unit @ux)) - ?~ ans ~ - ?~ u.ans ~ - (some (as-octs:mimes:html u.u.ans)) - =, dejs-soft:format - ((cu to-hex so) u.raw) + ?~ ans=((cu to-hex so) u.raw) ~ + ?~ u.ans ~ + (some (as-octs:mimes:html u.u.ans)) + :: + ++ tx + |= params=(map @t json) + ^- (unit l2-tx) + ?~ data=(~(get by params) 'tx') ~ + ?~ tx=(so u.data) ~ + =/ method=@tas (enkebab u.tx) + ?. ?=(l2-tx method) ~ + `method + :: + ++ nonce + |= params=(map @t json) + ^- (unit @ud) + ?~ nonce=(~(get by params) 'nonce') ~ + (ni u.nonce) -- :: ++ to-json + =, enjs:format |% ++ pending |= pending=(list pend-tx) ^- json - =, enjs:format :- %a %+ turn pending |= pend-tx ^- json - =, enjs:format %- pairs :~ ['force' b+force] + (en-address address) :: - :- 'raw-tx' + :- 'rawTx' %- pairs - :~ ['sig' (numb sig.raw-tx)] - ['tx' (tx:to-json tx.raw-tx)] + :~ ['tx' (tx:to-json tx.raw-tx)] + ['sig' (hex (as-octs:mimes:html sig.raw-tx))] == == :: + ++ en-address |=(a=@ux address+(hex 20 a)) + :: ++ tx |= =tx:naive ^- json - =, enjs:format |^ %- pairs :~ ['tx' (parse-tx +.tx)] @@ -203,7 +238,6 @@ == == :: ++ en-ship |=(s=@p ship+(ship s)) - ++ en-address |=(a=@ux address+s+(crip "0x{((x-co:co 20) a)}")) ++ en-spawn |=([s=@p a=@ux] ~[(en-ship s) (en-address a)]) ++ en-transfer |=([a=@ux r=?] ~[(en-address a) reset+b+r]) ++ en-keys @@ -211,7 +245,7 @@ ^- (list [@t json]) :~ ['encrypt' (numb encrypt)] ['auth' (numb auth)] - ['crypto-suite' (numb crypto-suite)] + ['cryptoSuite' (numb crypto-suite)] ['breach' b+breach] == -- @@ -221,10 +255,22 @@ ^- json a+(turn txs |=(=tx:naive (tx:to-json tx))) :: + ++ roller-txs + |= txs=(list roller-tx) + ^- json + :- %a + %+ turn txs + |= roller-tx + ^- json + %- pairs + :~ ['status' s+status] + ['hash' (hex (as-octs:mimes:html hash))] + ['type' s+type] + == + :: ++ point |= =point:naive ^- json - =, enjs:format %- pairs :~ ['dominion' s+dominion.point] :: @@ -244,15 +290,15 @@ =* net net.point :* ['rift' (numb rift.net)] :: + =, mimes:html :- 'keys' %- pairs :~ ['life' (numb life.keys.net)] ['suite' (numb suite.keys.net)] - ['auth' (numb auth.keys.net)] - ['crypt' (numb crypt.keys.net)] + ['auth' (hex (as-octs auth.keys.net))] + ['crypt' (hex (as-octs crypt.keys.net))] == :: - ['rift' (numb rift.net)] :- 'sponsor' %- pairs ~[['has' b+has.sponsor.net] ['who' (ship who.sponsor.net)]] @@ -261,220 +307,211 @@ ['escape' (ship u.escape.net)]~ == == :: + ++ points + |= points=(list [@p point:naive]) + ^- json + :- %a + %+ turn points + |= [ship=@p =point:naive] + %- pairs + :~ ['ship' (^ship ship)] + ['point' (^point point)] + == + :: + ++ ships + |= ships=(list @p) + ^- json + a+(turn ships ship) + :: ++ ownership |= [=address:naive =nonce:naive] ^- json - =, enjs:format %- pairs - :~ ['address' s+(crip "0x{((x-co:co 20) address)}")] + :~ (en-address address) ['nonce' (numb nonce)] == :: - ++ tx-status - |= =^tx-status + ++ spawned + |= children=(list [@p @ux]) ^- json - =, enjs:format + :- %a + %+ turn children + |= [child=@p address=@ux] %- pairs - :~ ['status' s+status.tx-status] - :: - :- 'tx' - ?~ tx.tx-status ~ - s+(crip "0x{((x-co:co 20) u.tx.tx-status)}") + :~ ['ship' (ship child)] + (en-address address) == + :: + ++ tx-status |=(=^tx-status ^-(json s+status.tx-status)) + :: + ++ config + |= roller-config + ^- json + %- pairs + :~ ['nextBatch' (time next-batch)] + ['frequency' (numb (div frequency ~s1))] + ['refreshTime' (numb (div refresh-time ~s1))] + ['contract' (hex 20 contract)] + ['chainId' (numb chain-id)] + == + :: + ++ hex + |= [p=@ q=@] + ^- json + s+(crip ['0' 'x' ((x-co:co (mul 2 p)) q)]) -- :: ++ to-hex |= =cord ^- (unit @ux) - =/ parsed=(unit (pair @ud @ux)) (de:base16:mimes:html cord) - ?~ parsed - ::~|(%non-hex-cord !!) + ?. =((end [3 2] cord) '0x') ~ + (rush (rsh [3 2] cord) hex) + :: + ++ build-l2-tx + |= [=l2-tx from=[@p proxy:naive] params=(map @t json)] + ^- (unit tx:naive) + ?: =(l2-tx %transfer-point) + ?~ data=(address-transfer:data:from-json params) + ~ + `[from %transfer-point u.data] + ?: =(l2-tx %spawn) + ?~ data=(address-ship:data:from-json params) + ~ + `[from %spawn u.data] + ?: =(l2-tx %configure-keys) + ?~ data=(keys:data:from-json params) + ~ + `[from %configure-keys u.data] + ?: ?=(spawn-action l2-tx) + ?~ data=(ship:data:from-json params) + ~ + ?- l2-tx + %escape `[from %escape u.data] + %cancel-escape `[from %cancel-escape u.data] + %adopt `[from %adopt u.data] + %reject `[from %reject u.data] + %detach `[from %detach u.data] + == + ?. ?=(proxy-action l2-tx) ~ - (some q.u.parsed) - :: - ++ rpc-res - |% - ++ sponsor - |= [id=@t params=(map @t json) action=spawn-action] - ^- [(unit cage) response:rpc] - ?. (params:validate params) - [~ ~(params error id)] - =/ sig=(unit @) (sig:from-json params) - =/ from=(unit [@p proxy:naive]) (from:from-json params) - =/ raw=(unit octs) (raw:from-json params) - =/ data=(unit @p) (ship:data:from-json params) - ?. &(?=(^ sig) ?=(^ from) ?=(^ raw) ?=(^ data)) - [~ ~(parse error id)] - :_ [%result id s+'ok'] - %- some - :- %aggregator-action - !> - =; =skim-tx:naive - [%submit | u.sig %ful u.raw u.from skim-tx] - ?- action - %escape [%escape u.data] - %cancel-escape [%cancel-escape u.data] - %adopt [%adopt u.data] - %reject [%reject u.data] - %detach [%detach u.data] - == - :: - ++ proxy - |= [id=@t params=(map @t json) action=proxy-action] - ^- [(unit cage) response:rpc] - ?. (params:validate params) - [~ ~(params error id)] - =/ sig=(unit @) (sig:from-json params) - =/ from=(unit [@p proxy:naive]) (from:from-json params) - =/ raw=(unit octs) (raw:from-json params) - =/ data=(unit @ux) (address:data:from-json params) - ?. &(?=(^ sig) ?=(^ from) ?=(^ raw) ?=(^ data)) - [~ ~(parse error id)] - :_ [%result id s+'ok'] - %- some - :- %aggregator-action - !> - =; =skim-tx:naive - [%submit | u.sig %ful u.raw u.from skim-tx] - ?- action - %set-management-proxy [%set-management-proxy u.data] - %set-spawn-proxy [%set-spawn-proxy u.data] - %set-transfer-proxy [%set-transfer-proxy u.data] - == - -- - :: - ++ error - |_ id=@t - :: https://www.jsonrpc.org/specification#error_object - :: - ++ parse [%error id '-32700' 'Failed to parsed'] - ++ request [%error id '-32600' 'Invalid Request'] - ++ method [%error id '-32601' 'Method not found'] - ++ params [%error id '-32602' 'Invalid params'] - ++ internal [%error id '-32603' 'Internal error'] - ++ not-found [%error id '-32000' 'Resource not found'] - -- - :: - ++ validate - |% - ++ params - |= params=(map @t json) - ^- ? - =((lent ~(tap by params)) 4) - -- + ?~ data=(address:data:from-json params) + ~ + ?- l2-tx + %set-management-proxy `[from %set-management-proxy u.data] + %set-spawn-proxy `[from %set-spawn-proxy u.data] + %set-transfer-proxy `[from %set-transfer-proxy u.data] + == -- |% ++ get-point |= [id=@t params=(map @t json) scry=$-(ship (unit point:naive))] ^- response:rpc - ?. =((lent ~(tap by params)) 1) - ~(params error id) + ?. =(~(wyt by params) 1) + ~(params error:json-rpc id) ?~ ship=(~(get by params) 'ship') - ~(params error id) - ?~ ship=(rush (so:dejs:format u.ship) ;~(pfix sig fed:ag)) - ~(params error id) + ~(params error:json-rpc id) + ?~ ship=(parse-ship u.ship) + ~(params error:json-rpc id) ?~ point=(scry u.ship) - ~(params error id) + ~(not-found error:json-rpc id) [%result id (point:to-json u.point)] :: -++ transfer-point +++ get-ships + |= [id=@t params=(map @t json) scry=$-(@ux (list @p))] + ^- response:rpc + ?. =(~(wyt by params) 1) + ~(params error:json-rpc id) + ?~ address=(address:from-json params) + ~(parse error:json-rpc id) + [%result id (ships:to-json (scry u.address))] +:: +++ get-dns + |= [id=@t params=(map @t json) dns=(list @t)] + ^- response:rpc + ?. =((lent ~(tap by params)) 0) + ~(params error:json-rpc id) + [%result id a+(turn dns (cork same (lead %s)))] +:: +++ cancel-tx |= [id=@t params=(map @t json)] ^- [(unit cage) response:rpc] - ?. (params:validate params) - [~ ~(params error id)] - =/ sig=(unit @) (sig:from-json params) - =/ from=(unit [ship @t]) (from:from-json params) - =/ raw=(unit octs) (raw:from-json params) - =/ data=(unit [@ux ?]) (address-transfer:data:from-json params) - ?: |(?=(~ sig) ?=(~ from) ?=(~ raw) ?=(~ data)) - [~ ~(parse error id)] + ?. =(~(wyt by params) 3) + [~ ~(params error:json-rpc id)] + =/ sig=(unit @) (sig:from-json params) + =/ keccak=(unit @ux) (hash:from-json params) + =/ data=(unit [l2-tx ship]) (cancel:data:from-json params) + ?. &(?=(^ sig) ?=(^ keccak) ?=(^ data)) + [~ ~(parse error:json-rpc id)] :_ [%result id s+'ok'] %- some - noun+!>([u.sig u.from u.data]) + aggregator-action+!>([%cancel u.sig u.keccak u.data]) :: -++ configure-keys - |= [id=@t params=(map @t json)] +++ get-spawned + |= [id=@t params=(map @t json) scry=$-(ship (list [ship @ux]))] + ^- response:rpc + ?. =((lent ~(tap by params)) 1) + ~(params error:json-rpc id) + ?~ ship=(ship:from-json params) + ~(params error:json-rpc id) + [%result id (spawned:to-json (scry u.ship))] +:: +++ process-rpc + |= [id=@t params=(map @t json) action=l2-tx] ^- [(unit cage) response:rpc] - ?. (params:validate params) - [~ ~(params error id)] - =/ sig=(unit @) (sig:from-json params) - =/ from=(unit [ship @t]) (from:from-json params) - =/ raw=(unit octs) (raw:from-json params) - =/ data=(unit [encrypt=@ auth=@ crypto-suite=@ breach=?]) - (keys:data:from-json params) - ?. &(?=(^ sig) ?=(^ from) ?=(^ raw) ?=(^ data)) - [~ ~(parse error id)] - :_ [%result id s+'ok'] + ?. =((lent ~(tap by params)) 4) + [~ ~(params error:json-rpc id)] + =+ ^- $: sig=(unit @) + from=(unit [ship proxy:naive]) + addr=(unit @ux) + == + =, from-json + [(sig params) (from params) (address params)] + ?: |(?=(~ sig) ?=(~ from) ?=(~ addr)) + [~ ~(parse error:json-rpc id)] + =/ tx=(unit tx:naive) (build-l2-tx action u.from params) + ?~ tx [~ ~(parse error:json-rpc id)] + =+ (gen-tx-octs:lib u.tx) + :_ [%result id (hex:to-json 32 (hash-tx:lib p q))] %- some - noun+!>([u.sig u.from u.data]) + aggregator-action+!>([%submit | u.addr u.sig %don u.tx]) :: -++ spawn - |= [id=@t params=(map @t json)] - ^- [(unit cage) response:rpc] - ?. (params:validate params) - [~ ~(params error id)] - =/ sig=(unit @) (sig:from-json params) - =/ from=(unit [@p proxy:naive]) (from:from-json params) - =/ raw=(unit octs) (raw:from-json params) - =/ data=(unit [@p @ux]) (address-ship:data:from-json params) - ?. &(?=(^ sig) ?=(^ from) ?=(^ raw) ?=(^ data)) - [~ ~(parse error id)] - :_ [%result id s+'ok'] - %- some - aggregator-action+!>([%submit | u.sig %ful u.raw u.from %spawn u.data]) -:: -++ escape sponsor:rpc-res -++ cancel-escape sponsor:rpc-res -++ adopt sponsor:rpc-res -++ detach sponsor:rpc-res -++ reject sponsor:rpc-res -++ management-proxy proxy:rpc-res -++ spawn-proxy proxy:rpc-res -++ transfer-proxy proxy:rpc-res -:: - readNonce(from=[ship proxy]) -> @ :: automatically increment for pending wraps -:: -++ read-nonce +++ nonce |= [id=@t params=(map @t json) scry=$-([ship proxy:naive] (unit @))] ^- response:rpc - ?. =((lent ~(tap by params)) 3) - ~(params error id) + ?. =((lent ~(tap by params)) 1) + ~(params error:json-rpc id) ?~ from=(from:from-json params) - ~(parse error id) + ~(parse error:json-rpc id) ?~ nonce=(scry u.from) - ~(params error id) + ~(not-found error:json-rpc id) [%result id (numb:enjs:format u.nonce)] :: ++ pending - :: FIXME: send raw-tx (i.e. tx with signature) instead? - :: |% - :: - readPendingRoll() -> (list pend-tx) :: ++ all |= [id=@t params=(map @t json) pending=(list pend-tx)] ^- response:rpc ?. =((lent ~(tap by params)) 0) - ~(params error id) + ~(params error:json-rpc id) [%result id (pending:to-json pending)] - :: - readPendingByShip(ship) -> (list pend-tx) :: ++ ship |= [id=@t params=(map @t json) scry=$-(@p (list pend-tx))] ^- response:rpc ?. =((lent ~(tap by params)) 1) - ~(params error id) + ~(params error:json-rpc id) ?~ ship=(ship:from-json params) - ~(parse error id) + ~(parse error:json-rpc id) [%result id (pending:to-json (scry u.ship))] - :: - readPendingByAddress(address) -> (list pend-tx) :: ++ addr |= [id=@t params=(map @t json) scry=$-(@ux (list pend-tx))] ^- response:rpc ?. =((lent ~(tap by params)) 1) - ~(params error id) + ~(params error:json-rpc id) ?~ address=(address:from-json params) - ~(parse error id) + ~(parse error:json-rpc id) [%result id (pending:to-json (scry u.address))] -- :: @@ -482,22 +519,50 @@ |= [id=@t params=(map @t json) scry=$-(@ tx-status)] ^- response:rpc ?. =((lent ~(tap by params)) 1) - ~(params error id) - ?~ keccak=(keccak:from-json params) - ~(parse error id) - [%result id (tx-status:to-json (scry u.keccak))] + ~(params error:json-rpc id) + ?~ hash=(hash:from-json params) + ~(parse error:json-rpc id) + [%result id (tx-status:to-json (scry u.hash))] :: -:: ++ history -:: |= $: id=@t -:: params=(map @t json) -:: :: FIXME: use proper type from aggregator/index -:: :: -:: scry=$-([@p proxy:naive] (list tx:naive)) -:: == -:: ^- response:rpc -:: ?. =((lent ~(tap by params)) 1) -:: ~(params error id) -:: ?~ from=(from:from-json params) -:: ~(parse error id) -:: [%result id (txs:to-json (scry u.from))] +++ next-batch + |= [id=@t params=(map @t json) when=time] + ^- response:rpc + ?. =((lent ~(tap by params)) 0) + ~(params error:json-rpc id) + [%result id (time:enjs:format when)] +:: +++ history + |= [id=@t params=(map @t json) scry=$-(address:naive (list roller-tx))] + ^- response:rpc + ?. =((lent ~(tap by params)) 1) + ~(params error:json-rpc id) + ?~ address=(address:from-json params) + ~(parse error:json-rpc id) + [%result id (roller-txs:to-json (scry u.address))] +:: +++ get-config + |= [id=@t params=(map @t json) =roller-config] + ^- response:rpc + ?. =((lent ~(tap by params)) 0) + ~(params error:json-rpc id) + [%result id (config:to-json roller-config)] +:: +++ hash-transaction + |= [id=@t params=(map @t json) chain-id=@] + ^- response:rpc + ?. =((lent ~(tap by params)) 4) + ~(params error:json-rpc id) + =+ ^- $: l2-tx=(unit l2-tx) + nonce=(unit @ud) + from=(unit [@p proxy:naive]) + == + =, from-json + [(tx params) (nonce params) (from params)] + ?: |(?=(~ nonce) ?=(~ from) ?=(~ l2-tx)) + ~(parse error:json-rpc id) + =/ tx=(unit tx:naive) (build-l2-tx u.l2-tx u.from params) + ?~ tx ~(parse error:json-rpc id) + :+ %result id + =- (hex:to-json 32 (hash-tx:lib p q)) + (unsigned-tx:lib chain-id u.nonce (gen-tx-octs:lib u.tx)) -- diff --git a/pkg/arvo/lib/azimuth.hoon b/pkg/arvo/lib/azimuth.hoon index 96e9967ea1..b834a4144b 100644 --- a/pkg/arvo/lib/azimuth.hoon +++ b/pkg/arvo/lib/azimuth.hoon @@ -132,7 +132,7 @@ 0x3e8c.a510.354b.c2fd.bbd6.1502.52d9.3105.c9c2.7bbe :: ++ naive - 0xb581.01cd.3bbb.cc6f.a40b.cdb0.4bb7.1623.b5c7.d39b + 0xe7cf.4b83.06d3.11ba.ca15.585f.e3f0.7cd0.441c.21d1 :: ++ launch 4.601.630 ++ public launch diff --git a/pkg/arvo/lib/bip/b158.hoon b/pkg/arvo/lib/bip/b158.hoon new file mode 100644 index 0000000000..bf7ac0badc --- /dev/null +++ b/pkg/arvo/lib/bip/b158.hoon @@ -0,0 +1,247 @@ +/- bc=bitcoin +/+ bcu=bitcoin-utils +|% +++ params + |% + ++ p 19 + ++ m 784.931 + -- +:: +++ siphash + |= [k=byts m=byts] + ^- byts + |^ + ?> =(wid.k 16) + ?> (lte (met 3 dat.k) wid.k) + ?> (lte (met 3 dat.m) wid.m) + =. k (flim:sha k) + =. m (flim:sha m) + (flim:sha (fin (comp m (init dat.k)))) + :: Initialise internal state + :: + ++ init + |= k=@ + ^- [@ @ @ @] + =/ k0=@ (end [6 1] k) + =/ k1=@ (cut 6 [1 1] k) + :^ (mix k0 0x736f.6d65.7073.6575) + (mix k1 0x646f.7261.6e64.6f6d) + (mix k0 0x6c79.6765.6e65.7261) + (mix k1 0x7465.6462.7974.6573) + :: + :: Compression rounds + ++ comp + |= [m=byts v=[v0=@ v1=@ v2=@ v3=@]] + ^- [@ @ @ @] + =/ len=@ud (div wid.m 8) + =/ last=@ (lsh [3 7] (mod wid.m 256)) + =| i=@ud + =| w=@ + |- + =. w (cut 6 [i 1] dat.m) + ?: =(i len) + =. v3.v (mix v3.v (mix last w)) + =. v (rnd (rnd v)) + =. v0.v (mix v0.v (mix last w)) + v + %= $ + v =. v3.v (mix v3.v w) + =. v (rnd (rnd v)) + =. v0.v (mix v0.v w) + v + i (add i 1) + == + :: + :: Finalisation rounds + ++ fin + |= v=[v0=@ v1=@ v2=@ v3=@] + ^- byts + =. v2.v (mix v2.v 0xff) + =. v (rnd (rnd (rnd (rnd v)))) + :- 8 + :(mix v0.v v1.v v2.v v3.v) + :: + :: Sipround + ++ rnd + |= [v0=@ v1=@ v2=@ v3=@] + ^- [@ @ @ @] + =. v0 (~(sum fe 6) v0 v1) + =. v2 (~(sum fe 6) v2 v3) + =. v1 (~(rol fe 6) 0 13 v1) + =. v3 (~(rol fe 6) 0 16 v3) + =. v1 (mix v1 v0) + =. v3 (mix v3 v2) + =. v0 (~(rol fe 6) 0 32 v0) + =. v2 (~(sum fe 6) v2 v1) + =. v0 (~(sum fe 6) v0 v3) + =. v1 (~(rol fe 6) 0 17 v1) + =. v3 (~(rol fe 6) 0 21 v3) + =. v1 (mix v1 v2) + =. v3 (mix v3 v0) + =. v2 (~(rol fe 6) 0 32 v2) + [v0 v1 v2 v3] + -- +:: +str: bit streams +:: read is from the front +:: write appends to the back +:: +++ str + |% + ++ read-bit + |= s=bits:bc + ^- [bit=@ub rest=bits:bc] + ?> (gth wid.s 0) + :* ?:((gth wid.s (met 0 dat.s)) 0b0 0b1) + [(dec wid.s) (end [0 (dec wid.s)] dat.s)] + == + :: + ++ read-bits + |= [n=@ s=bits:bc] + ^- [bits:bc rest=bits:bc] + =| bs=bits:bc + |- + ?: =(n 0) [bs s] + =^ b s (read-bit s) + $(n (dec n), bs (write-bits bs [1 b])) + :: + ++ write-bits + |= [s1=bits:bc s2=bits:bc] + ^- bits:bc + [(add wid.s1 wid.s2) (can 0 ~[s2 s1])] + -- +:: +gol: Golomb-Rice encoding/decoding +:: +++ gol + |% + :: +en: encode x and append to end of s + :: - s: bits stream + :: - x: number to add to the stream + :: - p: golomb-rice p param + :: + ++ en + |= [s=bits:bc x=@ p=@] + ^- bits:bc + =+ q=(rsh [0 p] x) + =+ unary=[+(q) (lsh [0 1] (dec (bex q)))] + =+ r=[p (end [0 p] x)] + %+ write-bits:str s + (write-bits:str unary r) + :: + ++ de + |= [s=bits:bc p=@] + ^- [delta=@ rest=bits:bc] + |^ ?> (gth wid.s 0) + =^ q s (get-q s) + =^ r s (read-bits:str p s) + [(add dat.r (lsh [0 p] q)) s] + :: + ++ get-q + |= s=bits:bc + =| q=@ + =^ first-bit s (read-bit:str s) + |- + ?: =(0 first-bit) [q s] + =^ b s (read-bit:str s) + $(first-bit b, q +(q)) + -- + -- +:: +hsh +:: +++ hsh + |% + :: +to-range + :: - item: scriptpubkey to hash + :: - f: N*M + :: - k: key for siphash (end of blockhash, reversed) + :: + ++ to-range + |= [item=byts f=@ k=byts] + ^- @ + (rsh [0 64] (mul f (swp 3 dat:(siphash k item)))) + :: +set-construct: return sorted hashes of scriptpubkeys + :: + ++ set-construct + |= [items=(list byts) k=byts f=@] + ^- (list @) + %+ sort + %+ turn items + |= item=byts + (to-range item f k) + lth + -- +:: +++ parse-filter + |= filter=hexb:bc + ^- [n=@ux gcs-set=bits:bc] + =/ n n:(de:csiz:bcu filter) + =/ lead=@ ?:(=(1 wid.n) 1 +(wid.n)) + :- dat.n + [(mul 8 (sub wid.filter lead)) `@ub`dat:(drop:byt:bcu lead filter)] +:: +to-key: blockhash (little endian) to key for siphash +:: +++ to-key + |= blockhash=tape + ^- byts + %+ take:byt:bcu 16 + %- flip:byt:bcu + (from-cord:hxb:bcu (crip blockhash)) +:: +match: whether block filter matches *any* target scriptpubkeys +:: - filter: full block filter, with leading N +:: - k: key for siphash (end of blockhash, reversed) +:: - targets: scriptpubkeys to match +:: +++ match + |= [filter=hexb:bc k=byts targets=(list byts)] + ^- ? + =/ [p=@ m=@] [p:params m:params] + =/ [n=@ux gcs-set=bits:bc] (parse-filter filter) + =+ target-hs=(set-construct:hsh targets k (mul n m)) + =+ last-val=0 + |- + ?~ target-hs %.n + ?: =(last-val i.target-hs) + %.y + ?: (gth last-val i.target-hs) + $(target-hs t.target-hs) + :: last-val is less than target: check next val in GCS, if any + :: + ?: (lth wid.gcs-set p) %.n + =^ delta gcs-set + (de:gol gcs-set p) + $(last-val (add delta last-val)) +:: +all-match: returns all target byts that match +:: - filter: full block filter, with leading N +:: - k: key for siphash (end of blockhash, reversed) +:: - targets: scriptpubkeys to match +:: +++ all-match + |= [filter=hexb:bc k=byts targets=(list byts)] + ^- (set hexb:bc) + %- ~(gas in *(set hexb:bc)) + =/ [p=@ m=@] [p:params m:params] + =/ [n=@ux gcs-set=bits:bc] (parse-filter filter) + =/ target-map=(map @ hexb:bc) + %- ~(gas by *(map @ hexb:bc)) + %+ turn targets + |=(t=hexb:bc [(to-range:hsh t (mul n m) k) t]) + =+ target-hs=(sort ~(tap in ~(key by target-map)) lth) + =+ last-val=0 + =| matches=(list @) + |- + ?~ target-hs + (murn matches ~(get by target-map)) + ?: =(last-val i.target-hs) + %= $ + target-hs t.target-hs + matches [last-val matches] + == + ?: (gth last-val i.target-hs) + $(target-hs t.target-hs) + :: last-val is less than target: get next val in GCS, if any + :: + ?: (lth wid.gcs-set p) + (murn matches ~(get by target-map)) + =^ delta gcs-set + (de:gol gcs-set p) + $(last-val (add delta last-val)) +-- diff --git a/pkg/arvo/lib/bip/b173.hoon b/pkg/arvo/lib/bip/b173.hoon new file mode 100644 index 0000000000..e2c46db1ac --- /dev/null +++ b/pkg/arvo/lib/bip/b173.hoon @@ -0,0 +1,144 @@ +:: BIP173: Bech32 Addresses +:: https://github.com/bitcoin/bips/blob/master/bip-0173.mediawiki +:: +:: Heavily copies: +:: https://github.com/bitcoinjs/bech32/blob/master/index.js +:: +/- sur=bitcoin +/+ bcu=bitcoin-utils +=, sur +=, bcu +|% +++ prefixes + ^- (map network tape) + (my [[%main "bc"] [%testnet "tb"] ~]) +++ charset "qpzry9x8gf2tvdw0s3jn54khce6mua7l" ++$ raw-decoded [hrp=tape data=(list @) checksum=(list @)] +:: below is a port of: https://github.com/bitcoinjs/bech32/blob/master/index.js +:: +++ polymod + |= values=(list @) + |^ ^- @ + =/ gen=(list @ux) + ~[0x3b6a.57b2 0x2650.8e6d 0x1ea1.19fa 0x3d42.33dd 0x2a14.62b3] + =/ chk=@ 1 + |- ?~ values chk + =/ top (rsh [0 25] chk) + =. chk + (mix i.values (lsh [0 5] (dis chk 0x1ff.ffff))) + $(values t.values, chk (update-chk chk top gen)) +:: + ++ update-chk + |= [chk=@ top=@ gen=(list @ux)] + =/ is (gulf 0 4) + |- ?~ is chk + ?: =(1 (dis 1 (rsh [0 i.is] top))) + $(is t.is, chk (mix chk (snag i.is gen))) + $(is t.is) + -- +:: +++ expand-hrp + |= hrp=tape + ^- (list @) + =/ front (turn hrp |=(p=@tD (rsh [0 5] p))) + =/ back (turn hrp |=(p=@tD (dis 31 p))) + (zing ~[front ~[0] back]) +:: +++ verify-checksum + |= [hrp=tape data-and-checksum=(list @)] + ^- ? + %- |=(a=@ =(1 a)) + %- polymod + (weld (expand-hrp hrp) data-and-checksum) +:: +++ checksum + |= [hrp=tape data=(list @)] + ^- (list @) + :: xor 1 with the polymod + :: + =/ pmod=@ + %+ mix 1 + %- polymod + (zing ~[(expand-hrp hrp) data (reap 6 0)]) + %+ turn (gulf 0 5) + |=(i=@ (dis 31 (rsh [0 (mul 5 (sub 5 i))] pmod))) +:: +++ charset-to-value + |= c=@tD + ^- (unit @) + (find ~[c] charset) +++ value-to-charset + |= value=@ + ^- (unit @tD) + ?: (gth value 31) ~ + `(snag value charset) +:: +++ is-valid + |= [bech=tape last-1-pos=@] ^- ? + ?& ?|(=((cass bech) bech) =((cuss bech) bech)) :: to upper or to lower is same as bech + (gte last-1-pos 1) + (lte (add last-1-pos 7) (lent bech)) + (lte (lent bech) 90) + (levy bech |=(c=@tD (gte c 33))) + (levy bech |=(c=@tD (lte c 126))) + == +:: data should be 5bit words +:: +++ encode-raw + |= [hrp=tape data=(list @)] + ^- cord + =/ combined=(list @) + (weld data (checksum hrp data)) + %- crip + (zing ~[hrp "1" (tape (murn combined value-to-charset))]) +++ decode-raw + |= body=cord + ^- (unit raw-decoded) + =/ bech (cass (trip body)) :: to lowercase + =/ pos (flop (fand "1" bech)) + ?~ pos ~ + =/ last-1=@ i.pos + ?. (is-valid bech last-1) :: check bech32 validity (not segwit validity or checksum) + ~ + =/ hrp (scag last-1 bech) + =/ encoded-data-and-checksum=(list @) + (slag +(last-1) bech) + =/ data-and-checksum=(list @) + %+ murn encoded-data-and-checksum + charset-to-value + ?. =((lent encoded-data-and-checksum) (lent data-and-checksum)) :: ensure all were in CHARSET + ~ + ?. (verify-checksum hrp data-and-checksum) + ~ + =/ checksum-pos (sub (lent data-and-checksum) 6) + `[hrp (scag checksum-pos data-and-checksum) (slag checksum-pos data-and-checksum)] +:: +from-address: BIP173 bech32 address encoding to hex +:: https://github.com/bitcoin/bips/blob/master/bip-0173.mediawiki +:: expects to drop a leading 5-bit 0 (the witness version) +:: +++ from-address + |= body=cord + ^- hexb + ~| "Invalid bech32 address" + =/ d=(unit raw-decoded) (decode-raw body) + ?> ?=(^ d) + =/ bs=bits (from-atoms:bit 5 data.u.d) + =/ byt-len=@ (div (sub wid.bs 5) 8) + ?> =(5^0b0 (take:bit 5 bs)) + ?> ?| =(20 byt-len) + =(32 byt-len) + == + [byt-len `@ux`dat:(take:bit (mul 8 byt-len) (drop:bit 5 bs))] +:: pubkey is the 33 byte ECC compressed public key +:: +++ encode-pubkey + |= [=network pubkey=byts] + ^- (unit cord) + ?. =(33 wid.pubkey) + ~|('pubkey must be a 33 byte ECC compressed public key' !!) + =/ prefix (~(get by prefixes) network) + ?~ prefix ~ + :- ~ + %+ encode-raw u.prefix + [0v0 (to-atoms:bit 5 [160 `@ub`dat:(hash-160 pubkey)])] +-- diff --git a/pkg/arvo/lib/bip/b174.hoon b/pkg/arvo/lib/bip/b174.hoon new file mode 100644 index 0000000000..3bae71d929 --- /dev/null +++ b/pkg/arvo/lib/bip/b174.hoon @@ -0,0 +1,182 @@ +:: BIP174: PSBTs +:: https://github.com/bitcoin/bips/blob/master/bip-0174.mediawiki +:: +/- sur=bitcoin +/+ bcu=bitcoin-utils +=, sur +=, bcu +|% +++ en + |% + ++ globals + |= rawtx=hexb + ^- map:psbt + :~ [[1 0x0] rawtx] + == + :: + ++ input + |= [only-witness=? i=in:psbt] + ^- map:psbt + %+ weld + ?: only-witness ~ + ~[[1^0x0 rawtx.i]] + :~ (witness-tx i) + (hdkey %input hdkey.i) + == + :: + ++ output + |= =out:psbt + ^- map:psbt + ?~ hk.out ~ + :~ (hdkey %output u.hk.out) + == + :: + ++ witness-tx + |= i=in:psbt + ^- keyval:psbt + :- [1 0x1] + %- cat:byt + :~ (flip:byt 8^value.utxo.i) + 1^0x16 + 2^0x14 + (hash-160 pubkey.hdkey.i) + == + :: + ++ hdkey + |= [=target:psbt h=^hdkey] + ^- keyval:psbt + =/ typ=@ux + ?- target + %input 0x6 + %output 0x2 + == + =/ coin-type=hexb + ?- network.h + %main + 1^0x0 + %testnet + 1^0x1 + == + :- (cat:byt ~[1^typ pubkey.h]) + %- cat:byt + :~ fprint.h + 1^`@ux`bipt.h 3^0x80 + coin-type 3^0x80 + 4^0x80 + 1^`@ux`chyg.h 3^0x0 + (flip:byt 4^idx.h) + == + :: + ++ keyval-byts + |= kv=keyval:psbt + ^- hexb + %- cat:byt + :~ 1^wid.key.kv + key.kv + 1^wid.val.kv + val.kv + == + :: + ++ map-byts + |= m=map:psbt + ^- (unit hexb) + ?~ m ~ + :- ~ + %- cat:byt + (turn m keyval-byts) + -- + ++ base64 + |= b=hexb + ^- base64:psbt + %- en:base64:mimes:html + (flip:byt b) +:: +encode: make base64 cord of PSBT +:: - only-witness: don't include non-witness UTXO +:: +++ encode + |= $: only-witness=? + rawtx=hexb + txid=hexb + inputs=(list in:psbt) + outputs=(list out:psbt) + == + ^- base64:psbt + =/ sep=(unit hexb) `1^0x0 + =/ final=(list (unit hexb)) + %+ join sep + %+ turn + %- zing + :~ ~[(globals:en rawtx)] + (turn inputs (cury input:en only-witness)) + (turn outputs output:en) + == + map-byts:en + %- base64:en + ^- byts + %- cat:byt + %+ weld ~[[5 0x70.7362.74ff]] + (murn (snoc final sep) same) +:: +++ parse + |= psbt-base64=cord + ^- (list map:psbt) + =/ todo=hexb + (drop:byt 5 (to-byts psbt-base64)) + =| acc=(list map:psbt) + =| m=map:psbt + |- + ?: =(wid.todo 0) + (snoc acc m) + :: 0x0: map separator + :: + ?: =(1^0x0 (take:byt 1 todo)) + $(acc (snoc acc m), m *map:psbt, todo (drop:byt 1 todo)) + =^ kv todo (next-keyval todo) + $(m (snoc m kv)) +:: +get-txid: extract txid from a valid PSBT +:: +++ get-txid + |= psbt-base64=cord + ^- hexb + =/ tx=hexb + %- raw-tx + %+ drop:byt 5 + (to-byts psbt-base64) + %- flip:byt + (dsha256 tx) +:: +raw-tx: extract hex transaction +:: looks for key 0x0 in global map +:: crashes if tx not in hex +:: +++ raw-tx + |= b=hexb + ^- hexb + |- + ?: =(wid.b 0) !! + ?: =(1^0x0 (take:byt 1 b)) !! + =/ nk (next-keyval b) + ?: =(0x0 dat.key.kv.nk) + val.kv.nk + $(b rest.nk) +:: +next-keyval: returns next key-val in a PSBT map +:: input first byte must be a map key length +:: +++ next-keyval + |= b=hexb + ^- [kv=keyval:psbt rest=hexb] + =/ klen dat:(take:byt 1 b) + =/ k (take:byt klen (drop:byt 1 b)) + =/ vlen dat:(take:byt 1 (drop:byt (add 1 klen) b)) + =/ v (take:byt vlen (drop:byt (add 2 klen) b)) + ?> ?&((gth wid.k 0) (gth wid.v 0)) + :- [k v] + (drop:byt ;:(add 2 klen vlen) b) +:: +++ to-byts + |= psbt-base64=cord + ^- hexb + ~| "Invalid PSBT" + =+ p=(de:base64:mimes:html psbt-base64) + ?~ p !! + (flip:byt u.p) +-- diff --git a/pkg/arvo/lib/bitcoin-json.hoon b/pkg/arvo/lib/bitcoin-json.hoon new file mode 100644 index 0000000000..fb2562932b --- /dev/null +++ b/pkg/arvo/lib/bitcoin-json.hoon @@ -0,0 +1,234 @@ +/- btc-wallet, btc-provider, bitcoin +/+ bl=bitcoin +|% +++ dejs + =, dejs:format + |% + ++ command + |= jon=json + ^- command:btc-wallet + %. jon + %- of + :~ set-provider+(mu ship) + check-provider+ship + check-payee+ship + set-current-wallet+so + add-wallet+add-wallet + delete-wallet+so + init-payment-external+init-payment-external + init-payment+init-payment + broadcast-tx+so + gen-new-address+|=(json ~) + == + :: + ++ ship (su ;~(pfix sig fed:ag)) + :: + ++ add-wallet + %- ot + :~ xpub+so + fprint+(at [ni ni ~]) + scan-to+(mu (at [ni ni ~])) + max-gap+(mu ni) + confs+(mu ni) + == + :: + ++ init-payment-external + %- ot + :~ address+address + value+ni + feyb+ni + note+(mu so) + == + :: + ++ init-payment + %- ot + :~ payee+ship + value+ni + feyb+ni + note+(mu so) + == + :: + ++ address + |= jon=json + ?> ?=([%s @t] jon) + ^- address:bitcoin + (from-cord:adr:bl +.jon) + -- +:: +++ enjs + =, enjs:format + |% + ++ status + |= sta=status:btc-provider + ^- json + %+ frond -.sta + ?- -.sta + %connected (connected sta) + %new-block (new-block sta) + %disconnected ~ + == + :: + ++ connected + |= sta=status:btc-provider + ?> ?=(%connected -.sta) + %- pairs + :~ network+s+network.sta + block+(numb block.sta) + fee+?~(fee.sta ~ (numb u.fee.sta)) + == + :: + ++ new-block + |= sta=status:btc-provider + ?> ?=(%new-block -.sta) + %- pairs + :~ network+s+network.sta + block+(numb block.sta) + fee+?~(fee.sta ~ (numb u.fee.sta)) + blockhash+(hexb blockhash.sta) + blockfilter+(hexb blockfilter.sta) + == + :: + ++ hexb + |= h=hexb:bitcoin + ^- json + %- pairs + :~ wid+(numb:enjs wid.h) + dat+s+(scot %ux dat.h) + == + :: + ++ update + |= upd=update:btc-wallet + ^- json + %+ frond -.upd + ?- -.upd + %initial (initial upd) + %change-provider (change-provider upd) + %change-wallet (change-wallet upd) + %psbt (psbt upd) + %btc-state (btc-state btc-state.upd) + %new-tx (hest hest.upd) + %cancel-tx (hexb txid.upd) + %new-address (address address.upd) + %balance (balance balance.upd) + %error s+error.upd + %broadcast-success ~ + == + :: + ++ initial + |= upd=update:btc-wallet + ?> ?=(%initial -.upd) + ^- json + %- pairs + :~ provider+(provider provider.upd) + wallet+?~(wallet.upd ~ [%s u.wallet.upd]) + balance+(balance balance.upd) + history+(history history.upd) + btc-state+(btc-state btc-state.upd) + address+?~(address.upd ~ (address u.address.upd)) + == + :: + ++ change-provider + |= upd=update:btc-wallet + ?> ?=(%change-provider -.upd) + ^- json + (provider provider.upd) + :: + ++ change-wallet + |= upd=update:btc-wallet + ?> ?=(%change-wallet -.upd) + ^- json + %- pairs + :~ wallet+?~(wallet.upd ~ [%s u.wallet.upd]) + balance+(balance balance.upd) + history+(history history.upd) + == + :: + ++ psbt + |= upd=update:btc-wallet + ?> ?=(%psbt -.upd) + ^- json + %- pairs + :~ pb+s+pb.upd + fee+(numb fee.upd) + == + :: + ++ balance + |= b=(unit [p=@ q=@]) + ^- json + ?~ b ~ + %- pairs + :~ confirmed+(numb p.u.b) + unconfirmed+(numb q.u.b) + == + :: + ++ btc-state + |= bs=btc-state:btc-wallet + ^- json + %- pairs + :~ block+(numb block.bs) + fee+?~(fee.bs ~ (numb u.fee.bs)) + date+(sect t.bs) + == + :: + ++ provider + |= p=(unit provider:btc-wallet) + ^- json + ?~ p ~ + %- pairs + :~ host+(ship host.u.p) + connected+b+connected.u.p + == + :: + ++ history + |= hy=history:btc-wallet + ^- json + :- %o + ^- (map @t json) + %- ~(rep by hy) + |= [[=txid:btc-wallet h=hest:btc-wallet] out=(map @t json)] + ^- (map @t json) + (~(put by out) (scot %ux dat.txid) (hest h)) + :: + ++ hest + |= h=hest:btc-wallet + ^- json + %- pairs + :~ xpub+s+xpub.h + txid+(hexb txid.h) + confs+(numb confs.h) + recvd+?~(recvd.h ~ (sect u.recvd.h)) + inputs+(vals inputs.h) + outputs+(vals outputs.h) + note+?~(note.h ~ [%s u.note.h]) + == + :: + ++ vals + |= vl=(list [=val:tx:bitcoin s=(unit @p)]) + ^- json + :- %a + %+ turn vl + |= [v=val:tx:bitcoin s=(unit @p)] + %- pairs + :~ val+(val v) + ship+?~(s ~ (ship u.s)) + == + :: + ++ val + |= v=val:tx:bitcoin + ^- json + %- pairs + :~ txid+(hexb txid.v) + pos+(numb pos.v) + address+(address address.v) + value+(numb value.v) + == + :: + ++ address + |= a=address:bitcoin + ^- json + ?- -.a + %base58 [%s (rsh [3 2] (scot %uc +.a))] + %bech32 [%s +.a] + == + -- +-- diff --git a/pkg/arvo/lib/bitcoin-utils.hoon b/pkg/arvo/lib/bitcoin-utils.hoon new file mode 100644 index 0000000000..69ba4738d3 --- /dev/null +++ b/pkg/arvo/lib/bitcoin-utils.hoon @@ -0,0 +1,166 @@ +:: lib/bitcoin-utils.hoon +:: Utilities for working with BTC data types and transactions +:: +/- sur=bitcoin +=, sur +|% +:: +:: TODO: move this bit/byt stuff to zuse +:: bit/byte utilities +:: +:: +:: +blop: munge bit and byt sequences (cat, flip, take, drop) +:: +++ blop + |_ =bloq + +$ biyts [wid=@ud dat=@] + ++ cat + |= bs=(list biyts) + ^- biyts + :- (roll (turn bs |=(b=biyts -.b)) add) + (can bloq (flop bs)) + :: +flip: flip endianness while preserving lead/trail zeroes + :: + ++ flip + |= b=biyts + ^- biyts + [wid.b (rev bloq b)] + :: +take: take n bloqs from front + :: pads front with extra zeroes if n is longer than input + :: + ++ take + |= [n=@ b=biyts] + ^- biyts + ?: (gth n wid.b) + [n dat.b] + [n (rsh [bloq (sub wid.b n)] dat.b)] + :: +drop: drop n bloqs from front + :: returns 0^0 if n >= width + :: + ++ drop + |= [n=@ b=biyts] + ^- biyts + ?: (gte n wid.b) + 0^0x0 + =+ n-take=(sub wid.b n) + [n-take (end [bloq n-take] dat.b)] + -- +++ byt ~(. blop 3) +:: +++ bit + =/ bl ~(. blop 0) + |% + ++ cat cat:bl:bit + ++ flip flip:bl:bit + ++ take take:bl:bit + ++ drop drop:bl:bit + ++ from-atoms + |= [bitwidth=@ digits=(list @)] + ^- bits + %- cat:bit + %+ turn digits + |= a=@ + ?> (lte (met 0 a) bitwidth) + [bitwidth `@ub`a] + :: +to-atoms: convert bits to atoms of bitwidth + :: + ++ to-atoms + |= [bitwidth=@ bs=bits] + ^- (list @) + =| res=(list @) + ?> =(0 (mod wid.bs bitwidth)) + |- + ?: =(0 wid.bs) res + %= $ + res (snoc res dat:(take:bit bitwidth bs)) + bs (drop:bit bitwidth bs) + == + -- +:: big endian sha256: input and output are both MSB first (big endian) +:: +++ sha256 + |= =byts + ^- hexb + %- flip:byt + [32 (shay (flip:byt byts))] +:: +++ dsha256 + |= =byts + (sha256 (sha256 byts)) +:: +++ hash-160 + |= val=byts + ^- hexb + =, ripemd:crypto + :- 20 + %- ripemd-160 + (sha256 val) + +:: +:: hxb: hex parsing utilities +:: +++ hxb + |% + ++ from-cord + |= h=@t + ^- hexb + ?: =('' h) 1^0x0 + :: Add leading 00 + :: + =+ (lsh [3 2] h) + :: Group by 4-size block + :: + =+ (rsh [3 2] -) + :: Parse hex to atom + :: + :- (div (lent (trip h)) 2) + `@ux`(rash - hex) + :: + ++ to-cord + |= =hexb + ^- cord + (en:base16:mimes:html hexb) + -- +:: +:: +csiz: CompactSize integers (a Bitcoin-specific datatype) +:: https://btcinformation.org/en/developer-reference#compactsize-unsigned-integers +:: - encode: big endian to little endian +:: - decode: little endian to big endian +:: +++ csiz + |% + ++ en + |= a=@ + ^- hexb + =/ l=@ (met 3 a) + ?: =(l 1) 1^a + ?: =(l 2) (cat:byt ~[1^0xfd (flip:byt 2^a)]) + ?: (lte l 4) (cat:byt ~[1^0xfe (flip:byt 4^a)]) + ?: (lte l 8) (cat:byt ~[1^0xff (flip:byt 8^a)]) + ~|("Cannot encode CompactSize longer than 8 bytes" !!) + :: + ++ de + |= h=hexb + ^- [n=hexb rest=hexb] + =/ s=@ux dat:(take:byt 1 h) + ?: (lth s 0xfd) [1^s (drop:byt 1 h)] + ~| "Invalid compact-size at start of {}" + =/ len=bloq + ?+ s !! + %0xfd 1 + %0xfe 2 + %0xff 3 + == + :_ (drop:byt (add 1 len) h) + %- flip:byt + (take:byt (bex len) (drop:byt 1 h)) + :: +dea: atom instead of hexb for parsed CompactSize + :: + ++ dea + |= h=hexb + ^- [a=@ rest=hexb] + => (de h) + [dat.n rest] + -- +:: +-- diff --git a/pkg/arvo/lib/bitcoin.hoon b/pkg/arvo/lib/bitcoin.hoon new file mode 100644 index 0000000000..d790ff5a03 --- /dev/null +++ b/pkg/arvo/lib/bitcoin.hoon @@ -0,0 +1,286 @@ +:: bitcoin.hoon +:: top-level Bitcoin constants +:: expose BIP libraries +:: +/- sur=bitcoin +/+ bech32=bip-b173, pbt=bip-b174, bcu=bitcoin-utils +=, sur +=, bcu +|% +++ overhead-weight ^-(vbytes 11) +++ input-weight + |= =bipt + ^- vbytes + ?- bipt + %44 148 + %49 91 + %84 68 + == +++ output-weight + |= =bipt + ^- vbytes + ?- bipt + %44 34 + %49 32 + %84 31 + == +:: +++ xpub-type + |= =xpub + ^- [=bipt =network] + =/ prefix=tape (scag 4 (trip xpub)) + ?: =("tpub" prefix) [%44 %testnet] + ?: =("upub" prefix) [%49 %testnet] + ?: =("vpub" prefix) [%84 %testnet] + ?: =("xpub" prefix) [%44 %main] + ?: =("ypub" prefix) [%49 %main] + ?: =("zpub" prefix) [%84 %main] + ~|("invalid xpub: {}" !!) +:: +:: adr: address manipulation +:: +++ adr + |% + ++ get-bipt + |= a=address + ^- bipt + =/ spk=hexb (to-script-pubkey:adr a) + ?: =(25 wid.spk) %44 + ?: =(23 wid.spk) %49 + ?: =(22 wid.spk) %84 + ?: =(34 wid.spk) %84 + ~|("Invalid address" !!) + :: + ++ to-cord + |= a=address ^- cord + ?: ?=([%base58 *] a) + (scot %uc +.a) + +.a + :: + ++ from-pubkey + |= [=bipt =network pubkey=hexb] + ^- address + ?- bipt + %44 + :- %base58 + =< ^-(@uc dat) + %- cat:byt + :- ?- network + %main 1^0x0 + %testnet 1^0x6f + == + ~[(hash-160 pubkey)] + :: + %49 + :- %base58 + =< ^-(@uc dat) + %- cat:byt + :~ ?- network + %main 1^0x5 + %testnet 1^0xc4 + == + %- hash-160 + (cat:byt ~[2^0x14 (hash-160 pubkey)]) + == + :: + %84 + :- %bech32 + (need (encode-pubkey:bech32 network pubkey)) + == + :: + ++ from-cord + |= addrc=@t + |^ + =/ addrt=tape (trip addrc) + ^- address + ?: (is-base58 addrt) + [%base58 `@uc`(scan addrt fim:ag)] + ?: (is-bech32 addrt) + [%bech32 addrc] + ~|("Invalid address: {}" !!) + :: + ++ is-base58 + |= at=tape + ^- ? + ?| =("m" (scag 1 at)) + =("1" (scag 1 at)) + =("3" (scag 1 at)) + =("2" (scag 1 at)) + == + :: + ++ is-bech32 + |= at=tape + ^- ? + ?| =("bc1" (scag 3 at)) + =("tb1" (scag 3 at)) + == + -- + :: + ++ to-script-pubkey + |= =address + ^- hexb + ?- -.address + %bech32 + =+ h=(from-address:bech32 +.address) + %- cat:byt + :~ 1^0x0 + 1^wid.h + h + == + :: + %base58 + =/ h=hexb [21 `@ux`+.address] + =+ lead-byt=dat:(take:byt 1 h) + =/ version-network=[bipt network] + ?: =(0x0 lead-byt) [%44 %main] + ?: =(0x6f lead-byt) [%44 %testnet] + ?: =(0x5 lead-byt) [%49 %main] + ?: =(0xc4 lead-byt) [%49 %testnet] + ~|("Invalid base58 address: {<+.address>}" !!) + %- cat:byt + ?: ?=(%44 -.version-network) + :~ 3^0x76.a914 + (drop:byt 1 h) + 2^0x88ac + == + :~ 2^0xa914 + (drop:byt 1 h) + 1^0x87 + == + == + -- +:: +:: +txu: transaction utility core +:: - primarily used for calculating txids +:: - ignores signatures in inputs +:: +++ txu + |% + ++ en + |% + ++ input + |= i=input:tx + ^- hexb + %- cat:byt + :~ (flip:byt txid.i) + (flip:byt 4^pos.i) + ?~ script-sig.i 1^0x0 + %- cat:byt + ~[(en:csiz wid.u.script-sig.i) u.script-sig.i] + (flip:byt sequence.i) + == + :: + ++ output + |= o=output:tx + ^- hexb + %- cat:byt + :~ (flip:byt 8^value.o) + 1^wid.script-pubkey.o + script-pubkey.o + == + -- + :: + ++ de + |% + ++ nversion + |= b=hexb + ^- [nversion=@ud rest=hexb] + :- dat:(flip:byt (take:byt 4 b)) + (drop:byt 4 b) + :: + ++ segwit + |= b=hexb + ^- [segwit=(unit @ud) rest=hexb] + ?. =(1^0x0 (take:byt 1 b)) + [~ b] + :- [~ dat:(take:byt 2 b)] + (drop:byt 2 b) + :: + ++ script-sig + |= b=hexb + ^- [sig=hexb rest=hexb] + =^ siglen=hexb b (de:csiz b) + :- (take:byt dat.siglen b) + (drop:byt dat.siglen b) + :: + ++ sequence + |= b=hexb + ^- [seq=hexb rest=hexb] + [(flip:byt (take:byt 4 b)) (drop:byt 4 b)] + :: + ++ inputs + |= b=hexb + ^- [is=(list input:tx) rest=hexb] + |^ + =| acc=(list input:tx) + =^ count b (dea:csiz b) + |- + ?: =(0 count) [acc b] + =^ i b (input b) + $(acc (snoc acc i), count (dec count)) + :: + ++ input + |= b=hexb + ^- [i=input:tx rest=hexb] + =/ txid (flip:byt (take:byt 32 b)) + =/ pos dat:(flip:byt (take:byt 4 (drop:byt 32 b))) + =^ sig=hexb b (script-sig (drop:byt 36 b)) + =^ seq=hexb b (sequence b) + :_ b + [txid pos seq ?:((gth wid.sig 0) `sig ~) ~ 0] + -- + :: + ++ outputs + |= b=hexb + ^- [os=(list output:tx) rest=hexb] + =| acc=(list output:tx) + =^ count b (dea:csiz b) + |- + ?: =(0 count) [acc b] + =/ value (flip:byt (take:byt 8 b)) + =^ scriptlen b (dea:csiz (drop:byt 8 b)) + %= $ + acc %+ snoc acc + :- (take:byt scriptlen b) + dat.value + b (drop:byt scriptlen b) + count (dec count) + == + -- + :: +basic-encode: encodes data in a format suitable for hashing + :: + ++ basic-encode + |= =data:tx + ^- hexb + %- cat:byt + %- zing + :~ ~[(flip:byt 4^nversion.data)] + ~[(en:csiz (lent is.data))] + (turn is.data input:en) + ~[(en:csiz (lent os.data))] + (turn os.data output:en) + ~[(flip:byt 4^locktime.data)] + == + ++ get-id + |= =data:tx + ^- hexb + %- flip:byt + %- dsha256 + (basic-encode data) + :: + ++ decode + |= b=hexb + ^- data:tx + =^ nversion b + (nversion:de b) + =^ segwit b + (segwit:de b) + =^ inputs b + (inputs:de b) + =^ outputs b + (outputs:de b) + =/ locktime=@ud + dat:(take:byt 4 (flip:byt b)) + [inputs outputs locktime nversion segwit] + -- +-- diff --git a/pkg/arvo/lib/btc-provider.hoon b/pkg/arvo/lib/btc-provider.hoon new file mode 100644 index 0000000000..98276607fa --- /dev/null +++ b/pkg/arvo/lib/btc-provider.hoon @@ -0,0 +1,209 @@ +/- bp=btc-provider, json-rpc +/+ bc=bitcoin +^? +::=< [sur .] +::=, sur +|% +:: +from-epoch: time since Jan 1, 1970 in seconds. +:: +++ 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)] + == == +-- diff --git a/pkg/arvo/lib/btc.hoon b/pkg/arvo/lib/btc.hoon new file mode 100644 index 0000000000..179431c3d1 --- /dev/null +++ b/pkg/arvo/lib/btc.hoon @@ -0,0 +1,571 @@ +:: lib/btc.hoon +:: +/- *btc-wallet, json-rpc, bp=btc-provider +/+ bip32, bc=bitcoin +=, secp:crypto +=+ ecc=secp256k1 +|% +:: +:: Formerly lib/btc-wallet.hoon +:: +:: +++ defaults + |% + ++ max-gap 20 + ++ confs 6 + -- + :: +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) +:: +++ num-confs + |= [last-block=@ud =utxo:bc] + ?: =(0 height.utxo) 0 + (add 1 (sub last-block height.utxo)) +:: +++ from-xpub + |= $: =xpub:bc + =fprint:bc + scan-to=(unit scon) + max-gap=(unit @ud) + confs=(unit @ud) + == + ^- walt + =/ [=bipt =network] (xpub-type:bc xpub) + :* xpub + network + fprint + +6:(from-extended:bip32 (trip xpub)) + bipt + *wach + [0 0] + %.n + (fall scan-to *scon) + (fall max-gap max-gap:defaults) + (fall confs confs:defaults) + == +:: +address-coords: find wallet info for the address, if any +:: +++ address-coords + |= [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]) + -- +:: +++ new-txbu + |= $: w=walt + payee=(unit ship) + =vbytes:bc + 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 + ~ + == +:: 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 + ^- sats:bc + =/ [in=sats out=sats] value + (sub in out) + :: + ++ vbytes + ^- vbytes:bc + %+ add overhead-weight:bc + %+ add + %+ roll + (turn txis.t |=(t=txi (input-weight:bc bipt.hdkey.t))) + add + %+ roll + (turn txos.t |=(t=txo (output-weight:bc (get-bipt:adr:bc address.t)))) + add + ++ tx-data + |^ + ^- data:tx:bc + :* (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 + :- (to-script-pubkey:adr:bc address.txo) + value.txo + -- + :: + ++ get-txid + ^- txid + (get-id:txu:bc tx-data) + :: + ++ get-rawtx + (basic-encode:txu:bc tx-data) + :: +add-output: append output (usually change) to txos + :: + ++ add-output + |= =txo + ^- txbu + :: todo update vbytes + t(txos (snoc [txos.t] txo)) + :: +to-psbt: returns a based 64 PSBT if + :: - all inputs have an associated rawtx + :: + ++ to-psbt + ^- (unit base64:psbt:bc) + =/ ins=(list in:psbt:bc) + %+ murn txis.t + |= =txi + ?~ rawtx.txi ~ + `[utxo.txi u.rawtx.txi hdkey.txi] + ?: (lth (lent ins) (lent txis.t)) + ~ + =/ outs=(list out:psbt:bc) + %+ turn txos.t + |=(=txo [address.txo hk.txo]) + `(encode:pbt:bc %.y get-rawtx get-txid ins outs) + -- +:: wad: door for processing walts (wallets) +:: parameterized on a walt and it's chyg account +:: +++ wad + |_ [w=walt =chyg] + ++ pubkey + |= =idx:bc + ^- hexb:bc + =/ pk=@ux + %- compress-point:ecc + pub:(derive-public:(~(derive-public bip32 wamp.w) chyg) idx) + [(met 3 pk) pk] + :: + ++ hdkey + |= =idx:bc + ^- hdkey:bc + [fprint.w (~(pubkey wad w chyg) idx) network.w bipt.w chyg idx] + :: + ++ mk-address + |= =idx:bc + ^- address:bc + (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 + ^- (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 + ^- (trel address:bc idx:bc walt) + =/ addr (mk-address nixt-idx) + :* addr + nixt-idx + %+ update-address addr + [%.n chyg nixt-idx *(set utxo:bc)] + == + :: +update-address + :: - insert a new address + :: - if it's used, move "nixt" to the next free address + :: - watch address + :: + ++ update-address + |= [a=address:bc =addi] + ^- walt + ?> =(chyg chyg.addi) + ?> =(a (mk-address idx.addi)) + =? w ?&(used.addi (is-nixt addi)) + bump-nixt + w(wach (~(put by wach.w) a addi)) + :: + ++ is-nixt + |= =addi ^- ? + ?: ?=(%0 chyg.addi) + =(idx.addi p.nixt.w) + =(idx.addi q.nixt.w) + ++ nixt-idx + ?:(?=(%0 chyg) p.nixt.w q.nixt.w) + :: +bump-nixt: return wallet with bumped nixt + :: - find next unused address + :: - watches that address + :: - crashes if max-index is passed + :: + ++ bump-nixt + |^ ^- walt + =/ new-idx=idx:bc +(nixt-idx) + |- ?> (lte new-idx max-index) + =+ addr=(mk-address new-idx) + =/ =addi + %+ ~(gut by wach.w) addr + [%.n chyg new-idx *(set utxo:bc)] + ?. used.addi + %= w + nixt (set-nixt new-idx) + wach (~(put by wach.w) addr addi) + == + $(new-idx +(new-idx)) + :: + ++ set-nixt + |= =idx:bc ^- nixt + ?:(?=(%0 chyg) [idx q.nixt.w] [p.nixt.w idx]) + -- + -- +:: sut: select utxos +:: +++ sut +|_ [w=walt eny=@uvJ last-block=@ud payee=(unit ship) =feyb txos=(list txo)] + ++ dust-sats 3 + ++ dust-threshold + |= output-bipt=bipt:bc + ^- vbytes + (mul dust-sats (input-weight:bc output-bipt)) + :: + ++ target-value + ^- sats + %+ roll (turn txos |=(=txo value.txo)) + |=([a=sats b=sats] (add a b)) + :: + ++ base-weight + ^- vbytes + %+ add overhead-weight:bc + %+ roll + %+ turn txos + |=(=txo (output-weight:bc (get-bipt:adr:bc address.txo))) + add + :: + ++ total-vbytes + |= selected=(list insel) + ^- vbytes + %+ add base-weight + (mul (input-weight:bc bipt.w) (lent selected)) + :: value of an input after fee + :: 0 if net is <= 0 + :: + ++ net-value + |= val=sats + ^- sats + =/ cost (mul (input-weight:bc bipt.w) feyb) + ?: (lte val cost) 0 + (sub val cost) + :: + :: +spendable: whether utxo has enough confs to spend + :: + ++ spendable + |= =utxo:bc ^- ? + (gte (num-confs last-block utxo) confs.w) + :: +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=(unit txbu) select-utxos + ?~ tb [~ ~] + =+ excess=~(fee txb u.tb) :: (inputs - outputs) + =/ new-fee=sats :: cost of this tx + one more output + (mul feyb (add (output-weight:bc bipt.w) vbytes.u.tb)) + ?. (gth excess new-fee) + [tb ~] + ?. (gth (sub excess new-fee) (dust-threshold bipt.w)) + [tb ~] + :- tb + `(sub excess new-fee) + :: Uses naive random selection. Should switch to branch-and-bound later. + :: + ++ select-utxos + |^ ^- (unit txbu) + ?. %+ levy txos + |= =txo + %+ gth value.txo + (dust-threshold (get-bipt:adr:bc address.txo)) + ~|("One or more suggested outputs is dust." !!) + =/ is=(unit (list insel)) + %- single-random-draw + %- zing + (turn ~(val by wach.w) to-insels) + ?~ is ~ + `(new-txbu w payee (total-vbytes u.is) u.is txos) + :: + ++ to-insels + |= =addi + ^- (list insel) + %+ turn ~(tap in utxos.addi) + |=(=utxo:bc [utxo chyg.addi idx.addi]) + -- + :: single-random-draw + :: randomly choose utxos until target is hit + :: only use an insel if its net-value > 0 + :: + ++ single-random-draw + |= is=(list insel) + ^- (unit (list insel)) + =/ rng ~(. og eny) + =/ target (add target-value (mul feyb base-weight)) :: add base fees to target + =| [select=(list insel) total=sats:bc] + |- + ?: =(~ is) ~ + =^ n rng (rads:rng (lent is)) + =/ i=insel (snag n is) + ?. (spendable utxo.i) + $(is (oust [n 1] is)) + =/ net-val (net-value value.utxo.i) + =? select (gth net-val 0) + [i select] + =/ new-total (add total net-val) + ?: (gte new-total target) `select + %= $ + is (oust [n 1] is) + total new-total + == + :: + -- +:: +:: +:: 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)] + == == +-- diff --git a/pkg/arvo/lib/dice.hoon b/pkg/arvo/lib/dice.hoon new file mode 100644 index 0000000000..b81ac66c51 --- /dev/null +++ b/pkg/arvo/lib/dice.hoon @@ -0,0 +1,73 @@ +:: dice: helper functions for L2 Rollers +:: +/- *dice +/+ naive, *naive-transactions +:: +|% +++ apply-effects + |= [=effects:naive nas=^state:naive own=owners chain-t=@] + ^+ [nas=nas own=own] + %+ roll effects + |= [=diff:naive nas=_nas own=_own] + ^+ [nas own] + ?. ?=([%tx *] diff) [nas own] + =< [nas own] + (apply-raw-tx | raw-tx.diff nas own chain-t) +:: +++ apply-raw-tx + |= [force=? =raw-tx:naive nas=^state:naive own=owners chain-t=@] + ^- [? nas=_nas own=_own] + =+ cache-nas=nas + =/ chain-t=@t (ud-to-ascii:naive chain-t) + ?. (verify-sig-and-nonce:naive verifier chain-t nas raw-tx) + ~& [%verify-sig-and-nonce %failed tx.raw-tx] + [force nas own] + =^ * points.nas + (increment-nonce:naive nas from.tx.raw-tx) + ?~ nex=(receive-tx:naive nas tx.raw-tx) + ~& [%receive-tx %failed] + [force ?:(force nas cache-nas) own] + =* new-nas +.u.nex + =* effects -.u.nex + :+ & + new-nas + (update-ownership effects cache-nas new-nas own) +:: +++ update-ownership + |= $: =effects:naive + cache-nas=^state:naive + nas=^state:naive + =owners + == + ^+ owners + %+ roll effects + |= [=diff:naive owners=_owners] + =, orm:naive + ?. ?=([%point *] diff) owners + =/ old=(unit point:naive) + (get points.cache-nas ship.diff) + =/ new=point:naive + (need (get points.nas ship.diff)) + =* event +>.diff + =; [to=@ux from=@ux] + =? owners !=(from 0x0) + (~(del ju owners) from ship.diff) + ?: =(to 0x0) owners + (~(put ju owners) to ship.diff) + ?+ -.event [0x0 0x0] + %owner + [+.event ?~(old 0x0 address.owner.own.u.old)] + :: + %spawn-proxy + [+.event ?~(old 0x0 address.spawn-proxy.own.u.old)] + :: + %management-proxy + [+.event ?~(old 0x0 address.management-proxy.own.u.old)] + :: + %voting-proxy + [+.event ?~(old 0x0 address.voting-proxy.own.u.old)] + :: + %transfer-proxy + [+.event ?~(old 0x0 address.transfer-proxy.own.u.old)] + == +-- diff --git a/pkg/arvo/lib/dm-hook.hoon b/pkg/arvo/lib/dm-hook.hoon new file mode 100644 index 0000000000..0bb26f22d9 --- /dev/null +++ b/pkg/arvo/lib/dm-hook.hoon @@ -0,0 +1,36 @@ +/- *dm-hook +|% +:: +++ dejs + =, dejs:format + |% + ++ action + |^ + %- of + :~ accept+ship + decline+ship + pendings+ships + screen+bo + == + :: + ++ ship (su ;~(pfix sig fed:ag)) + :: + ++ ships (as ship) + -- + -- +:: +++ enjs + =, enjs:format + |% + :: + ++ action + |= act=^action + %+ frond -.act + ?- -.act + ?(%accept %decline) (ship +.act) + %pendings a+(turn ~(tap in ships.act) ship) + %screen [%b +.act] + == + -- +-- + diff --git a/pkg/arvo/lib/ethereum.hoon b/pkg/arvo/lib/ethereum.hoon index e2ae7a2288..bc85922708 100644 --- a/pkg/arvo/lib/ethereum.hoon +++ b/pkg/arvo/lib/ethereum.hoon @@ -483,6 +483,7 @@ [%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] [%eth-get-transaction-receipt txh=@ux] [%eth-send-raw-transaction dat=@ux] == @@ -717,6 +718,9 @@ :~ (tape (address-to-hex adr.req)) (block-to-json block.req) == + :: + %eth-get-balance + ['eth_getBalance' (tape (address-to-hex adr.req)) ~] :: %eth-get-transaction-by-hash ['eth_getTransactionByHash' (tape (transaction-to-hex txh.req)) ~] @@ -796,6 +800,8 @@ :: ++ parse-eth-get-transaction-count parse-hex-result :: + ++ parse-eth-get-balance parse-hex-result + :: ++ parse-event-logs (ar:dejs:format parse-event-log) :: diff --git a/pkg/arvo/lib/ethio.hoon b/pkg/arvo/lib/ethio.hoon index fb919460fa..46926ecd9e 100644 --- a/pkg/arvo/lib/ethio.hoon +++ b/pkg/arvo/lib/ethio.hoon @@ -108,16 +108,25 @@ ++ parse-one-response |= =json ^- (unit response:rpc) - =/ res=(unit [@t ^json]) + ?. &(?=([%o *] json) (~(has by p.json) 'error')) + =/ res=(unit [@t ^json]) + %. json + =, dejs-soft:format + (ot id+so result+some ~) + ?~ res ~ + `[%result u.res] + ~| parse-one-response=json + =/ error=(unit [id=@t ^json code=@ta mssg=@t]) %. json =, dejs-soft:format - (ot id+so result+some ~) - ?^ res `[%result u.res] - ~| parse-one-response=json - :+ ~ %error %- need - %. json - =, dejs-soft:format - (ot id+so error+(ot code+no message+so ~) ~) + :: A 'result' member is present in the error + :: response when using ganache, even though + :: that goes against the JSON-RPC spec + :: + (ot id+so result+some error+(ot code+no message+so ~) ~) + ?~ error ~ + =* err u.error + `[%error id.err code.err mssg.err] -- :: :: +read-contract: calls a read function on a contract, produces result hex @@ -267,4 +276,14 @@ [%eth-get-transaction-count address [%label %latest]] %- pure:m (parse-eth-get-transaction-count:rpc:ethereum json) +:: +++ get-balance + |= [url=@ta =address] + =/ m (strand:strandio ,@ud) + ^- form:m + ;< =json bind:m + %^ request-rpc url `'balance' + [%eth-get-balance address] + %- pure:m + (parse-eth-get-balance:rpc:ethereum json) -- diff --git a/pkg/arvo/lib/graph-store.hoon b/pkg/arvo/lib/graph-store.hoon index 1dd12e82a3..5c64c9350f 100644 --- a/pkg/arvo/lib/graph-store.hoon +++ b/pkg/arvo/lib/graph-store.hoon @@ -1,105 +1,10 @@ /- sur=graph-store, pos=post -/+ res=resource +/+ res=resource, migrate =< [sur .] =< [pos .] =, sur =, pos |% -:: -++ update-log-to-one - |= =update-log:zero - ^- ^update-log - %+ gas:orm-log *^update-log - %+ turn (tap:orm-log:zero update-log) - |= [=time =logged-update:zero] - :- time - :- p.logged-update - (logged-update-to-one q.logged-update) -:: -++ logged-update-to-one - |= upd=logged-update-0:zero - ?+ -.upd upd - %add-graph upd(graph (graph-to-one graph.upd)) - %add-nodes upd(nodes (~(run by nodes.upd) node-to-one)) - == -:: -++ node-to-one - |= =node:zero - (node:(upgrade ,post:zero ,post) node post-to-one) -:: -++ graph-to-one - |= =graph:zero - (graph:(upgrade ,post:zero ,post) graph post-to-one) -:: -++ marked-graph-to-one - |= [=graph:zero m=(unit mark)] - [(graph-to-one graph) m] -:: -++ post-to-one - |= p=post:zero - ^- post - p(contents (contents-to-one contents.p)) -:: -++ contents-to-one - |= cs=(list content:zero) - ^- (list content) - %+ murn cs - |= =content:zero - ^- (unit ^content) - ?: ?=(%reference -.content) ~ - `content -:: -++ upgrade - |* [in-pst=mold out-pst=mold] - => - |% - ++ in-orm - ((ordered-map atom in-node) gth) - +$ in-node - [post=in-pst children=in-internal-graph] - +$ in-graph - ((mop atom in-node) gth) - +$ in-internal-graph - $~ [%empty ~] - $% [%graph p=in-graph] - [%empty ~] - == - :: - ++ out-orm - ((ordered-map atom out-node) gth) - +$ out-node - [post=out-pst children=out-internal-graph] - +$ out-graph - ((mop atom out-node) gth) - +$ out-internal-graph - $~ [%empty ~] - $% [%graph p=out-graph] - [%empty ~] - == - -- - |% - :: - ++ graph - |= $: gra=in-graph - fn=$-(in-pst out-pst) - == - ^- out-graph - %+ gas:out-orm *out-graph - ^- (list [atom out-node]) - %+ turn (tap:in-orm gra) - |= [a=atom n=in-node] - ^- [atom out-node] - [a (node n fn)] - :: - ++ node - |= [nod=in-node fn=$-(in-pst out-pst)] - ^- out-node - :- (fn post.nod) - ^- out-internal-graph - ?: ?=(%empty -.children.nod) - [%empty ~] - [%graph (graph p.children.nod fn)] - -- :: NOTE: move these functions to zuse ++ nu :: parse number as hex |= jon=json @@ -145,18 +50,17 @@ == :: ++ index - |= i=^index + |= ind=^index ^- json - ?: =(~ i) s+'/' - =/ j=^tape "" - |- - ?~ i [%s (crip j)] - =/ k=json (numb i.i) - ?> ?=(%n -.k) - %_ $ - i t.i - j (weld j (weld "/" (trip +.k))) - == + :- %s + ?: =(~ ind) + '/' + %+ roll ind + |= [cur=@ acc=@t] + ^- @t + =/ num (numb cur) + ?> ?=(%n -.num) + (rap 3 acc '/' p.num ~) :: ++ uid |= u=^uid @@ -212,6 +116,14 @@ s+(enjs-path:res grp) -- :: + ++ maybe-post + |= mp=^maybe-post + ^- json + ?- -.mp + %| s+(scot %ux p.mp) + %& (post p.mp) + == + :: ++ post |= p=^post ^- json @@ -252,8 +164,8 @@ [%nodes (nodes nodes.upd)] == :: - %remove-nodes - :- %remove-nodes + %remove-posts + :- %remove-posts %- pairs :~ [%resource (enjs:res resource.upd)] [%indices (indices indices.upd)] @@ -277,14 +189,14 @@ :- %add-tag %- pairs :~ [%term s+term.upd] - [%resource (enjs:res resource.upd)] + [%uid (uid uid.upd)] == :: %remove-tag :- %remove-tag %- pairs :~ [%term s+term.upd] - [%resource (enjs:res resource.upd)] + [%uid (uid uid.upd)] == :: %archive-graph @@ -306,9 +218,9 @@ :- %tag-queries %- pairs %+ turn ~(tap by tag-queries.upd) - |= [=term =resources] + |= [=term uids=(set ^uid)] ^- [cord json] - [term [%a (turn ~(tap in resources) enjs:res)]] + [term [%a (turn ~(tap in uids) uid)]] == :: ++ graph @@ -328,7 +240,7 @@ |= n=^node ^- json %- pairs - :~ [%post (post post.n)] + :~ [%post (maybe-post post.n)] :- %children ?- -.children.n %empty ~ @@ -336,7 +248,6 @@ == == :: - :: ++ nodes |= m=(map ^index ^node) ^- json @@ -370,7 +281,7 @@ ++ decode %- of :~ [%add-nodes add-nodes] - [%remove-nodes remove-nodes] + [%remove-posts remove-posts] [%add-signatures add-signatures] [%remove-signatures remove-signatures] :: @@ -422,7 +333,7 @@ :: ++ node %- ot - :~ [%post post] + :~ [%post maybe-post] [%children internal-graph] == :: @@ -433,6 +344,15 @@ [%empty ~] [%graph (graph jon)] :: + ++ maybe-post + |= jon=json + ^- ^maybe-post + ?~ jon !! + ?+ -.jon !! + %s [%| (nu jon)] + %o [%& (post jon)] + == + :: ++ post %- ot :~ [%author (su ;~(pfix sig fed:ag))] @@ -489,9 +409,8 @@ :~ expression+so output+tang == - :: - ++ remove-nodes + ++ remove-posts %- ot :~ [%resource dejs:res] [%indices (as index)] @@ -527,13 +446,13 @@ ++ add-tag %- ot :~ [%term so] - [%resource dejs:res] + [%uid uid] == :: ++ remove-tag %- ot :~ [%term so] - [%resource dejs:res] + [%uid uid] == :: ++ keys @@ -568,4 +487,391 @@ *signatures == -- +:: +++ upgrade + |% + :: + :: +two + :: + ++ marked-graph-to-two + |= [=graph:one m=(unit mark)] + [(graph-to-two graph) m] + :: + ++ graph-to-two + |= =graph:one + (graph:(upgrade ,post:one ,maybe-post) graph post-to-two) + :: + ++ post-to-two + |= p=post:one + ^- maybe-post + [%& p] + :: + :: + :: +one + :: + ++ update-log-to-one + |= =update-log:zero + ^- update-log:one + %+ gas:orm-log:one *update-log:one + %+ turn (tap:orm-log:zero update-log) + |= [=time =logged-update:zero] + ^- [^time logged-update:one] + :- time + :- p.logged-update + (logged-update-to-one q.logged-update) + :: + ++ logged-update-to-one + |= upd=logged-update-0:zero + ^- logged-action:one + ?+ -.upd upd + %add-graph upd(graph (graph-to-one graph.upd)) + %add-nodes upd(nodes (~(run by nodes.upd) node-to-one)) + == + :: + ++ node-to-one + |= =node:zero + (node:(upgrade ,post:zero ,post) node post-to-one) + :: + ++ graph-to-one + |= =graph:zero + (graph:(upgrade ,post:zero ,post) graph post-to-one) + :: + ++ marked-graph-to-one + |= [=graph:zero m=(unit mark)] + [(graph-to-one graph) m] + :: + ++ post-to-one + |= p=post:zero + ^- post + p(contents (contents-to-one contents.p)) + :: + ++ contents-to-one + |= cs=(list content:zero) + ^- (list content) + %+ murn cs + |= =content:zero + ^- (unit ^content) + ?: ?=(%reference -.content) ~ + `content + :: + ++ upgrade + |* [in-pst=mold out-pst=mold] + => + |% + ++ in-orm + ((ordered-map atom in-node) gth) + +$ in-node + [post=in-pst children=in-internal-graph] + +$ in-graph + ((mop atom in-node) gth) + +$ in-internal-graph + $~ [%empty ~] + $% [%graph p=in-graph] + [%empty ~] + == + :: + ++ out-orm + ((ordered-map atom out-node) gth) + +$ out-node + [post=out-pst children=out-internal-graph] + +$ out-graph + ((mop atom out-node) gth) + +$ out-internal-graph + $~ [%empty ~] + $% [%graph p=out-graph] + [%empty ~] + == + -- + |% + :: + ++ graph + |= $: gra=in-graph + fn=$-(in-pst out-pst) + == + ^- out-graph + %+ gas:out-orm *out-graph + ^- (list [atom out-node]) + %+ turn (tap:in-orm gra) + |= [a=atom n=in-node] + ^- [atom out-node] + [a (node n fn)] + :: + ++ node + |= [nod=in-node fn=$-(in-pst out-pst)] + ^- out-node + :- (fn post.nod) + ^- out-internal-graph + ?: ?=(%empty -.children.nod) + [%empty ~] + [%graph (graph p.children.nod fn)] + -- + :: + ++ zero-load + :: =* infinitely recurses + =, store=zero + =, orm=orm:zero + =, orm-log=orm-log:zero + |% + ++ change-revision-graph + |= [=graph:store q=(unit mark)] + ^- [graph:store (unit mark)] + |^ + :_ q + ?+ q graph + [~ %graph-validator-link] convert-links + [~ %graph-validator-publish] convert-publish + == + :: + ++ convert-links + %+ gas:orm *graph:store + %+ turn (tap:orm graph) + |= [=atom =node:store] + ^- [^atom node:store] + :: top-level + :: + :+ atom post.node + ?: ?=(%empty -.children.node) + [%empty ~] + :- %graph + %+ gas:orm *graph:store + %+ turn (tap:orm p.children.node) + |= [=^atom =node:store] + ^- [^^atom node:store] + :: existing comments get turned into containers for revisions + :: + :^ atom + post.node(contents ~, hash ~) + %graph + %+ gas:orm *graph:store + :_ ~ :- %0 + :_ [%empty ~] + post.node(index (snoc index.post.node atom), hash ~) + :: + ++ convert-publish + %+ gas:orm *graph:store + %+ turn (tap:orm graph) + |= [=atom =node:store] + ^- [^atom node:store] + :: top-level + :: + :+ atom post.node + ?: ?=(%empty -.children.node) + [%empty ~] + :- %graph + %+ gas:orm *graph:store + %+ turn (tap:orm p.children.node) + |= [=^atom =node:store] + ^- [^^atom node:store] + :: existing container for publish note revisions + :: + ?+ atom !! + %1 [atom node] + %2 + :+ atom post.node + ?: ?=(%empty -.children.node) + [%empty ~] + :- %graph + %+ gas:orm *graph:store + %+ turn (tap:orm p.children.node) + |= [=^^atom =node:store] + ^- [^^^atom node:store] + :+ atom post.node(contents ~, hash ~) + :- %graph + %+ gas:orm *graph:store + :_ ~ :- %1 + :_ [%empty ~] + post.node(index (snoc index.post.node atom), hash ~) + == + -- + :: + ++ maybe-unix-to-da + |= =atom + ^- @ + :: (bex 127) is roughly 226AD + ?. (lte atom (bex 127)) + atom + (add ~1970.1.1 (div (mul ~s1 atom) 1.000)) + :: + ++ convert-unix-timestamped-node + |= =node:store + ^- node:store + =. index.post.node + (convert-unix-timestamped-index index.post.node) + ?. ?=(%graph -.children.node) + node + :+ post.node + %graph + (convert-unix-timestamped-graph p.children.node) + :: + ++ convert-unix-timestamped-index + |= =index:store + (turn index maybe-unix-to-da) + :: + ++ convert-unix-timestamped-graph + |= =graph:store + %+ gas:orm *graph:store + %+ turn + (tap:orm graph) + |= [=atom =node:store] + ^- [^atom node:store] + :- (maybe-unix-to-da atom) + (convert-unix-timestamped-node node) + -- + -- +++ import + |= [arc=* our=ship] + ^- (quip card:agent:gall [%5 network]) + |^ + =/ sty [%5 (remake-network ;;(tree-network +.arc))] + :_ sty + %+ turn ~(tap by graphs.sty) + |= [rid=resource =marked-graph] + ^- card:agent:gall + ?: =(our entity.rid) + =/ =cage [%push-hook-action !>([%add rid])] + [%pass / %agent [our %graph-push-hook] %poke cage] + (try-rejoin rid 0) + :: + +$ tree-network + $: graphs=tree-graphs + tag-queries=(tree [term (tree uid)]) + update-logs=tree-update-logs + archive=tree-graphs + ~ + == + +$ tree-graphs (tree [resource tree-marked-graph]) + +$ tree-marked-graph [p=tree-graph q=(unit ^mark)] + +$ tree-graph (tree [atom tree-node]) + +$ tree-node [post=tree-maybe-post children=tree-internal-graph] + +$ tree-internal-graph + $~ [%empty ~] + $% [%graph p=tree-graph] + [%empty ~] + == + +$ tree-update-logs (tree [resource tree-update-log]) + +$ tree-update-log (tree [time tree-logged-update]) + +$ tree-logged-update + $: p=time + $= q + $% [%add-graph =resource =tree-graph mark=(unit ^mark) ow=?] + [%add-nodes =resource nodes=(tree [index tree-node])] + [%remove-posts =resource indices=(tree index)] + [%add-signatures =uid signatures=tree-signatures] + [%remove-signatures =uid signatures=tree-signatures] + == + == + +$ tree-signatures (tree signature) + +$ tree-maybe-post (each tree-post hash) + +$ tree-post + $: author=ship + =index + time-sent=time + contents=(list content) + hash=(unit hash) + signatures=tree-signatures + == + :: + ++ remake-network + |= t=tree-network + ^- network + :* (remake-graphs graphs.t) + (remake-jug:migrate tag-queries.t) + (remake-update-logs update-logs.t) + (remake-graphs archive.t) + ~ + == + :: + ++ remake-graphs + |= t=tree-graphs + ^- graphs + %- remake-map:migrate + (~(run by t) remake-marked-graph) + :: + ++ remake-marked-graph + |= t=tree-marked-graph + ^- marked-graph + [(remake-graph p.t) q.t] + :: + ++ remake-graph + |= t=tree-graph + ^- graph + %+ gas:orm *graph + %+ turn ~(tap by t) + |= [a=atom tn=tree-node] + ^- [atom node] + [a (remake-node tn)] + :: + ++ remake-internal-graph + |= t=tree-internal-graph + ^- internal-graph + ?: ?=(%empty -.t) + [%empty ~] + [%graph (remake-graph p.t)] + :: + ++ remake-node + |= t=tree-node + ^- node + :- (remake-post post.t) + (remake-internal-graph children.t) + :: + ++ remake-update-logs + |= t=tree-update-logs + ^- update-logs + %- remake-map:migrate + (~(run by t) remake-update-log) + :: + ++ remake-update-log + |= t=tree-update-log + ^- update-log + =/ ulm ((ordered-map time logged-update) gth) + %+ gas:ulm *update-log + %+ turn ~(tap by t) + |= [=time tlu=tree-logged-update] + ^- [^time logged-update] + [time (remake-logged-update tlu)] + :: + ++ remake-logged-update + |= t=tree-logged-update + ^- logged-update + :- p.t + ?- -.q.t + %add-graph + :* %add-graph + resource.q.t + (remake-graph tree-graph.q.t) + mark.q.t + ow.q.t + == + :: + %add-nodes + :- %add-nodes + :- resource.q.t + %- remake-map:migrate + (~(run by nodes.q.t) remake-node) + :: + %remove-posts + [%remove-posts resource.q.t (remake-set:migrate indices.q.t)] + :: + %add-signatures + [%add-signatures uid.q.t (remake-set:migrate signatures.q.t)] + :: + %remove-signatures + [%remove-signatures uid.q.t (remake-set:migrate signatures.q.t)] + == + :: + ++ remake-post + |= t=tree-maybe-post + ^- maybe-post + ?- -.t + %| t + %& t(signatures.p (remake-set:migrate signatures.p.t)) + == + :: + ++ try-rejoin + |= [rid=resource nack-count=@] + ^- card:agent:gall + =/ res-path (en-path:res rid) + =/ wire [%try-rejoin (scot %ud nack-count) res-path] + [%pass wire %agent [entity.rid %graph-push-hook] %watch resource+res-path] + -- -- diff --git a/pkg/arvo/lib/graph.hoon b/pkg/arvo/lib/graph.hoon index 8493ad064a..e578eee7b1 100644 --- a/pkg/arvo/lib/graph.hoon +++ b/pkg/arvo/lib/graph.hoon @@ -1,6 +1,14 @@ /- *resource /+ store=graph-store |_ =bowl:gall +++ cg + |% + ++ update + |= =update:store + ^- cage + [%graph-update-2 !>(update)] + -- +:: ++ scry-for |* [=mold =path] .^ mold @@ -19,7 +27,7 @@ %add-graph ~[resource.q.update] %remove-graph ~[resource.q.update] %add-nodes ~[resource.q.update] - %remove-nodes ~[resource.q.update] + %remove-posts ~[resource.q.update] %add-signatures ~[resource.uid.q.update] %remove-signatures ~[resource.uid.q.update] %archive-graph ~[resource.q.update] @@ -76,6 +84,7 @@ ++ get-graph |= res=resource ^- update:store + =- -(p *time) %+ scry-for update:store /graph/(scot %p entity.res)/[name.res] :: diff --git a/pkg/arvo/lib/group.hoon b/pkg/arvo/lib/group.hoon index 436bffdef5..1da653b7ec 100644 --- a/pkg/arvo/lib/group.hoon +++ b/pkg/arvo/lib/group.hoon @@ -34,10 +34,12 @@ :: ++ scry-group |= rid=resource + ^- (unit group) %+ scry-for ,(unit group) `path`groups+(en-path:resource rid) :: ++ scry-groups + ^- (set resource) .^ ,(set resource) %gy (scot %p our.bowl) @@ -48,6 +50,7 @@ :: ++ members |= rid=resource + ^- (set ship) =; =group members.group (fall (scry-group rid) *group) @@ -75,7 +78,12 @@ =/ grp=(unit group) (scry-group rid) ?~ grp ~ - =* group u.grp + (role-for-ship-with-group u.grp rid ship) +:: +++ role-for-ship-with-group + |= [grp=group rid=resource =ship] + ^- (unit (unit role-tag)) + =* group grp =* policy policy.group =* tags tags.group =/ admins=(set ^ship) @@ -96,6 +104,7 @@ :: ++ can-join |= [rid=resource =ship] + ^- ? %+ scry-for ,? ^- path :- %groups @@ -106,11 +115,17 @@ ^- (set ship) =/ grp=(unit group) (scry-group rid) - ?~ grp ~ - (~(get ju tags.u.grp) tag) + ?~ grp ~ + (get-tagged-ships-with-group u.grp rid tag) +:: +++ get-tagged-ships-with-group + |= [grp=group rid=resource =tag] + ^- (set ship) + (~(get ju tags.grp) tag) :: ++ is-managed |= rid=resource + ^- ? =/ group=(unit group) (scry-group rid) ?~ group %.n diff --git a/pkg/arvo/lib/hark/store.hoon b/pkg/arvo/lib/hark/store.hoon index 99b2f38df6..6c71819803 100644 --- a/pkg/arvo/lib/hark/store.hoon +++ b/pkg/arvo/lib/hark/store.hoon @@ -4,6 +4,247 @@ =< [. sur] =, sur |% +++ upgrade + |% + ++ to-three + =* two state-two + =* three state-three + |% + ++ index + |= =index:two + ^- (unit index:three) + `index + ++ contents + |= =contents:two + ^- (unit contents:three) + ?. ?=(%group -.contents) + `contents + =- ?: =(~ -) ~ + `[%group -] + %+ murn list.contents + |= =group-contents:two + ^- (unit group-contents:three) + ?: ?=(?(%add %remove) -.group-contents) + ~ + `group-contents + :: + ++ stats-index + |= =stats-index:two + ^- (unit stats-index:three) + `stats-index + :: + ++ notifications + upg-notifications:upg + :: + ++ upg + %. [index stats-index contents] + %: upgrade + index:two + stats-index:two + contents:two + index:three + stats-index:three + contents:three + == + -- + :: + ++ to-four + =* three state-three + =* four state-four + |% + ++ index + |= =index:three + ^- (unit index:four) + `index + ++ contents + |= =contents:three + ^- (unit contents:four) + ?. ?=(%graph -.contents) + `contents + `[%graph (turn list.contents post-to-one:upgrade:graph-store)] + :: + ++ unreads-each + upg-unreads-each:upg + :: + ++ notifications + upg-notifications:upg + :: + ++ stats-index + |= =stats-index:three + ^- (unit stats-index:four) + `stats-index + :: + ++ upg + %. [index stats-index contents] + %: upgrade + index:three + stats-index:three + contents:three + index:four + stats-index:four + contents:four + == + -- + :: + ++ to-five + =* four state-four + =* five sur + |% + ++ mark + |= module=@t + ^- (unit @t) + ?+ module ~ + %chat `%graph-validator-chat + %publish `%graph-validator-publish + %link `%graph-validator-link + == + ++ index + |= =index:four + ^- (unit index:five) + ?: ?=(%group -.index) + `index + =* i index + `[%graph graph.i (mark module.i) description.i index.i] + :: + ++ contents + |= =contents:four + ^- (unit contents:five) + `contents + :: + ++ unreads-each + upg-unreads-each:upg + :: + ++ stats-index + |= =stats-index:four + ^- (unit stats-index:five) + `stats-index + :: + ++ upg + %. [index stats-index contents] + %: upgrade + index:four + stats-index:four + contents:four + index:five + stats-index:five + contents:five + == + + ++ notifications + upg-notifications:upg + -- + :: + ++ upgrade + |* $: :: input molds + in-index=mold + in-stats-index=mold + in-contents=mold + :: output molds + out-index=mold + out-stats-index=mold + out-contents=mold + == + => . => + |% + :: + ++ in + |% + :: + +$ index in-index + +$ stats-index in-stats-index + +$ contents in-contents + +$ unreads-each (jug stats-index index) + +$ timebox (map index notification) + +$ notification + [date=@da read=? =contents] + ++ orm + ((ordered-map time timebox) gth) + +$ notifications + ((mop time timebox) gth) + -- + ++ out + |% + :: + :: + +$ index out-index + +$ stats-index out-stats-index + +$ contents out-contents + +$ timebox (map out-index notification) + +$ unreads-each (jug stats-index index) + +$ notification + [date=@da read=? contents=out-contents] + +$ notifications + ((mop time timebox) gth) + ++ orm + ((ordered-map time timebox) gth) + -- + -- + |= $: fun-index=$-(index:in (unit index:out)) + fun-stats-index=$-(stats-index:in (unit stats-index:out)) + fun-contents=$-(contents:in (unit contents:out)) + == + |% + :: + ++ upg-unreads-each + |= =unreads-each:in + ^- unreads-each:out + %- ~(gas by *unreads-each:out) + %+ murn ~(tap by unreads-each) + |= [=stats-index:in indices=(set index:in)] + ^- (unit [stats-index:out (set index:out)]) + =/ new-stats + (fun-stats-index stats-index) + ?~ new-stats ~ + =/ new-indices + (upg-indices indices) + ?: =(0 ~(wyt ^in new-indices)) ~ + `[u.new-stats new-indices] + :: + ++ upg-indices + |= indices=(set index:in) + ^- (set index:out) + %- ~(gas ^in *(set index:out)) + (murn ~(tap ^in indices) fun-index) + :: + ++ upg-notifications + |= =notifications:in + ^- notifications:out + %+ gas:orm:out *notifications:out + ^- (list [@da timebox:out]) + %+ murn (tap:orm:in notifications) + |= [time=@da =timebox:in] + ^- (unit [@da =timebox:out]) + =/ new-timebox=timebox:out + (upg-timebox timebox) + ?: =(0 ~(wyt by timebox)) + ~ + `[time new-timebox] + :: + ++ upg-timebox + |= =timebox:in + ^- timebox:out + %- ~(gas by *timebox:out) + %+ murn ~(tap by timebox) + |= [=index:in =notification:in] + ^- (unit [index:out notification:out]) + =/ new-index + (fun-index index) + ?~ new-index ~ + =/ new-notification + (upg-notification notification) + ?~ new-notification ~ + `[u.new-index u.new-notification] + :: + ++ upg-notification + |= n=notification:in + ^- (unit notification:out) + =/ new-contents + (fun-contents contents.n) + ?~ new-contents ~ + `[date.n read.n u.new-contents] + -- + -- + ++ dejs =, dejs:format |% @@ -21,9 +262,8 @@ :: ++ graph-index %- ot - :~ group+dejs-path:resource - graph+dejs-path:resource - module+so + :~ graph+dejs-path:resource + mark+(mu so) description+so index+(su ;~(pfix fas (more fas dem))) == @@ -47,9 +287,9 @@ `@da`(rash p.jon dem:ag) :: ++ notif-ref - ^- $-(json [@da ^index]) + ^- $-(json [(unit @da) ^index]) %- ot - :~ time+sd + :~ time+(mu sd) index+index == ++ graph-store-index @@ -70,8 +310,7 @@ %- of :~ seen+ul archive+notif-ref - unread-note+notif-ref - read-note+notif-ref + read-note+index add-note+add set-dnd+bo read-count+stats-index @@ -100,12 +339,21 @@ %unread-count (unread-count +.upd) %remove-graph s+(enjs-path:resource +.upd) %seen-index (seen-index +.upd) - %unreads (unreads +.upd) + %unreads (unreads +.upd) + %read-note (index +.upd) + %note-read (note-read +.upd) :: - ?(%archive %read-note %unread-note) + %archive (notif-ref +.upd) == :: + ++ note-read + |= [tim=@da idx=^index] + %- pairs + :~ time+s+(scot %ud tim) + index+(index idx) + == + :: ++ stats-index |= s=^stats-index %+ frond -.s @@ -151,23 +399,21 @@ ^- json %- pairs :~ unreads+(unread unreads.s) - notifications+a+(turn ~(tap in notifications.s) notif-ref) last+(time last-seen.s) == ++ added - |= [tim=@da idx=^index not=^notification] + |= [idx=^index not=^notification] ^- json %- pairs - :~ time+s+(scot %ud tim) - index+(index idx) + :~ index+(index idx) notification+(notification not) == :: ++ notif-ref - |= [tim=@da idx=^index] + |= [tim=(unit @da) idx=^index] ^- json %- pairs - :~ time+s+(scot %ud tim) + :~ [%time ?~(tim ~ s+(scot %ud u.tim))] index+(index idx) == ++ seen-index @@ -193,17 +439,15 @@ == :: ++ graph-index - |= $: group=resource - graph=resource - module=@t + |= $: graph=resource + mark=(unit mark) description=@t idx=index:graph-store == ^- json %- pairs - :~ group+s+(enjs-path:resource group) - graph+s+(enjs-path:resource graph) - module+s+module + :~ graph+s+(enjs-path:resource graph) + mark+s+(fall mark '') description+s+description index+(index:enjs:graph-store idx) == @@ -222,7 +466,6 @@ ^- json %- pairs :~ time+(time date) - read+b+read contents+(^contents contents) == :: @@ -259,11 +502,10 @@ == :: ++ timebox - |= [tim=@da arch=? l=(list [^index ^notification])] + |= [tim=(unit @da) l=(list [^index ^notification])] ^- json %- pairs - :~ time+s+(scot %ud tim) - archive+b+arch + :~ time+`json`?~(tim ~ s+(scot %ud u.tim)) :- %notifications ^- json :- %a diff --git a/pkg/arvo/lib/hood/drum.hoon b/pkg/arvo/lib/hood/drum.hoon index d89d81fb01..0023ed45e4 100644 --- a/pkg/arvo/lib/hood/drum.hoon +++ b/pkg/arvo/lib/hood/drum.hoon @@ -108,6 +108,7 @@ %metadata-pull-hook %group-view %settings-store + %dm-hook == :: ++ deft-fish :: default connects @@ -258,6 +259,8 @@ => (se-born | %home %contact-pull-hook) => (se-born | %home %settings-store) (se-born | %home %group-view) + =? ..on-load (lte hood-version %13) + (se-born | %home %dm-hook) ..on-load :: ++ reap-phat :: ack connect diff --git a/pkg/arvo/lib/hood/kiln.hoon b/pkg/arvo/lib/hood/kiln.hoon index 0e7f719eb2..79b07a668a 100644 --- a/pkg/arvo/lib/hood/kiln.hoon +++ b/pkg/arvo/lib/hood/kiln.hoon @@ -55,6 +55,12 @@ cas=case :: gim=?(%auto germ) :: == ++$ kiln-fuse + $@ ~ + $: syd=desk + bas=beak + con=(list [beak germ]) + == -- |= [bowl:gall state] ?> =(src our) @@ -381,6 +387,11 @@ ?~ +< abet abet:abet:(merge:(work syd) ali sud cas gim) :: +++ poke-fuse + |= k=kiln-fuse + ?~ k abet + abet:(emit [%pass /kiln/fuse/[syd.k] %arvo %c [%fuse syd.k bas.k con.k]]) +:: ++ poke-cancel |= a=@tas abet:(emit %pass /cancel %arvo %c [%drop a]) @@ -430,6 +441,7 @@ %kiln-info =;(f (f !<(_+<.f vase)) poke-info) %kiln-label =;(f (f !<(_+<.f vase)) poke-label) %kiln-merge =;(f (f !<(_+<.f vase)) poke-merge) + %kiln-fuse =;(f (f !<(_+<.f vase)) poke-fuse) %kiln-mount =;(f (f !<(_+<.f vase)) poke-mount) %kiln-ota =;(f (f !<(_+<.f vase)) poke:update) %kiln-ota-info =;(f (f !<(_+<.f vase)) poke-ota-info) @@ -489,6 +501,8 @@ ++ take |=(way=wire ?>(?=([@ ~] way) (work i.way))) :: general handler ++ take-mere :: |= [way=wire are=(each (set path) (pair term tang))] + ?. ?=([@ ~] way) + abet abet:abet:(mere:(take way) are) :: ++ take-coup-fancy :: diff --git a/pkg/arvo/lib/json/rpc.hoon b/pkg/arvo/lib/json/rpc.hoon index 2cc0c7ff04..110df6789a 100644 --- a/pkg/arvo/lib/json/rpc.hoon +++ b/pkg/arvo/lib/json/rpc.hoon @@ -17,7 +17,7 @@ |= request ^- json %- pairs:enjs:format - :~ jsonrpc+s+'0.2' + :~ jsonrpc+s+'2.0' id+s+id method+s+method :: @@ -25,7 +25,9 @@ ^- json ?- -.params %list [%a +.params] - %map [%o +.params] + :: FIXME: support either %map or %object (also in /sur/json/rpc) + :: + %map [%o +.params] %object [%o (~(gas by *(map @t json)) +.params)] == == :: @@ -35,14 +37,16 @@ :: TODO: consider all cases :: ?+ -.response ~|([%unsupported-rpc-response response] !!) + %batch a+(turn bas.response response-to-json) + :: %result :- %o %- molt ^- (list [@t json]) :: FIXME: return 'id' as string, number or NULL - :: - :~ ['jsonrpc' s+'2.0'] - ['id' s+id.response] + :: + :~ ['jsonrpc' s+'2.0'] + ['id' s+id.response] ['result' res.response] == :: @@ -50,39 +54,48 @@ :- %o %- molt ^- (list [@t json]) - :~ ['jsonrpc' s+'2.0'] - ['id' ?~(id.response ~ s+id.response)] + :~ ['jsonrpc' s+'2.0'] + ['id' ?~(id.response ~ s+id.response)] ['code' n+code.response] ['message' s+message.response] == == :: ++ validate-request - |= [body=(unit octs) parse-method=$-(@t term)] - ^- (unit request) + |= body=(unit octs) + ^- (unit batch-request) ?~ body ~ ?~ jon=(de-json:html q.u.body) ~ - :: ignores non-object responses - :: - :: ?. ?=([%o *] json) ~|([%format-not-valid json] !!) - ?. ?=([%o *] u.jon) ~ - %- some - %. u.jon - =, dejs:format - :: TODO: If parsing fails, return a proper error (not 500) - :: + =, dejs-soft:format + =; reparser + ?: ?=([%a *] u.jon) + (bind ((ar reparser) u.jon) (lead %a)) + (bind (reparser u.jon) (lead %o)) %- ot :~ :: FIXME: parse 'id' as string, number or NULL :: ['id' so] ['jsonrpc' (su (jest '2.0'))] - ['method' (cu parse-method so)] + ['method' so] :: :- 'params' |= =json - ^- request-params - ?+ -.json !! - %a [%list ((ar same) json)] - %o [%map ((om same) json)] + ^- (unit request-params) + ?+ -.json ~ + %a `[%list ((ar:dejs:format same) json)] + %o `[%map ((om:dejs:format same) json)] == == +:: +++ error + |_ id=@t + :: https://www.jsonrpc.org/specification#error_object + :: + ++ parse [%error id '-32700' 'Failed to parsed'] + ++ request [%error id '-32600' 'Invalid Request'] + ++ method [%error id '-32601' 'Method not found'] + ++ params [%error id '-32602' 'Invalid params'] + ++ internal [%error id '-32603' 'Internal error'] + ++ not-found [%error id '-32000' 'Resource not found'] + ++ todo [%error id '-32001' 'Method not implemented'] + -- -- diff --git a/pkg/arvo/lib/language-server/parser.hoon b/pkg/arvo/lib/language-server/parser.hoon index d4f5002fb6..cf57790926 100644 --- a/pkg/arvo/lib/language-server/parser.hoon +++ b/pkg/arvo/lib/language-server/parser.hoon @@ -21,7 +21,10 @@ (most ;~(plug com gaw) taut-rule) :: %+ rune tis - ;~(plug sym ;~(pfix gap fas (more fas urs:ab))) + ;~(plug sym ;~(pfix gap stap)) + :: + %+ rune sig + ;~((glue gap) sym wyde:vast stap) :: %+ rune cen ;~(plug sym ;~(pfix gap ;~(pfix cen sym))) @@ -37,7 +40,7 @@ ;~ (glue gap) sym ;~(pfix cen sym) - ;~(pfix fas (more fas urs:ab)) + stap == :: %+ stag %tssg diff --git a/pkg/arvo/lib/launch-store.hoon b/pkg/arvo/lib/launch-store.hoon index 36146faa08..b22005a544 100644 --- a/pkg/arvo/lib/launch-store.hoon +++ b/pkg/arvo/lib/launch-store.hoon @@ -71,7 +71,12 @@ [%'linkedUrl' s+linked-url.type] == :: - %custom (frond %custom ~) + %custom + %+ frond %custom + %- pairs + :~ [%'linkedUrl' ?~(linked-url.type ~ s+u.linked-url.type)] + [%'image' ?~(image.type ~ s+u.image.type)] + == == :: ++ terms @@ -105,10 +110,10 @@ [%'isShown' bo] == :: - ++ tile-type + ++ tile-type %- of :~ [%basic basic] - [%custom ul] + [%custom (ot [%'linkedUrl' (mu so)] [%'image' (mu so)] ~)] == :: ++ basic diff --git a/pkg/arvo/lib/metadata-store.hoon b/pkg/arvo/lib/metadata-store.hoon index 23a6878f97..304c988c57 100644 --- a/pkg/arvo/lib/metadata-store.hoon +++ b/pkg/arvo/lib/metadata-store.hoon @@ -23,13 +23,13 @@ %+ turn ~(tap by associations) |= [=md-resource [group=resource =^metadatum]] ^- [cord json] - :- - %- crip - ;: weld - (trip (spat (en-path:resource group))) - (weld "/" (trip app-name.md-resource)) - (trip (spat (en-path:resource resource.md-resource))) - == + :- %: rap 3 + (spat (en-path:resource group)) + '/' + app-name.md-resource + (spat (en-path:resource resource.md-resource)) + ~ + == %- pairs :~ [%group s+(enjs-path:resource group)] [%app-name s+app-name.md-resource] diff --git a/pkg/arvo/lib/metadata.hoon b/pkg/arvo/lib/metadata.hoon index 7b382c5800..a89d131cf8 100644 --- a/pkg/arvo/lib/metadata.hoon +++ b/pkg/arvo/lib/metadata.hoon @@ -53,6 +53,7 @@ :: ++ app-metadata-for-group |= [group=resource =app-name:store] + ^- associations:store =/ =associations:store (metadata-for-group group) %- ~(gas by *associations:store) @@ -62,6 +63,7 @@ :: ++ metadata-for-group |= group=resource + ^- associations:store .^ associations:store %gx (scot %p our.bowl) %metadata-store (scot %da now.bowl) %group (snoc (en-path:resource group) %noun) @@ -69,6 +71,7 @@ :: ++ md-resources-from-group |= group=resource + ^- (set md-resource:store) =- (~(get ju -) group) .^ (jug resource md-resource:store) %gy @@ -80,6 +83,7 @@ :: ++ peek-association |= [app-name=term rid=resource] + ^- (unit association:store) .^ (unit association:store) %gx (scot %p our.bowl) %metadata-store (scot %da now.bowl) %metadata app-name (snoc (en-path:resource rid) %noun) @@ -87,6 +91,7 @@ :: ++ peek-metadatum |= =md-resource:store + ^- (unit metadatum:store) %+ bind (peek-association md-resource) |=(association:store metadatum) :: diff --git a/pkg/arvo/lib/naive-transactions.hoon b/pkg/arvo/lib/naive-transactions.hoon index a2314da6e0..f0c65ac782 100644 --- a/pkg/arvo/lib/naive-transactions.hoon +++ b/pkg/arvo/lib/naive-transactions.hoon @@ -19,24 +19,54 @@ =, secp256k1:secp:crypto %- address-from-pub:key:ethereum %- serialize-point - (ecdsa-raw-recover (keccak-256:keccak:crypto dat) v r s) + (ecdsa-raw-recover (hash-tx dat) v r s) ?- -.result %| ~ %& `p.result == +:: Verify signature and produce signer address +:: +++ verify-sig + |= [sig=@ txdata=octs] + ^- (unit address) + |^ + :: Reversed of the usual r-s-v order because Ethereum integers are + :: big-endian + :: + =^ v sig (take 3) + =^ s sig (take 3 32) + =^ r sig (take 3 32) + :: In Ethereum, v is generally 27 + recid, and verifier expects a + :: recid. Old versions of geth used 0 + recid, so most software + :: now supports either format. See: + :: + :: https://github.com/ethereum/go-ethereum/issues/2053 + :: + =? v (gte v 27) (sub v 27) + (verifier txdata v r s) + :: + ++ take + |= =bite + [(end bite sig) (rsh bite sig)] + -- +:: +++ unsigned-tx + |= [chain-id=@ud =nonce tx=octs] + ^- octs + =/ prepared-data (prepare-for-sig chain-id nonce tx) + =/ len (rsh [3 2] (scot %ui p.prepared-data)) + %: cad:naive 3 + 26^'\19Ethereum Signed Message:\0a' + (met 3 len)^len + prepared-data + ~ + == :: ++ sign-tx |= [pk=@ =nonce tx=octs] ^- octs - =/ prepared-data (prepare-for-sig 1.337 nonce tx) =/ sign-data - =/ len (rsh [3 2] (scot %ui p.prepared-data)) - %- keccak-256:keccak:crypto - %: cad:naive 3 - 26^'\19Ethereum Signed Message:\0a' - (met 3 len)^len - prepared-data - ~ - == + %- hash-tx + (unsigned-tx 1.337 nonce tx) =+ (ecdsa-raw-sign:secp256k1:secp:crypto sign-data pk) (cad:naive 3 1^v 32^s 32^r tx ~) :: @@ -53,6 +83,18 @@ ~ == :: +++ extract-address + |= [=raw-tx:naive nas=^state:naive chain-id=@] + ^- (unit @ux) + ?~ point=(get:orm:naive points.nas ship.from.tx.raw-tx) + ~ + =/ =nonce:naive + =< nonce + (proxy-from-point:naive proxy.from.tx.raw-tx u.point) + =/ message=octs + (unsigned-tx chain-id nonce raw.raw-tx) + (verify-sig sig.raw-tx message) +:: ++ gen-tx |= [=nonce tx=tx:naive pk=@] ^- octs :: takes in a nonce, tx:naive, and private key and returned a signed transactions as octs @@ -161,4 +203,11 @@ :: -- :: +++ hash-tx keccak-256:keccak:crypto +:: +++ hash-raw-tx + |= =raw-tx:naive + ^- @ux + (hash-tx raw.raw-tx) +:: -- diff --git a/pkg/arvo/lib/naive.hoon b/pkg/arvo/lib/naive.hoon index efb0b4d01f..64f185242c 100644 --- a/pkg/arvo/lib/naive.hoon +++ b/pkg/arvo/lib/naive.hoon @@ -139,6 +139,7 @@ +$ nonce @ud +$ dominion ?(%l1 %l2 %spawn) +$ keys [=life suite=@ud auth=@ crypt=@] +++ orm ((on ship point) por) ++ point $: :: domain :: @@ -187,7 +188,7 @@ =operators dns=(list @t) == -+$ points (map ship point) ++$ points (tree [ship point]) +$ operators (jug address address) +$ effects (list diff) +$ proxy ?(%own %spawn %manage %vote %transfer) @@ -455,7 +456,7 @@ ++ get-point |= [=state =ship] ^- (unit point) - =/ existing (~(get by points.state) ship) + =/ existing (get:orm points.state ship) ?^ existing `u.existing =| =point @@ -517,7 +518,7 @@ =/ the-point (get-point state ship) ?> ?=(^ the-point) =* point u.the-point - =- [effects state(points (~(put by points.state) ship new-point))] + =- [effects state(points (put:orm points.state ship new-point))] ^- [=effects new-point=^point] :: ?: =(log-name changed-spawn-proxy:log-names) @@ -690,7 +691,7 @@ == :: :- [%nonce ship proxy nonce]~ - (~(put by points.state) ship u.point) + (put:orm points.state ship u.point) :: :: Receive an individual L2 transaction :: @@ -726,7 +727,7 @@ =/ res=(unit [=effects new-point=^point]) (fun u.point rest) ?~ res ~ - `[effects.u.res state(points (~(put by points.state) ship new-point.u.res))] + `[effects.u.res state(points (put:orm points.state ship new-point.u.res))] :: ++ w-point-esc |* [fun=$-([ship point *] (unit [effects point])) =ship rest=*] @@ -736,7 +737,7 @@ =/ res=(unit [=effects new-point=^point]) (fun u.point rest) ?~ res ~ - `[effects.u.res state(points (~(put by points.state) ship new-point.u.res))] + `[effects.u.res state(points (put:orm points.state ship new-point.u.res))] :: ++ w-point-spawn |* [fun=$-([ship point *] (unit [effects point])) =ship rest=*] @@ -747,7 +748,7 @@ =/ res=(unit [=effects new-point=^point]) (fun u.point rest) ?~ res ~ - `[effects.u.res state(points (~(put by points.state) ship new-point.u.res))] + `[effects.u.res state(points (put:orm points.state ship new-point.u.res))] :: ++ process-transfer-point |= [=point to=address reset=?] @@ -778,7 +779,7 @@ ?: =(0 life.keys.net.point) `rift.net.point :- [%point ship %rift +(rift.net.point)]~ - +(rift.net.point) + +(rift.net.point) =/ effects-4 :~ [%point ship %spawn-proxy *address] [%point ship %management-proxy *address] @@ -811,7 +812,7 @@ :: :: TODO: verify this means the ship exists on neither L1 nor L2 :: - ?: (~(has by points.state) ship) (debug %spawn-exists ~) + ?^ (get:orm points.state ship) (debug %spawn-exists ~) :: Assert one-level-down :: ?. =(+((ship-rank parent)) (ship-rank ship)) (debug %bad-rank ~) @@ -843,7 +844,7 @@ address.owner.own address.owner.own.u.parent-point address.transfer-proxy.own to == - `[effects state(points (~(put by points.state) ship new-point))] + `[effects state(points (put:orm points.state ship new-point))] :: ++ process-configure-keys |= [=point crypt=@ auth=@ suite=@ breach=?] diff --git a/pkg/arvo/lib/pull-hook.hoon b/pkg/arvo/lib/pull-hook.hoon index 59a21e1606..8b94e96f36 100644 --- a/pkg/arvo/lib/pull-hook.hoon +++ b/pkg/arvo/lib/pull-hook.hoon @@ -90,7 +90,12 @@ $: tracking=(map resource track) inner-state=vase == - +:: ++$ base-state-3 + $: prev-version=@ud + prev-min-version=@ud + base-state-2 + == :: +$ state-0 [%0 base-state-0] :: @@ -100,12 +105,23 @@ :: +$ state-3 [%3 base-state-2] :: ++$ state-4 [%4 base-state-3] +:: +$ versioned-state $% state-0 state-1 state-2 state-3 + state-4 == +:: +diplomatic: only renegotiate if versions changed +:: +:: If %.n please leave note as to why renegotiation necessary +:: +:: +++ diplomatic + ^- ? + %.y :: ++ default |* [pull-hook=* =config] @@ -198,7 +214,7 @@ ++ agent |* =config |= =(pull-hook config) - =| state-3 + =| state-4 =* state - ^- agent:gall =< @@ -224,13 +240,21 @@ =| cards=(list card:agent:gall) |^ ?- -.old - %3 + %4 =^ og-cards pull-hook (on-load:og inner-state.old) =. state old + =/ kick=(list card) + ?: ?& =(min-version.config prev-min-version.old) + =(version.config prev-version.old) + diplomatic + == + ~ + (poke-self:pass kick+!>(%kick))^~ :_ this - :(weld cards og-cards (poke-self:pass kick+!>(%kick))^~) + :(weld cards og-cards kick) :: + %3 $(old [%4 0 0 +.old]) %2 $(old (state-to-3 old)) %1 $(old [%2 +.old ~]) %0 !! :: pre-breach @@ -255,8 +279,10 @@ :: ++ on-save ^- vase - =. inner-state - on-save:og + =: inner-state on-save:og + prev-min-version min-version.config + prev-version version.config + == !>(state) :: ++ on-poke @@ -422,6 +448,7 @@ ?~ tan tr-core ?. versioned (tr-ap-og:tr-cleanup |.((on-pull-nack:og rid u.tan))) + %- (slog leaf+"versioned nack for {} in {}" u.tan) =/ pax (kick-mule:virt rid |.((on-pull-kick:og rid))) ?~ pax tr-failed-kick @@ -446,18 +473,18 @@ :: subscription tr-core (tr-suspend-pub-ver min-version.config) - =/ =vase + =/ =^cage (convert-to:ver cage) =/ =wire (make-wire /store) - =+ resources=(~(gas in *(set resource)) (resource-for-update:og vase)) + =+ resources=(~(gas in *(set resource)) (resource-for-update:og q.cage)) ?> ?| no-validate.config ?& (check-src resources) (~(has in resources) rid) == == =/ =mark (append-version:ver version.config) - (tr-emit (~(poke-our pass wire) store-name.config mark vase)) + (tr-emit (~(poke-our pass wire) store-name.config cage)) -- :: ++ tr-kick @@ -472,6 +499,7 @@ :: ++ tr-add |= [s=^ship r=resource] + ?< =(s our.bowl) =: ship s rid r status [%active ~] diff --git a/pkg/arvo/lib/push-hook.hoon b/pkg/arvo/lib/push-hook.hoon index 69d19e6820..fefebaee83 100644 --- a/pkg/arvo/lib/push-hook.hoon +++ b/pkg/arvo/lib/push-hook.hoon @@ -26,6 +26,7 @@ :: /- *push-hook /+ default-agent, resource, verb, versioning, agentio +~% %push-hook-top ..part ~ |% +$ card card:agent:gall :: @@ -57,15 +58,32 @@ inner-state=vase == :: ++$ base-state-1 + $: prev-version=@ud + prev-min-version=@ud + base-state-0 + == +:: +$ state-0 [%0 base-state-0] :: +$ state-1 [%1 base-state-0] ++$ state-2 [%2 base-state-1] :: +$ versioned-state $% state-0 state-1 + state-2 == +:: +diplomatic: only renegotiate if versions changed +:: +:: If %.n please leave note as to why renegotiation necessary +:: +++ diplomatic + ^- ? + %.y +:: ++ push-hook + ~/ %push-hook |* =config $_ ^| |_ bowl:gall @@ -95,7 +113,7 @@ :: ++ transform-proxy-update |~ vase - *(unit vase) + *[(list card) (unit vase)] :: +initial-watch: produce initial state for a subscription :: :: .resource is the resource being subscribed to. @@ -153,10 +171,11 @@ ++ agent |* =config |= =(push-hook config) - =| state-1 + =| state-2 =* state - ^- agent:gall =< + ~% %push-agent-lib ..poke-hook-action ~ |_ =bowl:gall +* this . og ~(. push-hook bowl) @@ -179,16 +198,21 @@ =| cards=(list card:agent:gall) |^ ?- -.old - %1 + %2 =^ og-cards push-hook (on-load:og inner-state.old) =/ old-subs - find-old-subs + (find-old-subs [prev-version prev-min-version]:old) =/ version-cards :- (fact:io version+!>(version.config) /version ~) ?~ old-subs ~ (kick:io old-subs)^~ [:(weld cards og-cards version-cards) this(state old)] + :: + %1 + %_ $ + old [%2 0 0 +.old] + == :: :: %0 @@ -205,6 +229,13 @@ == :: ++ find-old-subs + |= [prev-min-version=@ud prev-version=@ud] + ?: ?& =(min-version.config prev-min-version) + =(prev-version version.config) + diplomatic + == + :: bail on kick if we didn't change versions + ~ %~ tap in %+ roll ~(val by sup.bowl) @@ -230,13 +261,20 @@ -- :: ++ on-save - =. inner-state - on-save:og + =: prev-version version.config + prev-min-version min-version.config + inner-state on-save:og + == !>(state) :: ++ on-poke + ~/ %on-poke |= [=mark =vase] ^- (quip card:agent:gall agent:gall) + ?: =(mark %kick) + ?> (team:title [our src]:bowl) + :_ this + (kick:io (turn ~(val by sup.bowl) tail))^~ ?: =(mark %push-hook-action) ?> (team:title our.bowl src.bowl) =^ cards state @@ -251,6 +289,7 @@ [cards this] :: ++ on-watch + ~/ %on-watch |= =path ^- (quip card:agent:gall agent:gall) ?: ?=([%version ~] path) @@ -265,31 +304,32 @@ unversioned =/ =resource (de-path:resource t.t.path) + =/ requested=@ud + (slav %ud i.t.t.t.t.t.path) =/ =mark - (append-version:ver (slav %ud i.t.t.t.t.t.path)) + (append-version:ver (min requested version.config)) ?. (supported:ver mark) :_ this (fact-init-kick:io version+!>(min-version.config)) - =/ =vase - (convert-to:ver mark (initial-watch:og t.t.t.t.t.t.path resource)) :_ this - [%give %fact ~ mark vase]~ + =- [%give %fact ~ -]~ + (convert-to:ver mark (initial-watch:og t.t.t.t.t.t.path resource)) :: ++ unversioned ?> ?=([%ship @ @ *] t.path) - ?. =(min-version.config 0) - ~& >>> "unversioned req from: {}, nooping" - `this =/ =resource (de-path:resource t.path) - =/ =vase - %+ convert-to:ver update-mark.config + =/ =vase (initial-watch:og t.t.t.t.path resource) :_ this - [%give %fact ~ update-mark.config vase]~ + ?. =(min-version.config 0) + ~& >>> "unversioned req from: {}, nooping" + ~ + [%give %fact ~ (convert-to:ver update-mark.config vase)]~ -- :: ++ on-agent + ~/ %on-agent |= [=wire =sign:agent:gall] ^- (quip card:agent:gall agent:gall) ?. ?=([%helper %push-hook @ *] wire) @@ -343,6 +383,7 @@ [%x %min-version ~] ``version+!>(version.config) == -- + ~% %push-helper-lib ..card ~ |_ =bowl:gall +* og ~(. push-hook bowl) ver ~(. versioning [bowl [update-mark version min-version]:config]) @@ -350,6 +391,7 @@ pass pass:io :: ++ poke-hook-action + ~/ %poke-hook-action |= =action ^- (quip card:agent:gall _state) |^ @@ -418,6 +460,7 @@ [%pass wire %agent [our.bowl store-name.config] %watch store-path.config] :: ++ push-updates + ~/ %push-updates |= =cage ^- (list card:agent:gall) %+ roll (resource-for-update q.cage) @@ -439,11 +482,8 @@ %+ turn ~(tap by paths) |= [fact-ver=@ud paths=(set path)] =/ =mark - (append-version:ver fact-ver) - =/ =^cage - :- mark - (convert-from:ver mark q.cage) - (fact:io cage ~(tap in paths)) + (append-version:ver (min version.config fact-ver)) + (fact:io (convert-from:ver mark q.cage) ~(tap in paths)) :: TODO: deprecate ++ unversioned ?. =(min-version.config 0) ~ @@ -453,52 +493,47 @@ %- ~(gas in *(set path)) (turn (incoming-subscriptions prefix) tail) ?: =(0 ~(wyt in unversioned)) ~ - =/ =^cage - :- update-mark.config - (convert-from:ver update-mark.config q.cage) - (fact:io cage ~(tap in unversioned))^~ + (fact:io (convert-from:ver update-mark.config q.cage) ~(tap in unversioned))^~ -- :: ++ forward-update + ~/ %forward-update |= =cage ^- (list card:agent:gall) =- lis - =/ vas - (convert-to:ver cage) + =/ vas=vase + q:(convert-to:ver cage) %+ roll (resource-for-update q.cage) |= [rid=resource [lis=(list card:agent:gall) tf-vas=(unit vase)]] ^- [(list card:agent:gall) (unit vase)] =/ =path resource+(en-path:resource rid) - =/ =wire (make-wire path) =* ship entity.rid - =. tf-vas + =/ out=(pair (list card:agent:gall) (unit vase)) ?. =(our.bowl ship) :: do not transform before forwarding :: - `vas + ``vas :: use cached transform :: - ?^ tf-vas tf-vas + ?^ tf-vas `tf-vas :: transform before poking store :: (transform-proxy-update:og vas) - ~| "forwarding failed during transform. mark: {} resource: {}" - ?> ?=(^ tf-vas) - =/ =dock - :- ship - ?. =(our.bowl ship) - :: forward to host - :: - dap.bowl - :: poke our store + ~| "forwarding failed during transform. mark: {} rid: {}" + ?> ?=(^ q.out) + :_ q.out + :_ (weld lis p.out) + =/ =wire (make-wire path) + =- [%pass wire %agent - %poke [current-version:ver u.q.out]] + :- ship + ?. =(our.bowl ship) + :: forward to host :: - store-name.config - =/ cag=^cage - :- current-version:ver - u.tf-vas - :_ tf-vas - [[%pass wire %agent dock %poke cag] lis] + dap.bowl + :: poke our store + :: + store-name.config :: ++ ver-from-path |= =path @@ -508,6 +543,7 @@ (slav %ud i.extra) :: ++ resource-for-update + ~/ %resource-for-update |= =vase ^- (list resource) %~ tap in diff --git a/pkg/arvo/lib/server.hoon b/pkg/arvo/lib/server.hoon index 1f450c3e50..431b5db8ac 100644 --- a/pkg/arvo/lib/server.hoon +++ b/pkg/arvo/lib/server.hoon @@ -39,10 +39,10 @@ ~! +:*handler (handler inbound-request) :: - =/ redirect=cord - %- crip - "/~/login?redirect={(trip url.request.inbound-request)}" - [[307 ['location' redirect]~] ~] + =- [[307 ['location' -]~] ~] + %^ cat 3 + '/~/login?redirect=' + url.request.inbound-request :: :: +require-authorization-simple: :: redirect to the login page when unauthenticated @@ -56,10 +56,10 @@ ~! this simple-payload :: - =/ redirect=cord - %- crip - "/~/login?redirect={(trip url.request.inbound-request)}" - [[307 ['location' redirect]~] ~] + =- [[307 ['location' -]~] ~] + %^ cat 3 + '/~/login?redirect=' + url.request.inbound-request :: ++ give-simple-payload |= [eyre-id=@ta =simple-payload:http] @@ -86,36 +86,59 @@ :_ `octs [200 [['content-type' 'text/html'] ?:(cache [max-1-wk ~] ~)]] :: - ++ js-response - |= =octs - ^- simple-payload:http - [[200 [['content-type' 'text/javascript'] max-1-da ~]] `octs] - :: - ++ json-response - |= =json - ^- simple-payload:http - [[200 ['content-type' 'application/json']~] `(json-to-octs json)] - :: ++ css-response + =| cache=? |= =octs ^- simple-payload:http - [[200 [['content-type' 'text/css'] max-1-da ~]] `octs] + :_ `octs + [200 [['content-type' 'text/css'] ?:(cache [max-1-wk ~] ~)]] :: - ++ manx-response - |= man=manx + ++ js-response + =| cache=? + |= =octs ^- simple-payload:http - [[200 ['content-type' 'text/html']~] `(manx-to-octs man)] + :_ `octs + [200 [['content-type' 'text/javascript'] ?:(cache [max-1-wk ~] ~)]] :: ++ png-response + =| cache=? |= =octs ^- simple-payload:http - [[200 [['content-type' 'image/png'] max-1-wk ~]] `octs] + :_ `octs + [200 [['content-type' 'image/png'] ?:(cache [max-1-wk ~] ~)]] + :: + ++ svg-response + =| cache=? + |= =octs + ^- simple-payload:http + :_ `octs + [200 [['content-type' 'image/svg+xml'] ?:(cache [max-1-wk ~] ~)]] + :: + ++ ico-response + |= =octs + ^- simple-payload:http + [[200 [['content-type' 'image/x-icon'] max-1-wk ~]] `octs] :: ++ woff2-response + =| cache=? |= =octs ^- simple-payload:http [[200 [['content-type' 'font/woff2'] max-1-wk ~]] `octs] :: + ++ json-response + =| cache=_| + |= =json + ^- simple-payload:http + :_ `(json-to-octs json) + [200 [['content-type' 'application/json'] ?:(cache [max-1-da ~] ~)]] + :: + ++ manx-response + =| cache=_| + |= man=manx + ^- simple-payload:http + :_ `(manx-to-octs man) + [200 [['content-type' 'text/html'] ?:(cache [max-1-da ~] ~)]] + :: ++ not-found ^- simple-payload:http [[404 ~] ~] @@ -123,10 +146,10 @@ ++ login-redirect |= =request:http ^- simple-payload:http - =/ redirect=cord - %- crip - "/~/login?redirect={(trip url.request)}" - [[307 ['location' redirect]~] ~] + =- [[307 ['location' -]~] ~] + %^ cat 3 + '/~/login?redirect=' + url.request :: ++ redirect |= redirect=cord diff --git a/pkg/arvo/lib/std.hoon b/pkg/arvo/lib/std.hoon index 92915d158a..852868ce64 100644 --- a/pkg/arvo/lib/std.hoon +++ b/pkg/arvo/lib/std.hoon @@ -12,6 +12,7 @@ +$ step _`@u`1 +$ bite $@(bloq [=bloq =step]) +$ octs [p=@ud q=@] ++$ mold $~(* $-(* *)) ++ unit |$ [item] $@(~ [~ u=item]) ++ list |$ [item] $@(~ [i=item t=(list item)]) ++ lest |$ [item] [i=item t=(list item)] @@ -455,6 +456,22 @@ ?. ?=(@ b) & (lth a b) :: +++ por :: parent order + ~/ %por + |= [a=@p b=@p] + ^- ? + ?: =(a b) & + =| i=@ + |- + ?: =(i 2) + :: second two bytes + (lte a b) + :: first two bytes + =+ [c=(end 3 a) d=(end 3 b)] + ?: =(c d) + $(a (rsh 3 a), b (rsh 3 b), i +(i)) + (lth c d) +:: :: Maps :: ++ by @@ -534,6 +551,134 @@ == -- :: +++ on :: ordered map + ~/ %on + |* [key=mold val=mold] + => |% + +$ item [key=key val=val] + -- + :: + ~% %comp +>+ ~ + |= compare=$-([key key] ?) + ~% %core + ~ + |% + :: + ++ apt + ~/ %apt + |= a=(tree item) + =| [l=(unit key) r=(unit key)] + |- ^- ? + ?~ a %.y + ?& ?~(l %.y (compare key.n.a u.l)) + ?~(r %.y (compare u.r key.n.a)) + ?~(l.a %.y &((mor key.n.a key.n.l.a) $(a l.a, l `key.n.a))) + ?~(r.a %.y &((mor key.n.a key.n.r.a) $(a r.a, r `key.n.a))) + == + :: + ++ gas + ~/ %gas + |= [a=(tree item) b=(list item)] + ^- (tree item) + ?~ b a + $(b t.b, a (put a i.b)) + :: + ++ get + ~/ %get + |= [a=(tree item) b=key] + ^- (unit val) + ?~ a ~ + ?: =(b key.n.a) + `val.n.a + ?: (compare b key.n.a) + $(a l.a) + $(a r.a) + :: + ++ has + ~/ %has + |= [a=(tree item) b=key] + ^- ? + !=(~ (get a b)) + :: + ++ lot + ~/ %lot + |= $: tre=(tree item) + start=(unit key) + end=(unit key) + == + ^- (tree item) + |^ + ?: ?&(?=(~ start) ?=(~ end)) + tre + ?~ start + (del-span tre %end end) + ?~ end + (del-span tre %start start) + ?> (compare u.start u.end) + =. tre (del-span tre %start start) + (del-span tre %end end) + :: + ++ del-span + |= [a=(tree item) b=?(%start %end) c=(unit key)] + ^- (tree item) + ?~ a a + ?~ c a + ?- b + %start + ?: =(key.n.a u.c) + (nip a(l ~)) + ?: (compare key.n.a u.c) + $(a (nip a(l ~))) + a(l $(a l.a)) + :: + %end + ?: =(u.c key.n.a) + (nip a(r ~)) + ?: (compare key.n.a u.c) + a(r $(a r.a)) + $(a (nip a(r ~))) + == + -- + :: + ++ nip + ~/ %nip + |= a=(tree item) + ^- (tree item) + ?> ?=(^ a) + |- ^- (tree item) + ?~ l.a r.a + ?~ r.a l.a + ?: (mor key.n.l.a key.n.r.a) + l.a(r $(l.a r.l.a)) + r.a(l $(r.a l.r.a)) + :: + ++ put + ~/ %put + |= [a=(tree item) =key =val] + ^- (tree item) + ?~ a [n=[key val] l=~ r=~] + ?: =(key.n.a key) a(val.n val) + ?: (compare key key.n.a) + =/ l $(a l.a) + ?> ?=(^ l) + ?: (mor key.n.a key.n.l) + a(l l) + l(r a(l r.l)) + =/ r $(a r.a) + ?> ?=(^ r) + ?: (mor key.n.a key.n.r) + a(r r) + r(l a(r l.r)) + :: + ++ tap + ~/ %tap + |= a=(tree item) + ^- (list item) + =| b=(list item) + |- ^+ b + ?~ a b + $(a l.a, b [n.a $(a r.a)]) + -- +:: :: Sets :: ++ in diff --git a/pkg/arvo/lib/strandio.hoon b/pkg/arvo/lib/strandio.hoon index 7ccbfcbca7..d0574fc73c 100644 --- a/pkg/arvo/lib/strandio.hoon +++ b/pkg/arvo/lib/strandio.hoon @@ -490,7 +490,7 @@ =/ m (strand ,vase) ^- form:m ;< =riot:clay bind:m - (warp ship desk ~ %sing %b case /[mak]) + (warp ship desk ~ %sing %e case /[mak]) ?~ riot (strand-fail %build-nave >arg< ~) ?> =(%nave p.r.u.riot) diff --git a/pkg/arvo/lib/vere.hoon b/pkg/arvo/lib/vere.hoon index ee6687cd30..d682b22fa7 100644 --- a/pkg/arvo/lib/vere.hoon +++ b/pkg/arvo/lib/vere.hoon @@ -44,23 +44,10 @@ :: |give:dawn: produce requests for pre-boot validation :: ++ give - =, rpc:ethereum - =, abi:ethereum - =/ tract azimuth:contracts:azimuth |% - :: +bloq:give:dawn: Eth RPC for latest block number - :: - ++ bloq - ^- octs - %- as-octt:mimes:html - %- en-json:html - %+ request-to-json - `~.0 - [%eth-block-number ~] :: +czar:give:dawn: Eth RPC for galaxy table :: ++ czar - |= boq=@ud ^- octs %- as-octt:mimes:html %- en-json:html @@ -68,40 +55,43 @@ %+ turn (gulf 0 255) |= gal=@ %+ request-to-json - `(cat 3 'gal-' (scot %ud gal)) - :+ %eth-call - =- [from=~ to=tract gas=~ price=~ value=~ data=-] - (encode-call 'points(uint32)' [%uint gal]~) - [%number boq] + (cat 3 'gal-' (scot %ud gal)) + :- 'getPoint' + (~(put by *(map @t json)) 'ship' s+(scot %p gal)) :: +point:give:dawn: Eth RPC for ship's contract state :: ++ point - |= [boq=@ud who=ship] + |= who=ship ^- octs %- as-octt:mimes:html %- en-json:html %+ request-to-json - `~.0 - :+ %eth-call - =- [from=~ to=tract gas=~ price=~ value=~ data=-] - (encode-call 'points(uint32)' [%uint `@`who]~) - [%number boq] + ~. + :- 'getPoint' + (~(put by *(map @t json)) 'ship' s+(scot %p who)) :: +turf:give:dawn: Eth RPC for network domains :: ++ turf - |= boq=@ud ^- octs %- as-octt:mimes:html %- en-json:html - :- %a - %+ turn (gulf 0 2) - |= idx=@ %+ request-to-json - `(cat 3 'turf-' (scot %ud idx)) - :+ %eth-call - =- [from=~ to=tract gas=~ price=~ value=~ data=-] - (encode-call 'dnsDomains(uint256)' [%uint idx]~) - [%number boq] + 'turf' + ['getDns' ~] + :: +request-to-json:give:dawn: internally used for request generation + :: + ::NOTE we could import this from /lib/json/rpc, but adding that as a + :: dependency seems a bit unclean + :: + ++ request-to-json + |= [id=@t method=@t params=(map @t json)] + ^- json + %- pairs:enjs:format + :~ jsonrpc+s+'2.0' + id+s+id + method+s+method + params+o+params + == -- :: |take:dawn: parse responses for pre-boot validation :: @@ -111,23 +101,6 @@ =, azimuth =, dejs-soft:format |% - :: +bloq:take:dawn: parse block number - :: - ++ bloq - |= rep=octs - ^- (unit @ud) - =/ jon=(unit json) (de-json:html q.rep) - ?~ jon - ~&([%bloq-take-dawn %invalid-json] ~) - =/ res=(unit cord) ((ot result+so ~) u.jon) - ?~ res - ~&([%bloq-take-dawn %invalid-response rep] ~) - =/ out - %- mule |. - (hex-to-num:ethereum u.res) - ?: ?=(%& -.out) - (some p.out) - ~&([%bloq-take-dawn %invalid-block-number] ~) :: +czar:take:dawn: parse galaxy table :: ++ czar @@ -136,58 +109,94 @@ =/ jon=(unit json) (de-json:html q.rep) ?~ jon ~&([%czar-take-dawn %invalid-json] ~) - =/ res=(unit (list [@t @t])) - ((ar (ot id+so result+so ~)) u.jon) + =/ res=(unit (list [@t @ud @ud @])) + %. u.jon + =, dejs-soft:format + =- (ar (ot id+so result+(ot network+- ~) ~)) + %- ot + :~ :- 'rift' ni + :- 'keys' (ot 'life'^ni ~) + :- 'keys' %+ cu pass-from-eth:azimuth + %- ot + :~ 'crypt'^(cu (lead 32) ni) + 'auth'^(cu (lead 32) ni) + 'suite'^ni + == + == ?~ res - ~&([%czar-take-dawn %invalid-response rep] ~) - =/ dat=(unit (list [who=@p point:azimuth-types])) - =- ?:(?=(%| -.out) ~ (some p.out)) - ^= out %- mule |. - %+ turn u.res - |= [id=@t result=@t] - ^- [who=ship point:azimuth-types] - =/ who `@p`(slav %ud (rsh [3 4] id)) - :- who - %+ point-from-eth - who - :_ *deed:eth-noun - %+ decode-results - result - point:eth-type - ?~ dat - ~&([%bloq-take-dawn %invalid-galaxy-table] ~) + ~&([%czar-take-dawn %invalid-json] ~) :- ~ - %+ roll u.dat - |= $: [who=ship =point:azimuth-types] + %+ roll u.res + |= $: [id=@t deet=[=rift =life =pass]] kyz=(map ship [=rift =life =pass]) == ^+ kyz - ?~ net.point + ?: =(0 life.deet) kyz - (~(put by kyz) who [continuity-number life pass]:u.net.point) + %+ ~(put by kyz) + (slav %ud (rsh [3 4] id)) + deet :: +point:take:dawn: parse ship's contract state :: ++ point |= [who=ship rep=octs] ^- (unit point:azimuth) + ~! *point:azimuth =/ jon=(unit json) (de-json:html q.rep) ?~ jon ~&([%point-take-dawn %invalid-json] ~) - =/ res=(unit cord) ((ot result+so ~) u.jon) - ?~ res - ~&([%point-take-dawn %invalid-response rep] ~) - ~? =(u.res '0x') - :- 'bad result from node; is azimuth address correct?' - azimuth:contracts - =/ out - %- mule |. - %+ point-from-eth - who - :_ *deed:eth-noun ::TODO call rights to fill - (decode-results u.res point:eth-type) - ?: ?=(%& -.out) - (some p.out) - ~&([%point-take-dawn %invalid-point] ~) + =- ?~ res + ~&([%point-take-dawn %incomplete-json] ~) + =, u.res + %- some + :+ own + ?: =(0 life) ~ + `[life pass rift sponsor ~] ::NOTE escape unknown ::TODO could be! + ?. (gth who 0xffff) ~ + `[spawn ~] ::NOTE spawned unknown + ^- $= res + %- unit + $: [spawn=@ own=[@ @ @ @]] + [=rift =life =pass sponsor=[? ship]] + == + %. u.jon + =, dejs-soft:format + =- (ot result+- ~) + %- ot + :~ :- 'ownership' + %- ot + |^ :~ 'spawnProxy'^address + 'owner'^address + 'managementProxy'^address + 'votingProxy'^address + 'transferProxy'^address + == + :: + ++ address + (ot 'address'^(cu hex-to-num:ethereum so) ~) + -- + :: + :- 'network' + %- ot + ::TODO dedupe with +czar + :~ 'rift'^ni + 'keys'^(ot 'life'^ni ~) + :: + :- 'keys' + %+ cu pass-from-eth:azimuth + %- ot + :~ 'crypt'^(cu (lead 32) ni) + 'auth'^(cu (lead 32) ni) + 'suite'^ni + == + :: + ::TODO inconsistent @p string + 'sponsor'^(ot 'has'^bo 'who'^(su fed:ag) ~) + :: + ::TODO escape + ::TODO what if escape or sponsor not present? possible? + == + == :: +turf:take:dawn: parse network domains :: ++ turf @@ -196,94 +205,95 @@ =/ jon=(unit json) (de-json:html q.rep) ?~ jon ~&([%turf-take-dawn %invalid-json] ~) - =/ res=(unit (list [@t @t])) - ((ar (ot id+so result+so ~)) u.jon) + =/ res=(unit (list @t)) + ((ot result+(ar so) ~) u.jon) ?~ res ~&([%turf-take-dawn %invalid-response rep] ~) - =/ dat=(unit (list (pair @ud ^turf))) - =- ?:(?=(%| -.out) ~ (some p.out)) - ^= out %- mule |. - %+ turn u.res - |= [id=@t result=@t] - ^- (pair @ud ^turf) - :- (slav %ud (rsh [3 5] id)) - =/ dom=tape - (decode-results result [%string]~) - =/ hot=host:eyre - (scan dom thos:de-purl:html) - ?>(?=(%& -.hot) p.hot) - ?~ dat - ~&([%turf-take-dawn %invalid-domains] ~) - :- ~ - =* dom u.dat - :: sort by id, ascending, removing duplicates + :: remove duplicates, parse into turfs :: - =| tuf=(map ^turf @ud) - |- ^- (list ^turf) - ?~ dom - %+ turn - %+ sort ~(tap by tuf) - |=([a=(pair ^turf @ud) b=(pair ^turf @ud)] (lth q.a q.b)) - head - =? tuf !(~(has by tuf) q.i.dom) - (~(put by tuf) q.i.dom p.i.dom) - $(dom t.dom) + =- `doz + %+ roll u.res + |= [dom=@t doh=(set @t) doz=(list ^turf)] + ?: (~(has in doh) dom) [doh doz] + :- (~(put in doh) dom) + =/ hot=host:eyre + (rash dom thos:de-purl:html) + ?. ?=(%& -.hot) doz + (snoc doz p.hot) -- :: +veri:dawn: validate keys, life, discontinuity, &c :: ++ veri - |= [=seed:jael =point:azimuth =live] - ^- (unit error=term) - =/ rac (clan:title who.seed) - =/ cub (nol:nu:crub:crypto key.seed) - ?- rac - %pawn - :: a comet address is the fingerprint of the keypair - :: - ?. =(who.seed `@`fig:ex:cub) - `%key-mismatch - :: a comet can never be breached - :: - ?^ live - `%already-booted - :: a comet can never be re-keyed - :: - ?. ?=(%1 lyf.seed) - `%invalid-life - ~ + |= [=ship =feed:jael =point:azimuth =live] + ^- (each seed:jael (lest error=term)) + |^ ?@ -.feed + ?^ err=(test feed) |+[u.err ~] + &+feed + ?> ?=([%1 ~] -.feed) + =| errs=(list term) + |- + ?~ kyz.feed + |+?~(errs [%no-key ~] errs) + =/ =seed:jael [who [lyf key ~]:i.kyz]:feed + ?~ err=(test seed) + &+seed + =. errs (snoc errs u.err) + $(kyz.feed t.kyz.feed) :: - %earl - ~ - :: - * - :: on-chain ships must be launched + ++ test + |= =seed:jael + ^- (unit error=term) + ?. =(ship who.seed) `%not-our-key + =/ rac (clan:title who.seed) + =/ cub (nol:nu:crub:crypto key.seed) + ?- rac + %pawn + :: a comet address is the fingerprint of the keypair + :: + ?. =(who.seed `@`fig:ex:cub) + `%key-mismatch + :: a comet can never be breached + :: + ?^ live + `%already-booted + :: a comet can never be re-keyed + :: + ?. ?=(%1 lyf.seed) + `%invalid-life + ~ :: - ?~ net.point - `%not-keyed - =* net u.net.point - :: boot keys must match the contract + %earl + ~ :: - ?. =(pub:ex:cub pass.net) - ~& [%key-mismatch pub:ex:cub pass.net] - `%key-mismatch - :: life must match the contract - :: - ?. =(lyf.seed life.net) - `%life-mismatch - :: the boot life must be greater than and discontinuous with - :: the last seen life (per the sponsor) - :: - ?: ?& ?=(^ live) - ?| ?=(%| breach.u.live) - (lte life.net life.u.live) - == == - `%already-booted - :: produce the sponsor for vere - :: - ~? !has.sponsor.net - [%no-sponsorship-guarantees-from who.sponsor.net] - ~ - == + * + :: on-chain ships must be launched + :: + ?~ net.point + `%not-keyed + =* net u.net.point + :: boot keys must match the contract + :: + ?. =(pub:ex:cub pass.net) + `%key-mismatch + :: life must match the contract + :: + ?. =(lyf.seed life.net) + `%life-mismatch + :: the boot life must be greater than and discontinuous with + :: the last seen life (per the sponsor) + :: + ?: ?& ?=(^ live) + ?| ?=(%| breach.u.live) + (lte life.net life.u.live) + == == + `%already-booted + :: produce the sponsor for vere + :: + ~? !has.sponsor.net + [%no-sponsorship-guarantees-from who.sponsor.net] + ~ + == + -- :: +sponsor:dawn: retreive sponsor from point :: ++ sponsor diff --git a/pkg/arvo/lib/versioning.hoon b/pkg/arvo/lib/versioning.hoon index 418e215186..bddb10d574 100644 --- a/pkg/arvo/lib/versioning.hoon +++ b/pkg/arvo/lib/versioning.hoon @@ -29,11 +29,12 @@ &((gte ver min) (lte ver version)) :: ++ convert-to - |= =cage - ^- vase - ?: =(p.cage current-version) - q.cage - ((tube-to p.cage) q.cage) + |= [=mark =vase] + ^- cage + :- current-version + ?: =(mark current-version) + vase + ((tube-to mark) vase) :: ++ tube-to |= =mark @@ -44,10 +45,11 @@ .^(tube:clay %cc (scry:io %home /[current-version]/[mark])) :: ++ convert-from - |= =cage - ^- vase - ?: =(p.cage current-version) - q.cage - ((tube-from p.cage) q.cage) + |= [=mark =vase] + ^- cage + :- mark + ?: =(mark current-version) + vase + ((tube-from mark) vase) -- diff --git a/pkg/arvo/mar/btc-provider/action.hoon b/pkg/arvo/mar/btc-provider/action.hoon new file mode 100644 index 0000000000..52a0e1cbea --- /dev/null +++ b/pkg/arvo/mar/btc-provider/action.hoon @@ -0,0 +1,12 @@ +/- *btc-provider +|_ act=action +++ grad %noun +++ grow + |% + ++ noun act + -- +++ grab + |% + ++ noun action + -- +-- diff --git a/pkg/arvo/mar/btc-provider/status.hoon b/pkg/arvo/mar/btc-provider/status.hoon new file mode 100644 index 0000000000..c23fd5e2da --- /dev/null +++ b/pkg/arvo/mar/btc-provider/status.hoon @@ -0,0 +1,14 @@ +/- *btc-provider +/+ bitcoin-json +|_ sta=status +++ grad %noun +++ grow + |% + ++ noun sta + ++ json (status:enjs:bitcoin-json sta) + -- +++ grab + |% + ++ noun status + -- +-- diff --git a/pkg/arvo/mar/btc-provider/update.hoon b/pkg/arvo/mar/btc-provider/update.hoon new file mode 100644 index 0000000000..fae20d82b0 --- /dev/null +++ b/pkg/arvo/mar/btc-provider/update.hoon @@ -0,0 +1,12 @@ +/- *btc-provider +|_ upd=update +++ grad %noun +++ grow + |% + ++ noun upd + -- +++ grab +|% + ++ noun update + -- +-- diff --git a/pkg/arvo/mar/btc-wallet/action.hoon b/pkg/arvo/mar/btc-wallet/action.hoon new file mode 100644 index 0000000000..6d50771ea5 --- /dev/null +++ b/pkg/arvo/mar/btc-wallet/action.hoon @@ -0,0 +1,12 @@ +/- *btc-wallet +|_ act=action +++ grad %noun +++ grow + |% + ++ noun act + -- +++ grab + |% + ++ noun action + -- +-- diff --git a/pkg/arvo/mar/btc-wallet/command.hoon b/pkg/arvo/mar/btc-wallet/command.hoon new file mode 100644 index 0000000000..efee201ca7 --- /dev/null +++ b/pkg/arvo/mar/btc-wallet/command.hoon @@ -0,0 +1,14 @@ +/- *btc-wallet +/+ bitcoin-json +|_ com=command +++ grad %noun +++ grow + |% + ++ noun com + -- +++ grab + |% + ++ noun command + ++ json command:dejs:bitcoin-json + -- +-- diff --git a/pkg/arvo/mar/btc-wallet/internal.hoon b/pkg/arvo/mar/btc-wallet/internal.hoon new file mode 100644 index 0000000000..5096021aff --- /dev/null +++ b/pkg/arvo/mar/btc-wallet/internal.hoon @@ -0,0 +1,12 @@ +/- *btc-wallet +|_ intr=internal +++ grad %noun +++ grow + |% + ++ noun intr + -- +++ grab + |% + ++ noun internal + -- +-- diff --git a/pkg/arvo/mar/btc-wallet/update.hoon b/pkg/arvo/mar/btc-wallet/update.hoon new file mode 100644 index 0000000000..a6245637de --- /dev/null +++ b/pkg/arvo/mar/btc-wallet/update.hoon @@ -0,0 +1,14 @@ +/- *btc-wallet +/+ bitcoin-json +|_ upd=update +++ grad %noun +++ grow + |% + ++ noun upd + ++ json (update:enjs:bitcoin-json upd) + -- +++ grab + |% + ++ noun update + -- +-- diff --git a/pkg/arvo/mar/dm-hook-action.hoon b/pkg/arvo/mar/dm-hook-action.hoon new file mode 100644 index 0000000000..db028c3210 --- /dev/null +++ b/pkg/arvo/mar/dm-hook-action.hoon @@ -0,0 +1,18 @@ +/+ *dm-hook +|_ act=action +++ grad %noun +++ grow + |% + ++ noun act + ++ json + %+ frond:enjs:format %dm-hook-action + (action:enjs act) + -- +:: +++ grab + |% + ++ noun action + ++ json action:dejs + -- +-- + diff --git a/pkg/arvo/mar/graph/cache/hook.hoon b/pkg/arvo/mar/graph/cache/hook.hoon new file mode 100644 index 0000000000..1b9395a0f6 --- /dev/null +++ b/pkg/arvo/mar/graph/cache/hook.hoon @@ -0,0 +1,20 @@ +/- metadata=metadata-store, res=resource +|% ++$ cache-action + $% [%graph-to-mark (pair resource:res (unit mark))] + [%perm-marks (pair (pair mark @tas) tube:clay)] + [%transform-marks (pair mark tube:clay)] + == +-- +:: +|_ act=cache-action +++ grad %noun +++ grow + |% + ++ noun act + -- +++ grab + |% + ++ noun cache-action + -- +-- diff --git a/pkg/arvo/mar/graph/indexed-post.hoon b/pkg/arvo/mar/graph/indexed-post.hoon new file mode 100644 index 0000000000..1013a61aae --- /dev/null +++ b/pkg/arvo/mar/graph/indexed-post.hoon @@ -0,0 +1,14 @@ +/- *post +|_ i=indexed-post +++ grow + |% + ++ noun i + -- +:: +++ grab + |% + ++ noun indexed-post + -- +:: +++ grad %noun +-- diff --git a/pkg/arvo/mar/graph/update-1.hoon b/pkg/arvo/mar/graph/update-1.hoon index e6766edb51..be29d7e8fa 100644 --- a/pkg/arvo/mar/graph/update-1.hoon +++ b/pkg/arvo/mar/graph/update-1.hoon @@ -1,19 +1,17 @@ /+ *graph-store =* as-octs as-octs:mimes:html :: -|_ upd=update +|_ upd=update:one ++ grad %noun ++ grow |% ++ noun upd - ++ json (update:enjs upd) ++ mime [/application/x-urb-graph-update (as-octs (jam upd))] -- :: ++ grab |% - ++ noun update - ++ json update:dejs + ++ noun update:one ++ mime |=([* =octs] ;;(update (cue q.octs))) -- -- diff --git a/pkg/arvo/mar/graph/update-2.hoon b/pkg/arvo/mar/graph/update-2.hoon new file mode 100644 index 0000000000..e6766edb51 --- /dev/null +++ b/pkg/arvo/mar/graph/update-2.hoon @@ -0,0 +1,19 @@ +/+ *graph-store +=* as-octs as-octs:mimes:html +:: +|_ upd=update +++ grad %noun +++ grow + |% + ++ noun upd + ++ json (update:enjs upd) + ++ mime [/application/x-urb-graph-update (as-octs (jam upd))] + -- +:: +++ grab + |% + ++ noun update + ++ json update:dejs + ++ mime |=([* =octs] ;;(update (cue q.octs))) + -- +-- diff --git a/pkg/arvo/mar/graph/validator/chat.hoon b/pkg/arvo/mar/graph/validator/chat.hoon index 53c10c9f0e..63a6a8d3a7 100644 --- a/pkg/arvo/mar/graph/validator/chat.hoon +++ b/pkg/arvo/mar/graph/validator/chat.hoon @@ -4,6 +4,11 @@ |% ++ noun i :: + ++ graph-indexed-post + ^- indexed-post + ?> ?=([@ ~] index.p.i) + i + :: ++ graph-permissions-add |= vip=vip-metadata:met ^- permissions:graph @@ -30,13 +35,10 @@ =- [- post(index -)] [atom ~] -- -++ grab +:: +++ grab |% - ++ noun - |= p=* - =/ ip ;;(indexed-post p) - ?> ?=([@ ~] index.p.ip) - ip + ++ noun indexed-post -- :: ++ grad %noun diff --git a/pkg/arvo/mar/graph/validator/dm.hoon b/pkg/arvo/mar/graph/validator/dm.hoon new file mode 100644 index 0000000000..85fe37e9db --- /dev/null +++ b/pkg/arvo/mar/graph/validator/dm.hoon @@ -0,0 +1,26 @@ +/- *post, met=metadata-store, graph=graph-store, hark=hark-graph-hook +|_ i=indexed-post +++ grow + |% + ++ noun i + :: + ++ graph-indexed-post + ^- indexed-post + ?> ?=(?([@ ~] [@ @ ~]) index.p.i) + ?> (lth i.index.p.i (bex 128)) + i + :: + ++ notification-kind + ^- (unit notif-kind:hark) + ?+ index.p.i ~ + [@ @ ~] `[%message [1 2] %count %none] + == + :: + -- +++ grab + |% + ++ noun indexed-post + -- +:: +++ grad %noun +-- diff --git a/pkg/arvo/mar/graph/validator/link.hoon b/pkg/arvo/mar/graph/validator/link.hoon index d877018648..a59086f1b7 100644 --- a/pkg/arvo/mar/graph/validator/link.hoon +++ b/pkg/arvo/mar/graph/validator/link.hoon @@ -4,6 +4,28 @@ |% ++ noun i :: + ++ graph-indexed-post + ^- indexed-post + ?+ index.p.i ~|(index+index.p.i !!) + :: top-level link post; title and url + :: + [@ ~] + ?> ?=([[%text @] $%([%url @] [%reference *]) ~] contents.p.i) + i + :: + :: comment on link post; container structure + :: + [@ @ ~] + ?> ?=(~ contents.p.i) + i + :: + :: comment on link post; comment text + :: + [@ @ @ ~] + ?> ?=(^ contents.p.i) + i + == + :: ++ graph-permissions-add |= vip=vip-metadata:met ^- permissions:graph @@ -48,28 +70,7 @@ -- ++ grab |% - ++ noun - |= p=* - =/ ip ;;(indexed-post p) - ?+ index.p.ip ~|(index+index.p.ip !!) - :: top-level link post; title and url - :: - [@ ~] - ?> ?=([[%text @] $%([%url @] [%reference *]) ~] contents.p.ip) - ip - :: - :: comment on link post; container structure - :: - [@ @ ~] - ?> ?=(~ contents.p.ip) - ip - :: - :: comment on link post; comment text - :: - [@ @ @ ~] - ?> ?=(^ contents.p.ip) - ip - == + ++ noun indexed-post -- ++ grad %noun -- diff --git a/pkg/arvo/mar/graph/validator/post.hoon b/pkg/arvo/mar/graph/validator/post.hoon index a12e62e721..9de65dc375 100644 --- a/pkg/arvo/mar/graph/validator/post.hoon +++ b/pkg/arvo/mar/graph/validator/post.hoon @@ -3,6 +3,12 @@ ++ grow |% ++ noun i + :: + ++ graph-indexed-post + ^- indexed-post + ?> ?=(^ contents.p.i) + i + :: ++ graph-permissions-add |= vip=vip-metadata:met ^- permissions:graph @@ -40,13 +46,7 @@ -- ++ grab |% - :: +noun: validate post - :: - ++ noun - |= p=* - =/ ip ;;(indexed-post p) - ?> ?=(^ contents.p.ip) - ip + ++ noun indexed-post -- :: ++ grad %noun diff --git a/pkg/arvo/mar/graph/validator/publish.hoon b/pkg/arvo/mar/graph/validator/publish.hoon index c8da4405ca..e5cfd19117 100644 --- a/pkg/arvo/mar/graph/validator/publish.hoon +++ b/pkg/arvo/mar/graph/validator/publish.hoon @@ -3,6 +3,44 @@ ++ grow |% ++ noun i + :: + ++ graph-indexed-post + ^- indexed-post + ?+ index.p.i !! + :: top level post must have no content + [@ ~] + ?> ?=(~ contents.p.i) + i + :: container for revisions + :: + [@ %1 ~] + ?> ?=(~ contents.p.i) + i + :: specific revision + :: first content is the title + :: revisions are numbered by the revision count + :: starting at one + [@ %1 @ ~] + ?> ?=([* * *] contents.p.i) + ?> ?=(%text -.i.contents.p.i) + i + :: container for comments + :: + [@ %2 ~] + ?> ?=(~ contents.p.i) + i + :: container for comment revisions + :: + [@ %2 @ ~] + ?> ?=(~ contents.p.i) + i + :: specific comment revision + :: + [@ %2 @ @ ~] + ?> ?=(^ contents.p.i) + i + == + :: ++ graph-permissions-add |= vip=vip-metadata:met ^- permissions:graph @@ -55,45 +93,7 @@ -- ++ grab |% - :: +noun: validate publish note - :: - ++ noun - |= p=* - =/ ip ;;(indexed-post p) - ?+ index.p.ip !! - :: top level post must have no content - [@ ~] - ?> ?=(~ contents.p.ip) - ip - :: container for revisions - :: - [@ %1 ~] - ?> ?=(~ contents.p.ip) - ip - :: specific revision - :: first content is the title - :: revisions are numbered by the revision count - :: starting at one - [@ %1 @ ~] - ?> ?=([* * *] contents.p.ip) - ?> ?=(%text -.i.contents.p.ip) - ip - :: container for comments - :: - [@ %2 ~] - ?> ?=(~ contents.p.ip) - ip - :: container for comment revisions - :: - [@ %2 @ ~] - ?> ?=(~ contents.p.ip) - ip - :: specific comment revision - :: - [@ %2 @ @ ~] - ?> ?=(^ contents.p.ip) - ip - == + ++ noun indexed-post -- :: ++ grad %noun diff --git a/pkg/arvo/mar/ico.hoon b/pkg/arvo/mar/ico.hoon new file mode 100644 index 0000000000..e862b9b9a9 --- /dev/null +++ b/pkg/arvo/mar/ico.hoon @@ -0,0 +1,12 @@ +|_ dat=@ +++ grow + |% + ++ mime [/image/x-icon (as-octs:mimes:html dat)] + -- +++ grab + |% + ++ mime |=([p=mite q=octs] q.q) + ++ noun @ + -- +++ grad %mime +-- diff --git a/pkg/arvo/mar/lens/command.hoon b/pkg/arvo/mar/lens/command.hoon index 1e70b360db..84bab9cdb5 100644 --- a/pkg/arvo/mar/lens/command.hoon +++ b/pkg/arvo/mar/lens/command.hoon @@ -46,6 +46,7 @@ import-all+(ot base64-jam+so ~) as+(ot mark+(su sym) next+source ~) hoon+(ot code+so next+source ~) + cancel+none == ++ none |=(^^json (some ~)) ++ sink diff --git a/pkg/arvo/mar/svg.hoon b/pkg/arvo/mar/svg.hoon new file mode 100644 index 0000000000..2911e4900c --- /dev/null +++ b/pkg/arvo/mar/svg.hoon @@ -0,0 +1,12 @@ +|_ dat=@ +++ grow + |% + ++ mime [/image/'svg+xml' (as-octs:mimes:html dat)] + -- +++ grab + |% + ++ mime |=([p=mite q=octs] q.q) + ++ noun @ + -- +++ grad %mime +-- diff --git a/pkg/arvo/sur/bitcoin.hoon b/pkg/arvo/sur/bitcoin.hoon new file mode 100644 index 0000000000..06aee81785 --- /dev/null +++ b/pkg/arvo/sur/bitcoin.hoon @@ -0,0 +1,84 @@ +:: sur/btc.hoon +:: Utilities for working with BTC data types and transactions +:: +:: chyg: whether account is (non-)change. 0 or 1 +:: bytc: "btc-byts" with dat cast to @ux +|% ++$ network ?(%main %testnet) ++$ hexb [wid=@ dat=@ux] :: hex byts ++$ bits [wid=@ dat=@ub] ++$ xpub @ta ++$ address + $% [%base58 @uc] + [%bech32 cord] + == ++$ fprint hexb ++$ bipt $?(%44 %49 %84) ++$ chyg $?(%0 %1) ++$ idx @ud ++$ hdkey [=fprint pubkey=hexb =network =bipt =chyg =idx] ++$ sats @ud ++$ vbytes @ud ++$ txid hexb ++$ utxo [pos=@ =txid height=@ value=sats recvd=(unit @da)] +++ address-info + $: =address + confirmed-value=sats + unconfirmed-value=sats + utxos=(set utxo) + == +++ tx + |% + +$ data + $: is=(list input) + os=(list output) + locktime=@ud + nversion=@ud + segwit=(unit @ud) + == + +$ val + $: =txid + pos=@ud + =address + value=sats + == + :: included: whether tx is in the mempool or blockchain + :: + +$ info + $: included=? + =txid + confs=@ud + recvd=(unit @da) + inputs=(list val) + outputs=(list val) + == + +$ input + $: =txid + pos=@ud + sequence=hexb + script-sig=(unit hexb) + pubkey=(unit hexb) + value=sats + == + +$ output + $: script-pubkey=hexb + value=sats + == + -- +++ psbt + |% + +$ base64 cord + +$ in [=utxo rawtx=hexb =hdkey] + +$ out [=address hk=(unit hdkey)] + +$ target $?(%input %output) + +$ keyval [key=hexb val=hexb] + +$ map (list keyval) + -- +++ ops + |% + ++ op-dup 118 + ++ op-equalverify 136 + ++ op-hash160 169 + ++ op-checksig 172 + -- +-- diff --git a/pkg/arvo/sur/btc-provider.hoon b/pkg/arvo/sur/btc-provider.hoon new file mode 100644 index 0000000000..65c59be46f --- /dev/null +++ b/pkg/arvo/sur/btc-provider.hoon @@ -0,0 +1,78 @@ +/- *bitcoin, resource +|% ++$ host-info + $: api-url=@t + connected=? + =network + block=@ud + clients=(set ship) + == ++$ whitelist + $: public=? + kids=? + users=(set ship) + groups=(set resource:resource) + == +:: ++$ whitelist-target + $% [%public ~] + [%kids ~] + [%users users=(set ship)] + [%groups groups=(set resource:resource)] + == ++$ command + $% [%set-credentials api-url=@t =network] + [%add-whitelist wt=whitelist-target] + [%remove-whitelist wt=whitelist-target] + == ++$ action + $% [%address-info =address] + [%tx-info txid=hexb] + [%raw-tx txid=hexb] + [%broadcast-tx rawtx=hexb] + [%ping ~] + == +:: ++$ result + $% [%address-info =address utxos=(set utxo) used=? block=@ud] + [%tx-info =info:tx] + [%raw-tx txid=hexb rawtx=hexb] + [%broadcast-tx txid=hexb broadcast=? included=?] + == ++$ error + $% [%not-connected status=@ud] + [%bad-request status=@ud] + [%no-auth status=@ud] + [%rpc-error ~] + == ++$ update (each result error) ++$ status + $% [%connected =network block=@ud fee=(unit sats)] + [%new-block =network block=@ud fee=(unit sats) blockhash=hexb blockfilter=hexb] + [%disconnected ~] + == +:: +++ rpc-types + |% + +$ action + $% [%get-address-info =address] + [%get-tx-vals txid=hexb] + [%get-raw-tx txid=hexb] + [%broadcast-tx rawtx=hexb] + [%get-block-count ~] + [%get-block-info ~] + == + :: + +$ result + $% [%get-address-info =address utxos=(set utxo) used=? block=@ud] + [%get-tx-vals =info:tx] + [%get-raw-tx txid=hexb rawtx=hexb] + [%create-raw-tx rawtx=hexb] + [%broadcast-tx txid=hexb broadcast=? included=?] + [%get-block-count block=@ud] + [%get-block-info block=@ud fee=(unit sats) blockhash=hexb blockfilter=hexb] + + == + -- +-- +:: diff --git a/pkg/arvo/sur/btc-wallet.hoon b/pkg/arvo/sur/btc-wallet.hoon new file mode 100644 index 0000000000..85d30a2bac --- /dev/null +++ b/pkg/arvo/sur/btc-wallet.hoon @@ -0,0 +1,167 @@ +/- *bitcoin, bp=btc-provider +/+ bip32 +|% ++$ params [batch-size=@ud fam-limit=@ud piym-limit=@ud] ++$ provider [host=ship connected=?] ++$ block @ud ++$ btc-state [=block fee=(unit sats) t=@da] ++$ payment [pend=(unit txid) =xpub =address payer=ship value=sats note=(unit @t)] ++$ piym + $: ps=(map ship payment) + pend=(map txid payment) + num-fam=(map ship @ud) + == ++$ poym [txbu=(unit txbu) note=(unit @t)] +:: +:: command: run from the CLI or as API calls by our ship +:: ++$ command + $% [%set-provider provider=(unit ship)] + [%check-provider provider=ship] + [%check-payee payee=ship] + [%set-current-wallet =xpub] + [%add-wallet =xpub =fprint scan-to=(unit scon) max-gap=(unit @ud) confs=(unit @ud)] + [%delete-wallet =xpub] + [%init-payment-external =address value=sats feyb=sats note=(unit @t)] + [%init-payment payee=ship value=sats feyb=sats note=(unit @t)] + [%broadcast-tx txhex=cord] + [%gen-new-address ~] + == +:: action: how peers poke us +:: ++$ action + $% [%gen-pay-address value=sats note=(unit @t)] + [%give-pay-address =address value=sats] + [%expect-payment =txid value=sats] + == +:: internal: actions that simply make the state machine more explicit +:: ++$ internal + $% [%add-poym-raw-txi =txid rawtx=hexb] + [%close-pym ti=info:tx] + [%fail-broadcast-tx =txid] + [%succeed-broadcast-tx =txid] + == +:: +:: Wallet Types +:: +:: nixt: next indices to generate addresses from (non-change/change) +:: addi: HD path along with UTXOs +:: 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: copulates with thousands of indices to form addresses +:: +++ max-index (dec (pow 2 32)) ++$ 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 ++$ wamp [prv=@ pub=[x=@ y=@] cad=@ dep=@ud ind=@ud pif=@] +:: +:: 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-0 + $: =xpub + =network + =fprint + =wilt + =bipt + =wach + =nixt + scanned=? + scan-to=scon + max-gap=@ud + confs=@ud + == +:: ++$ walt + $: =xpub + =network + =fprint + =wamp + =bipt + =wach + =nixt + scanned=? + scan-to=scon + max-gap=@ud + confs=@ud + == +:: 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) +:: +:: 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 +:: ++$ insel [=utxo =chyg =idx] ++$ feyb sats ++$ txi [=utxo rawtx=(unit hexb) =hdkey] ++$ txo [=address value=sats hk=(unit hdkey)] ++$ txbu + $: =xpub + payee=(unit ship) + =vbytes + txis=(list txi) + txos=(list txo) + signed-tx=(unit hexb) + == +:: hest: an entry in the history log +:: ++$ hest + $: =xpub + =txid + confs=@ud + recvd=(unit @da) + inputs=(list [=val:tx s=(unit ship)]) + outputs=(list [=val:tx s=(unit ship)]) + note=(unit @t) + == ++$ history (map txid hest) +:: ++$ error + $? %cant-pay-ourselves + %no-comets + %no-dust + %tx-being-signed + %insufficient-balance + %broadcast-fail + == +:: data to send to the frontend +:: ++$ update + $% $: %initial + provider=(unit provider) + wallet=(unit xpub) + balance=(unit [confirmed=sats unconfirmed=sats]) + =history + =btc-state + address=(unit address) + == + [%broadcast-success ~] + [%change-provider provider=(unit provider)] + [%change-wallet wallet=(unit xpub) balance=(unit [p=sats q=sats]) =history] + [%psbt pb=@t fee=sats] + [%btc-state =btc-state] + [%new-tx =hest] + [%cancel-tx =txid] + [%new-address =address] + [%balance balance=(unit [confirmed=sats unconfirmed=sats])] + [%error =error] + == +:: +-- diff --git a/pkg/arvo/sur/dice.hoon b/pkg/arvo/sur/dice.hoon new file mode 100644 index 0000000000..82df7ef8c2 --- /dev/null +++ b/pkg/arvo/sur/dice.hoon @@ -0,0 +1,67 @@ +:: dice: structures for L2 Rollers +:: +/+ naive, ethereum +:: +|% ++$ owners (jug address:naive ship) +:: ++$ roller-config + $: next-batch=time + frequency=@dr + refresh-time=@dr + contract=@ux + chain-id=@ + == +:: ++$ keccak @ux +:: ++$ status + ?(%unknown %pending %sending %confirmed %failed) +:: ++$ tx-status + $: =status + pointer=(unit l1-tx-pointer) + == +:: ++$ l1-tx-pointer + $: =address:ethereum + nonce=@ud + == +:: ++$ l2-tx + $? %transfer-point + %spawn + %configure-keys + %escape + %cancel-escape + %adopt + %reject + %detach + %set-management-proxy + %set-spawn-proxy + %set-transfer-proxy + == +:: +:: TODO: add submission time? +:: ++$ roller-tx [=ship =status hash=keccak type=l2-tx] +:: ++$ pend-tx [force=? =address:naive =raw-tx:naive] +:: ++$ part-tx + $% [%raw raw=octs] + [%don =tx:naive] + [%ful raw=octs =tx:naive] ::TODO redundant? + == +:: ++$ rpc-send-roll + $: endpoint=@t + contract=address:ethereum + chain-id=@ + pk=@ + :: + nonce=@ud + next-gas-price=@ud + txs=(list raw-tx:naive) + == +-- diff --git a/pkg/arvo/sur/dm-hook.hoon b/pkg/arvo/sur/dm-hook.hoon new file mode 100644 index 0000000000..227566a27b --- /dev/null +++ b/pkg/arvo/sur/dm-hook.hoon @@ -0,0 +1,25 @@ +|% +++ action + =< action + |% + :: + ++ action + $% accept + decline + pendings + screen + == + :: + +$ accept + [%accept =ship] + :: + +$ decline + [%decline =ship] + :: + +$ pendings + [%pendings ships=(set ship)] + :: + +$ screen + [%screen screen=?] + -- +-- diff --git a/pkg/arvo/sur/glob.hoon b/pkg/arvo/sur/glob.hoon index 4c2950310e..7d2503785a 100644 --- a/pkg/arvo/sur/glob.hoon +++ b/pkg/arvo/sur/glob.hoon @@ -1,3 +1,5 @@ |% -+$ glob (map path mime) ++$ glob (map path mime) ++$ glob-details [hash=@uv glob=(unit (each glob tid=@ta))] ++$ globs (map serve-path=path glob-details) -- diff --git a/pkg/arvo/sur/graph-store.hoon b/pkg/arvo/sur/graph-store.hoon index 51f86a0a9a..2217038f0b 100644 --- a/pkg/arvo/sur/graph-store.hoon +++ b/pkg/arvo/sur/graph-store.hoon @@ -1,5 +1,61 @@ /- *post |% ++$ graph ((mop atom node) gth) ++$ marked-graph [p=graph q=(unit mark)] +:: ++$ maybe-post (each post hash) ++$ node [post=maybe-post children=internal-graph] ++$ graphs (map resource marked-graph) +:: ++$ tag-queries (jug term uid) +:: ++$ update-log ((mop time logged-update) gth) ++$ update-logs (map resource update-log) +:: ++$ internal-graph + $~ [%empty ~] + $% [%graph p=graph] + [%empty ~] + == +:: ++$ network + $: =graphs + =tag-queries + =update-logs + archive=graphs + ~ + == +:: ++$ update [p=time q=action] +:: ++$ logged-update [p=time q=logged-action] + +:: ++$ logged-action + $% [%add-graph =resource =graph mark=(unit mark) overwrite=?] + [%add-nodes =resource nodes=(map index node)] + [%remove-posts =resource indices=(set index)] + [%add-signatures =uid =signatures] + [%remove-signatures =uid =signatures] + == +:: ++$ action + $% logged-action + [%remove-graph =resource] + :: + [%add-tag =term =uid] + [%remove-tag =term =uid] + :: + [%archive-graph =resource] + [%unarchive-graph =resource] + [%run-updates =resource =update-log] + :: + :: NOTE: cannot be sent as pokes + :: + [%keys =resources] + [%tags tags=(set term)] + [%tag-queries =tag-queries] + == :: +$ permissions [admin=permission-level writer=permission-level reader=permission-level] @@ -13,11 +69,75 @@ +$ permission-level ?(%no %self %yes) :: +:: %graph-store types version 1 +:: +++ one + |% + ++ orm ((ordered-map atom node) gth) + ++ orm-log ((ordered-map time logged-update) gth) + :: + +$ graph ((mop atom node) gth) + +$ marked-graph [p=graph q=(unit mark)] + :: + +$ node [=post children=internal-graph] + +$ graphs (map resource marked-graph) + :: + +$ tag-queries (jug term resource) + :: + +$ update-log ((mop time logged-update) gth) + +$ update-logs (map resource update-log) + :: + +$ internal-graph + $~ [%empty ~] + $% [%graph p=graph] + [%empty ~] + == + :: + +$ network + $: =graphs + =tag-queries + =update-logs + archive=graphs + validators=(set mark) + == + :: + +$ update [p=time q=action] + :: + +$ logged-update [p=time q=logged-action] + :: + +$ logged-action + $% [%add-graph =resource =graph mark=(unit mark) overwrite=?] + [%add-nodes =resource nodes=(map index node)] + [%remove-nodes =resource indices=(set index)] + [%add-signatures =uid =signatures] + [%remove-signatures =uid =signatures] + == + :: + +$ action + $% logged-action + [%remove-graph =resource] + :: + [%add-tag =term =resource] + [%remove-tag =term =resource] + :: + [%archive-graph =resource] + [%unarchive-graph =resource] + [%run-updates =resource =update-log] + :: + :: NOTE: cannot be sent as pokes + :: + [%keys =resources] + [%tags tags=(set term)] + [%tag-queries =tag-queries] + == + -- +:: +:: %graph-store types version 0 +:: ++ zero =< [. post-zero] =, post-zero |% - :: ++ orm ((ordered-map atom node) gth) ++ orm-log ((ordered-map time logged-update) gth) :: @@ -81,61 +201,4 @@ [%tag-queries =tag-queries] == -- - -+$ graph ((mop atom node) gth) -+$ marked-graph [p=graph q=(unit mark)] -:: -+$ node [=post children=internal-graph] -+$ graphs (map resource marked-graph) -:: -+$ tag-queries (jug term resource) -:: -+$ update-log ((mop time logged-update) gth) -+$ update-logs (map resource update-log) -:: -:: -+$ internal-graph - $~ [%empty ~] - $% [%graph p=graph] - [%empty ~] - == -:: -+$ network - $: =graphs - =tag-queries - =update-logs - archive=graphs - validators=(set mark) - == -:: -+$ update [p=time q=action] -:: -+$ logged-update [p=time q=logged-action] - -:: -+$ logged-action - $% [%add-graph =resource =graph mark=(unit mark) overwrite=?] - [%add-nodes =resource nodes=(map index node)] - [%remove-nodes =resource indices=(set index)] - [%add-signatures =uid =signatures] - [%remove-signatures =uid =signatures] - == -:: -+$ action - $% logged-action - [%remove-graph =resource] - :: - [%add-tag =term =resource] - [%remove-tag =term =resource] - :: - [%archive-graph =resource] - [%unarchive-graph =resource] - [%run-updates =resource =update-log] - :: - :: NOTE: cannot be sent as pokes - :: - [%keys =resources] - [%tags tags=(set term)] - [%tag-queries =tag-queries] - == -- diff --git a/pkg/arvo/sur/hark-store.hoon b/pkg/arvo/sur/hark-store.hoon index 82a966f4e7..682d069dbb 100644 --- a/pkg/arvo/sur/hark-store.hoon +++ b/pkg/arvo/sur/hark-store.hoon @@ -1,6 +1,83 @@ /- chat-store, graph-store, post, *resource, group-store, metadata-store ^? |% ++$ index + $% $: %graph + graph=resource + mark=(unit mark) + description=@t + =index:graph-store + == + [%group group=resource description=@t] + == +:: ++$ group-contents + $~ [%add-members *resource ~] + $>(?(%add-members %remove-members) update:group-store) +:: ++$ notification + [date=@da read=? =contents] +:: ++$ contents + $% [%graph =(list post:post)] + [%group =(list group-contents)] + == +:: ++$ timebox + (map index notification) +:: ++$ notifications + ((mop @da timebox) gth) +:: ++$ action + $% [%add-note =index =notification] + :: if .time is ~, then archiving unread notification + :: else, archiving read notification + [%archive time=(unit @da) =index] + :: + [%unread-count =stats-index =time] + [%read-count =stats-index] + :: + [%unread-each =stats-index ref=index:graph-store time=@da] + [%read-each =stats-index ref=index:graph-store] + :: + [%read-note =index] + :: + [%seen-index time=@da =stats-index] + [%remove-graph =resource] + :: + [%read-all ~] + [%set-dnd dnd=?] + [%seen ~] + == +:: +++ stats-index + $% [%graph graph=resource =index:graph-store] + [%group group=resource] + == +:: ++$ indexed-notification + [index notification] +:: ++$ stats + [=unreads last-seen=@da] +:: ++$ unreads + $% [%count num=@ud] + [%each indices=(set index:graph-store)] + == +:: ++$ update + $% action + [%more more=(list update)] + [%added =index =notification] + [%note-read =time =index] + [%timebox time=(unit @da) =(list [index notification])] + [%count count=@ud] + [%clear =stats-index] + [%unreads unreads=(list [stats-index stats])] + == +:: historical ++ state-zero |% +$ state @@ -20,7 +97,7 @@ (map index notification) :: +$ index - $% [%graph group=resource graph=resource module=@t description=@t] + $% [%graph graph=resource module=@t description=@t] [%group group=resource description=@t] [%chat chat=path mention=?] == @@ -68,6 +145,17 @@ dnd=_| == :: + +$ index + $% $: %graph + group=resource + graph=resource + module=@t + description=@t + =index:graph-store + == + [%group group=resource description=@t] + == + :: ++ orm ((ordered-map @da timebox) gth) :: @@ -106,6 +194,17 @@ ++ orm ((ordered-map @da timebox) gth) :: + +$ index + $% $: %graph + group=resource + graph=resource + module=@t + description=@t + =index:graph-store + == + [%group group=resource description=@t] + == + :: +$ notification [date=@da read=? =contents] :: @@ -122,80 +221,97 @@ :: -- :: -+$ index - $% $: %graph - group=resource - graph=resource - module=@t - description=@t - =index:graph-store - == - [%group group=resource description=@t] - == -:: -+$ group-contents - $~ [%add-members *resource ~] - $>(?(%add-members %remove-members) update:group-store) -:: -+$ notification - [date=@da read=? =contents] -:: -+$ contents - $% [%graph =(list post:post)] - [%group =(list group-contents)] - == -:: -+$ timebox - (map index notification) -:: -+$ notifications - ((mop @da timebox) gth) -:: -+$ action - $% [%add-note =index =notification] - [%archive time=@da index] - :: - [%unread-count =stats-index =time] - [%read-count =stats-index] - :: - :: - [%unread-each =stats-index ref=index:graph-store time=@da] - [%read-each =stats-index ref=index:graph-store] - :: - [%read-note time=@da index] - [%unread-note time=@da index] - :: - [%seen-index time=@da =stats-index] - [%remove-graph =resource] - :: - [%read-all ~] - [%set-dnd dnd=?] - [%seen ~] - == -:: -++ stats-index - $% [%graph graph=resource =index:graph-store] - [%group group=resource] - == -:: -+$ indexed-notification - [index notification] -:: -+$ stats - [notifications=(set [time index]) =unreads last-seen=@da] -:: -+$ unreads - $% [%count num=@ud] - [%each indices=(set index:graph-store)] - == -:: -+$ update - $% action - [%more more=(list update)] - [%added time=@da =index =notification] - [%timebox time=@da archived=? =(list [index notification])] - [%count count=@ud] - [%clear =stats-index] - [%unreads unreads=(list [stats-index stats])] - == +++ state-four + =< base-state + |% + ++ orm + ((ordered-map @da timebox) gth) + :: + +$ base-state + $: unreads-each=(jug stats-index index:graph-store) + unreads-count=(map stats-index @ud) + last-seen=(map stats-index @da) + =notifications + archive=notifications + current-timebox=@da + dnd=_| + == + :: + +$ index + $% $: %graph + group=resource + graph=resource + module=@t + description=@t + =index:graph-store + == + [%group group=resource description=@t] + == + :: + +$ group-contents + $~ [%add-members *resource ~] + $>(?(%add-members %remove-members) update:group-store) + :: + +$ notification + [date=@da read=? =contents] + :: + +$ contents + $% [%graph =(list post:post)] + [%group =(list group-contents)] + == + :: + +$ timebox + (map index notification) + :: + +$ notifications + ((mop @da timebox) gth) + :: + +$ action + $% [%add-note =index =notification] + [%archive time=@da index] + :: + [%unread-count =stats-index =time] + [%read-count =stats-index] + :: + :: + [%unread-each =stats-index ref=index:graph-store time=@da] + [%read-each =stats-index ref=index:graph-store] + :: + [%read-note time=@da index] + [%unread-note time=@da index] + :: + [%seen-index time=@da =stats-index] + [%remove-graph =resource] + :: + [%read-all ~] + [%set-dnd dnd=?] + [%seen ~] + == + :: + ++ stats-index + $% [%graph graph=resource =index:graph-store] + [%group group=resource] + == + :: + +$ indexed-notification + [index notification] + :: + +$ stats + [notifications=(set [time index]) =unreads last-seen=@da] + :: + +$ unreads + $% [%count num=@ud] + [%each indices=(set index:graph-store)] + == + :: + +$ update + $% action + [%more more=(list update)] + [%added time=@da =index =notification] + [%timebox time=@da archived=? =(list [index notification])] + [%count count=@ud] + [%clear =stats-index] + [%unreads unreads=(list [stats-index stats])] + == + -- -- diff --git a/pkg/arvo/sur/json/rpc.hoon b/pkg/arvo/sur/json/rpc.hoon index 63f5a06f10..1c99b0f0ac 100644 --- a/pkg/arvo/sur/json/rpc.hoon +++ b/pkg/arvo/sur/json/rpc.hoon @@ -1,6 +1,11 @@ :: json-rpc: protocol types :: |% ++$ batch-request + $% [%a p=(list request)] + [%o p=request] + == +:: +$ request $: id=@t jsonrpc=@t diff --git a/pkg/arvo/sur/launch-store.hoon b/pkg/arvo/sur/launch-store.hoon index 8417ab2782..e46c379d55 100644 --- a/pkg/arvo/sur/launch-store.hoon +++ b/pkg/arvo/sur/launch-store.hoon @@ -1,4 +1,14 @@ |% ++$ tiles-0 (map term tile-0) ++$ tile-0 + $: type=tile-type-0 + is-shown=? + == ++$ tile-type-0 + $% [%basic title=cord icon-url=cord linked-url=cord] + [%custom ~] + == +:: +$ tiles (map term tile) +$ tile-ordering (list term) :: @@ -9,7 +19,7 @@ :: +$ tile-type $% [%basic title=cord icon-url=cord linked-url=cord] - [%custom ~] + [%custom linked-url=(unit cord) image=(unit cord)] == :: +$ action diff --git a/pkg/arvo/sur/lens.hoon b/pkg/arvo/sur/lens.hoon index e5f4bae4d4..6a574512a1 100644 --- a/pkg/arvo/sur/lens.hoon +++ b/pkg/arvo/sur/lens.hoon @@ -19,6 +19,7 @@ [%import app=@t base64-jam=@t] [%export-all ~] [%import-all base64-jam=@t] + [%cancel ~] == ++ sink $% [%stdout ~] diff --git a/pkg/arvo/sur/post.hoon b/pkg/arvo/sur/post.hoon index 01b05cdedd..6f4b33c167 100644 --- a/pkg/arvo/sur/post.hoon +++ b/pkg/arvo/sur/post.hoon @@ -1,27 +1,5 @@ /- *resource |% -:: -++ post-zero - |% - :: - +$ content - $% [%text text=cord] - [%mention =ship] - [%url url=cord] - [%code expression=cord output=(list tank)] - [%reference =uid] - == - :: - +$ post - $: author=ship - =index - time-sent=time - contents=(list content) - hash=(unit hash) - =signatures - == - -- - +$ index (list atom) +$ uid [=resource =index] :: @@ -60,4 +38,25 @@ [%code expression=cord output=(list tank)] [%reference =reference] == +:: +++ post-zero + |% + :: + +$ content + $% [%text text=cord] + [%mention =ship] + [%url url=cord] + [%code expression=cord output=(list tank)] + [%reference =uid] + == + :: + +$ post + $: author=ship + =index + time-sent=time + contents=(list content) + hash=(unit hash) + =signatures + == + -- -- diff --git a/pkg/arvo/sys/hoon.hoon b/pkg/arvo/sys/hoon.hoon index 834cbcf390..d84d3a35cf 100644 --- a/pkg/arvo/sys/hoon.hoon +++ b/pkg/arvo/sys/hoon.hoon @@ -5452,12 +5452,14 @@ :::: 4k: atom printing :: ++ co + !: ~% %co ..co ~ =< |_ lot=coin ++ rear |=(rom=tape rend(rep rom)) - ++ rent `@ta`(rap 3 rend) + ++ rent ~+ `@ta`(rap 3 rend) ++ rend ^- tape + ~+ ?: ?=(%blob -.lot) ['~' '0' ((v-co 1) (jam p.lot))] ?: ?=(%many -.lot) @@ -5602,18 +5604,17 @@ |= a=dn ?: ?=([%i *] a) (weld ?:(s.a "inf" "-inf") rep) ?: ?=([%n *] a) (weld "nan" rep) - =/ f=(pair tape @) - %. a.a - %+ ed-co(rep ~) [10 1] - |=([a=? b=@ c=tape] [~(d ne b) ?.(a c ['.' c])]) - =. e.a (sum:si e.a (sun:si (dec q.f))) - =/ res - %+ weld p.f - ?~ e.a - rep - %+ weld ?:((syn:si e.a) "e" "e-") - ((d-co 1) (abs:si e.a)) - ?:(s.a res ['-' res]) + =; rep ?:(s.a rep ['-' rep]) + =/ f ((d-co 1) a.a) + =^ e e.a + =/ e=@s (sun:si (lent f)) + =/ sci :(sum:si e.a e -1) + ?: (syn:si (dif:si e.a --3)) [--1 sci] :: 12000 -> 12e3 e>+2 + ?: !(syn:si (dif:si sci -2)) [--1 sci] :: 0.001 -> 1e-3 e<-2 + [(sum:si sci --1) --0] :: 1.234e2 -> '.'@3 -> 123 .4 + =? rep !=(--0 e.a) + :(weld ?:((syn:si e.a) "e" "e-") ((d-co 1) (abs:si e.a))) + (weld (ed-co e f) rep) :: ++ s-co |= esc=(list @) ^- tape @@ -5659,20 +5660,13 @@ :: - used only for @r* floats :: ++ ed-co - |= [[bas=@ min=@] par=$-([? @ tape] tape)] - =| [fir=? cou=@ud] - |= hol=@ - ^- [tape @] - ?: &(=(0 hol) =(0 min)) - [rep cou] - =/ [dar=@ rad=@] (dvr hol bas) - %= $ - min ?:(=(0 min) 0 (dec min)) - hol dar - rep (par &(=(0 dar) !fir) rad rep) - fir | - cou +(cou) - == + |= [exp=@s int=tape] ^- tape + =/ [pos=? dig=@u] [=(--1 (cmp:si exp --0)) (abs:si exp)] + ?. pos + (into (weld (reap +(dig) '0') int) 1 '.') + =/ len (lent int) + ?: (lth dig len) (into int dig '.') + (weld int (reap (sub dig len) '0')) :: :: +ox-co: format '.'-separated digit sequences in numeric base :: @@ -5965,9 +5959,8 @@ :: ++ spat |=(pax=path (crip (spud pax))) :: render path to cord ++ spud |=(pax=path ~(ram re (smyt pax))) :: render path to tape -++ stab :: parse cord to path - =+ fel=;~(pfix fas (more fas urs:ab)) - |=(zep=@t `path`(rash zep fel)) +++ stab |=(zep=@t `path`(rash zep stap)) :: parse cord to path +++ stap ;~(pfix fas (more fas urs:ab)) :: path parser :: :::: 4n: virtualization :: @@ -6627,7 +6620,7 @@ +$ seminoun :: partial noun; blocked subtrees are ~ :: - $~ [[%full ~] ~] + $~ [[%full / ~ ~] ~] [mask=stencil data=noun] :: :: +stencil: noun knowledge map diff --git a/pkg/arvo/sys/lull.hoon b/pkg/arvo/sys/lull.hoon index c10dc016d8..5d1c9b6f5e 100644 --- a/pkg/arvo/sys/lull.hoon +++ b/pkg/arvo/sys/lull.hoon @@ -762,6 +762,11 @@ her=@p dem=desk cas=case :: source how=germ :: method == :: + $: %fuse :: merge many + des=desk :: target desk + bas=beak :: base desk + con=(list [beak germ]) :: merges + == [%mont pot=term bem=beam] :: mount to unix [%dirk des=desk] :: mark mount dirty [%ogre pot=$@(desk beam)] :: delete mount point @@ -928,6 +933,7 @@ :: /- sur-file :: surface imports from /sur :: /+ lib-file :: library imports from /lib :: /= face /path :: imports built hoon file at path + :: /~ face type /path :: imports built hoon files from directory :: /% face %mark :: imports mark definition from /mar :: /$ face %from %to :: imports mark converter from /mar :: /* face %mark /path :: unbuilt file imports, as mark @@ -936,6 +942,7 @@ $: sur=(list taut) lib=(list taut) raw=(list [face=term =path]) + raz=(list [face=term =spec =path]) maz=(list [face=term =mark]) caz=(list [face=term =mars]) bar=(list [face=term =mark =path]) @@ -955,7 +962,6 @@ $_ ^? |% - ++ bunt *typ ++ diff |~([old=typ new=typ] *dif) ++ form *mark ++ join |~([a=dif b=dif] *(unit (unit dif))) @@ -970,7 +976,6 @@ +$ dais $_ ^| |_ sam=vase - ++ bunt sam ++ diff |~(new=_sam *vase) ++ form *mark ++ join |~([a=vase b=vase] *(unit (unit vase))) @@ -1865,7 +1870,12 @@ [%public-keys =public-keys-result] :: ethereum changes [%turf turf=(list turf)] :: domains == :: - :: +seed: private boot parameters + :: +feed: potential boot parameters + :: + +$ feed + $^ [[%1 ~] who=ship kyz=(list [lyf=life key=ring])] + seed + :: +seed: individual boot parameters :: +$ seed [who=ship lyf=life key=ring sig=(unit oath:pki)] :: diff --git a/pkg/arvo/sys/vane/ames.hoon b/pkg/arvo/sys/vane/ames.hoon index bd267477e4..fb09a2ebea 100644 --- a/pkg/arvo/sys/vane/ames.hoon +++ b/pkg/arvo/sys/vane/ames.hoon @@ -1944,11 +1944,11 @@ =/ =bone bone.shut-packet :: ?: ?=(%& -.meat.shut-packet) - =+ ?~ dud ~ + =+ ?. &(?=(^ dud) msg.veb) ~ %. ~ - %+ slog - leaf+"ames: {} fragment crashed {}" - ?.(msg.veb ~ tang.u.dud) + %- slog + :_ tang.u.dud + leaf+"ames: {} fragment crashed {}" (run-message-sink bone %hear lane shut-packet ?=(~ dud)) :: Just try again on error, printing trace :: @@ -1967,20 +1967,12 @@ ++ on-memo |= [=bone payload=* valence=?(%plea %boon)] ^+ peer-core - :: if we haven't been trying to talk to %live, reset timer - :: - =? last-contact.qos.peer-state - ?& ?=(%live -.qos.peer-state) - %- ~(all by snd.peer-state) - |= =message-pump-state - =(~ live.packet-pump-state.message-pump-state) - == - now - :: =/ =message-blob (dedup-message (jim payload)) =. peer-core (run-message-pump bone %memo message-blob) :: - ?: &(=(%boon valence) ?=(?(%dead %unborn) -.qos.peer-state)) + ?: ?& =(%boon valence) + (gte now (add ~s30 last-contact.qos.peer-state)) + == check-clog peer-core :: +dedup-message: replace with any existing copy of this message @@ -2535,7 +2527,7 @@ ++ assert ^+ message-pump =/ top-live - (peek:packet-queue:*make-packet-pump live.packet-pump-state.state) + (pry:packet-queue:*make-packet-pump live.packet-pump-state.state) ?. |(?=(~ top-live) (lte current.state message-num.key.u.top-live)) ~| [%strange-current current=current.state key.u.top-live] !! @@ -2603,7 +2595,7 @@ =| acc=(unit static-fragment) ^+ [static-fragment=acc live=live.state] :: - %^ (traverse:packet-queue _acc) live.state acc + %^ (dip:packet-queue _acc) live.state acc |= $: acc=_acc key=live-packet-key val=live-packet-val @@ -2681,7 +2673,7 @@ =/ acc resends=*(list static-fragment) :: - %^ (traverse:packet-queue _acc) live.state acc + %^ (dip:packet-queue _acc) live.state acc |= $: acc=_acc key=live-packet-key val=live-packet-val @@ -2734,7 +2726,7 @@ :: ^+ [acc live=live.state] :: - %^ (traverse:packet-queue _acc) live.state acc + %^ (dip:packet-queue _acc) live.state acc |= $: acc=_acc key=live-packet-key val=live-packet-val @@ -2781,7 +2773,7 @@ :: ^+ [metrics=metrics.state live=live.state] :: - %^ (traverse:packet-queue pump-metrics) live.state acc=metrics.state + %^ (dip:packet-queue pump-metrics) live.state acc=metrics.state |= $: metrics=pump-metrics key=live-packet-key val=live-packet-val @@ -2804,10 +2796,10 @@ :: ++ set-wake ^+ packet-pump - :: if nonempty .live, peek at head to get next wake time + :: if nonempty .live, pry at head to get next wake time :: =/ new-wake=(unit @da) - ?~ head=(peek:packet-queue live.state) + ?~ head=(pry:packet-queue live.state) ~ `(next-expiry:gauge u.head) :: no-op if no change diff --git a/pkg/arvo/sys/vane/behn.hoon b/pkg/arvo/sys/vane/behn.hoon index 3933c22e62..afff958eae 100644 --- a/pkg/arvo/sys/vane/behn.hoon +++ b/pkg/arvo/sys/vane/behn.hoon @@ -186,7 +186,7 @@ =* timers timers.state :: if no timers, cancel existing wakeup timer or no-op :: - =/ first=(unit [date=@da *]) (peek:timer-map timers.state) + =/ first=(unit [date=@da *]) (pry:timer-map timers.state) ?~ first ?~ next-wake event-core @@ -351,7 +351,7 @@ [%timers %next ~] :^ ~ ~ %noun !> ^- (unit @da) - (bind (peek:timer-map timers) head) + (bind (pry:timer-map timers) head) :: [%timers @ ~] ?~ til=(slaw %da i.t.tyl) diff --git a/pkg/arvo/sys/vane/clay.hoon b/pkg/arvo/sys/vane/clay.hoon index af2f653037..993ac2703a 100644 --- a/pkg/arvo/sys/vane/clay.hoon +++ b/pkg/arvo/sys/vane/clay.hoon @@ -59,6 +59,12 @@ :: +$ cult (jug wove duct) :: +:: State for ongoing %fuse merges. `con` maintains the ordering, +:: `sto` stores the data needed to merge, and `bas` is the base +:: beak for the merge. +:: ++$ melt [bas=beak con=(list [beak germ]) sto=(map beak (unit dome:clay))] +:: :: Domestic desk state. :: :: Includes subscriber list, dome (desk content), possible commit state (for @@ -69,6 +75,7 @@ dom=dome :: desk state per=regs :: read perms per path pew=regs :: write perms per path + fiz=melt :: state for mega merges == :: :: Desk state. @@ -118,11 +125,11 @@ :: Ford cache :: +$ ford-cache - $: files=(map path [res=vase dez=(set path)]) - naves=(map mark [res=vase dez=(set path)]) - marks=(map mark [res=dais dez=(set path)]) - casts=(map mars [res=vase dez=(set path)]) - tubes=(map mars [res=tube dez=(set path)]) + $: files=(map path [res=vase dez=(set [dir=? =path])]) + naves=(map mark [res=vase dez=(set [dir=? =path])]) + marks=(map mark [res=dais dez=(set [dir=? =path])]) + casts=(map mars [res=vase dez=(set [dir=? =path])]) + tubes=(map mars [res=tube dez=(set [dir=? =path])]) == :: $reef-cache: built system files :: @@ -212,6 +219,7 @@ dom=dome :: revision state per=regs :: read perms per path pew=regs :: write perms per path + fiz=melt :: domestic mega merges == :: :: :: Foreign request manager. @@ -303,6 +311,7 @@ $: %c :: to %clay $> $? %info :: internal edit %merg :: merge desks + %fuse :: merge many %pork :: %warp :: %werp :: @@ -428,18 +437,23 @@ :: ++ an |_ nak=ankh + :: +dug: produce ankh at path + :: + ++ dug + |= =path + ^- (unit ankh) + ?~ path `nak + ?~ kid=(~(get by dir.nak) i.path) + ~ + $(nak u.kid, path t.path) :: +get: produce file at path :: ++ get |= =path ^- (unit cage) - ?~ path - ?~ fil.nak - ~ - `q.u.fil.nak - ?~ kid=(~(get by dir.nak) i.path) - ~ - $(nak u.kid, path t.path) + ?~ nik=(dug path) ~ + ?~ fil.u.nik ~ + `q.u.fil.u.nik -- ++ with-face |=([face=@tas =vase] vase(p [%face face p.vase])) ++ with-faces @@ -472,7 +486,7 @@ +$ state $: baked=(map path cage) cache=ford-cache - stack=(list (set path)) + stack=(list (set [dir=? =path])) cycle=(set build) == +$ args @@ -493,8 +507,8 @@ :: +pop-stack: pop build stack, copying deps downward :: ++ pop-stack - ^- [(set path) _stack.nub] - =^ top=(set path) stack.nub stack.nub + ^- [(set [dir=? =path]) _stack.nub] + =^ top=(set [dir=? =path]) stack.nub stack.nub =? stack.nub ?=(^ stack.nub) stack.nub(i (~(uni in i.stack.nub) top)) [top stack.nub] @@ -559,7 +573,6 @@ =/ dif diff:deg ^- (nave typ dif) |% - ++ bunt +<.cor ++ diff |= [old=typ new=typ] ^- dif @@ -581,7 +594,6 @@ =/ dif _*diff:grad:cor ^- (nave:clay typ dif) |% - ++ bunt +<.cor ++ diff |=([old=typ new=typ] (diff:~(grad cor old) new)) ++ form form:grad:cor ++ join @@ -622,7 +634,6 @@ :_ nub ^- dais |_ sam=vase - ++ bunt (slap nav limb/%bunt) ++ diff |= new=vase (slam (slap nav limb/%diff) (slop sam new)) @@ -649,7 +660,7 @@ |= diff=vase (slam (slap nav limb/%pact) (slop sam diff)) ++ vale - |= =noun + |: noun=q:(slap nav !,(*hoon *vale)) (slam (slap nav limb/%vale) noun/noun) -- :: +build-cast: produce gate to convert mark .a to, statically typed @@ -805,9 +816,11 @@ =^ res=vase nub (run-pile pile) res :: - ++ build-file - |= =path + ++ build-dependency + |= dep=(each [dir=path fil=path] path) ^- [vase state] + =/ =path + ?:(?=(%| -.dep) p.dep fil.p.dep) ~| %error-building^path ?^ got=(~(get by files.cache.nub) path) =? stack.nub ?=(^ stack.nub) @@ -816,7 +829,9 @@ ?: (~(has in cycle.nub) file+path) ~|(cycle+file+path^stack.nub !!) =. cycle.nub (~(put in cycle.nub) file+path) - =. stack.nub [(sy path ~) stack.nub] + =. stack.nub + =- [(sy - ~) stack.nub] + ?:(?=(%| -.dep) dep [& dir.p.dep]) =^ cag=cage nub (read-file path) ?> =(%hoon p.cag) =/ tex=tape (trip !<(@t q.cag)) @@ -826,11 +841,42 @@ =. files.cache.nub (~(put by files.cache.nub) path [res top]) [res nub] :: + ++ build-file + |= =path + (build-dependency |+path) + :: +build-directory: builds files in top level of a directory + :: + :: this excludes files directly at /path/hoon, + :: instead only including files in the unix-style directory at /path, + :: such as /path/file/hoon, but not /path/more/file/hoon. + :: + ++ build-directory + |= =path + ^- [(map @ta vase) state] + =/ fiz=(list @ta) + =/ nuk=(unit _ankh) (~(dug an ankh) path) + ?~ nuk ~ + %+ murn + ~(tap by dir.u.nuk) + |= [nom=@ta nak=_ankh] + ?. ?=([~ [~ *] *] (~(get by dir.nak) %hoon)) ~ + `nom + :: + =| rez=(map @ta vase) + |- + ?~ fiz + [rez nub] + =* nom=@ta i.fiz + =/ pax=^path (weld path nom %hoon ~) + =^ res nub (build-dependency &+[path pax]) + $(fiz t.fiz, rez (~(put by rez) nom res)) + :: ++ run-pile |= =pile =^ sut=vase nub (run-tauts bud %sur sur.pile) =^ sut=vase nub (run-tauts sut %lib lib.pile) =^ sut=vase nub (run-raw sut raw.pile) + =^ sut=vase nub (run-raz sut raz.pile) =^ sut=vase nub (run-maz sut maz.pile) =^ sut=vase nub (run-caz sut caz.pile) =^ sut=vase nub (run-bar sut bar.pile) @@ -869,7 +915,10 @@ (most ;~(plug com gaw) taut-rule) :: %+ rune tis - ;~(plug sym ;~(pfix gap fas (more fas urs:ab))) + ;~(plug sym ;~(pfix gap stap)) + :: + %+ rune sig + ;~((glue gap) sym wyde:vast stap) :: %+ rune cen ;~(plug sym ;~(pfix gap ;~(pfix cen sym))) @@ -885,7 +934,7 @@ ;~ (glue gap) sym ;~(pfix cen sym) - ;~(pfix fas (more fas urs:ab)) + ;~(pfix stap) == :: %+ stag %tssg @@ -931,6 +980,30 @@ =. p.pin [%face face.i.raw p.pin] $(sut (slop pin sut), raw t.raw) :: + ++ run-raz + |= [sut=vase raz=(list [face=term =spec =path])] + ^- [vase state] + ?~ raz [sut nub] + =^ res=(map @ta vase) nub + (build-directory path.i.raz) + =; pin=vase + =. p.pin [%face face.i.raz p.pin] + $(sut (slop pin sut), raz t.raz) + :: + =/ =type (~(play ut p.sut) [%kttr spec.i.raz]) + :: ensure results nest in the specified type, + :: and produce a homogenous map containing that type. + :: + :- %- ~(play ut p.sut) + [%kttr %make [%wing ~[%map]] ~[[%base %atom %ta] spec.i.raz]] + |- + ?~ res ~ + ?. (~(nest ut type) | p.q.n.res) + ~| [%nest-fail path.i.raz p.n.res] + !! + :- [p.n.res q.q.n.res] + [$(res l.res) $(res r.res)] + :: ++ run-maz |= [sut=vase maz=(list [face=term =mark])] ^- [vase state] @@ -1043,12 +1116,12 @@ ~ =/ rus rus:(~(gut by hoy.ruf) her *rung) %+ ~(gut by rus) syd - [lim=~2000.1.1 ref=`*rind qyx=~ dom=*dome per=~ pew=~] + [lim=~2000.1.1 ref=`*rind qyx=~ dom=*dome per=~ pew=~ fiz=*melt] :: administrative duct, domestic +rede :: :+ ~ `hun.rom.ruf =/ jod (~(gut by dos.rom.ruf) syd *dojo) - [lim=now ref=~ [qyx dom per pew]:jod] + [lim=now ref=~ [qyx dom per pew fiz]:jod] :: =* red=rede ->+ |% @@ -1065,7 +1138,7 @@ :: %= ruf hun.rom (need hun) - dos.rom (~(put by dos.rom.ruf) syd [qyx dom per pew]:red) + dos.rom (~(put by dos.rom.ruf) syd [qyx dom per pew fiz]:red) == :: :: Handle `%sing` requests @@ -1256,6 +1329,24 @@ =/ =path [%question desk (scot %ud index) ~] (emit duct %pass wire %a %plea ship %c path `riff-any`[%1 riff]) :: + ++ foreign-capable + |= =rave + |^ + ?- -.rave + %many & + %sing (good-care care.mood.rave) + %next (good-care care.mood.rave) + %mult + %- ~(all in paths.mool.rave) + |= [=care =path] + (good-care care) + == + :: + ++ good-care + |= =care + (~(has in ^~((silt `(list ^care)`~[%u %w %x %y %z]))) care) + -- + :: :: Create a request that cannot be filled immediately. :: :: If it's a local request, we just put in in `qyx`, setting a timer if it's @@ -1275,6 +1366,10 @@ =. rave ?. ?=([%sing %v *] rave) rave [%many %| [%ud let.dom] case.mood.rave path.mood.rave] + :: + ?. (foreign-capable rave) + ~|([%clay-bad-foreign-request-care rave] !!) + :: =+ inx=nix.u.ref =. +>+.$ =< ?>(?=(^ ref) .) @@ -1582,12 +1677,19 @@ :: ++ invalidate |* [key=mold value=mold] - |= [cache=(map key [value dez=(set path)]) invalid=(set path)] - =/ builds=(list [key value dez=(set path)]) ~(tap by cache) + |= [cache=(map key [value dez=(set [dir=? =path])]) invalid=(set path)] + =/ builds=(list [key value dez=(set [dir=? =path])]) + ~(tap by cache) |- ^+ cache ?~ builds ~ - ?: ?=(^ (~(int in dez.i.builds) invalid)) + ?: %- ~(any in dez.i.builds) + |= [dir=? =path] + ?. dir (~(has in invalid) path) + =+ l=(lent path) + %- ~(any in invalid) + |= i=^path + &(=(path (scag l i)) ?=([@ %hoon ~] (slag l i))) $(builds t.builds) (~(put by $(builds t.builds)) i.builds) :: @@ -1962,32 +2064,178 @@ =/ =wire /merge/[syd]/(scot %p ali-ship)/[ali-desk]/[germ] (emit hen %pass wire %c %warp ali-ship ali-desk `[%sing %v case /]) :: + ++ make-melt + |= [bas=beak con=(list [beak germ])] + ^- melt + :+ bas con + %- ~(gas by *(map beak (unit dome:clay))) + :- [bas *(unit dome:clay)] + (turn con |=(a=[beak germ] [-.a *(unit dome:clay)])) + :: + ++ start-fuse + |= [bas=beak con=(list [beak germ])] + ^+ ..start-fuse + =/ moves=(list move) + %+ turn + [[bas *germ] con] + |= [bec=beak germ] + ^- move + =/ wir=wire /fuse/[syd]/(scot %p p.bec)/[q.bec]/(scot r.bec) + [hen %pass wir %c %warp p.bec q.bec `[%sing %v r.bec /]] + :: + :: We also want to clear the state (fiz) associated with this + :: merge and print a warning if it's non trivial i.e. we're + :: starting a new fuse before the previous one terminated. + :: + =/ err=tang + ?~ con.fiz + ~ + =/ discarded=tang + %+ turn + ~(tap in sto.fiz) + |= [k=beak v=(unit dome:clay)] + ^- tank + =/ received=tape ?~(v "missing" "received") + leaf+"{} {received}" + :_ discarded + leaf+"fusing into {} from {} {} - overwriting prior fuse" + =. fiz (make-melt bas con) + ((slog err) (emil moves)) + :: + ++ take-fuse + |^ + :: + |= [bec=beak =riot] + ^+ ..take-fuse + ?~ riot + :: + :: By setting fiz to *melt the merge is aborted - any further + :: responses we get for the merge will cause take-fuse to crash + :: + =. fiz *melt + ((slog [leaf+"clay: fuse failed, missing {}"]~) ..take-fuse) + ?> (~(has by sto.fiz) bec) + =. fiz + :+ bas.fiz con.fiz + (~(put by sto.fiz) bec `!<(dome:clay q.r.u.riot)) + =/ all-done=flag + %- ~(all by sto.fiz) + |= res=(unit dome:clay) + ^- flag + !=(res ~) + ?. all-done + ..take-fuse + =| rag=rang + =/ clean-state ..take-fuse + =/ initial-dome=dome:clay (need (~(got by sto.fiz) bas.fiz)) + =/ continuation-yaki=yaki + (~(got by hut.ran) (~(got by hit.initial-dome) let.initial-dome)) + =/ parents=(list tako) ~[(~(got by hit.initial-dome) let.initial-dome)] + =/ merges con.fiz + |- + ^+ ..take-fuse + ?~ merges + =/ t=tang [leaf+"{} fused from {} {}" ~] + =. ..take-fuse (done-fuse clean-state %& ~) + (park | [%| continuation-yaki(p (flop parents))] rag) + =/ [bec=beak g=germ] i.merges + =/ ali-dom=dome:clay (need (~(got by sto.fiz) bec)) + =/ result (merge-helper p.bec q.bec g ali-dom `continuation-yaki) + ?- -.result + %| + (done-fuse clean-state %| %fuse-merge-failed p.result) + :: + %& + =/ merge-result=(unit merge-result) +.result + ?~ merge-result + :: + :: This merge was a no-op, just continue + :: + $(merges t.merges) + ?^ conflicts.u.merge-result + :: + :: If there are merge conflicts send the error and abort the merge + :: + (done-fuse clean-state %& conflicts.u.merge-result) + =/ merged-yaki=yaki + ?- -.new.u.merge-result + %| + +.new.u.merge-result + :: + %& + :: + :: Convert the yuki to yaki + :: + =/ yuk=yuki +.new.u.merge-result + =/ lobes=(map path lobe) + %- ~(run by q.yuk) + |= val=(each page lobe) + ^- lobe + ?- -.val + %& (page-to-lobe +.val) + %| +.val + == + (make-yaki p.yuk lobes now) + == + %= $ + continuation-yaki merged-yaki + merges t.merges + hut.ran (~(put by hut.ran) r.merged-yaki merged-yaki) + lat.rag (~(uni by lat.rag) lat.u.merge-result) + parents [(~(got by hit.ali-dom) let.ali-dom) parents] + == + == + :: +done-fuse: restore state after a fuse is attempted, whether it + :: succeeds or fails. + :: + ++ done-fuse + |= [to-restore=_..take-fuse result=(each (set path) (pair term tang))] + ^+ ..take-fuse + =. fiz.to-restore *melt + (done:to-restore result) + -- + :: + ++ done + |= result=(each (set path) (pair term tang)) + ^+ ..merge + (emit hen %give %mere result) + :: ++ merge |= [=ali=ship =ali=desk =germ =riot] ^+ ..merge - |^ ?~ riot - (done %| %ali-unavailable >[ali-ship ali-desk germ]< ~) + (done %| %ali-unavailable ~[>[ali-ship ali-desk germ]<]) =/ ali-dome=dome:clay !<(dome:clay q.r.u.riot) + =/ result=(each (unit merge-result) (pair term tang)) + (merge-helper ali-ship ali-desk germ ali-dome ~) + ?- -.result + %| + (done %| +.result) + :: + %& + =/ mr=(unit merge-result) +.result + ?~ mr + (done %& ~) + =. ..merge (done %& conflicts.u.mr) + (park | new.u.mr ~ lat.u.mr) + == + :: + +$ merge-result [conflicts=(set path) new=yoki lat=(map lobe blob)] + :: + ++ merge-helper + |= [=ali=ship =ali=desk =germ ali-dome=dome:clay continuation-yaki=(unit yaki)] + ^- (each (unit merge-result) [term tang]) + |^ + ^- (each (unit merge-result) [term tang]) =/ ali-yaki=yaki (~(got by hut.ran) (~(got by hit.ali-dome) let.ali-dome)) =/ bob-yaki=(unit yaki) - ?~ let.dom - ~ - (~(get by hut.ran) (~(got by hit.dom) let.dom)) - =/ merge-result (merge-by-germ ali-yaki bob-yaki) - ?: ?=(%| -.merge-result) - (done %| p.merge-result) - ?~ p.merge-result - (done %& ~) - =. ..merge (done %& conflicts.u.p.merge-result) - (park | new.u.p.merge-result ~ lat.u.p.merge-result) + ?~ continuation-yaki + ?~ let.dom + ~ + (~(get by hut.ran) (~(got by hit.dom) let.dom)) + continuation-yaki + (merge-by-germ ali-yaki bob-yaki) :: - ++ done - |= result=(each (set path) (pair term tang)) - ^+ ..merge - (emit hen %give %mere result) - :: - +$ merge-result [conflicts=(set path) new=yoki lat=(map lobe blob)] ++ merge-by-germ |= [=ali=yaki bob-yaki=(unit yaki)] ^- (each (unit merge-result) [term tang]) @@ -2005,16 +2253,13 @@ ?- germ :: :: If this is a %only-this merge, we check to see if ali's and bob's - :: commits are the same, in which case we're done. Otherwise, we - :: check to see if ali's commit is in the ancestry of bob's, in - :: which case we're done. Otherwise, we create a new commit with - :: bob's data plus ali and bob as parents. + :: commits are the same, in which case we're done. + :: Otherwise, we create a new commit with bob's data plus ali and + :: bob as parents. :: %only-this ?: =(r.ali-yaki r.bob-yaki) &+~ - ?: (~(has in (reachable-takos:ze r.bob-yaki)) r.ali-yaki) - &+~ :* %& ~ conflicts=~ new=&+[[r.bob-yaki r.ali-yaki ~] (to-yuki q.bob-yaki)] @@ -2042,8 +2287,6 @@ %take-this ?: =(r.ali-yaki r.bob-yaki) &+~ - ?: (~(has in (reachable-takos:ze r.bob-yaki)) r.ali-yaki) - &+~ =/ new-data (~(uni by q.ali-yaki) q.bob-yaki) :* %& ~ conflicts=~ @@ -2313,7 +2556,7 @@ =+ (slag (dec (lent path)) path) ?~(- %$ i.-) =/ =dais (get-dais mark) - =/ res=(unit (unit vase)) (~(join dais bunt:dais) q.cal q.cob) + =/ res=(unit (unit vase)) (~(join dais *vale:dais) q.cal q.cob) ?~ res `[form:dais q.cob] ?~ u.res @@ -2665,6 +2908,9 @@ ++ start-request |= [for=(unit [ship @ud]) rav=rave] ^+ ..start-request + ?: &(?=(^ for) !(foreign-capable rav)) + ~& [%bad-foreign-request-care from=for rav] + ..start-request =^ [new-sub=(unit rove) sub-results=(list sub-result)] fod.dom (try-fill-sub for (rave-to-rove rav)) =. ..start-request (send-sub-results sub-results [hen ~ ~]) @@ -2721,14 +2967,23 @@ %r ~| %no-cages-please-they-are-just-way-too-big !! %s ~| %please-dont-get-your-takos-over-a-network !! %t ~| %requesting-foreign-directory-is-vaporware !! - %u ~| %prolly-poor-idea-to-get-rang-over-network !! %v ~| %weird-shouldnt-get-v-request-from-network !! - %z `(validate-z r.rand) + %u `(validate-u r.rand) %w `(validate-w r.rand) %x (validate-x [p.p q.p q r]:rand) %y `[p.r.rand !>(;;(arch q.r.rand))] + %z `(validate-z r.rand) == :: + :: Make sure the incoming data is a %u response + :: + ++ validate-u + |= =page + ^- cage + ?> ?=(%flag p.page) + :- p.page + !> ;;(? q.page) + :: :: Make sure the incoming data is a %w response :: ++ validate-w @@ -2749,7 +3004,11 @@ =/ vale-result %- mule |. %- wrap:fusion - (page-to-cage:(ford:fusion static-ford-args) peg) + :: Use %home's marks to validate, so we don't have to build the + :: foreign hoon/zuse + :: + =/ args %*(static-ford-args . dom dom:(~(got by dos.rom) %home)) + (page-to-cage:(ford:fusion args) peg) ?: ?=(%| -.vale-result) %- (slog >%validate-x-failed< p.vale-result) ~ @@ -2762,7 +3021,7 @@ ^- cage ?> ?=(%uvi p.page) :- p.page - !>(;;(@uvI q.page)) + !> ;;(@uvI q.page) -- :: :: Respond to backfill request @@ -3391,12 +3650,29 @@ |- ?: =(b let.dom) hit.dom + :: del everything after b $(hit.dom (~(del by hit.dom) let.dom), let.dom (dec let.dom)) b ?: =(0 b) [~ ~] - (data-twixt-takos =(0 ver) (~(get by hit.dom) a) (aeon-to-tako b)) - :: + =/ excludes=(set tako) + =| acc=(set tako) + =/ lower=@ud 1 + |- + :: a should be excluded, so wait until we're past it + ?: =(lower +(a)) + acc + =/ res=(set tako) (reachable-takos (~(got by hit.dom) lower)) + $(acc (~(uni in acc) res), lower +(lower)) + =/ includes=(set tako) + =| acc=(set tako) + =/ upper=@ud b + |- + ?: =(upper a) + acc + =/ res=(set tako) (reachable-takos (~(got by hit.dom) upper)) + $(acc (~(uni in acc) res), upper (dec upper)) + [(~(run in (~(dif in includes) excludes)) tako-to-yaki) ~] :: Traverse parentage and find all ancestor hashes :: ++ reachable-takos :: reachable @@ -3415,30 +3691,6 @@ =. s ^$(p i.p.y) $(p.y t.p.y) :: - :: Gets the data between two commit hashes, assuming the first is an - :: ancestor of the second. - :: - :: Get all the takos before `a`, then get all takos before `b` except the - :: ones we found before `a`. Then convert the takos to yakis and also get - :: all the data in all the yakis. - :: - :: What happens if you run an %init merge on a desk that already - :: had a commit? - :: - ++ data-twixt-takos - |= [plops=? a=(unit tako) b=tako] - ^- [(set yaki) (set plop)] - =+ old=?~(a ~ (reachable-takos u.a)) - =/ yal=(set tako) - %- silt - %+ skip - ~(tap in (reachable-takos b)) - |=(tak=tako (~(has in old) tak)) - :- (silt (turn ~(tap in yal) tako-to-yaki)) - ?. plops - ~ - (silt (turn ~(tap in (new-lobes (new-lobes ~ old) yal)) lobe-to-blob)) - :: :: Get all the lobes that are referenced in `a` except those that are :: already in `b`. :: @@ -3528,11 +3780,11 @@ [[~ ~] fod.dom] =/ cached=(unit [=vase *]) (~(get by naves.fod.dom) i.path) ?^ cached - :_(fod.dom [~ ~ %& %nave !>(vase.u.cached)]) + :_(fod.dom [~ ~ %& %nave vase.u.cached]) =^ =vase fod.dom %- wrap:fusion (build-nave:(ford:fusion static-ford-args) i.path) - :_(fod.dom [~ ~ %& %nave !>(vase)]) + :_(fod.dom [~ ~ %& %nave vase]) :: ++ read-f !. @@ -3958,12 +4210,14 @@ :: :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: =| :: instrument state - $: ver=%7 :: vane version + $: ver=%8 :: vane version ruf=raft :: revision tree == :: |= [now=@da eny=@uvJ rof=roof] :: current invocation +~% %clay-top ..part ~ |% :: ++ call :: handle request + ~/ %clay-call |= $: hen=duct dud=(unit goof) wrapped-task=(hobo task) @@ -4074,6 +4328,14 @@ =/ den ((de now rof hen ruf) our des.req) abet:(start-merge:den her.req dem.req cas.req how.req) [mos ..^$] + :: + %fuse + ?: =(%$ des.req) + ~&(%fuse-no-desk !!) + =^ mos ruf + =/ den ((de now rof hen ruf) our des.req) + abet:(start-fuse:den bas.req con.req) + [mos ..^$] :: %mont =. hez.ruf ?^(hez.ruf hez.ruf `[[%$ %sync ~] ~]) @@ -4202,11 +4464,41 @@ ++ load => |% +$ raft-any - $% [%7 raft-7] + $% [%8 raft-8] + [%7 raft-7] [%6 raft-6] == - +$ raft-7 raft - +$ dojo-7 dojo + +$ raft-8 raft + +$ raft-7 + $: rom=room-7 + hoy=(map ship rung-7) + ran=rang + mon=(map term beam) + hez=(unit duct) + cez=(map @ta crew) + pud=(unit [=desk =yoki]) + == + +$ room-7 + $: hun=duct + dos=(map desk dojo-7) + == + +$ rung-7 + $: rus=(map desk rede-7) + == + +$ dojo-7 + $: qyx=cult + dom=dome + per=regs + pew=regs + == + +$ rede-7 + $: lim=@da + ref=(unit rind) + qyx=cult + dom=dome + per=regs + pew=regs + == +$ ford-cache-7 ford-cache +$ raft-6 $: rom=room-6 :: domestic @@ -4249,7 +4541,8 @@ |= old=raft-any |^ =? old ?=(%6 -.old) 7+(raft-6-to-7 +.old) - ?> ?=(%7 -.old) + =? old ?=(%7 -.old) 8+(raft-7-to-8 +.old) + ?> ?=(%8 -.old) ..^^$(ruf +.old) :: +raft-6-to-7: delete stale ford caches (they could all be invalid) :: @@ -4270,9 +4563,30 @@ |= =rede-6 rede-6(dom dom.rede-6(fod *ford-cache-7)) == + :: +raft-7-to-8: create bunted melts in each dojo/rede + :: + ++ raft-7-to-8 + |= raf=raft-7 + ^- raft-8 + %= raf + dos.rom + %- ~(run by dos.rom.raf) + |= doj=dojo-7 + ^- dojo + [qyx.doj dom.doj per.doj pew.doj *melt] + :: + hoy + %- ~(run by hoy.raf) + |= =rung-7 + %- ~(run by rus.rung-7) + |= r=rede-7 + ^- rede + [lim.r ref.r qyx.r dom.r per.r pew.r *melt] + == -- :: ++ scry :: inspect + ~/ %clay-scry ^- roon |= [lyc=gang car=term bem=beam] ^- (unit (unit cage)) @@ -4334,6 +4648,7 @@ == :: ++ take :: accept response + ~/ %clay-take |= [tea=wire hen=duct dud=(unit goof) hin=sign] ^+ [*(list move) ..^$] ?^ dud @@ -4350,6 +4665,18 @@ abet:(merge:den ali-ship ali-desk germ p.hin) [mos ..^$] :: + ?: ?=([%fuse @ @ @ @ ~] tea) + ?> ?=(%writ +<.hin) + =* syd i.t.tea + =/ ali-ship=@p (slav %p i.t.t.tea) + =* ali-desk=desk i.t.t.t.tea + =/ ali-case (rash i.t.t.t.t.tea nuck:so) + ?> ?=([%$ *] ali-case) + =^ mos ruf + =/ den ((de now rof hen ruf) our i.t.tea) + abet:(take-fuse:den [ali-ship ali-desk (case +.ali-case)] p.hin) + [mos ..^$] + :: ?: ?=([%foreign-warp *] tea) ?> ?=(%writ +<.hin) :_ ..^$ diff --git a/pkg/arvo/sys/vane/eyre.hoon b/pkg/arvo/sys/vane/eyre.hoon index 998bd659a3..8072cbd507 100644 --- a/pkg/arvo/sys/vane/eyre.hoon +++ b/pkg/arvo/sys/vane/eyre.hoon @@ -215,7 +215,7 @@ ?: =('subscribe' u.maybe-key) %. item %+ pe %subscribe - (ot id+ni ship+(su fed:ag) app+so path+(su ;~(pfix fas (more fas urs:ab))) ~) + (ot id+ni ship+(su fed:ag) app+so path+(su stap) ~) ?: =('unsubscribe' u.maybe-key) %. item %+ pe %unsubscribe @@ -426,10 +426,12 @@ :- ~ %- as-octs:mimes:html %- crip - %- zing + %- zing ^- ^wall + %- zing ^- (list ^wall) %+ turn wall |= t=tape - "{t}\0a" + ^- ^wall + ~[t "\0a"] :: +internal-server-error: 500 page, with a tang :: ++ internal-server-error @@ -1598,6 +1600,7 @@ :: +channel-event-to-sign: attempt to recover a sign from a channel-event :: ++ channel-event-to-sign + ~% %eyre-channel-event-to-sign ..part ~ |= event=channel-event ^- (unit sign:agent:gall) ?. ?=(%fact -.event) `event @@ -1678,6 +1681,7 @@ == :: ++ event-json-to-wall + ~% %eyre-json-to-wall ..part ~ |= [event-id=@ud =json] ^- wall :~ (weld "id: " (format-ud-as-integer event-id)) @@ -2095,6 +2099,7 @@ ~% %http-server ..part ~ |% ++ call + ~/ %eyre-call |= [=duct dud=(unit goof) wrapped-task=(hobo task)] ^- [(list move) _http-server-gate] :: @@ -2297,6 +2302,7 @@ == :: ++ take + ~/ %eyre-take |= [=wire =duct dud=(unit goof) =sign] ^- [(list move) _http-server-gate] ?^ dud @@ -2484,6 +2490,7 @@ :: +scry: request a path in the urbit namespace :: ++ scry + ~/ %eyre-scry ^- roon |= [lyc=gang car=term bem=beam] ^- (unit (unit cage)) diff --git a/pkg/arvo/sys/vane/gall.hoon b/pkg/arvo/sys/vane/gall.hoon index a481307874..690fa615eb 100644 --- a/pkg/arvo/sys/vane/gall.hoon +++ b/pkg/arvo/sys/vane/gall.hoon @@ -646,6 +646,7 @@ :: cleared queue in +load 3-to-4 or +load-4-to-5 :: =? stand ?=(~ stand) + ~& [%gall-missing wire hen] (~(put to *(qeu remote-request)) %missing) ~| [full-wire=full-wire hen=hen stand=stand] =^ rr stand ~(get to stand) diff --git a/pkg/arvo/sys/vane/jael.hoon b/pkg/arvo/sys/vane/jael.hoon index 5edcebcfce..20a0555770 100644 --- a/pkg/arvo/sys/vane/jael.hoon +++ b/pkg/arvo/sys/vane/jael.hoon @@ -434,7 +434,7 @@ %- curd =< abet (private-keys:~(feel su hen now pki etn) life.tac ring.tac) :: - :: update private keys + :: register moon keys :: %moon ?. =(%earl (clan:title ship.tac)) @@ -717,6 +717,14 @@ =/ a-point=point (~(gut by pos.zim.pki) ship.i.udiffs *point) =/ a-diff=(unit diff:point) (udiff-to-diff:point udiff.i.udiffs a-point) =? this-su ?=(^ a-diff) + :: if this about our keys, and we already know these, start using them + :: + =? lyf.own + ?& =(our ship.i.udiffs) + ?=(%keys -.u.a-diff) + (~(has by jaw.own) life.to.u.a-diff) + == + life.to.u.a-diff (public-keys:feel original-pos %diff ship.i.udiffs u.a-diff) $(udiffs t.udiffs) :: @@ -926,7 +934,16 @@ ^+ ..feel ?: &(=(lyf.own life) =((~(get by jaw.own) life) `ring)) ..feel - =. lyf.own life + :: only eagerly update lyf if we were behind the chain life + :: + =? lyf.own + ?& (gth life lyf.own) + :: + =+ pon=(~(get by pos.zim) our) + ?~ pon | + (lth lyf.own life.u.pon) + == + life =. jaw.own (~(put by jaw.own) life ring) (exec yen.own [%give %private-keys lyf.own jaw.own]) :: diff --git a/pkg/arvo/sys/zuse.hoon b/pkg/arvo/sys/zuse.hoon index eaf098937b..168c15863a 100644 --- a/pkg/arvo/sys/zuse.hoon +++ b/pkg/arvo/sys/zuse.hoon @@ -3286,7 +3286,7 @@ ++ ship :: string from ship |= a=^ship ^- json - (tape (slag 1 (scow %p a))) + [%n (rap 3 '"' (rsh [3 1] (scot %p a)) '"' ~)] :: :: ++numb:enjs:format ++ numb :: number from unsigned |= a=@u @@ -3458,7 +3458,7 @@ [(rash a fel) b] :: :: ++pa:dejs:format ++ pa :: string as path - (su ;~(pfix fas (more fas urs:ab))) + (su stap) :: :: ++pe:dejs:format ++ pe :: prefix |* [pre=* wit=fist] @@ -5070,36 +5070,54 @@ |= ord=$-([key key] ?) |= a=* =/ b ;;((tree [key=key val=value]) a) - ?> (check-balance:((ordered-map key value) ord) b) + ?> (apt:((on key value) ord) b) b :: -:: $mk-item: constructor for +ordered-map item type :: -++ mk-item |$ [key val] [key=key val=val] -:: +ordered-map: treap with user-specified horizontal order -:: -:: Conceptually smaller items go on the left, so the item with the -:: smallest key can be popped off the head. If $key is `@` and -:: .compare is +lte, then the numerically smallest item is the head. +++ ordered-map on +:: +on: treap with user-specified horizontal order, ordered-map :: :: WARNING: ordered-map will not work properly if two keys can be :: unequal under noun equality but equal via the compare gate :: -++ ordered-map +++ on + ~/ %on |* [key=mold val=mold] => |% - +$ item (mk-item key val) + +$ item [key=key val=val] -- :: +compare: item comparator for horizontal order :: + ~% %comp +>+ ~ |= compare=$-([key key] ?) + ~% %core + ~ |% - :: +check-balance: verify horizontal and vertical orderings + :: +all: apply logical AND boolean test on all values :: - ++ check-balance - =| [l=(unit key) r=(unit key)] - |= a=(tree item) + ++ all + ~/ %all + |= [a=(tree item) b=$-(item ?)] ^- ? + |- + ?~ a + & + ?&((b n.a) $(a l.a) $(a r.a)) + :: +any: apply logical OR boolean test on all values + :: + ++ any + ~/ %any + |= [a=(tree item) b=$-(item ?)] + |- ^- ? + ?~ a + | + ?|((b n.a) $(a l.a) $(a r.a)) + :: +apt: verify horizontal and vertical orderings + :: + ++ apt + ~/ %apt + |= a=(tree item) + =| [l=(unit key) r=(unit key)] + |- ^- ? :: empty tree is valid :: ?~ a %.y @@ -5122,64 +5140,22 @@ :: ?~(r.a %.y &((mor key.n.a key.n.r.a) $(a r.a, r `key.n.a))) == - :: +put: ordered item insert + :: +bap: convert to list, right to left :: - ++ put - |= [a=(tree item) =key =val] - ^- (tree item) - :: base case: replace null with single-item tree - :: - ?~ a [n=[key val] l=~ r=~] - :: base case: overwrite existing .key with new .val - :: - ?: =(key.n.a key) a(val.n val) - :: if item goes on left, recurse left then rebalance vertical order - :: - ?: (compare key key.n.a) - =/ l $(a l.a) - ?> ?=(^ l) - ?: (mor key.n.a key.n.l) - a(l l) - l(r a(l r.l)) - :: item goes on right; recurse right then rebalance vertical order - :: - =/ r $(a r.a) - ?> ?=(^ r) - ?: (mor key.n.a key.n.r) - a(r r) - r(l a(r l.r)) - :: +peek: produce head (smallest item) or null - :: - ++ peek + ++ bap + ~/ %bap |= a=(tree item) - ^- (unit item) - :: - ?~ a ~ - ?~ l.a `n.a - $(a l.a) - :: - :: +pop: produce .head (smallest item) and .rest or crash if empty - :: - ++ pop - |= a=(tree item) - ^- [head=item rest=(tree item)] - :: - ?~ a !! - ?~ l.a [n.a r.a] - :: - =/ l $(a l.a) - :- head.l - :: load .rest.l back into .a and rebalance - :: - ?: |(?=(~ rest.l) (mor key.n.a key.n.rest.l)) - a(l rest.l) - rest.l(r a(r r.rest.l)) + ^- (list item) + =| b=(list item) + |- ^+ b + ?~ a b + $(a r.a, b [n.a $(a l.a)]) :: +del: delete .key from .a if it exists, producing value iff deleted :: ++ del + ~/ %del |= [a=(tree item) =key] ^- [(unit val) (tree item)] - :: ?~ a [~ ~] :: we found .key at the root; delete and rebalance :: @@ -5192,30 +5168,15 @@ [found a(l lef)] =+ [found rig]=$(a r.a) [found a(r rig)] - :: +nip: remove root; for internal use - :: - ++ nip - |= a=(tree item) - ^- (tree item) - :: - ?> ?=(^ a) - :: delete .n.a; merge and balance .l.a and .r.a - :: - |- ^- (tree item) - ?~ l.a r.a - ?~ r.a l.a - ?: (mor key.n.l.a key.n.r.a) - l.a(r $(l.a r.l.a)) - r.a(l $(r.a l.r.a)) - :: +traverse: stateful partial inorder traversal + :: +dip: stateful partial inorder traversal :: :: Mutates .state on each run of .f. Starts at .start key, or if - :: .start is ~, starts at the head (item with smallest key). Stops - :: when .f produces .stop=%.y. Traverses from smaller to larger - :: keys. Each run of .f can replace an item's value or delete the - :: item. + :: .start is ~, starts at the head. Stops when .f produces .stop=%.y. + :: Traverses from left to right keys. + :: Each run of .f can replace an item's value or delete the item. :: - ++ traverse + ++ dip + ~/ %dip |* state=mold |= $: a=(tree item) =state @@ -5274,63 +5235,18 @@ =/ rig main(a r.a) rig(a a(r a.rig)) -- - :: +tap: convert to list, smallest to largest - :: - ++ tap - |= a=(tree item) - ^- (list item) - :: - =| b=(list item) - |- ^+ b - ?~ a b - :: - $(a l.a, b [n.a $(a r.a)]) - :: +bap: convert to list, largest to smallest - :: - ++ bap - |= a=(tree item) - ^- (list item) - :: - =| b=(list item) - |- ^+ b - ?~ a b - :: - $(a r.a, b [n.a $(a l.a)]) :: +gas: put a list of items :: ++ gas + ~/ %gas |= [a=(tree item) b=(list item)] ^- (tree item) - :: ?~ b a $(b t.b, a (put a i.b)) - :: +uni: unify two ordered maps - :: - :: .b takes precedence over .a if keys overlap. - :: - ++ uni - |= [a=(tree item) b=(tree item)] - ^- (tree item) - :: - ?~ b a - ?~ a b - ?: =(key.n.a key.n.b) - :: - [n=n.b l=$(a l.a, b l.b) r=$(a r.a, b r.b)] - :: - ?: (mor key.n.a key.n.b) - :: - ?: (compare key.n.b key.n.a) - $(l.a $(a l.a, r.b ~), b r.b) - $(r.a $(a r.a, l.b ~), b l.b) - :: - ?: (compare key.n.a key.n.b) - $(l.b $(b l.b, r.a ~), a r.a) - $(r.b $(b r.b, l.a ~), a l.a) - :: :: +get: get val at key or return ~ :: ++ get + ~/ %get |= [a=(tree item) b=key] ^- (unit val) ?~ a ~ @@ -5339,11 +5255,24 @@ ?: (compare b key.n.a) $(a l.a) $(a r.a) + :: +got: need value at key :: - :: +subset: take a range excluding start and/or end and all elements + ++ got + |= [a=(tree item) b=key] + ^- val + (need (get a b)) + :: +has: check for key existence + :: + ++ has + ~/ %has + |= [a=(tree item) b=key] + ^- ? + !=(~ (get a b)) + :: +lot: take a subset range excluding start and/or end and all elements :: outside the range :: - ++ subset + ++ lot + ~/ %lot |= $: tre=(tree item) start=(unit key) end=(unit key) @@ -5389,6 +5318,154 @@ $(a (nip a(r ~))) == -- + :: +nip: remove root; for internal use + :: + ++ nip + ~/ %nip + |= a=(tree item) + ^- (tree item) + ?> ?=(^ a) + :: delete .n.a; merge and balance .l.a and .r.a + :: + |- ^- (tree item) + ?~ l.a r.a + ?~ r.a l.a + ?: (mor key.n.l.a key.n.r.a) + l.a(r $(l.a r.l.a)) + r.a(l $(r.a l.r.a)) + :: + :: +pop: produce .head (leftmost item) and .rest or crash if empty + :: + ++ pop + ~/ %pop + |= a=(tree item) + ^- [head=item rest=(tree item)] + ?~ a !! + ?~ l.a [n.a r.a] + =/ l $(a l.a) + :- head.l + :: load .rest.l back into .a and rebalance + :: + ?: |(?=(~ rest.l) (mor key.n.a key.n.rest.l)) + a(l rest.l) + rest.l(r a(r r.rest.l)) + :: +pry: produce head (leftmost item) or null + :: + ++ pry + ~/ %pry + |= a=(tree item) + ^- (unit item) + ?~ a ~ + |- + ?~ l.a `n.a + $(a l.a) + :: +put: ordered item insert + :: + ++ put + ~/ %put + |= [a=(tree item) =key =val] + ^- (tree item) + :: base case: replace null with single-item tree + :: + ?~ a [n=[key val] l=~ r=~] + :: base case: overwrite existing .key with new .val + :: + ?: =(key.n.a key) a(val.n val) + :: if item goes on left, recurse left then rebalance vertical order + :: + ?: (compare key key.n.a) + =/ l $(a l.a) + ?> ?=(^ l) + ?: (mor key.n.a key.n.l) + a(l l) + l(r a(l r.l)) + :: item goes on right; recurse right then rebalance vertical order + :: + =/ r $(a r.a) + ?> ?=(^ r) + ?: (mor key.n.a key.n.r) + a(r r) + r(l a(r l.r)) + :: +ram: produce tail (rightmost item) or null + :: + ++ ram + ~/ %ram + |= a=(tree item) + ^- (unit item) + ?~ a ~ + |- + ?~ r.a `n.a + $(a r.a) + :: +run: apply gate to transform all values in place + :: + ++ run + ~/ %run + |* [a=(tree item) b=$-(val *)] + |- + ?~ a a + [n=[key.n.a (b val.n.a)] l=$(a l.a) r=$(a r.a)] + :: +tab: tabulate a subset excluding start element with a max count + :: + ++ tab + ~/ %tab + |= [a=(tree item) b=(unit key) c=@] + ^- (list item) + |^ + (flop e:(tabulate (del-span a b) b c)) + :: + ++ tabulate + |= [a=(tree item) b=(unit key) c=@] + ^- [d=@ e=(list item)] + ?: ?&(?=(~ b) =(c 0)) + [0 ~] + =| f=[d=@ e=(list item)] + |- ^+ f + ?: ?|(?=(~ a) =(d.f c)) f + =. f $(a l.a) + ?: =(d.f c) f + =. f [+(d.f) [n.a e.f]] + ?:(=(d.f c) f $(a r.a)) + :: + ++ del-span + |= [a=(tree item) b=(unit key)] + ^- (tree item) + ?~ a a + ?~ b a + ?: =(key.n.a u.b) + r.a + ?: (compare key.n.a u.b) + $(a r.a) + a(l $(a l.a)) + -- + :: +tap: convert to list, left to right + :: + ++ tap + ~/ %tap + |= a=(tree item) + ^- (list item) + =| b=(list item) + |- ^+ b + ?~ a b + $(a l.a, b [n.a $(a r.a)]) + :: +uni: unify two ordered maps + :: + :: .b takes precedence over .a if keys overlap. + :: + ++ uni + ~/ %uni + |= [a=(tree item) b=(tree item)] + ^- (tree item) + ?~ b a + ?~ a b + ?: =(key.n.a key.n.b) + [n=n.b l=$(a l.a, b l.b) r=$(a r.a, b r.b)] + ?: (mor key.n.a key.n.b) + ?: (compare key.n.b key.n.a) + $(l.a $(a l.a, r.b ~), b r.b) + $(r.a $(a r.a, l.b ~), b l.b) + ?: (compare key.n.a key.n.b) + $(l.b $(b l.b, r.a ~), a r.a) + $(r.b $(b r.b, l.a ~), a l.a) -- :: :: :::: ++userlib :: (2u) non-vane utils @@ -5535,7 +5612,8 @@ :: :: ++unm:chrono:userlib ++ unm :: Urbit to Unix ms |= a=@da - (div (mul (sub a ~1970.1.1) 1.000) ~s1) + =- (div (mul - 1.000) ~s1) + (sub (add a (div ~s1 2.000)) ~1970.1.1) :: :: ++unt:chrono:userlib ++ unt :: Urbit to Unix time |= a=@da diff --git a/pkg/arvo/ted/aggregator/nonce.hoon b/pkg/arvo/ted/aggregator/nonce.hoon new file mode 100644 index 0000000000..311e1e9d59 --- /dev/null +++ b/pkg/arvo/ted/aggregator/nonce.hoon @@ -0,0 +1,15 @@ +:: aggregator/nonce: get next nonce +:: +/- rpc=json-rpc +/+ ethereum, ethio, strandio +:: +|= args=vase +=+ !<([endpoint=@t pk=@] args) +=/ m (strand:strandio ,vase) +^- form:m +:: +=/ =address:ethereum + (address-from-prv:key:ethereum pk) +;< expected-nonce=@ud bind:m + (get-next-nonce:ethio endpoint address) +(pure:m !>(expected-nonce)) diff --git a/pkg/arvo/ted/aggregator/send.hoon b/pkg/arvo/ted/aggregator/send.hoon index fd16da29be..6a5a77d7a7 100644 --- a/pkg/arvo/ted/aggregator/send.hoon +++ b/pkg/arvo/ted/aggregator/send.hoon @@ -1,28 +1,17 @@ :: aggregator/send: send rollup tx :: -/- rpc=json-rpc +/- rpc=json-rpc, *dice /+ naive, ethereum, ethio, strandio :: -=/ gas-limit=@ud 30.000 ::TODO verify, maybe scale with roll size :: |= args=vase -=+ !< $: endpoint=@t - contract=address:ethereum - chain-id=@ - pk=@ - :: - nonce=@ud - next-gas-price=@ud - txs=(list raw-tx:naive) - == - args +=+ !<(rpc-send-roll args) =/ m (strand:strandio ,vase) |^ ^- form:m -=* not-sent (pure:m !>(next-gas-price)) +=* not-sent (pure:m !>(%.y^next-gas-price)) :: -=/ =address:ethereum - (address-from-pub:key:ethereum pk) +=/ =address:ethereum (address-from-prv:key:ethereum pk) ;< expected-nonce=@ud bind:m (get-next-nonce:ethio endpoint address) :: if chain expects a different nonce, don't send this transaction @@ -34,17 +23,39 @@ ;< use-gas-price=@ud bind:m ?: =(0 next-gas-price) fetch-gas-price (pure:(strand:strandio @ud) next-gas-price) +:: +=/ batch-data=octs + %+ cad:naive 3 + %+ roll txs + |= [=raw-tx:naive out=(list octs)] + %+ weld + out + :_ [raw.raw-tx ~] + (met 3 sig.raw-tx)^sig.raw-tx +:: each l2 signature is 65 bytes + XX bytes for the raw data +:: from the ethereum yellow paper: +:: gasLimit = G_transaction + G_txdatanonzero × dataByteLength +:: where +:: G_transaction = 21000 gas (base fee) +:: + G_txdatanonzero = 68 gas +:: * dataByteLength = (65 + raw) * (lent txs) bytes +:: +:: TODO: enforce max number of tx in batch? +:: +=/ gas-limit=@ud (add 21.000 (mul 68 p.batch-data)) :: if we cannot pay for the transaction, don't bother sending it out :: =/ max-cost=@ud (mul gas-limit use-gas-price) ;< balance=@ud bind:m - ::TODO implement %eth-get-balance in /lib/ethio and /lib/ethereum - !! + (get-balance:ethio endpoint address) ?: (gth max-cost balance) ~& [%insufficient-aggregator-balance address] not-sent :: -=/ tx=@ux +::NOTE this fails the thread if sending fails, which in the app gives us +:: the "retry with same gas price" behavior we want +;< =response:rpc bind:m + %+ send-batch endpoint =; tx=transaction:rpc:ethereum (sign-transaction:key:ethereum tx pk) :* nonce @@ -52,20 +63,21 @@ gas-limit contract 0 - roll ::TODO tx data + q.batch-data chain-id == -:: -::NOTE this fails the thread if sending fails, which in the app gives us -:: the "retry with same gas price" behavior we want -;< jon=json bind:m - %+ request-rpc:ethio endpoint - [~ %eth-send-raw-transaction tx] -::TODO check that tx-hash in jon is non-zero? -::TODO enforce max here, or in app? -:: add five gwei to gas price of next attempt -:: -(pure:m !>((add use-gas-price 5.000.000.000))) +%- pure:m +!> ^- (each @ud @t) +?+ -.response %.n^'unexpected rpc response' + %error %.n^message.response + :: TODO: + :: check that tx-hash in +.response is non-zero? + :: log tx-hash to getTransactionReceipt(tx-hash)? + :: enforce max here, or in app? + :: add five gwei to gas price of next attempt + :: + %result %.y^(add use-gas-price 5.000.000.000) +== :: ::TODO should be distilled further, partially added to strandio? ++ fetch-gas-price @@ -83,7 +95,7 @@ take-maybe-response:strandio =* fallback ~& %fallback-gas-price - (pure:m 40.000.000.000) ::TODO maybe even lower, considering we increment? + (pure:m 10.000.000.000) ?. ?& ?=([~ %finished *] rep) ?=(^ full-file.u.rep) == @@ -96,5 +108,20 @@ (mul 1.000.000.000 u.res) ::NOTE gwei to wei %. u.jon =, dejs-soft:format - (ot 'result'^(ot 'FastGasPrice'^ni) ~) + (ot 'result'^(ot 'FastGasPrice'^ni ~) ~) +:: +++ send-batch + |= [endpoint=@ta batch=@ux] + =/ m (strand:strandio ,response:rpc) + ^- form:m + =/ req=[(unit @t) request:rpc:ethereum] + [`'sendRawTransaction' %eth-send-raw-transaction batch] + ;< res=(list response:rpc) bind:m + (request-batch-rpc-loose:ethio endpoint [req]~) + ?: ?=([* ~] res) + (pure:m i.res) + %+ strand-fail:strandio + %unexpected-multiple-results + [>(lent res)< ~] +:: -- diff --git a/pkg/arvo/ted/btc-rpc.hoon b/pkg/arvo/ted/btc-rpc.hoon new file mode 100644 index 0000000000..81f2ddc460 --- /dev/null +++ b/pkg/arvo/ted/btc-rpc.hoon @@ -0,0 +1,95 @@ +:: Note: these are for BTC testnet +:: +/- spider, rpc=json-rpc +/+ strandio, bc=bitcoin +=, strand=strand:spider +=> +|% +++ url1 "http://localhost:50002" +++ addr ^-(address:bc [%bech32 'bc1q39wus23jwe7m2j7xmrfr2svhrtejmsn262x3j2']) +++ btc-req + ^- request:http + =, enjs:format + :* method=%'POST' + url=`@ta`(crip (weld url1 "/btc-rpc")) + header-list=['Content-Type'^'application/json' ~] + ^= body + %- some + %- as-octt:mimes:html + %- en-json:html + %- pairs + :~ jsonrpc+s+'2.0' + id+s+'block-info' + method+s+'getblockchaininfo' + == + == +++ electrs-req + ^- request:http + =, enjs:format + :* method=%'POST' + url=`@ta`(crip (weld url1 "/electrs-rpc")) + header-list=['Content-Type'^'application/json' ~] + ^= body + %- some + %- as-octt:mimes:html + %- en-json:html + %- pairs + :~ jsonrpc+s+'2.0' + id+s+'list-unspent' + method+s+'blockchain.scripthash.listunspent' + params+a+~[[%s '34aae877286aa09828803af27ce2315e72c4888efdf74d7d067c975b7c558789']] + == + == +:: +:: convert address to Electrs ScriptHash that it uses to index +:: big-endian sha256 of the output script +:: +++ electrs-script-hash + |= a=address:bc + ^- hexb:bc + %- flip:byt:bc + %- sha256:bc + (script-pubkey:bc a) +:: +++ parse-json-rpc + |= =json + ^- (unit response:rpc) + =/ res=(unit [@t ^json]) + %. json + =, dejs-soft:format + (ot id+so result+some ~) + ?^ res `[%result u.res] + ~| parse-one-response=json + :+ ~ %error %- need + %. json + =, dejs-soft:format + (ot id+so error+(ot code+no message+so ~) ~) +:: +++ parse-response + |= =client-response:iris + =/ m (strand:strandio ,(unit response:rpc)) + ^- form:m + ?> ?=(%finished -.client-response) + ?~ full-file.client-response + (pure:m ~) + =/ body=@t q.data.u.full-file.client-response + =/ jon=(unit json) (de-json:html body) + ?~ jon (pure:m ~) + (pure:m (parse-json-rpc u.jon)) +:: +++ attempt-request + |= =request:http + =/ m (strand:strandio ,~) + ^- form:m + (send-request:strandio request) +-- +^- thread:spider +|= arg=vase +:: =+ !<([~ a=@ud] arg) +=/ m (strand ,vase) +^- form:m +;< ~ bind:m (attempt-request electrs-req) +;< rep=client-response:iris bind:m + take-client-response:strandio +;< rpc-resp=(unit response:rpc) bind:m (parse-response rep) +(pure:m !>(rpc-resp)) diff --git a/pkg/arvo/ted/glob.hoon b/pkg/arvo/ted/glob.hoon index c207b7a1c8..914691422c 100644 --- a/pkg/arvo/ted/glob.hoon +++ b/pkg/arvo/ted/glob.hoon @@ -8,5 +8,6 @@ =+ !<([~ hash=@uv] arg) =/ url "https://bootstrap.urbit.org/glob-{(scow %uv hash)}.glob" ;< =cord bind:m (fetch-cord:strandio url) +~| failed-glob+hash =+ ;;(=glob:glob (cue cord)) (pure:m !>(glob)) diff --git a/pkg/arvo/ted/graph/add-nodes.hoon b/pkg/arvo/ted/graph/add-nodes.hoon index 3c6a3ccf67..2359c5efe1 100644 --- a/pkg/arvo/ted/graph/add-nodes.hoon +++ b/pkg/arvo/ted/graph/add-nodes.hoon @@ -32,8 +32,9 @@ =/ hashes (nodes-to-pending-indices nodes.q.update) ;< ~ bind:m %^ poke-our %graph-push-hook - %graph-update-1 - !>(update) + %graph-update-2 + !> ^- update:store + update (pure:m !>(`action:graph-view`[%pending-indices hashes])) :: ++ sort-nodes @@ -75,7 +76,8 @@ ^- [index:store node:store] =* loop $ :- index - =* p post.node + ?> ?=(%& -.post.node) + =* p p.post.node =/ =hash:store =- `@ux`(sham -) :^ ?^ parent-hash @@ -85,9 +87,9 @@ time-sent.p contents.p %_ node - hash.post `hash + hash.p.post `hash :: - signatures.post + signatures.p.post %- ~(gas in *signatures:store) [(sign:sig our.bowl now.bowl hash)]~ :: @@ -115,7 +117,8 @@ ?: ?=([@ ~] index) ~ =/ node (got-deep:gra graph (snip `(list atom)`index)) - hash.post.node + ?> ?=(%& -.post.node) + hash.p.post.node :: ++ nodes-to-pending-indices |= nodes=(map index:store node:store) @@ -124,6 +127,7 @@ %+ turn ~(tap by nodes) |= [=index:store =node:store] ^- [hash:store index:store] - ?> ?=(^ hash.post.node) - [u.hash.post.node index] + ?> ?=(%& -.post.node) + ?> ?=(^ hash.p.post.node) + [u.hash.p.post.node index] -- diff --git a/pkg/arvo/ted/graph/create-group-feed.hoon b/pkg/arvo/ted/graph/create-group-feed.hoon index 99ce29dd21..83590f5928 100644 --- a/pkg/arvo/ted/graph/create-group-feed.hoon +++ b/pkg/arvo/ted/graph/create-group-feed.hoon @@ -39,7 +39,7 @@ == ;< ~ bind:m %+ poke-our %graph-store - :- %graph-update-1 + :- %graph-update-2 !> ^- update:graph [now.bowl %add-graph feed-rid *graph:graph `%graph-validator-post %&] ;< ~ bind:m diff --git a/pkg/arvo/ted/graph/create.hoon b/pkg/arvo/ted/graph/create.hoon index e4bb3ee573..44f9cd098e 100644 --- a/pkg/arvo/ted/graph/create.hoon +++ b/pkg/arvo/ted/graph/create.hoon @@ -54,7 +54,7 @@ =/ =update:graph [now.bowl %add-graph rid.action *graph:graph mark.action overwrite] ;< ~ bind:m - (poke-our %graph-store graph-update-1+!>(update)) + (poke-our %graph-store graph-update-2+!>(update)) ;< ~ bind:m (poke-our %graph-push-hook %push-hook-action !>([%add rid.action])) :: diff --git a/pkg/arvo/ted/graph/delete.hoon b/pkg/arvo/ted/graph/delete.hoon index 5d4d1765d6..bab899d6ef 100644 --- a/pkg/arvo/ted/graph/delete.hoon +++ b/pkg/arvo/ted/graph/delete.hoon @@ -36,7 +36,7 @@ ^- form:m ;< =bowl:spider bind:m get-bowl:strandio ;< ~ bind:m - (poke-our %graph-store %graph-update-1 !>([now.bowl %remove-graph rid])) + (poke-our %graph-store %graph-update-2 !>([now.bowl %remove-graph rid])) ;< ~ bind:m (poke-our %graph-push-hook %push-hook-action !>([%remove rid])) ;< ~ bind:m diff --git a/pkg/arvo/ted/graph/disable-group-feed.hoon b/pkg/arvo/ted/graph/disable-group-feed.hoon index 77356ed935..5b9d6555e8 100644 --- a/pkg/arvo/ted/graph/disable-group-feed.hoon +++ b/pkg/arvo/ted/graph/disable-group-feed.hoon @@ -40,7 +40,7 @@ ?: ?=([~ ^] feed.config.metadatum) ;< ~ bind:m %+ poke-our %graph-store - :- %graph-update-1 + :- %graph-update-2 !> ^- update:graph [now.bowl [%archive-graph resource.u.u.feed.config.metadatum]] (pure:m !>(~)) diff --git a/pkg/arvo/ted/graph/leave.hoon b/pkg/arvo/ted/graph/leave.hoon index 8802e45388..b84e53a164 100644 --- a/pkg/arvo/ted/graph/leave.hoon +++ b/pkg/arvo/ted/graph/leave.hoon @@ -39,7 +39,7 @@ ;< ~ bind:m (poke-our %graph-pull-hook %pull-hook-action !>([%remove rid])) ;< ~ bind:m - (poke-our %graph-store %graph-update-1 !>([now [%remove-graph rid]])) + (poke-our %graph-store %graph-update-2 !>([now [%remove-graph rid]])) (pure:m ~) -- :: diff --git a/pkg/arvo/ted/graph/restore.hoon b/pkg/arvo/ted/graph/restore.hoon index 9dca26e3d5..3efac97241 100644 --- a/pkg/arvo/ted/graph/restore.hoon +++ b/pkg/arvo/ted/graph/restore.hoon @@ -17,7 +17,7 @@ ;< =bowl:spider bind:m get-bowl:strandio :: unarchive graph and share it ;< ~ bind:m - (poke-our %graph-store %graph-update-1 !>([now.bowl %unarchive-graph rid])) + (poke-our %graph-store %graph-update-2 !>([now.bowl %unarchive-graph rid])) ;< ~ bind:m (poke-our %graph-push-hook %push-hook-action !>([%add rid])) :: diff --git a/pkg/arvo/ted/group/on-leave.hoon b/pkg/arvo/ted/group/on-leave.hoon index 5f2b501946..34b2b6636e 100644 --- a/pkg/arvo/ted/group/on-leave.hoon +++ b/pkg/arvo/ted/group/on-leave.hoon @@ -70,7 +70,7 @@ ;< ~ bind:m %+ raw-poke [our.bowl %graph-store] - :- %graph-update-1 + :- %graph-update-2 !> ^- update:gra [now.bowl [%archive-graph app-resource]] ;< ~ bind:m diff --git a/pkg/arvo/ted/ph/migrate/make-graphs.hoon b/pkg/arvo/ted/ph/migrate/make-graphs.hoon index 41ef5ae082..c24b70c8c9 100644 --- a/pkg/arvo/ted/ph/migrate/make-graphs.hoon +++ b/pkg/arvo/ted/ph/migrate/make-graphs.hoon @@ -12,9 +12,9 @@ |= [our=@p wen=@da rid=resource body=cord id=@] =/ =index:post [id]~ =/ =post:post [our index wen [%text body]~ ~ ~] - =/ =node:graph-store [post %empty ~] + =/ =node:graph-store [[%& post] %empty ~] =/ act=update:graph-store [wen %add-nodes rid (my [index node] ~)] - (poke-app our %graph-push-hook %graph-update-1 act) + (poke-app our %graph-push-hook %graph-update-2 act) -- :: ^- thread:spider diff --git a/pkg/arvo/ted/ph/migrate/post-import-graphs.hoon b/pkg/arvo/ted/ph/migrate/post-import-graphs.hoon index 7c1d5ed4b3..00b2309727 100644 --- a/pkg/arvo/ted/ph/migrate/post-import-graphs.hoon +++ b/pkg/arvo/ted/ph/migrate/post-import-graphs.hoon @@ -12,9 +12,9 @@ |= [our=@p wen=@da rid=resource body=cord id=@] =/ =index:post [id]~ =/ =post:post [our index wen [%text body]~ ~ ~] - =/ =node:graph-store [post %empty ~] + =/ =node:graph-store [[%& post] %empty ~] =/ act=update:graph-store [wen %add-nodes rid (my [index node] ~)] - (poke-app our %graph-push-hook %graph-update-1 act) + (poke-app our %graph-push-hook %graph-update-2 act) -- :: ^- thread:spider diff --git a/pkg/arvo/ted/rpc.hoon b/pkg/arvo/ted/rpc.hoon new file mode 100644 index 0000000000..3e037bf9bb --- /dev/null +++ b/pkg/arvo/ted/rpc.hoon @@ -0,0 +1,13 @@ +/- spider, rpc=json-rpc +/+ strandio, bcu=bitcoin-utils +=, strand=strand:spider +=> +|% +++ blah 2 +-- +^- thread:spider +|= arg=vase +:: =+ !<([~ a=@ud] arg) +=/ m (strand ,vase) +^- form:m +(pure:m !>(blah)) diff --git a/pkg/arvo/tests/lib/bip/b158.hoon b/pkg/arvo/tests/lib/bip/b158.hoon new file mode 100644 index 0000000000..925ca1cfb5 --- /dev/null +++ b/pkg/arvo/tests/lib/bip/b158.hoon @@ -0,0 +1,159 @@ +/+ *test, *bip-b158, *bitcoin-utils +|% ++$ filter-vector + $: filter=hexb + expect=[parse=[n=@ux gcs-set=bits] decode=[delta=@ rest=bits]] +== ++$ siphash-vector + $: blockhash=tape + filter=hexb + item=hexb + expect=@ + == ++$ match-vector + $: blockhash=tape + filter=hexb + inc-spks=(list hexb) + exc-spks=(list hexb) + expect=(list @) + == +:: +++ filter-vectors + ^- (list filter-vector) + :~ + :: testnet genesis block + :: + :* 4^0x19d.fca8 + :* 0x1 + 24^0b1001.1101.1111.1100.1010.1000 + == + [769.941 [3 0b0]] + == + :: testnet block 926485 + :: + :* 25^0x9.027a.cea6.1b6c.c3fb.33f5.d52f.7d08.8a6b.2f75.d234.e89c.a800 + :* 0x9 + 192^0b10.0111.1010.1100.1110.1010.0110.0001.1011.0110.1100.1100.0011.1111.1011.0011.0011.1111.0101.1101.0101.0010.1111.0111.1101.0000.1000.1000.1010.0110.1011.0010.1111.0111.0101.1101.0010.0011.0100.1110.1000.1001.1100.1010.1000.0000.0000 + == + [10.156 172^0b1110.1010.0110.0001.1011.0110.1100.1100.0011.1111.1011.0011.0011.1111.0101.1101.0101.0010.1111.0111.1101.0000.1000.1000.1010.0110.1011.0010.1111.0111.0101.1101.0010.0011.0100.1110.1000.1001.1100.1010.1000.0000.0000] + == + :: 3 vectors with large Ns (i.e. CompactSize starting with 0xfd/fe/ff) + :: + :* 6^0xfd88.279d.fca8 + :* 0x2788 + 24^0b1001.1101.1111.1100.1010.1000 + == + [769.941 [3 0b0]] + == + :: + :* 8^0xfe11.2233.449d.fca8 + :* 0x4433.2211 + 24^0b1001.1101.1111.1100.1010.1000 + == + [769.941 [3 0b0]] + == + :: + :* 12^0xff11.2233.4455.6677.889d.fca8 + :* 0x8877.6655.4433.2211 + 24^0b1001.1101.1111.1100.1010.1000 + == + [769.941 [3 0b0]] + == + == +:: +++ siphash-vectors + ^- (list siphash-vector) + :: testnet genesis block + :~ :* "000000000933ea01ad0ee984209779baaec3ced90fa3f408719526f8d77f4943" + 4^0x19d.fca8 + 67^0x41.0467.8afd.b0fe.5548.2719.67f1.a671.30b7.105c.d6a8.28e0.3909.a679.62e0.ea1f.61de.b649.f6bc.3f4c.ef38.c4f3.5504.e51e.c112.de5c.384d.f7ba.0b8d.578a.4c70.2b6b.f11d.5fac + 769.941 + == + == +:: +++ match-vectors + ^- (list match-vector) + :: testnet genesis block + :~ :* "000000000933ea01ad0ee984209779baaec3ced90fa3f408719526f8d77f4943" + 4^0x19d.fca8 + ~[67^0x41.0467.8afd.b0fe.5548.2719.67f1.a671.30b7.105c.d6a8.28e0.3909.a679.62e0.ea1f.61de.b649.f6bc.3f4c.ef38.c4f3.5504.e51e.c112.de5c.384d.f7ba.0b8d.578a.4c70.2b6b.f11d.5fac] + ~[25^0x76.a914.3ebc.40e4.11ed.3c76.f867.1150.7ab9.5230.0890.3972.88ac] + ~[271.501 769.941] + == + :: testnet block 926485 + :: + :* "000000000000015d6077a411a8f5cc95caf775ccf11c54e27df75ce58d187313" + 25^0x9.027a.cea6.1b6c.c3fb.33f5.d52f.7d08.8a6b.2f75.d234.e89c.a800 + :~ 25^0x76.a914.3ebc.40e4.11ed.3c76.f867.1150.7ab9.5230.0890.3972.88ac + 25^0x76.a914.5033.3046.115e.aa0a.c9e0.2165.65f9.4507.0e44.5739.88ac + == + :~ 21^0x14.7e69.a44c.1a94.2139.c8ab.4127.8325.5e1e.46d0.f0da + == + ~[176.536 2.341.508 3.078.625] + == + == +:: +++ test-all-vectors + =/ [p=@ m=@] [p:params m:params] + ^- tang + |^ ;: weld + %+ category "parse filters" + (zing (turn filter-vectors check-filter-parse)) + %+ category "decode GCS" + (zing (turn filter-vectors check-gcs-decode)) + %+ category "siphash" + (zing (turn siphash-vectors check-siphash)) + %+ category "hash script-pubkeys" + (zing (turn match-vectors check-hashing)) + %+ category "whether filter matches any script-pubkey" + (zing (turn match-vectors check-match)) + %+ category "get all script-pubkey matches for a block filter" + (zing (turn match-vectors check-all-match)) + == + :: + ++ check-filter-parse + |= v=filter-vector + %+ expect-eq + !>(parse.expect.v) + !>((parse-filter filter.v)) + :: + ++ check-gcs-decode + |= v=filter-vector + %+ expect-eq + !>(decode.expect.v) + !>((de:gol gcs-set:(parse-filter filter.v) p)) + :: + ++ check-siphash + |= v=siphash-vector + =+ f=(mul n:(parse-filter filter.v) m) + %+ expect-eq + !>(expect.v) + !>((to-range:hsh item.v f (to-key blockhash.v))) + :: + ++ check-hashing + |= v=match-vector + =/ [n=@ux gcs-set=bits] (parse-filter filter.v) + =+ k=(to-key blockhash.v) + %+ expect-eq + !>(expect.v) + !>((set-construct:hsh (weld inc-spks.v exc-spks.v) k (mul n m))) + :: + ++ check-match + |= v=match-vector + =+ k=(to-key blockhash.v) + %+ weld + %+ expect-eq + !>(%.y) + !>((match filter.v k inc-spks.v)) + %+ expect-eq + !>(%.n) + !>((match filter.v k exc-spks.v)) + :: + ++ check-all-match + |= v=match-vector + =+ k=(to-key blockhash.v) + %+ expect-eq + !>(`(set hexb)`(sy inc-spks.v)) + !>(`(set hexb)`(all-match filter.v k (weld inc-spks.v exc-spks.v))) + -- +-- diff --git a/pkg/arvo/tests/lib/bip/b174.hoon b/pkg/arvo/tests/lib/bip/b174.hoon new file mode 100644 index 0000000000..50be45d956 --- /dev/null +++ b/pkg/arvo/tests/lib/bip/b174.hoon @@ -0,0 +1,49 @@ +/- *bitcoin +/+ *test, *bip-b158, bcu=bitcoin-utils, pbt=bip-b174 +|% ++$ psbt-vector + $: =hdkey + hdkey-hex=hexb + == +++ fprint 4^0xdead.beef +++ psbt-vectors + ^- (list psbt-vector) + :~ :* [fprint 33^0x1 %testnet %44 %0 1] + 20^0x2c00.0080.0100.0080.0000.0080.0000.0000.0100.0000 + == + :: + :* [fprint 33^0x1 %testnet %49 %0 1] + 20^0x3100.0080.0100.0080.0000.0080.0000.0000.0100.0000 + == + :: + :* [fprint 33^0x1 %testnet %84 %0 1] + 20^0x5400.0080.0100.0080.0000.0080.0000.0000.0100.0000 + == + :: + :* [fprint 33^0x1 %main %44 %0 1] + 20^0x2c00.0080.0000.0080.0000.0080.0000.0000.0100.0000 + == + :: + :* [fprint 33^0x1 %main %49 %0 1] + 20^0x3100.0080.0000.0080.0000.0080.0000.0000.0100.0000 + == + :: + :* [fprint 33^0x1 %main %84 %0 1] + 20^0x5400.0080.0000.0080.0000.0080.0000.0000.0100.0000 + == + == +++ test-all-vectors +^- tang + |^ ;: weld + %+ category "check PSBT" + (zing (turn psbt-vectors check-psbt)) + == + ++ check-psbt + |= v=psbt-vector + =/ key=hexb + (cat:byt:bcu ~[1^0x6 pubkey.hdkey.v]) :: %input target + %+ expect-eq + !>([key (cat:byt:bcu ~[fprint.hdkey.v hdkey-hex.v])]) + !>((hdkey:en:pbt %input hdkey.v)) + -- +-- diff --git a/pkg/arvo/tests/lib/bitcoin.hoon b/pkg/arvo/tests/lib/bitcoin.hoon new file mode 100644 index 0000000000..29bc64eff8 --- /dev/null +++ b/pkg/arvo/tests/lib/bitcoin.hoon @@ -0,0 +1,207 @@ +/+ *test, *bitcoin, bip32 +=, secp:crypto +=+ ecc=secp256k1 +|% ++$ chyg ?(%0 %1) ++$ bits-vector [bitwidth=@ atoms=(list @) =bits] ++$ compact-size-vector @ux ++$ tx-vector [hex-cord=@t txid=hexb] ++$ xpub-vector + $: =xpub + =network + hdpath=[=bipt =chyg =idx] + pubkey=hexb + =address + == ++$ script-pubkey-vector [=address spk=hexb] +:: +++ bits-vectors + ^- (list bits-vector) + :~ :* 5 + ~[0 31 31 0 31 0] + [30 0b1.1111.1111.1000.0011.1110.0000] + == + == +:: +++ compact-size-vectors + ^- (list compact-size-vector) + :~ 0x98 + 0x302 + 0xaa.bbcc + 0xaabb.ccdd + 0xaa.bbcc.ddee + 0xaabb.ccdd.eeff.1122 + == +:: +++ tx-vectors + ^- (list tx-vector) + :~ :* '0200000002ab0949a08c5af7c49b8212f417e2f15ab3f5c33dcf153821a8139f877a5b7be40000000000feffffffab0949a08c5af7c49b8212f417e2f15ab3f5c33dcf153821a8139f877a5b7be40100000000feffffff02603bea0b000000001976a914768a40bbd740cbe81d988e71de2a4d5c71396b1d88ac8e240000000000001976a9146f4620b553fa095e721b9ee0efe9fa039cca459788ac00000000' + 32^0xfed6.cd1f.de4d.b4e1.3e7e.8003.17e3.7f9c.bd75.ec36.4389.670e.eff8.0da9.93c7.e560 + == + :: + :* '01000000000102267b34b058a44e678ca0609825fe37c5bb893462337334ad5a2e887b8ebef65c000000002322002049f80613b30fe3063d4e6ce75c53a7bc573ea17fe49dcb805aca416a8af5d991ffffffff666f99bf914bb18e28ec39af93d99a1938767fbec892408acced7c80663f0df40000000023220020096def7756afb4dba661ec58602cf6af1f7881e48e4b8a8c12be9985073f5adeffffffff06602d59010000000017a914572290324c72e6842e8a77c2cbb9882a3b9c2a9f87195c0e00000000001976a914abfdf3698ceef95986b31b763e6764cfe3ce584e88ac68642400000000001976a91456cf5fcc3654c5646b930e8773a95dce98c49e0588acfbb34b00000000001976a914518ee0d1b48f3d99f76e6e8283006610e39aeeba88acf492560000000000160014d1930fff9862af879ee14ecd3e1b9dc1099524ce0374c802000000001976a9148a6727bc345abeae523b6af7828053f95332918688ac03483045022100fcc8336b7c81e67cc7b53587fb06c3f950a6d3349e658594a444618c75988e45022065b3b957e0f7d2def98565a34c1ee7843d60525c4337730fa5d6ac8ff7aacb89012102ac604909ed86488338ec6255b0bdc0162562b299e66b860480a8bd2b99c7f3291976a9140e60a2ad39efd10ad61e2a3e6e5c1baa73190ee088ac0347304402202ec01f623cd48ba990caea70463de86b37ffb2363640b510ce9d80c750a54eec02204915d11649d636e5e8503b226d7a6a45da0163f3f0ca4b07bdaaa9af4d6f850f012102a52b3f9958c0f4b57b99f287832ea75775ddf7c83fa0648b6b1545ec4881ef2d1976a91401121fe150c9b05f9146bac57ad9947ea5c1478e88ac00000000' + 32^0x2b9c.60c4.dfcd.0aa2.b1b8.83a5.0a4a.2a96.197b.07d8.cdd1.e749.f0a1.f296.0f43.b339 + == + == +:: below use mnemonic: +:: abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about +:: +++ xpub-vectors + ^- (list xpub-vector) + :~ :* 'tpubDC5FSnBiZDMmhiuCmWAYsLwgLYrrT9rAqvTySfuCCrgsWz8wxMXUS9Tb9iVMvcRbvFcAHGkMD5Kx8koh4GquNGNTfohfk7pgjhaPCdXpoba' + %testnet + [%44 %0 0] + 33^0x2.a745.1395.7353.69f2.ecdf.c829.c0f7.74e8.8ef1.303d.fe5b.2f04.dbaa.b30a.535d.fdd6 + [%base58 0cmkpZhYtJu2r87Js3pDiWJDmPte2NRZ8bJV] + == + :: + :* 'upub5EFU65HtV5TeiSHmZZm7FUffBGy8UKeqp7vw43jYbvZPpoVsgU93oac7Wk3u6moKegAEWtGNF8DehrnHtv21XXEMYRUocHqguyjknFHYfgY' + %testnet + [%49 %0 0] + 33^0x3.a1af.804a.c108.a8a5.1782.198c.2d03.4b28.bf90.c880.3f5a.53f7.6276.fa69.a4ea.e77f + [%base58 0c2Mww8dCYPUpKHofjgcXcBCEGmniw9CoaiD2] + == + :: + :* 'vpub5Y6cjg78GGuNLsaPhmYsiw4gYX3HoQiRBiSwDaBXKUafCt9bNwWQiitDk5VZ5BVxYnQdwoTyXSs2JHRPAgjAvtbBrf8ZhDYe2jWAqvZVnsc' + %testnet + [%84 %0 0] + 33^0x2.e7ab.2537.b5d4.9e97.0309.aae0.6e9e.49f3.6ce1.c9fe.bbd4.4ec8.e0d1.cca0.b4f9.c319 + [%bech32 'tb1q6rz28mcfaxtmd6v789l9rrlrusdprr9pqcpvkl'] + == + :: + :* 'xpub6BosfCnifzxcFwrSzQiqu2DBVTshkCXacvNsWGYJVVhhawA7d4R5WSWGFNbi8Aw6ZRc1brxMyWMzG3DSSSSoekkudhUd9yLb6qx39T9nMdj' + %main + [%44 %0 0] + 33^0x3.aaeb.52dd.7494.c361.049d.e67c.c680.e83e.bcbb.bdbe.b136.37d9.2cd8.45f7.0308.af5e + [%base58 0c1LqBGSKuX5yYUonjxT5qGfpUsXKYYWeabA] + == + :: + :* 'ypub6Ww3ibxVfGzLrAH1PNcjyAWenMTbbAosGNB6VvmSEgytSER9azLDWCxoJwW7Ke7icmizBMXrzBx9979FfaHxHcrArf3zbeJJJUZPf663zsP' + %main + [%49 %0 0] + 33^0x3.9b3b.694b.8fc5.b5e0.7fb0.69c7.83ca.c754.f5d3.8c3e.08be.d196.0e31.fdb1.dda3.5c24 + [%base58 0c37VucYSaXLCAsxYyAPfbSi9eh4iEcbShgf] + == + :: + :* 'zpub6rFR7y4Q2AijBEqTUquhVz398htDFrtymD9xYYfG1m4wAcvPhXNfE3EfH1r1ADqtfSdVCToUG868RvUUkgDKf31mGDtKsAYz2oz2AGutZYs' + %main + [%84 %0 0] + 33^0x3.30d5.4fd0.dd42.0a6e.5f8d.3624.f5f3.482c.ae35.0f79.d5f0.753b.f5be.ef9c.2d91.af3c + [%bech32 'bc1qcr8te4kr609gcawutmrza0j4xv80jy8z306fyu'] + == + == +:: +++ script-pubkey-vectors + ^- (list script-pubkey-vector) + :~ :* [%bech32 'bc1qcr8te4kr609gcawutmrza0j4xv80jy8z306fyu'] + [wid=22 dat=0x14.c0ce.bcd6.c3d3.ca8c.75dc.5ec6.2ebe.5533.0ef9.10e2] + == + :: + :* [%bech32 'bc1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3qccfmv3'] + [wid=34 dat=0x20.1863.143c.14c5.1668.04bd.1920.3356.da13.6c98.5678.cd4d.27a1.b8c6.3296.0490.3262] + == + :: + :* [%bech32 'tb1q6rz28mcfaxtmd6v789l9rrlrusdprr9pqcpvkl'] + [wid=22 dat=0x14.d0c4.a3ef.09e9.97b6.e99e.397e.518f.e3e4.1a11.8ca1] + == + :: + :* [%base58 0c1LqBGSKuX5yYUonjxT5qGfpUsXKYYWeabA] + [wid=25 dat=0x76.a914.d986.ed01.b7a2.2225.a70e.dbf2.ba7c.fb63.a15c.b3aa.88ac] + == + :: + :* [%base58 0cmxVFsFW5N4mu1HPkxPttorvocvzeZ7KZyk] + [wid=25 dat=0x76.a914.ba27.f99e.007c.7f60.5a83.05e3.18c1.abde.3cd2.20ac.88ac] + == + :: + :* [%base58 0cmfWxJ45yp2SFn7UciZyNpvDKrzbhyfKrY8] + [wid=25 dat=0x76.a914.0000.0000.0000.0000.0000.0000.0000.0000.0000.0000.88ac] + == + :: + :* [%base58 0c37VucYSaXLCAsxYyAPfbSi9eh4iEcbShgf] + [wid=23 dat=0xa9.143f.b6e9.5812.e57b.b469.1f9a.4a62.8862.a61a.4f76.9b87] + == + :: + :* [%base58 0c2MvLWCyKPQQ6oqJKSJ9ic8hYVmLyNry6yuF] + [wid=23 dat=0xa9.1421.e7fe.f309.cf6f.6cfc.fe94.c572.e541.d74f.d848.5487] + == + == +:: +++ mk-pubkey +|= [=xpub =chyg =idx] + ^- hexb + =/ pk=@ux + %- compress-point:ecc + pub:(derive-public:(derive-public:(from-extended:bip32 (trip xpub)) (@ chyg)) idx) + [(met 3 pk) pk] +:: +++ test-all-vectors +^- tang + |^ ;: weld + %+ category "bit manipulation" + (zing (turn bits-vectors check-bits)) + %+ category "compact-size en/decoding" + (zing (turn compact-size-vectors check-compact-size)) + %+ category "check TX en/decoding" + (zing (turn tx-vectors check-tx)) + %+ category "xpub parsing" + (zing (turn xpub-vectors check-xpub-parsing)) + %+ category "pubkey derivation" + (zing (turn xpub-vectors check-pubkey-derivation)) + %+ category "address derivation" + (zing (turn xpub-vectors check-address-derivation)) + %+ category "script-pubkey derivation" + (zing (turn script-pubkey-vectors check-script-pubkey-derivation)) + == + :: + ++ check-bits + |= v=bits-vector + ;: weld + :: TODO: from-atoms works, but to-atoms doesn't + %+ expect-eq + !>(bits.v) + !>((from-atoms:bit bitwidth.v atoms.v)) + %+ expect-eq + !>(atoms.v) + !>((to-atoms:bit bitwidth.v bits.v)) + == + :: + ++ check-compact-size + |= v=compact-size-vector + %+ expect-eq + !>(v) + !>(dat:n:(de:csiz (en:csiz v))) + :: + ++ check-tx + |= v=tx-vector + %+ expect-eq + !>(txid.v) + !>((get-id:txu (decode:txu (from-cord:hxb hex-cord.v)))) + :: + ++ check-xpub-parsing + |= v=xpub-vector + =/ [b=bipt n=network] (xpub-type xpub.v) + %+ expect-eq + !>([b n]) + !>([bipt.hdpath.v network.v]) + :: + ++ check-pubkey-derivation + |= v=xpub-vector + %+ expect-eq + !>(pubkey.v) + !>((mk-pubkey xpub.v chyg.hdpath.v idx.hdpath.v)) + :: + ++ check-address-derivation + |= v=xpub-vector + =/ [b=bipt n=network] (xpub-type xpub.v) + %+ expect-eq + !>(address.v) + !>((from-pubkey:adr b n pubkey.v)) + :: + ++ check-script-pubkey-derivation + |= v=script-pubkey-vector + %+ expect-eq + !>(spk.v) + !>((to-script-pubkey:adr address.v)) + -- +:: +-- diff --git a/pkg/arvo/tests/lib/btc.hoon b/pkg/arvo/tests/lib/btc.hoon new file mode 100644 index 0000000000..9fffe31b19 --- /dev/null +++ b/pkg/arvo/tests/lib/btc.hoon @@ -0,0 +1,190 @@ +/- bc=bitcoin +/+ *test, *btc +|% ++$ wallet-vector + $: =xpub:bc + =chyg + =idx:bc + =address:bc + == ++$ vector + $: =xpub:bc + eny=@uv + block=@ud + feyb=sats + ins=(list insel) + outs=(list txo) + expect=[selected=(unit (list insel)) chng=(unit sats:bc)] + == +++ mk-utxo + |= value=sats:bc + ^- utxo:bc + :* pos=0 + [wid=32 dat=0xc493.f6f1.4668.5f76.b44f.0c77.ca88.120c.b8bc.89f5.34fe.69b6.8288.27b9.74e6.8849] + height=3 + value + recvd=~ + == +:: +++ fprint 4^0xdead.beef +:: +++ wallet-vectors + ^- (list wallet-vector) + :~ :* 'zpub6rFR7y4Q2AijBEqTUquhVz398htDFrtymD9xYYfG1m4wAcvPhXNfE3EfH1r1ADqtfSdVCToUG868RvUUkgDKf31mGDtKsAYz2oz2AGutZYs' + %0 + 0 + [%bech32 'bc1qcr8te4kr609gcawutmrza0j4xv80jy8z306fyu'] + == + == +:: +++ vectors + =| w=walt + ^- (list vector) + :~ :* 'zpub6rFR7y4Q2AijBEqTUquhVz398htDFrtymD9xYYfG1m4wAcvPhXNfE3EfH1r1ADqtfSdVCToUG868RvUUkgDKf31mGDtKsAYz2oz2AGutZYs' + 0v3uc.iuebi.5qilc.l8d87.c1k6n.7iksq.nkobs.8s5he.raq40.9ff0b.5tj3u.kjtg7.aq59e.hatv7.oioam.mlsr4.pqqcd.cnbjn.pnpi2.1m5rt.k4scg + 999 + 10 + :~ [(mk-utxo 200.000) %0 1] + [(mk-utxo 500.000) %0 2] + [(mk-utxo 204) %0 3] + [(mk-utxo 235.000) %1 2] + == + :~ [[%bech32 'bc1q59u5epktervh6fxqay2dlph0wxu9hjnx6v8n66'] 200.100 ~] + [[%bech32 'bc1qlwd7mw33uea5m8r2lsnsrkc7gp2qynrxsfxpfm'] 200.000 ~] + == + :* `~[[(mk-utxo 500.000) %0 2]] + `332.500 + == + == + :: + :* 'zpub6rFR7y4Q2AijBEqTUquhVz398htDFrtymD9xYYfG1m4wAcvPhXNfE3EfH1r1ADqtfSdVCToUG868RvUUkgDKf31mGDtKsAYz2oz2AGutZYs' + 0v1gt.mc4ca.lfs0m.q1dal.lqobu.mmlbd.2umnp.lj9dr.4pf4s.pvclr.dps96.4a6i8.rt6n9.krp0r.11kqu.ckqe4.1tmat.gr754.463aj.a4b41.jj7qg + 999 + 10 + :~ [(mk-utxo 200.000) %0 1] + [(mk-utxo 500.000) %0 2] + [(mk-utxo 204) %0 3] + [(mk-utxo 235.000) %1 2] + == + :~ [[%bech32 'bc1q59u5epktervh6fxqay2dlph0wxu9hjnx6v8n66'] 200.100 ~] + [[%bech32 'bc1qlwd7mw33uea5m8r2lsnsrkc7gp2qynrxsfxpfm'] 200.000 ~] + == + :* `~[[(mk-utxo 235.000) %1 2] [(mk-utxo 200.000) %0 1]] + `297.500 + == + == + :: + :* 'zpub6rFR7y4Q2AijBEqTUquhVz398htDFrtymD9xYYfG1m4wAcvPhXNfE3EfH1r1ADqtfSdVCToUG868RvUUkgDKf31mGDtKsAYz2oz2AGutZYs' + 0v1gt.mc4ca.lfs0m.q1dal.lqobu.mmlbd.2umnp.lj9dr.4pf4s.pvclr.dps96.4a6i8.rt6n9.krp0r.11kqu.ckqe4.1tmat.gr754.463aj.a4b41.jj7qg + 999 + 10 + ~[[(mk-utxo 500.000) %0 2]] + :~ [[%bech32 'bc1q59u5epktervh6fxqay2dlph0wxu9hjnx6v8n66'] 299.797 ~] + [[%bech32 'bc1qlwd7mw33uea5m8r2lsnsrkc7gp2qynrxsfxpfm'] 200.000 ~] + == + :* *(unit (list insel)) + *(unit sats:bc) + == + == + :* 'zpub6rFR7y4Q2AijBEqTUquhVz398htDFrtymD9xYYfG1m4wAcvPhXNfE3EfH1r1ADqtfSdVCToUG868RvUUkgDKf31mGDtKsAYz2oz2AGutZYs' + 0v1gt.mc4ca.lfs0m.q1dal.lqobu.mmlbd.2umnp.lj9dr.4pf4s.pvclr.dps96.4a6i8.rt6n9.krp0r.11kqu.ckqe4.1tmat.gr754.463aj.a4b41.jj7qg + 999 + 10 + ~[[(mk-utxo 500.000) %0 2]] + :~ [[%bech32 'bc1q59u5epktervh6fxqay2dlph0wxu9hjnx6v8n66'] 298.500 ~] + [[%bech32 'bc1qlwd7mw33uea5m8r2lsnsrkc7gp2qynrxsfxpfm'] 200.000 ~] + == + :* `~[[(mk-utxo 500.000) %0 2]] + *(unit sats:bc) + == + == + == +:: +++ dust-output-vectors + =| w=walt + ^- (list vector) + :~ + :* 'zpub6rFR7y4Q2AijBEqTUquhVz398htDFrtymD9xYYfG1m4wAcvPhXNfE3EfH1r1ADqtfSdVCToUG868RvUUkgDKf31mGDtKsAYz2oz2AGutZYs' + 0v1gt.mc4ca.lfs0m.q1dal.lqobu.mmlbd.2umnp.lj9dr.4pf4s.pvclr.dps96.4a6i8.rt6n9.krp0r.11kqu.ckqe4.1tmat.gr754.463aj.a4b41.jj7qg + 999 + 10 + ~[[(mk-utxo 500.000) %0 2]] + :~ [[%bech32 'bc1q59u5epktervh6fxqay2dlph0wxu9hjnx6v8n66'] 298.580 ~] + [[%bech32 'bc1qlwd7mw33uea5m8r2lsnsrkc7gp2qynrxsfxpfm'] 204 ~] + == + :* `~[[(mk-utxo 500.000) %0 2]] + *(unit sats:bc) + == + == + == +:: +++ 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)) + :: + %+ category "select with change" + (zing (turn vectors check-change)) + :: + %+ category "don't allow dust outputs" + (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:bc)]) + =/ [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 ~ ~ ~) + %+ expect-eq + !>(selected.expect.v) + !>((~(single-random-draw sut [w eny.v block.v ~ feyb.v outs.v]) ins.v)) + :: + ++ check-change + |= v=vector + =/ w=walt (from-xpub xpub.v fprint ~ ~ ~) + =. wach.w + %- ~(gas by *(map address:bc addi)) + %+ turn ins.v + |= i=insel + :- (~(mk-address wad w chyg.i) idx.i) + [%.y %0 0 (sy ~[utxo.i])] + %+ expect-eq + !>(chng.expect.v) + !>(chng:~(with-change sut [w eny.v block.v ~ feyb.v outs.v])) + :: + ++ check-dust-output + |= v=vector + =/ w=walt (from-xpub xpub.v fprint ~ ~ ~) + =. wach.w (insels-to-wach w ins.v) + %- expect-fail + |.(~(with-change sut [w eny.v block.v ~ feyb.v outs.v])) + :: + ++ insels-to-wach + |= [w=walt is=(list insel)] + ^- wach + %- ~(gas by *(map address:bc addi)) + %+ turn is + |= i=insel + :- (~(mk-address wad w chyg.i) idx.i) + [%.y %0 0 (sy ~[utxo.i])] + -- + :: if a non-change output is dust, error + :: change shouldn't be returned when change is dust + :: +-- diff --git a/pkg/arvo/tests/lib/vere/dawn.hoon b/pkg/arvo/tests/lib/vere/dawn.hoon index 059120fb9f..80e3f37e0a 100644 --- a/pkg/arvo/tests/lib/vere/dawn.hoon +++ b/pkg/arvo/tests/lib/vere/dawn.hoon @@ -188,37 +188,53 @@ ++ test-veri-good =/ sed [~zod 1 sec ~] %+ expect-eq - !> ~ - !> (veri:dawn sed pot ~) + !> &+sed + !> (veri:dawn ~zod sed pot ~) :: ++ test-veri-not-spawned =/ sed [~zod 1 sec ~] %+ expect-eq - !> `%not-keyed - !> (veri:dawn sed =>(pot .(net ~)) ~) + !> |+[%not-keyed ~] + !> (veri:dawn ~zod sed =>(pot .(net ~)) ~) :: ++ test-veri-wrong-key =/ sed [~zod 1 sec:ex:(pit:nu:crub:crypto 24 %foo) ~] %+ expect-eq - !> `%key-mismatch - !> (veri:dawn sed pot ~) + !> |+[%key-mismatch ~] + !> (veri:dawn ~zod sed pot ~) :: ++ test-veri-life-mismatch =/ sed [~zod 2 sec ~] %+ expect-eq - !> `%life-mismatch - !> (veri:dawn sed pot ~) + !> |+[%life-mismatch ~] + !> (veri:dawn ~zod sed pot ~) +:: +++ test-veri-bad-multikey + =/ fed=feed:jael + :- [%1 ~] + :- ~zod + :~ [1 sec:ex:(pit:nu:crub:crypto 24 %foo)] + [2 sec] + == + %+ expect-eq + !> |+[%key-mismatch %life-mismatch ~] + !> (veri:dawn ~zod fed pot ~) +:: +++ test-veri-none-multikey + %+ expect-eq + !> |+[%no-key ~] + !> (veri:dawn ~zod [[%1 ~] ~zod ~] pot ~) :: ++ test-veri-already-booted =/ sed [~zod 1 sec ~] ;: weld %+ expect-eq - !> `%already-booted - !> (veri:dawn sed pot `[1 |]) + !> |+[%already-booted ~] + !> (veri:dawn ~zod sed pot `[1 |]) :: %+ expect-eq - !> `%already-booted - !> (veri:dawn sed pot `[2 &]) + !> |+[%already-booted ~] + !> (veri:dawn ~zod sed pot `[2 &]) == :: ++ test-veri-earl-good @@ -230,8 +246,8 @@ (shaf %earl (sham who 1 pub:ex:cub)) [who 1 sec:ex:cub `sig] %+ expect-eq - !> ~ - !> (veri:dawn sed pot ~) + !> &+sed + !> (veri:dawn who sed pot ~) :: ++ test-veri-earl-parent-not-keyed =/ cub (pit:nu:crub:crypto 24 %foo) @@ -242,38 +258,38 @@ (shaf %earl (sham who 1 pub:ex:cub)) [who 1 sec:ex:cub `sig] %+ expect-eq - !> ~ - !> (veri:dawn sed =>(pot .(net ~)) ~) + !> &+sed + !> (veri:dawn who sed =>(pot .(net ~)) ~) :: ++ test-veri-pawn-good =/ cub (pit:nu:crub:crypto 24 %foo) =/ who=ship `@`fig:ex:cub =/ sed [who 1 sec:ex:cub ~] %+ expect-eq - !> ~ - !> (veri:dawn sed *point:azimuth-types ~) + !> &+sed + !> (veri:dawn who sed *point:azimuth-types ~) :: ++ test-veri-pawn-key-mismatch =/ cub (pit:nu:crub:crypto 24 %foo) =/ who=ship `@`fig:ex:cub =/ sed [who 1 sec:ex:(pit:nu:crub:crypto 24 %bar) ~] %+ expect-eq - !> `%key-mismatch - !> (veri:dawn sed *point:azimuth-types ~) + !> |+[%key-mismatch ~] + !> (veri:dawn who sed *point:azimuth-types ~) :: ++ test-veri-pawn-invalid-life =/ cub (pit:nu:crub:crypto 24 %foo) =/ who=ship `@`fig:ex:cub =/ sed [who 2 sec:ex:cub ~] %+ expect-eq - !> `%invalid-life - !> (veri:dawn sed *point:azimuth-types ~) + !> |+[%invalid-life ~] + !> (veri:dawn who sed *point:azimuth-types ~) :: ++ test-veri-pawn-already-booted =/ cub (pit:nu:crub:crypto 24 %foo) =/ who=ship `@`fig:ex:cub =/ sed [who 1 sec:ex:cub ~] %+ expect-eq - !> `%already-booted - !> (veri:dawn sed *point:azimuth-types `[1 |]) + !> |+[%already-booted ~] + !> (veri:dawn who sed *point:azimuth-types `[1 |]) -- diff --git a/pkg/arvo/tests/sys/vane/clay.hoon b/pkg/arvo/tests/sys/vane/clay.hoon index 636efd3a35..457a810db0 100644 --- a/pkg/arvo/tests/sys/vane/clay.hoon +++ b/pkg/arvo/tests/sys/vane/clay.hoon @@ -23,7 +23,7 @@ =/ src "." %+ expect-eq !> ^- pile:fusion - :* ~ ~ ~ ~ ~ ~ + :* ~ ~ ~ ~ ~ ~ ~ tssg+[%dbug [/sur/foo/hoon [[1 1] [1 2]]] [%cnts ~[[%.y 1]] ~]]~ == !> (parse-pile:(ford):fusion /sur/foo/hoon src) @@ -32,7 +32,7 @@ =/ src "/% moo %mime\0a." %+ expect-eq !> ^- pile:fusion - :* sur=~ lib=~ raw=~ + :* sur=~ lib=~ raw=~ raz=~ maz=[face=%moo mark=%mime]~ caz=~ bar=~ tssg+[%dbug [/sur/foo/hoon [[2 1] [2 2]]] [%cnts ~[[%.y 1]] ~]]~ @@ -43,7 +43,7 @@ =/ src "/$ goo %mime %txt\0a." %+ expect-eq !> ^- pile:fusion - :* sur=~ lib=~ raw=~ maz=~ + :* sur=~ lib=~ raw=~ raz=~ maz=~ caz=[face=%goo from=%mime to=%txt]~ bar=~ tssg+[%dbug [/sur/foo/hoon [[2 1] [2 2]]] [%cnts ~[[%.y 1]] ~]]~ @@ -74,7 +74,7 @@ [`%hood-drum %hood-drum] [`%hood-write %hood-write] == - raw=~ maz=~ caz=~ bar=~ + raw=~ raz=~ maz=~ caz=~ bar=~ tssg+[%dbug [/sur/foo/hoon [[10 1] [10 2]]] [%cnts ~[[%.y 1]] ~]]~ == !> (parse-pile:(ford):fusion /sur/foo/hoon src) @@ -112,10 +112,10 @@ ;: weld %+ expect-eq !>(*mime) - (slap res limb/%bunt) + (slap res !,(*hoon *vale)) :: %+ expect-eq - !> (~(gas in *(set path)) /mar/mime/hoon ~) + !> (~(gas in *(set [? path])) |^/mar/mime/hoon ~) !> dez:(~(got by files.cache.nub) /mar/mime/hoon) == :: @@ -139,10 +139,10 @@ ;: weld %+ expect-eq !>(*@t) - (slap res limb/%bunt) + (slap res !,(*hoon *vale)) :: %+ expect-eq - !> (~(gas in *(set path)) /mar/udon/hoon /lib/cram/hoon ~) + !> (~(gas in *(set [? path])) |^/mar/udon/hoon |^/lib/cram/hoon ~) !> dez:(~(got by files.cache.nub) /mar/udon/hoon) == :: @@ -170,7 +170,7 @@ =/ changes %- my :~ [/mar/mime/hoon &+hoon+mar-mime] - [/lib/foo/hoon &+hoon+'/% moo %mime\0abunt:moo'] + [/lib/foo/hoon &+hoon+'/% moo %mime\0a*vale:moo'] == =/ ford %: ford:fusion @@ -224,7 +224,7 @@ (slap res (ream '(+ [*^ [%bob ~] ~])')) :: %+ expect-eq - !> (~(gas in *(set path)) /gen/hello/hoon ~) + !> (~(gas in *(set [? path])) |^/gen/hello/hoon ~) !> dez:(~(got by files.cache.nub) /gen/hello/hoon) == :: @@ -249,10 +249,10 @@ !>((slab %read %get-our -.res)) :: %+ expect-eq - !> %- ~(gas in *(set path)) - :~ /lib/strandio/hoon - /lib/strand/hoon - /sur/spider/hoon + !> %- ~(gas in *(set [? path])) + :~ [| /lib/strandio/hoon] + [| /lib/strand/hoon] + [| /sur/spider/hoon] == !> dez:(~(got by files.cache.nub) /lib/strandio/hoon) == diff --git a/pkg/arvo/tests/sys/vane/eyre.hoon b/pkg/arvo/tests/sys/vane/eyre.hoon index 0b83d3a843..6710fa2aee 100644 --- a/pkg/arvo/tests/sys/vane/eyre.hoon +++ b/pkg/arvo/tests/sys/vane/eyre.hoon @@ -2353,7 +2353,6 @@ :^ ~ ~ %dais !> ^- dais:clay |_ sam=vase - ++ bunt !! ++ diff !! ++ form !! ++ join !! diff --git a/pkg/arvo/tests/sys/zuse/format.hoon b/pkg/arvo/tests/sys/zuse/format.hoon index 8719ab241e..dddc9ef2e4 100644 --- a/pkg/arvo/tests/sys/zuse/format.hoon +++ b/pkg/arvo/tests/sys/zuse/format.hoon @@ -179,10 +179,15 @@ %+ expect-eq !> [%n '1000'] !> (time ~1970.1.1..0.0.1) + :: timestamps should invert + :: + %+ expect-eq + !> [%n '1001'] + !> (time (from-unix-ms:chrono:userlib 1.001)) :: ship - store ship identity as a string :: %+ expect-eq - !> [%s 'zod'] + !> [%n '"zod"'] !> (ship ~zod) == :: dejs - recursive processing of `json` values diff --git a/pkg/arvo/tests/sys/zuse/ordered-map.hoon b/pkg/arvo/tests/sys/zuse/ordered-map.hoon index 261b8f0851..247b85c91b 100644 --- a/pkg/arvo/tests/sys/zuse/ordered-map.hoon +++ b/pkg/arvo/tests/sys/zuse/ordered-map.hoon @@ -9,6 +9,7 @@ (items-from-keys (gulf 0 6)) :: =/ atom-map ((ordered-map @ud @tas) lte) +=/ gte-atom-map ((ordered-map @ud @tas) gte) :: |% ++ test-ordered-map-gas ^- tang @@ -17,7 +18,7 @@ :: %+ expect-eq !> %.y - !> (check-balance:atom-map a) + !> (apt:atom-map a) :: ++ test-ordered-map-tap ^- tang :: @@ -27,6 +28,72 @@ !> test-items !> (tap:atom-map a) :: +++ test-ordered-map-tab-gte ^- tang + :: + =/ a=(tree [@ud @tas]) (gas:gte-atom-map ~ test-items) + :: + %+ expect-eq + !> (flop test-items) + !> (tab:gte-atom-map a ~ 7) +:: +++ test-ordered-map-tab-gte-starting-from ^- tang + :: + =/ a=(tree [@ud @tas]) (gas:gte-atom-map ~ test-items) + =/ small-test-items=(list [@ud @tas]) + (items-from-keys (gulf 2 5)) + :: + %+ expect-eq + !> (flop small-test-items) + !> (tab:gte-atom-map a [~ 6] 4) +:: +++ test-ordered-map-tab-gte-count ^- tang + :: + =/ a=(tree [@ud @tas]) (gas:gte-atom-map ~ test-items) + =/ small-test-items=(list [@ud @tas]) + (items-from-keys (gulf 4 6)) + :: + %+ expect-eq + !> (flop small-test-items) + !> (tab:gte-atom-map a ~ 3) +:: +++ test-ordered-map-tab ^- tang + :: + =/ a=(tree [@ud @tas]) (gas:atom-map ~ test-items) + :: + %+ expect-eq + !> test-items + !> (tab:atom-map a ~ 7) +:: +++ test-ordered-map-tab-starting-from ^- tang + :: + =/ a=(tree [@ud @tas]) (gas:atom-map ~ test-items) + =/ small-test-items=(list [@ud @tas]) + (items-from-keys (gulf 1 4)) + :: + %+ expect-eq + !> small-test-items + !> (tab:atom-map a [~ 0] 4) +:: +++ test-ordered-map-tab-count ^- tang + :: + =/ a=(tree [@ud @tas]) (gas:atom-map ~ test-items) + =/ small-test-items=(list [@ud @tas]) + (items-from-keys (gulf 0 2)) + :: + %+ expect-eq + !> small-test-items + !> (tab:atom-map a ~ 3) +:: +++ test-ordered-map-tab-more-than-exist ^- tang + :: + =/ specific-test-items=(list [@ud @tas]) + (items-from-keys (gulf 1 6)) + =/ a=(tree [@ud @tas]) (gas:atom-map ~ specific-test-items) + :: + %+ expect-eq + !> specific-test-items + !> (tab:atom-map a [~ 0] 8) +:: ++ test-ordered-map-pop ^- tang :: =/ a=(tree [@ud @tas]) (gas:atom-map ~ test-items) @@ -35,13 +102,13 @@ !> [[0 %a] (gas:atom-map ~ (items-from-keys (gulf 1 6)))] !> (pop:atom-map a) :: -++ test-ordered-map-peek ^- tang +++ test-ordered-map-pry ^- tang :: =/ a=(tree [@ud @tas]) (gas:atom-map ~ test-items) :: %+ expect-eq !> `[0 %a] - !> (peek:atom-map a) + !> (pry:atom-map a) :: ++ test-ordered-map-nip ^- tang :: @@ -53,61 +120,61 @@ !> (gas:atom-map ~ ~[[0^%a] [1^%b] [2^%c] [3^%d] [4^%e] [5^%f]]) !> b :: -++ test-ordered-map-subset ^- tang +++ test-ordered-map-lot ^- tang :: =/ a=(tree [@ud @tas]) (gas:atom-map ~ test-items) :: - =/ b (subset:atom-map a `0 `4) + =/ b (lot:atom-map a `0 `4) :: %+ expect-eq !> (gas:atom-map ~ ~[[1^%b] [2^%c] [3^%d]]) !> b :: -++ test-ordered-map-null-start-subset ^- tang +++ test-ordered-map-null-start-lot ^- tang :: =/ a=(tree [@ud @tas]) (gas:atom-map ~ test-items) :: - =/ b (subset:atom-map a ~ `5) + =/ b (lot:atom-map a ~ `5) :: %+ expect-eq !> (gas:atom-map ~ ~[[0^%a] [1^%b] [2^%c] [3^%d] [4^%e]]) !> b :: -++ test-ordered-map-null-end-subset ^- tang +++ test-ordered-map-null-end-lot ^- tang :: =/ a=(tree [@ud @tas]) (gas:atom-map ~ test-items) :: - =/ b (subset:atom-map a `1 ~) + =/ b (lot:atom-map a `1 ~) :: %+ expect-eq !> (gas:atom-map ~ ~[[2^%c] [3^%d] [4^%e] [5^%f] [6^%g]]) !> b :: -++ test-ordered-map-double-null-subset ^- tang +++ test-ordered-map-double-null-lot ^- tang :: =/ a=(tree [@ud @tas]) (gas:atom-map ~ test-items) :: - =/ b (subset:atom-map a ~ ~) + =/ b (lot:atom-map a ~ ~) :: %+ expect-eq !> (gas:atom-map ~ ~[[0^%a] [1^%b] [2^%c] [3^%d] [4^%e] [5^%f] [6^%g]]) !> b :: -++ test-ordered-map-not-found-start-subset ^- tang +++ test-ordered-map-not-found-start-lot ^- tang :: =/ a=(tree [@ud @tas]) (gas:atom-map ~ ~[[1^%b]]) :: - =/ b (subset:atom-map a `0 ~) + =/ b (lot:atom-map a `0 ~) :: %+ expect-eq !> (gas:atom-map ~ ~[[1^%b]]) !> b :: -++ test-ordered-map-traverse ^- tang +++ test-ordered-map-dip ^- tang :: =/ a=(tree [@ud @tas]) (gas:atom-map ~ test-items) :: - =/ b %- (traverse:atom-map ,(list [@ud @tas])) + =/ b %- (dip:atom-map ,(list [@ud @tas])) :* a state=~ :: @@ -129,11 +196,11 @@ !> -.b == :: -++ test-ordered-map-traverse-delete-all ^- tang +++ test-ordered-map-dip-delete-all ^- tang ;: weld =/ q ((ordered-map ,@ ,~) lte) =/ o (gas:q ~ ~[1/~ 2/~ 3/~]) - =/ b ((traverse:q ,~) o ~ |=([~ key=@ ~] [~ %| ~])) + =/ b ((dip:q ,~) o ~ |=([~ key=@ ~] [~ %| ~])) %+ expect-eq !> [~ ~] !> b @@ -147,7 +214,7 @@ ?:((lth aa ba) %.y ?:((gth aa ba) %.n (lte ab bb))) =/ q ((ordered-map ,[@ @] ,~) compare) =/ o (gas:q ~ c) - =/ b ((traverse:q ,~) o ~ |=([~ key=[@ @] ~] [~ %| ~])) + =/ b ((dip:q ,~) o ~ |=([~ key=[@ @] ~] [~ %| ~])) %+ expect-eq !> [~ ~] !> b diff --git a/pkg/btc-wallet/README.md b/pkg/btc-wallet/README.md new file mode 100644 index 0000000000..9c8cc6f54f --- /dev/null +++ b/pkg/btc-wallet/README.md @@ -0,0 +1,8 @@ +To verify your version of the bitcoin wallet, run the following command in the +dojo: + +`> +btc-wallet-check` + +it should return with the following hash: + +`0v2.3qak4.al612.8m1ig.kg03r.mfide` diff --git a/pkg/btc-wallet/config/urbitrc-sample b/pkg/btc-wallet/config/urbitrc-sample new file mode 100644 index 0000000000..0654ff48d1 --- /dev/null +++ b/pkg/btc-wallet/config/urbitrc-sample @@ -0,0 +1,6 @@ +module.exports = { + URBIT_PIERS: [ + "%URBITPIER%", + ], + URL: 'http://localhost:80' +}; diff --git a/pkg/btc-wallet/config/webpack.dev.js b/pkg/btc-wallet/config/webpack.dev.js new file mode 100644 index 0000000000..368de77177 --- /dev/null +++ b/pkg/btc-wallet/config/webpack.dev.js @@ -0,0 +1,128 @@ +const path = require('path'); +const webpack = require('webpack'); +// const HtmlWebpackPlugin = require('html-webpack-plugin'); +// const { CleanWebpackPlugin } = require('clean-webpack-plugin'); +const urbitrc = require('./urbitrc'); +const fs = require('fs-extra'); +const _ = require('lodash'); + +function copy(src,dest) { + return new Promise((res,rej) => + fs.copy(src,dest, err => err ? rej(err) : res())); +} + +class UrbitShipPlugin { + constructor(urbitrc) { + this.piers = urbitrc.URBIT_PIERS; + } + + apply(compiler) { + compiler.hooks.afterEmit.tapPromise( + 'UrbitShipPlugin', + async (compilation) => { + const src = path.resolve(compiler.options.output.path, 'index.js'); + } + ); + } +} + +let devServer = { + contentBase: path.join(__dirname, '../dist'), + hot: true, + port: 9000, + host: '0.0.0.0', + disableHostCheck: true, + historyApiFallback: true, +}; + +const router = _.mapKeys(urbitrc.FLEET || {}, (value, key) => `${key}.localhost:9000`); + +if(urbitrc.URL) { + devServer = { + ...devServer, + index: '', + proxy: { + '/~btc/js/bundle/index.*.js': { + target: 'http://localhost:9000', + pathRewrite: (req, path) => { + return '/index.js' + } + }, + '**': { + changeOrigin: true, + target: urbitrc.URL, + router, + // ensure proxy doesn't timeout channels + proxyTimeout: 0 + } + } + }; +} + +module.exports = { + node: { fs: 'empty' }, + mode: 'development', + entry: { + app: './src/index.js' + }, + module: { + rules: [ + { + test: /\.(j|t)sx?$/, + use: { + loader: 'babel-loader', + options: { + presets: ['@babel/preset-env', ['@babel/preset-react', { + runtime: 'automatic', + development: 'true', + importSource: '@welldone-software/why-did-you-render', + }]], + plugins: [ + '@babel/transform-runtime', + '@babel/plugin-proposal-object-rest-spread', + '@babel/plugin-proposal-optional-chaining', + '@babel/plugin-proposal-class-properties', + 'react-hot-loader/babel' + ] + } + }, + exclude: /node_modules/ + }, + { + test: /\.css$/i, + use: [ + // Creates `style` nodes from JS strings + 'style-loader', + // Translates CSS into CommonJS + 'css-loader', + // Compiles Sass to CSS + 'sass-loader' + ] + } + ] + }, + resolve: { + extensions: ['.js', '.ts', '.tsx'] + }, + devtool: 'inline-source-map', + devServer: devServer, + plugins: [ + new UrbitShipPlugin(urbitrc) + ], + watch: true, + watchOptions: { + poll: true, + ignored: '/node_modules/' + }, + output: { + filename: 'index.js', + chunkFilename: 'index.js', + path: path.resolve(__dirname, '../dist'), + publicPath: '/', + globalObject: 'this' + }, + optimization: { + minimize: false, + usedExports: true + } +}; diff --git a/pkg/btc-wallet/config/webpack.prod.js b/pkg/btc-wallet/config/webpack.prod.js new file mode 100644 index 0000000000..ed7403784e --- /dev/null +++ b/pkg/btc-wallet/config/webpack.prod.js @@ -0,0 +1,60 @@ +const path = require('path'); +const { CleanWebpackPlugin } = require('clean-webpack-plugin'); +// const urbitrc = require('./urbitrc'); + +module.exports = { + node: { fs: 'empty' }, + mode: 'production', + entry: { + app: './src/index.js' + }, + module: { + rules: [ + { + test: /\.jsx?$/, + use: { + loader: 'babel-loader', + options: { + presets: ['@babel/preset-env', '@babel/preset-react'], + plugins: [ + '@babel/transform-runtime', + '@babel/plugin-proposal-object-rest-spread', + '@babel/plugin-proposal-optional-chaining', + '@babel/plugin-proposal-class-properties' + ] + } + }, + exclude: /node_modules/ + }, + { + test: /\.css$/i, + use: [ + // Creates `style` nodes from JS strings + 'style-loader', + // Translates CSS into CommonJS + 'css-loader', + // Compiles Sass to CSS + 'sass-loader' + ] + } + ] + }, + resolve: { + extensions: ['.js', '.ts', '.tsx'] + }, + devtool: 'source-map', + plugins: [ + new CleanWebpackPlugin() + ], + output: { + filename: (pathData) => { + return pathData.chunk.name === 'app' ? 'index.[contenthash].js' : '[name].js'; + }, + path: path.resolve(__dirname, `../../arvo/app/btc-wallet/js/bundle`), + publicPath: '/', + }, + optimization: { + minimize: true, + usedExports: true + } +}; diff --git a/pkg/btc-wallet/package-lock.json b/pkg/btc-wallet/package-lock.json new file mode 100644 index 0000000000..1f96d7f142 Binary files /dev/null and b/pkg/btc-wallet/package-lock.json differ diff --git a/pkg/btc-wallet/package.json b/pkg/btc-wallet/package.json new file mode 100644 index 0000000000..0b0a456045 --- /dev/null +++ b/pkg/btc-wallet/package.json @@ -0,0 +1,75 @@ +{ + "name": "urbit-bitcoin-wallet", + "version": "0.1.0", + "main": "node install.js", + "scripts": { + "start": "webpack-dev-server --config config/webpack.dev.js", + "build:dev": "cross-env NODE_ENV=production webpack --config config/webpack.dev.js", + "build:prod": "cross-env NODE_ENV=production webpack --config config/webpack.prod.js" + }, + "author": "Tlon Corp", + "license": "MIT", + "devDependencies": { + "@babel/core": "^7.9.0", + "@babel/plugin-proposal-class-properties": "^7.8.3", + "@babel/plugin-proposal-object-rest-spread": "^7.9.5", + "@babel/plugin-proposal-optional-chaining": "^7.9.0", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", + "@babel/plugin-transform-runtime": "^7.10.5", + "@babel/preset-env": "^7.9.5", + "@babel/preset-react": "^7.9.4", + "@babel/preset-typescript": "^7.13.0", + "@welldone-software/why-did-you-render": "^6.1.1", + "babel-loader": "^8.1.0", + "babel-plugin-root-import": "^6.5.0", + "clean-webpack-plugin": "^3.0.0", + "cross-env": "^7.0.2", + "file-loader": "^6.0.0", + "html-webpack-plugin": "^4.2.0", + "react-hot-loader": "^4.12.21", + "sass": "^1.26.5", + "sass-loader": "^8.0.2", + "typescript": "^4.2.3", + "webpack": "^4.43.0", + "webpack-cli": "^3.3.11", + "webpack-dev-server": "^3.10.3" + }, + "dependencies": { + "@babel/runtime": "^7.10.5", + "@reach/disclosure": "^0.10.5", + "@reach/menu-button": "^0.10.5", + "@reach/tabs": "^0.10.5", + "@tlon/indigo-light": "^1.0.7", + "@tlon/indigo-react": "^1.2.22", + "@tlon/sigil-js": "^1.4.3", + "bip39": "^2.5.0", + "bitcoin-address-validation": "^2.0.1", + "bitcoinjs-lib": "^5.2.0", + "bs58check": "^2.1.2", + "buffer": "^6.0.3", + "classnames": "^2.2.6", + "css-loader": "^3.5.3", + "formik": "^2.2.0", + "fs-extra": "^8.1.0", + "lodash": "^4.17.11", + "markdown-to-jsx": "^7.1.2", + "moment": "^2.20.1", + "mousetrap": "^1.6.3", + "mv": "^2.1.1", + "promise": "^8.0.3", + "prompt": "^1.0.0", + "react": "^16.14.0", + "react-dom": "^16.14.0", + "react-router-dom": "^5.0.0", + "replace-in-file": "^4.1.1", + "style-loader": "^1.2.1", + "styled-components": "^5.2.3", + "styled-system": "^5.1.5", + "urbit-key-generation": "^0.19.0", + "urbit-ob": "^5.0.0", + "urbit-sigil-js": "^1.3.13" + }, + "resolutions": { + "natives": "1.1.3" + } +} diff --git a/pkg/btc-wallet/src/css/custom.css b/pkg/btc-wallet/src/css/custom.css new file mode 100644 index 0000000000..e5a56b4519 --- /dev/null +++ b/pkg/btc-wallet/src/css/custom.css @@ -0,0 +1,158 @@ +p, h1, h2, h3, h4, h5, h6, a, input, textarea, button { + margin-block-end: unset; + margin-block-start: unset; + -webkit-margin-before: unset; + -webkit-margin-after: unset; + font-family: Inter, sans-serif; +} + +a { + color: #000; + text-decoration: none; +} + +textarea, select, input, button { + outline: none; + -webkit-appearance: none; + border: none; + background-color: #fff; +} + +.body-regular { + font-size: 16px; + line-height: 24px; + font-weight: 600; +} + +.body-large { + font-size: 20px; + line-height: 24px; +} + +.label-regular { + font-size: 14px; + line-height: 24px; +} + +.label-small-mono { + font-size: 12px; + line-height: 24px; + font-family: "Source Code Pro", monospace; +} + +.body-regular-400 { + font-size: 16px; + line-height: 24px; + font-weight: 400; +} + +.plus-font { + font-size: 48px; + line-height: 24px; +} + +.btn-font { + font-size: 14px; + line-height: 16px; + font-weight: 600; +} +.mono { + font-family: "Source Code Pro", monospace; +} + +.inter { + font-family: Inter, sans-serif; +} + +.mix-blend-diff { + mix-blend-mode: difference; +} + +/* dark */ + +@media (prefers-color-scheme: dark) { + body { + background-color: #333; + } + .bg-black-d { + background-color: black; + } + .white-d { + color: white; + } + .gray1-d { + color: #4d4d4d; + } + .gray2-d { + color: #7f7f7f; + } + .gray3-d { + color: #b1b2b3; + } + .gray4-d { + color: #e6e6e6; + } + .bg-gray0-d { + background-color: #333; + } + .bg-gray1-d { + background-color: #4d4d4d; + } + .b--gray0-d { + border-color: #333; + } + .b--gray1-d { + border-color: #4d4d4d; + } + .b--gray2-d { + border-color: #7f7f7f; + } + .b--white-d { + border-color: #fff; + } + .bb-d { + border-bottom-width: 1px; + border-bottom-style: solid; + } + .invert-d { + filter: invert(1); + } + .o-80-d { + opacity: .8; + } + .focus-b--white-d:focus { + border-color: #fff; + } + a { + color: #fff; + } + .hover-bg-gray1-d:hover { + color: #4d4d4d; + } +} + +/* responsive */ + +@media all and (max-width: 34.375em) { + .h-100-minus-40-s { + height: calc(100% - 40px); + } +} + +@media all and (min-width: 34.375em) and (max-width: 46.875em) { + .h-100-minus-40-m { + height: calc(100% - 40px); + } +} + +@media all and (min-width: 46.875em) and (max-width: 60em) { + .h-100-minus-40-l { + height: calc(100% - 40px); + } +} + +@media all and (min-width: 60em) { + .h-100-minus-40-xl { + height: calc(100% - 40px); + } +} diff --git a/pkg/btc-wallet/src/css/fonts.css b/pkg/btc-wallet/src/css/fonts.css new file mode 100644 index 0000000000..34de928ec5 --- /dev/null +++ b/pkg/btc-wallet/src/css/fonts.css @@ -0,0 +1,63 @@ +@font-face { + font-family: 'Inter'; + font-style: normal; + font-weight: 400; + src: url("https://media.urbit.org/fonts/Inter-Regular.woff2") format("woff2"); +} + +@font-face { + font-family: 'Inter'; + font-style: italic; + font-weight: 400; + src: url("https://media.urbit.org/fonts/Inter-Italic.woff2") format("woff2"); +} + +@font-face { + font-family: 'Inter'; + font-style: normal; + font-weight: 700; + src: url("https://media.urbit.org/fonts/Inter-Bold.woff2") format("woff2"); +} +@font-face { + font-family: 'Inter'; + font-style: italic; + font-weight: 700; + src: url("https://media.urbit.org/fonts/Inter-BoldItalic.woff2") format("woff2"); +} + +@font-face { + font-family: "Source Code Pro"; + src: url("https://storage.googleapis.com/media.urbit.org/fonts/scp-extralight.woff"); + font-weight: 200; +} + +@font-face { + font-family: "Source Code Pro"; + src: url("https://storage.googleapis.com/media.urbit.org/fonts/scp-light.woff"); + font-weight: 300; +} + +@font-face { + font-family: "Source Code Pro"; + src: url("https://storage.googleapis.com/media.urbit.org/fonts/scp-regular.woff"); + font-weight: 400; +} + +@font-face { + font-family: "Source Code Pro"; + src: url("https://storage.googleapis.com/media.urbit.org/fonts/scp-medium.woff"); + font-weight: 500; +} + +@font-face { + font-family: "Source Code Pro"; + src: url("https://storage.googleapis.com/media.urbit.org/fonts/scp-semibold.woff"); + font-weight: 600; +} + +@font-face { + font-family: "Source Code Pro"; + src: url("https://storage.googleapis.com/media.urbit.org/fonts/scp-bold.woff"); + font-weight: 700; +} + diff --git a/pkg/btc-wallet/src/css/indigo-static.css b/pkg/btc-wallet/src/css/indigo-static.css new file mode 100644 index 0000000000..bc23d73a1c --- /dev/null +++ b/pkg/btc-wallet/src/css/indigo-static.css @@ -0,0 +1 @@ +/*! normalize.css v7.0.0 | MIT License | github.com/necolas/normalize.css */html{line-height:1.15;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}body{margin:0}article,aside,footer,header,nav,section{display:block}h1{font-size:2em;margin:.67em 0}figcaption,figure,main{display:block}figure{margin:1em 40px}hr{box-sizing:content-box;height:0;overflow:visible}pre{font-family:monospace,monospace;font-size:1em}a{background-color:transparent;-webkit-text-decoration-skip:objects}abbr[title]{border-bottom:none;text-decoration:underline;-webkit-text-decoration:underline dotted;text-decoration:underline dotted}b,strong{font-weight:inherit;font-weight:bolder}code,kbd,samp{font-family:monospace,monospace;font-size:1em}dfn{font-style:italic}mark{background-color:#ff0;color:#000}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}audio,video{display:inline-block}audio:not([controls]){display:none;height:0}img{border-style:none}svg:not(:root){overflow:hidden}button,input,optgroup,select,textarea{font-family:sans-serif;font-size:100%;line-height:1.15;margin:0}button,input{overflow:visible}button,select{text-transform:none}[type=reset],[type=submit],button,html [type=button]{-webkit-appearance:button}[type=button]::-moz-focus-inner,[type=reset]::-moz-focus-inner,[type=submit]::-moz-focus-inner,button::-moz-focus-inner{border-style:none;padding:0}[type=button]:-moz-focusring,[type=reset]:-moz-focusring,[type=submit]:-moz-focusring,button:-moz-focusring{outline:1px dotted ButtonText}fieldset{padding:.35em .75em .625em}legend{box-sizing:border-box;color:inherit;display:table;max-width:100%;padding:0;white-space:normal}progress{display:inline-block;vertical-align:baseline}textarea{overflow:auto}[type=checkbox],[type=radio]{box-sizing:border-box;padding:0}[type=number]::-webkit-inner-spin-button,[type=number]::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}[type=search]::-webkit-search-cancel-button,[type=search]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}details,menu{display:block}summary{display:list-item}canvas{display:inline-block}[hidden],template{display:none}.aspect-ratio{height:0;position:relative}.aspect-ratio--16x9{padding-bottom:56.25%}.aspect-ratio--9x16{padding-bottom:177.77%}.aspect-ratio--4x3{padding-bottom:75%}.aspect-ratio--3x4{padding-bottom:133.33%}.aspect-ratio--6x4{padding-bottom:66.6%}.aspect-ratio--4x6{padding-bottom:150%}.aspect-ratio--8x5{padding-bottom:62.5%}.aspect-ratio--5x8{padding-bottom:160%}.aspect-ratio--7x5{padding-bottom:71.42%}.aspect-ratio--5x7{padding-bottom:140%}.aspect-ratio--1x1{padding-bottom:100%}.aspect-ratio--object{position:absolute;top:0;right:0;bottom:0;left:0;width:100%;height:100%;z-index:100}.cover{background-size:cover!important}.contain{background-size:contain!important}.bg-center{background-position:50%}.bg-center,.bg-top{background-repeat:no-repeat}.bg-top{background-position:top}.bg-right{background-position:100%}.bg-bottom,.bg-right{background-repeat:no-repeat}.bg-bottom{background-position:bottom}.bg-left{background-repeat:no-repeat;background-position:0}.ba{border-style:solid;border-width:1px}.bt{border-top-style:solid;border-top-width:1px}.br{border-right-style:solid;border-right-width:1px}.bb{border-bottom-style:solid;border-bottom-width:1px}.bl{border-left-style:solid;border-left-width:1px}.bn{border-style:none;border-width:0}.b--black{border-color:#000}.b--white{border-color:#fff}.b--gray0{border-color:#333}.b--gray1{border-color:#4d4d4d}.b--gray2{border-color:#7f7f7f}.b--gray3{border-color:#b1b2b3}.b--gray4{border-color:#e6e6e6}.b--gray5{border-color:#f9f9f9}.b--blue0{border-color:#ecf6ff}.b--blue1{border-color:#b0c7ff}.b--blue2{border-color:#4330fc}.b--blue3{border-color:#190d7b}.b--red0{border-color:#f9d6ce}.b--red1{border-color:#ffa073}.b--red2{border-color:#ee5432}.b--red3{border-color:#c10d30}.b--green0{border-color:#bdebcc}.b--green1{border-color:#2ed196}.b--green2{border-color:#2aa779}.b--green3{border-color:#286e55}.b--yellow0{border-color:#ffefc5}.b--yellow1{border-color:#ffd972}.b--yellow2{border-color:#fcc440}.b--yellow3{border-color:#ee892b}.b--transparent{border-color:transparent}.br0{border-radius:0}.br1{border-radius:.125rem}.br2{border-radius:.25rem}.br3{border-radius:.5rem}.br4{border-radius:1rem}.br-100{border-radius:100%}.br-pill{border-radius:9999px}.br--bottom{border-top-left-radius:0;border-top-right-radius:0}.br--top{border-bottom-right-radius:0}.br--right,.br--top{border-bottom-left-radius:0}.br--right{border-top-left-radius:0}.br--left{border-top-right-radius:0;border-bottom-right-radius:0}.b--dotted{border-style:dotted}.b--dashed{border-style:dashed}.b--solid{border-style:solid}.b--none{border-style:none}.bw0{border-width:0}.bw1{border-width:.125rem}.bw2{border-width:.25rem}.bw3{border-width:.5rem}.bw4{border-width:1rem}.bw5{border-width:2rem}.bt-0{border-top-width:0}.br-0{border-right-width:0}.bb-0{border-bottom-width:0}.bl-0{border-left-width:0}.shadow-1{box-shadow:0 0 4px 2px rgba(0,0,0,.2)}.shadow-2{box-shadow:0 0 8px 2px rgba(0,0,0,.2)}.shadow-3{box-shadow:2px 2px 4px 2px rgba(0,0,0,.2)}.shadow-4{box-shadow:2px 2px 8px 0 rgba(0,0,0,.2)}.shadow-5{box-shadow:4px 4px 8px 0 rgba(0,0,0,.2)}.border-box,a,article,aside,blockquote,body,code,dd,div,dl,dt,fieldset,figcaption,figure,footer,form,h1,h2,h3,h4,h5,h6,header,html,input[type=email],input[type=number],input[type=password],input[type=tel],input[type=text],input[type=url],legend,li,main,nav,ol,p,pre,section,table,td,textarea,th,tr,ul{box-sizing:border-box}.pre{overflow-x:auto;overflow-y:hidden;overflow:scroll}.pa0{padding:0}.ma0,.na0{margin:0}.pl0{padding-left:0}.ml0,.nl0{margin-left:0}.pr0{padding-right:0}.mr0,.nr0{margin-right:0}.pt0{padding-top:0}.mt0,.nt0{margin-top:0}.pb0{padding-bottom:0}.mb0,.nb0{margin-bottom:0}.pv0{padding-top:0;padding-bottom:0}.mv0,.nv0{margin-top:0;margin-bottom:0}.ph0{padding-left:0;padding-right:0}.mh0,.nh0{margin-left:0;margin-right:0}.pa1{padding:.25rem}.ma1{margin:.25rem}.na1{margin:-.25rem}.pl1{padding-left:.25rem}.ml1{margin-left:.25rem}.nl1{margin-left:-.25rem}.pr1{padding-right:.25rem}.mr1{margin-right:.25rem}.nr1{margin-right:-.25rem}.pt1{padding-top:.25rem}.mt1{margin-top:.25rem}.nt1{margin-top:-.25rem}.pb1{padding-bottom:.25rem}.mb1{margin-bottom:.25rem}.nb1{margin-bottom:-.25rem}.pv1{padding-top:.25rem;padding-bottom:.25rem}.mv1{margin-top:.25rem;margin-bottom:.25rem}.nv1{margin-top:-.25rem;margin-bottom:-.25rem}.ph1{padding-left:.25rem;padding-right:.25rem}.mh1{margin-left:.25rem;margin-right:.25rem}.nh1{margin-left:-.25rem;margin-right:-.25rem}.pa2{padding:.5rem}.ma2{margin:.5rem}.na2{margin:-.5rem}.pl2{padding-left:.5rem}.ml2{margin-left:.5rem}.nl2{margin-left:-.5rem}.pr2{padding-right:.5rem}.mr2{margin-right:.5rem}.nr2{margin-right:-.5rem}.pt2{padding-top:.5rem}.mt2{margin-top:.5rem}.nt2{margin-top:-.5rem}.pb2{padding-bottom:.5rem}.mb2{margin-bottom:.5rem}.nb2{margin-bottom:-.5rem}.pv2{padding-top:.5rem;padding-bottom:.5rem}.mv2{margin-top:.5rem;margin-bottom:.5rem}.nv2{margin-top:-.5rem;margin-bottom:-.5rem}.ph2{padding-left:.5rem;padding-right:.5rem}.mh2{margin-left:.5rem;margin-right:.5rem}.nh2{margin-left:-.5rem;margin-right:-.5rem}.pa3{padding:.75rem}.ma3{margin:.75rem}.na3{margin:-.75rem}.pl3{padding-left:.75rem}.ml3{margin-left:.75rem}.nl3{margin-left:-.75rem}.pr3{padding-right:.75rem}.mr3{margin-right:.75rem}.nr3{margin-right:-.75rem}.pt3{padding-top:.75rem}.mt3{margin-top:.75rem}.nt3{margin-top:-.75rem}.pb3{padding-bottom:.75rem}.mb3{margin-bottom:.75rem}.nb3{margin-bottom:-.75rem}.pv3{padding-top:.75rem;padding-bottom:.75rem}.mv3{margin-top:.75rem;margin-bottom:.75rem}.nv3{margin-top:-.75rem;margin-bottom:-.75rem}.ph3{padding-left:.75rem;padding-right:.75rem}.mh3{margin-left:.75rem;margin-right:.75rem}.nh3{margin-left:-.75rem;margin-right:-.75rem}.pa4{padding:1rem}.ma4{margin:1rem}.na4{margin:-1rem}.pl4{padding-left:1rem}.ml4{margin-left:1rem}.nl4{margin-left:-1rem}.pr4{padding-right:1rem}.mr4{margin-right:1rem}.nr4{margin-right:-1rem}.pt4{padding-top:1rem}.mt4{margin-top:1rem}.nt4{margin-top:-1rem}.pb4{padding-bottom:1rem}.mb4{margin-bottom:1rem}.nb4{margin-bottom:-1rem}.pv4{padding-top:1rem;padding-bottom:1rem}.mv4{margin-top:1rem;margin-bottom:1rem}.nv4{margin-top:-1rem;margin-bottom:-1rem}.ph4{padding-left:1rem;padding-right:1rem}.mh4{margin-left:1rem;margin-right:1rem}.nh4{margin-left:-1rem;margin-right:-1rem}.pa5{padding:1.25rem}.ma5{margin:1.25rem}.na5{margin:-1.25rem}.pl5{padding-left:1.25rem}.ml5{margin-left:1.25rem}.nl5{margin-left:-1.25rem}.pr5{padding-right:1.25rem}.mr5{margin-right:1.25rem}.nr5{margin-right:-1.25rem}.pt5{padding-top:1.25rem}.mt5{margin-top:1.25rem}.nt5{margin-top:-1.25rem}.pb5{padding-bottom:1.25rem}.mb5{margin-bottom:1.25rem}.nb5{margin-bottom:-1.25rem}.pv5{padding-top:1.25rem;padding-bottom:1.25rem}.mv5{margin-top:1.25rem;margin-bottom:1.25rem}.nv5{margin-top:-1.25rem;margin-bottom:-1.25rem}.ph5{padding-left:1.25rem;padding-right:1.25rem}.mh5{margin-left:1.25rem;margin-right:1.25rem}.nh5{margin-left:-1.25rem;margin-right:-1.25rem}.pa6{padding:1.5rem}.ma6{margin:1.5rem}.na6{margin:-1.5rem}.pl6{padding-left:1.5rem}.ml6{margin-left:1.5rem}.nl6{margin-left:-1.5rem}.pr6{padding-right:1.5rem}.mr6{margin-right:1.5rem}.nr6{margin-right:-1.5rem}.pt6{padding-top:1.5rem}.mt6{margin-top:1.5rem}.nt6{margin-top:-1.5rem}.pb6{padding-bottom:1.5rem}.mb6{margin-bottom:1.5rem}.nb6{margin-bottom:-1.5rem}.pv6{padding-top:1.5rem;padding-bottom:1.5rem}.mv6{margin-top:1.5rem;margin-bottom:1.5rem}.nv6{margin-top:-1.5rem;margin-bottom:-1.5rem}.ph6{padding-left:1.5rem;padding-right:1.5rem}.mh6{margin-left:1.5rem;margin-right:1.5rem}.nh6{margin-left:-1.5rem;margin-right:-1.5rem}.pa7{padding:2rem}.ma7{margin:2rem}.na7{margin:-2rem}.pl7{padding-left:2rem}.ml7{margin-left:2rem}.nl7{margin-left:-2rem}.pr7{padding-right:2rem}.mr7{margin-right:2rem}.nr7{margin-right:-2rem}.pt7{padding-top:2rem}.mt7{margin-top:2rem}.nt7{margin-top:-2rem}.pb7{padding-bottom:2rem}.mb7{margin-bottom:2rem}.nb7{margin-bottom:-2rem}.pv7{padding-top:2rem;padding-bottom:2rem}.mv7{margin-top:2rem;margin-bottom:2rem}.nv7{margin-top:-2rem;margin-bottom:-2rem}.ph7{padding-left:2rem;padding-right:2rem}.mh7{margin-left:2rem;margin-right:2rem}.nh7{margin-left:-2rem;margin-right:-2rem}.pa8{padding:3rem}.ma8{margin:3rem}.na8{margin:-3rem}.pl8{padding-left:3rem}.ml8{margin-left:3rem}.nl8{margin-left:-3rem}.pr8{padding-right:3rem}.mr8{margin-right:3rem}.nr8{margin-right:-3rem}.pt8{padding-top:3rem}.mt8{margin-top:3rem}.nt8{margin-top:-3rem}.pb8{padding-bottom:3rem}.mb8{margin-bottom:3rem}.nb8{margin-bottom:-3rem}.pv8{padding-top:3rem;padding-bottom:3rem}.mv8{margin-top:3rem;margin-bottom:3rem}.nv8{margin-top:-3rem;margin-bottom:-3rem}.ph8{padding-left:3rem;padding-right:3rem}.mh8{margin-left:3rem;margin-right:3rem}.nh8{margin-left:-3rem;margin-right:-3rem}.pa9{padding:4rem}.ma9{margin:4rem}.na9{margin:-4rem}.pl9{padding-left:4rem}.ml9{margin-left:4rem}.nl9{margin-left:-4rem}.pr9{padding-right:4rem}.mr9{margin-right:4rem}.nr9{margin-right:-4rem}.pt9{padding-top:4rem}.mt9{margin-top:4rem}.nt9{margin-top:-4rem}.pb9{padding-bottom:4rem}.mb9{margin-bottom:4rem}.nb9{margin-bottom:-4rem}.pv9{padding-top:4rem;padding-bottom:4rem}.mv9{margin-top:4rem;margin-bottom:4rem}.nv9{margin-top:-4rem;margin-bottom:-4rem}.ph9{padding-left:4rem;padding-right:4rem}.mh9{margin-left:4rem;margin-right:4rem}.nh9{margin-left:-4rem;margin-right:-4rem}.pa10{padding:6rem}.ma10{margin:6rem}.na10{margin:-6rem}.pl10{padding-left:6rem}.ml10{margin-left:6rem}.nl10{margin-left:-6rem}.pr10{padding-right:6rem}.mr10{margin-right:6rem}.nr10{margin-right:-6rem}.pt10{padding-top:6rem}.mt10{margin-top:6rem}.nt10{margin-top:-6rem}.pb10{padding-bottom:6rem}.mb10{margin-bottom:6rem}.nb10{margin-bottom:-6rem}.pv10{padding-top:6rem;padding-bottom:6rem}.mv10{margin-top:6rem;margin-bottom:6rem}.nv10{margin-top:-6rem;margin-bottom:-6rem}.ph10{padding-left:6rem;padding-right:6rem}.mh10{margin-left:6rem;margin-right:6rem}.nh10{margin-left:-6rem;margin-right:-6rem}.pa11{padding:10rem}.ma11{margin:10rem}.na11{margin:-10rem}.pl11{padding-left:10rem}.ml11{margin-left:10rem}.nl11{margin-left:-10rem}.pr11{padding-right:10rem}.mr11{margin-right:10rem}.nr11{margin-right:-10rem}.pt11{padding-top:10rem}.mt11{margin-top:10rem}.nt11{margin-top:-10rem}.pb11{padding-bottom:10rem}.mb11{margin-bottom:10rem}.nb11{margin-bottom:-10rem}.pv11{padding-top:10rem;padding-bottom:10rem}.mv11{margin-top:10rem;margin-bottom:10rem}.nv11{margin-top:-10rem;margin-bottom:-10rem}.ph11{padding-left:10rem;padding-right:10rem}.mh11{margin-left:10rem;margin-right:10rem}.nh11{margin-left:-10rem;margin-right:-10rem}.pa12{padding:18rem}.ma12{margin:18rem}.na12{margin:-18rem}.pl12{padding-left:18rem}.ml12{margin-left:18rem}.nl12{margin-left:-18rem}.pr12{padding-right:18rem}.mr12{margin-right:18rem}.nr12{margin-right:-18rem}.pt12{padding-top:18rem}.mt12{margin-top:18rem}.nt12{margin-top:-18rem}.pb12{padding-bottom:18rem}.mb12{margin-bottom:18rem}.nb12{margin-bottom:-18rem}.pv12{padding-top:18rem;padding-bottom:18rem}.mv12{margin-top:18rem;margin-bottom:18rem}.nv12{margin-top:-18rem;margin-bottom:-18rem}.ph12{padding-left:18rem;padding-right:18rem}.mh12{margin-left:18rem;margin-right:18rem}.nh12{margin-left:-18rem;margin-right:-18rem}.top-0{top:0}.right-0{right:0}.bottom-0{bottom:0}.left-0{left:0}.top-1{top:1rem}.right-1{right:1rem}.bottom-1{bottom:1rem}.left-1{left:1rem}.top-2{top:2rem}.right-2{right:2rem}.bottom-2{bottom:2rem}.left-2{left:2rem}.top--1{top:-1rem}.right--1{right:-1rem}.bottom--1{bottom:-1rem}.left--1{left:-1rem}.top--2{top:-2rem}.right--2{right:-2rem}.bottom--2{bottom:-2rem}.left--2{left:-2rem}.absolute--fill{top:0;right:0;bottom:0;left:0}.cf:after,.cf:before{content:" ";display:table}.cf:after{clear:both}.cf{*zoom:1}.cl{clear:left}.cr{clear:right}.cb{clear:both}.cn{clear:none}.dn{display:none}.di{display:inline}.db{display:block}.dib{display:inline-block}.dit{display:inline-table}.dt{display:table}.dtc{display:table-cell}.dt-row{display:table-row}.dt-row-group{display:table-row-group}.dt-column{display:table-column}.dt-column-group{display:table-column-group}.dt--fixed{table-layout:fixed;width:100%}.flex{display:flex}.inline-flex{display:inline-flex}.flex-auto{flex:1 1 auto;min-width:0;min-height:0}.flex-none{flex:none}.flex-column{flex-direction:column}.flex-row{flex-direction:row}.flex-wrap{flex-wrap:wrap}.flex-nowrap{flex-wrap:nowrap}.flex-wrap-reverse{flex-wrap:wrap-reverse}.flex-column-reverse{flex-direction:column-reverse}.flex-row-reverse{flex-direction:row-reverse}.items-start{align-items:flex-start}.items-end{align-items:flex-end}.items-center{align-items:center}.items-baseline{align-items:baseline}.items-stretch{align-items:stretch}.self-start{align-self:flex-start}.self-end{align-self:flex-end}.self-center{align-self:center}.self-baseline{align-self:baseline}.self-stretch{align-self:stretch}.justify-start{justify-content:flex-start}.justify-end{justify-content:flex-end}.justify-center{justify-content:center}.justify-between{justify-content:space-between}.justify-around{justify-content:space-around}.content-start{align-content:flex-start}.content-end{align-content:flex-end}.content-center{align-content:center}.content-between{align-content:space-between}.content-around{align-content:space-around}.content-stretch{align-content:stretch}.order-0{order:0}.order-1{order:1}.order-2{order:2}.order-3{order:3}.order-4{order:4}.order-5{order:5}.order-6{order:6}.order-7{order:7}.order-8{order:8}.order-last{order:99999}.flex-grow-0{flex-grow:0}.flex-grow-1{flex-grow:1}.flex-shrink-0{flex-shrink:0}.flex-shrink-1{flex-shrink:1}.fl{float:left}.fl,.fr{_display:inline}.fr{float:right}.fn{float:none}.sans-serif{font-family:-apple-system,BlinkMacSystemFont,avenir next,avenir,helvetica neue,helvetica,ubuntu,roboto,noto,segoe ui,arial,sans-serif}.serif{font-family:georgia,times,serif}.system-sans-serif{font-family:sans-serif}.system-serif{font-family:serif}.code,code{font-family:Consolas,monaco,monospace}.courier{font-family:Courier Next,courier,monospace}.helvetica{font-family:helvetica neue,helvetica,sans-serif}.avenir{font-family:avenir next,avenir,sans-serif}.athelas{font-family:athelas,georgia,serif}.georgia{font-family:georgia,serif}.times{font-family:times,serif}.bodoni{font-family:Bodoni MT,serif}.calisto{font-family:Calisto MT,serif}.garamond{font-family:garamond,serif}.baskerville{font-family:baskerville,serif}.i{font-style:italic}.fs-normal{font-style:normal}.normal{font-weight:400}.b{font-weight:700}.fw1{font-weight:100}.fw2{font-weight:200}.fw3{font-weight:300}.fw4{font-weight:400}.fw5{font-weight:500}.fw6{font-weight:600}.fw7{font-weight:700}.fw8{font-weight:800}.fw9{font-weight:900}.input-reset{-webkit-appearance:none;-moz-appearance:none}.button-reset::-moz-focus-inner,.input-reset::-moz-focus-inner{border:0;padding:0}.debug *{outline:1px solid gold}.debug-white *{outline:1px solid #fff}.debug-black *{outline:1px solid #000}.debug-grid{background:transparent url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAICAYAAADED76LAAAAFElEQVR4AWPAC97/9x0eCsAEPgwAVLshdpENIxcAAAAASUVORK5CYII=) repeat 0 0}.debug-grid-16{background:transparent url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAMklEQVR4AWOgCLz/b0epAa6UGuBOqQHOQHLUgFEDnAbcBZ4UGwDOkiCnkIhdgNgNxAYAiYlD+8sEuo8AAAAASUVORK5CYII=) repeat 0 0}.debug-grid-8-solid{background:#fff url(data:image/gif;base64,R0lGODdhCAAIAPEAAADw/wDx/////wAAACwAAAAACAAIAAACDZQvgaeb/lxbAIKA8y0AOw==) repeat 0 0}.debug-grid-16-solid{background:#fff url(data:image/gif;base64,R0lGODdhEAAQAPEAAADw/wDx/xXy/////ywAAAAAEAAQAAACIZyPKckYDQFsb6ZqD85jZ2+BkwiRFKehhqQCQgDHcgwEBQA7) repeat 0 0}.link{text-decoration:none}.link,.link:active,.link:focus,.link:hover,.link:link,.link:visited{transition:color .15s ease-in}.link:focus{outline:1px dotted currentColor}.list{list-style-type:none}.h1{height:1rem}.h2{height:2rem}.h3{height:4rem}.h4{height:8rem}.h5{height:16rem}.h-25{height:25%}.h-50{height:50%}.h-75{height:75%}.h-100{height:100%}.min-h-100{min-height:100%}.vh-25{height:25vh}.vh-50{height:50vh}.vh-75{height:75vh}.vh-100{height:100vh}.min-vh-100{min-height:100vh}.h-auto{height:auto}.h-inherit{height:inherit}.black{color:#000}.white{color:#fff}.gray0{color:#333}.gray1{color:#4d4d4d}.gray2{color:#7f7f7f}.gray3{color:#b1b2b3}.gray4{color:#e6e6e6}.gray5{color:#f9f9f9}.blue0{color:#ecf6ff}.blue1{color:#b0c7ff}.blue2{color:#4330fc}.blue3{color:#190d7b}.red0{color:#f9d6ce}.red1{color:#ffa073}.red2{color:#ee5432}.red3{color:#c10d30}.green0{color:#bdebcc}.green1{color:#2ed196}.green2{color:#2aa779}.green3{color:#286e55}.yellow0{color:#ffefc5}.yellow1{color:#ffd972}.yellow2{color:#fcc440}.yellow3{color:#ee892b}.bg-black{background-color:#000}.bg-white{background-color:#fff}.bg-gray0{background-color:#333}.bg-gray1{background-color:#4d4d4d}.bg-gray2{background-color:#7f7f7f}.bg-gray3{background-color:#b1b2b3}.bg-gray4{background-color:#e6e6e6}.bg-gray5{background-color:#f9f9f9}.bg-blue0{background-color:#ecf6ff}.bg-blue1{background-color:#b0c7ff}.bg-blue2{background-color:#4330fc}.bg-blue3{background-color:#190d7b}.bg-red0{background-color:#f9d6ce}.bg-red1{background-color:#ffa073}.bg-red2{background-color:#ee5432}.bg-red3{background-color:#c10d30}.bg-green0{background-color:#bdebcc}.bg-green1{background-color:#2ed196}.bg-green2{background-color:#2aa779}.bg-green3{background-color:#286e55}.bg-yellow0{background-color:#ffefc5}.bg-yellow1{background-color:#ffd972}.bg-yellow2{background-color:#fcc440}.bg-yellow3{background-color:#ee892b}.bg-transparent{background-color:transparent}.hover-black:focus,.hover-black:hover{color:#000}.hover-white:focus,.hover-white:hover{color:#fff}.hover-gray0:focus,.hover-gray0:hover{color:#333}.hover-gray1:focus,.hover-gray1:hover{color:#4d4d4d}.hover-gray2:focus,.hover-gray2:hover{color:#7f7f7f}.hover-gray3:focus,.hover-gray3:hover{color:#b1b2b3}.hover-gray4:focus,.hover-gray4:hover{color:#e6e6e6}.hover-gray5:focus,.hover-gray5:hover{color:#f9f9f9}.hover-blue0:focus,.hover-blue0:hover{color:#ecf6ff}.hover-blue1:focus,.hover-blue1:hover{color:#b0c7ff}.hover-blue2:focus,.hover-blue2:hover{color:#4330fc}.hover-blue3:focus,.hover-blue3:hover{color:#190d7b}.hover-red0:focus,.hover-red0:hover{color:#f9d6ce}.hover-red1:focus,.hover-red1:hover{color:#ffa073}.hover-red2:focus,.hover-red2:hover{color:#ee5432}.hover-red3:focus,.hover-red3:hover{color:#c10d30}.hover-green0:focus,.hover-green0:hover{color:#bdebcc}.hover-green1:focus,.hover-green1:hover{color:#2ed196}.hover-green2:focus,.hover-green2:hover{color:#2aa779}.hover-green3:focus,.hover-green3:hover{color:#286e55}.hover-yellow0:focus,.hover-yellow0:hover{color:#ffefc5}.hover-yellow1:focus,.hover-yellow1:hover{color:#ffd972}.hover-yellow2:focus,.hover-yellow2:hover{color:#fcc440}.hover-yellow3:focus,.hover-yellow3:hover{color:#ee892b}.hover-bg-black:focus,.hover-bg-black:hover{background-color:#000}.hover-bg-white:focus,.hover-bg-white:hover{background-color:#fff}.hover-bg-gray0:focus,.hover-bg-gray0:hover{background-color:#333}.hover-bg-gray1:focus,.hover-bg-gray1:hover{background-color:#4d4d4d}.hover-bg-gray2:focus,.hover-bg-gray2:hover{background-color:#7f7f7f}.hover-bg-gray3:focus,.hover-bg-gray3:hover{background-color:#b1b2b3}.hover-bg-gray4:focus,.hover-bg-gray4:hover{background-color:#e6e6e6}.hover-bg-gray5:focus,.hover-bg-gray5:hover{background-color:#f9f9f9}.hover-bg-blue0:focus,.hover-bg-blue0:hover{background-color:#ecf6ff}.hover-bg-blue1:focus,.hover-bg-blue1:hover{background-color:#b0c7ff}.hover-bg-blue2:focus,.hover-bg-blue2:hover{background-color:#4330fc}.hover-bg-blue3:focus,.hover-bg-blue3:hover{background-color:#190d7b}.hover-bg-red0:focus,.hover-bg-red0:hover{background-color:#f9d6ce}.hover-bg-red1:focus,.hover-bg-red1:hover{background-color:#ffa073}.hover-bg-red2:focus,.hover-bg-red2:hover{background-color:#ee5432}.hover-bg-red3:focus,.hover-bg-red3:hover{background-color:#c10d30}.hover-bg-green0:focus,.hover-bg-green0:hover{background-color:#bdebcc}.hover-bg-green1:focus,.hover-bg-green1:hover{background-color:#2ed196}.hover-bg-green2:focus,.hover-bg-green2:hover{background-color:#2aa779}.hover-bg-green3:focus,.hover-bg-green3:hover{background-color:#286e55}.hover-bg-yellow0:focus,.hover-bg-yellow0:hover{background-color:#ffefc5}.hover-bg-yellow1:focus,.hover-bg-yellow1:hover{background-color:#ffd972}.hover-bg-yellow2:focus,.hover-bg-yellow2:hover{background-color:#fcc440}.hover-bg-yellow3:focus,.hover-bg-yellow3:hover{background-color:#ee892b}.hover-bg-transparent:focus,.hover-bg-transparent:hover{background-color:transparent}img{max-width:100%}.tracked{letter-spacing:.1em}.tracked-tight{letter-spacing:-.05em}.tracked-mega{letter-spacing:.25em}.lh-solid{line-height:1.333333}.lh-title{line-height:1.5}.lh-copy{line-height:1.666666}.mw1{max-width:1rem}.mw2{max-width:2rem}.mw3{max-width:4rem}.mw4{max-width:8rem}.mw5{max-width:16rem}.mw6{max-width:32rem}.mw7{max-width:48rem}.mw8{max-width:64rem}.mw9{max-width:96rem}.mw-none{max-width:none}.mw-100{max-width:100%}.nested-copy-line-height ol,.nested-copy-line-height p,.nested-copy-line-height ul{line-height:1.5}.nested-headline-line-height h1,.nested-headline-line-height h2,.nested-headline-line-height h3,.nested-headline-line-height h4,.nested-headline-line-height h5,.nested-headline-line-height h6{line-height:1.25}.nested-list-reset ol,.nested-list-reset ul{padding-left:0;margin-left:0;list-style-type:none}.nested-copy-indent p+p{text-indent:1em;margin-top:0;margin-bottom:0}.nested-copy-separator p+p{margin-top:1.5em}.nested-img img{width:100%;max-width:100%;display:block}.nested-links a{color:#357edd;transition:color .15s ease-in}.nested-links a:focus,.nested-links a:hover{color:#96ccff;transition:color .15s ease-in}.dim{opacity:1}.dim,.dim:focus,.dim:hover{transition:opacity .15s ease-in}.dim:focus,.dim:hover{opacity:.5}.dim:active{opacity:.8;transition:opacity .15s ease-out}.glow,.glow:focus,.glow:hover{transition:opacity .15s ease-in}.glow:focus,.glow:hover{opacity:1}.hide-child .child{opacity:0;transition:opacity .15s ease-in}.hide-child:active .child,.hide-child:focus .child,.hide-child:hover .child{opacity:1;transition:opacity .15s ease-in}.underline-hover:focus,.underline-hover:hover{text-decoration:underline}.grow{-moz-osx-font-smoothing:grayscale;-webkit-backface-visibility:hidden;backface-visibility:hidden;-webkit-transform:translateZ(0);transform:translateZ(0);transition:-webkit-transform .25s ease-out;transition:transform .25s ease-out;transition:transform .25s ease-out,-webkit-transform .25s ease-out}.grow:focus,.grow:hover{-webkit-transform:scale(1.05);transform:scale(1.05)}.grow:active{-webkit-transform:scale(.9);transform:scale(.9)}.grow-large{-moz-osx-font-smoothing:grayscale;-webkit-backface-visibility:hidden;backface-visibility:hidden;-webkit-transform:translateZ(0);transform:translateZ(0);transition:-webkit-transform .25s ease-in-out;transition:transform .25s ease-in-out;transition:transform .25s ease-in-out,-webkit-transform .25s ease-in-out}.grow-large:focus,.grow-large:hover{-webkit-transform:scale(1.2);transform:scale(1.2)}.grow-large:active{-webkit-transform:scale(.95);transform:scale(.95)}.pointer:hover,.shadow-hover{cursor:pointer}.shadow-hover{position:relative;transition:all .5s cubic-bezier(.165,.84,.44,1)}.shadow-hover:after{content:"";box-shadow:0 0 16px 2px rgba(0,0,0,.2);border-radius:inherit;opacity:0;position:absolute;top:0;left:0;width:100%;height:100%;z-index:-1;transition:opacity .5s cubic-bezier(.165,.84,.44,1)}.shadow-hover:focus:after,.shadow-hover:hover:after{opacity:1}.bg-animate,.bg-animate:focus,.bg-animate:hover{transition:background-color .15s ease-in-out}.o-100{opacity:1}.o-90{opacity:.9}.o-80{opacity:.8}.o-70{opacity:.7}.o-60{opacity:.6}.o-50{opacity:.5}.o-40{opacity:.4}.o-30{opacity:.3}.o-20{opacity:.2}.o-10{opacity:.1}.o-05{opacity:.05}.o-025{opacity:.025}.o-0{opacity:0}.rotate-45{-webkit-transform:rotate(45deg);transform:rotate(45deg)}.rotate-90{-webkit-transform:rotate(90deg);transform:rotate(90deg)}.rotate-135{-webkit-transform:rotate(135deg);transform:rotate(135deg)}.rotate-180{-webkit-transform:rotate(180deg);transform:rotate(180deg)}.rotate-225{-webkit-transform:rotate(225deg);transform:rotate(225deg)}.rotate-270{-webkit-transform:rotate(270deg);transform:rotate(270deg)}.rotate-315{-webkit-transform:rotate(315deg);transform:rotate(315deg)}.outline{outline:1px solid}.outline-transparent{outline:1px solid transparent}.outline-0{outline:0}.overflow-visible{overflow:visible}.overflow-hidden{overflow:hidden}.overflow-scroll{overflow:scroll}.overflow-auto{overflow:auto}.overflow-x-visible{overflow-x:visible}.overflow-x-hidden{overflow-x:hidden}.overflow-x-scroll{overflow-x:scroll}.overflow-x-auto{overflow-x:auto}.overflow-y-visible{overflow-y:visible}.overflow-y-hidden{overflow-y:hidden}.overflow-y-scroll{overflow-y:scroll}.overflow-y-auto{overflow-y:auto}.static{position:static}.relative{position:relative}.absolute{position:absolute}.fixed{position:fixed}.collapse{border-collapse:collapse;border-spacing:0}.striped--light-silver:nth-child(odd){background-color:#aaa}.striped--moon-gray:nth-child(odd){background-color:#ccc}.striped--light-gray:nth-child(odd){background-color:#eee}.striped--near-white:nth-child(odd){background-color:#f4f4f4}.stripe-light:nth-child(odd){background-color:hsla(0,0%,100%,.1)}.stripe-dark:nth-child(odd){background-color:rgba(0,0,0,.1)}.strike{text-decoration:line-through}.underline{text-decoration:underline}.no-underline{text-decoration:none}.tl{text-align:left}.tr{text-align:right}.tc{text-align:center}.tj{text-align:justify}.ttc{text-transform:capitalize}.ttl{text-transform:lowercase}.ttu{text-transform:uppercase}.ttn{text-transform:none}.v-base{vertical-align:baseline}.v-mid{vertical-align:middle}.v-top{vertical-align:top}.v-btm{vertical-align:bottom}.f1{font-size:4.5rem}.f2{font-size:4rem}.f3{font-size:3rem}.f4{font-size:2rem}.f5{font-size:1.5rem}.f6{font-size:1.125rem}.f7{font-size:1rem}.f8{font-size:.875rem}.f9{font-size:.75rem}.measure{max-width:30em}.measure-wide{max-width:34em}.measure-narrow{max-width:20em}.small-caps{font-variant:small-caps}.indent{text-indent:1em;margin-top:0;margin-bottom:0}.truncate{white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.overflow-container{overflow-y:scroll}.center{margin-left:auto}.center,.mr-auto{margin-right:auto}.ml-auto{margin-left:auto}.clip{position:fixed!important;_position:absolute!important;clip:rect(1px 1px 1px 1px);clip:rect(1px,1px,1px,1px)}.ws-normal{white-space:normal}.nowrap{white-space:nowrap}.pre{white-space:pre}.w1{width:1rem}.w2{width:2rem}.w3{width:4rem}.w4{width:8rem}.w5{width:16rem}.w-10{width:10%}.w-20{width:20%}.w-25{width:25%}.w-30{width:30%}.w-33{width:33%}.w-34{width:34%}.w-40{width:40%}.w-50{width:50%}.w-60{width:60%}.w-70{width:70%}.w-75{width:75%}.w-80{width:80%}.w-90{width:90%}.w-100{width:100%}.w-third{width:33.33333%}.w-two-thirds{width:66.66667%}.w-auto{width:auto}.z-0{z-index:0}.z-1{z-index:1}.z-2{z-index:2}.z-3{z-index:3}.z-4{z-index:4}.z-5{z-index:5}.z-999{z-index:999}.z-9999{z-index:9999}.z-max{z-index:2147483647}.z-inherit{z-index:inherit}.z-initial{z-index:auto}.z-unset{z-index:unset}@media screen and (min-width:34.375em) and (max-width:46.875em){.aspect-ratio-m{height:0;position:relative}.aspect-ratio--16x9-m{padding-bottom:56.25%}.aspect-ratio--9x16-m{padding-bottom:177.77%}.aspect-ratio--4x3-m{padding-bottom:75%}.aspect-ratio--3x4-m{padding-bottom:133.33%}.aspect-ratio--6x4-m{padding-bottom:66.6%}.aspect-ratio--4x6-m{padding-bottom:150%}.aspect-ratio--8x5-m{padding-bottom:62.5%}.aspect-ratio--5x8-m{padding-bottom:160%}.aspect-ratio--7x5-m{padding-bottom:71.42%}.aspect-ratio--5x7-m{padding-bottom:140%}.aspect-ratio--1x1-m{padding-bottom:100%}.aspect-ratio--object-m{position:absolute;top:0;right:0;bottom:0;left:0;width:100%;height:100%;z-index:100}.cover-m{background-size:cover!important}.contain-m{background-size:contain!important}.bg-center-m{background-position:50%}.bg-center-m,.bg-top-m{background-repeat:no-repeat}.bg-top-m{background-position:top}.bg-right-m{background-position:100%}.bg-bottom-m,.bg-right-m{background-repeat:no-repeat}.bg-bottom-m{background-position:bottom}.bg-left-m{background-repeat:no-repeat;background-position:0}.ba-m{border-style:solid;border-width:1px}.bt-m{border-top-style:solid;border-top-width:1px}.br-m{border-right-style:solid;border-right-width:1px}.bb-m{border-bottom-style:solid;border-bottom-width:1px}.bl-m{border-left-style:solid;border-left-width:1px}.bn-m{border-style:none;border-width:0}.br0-m{border-radius:0}.br1-m{border-radius:.125rem}.br2-m{border-radius:.25rem}.br3-m{border-radius:.5rem}.br4-m{border-radius:1rem}.br-100-m{border-radius:100%}.br-pill-m{border-radius:9999px}.br--bottom-m{border-top-left-radius:0;border-top-right-radius:0}.br--top-m{border-bottom-right-radius:0}.br--right-m,.br--top-m{border-bottom-left-radius:0}.br--right-m{border-top-left-radius:0}.br--left-m{border-top-right-radius:0;border-bottom-right-radius:0}.b--dotted-m{border-style:dotted}.b--dashed-m{border-style:dashed}.b--solid-m{border-style:solid}.b--none-m{border-style:none}.bw0-m{border-width:0}.bw1-m{border-width:.125rem}.bw2-m{border-width:.25rem}.bw3-m{border-width:.5rem}.bw4-m{border-width:1rem}.bw5-m{border-width:2rem}.bt-0-m{border-top-width:0}.br-0-m{border-right-width:0}.bb-0-m{border-bottom-width:0}.bl-0-m{border-left-width:0}.shadow-1-m{box-shadow:0 0 4px 2px rgba(0,0,0,.2)}.shadow-2-m{box-shadow:0 0 8px 2px rgba(0,0,0,.2)}.shadow-3-m{box-shadow:2px 2px 4px 2px rgba(0,0,0,.2)}.shadow-4-m{box-shadow:2px 2px 8px 0 rgba(0,0,0,.2)}.shadow-5-m{box-shadow:4px 4px 8px 0 rgba(0,0,0,.2)}.pa0-m{padding:0}.ma0-m,.na0-m{margin:0}.pl0-m{padding-left:0}.ml0-m,.nl0-m{margin-left:0}.pr0-m{padding-right:0}.mr0-m,.nr0-m{margin-right:0}.pt0-m{padding-top:0}.mt0-m,.nt0-m{margin-top:0}.pb0-m{padding-bottom:0}.mb0-m,.nb0-m{margin-bottom:0}.pv0-m{padding-top:0;padding-bottom:0}.mv0-m,.nv0-m{margin-top:0;margin-bottom:0}.ph0-m{padding-left:0;padding-right:0}.mh0-m,.nh0-m{margin-left:0;margin-right:0}.pa1-m{padding:.25rem}.ma1-m{margin:.25rem}.na1-m{margin:-.25rem}.pl1-m{padding-left:.25rem}.ml1-m{margin-left:.25rem}.nl1-m{margin-left:-.25rem}.pr1-m{padding-right:.25rem}.mr1-m{margin-right:.25rem}.nr1-m{margin-right:-.25rem}.pt1-m{padding-top:.25rem}.mt1-m{margin-top:.25rem}.nt1-m{margin-top:-.25rem}.pb1-m{padding-bottom:.25rem}.mb1-m{margin-bottom:.25rem}.nb1-m{margin-bottom:-.25rem}.pv1-m{padding-top:.25rem;padding-bottom:.25rem}.mv1-m{margin-top:.25rem;margin-bottom:.25rem}.nv1-m{margin-top:-.25rem;margin-bottom:-.25rem}.ph1-m{padding-left:.25rem;padding-right:.25rem}.mh1-m{margin-left:.25rem;margin-right:.25rem}.nh1-m{margin-left:-.25rem;margin-right:-.25rem}.pa2-m{padding:.5rem}.ma2-m{margin:.5rem}.na2-m{margin:-.5rem}.pl2-m{padding-left:.5rem}.ml2-m{margin-left:.5rem}.nl2-m{margin-left:-.5rem}.pr2-m{padding-right:.5rem}.mr2-m{margin-right:.5rem}.nr2-m{margin-right:-.5rem}.pt2-m{padding-top:.5rem}.mt2-m{margin-top:.5rem}.nt2-m{margin-top:-.5rem}.pb2-m{padding-bottom:.5rem}.mb2-m{margin-bottom:.5rem}.nb2-m{margin-bottom:-.5rem}.pv2-m{padding-top:.5rem;padding-bottom:.5rem}.mv2-m{margin-top:.5rem;margin-bottom:.5rem}.nv2-m{margin-top:-.5rem;margin-bottom:-.5rem}.ph2-m{padding-left:.5rem;padding-right:.5rem}.mh2-m{margin-left:.5rem;margin-right:.5rem}.nh2-m{margin-left:-.5rem;margin-right:-.5rem}.pa3-m{padding:.75rem}.ma3-m{margin:.75rem}.na3-m{margin:-.75rem}.pl3-m{padding-left:.75rem}.ml3-m{margin-left:.75rem}.nl3-m{margin-left:-.75rem}.pr3-m{padding-right:.75rem}.mr3-m{margin-right:.75rem}.nr3-m{margin-right:-.75rem}.pt3-m{padding-top:.75rem}.mt3-m{margin-top:.75rem}.nt3-m{margin-top:-.75rem}.pb3-m{padding-bottom:.75rem}.mb3-m{margin-bottom:.75rem}.nb3-m{margin-bottom:-.75rem}.pv3-m{padding-top:.75rem;padding-bottom:.75rem}.mv3-m{margin-top:.75rem;margin-bottom:.75rem}.nv3-m{margin-top:-.75rem;margin-bottom:-.75rem}.ph3-m{padding-left:.75rem;padding-right:.75rem}.mh3-m{margin-left:.75rem;margin-right:.75rem}.nh3-m{margin-left:-.75rem;margin-right:-.75rem}.pa4-m{padding:1rem}.ma4-m{margin:1rem}.na4-m{margin:-1rem}.pl4-m{padding-left:1rem}.ml4-m{margin-left:1rem}.nl4-m{margin-left:-1rem}.pr4-m{padding-right:1rem}.mr4-m{margin-right:1rem}.nr4-m{margin-right:-1rem}.pt4-m{padding-top:1rem}.mt4-m{margin-top:1rem}.nt4-m{margin-top:-1rem}.pb4-m{padding-bottom:1rem}.mb4-m{margin-bottom:1rem}.nb4-m{margin-bottom:-1rem}.pv4-m{padding-top:1rem;padding-bottom:1rem}.mv4-m{margin-top:1rem;margin-bottom:1rem}.nv4-m{margin-top:-1rem;margin-bottom:-1rem}.ph4-m{padding-left:1rem;padding-right:1rem}.mh4-m{margin-left:1rem;margin-right:1rem}.nh4-m{margin-left:-1rem;margin-right:-1rem}.pa5-m{padding:1.25rem}.ma5-m{margin:1.25rem}.na5-m{margin:-1.25rem}.pl5-m{padding-left:1.25rem}.ml5-m{margin-left:1.25rem}.nl5-m{margin-left:-1.25rem}.pr5-m{padding-right:1.25rem}.mr5-m{margin-right:1.25rem}.nr5-m{margin-right:-1.25rem}.pt5-m{padding-top:1.25rem}.mt5-m{margin-top:1.25rem}.nt5-m{margin-top:-1.25rem}.pb5-m{padding-bottom:1.25rem}.mb5-m{margin-bottom:1.25rem}.nb5-m{margin-bottom:-1.25rem}.pv5-m{padding-top:1.25rem;padding-bottom:1.25rem}.mv5-m{margin-top:1.25rem;margin-bottom:1.25rem}.nv5-m{margin-top:-1.25rem;margin-bottom:-1.25rem}.ph5-m{padding-left:1.25rem;padding-right:1.25rem}.mh5-m{margin-left:1.25rem;margin-right:1.25rem}.nh5-m{margin-left:-1.25rem;margin-right:-1.25rem}.pa6-m{padding:1.5rem}.ma6-m{margin:1.5rem}.na6-m{margin:-1.5rem}.pl6-m{padding-left:1.5rem}.ml6-m{margin-left:1.5rem}.nl6-m{margin-left:-1.5rem}.pr6-m{padding-right:1.5rem}.mr6-m{margin-right:1.5rem}.nr6-m{margin-right:-1.5rem}.pt6-m{padding-top:1.5rem}.mt6-m{margin-top:1.5rem}.nt6-m{margin-top:-1.5rem}.pb6-m{padding-bottom:1.5rem}.mb6-m{margin-bottom:1.5rem}.nb6-m{margin-bottom:-1.5rem}.pv6-m{padding-top:1.5rem;padding-bottom:1.5rem}.mv6-m{margin-top:1.5rem;margin-bottom:1.5rem}.nv6-m{margin-top:-1.5rem;margin-bottom:-1.5rem}.ph6-m{padding-left:1.5rem;padding-right:1.5rem}.mh6-m{margin-left:1.5rem;margin-right:1.5rem}.nh6-m{margin-left:-1.5rem;margin-right:-1.5rem}.pa7-m{padding:2rem}.ma7-m{margin:2rem}.na7-m{margin:-2rem}.pl7-m{padding-left:2rem}.ml7-m{margin-left:2rem}.nl7-m{margin-left:-2rem}.pr7-m{padding-right:2rem}.mr7-m{margin-right:2rem}.nr7-m{margin-right:-2rem}.pt7-m{padding-top:2rem}.mt7-m{margin-top:2rem}.nt7-m{margin-top:-2rem}.pb7-m{padding-bottom:2rem}.mb7-m{margin-bottom:2rem}.nb7-m{margin-bottom:-2rem}.pv7-m{padding-top:2rem;padding-bottom:2rem}.mv7-m{margin-top:2rem;margin-bottom:2rem}.nv7-m{margin-top:-2rem;margin-bottom:-2rem}.ph7-m{padding-left:2rem;padding-right:2rem}.mh7-m{margin-left:2rem;margin-right:2rem}.nh7-m{margin-left:-2rem;margin-right:-2rem}.pa8-m{padding:3rem}.ma8-m{margin:3rem}.na8-m{margin:-3rem}.pl8-m{padding-left:3rem}.ml8-m{margin-left:3rem}.nl8-m{margin-left:-3rem}.pr8-m{padding-right:3rem}.mr8-m{margin-right:3rem}.nr8-m{margin-right:-3rem}.pt8-m{padding-top:3rem}.mt8-m{margin-top:3rem}.nt8-m{margin-top:-3rem}.pb8-m{padding-bottom:3rem}.mb8-m{margin-bottom:3rem}.nb8-m{margin-bottom:-3rem}.pv8-m{padding-top:3rem;padding-bottom:3rem}.mv8-m{margin-top:3rem;margin-bottom:3rem}.nv8-m{margin-top:-3rem;margin-bottom:-3rem}.ph8-m{padding-left:3rem;padding-right:3rem}.mh8-m{margin-left:3rem;margin-right:3rem}.nh8-m{margin-left:-3rem;margin-right:-3rem}.pa9-m{padding:4rem}.ma9-m{margin:4rem}.na9-m{margin:-4rem}.pl9-m{padding-left:4rem}.ml9-m{margin-left:4rem}.nl9-m{margin-left:-4rem}.pr9-m{padding-right:4rem}.mr9-m{margin-right:4rem}.nr9-m{margin-right:-4rem}.pt9-m{padding-top:4rem}.mt9-m{margin-top:4rem}.nt9-m{margin-top:-4rem}.pb9-m{padding-bottom:4rem}.mb9-m{margin-bottom:4rem}.nb9-m{margin-bottom:-4rem}.pv9-m{padding-top:4rem;padding-bottom:4rem}.mv9-m{margin-top:4rem;margin-bottom:4rem}.nv9-m{margin-top:-4rem;margin-bottom:-4rem}.ph9-m{padding-left:4rem;padding-right:4rem}.mh9-m{margin-left:4rem;margin-right:4rem}.nh9-m{margin-left:-4rem;margin-right:-4rem}.pa10-m{padding:6rem}.ma10-m{margin:6rem}.na10-m{margin:-6rem}.pl10-m{padding-left:6rem}.ml10-m{margin-left:6rem}.nl10-m{margin-left:-6rem}.pr10-m{padding-right:6rem}.mr10-m{margin-right:6rem}.nr10-m{margin-right:-6rem}.pt10-m{padding-top:6rem}.mt10-m{margin-top:6rem}.nt10-m{margin-top:-6rem}.pb10-m{padding-bottom:6rem}.mb10-m{margin-bottom:6rem}.nb10-m{margin-bottom:-6rem}.pv10-m{padding-top:6rem;padding-bottom:6rem}.mv10-m{margin-top:6rem;margin-bottom:6rem}.nv10-m{margin-top:-6rem;margin-bottom:-6rem}.ph10-m{padding-left:6rem;padding-right:6rem}.mh10-m{margin-left:6rem;margin-right:6rem}.nh10-m{margin-left:-6rem;margin-right:-6rem}.pa11-m{padding:10rem}.ma11-m{margin:10rem}.na11-m{margin:-10rem}.pl11-m{padding-left:10rem}.ml11-m{margin-left:10rem}.nl11-m{margin-left:-10rem}.pr11-m{padding-right:10rem}.mr11-m{margin-right:10rem}.nr11-m{margin-right:-10rem}.pt11-m{padding-top:10rem}.mt11-m{margin-top:10rem}.nt11-m{margin-top:-10rem}.pb11-m{padding-bottom:10rem}.mb11-m{margin-bottom:10rem}.nb11-m{margin-bottom:-10rem}.pv11-m{padding-top:10rem;padding-bottom:10rem}.mv11-m{margin-top:10rem;margin-bottom:10rem}.nv11-m{margin-top:-10rem;margin-bottom:-10rem}.ph11-m{padding-left:10rem;padding-right:10rem}.mh11-m{margin-left:10rem;margin-right:10rem}.nh11-m{margin-left:-10rem;margin-right:-10rem}.pa12-m{padding:18rem}.ma12-m{margin:18rem}.na12-m{margin:-18rem}.pl12-m{padding-left:18rem}.ml12-m{margin-left:18rem}.nl12-m{margin-left:-18rem}.pr12-m{padding-right:18rem}.mr12-m{margin-right:18rem}.nr12-m{margin-right:-18rem}.pt12-m{padding-top:18rem}.mt12-m{margin-top:18rem}.nt12-m{margin-top:-18rem}.pb12-m{padding-bottom:18rem}.mb12-m{margin-bottom:18rem}.nb12-m{margin-bottom:-18rem}.pv12-m{padding-top:18rem;padding-bottom:18rem}.mv12-m{margin-top:18rem;margin-bottom:18rem}.nv12-m{margin-top:-18rem;margin-bottom:-18rem}.ph12-m{padding-left:18rem;padding-right:18rem}.mh12-m{margin-left:18rem;margin-right:18rem}.nh12-m{margin-left:-18rem;margin-right:-18rem}.top-0-m{top:0}.right-0-m{right:0}.bottom-0-m{bottom:0}.left-0-m{left:0}.top-1-m{top:1rem}.right-1-m{right:1rem}.bottom-1-m{bottom:1rem}.left-1-m{left:1rem}.top-2-m{top:2rem}.right-2-m{right:2rem}.bottom-2-m{bottom:2rem}.left-2-m{left:2rem}.top--1-m{top:-1rem}.right--1-m{right:-1rem}.bottom--1-m{bottom:-1rem}.left--1-m{left:-1rem}.top--2-m{top:-2rem}.right--2-m{right:-2rem}.bottom--2-m{bottom:-2rem}.left--2-m{left:-2rem}.absolute--fill-m{top:0;right:0;bottom:0;left:0}.cf-m:after,.cf-m:before{content:" ";display:table}.cf-m:after{clear:both}.cf-m{*zoom:1}.cl-m{clear:left}.cr-m{clear:right}.cb-m{clear:both}.cn-m{clear:none}.dn-m{display:none}.di-m{display:inline}.db-m{display:block}.dib-m{display:inline-block}.dit-m{display:inline-table}.dt-m{display:table}.dtc-m{display:table-cell}.dt-row-m{display:table-row}.dt-row-group-m{display:table-row-group}.dt-column-m{display:table-column}.dt-column-group-m{display:table-column-group}.dt--fixed-m{table-layout:fixed;width:100%}.flex-m{display:flex}.inline-flex-m{display:inline-flex}.flex-auto-m{flex:1 1 auto;min-width:0;min-height:0}.flex-none-m{flex:none}.flex-column-m{flex-direction:column}.flex-row-m{flex-direction:row}.flex-wrap-m{flex-wrap:wrap}.flex-nowrap-m{flex-wrap:nowrap}.flex-wrap-reverse-m{flex-wrap:wrap-reverse}.flex-column-reverse-m{flex-direction:column-reverse}.flex-row-reverse-m{flex-direction:row-reverse}.items-start-m{align-items:flex-start}.items-end-m{align-items:flex-end}.items-center-m{align-items:center}.items-baseline-m{align-items:baseline}.items-stretch-m{align-items:stretch}.self-start-m{align-self:flex-start}.self-end-m{align-self:flex-end}.self-center-m{align-self:center}.self-baseline-m{align-self:baseline}.self-stretch-m{align-self:stretch}.justify-start-m{justify-content:flex-start}.justify-end-m{justify-content:flex-end}.justify-center-m{justify-content:center}.justify-between-m{justify-content:space-between}.justify-around-m{justify-content:space-around}.content-start-m{align-content:flex-start}.content-end-m{align-content:flex-end}.content-center-m{align-content:center}.content-between-m{align-content:space-between}.content-around-m{align-content:space-around}.content-stretch-m{align-content:stretch}.order-0-m{order:0}.order-1-m{order:1}.order-2-m{order:2}.order-3-m{order:3}.order-4-m{order:4}.order-5-m{order:5}.order-6-m{order:6}.order-7-m{order:7}.order-8-m{order:8}.order-last-m{order:99999}.flex-grow-0-m{flex-grow:0}.flex-grow-1-m{flex-grow:1}.flex-shrink-0-m{flex-shrink:0}.flex-shrink-1-m{flex-shrink:1}.fl-m{float:left}.fl-m,.fr-m{_display:inline}.fr-m{float:right}.fn-m{float:none}.i-m{font-style:italic}.fs-normal-m{font-style:normal}.normal-m{font-weight:400}.b-m{font-weight:700}.fw1-m{font-weight:100}.fw2-m{font-weight:200}.fw3-m{font-weight:300}.fw4-m{font-weight:400}.fw5-m{font-weight:500}.fw6-m{font-weight:600}.fw7-m{font-weight:700}.fw8-m{font-weight:800}.fw9-m{font-weight:900}.h1-m{height:1rem}.h2-m{height:2rem}.h3-m{height:4rem}.h4-m{height:8rem}.h5-m{height:16rem}.h-25-m{height:25%}.h-50-m{height:50%}.h-75-m{height:75%}.h-100-m{height:100%}.min-h-100-m{min-height:100%}.vh-25-m{height:25vh}.vh-50-m{height:50vh}.vh-75-m{height:75vh}.vh-100-m{height:100vh}.min-vh-100-m{min-height:100vh}.h-auto-m{height:auto}.h-inherit-m{height:inherit}.tracked-m{letter-spacing:.1em}.tracked-tight-m{letter-spacing:-.05em}.tracked-mega-m{letter-spacing:.25em}.lh-solid-m{line-height:1.333333}.lh-title-m{line-height:1.5}.lh-copy-m{line-height:1.666666}.mw1-m{max-width:1rem}.mw2-m{max-width:2rem}.mw3-m{max-width:4rem}.mw4-m{max-width:8rem}.mw5-m{max-width:16rem}.mw6-m{max-width:32rem}.mw7-m{max-width:48rem}.mw8-m{max-width:64rem}.mw9-m{max-width:96rem}.mw-none-m{max-width:none}.mw-100-m{max-width:100%}.o-100-m{opacity:1}.o-90-m{opacity:.9}.o-80-m{opacity:.8}.o-70-m{opacity:.7}.o-60-m{opacity:.6}.o-50-m{opacity:.5}.o-40-m{opacity:.4}.o-30-m{opacity:.3}.o-20-m{opacity:.2}.o-10-m{opacity:.1}.o-05-m{opacity:.05}.o-025-m{opacity:.025}.o-0-m{opacity:0}.rotate-45-m{-webkit-transform:rotate(45deg);transform:rotate(45deg)}.rotate-90-m{-webkit-transform:rotate(90deg);transform:rotate(90deg)}.rotate-135-m{-webkit-transform:rotate(135deg);transform:rotate(135deg)}.rotate-180-m{-webkit-transform:rotate(180deg);transform:rotate(180deg)}.rotate-225-m{-webkit-transform:rotate(225deg);transform:rotate(225deg)}.rotate-270-m{-webkit-transform:rotate(270deg);transform:rotate(270deg)}.rotate-315-m{-webkit-transform:rotate(315deg);transform:rotate(315deg)}.outline-m{outline:1px solid}.outline-transparent-m{outline:1px solid transparent}.outline-0-m{outline:0}.overflow-visible-m{overflow:visible}.overflow-hidden-m{overflow:hidden}.overflow-scroll-m{overflow:scroll}.overflow-auto-m{overflow:auto}.overflow-x-visible-m{overflow-x:visible}.overflow-x-hidden-m{overflow-x:hidden}.overflow-x-scroll-m{overflow-x:scroll}.overflow-x-auto-m{overflow-x:auto}.overflow-y-visible-m{overflow-y:visible}.overflow-y-hidden-m{overflow-y:hidden}.overflow-y-scroll-m{overflow-y:scroll}.overflow-y-auto-m{overflow-y:auto}.static-m{position:static}.relative-m{position:relative}.absolute-m{position:absolute}.fixed-m{position:fixed}.strike-m{text-decoration:line-through}.underline-m{text-decoration:underline}.no-underline-m{text-decoration:none}.tl-m{text-align:left}.tr-m{text-align:right}.tc-m{text-align:center}.tj-m{text-align:justify}.ttc-m{text-transform:capitalize}.ttl-m{text-transform:lowercase}.ttu-m{text-transform:uppercase}.ttn-m{text-transform:none}.f1-m{font-size:4.5rem}.f2-m{font-size:4rem}.f3-m{font-size:3rem}.f4-m{font-size:2rem}.f5-m{font-size:1.5rem}.f6-m{font-size:1.125rem}.f7-m{font-size:1rem}.f8-m{font-size:.875rem}.f9-m{font-size:.75rem}.measure-m{max-width:30em}.measure-wide-m{max-width:34em}.measure-narrow-m{max-width:20em}.small-caps-m{font-variant:small-caps}.indent-m{text-indent:1em;margin-top:0;margin-bottom:0}.truncate-m{white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.clip-m{position:fixed!important;_position:absolute!important;clip:rect(1px 1px 1px 1px);clip:rect(1px,1px,1px,1px)}.ws-normal-m{white-space:normal}.nowrap-m{white-space:nowrap}.pre-m{white-space:pre}.w1-m{width:1rem}.w2-m{width:2rem}.w3-m{width:4rem}.w4-m{width:8rem}.w5-m{width:16rem}.w-10-m{width:10%}.w-20-m{width:20%}.w-25-m{width:25%}.w-30-m{width:30%}.w-33-m{width:33%}.w-34-m{width:34%}.w-40-m{width:40%}.w-50-m{width:50%}.w-60-m{width:60%}.w-70-m{width:70%}.w-75-m{width:75%}.w-80-m{width:80%}.w-90-m{width:90%}.w-100-m{width:100%}.w-third-m{width:33.33333%}.w-two-thirds-m{width:66.66667%}.w-auto-m{width:auto}}@media screen and (min-width:46.875em) and (max-width:60em){.aspect-ratio-l{height:0;position:relative}.aspect-ratio--16x9-l{padding-bottom:56.25%}.aspect-ratio--9x16-l{padding-bottom:177.77%}.aspect-ratio--4x3-l{padding-bottom:75%}.aspect-ratio--3x4-l{padding-bottom:133.33%}.aspect-ratio--6x4-l{padding-bottom:66.6%}.aspect-ratio--4x6-l{padding-bottom:150%}.aspect-ratio--8x5-l{padding-bottom:62.5%}.aspect-ratio--5x8-l{padding-bottom:160%}.aspect-ratio--7x5-l{padding-bottom:71.42%}.aspect-ratio--5x7-l{padding-bottom:140%}.aspect-ratio--1x1-l{padding-bottom:100%}.aspect-ratio--object-l{position:absolute;top:0;right:0;bottom:0;left:0;width:100%;height:100%;z-index:100}.cover-l{background-size:cover!important}.contain-l{background-size:contain!important}.bg-center-l{background-position:50%}.bg-center-l,.bg-top-l{background-repeat:no-repeat}.bg-top-l{background-position:top}.bg-right-l{background-position:100%}.bg-bottom-l,.bg-right-l{background-repeat:no-repeat}.bg-bottom-l{background-position:bottom}.bg-left-l{background-repeat:no-repeat;background-position:0}.ba-l{border-style:solid;border-width:1px}.bt-l{border-top-style:solid;border-top-width:1px}.br-l{border-right-style:solid;border-right-width:1px}.bb-l{border-bottom-style:solid;border-bottom-width:1px}.bl-l{border-left-style:solid;border-left-width:1px}.bn-l{border-style:none;border-width:0}.br0-l{border-radius:0}.br1-l{border-radius:.125rem}.br2-l{border-radius:.25rem}.br3-l{border-radius:.5rem}.br4-l{border-radius:1rem}.br-100-l{border-radius:100%}.br-pill-l{border-radius:9999px}.br--bottom-l{border-top-left-radius:0;border-top-right-radius:0}.br--top-l{border-bottom-right-radius:0}.br--right-l,.br--top-l{border-bottom-left-radius:0}.br--right-l{border-top-left-radius:0}.br--left-l{border-top-right-radius:0;border-bottom-right-radius:0}.b--dotted-l{border-style:dotted}.b--dashed-l{border-style:dashed}.b--solid-l{border-style:solid}.b--none-l{border-style:none}.bw0-l{border-width:0}.bw1-l{border-width:.125rem}.bw2-l{border-width:.25rem}.bw3-l{border-width:.5rem}.bw4-l{border-width:1rem}.bw5-l{border-width:2rem}.bt-0-l{border-top-width:0}.br-0-l{border-right-width:0}.bb-0-l{border-bottom-width:0}.bl-0-l{border-left-width:0}.shadow-1-l{box-shadow:0 0 4px 2px rgba(0,0,0,.2)}.shadow-2-l{box-shadow:0 0 8px 2px rgba(0,0,0,.2)}.shadow-3-l{box-shadow:2px 2px 4px 2px rgba(0,0,0,.2)}.shadow-4-l{box-shadow:2px 2px 8px 0 rgba(0,0,0,.2)}.shadow-5-l{box-shadow:4px 4px 8px 0 rgba(0,0,0,.2)}.pa0-l{padding:0}.ma0-l,.na0-l{margin:0}.pl0-l{padding-left:0}.ml0-l,.nl0-l{margin-left:0}.pr0-l{padding-right:0}.mr0-l,.nr0-l{margin-right:0}.pt0-l{padding-top:0}.mt0-l,.nt0-l{margin-top:0}.pb0-l{padding-bottom:0}.mb0-l,.nb0-l{margin-bottom:0}.pv0-l{padding-top:0;padding-bottom:0}.mv0-l,.nv0-l{margin-top:0;margin-bottom:0}.ph0-l{padding-left:0;padding-right:0}.mh0-l,.nh0-l{margin-left:0;margin-right:0}.pa1-l{padding:.25rem}.ma1-l{margin:.25rem}.na1-l{margin:-.25rem}.pl1-l{padding-left:.25rem}.ml1-l{margin-left:.25rem}.nl1-l{margin-left:-.25rem}.pr1-l{padding-right:.25rem}.mr1-l{margin-right:.25rem}.nr1-l{margin-right:-.25rem}.pt1-l{padding-top:.25rem}.mt1-l{margin-top:.25rem}.nt1-l{margin-top:-.25rem}.pb1-l{padding-bottom:.25rem}.mb1-l{margin-bottom:.25rem}.nb1-l{margin-bottom:-.25rem}.pv1-l{padding-top:.25rem;padding-bottom:.25rem}.mv1-l{margin-top:.25rem;margin-bottom:.25rem}.nv1-l{margin-top:-.25rem;margin-bottom:-.25rem}.ph1-l{padding-left:.25rem;padding-right:.25rem}.mh1-l{margin-left:.25rem;margin-right:.25rem}.nh1-l{margin-left:-.25rem;margin-right:-.25rem}.pa2-l{padding:.5rem}.ma2-l{margin:.5rem}.na2-l{margin:-.5rem}.pl2-l{padding-left:.5rem}.ml2-l{margin-left:.5rem}.nl2-l{margin-left:-.5rem}.pr2-l{padding-right:.5rem}.mr2-l{margin-right:.5rem}.nr2-l{margin-right:-.5rem}.pt2-l{padding-top:.5rem}.mt2-l{margin-top:.5rem}.nt2-l{margin-top:-.5rem}.pb2-l{padding-bottom:.5rem}.mb2-l{margin-bottom:.5rem}.nb2-l{margin-bottom:-.5rem}.pv2-l{padding-top:.5rem;padding-bottom:.5rem}.mv2-l{margin-top:.5rem;margin-bottom:.5rem}.nv2-l{margin-top:-.5rem;margin-bottom:-.5rem}.ph2-l{padding-left:.5rem;padding-right:.5rem}.mh2-l{margin-left:.5rem;margin-right:.5rem}.nh2-l{margin-left:-.5rem;margin-right:-.5rem}.pa3-l{padding:.75rem}.ma3-l{margin:.75rem}.na3-l{margin:-.75rem}.pl3-l{padding-left:.75rem}.ml3-l{margin-left:.75rem}.nl3-l{margin-left:-.75rem}.pr3-l{padding-right:.75rem}.mr3-l{margin-right:.75rem}.nr3-l{margin-right:-.75rem}.pt3-l{padding-top:.75rem}.mt3-l{margin-top:.75rem}.nt3-l{margin-top:-.75rem}.pb3-l{padding-bottom:.75rem}.mb3-l{margin-bottom:.75rem}.nb3-l{margin-bottom:-.75rem}.pv3-l{padding-top:.75rem;padding-bottom:.75rem}.mv3-l{margin-top:.75rem;margin-bottom:.75rem}.nv3-l{margin-top:-.75rem;margin-bottom:-.75rem}.ph3-l{padding-left:.75rem;padding-right:.75rem}.mh3-l{margin-left:.75rem;margin-right:.75rem}.nh3-l{margin-left:-.75rem;margin-right:-.75rem}.pa4-l{padding:1rem}.ma4-l{margin:1rem}.na4-l{margin:-1rem}.pl4-l{padding-left:1rem}.ml4-l{margin-left:1rem}.nl4-l{margin-left:-1rem}.pr4-l{padding-right:1rem}.mr4-l{margin-right:1rem}.nr4-l{margin-right:-1rem}.pt4-l{padding-top:1rem}.mt4-l{margin-top:1rem}.nt4-l{margin-top:-1rem}.pb4-l{padding-bottom:1rem}.mb4-l{margin-bottom:1rem}.nb4-l{margin-bottom:-1rem}.pv4-l{padding-top:1rem;padding-bottom:1rem}.mv4-l{margin-top:1rem;margin-bottom:1rem}.nv4-l{margin-top:-1rem;margin-bottom:-1rem}.ph4-l{padding-left:1rem;padding-right:1rem}.mh4-l{margin-left:1rem;margin-right:1rem}.nh4-l{margin-left:-1rem;margin-right:-1rem}.pa5-l{padding:1.25rem}.ma5-l{margin:1.25rem}.na5-l{margin:-1.25rem}.pl5-l{padding-left:1.25rem}.ml5-l{margin-left:1.25rem}.nl5-l{margin-left:-1.25rem}.pr5-l{padding-right:1.25rem}.mr5-l{margin-right:1.25rem}.nr5-l{margin-right:-1.25rem}.pt5-l{padding-top:1.25rem}.mt5-l{margin-top:1.25rem}.nt5-l{margin-top:-1.25rem}.pb5-l{padding-bottom:1.25rem}.mb5-l{margin-bottom:1.25rem}.nb5-l{margin-bottom:-1.25rem}.pv5-l{padding-top:1.25rem;padding-bottom:1.25rem}.mv5-l{margin-top:1.25rem;margin-bottom:1.25rem}.nv5-l{margin-top:-1.25rem;margin-bottom:-1.25rem}.ph5-l{padding-left:1.25rem;padding-right:1.25rem}.mh5-l{margin-left:1.25rem;margin-right:1.25rem}.nh5-l{margin-left:-1.25rem;margin-right:-1.25rem}.pa6-l{padding:1.5rem}.ma6-l{margin:1.5rem}.na6-l{margin:-1.5rem}.pl6-l{padding-left:1.5rem}.ml6-l{margin-left:1.5rem}.nl6-l{margin-left:-1.5rem}.pr6-l{padding-right:1.5rem}.mr6-l{margin-right:1.5rem}.nr6-l{margin-right:-1.5rem}.pt6-l{padding-top:1.5rem}.mt6-l{margin-top:1.5rem}.nt6-l{margin-top:-1.5rem}.pb6-l{padding-bottom:1.5rem}.mb6-l{margin-bottom:1.5rem}.nb6-l{margin-bottom:-1.5rem}.pv6-l{padding-top:1.5rem;padding-bottom:1.5rem}.mv6-l{margin-top:1.5rem;margin-bottom:1.5rem}.nv6-l{margin-top:-1.5rem;margin-bottom:-1.5rem}.ph6-l{padding-left:1.5rem;padding-right:1.5rem}.mh6-l{margin-left:1.5rem;margin-right:1.5rem}.nh6-l{margin-left:-1.5rem;margin-right:-1.5rem}.pa7-l{padding:2rem}.ma7-l{margin:2rem}.na7-l{margin:-2rem}.pl7-l{padding-left:2rem}.ml7-l{margin-left:2rem}.nl7-l{margin-left:-2rem}.pr7-l{padding-right:2rem}.mr7-l{margin-right:2rem}.nr7-l{margin-right:-2rem}.pt7-l{padding-top:2rem}.mt7-l{margin-top:2rem}.nt7-l{margin-top:-2rem}.pb7-l{padding-bottom:2rem}.mb7-l{margin-bottom:2rem}.nb7-l{margin-bottom:-2rem}.pv7-l{padding-top:2rem;padding-bottom:2rem}.mv7-l{margin-top:2rem;margin-bottom:2rem}.nv7-l{margin-top:-2rem;margin-bottom:-2rem}.ph7-l{padding-left:2rem;padding-right:2rem}.mh7-l{margin-left:2rem;margin-right:2rem}.nh7-l{margin-left:-2rem;margin-right:-2rem}.pa8-l{padding:3rem}.ma8-l{margin:3rem}.na8-l{margin:-3rem}.pl8-l{padding-left:3rem}.ml8-l{margin-left:3rem}.nl8-l{margin-left:-3rem}.pr8-l{padding-right:3rem}.mr8-l{margin-right:3rem}.nr8-l{margin-right:-3rem}.pt8-l{padding-top:3rem}.mt8-l{margin-top:3rem}.nt8-l{margin-top:-3rem}.pb8-l{padding-bottom:3rem}.mb8-l{margin-bottom:3rem}.nb8-l{margin-bottom:-3rem}.pv8-l{padding-top:3rem;padding-bottom:3rem}.mv8-l{margin-top:3rem;margin-bottom:3rem}.nv8-l{margin-top:-3rem;margin-bottom:-3rem}.ph8-l{padding-left:3rem;padding-right:3rem}.mh8-l{margin-left:3rem;margin-right:3rem}.nh8-l{margin-left:-3rem;margin-right:-3rem}.pa9-l{padding:4rem}.ma9-l{margin:4rem}.na9-l{margin:-4rem}.pl9-l{padding-left:4rem}.ml9-l{margin-left:4rem}.nl9-l{margin-left:-4rem}.pr9-l{padding-right:4rem}.mr9-l{margin-right:4rem}.nr9-l{margin-right:-4rem}.pt9-l{padding-top:4rem}.mt9-l{margin-top:4rem}.nt9-l{margin-top:-4rem}.pb9-l{padding-bottom:4rem}.mb9-l{margin-bottom:4rem}.nb9-l{margin-bottom:-4rem}.pv9-l{padding-top:4rem;padding-bottom:4rem}.mv9-l{margin-top:4rem;margin-bottom:4rem}.nv9-l{margin-top:-4rem;margin-bottom:-4rem}.ph9-l{padding-left:4rem;padding-right:4rem}.mh9-l{margin-left:4rem;margin-right:4rem}.nh9-l{margin-left:-4rem;margin-right:-4rem}.pa10-l{padding:6rem}.ma10-l{margin:6rem}.na10-l{margin:-6rem}.pl10-l{padding-left:6rem}.ml10-l{margin-left:6rem}.nl10-l{margin-left:-6rem}.pr10-l{padding-right:6rem}.mr10-l{margin-right:6rem}.nr10-l{margin-right:-6rem}.pt10-l{padding-top:6rem}.mt10-l{margin-top:6rem}.nt10-l{margin-top:-6rem}.pb10-l{padding-bottom:6rem}.mb10-l{margin-bottom:6rem}.nb10-l{margin-bottom:-6rem}.pv10-l{padding-top:6rem;padding-bottom:6rem}.mv10-l{margin-top:6rem;margin-bottom:6rem}.nv10-l{margin-top:-6rem;margin-bottom:-6rem}.ph10-l{padding-left:6rem;padding-right:6rem}.mh10-l{margin-left:6rem;margin-right:6rem}.nh10-l{margin-left:-6rem;margin-right:-6rem}.pa11-l{padding:10rem}.ma11-l{margin:10rem}.na11-l{margin:-10rem}.pl11-l{padding-left:10rem}.ml11-l{margin-left:10rem}.nl11-l{margin-left:-10rem}.pr11-l{padding-right:10rem}.mr11-l{margin-right:10rem}.nr11-l{margin-right:-10rem}.pt11-l{padding-top:10rem}.mt11-l{margin-top:10rem}.nt11-l{margin-top:-10rem}.pb11-l{padding-bottom:10rem}.mb11-l{margin-bottom:10rem}.nb11-l{margin-bottom:-10rem}.pv11-l{padding-top:10rem;padding-bottom:10rem}.mv11-l{margin-top:10rem;margin-bottom:10rem}.nv11-l{margin-top:-10rem;margin-bottom:-10rem}.ph11-l{padding-left:10rem;padding-right:10rem}.mh11-l{margin-left:10rem;margin-right:10rem}.nh11-l{margin-left:-10rem;margin-right:-10rem}.pa12-l{padding:18rem}.ma12-l{margin:18rem}.na12-l{margin:-18rem}.pl12-l{padding-left:18rem}.ml12-l{margin-left:18rem}.nl12-l{margin-left:-18rem}.pr12-l{padding-right:18rem}.mr12-l{margin-right:18rem}.nr12-l{margin-right:-18rem}.pt12-l{padding-top:18rem}.mt12-l{margin-top:18rem}.nt12-l{margin-top:-18rem}.pb12-l{padding-bottom:18rem}.mb12-l{margin-bottom:18rem}.nb12-l{margin-bottom:-18rem}.pv12-l{padding-top:18rem;padding-bottom:18rem}.mv12-l{margin-top:18rem;margin-bottom:18rem}.nv12-l{margin-top:-18rem;margin-bottom:-18rem}.ph12-l{padding-left:18rem;padding-right:18rem}.mh12-l{margin-left:18rem;margin-right:18rem}.nh12-l{margin-left:-18rem;margin-right:-18rem}.top-0-l{top:0}.right-0-l{right:0}.bottom-0-l{bottom:0}.left-0-l{left:0}.top-1-l{top:1rem}.right-1-l{right:1rem}.bottom-1-l{bottom:1rem}.left-1-l{left:1rem}.top-2-l{top:2rem}.right-2-l{right:2rem}.bottom-2-l{bottom:2rem}.left-2-l{left:2rem}.top--1-l{top:-1rem}.right--1-l{right:-1rem}.bottom--1-l{bottom:-1rem}.left--1-l{left:-1rem}.top--2-l{top:-2rem}.right--2-l{right:-2rem}.bottom--2-l{bottom:-2rem}.left--2-l{left:-2rem}.absolute--fill-l{top:0;right:0;bottom:0;left:0}.cf-l:after,.cf-l:before{content:" ";display:table}.cf-l:after{clear:both}.cf-l{*zoom:1}.cl-l{clear:left}.cr-l{clear:right}.cb-l{clear:both}.cn-l{clear:none}.dn-l{display:none}.di-l{display:inline}.db-l{display:block}.dib-l{display:inline-block}.dit-l{display:inline-table}.dt-l{display:table}.dtc-l{display:table-cell}.dt-row-l{display:table-row}.dt-row-group-l{display:table-row-group}.dt-column-l{display:table-column}.dt-column-group-l{display:table-column-group}.dt--fixed-l{table-layout:fixed;width:100%}.flex-l{display:flex}.inline-flex-l{display:inline-flex}.flex-auto-l{flex:1 1 auto;min-width:0;min-height:0}.flex-none-l{flex:none}.flex-column-l{flex-direction:column}.flex-row-l{flex-direction:row}.flex-wrap-l{flex-wrap:wrap}.flex-nowrap-l{flex-wrap:nowrap}.flex-wrap-reverse-l{flex-wrap:wrap-reverse}.flex-column-reverse-l{flex-direction:column-reverse}.flex-row-reverse-l{flex-direction:row-reverse}.items-start-l{align-items:flex-start}.items-end-l{align-items:flex-end}.items-center-l{align-items:center}.items-baseline-l{align-items:baseline}.items-stretch-l{align-items:stretch}.self-start-l{align-self:flex-start}.self-end-l{align-self:flex-end}.self-center-l{align-self:center}.self-baseline-l{align-self:baseline}.self-stretch-l{align-self:stretch}.justify-start-l{justify-content:flex-start}.justify-end-l{justify-content:flex-end}.justify-center-l{justify-content:center}.justify-between-l{justify-content:space-between}.justify-around-l{justify-content:space-around}.content-start-l{align-content:flex-start}.content-end-l{align-content:flex-end}.content-center-l{align-content:center}.content-between-l{align-content:space-between}.content-around-l{align-content:space-around}.content-stretch-l{align-content:stretch}.order-0-l{order:0}.order-1-l{order:1}.order-2-l{order:2}.order-3-l{order:3}.order-4-l{order:4}.order-5-l{order:5}.order-6-l{order:6}.order-7-l{order:7}.order-8-l{order:8}.order-last-l{order:99999}.flex-grow-0-l{flex-grow:0}.flex-grow-1-l{flex-grow:1}.flex-shrink-0-l{flex-shrink:0}.flex-shrink-1-l{flex-shrink:1}.fl-l{float:left}.fl-l,.fr-l{_display:inline}.fr-l{float:right}.fn-l{float:none}.i-l{font-style:italic}.fs-normal-l{font-style:normal}.normal-l{font-weight:400}.b-l{font-weight:700}.fw1-l{font-weight:100}.fw2-l{font-weight:200}.fw3-l{font-weight:300}.fw4-l{font-weight:400}.fw5-l{font-weight:500}.fw6-l{font-weight:600}.fw7-l{font-weight:700}.fw8-l{font-weight:800}.fw9-l{font-weight:900}.h1-l{height:1rem}.h2-l{height:2rem}.h3-l{height:4rem}.h4-l{height:8rem}.h5-l{height:16rem}.h-25-l{height:25%}.h-50-l{height:50%}.h-75-l{height:75%}.h-100-l{height:100%}.min-h-100-l{min-height:100%}.vh-25-l{height:25vh}.vh-50-l{height:50vh}.vh-75-l{height:75vh}.vh-100-l{height:100vh}.min-vh-100-l{min-height:100vh}.h-auto-l{height:auto}.h-inherit-l{height:inherit}.tracked-l{letter-spacing:.1em}.tracked-tight-l{letter-spacing:-.05em}.tracked-mega-l{letter-spacing:.25em}.lh-solid-l{line-height:1.333333}.lh-title-l{line-height:1.5}.lh-copy-l{line-height:1.666666}.mw1-l{max-width:1rem}.mw2-l{max-width:2rem}.mw3-l{max-width:4rem}.mw4-l{max-width:8rem}.mw5-l{max-width:16rem}.mw6-l{max-width:32rem}.mw7-l{max-width:48rem}.mw8-l{max-width:64rem}.mw9-l{max-width:96rem}.mw-none-l{max-width:none}.mw-100-l{max-width:100%}.o-100-l{opacity:1}.o-90-l{opacity:.9}.o-80-l{opacity:.8}.o-70-l{opacity:.7}.o-60-l{opacity:.6}.o-50-l{opacity:.5}.o-40-l{opacity:.4}.o-30-l{opacity:.3}.o-20-l{opacity:.2}.o-10-l{opacity:.1}.o-05-l{opacity:.05}.o-025-l{opacity:.025}.o-0-l{opacity:0}.rotate-45-l{-webkit-transform:rotate(45deg);transform:rotate(45deg)}.rotate-90-l{-webkit-transform:rotate(90deg);transform:rotate(90deg)}.rotate-135-l{-webkit-transform:rotate(135deg);transform:rotate(135deg)}.rotate-180-l{-webkit-transform:rotate(180deg);transform:rotate(180deg)}.rotate-225-l{-webkit-transform:rotate(225deg);transform:rotate(225deg)}.rotate-270-l{-webkit-transform:rotate(270deg);transform:rotate(270deg)}.rotate-315-l{-webkit-transform:rotate(315deg);transform:rotate(315deg)}.outline-l{outline:1px solid}.outline-transparent-l{outline:1px solid transparent}.outline-0-l{outline:0}.overflow-visible-l{overflow:visible}.overflow-hidden-l{overflow:hidden}.overflow-scroll-l{overflow:scroll}.overflow-auto-l{overflow:auto}.overflow-x-visible-l{overflow-x:visible}.overflow-x-hidden-l{overflow-x:hidden}.overflow-x-scroll-l{overflow-x:scroll}.overflow-x-auto-l{overflow-x:auto}.overflow-y-visible-l{overflow-y:visible}.overflow-y-hidden-l{overflow-y:hidden}.overflow-y-scroll-l{overflow-y:scroll}.overflow-y-auto-l{overflow-y:auto}.static-l{position:static}.relative-l{position:relative}.absolute-l{position:absolute}.fixed-l{position:fixed}.strike-l{text-decoration:line-through}.underline-l{text-decoration:underline}.no-underline-l{text-decoration:none}.tl-l{text-align:left}.tr-l{text-align:right}.tc-l{text-align:center}.tj-l{text-align:justify}.ttc-l{text-transform:capitalize}.ttl-l{text-transform:lowercase}.ttu-l{text-transform:uppercase}.ttn-l{text-transform:none}.f1-l{font-size:4.5rem}.f2-l{font-size:4rem}.f3-l{font-size:3rem}.f4-l{font-size:2rem}.f5-l{font-size:1.5rem}.f6-l{font-size:1.125rem}.f7-l{font-size:1rem}.f8-l{font-size:.875rem}.f9-l{font-size:.75rem}.measure-l{max-width:30em}.measure-wide-l{max-width:34em}.measure-narrow-l{max-width:20em}.small-caps-l{font-variant:small-caps}.indent-l{text-indent:1em;margin-top:0;margin-bottom:0}.truncate-l{white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.clip-l{position:fixed!important;_position:absolute!important;clip:rect(1px 1px 1px 1px);clip:rect(1px,1px,1px,1px)}.ws-normal-l{white-space:normal}.nowrap-l{white-space:nowrap}.pre-l{white-space:pre}.w1-l{width:1rem}.w2-l{width:2rem}.w3-l{width:4rem}.w4-l{width:8rem}.w5-l{width:16rem}.w-10-l{width:10%}.w-20-l{width:20%}.w-25-l{width:25%}.w-30-l{width:30%}.w-33-l{width:33%}.w-34-l{width:34%}.w-40-l{width:40%}.w-50-l{width:50%}.w-60-l{width:60%}.w-70-l{width:70%}.w-75-l{width:75%}.w-80-l{width:80%}.w-90-l{width:90%}.w-100-l{width:100%}.w-third-l{width:33.33333%}.w-two-thirds-l{width:66.66667%}.w-auto-l{width:auto}}@media screen and (min-width:60em){.aspect-ratio-xl{height:0;position:relative}.aspect-ratio--16x9-xl{padding-bottom:56.25%}.aspect-ratio--9x16-xl{padding-bottom:177.77%}.aspect-ratio--4x3-xl{padding-bottom:75%}.aspect-ratio--3x4-xl{padding-bottom:133.33%}.aspect-ratio--6x4-xl{padding-bottom:66.6%}.aspect-ratio--4x6-xl{padding-bottom:150%}.aspect-ratio--8x5-xl{padding-bottom:62.5%}.aspect-ratio--5x8-xl{padding-bottom:160%}.aspect-ratio--7x5-xl{padding-bottom:71.42%}.aspect-ratio--5x7-xl{padding-bottom:140%}.aspect-ratio--1x1-xl{padding-bottom:100%}.aspect-ratio--object-xl{position:absolute;top:0;right:0;bottom:0;left:0;width:100%;height:100%;z-index:100}.cover-xl{background-size:cover!important}.contain-xl{background-size:contain!important}.bg-center-xl{background-position:50%}.bg-center-xl,.bg-top-xl{background-repeat:no-repeat}.bg-top-xl{background-position:top}.bg-right-xl{background-position:100%}.bg-bottom-xl,.bg-right-xl{background-repeat:no-repeat}.bg-bottom-xl{background-position:bottom}.bg-left-xl{background-repeat:no-repeat;background-position:0}.ba-xl{border-style:solid;border-width:1px}.bt-xl{border-top-style:solid;border-top-width:1px}.br-xl{border-right-style:solid;border-right-width:1px}.bb-xl{border-bottom-style:solid;border-bottom-width:1px}.bl-xl{border-left-style:solid;border-left-width:1px}.bn-xl{border-style:none;border-width:0}.br0-xl{border-radius:0}.br1-xl{border-radius:.125rem}.br2-xl{border-radius:.25rem}.br3-xl{border-radius:.5rem}.br4-xl{border-radius:1rem}.br-100-xl{border-radius:100%}.br-pill-xl{border-radius:9999px}.br--bottom-xl{border-top-left-radius:0;border-top-right-radius:0}.br--top-xl{border-bottom-right-radius:0}.br--right-xl,.br--top-xl{border-bottom-left-radius:0}.br--right-xl{border-top-left-radius:0}.br--left-xl{border-top-right-radius:0;border-bottom-right-radius:0}.b--dotted-xl{border-style:dotted}.b--dashed-xl{border-style:dashed}.b--solid-xl{border-style:solid}.b--none-xl{border-style:none}.bw0-xl{border-width:0}.bw1-xl{border-width:.125rem}.bw2-xl{border-width:.25rem}.bw3-xl{border-width:.5rem}.bw4-xl{border-width:1rem}.bw5-xl{border-width:2rem}.bt-0-xl{border-top-width:0}.br-0-xl{border-right-width:0}.bb-0-xl{border-bottom-width:0}.bl-0-xl{border-left-width:0}.shadow-1-xl{box-shadow:0 0 4px 2px rgba(0,0,0,.2)}.shadow-2-xl{box-shadow:0 0 8px 2px rgba(0,0,0,.2)}.shadow-3-xl{box-shadow:2px 2px 4px 2px rgba(0,0,0,.2)}.shadow-4-xl{box-shadow:2px 2px 8px 0 rgba(0,0,0,.2)}.shadow-5-xl{box-shadow:4px 4px 8px 0 rgba(0,0,0,.2)}.pa0-xl{padding:0}.ma0-xl,.na0-xl{margin:0}.pl0-xl{padding-left:0}.ml0-xl,.nl0-xl{margin-left:0}.pr0-xl{padding-right:0}.mr0-xl,.nr0-xl{margin-right:0}.pt0-xl{padding-top:0}.mt0-xl,.nt0-xl{margin-top:0}.pb0-xl{padding-bottom:0}.mb0-xl,.nb0-xl{margin-bottom:0}.pv0-xl{padding-top:0;padding-bottom:0}.mv0-xl,.nv0-xl{margin-top:0;margin-bottom:0}.ph0-xl{padding-left:0;padding-right:0}.mh0-xl,.nh0-xl{margin-left:0;margin-right:0}.pa1-xl{padding:.25rem}.ma1-xl{margin:.25rem}.na1-xl{margin:-.25rem}.pl1-xl{padding-left:.25rem}.ml1-xl{margin-left:.25rem}.nl1-xl{margin-left:-.25rem}.pr1-xl{padding-right:.25rem}.mr1-xl{margin-right:.25rem}.nr1-xl{margin-right:-.25rem}.pt1-xl{padding-top:.25rem}.mt1-xl{margin-top:.25rem}.nt1-xl{margin-top:-.25rem}.pb1-xl{padding-bottom:.25rem}.mb1-xl{margin-bottom:.25rem}.nb1-xl{margin-bottom:-.25rem}.pv1-xl{padding-top:.25rem;padding-bottom:.25rem}.mv1-xl{margin-top:.25rem;margin-bottom:.25rem}.nv1-xl{margin-top:-.25rem;margin-bottom:-.25rem}.ph1-xl{padding-left:.25rem;padding-right:.25rem}.mh1-xl{margin-left:.25rem;margin-right:.25rem}.nh1-xl{margin-left:-.25rem;margin-right:-.25rem}.pa2-xl{padding:.5rem}.ma2-xl{margin:.5rem}.na2-xl{margin:-.5rem}.pl2-xl{padding-left:.5rem}.ml2-xl{margin-left:.5rem}.nl2-xl{margin-left:-.5rem}.pr2-xl{padding-right:.5rem}.mr2-xl{margin-right:.5rem}.nr2-xl{margin-right:-.5rem}.pt2-xl{padding-top:.5rem}.mt2-xl{margin-top:.5rem}.nt2-xl{margin-top:-.5rem}.pb2-xl{padding-bottom:.5rem}.mb2-xl{margin-bottom:.5rem}.nb2-xl{margin-bottom:-.5rem}.pv2-xl{padding-top:.5rem;padding-bottom:.5rem}.mv2-xl{margin-top:.5rem;margin-bottom:.5rem}.nv2-xl{margin-top:-.5rem;margin-bottom:-.5rem}.ph2-xl{padding-left:.5rem;padding-right:.5rem}.mh2-xl{margin-left:.5rem;margin-right:.5rem}.nh2-xl{margin-left:-.5rem;margin-right:-.5rem}.pa3-xl{padding:.75rem}.ma3-xl{margin:.75rem}.na3-xl{margin:-.75rem}.pl3-xl{padding-left:.75rem}.ml3-xl{margin-left:.75rem}.nl3-xl{margin-left:-.75rem}.pr3-xl{padding-right:.75rem}.mr3-xl{margin-right:.75rem}.nr3-xl{margin-right:-.75rem}.pt3-xl{padding-top:.75rem}.mt3-xl{margin-top:.75rem}.nt3-xl{margin-top:-.75rem}.pb3-xl{padding-bottom:.75rem}.mb3-xl{margin-bottom:.75rem}.nb3-xl{margin-bottom:-.75rem}.pv3-xl{padding-top:.75rem;padding-bottom:.75rem}.mv3-xl{margin-top:.75rem;margin-bottom:.75rem}.nv3-xl{margin-top:-.75rem;margin-bottom:-.75rem}.ph3-xl{padding-left:.75rem;padding-right:.75rem}.mh3-xl{margin-left:.75rem;margin-right:.75rem}.nh3-xl{margin-left:-.75rem;margin-right:-.75rem}.pa4-xl{padding:1rem}.ma4-xl{margin:1rem}.na4-xl{margin:-1rem}.pl4-xl{padding-left:1rem}.ml4-xl{margin-left:1rem}.nl4-xl{margin-left:-1rem}.pr4-xl{padding-right:1rem}.mr4-xl{margin-right:1rem}.nr4-xl{margin-right:-1rem}.pt4-xl{padding-top:1rem}.mt4-xl{margin-top:1rem}.nt4-xl{margin-top:-1rem}.pb4-xl{padding-bottom:1rem}.mb4-xl{margin-bottom:1rem}.nb4-xl{margin-bottom:-1rem}.pv4-xl{padding-top:1rem;padding-bottom:1rem}.mv4-xl{margin-top:1rem;margin-bottom:1rem}.nv4-xl{margin-top:-1rem;margin-bottom:-1rem}.ph4-xl{padding-left:1rem;padding-right:1rem}.mh4-xl{margin-left:1rem;margin-right:1rem}.nh4-xl{margin-left:-1rem;margin-right:-1rem}.pa5-xl{padding:1.25rem}.ma5-xl{margin:1.25rem}.na5-xl{margin:-1.25rem}.pl5-xl{padding-left:1.25rem}.ml5-xl{margin-left:1.25rem}.nl5-xl{margin-left:-1.25rem}.pr5-xl{padding-right:1.25rem}.mr5-xl{margin-right:1.25rem}.nr5-xl{margin-right:-1.25rem}.pt5-xl{padding-top:1.25rem}.mt5-xl{margin-top:1.25rem}.nt5-xl{margin-top:-1.25rem}.pb5-xl{padding-bottom:1.25rem}.mb5-xl{margin-bottom:1.25rem}.nb5-xl{margin-bottom:-1.25rem}.pv5-xl{padding-top:1.25rem;padding-bottom:1.25rem}.mv5-xl{margin-top:1.25rem;margin-bottom:1.25rem}.nv5-xl{margin-top:-1.25rem;margin-bottom:-1.25rem}.ph5-xl{padding-left:1.25rem;padding-right:1.25rem}.mh5-xl{margin-left:1.25rem;margin-right:1.25rem}.nh5-xl{margin-left:-1.25rem;margin-right:-1.25rem}.pa6-xl{padding:1.5rem}.ma6-xl{margin:1.5rem}.na6-xl{margin:-1.5rem}.pl6-xl{padding-left:1.5rem}.ml6-xl{margin-left:1.5rem}.nl6-xl{margin-left:-1.5rem}.pr6-xl{padding-right:1.5rem}.mr6-xl{margin-right:1.5rem}.nr6-xl{margin-right:-1.5rem}.pt6-xl{padding-top:1.5rem}.mt6-xl{margin-top:1.5rem}.nt6-xl{margin-top:-1.5rem}.pb6-xl{padding-bottom:1.5rem}.mb6-xl{margin-bottom:1.5rem}.nb6-xl{margin-bottom:-1.5rem}.pv6-xl{padding-top:1.5rem;padding-bottom:1.5rem}.mv6-xl{margin-top:1.5rem;margin-bottom:1.5rem}.nv6-xl{margin-top:-1.5rem;margin-bottom:-1.5rem}.ph6-xl{padding-left:1.5rem;padding-right:1.5rem}.mh6-xl{margin-left:1.5rem;margin-right:1.5rem}.nh6-xl{margin-left:-1.5rem;margin-right:-1.5rem}.pa7-xl{padding:2rem}.ma7-xl{margin:2rem}.na7-xl{margin:-2rem}.pl7-xl{padding-left:2rem}.ml7-xl{margin-left:2rem}.nl7-xl{margin-left:-2rem}.pr7-xl{padding-right:2rem}.mr7-xl{margin-right:2rem}.nr7-xl{margin-right:-2rem}.pt7-xl{padding-top:2rem}.mt7-xl{margin-top:2rem}.nt7-xl{margin-top:-2rem}.pb7-xl{padding-bottom:2rem}.mb7-xl{margin-bottom:2rem}.nb7-xl{margin-bottom:-2rem}.pv7-xl{padding-top:2rem;padding-bottom:2rem}.mv7-xl{margin-top:2rem;margin-bottom:2rem}.nv7-xl{margin-top:-2rem;margin-bottom:-2rem}.ph7-xl{padding-left:2rem;padding-right:2rem}.mh7-xl{margin-left:2rem;margin-right:2rem}.nh7-xl{margin-left:-2rem;margin-right:-2rem}.pa8-xl{padding:3rem}.ma8-xl{margin:3rem}.na8-xl{margin:-3rem}.pl8-xl{padding-left:3rem}.ml8-xl{margin-left:3rem}.nl8-xl{margin-left:-3rem}.pr8-xl{padding-right:3rem}.mr8-xl{margin-right:3rem}.nr8-xl{margin-right:-3rem}.pt8-xl{padding-top:3rem}.mt8-xl{margin-top:3rem}.nt8-xl{margin-top:-3rem}.pb8-xl{padding-bottom:3rem}.mb8-xl{margin-bottom:3rem}.nb8-xl{margin-bottom:-3rem}.pv8-xl{padding-top:3rem;padding-bottom:3rem}.mv8-xl{margin-top:3rem;margin-bottom:3rem}.nv8-xl{margin-top:-3rem;margin-bottom:-3rem}.ph8-xl{padding-left:3rem;padding-right:3rem}.mh8-xl{margin-left:3rem;margin-right:3rem}.nh8-xl{margin-left:-3rem;margin-right:-3rem}.pa9-xl{padding:4rem}.ma9-xl{margin:4rem}.na9-xl{margin:-4rem}.pl9-xl{padding-left:4rem}.ml9-xl{margin-left:4rem}.nl9-xl{margin-left:-4rem}.pr9-xl{padding-right:4rem}.mr9-xl{margin-right:4rem}.nr9-xl{margin-right:-4rem}.pt9-xl{padding-top:4rem}.mt9-xl{margin-top:4rem}.nt9-xl{margin-top:-4rem}.pb9-xl{padding-bottom:4rem}.mb9-xl{margin-bottom:4rem}.nb9-xl{margin-bottom:-4rem}.pv9-xl{padding-top:4rem;padding-bottom:4rem}.mv9-xl{margin-top:4rem;margin-bottom:4rem}.nv9-xl{margin-top:-4rem;margin-bottom:-4rem}.ph9-xl{padding-left:4rem;padding-right:4rem}.mh9-xl{margin-left:4rem;margin-right:4rem}.nh9-xl{margin-left:-4rem;margin-right:-4rem}.pa10-xl{padding:6rem}.ma10-xl{margin:6rem}.na10-xl{margin:-6rem}.pl10-xl{padding-left:6rem}.ml10-xl{margin-left:6rem}.nl10-xl{margin-left:-6rem}.pr10-xl{padding-right:6rem}.mr10-xl{margin-right:6rem}.nr10-xl{margin-right:-6rem}.pt10-xl{padding-top:6rem}.mt10-xl{margin-top:6rem}.nt10-xl{margin-top:-6rem}.pb10-xl{padding-bottom:6rem}.mb10-xl{margin-bottom:6rem}.nb10-xl{margin-bottom:-6rem}.pv10-xl{padding-top:6rem;padding-bottom:6rem}.mv10-xl{margin-top:6rem;margin-bottom:6rem}.nv10-xl{margin-top:-6rem;margin-bottom:-6rem}.ph10-xl{padding-left:6rem;padding-right:6rem}.mh10-xl{margin-left:6rem;margin-right:6rem}.nh10-xl{margin-left:-6rem;margin-right:-6rem}.pa11-xl{padding:10rem}.ma11-xl{margin:10rem}.na11-xl{margin:-10rem}.pl11-xl{padding-left:10rem}.ml11-xl{margin-left:10rem}.nl11-xl{margin-left:-10rem}.pr11-xl{padding-right:10rem}.mr11-xl{margin-right:10rem}.nr11-xl{margin-right:-10rem}.pt11-xl{padding-top:10rem}.mt11-xl{margin-top:10rem}.nt11-xl{margin-top:-10rem}.pb11-xl{padding-bottom:10rem}.mb11-xl{margin-bottom:10rem}.nb11-xl{margin-bottom:-10rem}.pv11-xl{padding-top:10rem;padding-bottom:10rem}.mv11-xl{margin-top:10rem;margin-bottom:10rem}.nv11-xl{margin-top:-10rem;margin-bottom:-10rem}.ph11-xl{padding-left:10rem;padding-right:10rem}.mh11-xl{margin-left:10rem;margin-right:10rem}.nh11-xl{margin-left:-10rem;margin-right:-10rem}.pa12-xl{padding:18rem}.ma12-xl{margin:18rem}.na12-xl{margin:-18rem}.pl12-xl{padding-left:18rem}.ml12-xl{margin-left:18rem}.nl12-xl{margin-left:-18rem}.pr12-xl{padding-right:18rem}.mr12-xl{margin-right:18rem}.nr12-xl{margin-right:-18rem}.pt12-xl{padding-top:18rem}.mt12-xl{margin-top:18rem}.nt12-xl{margin-top:-18rem}.pb12-xl{padding-bottom:18rem}.mb12-xl{margin-bottom:18rem}.nb12-xl{margin-bottom:-18rem}.pv12-xl{padding-top:18rem;padding-bottom:18rem}.mv12-xl{margin-top:18rem;margin-bottom:18rem}.nv12-xl{margin-top:-18rem;margin-bottom:-18rem}.ph12-xl{padding-left:18rem;padding-right:18rem}.mh12-xl{margin-left:18rem;margin-right:18rem}.nh12-xl{margin-left:-18rem;margin-right:-18rem}.top-0-xl{top:0}.right-0-xl{right:0}.bottom-0-xl{bottom:0}.left-0-xl{left:0}.top-1-xl{top:1rem}.right-1-xl{right:1rem}.bottom-1-xl{bottom:1rem}.left-1-xl{left:1rem}.top-2-xl{top:2rem}.right-2-xl{right:2rem}.bottom-2-xl{bottom:2rem}.left-2-xl{left:2rem}.top--1-xl{top:-1rem}.right--1-xl{right:-1rem}.bottom--1-xl{bottom:-1rem}.left--1-xl{left:-1rem}.top--2-xl{top:-2rem}.right--2-xl{right:-2rem}.bottom--2-xl{bottom:-2rem}.left--2-xl{left:-2rem}.absolute--fill-xl{top:0;right:0;bottom:0;left:0}.cf-xl:after,.cf-xl:before{content:" ";display:table}.cf-xl:after{clear:both}.cf-xl{*zoom:1}.cl-xl{clear:left}.cr-xl{clear:right}.cb-xl{clear:both}.cn-xl{clear:none}.dn-xl{display:none}.di-xl{display:inline}.db-xl{display:block}.dib-xl{display:inline-block}.dit-xl{display:inline-table}.dt-xl{display:table}.dtc-xl{display:table-cell}.dt-row-xl{display:table-row}.dt-row-group-xl{display:table-row-group}.dt-column-xl{display:table-column}.dt-column-group-xl{display:table-column-group}.dt--fixed-xl{table-layout:fixed;width:100%}.flex-xl{display:flex}.inline-flex-xl{display:inline-flex}.flex-auto-xl{flex:1 1 auto;min-width:0;min-height:0}.flex-none-xl{flex:none}.flex-column-xl{flex-direction:column}.flex-row-xl{flex-direction:row}.flex-wrap-xl{flex-wrap:wrap}.flex-nowrap-xl{flex-wrap:nowrap}.flex-wrap-reverse-xl{flex-wrap:wrap-reverse}.flex-column-reverse-xl{flex-direction:column-reverse}.flex-row-reverse-xl{flex-direction:row-reverse}.items-start-xl{align-items:flex-start}.items-end-xl{align-items:flex-end}.items-center-xl{align-items:center}.items-baseline-xl{align-items:baseline}.items-stretch-xl{align-items:stretch}.self-start-xl{align-self:flex-start}.self-end-xl{align-self:flex-end}.self-center-xl{align-self:center}.self-baseline-xl{align-self:baseline}.self-stretch-xl{align-self:stretch}.justify-start-xl{justify-content:flex-start}.justify-end-xl{justify-content:flex-end}.justify-center-xl{justify-content:center}.justify-between-xl{justify-content:space-between}.justify-around-xl{justify-content:space-around}.content-start-xl{align-content:flex-start}.content-end-xl{align-content:flex-end}.content-center-xl{align-content:center}.content-between-xl{align-content:space-between}.content-around-xl{align-content:space-around}.content-stretch-xl{align-content:stretch}.order-0-xl{order:0}.order-1-xl{order:1}.order-2-xl{order:2}.order-3-xl{order:3}.order-4-xl{order:4}.order-5-xl{order:5}.order-6-xl{order:6}.order-7-xl{order:7}.order-8-xl{order:8}.order-last-xl{order:99999}.flex-grow-0-xl{flex-grow:0}.flex-grow-1-xl{flex-grow:1}.flex-shrink-0-xl{flex-shrink:0}.flex-shrink-1-xl{flex-shrink:1}.fl-xl{float:left}.fl-xl,.fr-xl{_display:inline}.fr-xl{float:right}.fn-xl{float:none}.i-xl{font-style:italic}.fs-normal-xl{font-style:normal}.normal-xl{font-weight:400}.b-xl{font-weight:700}.fw1-xl{font-weight:100}.fw2-xl{font-weight:200}.fw3-xl{font-weight:300}.fw4-xl{font-weight:400}.fw5-xl{font-weight:500}.fw6-xl{font-weight:600}.fw7-xl{font-weight:700}.fw8-xl{font-weight:800}.fw9-xl{font-weight:900}.h1-xl{height:1rem}.h2-xl{height:2rem}.h3-xl{height:4rem}.h4-xl{height:8rem}.h5-xl{height:16rem}.h-25-xl{height:25%}.h-50-xl{height:50%}.h-75-xl{height:75%}.h-100-xl{height:100%}.min-h-100-xl{min-height:100%}.vh-25-xl{height:25vh}.vh-50-xl{height:50vh}.vh-75-xl{height:75vh}.vh-100-xl{height:100vh}.min-vh-100-xl{min-height:100vh}.h-auto-xl{height:auto}.h-inherit-xl{height:inherit}.tracked-xl{letter-spacing:.1em}.tracked-tight-xl{letter-spacing:-.05em}.tracked-mega-xl{letter-spacing:.25em}.lh-solid-xl{line-height:1.333333}.lh-title-xl{line-height:1.5}.lh-copy-xl{line-height:1.666666}.mw1-xl{max-width:1rem}.mw2-xl{max-width:2rem}.mw3-xl{max-width:4rem}.mw4-xl{max-width:8rem}.mw5-xl{max-width:16rem}.mw6-xl{max-width:32rem}.mw7-xl{max-width:48rem}.mw8-xl{max-width:64rem}.mw9-xl{max-width:96rem}.mw-none-xl{max-width:none}.mw-100-xl{max-width:100%}.o-100-xl{opacity:1}.o-90-xl{opacity:.9}.o-80-xl{opacity:.8}.o-70-xl{opacity:.7}.o-60-xl{opacity:.6}.o-50-xl{opacity:.5}.o-40-xl{opacity:.4}.o-30-xl{opacity:.3}.o-20-xl{opacity:.2}.o-10-xl{opacity:.1}.o-05-xl{opacity:.05}.o-025-xl{opacity:.025}.o-0-xl{opacity:0}.rotate-45-xl{-webkit-transform:rotate(45deg);transform:rotate(45deg)}.rotate-90-xl{-webkit-transform:rotate(90deg);transform:rotate(90deg)}.rotate-135-xl{-webkit-transform:rotate(135deg);transform:rotate(135deg)}.rotate-180-xl{-webkit-transform:rotate(180deg);transform:rotate(180deg)}.rotate-225-xl{-webkit-transform:rotate(225deg);transform:rotate(225deg)}.rotate-270-xl{-webkit-transform:rotate(270deg);transform:rotate(270deg)}.rotate-315-xl{-webkit-transform:rotate(315deg);transform:rotate(315deg)}.outline-xl{outline:1px solid}.outline-transparent-xl{outline:1px solid transparent}.outline-0-xl{outline:0}.overflow-visible-xl{overflow:visible}.overflow-hidden-xl{overflow:hidden}.overflow-scroll-xl{overflow:scroll}.overflow-auto-xl{overflow:auto}.overflow-x-visible-xl{overflow-x:visible}.overflow-x-hidden-xl{overflow-x:hidden}.overflow-x-scroll-xl{overflow-x:scroll}.overflow-x-auto-xl{overflow-x:auto}.overflow-y-visible-xl{overflow-y:visible}.overflow-y-hidden-xl{overflow-y:hidden}.overflow-y-scroll-xl{overflow-y:scroll}.overflow-y-auto-xl{overflow-y:auto}.static-xl{position:static}.relative-xl{position:relative}.absolute-xl{position:absolute}.fixed-xl{position:fixed}.strike-xl{text-decoration:line-through}.underline-xl{text-decoration:underline}.no-underline-xl{text-decoration:none}.tl-xl{text-align:left}.tr-xl{text-align:right}.tc-xl{text-align:center}.tj-xl{text-align:justify}.ttc-xl{text-transform:capitalize}.ttl-xl{text-transform:lowercase}.ttu-xl{text-transform:uppercase}.ttn-xl{text-transform:none}.f1-xl{font-size:4.5rem}.f2-xl{font-size:4rem}.f3-xl{font-size:3rem}.f4-xl{font-size:2rem}.f5-xl{font-size:1.5rem}.f6-xl{font-size:1.125rem}.f7-xl{font-size:1rem}.f8-xl{font-size:.875rem}.f9-xl{font-size:.75rem}.measure-xl{max-width:30em}.measure-wide-xl{max-width:34em}.measure-narrow-xl{max-width:20em}.small-caps-xl{font-variant:small-caps}.indent-xl{text-indent:1em;margin-top:0;margin-bottom:0}.truncate-xl{white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.clip-xl{position:fixed!important;_position:absolute!important;clip:rect(1px 1px 1px 1px);clip:rect(1px,1px,1px,1px)}.ws-normal-xl{white-space:normal}.nowrap-xl{white-space:nowrap}.pre-xl{white-space:pre}.w1-xl{width:1rem}.w2-xl{width:2rem}.w3-xl{width:4rem}.w4-xl{width:8rem}.w5-xl{width:16rem}.w-10-xl{width:10%}.w-20-xl{width:20%}.w-25-xl{width:25%}.w-30-xl{width:30%}.w-33-xl{width:33%}.w-34-xl{width:34%}.w-40-xl{width:40%}.w-50-xl{width:50%}.w-60-xl{width:60%}.w-70-xl{width:70%}.w-75-xl{width:75%}.w-80-xl{width:80%}.w-90-xl{width:90%}.w-100-xl{width:100%}.w-third-xl{width:33.33333%}.w-two-thirds-xl{width:66.66667%}.w-auto-xl{width:auto}} \ No newline at end of file diff --git a/pkg/btc-wallet/src/index.js b/pkg/btc-wallet/src/index.js new file mode 100644 index 0000000000..64f6a05261 --- /dev/null +++ b/pkg/btc-wallet/src/index.js @@ -0,0 +1,25 @@ +import React from 'react'; +import ReactDOM from 'react-dom'; +import { Root } from './js/components/root.js'; +import { api } from './js/api.js'; +import { subscription } from "./js/subscription.js"; + +import './css/indigo-static.css'; +import './css/fonts.css'; +import './css/custom.css'; + +// rebuild x3 + +window.NETWORK = 'testnet'; // 'bitcoin' + +const channel = new window.channel(); +api.setChannel(window.ship, channel); + + +if (module.hot) { + module.hot.accept() +} + +ReactDOM.render(( + +), document.querySelectorAll("#root")[0]); diff --git a/pkg/btc-wallet/src/js/api.js b/pkg/btc-wallet/src/js/api.js new file mode 100644 index 0000000000..3de39b6ea3 --- /dev/null +++ b/pkg/btc-wallet/src/js/api.js @@ -0,0 +1,52 @@ +import _ from 'lodash'; + +class UrbitApi { + setChannel(ship, channel) { + this.ship = ship; + this.channel = channel; + this.bindPaths = []; + } + + bind(path, method, ship = this.ship, appl = "btc-wallet", success, fail) { + this.bindPaths = _.uniq([...this.bindPaths, path]); + + window.subscriptionId = this.channel.subscribe(ship, appl, path, + (err) => { + fail(err); + }, + (event) => { + success({ + data: event, + from: { + ship, + path + } + }); + }, + (err) => { + fail(err); + }); + } + + btcWalletCommand(data) { + return this.action("btc-wallet", "btc-wallet-command", data); + } + + settingsEvent(data) { + return this.action("settings-store", "settings-event", data); + } + + action(appl, mark, data) { + return new Promise((resolve, reject) => { + this.channel.poke(ship, appl, mark, data, + (json) => { + resolve(json); + }, + (err) => { + reject(err); + }); + }); + } +} +export let api = new UrbitApi(); +window.api = api; diff --git a/pkg/btc-wallet/src/js/components/lib/balance.js b/pkg/btc-wallet/src/js/components/lib/balance.js new file mode 100644 index 0000000000..655b1b5142 --- /dev/null +++ b/pkg/btc-wallet/src/js/components/lib/balance.js @@ -0,0 +1,149 @@ +import React, { Component } from 'react'; +import { + Box, + Icon, + Row, + Text, + Button, + Col, +} from '@tlon/indigo-react'; + +import Send from './send.js' +import CurrencyPicker from './currencyPicker.js' +import { currencyToSats, satsToCurrency } from '../../lib/util.js'; + + +export default class Balance extends Component { + constructor(props) { + super(props); + + this.state = { + sending: false, + copiedButton: false, + copiedString: false, + } + + this.copyAddress = this.copyAddress.bind(this); + } + + copyAddress(arg) { + let address = this.props.state.address; + function listener(e) { + e.clipboardData.setData('text/plain', address); + e.preventDefault(); + } + + document.addEventListener('copy', listener); + document.execCommand('copy'); + document.removeEventListener('copy', listener); + + this.props.api.btcWalletCommand({'gen-new-address': null}); + + if (arg === 'button'){ + this.setState({copiedButton: true}); + setTimeout(() => { this.setState({copiedButton: false}); }, 2000); + } else if (arg === 'string') { + this.setState({copiedString: true}); + setTimeout(() => { this.setState({copiedString: false}); }, 2000); + } + } + + + render() { + const sats = (this.props.state.confirmedBalance || 0); + const unconfirmedSats = this.props.state.unconfirmedBalance; + + const unconfirmedString = unconfirmedSats ? ` (${unconfirmedSats}) ` : ''; + + const denomination = this.props.state.denomination; + const value = satsToCurrency(sats, denomination, this.props.state.currencyRates); + const sendDisabled = (sats === 0); + const addressText = (this.props.state.address === null) ? '' : + this.props.state.address.slice(0, 6) + '...' + + this.props.state.address.slice(-6); + + const conversion = this.props.state.currencyRates[denomination].last; + + return ( + <> + {this.state.sending ? + { + this.setState({sending: false}); + store.handleEvent({data: {psbt: '', fee: 0, error: '', "broadcast-fail": null}}); + }} + /> : + + + Balance + {this.copyAddress('string')}}> + {this.state.copiedString ? "copied" : addressText} + + + + + + {value} + + {`${sats}${unconfirmedString} sats`} + + + + + ) : ( - + - Welcome - - You have been invited to use Landscape, an interface to chat + Welcome + + You have been invited to use Landscape, an interface to chat and interact with communities
Would you like a tour of Landscape?
- + + } > - {children} - - {time && notification && ( - - - - )} - - + {children} + + {notification && ( + + + + )} + + + ); } export function Notification(props: NotificationProps) { - const { notification, associations, archived } = props; - const { read, contents, time } = notification.notification; + const { notification, unread } = props; + const { contents, time } = notification.notification; const wrapperProps = { notification, + read: !unread, time: props.time, - api: props.api, + api: props.api }; - if ("graph" in notification.index) { + if ('graph' in notification.index) { const index = notification.index.graph; const c: GraphNotificationContents = (contents as any).graph; @@ -153,15 +157,14 @@ export function Notification(props: NotificationProps) { api={props.api} index={index} contents={c} - read={read} - archived={archived} + read={!unread} timebox={props.time} time={time} /> ); } - if ("group" in notification.index) { + if ('group' in notification.index) { const index = notification.index.group; const c: GroupNotificationContents = (contents as any).group; return ( @@ -170,9 +173,8 @@ export function Notification(props: NotificationProps) { api={props.api} index={index} contents={c} - read={read} + read={!unread} timebox={props.time} - archived={archived} time={time} /> diff --git a/pkg/interface/src/views/apps/notifications/notifications.tsx b/pkg/interface/src/views/apps/notifications/notifications.tsx index cb916dd684..bfef8998e3 100644 --- a/pkg/interface/src/views/apps/notifications/notifications.tsx +++ b/pkg/interface/src/views/apps/notifications/notifications.tsx @@ -1,21 +1,15 @@ -import React, { useCallback, useState, useRef, ReactElement } from 'react'; -import _ from 'lodash'; -import { Link, Switch, Route } from 'react-router-dom'; +import { Box, Col, Icon, Row, Text } from '@tlon/indigo-react'; +import React, { ReactElement, useCallback, useRef, useState } from 'react'; import Helmet from 'react-helmet'; - -import { Box, Icon, Col, Text, Row } from '@tlon/indigo-react'; - -import { Body } from '~/views/components/Body'; -import { PropFunc } from '~/types/util'; -import Inbox from './inbox'; -import { Dropdown } from '~/views/components/Dropdown'; -import { FormikOnBlur } from '~/views/components/FormikOnBlur'; -import GroupSearch from '~/views/components/GroupSearch'; -import { useTutorialModal } from '~/views/components/useTutorialModal'; +import { Link, Route, Switch } from 'react-router-dom'; +import useGroupState from '~/logic/state/group'; import useHarkState from '~/logic/state/hark'; import useMetadataState from '~/logic/state/metadata'; -import useGroupState from '~/logic/state/group'; -import {StatelessAsyncAction} from '~/views/components/StatelessAsyncAction'; +import { PropFunc } from '~/types/util'; +import { Body } from '~/views/components/Body'; +import { StatelessAsyncAction } from '~/views/components/StatelessAsyncAction'; +import { useTutorialModal } from '~/views/components/useTutorialModal'; +import Inbox from './inbox'; const baseUrl = '/~notifications'; @@ -29,7 +23,7 @@ const HeaderLink = React.forwardRef(( return ( - + ); }); @@ -73,32 +67,33 @@ export default function NotificationsScreen(props: any): ReactElement { - + Notifications Mark All Read - + @@ -106,8 +101,8 @@ export default function NotificationsScreen(props: any): ReactElement { {!view && } + filter={filter.groups} + />} diff --git a/pkg/interface/src/views/apps/permalinks/TranscludedNode.tsx b/pkg/interface/src/views/apps/permalinks/TranscludedNode.tsx index c84630ff78..6d888956aa 100644 --- a/pkg/interface/src/views/apps/permalinks/TranscludedNode.tsx +++ b/pkg/interface/src/views/apps/permalinks/TranscludedNode.tsx @@ -1,18 +1,19 @@ -import React from "react"; -import { Anchor, Icon, Box, Row, Col, Text } from "@tlon/indigo-react"; -import ChatMessage from "../chat/components/ChatMessage"; -import { Association, GraphNode, Post, Group } from "@urbit/api"; -import { useGroupForAssoc } from "~/logic/state/group"; -import { MentionText } from "~/views/components/MentionText"; -import Author from "~/views/components/Author"; -import { NoteContent } from "../publish/components/Note"; -import { PostContent } from "~/views/landscape/components/Home/Post/PostContent"; -import bigInt from "big-integer"; -import { getSnippet } from "~/logic/lib/publish"; -import { NotePreviewContent } from "../publish/components/NotePreview"; -import GlobalApi from "~/logic/api/global"; -import {PermalinkEmbed} from "./embed"; -import {referenceToPermalink} from "~/logic/lib/permalinks"; +/* eslint-disable no-case-declarations */ +/* eslint-disable @typescript-eslint/no-non-null-asserted-optional-chain */ +import { Anchor, Box, Col, Icon, Row, Text } from '@tlon/indigo-react'; +import { Association, GraphConfig, GraphNode, Group, Post, ReferenceContent, TextContent, UrlContent } from '@urbit/api'; +import bigInt from 'big-integer'; +import React from 'react'; +import GlobalApi from '~/logic/api/global'; +import { referenceToPermalink } from '~/logic/lib/permalinks'; +import { getSnippet } from '~/logic/lib/publish'; +import { useGroupForAssoc } from '~/logic/state/group'; +import Author from '~/views/components/Author'; +import { MentionText } from '~/views/components/MentionText'; +import { GraphContent } from '~/views/landscape/components/Graph/GraphContent'; +import ChatMessage from '../chat/components/ChatMessage'; +import { NotePreviewContent } from '../publish/components/NotePreview'; +import { PermalinkEmbed } from './embed'; function TranscludedLinkNode(props: { node: GraphNode; @@ -21,25 +22,64 @@ function TranscludedLinkNode(props: { api: GlobalApi; }) { const { node, api, assoc, transcluded } = props; - const idx = node.post.index.slice(1).split("/"); + const idx = node?.post?.index?.slice(1)?.split('/') ?? []; + + if (typeof node?.post === 'string') { + return ( + + This link has been deleted. + + ); + } switch (idx.length) { case 1: - const [{ text }, link] = node.post.contents; + const [{ text }, link] = node.post.contents as [TextContent, UrlContent | ReferenceContent]; if('reference' in link) { const permalink = referenceToPermalink(link).link; - return - + return ; } - + return ( - - - - {text} - + + + + + + {text} + + ); + case 2: return ( + This comment has been deleted. + + ); + } + const group = useGroupForAssoc(assoc)!; const comment = node.children?.peekLargest()![1]!; return ( - - + @@ -93,27 +152,46 @@ function TranscludedPublishNode(props: { }) { const { node, assoc, transcluded, api } = props; const group = useGroupForAssoc(assoc)!; - const idx = node.post.index.slice(1).split("/"); + + if (typeof node?.post === 'string') { + return ( + + This note has been deleted. + + ); + } + + const idx = node?.post?.index?.slice(1)?.split('/') ?? []; switch (idx.length) { case 1: const post = node.children ?.get(bigInt.one) ?.children?.peekLargest()?.[1]!; return ( - + - - {post.post.contents[0]?.text} + + {(post.post.contents[0] as TextContent)?.text} - + @@ -137,19 +215,39 @@ export function TranscludedPost(props: { post: Post; api: GlobalApi; transcluded: number; + commentsCount?: number; group: Group; }) { - const { transcluded, post, group, api } = props; + const { transcluded, post, group, commentsCount, api } = props; + + if (typeof post === 'string') { + return ( + + This post has been deleted. + + ); + } + return ( - + + {commentsCount >= 1 ? + + + {commentsCount} {commentsCount === 1 ? 'reply' : 'replies'} + + + : null} ); } @@ -168,40 +273,56 @@ export function TranscludedNode(props: { api: GlobalApi; showOurContact?: boolean; }) { - const { node, showOurContact, assoc, transcluded } = props; + const { node, showOurContact, assoc, transcluded, api } = props; const group = useGroupForAssoc(assoc)!; - switch (assoc.metadata.config.graph) { - case "chat": + + if ( + typeof node?.post === 'string' && + (assoc.metadata.config as GraphConfig).graph === 'chat' + ) { + return ( + + This message has been deleted. + + ); + } + + switch ((assoc.metadata.config as GraphConfig).graph) { + case 'chat': return ( ); - case "publish": + case 'publish': return ; - case "link": + case 'link': return ; - case "post": + case 'post': return ( ) ; default: diff --git a/pkg/interface/src/views/apps/permalinks/app.tsx b/pkg/interface/src/views/apps/permalinks/app.tsx index c85778e69b..2d737b0a42 100644 --- a/pkg/interface/src/views/apps/permalinks/app.tsx +++ b/pkg/interface/src/views/apps/permalinks/app.tsx @@ -1,26 +1,21 @@ -import React, { useCallback } from "react"; - -import useMetadataState from "~/logic/state/metadata"; -import useGroupState from "~/logic/state/group"; +import { Association, GraphConfig } from '@urbit/api'; +import React, { useCallback } from 'react'; import { - Switch, - Route, - Redirect, - useLocation, - useParams, -} from "react-router-dom"; -import { makeResource, Association } from "@urbit/api"; -import { getGraphPermalink } from "./graphIndex"; -import { useQuery } from "~/logic/lib/useQuery"; -import useGraphState from "~/logic/state/graph"; + Redirect, Route, Switch +} from 'react-router-dom'; +import { useQuery } from '~/logic/lib/useQuery'; +import useGraphState from '~/logic/state/graph'; +import useGroupState from '~/logic/state/group'; +import useMetadataState from '~/logic/state/metadata'; +import { getGraphPermalink } from './graphIndex'; interface ResourceRouteProps { ship: string; name: string; } -export function PermalinkRoutes(props: {}) { - const groups = useGroupState((s) => s.groups); +export function PermalinkRoutes(props: unknown) { + const groups = useGroupState(s => s.groups); const { query, toQuery } = useQuery(); return ( @@ -52,8 +47,8 @@ export function PermalinkRoutes(props: {}) { function FallbackRoutes(props: { query: URLSearchParams }) { const { query } = props; - if (query.has("ext")) { - const ext = query.get("ext")!; + if (query.has('ext')) { + const ext = query.get('ext')!; const url = `/perma${ext.slice(16)}`; console.log(url); return ; @@ -65,7 +60,7 @@ function FallbackRoutes(props: { query: URLSearchParams }) { function GroupRoutes(props: { group: string; url: string }) { const { group, url } = props; const makePath = (s: string) => url + s; - const associations = useMetadataState((s) => s.associations); + const associations = useMetadataState(s => s.associations); const graphKeys = useGraphState(s => s.graphKeys); const { toQuery } = useQuery(); const groupUrl = `/~landscape${group}`; @@ -73,9 +68,9 @@ function GroupRoutes(props: { group: string; url: string }) { return ( { - const { ship, name } = match.params as ResourceRouteProps; + const { ship, name } = match.params as unknown as ResourceRouteProps; const path = `/ship/${ship}/${name}`; const association = associations.graph[path]; const { url: routeUrl } = match; @@ -87,9 +82,9 @@ function GroupRoutes(props: { group: string; url: string }) { return ; + />; } return null; } @@ -99,7 +94,7 @@ function GroupRoutes(props: { group: string; url: string }) { /> { return ; }} @@ -113,10 +108,10 @@ export function GraphIndexRoutes(props: { url: string; index?: string; }) { - const { index = "", association, url } = props; + const { index = '', association, url } = props; const makePath = (s: string) => url + s; const group = useGroupState( - useCallback((s) => s.groups[association.group], [association]) + useCallback(s => s.groups[association.group], [association]) ); if(!group) { @@ -126,7 +121,7 @@ export function GraphIndexRoutes(props: { return ( { const newIndex = `${index}/${match.params.id}`; const { url: newUrl } = match; @@ -139,7 +134,7 @@ export function GraphIndexRoutes(props: { ); }} /> - + diff --git a/pkg/interface/src/views/apps/permalinks/embed.tsx b/pkg/interface/src/views/apps/permalinks/embed.tsx index 2f7c8e11cc..56c1849444 100644 --- a/pkg/interface/src/views/apps/permalinks/embed.tsx +++ b/pkg/interface/src/views/apps/permalinks/embed.tsx @@ -1,29 +1,59 @@ -import React, { useCallback, useEffect, useState } from "react"; +import { BaseAnchor, Box, Center, Col, Icon, Row, Text } from '@tlon/indigo-react'; +import { Association, GraphNode, resourceFromPath, GraphConfig } from '@urbit/api'; +import React, { useCallback, useEffect, useState } from 'react'; +import _ from 'lodash'; +import { useHistory, useLocation } from 'react-router-dom'; +import GlobalApi from '~/logic/api/global'; import { - parsePermalink, - GraphPermalink as IGraphPermalink, - getPermalinkForGraph, - usePermalinkForGraph, -} from "~/logic/lib/permalinks"; -import { - Action, - Box, - Text, - BaseAnchor, - Row, - Icon, - Col, -} from "@tlon/indigo-react"; -import { GroupLink } from "~/views/components/GroupLink"; -import GlobalApi from "~/logic/api/global"; -import { getModuleIcon } from "~/logic/lib/util"; -import useMetadataState from "~/logic/state/metadata"; -import { Association, resourceFromPath } from "@urbit/api"; -import { Link } from "react-router-dom"; -import useGraphState from "~/logic/state/graph"; -import { GraphNodeContent } from "../notifications/graph"; -import { TranscludedNode } from "./TranscludedNode"; -import {useVirtualResizeProp} from "~/logic/lib/virtualContext"; + getPermalinkForGraph, GraphPermalink as IGraphPermalink, parsePermalink +} from '~/logic/lib/permalinks'; +import { getModuleIcon, GraphModule } from '~/logic/lib/util'; +import { useVirtualResizeProp } from '~/logic/lib/virtualContext'; +import useGraphState from '~/logic/state/graph'; +import useMetadataState from '~/logic/state/metadata'; +import { GroupLink } from '~/views/components/GroupLink'; +import { TranscludedNode } from './TranscludedNode'; + +function Placeholder(type) { + const lines = (type) => { + switch (type) { + case 'publish': + return 5; + case 'post': + return 3; + default: + return 1; + } + }; + return ( + + + + + + {_.times(lines(type), i => ( + + + + ))} + + ); +} function GroupPermalink(props: { group: string; api: GlobalApi }) { const { group, api } = props; @@ -31,9 +61,9 @@ function GroupPermalink(props: { group: string; api: GlobalApi }) { ); @@ -48,70 +78,115 @@ function GraphPermalink( full?: boolean; } ) { - const { full = false, showOurContact, pending, link, graph, group, index, api, transcluded } = props; + const { full = false, showOurContact, pending, graph, group, index, api, transcluded } = props; + const history = useHistory(); + const location = useLocation(); const { ship, name } = resourceFromPath(graph); const node = useGraphState( - useCallback((s) => s.looseNodes?.[`${ship.slice(1)}/${name}`]?.[index], [ + useCallback(s => s.looseNodes?.[`${ship.slice(1)}/${name}`]?.[index] as GraphNode, [ graph, - index, + index ]) ); const [errored, setErrored] = useState(false); + const [loading, setLoading] = useState(false); const association = useMetadataState( - useCallback((s) => s.associations.graph[graph] as Association | null, [ - graph, + useCallback(s => s.associations.graph[graph] as Association | null, [ + graph ]) ); - useVirtualResizeProp(node) + useVirtualResizeProp(Boolean(node)); useEffect(() => { (async () => { if (pending || !index) { return; } try { + setLoading(true); await api.graph.getNode(ship, name, index); + setLoading(false); } catch (e) { console.log(e); + setLoading(false); setErrored(true); } })(); }, [pending, graph, index]); - const showTransclusion = !!(association && node && transcluded < 1); + const showTransclusion = Boolean(association && node && transcluded < 1); const permalink = getPermalinkForGraph(group, graph, index); + const navigate = (e) => { + e.stopPropagation(); + history.push(`/perma${permalink.slice(16)}`); + }; + + const [nodeGroupHost, nodeGroupName] = association?.group.split('/').slice(-2) ?? ['Unknown', 'Unknown']; + const [nodeChannelHost, nodeChannelName] = association?.resource + .split('/') + .slice(-2) ?? ['Unknown', 'Unknown']; + const [ + locChannelName, + locChannelHost, + , + , + , + locGroupName, + locGroupHost + ] = location.pathname.split('/').reverse(); + + const isInSameResource = + locChannelHost === nodeChannelHost && + locChannelName === nodeChannelName && + locGroupName === nodeGroupName && + locGroupHost === nodeGroupHost; + return ( { e.stopPropagation(); }} + borderRadius={2} + cursor="pointer" + onClick={(e) => { + navigate(e); + }} > - {showTransclusion && index && ( - - - + {loading && association && !errored && Placeholder((association.metadata.config as GraphConfig).graph)} + {showTransclusion && index && !loading && ( + )} - {!!association ? ( + {association && !isInSameResource && !loading && ( - ) : ( + )} + {association && isInSameResource && transcluded === 2 && !loading && ( + + )} + {isInSameResource && transcluded !== 2 && !loading && } + {!association && !loading && ( @@ -125,16 +200,13 @@ function PermalinkDetails(props: { icon: any; permalink: string; showTransclusion?: boolean; + showDetails?: boolean; known?: boolean; }) { - const { title, icon, permalink, known, showTransclusion } = props; + const { title, icon, known, showTransclusion } = props; const rowTransclusionStyle = showTransclusion - ? { - borderTop: "1", - borderTopColor: "lightGray", - my: "1", - } - : {}; + ? { p: '12px 12px 11px 11px' } + : { p: '12px' }; return ( - - + +
+ +
+
+ {title}
- - Go to link -
); } @@ -165,6 +236,7 @@ export function PermalinkEmbed(props: { transcluded: number; showOurContact?: boolean; full?: boolean; + pending?: any; }) { const permalink = parsePermalink(props.link); @@ -173,9 +245,9 @@ export function PermalinkEmbed(props: { } switch (permalink.type) { - case "group": + case 'group': return ; - case "graph": + case 'graph': return ( { if (i === 0) { - return {...acc, pathname: `${acc.pathname}/note/${val}` }; + return { ...acc, pathname: `${acc.pathname}/note/${val}` }; } else if (i === 1 && val === '2') { isComment = true; return acc; @@ -69,16 +68,16 @@ function getLinkPermalink( assoc: Association, index: string ) { - const idx = index.split("/").slice(1); + const idx = index.split('/').slice(1); const base = `${groupPath}/resource/link${assoc.resource}`; const res = _.reduce( idx, (acc, val, i) => { console.log(acc); if (i === 0) { - return {...acc, pathname: `${acc.pathname}/index/${val}` }; + return { ...acc, pathname: `${acc.pathname}/index/${val}` }; } else if (i === 1) { - return {...acc, search: `?selected=${val}` }; + return { ...acc, search: `?selected=${val}` }; } return acc; }, @@ -92,7 +91,7 @@ function getChatPermalink( assoc: Association, index: string ) { - const idx = index.split("/").slice(1); + const idx = index.split('/').slice(1); if (idx.length === 0) { return `${groupPath}/resource/chat${assoc.resource}`; } diff --git a/pkg/interface/src/views/apps/profile/components/EditProfile.tsx b/pkg/interface/src/views/apps/profile/components/EditProfile.tsx index d053007d29..4b591a64c6 100644 --- a/pkg/interface/src/views/apps/profile/components/EditProfile.tsx +++ b/pkg/interface/src/views/apps/profile/components/EditProfile.tsx @@ -1,32 +1,26 @@ -import React, { ReactElement, useRef, useState } from 'react'; -import * as Yup from 'yup'; -import _ from 'lodash'; -import { Formik } from 'formik'; -import { useHistory } from 'react-router-dom'; - import { - ManagedForm as Form, - ManagedTextInputField as Input, - ManagedCheckboxField as Checkbox, - Col, - Text, - Row, - Button -} from '@tlon/indigo-react'; + Button, Col, ManagedCheckboxField as Checkbox, ManagedForm as Form, + ManagedTextInputField as Input, + Row, Text +} from '@tlon/indigo-react'; +import { Formik } from 'formik'; +import _ from 'lodash'; +import React, { ReactElement, useState } from 'react'; +import { useHistory } from 'react-router-dom'; +import * as Yup from 'yup'; +import { resourceFromPath } from '~/logic/lib/group'; import { uxToHex } from '~/logic/lib/util'; +import useContactState from '~/logic/state/contact'; +import { MarkdownField } from '~/views/apps/publish/components/MarkdownField'; import { AsyncButton } from '~/views/components/AsyncButton'; import { ColorInput } from '~/views/components/ColorInput'; -import { ImageInput } from '~/views/components/ImageInput'; -import { MarkdownField } from '~/views/apps/publish/components/MarkdownField'; -import { resourceFromPath } from '~/logic/lib/group'; import GroupSearch from '~/views/components/GroupSearch'; -import useContactState from '~/logic/state/contact'; +import { ImageInput } from '~/views/components/ImageInput'; import { - ProfileHeader, - ProfileControls, - ProfileStatus, - ProfileImages + ProfileControls, ProfileHeader, + + ProfileImages, ProfileStatus } from './Profile'; const formSchema = Yup.object({ @@ -67,7 +61,7 @@ export function ProfileHeaderImageEdit(props: any): ReactElement { ) : ( - { diff --git a/pkg/interface/src/views/apps/profile/components/Profile.tsx b/pkg/interface/src/views/apps/profile/components/Profile.tsx index 3d35126d3c..f98794e580 100644 --- a/pkg/interface/src/views/apps/profile/components/Profile.tsx +++ b/pkg/interface/src/views/apps/profile/components/Profile.tsx @@ -1,22 +1,22 @@ -import React, { ReactElement, useEffect, useRef, useState } from 'react'; +import { BaseImage, Box, Center, Row, Text } from '@tlon/indigo-react'; +import React, { ReactElement, useEffect, useRef } from 'react'; import { useHistory } from 'react-router-dom'; -import { Center, Box, Row, BaseImage, Text } from '@tlon/indigo-react'; -import RichText from '~/views/components/RichText'; -import useSettingsState, { selectCalmState } from '~/logic/state/settings'; import { Sigil } from '~/logic/lib/sigil'; -import { ViewProfile } from './ViewProfile'; -import { EditProfile } from './EditProfile'; -import { SetStatusBarModal } from '~/views/components/SetStatusBarModal'; import { uxToHex } from '~/logic/lib/util'; -import { useTutorialModal } from '~/views/components/useTutorialModal'; import useContactState from '~/logic/state/contact'; +import useSettingsState, { selectCalmState } from '~/logic/state/settings'; +import RichText from '~/views/components/RichText'; +import { SetStatusBarModal } from '~/views/components/SetStatusBarModal'; +import { useTutorialModal } from '~/views/components/useTutorialModal'; +import { EditProfile } from './EditProfile'; +import { ViewProfile } from './ViewProfile'; export function ProfileHeader(props: any): ReactElement { return ( @@ -27,10 +27,10 @@ export function ProfileHeader(props: any): ReactElement { export function ProfileImages(props: any): ReactElement { const { hideAvatars } = useSettingsState(selectCalmState); - const { contact, hideCover, ship } = { ...props }; + const { contact, hideCover, ship } = props; const hexColor = contact?.color ? `#${uxToHex(contact.color)}` : '#000000'; - const anchorRef = useRef(null) + const anchorRef = useRef(null); useTutorialModal('profile', ship === `~${window.ship}`, anchorRef); @@ -76,7 +76,7 @@ export function ProfileImages(props: any): ReactElement { + {props.children} ); } export function ProfileStatus(props: any): ReactElement { - const { contact } = { ...props }; + const { contact } = props; return ( {ship === `~${window.ship}` ? ( <> { @@ -138,14 +138,15 @@ export function ProfileActions(props: any): ReactElement { + display={['none','inline']} + > {isPublic ? ' Public' : ' Private'} Profile history.push(`/~landscape/dm/${ship.substring(1)}`)} + onClick={() => history.push(`/~landscape/messages/dm/${ship}`)} > Message diff --git a/pkg/interface/src/views/apps/profile/components/SetStatus.tsx b/pkg/interface/src/views/apps/profile/components/SetStatus.tsx index a19ca2253f..af66a6395b 100644 --- a/pkg/interface/src/views/apps/profile/components/SetStatus.tsx +++ b/pkg/interface/src/views/apps/profile/components/SetStatus.tsx @@ -1,16 +1,14 @@ -import React, { - useState, - useCallback, - useEffect, - ChangeEvent, - useRef -} from 'react'; import { - Row, - Text, - Button, + Button, Row, + StatelessTextInput as Input } from '@tlon/indigo-react'; +import React, { + ChangeEvent, useCallback, + useEffect, + + useRef, useState +} from 'react'; export function SetStatus(props: any) { const { contact, ship, api, callback } = props; @@ -41,7 +39,7 @@ export function SetStatus(props: any) { ref={inputRef} onChange={onStatusChange} value={_status} - autocomplete='off' + autoComplete='off' width='75%' mr={2} onKeyPress={(evt) => { diff --git a/pkg/interface/src/views/apps/profile/components/ViewProfile.tsx b/pkg/interface/src/views/apps/profile/components/ViewProfile.tsx index 9ae43ced51..8afcfc3194 100644 --- a/pkg/interface/src/views/apps/profile/components/ViewProfile.tsx +++ b/pkg/interface/src/views/apps/profile/components/ViewProfile.tsx @@ -1,21 +1,15 @@ +import { Box, Center, Col, Row, Text } from '@tlon/indigo-react'; import React, { ReactElement } from 'react'; -import _ from 'lodash'; -import { useHistory } from 'react-router-dom'; -import { Center, Box, Text, Row, Col } from '@tlon/indigo-react'; -import RichText from '~/views/components/RichText'; -import useSettingsState, { selectCalmState } from '~/logic/state/settings'; -import { Sigil } from '~/logic/lib/sigil'; -import { GroupLink } from '~/views/components/GroupLink'; import { lengthOrder } from '~/logic/lib/util'; -import useLocalState from '~/logic/state/local'; -import { - ProfileHeader, - ProfileControls, - ProfileActions, - ProfileStatus, - ProfileImages -} from './Profile'; import useContactState from '~/logic/state/contact'; +import useSettingsState, { selectCalmState } from '~/logic/state/settings'; +import { GroupLink } from '~/views/components/GroupLink'; +import RichText from '~/views/components/RichText'; +import { + ProfileActions, ProfileControls, ProfileHeader, + + ProfileImages, ProfileStatus +} from './Profile'; export function ViewProfile(props: any): ReactElement { const { hideNicknames } = useSettingsState(selectCalmState); @@ -51,20 +45,21 @@ export function ViewProfile(props: any): ReactElement { - +
- + {contact?.bio ? contact.bio : ''}
{(contact?.groups || []).length > 0 && ( - + Pinned Groups - {contact?.groups.slice().sort(lengthOrder).map((g) => ( + {contact?.groups.slice().sort(lengthOrder).map((g, i) => ( {}} /> diff --git a/pkg/interface/src/views/apps/profile/profile.tsx b/pkg/interface/src/views/apps/profile/profile.tsx index 0edf0cd2e3..3e1faf914b 100644 --- a/pkg/interface/src/views/apps/profile/profile.tsx +++ b/pkg/interface/src/views/apps/profile/profile.tsx @@ -1,12 +1,10 @@ -import React from 'react'; -import { Route, Link } from 'react-router-dom'; -import Helmet from 'react-helmet'; - import { Box } from '@tlon/indigo-react'; - -import { Profile } from './components/Profile'; +import React from 'react'; +import Helmet from 'react-helmet'; +import { Route } from 'react-router-dom'; import useContactState from '~/logic/state/contact'; import useHarkState from '~/logic/state/hark'; +import { Profile } from './components/Profile'; export default function ProfileScreen(props: any) { const contacts = useContactState(state => state.contacts); @@ -38,7 +36,7 @@ export default function ProfileScreen(props: any) { border={1} borderColor='lightGray' overflowY='auto' - flexGrow + flexGrow={1} > { const search = new URLSearchParams(location.search); if(search.has('selected') || search.has('edit') || !scrollRef.current) { return; } scrollRef.current.scrollTop = 0; - - - - }, [location]) + }, [location]); return ( diff --git a/pkg/interface/src/views/apps/publish/components/EditPost.tsx b/pkg/interface/src/views/apps/publish/components/EditPost.tsx index dd73baa5b0..b7585fd110 100644 --- a/pkg/interface/src/views/apps/publish/components/EditPost.tsx +++ b/pkg/interface/src/views/apps/publish/components/EditPost.tsx @@ -1,18 +1,18 @@ -import React, { ReactElement } from 'react'; -import _ from 'lodash'; -import { FormikHelpers } from 'formik'; -import { RouteComponentProps, useLocation } from 'react-router-dom'; - import { GraphNode } from '@urbit/api'; - -import { PostFormSchema, PostForm } from './NoteForm'; +import bigInt from 'big-integer'; +import { FormikHelpers } from 'formik'; +import _ from 'lodash'; +import React, { ReactElement } from 'react'; +import { RouteComponentProps, useLocation } from 'react-router-dom'; import GlobalApi from '~/logic/api/global'; -import { getLatestRevision, editPost } from '~/logic/lib/publish'; +import { referenceToPermalink } from '~/logic/lib/permalinks'; +import { editPost, getLatestRevision } from '~/logic/lib/publish'; import { useWaitForProps } from '~/logic/lib/useWaitForProps'; +import { PostForm, PostFormSchema } from './NoteForm'; interface EditPostProps { ship: string; - noteId: number; + noteId: bigInt.BigInteger; note: GraphNode; api: GlobalApi; book: string; @@ -23,10 +23,27 @@ export function EditPost(props: EditPostProps & RouteComponentProps): ReactEleme const [revNum, title, body] = getLatestRevision(note); const location = useLocation(); + let editContent = null; + editContent = body.reduce((val, curr) => { + if ('text' in curr) { + val = val + curr.text; + } else if ('mention' in curr) { + val = val + `~${curr.mention}`; + } else if ('url' in curr) { + val = val + curr.url; + } else if ('code' in curr) { + val = val + curr.code.expression; + } else if ('reference' in curr) { + val = `${val}${referenceToPermalink(curr).link}`; + } + + return val; + }, ''); + const waiter = useWaitForProps(props); const initial: PostFormSchema = { title, - body + body: editContent }; const onSubmit = async ( diff --git a/pkg/interface/src/views/apps/publish/components/MarkdownEditor.tsx b/pkg/interface/src/views/apps/publish/components/MarkdownEditor.tsx index 47d69f8f6e..57dc260e93 100644 --- a/pkg/interface/src/views/apps/publish/components/MarkdownEditor.tsx +++ b/pkg/interface/src/views/apps/publish/components/MarkdownEditor.tsx @@ -1,23 +1,18 @@ -import React, { createRef, useCallback, useRef } from 'react'; -import { IUnControlledCodeMirror, UnControlled as CodeEditor } from 'react-codemirror2'; -import { useFormikContext } from 'formik'; -import { Prompt } from 'react-router-dom'; +import { Box } from '@tlon/indigo-react'; import { Editor } from 'codemirror'; - -import { MOBILE_BROWSER_REGEX, usePreventWindowUnload } from '~/logic/lib/util'; -import { PropFunc } from '~/types/util'; -import CodeMirror from 'codemirror'; - -import 'codemirror/mode/markdown/markdown'; import 'codemirror/addon/display/placeholder'; import 'codemirror/addon/edit/continuelist'; - import 'codemirror/lib/codemirror.css'; -import { Box } from '@tlon/indigo-react'; +import 'codemirror/mode/markdown/markdown'; +import { useFormikContext } from 'formik'; +import React, { useCallback, useRef } from 'react'; +import { UnControlled as CodeEditor } from 'react-codemirror2'; +import { Prompt } from 'react-router-dom'; import { useFileDrag } from '~/logic/lib/useDrag'; -import SubmitDragger from '~/views/components/SubmitDragger'; import useStorage from '~/logic/lib/useStorage'; -import { StorageState } from '~/types'; +import { usePreventWindowUnload } from '~/logic/lib/util'; +import { PropFunc } from '~/types/util'; +import SubmitDragger from '~/views/components/SubmitDragger'; const MARKDOWN_CONFIG = { name: 'markdown' @@ -98,7 +93,6 @@ export function MarkdownEditor( return ( bind.onDragLeave(e)} - onDragOver={(editor, e) => bind.onDragOver(e)} - onDrop={(editor, e) => bind.onDrop(e)} - onDragEnter={(editor, e) => bind.onDragEnter(e)} + onDragLeave={(editor, e: DragEvent) => bind.onDragLeave(e)} + onDragOver={(editor, e: DragEvent) => bind.onDragOver(e)} + onDrop={(editor, e: DragEvent) => bind.onDrop(e)} + onDragEnter={(editor, e: DragEvent) => bind.onDragEnter(e)} /> {dragging && } diff --git a/pkg/interface/src/views/apps/publish/components/MarkdownField.tsx b/pkg/interface/src/views/apps/publish/components/MarkdownField.tsx index aa67e27dec..f779b1c6a4 100644 --- a/pkg/interface/src/views/apps/publish/components/MarkdownField.tsx +++ b/pkg/interface/src/views/apps/publish/components/MarkdownField.tsx @@ -1,7 +1,7 @@ -import React, { useCallback } from 'react'; -import _ from 'lodash'; import { Box, ErrorLabel } from '@tlon/indigo-react'; import { useField } from 'formik'; +import _ from 'lodash'; +import React, { useCallback } from 'react'; import { MarkdownEditor } from './MarkdownEditor'; export const MarkdownField = ({ @@ -36,7 +36,7 @@ export const MarkdownField = ({ value={value} onChange={setValue} /> - + {error} diff --git a/pkg/interface/src/views/apps/publish/components/Note.tsx b/pkg/interface/src/views/apps/publish/components/Note.tsx index 3383b60da5..f609c63af1 100644 --- a/pkg/interface/src/views/apps/publish/components/Note.tsx +++ b/pkg/interface/src/views/apps/publish/components/Note.tsx @@ -1,20 +1,20 @@ -import React, { useState, useEffect } from 'react'; -import { Box, Text, Col, Anchor, Row, Action } from '@tlon/indigo-react'; -import ReactMarkdown from 'react-markdown'; +import { Action, Anchor, Box, Col, Row, Text } from '@tlon/indigo-react'; +import { Association, Graph, GraphNode, Group } from '@urbit/api'; import bigInt from 'big-integer'; - +import React, { useEffect, useState } from 'react'; import { Link, RouteComponentProps } from 'react-router-dom'; -import { Spinner } from '~/views/components/Spinner'; -import { Comments } from '~/views/components/Comments'; -import { NoteNavigation } from './NoteNavigation'; import GlobalApi from '~/logic/api/global'; -import { getLatestRevision, getComments } from '~/logic/lib/publish'; import { roleForShip } from '~/logic/lib/group'; -import Author from '~/views/components/Author'; -import { Contacts, GraphNode, Graph, Association, Unreads, Group } from '@urbit/api'; -import {useCopy} from '~/logic/lib/useCopy'; import { getPermalinkForGraph } from '~/logic/lib/permalinks'; -import {useQuery} from '~/logic/lib/useQuery'; +import { getComments, getLatestRevision } from '~/logic/lib/publish'; +import { useCopy } from '~/logic/lib/useCopy'; +import { useQuery } from '~/logic/lib/useQuery'; +import Author from '~/views/components/Author'; +import { Comments } from '~/views/components/Comments'; +import { Spinner } from '~/views/components/Spinner'; +import { GraphContent } from '~/views/landscape/components/Graph/GraphContent'; +import { NoteNavigation } from './NoteNavigation'; +import { Redirect } from 'react-router-dom'; interface NoteProps { ship: string; @@ -28,22 +28,12 @@ interface NoteProps { group: Group; } -const renderers = { - link: ({ href, children }) => { - return ( - {children} - ) - } -}; - -export function NoteContent({ body }) { +export function NoteContent({ post, api }) { return ( - - + ); - } export function Note(props: NoteProps & RouteComponentProps) { @@ -54,10 +44,18 @@ export function Note(props: NoteProps & RouteComponentProps) { const deletePost = async () => { setDeleting(true); const indices = [note.post.index]; - await api.graph.removeNodes(ship, book, indices); + await api.graph.removePosts(ship, book, indices); props.history.push(rootUrl); }; + if (typeof note.post === 'string' || !note.post) { + return ( + + This note has been deleted. + + ); + } + const { query } = useQuery(); const comments = getComments(note); const [revNum, title, body, post] = getLatestRevision(note); @@ -68,23 +66,23 @@ export function Note(props: NoteProps & RouteComponentProps) { api.hark.markEachAsRead(props.association, '/',`/${index[1]}/1/1`, 'note', 'publish'); }, [props.association, props.note]); - let adminLinks: JSX.Element[] = []; + const adminLinks: JSX.Element[] = []; const ourRole = roleForShip(group, window.ship); if (window.ship === note?.post?.author) { adminLinks.push( - Update + Update - ) - }; + ); + } - if (window.ship === note?.post?.author || ourRole === "admin") { + if (window.ship === note?.post?.author || ourRole === 'admin') { adminLinks.push( - + Delete - ) - }; + ); + } const permalink = getPermalinkForGraph( association.group, @@ -94,7 +92,6 @@ export function Note(props: NoteProps & RouteComponentProps) { const { doCopy, copyDisplay } = useCopy(permalink, 'Copy Link'); - return ( - + {copyDisplay} {adminLinks}
- +
- + @@ -50,12 +48,12 @@ function getAdjacentId( return target?.[0] || null; } -function makeNoteUrl(noteId: number) { +function makeNoteUrl(noteId: BigInteger) { return noteId.toString(); } interface NoteNavigationProps { - noteId: number; + noteId: BigInteger; notebook: Graph; baseUrl: string; } diff --git a/pkg/interface/src/views/apps/publish/components/NotePreview.tsx b/pkg/interface/src/views/apps/publish/components/NotePreview.tsx index 24f4c12079..3f9ecc0954 100644 --- a/pkg/interface/src/views/apps/publish/components/NotePreview.tsx +++ b/pkg/interface/src/views/apps/publish/components/NotePreview.tsx @@ -1,19 +1,17 @@ +import { Box, Col, Icon, Image, Row, Text } from '@tlon/indigo-react'; +import { Group } from '@urbit/api'; +import { GraphNode } from '@urbit/api/graph'; import React from 'react'; +import ReactMarkdown from 'react-markdown'; import { Link } from 'react-router-dom'; import styled from 'styled-components'; -import { Col, Row, Box, Text, Icon, Image } from '@tlon/indigo-react'; - -import Author from '~/views/components/Author'; -import { GraphNode } from '@urbit/api/graph'; -import { Contacts, Group } from '@urbit/api'; import { - getComments, - getLatestRevision, - getSnippet + getComments, + getLatestRevision, + getSnippet } from '~/logic/lib/publish'; -import { Unreads } from '@urbit/api'; -import ReactMarkdown from 'react-markdown'; import useHarkState from '~/logic/state/hark'; +import Author from '~/views/components/Author'; interface NotePreviewProps { host: string; @@ -37,10 +35,15 @@ export function NotePreviewContent({ snippet }) { - + + ), + paragraph: props => ( + + {props.children} + ) }} source={snippet} @@ -51,8 +54,12 @@ export function NotePreviewContent({ snippet }) { export function NotePreview(props: NotePreviewProps) { const { node, group } = props; const { post } = node; - if (!post) { - return null; + if (!post || typeof post === 'string') { + return ( + + This note has been deleted. + + ); } const numComments = getComments(node).children.size; @@ -62,6 +69,7 @@ export function NotePreview(props: NotePreviewProps) { const [rev, title, body, content] = getLatestRevision(node); const appPath = `/ship/${props.host}/${props.book}`; const unreads = useHarkState(state => state.unreads); + // @ts-ignore hark will have to choose between sets and numbers const isUnread = unreads.graph?.[appPath]?.['/']?.unreads?.has(`/${noteId}/1/1`); const snippet = getSnippet(body); @@ -74,7 +82,8 @@ export function NotePreview(props: NotePreviewProps) { + style={ { cursor: cursorStyle } } + > {title} - + - + + {association.metadata?.title} @@ -52,7 +49,7 @@ export function Notebook(props: NotebookProps & RouteComponentProps): ReactEleme - + state.contacts); return ( {Array.from(props.graph || []).map( @@ -25,7 +24,6 @@ export function NotebookPosts(props: NotebookPostsProps) { key={date.toString()} host={props.host} book={props.book} - contact={contacts[`~${node.post.author}`]} node={node} baseUrl={props.baseUrl} group={props.group} diff --git a/pkg/interface/src/views/apps/publish/components/NotebookRoutes.tsx b/pkg/interface/src/views/apps/publish/components/NotebookRoutes.tsx index 56c6b3b23f..ab28a3c6b1 100644 --- a/pkg/interface/src/views/apps/publish/components/NotebookRoutes.tsx +++ b/pkg/interface/src/views/apps/publish/components/NotebookRoutes.tsx @@ -1,24 +1,16 @@ -import React, { useEffect } from 'react'; -import { RouteComponentProps, Route, Switch } from 'react-router-dom'; -import GlobalApi from '~/logic/api/global'; -import { - Association, - Associations, - Graphs, - Groups, - Contacts, - Rolodex, - Unreads, -} from '@urbit/api'; import { Center, LoadingSpinner } from '@tlon/indigo-react'; -import { StorageState } from '~/types'; +import { + Association +} from '@urbit/api'; import bigInt from 'big-integer'; - -import Notebook from './Notebook'; -import NewPost from './new-post'; -import { NoteRoutes } from './NoteRoutes'; +import React, { useEffect } from 'react'; +import { Route, RouteComponentProps, Switch } from 'react-router-dom'; +import GlobalApi from '~/logic/api/global'; import useGraphState from '~/logic/state/graph'; import useGroupState from '~/logic/state/group'; +import NewPost from './new-post'; +import Notebook from './Notebook'; +import { NoteRoutes } from './NoteRoutes'; interface NotebookRoutesProps { api: GlobalApi; diff --git a/pkg/interface/src/views/apps/publish/components/Writers.js b/pkg/interface/src/views/apps/publish/components/Writers.tsx similarity index 65% rename from pkg/interface/src/views/apps/publish/components/Writers.js rename to pkg/interface/src/views/apps/publish/components/Writers.tsx index d06e1a78bd..affb8e8ea9 100644 --- a/pkg/interface/src/views/apps/publish/components/Writers.js +++ b/pkg/interface/src/views/apps/publish/components/Writers.tsx @@ -1,13 +1,20 @@ -import React, { Component } from 'react'; import { Box, Text } from '@tlon/indigo-react'; -import { ShipSearch } from '~/views/components/ShipSearch'; -import { Formik, Form } from 'formik'; +import { Association, Group } from '@urbit/api'; +import { Form, Formik } from 'formik'; +import React, { ReactElement } from 'react'; +import GlobalApi from '~/logic/api/global'; import { resourceFromPath } from '~/logic/lib/group'; import { AsyncButton } from '~/views/components/AsyncButton'; +import { ShipSearch } from '~/views/components/ShipSearch'; -export class Writers extends Component { - render() { - const { association, groups, api } = this.props; +interface WritersProps { + api: GlobalApi; + association: Association; + groups: Group[]; +} + +export const Writers = (props: WritersProps): ReactElement => { + const { association, groups, api } = props; const resource = resourceFromPath(association?.group); @@ -16,7 +23,7 @@ export class Writers extends Component { const ships = values.ships.map(e => `~${e}`); await api.groups.addTag( resource, - { app: 'graph', resource: association.resource, tag: `writers` }, + { app: 'graph', resource: association.resource, tag: 'writers' }, ships ); actions.resetForm(); @@ -28,11 +35,10 @@ export class Writers extends Component { }; const writers = Array.from(groups?.[association?.group]?.tags.graph[association.resource]?.writers || []).map(s => `~${s}`).join(', '); - return ( Writers - Add additional writers to this notebook + Add additional writers to this notebook - + Submit {writers.length > 0 ? <> - Current writers: - {writers} - : - + Current writers: + {writers} + : + All group members can write to this channel } ); - } -} +}; export default Writers; diff --git a/pkg/interface/src/views/apps/publish/components/new-post.tsx b/pkg/interface/src/views/apps/publish/components/new-post.tsx index 0334f04954..7a666094a0 100644 --- a/pkg/interface/src/views/apps/publish/components/new-post.tsx +++ b/pkg/interface/src/views/apps/publish/components/new-post.tsx @@ -1,14 +1,12 @@ -import React from 'react'; -import { FormikHelpers } from 'formik'; -import GlobalApi from '~/logic/api/global'; -import { useWaitForProps } from '~/logic/lib/useWaitForProps'; -import { RouteComponentProps } from 'react-router-dom'; -import { PostForm, PostFormSchema } from './NoteForm'; -import { createPost } from '~/logic/api/graph'; -import { Graph } from '@urbit/api/graph'; import { Association } from '@urbit/api'; -import { StorageState } from '~/types'; +import { Graph } from '@urbit/api/graph'; +import { FormikHelpers } from 'formik'; +import React from 'react'; +import { RouteComponentProps } from 'react-router-dom'; +import GlobalApi from '~/logic/api/global'; import { newPost } from '~/logic/lib/publish'; +import { useWaitForProps } from '~/logic/lib/useWaitForProps'; +import { PostForm, PostFormSchema } from './NoteForm'; interface NewPostProps { api: GlobalApi; diff --git a/pkg/interface/src/views/apps/publish/css/custom.css b/pkg/interface/src/views/apps/publish/css/custom.css index 4b22706658..5c11ec7095 100644 --- a/pkg/interface/src/views/apps/publish/css/custom.css +++ b/pkg/interface/src/views/apps/publish/css/custom.css @@ -137,46 +137,6 @@ display: none; } -.md h1, .md h2, .md h3, .md h4, .md h5, .md p, .md a, .md ul, .md ol, .md blockquote,.md code,.md pre { - font-size: 14px; - margin-bottom: 16px; -} - -.md ul ul { - margin-bottom: 0px; -} - -.md h2, .md h3, .md h4, .md h5, .md p, .md a, .md ul { - font-weight: 400; -} - -.md h1 { - font-weight: 600; -} - -.md h2, .md h3, .md h4, .md h5 { - color:var(--gray); -} - -.md { - line-height: 1.5; -} -.md code, .md pre { - font-family: "Source Code Pro", mono; - white-space: pre-wrap; -} -.md ul>li, .md ol>li { - line-height: 1.5; -} -.md a { - border-bottom-style: solid; - border-bottom-width: 1px; -} - -.md img { - margin-bottom: 8px; -} - @media all and (prefers-color-scheme: dark) { .options.open { background-color: #4d4d4d; diff --git a/pkg/interface/src/views/apps/settings/components/lib/BackButton.tsx b/pkg/interface/src/views/apps/settings/components/lib/BackButton.tsx index 1fdcc633cf..6218bcd58d 100644 --- a/pkg/interface/src/views/apps/settings/components/lib/BackButton.tsx +++ b/pkg/interface/src/views/apps/settings/components/lib/BackButton.tsx @@ -1,13 +1,13 @@ +import { Text } from '@tlon/indigo-react'; import React from 'react'; import { Link } from 'react-router-dom'; -import { Text } from '@tlon/indigo-react'; export function BackButton(props: {}) { return ( Landscape Background - - Set an image background + + Set an image background - - Set a hex-based background - + + Set a hex-based background + + id="none" + /> ); } diff --git a/pkg/interface/src/views/apps/settings/components/lib/BucketList.tsx b/pkg/interface/src/views/apps/settings/components/lib/BucketList.tsx index 25afa1dff3..e1bd736ae7 100644 --- a/pkg/interface/src/views/apps/settings/components/lib/BucketList.tsx +++ b/pkg/interface/src/views/apps/settings/components/lib/BucketList.tsx @@ -1,19 +1,16 @@ -import React, { ReactElement, useCallback, useState } from "react"; -import { Formik, FormikHelpers } from 'formik'; - import { - ManagedTextInputField as Input, - ManagedForm as Form, - Box, - Button, - Text, - Menu, - MenuButton, - MenuList, - MenuItem, - Row, -} from "@tlon/indigo-react"; + Box, + Button, ManagedForm as Form, ManagedTextInputField as Input, + Menu, + MenuButton, + + MenuItem, MenuList, + + Row, Text +} from '@tlon/indigo-react'; +import { Formik, FormikHelpers } from 'formik'; +import React, { ReactElement, useCallback, useState } from 'react'; import GlobalApi from '~/logic/api/global'; export function BucketList({ @@ -32,7 +29,7 @@ export function BucketList({ const onSubmit = useCallback( (values: { newBucket: string }, actions: FormikHelpers) => { api.s3.addBucket(values.newBucket); - actions.resetForm({ values: { newBucket: "" } }); + actions.resetForm({ values: { newBucket: '' } }); }, [api] ); @@ -98,22 +95,22 @@ export function BucketList({ {adding && ( )} - + diff --git a/pkg/interface/src/views/apps/settings/components/lib/CalmPref.tsx b/pkg/interface/src/views/apps/settings/components/lib/CalmPref.tsx index 2047f73b2c..d1d8bb9cbc 100644 --- a/pkg/interface/src/views/apps/settings/components/lib/CalmPref.tsx +++ b/pkg/interface/src/views/apps/settings/components/lib/CalmPref.tsx @@ -1,17 +1,16 @@ -import React, {useCallback} from "react"; import { - Box, - ManagedToggleSwitchField as Toggle, - Button, - Col, - Text, -} from "@tlon/indigo-react"; -import { Formik, Form, FormikHelpers } from "formik"; -import * as Yup from "yup"; -import { BackButton } from "./BackButton"; -import useSettingsState, {selectSettingsState} from "~/logic/state/settings"; -import GlobalApi from "~/logic/api/global"; -import {AsyncButton} from "~/views/components/AsyncButton"; + Col, ManagedToggleSwitchField as Toggle, + + Text +} from '@tlon/indigo-react'; +import { Form, Formik, FormikHelpers } from 'formik'; +import React, { useCallback } from 'react'; +import GlobalApi from '~/logic/api/global'; +import useSettingsState, { selectSettingsState, SettingsState } from '~/logic/state/settings'; +import { AsyncButton } from '~/views/components/AsyncButton'; +import { BackButton } from './BackButton'; +import _ from 'lodash'; +import {FormikOnBlur} from '~/views/components/FormikOnBlur'; interface FormSchema { hideAvatars: boolean; @@ -25,62 +24,43 @@ interface FormSchema { videoShown: boolean; } -const settingsSel = selectSettingsState(["calm", "remoteContentPolicy"]); +const settingsSel = (s: SettingsState): FormSchema => ({ + hideAvatars: s.calm.hideAvatars, + hideNicknames: s.calm.hideAvatars, + hideUnreads: s.calm.hideUnreads, + hideGroups: s.calm.hideGroups, + hideUtilities: s.calm.hideUtilities, + imageShown: !s.remoteContentPolicy.imageShown, + videoShown: !s.remoteContentPolicy.videoShown, + oembedShown: !s.remoteContentPolicy.oembedShown, + audioShown: !s.remoteContentPolicy.audioShown +}); + export function CalmPrefs(props: { api: GlobalApi; }) { const { api } = props; - const { - calm: { - hideAvatars, - hideNicknames, - hideUnreads, - hideGroups, - hideUtilities - }, - remoteContentPolicy: { - imageShown, - videoShown, - oembedShown, - audioShown, - } - } = useSettingsState(settingsSel); - - - const initialValues: FormSchema = { - hideAvatars, - hideNicknames, - hideUnreads, - hideGroups, - hideUtilities, - imageShown: !imageShown, - videoShown: !videoShown, - oembedShown: !oembedShown, - audioShown: !audioShown - }; + const initialValues = useSettingsState(settingsSel); const onSubmit = useCallback(async (v: FormSchema, actions: FormikHelpers) => { - await Promise.all([ - api.settings.putEntry('calm', 'hideAvatars', v.hideAvatars), - api.settings.putEntry('calm', 'hideNicknames', v.hideNicknames), - api.settings.putEntry('calm', 'hideUnreads', v.hideUnreads), - api.settings.putEntry('calm', 'hideGroups', v.hideGroups), - api.settings.putEntry('calm', 'hideUtilities', v.hideUtilities), - api.settings.putEntry('remoteContentPolicy', 'imageShown', !v.imageShown), - api.settings.putEntry('remoteContentPolicy', 'videoShown', !v.videoShown), - api.settings.putEntry('remoteContentPolicy', 'audioShown', !v.audioShown), - api.settings.putEntry('remoteContentPolicy', 'oembedShown', !v.oembedShown), - ]); + let promises: Promise[] = []; + _.forEach(v, (bool, key) => { + const bucket = ['imageShown', 'videoShown', 'audioShown', 'oembedShown'].includes(key) ? 'remoteContentPolicy' : 'calm'; + if(initialValues[key] !== bool) { + promises.push(api.settings.putEntry(bucket, key, bool)); + } + }) + await Promise.all(promises); actions.setStatus({ success: null }); }, [api]); return ( - +
- - - + + + CalmEngine @@ -136,12 +116,8 @@ export function CalmPrefs(props: { id="oembedShown" caption="Embedded content may contain scripts that can track you" /> - - - Save - -
+ ); } diff --git a/pkg/interface/src/views/apps/settings/components/lib/Debug.tsx b/pkg/interface/src/views/apps/settings/components/lib/Debug.tsx index c667ee2bcd..fa4ed43f1f 100644 --- a/pkg/interface/src/views/apps/settings/components/lib/Debug.tsx +++ b/pkg/interface/src/views/apps/settings/components/lib/Debug.tsx @@ -1,22 +1,22 @@ -import { BaseInput, Box, Col, Text } from "@tlon/indigo-react"; -import _ from "lodash"; -import React, { useCallback, useState } from "react"; -import { UseStore } from "zustand"; -import { BaseState } from "~/logic/state/base"; -import useContactState from "~/logic/state/contact"; -import useGraphState from "~/logic/state/graph"; -import useGroupState from "~/logic/state/group"; -import useHarkState from "~/logic/state/hark"; -import useInviteState from "~/logic/state/invite"; -import useLaunchState from "~/logic/state/launch"; -import useMetadataState from "~/logic/state/metadata"; -import useSettingsState from "~/logic/state/settings"; -import useStorageState from "~/logic/state/storage"; -import { BackButton } from "./BackButton"; +import { BaseInput, Box, Col, Text } from '@tlon/indigo-react'; +import _ from 'lodash'; +import React, { useCallback, useState } from 'react'; +import { UseStore } from 'zustand'; +import { BaseState } from '~/logic/state/base'; +import useContactState from '~/logic/state/contact'; +import useGraphState from '~/logic/state/graph'; +import useGroupState from '~/logic/state/group'; +import useHarkState from '~/logic/state/hark'; +import useInviteState from '~/logic/state/invite'; +import useLaunchState from '~/logic/state/launch'; +import useMetadataState from '~/logic/state/metadata'; +import useSettingsState from '~/logic/state/settings'; +import useStorageState from '~/logic/state/storage'; +import { BackButton } from './BackButton'; interface StoreDebuggerProps { name: string; - useStore: UseStore>; + useStore: UseStore & any>; } const objectToString = (obj: any): string => JSON.stringify(obj, null, ' '); @@ -27,7 +27,7 @@ const StoreDebugger = (props: StoreDebuggerProps) => { const [filter, setFilter] = useState(''); const [text, setText] = useState(objectToString(state)); const [visible, setVisible] = useState(false); - + const tryFilter = useCallback((filterToTry) => { let output: any = false; try { @@ -39,7 +39,6 @@ const StoreDebugger = (props: StoreDebuggerProps) => { setFilter(filterToTry); } }, [state, filter, text]); - return ( @@ -53,19 +52,22 @@ const StoreDebugger = (props: StoreDebuggerProps) => { backgroundColor='white' color='black' border='1px solid transparent' - borderRadius='2' + borderRadius={2} fontSize={1} placeholder="Drill Down" width="100%" - onKeyUp={event => { + onKeyUp={(event) => { + // @ts-ignore clearly value is in eventtarget if (event.target.value) { + // @ts-ignore clearly value is in eventtarget tryFilter(event.target.value); } else { setFilter(''); setText(objectToString(state)); } - }} /> - {text} + }} + /> + {text} }
); @@ -75,8 +77,8 @@ const DebugPane = () => { return ( <> - - + + Debug Menu @@ -95,7 +97,7 @@ const DebugPane = () => { - ) + ); }; -export default DebugPane; \ No newline at end of file +export default DebugPane; diff --git a/pkg/interface/src/views/apps/settings/components/lib/DisplayForm.tsx b/pkg/interface/src/views/apps/settings/components/lib/DisplayForm.tsx index 34b9862452..a6c3745e5d 100644 --- a/pkg/interface/src/views/apps/settings/components/lib/DisplayForm.tsx +++ b/pkg/interface/src/views/apps/settings/components/lib/DisplayForm.tsx @@ -1,30 +1,28 @@ -import React from "react"; - import { - Col, - Text, - Label, - ManagedRadioButtonField as Radio -} from "@tlon/indigo-react"; -import { Formik, Form } from "formik"; -import * as Yup from "yup"; + Col, -import GlobalApi from "~/logic/api/global"; -import { uxToHex } from "~/logic/lib/util"; -import { S3State, BackgroundConfig, StorageState } from "~/types"; -import { BackgroundPicker, BgType } from "./BackgroundPicker"; -import useSettingsState, { SettingsState, selectSettingsState } from "~/logic/state/settings"; -import {AsyncButton} from "~/views/components/AsyncButton"; -import { BackButton } from "./BackButton"; + Label, + ManagedRadioButtonField as Radio, Text +} from '@tlon/indigo-react'; +import { Form, Formik } from 'formik'; +import React from 'react'; +import * as Yup from 'yup'; +import GlobalApi from '~/logic/api/global'; +import { uxToHex } from '~/logic/lib/util'; +import useSettingsState, { selectSettingsState } from '~/logic/state/settings'; +import { AsyncButton } from '~/views/components/AsyncButton'; +import {FormikOnBlur} from '~/views/components/FormikOnBlur'; +import { BackButton } from './BackButton'; +import { BackgroundPicker, BgType } from './BackgroundPicker'; const formSchema = Yup.object().shape({ bgType: Yup.string() - .oneOf(["none", "color", "url"], "invalid") - .required("Required"), + .oneOf(['none', 'color', 'url'], 'invalid') + .required('Required'), background: Yup.string(), theme: Yup.string() - .oneOf(["light", "dark", "auto"]) - .required("Required") + .oneOf(['light', 'dark', 'auto']) + .required('Required') }); interface FormSchema { @@ -38,7 +36,7 @@ interface DisplayFormProps { api: GlobalApi; } -const settingsSel = selectSettingsState(["display"]); +const settingsSel = selectSettingsState(['display']); export default function DisplayForm(props: DisplayFormProps) { const { api } = props; @@ -51,36 +49,35 @@ export default function DisplayForm(props: DisplayFormProps) { } } = useSettingsState(settingsSel); - let bgColor, bgUrl; - if (backgroundType === "url") { + if (backgroundType === 'url') { bgUrl = background; } - if (backgroundType === "color") { + if (backgroundType === 'color') { bgColor = background; } - const bgType = backgroundType || "none"; + const bgType = backgroundType || 'none'; return ( - { - let promises = [] as Promise[]; + const promises = [] as Promise[]; promises.push(api.settings.putEntry('display', 'backgroundType', values.bgType)); promises.push( api.settings.putEntry('display', 'background', - values.bgType === "color" - ? `#${uxToHex(values.bgColor || "0x0")}` - : values.bgType === "url" - ? values.bgUrl || "" + values.bgType === 'color' + ? `#${uxToHex(values.bgColor || '0x0')}` + : values.bgType === 'url' + ? values.bgUrl || '' : false )); @@ -88,14 +85,12 @@ export default function DisplayForm(props: DisplayFormProps) { await Promise.all(promises); actions.setStatus({ success: null }); - }} > - {(props) => (
- - - + + + Display Preferences @@ -104,12 +99,11 @@ export default function DisplayForm(props: DisplayFormProps) { - + @@ -117,7 +111,6 @@ export default function DisplayForm(props: DisplayFormProps) { - )} -
+ ); } diff --git a/pkg/interface/src/views/apps/settings/components/lib/DmSettings.tsx b/pkg/interface/src/views/apps/settings/components/lib/DmSettings.tsx new file mode 100644 index 0000000000..201a560211 --- /dev/null +++ b/pkg/interface/src/views/apps/settings/components/lib/DmSettings.tsx @@ -0,0 +1,58 @@ +import { + Text, + Col, + Box, + ManagedToggleSwitchField +} from '@tlon/indigo-react'; +import { Form, Formik } from 'formik'; +import React, { useCallback } from 'react'; +import GlobalApi from '~/logic/api/global'; +import useGraphState from '~/logic/state/graph'; +import { AsyncButton } from '~/views/components/AsyncButton'; + +export function DmSettings(props: { api: GlobalApi }) { + const { api } = props; + const screening = useGraphState(s => s.screening); + const initialValues = { accept: !screening }; + const onSubmit = useCallback( + async (values, actions) => { + await api.graph.setScreen(!values.accept); + actions.setStatus({ success: null }); + }, + [screening] + ); + + return ( + + + + Privacy + + + + Control other people's ability to message you + + + + + + Direct Messages + + +
+ + + + Save Changes + + + +
+ + + ); +} diff --git a/pkg/interface/src/views/apps/settings/components/lib/GroupChannelPicker.tsx b/pkg/interface/src/views/apps/settings/components/lib/GroupChannelPicker.tsx index 5c5b4eca76..f442e9762a 100644 --- a/pkg/interface/src/views/apps/settings/components/lib/GroupChannelPicker.tsx +++ b/pkg/interface/src/views/apps/settings/components/lib/GroupChannelPicker.tsx @@ -1,29 +1,27 @@ -import React, { useState, useEffect } from "react"; import { Box, - Text, - Icon, - ManagedToggleSwitchField, - StatelessToggleSwitchField, - Col, - Center, -} from "@tlon/indigo-react"; -import _ from "lodash"; -import useMetadataState, { useGraphsForGroup } from "~/logic/state/metadata"; -import { Association, resourceFromPath } from "@urbit/api"; -import { MetadataIcon } from "~/views/landscape/components/MetadataIcon"; -import useGraphState from "~/logic/state/graph"; -import { useField } from "formik"; -import useHarkState from "~/logic/state/hark"; -import { getModuleIcon } from "~/logic/lib/util"; -import {isWatching} from "~/logic/lib/hark"; + Center, Col, Icon, + + ToggleSwitch, Text, + StatelessToggleSwitchField +} from '@tlon/indigo-react'; +import { Association, GraphConfig, resourceFromPath } from '@urbit/api'; +import { useField } from 'formik'; +import _ from 'lodash'; +import React, { useEffect, useState } from 'react'; +import { isWatching } from '~/logic/lib/hark'; +import { getModuleIcon, GraphModule } from '~/logic/lib/util'; +import useGraphState from '~/logic/state/graph'; +import useHarkState from '~/logic/state/hark'; +import useMetadataState, { useGraphsForGroup } from '~/logic/state/metadata'; +import { MetadataIcon } from '~/views/landscape/components/MetadataIcon'; export function GroupChannelPicker(props: {}) { - const associations = useMetadataState((s) => s.associations); + const associations = useMetadataState(s => s.associations); return ( - + {_.map(associations.groups, (assoc: Association, group: string) => ( ))} @@ -35,26 +33,24 @@ function GroupWithChannels(props: { association: Association }) { const { association } = props; const { metadata } = association; - const groupWatched = useHarkState((s) => + const groupWatched = useHarkState(s => s.notificationsGroupConfig.includes(association.group) ); const [{ value }, meta, { setValue }] = useField( `groups["${association.group}"]` ); - const onChange = () => { setValue(!value); }; - useEffect(() => { setValue(groupWatched); }, []); const graphs = useGraphsForGroup(association.group); - const joinedGraphs = useGraphState((s) => s.graphKeys); + const joinedGraphs = useGraphState(s => s.graphKeys); const joinedGroupGraphs = _.pickBy(graphs, (_, graph: string) => { const { ship, name } = resourceFromPath(graph); return joinedGraphs.has(`${ship.slice(1)}/${name}`); @@ -67,16 +63,16 @@ function GroupWithChannels(props: { association: Association }) { display="grid" gridTemplateColumns="24px 24px 1fr 24px 24px" gridTemplateRows="auto" - gridGap="2" + gridGap={2} gridTemplateAreas="'arrow icon title graphToggle groupToggle'" > {Object.keys(joinedGroupGraphs).length > 0 && (
setOpen((o) => !o)} + onClick={() => setOpen(o => !o)} gridArea="arrow" > - +
)} { + const onClick = () => { setValue(!value); - }; + setTouched(true); + } - const icon = getModuleIcon(metadata.config?.graph); + + const icon = getModuleIcon((metadata.config as GraphConfig)?.graph as GraphModule); return ( <> -
+
- + {metadata.title} - - + + ); diff --git a/pkg/interface/src/views/apps/settings/components/lib/LeapSettings.tsx b/pkg/interface/src/views/apps/settings/components/lib/LeapSettings.tsx index a4f7992824..590ae05fed 100644 --- a/pkg/interface/src/views/apps/settings/components/lib/LeapSettings.tsx +++ b/pkg/interface/src/views/apps/settings/components/lib/LeapSettings.tsx @@ -1,30 +1,27 @@ -import React, { useCallback } from "react"; -import _ from "lodash"; import { - Col, - Text, - ManagedToggleSwitchField as Toggle, - ManagedCheckboxField, - BaseInput, -} from "@tlon/indigo-react"; -import { Form, FormikHelpers, useField, useFormikContext } from "formik"; -import { FormikOnBlur } from "~/views/components/FormikOnBlur"; -import { BackButton } from "./BackButton"; -import GlobalApi from "~/logic/api/global"; + Col, + + ManagedCheckboxField, Text +} from '@tlon/indigo-react'; +import { Form, useField, useFormikContext } from 'formik'; +import _ from 'lodash'; +import React from 'react'; +import GlobalApi from '~/logic/api/global'; +import useSettingsState, { selectSettingsState } from '~/logic/state/settings'; import { - NotificationGraphConfig, - LeapCategories, - leapCategories, -} from "~/types"; -import useSettingsState, { selectSettingsState } from "~/logic/state/settings"; -import { ShuffleFields } from "~/views/components/ShuffleFields"; + LeapCategories, + leapCategories +} from '~/types'; +import { FormikOnBlur } from '~/views/components/FormikOnBlur'; +import { ShuffleFields } from '~/views/components/ShuffleFields'; +import { BackButton } from './BackButton'; const labels: Record = { - mychannel: "My Channel", - updates: "Notifications", - profile: "Profile", - messages: "Messages", - logout: "Log Out", + mychannel: 'My Channel', + updates: 'Notifications', + profile: 'Profile', + messages: 'Messages', + logout: 'Log Out' }; interface FormSchema { @@ -46,23 +43,22 @@ function CategoryCheckbox(props: { index: number }) { ); } -const settingsSel = selectSettingsState(["leap", "set"]); +const settingsSel = selectSettingsState(['leap', 'set']); export function LeapSettings(props: { api: GlobalApi; }) { const { api } = props; const { leap, set: setSettingsState } = useSettingsState(settingsSel); const categories = leap.categories as LeapCategories[]; const missing = _.difference(leapCategories, categories); - console.log(categories); const initialValues = { categories: [ - ...categories.map((cat) => ({ + ...categories.map(cat => ({ category: cat, - display: true, + display: true })), - ...missing.map((cat) => ({ category: cat, display: false })), - ], + ...missing.map(cat => ({ category: cat, display: false })) + ] }; const onSubmit = async (values: FormSchema) => { @@ -75,10 +71,10 @@ export function LeapSettings(props: { api: GlobalApi; }) { return ( <> - - - - + + + + Leap @@ -87,7 +83,7 @@ export function LeapSettings(props: { api: GlobalApi; }) {
- + Customize default Leap sections diff --git a/pkg/interface/src/views/apps/settings/components/lib/NotificationPref.tsx b/pkg/interface/src/views/apps/settings/components/lib/NotificationPref.tsx index 3759572462..5bda6924b8 100644 --- a/pkg/interface/src/views/apps/settings/components/lib/NotificationPref.tsx +++ b/pkg/interface/src/views/apps/settings/components/lib/NotificationPref.tsx @@ -1,17 +1,21 @@ -import React, { useCallback } from "react"; import { + Button, Col, - Text, - ManagedToggleSwitchField as Toggle, -} from "@tlon/indigo-react"; -import { Formik, Form, FormikHelpers } from "formik"; -import { BackButton } from "./BackButton"; -import GlobalApi from "~/logic/api/global"; -import useHarkState from "~/logic/state/hark"; -import _ from "lodash"; -import {AsyncButton} from "~/views/components/AsyncButton"; -import {GroupChannelPicker} from "./GroupChannelPicker"; -import {isWatching} from "~/logic/lib/hark"; + + + + + ManagedToggleSwitchField as Toggle, Text +} from '@tlon/indigo-react'; +import { Form, FormikHelpers } from 'formik'; +import _ from 'lodash'; +import React, { useCallback, useState } from 'react'; +import GlobalApi from '~/logic/api/global'; +import { isWatching } from '~/logic/lib/hark'; +import useHarkState from '~/logic/state/hark'; +import { FormikOnBlur } from '~/views/components/FormikOnBlur'; +import { BackButton } from './BackButton'; +import { GroupChannelPicker } from './GroupChannelPicker'; interface FormSchema { mentions: boolean; @@ -35,12 +39,12 @@ export function NotificationPreferences(props: { const initialValues = { mentions: graphConfig.mentions, dnd: dnd, - watchOnSelf: graphConfig.watchOnSelf, + watchOnSelf: graphConfig.watchOnSelf }; const onSubmit = useCallback(async (values: FormSchema, actions: FormikHelpers) => { try { - let promises: Promise[] = []; + const promises: Promise[] = []; if (values.mentions !== graphConfig.mentions) { promises.push(api.hark.setMentions(values.mentions)); } @@ -48,16 +52,16 @@ export function NotificationPreferences(props: { promises.push(api.hark.setWatchOnSelf(values.watchOnSelf)); } if (values.dnd !== dnd && !_.isUndefined(values.dnd)) { - promises.push(api.hark.setDoNotDisturb(values.dnd)) + promises.push(api.hark.setDoNotDisturb(values.dnd)); } _.forEach(values.graph, (listen: boolean, graph: string) => { if(listen !== isWatching(graphConfig, graph)) { - promises.push(api.hark[listen ? "listenGraph" : "ignoreGraph"](graph, "/")) + promises.push(api.hark[listen ? 'listenGraph' : 'ignoreGraph'](graph, '/')); } }); _.forEach(values.groups, (listen: boolean, group: string) => { if(listen !== groupConfig.includes(group)) { - promises.push(api.hark[listen ? "listenGroup" : "ignoreGroup"](group)); + promises.push(api.hark[listen ? 'listenGroup' : 'ignoreGroup'](group)); } }); @@ -69,12 +73,14 @@ export function NotificationPreferences(props: { } }, [api, graphConfig, dnd]); + const [notificationsAllowed, setNotificationsAllowed] = useState('Notification' in window && Notification.permission !== 'default'); + return ( <> - - - - + + + + Notification Preferences @@ -82,9 +88,17 @@ export function NotificationPreferences(props: { messaging - + + {notificationsAllowed || !('Notification' in window) + ? null + : + } - + Activity @@ -109,12 +123,9 @@ export function NotificationPreferences(props: { - - Save - - +
); diff --git a/pkg/interface/src/views/apps/settings/components/lib/S3Form.tsx b/pkg/interface/src/views/apps/settings/components/lib/S3Form.tsx index 5432215daf..cf07907be5 100644 --- a/pkg/interface/src/views/apps/settings/components/lib/S3Form.tsx +++ b/pkg/interface/src/views/apps/settings/components/lib/S3Form.tsx @@ -1,24 +1,15 @@ -import React, { ReactElement, useCallback } from 'react'; -import { Formik, FormikHelpers } from 'formik'; - import { - ManagedTextInputField as Input, - ManagedForm as Form, - Box, - Text, - Button, - Col, - Anchor -} from '@tlon/indigo-react'; -import { AsyncButton } from "~/views/components/AsyncButton"; + Anchor, Col, ManagedForm as Form, ManagedTextInputField as Input, + Text +} from '@tlon/indigo-react'; +import { Formik, FormikHelpers } from 'formik'; +import React, { ReactElement, useCallback } from 'react'; import GlobalApi from '~/logic/api/global'; -import { BucketList } from './BucketList'; -import { S3State } from '~/types/s3-update'; -import useS3State from '~/logic/state/storage'; -import { BackButton } from './BackButton'; -import { StorageState } from '~/types'; import useStorageState from '~/logic/state/storage'; +import { AsyncButton } from '~/views/components/AsyncButton'; +import { BackButton } from './BackButton'; +import { BucketList } from './BucketList'; interface FormSchema { s3bucket: string; @@ -34,7 +25,7 @@ interface S3FormProps { export default function S3Form(props: S3FormProps): ReactElement { const { api } = props; - const s3 = useStorageState((state) => state.s3); + const s3 = useStorageState(state => state.s3); const onSubmit = useCallback(async (values: FormSchema, actions: FormikHelpers) => { if (values.s3secretAccessKey !== s3.credentials?.secretAccessKey) { @@ -56,7 +47,7 @@ export default function S3Form(props: S3FormProps): ReactElement { return ( <> - +
- - + + S3 Storage Setup @@ -81,8 +72,8 @@ export default function S3Form(props: S3FormProps): ReactElement { Learn more @@ -103,8 +94,8 @@ export default function S3Form(props: S3FormProps): ReactElement {
- - + + S3 Buckets diff --git a/pkg/interface/src/views/apps/settings/components/lib/Security.tsx b/pkg/interface/src/views/apps/settings/components/lib/Security.tsx index e577103175..1cb273557b 100644 --- a/pkg/interface/src/views/apps/settings/components/lib/Security.tsx +++ b/pkg/interface/src/views/apps/settings/components/lib/Security.tsx @@ -1,14 +1,11 @@ -import React, { useState } from "react"; import { - Box, - Text, - Button, - Col, - StatelessCheckboxField, -} from "@tlon/indigo-react"; - -import GlobalApi from "~/logic/api/global"; -import { BackButton } from "./BackButton"; + Button, + Col, + StatelessCheckboxField, Text +} from '@tlon/indigo-react'; +import React, { useState } from 'react'; +import GlobalApi from '~/logic/api/global'; +import { BackButton } from './BackButton'; interface SecuritySettingsProps { api: GlobalApi; @@ -18,9 +15,9 @@ export default function SecuritySettings({ api }: SecuritySettingsProps) { const [allSessions, setAllSessions] = useState(false); return ( <> - - - + + + Security Preferences @@ -28,19 +25,19 @@ export default function SecuritySettings({ api }: SecuritySettingsProps) { Manage sessions, login credentials and Landscape access
- + Log out of this session - + {allSessions - ? "You will be logged out of all browsers that have currently logged into your Urbit." - : "You will be logged out of your Urbit on this browser."} + ? 'You will be logged out of all browsers that have currently logged into your Urbit.' + : 'You will be logged out of your Urbit on this browser.'} setAllSessions((s) => !s)} + onChange={() => setAllSessions(s => !s)} > Log out of all sessions @@ -50,7 +47,7 @@ export default function SecuritySettings({ api }: SecuritySettingsProps) { primary destructive border={1} - style={{ cursor: "pointer" }} + style={{ cursor: 'pointer' }} > Logout diff --git a/pkg/interface/src/views/apps/settings/components/lib/ShortcutSettings.tsx b/pkg/interface/src/views/apps/settings/components/lib/ShortcutSettings.tsx new file mode 100644 index 0000000000..e5dca84799 --- /dev/null +++ b/pkg/interface/src/views/apps/settings/components/lib/ShortcutSettings.tsx @@ -0,0 +1,117 @@ +import React, { useCallback, useEffect, useState } from 'react'; +import _ from 'lodash'; + +import { Box, Col, Text } from '@tlon/indigo-react'; +import { Formik, Form, useField } from 'formik'; + +import GlobalApi from '~/logic/api/global'; +import { getChord } from '~/logic/lib/util'; +import useSettingsState, { + selectSettingsState, + ShortcutMapping, +} from '~/logic/state/settings'; +import { AsyncButton } from '~/views/components/AsyncButton'; +import { BackButton } from './BackButton'; + +interface ShortcutSettingsProps { + api: GlobalApi; +} + +const settingsSel = selectSettingsState(['keyboard']); + +export function ChordInput(props: { id: string; label: string }) { + const { id, label } = props; + const [capturing, setCapturing] = useState(false); + const [{ value }, , { setValue }] = useField(id); + const onCapture = useCallback(() => { + setCapturing(true); + }, []); + useEffect(() => { + if (!capturing) { + return; + } + function onKeydown(e: KeyboardEvent) { + if (['Control', 'Shift', 'Meta'].includes(e.key)) { + return; + } + const chord = getChord(e); + setValue(chord); + e.stopImmediatePropagation(); + e.preventDefault(); + setCapturing(false); + } + document.addEventListener('keydown', onKeydown); + return () => { + document.removeEventListener('keydown', onKeydown); + }; + }, [capturing]); + + return ( + <> + + {label} + + + {capturing ? 'Press' : value} + + + ); +} + +export default function ShortcutSettings(props: ShortcutSettingsProps) { + const { api } = props; + + const { keyboard } = useSettingsState(settingsSel); + + return ( + { + const promises = _.map(values, (value, key) => { + return keyboard[key] !== value + ? api.settings.putEntry('keyboard', key, value) + : Promise.resolve(); + }); + await Promise.all(promises); + actions.setStatus({ success: null }); + }} + > +
+ + + + + Shortcuts + + Customize keyboard shortcuts for landscape + + + + + + + + + Save Changes + + +
+ ); +} diff --git a/pkg/interface/src/views/apps/settings/components/settings.tsx b/pkg/interface/src/views/apps/settings/components/settings.tsx index 9c49fd414f..769702e130 100644 --- a/pkg/interface/src/views/apps/settings/components/settings.tsx +++ b/pkg/interface/src/views/apps/settings/components/settings.tsx @@ -1,15 +1,6 @@ -import React from "react"; - -import { Row, Icon, Box, Col, Text } from "@tlon/indigo-react"; - -import GlobalApi from "~/logic/api/global"; -import { StoreState } from "~/logic/store/type"; -import DisplayForm from "./lib/DisplayForm"; -import S3Form from "./lib/S3Form"; -import SecuritySettings from "./lib/Security"; -import { NotificationPreferences } from "./lib/NotificationPref"; -import { CalmPrefs } from "./lib/CalmPref"; -import { Link } from "react-router-dom"; +import { Box, Col, Row, Text } from '@tlon/indigo-react'; +import React from 'react'; +import { Link } from 'react-router-dom'; export function SettingsItem(props: { title: string; @@ -19,14 +10,14 @@ export function SettingsItem(props: { const { to, title, description } = props; return ( - + - + {title} {description} @@ -37,17 +28,17 @@ export function SettingsItem(props: { export default function Settings(props: {}) { return ( - - - System Preferences + + + System Preferences Configure and customize Landscape ( @@ -63,7 +62,7 @@ function SettingsItem(props: { children: ReactNode }) { const { children } = props; return ( - + {children} ); @@ -76,7 +75,8 @@ export default function SettingsScreen(props: any) { useEffect(() => { const debugShower = (event) => { - if (hash) return; + if (hash) +return; if (event.key === '~') { window.location.hash = 'debug'; } @@ -85,7 +85,7 @@ export default function SettingsScreen(props: any) { return () => { document.removeEventListener('keyup', debugShower); - } + }; }, [hash]); return ( @@ -96,25 +96,27 @@ export default function SettingsScreen(props: any) { - + System Preferences + + )} {hash === 'display' && } + {hash === 'dm' && } + {hash === 'shortcuts' && } {hash === 's3' && } {hash === 'leap' && } {hash === 'calm' && } diff --git a/pkg/interface/src/views/apps/term/api.js b/pkg/interface/src/views/apps/term/api.tsx similarity index 87% rename from pkg/interface/src/views/apps/term/api.js rename to pkg/interface/src/views/apps/term/api.tsx index 8216a771dc..077263efc3 100644 --- a/pkg/interface/src/views/apps/term/api.js +++ b/pkg/interface/src/views/apps/term/api.tsx @@ -1,6 +1,9 @@ import _ from 'lodash'; export default class Api { + ship: any; + channel: any; + bindPaths: any[]; constructor(ship, channel) { this.ship = ship; this.channel = channel; @@ -10,7 +13,7 @@ export default class Api { bind(path, method, ship = this.ship, appl = 'herm', success, fail) { this.bindPaths = _.uniq([...this.bindPaths, path]); - window.subscriptionId = this.channel.subscribe(ship, appl, path, + (window as any).subscriptionId = this.channel.subscribe(ship, appl, path, (err) => { fail(err); }, diff --git a/pkg/interface/src/views/apps/term/app.js b/pkg/interface/src/views/apps/term/app.tsx similarity index 88% rename from pkg/interface/src/views/apps/term/app.js rename to pkg/interface/src/views/apps/term/app.tsx index 435db57e88..edd546fdbf 100644 --- a/pkg/interface/src/views/apps/term/app.js +++ b/pkg/interface/src/views/apps/term/app.tsx @@ -1,21 +1,22 @@ -import React, { Component } from 'react'; -import { Route } from 'react-router-dom'; -import Helmet from 'react-helmet'; - -import { History } from './components/history'; -import { Input } from './components/input'; - import { Box, Col } from '@tlon/indigo-react'; - -import Api from './api'; -import Store from './store'; -import Subscription from './subscription'; +import React, { Component } from 'react'; +import Helmet from 'react-helmet'; +import { Route } from 'react-router-dom'; import withState from '~/logic/lib/withState'; import useHarkState from '~/logic/state/hark'; - +import Api from './api'; +import { History } from './components/history'; +import { Input } from './components/input'; import './css/custom.css'; +import Store from './store'; +import Subscription from './subscription'; class TermApp extends Component { + store: Store; + api: any; + subscription: any; + props: any; + state: any; constructor(props) { super(props); this.store = new Store(); @@ -31,7 +32,7 @@ class TermApp extends Component { componentDidMount() { this.resetControllers(); - const channel = new window.channel(); + const channel = new (window as any).channel(); this.api = new Api(this.props.ship, channel); this.store.api = this.api; @@ -65,18 +66,19 @@ class TermApp extends Component { display='flex' > + {/* @ts-ignore declare props in later pass */} + {/* @ts-ignore declare props in later pass */} {this.props.log.map((line, i) => { + // @ts-ignore react memo not passing props return ; })} diff --git a/pkg/interface/src/views/apps/term/components/input.js b/pkg/interface/src/views/apps/term/components/input.tsx similarity index 61% rename from pkg/interface/src/views/apps/term/components/input.js rename to pkg/interface/src/views/apps/term/components/input.tsx index 695c4a80f1..8a8ebc1eb7 100644 --- a/pkg/interface/src/views/apps/term/components/input.js +++ b/pkg/interface/src/views/apps/term/components/input.tsx @@ -1,7 +1,9 @@ +import { BaseInput, Box, Row } from '@tlon/indigo-react'; import React, { Component } from 'react'; -import { Row, Box, BaseInput } from '@tlon/indigo-react'; export class Input extends Component { + inputRef: React.RefObject; + props: any; constructor(props) { super(props); this.state = {}; @@ -13,40 +15,53 @@ export class Input extends Component { componentDidUpdate() { if ( - !document.activeElement == document.body - || document.activeElement == this.inputRef.current + document.activeElement == this.inputRef.current ) { + // @ts-ignore ref type issues this.inputRef.current.focus(); + // @ts-ignore ref type issues this.inputRef.current.setSelectionRange(this.props.cursor, this.props.cursor); } } keyPress(e) { - let key = e.key; + const key = e.key; // let paste and leap events pass - if ((e.getModifierState('Control') || event.getModifierState('Meta')) + if ((e.getModifierState('Control') || e.getModifierState('Meta')) && (e.key === 'v' || e.key === '/')) { return; } let belt = null; - if (key === 'ArrowLeft') belt = {aro: 'l'}; - else if (key === 'ArrowRight') belt = {aro: 'r'}; - else if (key === 'ArrowUp') belt = {aro: 'u'}; - else if (key === 'ArrowDown') belt = {aro: 'd'}; - else if (key === 'Backspace') belt = {bac: null}; - else if (key === 'Delete') belt = {del: null}; - else if (key === 'Tab') belt = {ctl: 'i'}; - else if (key === 'Enter') belt = {ret: null}; - else if (key.length === 1) belt = {txt: [key]}; - else belt = null; + if (key === 'ArrowLeft') +belt = { aro: 'l' }; + else if (key === 'ArrowRight') +belt = { aro: 'r' }; + else if (key === 'ArrowUp') +belt = { aro: 'u' }; + else if (key === 'ArrowDown') +belt = { aro: 'd' }; + else if (key === 'Backspace') +belt = { bac: null }; + else if (key === 'Delete') +belt = { del: null }; + else if (key === 'Tab') +belt = { ctl: 'i' }; + else if (key === 'Enter') +belt = { ret: null }; + else if (key.length === 1) +belt = { txt: [key] }; + else +belt = null; if (belt && e.getModifierState('Control')) { - if (belt.txt !== undefined) belt = {ctl: belt.txt[0]}; + if (belt.txt !== undefined) +belt = { ctl: belt.txt[0] }; } else if (belt && (e.getModifierState('Meta') || e.getModifierState('Alt'))) { - if (belt.bac !== undefined) belt = {met: 'bac'}; + if (belt.bac !== undefined) +belt = { met: 'bac' }; } if (belt !== null) { @@ -57,7 +72,7 @@ export class Input extends Component { } paste(e) { - const clipboardData = e.clipboardData || window.clipboardData; + const clipboardData = e.clipboardData || (window as any).clipboardData; const clipboardText = clipboardData.getData('Text'); this.props.api.belt({ txt: [...clipboardText] }); e.preventDefault(); @@ -76,24 +91,24 @@ export class Input extends Component { if (line.lin) { prompt = line.lin.join(''); } - //TODO render prompt style + // TODO render prompt style else if (line.klr) { prompt = line.klr.reduce((l, p) => (l + p.text.join('')), ''); } } return ( - - + + { - +import React from 'react'; +// @ts-ignore line isn't in props? +export default React.memo(({ line }) => { // line body to jsx - //NOTE lines are lists of characters that might span multiple codepoints + // NOTE lines are lists of characters that might span multiple codepoints // let text = ''; if (line.lin) { text = line.lin.join(''); - } - else if (line.klr) { + } else if (line.klr) { text = line.klr.map((part, i) => { - let prop = part.stye.deco.reduce((prop, deco) => { + const prop = part.stye.deco.reduce((prop, deco) => { switch (deco) { case null: return prop; - case 'br': return {bold: true, ...prop}; - case 'bl': return {className: 'blink', ...prop}; - case 'un': return {style: {textDecoration: 'underline'}, ...prop}; + case 'br': return { bold: true, ...prop }; + case 'bl': return { className: 'blink', ...prop }; + case 'un': return { style: { textDecoration: 'underline' }, ...prop }; default: console.log('weird deco', deco); return prop; } }, {}); @@ -45,8 +43,7 @@ export default React.memo(({line}) => { case 'w': prop.backgroundColor = 'white'; break; default: prop.backgroundColor = '#' + part.stye.back; } - if (Object.keys(prop).length === 0) - { + if (Object.keys(prop).length === 0) { return part.text; } else { return ( @@ -59,7 +56,8 @@ export default React.memo(({line}) => { // render line // return ( - {text} diff --git a/pkg/interface/src/views/apps/term/store.js b/pkg/interface/src/views/apps/term/store.tsx similarity index 74% rename from pkg/interface/src/views/apps/term/store.js rename to pkg/interface/src/views/apps/term/store.tsx index ac74cafe2a..d2166083a4 100644 --- a/pkg/interface/src/views/apps/term/store.js +++ b/pkg/interface/src/views/apps/term/store.tsx @@ -1,7 +1,10 @@ import { saveAs } from 'file-saver'; -import bel from '../../../logic/lib/bel' +import bel from '../../../logic/lib/bel'; export default class Store { + state: any; + api: any; + setState: any; constructor() { this.state = this.initialState(); } @@ -9,19 +12,19 @@ export default class Store { initialState() { return { lines: [''], - cursor: 0, + cursor: 0 }; } clear() { - this.setState(this.initialState()) + this.setState(this.initialState()); } handleEvent(data) { // process slogs // if (data.slog) { - this.state.lines.splice(this.state.lines.length-1, 0, {lin: [data.slog]}); + this.state.lines.splice(this.state.lines.length-1, 0, { lin: [data.slog] }); this.setState({ lines: this.state.lines }); return; } @@ -42,15 +45,15 @@ export default class Store { // codepoints, we need to calculate the byte-wise cursor position // to avoid incorrect cursor rendering. // - let line = this.state.lines[this.state.lines.length - 1]; + const line = this.state.lines[this.state.lines.length - 1]; let hops; if (line.lin) { hops = line.lin.slice(0, blit.hop); - } - else if (line.klr) { + } else if (line.klr) { hops = line.klr.reduce((h, p) => { - if (h.length >= blit.hop) return h; - return [...h, ...p.text.slice(0, blit.hop - h.length)] + if (h.length >= blit.hop) +return h; + return [...h, ...p.text.slice(0, blit.hop - h.length)]; }, []); } this.setState({ cursor: hops.join('').length }); @@ -69,14 +72,15 @@ export default class Store { break; case 'sag': blit.sav = blit.sag; + break; case 'sav': - let name = blit.sav.path.split('/').slice(-2).join('.'); - let buff = new Buffer(blit.sav.file, 'base64'); - let blob = new Blob([buff], {type: 'application/octet-stream'}); + const name = blit.sav.path.split('/').slice(-2).join('.'); + const buff = new Buffer(blit.sav.file, 'base64'); + const blob = new Blob([buff], { type: 'application/octet-stream' }); saveAs(blob, name); break; case 'url': - //TODO too invasive? just print as ? + // TODO too invasive? just print as ? window.open(blit.url); break; default: console.log('weird blit', blit); diff --git a/pkg/interface/src/views/apps/term/subscription.js b/pkg/interface/src/views/apps/term/subscription.tsx similarity index 84% rename from pkg/interface/src/views/apps/term/subscription.js rename to pkg/interface/src/views/apps/term/subscription.tsx index 21f3cc8367..178246e43f 100644 --- a/pkg/interface/src/views/apps/term/subscription.js +++ b/pkg/interface/src/views/apps/term/subscription.tsx @@ -1,4 +1,8 @@ export default class Subscription { + store: any; + api: any; + channel: any; + firstRoundComplete: boolean; constructor(store, api, channel) { this.store = store; this.api = api; @@ -21,25 +25,26 @@ export default class Subscription { let available = false; const slog = new EventSource('/~_~/slog', { withCredentials: true }); - slog.onopen = e => { + slog.onopen = (e) => { console.log('slog: opened stream'); available = true; - } + }; - slog.onmessage = e => { + slog.onmessage = (e) => { this.handleEvent({ slog: e.data }); - } + }; - slog.onerror = e => { + slog.onerror = (e) => { console.error('slog: eventsource error:', e); if (available) { window.setTimeout(() => { - if (slog.readyState !== EventSource.CLOSED) return; + if (slog.readyState !== EventSource.CLOSED) +return; console.log('slog: reconnecting...'); this.setupSlog(); }, 10000); } - } + }; } delete() { @@ -50,13 +55,13 @@ export default class Subscription { console.error('event source error: ', err); console.log('initiating new channel'); this.firstRoundComplete = false; - setTimeout(2000, () => { + setTimeout(() => { this.store.handleEvent({ data: { clear : true } }); this.start(); - }); + }, 2000); } subscribe(path, app) { diff --git a/pkg/interface/src/views/components/ArrayVirtualScroller.tsx b/pkg/interface/src/views/components/ArrayVirtualScroller.tsx new file mode 100644 index 0000000000..8d7570b77a --- /dev/null +++ b/pkg/interface/src/views/components/ArrayVirtualScroller.tsx @@ -0,0 +1,639 @@ +/* eslint-disable valid-jsdoc */ +import { Box, Center, LoadingSpinner } from '@tlon/indigo-react'; +import BigIntArrayOrderedMap, { + arrToString, + stringToArr +} from '@urbit/api/lib/BigIntArrayOrderedMap'; +import bigInt, { BigInteger } from 'big-integer'; +import _ from 'lodash'; +import normalizeWheel from 'normalize-wheel'; +import React, { Component, SyntheticEvent, useCallback } from 'react'; +import styled from 'styled-components'; +import { IS_IOS } from '~/logic/lib/platform'; +import { VirtualContext } from '~/logic/lib/virtualContext'; +import { clamp } from '~/logic/lib/util'; + +const ScrollbarLessBox = styled(Box)` + scrollbar-width: none !important; + + ::-webkit-scrollbar { + display: none; + } +`; + +interface RendererProps { + index: BigInteger[]; + scrollWindow: any; + ref: (el: HTMLElement | null) => void; +} + +export { arrToString, stringToArr }; + +export function indexEqual(a: BigInteger[], b: BigInteger[]) { + const aLen = a.length; + const bLen = b.length; + + if (aLen === bLen) { + let i = 0; + while (i < aLen && i < bLen) { + if (a[i].eq(b[i])) { + if (i === aLen - 1) { + return true; + } + i++; + } else { + return false; + } + } + } + + return false; +} + +interface VirtualScrollerProps { + /** + * Start scroll from + */ + origin: 'top' | 'bottom'; + /** + * Load more of the graph + * + * @returns boolean whether or not the graph is now fully loaded + */ + loadRows(newer: boolean): Promise; + /** + * The data to iterate over + */ + data: BigIntArrayOrderedMap; + /** + * The component to render the items + * + * @remarks + * + * This component must be referentially stable, so either use `useCallback` or + * a instance method. It must also forward the DOM ref from its root DOM node + */ + renderer: (props: RendererProps) => JSX.Element | null; + onStartReached?(): void; + onEndReached?(): void; + size: number; + pendingSize: number; + /** + * Average height of a single rendered item + * + * @remarks + * This is used primarily to calculate how many items should be onscreen. If + * size is variable, err on the lower side. + */ + averageHeight: number; + /** + * The offset to begin rendering at, on load. + * + * @remarks + * This is only looked up once, on component creation. Subsequent changes to + * this prop will have no effect + */ + offset: number; + style?: any; + /** + * Callback to execute when finished loading from start + */ + onBottomLoaded?: () => void; +} + +interface VirtualScrollerState { + visibleItems: BigInteger[][]; + scrollbar: number; + loaded: { + top: boolean; + bottom: boolean; + } +} + +type LogLevel = 'scroll' | 'network' | 'bail' | 'reflow'; +const logLevel = ['network', 'bail', 'scroll', 'reflow'] as LogLevel[]; + +const log = (level: LogLevel, message: string) => { + if(logLevel.includes(level)) { + console.log(`[${level}]: ${message}`); + } +}; + +const ZONE_SIZE = IS_IOS ? 20 : 80; + +// nb: in this file, an index refers to a BigInteger[] and an offset refers to a +// number used to index a listified BigIntArrayOrderedMap + +/** + * A virtualscroller for a `BigIntArrayOrderedMap`. + * + * VirtualScroller does not clean up or reset itself, so please use `key` + * to ensure a new instance is created for each BigIntArrayOrderedMap + */ +export default class ArrayVirtualScroller extends Component, VirtualScrollerState> { + /** + * A reference to our scroll container + */ + window: HTMLDivElement | null = null; + /** + * A map of child refs, used to calculate scroll position + */ + private childRefs = new Map(); + /** + * A set of child refs which have been unmounted + */ + private orphans = new Set(); + /** + * If saving, the bottommost visible element that we pin our scroll to + */ + private savedIndex: BigInteger[] | null = null; + /** + * If saving, the distance between the top of `this.savedEl` and the bottom + * of the screen + */ + private savedDistance = 0; + + /** + * If saving, the number of requested saves. If several images are loading + * at once, we save the scroll pos the first time we see it and restore + * once the number of requested saves is zero + */ + private saveDepth = 0; + + scrollLocked = true; + + private pageSize = 50; + + private pageDelta = 15; + + private scrollRef: HTMLElement | null = null; + + private cleanupRefInterval: NodeJS.Timeout | null = null; + + constructor(props: VirtualScrollerProps) { + super(props); + this.state = { + visibleItems: [], + scrollbar: 0, + loaded: { + top: false, + bottom: false + } + }; + + this.updateVisible = this.updateVisible.bind(this); + + this.invertedKeyHandler = this.invertedKeyHandler.bind(this); + this.onScroll = IS_IOS ? _.debounce(this.onScroll.bind(this), 200) : this.onScroll.bind(this); + this.scrollKeyMap = this.scrollKeyMap.bind(this); + this.setWindow = this.setWindow.bind(this); + this.restore = this.restore.bind(this); + this.startOffset = this.startOffset.bind(this); + } + + componentDidMount() { + this.updateVisible(0); + this.loadTop(); + this.loadBottom(); + this.cleanupRefInterval = setInterval(this.cleanupRefs, 5000); + } + + cleanupRefs = () => { + if(this.saveDepth > 0) { + return; + } + [...this.orphans].forEach((o) => { + this.childRefs.delete(o); + }); + this.orphans.clear(); + }; + + // manipulate scrollbar manually, to dodge change detection + updateScroll = IS_IOS ? () => {} : _.throttle(() => { + if(!this.window || !this.scrollRef) { + return; + } + const { scrollTop, scrollHeight } = this.window; + + const unloaded = (this.startOffset() / this.pageSize); + const totalpages = this.props.size / this.pageSize; + + const loaded = (scrollTop / scrollHeight); + const result = ((unloaded + loaded) / totalpages) * this.window.offsetHeight; + this.scrollRef.style[this.props.origin] = `${result}px`; + }, 50); + + componentDidUpdate(prevProps: VirtualScrollerProps, _prevState: VirtualScrollerState) { + const { size, pendingSize } = this.props; + + if(size !== prevProps.size || pendingSize !== prevProps.pendingSize) { + if((this.window?.scrollTop ?? 0) < ZONE_SIZE) { + this.scrollLocked = true; + this.updateVisible(0); + this.resetScroll(); + } + } + } + + componentWillUnmount() { + window.removeEventListener('keydown', this.invertedKeyHandler); + if(this.cleanupRefInterval) { + clearInterval(this.cleanupRefInterval); + } + this.cleanupRefs(); + this.childRefs.clear(); + } + + startOffset() { + const { data } = this.props; + const startIndex = this.state.visibleItems?.[0]; + if(!startIndex) { + return 0; + } + const dataList = Array.from(data); + const offset = dataList.findIndex(([i]) => indexEqual(i, startIndex)); + if(offset === -1) { + // TODO: revisit when we remove nodes for any other reason than + // pending indices being removed + return 0; + } + return offset; + } + + /** + * Updates the `startOffset` and adjusts visible items accordingly. + * Saves the scroll positions before repainting and restores it afterwards + * + * @param newOffset new startOffset + */ + updateVisible(newOffset: number) { + if (!this.window) { + return; + } + log('reflow', `from: ${this.startOffset()} to: ${newOffset}`); + + const { data } = this.props; + const visibleItems = data.keys().slice(newOffset, newOffset + this.pageSize); + + this.save(); + + this.setState({ + visibleItems + }); + requestAnimationFrame(() => { + this.restore(); + }); + } + + scrollKeyMap(): Map { + return new Map([ + ['ArrowUp', this.props.averageHeight], + ['ArrowDown', this.props.averageHeight * -1], + ['PageUp', this.window!.offsetHeight], + ['PageDown', this.window!.offsetHeight * -1], + ['Home', this.window!.scrollHeight], + ['End', this.window!.scrollHeight * -1], + ['Space', this.window!.offsetHeight * -1] + ]); + } + + invertedKeyHandler(event): void | false { + const map = this.scrollKeyMap(); + if (map.has(event.code) && document.body.isSameNode(document.activeElement)) { + event.preventDefault(); + event.stopImmediatePropagation(); + let distance = map.get(event.code)!; + if (event.code === 'Space' && event.shiftKey) { + distance = distance * -1; + } + this.window!.scrollBy(0, distance); + return false; + } + } + + setWindow(element) { + if (!element) + return; + this.save(); + + if (this.window) { + if (this.window.isSameNode(element)) { + return; + } else { + window.removeEventListener('keydown', this.invertedKeyHandler); + } + } + const { averageHeight } = this.props; + + this.window = element; + this.pageSize = Math.floor(element.offsetHeight / Math.floor(averageHeight / 2)); + this.pageDelta = Math.floor(this.pageSize / 4); + + if (this.props.origin === 'bottom') { + element.addEventListener('wheel', (event) => { + event.preventDefault(); + const normalized = normalizeWheel(event); + element.scrollBy(0, normalized.pixelY * -1); + return false; + }, { passive: false }); + + window.addEventListener('keydown', this.invertedKeyHandler, { passive: false }); + } + this.restore(); + } + + resetScroll() { + if (!this.window) { + return; + } + this.window.scrollTop = 0; + this.savedIndex = null; + this.savedDistance = 0; + this.saveDepth = 0; + } + loadTop = _.throttle(() => this.loadRows(false), 100); + loadBottom = _.throttle(() => this.loadRows(true), 100); + + loadRows = async (newer: boolean) => { + const dir = newer ? 'bottom' : 'top'; + if(this.state.loaded[dir]) { + return; + } + log('network', `loading more at ${dir}`); + const done = await this.props.loadRows(newer); + if(done) { + this.setState({ + loaded: { + ...this.state.loaded, + [dir]: done + } + }); + if(newer && this.props.onBottomLoaded) { + this.props.onBottomLoaded(); + } + } + }; + + onScroll(event: SyntheticEvent) { + this.updateScroll(); + if(!this.window) { + // bail if we're going to adjust scroll anyway + return; + } + if(this.saveDepth > 0) { + log('bail', 'deep scroll queue'); + return; + } + const { onStartReached, onEndReached } = this.props; + const windowHeight = this.window.offsetHeight; + const { scrollTop, scrollHeight } = this.window; + + const startOffset = this.startOffset(); + + if (scrollTop < ZONE_SIZE) { + log('scroll', `Entered start zone ${scrollTop}`); + if (startOffset === 0) { + onStartReached && onStartReached(); + this.scrollLocked = true; + } + + const newOffset = + clamp(startOffset - this.pageDelta, 0, this.props.data.size - this.pageSize); + if(newOffset < 10) { + this.loadBottom(); + } + + if(newOffset !== startOffset) { + this.updateVisible(newOffset); + } + } else if (scrollTop + windowHeight >= scrollHeight - ZONE_SIZE) { + this.scrollLocked = false; + log('scroll', `Entered end zone ${scrollTop}`); + + const newOffset = + clamp(startOffset + this.pageDelta, 0, this.props.data.size - this.pageSize); + if (onEndReached && startOffset === 0) { + onEndReached(); + } + + if((newOffset + (3 * this.pageSize) > this.props.data.size)) { + this.loadTop(); + } + + if(newOffset !== startOffset) { + this.updateVisible(newOffset); + } + } else { + this.scrollLocked = false; + } + } + + restore() { + if(!this.window || !this.savedIndex) { + return; + } + if(this.saveDepth !== 1) { + log('bail', 'Deep restore'); + return; + } + if(this.scrollLocked) { + this.resetScroll(); + requestAnimationFrame(() => { + this.savedIndex = null; + this.savedDistance = 0; + this.saveDepth--; + }); + return; + } + + const ref = this.childRefs.get(arrToString(this.savedIndex)); + if(!ref) { + return; + } + + const newScrollTop = this.props.origin === 'top' + ? this.savedDistance + ref.offsetTop + : this.window.scrollHeight - ref.offsetTop - this.savedDistance; + + this.window.scrollTo(0, newScrollTop); + requestAnimationFrame(() => { + this.savedIndex = null; + this.savedDistance = 0; + this.saveDepth--; + }); + } + + scrollToIndex = (index: BigInteger[]) => { + let ref = this.childRefs.get(arrToString(index)); + if(!ref) { + const offset = [...this.props.data].findIndex(([idx]) => indexEqual(idx, index)); + if(offset === -1) { + return; + } + this.scrollLocked = false; + this.updateVisible(Math.max(offset - this.pageDelta, 0)); + requestAnimationFrame(() => { + ref = this.childRefs.get(arrToString(index)); + requestAnimationFrame(() => { + this.savedIndex = null; + this.savedDistance = 0; + this.saveDepth = 0; + }); + + ref?.scrollIntoView({ block: 'center' }); + }); + } else { + ref?.scrollIntoView({ block: 'center' }); + requestAnimationFrame(() => { + this.savedIndex = null; + this.savedDistance = 0; + this.saveDepth = 0; + }); + } + }; + + save() { + if(!this.window || this.savedIndex) { + return; + } + if(this.saveDepth !== 0) { + return; + } + + log('scroll', 'saving...'); + + this.saveDepth++; + const { visibleItems } = this.state; + + let bottomIndex = visibleItems[visibleItems.length - 1]; + const { scrollTop, scrollHeight } = this.window; + const topSpacing = this.props.origin === 'top' ? scrollTop : scrollHeight - scrollTop; + const items = this.props.origin === 'top' ? visibleItems : [...visibleItems].reverse(); + items.forEach((index) => { + const el = this.childRefs.get(arrToString(index)); + if(!el) { + return; + } + const { offsetTop } = el; + if(offsetTop < topSpacing) { + bottomIndex = index; + } + }); + + if(!bottomIndex) { + // weird, shouldn't really happen + this.saveDepth--; + log('bail', 'no index found'); + return; + } + + this.savedIndex = bottomIndex; + const ref = this.childRefs.get(arrToString(bottomIndex))!; + if(!ref) { + this.saveDepth--; + log('bail', 'missing ref'); + return; + } + const { offsetTop } = ref; + this.savedDistance = topSpacing - offsetTop; + } + + // disabled until we work out race conditions with loading new nodes + shiftLayout = { save: () => {}, restore: () => {} }; + + setRef = (element: HTMLElement | null, index: BigInteger[]) => { + if(element) { + this.childRefs.set(arrToString(index), element); + this.orphans.delete(arrToString(index)); + } else { + this.orphans.add(arrToString(index)); + } + } + + render() { + const { + visibleItems + } = this.state; + + const { + origin = 'top', + renderer, + style + } = this.props; + + const isTop = origin === 'top'; + + const transform = isTop ? 'scale3d(1, 1, 1)' : 'scale3d(1, -1, 1)'; + const children = isTop ? visibleItems : [...visibleItems].reverse(); + + const atStart = + indexEqual( + (this.props.data.peekLargest()?.[0] ?? [bigInt.zero]), + (visibleItems?.[0] || [bigInt.zero]) + ); + + const atEnd = + indexEqual( + (this.props.data.peekSmallest()?.[0] ?? [bigInt.zero]), + (visibleItems?.[visibleItems.length - 1] || [bigInt.zero]) + ); + + return ( + <> + {!IS_IOS && ( { + this.scrollRef = el; +}} +right={0} height="50px" +position="absolute" width="4px" +backgroundColor="lightGray" + />)} + + + + {(isTop ? !atStart : !atEnd) && ( +
+ +
+ )} + + {children.map(index => ( + + ))} + + {(!isTop ? !atStart : !atEnd) && + (
+ +
)} +
+
+ + ); + } +} + +interface VirtualChildProps { + index: BigInteger[]; + scrollWindow: any; + setRef: (el: HTMLElement | null, index: BigInteger[]) => void; + renderer: (p: RendererProps) => JSX.Element | null; +} + +function VirtualChild(props: VirtualChildProps) { + const { setRef, renderer: Renderer, ...rest } = props; + + const ref = useCallback((el: HTMLElement | null) => { + setRef(el, props.index); + // VirtualChild should always be keyed on the index, so the index should be + // valid for the entire lifecycle of the component, hence no dependencies + }, []); + + return ; +} + diff --git a/pkg/interface/src/views/components/AsyncButton.tsx b/pkg/interface/src/views/components/AsyncButton.tsx index bc1120b12e..d6341ff21e 100644 --- a/pkg/interface/src/views/components/AsyncButton.tsx +++ b/pkg/interface/src/views/components/AsyncButton.tsx @@ -1,8 +1,6 @@ -import React, { useState, useEffect } from 'react'; - import { Button, LoadingSpinner } from '@tlon/indigo-react'; - import { useFormikContext } from 'formik'; +import React, { useEffect, useState } from 'react'; export function AsyncButton({ children, diff --git a/pkg/interface/src/views/components/Author.tsx b/pkg/interface/src/views/components/Author.tsx index 41d0fb723b..6cfd8b5d49 100644 --- a/pkg/interface/src/views/components/Author.tsx +++ b/pkg/interface/src/views/components/Author.tsx @@ -1,22 +1,18 @@ -import React, { ReactElement, ReactNode, useState } from 'react'; +import { BaseImage, Box, Row } from '@tlon/indigo-react'; import moment from 'moment'; -import { useHistory } from 'react-router-dom'; - -import { Col, Row, Box, BaseImage } from '@tlon/indigo-react'; -import { Contacts } from '@urbit/api/contacts'; -import { Group } from '@urbit/api'; - -import { uxToHex, cite, useShowNickname, deSig } from '~/logic/lib/util'; -import useSettingsState, {selectCalmState} from "~/logic/state/settings"; -import useLocalState from "~/logic/state/local"; +import React, { ReactElement, ReactNode } from 'react'; +import GlobalApi from '~/logic/api/global'; import { Sigil } from '~/logic/lib/sigil'; -import Timestamp from './Timestamp'; -import useContactState from '~/logic/state/contact'; import { useCopy } from '~/logic/lib/useCopy'; -import ProfileOverlay from './ProfileOverlay'; +import { cite, deSig, useShowNickname, uxToHex } from '~/logic/lib/util'; +import useContactState from '~/logic/state/contact'; +import useLocalState from '~/logic/state/local'; +import useSettingsState, { selectCalmState } from '~/logic/state/settings'; import { PropFunc } from '~/types'; +import ProfileOverlay from './ProfileOverlay'; +import Timestamp from './Timestamp'; -interface AuthorProps { +export interface AuthorProps { ship: string; date?: number; showImage?: boolean; @@ -24,6 +20,8 @@ interface AuthorProps { unread?: boolean; api?: GlobalApi; size?: number; + lineHeight?: string | number; + isRelativeTime?: boolean; } // eslint-disable-next-line max-lines-per-function @@ -35,21 +33,20 @@ export default function Author(props: AuthorProps & PropFunc): React fullNotIcon, children, unread, - group, isRelativeTime, dontShowTime, + lineHeight = 'tall', ...rest } = props; - const time = props.time || false; + const time = props.time || props.date || false; const size = props.size || 16; const sigilPadding = props.sigilPadding || 2; - const history = useHistory(); - const osDark = useLocalState((state) => state.dark); + const osDark = useLocalState(state => state.dark); const theme = useSettingsState(s => s.display.theme); - const dark = theme === 'dark' || (theme === 'auto' && osDark) + const dark = theme === 'dark' || (theme === 'auto' && osDark); let contact; const contacts = useContactState(state => state.contacts); @@ -61,13 +58,7 @@ export default function Author(props: AuthorProps & PropFunc): React const { hideAvatars } = useSettingsState(selectCalmState); const name = showNickname && contact ? contact.nickname : cite(ship); const stamp = moment(date); - const { copyDisplay, doCopy, didCopy } = useCopy(`~${ship}`, name); - - const [showOverlay, setShowOverlay] = useState(false); - - const toggleOverlay = () => { - setShowOverlay(value => !value); - }; + const { copyDisplay, doCopy } = useCopy(`~${ship}`, name); const sigil = fullNotIcon ? ( @@ -89,13 +80,10 @@ export default function Author(props: AuthorProps & PropFunc): React ) : sigil; return ( - + { - e.stopPropagation(); - toggleOverlay(); - }} - height={size} + height={`${size}px`} + overflow='hidden' position='relative' cursor='pointer' > @@ -105,30 +93,34 @@ export default function Author(props: AuthorProps & PropFunc): React )} - - {copyDisplay} + + + {copyDisplay} + + { !dontShowTime && time && ( + + )} + {children} - { !dontShowTime && time && ( - - )} - {children} ); } diff --git a/pkg/interface/src/views/components/Body.tsx b/pkg/interface/src/views/components/Body.tsx index 1f33b95752..ca119bc516 100644 --- a/pkg/interface/src/views/components/Body.tsx +++ b/pkg/interface/src/views/components/Body.tsx @@ -1,6 +1,5 @@ -import React, { ReactNode } from 'react'; - import { Box } from '@tlon/indigo-react'; +import React, { ReactNode } from 'react'; export function Body( props: { children: ReactNode } & Parameters[0] diff --git a/pkg/interface/src/views/components/ChipInput.tsx b/pkg/interface/src/views/components/ChipInput.tsx index 02471d817d..bc075d86f2 100644 --- a/pkg/interface/src/views/components/ChipInput.tsx +++ b/pkg/interface/src/views/components/ChipInput.tsx @@ -1,31 +1,28 @@ -import React, { - useCallback, - useState, - ReactNode, - useEffect, - useRef, - ReactElement -} from 'react'; +import { + Col, + + ErrorLabel, Label, + Row, + + StatelessTextInput as Input +} from '@tlon/indigo-react'; import { useField } from 'formik'; import Mousetrap from 'mousetrap'; +import React, { + ReactElement, ReactNode, useCallback, -import { - Label, - Row, - Col, - StatelessTextInput as Input, - ErrorLabel -} from '@tlon/indigo-react'; - + useEffect, + useRef, useState +} from 'react'; function Chip(props: { children: ReactNode }): ReactElement { return ( @@ -96,15 +93,15 @@ export function ChipInput(props: ChipInputProps): ReactElement { }, [inputRef.current, addNewChip, newChip]); return ( - + {caption && } - + {meta.error} diff --git a/pkg/interface/src/views/components/ColorInput.tsx b/pkg/interface/src/views/components/ColorInput.tsx index 0a4036e7c6..935bf39b8f 100644 --- a/pkg/interface/src/views/components/ColorInput.tsx +++ b/pkg/interface/src/views/components/ColorInput.tsx @@ -1,18 +1,16 @@ -import React, { FormEvent, ReactElement } from 'react'; -import { useField } from 'formik'; - import { - Col, - Label, - Row, - Box, - ErrorLabel, - StatelessTextInput as Input -} from '@tlon/indigo-react'; + Box, Col, + ErrorLabel, Label, + Row, + + StatelessTextInput as Input +} from '@tlon/indigo-react'; +import { useField } from 'formik'; +import React, { FormEvent } from 'react'; import { hexToUx } from '~/logic/lib/util'; -type ColorInputProps = Parameters[0] & { +export type ColorInputProps = Parameters[0] & { id: string; label?: string; placeholder?: string; @@ -42,11 +40,11 @@ export function ColorInput(props: ColorInputProps) { {caption ? ( - - + {meta.error}
diff --git a/pkg/interface/src/views/components/CommentInput.tsx b/pkg/interface/src/views/components/CommentInput.tsx index df61fc1618..9e9809288b 100644 --- a/pkg/interface/src/views/components/CommentInput.tsx +++ b/pkg/interface/src/views/components/CommentInput.tsx @@ -1,8 +1,8 @@ +import { ManagedTextAreaField as TextArea } from '@tlon/indigo-react'; +import { Form, Formik, FormikHelpers, useFormikContext } from 'formik'; import React from 'react'; import * as Yup from 'yup'; -import { Formik, FormikHelpers, Form, useFormikContext } from 'formik'; import { AsyncButton } from './AsyncButton'; -import { ManagedTextAreaField as TextArea } from '@tlon/indigo-react'; interface FormSchema { comment: string; diff --git a/pkg/interface/src/views/components/CommentItem.tsx b/pkg/interface/src/views/components/CommentItem.tsx index f652731895..68401d2f98 100644 --- a/pkg/interface/src/views/components/CommentItem.tsx +++ b/pkg/interface/src/views/components/CommentItem.tsx @@ -1,26 +1,17 @@ -import React, {useEffect, useRef, useCallback} from 'react'; -import { Link, useHistory } from 'react-router-dom'; -import styled from 'styled-components'; - -import { Box, Row, Text, Action } from '@tlon/indigo-react'; -import { Contacts } from '@urbit/api/contacts'; -import { GraphNode } from '@urbit/api/graph'; +import { Action, Box, Row, Text } from '@tlon/indigo-react'; import { Group } from '@urbit/api'; - +import { GraphNode } from '@urbit/api/graph'; +import bigInt from 'big-integer'; +import React, { useCallback, useEffect, useRef } from 'react'; +import { Link } from 'react-router-dom'; import GlobalApi from '~/logic/api/global'; -import Author from '~/views/components/Author'; -import { MentionText } from '~/views/components/MentionText'; import { roleForShip } from '~/logic/lib/group'; +import { getPermalinkForGraph } from '~/logic/lib/permalinks'; import { getLatestCommentRevision } from '~/logic/lib/publish'; -import {useCopy} from '~/logic/lib/useCopy'; -import { getPermalinkForGraph} from '~/logic/lib/permalinks'; +import { useCopy } from '~/logic/lib/useCopy'; import useMetadataState from '~/logic/state/metadata'; -import {GraphContentWide} from '../landscape/components/Graph/GraphContentWide'; - -const ClickBox = styled(Box)` - cursor: pointer; - padding-left: ${p => p.theme.space[2]}px; -`; +import Author from '~/views/components/Author'; +import { GraphContent } from '../landscape/components/Graph/GraphContent'; interface CommentItemProps { pending?: boolean; @@ -34,19 +25,46 @@ interface CommentItemProps { highlighted: boolean; } -export function CommentItem(props: CommentItemProps): ReactElement { +export function CommentItem(props: CommentItemProps) { + let { highlighted } = props; const { ship, name, api, comment, group } = props; const association = useMetadataState( useCallback(s => s.associations.graph[`/ship/${ship}/${name}`], [ship,name]) ); - const ref = useRef(null); + const ref = useRef(null); const [, post] = getLatestCommentRevision(comment); const disabled = props.pending; const onDelete = async () => { - await api.graph.removeNodes(ship, name, [comment.post?.index]); + const revs = comment.children.get(bigInt(1)); + const children = Array.from(revs.children); + const indices = []; + for (const child in children) { + const node = children[child] as any; + if (!node?.post || typeof node.post !== 'string') { + indices.push(node.post?.index); + } + } + + await api.graph.removePosts(ship, name, [ + comment.post?.index, + revs?.post?.index, + ...indices + ]); }; + const ourMention = post?.contents?.some((e) => { + if (!('mention' in e)) +return false; + return e?.mention && e?.mention === window.ship; + }); + + if (!highlighted) { + if (ourMention) { + highlighted = true; + } + } + const commentIndexArray = (comment.post?.index || '/').split('/'); const commentIndex = commentIndexArray[commentIndexArray.length - 1]; @@ -54,62 +72,70 @@ export function CommentItem(props: CommentItemProps): ReactElement { const ourRole = roleForShip(group, window.ship); if (window.ship == post?.author && !disabled) { adminLinks.push( - + Update - ) - }; + ); + } - if ((window.ship == post?.author || ourRole == "admin") && !disabled) { + if ((window.ship == post?.author || ourRole == 'admin') && !disabled) { adminLinks.push( Delete - ) - }; + ); + } useEffect(() => { if(ref.current && props.highlighted) { ref.current.scrollIntoView({ block: 'center' }); } }, [ref, props.highlighted]); - const history = useHistory(); const { copyDisplay, doCopy } = useCopy( getPermalinkForGraph( association.group, association.resource, - post.index.split('/').slice(0, -1).join('/') + post?.index?.split('/').slice(0, -1).join('/') ), 'Copy Link' ); + if (!post || typeof post === 'string' || typeof comment.post === 'string') { + return ( + + This comment has been deleted. + + ); + } + return ( - + - + {copyDisplay} {adminLinks} - diff --git a/pkg/interface/src/views/components/Comments.tsx b/pkg/interface/src/views/components/Comments.tsx index 16c6bf2db2..c9e0a6060b 100644 --- a/pkg/interface/src/views/components/Comments.tsx +++ b/pkg/interface/src/views/components/Comments.tsx @@ -1,21 +1,20 @@ -import React, { useEffect, useMemo } from 'react'; -import bigInt from 'big-integer'; import { Col } from '@tlon/indigo-react'; -import { CommentItem } from './CommentItem'; -import CommentInput from './CommentInput'; -import { Contacts } from '@urbit/api/contacts'; -import GlobalApi from '~/logic/api/global'; +import { Association, GraphNode, Group } from '@urbit/api'; +import bigInt from 'big-integer'; import { FormikHelpers } from 'formik'; -import { Group, GraphNode, Association } from '@urbit/api'; -import { createPost, createBlankNodeWithChildPost } from '~/logic/api/graph'; +import React, { useEffect, useMemo } from 'react'; +import GlobalApi from '~/logic/api/global'; +import { createBlankNodeWithChildPost, createPost } from '~/logic/api/graph'; +import { isWriter } from '~/logic/lib/group'; +import { getUnreadCount } from '~/logic/lib/hark'; +import { referenceToPermalink } from '~/logic/lib/permalinks'; import { getLatestCommentRevision } from '~/logic/lib/publish'; import tokenizeMessage from '~/logic/lib/tokenizeMessage'; -import { getUnreadCount } from '~/logic/lib/hark'; -import { PropFunc } from '~/types/util'; -import { isWriter } from '~/logic/lib/group'; +import { useQuery } from '~/logic/lib/useQuery'; import useHarkState from '~/logic/state/hark'; -import {useQuery} from '~/logic/lib/useQuery'; -import {referenceToPermalink} from '~/logic/lib/permalinks'; +import { PropFunc } from '~/types/util'; +import CommentInput from './CommentInput'; +import { CommentItem } from './CommentItem'; interface CommentsProps { comments: GraphNode; @@ -42,16 +41,15 @@ export function Comments(props: CommentsProps & PropFunc) { const { query } = useQuery(); const selectedComment = useMemo(() => { - const id = query.get('selected') + const id = query.get('selected'); return id ? bigInt(id) : null; }, [query]); const editCommentId = useMemo(() => { - const id = query.get('edit') + const id = query.get('edit'); return id || ''; }, [query]); - const onSubmit = async ( { comment }, actions: FormikHelpers<{ comment: string }> @@ -84,7 +82,7 @@ export function Comments(props: CommentsProps & PropFunc) { const post = createPost( content, commentNode.post.index, - parseInt(idx + 1, 10) + parseInt((idx + 1).toString(), 10).toString() ); await api.graph.addPost(ship, name, post); history.push(baseUrl); @@ -130,7 +128,7 @@ export function Comments(props: CommentsProps & PropFunc) { const canComment = isWriter(group, association.resource) || association.metadata.vip === 'reader-comments'; return ( - + {( !editCommentId && canComment ? : null )} {( editCommentId ? ( (null); - const anchorRef = useRef(null); + const { children, options, offsetX = 0, offsetY = 0, flexShrink = 1 } = props; + const dropdownRef = useRef(null); + const anchorRef = useRef(null); const { pathname } = useLocation(); const [open, setOpen] = useState(false); const [coords, setCoords] = useState({}); const updatePos = useCallback(() => { + if(!anchorRef.current) { + return; + } const newCoords = getRelativePosition(anchorRef.current, props.alignX, props.alignY, offsetX, offsetY); if(newCoords) { setCoords(newCoords); @@ -86,7 +84,7 @@ export function Dropdown(props: DropdownProps): ReactElement { }, []); return ( - + {children} diff --git a/pkg/interface/src/views/components/DropdownSearch.tsx b/pkg/interface/src/views/components/DropdownSearch.tsx index 39a69c49ed..e2ba2dd2e2 100644 --- a/pkg/interface/src/views/components/DropdownSearch.tsx +++ b/pkg/interface/src/views/components/DropdownSearch.tsx @@ -1,20 +1,15 @@ -import React, { - useRef, - useState, - useMemo, - useCallback, - useEffect, - ChangeEvent, - ReactElement -} from 'react'; +import { + Box, + StatelessTextInput as Input +} from '@tlon/indigo-react'; import _ from 'lodash'; import Mousetrap from 'mousetrap'; - -import { - Box, - StatelessTextInput as Input -} from '@tlon/indigo-react'; - +import React, { + ChangeEvent, + ReactElement, useCallback, + useEffect, useMemo, useRef, + useState +} from 'react'; import { useDropdown } from '~/logic/lib/useDropdown'; import { PropFunc } from '~/types/util'; @@ -45,7 +40,7 @@ type DropdownSearchProps = PropFunc & DropdownSearchExtraProps; export function DropdownSearch(props: DropdownSearchProps): ReactElement { - const textarea = useRef(); + const textarea = useRef(null); const { candidates, getKey, @@ -134,6 +129,7 @@ export function DropdownSearch(props: DropdownSearchProps): ReactElement { return ( + { /* @ts-ignore investigate onblur on styled-system component later */} (props: DropdownSearchProps): ReactElement { /> {dropdown.length !== 0 && query.length !== 0 && ( { body =`\`\`\`%0A${error.stack?.replaceAll('\n', '%0A')}%0A\`\`\``; } return ( - + {code ? code : 'Error'} diff --git a/pkg/interface/src/views/components/ErrorBoundary.tsx b/pkg/interface/src/views/components/ErrorBoundary.tsx index 6ab7516a6c..2c7ab28db1 100644 --- a/pkg/interface/src/views/components/ErrorBoundary.tsx +++ b/pkg/interface/src/views/components/ErrorBoundary.tsx @@ -1,8 +1,8 @@ import React, { Component } from 'react'; import { RouteComponentProps, withRouter } from 'react-router-dom'; import StackTrace from 'stacktrace-js'; -import ErrorComponent from './Error'; import { Spinner } from '~/views/components/Spinner'; +import ErrorComponent from './Error'; class ErrorBoundary extends Component< RouteComponentProps, @@ -23,12 +23,12 @@ class ErrorBoundary extends Component< componentDidCatch(error: Error) { this.setState({ error: true }); - StackTrace.fromError(error).then(stackframes => { - const stack = stackframes.map(frame => { + StackTrace.fromError(error).then((stackframes) => { + const stack = stackframes.map((frame) => { return `${frame.functionName} (${frame.fileName} ${frame.lineNumber}:${frame.columnNumber})`; }).join('\n'); error = { name: error.name, message: error.message, stack }; - this.setState({ error }) + this.setState({ error }); }); return false; } diff --git a/pkg/interface/src/views/components/FormError.tsx b/pkg/interface/src/views/components/FormError.tsx index ed10100a86..e34839874f 100644 --- a/pkg/interface/src/views/components/FormError.tsx +++ b/pkg/interface/src/views/components/FormError.tsx @@ -1,6 +1,6 @@ -import React from 'react'; -import { useFormikContext } from 'formik'; import { ErrorLabel } from '@tlon/indigo-react'; +import { useFormikContext } from 'formik'; +import React from 'react'; import { PropFunc } from '~/types/util'; export function FormError(props: { message?: string } & PropFunc) { diff --git a/pkg/interface/src/views/components/FormGroup.tsx b/pkg/interface/src/views/components/FormGroup.tsx index 9295355d6b..84a5cc0910 100644 --- a/pkg/interface/src/views/components/FormGroup.tsx +++ b/pkg/interface/src/views/components/FormGroup.tsx @@ -1,18 +1,16 @@ +import { Box, Button, Row } from '@tlon/indigo-react'; +import { useFormikContext } from 'formik'; +import _ from 'lodash'; import React, { - ReactNode, - useEffect, - useCallback, - useState, - useMemo, -} from "react"; -import { Button, Box, Row, Col } from "@tlon/indigo-react"; -import _ from "lodash"; -import { useFormikContext } from "formik"; -import { PropFunc } from "~/types"; -import { FormGroupContext, SubmitHandler } from "~/logic/lib/formGroup"; -import { StatelessAsyncButton } from "./StatelessAsyncButton"; -import { Prompt } from "react-router-dom"; -import { usePreventWindowUnload } from "~/logic/lib/util"; + useCallback, useEffect, + + useMemo, useState +} from 'react'; +import { Prompt } from 'react-router-dom'; +import { FormGroupContext, SubmitHandler } from '~/logic/lib/formGroup'; +import { usePreventWindowUnload } from '~/logic/lib/util'; +import { PropFunc } from '~/types'; +import { StatelessAsyncButton } from './StatelessAsyncButton'; export function useFormGroupContext(id: string) { const ctx = React.useContext(FormGroupContext); @@ -47,7 +45,7 @@ export function useFormGroupContext(id: string) { onDirty, addSubmit, onErrors, - addReset, + addReset }; } @@ -69,7 +67,6 @@ export function FormGroupChild(props: { id: string }) { resetForm({ touched: {}, values }); } addSubmit(submit); - }, [submitForm, values]); useEffect(() => { @@ -77,7 +74,7 @@ export function FormGroupChild(props: { id: string }) { }, [dirty, onDirty]); useEffect(() => { - onErrors(_.keys(_.pickBy(errors, (s) => !!s)).length > 0); + onErrors(_.keys(_.pickBy(errors, s => Boolean(s))).length > 0); }, [errors, onErrors]); useEffect(() => { @@ -97,11 +94,11 @@ export function FormGroup(props: { onReset?: () => void; } & PropFunc); const [errors, setErrors] = useState({} as Record); const addSubmit = useCallback((id: string, s: SubmitHandler) => { - setSubmits((ss) => ({ ...ss, [id]: s })); + setSubmits(ss => ({ ...ss, [id]: s })); }, []); const resetAll = useCallback(() => { - _.map(resets, (r) => r()); + _.map(resets, r => r()); onReset && onReset(); }, [resets, onReset]); @@ -109,29 +106,29 @@ export function FormGroup(props: { onReset?: () => void; } & PropFunc dirty[k]), - (f) => f() + f => f() ) ); }, [submits, dirty]); const onDirty = useCallback( (id: string, t: boolean) => { - setDirty((ts) => ({ ...ts, [id]: t })); + setDirty(ts => ({ ...ts, [id]: t })); }, [setDirty] ); const onErrors = useCallback((id: string, e: boolean) => { - setErrors((es) => ({ ...es, [id]: e })); + setErrors(es => ({ ...es, [id]: e })); }, []); const addReset = useCallback((id: string, reset: () => void) => { - setResets((rs) => ({ ...rs, [id]: reset })); + setResets(rs => ({ ...rs, [id]: reset })); }, []); const context = { addSubmit, submitAll, onErrors, onDirty, addReset }; const hasErrors = useMemo( - () => _.keys(_.pickBy(errors, (s) => !!s)).length > 0, + () => _.keys(_.pickBy(errors, s => Boolean(s))).length > 0, [errors] ); const isDirty = useMemo( @@ -155,10 +152,10 @@ export function FormGroup(props: { onReset?: () => void; } & PropFunc diff --git a/pkg/interface/src/views/components/FormSubmit.tsx b/pkg/interface/src/views/components/FormSubmit.tsx index 8cad049ab9..ad53f6b3f0 100644 --- a/pkg/interface/src/views/components/FormSubmit.tsx +++ b/pkg/interface/src/views/components/FormSubmit.tsx @@ -1,6 +1,6 @@ -import React, { useCallback, ReactNode, ReactElement } from 'react'; +import { Button, Row } from '@tlon/indigo-react'; import { useFormikContext } from 'formik'; -import { Row, Button } from '@tlon/indigo-react'; +import React, { ReactElement, ReactNode, useCallback } from 'react'; import { AsyncButton } from './AsyncButton'; interface FormSubmitProps { @@ -22,10 +22,10 @@ export function FormSubmit(props: FormSubmitProps): ReactElement { return ( {dirty && !isSubmitting && ( diff --git a/pkg/interface/src/views/components/FormikOnBlur.tsx b/pkg/interface/src/views/components/FormikOnBlur.tsx index 6b444b1b90..9f09d67ff3 100644 --- a/pkg/interface/src/views/components/FormikOnBlur.tsx +++ b/pkg/interface/src/views/components/FormikOnBlur.tsx @@ -1,28 +1,32 @@ -import React, { useImperativeHandle, useEffect } from 'react'; -import { FormikValues, useFormik, FormikProvider, FormikConfig } from 'formik'; +import { FormikConfig, FormikProvider, FormikValues, useFormik } from 'formik'; +import React, { useEffect, useImperativeHandle, useState } from 'react'; export function FormikOnBlur< Values extends FormikValues = FormikValues, ExtraProps = {} >(props: FormikConfig & ExtraProps) { const formikBag = useFormik({ ...props, validateOnBlur: true }); + const [submitting, setSubmitting] = useState(false); useEffect(() => { if ( Object.keys(formikBag.errors || {}).length === 0 && - Object.keys(formikBag.touched || {}).length !== 0 && - !formikBag.isSubmitting + formikBag.dirty && + !formikBag.isSubmitting && + !submitting ) { + setSubmitting(true); const { values } = formikBag; formikBag.submitForm().then(() => { - formikBag.resetForm({ values, touched: {} }); + formikBag.resetForm({ values }) + setSubmitting(false); }); } }, [ formikBag.errors, - formikBag.touched, - formikBag.submitForm, - formikBag.values + formikBag.dirty, + submitting, + formikBag.isSubmitting ]); const { children, innerRef } = props; diff --git a/pkg/interface/src/views/components/GroupLink.tsx b/pkg/interface/src/views/components/GroupLink.tsx index 9e36db9a93..290073385a 100644 --- a/pkg/interface/src/views/components/GroupLink.tsx +++ b/pkg/interface/src/views/components/GroupLink.tsx @@ -1,15 +1,14 @@ -import React, { useEffect, useState, useLayoutEffect, ReactElement } from 'react'; +import { Box, Col, Icon, Row, Text } from '@tlon/indigo-react'; +import { MetadataUpdatePreview } from '@urbit/api'; +import React, { ReactElement, useEffect, useLayoutEffect, useState } from 'react'; import { useHistory } from 'react-router-dom'; -import { Box, Text, Row, Col } from '@tlon/indigo-react'; -import { Associations, Groups } from '@urbit/api'; import GlobalApi from '~/logic/api/global'; -import { MetadataIcon } from '../landscape/components/MetadataIcon'; -import { JoinGroup } from '../landscape/components/JoinGroup'; import { useModal } from '~/logic/lib/useModal'; -import { GroupSummary } from '../landscape/components/GroupSummary'; -import { PropFunc } from '~/types'; +import { useVirtual } from '~/logic/lib/virtualContext'; import useMetadataState from '~/logic/state/metadata'; -import {useVirtual} from '~/logic/lib/virtualContext'; +import { PropFunc } from '~/types'; +import { JoinGroup } from '../landscape/components/JoinGroup'; +import { MetadataIcon } from '../landscape/components/MetadataIcon'; export function GroupLink( props: { @@ -48,26 +47,47 @@ export function GroupLink( }, [preview]); return ( - { e.stopPropagation(); }}> + { + e.stopPropagation(); + }} + backgroundColor='white' + borderColor={props.borderColor} + > {modal} history.push(`/~landscape/ship/${name}`) : showModal } - cursor='pointer' opacity={preview ? '1' : '0.6'} > - + - + {preview ? preview.metadata.title : name} - {preview ? `${preview.members} members` : "Fetching member count"} + + {preview ? + <> + + + + {preview.members} + {' '} + {preview.members > 1 ? 'peers' : 'peer'} + + + + : Fetching member count} + diff --git a/pkg/interface/src/views/components/GroupSearch.tsx b/pkg/interface/src/views/components/GroupSearch.tsx index 1bf50137ce..563c4e711a 100644 --- a/pkg/interface/src/views/components/GroupSearch.tsx +++ b/pkg/interface/src/views/components/GroupSearch.tsx @@ -1,25 +1,21 @@ -import React, { ReactElement, useMemo, useState } from 'react'; -import { useFormikContext, FieldArray } from 'formik'; -import _ from 'lodash'; -import styled from 'styled-components'; - import { - Box, - Text, - Label, - Row, - Col, - Icon, - ErrorLabel + Box, + + Col, + + ErrorLabel, Icon, Label, + Row, Text } from '@tlon/indigo-react'; -import { Groups } from '@urbit/api'; -import { Associations, Association } from '@urbit/api/metadata'; - - +import { OpenPolicy } from '@urbit/api'; +import { Association } from '@urbit/api/metadata'; +import { FieldArray, useFormikContext } from 'formik'; +import _ from 'lodash'; +import React, { ReactElement, useMemo, useState } from 'react'; +import styled from 'styled-components'; import { roleForShip } from '~/logic/lib/group'; -import { DropdownSearch } from './DropdownSearch'; import useGroupState from '~/logic/state/group'; import useMetadataState from '~/logic/state/metadata'; +import { DropdownSearch } from './DropdownSearch'; interface GroupSearchProps { disabled?: boolean; @@ -105,7 +101,7 @@ export function GroupSearch>(props: Gr return Object.values( Object.keys(associations.groups) .filter( - e => groupState?.[e]?.policy?.open + e => (groupState?.[e]?.policy as OpenPolicy)?.open ) .reduce((obj, key) => { obj[key] = associations.groups[key]; @@ -137,12 +133,12 @@ export function GroupSearch>(props: Gr {caption && ( -