diff --git a/pkg/arvo/app/aqua.hoon b/pkg/arvo/app/aqua.hoon index b530003ae6..ac4cdb1a67 100644 --- a/pkg/arvo/app/aqua.hoon +++ b/pkg/arvo/app/aqua.hoon @@ -30,7 +30,7 @@ == +$ state-0 $: %0 - pil=pill + pil=$>(%pill pill) assembled=* tym=@da fleet-snaps=(map term fleet) @@ -38,11 +38,7 @@ == :: XX temporarily shadowed, fix and remove :: - +$ pill - $: boot-ova=* - kernel-ova=(list unix-event) - userspace-ova=(list unix-event) - == + +$ pill pill:pill-lib :: +$ fleet [ships=(map ship pier) azi=az-state] +$ pier @@ -86,7 +82,7 @@ =^ cards state ?+ mark ~|([%aqua-bad-mark mark] !!) %aqua-events (poke-aqua-events:ac !<((list aqua-event) vase)) - %pill (poke-pill:ac !<(pill vase)) + %pill (poke-pill:ac !<(pill vase)) %noun (poke-noun:ac !<(* vase)) %azimuth-action (poke-azimuth-action:ac !<(azimuth-action vase)) == @@ -183,7 +179,7 @@ ?. processing-events ..abet-pe =^ ue next-events ~(get to next-events) - =/ poke-arm (mox +47.snap) + =/ poke-arm (mox +23.snap) ?> ?=(%0 -.poke-arm) =/ poke p.poke-arm =. tym (max +(tym) now.hid) @@ -202,20 +198,21 @@ :: ++ peek |= p=* - =/ res (mox +46.snap) + =/ res (mox +22.snap) ?> ?=(%0 -.res) =/ peek p.res =/ pax (path p) ?> ?=([@ @ @ @ *] pax) =. i.t.t.t.pax (scot %da tym) - =/ pek (slum peek [tym pax]) - pek - :: + =/ pek (slum peek [[~ ~] & pax]) + =+ ;;(res=(unit (cask)) pek) + (bind res tail) + :: :: Wish :: ++ wish |= txt=@t - =/ res (mox +22.snap) + =/ res (mox +10.snap) ?> ?=(%0 -.res) =/ wish p.res ~& [who=who %wished (slum wish txt)] @@ -373,6 +370,7 @@ ++ poke-pill |= p=pill ^- (quip card:agent:gall _state) + ?< ?=(%ivory -.p) =. this apex-aqua =< abet-aqua =. pil p ~& lent=(met 3 (jam boot-ova.pil)) @@ -411,10 +409,11 @@ :: ?+ val ~|(%bad-noun-arg !!) [%swap-vanes vs=*] - ?> ?=([[%7 * %1 installed=*] ~] boot-ova.pil) - =. installed.boot-ova.pil + ?> ?=(^ boot-ova.pil) + ?> ?=([%7 * %1 installed=*] i.boot-ova.pil) + =. installed.i.boot-ova.pil %+ roll (,(list term) vs.val) - |= [v=term =_installed.boot-ova.pil] + |= [v=term =_installed.i.boot-ova.pil] %^ slum installed now.hid =/ vane ?+ v ~|([%unknown-vane v] !!) @@ -507,28 +506,42 @@ ?- -.ae :: %init-ship - :: XX Note that the keys that get passed in are unused. The keys field - :: should be deleted now that aqua is capable of managing azimuth state - :: internally. Its been left this way for now until all the ph tests - :: can be rewritten - =/ keys=dawn-event:jael (dawn who.ae) =. this abet-pe:(publish-effect:(pe who.ae) [/ %sleep ~]) =/ initted =< plow %- push-events:apex:(pe who.ae) ^- (list unix-event) - :~ [/ %wack 0] :: eny - [/ %whom who.ae] :: eny - [//newt/0v1n.2m9vh %born ~] - [//behn/0v1n.2m9vh %born ~] - :^ //term/1 %boot & - ?~ keys.ae - [%fake who.ae] - [%dawn keys] - -.userspace-ova.pil - [//http-client/0v1n.2m9vh %born ~] - [//http-server/0v1n.2m9vh %born ~] - [//http-server/0v1n.2m9vh %live 8.080 `8.445] + %- zing + :~ + :~ [/ %wack 0] :: eny + :: [/ %verb `|] :: possible verb + :^ / %wyrd [~.nonce /aqua] :: dummy runtime version + nonce + ^- (list (pair term @)) + :~ zuse+zuse + lull+lull + arvo+arvo + hoon+hoon-version + nock+4 + == + [/ %whom who.ae] :: who + == + :: + kernel-ova.pil :: load compiler + :: + :_ ~ + :^ /d/term/1 %boot & + ?: fake.ae + [%fake who.ae] + [%dawn (dawn who.ae)] + :: + userspace-ova.pil :: load os + :: + :~ [/b/behn/0v1n.2m9vh %born ~] + [/i/http-client/0v1n.2m9vh %born ~] + [/e/http-server/0v1n.2m9vh %born ~] + [/e/http-server/0v1n.2m9vh %live 8.080 `8.445] + [/a/newt/0v1n.2m9vh %born ~] + == == =. this abet-pe:initted (pe who.ae) diff --git a/pkg/arvo/gen/aqua/dojo.hoon b/pkg/arvo/gen/aqua/dojo.hoon index 9850f34bf1..03d63f5058 100644 --- a/pkg/arvo/gen/aqua/dojo.hoon +++ b/pkg/arvo/gen/aqua/dojo.hoon @@ -5,10 +5,10 @@ :- %aqua-events %+ turn ^- (list unix-event) - :~ [//term/1 %belt %ctl `@c`%e] - [//term/1 %belt %ctl `@c`%u] - [//term/1 %belt %txt ((list @c) command)] - [//term/1 %belt %ret ~] + :~ [/d/term/1 %belt %ctl `@c`%e] + [/d/term/1 %belt %ctl `@c`%u] + [/d/term/1 %belt %txt ((list @c) command)] + [/d/term/1 %belt %ret ~] == |= ue=unix-event [%event her ue] diff --git a/pkg/arvo/gen/aqua/file.hoon b/pkg/arvo/gen/aqua/file.hoon index 6486b19112..4b0958c5c1 100644 --- a/pkg/arvo/gen/aqua/file.hoon +++ b/pkg/arvo/gen/aqua/file.hoon @@ -7,5 +7,5 @@ :+ %event her ?> ?=([@ @ @ *] pax) =/ file [/text/plain (as-octs:mimes:html .^(@ %cx pax))] -:- //sync/0v1n.2m9vh +:- /c/sync/0v1n.2m9vh [%into `desk`i.t.pax | `mode:clay`[t.t.t.pax `file]~] diff --git a/pkg/arvo/gen/aqua/init.hoon b/pkg/arvo/gen/aqua/init.hoon index 2eec2fba25..4d7f922a2c 100644 --- a/pkg/arvo/gen/aqua/init.hoon +++ b/pkg/arvo/gen/aqua/init.hoon @@ -1,6 +1,8 @@ +:: Start an aqua ship +:: /- aquarium =, aquarium :- %say -|= [* [her=ship ~] ~] +|= [* [her=ship fake=? ~] ~] :- %aqua-events -[%init-ship her `*dawn-event:jael]~ +[%init-ship her fake]~ diff --git a/pkg/arvo/gen/brass.hoon b/pkg/arvo/gen/brass.hoon index 6009d918cc..ae72d14651 100644 --- a/pkg/arvo/gen/brass.hoon +++ b/pkg/arvo/gen/brass.hoon @@ -12,7 +12,7 @@ arg=$@(~ [top=path ~]) ~ == -:- %noun +:- %boot-pill ^- pill:pill :: :: sys: root path to boot system, `/~me/[desk]/now/sys` diff --git a/pkg/arvo/gen/hood/crunch.hoon b/pkg/arvo/gen/hood/crunch.hoon new file mode 100644 index 0000000000..6998e7bf27 --- /dev/null +++ b/pkg/arvo/gen/hood/crunch.hoon @@ -0,0 +1,30 @@ +/- ms=metadata-store +/+ crunch +:- %say +|= [[now=@da * bec=beak] [csv-path=path from=@da ~] [to=@da groups=(list path) content=(unit ?) ~]] +=/ our=@p p.bec +:: check given path has `csv` mark +:: +?> =(%csv (snag (dec (lent csv-path)) csv-path)) +:: get all graph associations ship is a part of +:: +=/ associations=associations:ms + (~(scry-graph-associations crunch [our now])) +:: filter by input groups, if any (default: all from scry) +:: +=/ filtered-associations=associations:ms + ?~ groups + associations + %+ filter-associations-by-group-resources.crunch + associations + (paths-to-resources.crunch groups) +:: walk graphs to extract content +:: +=/ file-content=wain + %: ~(walk-graph-associations crunch [our now]) + filtered-associations + ?~ content %.n u.content + from + ?: =(*@da to) now to + == +[%helm-pass (note-write-csv-to-clay.crunch csv-path file-content)] diff --git a/pkg/arvo/gen/solid.hoon b/pkg/arvo/gen/solid.hoon index d131a73387..5a747004e0 100644 --- a/pkg/arvo/gen/solid.hoon +++ b/pkg/arvo/gen/solid.hoon @@ -29,7 +29,7 @@ :: dub=_| == -:- %pill +:- %boot-pill ^- pill:pill :: sys: root path to boot system, `/~me/[desk]/now/sys` :: bas: root path to boot system' desk diff --git a/pkg/arvo/lib/aqua-azimuth.hoon b/pkg/arvo/lib/aqua-azimuth.hoon index 7a2a46db57..4aec08d1c6 100644 --- a/pkg/arvo/lib/aqua-azimuth.hoon +++ b/pkg/arvo/lib/aqua-azimuth.hoon @@ -122,7 +122,7 @@ :_ ~ :* %event her - //http-client/0v1n.2m9vh + /i/http-client/0v1n.2m9vh %receive num.u.ask [%start [200 ~] `(as-octs:mimes:html resp) &] diff --git a/pkg/arvo/lib/crunch.hoon b/pkg/arvo/lib/crunch.hoon new file mode 100644 index 0000000000..237f1498d3 --- /dev/null +++ b/pkg/arvo/lib/crunch.hoon @@ -0,0 +1,356 @@ +/- c=crunch, gs=graph-store, ms=metadata-store, p=post, r=resource +:: +=< + |_ [our=ship now=@da] + ++ walk-graph-associations + |= [=associations:ms content=? from=@da to=@da] + ^- wain + :: graph resources in `our`; used to avoid scrying, e.g., + :: a graph `our` has left and can no longer access + :: + =/ accessible-graphs=(set resource:r) (scry-graph-resources) + %- ~(rep by associations) + |= [[=md-resource:ms =association:ms] out=wain] + ^- wain + ?. ?=(%graph app-name.md-resource) + out + ?. ?=(%graph -.config.metadatum.association) + out + :: ensure graph, given by association, exists in `our` + :: + ?. (~(has in accessible-graphs) resource.md-resource) + out + :: scry the graph + :: + =/ graph=(unit graph:gs) (scry-graph resource.md-resource) + ?~ graph + out + :: prepare channel-info argument + :: + =/ channel-info=channel-info:c + :* group.association + resource.md-resource + module.config.metadatum.association + == + :: walk the graph + :: + ?+ module.config.metadatum.association + :: non-chat (e.g. links & notes) + :: + %+ weld out + %: walk-nested-graph-for-most-recent-entries + u.graph + content + channel-info + from + to + == + :: + %chat + %+ weld out + %: walk-chat-graph + u.graph + content + channel-info + from + to + == + == + :: + ++ scry-graph + |= graph-resource=resource:r + ^- (unit graph:gs) + =/ scry-response=update:gs + .^ update:gs + %gx + (scot %p our) + %graph-store + (scot %da now) + %graph + (scot %p entity.graph-resource) + name.graph-resource + /noun + == + ?. ?=(%add-graph -.q.scry-response) + ~ + ?~ graph.q.scry-response + ~ + [~ graph.q.scry-response] + :: + ++ scry-graph-resources + |= ~ + ^- (set resource:r) + =/ scry-response=update:gs + .^ update:gs + %gx + (scot %p our) + %graph-store + (scot %da now) + /keys/noun + == + ?. ?=(%keys -.q.scry-response) + ~ + resources.q.scry-response + :: helper arm for callers to get graph associations + :: to pass to `walk-graph-associations` + :: + ++ scry-graph-associations + |= ~ + ^- associations:ms + .^ associations:ms + %gx + (scot %p our) + %metadata-store + (scot %da now) + /app-name/graph/noun + == + -- +:: +|% +:: +:: parsing and formatting +:: +++ resource-to-cord + |= =resource:r + ^- @t + (rap 3 (scot %p entity.resource) '/' (scot %tas name.resource) ~) +:: +++ paths-to-resources + |= paxs=(list path) + ^- (set resource:r) + %- ~(gas in *(set resource:r)) + (turn paxs path-to-resource) +:: +++ path-to-resource + |= pax=path + ^- resource:r + =/ entity=@p (slav %p -.pax) + =/ name=@tas -.+.pax + [entity name] +:: +++ escape-characters-in-cord + |= =cord + ^- @t + %- crip + %- mesc + :: specific to CSVs: make sure content does not + :: contain commas (only allowed as delimiters) + :: + %- replace-tape-commas-with-semicolons + %- trip + cord +:: +++ replace-tape-commas-with-semicolons + |= string=tape + ^- tape + =/ comma-indices=(list @ud) (fand "," string) + |- + ^- tape + ?~ comma-indices + string + $(string (snap string i.comma-indices ';'), comma-indices t.comma-indices) +:: +++ contents-to-cord + |= contents=(list content:p) + ^- @t + ?~ contents + '' + %+ join-cords + ' ' + (turn contents content-to-cord) +:: +++ content-to-cord + |= =content:p + ^- @t + ?- -.content + %text (escape-characters-in-cord text.content) + %mention (scot %p ship.content) + %url url.content + %code expression.content :: TODO: also print output? + %reference (reference-content-to-cord reference.content) + == +:: +++ reference-content-to-cord + |= =reference:p + ^- @t + ?- -.reference + %group (resource-to-cord group.reference) + %graph (rap 3 (resource-to-cord group.reference) ': ' (resource-to-cord resource.uid.reference) ~) + == +:: +++ format-post-to-comma-separated-cord + |= [=post:gs =channel-info:c] + ^- @t + %+ join-cords + ',' + :~ (scot %da time-sent.post) + (scot %p author.post) + (resource-to-cord group.channel-info) + (resource-to-cord channel.channel-info) + (scot %tas channel-type.channel-info) + :: exclude content; optionally add later + :: + == +:: +++ join-cords + |= [delimiter=@t cords=(list @t)] + ^- @t + %+ roll cords + |= [cord=@t out=@t] + ^- @t + ?: =('' out) + :: don't put delimiter before first element + :: + cord + (rap 3 out delimiter cord ~) +:: +:: walking graphs +:: +++ walk-chat-graph + |= [=graph:gs content=? =channel-info:c from=@da to=@da] + ^- wain + %- flop + %+ roll + :: filter by time + :: + %+ only-nodes-older-than to + %+ only-nodes-newer-than from + ~(val by graph) + |= [=node:gs out=wain] + ^- wain + ?- -.post.node + %| + :: do not output deleted posts + :: + out + %& + ?~ contents.p.post.node + :: do not output structural nodes + :: + out + :_ out + =/ post-no-content=@t (format-post-to-comma-separated-cord p.post.node channel-info) + ?- content + %| post-no-content + %& + %+ join-cords ',' + ~[post-no-content (contents-to-cord contents.p.post.node)] + == + == +:: +++ walk-nested-graph-for-most-recent-entries + |= [=graph:gs content=? =channel-info:c from=@da to=@da] + ^- wain + =| out=wain + =| most-recent-post-content=@t + =/ nodes + :: filter by time + :: + %+ only-nodes-older-than to + %+ only-nodes-newer-than from + ~(val by graph) + %- flop + |- + ^- wain + ?~ nodes + ?: =('' most-recent-post-content) + :: don't return a cell: `['' ~]` + :: we want either an empty list `~` + :: or a list populated with actual entries + :: + out + [most-recent-post-content out] + :: + =? out ?=(%graph -.children.i.nodes) + %+ weld out + %: walk-nested-graph-for-most-recent-entries + p.children.i.nodes + content + channel-info + from + to + == + :: + ?- -.post.i.nodes + %| + :: do not keep deleted posts + :: + $(nodes t.nodes) + %& + ?~ contents.p.post.i.nodes + :: do not keep structural nodes + :: + $(nodes t.nodes) + =/ post-no-content=@t (format-post-to-comma-separated-cord p.post.i.nodes channel-info) + %= $ + nodes t.nodes + most-recent-post-content + ?- content + %| post-no-content + %& + %+ join-cords ',' + ~[post-no-content (contents-to-cord contents.p.post.i.nodes)] + == + == + == +:: +:: filters +:: +++ filter-associations-by-group-resources + |= [=associations:ms group-resources=(set resource:r)] + ^- associations:ms + %- ~(rep by associations) + |= [[=md-resource:ms =association:ms] out=associations:ms] + ^- associations:ms + ?. (~(has in group-resources) group.association) + out + (~(put by out) md-resource association) +:: wrappers for intuitive use of `filter-nodes-by-timestamp`: +:: pass `nodes` as given by the `graph-store` scry and no +:: need to worry about comparators +:: +++ only-nodes-older-than + |= [time=@da nodes=(list node:gs)] + (filter-nodes-by-timestamp nodes lte time) +:: +++ only-nodes-newer-than + |= [time=@da nodes=(list node:gs)] + %- flop + (filter-nodes-by-timestamp (flop nodes) gte time) +:: +++ filter-nodes-by-timestamp + |= [nodes=(list node:gs) comparator=$-([@ @] ?) time=@da] + =| out=(list node:gs) + :: return `out` in same time-order as `nodes` + :: + %- flop + |- + ^- (list node:gs) + ?~ nodes + out + ?- -.post.i.nodes + %| + :: skip deleted posts + :: + $(nodes t.nodes) + %& + ?. (comparator time-sent.p.post.i.nodes time) + :: assume: + :: * time is monotonic + :: * first `%.n` we hit indicates nodes further on are `%.n` + :: (i.e. `nodes` must be ordered st. they start `%.y`, + :: e.g. if want all `nodes` older than given time, + :: `nodes` must start with oldest and comparator is `lth`) + :: + out + $(nodes t.nodes, out [i.nodes out]) + == +:: +:: io +:: +++ note-write-csv-to-clay + |= [pax=path file-content=wain] + ?> =(%csv (snag (dec (lent pax)) pax)) + [%c [%info %home %& [pax %ins %csv !>(file-content)]~]] +:: +-- diff --git a/pkg/arvo/lib/ph/io.hoon b/pkg/arvo/lib/ph/io.hoon deleted file mode 120000 index 9b660df8f5..0000000000 --- a/pkg/arvo/lib/ph/io.hoon +++ /dev/null @@ -1 +0,0 @@ -../../../base-dev/lib/ph/io.hoon \ No newline at end of file diff --git a/pkg/arvo/lib/ph/util.hoon b/pkg/arvo/lib/ph/util.hoon deleted file mode 120000 index ec423efc03..0000000000 --- a/pkg/arvo/lib/ph/util.hoon +++ /dev/null @@ -1 +0,0 @@ -../../../base-dev/lib/ph/util.hoon \ No newline at end of file diff --git a/pkg/arvo/lib/pill.hoon b/pkg/arvo/lib/pill.hoon deleted file mode 120000 index 41e912cf19..0000000000 --- a/pkg/arvo/lib/pill.hoon +++ /dev/null @@ -1 +0,0 @@ -../../base-dev/lib/pill.hoon \ No newline at end of file diff --git a/pkg/arvo/mar/csv.hoon b/pkg/arvo/mar/csv.hoon new file mode 100644 index 0000000000..15445aad90 --- /dev/null +++ b/pkg/arvo/mar/csv.hoon @@ -0,0 +1,15 @@ +=, format +=, mimes:html +|_ csv=wain +:: +++ grab :: convert from + |% + ++ mime |=((pair mite octs) (to-wain q.q)) + ++ noun wain :: clam from %noun + -- +++ grow + |% + ++ mime [/text/csv (as-octs (of-wain csv))] + -- +++ grad %mime +-- diff --git a/pkg/arvo/sur/aquarium.hoon b/pkg/arvo/sur/aquarium.hoon deleted file mode 120000 index 1195b2c62e..0000000000 --- a/pkg/arvo/sur/aquarium.hoon +++ /dev/null @@ -1 +0,0 @@ -../../base-dev/sur/aquarium.hoon \ No newline at end of file diff --git a/pkg/arvo/sur/crunch.hoon b/pkg/arvo/sur/crunch.hoon new file mode 100644 index 0000000000..e2d5a5a735 --- /dev/null +++ b/pkg/arvo/sur/crunch.hoon @@ -0,0 +1,9 @@ +/- resource +:: +|% ++$ channel-info + $: group=resource:resource + channel=resource:resource + channel-type=term + == +-- diff --git a/pkg/arvo/ted/aqua/ames.hoon b/pkg/arvo/ted/aqua/ames.hoon index e609353a43..80125cbc78 100644 --- a/pkg/arvo/ted/aqua/ames.hoon +++ b/pkg/arvo/ted/aqua/ames.hoon @@ -18,7 +18,7 @@ |= [our=ship who=@p] ^- (list card:agent:gall) %+ emit-aqua-events our - [%event who [//newt/0v1n.2m9vh %born ~]]~ + [%event who [/a/newt/0v1n.2m9vh %born ~]]~ :: ++ handle-send |= [our=ship now=@da sndr=@p way=wire %send lan=lane:ames pac=@] @@ -26,7 +26,7 @@ =/ rcvr=ship (lane-to-ship lan) =/ hear-lane (ship-to-lane sndr) %+ emit-aqua-events our - [%event rcvr //newt/0v1n.2m9vh %hear hear-lane pac]~ + [%event rcvr /a/newt/0v1n.2m9vh %hear hear-lane pac]~ :: +lane-to-ship: decode a ship from an aqua lane :: :: Special-case one comet, since its address doesn't fit into a lane. diff --git a/pkg/arvo/ted/aqua/behn.hoon b/pkg/arvo/ted/aqua/behn.hoon index 517d2f3c72..1b5ac20922 100644 --- a/pkg/arvo/ted/aqua/behn.hoon +++ b/pkg/arvo/ted/aqua/behn.hoon @@ -40,7 +40,7 @@ ^+ ..abet-pe =. this %- emit-aqua-events - [%event who [//behn/0v1n.2m9vh %born ~]]~ + [%event who [/b/behn/0v1n.2m9vh %born ~]]~ ..abet-pe :: ++ handle-doze @@ -82,7 +82,7 @@ :_ ~ ^- aqua-event :+ %event who - [//behn/0v1n.2m9vh [%wake ~]] + [/b/behn/0v1n.2m9vh [%wake ~]] ..abet-pe -- -- diff --git a/pkg/arvo/ted/aqua/eyre-azimuth.hoon b/pkg/arvo/ted/aqua/eyre-azimuth.hoon index 1700c5d614..4a098961b1 100644 --- a/pkg/arvo/ted/aqua/eyre-azimuth.hoon +++ b/pkg/arvo/ted/aqua/eyre-azimuth.hoon @@ -179,7 +179,7 @@ :_ ~ :* %event her - //http-client/0v1n.2m9vh + /i/http-client/0v1n.2m9vh %receive num.u.ask [%start [200 ~] `(as-octs:mimes:html resp) &] @@ -274,7 +274,7 @@ =/ clan (clan:title who) ?- clan ?(%czar %king %duke) - ;< ~ bind:m (raw-ship:ph-io who `(dawn who ~)) + ;< ~ bind:m (init-ship:ph-io who |) (pure:m state) :: ?(%earl %pawn) @@ -294,7 +294,7 @@ =/ rank ?:(=(%earl clan) "moon" "comet") "|{rank} {(scow %p who)}, =public-key {(scow %uw pass)}" ;< ~ bind:m (dojo:ph-io spon com) - ;< ~ bind:m (raw-ship:ph-io who `(dawn who `seed)) + ;< ~ bind:m (init-ship:ph-io who |) (pure:m state) == :: diff --git a/pkg/arvo/ted/aqua/eyre.hoon b/pkg/arvo/ted/aqua/eyre.hoon index 6c1d85c1d7..b8a7b7616c 100644 --- a/pkg/arvo/ted/aqua/eyre.hoon +++ b/pkg/arvo/ted/aqua/eyre.hoon @@ -39,7 +39,7 @@ ^+ ..abet-pe =. this %- emit-aqua-events - [%event who [//http/0v1n.2m9vh %born ~]]~ + [%event who [/i/http/0v1n.2m9vh %born ~]]~ ..abet-pe :: ++ handle-thus @@ -81,7 +81,7 @@ ..abet-pe =. http-requests (~(del in http-requests) num) =. this - (emit-aqua-events [%event who [//http/0v1n.2m9vh %receive num [%start [p.res q.res] r.res &]]]~) + (emit-aqua-events [%event who [/i/http/0v1n.2m9vh %receive num [%start [p.res q.res] r.res &]]]~) ..abet-pe :: :: Got error in HTTP response diff --git a/pkg/arvo/ted/ph/add.hoon b/pkg/arvo/ted/ph/add.hoon index e5507cbee9..3a0004c05c 100644 --- a/pkg/arvo/ted/ph/add.hoon +++ b/pkg/arvo/ted/ph/add.hoon @@ -5,8 +5,8 @@ |= args=vase =/ m (strand ,vase) ;< ~ bind:m start-simple -;< ~ bind:m (raw-ship ~bud ~) +;< ~ bind:m (init-ship ~bud &) ;< ~ bind:m (dojo ~bud "[%test-result (add 2 3)]") ;< ~ bind:m (wait-for-output ~bud "[%test-result 5]") -;< ~ bind:m end-simple +;< ~ bind:m end (pure:m *vase) diff --git a/pkg/arvo/ted/ph/boot-az.hoon b/pkg/arvo/ted/ph/boot-az.hoon index a430d620ff..44a012e64e 100644 --- a/pkg/arvo/ted/ph/boot-az.hoon +++ b/pkg/arvo/ted/ph/boot-az.hoon @@ -4,8 +4,8 @@ ^- thread:spider |= vase =/ m (strand ,vase) -;< az=tid:spider bind:m start-azimuth -;< ~ bind:m (spawn az ~bud) -;< ~ bind:m (real-ship az ~bud) -;< ~ bind:m end-azimuth +;< ~ bind:m start-azimuth +;< ~ bind:m (spawn ~bud) +;< ~ bind:m (init-ship ~bud |) +;< ~ bind:m end (pure:m *vase) diff --git a/pkg/arvo/ted/ph/boot-planet.hoon b/pkg/arvo/ted/ph/boot-planet.hoon index 2b49371544..c5042c8725 100644 --- a/pkg/arvo/ted/ph/boot-planet.hoon +++ b/pkg/arvo/ted/ph/boot-planet.hoon @@ -5,8 +5,8 @@ |= vase =/ m (strand ,vase) ;< ~ bind:m start-simple -;< ~ bind:m (raw-ship ~bud ~) -;< ~ bind:m (raw-ship ~marbud ~) -;< ~ bind:m (raw-ship ~linnup-torsyx ~) -;< ~ bind:m end-simple +;< ~ bind:m (init-ship ~bud &) +;< ~ bind:m (init-ship ~marbud &) +;< ~ bind:m (init-ship ~linnup-torsyx &) +;< ~ bind:m end (pure:m *vase) diff --git a/pkg/arvo/ted/ph/breach-hi-aqua.hoon b/pkg/arvo/ted/ph/breach-hi-aqua.hoon index 473ddb846b..8b8d70f3c1 100644 --- a/pkg/arvo/ted/ph/breach-hi-aqua.hoon +++ b/pkg/arvo/ted/ph/breach-hi-aqua.hoon @@ -1,19 +1,19 @@ /- spider -/+ *ph-io, *ph-util +/+ *ph-io, *ph-util, strandio =, strand=strand:spider ^- thread:spider |= vase =/ m (strand ,vase) ;< =bowl:spider bind:m get-bowl -;< ~ bind:m start-simple -;< ~ bind:m init-azimuth -;< ~ bind:m (spawn-aqua ~bud) -;< ~ bind:m (spawn-aqua ~dev) -;< ~ bind:m (init-ship ~bud) -;< ~ bind:m (init-ship ~dev) +;< ~ bind:m start-azimuth +;< ~ bind:m (spawn ~bud) +;< ~ bind:m (spawn ~dev) +;< ~ bind:m (init-ship ~bud |) +;< ~ bind:m (init-ship ~dev |) +;< ~ bind:m (sleep:strandio ~s10) ;< ~ bind:m (send-hi ~bud ~dev) -;< ~ bind:m (breach-and-hear-aqua ~dev ~bud) +;< ~ bind:m (breach-and-hear ~dev ~bud) ;< ~ bind:m (send-hi-not-responding ~bud ~dev) -;< ~ bind:m (init-ship ~dev) +;< ~ bind:m (init-ship ~dev |) ;< ~ bind:m (wait-for-output ~bud "hi ~dev successful") (pure:m *vase) diff --git a/pkg/arvo/ted/ph/breach-hi-cousin.hoon b/pkg/arvo/ted/ph/breach-hi-cousin.hoon index 5984913e7b..d2f4dd6584 100644 --- a/pkg/arvo/ted/ph/breach-hi-cousin.hoon +++ b/pkg/arvo/ted/ph/breach-hi-cousin.hoon @@ -8,20 +8,19 @@ ^- thread:spider |= vase =/ m (strand ,vase) -;< az=tid:spider bind:m - start-azimuth -;< ~ bind:m (spawn az ~bud) -;< ~ bind:m (spawn az ~dev) -;< ~ bind:m (spawn az ~marbud) -;< ~ bind:m (spawn az ~mardev) -;< ~ bind:m (real-ship az ~bud) -;< ~ bind:m (real-ship az ~dev) -;< ~ bind:m (real-ship az ~marbud) -;< ~ bind:m (real-ship az ~mardev) +;< ~ bind:m start-azimuth +;< ~ bind:m (spawn ~bud) +;< ~ bind:m (spawn ~dev) +;< ~ bind:m (spawn ~marbud) +;< ~ bind:m (spawn ~mardev) +;< ~ bind:m (init-ship ~bud |) +;< ~ bind:m (init-ship ~dev |) +;< ~ bind:m (init-ship ~marbud |) +;< ~ bind:m (init-ship ~mardev |) ;< ~ bind:m (send-hi ~marbud ~mardev) -;< ~ bind:m (breach-and-hear az ~mardev ~marbud) +;< ~ bind:m (breach-and-hear ~mardev ~marbud) ;< ~ bind:m (send-hi-not-responding ~marbud ~mardev) -;< ~ bind:m (real-ship az ~mardev) +;< ~ bind:m (init-ship ~mardev |) ;< ~ bind:m (wait-for-output ~marbud "hi ~mardev successful") -;< ~ bind:m end-azimuth +;< ~ bind:m end (pure:m *vase) diff --git a/pkg/arvo/ted/ph/breach-hi.hoon b/pkg/arvo/ted/ph/breach-hi.hoon index 2c484a6029..e255530886 100644 --- a/pkg/arvo/ted/ph/breach-hi.hoon +++ b/pkg/arvo/ted/ph/breach-hi.hoon @@ -4,16 +4,15 @@ ^- thread:spider |= vase =/ m (strand ,vase) -;< az=tid:spider bind:m - start-azimuth -;< ~ bind:m (spawn az ~bud) -;< ~ bind:m (spawn az ~dev) -;< ~ bind:m (real-ship az ~bud) -;< ~ bind:m (real-ship az ~dev) +;< ~ bind:m start-azimuth +;< ~ bind:m (spawn ~bud) +;< ~ bind:m (spawn ~dev) +;< ~ bind:m (init-ship ~bud |) +;< ~ bind:m (init-ship ~dev |) ;< ~ bind:m (send-hi ~bud ~dev) -;< ~ bind:m (breach-and-hear az ~dev ~bud) +;< ~ bind:m (breach-and-hear ~dev ~bud) ;< ~ bind:m (send-hi-not-responding ~bud ~dev) -;< ~ bind:m (real-ship az ~dev) +;< ~ bind:m (init-ship ~dev |) ;< ~ bind:m (wait-for-output ~bud "hi ~dev successful") -;< ~ bind:m end-azimuth +;< ~ bind:m end (pure:m *vase) diff --git a/pkg/arvo/ted/ph/breach-multiple.hoon b/pkg/arvo/ted/ph/breach-multiple.hoon index d486f9d43c..33eca2ea8b 100644 --- a/pkg/arvo/ted/ph/breach-multiple.hoon +++ b/pkg/arvo/ted/ph/breach-multiple.hoon @@ -6,19 +6,19 @@ ^- thread:spider |= vase =/ m (strand ,vase) -;< az=tid:spider bind:m start-azimuth -;< ~ bind:m (spawn az ~bud) -;< ~ bind:m (spawn az ~marbud) -;< ~ bind:m (real-ship az ~bud) -;< ~ bind:m (real-ship az ~marbud) -;< file=@t bind:m (touch-file ~bud %kids %foo) -;< ~ bind:m (check-file-touched ~marbud %home file) -;< ~ bind:m (breach-and-hear az ~bud ~marbud) -;< ~ bind:m (real-ship az ~bud) -;< ~ bind:m (breach-and-hear az ~marbud ~bud) -;< ~ bind:m (real-ship az ~marbud) -;< file=@t bind:m (touch-file ~bud %kids %bar) -;< file=@t bind:m (touch-file ~bud %kids %baz) -;< ~ bind:m (check-file-touched ~marbud %home file) -;< ~ bind:m end-azimuth +;< ~ bind:m start-azimuth +;< ~ bind:m (spawn ~bud) +;< ~ bind:m (spawn ~marbud) +;< ~ bind:m (init-ship ~bud |) +;< ~ bind:m (init-ship ~marbud |) +;< file=@t bind:m (touch-file ~bud %kids %foo) +;< ~ bind:m (check-file-touched ~marbud %home file) +;< ~ bind:m (breach-and-hear ~bud ~marbud) +;< ~ bind:m (init-ship ~bud |) +;< ~ bind:m (breach-and-hear ~marbud ~bud) +;< ~ bind:m (init-ship ~marbud |) +;< file=@t bind:m (touch-file ~bud %kids %bar) +;< file=@t bind:m (touch-file ~bud %kids %baz) +;< ~ bind:m (check-file-touched ~marbud %home file) +;< ~ bind:m end (pure:m *vase) diff --git a/pkg/arvo/ted/ph/breach-sudden.hoon b/pkg/arvo/ted/ph/breach-sudden.hoon index ba03c2168a..6e1c91ece4 100644 --- a/pkg/arvo/ted/ph/breach-sudden.hoon +++ b/pkg/arvo/ted/ph/breach-sudden.hoon @@ -8,19 +8,19 @@ ^- thread:spider |= vase =/ m (strand ,vase) -;< az=tid:spider bind:m start-azimuth -;< ~ bind:m (spawn az ~bud) -;< ~ bind:m (spawn az ~marbud) -;< ~ bind:m (real-ship az ~bud) -;< ~ bind:m (real-ship az ~marbud) -;< file=@t bind:m (touch-file ~bud %kids %foo) -;< ~ bind:m (check-file-touched ~marbud %home file) -;< ~ bind:m (breach az ~bud) -;< ~ bind:m (real-ship az ~bud) -;< ~ bind:m +;< ~ bind:m start-azimuth +;< ~ bind:m (spawn ~bud) +;< ~ bind:m (spawn ~marbud) +;< ~ bind:m (init-ship ~bud |) +;< ~ bind:m (init-ship ~marbud |) +;< file=@t bind:m (touch-file ~bud %kids %foo) +;< ~ bind:m (check-file-touched ~marbud %home file) +;< ~ bind:m (breach ~bud) +;< ~ bind:m (init-ship ~bud |) +;< ~ bind:m (dojo ~bud "|merge %home ~marbud %kids, =gem %only-this") -;< file=@t bind:m (touch-file ~bud %kids %bar) -;< file=@t bind:m (touch-file ~bud %kids %baz) -;< ~ bind:m (check-file-touched ~marbud %home file) -;< ~ bind:m end-azimuth +;< file=@t bind:m (touch-file ~bud %kids %bar) +;< file=@t bind:m (touch-file ~bud %kids %baz) +;< ~ bind:m (check-file-touched ~marbud %home file) +;< ~ bind:m end (pure:m *vase) diff --git a/pkg/arvo/ted/ph/breach-sync.hoon b/pkg/arvo/ted/ph/breach-sync.hoon index 5464594bca..e40cf9f68c 100644 --- a/pkg/arvo/ted/ph/breach-sync.hoon +++ b/pkg/arvo/ted/ph/breach-sync.hoon @@ -6,23 +6,23 @@ ^- thread:spider |= vase =/ m (strand ,vase) -;< az=tid:spider bind:m start-azimuth -;< ~ bind:m (spawn az ~bud) -;< ~ bind:m (spawn az ~marbud) -;< ~ bind:m (real-ship az ~bud) -;< ~ bind:m (real-ship az ~marbud) +;< ~ bind:m start-azimuth +;< ~ bind:m (spawn ~bud) +;< ~ bind:m (spawn ~marbud) +;< ~ bind:m (init-ship ~bud |) +;< ~ bind:m (init-ship ~marbud |) ;< file=@t bind:m (touch-file ~bud %kids %foo) ;< ~ bind:m (check-file-touched ~marbud %home file) :: Merge so that when we unify history with the %only-this merge later, we :: don't get a spurious conflict in %home :: ;< ~ bind:m (dojo ~marbud "|merge %kids our %home") -;< ~ bind:m (breach-and-hear az ~bud ~marbud) -;< ~ bind:m (real-ship az ~bud) +;< ~ bind:m (breach-and-hear ~bud ~marbud) +;< ~ bind:m (init-ship ~bud |) ;< ~ bind:m (dojo ~bud "|merge %kids ~marbud %kids, =gem %only-this") ;< file=@t bind:m (touch-file ~bud %kids %bar) ;< file=@t bind:m (touch-file ~bud %kids %baz) ;< ~ bind:m (check-file-touched ~marbud %home file) -;< ~ bind:m end-azimuth +;< ~ bind:m end (pure:m *vase) diff --git a/pkg/arvo/ted/ph/change-file.hoon b/pkg/arvo/ted/ph/change-file.hoon index cfbf3ab243..34b3b4d4ee 100644 --- a/pkg/arvo/ted/ph/change-file.hoon +++ b/pkg/arvo/ted/ph/change-file.hoon @@ -5,8 +5,8 @@ |= vase =/ m (strand ,vase) ;< ~ bind:m start-simple -;< ~ bind:m (raw-ship ~bud ~) +;< ~ bind:m (init-ship ~bud &) ;< file=@t bind:m (touch-file ~bud %home %foo) ;< ~ bind:m (check-file-touched ~bud %home file) -;< ~ bind:m end-simple +;< ~ bind:m end (pure:m *vase) diff --git a/pkg/arvo/ted/ph/child-sync.hoon b/pkg/arvo/ted/ph/child-sync.hoon index 381a67064b..1d67a4e516 100644 --- a/pkg/arvo/ted/ph/child-sync.hoon +++ b/pkg/arvo/ted/ph/child-sync.hoon @@ -5,10 +5,10 @@ |= vase =/ m (strand ,vase) ;< ~ bind:m start-simple -;< ~ bind:m (raw-ship ~bud ~) -;< ~ bind:m (raw-ship ~marbud ~) +;< ~ bind:m (init-ship ~bud &) +;< ~ bind:m (init-ship ~marbud &) ;< file=@t bind:m (touch-file ~bud %home %foo) ;< ~ bind:m (dojo ~bud "|merge %kids our %home") ;< ~ bind:m (check-file-touched ~marbud %home file) -;< ~ bind:m end-simple +;< ~ bind:m end (pure:m *vase) diff --git a/pkg/arvo/ted/ph/child-update.hoon b/pkg/arvo/ted/ph/child-update.hoon index d505e74ea7..e377aed6e2 100644 --- a/pkg/arvo/ted/ph/child-update.hoon +++ b/pkg/arvo/ted/ph/child-update.hoon @@ -6,12 +6,12 @@ |^ =/ m (strand ,vase) ;< ~ bind:m start-simple -;< ~ bind:m (raw-ship ~bud ~) -;< ~ bind:m (raw-ship ~marbud ~) +;< ~ bind:m (init-ship ~bud |) +;< ~ bind:m (init-ship ~marbud |) ;< [path @t] bind:m (modify ~bud %home) ;< [=path file=@t] bind:m (modify ~bud %kids) ;< ~ bind:m (check-touched ~marbud %kids path file) -;< ~ bind:m end-simple +;< ~ bind:m end (pure:m *vase) :: ++ modify diff --git a/pkg/arvo/ted/ph/group-rejoin.hoon b/pkg/arvo/ted/ph/group-rejoin.hoon index 9fcf2e8ac1..c2e7c272d6 100644 --- a/pkg/arvo/ted/ph/group-rejoin.hoon +++ b/pkg/arvo/ted/ph/group-rejoin.hoon @@ -42,27 +42,26 @@ ^- thread:spider |= args=vase =/ m (strand ,vase) -;< az=tid:spider - bind:m start-azimuth -;< ~ bind:m (spawn az ~bud) -;< ~ bind:m (spawn az ~marbud) -;< ~ bind:m (spawn az ~zod) -;< ~ bind:m (spawn az ~marzod) -;< ~ bind:m (real-ship az ~bud) -;< ~ bind:m (real-ship az ~marbud) +;< ~ bind:m start-azimuth +;< ~ bind:m (spawn ~bud) +;< ~ bind:m (spawn ~marbud) +;< ~ bind:m (spawn ~zod) +;< ~ bind:m (spawn ~marzod) +;< ~ bind:m (init-ship ~bud |) +;< ~ bind:m (init-ship ~marbud |) ;< ~ bind:m (wait-for-goad ~marbud) -;< ~ bind:m (real-ship az ~zod) -;< ~ bind:m (real-ship az ~marzod) +;< ~ bind:m (init-ship ~zod |) +;< ~ bind:m (init-ship ~marzod |) ;< ~ bind:m (wait-for-goad ~marzod) ;< ~ bind:m (start-group-agents ~marbud) ;< ~ bind:m (start-group-agents ~marzod) ;< ~ bind:m (dojo ~marbud ":group-store|create 'test-group'") ;< ~ bind:m (wait-for-output ~marbud ">=") ;< ~ bind:m (sleep ~s1) -;< ~ bind:m (breach-and-hear az ~marzod ~marbud) -;< ~ bind:m (real-ship az ~marzod) +;< ~ bind:m (breach-and-hear ~marzod ~marbud) +;< ~ bind:m (init-ship ~marzod |) ;< ~ bind:m (wait-for-goad ~marzod) ;< ~ bind:m (start-group-agents ~marzod) ;< ~ bind:m (sleep ~s3) -;< ~ bind:m end-azimuth +;< ~ bind:m end (pure:m *vase) diff --git a/pkg/arvo/ted/ph/hi-az.hoon b/pkg/arvo/ted/ph/hi-az.hoon index b5354cdea0..5133e1222e 100644 --- a/pkg/arvo/ted/ph/hi-az.hoon +++ b/pkg/arvo/ted/ph/hi-az.hoon @@ -4,12 +4,11 @@ ^- thread:spider |= vase =/ m (strand ,vase) -;< az=tid:spider - bind:m start-azimuth -;< ~ bind:m (spawn az ~bud) -;< ~ bind:m (spawn az ~dev) -;< ~ bind:m (real-ship az ~bud) -;< ~ bind:m (real-ship az ~dev) +;< ~ bind:m start-azimuth +;< ~ bind:m (spawn ~bud) +;< ~ bind:m (spawn ~dev) +;< ~ bind:m (init-ship ~bud |) +;< ~ bind:m (init-ship ~dev |) ;< ~ bind:m (send-hi ~bud ~dev) -;< ~ bind:m end-azimuth +;< ~ bind:m end (pure:m *vase) diff --git a/pkg/arvo/ted/ph/hi-comet-az.hoon b/pkg/arvo/ted/ph/hi-comet-az.hoon index 5b82b5e718..9e4eb737b9 100644 --- a/pkg/arvo/ted/ph/hi-comet-az.hoon +++ b/pkg/arvo/ted/ph/hi-comet-az.hoon @@ -5,22 +5,21 @@ |= vase =/ m (strand ,vase) =/ comet ~bosrym-podwyl-magnes-dacrys--pander-hablep-masrym-marbud -;< az=tid:spider - bind:m start-azimuth -;< ~ bind:m (spawn az ~bud) -;< ~ bind:m (real-ship az ~bud) +;< ~ bind:m start-azimuth +;< ~ bind:m (spawn ~bud) +;< ~ bind:m (init-ship ~bud |) :: -;< ~ bind:m (spawn az ~marbud) -;< ~ bind:m (real-ship az ~marbud) +;< ~ bind:m (spawn ~marbud) +;< ~ bind:m (init-ship ~marbud |) :: -;< ~ bind:m (real-ship az comet) -;< ~ bind:m (send-hi comet ~bud) +;< ~ bind:m (init-ship comet |) +;< ~ bind:m (send-hi comet ~bud) :: -;< ~ bind:m (spawn az ~linnup-torsyx) -;< ~ bind:m (real-ship az ~linnup-torsyx) +;< ~ bind:m (spawn ~linnup-torsyx) +;< ~ bind:m (init-ship ~linnup-torsyx |) :: ;< ~ bind:m (send-hi comet ~linnup-torsyx) ;< ~ bind:m (send-hi ~linnup-torsyx comet) :: -;< ~ bind:m end-azimuth +;< ~ bind:m end (pure:m *vase) diff --git a/pkg/arvo/ted/ph/hi-cousin-az.hoon b/pkg/arvo/ted/ph/hi-cousin-az.hoon index 7facac1aac..c7010cbdcf 100644 --- a/pkg/arvo/ted/ph/hi-cousin-az.hoon +++ b/pkg/arvo/ted/ph/hi-cousin-az.hoon @@ -4,16 +4,15 @@ ^- thread:spider |= vase =/ m (strand ,vase) -;< az=tid:spider - bind:m start-azimuth -;< ~ bind:m (spawn az ~bud) -;< ~ bind:m (spawn az ~marbud) -;< ~ bind:m (spawn az ~dev) -;< ~ bind:m (spawn az ~mardev) -;< ~ bind:m (real-ship az ~bud) -;< ~ bind:m (real-ship az ~marbud) -;< ~ bind:m (real-ship az ~dev) -;< ~ bind:m (real-ship az ~mardev) +;< ~ bind:m start-azimuth +;< ~ bind:m (spawn ~bud) +;< ~ bind:m (spawn ~marbud) +;< ~ bind:m (spawn ~dev) +;< ~ bind:m (spawn ~mardev) +;< ~ bind:m (init-ship ~bud |) +;< ~ bind:m (init-ship ~marbud |) +;< ~ bind:m (init-ship ~dev |) +;< ~ bind:m (init-ship ~mardev |) ;< ~ bind:m (send-hi ~mardev ~marbud) -;< ~ bind:m end-azimuth +;< ~ bind:m end (pure:m *vase) diff --git a/pkg/arvo/ted/ph/hi-linnup-az-backward.hoon b/pkg/arvo/ted/ph/hi-linnup-az-backward.hoon index e072f801ab..a7052138de 100644 --- a/pkg/arvo/ted/ph/hi-linnup-az-backward.hoon +++ b/pkg/arvo/ted/ph/hi-linnup-az-backward.hoon @@ -4,14 +4,13 @@ ^- thread:spider |= vase =/ m (strand ,vase) -;< az=tid:spider - bind:m start-azimuth -;< ~ bind:m (spawn az ~bud) -;< ~ bind:m (spawn az ~marbud) -;< ~ bind:m (spawn az ~linnup-torsyx) -;< ~ bind:m (real-ship az ~linnup-torsyx) -;< ~ bind:m (real-ship az ~marbud) -;< ~ bind:m (real-ship az ~bud) +;< ~ bind:m start-azimuth +;< ~ bind:m (spawn ~bud) +;< ~ bind:m (spawn ~marbud) +;< ~ bind:m (spawn ~linnup-torsyx) +;< ~ bind:m (init-ship ~linnup-torsyx |) +;< ~ bind:m (init-ship ~marbud |) +;< ~ bind:m (init-ship ~bud |) ;< ~ bind:m (send-hi ~linnup-torsyx ~marbud) -;< ~ bind:m end-azimuth +;< ~ bind:m end (pure:m *vase) diff --git a/pkg/arvo/ted/ph/hi-linnup-az.hoon b/pkg/arvo/ted/ph/hi-linnup-az.hoon index abcfd4151b..cdea3c006a 100644 --- a/pkg/arvo/ted/ph/hi-linnup-az.hoon +++ b/pkg/arvo/ted/ph/hi-linnup-az.hoon @@ -4,14 +4,13 @@ ^- thread:spider |= vase =/ m (strand ,vase) -;< az=tid:spider - bind:m start-azimuth -;< ~ bind:m (spawn az ~bud) -;< ~ bind:m (spawn az ~marbud) -;< ~ bind:m (spawn az ~linnup-torsyx) -;< ~ bind:m (real-ship az ~bud) -;< ~ bind:m (real-ship az ~marbud) -;< ~ bind:m (real-ship az ~linnup-torsyx) +;< ~ bind:m start-azimuth +;< ~ bind:m (spawn ~bud) +;< ~ bind:m (spawn ~marbud) +;< ~ bind:m (spawn ~linnup-torsyx) +;< ~ bind:m (init-ship ~bud |) +;< ~ bind:m (init-ship ~marbud |) +;< ~ bind:m (init-ship ~linnup-torsyx |) ;< ~ bind:m (send-hi ~linnup-torsyx ~marbud) -;< ~ bind:m end-azimuth +;< ~ bind:m end (pure:m *vase) diff --git a/pkg/arvo/ted/ph/hi-marbud-az.hoon b/pkg/arvo/ted/ph/hi-marbud-az.hoon index a53d81a852..010e274458 100644 --- a/pkg/arvo/ted/ph/hi-marbud-az.hoon +++ b/pkg/arvo/ted/ph/hi-marbud-az.hoon @@ -4,12 +4,11 @@ ^- thread:spider |= vase =/ m (strand ,vase) -;< az=tid:spider - bind:m start-azimuth -;< ~ bind:m (spawn az ~bud) -;< ~ bind:m (spawn az ~marbud) -;< ~ bind:m (real-ship az ~bud) -;< ~ bind:m (real-ship az ~marbud) +;< ~ bind:m start-azimuth +;< ~ bind:m (spawn ~bud) +;< ~ bind:m (spawn ~marbud) +;< ~ bind:m (init-ship ~bud |) +;< ~ bind:m (init-ship ~marbud |) ;< ~ bind:m (send-hi ~bud ~marbud) -;< ~ bind:m end-azimuth +;< ~ bind:m end (pure:m *vase) diff --git a/pkg/arvo/ted/ph/hi-nephew-az.hoon b/pkg/arvo/ted/ph/hi-nephew-az.hoon index 286367eddf..dbe1a5fbae 100644 --- a/pkg/arvo/ted/ph/hi-nephew-az.hoon +++ b/pkg/arvo/ted/ph/hi-nephew-az.hoon @@ -4,14 +4,13 @@ ^- thread:spider |= vase =/ m (strand ,vase) -;< az=tid:spider - bind:m start-azimuth -;< ~ bind:m (spawn az ~bud) -;< ~ bind:m (spawn az ~marbud) -;< ~ bind:m (spawn az ~dev) -;< ~ bind:m (real-ship az ~bud) -;< ~ bind:m (real-ship az ~marbud) -;< ~ bind:m (real-ship az ~dev) +;< ~ bind:m start-azimuth +;< ~ bind:m (spawn ~bud) +;< ~ bind:m (spawn ~marbud) +;< ~ bind:m (spawn ~dev) +;< ~ bind:m (init-ship ~bud |) +;< ~ bind:m (init-ship ~marbud |) +;< ~ bind:m (init-ship ~dev |) ;< ~ bind:m (send-hi ~dev ~marbud) -;< ~ bind:m end-azimuth +;< ~ bind:m end (pure:m *vase) diff --git a/pkg/arvo/ted/ph/hi-uncle-az.hoon b/pkg/arvo/ted/ph/hi-uncle-az.hoon index c73b10cb43..f3a004044d 100644 --- a/pkg/arvo/ted/ph/hi-uncle-az.hoon +++ b/pkg/arvo/ted/ph/hi-uncle-az.hoon @@ -4,14 +4,13 @@ ^- thread:spider |= vase =/ m (strand ,vase) -;< az=tid:spider - bind:m start-azimuth -;< ~ bind:m (spawn az ~bud) -;< ~ bind:m (spawn az ~marbud) -;< ~ bind:m (spawn az ~dev) -;< ~ bind:m (real-ship az ~bud) -;< ~ bind:m (real-ship az ~marbud) -;< ~ bind:m (real-ship az ~dev) +;< ~ bind:m start-azimuth +;< ~ bind:m (spawn ~bud) +;< ~ bind:m (spawn ~marbud) +;< ~ bind:m (spawn ~dev) +;< ~ bind:m (init-ship ~bud |) +;< ~ bind:m (init-ship ~marbud |) +;< ~ bind:m (init-ship ~dev |) ;< ~ bind:m (send-hi ~marbud ~dev) -;< ~ bind:m end-azimuth +;< ~ bind:m end (pure:m *vase) diff --git a/pkg/arvo/ted/ph/hi.hoon b/pkg/arvo/ted/ph/hi.hoon index 1c2b0db91e..02ecd1b8f2 100644 --- a/pkg/arvo/ted/ph/hi.hoon +++ b/pkg/arvo/ted/ph/hi.hoon @@ -5,9 +5,9 @@ |= vase =/ m (strand ,vase) ;< ~ bind:m start-simple -;< ~ bind:m (raw-ship ~bud ~) -;< ~ bind:m (raw-ship ~dev ~) -;< ~ bind:m (raw-ship ~dev ~) +;< ~ bind:m (init-ship ~bud &) +;< ~ bind:m (init-ship ~dev &) +;< ~ bind:m (init-ship ~dev &) ;< ~ bind:m (send-hi ~bud ~dev) -;< ~ bind:m end-simple +;< ~ bind:m end (pure:m *vase) diff --git a/pkg/arvo/ted/ph/lib-hooks.hoon b/pkg/arvo/ted/ph/lib-hooks.hoon index 1f2aa5966c..77d357a799 100644 --- a/pkg/arvo/ted/ph/lib-hooks.hoon +++ b/pkg/arvo/ted/ph/lib-hooks.hoon @@ -1,6 +1,7 @@ /- spider -/+ *ph-io, *strandio +/+ io=ph-io, *strandio => +=, io |% ++ strand strand:spider ++ start-agents @@ -27,12 +28,11 @@ ^- thread:spider |= vase =/ m (strand ,vase) -;< az=tid:spider - bind:m start-azimuth -;< ~ bind:m (spawn az ~bud) -;< ~ bind:m (spawn az ~dev) -;< ~ bind:m (real-ship az ~bud) -;< ~ bind:m (real-ship az ~dev) +;< ~ bind:m start-azimuth +;< ~ bind:m (spawn ~bud) +;< ~ bind:m (spawn ~dev) +;< ~ bind:m (init-ship ~bud |) +;< ~ bind:m (init-ship ~dev |) ;< ~ bind:m (start-agents ~bud) ;< ~ bind:m (start-agents ~dev) ;< ~ bind:m (send-hi ~bud ~dev) @@ -61,5 +61,6 @@ ;< ~ bind:m (dojo ~dev ":graph-store +dbug") ;< ~ bind:m (dojo ~bud ":graph-push-hook +dbug %bowl") ;< ~ bind:m (dojo ~bud ":graph-store +dbug") -;< ~ bind:m end-azimuth +;< ~ bind:m end (pure:m *vase) +::(pure:m *vase) diff --git a/pkg/arvo/ted/ph/migrate/end.hoon b/pkg/arvo/ted/ph/migrate/end.hoon new file mode 100644 index 0000000000..e69de29bb2 diff --git a/pkg/arvo/ted/ph/migrate/make-groups.hoon b/pkg/arvo/ted/ph/migrate/make-groups.hoon new file mode 100644 index 0000000000..4e814ddaeb --- /dev/null +++ b/pkg/arvo/ted/ph/migrate/make-groups.hoon @@ -0,0 +1,49 @@ +/- spider, + graph-store, + graph-view, + post, + *resource, + *group +/+ *ph-io, strandio +=, strand=strand:spider +=> +|% +:: +++ create-group + |= our=@p + %^ dojo-thread our %group-create + :- %group-view-action + :* %create + %group-1 + [%open ~ ~] + 'Test Group' + 'A description' + == +:: +++ join-group + |= our=@p + %^ poke-app our %group-view + :- %group-view-action + :* %join + [~zod %group-1] + ~zod + == +-- +:: +^- thread:spider +|= vase +=/ m (strand ,vase) +;< ~ bind:m start-simple +;< ~ bind:m (create-group ~zod) +;< ~ bind:m (join-group ~bus) +;< ~ bind:m (join-group ~web) +;< ~ bind:m (send-hi ~zod ~bus) +;< ~ bind:m (send-hi ~zod ~web) +;< ~ bind:m (send-hi ~bus ~zod) +;< ~ bind:m (send-hi ~bus ~web) +;< ~ bind:m (send-hi ~web ~zod) +;< ~ bind:m (send-hi ~web ~bus) +(pure:m *vase) + + + diff --git a/pkg/arvo/ted/ph/migrate/setup.hoon b/pkg/arvo/ted/ph/migrate/setup.hoon new file mode 100644 index 0000000000..71b420b903 --- /dev/null +++ b/pkg/arvo/ted/ph/migrate/setup.hoon @@ -0,0 +1,38 @@ +/- spider, + graph-store, + graph-view, + post, + *resource, + *group +/+ *ph-io, strandio +=, strand=strand:spider +=> +|% +:: +++ create-group + |= our=@p + %^ dojo-thread our %group-create + :- %group-view-action + :* %create + %group-1 + [%open ~ ~] + 'Test Group' + 'A description' + == +:: +++ hang + =/ m (strand ,~) + ^- form:m + |= tin=strand-input:strand + `[%wait ~] +-- +:: +^- thread:spider +|= vase +=/ m (strand ,vase) +;< ~ bind:m start-simple +;< ~ bind:m hang +(pure:m *vase) + + + diff --git a/pkg/arvo/ted/ph/migrate/wait.hoon b/pkg/arvo/ted/ph/migrate/wait.hoon new file mode 100644 index 0000000000..7eda30b9cb --- /dev/null +++ b/pkg/arvo/ted/ph/migrate/wait.hoon @@ -0,0 +1,42 @@ +/- spider, + graph-store, + graph-view, + post, + *resource, + *group +/+ *ph-io, strandio +=, strand=strand:spider +=> +|% +:: +++ create-group + |= our=@p + %^ dojo-thread our %group-create + :- %group-view-action + :* %create + %group-1 + [%open ~ ~] + 'Test Group' + 'A description' + == +:: +++ join-group + |= our=@p + %^ poke-app our %group-view + :- %group-view-action + :* %join + [~zod %group-1] + ~zod + == +-- +:: +^- thread:spider +|= vase +=/ m (strand ,vase) +;< ~ bind:m start-simple +;< ~ bind:m (sleep ~s10) +;< ~ bind:m end +(pure:m *vase) + + + diff --git a/pkg/arvo/ted/ph/moon-az.hoon b/pkg/arvo/ted/ph/moon-az.hoon index 4cfcbb601b..22a077941f 100644 --- a/pkg/arvo/ted/ph/moon-az.hoon +++ b/pkg/arvo/ted/ph/moon-az.hoon @@ -4,20 +4,19 @@ ^- thread:spider |= vase =/ m (strand ,vase) -;< az=tid:spider bind:m - start-azimuth -;< ~ bind:m (spawn az ~bud) -;< ~ bind:m (spawn az ~marbud) -;< ~ bind:m (spawn az ~linnup-torsyx) -;< ~ bind:m (spawn az ~dev) -;< ~ bind:m (real-ship az ~bud) -;< ~ bind:m (real-ship az ~marbud) -;< ~ bind:m (real-ship az ~linnup-torsyx) -;< ~ bind:m (real-ship az ~linnup-torsyx-linnup-torsyx) +;< ~ bind:m start-azimuth +;< ~ bind:m (spawn ~bud) +;< ~ bind:m (spawn ~marbud) +;< ~ bind:m (spawn ~linnup-torsyx) +;< ~ bind:m (spawn ~dev) +;< ~ bind:m (init-ship ~bud |) +;< ~ bind:m (init-ship ~marbud |) +;< ~ bind:m (init-ship ~linnup-torsyx |) +;< ~ bind:m (init-ship ~linnup-torsyx-linnup-torsyx |) ;< ~ bind:m (send-hi ~bud ~linnup-torsyx-linnup-torsyx) ;< ~ bind:m (send-hi ~linnup-torsyx-linnup-torsyx ~marbud) -;< ~ bind:m (real-ship az ~dev) +;< ~ bind:m (init-ship ~dev |) ;< ~ bind:m (send-hi ~linnup-torsyx-linnup-torsyx ~dev) ;< ~ bind:m (send-hi ~dev ~linnup-torsyx-linnup-torsyx) -;< ~ bind:m end-azimuth +;< ~ bind:m end (pure:m *vase) diff --git a/pkg/arvo/ted/ph/second-cousin-hi.hoon b/pkg/arvo/ted/ph/second-cousin-hi.hoon index 60f18b96c7..02e7a3b22c 100644 --- a/pkg/arvo/ted/ph/second-cousin-hi.hoon +++ b/pkg/arvo/ted/ph/second-cousin-hi.hoon @@ -5,12 +5,12 @@ |= vase =/ m (strand ,vase) ;< ~ bind:m start-simple -;< ~ bind:m (raw-ship ~bud ~) -;< ~ bind:m (raw-ship ~marbud ~) -;< ~ bind:m (raw-ship ~linnup-torsyx ~) -;< ~ bind:m (raw-ship ~dev ~) -;< ~ bind:m (raw-ship ~mardev ~) -;< ~ bind:m (raw-ship ~mitnep-todsut ~) +;< ~ bind:m (init-ship ~bud &) +;< ~ bind:m (init-ship ~marbud &) +;< ~ bind:m (init-ship ~linnup-torsyx &) +;< ~ bind:m (init-ship ~dev &) +;< ~ bind:m (init-ship ~mardev &) +;< ~ bind:m (init-ship ~mitnep-todsut &) ;< ~ bind:m (send-hi ~linnup-torsyx ~mitnep-todsut) -;< ~ bind:m end-simple +;< ~ bind:m end (pure:m *vase) diff --git a/pkg/base-dev/lib/ph/io.hoon b/pkg/base-dev/lib/ph/io.hoon index fe0460bdb9..d6cbed8d72 100644 --- a/pkg/base-dev/lib/ph/io.hoon +++ b/pkg/base-dev/lib/ph/io.hoon @@ -23,17 +23,14 @@ :: ++ start-simple (start-test %aqua-ames %aqua-behn %aqua-dill %aqua-eyre ~) -++ end-simple - (end-test %aqua-ames %aqua-behn %aqua-dill %aqua-eyre ~) :: ++ start-azimuth - =/ m (strand ,tid:spider) + =/ m (strand ,~) ^- form:m - ;< ~ bind:m (start-test %aqua-ames %aqua-behn %aqua-dill ~) - (start-thread %aqua-eyre-azimuth) + ;<(~ bind:m start-simple init) :: -++ end-azimuth - (end-test %aqua-ames %aqua-behn %aqua-dill %aqua-eyre-azimuth ~) +++ end + (end-test %aqua-ames %aqua-behn %aqua-dill %aqua-eyre ~) :: ++ start-test |= vane-threads=(list term) @@ -91,77 +88,32 @@ ^- form:m (pure:m ~) :: -:: XX +spawn-aqua and +breach-aqua mean do these actions using aqua's internal -:: azimuth management system, eventually these should just replace +spawn -:: +breach :: -++ init-azimuth +++ init =/ m (strand ,~) ^- form:m (send-azimuth-action %init-azimuth ~) :: -++ spawn-aqua +++ spawn |= =ship ~& > "spawning {}" =/ m (strand ,~) ^- form:m (send-azimuth-action %spawn ship) :: -++ breach-aqua +++ breach |= =ship ~& > "breaching {}" =/ m (strand ,~) ^- form:m (send-azimuth-action %breach ship) :: -++ spawn - |= [=tid:spider =ship] - ~& > "spawning {}" - =/ m (strand ,~) - =/ =vase !>(`input:spider`[tid %azimuth-command !>([%spawn ship])]) - (poke-our %spider %spider-input vase) -:: -++ breach - |= [=tid:spider who=ship] - =/ m (strand ,~) - ~& > "breaching {}" - =/ =vase - !>([tid %azimuth-command !>([%breach who])]) - (poke-our %spider %spider-input vase) -:: :: who: breachee :: her: wait until hears about breach :: ++ breach-and-hear - |= [=tid:spider who=ship her=ship] - =/ m (strand ,~) - ~& > "breaching {} for {}" - ;< =bowl:spider bind:m get-bowl - =/ aqua-pax - :- %i - /(scot %p her)/j/(scot %p her)/rift/(scot %da now.bowl)/(scot %p who)/noun - =/ old-rut ;;((unit @) (scry-aqua:util noun our.bowl now.bowl aqua-pax)) - =/ new-rut - ?~ old-rut - 1 - +(+.old-rut) - =/ =vase - !>([tid %azimuth-command !>([%breach who])]) - ;< ~ bind:m (poke-our %spider %spider-input vase) - |- ^- form:m - =* loop $ - ;< [him=ship =unix-effect] bind:m take-unix-effect - ;< =bowl:spider bind:m get-bowl - =/ aqua-pax - :- %i - /(scot %p her)/j/(scot %p her)/rift/(scot %da now.bowl)/(scot %p who)/noun - =/ rut (scry-aqua:util noun our.bowl now.bowl aqua-pax) - ?: =([~ new-rut] rut) - (pure:m ~) - loop -:: -++ breach-and-hear-aqua |= [who=ship her=ship] + ~& > "breaching {} for {}" =/ m (strand ,~) ;< =bowl:spider bind:m get-bowl =/ aqua-pax @@ -186,27 +138,11 @@ loop :: ++ init-ship - |= =ship + |= [=ship fake=?] =/ m (strand ,~) ^- form:m ~& > "starting {}" - ;< ~ bind:m (send-events (init:util ship `*dawn-event:jael)) - (check-ship-booted ship) -:: -++ real-ship - |= [=tid:spider =ship] - ~& > "booting real {}" - =/ m (strand ,~) - =/ =vase !>([tid %azimuth-command !>([%create-ship ship])]) - ;< ~ bind:m (poke-our %spider %spider-input vase) - (check-ship-booted ship) -:: -++ raw-ship - |= [=ship keys=(unit dawn-event:jael)] - =/ m (strand ,~) - ^- form:m - ~& > "starting {}" - ;< ~ bind:m (send-events (init:util ship keys)) + ;< ~ bind:m (send-events (init:util ship fake)) (check-ship-booted ship) :: ++ check-ship-booted @@ -258,6 +194,7 @@ :: ++ send-hi-not-responding |= [from=@p to=@p] + ~& > 'sending hi not responding' =/ m (strand ,~) ;< ~ bind:m (dojo from "|hi {(scow %p to)}") (wait-for-output from "{(scow %p to)} not responding still trying") diff --git a/pkg/base-dev/lib/ph/util.hoon b/pkg/base-dev/lib/ph/util.hoon index 09e7ff623d..53979b4258 100644 --- a/pkg/base-dev/lib/ph/util.hoon +++ b/pkg/base-dev/lib/ph/util.hoon @@ -16,9 +16,9 @@ :: Start a ship (low-level; prefer +raw-ship) :: ++ init - |= [who=ship keys=(unit dawn-event:jael)] + |= [who=ship fake=?] ^- (list aqua-event) - [%init-ship who keys]~ + [%init-ship who fake]~ :: :: Send dojo command :: @@ -28,10 +28,10 @@ %+ send-events-to who ^- (list unix-event) :~ - [//term/1 %belt %ctl `@c`%e] - [//term/1 %belt %ctl `@c`%u] - [//term/1 %belt %txt ((list @c) what)] - [//term/1 %belt %ret ~] + [/d/term/1 %belt %ctl `@c`%e] + [/d/term/1 %belt %ctl `@c`%u] + [/d/term/1 %belt %txt ((list @c) what)] + [/d/term/1 %belt %ret ~] == :: :: Control character @@ -40,7 +40,7 @@ |= [who=ship what=term] ^- (list ph-event) %+ send-events-to who - :~ [//term/1 %belt %ctl (,@c what)] + :~ [/d/term/1 %belt %ctl (,@c what)] == :: :: Inject a file into a ship @@ -54,7 +54,7 @@ [path ~ /text/plain (as-octs:mimes:html txt)] %+ send-events-to who :~ - [//sync/0v1n.2m9vh %into des | input] + [/c/sync/0v1n.2m9vh %into des | input] == :: :: Checks whether the given event is a dojo output blit containing the diff --git a/pkg/base-dev/lib/pill.hoon b/pkg/base-dev/lib/pill.hoon index 093afae6b9..878296789e 100644 --- a/pkg/base-dev/lib/pill.hoon +++ b/pkg/base-dev/lib/pill.hoon @@ -18,6 +18,8 @@ [%what p=(list (pair path (cask)))] [%whom p=ship] [%boot ? $%($>(%fake task:jael) $>(%dawn task:jael))] + [%wyrd p=vere] + [%verb p=(unit ?)] unix-task == :: +boot-ovum: boostrap kernel filesystem load diff --git a/pkg/base-dev/sur/aquarium.hoon b/pkg/base-dev/sur/aquarium.hoon index fbf0f5b97f..d71d9f5197 100644 --- a/pkg/base-dev/sur/aquarium.hoon +++ b/pkg/base-dev/sur/aquarium.hoon @@ -28,7 +28,7 @@ +$ pill pill:pill-lib :: +$ aqua-event - $% [%init-ship who=ship keys=(unit dawn-event:jael)] + $% [%init-ship who=ship fake=?] [%pause-events who=ship] [%snap-ships lab=term hers=(list ship)] [%restore-snap lab=term] diff --git a/pkg/btc-wallet/.eslintignore b/pkg/btc-wallet/.eslintignore new file mode 100644 index 0000000000..8dbaf7dc44 --- /dev/null +++ b/pkg/btc-wallet/.eslintignore @@ -0,0 +1,2 @@ +config/webpack.dev.js +config/webpack.prod.js diff --git a/pkg/btc-wallet/README.md b/pkg/btc-wallet/README.md index bc1e3f3b6f..60933f149e 100644 --- a/pkg/btc-wallet/README.md +++ b/pkg/btc-wallet/README.md @@ -5,4 +5,4 @@ dojo: it should return with the following hash: -`0v7.v4dng.o33qi.kc497.5jc02.ke5es` +`0v758lj.uf0s5.0nh3m.gunn6.942gj` diff --git a/pkg/btc-wallet/config/webpack.dev.js b/pkg/btc-wallet/config/webpack.dev.js index 532f849740..8b31012044 100644 --- a/pkg/btc-wallet/config/webpack.dev.js +++ b/pkg/btc-wallet/config/webpack.dev.js @@ -6,9 +6,10 @@ 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())); +function copy(src, dest) { + return new Promise((res, rej) => + fs.copy(src, dest, (err) => (err ? rej(err) : res())) + ); } class UrbitShipPlugin { @@ -36,24 +37,29 @@ let devServer = { publicPath: '/apps/bitcoin/', }; -const router = _.mapKeys(urbitrc.FLEET || {}, (value, key) => `${key}.localhost:9000`); +const router = _.mapKeys( + urbitrc.FLEET || {}, + (value, key) => `${key}.localhost:9000` +); -if(urbitrc.URL) { +if (urbitrc.URL) { devServer = { ...devServer, index: 'index.html', - proxy: [{ - target: 'http://localhost:9000', - changeOrigin: true, - target: urbitrc.URL, - router, - context: path => { - if(path === '/apps/bitcoin/desk.js') { - return true; - } - return !path.startsWith('/apps/bitcoin') - } - }] + proxy: [ + { + target: 'http://localhost:9000', + changeOrigin: true, + target: urbitrc.URL, + router, + context: (path) => { + if (path === '/apps/bitcoin/desk.js') { + return true; + } + return !path.startsWith('/apps/bitcoin'); + }, + }, + ], }; } @@ -61,30 +67,16 @@ module.exports = { node: { fs: 'empty' }, mode: 'development', entry: { - app: './src/index.js' + app: './src/index.tsx', }, 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' - ] - } + loader: 'ts-loader', }, - exclude: /node_modules/ + exclude: /node_modules/, }, { test: /\.css$/i, @@ -94,13 +86,13 @@ module.exports = { // Translates CSS into CommonJS 'css-loader', // Compiles Sass to CSS - 'sass-loader' - ] - } - ] + 'sass-loader', + ], + }, + ], }, resolve: { - extensions: ['.js', '.ts', '.tsx'] + extensions: ['.js', '.ts', '.tsx'], }, devtool: 'inline-source-map', devServer: devServer, @@ -108,24 +100,23 @@ module.exports = { new UrbitShipPlugin(urbitrc), new HtmlWebpackPlugin({ title: 'Bitcoin Wallet', - template: './public/index.html' - }) - + template: './public/index.html', + }), ], watch: true, watchOptions: { poll: true, - ignored: '/node_modules/' + ignored: '/node_modules/', }, output: { filename: 'index.js', chunkFilename: 'index.js', path: path.resolve(__dirname, '../dist'), publicPath: '/apps/bitcoin/', - globalObject: 'this' + globalObject: 'this', }, optimization: { minimize: false, - usedExports: true - } + usedExports: true, + }, }; diff --git a/pkg/btc-wallet/config/webpack.prod.js b/pkg/btc-wallet/config/webpack.prod.js index fbdbae0590..930182af0f 100644 --- a/pkg/btc-wallet/config/webpack.prod.js +++ b/pkg/btc-wallet/config/webpack.prod.js @@ -7,59 +7,52 @@ module.exports = { node: { fs: 'empty' }, mode: 'production', entry: { - app: './src/index.js' + app: './src/index.tsx', }, module: { rules: [ { - test: /\.jsx?$/, + test: /\.(j|t)sx?$/, 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' - ] - } + loader: 'ts-loader', }, - exclude: /node_modules/ + exclude: /node_modules/, }, { - test: /\.css$/i, + test: /\.css$/i, use: [ // Creates `style` nodes from JS strings 'style-loader', // Translates CSS into CommonJS 'css-loader', // Compiles Sass to CSS - 'sass-loader' - ] - } - ] + 'sass-loader', + ], + }, + ], }, resolve: { - extensions: ['.js', '.ts', '.tsx'] + extensions: ['.js', '.ts', '.tsx'], }, devtool: 'source-map', plugins: [ new CleanWebpackPlugin(), new HtmlWebpackPlugin({ title: 'Bitcoin Wallet', - template: './public/index.html' - }) + template: './public/index.html', + }), ], output: { filename: (pathData) => { - return pathData.chunk.name === 'app' ? 'index.[contenthash].js' : '[name].js'; + return pathData.chunk.name === 'app' + ? 'index.[contenthash].js' + : '[name].js'; }, path: path.resolve(__dirname, '../dist'), publicPath: '/apps/bitcoin/', }, optimization: { minimize: true, - usedExports: true - } + usedExports: true, + }, }; diff --git a/pkg/btc-wallet/package-lock.json b/pkg/btc-wallet/package-lock.json index 458e93169b..6ade588292 100644 --- a/pkg/btc-wallet/package-lock.json +++ b/pkg/btc-wallet/package-lock.json @@ -1430,6 +1430,22 @@ "@types/node": "*" } }, + "@types/history": { + "version": "4.7.9", + "resolved": "https://registry.npmjs.org/@types/history/-/history-4.7.9.tgz", + "integrity": "sha512-MUc6zSmU3tEVnkQ78q0peeEjKWPUADMlC/t++2bI8WnAG2tvYRPIgHG8lWkXwqc8MsUF6Z2MOf+Mh5sazOmhiQ==", + "dev": true + }, + "@types/hoist-non-react-statics": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.1.tgz", + "integrity": "sha512-iMIqiko6ooLrTh1joXodJK5X9xeEALT1kM5G3ZLhD3hszxBdIEd5C75U834D9mLcINgD4OyZf5uQXjkuYydWvA==", + "dev": true, + "requires": { + "@types/react": "*", + "hoist-non-react-statics": "^3.3.0" + } + }, "@types/html-minifier-terser": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/@types/html-minifier-terser/-/html-minifier-terser-5.1.1.tgz", @@ -1441,6 +1457,12 @@ "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.7.tgz", "integrity": "sha512-cxWFQVseBm6O9Gbw1IWb8r6OS4OhSt3hPZLkFApLjM8TEXROBuQGLAH2i2gZpcXdLBIrpXuTDhH7Vbm1iXmNGA==" }, + "@types/lodash": { + "version": "4.14.171", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.171.tgz", + "integrity": "sha512-7eQ2xYLLI/LsicL2nejW9Wyko3lcpN6O/z0ZLHrEQsg280zIdCv1t/0m6UtBjUHokCGBQ3gYTbHzDkZ1xOBwwg==", + "dev": true + }, "@types/minimatch": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.4.tgz", @@ -1459,12 +1481,76 @@ "integrity": "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==", "dev": true }, + "@types/prop-types": { + "version": "15.7.4", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.4.tgz", + "integrity": "sha512-rZ5drC/jWjrArrS8BR6SIr4cWpW09RNTYt9AMZo3Jwwif+iacXAqgVjm0B0Bv/S1jhDXKHqRVNCbACkJ89RAnQ==", + "dev": true + }, + "@types/react": { + "version": "17.0.15", + "resolved": "https://registry.npmjs.org/@types/react/-/react-17.0.15.tgz", + "integrity": "sha512-uTKHDK9STXFHLaKv6IMnwp52fm0hwU+N89w/p9grdUqcFA6WuqDyPhaWopbNyE1k/VhgzmHl8pu1L4wITtmlLw==", + "dev": true, + "requires": { + "@types/prop-types": "*", + "@types/scheduler": "*", + "csstype": "^3.0.2" + } + }, + "@types/react-dom": { + "version": "17.0.9", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-17.0.9.tgz", + "integrity": "sha512-wIvGxLfgpVDSAMH5utdL9Ngm5Owu0VsGmldro3ORLXV8CShrL8awVj06NuEXFQ5xyaYfdca7Sgbk/50Ri1GdPg==", + "dev": true, + "requires": { + "@types/react": "*" + } + }, + "@types/react-router": { + "version": "5.1.16", + "resolved": "https://registry.npmjs.org/@types/react-router/-/react-router-5.1.16.tgz", + "integrity": "sha512-8d7nR/fNSqlTFGHti0R3F9WwIertOaaA1UEB8/jr5l5mDMOs4CidEgvvYMw4ivqrBK+vtVLxyTj2P+Pr/dtgzg==", + "dev": true, + "requires": { + "@types/history": "*", + "@types/react": "*" + } + }, + "@types/react-router-dom": { + "version": "5.1.8", + "resolved": "https://registry.npmjs.org/@types/react-router-dom/-/react-router-dom-5.1.8.tgz", + "integrity": "sha512-03xHyncBzG0PmDmf8pf3rehtjY0NpUj7TIN46FrT5n1ZWHPZvXz32gUyNboJ+xsL8cpg8bQVLcllptcQHvocrw==", + "dev": true, + "requires": { + "@types/history": "*", + "@types/react": "*", + "@types/react-router": "*" + } + }, + "@types/scheduler": { + "version": "0.16.2", + "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.2.tgz", + "integrity": "sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew==", + "dev": true + }, "@types/source-list-map": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/@types/source-list-map/-/source-list-map-0.1.2.tgz", "integrity": "sha512-K5K+yml8LTo9bWJI/rECfIPrGgxdpeNbj+d53lwN4QjW1MCwlkhUms+gtdzigTeUyBr09+u8BwOIY3MXvHdcsA==", "dev": true }, + "@types/styled-components": { + "version": "5.1.11", + "resolved": "https://registry.npmjs.org/@types/styled-components/-/styled-components-5.1.11.tgz", + "integrity": "sha512-u8g3bSw9KUiZY+S++gh+LlURGraqBe3MC5I5dygrNjGDHWWQfsmZZRTJ9K9oHU2CqWtxChWmJkDI/gp+TZPQMw==", + "dev": true, + "requires": { + "@types/hoist-non-react-statics": "*", + "@types/react": "*", + "csstype": "^3.0.2" + } + }, "@types/tapable": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/@types/tapable/-/tapable-1.0.7.tgz", @@ -1499,6 +1585,12 @@ "source-map": "^0.6.0" } }, + "@types/webpack-env": { + "version": "1.16.2", + "resolved": "https://registry.npmjs.org/@types/webpack-env/-/webpack-env-1.16.2.tgz", + "integrity": "sha512-vKx7WNQNZDyJveYcHAm9ZxhqSGLYwoyLhrHjLBOkw3a7cT76sTdjgtwyijhk1MaHyRIuSztcVwrUOO/NEu68Dw==", + "dev": true + }, "@types/webpack-sources": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/@types/webpack-sources/-/webpack-sources-2.1.0.tgz", @@ -3472,6 +3564,12 @@ "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==" }, + "csstype": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.0.8.tgz", + "integrity": "sha512-jXKhWqXPmlUeoQnF/EhTtTl4C9SnrxSH/jZUih3jmO6lBKr99rP3/+FmrMj4EFpOXzMtXHAZkd3x0E6h6Fgflw==", + "dev": true + }, "cycle": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/cycle/-/cycle-1.0.3.tgz", @@ -10240,6 +10338,130 @@ "resolved": "https://registry.npmjs.org/transformation-matrix/-/transformation-matrix-2.1.1.tgz", "integrity": "sha512-74MoNHhwLVuzwaPDcAecFjSkOA9vwWqyOdkeB0Be8Jc/IWSS5SNZKapFllqzkTliqZptkvqX5CZnVeDvfhN8cw==" }, + "ts-loader": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/ts-loader/-/ts-loader-8.2.0.tgz", + "integrity": "sha512-ebXBFrNyMSmbWgjnb3WBloUBK+VSx1xckaXsMXxlZRDqce/OPdYBVN5efB0W3V0defq0Gcy4YuzvPGqRgjj85A==", + "dev": true, + "requires": { + "chalk": "^4.1.0", + "enhanced-resolve": "^4.0.0", + "loader-utils": "^2.0.0", + "micromatch": "^4.0.0", + "semver": "^7.3.4" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "json5": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.0.tgz", + "integrity": "sha512-f+8cldu7X/y7RAJurMEJmdoKXGB/X550w2Nr3tTbezL6RwEE/iMcm+tZnXeoZtKuOq6ft8+CqzEkrIgx1fPoQA==", + "dev": true, + "requires": { + "minimist": "^1.2.5" + } + }, + "loader-utils": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.0.tgz", + "integrity": "sha512-rP4F0h2RaWSvPEkD7BLDFQnvSf+nK+wr3ESUjNTyAGobqrijmW92zc+SO6d4p4B1wh7+B/Jg1mkQe5NYUEHtHQ==", + "dev": true, + "requires": { + "big.js": "^5.2.2", + "emojis-list": "^3.0.0", + "json5": "^2.1.2" + } + }, + "lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "requires": { + "yallist": "^4.0.0" + } + }, + "micromatch": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.4.tgz", + "integrity": "sha512-pRmzw/XUcwXGpD9aI9q/0XOwLNygjETJ8y0ao0wdqprrzDa4YnxLcz7fQRZr8voh8V10kGhABbNcHVk5wHgWwg==", + "dev": true, + "requires": { + "braces": "^3.0.1", + "picomatch": "^2.2.3" + } + }, + "picomatch": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.0.tgz", + "integrity": "sha512-lY1Q/PiJGC2zOv/z391WOTD+Z02bCgsFfvxoXXf6h7kv9o+WmsmzYqrAwY63sNgOxE4xEdq0WyUnXfKeBrSvYw==", + "dev": true + }, + "semver": { + "version": "7.3.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", + "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", + "dev": true, + "requires": { + "lru-cache": "^6.0.0" + } + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + }, + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + } + } + }, "tslib": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.1.0.tgz", @@ -10293,9 +10515,9 @@ "integrity": "sha512-7uc1O8h1M1g0rArakJdf0uLRSSgFcYexrVoKo+bzJd32gd4gDy2L/Z+8/FjPnU9ydY3pEnVPtr9FyscYY60K1g==" }, "typescript": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.2.3.tgz", - "integrity": "sha512-qOcYwxaByStAWrBf4x0fibwZvMRG+r4cQoTjbPtUlrWjBHbmCAww1i448U0GJ+3cNNEtebDteo/cHOR3xJ4wEw==", + "version": "4.3.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.3.5.tgz", + "integrity": "sha512-DqQgihaQ9cUrskJo9kIyW/+g0Vxsk8cDtZ52a3NGh0YNTfpUSArXSohyUGnvbPazEPLu398C0UxmKSOrPumUzA==", "dev": true }, "unbox-primitive": { diff --git a/pkg/btc-wallet/package.json b/pkg/btc-wallet/package.json index 8e4904344c..5481320935 100644 --- a/pkg/btc-wallet/package.json +++ b/pkg/btc-wallet/package.json @@ -20,6 +20,11 @@ "@babel/preset-env": "^7.9.5", "@babel/preset-react": "^7.9.4", "@babel/preset-typescript": "^7.13.0", + "@types/lodash": "^4.14.171", + "@types/react-dom": "^17.0.9", + "@types/react-router-dom": "^5.1.8", + "@types/styled-components": "^5.1.11", + "@types/webpack-env": "^1.16.2", "@welldone-software/why-did-you-render": "^6.1.1", "babel-loader": "^8.1.0", "babel-plugin-root-import": "^6.5.0", @@ -36,7 +41,8 @@ "react-hot-loader": "^4.12.21", "sass": "^1.26.5", "sass-loader": "^8.0.2", - "typescript": "^4.2.3", + "ts-loader": "8.2.0", + "typescript": "^4.3.5", "webpack": "^4.43.0", "webpack-cli": "^3.3.11", "webpack-dev-server": "^3.10.3" diff --git a/pkg/btc-wallet/src/App.tsx b/pkg/btc-wallet/src/App.tsx new file mode 100644 index 0000000000..a47ad3a9d2 --- /dev/null +++ b/pkg/btc-wallet/src/App.tsx @@ -0,0 +1,40 @@ +import React from 'react'; +import { BrowserRouter } from 'react-router-dom'; +import { ThemeProvider } from 'styled-components'; +import light from './themes/light'; +import { Box, Reset } from '@tlon/indigo-react'; +import StartupModal from './components/StartupModal'; +import { useSettings } from './hooks/useSettings'; +import Body from './components/Body'; + +const App: React.FC = () => { + const { loaded, wallet, provider, scanProgress } = useSettings(); + const scanning = scanProgress?.main !== null || scanProgress?.change !== null; + const blur = !loaded || scanning ? false : !(wallet && provider); + + return ( + + + + {loaded && !scanning ? : null} + + + + + + ); +}; + +export default App; diff --git a/pkg/btc-wallet/src/components/Balance.tsx b/pkg/btc-wallet/src/components/Balance.tsx new file mode 100644 index 0000000000..2fcab6afd8 --- /dev/null +++ b/pkg/btc-wallet/src/components/Balance.tsx @@ -0,0 +1,162 @@ +import React, { useState } from 'react'; +import { Row, Text, Button, Col } from '@tlon/indigo-react'; +import Send from './Send/Send'; +import CurrencyPicker from './CurrencyPicker'; +import { copyToClipboard, satsToCurrency } from '../lib/util'; +import { useSettings } from '../hooks/useSettings'; +import { api } from '../lib/api'; + +const Balance = () => { + const { + address, + confirmedBalance: sats, + unconfirmedBalance: unconfirmedSats, + denomination, + currencyRates, + setPsbt, + setFee, + setError, + scanProgress, + } = useSettings(); + const [sending, setSending] = useState(false); + const [copiedButton, setCopiedButton] = useState(false); + const [copiedString, setCopiedString] = useState(false); + const scanning = scanProgress?.main !== null || scanProgress?.change !== null; + + const copyAddress = async (arg: 'string' | 'button') => { + await copyToClipboard(address); + api.btcWalletCommand({ 'gen-new-address': null }); + + if (arg === 'button') { + setCopiedButton(true); + setTimeout(() => { + setCopiedButton(false); + }, 2000); + } else if (arg === 'string') { + setCopiedString(true); + setTimeout(() => { + setCopiedString(false); + }, 2000); + } + }; + + const unconfirmedString = unconfirmedSats ? ` (${unconfirmedSats}) ` : ''; + + const value = satsToCurrency(sats, denomination, currencyRates); + const sendDisabled = sats === 0; + const addressText = + address === null ? '' : address.slice(0, 6) + '...' + address.slice(-6); + + const conversion = currencyRates[denomination]?.last; + + return ( + <> + {sending ? ( + { + setSending(false); + setPsbt(''); + setFee(0); + setError(''); + }} + /> + ) : ( + + + + Balance + + copyAddress('string')} + > + {copiedString ? 'copied' : addressText} + + + + + + {value} + + {scanning ? ( + + + + Balance will be updated shortly: + + + + + {scanProgress.main === null ? 0 : scanProgress.main} main + wallet addresses scanned + + + + {scanProgress.change === null ? 0 : scanProgress.change}{' '} + change wallet addresses scanned + + + ) : ( + {`${sats}${unconfirmedString} sats`} + )} + + + + + + + )} + + ); +}; + +export default Balance; diff --git a/pkg/btc-wallet/src/components/Body.tsx b/pkg/btc-wallet/src/components/Body.tsx new file mode 100644 index 0000000000..57e3be8c64 --- /dev/null +++ b/pkg/btc-wallet/src/components/Body.tsx @@ -0,0 +1,44 @@ +import React from 'react'; +import { Box, LoadingSpinner, Col } from '@tlon/indigo-react'; +import { Switch, Route } from 'react-router-dom'; +import Balance from './Balance'; +import Transactions from './Transactions/Transactions'; +import Warning from './Warning'; +import Header from './Header'; +import Settings from './Settings'; +import { useSettings } from '../hooks/useSettings'; + +const Body: React.FC = () => { + const { loaded, showWarning: warning } = useSettings(); + const cardWidth = window.innerWidth <= 475 ? '350px' : '400px'; + return !loaded ? ( + + + + ) : ( + + + +
+ + + + + +
+ {!warning ? null : } + + + + + + ); +}; + +export default Body; diff --git a/pkg/btc-wallet/src/components/CurrencyPicker.tsx b/pkg/btc-wallet/src/components/CurrencyPicker.tsx new file mode 100644 index 0000000000..7e3498d26b --- /dev/null +++ b/pkg/btc-wallet/src/components/CurrencyPicker.tsx @@ -0,0 +1,39 @@ +import React from 'react'; +import { Icon, Row, Text } from '@tlon/indigo-react'; +import { api } from '../lib/api'; +import { useSettings } from '../hooks/useSettings'; + +const CurrencyPicker = () => { + const { denomination, currencyRates } = useSettings(); + const switchCurrency = () => { + let newCurrency; + if (denomination === 'BTC') { + if ((currencyRates as any)['USD']) { + newCurrency = 'USD'; + } + } else if (denomination === 'USD') { + newCurrency = 'BTC'; + } + console.log({ newCurrency, denomination }); + let setCurrency = { + 'put-entry': { + desk: window.desk, + value: newCurrency, + 'entry-key': 'currency', + 'bucket-key': 'btc-wallet', + }, + }; + api.settingsEvent(setCurrency); + }; + + return ( + switchCurrency()}> + + + {denomination} + + + ); +}; + +export default CurrencyPicker; diff --git a/pkg/btc-wallet/src/components/Error.tsx b/pkg/btc-wallet/src/components/Error.tsx new file mode 100644 index 0000000000..cf41266df6 --- /dev/null +++ b/pkg/btc-wallet/src/components/Error.tsx @@ -0,0 +1,32 @@ +import React from 'react'; +import { Text } from '@tlon/indigo-react'; + +enum ErrorTypes { + 'cant-pay-ourselves' = 'Cannot pay ourselves', + 'no-comets' = 'Cannot pay comets', + 'no-dust' = 'Cannot send dust', + 'tx-being-signed' = 'Cannot pay when transaction is being signed', + 'insufficient-balance' = 'Insufficient confirmed balance', + 'broadcast-fail' = 'Transaction broadcast failed', + 'invalid-master-ticker' = 'Invalid master ticket', + 'invalid-signed' = 'Invalid signed bitcoin transaction', +} + +const Error = ({ + error, + fontSize, + ...rest +}: { + error: string; + fontSize?: string; +}) => ( + + { + (ErrorTypes as any)[ + Object.keys(ErrorTypes).filter((et) => et === error)[0] + ] + } + +); + +export default Error; diff --git a/pkg/btc-wallet/src/components/Header.tsx b/pkg/btc-wallet/src/components/Header.tsx new file mode 100644 index 0000000000..4e1422b887 --- /dev/null +++ b/pkg/btc-wallet/src/components/Header.tsx @@ -0,0 +1,81 @@ +import React from 'react'; +import { Box, Icon, Row, Text } from '@tlon/indigo-react'; +import { Link } from 'react-router-dom'; +import { useSettings } from '../hooks/useSettings'; + +const Header = ({ settings }: { settings: boolean }) => { + const { provider } = useSettings(); + let icon = settings ? 'X' : 'Adjust'; + let iconColor = settings ? 'black' : 'orange'; + let iconLink = settings ? '/' : '/settings'; + + let connection = null; + let badge = null; + if (!(provider && provider.connected)) { + connection = ( + + Provider Offline + + ); + + if (!settings) { + badge = ( + + ); + } + } + + return ( + + + + + + + Bitcoin + + + + {connection} + + + {badge} + + + + + + ); +}; + +export default Header; diff --git a/pkg/btc-wallet/src/components/ProviderModal.tsx b/pkg/btc-wallet/src/components/ProviderModal.tsx new file mode 100644 index 0000000000..7ce440ce22 --- /dev/null +++ b/pkg/btc-wallet/src/components/ProviderModal.tsx @@ -0,0 +1,166 @@ +import React, { useEffect, useState } from 'react'; +import { + Box, + Text, + Button, + StatelessTextInput, + Icon, + Row, + LoadingSpinner, +} from '@tlon/indigo-react'; +import { isValidPatp } from 'urbit-ob'; +import { api } from '../lib/api'; +import { useSettings } from '../hooks/useSettings'; + +enum providerStatuses { + checking, + failed, + ready, + initial = '', +} + +const ProviderModal = () => { + const { providerPerms } = useSettings(); + const [providerStatus, setProviderStatus] = useState( + providerStatuses.initial + ); + const [potentialProvider, setPotentialProvider] = useState(null); + const [provider, setProvider] = useState(null); + const [connecting, setConnecting] = useState(false); + + const checkProvider = (e: React.ChangeEvent) => { + // TODO: loading states + setProviderStatus(providerStatuses.initial); + let givenProvider = e.target.value; + if (isValidPatp(givenProvider)) { + let command = { + 'check-provider': givenProvider, + }; + setPotentialProvider(givenProvider); + setProviderStatus(providerStatuses.checking); + api.btcWalletCommand(command); + setTimeout(() => { + setProviderStatus(providerStatuses.failed); + }, 5000); + } + setProvider(givenProvider); + }; + + const submitProvider = () => { + if (providerStatus === providerStatuses.ready) { + let command = { + 'set-provider': provider, + }; + api.btcWalletCommand(command); + setConnecting(true); + } + }; + + useEffect(() => { + if (providerStatus !== providerStatuses.ready) { + if (providerPerms.provider === provider && providerPerms.permitted) { + setProviderStatus(providerStatuses.ready); + } + } + }, [providerStatus, providerPerms, provider, setProviderStatus]); + + let workingNode = null; + let workingColor = null; + let workingBg = null; + if (providerStatus === providerStatuses.ready) { + workingColor = 'green'; + workingBg = 'veryLightGreen'; + workingNode = ( + + + {provider} is a working provider node + + + ); + } else if (providerStatus === providerStatuses.failed) { + workingColor = 'red'; + workingBg = 'veryLightRed'; + workingNode = ( + + + {potentialProvider} is not a working provider node + + + ); + } + + return ( + + + + + Step 1 of 2: Set up Bitcoin Provider Node + + + + + In order to perform Bitcoin transaction in Landscape, you'll need + to set a provider node. A provider node is an urbit which maintains a + synced Bitcoin ledger. + + {' '} + Learn More + + + + + + Provider Node + + + + ) => + checkProvider(e) + } + /> + {providerStatus === providerStatuses.checking ? ( + + ) : null} + + {workingNode} + + + {connecting ? : null} + + + ); +}; + +export default ProviderModal; diff --git a/pkg/btc-wallet/src/components/Send/BridgeInvoice.tsx b/pkg/btc-wallet/src/components/Send/BridgeInvoice.tsx new file mode 100644 index 0000000000..1753d79beb --- /dev/null +++ b/pkg/btc-wallet/src/components/Send/BridgeInvoice.tsx @@ -0,0 +1,223 @@ +import React, { useEffect, useRef, useState } from 'react'; +import { + Box, + Icon, + StatelessTextInput as Input, + Row, + Text, + Button, + Col, + LoadingSpinner, +} from '@tlon/indigo-react'; +import Sigil from '../Sigil'; +import * as bitcoin from 'bitcoinjs-lib'; +import { isValidPatp } from 'urbit-ob'; +import Sent from './Sent'; +import Error from '../Error'; +import { satsToCurrency } from '../../lib/util'; +import { useSettings } from '../../hooks/useSettings'; +import { api } from '../../lib/api'; + +type Props = { + payee: string; + stopSending: () => void; + satsAmount: number; +}; + +const BridgeInvoice: React.FC = ({ payee, stopSending, satsAmount }) => { + const { error, currencyRates, fee, broadcastSuccess, denomination, psbt } = + useSettings(); + const [txHex, setTxHex] = useState(''); + const [ready, setReady] = useState(false); + const [localError, setLocalError] = useState(''); + const [broadcasting, setBroadcasting] = useState(false); + const invoiceRef = useRef(); + + useEffect(() => { + if (broadcasting && localError !== '') { + setBroadcasting(false); + } + if (error !== '') { + setLocalError(error); + } + }, [error, broadcasting, setBroadcasting]); + + useEffect(() => { + window.open('https://bridge.urbit.org/?kind=btc&utx=' + psbt); + }); + + const broadCastTx = (hex: string) => { + let command = { + 'broadcast-tx': hex, + }; + return api.btcWalletCommand(command); + }; + + const sendBitcoin = (hex: string) => { + try { + bitcoin.Transaction.fromHex(hex); + broadCastTx(hex); + setBroadcasting(true); + } catch (e) { + setLocalError('invalid-signed'); + setBroadcasting(false); + } + }; + + const checkTxHex = (e: React.ChangeEvent) => { + setTxHex(e.target.value); + setReady(txHex.length > 0); + setLocalError(''); + }; + + let inputColor = 'black'; + let inputBg = 'white'; + let inputBorder = 'lightGray'; + + if (localError !== '') { + inputColor = 'red'; + inputBg = 'veryLightRed'; + inputBorder = 'red'; + } + + const isShip = isValidPatp(payee); + + const icon = isShip ? ( + + ) : ( + + + + ); + + return ( + <> + {broadcastSuccess ? ( + + ) : ( + + + stopSending()} /> + + + + + {satsToCurrency(satsAmount, denomination, currencyRates)} + + + + {`${satsAmount} sats`} + + + {`Fee: ${satsToCurrency( + fee, + denomination, + currencyRates + )} (${fee} sats)`} + + + + You are paying + + + + {icon} + + {payee} + + + + + + Bridge signed transaction + + + + + Copy the signed transaction from Bridge + + + ) => checkTxHex(e)} + /> + {localError !== '' && ( + + + + )} + + + { + // @ts-ignore + broadcasting ? : null + } + + + )} + + ); +}; + +export default BridgeInvoice; diff --git a/pkg/btc-wallet/src/components/Send/ExternalInvoice.tsx b/pkg/btc-wallet/src/components/Send/ExternalInvoice.tsx new file mode 100644 index 0000000000..864425c59a --- /dev/null +++ b/pkg/btc-wallet/src/components/Send/ExternalInvoice.tsx @@ -0,0 +1,275 @@ +import React, { useEffect, useRef, useState } from 'react'; +import { + Box, + Icon, + StatelessTextInput as Input, + Row, + Text, + Button, + Col, + LoadingSpinner, +} from '@tlon/indigo-react'; +import Sigil from '../Sigil'; +import * as bitcoin from 'bitcoinjs-lib'; +import { isValidPatp } from 'urbit-ob'; +import Sent from './Sent'; +import Error from '../Error'; +import { copyToClipboard, satsToCurrency } from '../../lib/util'; +import { useSettings } from '../../hooks/useSettings'; +import { api } from '../../lib/api'; + +type Props = { + payee: string; + stopSending: () => void; + satsAmount: number; +}; + +const ExternalInvoice: React.FC = ({ + payee, + stopSending, + satsAmount, +}) => { + const { error, currencyRates, fee, broadcastSuccess, denomination, psbt } = + useSettings(); + const [txHex, setTxHex] = useState(''); + const [ready, setReady] = useState(false); + const [localError, setLocalError] = useState(''); + const [broadcasting, setBroadcasting] = useState(false); + const invoiceRef = useRef(); + + useEffect(() => { + if (broadcasting && localError !== '') { + setBroadcasting(false); + } + if (error !== '') { + setLocalError(error); + } + }, [error, broadcasting, setBroadcasting]); + + const broadCastTx = (hex: string) => { + let command = { + 'broadcast-tx': hex, + }; + return api.btcWalletCommand(command); + }; + + const sendBitcoin = (hex: string) => { + try { + bitcoin.Transaction.fromHex(hex); + broadCastTx(hex); + setBroadcasting(true); + } catch (e) { + setLocalError('invalid-signed'); + setBroadcasting(false); + } + }; + + const checkTxHex = (e: React.ChangeEvent) => { + setTxHex(e.target.value); + setReady(txHex.length > 0); + setLocalError(''); + }; + + const copyPsbt = () => { + copyToClipboard(psbt); + }; + + const downloadPsbtFile = () => { + const blob = new Blob([psbt]); + const downloadURL = URL.createObjectURL(blob); + const link = document.createElement('a'); + link.href = downloadURL; + link.setAttribute('download', 'urbit.psbt'); + document.body.appendChild(link); + link.click(); + link.parentNode.removeChild(link); + }; + + let inputColor = 'black'; + let inputBg = 'white'; + let inputBorder = 'lightGray'; + + if (localError !== '') { + inputColor = 'red'; + inputBg = 'veryLightRed'; + inputBorder = 'red'; + } + + const isShip = isValidPatp(payee); + + const icon = isShip ? ( + + ) : ( + + + + ); + + return ( + <> + {broadcastSuccess ? ( + + ) : ( + + + stopSending()} /> + + + + + {satsToCurrency(satsAmount, denomination, currencyRates)} + + + + {`${satsAmount} sats`} + + + {`Fee: ${satsToCurrency( + fee, + denomination, + currencyRates + )} (${fee} sats)`} + + + + You are paying + + + + {icon} + + {payee} + + + + + + Partially-signed Bitcoin Transaction (PSBT) + + + + + + + + + + + Signed Tx + + ) => + checkTxHex(e) + } + /> + {localError !== '' && ( + + + + )} + + + + {broadcasting ? : null} + + + )} + + ); +}; + +export default ExternalInvoice; diff --git a/pkg/btc-wallet/src/components/Send/FeePicker.tsx b/pkg/btc-wallet/src/components/Send/FeePicker.tsx new file mode 100644 index 0000000000..b84ef01b7c --- /dev/null +++ b/pkg/btc-wallet/src/components/Send/FeePicker.tsx @@ -0,0 +1,100 @@ +import React, { useEffect, useRef } from 'react'; +import { + Box, + Text, + Col, + StatelessRadioButtonField as RadioButton, + Label, +} from '@tlon/indigo-react'; +import { FeeChoices, feeLevels } from './Send'; + +type Props = { + feeChoices: FeeChoices; + feeValue: number; + setFeeValue: React.Dispatch; + feeDismiss: () => void; +}; + +const FeePicker: React.FC = ({ + feeChoices, + feeValue, + setFeeValue, + feeDismiss, +}) => { + const modalRef = useRef(null); + const clickDismiss = (e: any) => { + if (modalRef && !modalRef.current.contains(e.target)) { + feeDismiss(); + } + }; + useEffect(() => { + document.addEventListener('click', clickDismiss); + return () => { + document.removeEventListener('click', clickDismiss); + }; + }, []); + + return ( + + + Transaction Speed + + + { + setFeeValue(feeLevels.low); + feeDismiss(); + }} + > + + + + { + setFeeValue(feeLevels.mid); + feeDismiss(); + }} + > + + + + { + setFeeValue(feeLevels.high); + feeDismiss(); + }} + > + + + + + ); +}; + +export default FeePicker; diff --git a/pkg/btc-wallet/src/components/Send/Invoice.tsx b/pkg/btc-wallet/src/components/Send/Invoice.tsx new file mode 100644 index 0000000000..e26ca82c81 --- /dev/null +++ b/pkg/btc-wallet/src/components/Send/Invoice.tsx @@ -0,0 +1,280 @@ +import React, { useState, useEffect } from 'react'; +import { + Box, + Icon, + StatelessTextInput as Input, + Row, + Text, + Button, + Col, + LoadingSpinner, +} from '@tlon/indigo-react'; +import * as kg from 'urbit-key-generation'; +import { patp2dec, isValidPatq, isValidPatp } from 'urbit-ob'; +import * as bitcoin from 'bitcoinjs-lib'; +import Sigil from '../Sigil'; +import Sent from './Sent'; +import { satsToCurrency } from '../../lib/util'; +import Error from '../Error'; +import { useSettings } from '../../hooks/useSettings'; +import { api } from '../../lib/api'; +import { UrbitWallet } from '../../types'; + +const BITCOIN_MAINNET_INFO = { + messagePrefix: '\x18Bitcoin Signed Message:\n', + bech32: 'bc', + bip32: { + public: 0x04b24746, + private: 0x04b2430c, + }, + pubKeyHash: 0x00, + scriptHash: 0x05, + wif: 0x80, +}; + +const BITCOIN_TESTNET_INFO = { + messagePrefix: '\x18Bitcoin Signed Message:\n', + bech32: 'tb', + bip32: { + public: 0x045f1cf6, + private: 0x045f18bc, + }, + pubKeyHash: 0x6f, + scriptHash: 0xc4, + wif: 0xef, +}; + +type Props = { + stopSending: () => void; + payee: string; + satsAmount: number; +}; + +const Invoice: React.FC = ({ stopSending, payee, satsAmount }) => { + const { + error, + currencyRates, + psbt, + fee, + broadcastSuccess, + network, + denomination, + } = useSettings(); + const [masterTicket, setMasterTicket] = useState(''); + const [ready, setReady] = useState(false); + const [localError, setLocalError] = useState(error); + const [broadcasting, setBroadcasting] = useState(false); + + useEffect(() => { + if (broadcasting && localError !== '') { + setBroadcasting(false); + } + }, [error, broadcasting, setBroadcasting]); + + const broadCastTx = (psbtHex: string) => { + let command = { + 'broadcast-tx': psbtHex, + }; + return api.btcWalletCommand(command); + }; + + const sendBitcoin = (ticket: string, psbt: string) => { + const newPsbt = bitcoin.Psbt.fromBase64(psbt); + setBroadcasting(true); + kg.generateWallet({ + ticket, + ship: parseInt(patp2dec('~' + window.ship)), + }).then((urbitWallet: UrbitWallet) => { + // this wasn't being used, not clear why it was pulled out. + // const { xpub } = + // network === 'testnet' + // ? urbitWallet.bitcoinTestnet.keys + // : urbitWallet.bitcoinMainnet.keys; + + const { xprv: zprv } = urbitWallet.bitcoinMainnet.keys; + const { xprv: vprv } = urbitWallet.bitcoinTestnet.keys; + + const isTestnet = network === 'testnet'; + const derivationPrefix = isTestnet ? "m/84'/1'/0'/" : "m/84'/0'/0'/"; + + const btcWallet = isTestnet + ? bitcoin.bip32.fromBase58(vprv, BITCOIN_TESTNET_INFO) + : bitcoin.bip32.fromBase58(zprv, BITCOIN_MAINNET_INFO); + + try { + const hex = newPsbt.data.inputs + .reduce((psbt, input, idx) => { + // removing already derived part, eg m/84'/0'/0'/0/0 becomes 0/0 + const path = input.bip32Derivation[0].path + .split(derivationPrefix) + .join(''); + const prv = btcWallet.derivePath(path).privateKey; + return psbt.signInput(idx, bitcoin.ECPair.fromPrivateKey(prv)); + }, newPsbt) + .finalizeAllInputs() + .extractTransaction() + .toHex(); + + broadCastTx(hex); + } catch (e) { + setLocalError('invalid-master-ticket'); + setBroadcasting(false); + } + }); + }; + + const checkTicket = ({ + target: { value }, + }: React.ChangeEvent) => { + // TODO: port over bridge ticket validation logic + setMasterTicket(value); + setReady(isValidPatq(value)); + setLocalError(isValidPatq(value) ? '' : 'invalid-master-ticket'); + }; + + let inputColor = 'black'; + let inputBg = 'white'; + let inputBorder = 'lightGray'; + + if (error !== '') { + inputColor = 'red'; + inputBg = 'veryLightRed'; + inputBorder = 'red'; + } + + const isShip = isValidPatp(payee); + + const icon = isShip ? ( + + ) : ( + + + + ); + + return ( + <> + {broadcastSuccess ? ( + + ) : ( + + + stopSending()} /> + + + + + {satsToCurrency(satsAmount, denomination, currencyRates)} + + + + {`${satsAmount} sats`} + + + {`Fee: ${satsToCurrency( + fee, + denomination, + currencyRates + )} (${fee} sats)`} + + + + You are paying + + + + {icon} + + {payee} + + + + + + Ticket + + value.replace(/[^~-]+/g, '••••••')} + placeholder="••••••-••••••-••••••-••••••" + autoCapitalize="none" + autoCorrect="off" + color={inputColor} + backgroundColor={inputBg} + borderColor={inputBorder} + onChange={(e: React.ChangeEvent) => + checkTicket(e) + } + /> + + {error !== '' && ( + + + + )} + + + { + // @ts-ignore + broadcasting ? : null + } + + + )} + + ); +}; + +export default Invoice; diff --git a/pkg/btc-wallet/src/components/Send/Send.tsx b/pkg/btc-wallet/src/components/Send/Send.tsx new file mode 100644 index 0000000000..22c5ab3c3d --- /dev/null +++ b/pkg/btc-wallet/src/components/Send/Send.tsx @@ -0,0 +1,512 @@ +import React, { useEffect, useState } from 'react'; +import { + Box, + Icon, + StatelessTextInput as Input, + Row, + Text, + Button, + Col, + LoadingSpinner, +} from '@tlon/indigo-react'; +import Invoice from './Invoice'; +import BridgeInvoice from './BridgeInvoice'; +import ExternalInvoice from './ExternalInvoice'; +import FeePicker from './FeePicker'; +import Error from '../Error'; +import Signer from './Signer'; +import { validate } from 'bitcoin-address-validation'; +import * as ob from 'urbit-ob'; +import { useSettings } from '../../hooks/useSettings'; +import { api } from '../../lib/api'; +import { deSig } from '../../lib/util'; + +enum focusFields { + payee, + currency, + sats, + note, + empty = '', +} + +export enum feeLevels { + low, + mid, + high, +} + +export enum signMethods { + bridge = 'bridge', + masterTicket = 'masterTicket', + external = 'external', +} + +enum payeeTypes { + ship, + address, + initial = '', +} + +export type FeeChoices = { + [feeLevels.low]: [number, number]; + [feeLevels.mid]: [number, number]; + [feeLevels.high]: [number, number]; +}; + +type Props = { + stopSending: () => void; + value: string; + conversion: number; +}; + +const Send: React.FC = ({ stopSending, value, conversion }) => { + const { error, setError, network, psbt, denomination, shipWallets } = + useSettings(); + const [signing, setSigning] = useState(false); + const [denomAmount, setDenomAmount] = useState(0.0); + const [satsAmount, setSatsAmount] = useState(0); + const [payee, setPayee] = useState(''); + const [checkingPatp, setCheckingPatp] = useState(false); + const [payeeType, setPayeeType] = useState(payeeTypes.initial); + const [ready, setReady] = useState(false); + const [validPayee, setValidPayee] = useState(false); + const [focusedField, setFocusedField] = useState(focusFields.empty); + const [feeChoices, setFeeChoices] = useState({ + [feeLevels.low]: [10, 1], + [feeLevels.mid]: [10, 1], + [feeLevels.high]: [10, 1], + }); + const [feeValue, setFeeValue] = useState(feeLevels.mid); + const [showFeePicker, setShowFeePicker] = useState(false); + const [note, setNote] = useState(''); + const [choosingSignMethod, setChoosingSignMethod] = useState(false); + const [signMethod, setSignMethod] = useState(signMethods.bridge); + + const feeDismiss = () => { + setShowFeePicker(false); + }; + + const handleSetSignMethod = (signMethod: signMethods) => { + setSignMethod(signMethod); + setChoosingSignMethod(false); + }; + + const checkPayee = (e: React.ChangeEvent) => { + setError(''); + + const validPatPCommand = (validPatP: string) => { + let command = { 'check-payee': validPatP }; + api.btcWalletCommand(command); + setTimeout(() => { + setCheckingPatp(false); + }, 5000); + setCheckingPatp(true); + setPayeeType(payeeTypes.ship); + setPayee(validPatP); + }; + + let payeeReceived = e.target.value; + let isPatp = ob.isValidPatp(`~${deSig(payeeReceived)}`); + let isAddress = validate(payeeReceived); + if (isPatp) { + validPatPCommand(`~${deSig(payeeReceived)}`); + } else if (isAddress) { + setPayee(payeeReceived); + setReady(true); + setCheckingPatp(false); + setPayeeType(payeeTypes.address); + setValidPayee(true); + } else { + setPayee(payeeReceived); + setReady(false); + setCheckingPatp(false); + setPayeeType(payeeTypes.initial); + setValidPayee(false); + } + }; + + const toggleSignMethod = () => { + setChoosingSignMethod(!choosingSignMethod); + }; + + const initPayment = () => { + if (payeeType === payeeTypes.ship) { + let command = { + 'init-payment': { + payee, + value: satsAmount, + feyb: feeChoices[feeValue][1], + note: note || null, + }, + }; + + api.btcWalletCommand(command).then(() => setSigning(true)); + } else if (payeeType === payeeTypes.address) { + let command = { + 'init-payment-external': { + address: payee, + value: satsAmount, + feyb: 1, + note: note || null, + }, + }; + api.btcWalletCommand(command).then(() => setSigning(true)); + } + }; + + useEffect(() => { + if (network === 'bitcoin') { + let url = 'https://bitcoiner.live/api/fees/estimates/latest'; + fetch(url) + .then((res) => res.json()) + .then((n) => { + // let estimates = Object.keys(n.estimates); + // let mid = Math.floor(estimates.length / 2); + // let high = estimates.length - 1; + setFeeChoices({ + [feeLevels.high]: [30, n.estimates[30]['sat_per_vbyte']], + [feeLevels.mid]: [180, n.estimates[180]['sat_per_vbyte']], + [feeLevels.low]: [360, n.estimates[360]['sat_per_vbyte']], + }); + }); + } + }, []); + + useEffect(() => { + if (!ready && !checkingPatp) { + if (shipWallets.payee === payee.slice(1) && shipWallets.hasWallet) { + setReady(true); + setCheckingPatp(false); + setValidPayee(true); + } + } + }, [ready, checkingPatp, shipWallets, payee]); + + let payeeColor = 'black'; + let payeeBg = 'white'; + let payeeBorder = 'lightGray'; + if (error) { + payeeColor = 'red'; + payeeBorder = 'red'; + payeeBg = 'veryLightRed'; + } else if (focusedField === focusFields.payee && validPayee) { + payeeColor = 'green'; + payeeBorder = 'green'; + payeeBg = 'veryLightGreen'; + } else if (focusedField !== focusFields.payee && validPayee) { + payeeColor = 'blue'; + payeeBorder = 'white'; + payeeBg = 'white'; + } else if (focusedField !== focusFields.payee && !validPayee) { + payeeColor = 'red'; + payeeBorder = 'red'; + payeeBg = 'veryLightRed'; + } else if ( + focusedField === focusFields.payee && + !validPayee && + !checkingPatp && + payeeType === payeeTypes.ship + ) { + payeeColor = 'red'; + payeeBorder = 'red'; + payeeBg = 'veryLightRed'; + } + + const signReady = ready && satsAmount > 0 && !signing; + + let invoice = null; + + switch (signMethod) { + case signMethods.masterTicket: { + invoice = ( + + ); + break; + } + case signMethods.bridge: { + invoice = ( + + ); + break; + } + case signMethods.external: { + invoice = ( + + ); + break; + } + default: + break; + } + + return ( + <> + {signing && psbt ? ( + invoice + ) : ( + + + + + Send BTC + + + {value} + + stopSending()} /> + + + + + To + + {checkingPatp ? ( + + ) : null} + + { + setFocusedField(focusFields.payee); + }} + onBlur={() => { + setFocusedField(focusFields.empty); + }} + color={payeeColor} + backgroundColor={payeeBg} + borderColor={payeeBorder} + ml={2} + flexGrow="1" + fontSize="14px" + placeholder="~sampel-palnet or BTC address" + value={payee} + fontFamily="mono" + disabled={signing} + onChange={(e: React.ChangeEvent) => + checkPayee(e) + } + /> + + {error && ( + + {/* yes this is a hack */} + + + + )} + + + Amount + + { + setFocusedField(focusFields.currency); + }} + onBlur={() => { + setFocusedField(focusFields.empty); + }} + fontSize="14px" + width="100%" + type="number" + borderColor={ + focusedField === focusFields.currency ? 'lightGray' : 'none' + } + disabled={signing} + value={denomAmount} + onChange={(e: React.ChangeEvent) => { + setDenomAmount(parseFloat(e.target.value)); + setSatsAmount( + Math.round( + (parseFloat(e.target.value) / conversion) * 100000000 + ) + ); + }} + /> + + {denomination} + + + + {/* yes this is a hack */} + + { + setFocusedField(focusFields.sats); + }} + onBlur={() => { + setFocusedField(focusFields.empty); + }} + fontSize="14px" + width="100%" + type="number" + borderColor={ + focusedField === focusFields.sats ? 'lightGray' : 'none' + } + disabled={signing} + value={satsAmount} + onChange={(e: React.ChangeEvent) => { + setDenomAmount( + parseFloat(e.target.value) * (conversion / 100000000) + ); + setSatsAmount(parseInt(e.target.value, 10)); + }} + /> + + sats + + + + + Fee + + + + {feeChoices[feeValue][1]} sats/vbyte + + + + + + {!showFeePicker ? null : ( + + )} + + + + Note + + { + setFocusedField(focusFields.note); + }} + onBlur={() => { + setFocusedField(focusFields.empty); + }} + fontSize="14px" + width="100%" + placeholder="What's this for?" + type="text" + borderColor={ + focusedField === focusFields.note ? 'lightGray' : 'none' + } + disabled={signing} + value={note} + onChange={(e: React.ChangeEvent) => { + setNote(e.target.value); + }} + /> + + + + {!(signing && !error) ? null : ( + + )} + + + + {signMethod === signMethods.masterTicket && ( + + + + We recommend that you sign transactions using Bridge to protect + your master ticket. + + + )} + + )} + + ); +}; + +export default Send; diff --git a/pkg/btc-wallet/src/components/Send/Sent.tsx b/pkg/btc-wallet/src/components/Send/Sent.tsx new file mode 100644 index 0000000000..1990d4b285 --- /dev/null +++ b/pkg/btc-wallet/src/components/Send/Sent.tsx @@ -0,0 +1,42 @@ +import React from 'react'; +import { Icon, Row, Col, Center, Text } from '@tlon/indigo-react'; +import { satsToCurrency } from '../../lib/util'; +import { useSettings } from '../../hooks/useSettings'; + +type Props = { + payee: string; + stopSending: () => void; + satsAmount: number; +}; + +const Sent: React.FC = ({ payee, stopSending, satsAmount }) => { + const { denomination, currencyRates } = useSettings(); + return ( + + + + +
+ {`You sent BTC to ${payee}`} +
+
+ + {satsToCurrency(satsAmount, denomination, currencyRates)} + + {`${satsAmount} sats`} +
+ + ); +}; + +export default Sent; diff --git a/pkg/btc-wallet/src/components/Send/Signer.tsx b/pkg/btc-wallet/src/components/Send/Signer.tsx new file mode 100644 index 0000000000..f783011727 --- /dev/null +++ b/pkg/btc-wallet/src/components/Send/Signer.tsx @@ -0,0 +1,79 @@ +import React from 'react'; +import { Box, Button, Icon, Row } from '@tlon/indigo-react'; +import { signMethods } from './Send'; + +const signMethodLabels = { + bridge: 'Sign with Bridge', + masterTicket: 'Sign with Master Ticket', + external: 'Sign Externally (PSBT)', +}; + +type Props = { + signReady: boolean; + initPayment: () => void; + choosingSignMethod: boolean; + signMethod: signMethods; + setSignMethod: (arg: signMethods) => void; +}; + +const Signer: React.FC = ({ + signReady, + initPayment, + choosingSignMethod, + signMethod, + setSignMethod, +}) => { + return choosingSignMethod ? ( + + {Object.keys(signMethods).map((method) => ( + + + {signMethod === (signMethods as any)[method] && ( + + )} + + ))} + + ) : ( + + ); +}; + +export default Signer; diff --git a/pkg/btc-wallet/src/components/Settings.tsx b/pkg/btc-wallet/src/components/Settings.tsx new file mode 100644 index 0000000000..3a376f506d --- /dev/null +++ b/pkg/btc-wallet/src/components/Settings.tsx @@ -0,0 +1,115 @@ +import React from 'react'; +import { Row, Text, Button, Col } from '@tlon/indigo-react'; +import { useSettings } from '../hooks/useSettings'; +import { api } from '../lib/api'; + +const Settings = () => { + const { wallet, provider } = useSettings(); + + const changeProvider = () => { + api.btcWalletCommand({ 'set-provider': null }); + window.location.reload(); + }; + + const replaceWallet = () => { + api.btcWalletCommand({ + 'delete-wallet': wallet, + }); + }; + + let connColor = 'red'; + let connBackground = 'veryLightRed'; + let conn = 'Offline'; + let host = ''; + if (provider) { + if (provider.connected) conn = 'Connected'; + if (provider.host) host = provider.host; + if (provider.connected && provider.host) { + connColor = 'orange'; + connBackground = 'lightOrange'; + } + } + + return ( + + + + XPub Derivation + + + + + {wallet} + + + + + + + + BTC Node Provider + + + + + ~{host} + + + {conn} + + + + + + + ); +}; + +export default Settings; diff --git a/pkg/btc-wallet/src/js/components/lib/sigil.js b/pkg/btc-wallet/src/components/Sigil.tsx similarity index 78% rename from pkg/btc-wallet/src/js/components/lib/sigil.js rename to pkg/btc-wallet/src/components/Sigil.tsx index e47eff93f1..5de8569bdf 100644 --- a/pkg/btc-wallet/src/js/components/lib/sigil.js +++ b/pkg/btc-wallet/src/components/Sigil.tsx @@ -2,11 +2,11 @@ import React, { memo } from 'react'; import { sigil, reactRenderer } from '@tlon/sigil-js'; import { Box } from '@tlon/indigo-react'; -export const foregroundFromBackground = (background) => { +export const foregroundFromBackground = (background: string) => { const rgb = { r: parseInt(background.slice(1, 3), 16), g: parseInt(background.slice(3, 5), 16), - b: parseInt(background.slice(5, 7), 16) + b: parseInt(background.slice(5, 7), 16), }; const brightness = (299 * rgb.r + 587 * rgb.g + 114 * rgb.b) / 1000; const whiteBrightness = 255; @@ -14,7 +14,19 @@ export const foregroundFromBackground = (background) => { return whiteBrightness - brightness < 50 ? 'black' : 'white'; }; -export const Sigil = memo( +type Props = { + classes?: string; + color: string; + foreground?: string; + ship: string; + size: number; + svgClass?: string; + icon?: boolean; + padding?: number; + display?: string; +}; + +const Sigil: React.FC = memo( ({ classes = '', color, @@ -24,7 +36,7 @@ export const Sigil = memo( svgClass = '', icon = false, padding = 0, - display = 'inline-block' + display = 'inline-block', }) => { const innerSize = Number(size) - 2 * padding; const paddingPx = `${padding}px`; @@ -55,7 +67,7 @@ export const Sigil = memo( size: innerSize, icon, colors: [color, foregroundColor], - class: svgClass + class: svgClass, })} ); diff --git a/pkg/btc-wallet/src/components/StartupModal.tsx b/pkg/btc-wallet/src/components/StartupModal.tsx new file mode 100644 index 0000000000..e21f6ce2cc --- /dev/null +++ b/pkg/btc-wallet/src/components/StartupModal.tsx @@ -0,0 +1,44 @@ +import React from 'react'; +import { Box } from '@tlon/indigo-react'; +import WalletModal from './WalletModal'; +import ProviderModal from './ProviderModal'; +import { useSettings } from '../hooks/useSettings'; + +const StartupModal: React.FC = () => { + const { wallet, provider } = useSettings(); + let modal = null; + + if (wallet && provider) { + return null; + } else if (!provider) { + modal = ; + } else if (!wallet) { + modal = ; + } + return ( + + + {modal} + + + ); +}; + +export default StartupModal; diff --git a/pkg/btc-wallet/src/components/Transactions/Transaction.tsx b/pkg/btc-wallet/src/components/Transactions/Transaction.tsx new file mode 100644 index 0000000000..fd0df48ca4 --- /dev/null +++ b/pkg/btc-wallet/src/components/Transactions/Transaction.tsx @@ -0,0 +1,101 @@ +import React from 'react'; +import { Box, Row, Text, Col } from '@tlon/indigo-react'; +import _ from 'lodash'; +import TxAction from './TxAction'; +import TxCounterparty from './TxCounterparty'; +import { satsToCurrency } from '../../lib/util'; +import { useSettings } from '../../hooks/useSettings'; +import { Transaction as TransactionType } from '../../types'; + +const Transaction = ({ tx }: { tx: TransactionType }) => { + const { denomination, currencyRates } = useSettings(); + const pending = !tx.recvd; + + let weSent = _.find(tx.inputs, (input) => { + return input.ship === window.ship; + }); + let weRecv = tx.outputs.every((output) => { + return output.ship === window.ship; + }); + + let action: 'sent' | 'recv' | 'fail' = weRecv + ? 'recv' + : weSent + ? 'sent' + : 'recv'; + + let counterShip = null; + let counterAddress = null; + let value; + let sign; + + if (action === 'sent') { + let counter = _.find(tx.outputs, (output) => { + return output.ship !== window.ship; + }); + counterShip = _.get(counter, 'ship', null); + counterAddress = _.get(counter, 'val.address', null); + value = _.get(counter, 'val.value', null); + sign = '-'; + } else if (action === 'recv') { + value = _.reduce( + tx.outputs, + (sum, output) => { + if (output.ship === window.ship) { + return sum + output.val.value; + } else { + return sum; + } + }, + 0 + ); + + if (weSent && weRecv) { + counterAddress = _.get( + _.find(tx.inputs, (input) => { + return input.ship === window.ship; + }), + 'val.address', + null + ); + } else { + let counter = _.find(tx.inputs, (input) => { + return input.ship !== window.ship; + }); + counterShip = _.get(counter, 'ship', null); + counterAddress = _.get(counter, 'val.address', null); + } + sign = ''; + } + + let currencyValue = sign + satsToCurrency(value, denomination, currencyRates); + + const failure = Boolean(tx.failure); + if (failure) action = 'fail'; + + const txid = tx.txid.dat.slice(2).replaceAll('.', ''); + + return ( + + + + + {sign} + {value} sats + + + + + + {currencyValue} + + + ); +}; + +export default Transaction; diff --git a/pkg/btc-wallet/src/components/Transactions/Transactions.tsx b/pkg/btc-wallet/src/components/Transactions/Transactions.tsx new file mode 100644 index 0000000000..0eec82fb96 --- /dev/null +++ b/pkg/btc-wallet/src/components/Transactions/Transactions.tsx @@ -0,0 +1,43 @@ +import React from 'react'; +import { Box, Text, Col } from '@tlon/indigo-react'; +import Transaction from './Transaction'; +import { useSettings } from '../../hooks/useSettings'; + +const Transactions = () => { + const { history } = useSettings(); + if (!history || history.length <= 0) { + return ( + + + No Transactions Yet + + + ); + } else { + return ( + + {history.map((tx, i) => { + return ; + })} + + ); + } +}; + +export default Transactions; diff --git a/pkg/btc-wallet/src/components/Transactions/TxAction.tsx b/pkg/btc-wallet/src/components/Transactions/TxAction.tsx new file mode 100644 index 0000000000..6ed9e954d4 --- /dev/null +++ b/pkg/btc-wallet/src/components/Transactions/TxAction.tsx @@ -0,0 +1,78 @@ +import React from 'react'; +import { Box, Icon, Row, Text, LoadingSpinner } from '@tlon/indigo-react'; +import { useSettings } from '../../hooks/useSettings'; + +type Props = { + action: 'sent' | 'recv' | 'fail'; + pending: boolean; + txid: string; +}; + +const TxAction: React.FC = ({ action, pending, txid }) => { + const { network } = useSettings(); + const leftIcon = + action === 'sent' + ? 'ArrowSouth' + : action === 'recv' + ? 'ArrowNorth' + : action === 'fail' + ? 'X' + : 'NullIcon'; + + const actionColor = + action === 'sent' + ? 'sentBlue' + : action === 'recv' + ? 'recvGreen' + : action === 'fail' + ? 'gray' + : 'red'; + + const actionText = + action === 'sent' && !pending + ? 'Sent BTC' + : action === 'sent' && pending + ? 'Sending BTC' + : action === 'recv' && !pending + ? 'Received BTC' + : action === 'recv' && pending + ? 'Receiving BTC' + : action === 'fail' + ? 'Failed' + : 'error'; + + const pendingSpinner = !pending ? null : ( + + ); + + const url = + network === 'testnet' + ? `http://blockstream.info/testnet/tx/${txid}` + : `http://blockstream.info/tx/${txid}`; + + return ( + + + + + + {actionText} + + + + + {pendingSpinner} + + ); +}; + +export default TxAction; diff --git a/pkg/btc-wallet/src/components/Transactions/TxCounterparty.tsx b/pkg/btc-wallet/src/components/Transactions/TxCounterparty.tsx new file mode 100644 index 0000000000..8bbdad07dc --- /dev/null +++ b/pkg/btc-wallet/src/components/Transactions/TxCounterparty.tsx @@ -0,0 +1,41 @@ +import React from 'react'; +import { Box, Icon, Row, Text } from '@tlon/indigo-react'; +import Sigil from '../Sigil'; + +type Props = { + ship: string; + address: string; +}; + +const TxCounterparty: React.FC = ({ ship, address }) => { + const icon = ship ? ( + + ) : ( + + + + ); + const addressText = !address + ? '' + : address.slice(0, 6) + '...' + address.slice(-6); + const text = ship ? `~${ship}` : addressText; + + return ( + + {icon} + + {text} + + + ); +}; + +export default TxCounterparty; diff --git a/pkg/btc-wallet/src/components/WalletModal.tsx b/pkg/btc-wallet/src/components/WalletModal.tsx new file mode 100644 index 0000000000..e46c865d7e --- /dev/null +++ b/pkg/btc-wallet/src/components/WalletModal.tsx @@ -0,0 +1,268 @@ +import React, { useState } from 'react'; +import { + Box, + Text, + Button, + StatelessTextInput, + Icon, + Row, + LoadingSpinner, +} from '@tlon/indigo-react'; +import { patp2dec, isValidPatq } from 'urbit-ob'; +import * as kg from 'urbit-key-generation'; +import { useSettings } from '../hooks/useSettings'; +import { api } from '../lib/api'; +import { UrbitWallet } from '../types'; + +const WalletModal: React.FC = () => { + const { network } = useSettings(); + const [mode, setMode] = useState('xpub'); + const [masterTicket, setMasterTicket] = useState(''); + const [confirmedMasterTicket, setConfirmedMasterTicket] = useState(''); + const [xpub, setXpub] = useState(''); + const [readyToSubmit, setReadyToSubmit] = useState(false); + const [processingSubmission, setProcessingSubmission] = useState(false); + const [confirmingMasterTicket, setConfirmingMasterTicket] = useState(false); + const [error, setError] = useState(false); + + const checkTicket = ({ + target: { value }, + }: React.ChangeEvent) => { + // TODO: port over bridge ticket validation logic + if (confirmingMasterTicket) { + setConfirmedMasterTicket(value); + setReadyToSubmit(isValidPatq(value)); + } else { + setMasterTicket(value); + setReadyToSubmit(isValidPatq(value)); + } + }; + + const checkXPub = ({ + target: { value: xpubGiven }, + }: React.ChangeEvent) => { + setXpub(xpubGiven); + setReadyToSubmit(xpubGiven.length > 0); + }; + + const submitXPub = (givenXpub: string) => { + type AddWalletCommand = { + 'add-wallet': { + xpub: string; + fprint: number[]; + 'scan-to': number | null; + 'max-gap': number; + confs: number; + }; + }; + const command: AddWalletCommand = { + 'add-wallet': { + xpub: givenXpub, + fprint: [4, 0], + 'scan-to': null, + 'max-gap': 8, + confs: 1, + }, + }; + api.btcWalletCommand(command); + setProcessingSubmission(true); + }; + + const submitMasterTicket = (ticket: string) => { + setProcessingSubmission(true); + kg.generateWallet({ + ticket, + ship: parseInt(patp2dec('~' + window.ship)), + }).then((urbitWallet: UrbitWallet) => { + const { xpub: xpubFromWallet } = + network === 'testnet' + ? urbitWallet.bitcoinTestnet.keys + : urbitWallet.bitcoinMainnet.keys; + + submitXPub(xpubFromWallet); + }); + }; + + const buttonDisabled = !readyToSubmit || processingSubmission; + const inputDisabled = processingSubmission; + // const processingSpinner = !processingSubmission ? null : ; + + if (mode === 'masterTicket') { + return ( + + + + + Step 2 of 2: Import your extended public key + + + + + + We recommend that you import your wallet using Bridge to protect + your master ticket. + + + + {confirmingMasterTicket && ( + { + setConfirmingMasterTicket(false); + setMasterTicket(''); + setConfirmedMasterTicket(''); + setError(false); + }} + /> + )} + + {confirmingMasterTicket ? 'Confirm Master Ticket' : 'Master Ticket'} + + + + value.replace(/[^~-]+/g, '••••••')} + placeholder="••••••-••••••-••••••-••••••" + autoCapitalize="none" + autoCorrect="off" + onChange={(e: React.ChangeEvent) => + checkTicket(e) + } + /> + {!inputDisabled ? null : } + + {error && ( + + + Master tickets do not match + + + )} + + + + + + ); + } else if (mode === 'xpub') { + return ( + + + + + Step 2 of 2: Import your extended public key + + + + + Visit{' '} + + bridge.urbit.org + {' '} + to obtain your key + + + + + Extended Public Key (XPub) + + + + ) => checkXPub(e)} + mr={1} + /> + {!inputDisabled ? null : } + + + { + if (inputDisabled) return; + setMode('masterTicket'); + setXpub(''); + setMasterTicket(''); + setReadyToSubmit(false); + }} + > + Import using master ticket -> + + + + + ); + } +}; + +export default WalletModal; diff --git a/pkg/btc-wallet/src/components/Warning.tsx b/pkg/btc-wallet/src/components/Warning.tsx new file mode 100644 index 0000000000..466f144142 --- /dev/null +++ b/pkg/btc-wallet/src/components/Warning.tsx @@ -0,0 +1,73 @@ +import React from 'react'; +import { Box, Text, Button, Col, Anchor } from '@tlon/indigo-react'; +import { api } from '../lib/api'; +import { useSettings } from '../hooks/useSettings'; + +const Warning = () => { + const { setShowWarning } = useSettings(); + const understand = () => { + setShowWarning(false); + let removeWarning = { + 'put-entry': { + value: false, + desk: window.desk, + 'entry-key': 'warning', + 'bucket-key': 'btc-wallet', + }, + }; + api.settingsEvent(removeWarning); + }; + + return ( + + + + Warning! + +
+ + Be safe while using this wallet, and be sure to store responsible + amounts of BTC. + + + Always ensure that the checksum of the wallet matches that of the + wallet's repo. + +
+ + + Learn more on urbit.org + + + + +
+ ); +}; + +export default Warning; diff --git a/pkg/btc-wallet/src/hooks/useSettings.tsx b/pkg/btc-wallet/src/hooks/useSettings.tsx new file mode 100644 index 0000000000..8460c3998c --- /dev/null +++ b/pkg/btc-wallet/src/hooks/useSettings.tsx @@ -0,0 +1,397 @@ +import React, { createContext, useContext, useEffect, useState } from 'react'; +import _ from 'lodash'; +import { api } from '../lib/api'; +import { mapDenominationToSymbol, reduceHistory } from '../lib/util'; +import { + CurrencyRate, + Denomination, + Network, + Provider, + ProviderPerms, + ScanProgress, + ShipWallets, + Transaction, + TxidType, +} from '../types'; + +type SettingsContextType = { + network: Network; + setNetwork: React.Dispatch>; + loadedBtc: boolean; + setLoadedBtc: React.Dispatch>; + loadedSettings: boolean; + setLoadedSettings: React.Dispatch>; + loaded: boolean; + setLoaded: React.Dispatch>; + providerPerms: ProviderPerms; + setProviderPerms: React.Dispatch>; + shipWallets: ShipWallets; + setShipWallets: React.Dispatch>; + provider: Provider; + setProvider: React.Dispatch>; + wallet: string | null; + setWallet: React.Dispatch>; + confirmedBalance: number; + setConfirmedBalance: React.Dispatch>; + unconfirmedBalance: number; + setUnconfirmedBalance: React.Dispatch>; + btcState: any; + setBtcState: React.Dispatch>; + history: Transaction[]; + setHistory: React.Dispatch>; + fee: number; + setFee: React.Dispatch>; + psbt: string; + setPsbt: React.Dispatch>; + address: string | null; + setAddress: React.Dispatch>; + currencyRates: CurrencyRate; + setCurrencyRates: React.Dispatch>; + denomination: Denomination; + setDenomination: React.Dispatch>; + showWarning: boolean; + setShowWarning: React.Dispatch>; + error: string; + setError: React.Dispatch>; + broadcastSuccess: boolean; + setBroadcastSuccess: React.Dispatch>; + scanProgress: ScanProgress; + setScanProgress: React.Dispatch>; +}; + +export const SettingsContext = createContext({ + network: 'bitcoin', + setNetwork: () => {}, + loadedBtc: false, + setLoadedBtc: () => {}, + loadedSettings: true, + setLoadedSettings: () => {}, + loaded: false, + setLoaded: () => {}, + providerPerms: { provider: '', permitted: false }, + setProviderPerms: () => {}, + shipWallets: { payee: '', hasWallet: false }, + setShipWallets: () => {}, + provider: null, + setProvider: () => {}, + wallet: null, + setWallet: () => {}, + confirmedBalance: 0, + setConfirmedBalance: () => {}, + unconfirmedBalance: 0, + setUnconfirmedBalance: () => {}, + btcState: null, + setBtcState: () => {}, + history: [], + setHistory: () => {}, + fee: 0, + setFee: () => {}, + psbt: '', + setPsbt: () => {}, + address: null, + setAddress: () => {}, + currencyRates: { + BTC: { last: 1, symbol: 'BTC' }, + }, + setCurrencyRates: () => {}, + denomination: 'BTC', + setDenomination: () => {}, + showWarning: false, + setShowWarning: () => {}, + error: '', + setError: () => {}, + broadcastSuccess: false, + setBroadcastSuccess: () => {}, + scanProgress: { main: null, change: null }, + setScanProgress: () => {}, +}); + +type Props = { + channel: { setOnChannelError: (arg: () => void) => void }; +}; + +export const SettingsProvider: React.FC = ({ channel, children }) => { + const [network, setNetwork] = useState('bitcoin'); + const [channelData, setChannelData] = useState(null); + const [loadedBtc, setLoadedBtc] = useState(false); + const [loadedSettings, setLoadedSettings] = useState(false); + const [loaded, setLoaded] = useState(false); + const [providerPerms, setProviderPerms] = useState({ + provider: '', + permitted: false, + }); + const [shipWallets, setShipWallets] = useState({ + payee: '', + hasWallet: false, + }); + const [provider, setProvider] = useState(null); + const [wallet, setWallet] = useState(null); + const [confirmedBalance, setConfirmedBalance] = useState(0); + const [unconfirmedBalance, setUnconfirmedBalance] = useState(0); + const [btcState, setBtcState] = useState(null); + const [history, setHistory] = useState([]); + const [psbt, setPsbt] = useState(''); + const [fee, setFee] = useState(0); + const [address, setAddress] = useState(null); + const [currencyRates, setCurrencyRates] = useState({ + BTC: { last: 1, symbol: 'BTC' }, + }); + const [denomination, setDenomination] = useState('BTC'); + const [showWarning, setShowWarning] = useState(false); + const [error, setError] = useState(''); + const [broadcastSuccess, setBroadcastSuccess] = useState(false); + const [scanProgress, setScanProgress] = useState({ + main: null, + change: null, + }); + + const { Provider } = SettingsContext; + + const success = (event: any) => { + console.log({ event }); + setChannelData(event); + }; + const fail = (error: any) => console.log({ error }); + + const initializeBtcWallet = () => { + api.bind('/all', 'PUT', api.ship, 'btc-wallet', success, fail); + }; + + const initializeSettings = () => { + let app = 'settings-store'; + let path = `/bucket/${window.desk}btc-wallet`; + + fetch(`/~/scry/${app}${path}.json`) + .then((res) => res.json()) + .then((n) => { + let data = _.get(n, 'initial', false); + let bucketData = _.get(n, 'bucket', false); + if (data) { + setChannelData(n); + } + if (bucketData) { + let bucketWarning = _.get(n, 'bucket.warning', -1); + if (bucketWarning !== -1) { + setShowWarning(bucketWarning); + } + let bucketCurrency = _.get(n, 'bucket.currency', -1); + if (bucketCurrency !== -1) { + setDenomination(bucketCurrency); + } + setLoadedSettings(true); + if (loadedBtc) { + setLoaded(true); + } + } + }); + + api.bind(path, 'PUT', api.ship, app, success, fail); + }; + + const initializeCurrencyPoll = () => { + fetch('https://blockchain.info/ticker') + .then((res) => res.json()) + .then((n) => { + const newCurrencyRates: any = currencyRates; + for (let c in n) { + newCurrencyRates[c] = n[c]; + newCurrencyRates[c].symbol = mapDenominationToSymbol(c); + } + setCurrencyRates(newCurrencyRates); + setTimeout(() => initializeCurrencyPoll(), 1000 * 60 * 15); + }); + }; + + const start = () => { + if (api.ship) { + initializeBtcWallet(); + initializeSettings(); + initializeCurrencyPoll(); + } + }; + + const handleNewTx = (newTx: Transaction) => { + const { txid, recvd } = newTx; + let old = _.findIndex(history, (h: Transaction) => { + return h.txid.dat === txid.dat && h.txid.wid === txid.wid; + }); + if (old !== -1) { + const newHistory = history.filter((_, i) => i !== old); + setHistory(newHistory); + } + if (recvd === null && old === -1) { + const newHistory = [...history, newTx]; + setHistory(newHistory); + } else if (recvd !== null && old === -1) { + // we expect history to have null recvd values first, and the rest in + // descending order + let insertionIndex = _.findIndex(history, (h: Transaction) => { + return h.recvd < recvd && h.recvd !== null; + }); + const newHistory = history.map((o, i) => + i === insertionIndex ? newTx : o + ); + setHistory(newHistory); + } + }; + + const handleCancelTx = ({ wid, dat }: TxidType) => { + let entryIndex = _.findIndex(history, (h: Transaction) => { + return wid === h.txid.wid && dat === h.txid.dat; + }); + if (entryIndex > -1) { + history[entryIndex].failure = true; + } + }; + + useEffect(() => { + const initialData = channelData?.data?.initial; + const putEntryData = channelData?.data?.['settings-event']?.['put-entry']; + const btcStateData = channelData?.data?.['btc-state']; + const changeProvider = channelData?.data?.['change-provider']; + const newTx = channelData?.data?.['new-tx']; + const providerStatus = channelData?.data?.providerStatus; + const checkPayee = channelData?.data?.checkPayee; + const changeWallet = channelData?.data?.changeWallet; + const psbtData = channelData?.data.psbt; + const cancelTx = channelData?.data['cancel-tx']; + const addressData = channelData?.data?.address; + const balanceData = channelData?.data?.balance; + const errorData = channelData?.data?.error; + const broadcastSuccessData = channelData?.data?.['broadcast-success']; + const broadcastFailData = channelData?.data?.['broadcast-fail']; + const scanProgressData = channelData?.data?.['scan-progress']; + if (initialData) { + setProvider(initialData.provider); + setWallet(initialData.wallet); + setConfirmedBalance(_.get(initialData.balance, 'confirmed', null)); + setUnconfirmedBalance(_.get(initialData.balance, 'unconfirmed', null)); + setBtcState(initialData['btc-state']); + setHistory(reduceHistory(initialData.history)); + setAddress(initialData.address); + setLoadedBtc(true); + if (loadedSettings) { + setLoaded(true); + } + } + if (putEntryData && putEntryData?.['entry-key'] === 'currency') { + setDenomination(putEntryData.value); + } + if (putEntryData && putEntryData?.['entry-key'] === 'warning') { + setShowWarning(putEntryData.value); + } + if (btcStateData) { + setBtcState(btcStateData); + } + if (changeProvider) { + setProvider(changeProvider); + } + if (newTx) { + handleNewTx(newTx); + } + if (providerStatus) { + let newProviderPerms: any = providerPerms; + for (let c in providerStatus) { + newProviderPerms[c] = providerStatus[c]; + } + setProviderPerms(newProviderPerms); + } + if (checkPayee) { + let newShipWallets: any = shipWallets; + + for (let c in checkPayee) { + newShipWallets[c] = checkPayee[c]; + } + setShipWallets(newShipWallets); + } + if (changeWallet) { + setWallet(changeWallet); + } + if (psbtData) { + setPsbt(psbtData.pb); + setFee(psbtData.fee); + } + if (cancelTx) { + handleCancelTx(cancelTx); + } + if (addressData) { + setAddress(addressData); + } + if (balanceData) { + setUnconfirmedBalance(balanceData.unconfirmed); + setConfirmedBalance(balanceData.confirmed); + } + if (errorData) { + setError(errorData); + } + if (broadcastSuccessData) { + setBroadcastSuccess(true); + } + if (broadcastFailData) { + setBroadcastSuccess(false); + } + if (scanProgressData) { + setScanProgress(scanProgressData); + } + }, [channelData]); + + useEffect(() => { + channel.setOnChannelError(() => { + start(); + }); + start(); + }, []); + + return ( + + {children} + + ); +}; + +export const useSettings = () => useContext(SettingsContext); diff --git a/pkg/btc-wallet/src/index.js b/pkg/btc-wallet/src/index.js deleted file mode 100644 index 5aa431a923..0000000000 --- a/pkg/btc-wallet/src/index.js +++ /dev/null @@ -1,23 +0,0 @@ -import React from 'react'; -import ReactDOM from 'react-dom'; -import { Root } from './js/components/root.js'; -import { api } from './js/api.js'; -import Channel from './js/channel'; - -import './css/indigo-static.css'; -import './css/fonts.css'; -import './css/custom.css'; - -// rebuild x3 - -const channel = new 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/index.tsx b/pkg/btc-wallet/src/index.tsx new file mode 100644 index 0000000000..848e9910de --- /dev/null +++ b/pkg/btc-wallet/src/index.tsx @@ -0,0 +1,24 @@ +import React from 'react'; +import ReactDOM from 'react-dom'; +import { api } from './lib/api'; +import Channel from './lib/channel'; +import { SettingsProvider } from './hooks/useSettings'; +import App from './App'; + +import './css/indigo-static.css'; +import './css/fonts.css'; +import './css/custom.css'; + +const channel = new 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/components/lib/balance.js b/pkg/btc-wallet/src/js/components/lib/balance.js deleted file mode 100644 index 7d2e8eb300..0000000000 --- a/pkg/btc-wallet/src/js/components/lib/balance.js +++ /dev/null @@ -1,170 +0,0 @@ -import React, { Component } from 'react'; -import { Row, Text, Button, Col } from '@tlon/indigo-react'; -import Send from './send.js'; -import CurrencyPicker from './currencyPicker.js'; -import { satsToCurrency } from '../../lib/util.js'; -import { store } from '../../store.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; - navigator.clipboard.writeText(address); - 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`} - - - - - - - )} - - ); - } -} diff --git a/pkg/btc-wallet/src/js/components/lib/body.js b/pkg/btc-wallet/src/js/components/lib/body.js deleted file mode 100644 index 0fe1d798d6..0000000000 --- a/pkg/btc-wallet/src/js/components/lib/body.js +++ /dev/null @@ -1,72 +0,0 @@ -import React, { Component } from 'react'; -import { - Box, - Icon, - Row, - Text, - LoadingSpinner, - Col, -} from '@tlon/indigo-react'; -import { - Switch, - Route, -} from 'react-router-dom'; -import Balance from './balance.js'; -import Transactions from './transactions.js'; -import Warning from './warning.js'; -import Header from './header.js'; -import Settings from './settings.js'; - -export default class Body extends Component { - constructor(props) { - super(props); - } - - render() { - - const cardWidth = window.innerWidth <= 475 ? '350px' : '400px' - - if (!this.props.loaded) { - return ( - - - - ); - } else { - return ( - - - -
- - - - - -
- { (!this.props.warning) ? null : } - - - - - - ); - } - } -} diff --git a/pkg/btc-wallet/src/js/components/lib/bridgeInvoice.js b/pkg/btc-wallet/src/js/components/lib/bridgeInvoice.js deleted file mode 100644 index acc147a8e1..0000000000 --- a/pkg/btc-wallet/src/js/components/lib/bridgeInvoice.js +++ /dev/null @@ -1,239 +0,0 @@ -import React, { Component } from 'react'; -import { - Box, - Icon, - StatelessTextInput as Input, - Row, - Text, - Button, - Col, - LoadingSpinner, -} from '@tlon/indigo-react'; - -import { Sigil } from './sigil.js'; - -import * as bitcoin from 'bitcoinjs-lib'; -import * as kg from 'urbit-key-generation'; -import { isValidPatp } from 'urbit-ob'; - -import Sent from './sent.js' -import Error from './error.js' - -import { satsToCurrency } from '../../lib/util.js'; - -export default class BridgeInvoice extends Component { - constructor(props) { - super(props); - - this.state = { - txHex: '', - ready: false, - error: this.props.state.error, - broadcasting: false, - }; - - this.checkTxHex = this.checkTxHex.bind(this); - this.broadCastTx = this.broadCastTx.bind(this); - this.sendBitcoin = this.sendBitcoin.bind(this); - this.clickDismiss = this.clickDismiss.bind(this); - this.setInvoiceRef = this.setInvoiceRef.bind(this); - } - - broadCastTx(hex) { - let command = { - 'broadcast-tx': hex - } - return this.props.api.btcWalletCommand(command) - } - - componentDidMount() { - window.open('https://bridge.urbit.org/?kind=btc&utx=' + this.props.psbt); - document.addEventListener("click", this.clickDismiss); - } - - componentWillUnmount(){ - document.removeEventListener("click", this.clickDismiss); - } - - setInvoiceRef(n){ - this.invoiceRef = n; - } - - clickDismiss(e){ - if (this.invoiceRef && !(this.invoiceRef.contains(e.target))){ - this.props.stopSending(); - } - } - - componentDidUpdate(prevProps){ - if (this.state.broadcasting) { - if (this.state.error !== '') { - this.setState({broadcasting: false}); - } - } - - if (prevProps.state.error !== this.props.state.error) { - this.setState({error: this.props.state.error}); - } - } - - sendBitcoin(hex) { - try { - bitcoin.Transaction.fromHex(hex) - this.broadCastTx(hex) - this.setState({broadcasting: true}); - } - - catch(e) { - this.setState({error: 'invalid-signed', broadcasting: false}); - } - } - - checkTxHex(e){ - let txHex = e.target.value; - let ready = (txHex.length > 0); - let error = ''; - this.setState({txHex, ready, error}); - } - - render() { - const { stopSending, payee, denomination, satsAmount, psbt, currencyRates, fee } = this.props; - const { error, txHex } = this.state; - - let inputColor = 'black'; - let inputBg = 'white'; - let inputBorder = 'lightGray'; - - if (error !== '') { - inputColor = 'red'; - inputBg = 'veryLightRed'; - inputBorder = 'red'; - } - - const isShip = isValidPatp(payee); - - const icon = (isShip) - ? - : ; - - return ( - <> - { this.props.state.broadcastSuccess ? - : - - - - {satsToCurrency(satsAmount, denomination, currencyRates)} - - - {`${satsAmount} sats`} - - - {`Fee: ${satsToCurrency(fee, denomination, currencyRates)} (${fee} sats)`} - - - You are paying - - - {icon} - {payee} - - - - - Bridge signed transaction - - - - - Copy the signed transaction from Bridge - - - - { (error !== '') && - - - - } - - - {this.state.connecting ? : null} - - - ); - } -} diff --git a/pkg/btc-wallet/src/js/components/lib/send.js b/pkg/btc-wallet/src/js/components/lib/send.js deleted file mode 100644 index df5eef8f5c..0000000000 --- a/pkg/btc-wallet/src/js/components/lib/send.js +++ /dev/null @@ -1,457 +0,0 @@ -import React, { Component } from 'react'; -import { - Box, - Icon, - StatelessTextInput as Input, - Row, - Text, - Button, - Col, - LoadingSpinner, - StatelessRadioButtonField as RadioButton, -} from '@tlon/indigo-react'; - -import Invoice from './invoice.js' -import BridgeInvoice from './bridgeInvoice.js' -import FeePicker from './feePicker.js' -import Error from './error.js' -import Signer from './signer.js' - -import { validate } from 'bitcoin-address-validation'; - -import * as ob from 'urbit-ob'; - -export default class Send extends Component { - constructor(props) { - super(props); - - this.state = { - signing: false, - denomAmount: '0.00', - satsAmount: '0', - payee: '', - checkingPatp: false, - payeeType: '', - ready: false, - validPayee: false, - focusPayee: true, - focusCurrency: false, - focusSats: false, - focusNote: false, - submitting: false, - feeChoices: { - low: [10, 1], - mid: [10, 1], - high: [10, 1], - }, - feeValue: "mid", - showModal: false, - note: '', - choosingSignMethod: false, - signMethod: 'bridge', - }; - - this.initPayment = this.initPayment.bind(this); - this.checkPayee = this.checkPayee.bind(this); - this.feeSelect = this.feeSelect.bind(this); - this.feeDismiss = this.feeDismiss.bind(this); - this.toggleSignMethod = this.toggleSignMethod.bind(this); - this.setSignMethod = this.setSignMethod.bind(this); - } - - feeDismiss() { - this.setState({showModal: false}); - } - - feeSelect(which) { - this.setState({feeValue: which}); - } - - componentDidMount(){ - if (this.props.network === 'bitcoin'){ - let url = "https://bitcoiner.live/api/fees/estimates/latest"; - fetch(url).then(res => res.json()).then(n => { - let estimates = Object.keys(n.estimates); - let mid = Math.floor(estimates.length/2) - let high = estimates.length - 1; - this.setState({ - feeChoices: { - high: [30, n.estimates[30]["sat_per_vbyte"]], - mid: [180, n.estimates[180]["sat_per_vbyte"]], - low: [360, n.estimates[360]["sat_per_vbyte"]], - } - }); - }) - } - } - - setSignMethod(signMethod) { - this.setState({signMethod, choosingSignMethod: false}); - } - - checkPayee(e){ - store.handleEvent({data: {error: ''}}); - - let payee = e.target.value; - let isPatp = ob.isValidPatp(payee); - let isAddress = validate(payee); - - - if (isPatp) { - let command = {'check-payee': payee} - this.props.api.btcWalletCommand(command) - setTimeout(() => { - this.setState({checkingPatp: false}); - }, 5000); - this.setState({ - checkingPatp: true, - payeeType: 'ship', - payee, - }); - } else if (isAddress) { - this.setState({ - payee, - ready: true, - checkingPatp: false, - payeeType: 'address', - validPayee: true, - }); - } else { - this.setState({ - payee, - ready: false, - checkingPatp: false, - payeeType: '', - validPayee: false, - }); - } - } - - componentDidUpdate(prevProps, prevState) { - if ((prevProps.error !== this.props.error) && - (this.props.error !== '') && (this.props.error !== 'broadcast-fail')) { - this.setState({signing: false}); - } - - if (!this.state.ready && this.state.checkingPatp) { - if (this.props.shipWallets[this.state.payee.slice(1)]) { - this.setState({ready: true, checkingPatp: false, validPayee: true}); - } - } - } - - toggleSignMethod(toggle) { - this.setState({choosingSignMethod: !toggle}); - } - - initPayment() { - if (this.state.payeeType === 'ship') { - let command = { - 'init-payment': { - 'payee': this.state.payee, - 'value': parseInt(this.state.satsAmount), - 'feyb': this.state.feeChoices[this.state.feeValue][1], - 'note': (this.state.note || null), - } - } - this.props.api.btcWalletCommand(command).then(res => this.setState({signing: true})); - } else if (this.state.payeeType === 'address') { - let command = { - 'init-payment-external': { - 'address': this.state.payee, - 'value': parseInt(this.state.satsAmount), - 'feyb': 1, - 'note': (this.state.note || null), - } - } - this.props.api.btcWalletCommand(command).then(res => this.setState({signing: true})); - } - } - - render() { - let payeeColor = "black"; - let payeeBg = "white"; - let payeeBorder = "lightGray"; - if (this.props.error) { - payeeColor="red"; - payeeBorder = "red"; - payeeBg="veryLightRed"; - } else if (this.state.focusPayee && this.state.validPayee) { - payeeColor = "green"; - payeeBorder = "green"; - payeeBg = "veryLightGreen"; - } else if (!this.state.focusPayee && this.state.validPayee){ - payeeColor="blue"; - payeeBorder = "white"; - payeeBg = "white"; - } else if (!this.state.focusPayee && !this.state.validPayee) { - payeeColor="red"; - payeeBorder = "red"; - payeeBg="veryLightRed"; - } else if (this.state.focusPayee && - !this.state.validPayee && - !this.state.checkingPatp && - this.state.payeeType === 'ship'){ - payeeColor="red"; - payeeBorder = "red"; - payeeBg="veryLightRed"; - } - - - const { api, value, conversion, stopSending, denomination, psbt, currencyRates, error, network, fee } = this.props; - const { denomAmount, satsAmount, signing, payee, choosingSignMethod, signMethod } = this.state; - - const signReady = (this.state.ready && (parseInt(this.state.satsAmount) > 0)) && !signing; - - let invoice = null; - if (signMethod === 'masterTicket') { - invoice = - - } else if (signMethod === 'bridge') { - invoice = - - } - - return ( - <> - { (signing && psbt) ? invoice : - - - - Send BTC - {value} - stopSending()} - /> - - - - To - {this.state.checkingPatp ? - : null - } - - {this.setState({focusPayee: true})}} - onBlur={() => {this.setState({focusPayee: false})}} - color={payeeColor} - backgroundColor={payeeBg} - borderColor={payeeBorder} - ml={2} - flexGrow="1" - fontSize='14px' - placeholder='~sampel-palnet or BTC address' - value={payee} - fontFamily="mono" - disabled={signing} - onChange={this.checkPayee} - /> - - {error && - - {/* yes this is a hack */} - - - - } - - Amount - {this.setState({focusCurrency: true})}} - onBlur={() => {this.setState({focusCurrency: false})}} - fontSize='14px' - width='100%' - type='number' - borderColor={this.state.focusCurrency ? "lightGray" : "none"} - disabled={signing} - value={denomAmount} - onChange={e => { - this.setState({ - denomAmount: e.target.value, - satsAmount: Math.round(parseFloat(e.target.value) / conversion * 100000000) - }); - }} - /> - {denomination} - - - {/* yes this is a hack */} - - {this.setState({focusSats: true})}} - onBlur={() => {this.setState({focusSats: false})}} - fontSize='14px' - width='100%' - type='number' - borderColor={this.state.focusSats ? "lightGray" : "none"} - disabled={signing} - value={satsAmount} - onChange={e => { - this.setState({ - denomAmount: parseFloat(e.target.value) * (conversion / 100000000), - satsAmount: e.target.value - }); - }} - /> - sats - - - Fee - - - {this.state.feeChoices[this.state.feeValue][1]} sats/vbyte - - {if (!this.state.showModal) this.setState({showModal: true}); }} - cursor="pointer"/> - - - - {!this.state.showModal ? null : - - } - - - Note - {this.setState({focusNote: true})}} - onBlur={() => {this.setState({focusNote: false})}} - fontSize='14px' - width='100%' - placeholder="What's this for?" - type='text' - borderColor={this.state.focusNote ? "lightGray" : "none"} - disabled={signing} - value={this.state.note} - onChange={e => { - this.setState({ - note: e.target.value, - }); - }} - /> - - - - - { (!(signing && !error)) ? null : - - } -