mirror of
https://github.com/urbit/shrub.git
synced 2024-12-18 15:55:00 +03:00
Merge branch 'ea/master-dist-deconflict' into dist
This commit is contained in:
commit
72e3aca57c
@ -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)
|
||||
|
@ -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]
|
||||
|
@ -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]~]
|
||||
|
@ -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]~
|
||||
|
@ -12,7 +12,7 @@
|
||||
arg=$@(~ [top=path ~])
|
||||
~
|
||||
==
|
||||
:- %noun
|
||||
:- %boot-pill
|
||||
^- pill:pill
|
||||
::
|
||||
:: sys: root path to boot system, `/~me/[desk]/now/sys`
|
||||
|
30
pkg/arvo/gen/hood/crunch.hoon
Normal file
30
pkg/arvo/gen/hood/crunch.hoon
Normal file
@ -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)]
|
@ -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
|
||||
|
@ -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) &]
|
||||
|
356
pkg/arvo/lib/crunch.hoon
Normal file
356
pkg/arvo/lib/crunch.hoon
Normal file
@ -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)]~]]
|
||||
::
|
||||
--
|
@ -1 +0,0 @@
|
||||
../../../base-dev/lib/ph/io.hoon
|
@ -1 +0,0 @@
|
||||
../../../base-dev/lib/ph/util.hoon
|
@ -1 +0,0 @@
|
||||
../../base-dev/lib/pill.hoon
|
15
pkg/arvo/mar/csv.hoon
Normal file
15
pkg/arvo/mar/csv.hoon
Normal file
@ -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
|
||||
--
|
@ -1 +0,0 @@
|
||||
../../base-dev/sur/aquarium.hoon
|
9
pkg/arvo/sur/crunch.hoon
Normal file
9
pkg/arvo/sur/crunch.hoon
Normal file
@ -0,0 +1,9 @@
|
||||
/- resource
|
||||
::
|
||||
|%
|
||||
+$ channel-info
|
||||
$: group=resource:resource
|
||||
channel=resource:resource
|
||||
channel-type=term
|
||||
==
|
||||
--
|
@ -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.
|
||||
|
@ -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
|
||||
--
|
||||
--
|
||||
|
@ -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)
|
||||
==
|
||||
::
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
0
pkg/arvo/ted/ph/migrate/end.hoon
Normal file
0
pkg/arvo/ted/ph/migrate/end.hoon
Normal file
49
pkg/arvo/ted/ph/migrate/make-groups.hoon
Normal file
49
pkg/arvo/ted/ph/migrate/make-groups.hoon
Normal file
@ -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)
|
||||
|
||||
|
||||
|
38
pkg/arvo/ted/ph/migrate/setup.hoon
Normal file
38
pkg/arvo/ted/ph/migrate/setup.hoon
Normal file
@ -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)
|
||||
|
||||
|
||||
|
42
pkg/arvo/ted/ph/migrate/wait.hoon
Normal file
42
pkg/arvo/ted/ph/migrate/wait.hoon
Normal file
@ -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)
|
||||
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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 {<ship>}"
|
||||
=/ m (strand ,~)
|
||||
^- form:m
|
||||
(send-azimuth-action %spawn ship)
|
||||
::
|
||||
++ breach-aqua
|
||||
++ breach
|
||||
|= =ship
|
||||
~& > "breaching {<ship>}"
|
||||
=/ m (strand ,~)
|
||||
^- form:m
|
||||
(send-azimuth-action %breach ship)
|
||||
::
|
||||
++ spawn
|
||||
|= [=tid:spider =ship]
|
||||
~& > "spawning {<ship>}"
|
||||
=/ m (strand ,~)
|
||||
=/ =vase !>(`input:spider`[tid %azimuth-command !>([%spawn ship])])
|
||||
(poke-our %spider %spider-input vase)
|
||||
::
|
||||
++ breach
|
||||
|= [=tid:spider who=ship]
|
||||
=/ m (strand ,~)
|
||||
~& > "breaching {<who>}"
|
||||
=/ =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 {<who>} for {<her>}"
|
||||
;< =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 {<who>} for {<her>}"
|
||||
=/ 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 {<ship>}"
|
||||
;< ~ bind:m (send-events (init:util ship `*dawn-event:jael))
|
||||
(check-ship-booted ship)
|
||||
::
|
||||
++ real-ship
|
||||
|= [=tid:spider =ship]
|
||||
~& > "booting real {<ship>}"
|
||||
=/ 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 {<ship>}"
|
||||
;< ~ 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")
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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]
|
||||
|
2
pkg/btc-wallet/.eslintignore
Normal file
2
pkg/btc-wallet/.eslintignore
Normal file
@ -0,0 +1,2 @@
|
||||
config/webpack.dev.js
|
||||
config/webpack.prod.js
|
@ -5,4 +5,4 @@ dojo:
|
||||
|
||||
it should return with the following hash:
|
||||
|
||||
`0v7.v4dng.o33qi.kc497.5jc02.ke5es`
|
||||
`0v758lj.uf0s5.0nh3m.gunn6.942gj`
|
||||
|
@ -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,
|
||||
},
|
||||
};
|
||||
|
@ -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,
|
||||
},
|
||||
};
|
||||
|
228
pkg/btc-wallet/package-lock.json
generated
228
pkg/btc-wallet/package-lock.json
generated
@ -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": {
|
||||
|
@ -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"
|
||||
|
40
pkg/btc-wallet/src/App.tsx
Normal file
40
pkg/btc-wallet/src/App.tsx
Normal file
@ -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 (
|
||||
<BrowserRouter basename="/apps/bitcoin">
|
||||
<ThemeProvider theme={light}>
|
||||
<Reset />
|
||||
{loaded && !scanning ? <StartupModal /> : null}
|
||||
<Box
|
||||
display="flex"
|
||||
flexDirection="column"
|
||||
position="absolute"
|
||||
alignItems="center"
|
||||
backgroundColor="lightOrange"
|
||||
width="100%"
|
||||
minHeight={loaded && !scanning ? '100%' : 'none'}
|
||||
height={loaded && !scanning ? 'none' : '100%'}
|
||||
style={{ filter: blur ? 'blur(8px)' : 'none' }}
|
||||
px={[0, 4]}
|
||||
pb={[0, 4]}
|
||||
>
|
||||
<Body />
|
||||
</Box>
|
||||
</ThemeProvider>
|
||||
</BrowserRouter>
|
||||
);
|
||||
};
|
||||
|
||||
export default App;
|
162
pkg/btc-wallet/src/components/Balance.tsx
Normal file
162
pkg/btc-wallet/src/components/Balance.tsx
Normal file
@ -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 ? (
|
||||
<Send
|
||||
value={value}
|
||||
conversion={conversion}
|
||||
stopSending={() => {
|
||||
setSending(false);
|
||||
setPsbt('');
|
||||
setFee(0);
|
||||
setError('');
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<Col
|
||||
height="400px"
|
||||
width="100%"
|
||||
backgroundColor="white"
|
||||
borderRadius="48px"
|
||||
justifyContent="space-between"
|
||||
mb={5}
|
||||
p={5}
|
||||
>
|
||||
<Row justifyContent="space-between">
|
||||
<Text color="orange" fontSize={1}>
|
||||
Balance
|
||||
</Text>
|
||||
<Text
|
||||
color="lightGray"
|
||||
fontSize="14px"
|
||||
mono
|
||||
style={{ cursor: 'pointer' }}
|
||||
onClick={() => copyAddress('string')}
|
||||
>
|
||||
{copiedString ? 'copied' : addressText}
|
||||
</Text>
|
||||
<CurrencyPicker />
|
||||
</Row>
|
||||
<Col justifyContent="center" alignItems="center">
|
||||
<Text
|
||||
fontSize="40px"
|
||||
color="orange"
|
||||
style={{ whiteSpace: 'nowrap' }}
|
||||
>
|
||||
{value}
|
||||
</Text>
|
||||
{scanning ? (
|
||||
<Col alignItems="center">
|
||||
<Row>
|
||||
<Text fontSize={1} color="orange">
|
||||
Balance will be updated shortly:
|
||||
</Text>
|
||||
</Row>
|
||||
<Row>
|
||||
<Text fontSize={1} color="orange">
|
||||
{scanProgress.main === null ? 0 : scanProgress.main} main
|
||||
wallet addresses scanned
|
||||
</Text>
|
||||
</Row>
|
||||
<Text fontSize={1} color="orange">
|
||||
{scanProgress.change === null ? 0 : scanProgress.change}{' '}
|
||||
change wallet addresses scanned
|
||||
</Text>
|
||||
</Col>
|
||||
) : (
|
||||
<Text
|
||||
fontSize={1}
|
||||
color="orange"
|
||||
>{`${sats}${unconfirmedString} sats`}</Text>
|
||||
)}
|
||||
</Col>
|
||||
<Row flexDirection="row-reverse">
|
||||
<Button
|
||||
disabled={sendDisabled}
|
||||
fontSize={1}
|
||||
fontWeight="bold"
|
||||
color={sendDisabled ? 'lighterGray' : 'white'}
|
||||
backgroundColor={sendDisabled ? 'veryLightGray' : 'orange'}
|
||||
style={{ cursor: sendDisabled ? 'default' : 'pointer' }}
|
||||
borderColor="none"
|
||||
borderRadius="24px"
|
||||
height="48px"
|
||||
onClick={() => setSending(true)}
|
||||
>
|
||||
Send
|
||||
</Button>
|
||||
<Button
|
||||
mr={3}
|
||||
disabled={copiedButton}
|
||||
fontSize={1}
|
||||
fontWeight="bold"
|
||||
color={copiedButton ? 'green' : 'orange'}
|
||||
backgroundColor={copiedButton ? 'veryLightGreen' : 'midOrange'}
|
||||
style={{
|
||||
cursor: copiedButton ? 'default' : 'pointer',
|
||||
}}
|
||||
borderColor="none"
|
||||
borderRadius="24px"
|
||||
height="48px"
|
||||
onClick={() => copyAddress('button')}
|
||||
>
|
||||
{copiedButton ? 'Address Copied!' : 'Copy Address'}
|
||||
</Button>
|
||||
</Row>
|
||||
</Col>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default Balance;
|
44
pkg/btc-wallet/src/components/Body.tsx
Normal file
44
pkg/btc-wallet/src/components/Body.tsx
Normal file
@ -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 ? (
|
||||
<Box
|
||||
display="flex"
|
||||
width="100%"
|
||||
height="100%"
|
||||
alignItems="center"
|
||||
justifyContent="center"
|
||||
>
|
||||
<LoadingSpinner background="midOrange" foreground="orange" />
|
||||
</Box>
|
||||
) : (
|
||||
<Switch>
|
||||
<Route path="/settings">
|
||||
<Col display="flex" flexDirection="column" width={cardWidth}>
|
||||
<Header settings={true} />
|
||||
<Settings />
|
||||
</Col>
|
||||
</Route>
|
||||
<Route path="/">
|
||||
<Col display="flex" flexDirection="column" width={cardWidth}>
|
||||
<Header settings={false} />
|
||||
{!warning ? null : <Warning />}
|
||||
<Balance />
|
||||
<Transactions />
|
||||
</Col>
|
||||
</Route>
|
||||
</Switch>
|
||||
);
|
||||
};
|
||||
|
||||
export default Body;
|
39
pkg/btc-wallet/src/components/CurrencyPicker.tsx
Normal file
39
pkg/btc-wallet/src/components/CurrencyPicker.tsx
Normal file
@ -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 (
|
||||
<Row style={{ cursor: 'pointer' }} onClick={() => switchCurrency()}>
|
||||
<Icon icon="ChevronDouble" color="orange" pt="2px" pr={1} />
|
||||
<Text color="orange" fontSize={1}>
|
||||
{denomination}
|
||||
</Text>
|
||||
</Row>
|
||||
);
|
||||
};
|
||||
|
||||
export default CurrencyPicker;
|
32
pkg/btc-wallet/src/components/Error.tsx
Normal file
32
pkg/btc-wallet/src/components/Error.tsx
Normal file
@ -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;
|
||||
}) => (
|
||||
<Text color="red" style={{ fontSize }} {...rest}>
|
||||
{
|
||||
(ErrorTypes as any)[
|
||||
Object.keys(ErrorTypes).filter((et) => et === error)[0]
|
||||
]
|
||||
}
|
||||
</Text>
|
||||
);
|
||||
|
||||
export default Error;
|
81
pkg/btc-wallet/src/components/Header.tsx
Normal file
81
pkg/btc-wallet/src/components/Header.tsx
Normal file
@ -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 = (
|
||||
<Text fontSize={1} color="red" fontWeight="bold" mr={3}>
|
||||
Provider Offline
|
||||
</Text>
|
||||
);
|
||||
|
||||
if (!settings) {
|
||||
badge = (
|
||||
<Box
|
||||
borderRadius="50%"
|
||||
width="8px"
|
||||
height="8px"
|
||||
backgroundColor="red"
|
||||
position="absolute"
|
||||
top="0px"
|
||||
right="0px"
|
||||
></Box>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<Row
|
||||
height={8}
|
||||
width="100%"
|
||||
justifyContent="space-between"
|
||||
alignItems="center"
|
||||
pt={5}
|
||||
pb={5}
|
||||
>
|
||||
<Row alignItems="center" justifyContent="center">
|
||||
<Box
|
||||
backgroundColor="orange"
|
||||
borderRadius={4}
|
||||
mr="12px"
|
||||
width={5}
|
||||
height={5}
|
||||
alignItems="center"
|
||||
justifyContent="center"
|
||||
>
|
||||
<Icon icon="Bitcoin" width={4} p={1} height={4} color="white" />
|
||||
</Box>
|
||||
<Text fontSize={2} fontWeight="bold" color="orange">
|
||||
Bitcoin
|
||||
</Text>
|
||||
</Row>
|
||||
<Row alignItems="center">
|
||||
{connection}
|
||||
<Link to={iconLink}>
|
||||
<Box
|
||||
backgroundColor="white"
|
||||
borderRadius={4}
|
||||
width={5}
|
||||
height={5}
|
||||
p={2}
|
||||
position="relative"
|
||||
>
|
||||
{badge}
|
||||
<Icon icon={icon} color={iconColor} />
|
||||
</Box>
|
||||
</Link>
|
||||
</Row>
|
||||
</Row>
|
||||
);
|
||||
};
|
||||
|
||||
export default Header;
|
166
pkg/btc-wallet/src/components/ProviderModal.tsx
Normal file
166
pkg/btc-wallet/src/components/ProviderModal.tsx
Normal file
@ -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<HTMLInputElement>) => {
|
||||
// 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 = (
|
||||
<Box mt={3}>
|
||||
<Text fontSize="14px" color="green">
|
||||
{provider} is a working provider node
|
||||
</Text>
|
||||
</Box>
|
||||
);
|
||||
} else if (providerStatus === providerStatuses.failed) {
|
||||
workingColor = 'red';
|
||||
workingBg = 'veryLightRed';
|
||||
workingNode = (
|
||||
<Box mt={3}>
|
||||
<Text fontSize="14px" color="red">
|
||||
{potentialProvider} is not a working provider node
|
||||
</Text>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Box width="100%" height="100%" padding={3}>
|
||||
<Row>
|
||||
<Icon icon="Bitcoin" mr={2} />
|
||||
<Text fontSize="14px" fontWeight="bold">
|
||||
Step 1 of 2: Set up Bitcoin Provider Node
|
||||
</Text>
|
||||
</Row>
|
||||
<Box mt={3}>
|
||||
<Text fontSize="14px" fontWeight="regular" color="gray">
|
||||
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.
|
||||
<a
|
||||
style={{ fontSize: '14px' }}
|
||||
target="_blank"
|
||||
href="https://urbit.org/bitcoin-wallet"
|
||||
rel="noreferrer"
|
||||
>
|
||||
{' '}
|
||||
Learn More
|
||||
</a>
|
||||
</Text>
|
||||
</Box>
|
||||
<Box mt={3} mb={2}>
|
||||
<Text fontSize="14px" fontWeight="500">
|
||||
Provider Node
|
||||
</Text>
|
||||
</Box>
|
||||
<Row alignItems="center">
|
||||
<StatelessTextInput
|
||||
mr={2}
|
||||
width="256px"
|
||||
fontSize="14px"
|
||||
type="text"
|
||||
name="masterTicket"
|
||||
placeholder="e.g. ~zod"
|
||||
autoCapitalize="none"
|
||||
autoCorrect="off"
|
||||
mono
|
||||
backgroundColor={workingBg}
|
||||
color={workingColor}
|
||||
borderColor={workingColor}
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
|
||||
checkProvider(e)
|
||||
}
|
||||
/>
|
||||
{providerStatus === providerStatuses.checking ? (
|
||||
<LoadingSpinner />
|
||||
) : null}
|
||||
</Row>
|
||||
{workingNode}
|
||||
<Row alignItems="center" mt={3}>
|
||||
<Button
|
||||
mr={2}
|
||||
primary
|
||||
disabled={providerStatus !== providerStatuses.ready}
|
||||
fontSize="14px"
|
||||
style={{
|
||||
cursor:
|
||||
providerStatus === providerStatuses.ready ? 'pointer' : 'default',
|
||||
}}
|
||||
onClick={() => {
|
||||
submitProvider();
|
||||
}}
|
||||
>
|
||||
Set Peer Node
|
||||
</Button>
|
||||
{connecting ? <LoadingSpinner /> : null}
|
||||
</Row>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default ProviderModal;
|
223
pkg/btc-wallet/src/components/Send/BridgeInvoice.tsx
Normal file
223
pkg/btc-wallet/src/components/Send/BridgeInvoice.tsx
Normal file
@ -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<Props> = ({ 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<HTMLInputElement>) => {
|
||||
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 ? (
|
||||
<Sigil ship={payee} size={24} color="black" classes={''} icon padding={5} />
|
||||
) : (
|
||||
<Box
|
||||
backgroundColor="lighterGray"
|
||||
width="24px"
|
||||
height="24px"
|
||||
textAlign="center"
|
||||
alignItems="center"
|
||||
borderRadius="2px"
|
||||
p={1}
|
||||
>
|
||||
<Icon icon="Bitcoin" color="gray" />
|
||||
</Box>
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
{broadcastSuccess ? (
|
||||
<Sent payee={payee} stopSending={stopSending} satsAmount={satsAmount} />
|
||||
) : (
|
||||
<Col
|
||||
ref={invoiceRef}
|
||||
width="100%"
|
||||
backgroundColor="white"
|
||||
borderRadius="48px"
|
||||
mb={5}
|
||||
p={5}
|
||||
>
|
||||
<Row flexDirection="row-reverse">
|
||||
<Icon icon="X" cursor="pointer" onClick={() => stopSending()} />
|
||||
</Row>
|
||||
<Col
|
||||
p={5}
|
||||
mt={4}
|
||||
backgroundColor="veryLightGreen"
|
||||
borderRadius="24px"
|
||||
alignItems="center"
|
||||
>
|
||||
<Row>
|
||||
<Text color="green" fontSize="40px">
|
||||
{satsToCurrency(satsAmount, denomination, currencyRates)}
|
||||
</Text>
|
||||
</Row>
|
||||
<Row>
|
||||
<Text
|
||||
fontWeight="bold"
|
||||
fontSize="16px"
|
||||
color="midGreen"
|
||||
>{`${satsAmount} sats`}</Text>
|
||||
</Row>
|
||||
<Row mt={2}>
|
||||
<Text fontSize="14px" color="midGreen">{`Fee: ${satsToCurrency(
|
||||
fee,
|
||||
denomination,
|
||||
currencyRates
|
||||
)} (${fee} sats)`}</Text>
|
||||
</Row>
|
||||
<Row mt={4}>
|
||||
<Text fontSize="16px" fontWeight="bold" color="gray">
|
||||
You are paying
|
||||
</Text>
|
||||
</Row>
|
||||
<Row mt={2} alignItems="center">
|
||||
{icon}
|
||||
<Text
|
||||
ml={2}
|
||||
mono
|
||||
color="gray"
|
||||
fontSize="14px"
|
||||
style={{ display: 'block', overflowWrap: 'anywhere' }}
|
||||
>
|
||||
{payee}
|
||||
</Text>
|
||||
</Row>
|
||||
</Col>
|
||||
<Box mt={3}>
|
||||
<Text fontSize="14px" fontWeight="500">
|
||||
Bridge signed transaction
|
||||
</Text>
|
||||
</Box>
|
||||
<Box mt={1} mb={2}>
|
||||
<Text gray fontSize="14px">
|
||||
Copy the signed transaction from Bridge
|
||||
</Text>
|
||||
</Box>
|
||||
<Input
|
||||
value={txHex}
|
||||
fontSize="14px"
|
||||
placeholder="010000000001019e478cc370323ac539097..."
|
||||
autoCapitalize="none"
|
||||
autoCorrect="off"
|
||||
color={inputColor}
|
||||
backgroundColor={inputBg}
|
||||
borderColor={inputBorder}
|
||||
style={{ lineHeight: '4' }}
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => checkTxHex(e)}
|
||||
/>
|
||||
{localError !== '' && (
|
||||
<Row>
|
||||
<Error error={localError} fontSize="14px" />
|
||||
</Row>
|
||||
)}
|
||||
<Row flexDirection="row-reverse" mt={4} alignItems="center">
|
||||
<Button
|
||||
primary
|
||||
mr={3}
|
||||
fontSize={1}
|
||||
borderRadius="24px"
|
||||
border="none"
|
||||
height="48px"
|
||||
onClick={() => sendBitcoin(txHex)}
|
||||
disabled={!ready || localError || broadcasting}
|
||||
color={
|
||||
ready && !localError && !broadcasting ? 'white' : 'lighterGray'
|
||||
}
|
||||
backgroundColor={
|
||||
ready && !localError && !broadcasting
|
||||
? 'green'
|
||||
: 'veryLightGray'
|
||||
}
|
||||
style={{
|
||||
cursor: ready && !localError ? 'pointer' : 'default',
|
||||
}}
|
||||
>
|
||||
Send BTC
|
||||
</Button>
|
||||
{
|
||||
// @ts-ignore
|
||||
broadcasting ? <LoadingSpinner mr={3} /> : null
|
||||
}
|
||||
</Row>
|
||||
</Col>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default BridgeInvoice;
|
275
pkg/btc-wallet/src/components/Send/ExternalInvoice.tsx
Normal file
275
pkg/btc-wallet/src/components/Send/ExternalInvoice.tsx
Normal file
@ -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<Props> = ({
|
||||
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<HTMLInputElement>) => {
|
||||
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 ? (
|
||||
<Sigil ship={payee} size={24} color="black" classes={''} icon padding={5} />
|
||||
) : (
|
||||
<Box
|
||||
backgroundColor="lighterGray"
|
||||
width="24px"
|
||||
height="24px"
|
||||
textAlign="center"
|
||||
alignItems="center"
|
||||
borderRadius="2px"
|
||||
p={1}
|
||||
>
|
||||
<Icon icon="Bitcoin" color="gray" />
|
||||
</Box>
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
{broadcastSuccess ? (
|
||||
<Sent payee={payee} stopSending={stopSending} satsAmount={satsAmount} />
|
||||
) : (
|
||||
<Col
|
||||
ref={invoiceRef}
|
||||
width="100%"
|
||||
backgroundColor="white"
|
||||
borderRadius="48px"
|
||||
mb={5}
|
||||
p={5}
|
||||
>
|
||||
<Row flexDirection="row-reverse">
|
||||
<Icon icon="X" cursor="pointer" onClick={() => stopSending()} />
|
||||
</Row>
|
||||
<Col
|
||||
p={5}
|
||||
mt={4}
|
||||
backgroundColor="veryLightGreen"
|
||||
borderRadius="24px"
|
||||
alignItems="center"
|
||||
>
|
||||
<Row>
|
||||
<Text color="green" fontSize="40px">
|
||||
{satsToCurrency(satsAmount, denomination, currencyRates)}
|
||||
</Text>
|
||||
</Row>
|
||||
<Row>
|
||||
<Text
|
||||
fontWeight="bold"
|
||||
fontSize="16px"
|
||||
color="midGreen"
|
||||
>{`${satsAmount} sats`}</Text>
|
||||
</Row>
|
||||
<Row mt={2}>
|
||||
<Text fontSize="14px" color="midGreen">{`Fee: ${satsToCurrency(
|
||||
fee,
|
||||
denomination,
|
||||
currencyRates
|
||||
)} (${fee} sats)`}</Text>
|
||||
</Row>
|
||||
<Row mt={4}>
|
||||
<Text fontSize="16px" fontWeight="bold" color="gray">
|
||||
You are paying
|
||||
</Text>
|
||||
</Row>
|
||||
<Row mt={2} alignItems="center">
|
||||
{icon}
|
||||
<Text
|
||||
ml={2}
|
||||
mono
|
||||
color="gray"
|
||||
fontSize="14px"
|
||||
style={{ display: 'block', overflowWrap: 'anywhere' }}
|
||||
>
|
||||
{payee}
|
||||
</Text>
|
||||
</Row>
|
||||
</Col>
|
||||
<Box mt={3}>
|
||||
<Text fontSize="14px" fontWeight="500">
|
||||
Partially-signed Bitcoin Transaction (PSBT)
|
||||
</Text>
|
||||
</Box>
|
||||
<Box mt={3}>
|
||||
<Row flexDirection="row">
|
||||
<Button
|
||||
borderRadius="24px"
|
||||
border="none"
|
||||
width="24px"
|
||||
height="24px"
|
||||
padding="0px"
|
||||
backgroundColor="veryLightGray"
|
||||
onClick={() => downloadPsbtFile()}
|
||||
mr={2}
|
||||
>
|
||||
<Icon icon="Download" width="12px" />
|
||||
</Button>
|
||||
<Button
|
||||
borderRadius="24px"
|
||||
border="none"
|
||||
width="24px"
|
||||
height="24px"
|
||||
padding="0px"
|
||||
backgroundColor="veryLightGray"
|
||||
onClick={() => copyPsbt()}
|
||||
>
|
||||
<Icon icon="Copy" />
|
||||
</Button>
|
||||
</Row>
|
||||
</Box>
|
||||
<Row
|
||||
justifyContent="flex-end"
|
||||
alignItems="center"
|
||||
flexDirection="row"
|
||||
>
|
||||
<Text gray bold fontSize="16px" mr={2}>
|
||||
Signed Tx
|
||||
</Text>
|
||||
<Input
|
||||
value={txHex}
|
||||
fontSize="14px"
|
||||
placeholder="7f3..."
|
||||
autoCapitalize="none"
|
||||
autoCorrect="off"
|
||||
color={inputColor}
|
||||
width="70%"
|
||||
backgroundColor={inputBg}
|
||||
borderColor={inputBorder}
|
||||
style={{ lineHeight: '4' }}
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
|
||||
checkTxHex(e)
|
||||
}
|
||||
/>
|
||||
{localError !== '' && (
|
||||
<Row>
|
||||
<Error error={localError} fontSize="14px" />
|
||||
</Row>
|
||||
)}
|
||||
</Row>
|
||||
<Row
|
||||
flexDirection="row"
|
||||
mt={4}
|
||||
alignItems="center"
|
||||
justifyContent="center"
|
||||
>
|
||||
<Button
|
||||
primary
|
||||
mr={3}
|
||||
fontSize={1}
|
||||
borderRadius="24px"
|
||||
border="none"
|
||||
height="48px"
|
||||
width="100%"
|
||||
onClick={() => sendBitcoin(txHex)}
|
||||
disabled={!ready || localError || broadcasting}
|
||||
color={
|
||||
ready && !localError && !broadcasting ? 'white' : 'lighterGray'
|
||||
}
|
||||
backgroundColor={
|
||||
ready && !localError && !broadcasting
|
||||
? 'green'
|
||||
: 'veryLightGray'
|
||||
}
|
||||
style={{
|
||||
cursor: ready && !localError ? 'pointer' : 'default',
|
||||
}}
|
||||
>
|
||||
Send BTC
|
||||
</Button>
|
||||
{broadcasting ? <LoadingSpinner /> : null}
|
||||
</Row>
|
||||
</Col>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default ExternalInvoice;
|
100
pkg/btc-wallet/src/components/Send/FeePicker.tsx
Normal file
100
pkg/btc-wallet/src/components/Send/FeePicker.tsx
Normal file
@ -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<feeLevels>;
|
||||
feeDismiss: () => void;
|
||||
};
|
||||
|
||||
const FeePicker: React.FC<Props> = ({
|
||||
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 (
|
||||
<Box
|
||||
position="absolute"
|
||||
p={4}
|
||||
border="1px solid green"
|
||||
zIndex={10}
|
||||
backgroundColor="white"
|
||||
borderRadius={3}
|
||||
ref={modalRef}
|
||||
>
|
||||
<Text fontSize={1} color="black" fontWeight="bold" mb={4}>
|
||||
Transaction Speed
|
||||
</Text>
|
||||
<Col mt={4}>
|
||||
<RadioButton
|
||||
name="feeRadio"
|
||||
selected={feeValue === feeLevels.low}
|
||||
p="2"
|
||||
onChange={() => {
|
||||
setFeeValue(feeLevels.low);
|
||||
feeDismiss();
|
||||
}}
|
||||
>
|
||||
<Label fontSize="14px">
|
||||
Slow: {feeChoices[feeLevels.low][1]} sats/vbyte ~
|
||||
{feeChoices[feeLevels.low][0]}m
|
||||
</Label>
|
||||
</RadioButton>
|
||||
|
||||
<RadioButton
|
||||
name="feeRadio"
|
||||
selected={feeValue === feeLevels.mid}
|
||||
p="2"
|
||||
onChange={() => {
|
||||
setFeeValue(feeLevels.mid);
|
||||
feeDismiss();
|
||||
}}
|
||||
>
|
||||
<Label fontSize="14px">
|
||||
Normal: {feeChoices[feeLevels.mid][1]} sats/vbyte ~
|
||||
{feeChoices[feeLevels.mid][0]}m
|
||||
</Label>
|
||||
</RadioButton>
|
||||
|
||||
<RadioButton
|
||||
name="feeRadio"
|
||||
selected={feeValue === feeLevels.high}
|
||||
p="2"
|
||||
onChange={() => {
|
||||
setFeeValue(feeLevels.high);
|
||||
feeDismiss();
|
||||
}}
|
||||
>
|
||||
<Label fontSize="14px">
|
||||
Fast: {feeChoices[feeLevels.high][1]} sats/vbyte ~
|
||||
{feeChoices[feeLevels.high][0]}m
|
||||
</Label>
|
||||
</RadioButton>
|
||||
</Col>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default FeePicker;
|
280
pkg/btc-wallet/src/components/Send/Invoice.tsx
Normal file
280
pkg/btc-wallet/src/components/Send/Invoice.tsx
Normal file
@ -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<Props> = ({ 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<HTMLInputElement>) => {
|
||||
// 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 ? (
|
||||
<Sigil ship={payee} size={24} color="black" classes={''} icon padding={5} />
|
||||
) : (
|
||||
<Box
|
||||
backgroundColor="lighterGray"
|
||||
width="24px"
|
||||
height="24px"
|
||||
textAlign="center"
|
||||
alignItems="center"
|
||||
borderRadius="2px"
|
||||
p={1}
|
||||
>
|
||||
<Icon icon="Bitcoin" color="gray" />
|
||||
</Box>
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
{broadcastSuccess ? (
|
||||
<Sent payee={payee} stopSending={stopSending} satsAmount={satsAmount} />
|
||||
) : (
|
||||
<Col
|
||||
width="100%"
|
||||
backgroundColor="white"
|
||||
borderRadius="48px"
|
||||
mb={5}
|
||||
p={5}
|
||||
>
|
||||
<Row flexDirection="row-reverse">
|
||||
<Icon icon="X" cursor="pointer" onClick={() => stopSending()} />
|
||||
</Row>
|
||||
<Col
|
||||
p={5}
|
||||
mt={4}
|
||||
backgroundColor="veryLightGreen"
|
||||
borderRadius="24px"
|
||||
alignItems="center"
|
||||
>
|
||||
<Row>
|
||||
<Text color="green" fontSize="40px">
|
||||
{satsToCurrency(satsAmount, denomination, currencyRates)}
|
||||
</Text>
|
||||
</Row>
|
||||
<Row>
|
||||
<Text
|
||||
fontWeight="bold"
|
||||
fontSize="16px"
|
||||
color="midGreen"
|
||||
>{`${satsAmount} sats`}</Text>
|
||||
</Row>
|
||||
<Row mt={2}>
|
||||
<Text fontSize="14px" color="midGreen">{`Fee: ${satsToCurrency(
|
||||
fee,
|
||||
denomination,
|
||||
currencyRates
|
||||
)} (${fee} sats)`}</Text>
|
||||
</Row>
|
||||
<Row mt={4}>
|
||||
<Text fontSize="16px" fontWeight="bold" color="gray">
|
||||
You are paying
|
||||
</Text>
|
||||
</Row>
|
||||
<Row mt={2} alignItems="center">
|
||||
{icon}
|
||||
<Text
|
||||
ml={2}
|
||||
mono
|
||||
color="gray"
|
||||
fontSize="14px"
|
||||
style={{ display: 'block', overflowWrap: 'anywhere' }}
|
||||
>
|
||||
{payee}
|
||||
</Text>
|
||||
</Row>
|
||||
</Col>
|
||||
<Row mt={3} mb={2} alignItems="center">
|
||||
<Text gray fontSize={1} fontWeight="600" mr={4}>
|
||||
Ticket
|
||||
</Text>
|
||||
<Input
|
||||
value={masterTicket}
|
||||
fontSize="14px"
|
||||
type="password"
|
||||
name="masterTicket"
|
||||
obscure={(value: string) => value.replace(/[^~-]+/g, '••••••')}
|
||||
placeholder="••••••-••••••-••••••-••••••"
|
||||
autoCapitalize="none"
|
||||
autoCorrect="off"
|
||||
color={inputColor}
|
||||
backgroundColor={inputBg}
|
||||
borderColor={inputBorder}
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
|
||||
checkTicket(e)
|
||||
}
|
||||
/>
|
||||
</Row>
|
||||
{error !== '' && (
|
||||
<Row>
|
||||
<Error fontSize="14px" error={error} />
|
||||
</Row>
|
||||
)}
|
||||
<Row flexDirection="row-reverse" mt={4} alignItems="center">
|
||||
<Button
|
||||
primary
|
||||
mr={3}
|
||||
fontSize={1}
|
||||
border="none"
|
||||
borderRadius="24px"
|
||||
color={ready && !error && !broadcasting ? 'white' : 'lighterGray'}
|
||||
backgroundColor={
|
||||
ready && !error && !broadcasting ? 'green' : 'veryLightGray'
|
||||
}
|
||||
height="48px"
|
||||
onClick={() => sendBitcoin(masterTicket, psbt)}
|
||||
disabled={!ready || error || broadcasting}
|
||||
style={{
|
||||
cursor:
|
||||
ready && !error && !broadcasting ? 'pointer' : 'default',
|
||||
}}
|
||||
>
|
||||
Send BTC
|
||||
</Button>
|
||||
{
|
||||
// @ts-ignore
|
||||
broadcasting ? <LoadingSpinner mr={3} /> : null
|
||||
}
|
||||
</Row>
|
||||
</Col>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default Invoice;
|
512
pkg/btc-wallet/src/components/Send/Send.tsx
Normal file
512
pkg/btc-wallet/src/components/Send/Send.tsx
Normal file
@ -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<Props> = ({ 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>(payeeTypes.initial);
|
||||
const [ready, setReady] = useState(false);
|
||||
const [validPayee, setValidPayee] = useState(false);
|
||||
const [focusedField, setFocusedField] = useState(focusFields.empty);
|
||||
const [feeChoices, setFeeChoices] = useState<FeeChoices>({
|
||||
[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>(signMethods.bridge);
|
||||
|
||||
const feeDismiss = () => {
|
||||
setShowFeePicker(false);
|
||||
};
|
||||
|
||||
const handleSetSignMethod = (signMethod: signMethods) => {
|
||||
setSignMethod(signMethod);
|
||||
setChoosingSignMethod(false);
|
||||
};
|
||||
|
||||
const checkPayee = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
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 = (
|
||||
<Invoice
|
||||
stopSending={stopSending}
|
||||
payee={payee}
|
||||
satsAmount={satsAmount}
|
||||
/>
|
||||
);
|
||||
break;
|
||||
}
|
||||
case signMethods.bridge: {
|
||||
invoice = (
|
||||
<BridgeInvoice
|
||||
stopSending={stopSending}
|
||||
payee={payee}
|
||||
satsAmount={satsAmount}
|
||||
/>
|
||||
);
|
||||
break;
|
||||
}
|
||||
case signMethods.external: {
|
||||
invoice = (
|
||||
<ExternalInvoice
|
||||
stopSending={stopSending}
|
||||
payee={payee}
|
||||
satsAmount={satsAmount}
|
||||
/>
|
||||
);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{signing && psbt ? (
|
||||
invoice
|
||||
) : (
|
||||
<Col
|
||||
width="100%"
|
||||
backgroundColor="white"
|
||||
borderRadius="48px"
|
||||
mb={5}
|
||||
p={5}
|
||||
>
|
||||
<Col width="100%">
|
||||
<Row justifyContent="space-between" alignItems="center">
|
||||
<Text highlight color="blue" fontSize={1}>
|
||||
Send BTC
|
||||
</Text>
|
||||
<Text highlight color="blue" fontSize={1}>
|
||||
{value}
|
||||
</Text>
|
||||
<Icon icon="X" cursor="pointer" onClick={() => stopSending()} />
|
||||
</Row>
|
||||
<Row alignItems="center" mt={6} justifyContent="space-between">
|
||||
<Row
|
||||
justifyContent="space-between"
|
||||
width="calc(40% - 30px)"
|
||||
alignItems="center"
|
||||
>
|
||||
<Text gray fontSize={1} fontWeight="600">
|
||||
To
|
||||
</Text>
|
||||
{checkingPatp ? (
|
||||
<LoadingSpinner background="midOrange" foreground="orange" />
|
||||
) : null}
|
||||
</Row>
|
||||
<Input
|
||||
// autoFocus
|
||||
onFocus={() => {
|
||||
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<HTMLInputElement>) =>
|
||||
checkPayee(e)
|
||||
}
|
||||
/>
|
||||
</Row>
|
||||
{error && (
|
||||
<Row alignItems="center" justifyContent="space-between">
|
||||
{/* yes this is a hack */}
|
||||
<Box width="calc(40% - 30px)" />
|
||||
<Error error={error} fontSize="14px" />
|
||||
</Row>
|
||||
)}
|
||||
<Row alignItems="center" mt={4} justifyContent="space-between">
|
||||
<Text gray fontSize={1} fontWeight="600" width="40%">
|
||||
Amount
|
||||
</Text>
|
||||
<Input
|
||||
onFocus={() => {
|
||||
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<HTMLInputElement>) => {
|
||||
setDenomAmount(parseFloat(e.target.value));
|
||||
setSatsAmount(
|
||||
Math.round(
|
||||
(parseFloat(e.target.value) / conversion) * 100000000
|
||||
)
|
||||
);
|
||||
}}
|
||||
/>
|
||||
<Text color="lighterGray" fontSize={1} ml={3}>
|
||||
{denomination}
|
||||
</Text>
|
||||
</Row>
|
||||
<Row alignItems="center" mt={2} justifyContent="space-between">
|
||||
{/* yes this is a hack */}
|
||||
<Box width="40%" />
|
||||
<Input
|
||||
onFocus={() => {
|
||||
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<HTMLInputElement>) => {
|
||||
setDenomAmount(
|
||||
parseFloat(e.target.value) * (conversion / 100000000)
|
||||
);
|
||||
setSatsAmount(parseInt(e.target.value, 10));
|
||||
}}
|
||||
/>
|
||||
<Text color="lightGray" fontSize={1} ml={3}>
|
||||
sats
|
||||
</Text>
|
||||
</Row>
|
||||
<Row mt={4} width="100%" justifyContent="space-between">
|
||||
<Text gray fontSize={1} fontWeight="600" width="40%">
|
||||
Fee
|
||||
</Text>
|
||||
<Row
|
||||
alignItems="center"
|
||||
backgroundColor="blue"
|
||||
borderRadius="24px"
|
||||
paddingX="12px"
|
||||
paddingY="8px"
|
||||
>
|
||||
<Text mr={2} color="white" fontSize="14px">
|
||||
{feeChoices[feeValue][1]} sats/vbyte
|
||||
</Text>
|
||||
<Button
|
||||
borderRadius="24px"
|
||||
height="24px"
|
||||
width="24px"
|
||||
border="none"
|
||||
backgroundColor="rgba(33, 157, 255)"
|
||||
onClick={() => {
|
||||
setShowFeePicker((prev) => {
|
||||
if (prev) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
}}
|
||||
>
|
||||
<Icon
|
||||
icon="ChevronSouth"
|
||||
width="12px"
|
||||
color="white"
|
||||
cursor="pointer"
|
||||
/>
|
||||
</Button>
|
||||
</Row>
|
||||
</Row>
|
||||
<Col alignItems="center">
|
||||
{!showFeePicker ? null : (
|
||||
<FeePicker
|
||||
feeChoices={feeChoices}
|
||||
feeValue={feeValue}
|
||||
setFeeValue={setFeeValue}
|
||||
feeDismiss={feeDismiss}
|
||||
/>
|
||||
)}
|
||||
</Col>
|
||||
<Row
|
||||
mt={4}
|
||||
width="100%"
|
||||
justifyContent="space-between"
|
||||
alignItems="center"
|
||||
>
|
||||
<Text gray fontSize={1} fontWeight="600" width="40%">
|
||||
Note
|
||||
</Text>
|
||||
<Input
|
||||
onFocus={() => {
|
||||
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<HTMLInputElement>) => {
|
||||
setNote(e.target.value);
|
||||
}}
|
||||
/>
|
||||
</Row>
|
||||
</Col>
|
||||
<Row
|
||||
flexDirection="row"
|
||||
alignItems="center"
|
||||
mt={4}
|
||||
justifyContent="flex-end"
|
||||
>
|
||||
{!(signing && !error) ? null : (
|
||||
<LoadingSpinner background="midOrange" foreground="orange" />
|
||||
)}
|
||||
<Signer
|
||||
signReady={signReady}
|
||||
choosingSignMethod={choosingSignMethod}
|
||||
signMethod={signMethod}
|
||||
setSignMethod={handleSetSignMethod}
|
||||
initPayment={initPayment}
|
||||
/>
|
||||
<Button
|
||||
ml={2}
|
||||
width="48px"
|
||||
fontSize={1}
|
||||
fontWeight="bold"
|
||||
borderRadius="24px"
|
||||
height="48px"
|
||||
onClick={() => toggleSignMethod()}
|
||||
color={signReady ? 'white' : 'lighterGray'}
|
||||
backgroundColor={
|
||||
signReady ? 'rgba(33, 157, 255, 0.2)' : 'veryLightGray'
|
||||
}
|
||||
disabled={!signReady}
|
||||
border="none"
|
||||
style={{ cursor: signReady ? 'pointer' : 'default' }}
|
||||
>
|
||||
<Icon
|
||||
icon="ChevronSouth"
|
||||
color={signReady ? 'blue' : 'lighterGray'}
|
||||
/>
|
||||
</Button>
|
||||
</Row>
|
||||
{signMethod === signMethods.masterTicket && (
|
||||
<Row mt={4} alignItems="center">
|
||||
<Icon icon="Info" color="yellow" height={4} width={4} />
|
||||
<Text fontSize="14px" fontWeight="regular" color="gray" ml={2}>
|
||||
We recommend that you sign transactions using Bridge to protect
|
||||
your master ticket.
|
||||
</Text>
|
||||
</Row>
|
||||
)}
|
||||
</Col>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default Send;
|
42
pkg/btc-wallet/src/components/Send/Sent.tsx
Normal file
42
pkg/btc-wallet/src/components/Send/Sent.tsx
Normal file
@ -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<Props> = ({ payee, stopSending, satsAmount }) => {
|
||||
const { denomination, currencyRates } = useSettings();
|
||||
return (
|
||||
<Col
|
||||
height="400px"
|
||||
width="100%"
|
||||
backgroundColor="orange"
|
||||
borderRadius="48px"
|
||||
mb={5}
|
||||
p={5}
|
||||
>
|
||||
<Row flexDirection="row-reverse">
|
||||
<Icon color="white" icon="X" cursor="pointer" onClick={stopSending} />
|
||||
</Row>
|
||||
<Center>
|
||||
<Text
|
||||
style={{ display: 'block', overflowWrap: 'anywhere' }}
|
||||
color="white"
|
||||
>{`You sent BTC to ${payee}`}</Text>
|
||||
</Center>
|
||||
<Center flexDirection="column" flex="1 1 auto">
|
||||
<Text color="white" fontSize="40px">
|
||||
{satsToCurrency(satsAmount, denomination, currencyRates)}
|
||||
</Text>
|
||||
<Text color="white">{`${satsAmount} sats`}</Text>
|
||||
</Center>
|
||||
</Col>
|
||||
);
|
||||
};
|
||||
|
||||
export default Sent;
|
79
pkg/btc-wallet/src/components/Send/Signer.tsx
Normal file
79
pkg/btc-wallet/src/components/Send/Signer.tsx
Normal file
@ -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<Props> = ({
|
||||
signReady,
|
||||
initPayment,
|
||||
choosingSignMethod,
|
||||
signMethod,
|
||||
setSignMethod,
|
||||
}) => {
|
||||
return choosingSignMethod ? (
|
||||
<Box borderRadius="24px" backgroundColor="rgba(33, 157, 255, 0.2)">
|
||||
{Object.keys(signMethods).map((method) => (
|
||||
<Row key={method} flexDirection="row" alignItems="center">
|
||||
<Button
|
||||
border="none"
|
||||
backgroundColor="transparent"
|
||||
fontWeight="bold"
|
||||
cursor="pointer"
|
||||
color={
|
||||
signMethod === (signMethods as any)[method] ? 'blue' : 'lightBlue'
|
||||
}
|
||||
height="48px"
|
||||
onClick={() => setSignMethod((signMethods as any)[method])}
|
||||
>
|
||||
{(signMethodLabels as any)[method]}
|
||||
</Button>
|
||||
{signMethod === (signMethods as any)[method] && (
|
||||
<Button
|
||||
borderRadius="24px"
|
||||
width="24px"
|
||||
height="24px"
|
||||
backgroundColor="blue"
|
||||
border="none"
|
||||
padding="0px"
|
||||
mr="12px"
|
||||
>
|
||||
<Icon width="12px" icon="Checkmark" color="white" />
|
||||
</Button>
|
||||
)}
|
||||
</Row>
|
||||
))}
|
||||
</Box>
|
||||
) : (
|
||||
<Button
|
||||
primary
|
||||
fontSize={1}
|
||||
fontWeight="bold"
|
||||
borderRadius="24px"
|
||||
mr={2}
|
||||
height="48px"
|
||||
onClick={initPayment}
|
||||
color={signReady ? 'white' : 'lighterGray'}
|
||||
backgroundColor={signReady ? 'blue' : 'veryLightGray'}
|
||||
disabled={!signReady}
|
||||
border="none"
|
||||
style={{ cursor: signReady ? 'pointer' : 'default' }}
|
||||
>
|
||||
{(signMethodLabels as any)[signMethod]}
|
||||
</Button>
|
||||
);
|
||||
};
|
||||
|
||||
export default Signer;
|
115
pkg/btc-wallet/src/components/Settings.tsx
Normal file
115
pkg/btc-wallet/src/components/Settings.tsx
Normal file
@ -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 (
|
||||
<Col
|
||||
display="flex"
|
||||
width="100%"
|
||||
p={5}
|
||||
mb={5}
|
||||
borderRadius="48px"
|
||||
backgroundColor="white"
|
||||
>
|
||||
<Row mb="12px">
|
||||
<Text fontSize={1} fontWeight="bold" color="black">
|
||||
XPub Derivation
|
||||
</Text>
|
||||
</Row>
|
||||
<Row
|
||||
borderRadius="12px"
|
||||
backgroundColor="veryLightGray"
|
||||
py={5}
|
||||
px="36px"
|
||||
mb="12px"
|
||||
alignItems="center"
|
||||
justifyContent="space-between"
|
||||
>
|
||||
<Text mono fontSize={1} style={{ wordBreak: 'break-all' }} color="gray">
|
||||
{wallet}
|
||||
</Text>
|
||||
</Row>
|
||||
<Row width="100%" mb={5}>
|
||||
<Button
|
||||
width="100%"
|
||||
fontSize={1}
|
||||
fontWeight="bold"
|
||||
backgroundColor="gray"
|
||||
color="white"
|
||||
borderColor="none"
|
||||
borderRadius="12px"
|
||||
p={4}
|
||||
onClick={() => replaceWallet()}
|
||||
>
|
||||
Replace Wallet
|
||||
</Button>
|
||||
</Row>
|
||||
<Row mb="12px">
|
||||
<Text fontSize={1} fontWeight="bold" color="black">
|
||||
BTC Node Provider
|
||||
</Text>
|
||||
</Row>
|
||||
<Col
|
||||
mb="12px"
|
||||
py={5}
|
||||
px="36px"
|
||||
borderRadius="12px"
|
||||
backgroundColor={connBackground}
|
||||
alignItems="center"
|
||||
justifyContent="space-between"
|
||||
>
|
||||
<Text fontSize={1} color={connColor} mono>
|
||||
~{host}
|
||||
</Text>
|
||||
<Text fontSize={0} color={connColor}>
|
||||
{conn}
|
||||
</Text>
|
||||
</Col>
|
||||
<Row width="100%">
|
||||
<Button
|
||||
width="100%"
|
||||
fontSize={1}
|
||||
fontWeight="bold"
|
||||
backgroundColor="orange"
|
||||
color="white"
|
||||
borderColor="none"
|
||||
borderRadius="12px"
|
||||
p={4}
|
||||
onClick={() => changeProvider()}
|
||||
>
|
||||
Change Provider
|
||||
</Button>
|
||||
</Row>
|
||||
</Col>
|
||||
);
|
||||
};
|
||||
|
||||
export default Settings;
|
@ -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<Props> = 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,
|
||||
})}
|
||||
</Box>
|
||||
);
|
44
pkg/btc-wallet/src/components/StartupModal.tsx
Normal file
44
pkg/btc-wallet/src/components/StartupModal.tsx
Normal file
@ -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 = <ProviderModal />;
|
||||
} else if (!wallet) {
|
||||
modal = <WalletModal />;
|
||||
}
|
||||
return (
|
||||
<Box
|
||||
backgroundColor="scales.black20"
|
||||
left="0px"
|
||||
top="0px"
|
||||
width="100%"
|
||||
height="100%"
|
||||
position="fixed"
|
||||
display="flex"
|
||||
zIndex={10}
|
||||
justifyContent="center"
|
||||
alignItems="center"
|
||||
>
|
||||
<Box
|
||||
display="flex"
|
||||
flexDirection="column"
|
||||
width="400px"
|
||||
backgroundColor="white"
|
||||
borderRadius={3}
|
||||
>
|
||||
{modal}
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default StartupModal;
|
101
pkg/btc-wallet/src/components/Transactions/Transaction.tsx
Normal file
101
pkg/btc-wallet/src/components/Transactions/Transaction.tsx
Normal file
@ -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 (
|
||||
<Col
|
||||
width="100%"
|
||||
backgroundColor="white"
|
||||
justifyContent="space-between"
|
||||
mb="16px"
|
||||
>
|
||||
<Row justifyContent="space-between" alignItems="center">
|
||||
<TxAction action={action} pending={pending} txid={txid} />
|
||||
<Text fontSize="14px" alignItems="center" color="gray">
|
||||
{sign}
|
||||
{value} sats
|
||||
</Text>
|
||||
</Row>
|
||||
<Box ml="11px" borderLeft="2px solid black" height="4px"></Box>
|
||||
<Row justifyContent="space-between" alignItems="center">
|
||||
<TxCounterparty address={counterAddress} ship={counterShip} />
|
||||
<Text fontSize="14px">{currencyValue}</Text>
|
||||
</Row>
|
||||
</Col>
|
||||
);
|
||||
};
|
||||
|
||||
export default Transaction;
|
43
pkg/btc-wallet/src/components/Transactions/Transactions.tsx
Normal file
43
pkg/btc-wallet/src/components/Transactions/Transactions.tsx
Normal file
@ -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 (
|
||||
<Box
|
||||
alignItems="center"
|
||||
display="flex"
|
||||
justifyContent="center"
|
||||
height="340px"
|
||||
width="100%"
|
||||
p={5}
|
||||
mb={5}
|
||||
borderRadius="48px"
|
||||
backgroundColor="white"
|
||||
>
|
||||
<Text color="gray" fontSize={2} fontWeight="bold">
|
||||
No Transactions Yet
|
||||
</Text>
|
||||
</Box>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<Col
|
||||
width="100%"
|
||||
backgroundColor="white"
|
||||
borderRadius="48px"
|
||||
mb={5}
|
||||
p={5}
|
||||
>
|
||||
{history.map((tx, i) => {
|
||||
return <Transaction tx={tx} key={i} />;
|
||||
})}
|
||||
</Col>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
export default Transactions;
|
78
pkg/btc-wallet/src/components/Transactions/TxAction.tsx
Normal file
78
pkg/btc-wallet/src/components/Transactions/TxAction.tsx
Normal file
@ -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<Props> = ({ 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 : (
|
||||
<LoadingSpinner background="midOrange" foreground="orange" />
|
||||
);
|
||||
|
||||
const url =
|
||||
network === 'testnet'
|
||||
? `http://blockstream.info/testnet/tx/${txid}`
|
||||
: `http://blockstream.info/tx/${txid}`;
|
||||
|
||||
return (
|
||||
<Row alignItems="center">
|
||||
<Box
|
||||
backgroundColor={actionColor}
|
||||
width="24px"
|
||||
height="24px"
|
||||
textAlign="center"
|
||||
alignItems="center"
|
||||
borderRadius="2px"
|
||||
mr={2}
|
||||
p={1}
|
||||
>
|
||||
<Icon icon={leftIcon} color="white" />
|
||||
</Box>
|
||||
<Text color={actionColor} fontSize="14px">
|
||||
{actionText}
|
||||
</Text>
|
||||
<a href={url} target="_blank" rel="noreferrer">
|
||||
<Icon color={actionColor} icon="ArrowNorthEast" ml={1} mr={2} />
|
||||
</a>
|
||||
{pendingSpinner}
|
||||
</Row>
|
||||
);
|
||||
};
|
||||
|
||||
export default TxAction;
|
@ -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<Props> = ({ ship, address }) => {
|
||||
const icon = ship ? (
|
||||
<Sigil ship={ship} size={24} color="black" classes={''} icon padding={5} />
|
||||
) : (
|
||||
<Box
|
||||
backgroundColor="lighterGray"
|
||||
width="24px"
|
||||
height="24px"
|
||||
textAlign="center"
|
||||
alignItems="center"
|
||||
borderRadius="2px"
|
||||
p={1}
|
||||
>
|
||||
<Icon icon="Bitcoin" color="gray" />
|
||||
</Box>
|
||||
);
|
||||
const addressText = !address
|
||||
? ''
|
||||
: address.slice(0, 6) + '...' + address.slice(-6);
|
||||
const text = ship ? `~${ship}` : addressText;
|
||||
|
||||
return (
|
||||
<Row alignItems="center">
|
||||
{icon}
|
||||
<Text ml={2} mono fontSize="14px" color="gray">
|
||||
{text}
|
||||
</Text>
|
||||
</Row>
|
||||
);
|
||||
};
|
||||
|
||||
export default TxCounterparty;
|
268
pkg/btc-wallet/src/components/WalletModal.tsx
Normal file
268
pkg/btc-wallet/src/components/WalletModal.tsx
Normal file
@ -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<HTMLInputElement>) => {
|
||||
// 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<HTMLInputElement>) => {
|
||||
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 : <LoadingSpinner />;
|
||||
|
||||
if (mode === 'masterTicket') {
|
||||
return (
|
||||
<Box width="100%" height="100%" padding={3}>
|
||||
<Row>
|
||||
<Icon icon="Bitcoin" mr={2} />
|
||||
<Text fontSize="14px" fontWeight="bold">
|
||||
Step 2 of 2: Import your extended public key
|
||||
</Text>
|
||||
</Row>
|
||||
<Row mt={3} alignItems="center">
|
||||
<Icon icon="Info" color="yellow" height={4} width={4} />
|
||||
<Text fontSize="14px" fontWeight="regular" color="gray" ml={2}>
|
||||
We recommend that you import your wallet using Bridge to protect
|
||||
your master ticket.
|
||||
</Text>
|
||||
</Row>
|
||||
<Box display="flex" alignItems="center" mt={3} mb={2}>
|
||||
{confirmingMasterTicket && (
|
||||
<Icon
|
||||
icon="ArrowWest"
|
||||
cursor="pointer"
|
||||
onClick={() => {
|
||||
setConfirmingMasterTicket(false);
|
||||
setMasterTicket('');
|
||||
setConfirmedMasterTicket('');
|
||||
setError(false);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
<Text fontSize="14px" fontWeight="500">
|
||||
{confirmingMasterTicket ? 'Confirm Master Ticket' : 'Master Ticket'}
|
||||
</Text>
|
||||
</Box>
|
||||
<Row alignItems="center">
|
||||
<StatelessTextInput
|
||||
mr={2}
|
||||
width="256px"
|
||||
value={
|
||||
confirmingMasterTicket ? confirmedMasterTicket : masterTicket
|
||||
}
|
||||
disabled={inputDisabled}
|
||||
fontSize="14px"
|
||||
type="password"
|
||||
name="masterTicket"
|
||||
obscure={(value: string) => value.replace(/[^~-]+/g, '••••••')}
|
||||
placeholder="••••••-••••••-••••••-••••••"
|
||||
autoCapitalize="none"
|
||||
autoCorrect="off"
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
|
||||
checkTicket(e)
|
||||
}
|
||||
/>
|
||||
{!inputDisabled ? null : <LoadingSpinner />}
|
||||
</Row>
|
||||
{error && (
|
||||
<Row mt={2}>
|
||||
<Text fontSize="14px" color="red">
|
||||
Master tickets do not match
|
||||
</Text>
|
||||
</Row>
|
||||
)}
|
||||
<Row mt={3}>
|
||||
<Button
|
||||
primary
|
||||
color="black"
|
||||
backgroundColor="veryLightGray"
|
||||
borderColor="veryLightGray"
|
||||
fontSize="14px"
|
||||
mr={2}
|
||||
style={{ cursor: 'pointer' }}
|
||||
onClick={() => {
|
||||
setMode('xpub');
|
||||
setMasterTicket('');
|
||||
setXpub('');
|
||||
setReadyToSubmit(false);
|
||||
}}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
primary
|
||||
disabled={buttonDisabled}
|
||||
fontSize="14px"
|
||||
style={{ cursor: buttonDisabled ? 'default' : 'pointer' }}
|
||||
onClick={() => {
|
||||
if (!confirmingMasterTicket) {
|
||||
setConfirmingMasterTicket(true);
|
||||
} else {
|
||||
if (masterTicket === confirmedMasterTicket) {
|
||||
setError(false);
|
||||
submitMasterTicket(masterTicket);
|
||||
} else {
|
||||
setError(true);
|
||||
}
|
||||
}
|
||||
}}
|
||||
>
|
||||
Next Step
|
||||
</Button>
|
||||
</Row>
|
||||
</Box>
|
||||
);
|
||||
} else if (mode === 'xpub') {
|
||||
return (
|
||||
<Box width="100%" height="100%" padding={3}>
|
||||
<Row>
|
||||
<Icon icon="Bitcoin" mr={2} />
|
||||
<Text fontSize="14px" fontWeight="bold">
|
||||
Step 2 of 2: Import your extended public key
|
||||
</Text>
|
||||
</Row>
|
||||
<Box mt={3}>
|
||||
<Text fontSize="14px" fontWeight="regular" color="gray">
|
||||
Visit{' '}
|
||||
<a
|
||||
rel="noreferrer"
|
||||
href="https://bridge.urbit.org/?kind=xpub"
|
||||
target="_blank"
|
||||
style={{ color: 'black' }}
|
||||
>
|
||||
bridge.urbit.org
|
||||
</a>{' '}
|
||||
to obtain your key
|
||||
</Text>
|
||||
</Box>
|
||||
<Box mt={3} mb={2}>
|
||||
<Text fontSize="14px" fontWeight="500">
|
||||
Extended Public Key (XPub)
|
||||
</Text>
|
||||
</Box>
|
||||
<Row alignItems="center">
|
||||
<StatelessTextInput
|
||||
value={xpub}
|
||||
disabled={inputDisabled}
|
||||
fontSize="14px"
|
||||
type="password"
|
||||
name="xpub"
|
||||
autoCapitalize="none"
|
||||
autoCorrect="off"
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => checkXPub(e)}
|
||||
mr={1}
|
||||
/>
|
||||
{!inputDisabled ? null : <LoadingSpinner />}
|
||||
</Row>
|
||||
<Box mt={3} mb={3}>
|
||||
<Text
|
||||
fontSize="14px"
|
||||
fontWeight="regular"
|
||||
color={inputDisabled ? 'lighterGray' : 'gray'}
|
||||
style={{ cursor: inputDisabled ? 'default' : 'pointer' }}
|
||||
onClick={() => {
|
||||
if (inputDisabled) return;
|
||||
setMode('masterTicket');
|
||||
setXpub('');
|
||||
setMasterTicket('');
|
||||
setReadyToSubmit(false);
|
||||
}}
|
||||
>
|
||||
Import using master ticket ->
|
||||
</Text>
|
||||
</Box>
|
||||
<Button
|
||||
primary
|
||||
mt={3}
|
||||
disabled={buttonDisabled}
|
||||
fontSize="14px"
|
||||
style={{ cursor: readyToSubmit ? 'pointer' : 'default' }}
|
||||
onClick={() => {
|
||||
submitXPub(xpub);
|
||||
}}
|
||||
>
|
||||
Next Step
|
||||
</Button>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
export default WalletModal;
|
73
pkg/btc-wallet/src/components/Warning.tsx
Normal file
73
pkg/btc-wallet/src/components/Warning.tsx
Normal file
@ -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 (
|
||||
<Box
|
||||
backgroundColor="red"
|
||||
color="white"
|
||||
borderRadius="32px"
|
||||
justifyContent="space-between"
|
||||
width="100%"
|
||||
p={5}
|
||||
mb={5}
|
||||
>
|
||||
<Col>
|
||||
<Text color="white" fontWeight="bold" fontSize={1}>
|
||||
Warning!
|
||||
</Text>
|
||||
<br />
|
||||
<Text color="white" fontWeight="bold" fontSize={1}>
|
||||
Be safe while using this wallet, and be sure to store responsible
|
||||
amounts of BTC.
|
||||
</Text>
|
||||
<Text color="white" fontWeight="bold" fontSize={1}>
|
||||
Always ensure that the checksum of the wallet matches that of the
|
||||
wallet's repo.
|
||||
</Text>
|
||||
<br />
|
||||
<Anchor href="https://urbit.org/bitcoin-wallet" target="_blank">
|
||||
<Text
|
||||
color="white"
|
||||
fontWeight="bold"
|
||||
fontSize={1}
|
||||
style={{ textDecoration: 'underline' }}
|
||||
>
|
||||
Learn more on urbit.org
|
||||
</Text>
|
||||
</Anchor>
|
||||
</Col>
|
||||
<Button
|
||||
backgroundColor="white"
|
||||
fontSize={1}
|
||||
mt={5}
|
||||
color="red"
|
||||
fontWeight="bold"
|
||||
borderRadius="24px"
|
||||
p="24px"
|
||||
borderColor="none"
|
||||
onClick={() => understand()}
|
||||
>
|
||||
I understand
|
||||
</Button>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default Warning;
|
397
pkg/btc-wallet/src/hooks/useSettings.tsx
Normal file
397
pkg/btc-wallet/src/hooks/useSettings.tsx
Normal file
@ -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<React.SetStateAction<Network>>;
|
||||
loadedBtc: boolean;
|
||||
setLoadedBtc: React.Dispatch<React.SetStateAction<boolean>>;
|
||||
loadedSettings: boolean;
|
||||
setLoadedSettings: React.Dispatch<React.SetStateAction<boolean>>;
|
||||
loaded: boolean;
|
||||
setLoaded: React.Dispatch<React.SetStateAction<boolean>>;
|
||||
providerPerms: ProviderPerms;
|
||||
setProviderPerms: React.Dispatch<React.SetStateAction<ProviderPerms>>;
|
||||
shipWallets: ShipWallets;
|
||||
setShipWallets: React.Dispatch<React.SetStateAction<ShipWallets>>;
|
||||
provider: Provider;
|
||||
setProvider: React.Dispatch<React.SetStateAction<string | null>>;
|
||||
wallet: string | null;
|
||||
setWallet: React.Dispatch<React.SetStateAction<string | null>>;
|
||||
confirmedBalance: number;
|
||||
setConfirmedBalance: React.Dispatch<React.SetStateAction<number>>;
|
||||
unconfirmedBalance: number;
|
||||
setUnconfirmedBalance: React.Dispatch<React.SetStateAction<number>>;
|
||||
btcState: any;
|
||||
setBtcState: React.Dispatch<React.SetStateAction<any>>;
|
||||
history: Transaction[];
|
||||
setHistory: React.Dispatch<React.SetStateAction<Transaction[]>>;
|
||||
fee: number;
|
||||
setFee: React.Dispatch<React.SetStateAction<number>>;
|
||||
psbt: string;
|
||||
setPsbt: React.Dispatch<React.SetStateAction<string>>;
|
||||
address: string | null;
|
||||
setAddress: React.Dispatch<React.SetStateAction<string | null>>;
|
||||
currencyRates: CurrencyRate;
|
||||
setCurrencyRates: React.Dispatch<React.SetStateAction<{}>>;
|
||||
denomination: Denomination;
|
||||
setDenomination: React.Dispatch<React.SetStateAction<Denomination>>;
|
||||
showWarning: boolean;
|
||||
setShowWarning: React.Dispatch<React.SetStateAction<boolean>>;
|
||||
error: string;
|
||||
setError: React.Dispatch<React.SetStateAction<string>>;
|
||||
broadcastSuccess: boolean;
|
||||
setBroadcastSuccess: React.Dispatch<React.SetStateAction<boolean>>;
|
||||
scanProgress: ScanProgress;
|
||||
setScanProgress: React.Dispatch<React.SetStateAction<ScanProgress>>;
|
||||
};
|
||||
|
||||
export const SettingsContext = createContext<SettingsContextType>({
|
||||
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<Props> = ({ channel, children }) => {
|
||||
const [network, setNetwork] = useState<Network>('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<ProviderPerms>({
|
||||
provider: '',
|
||||
permitted: false,
|
||||
});
|
||||
const [shipWallets, setShipWallets] = useState<ShipWallets>({
|
||||
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<Denomination>('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 (
|
||||
<Provider
|
||||
value={{
|
||||
network,
|
||||
setNetwork,
|
||||
loadedBtc,
|
||||
setLoadedBtc,
|
||||
loadedSettings,
|
||||
setLoadedSettings,
|
||||
loaded,
|
||||
setLoaded,
|
||||
providerPerms,
|
||||
setProviderPerms,
|
||||
shipWallets,
|
||||
setShipWallets,
|
||||
provider,
|
||||
setProvider,
|
||||
wallet,
|
||||
setWallet,
|
||||
confirmedBalance,
|
||||
setConfirmedBalance,
|
||||
unconfirmedBalance,
|
||||
setUnconfirmedBalance,
|
||||
btcState,
|
||||
setBtcState,
|
||||
history,
|
||||
setHistory,
|
||||
psbt,
|
||||
setPsbt,
|
||||
fee,
|
||||
setFee,
|
||||
address,
|
||||
setAddress,
|
||||
currencyRates,
|
||||
setCurrencyRates,
|
||||
denomination,
|
||||
setDenomination,
|
||||
showWarning,
|
||||
setShowWarning,
|
||||
error,
|
||||
setError,
|
||||
broadcastSuccess,
|
||||
setBroadcastSuccess,
|
||||
scanProgress,
|
||||
setScanProgress,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</Provider>
|
||||
);
|
||||
};
|
||||
|
||||
export const useSettings = () => useContext(SettingsContext);
|
@ -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((
|
||||
<Root channel={channel}/>
|
||||
), document.querySelectorAll("#root")[0]);
|
24
pkg/btc-wallet/src/index.tsx
Normal file
24
pkg/btc-wallet/src/index.tsx
Normal file
@ -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(
|
||||
<SettingsProvider channel={channel}>
|
||||
<App />
|
||||
</SettingsProvider>,
|
||||
document.querySelectorAll('#root')[0]
|
||||
);
|
@ -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 ? (
|
||||
<Send
|
||||
state={this.props.state}
|
||||
api={this.props.api}
|
||||
psbt={this.props.state.psbt}
|
||||
fee={this.props.state.fee}
|
||||
currencyRates={this.props.state.currencyRates}
|
||||
shipWallets={this.props.state.shipWallets}
|
||||
value={value}
|
||||
denomination={denomination}
|
||||
sats={sats}
|
||||
conversion={conversion}
|
||||
network={this.props.network}
|
||||
error={this.props.state.error}
|
||||
stopSending={() => {
|
||||
this.setState({ sending: false });
|
||||
store.handleEvent({
|
||||
data: { psbt: '', fee: 0, error: '', 'broadcast-fail': null },
|
||||
});
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<Col
|
||||
height="400px"
|
||||
width="100%"
|
||||
backgroundColor="white"
|
||||
borderRadius="48px"
|
||||
justifyContent="space-between"
|
||||
mb={5}
|
||||
p={5}
|
||||
>
|
||||
<Row justifyContent="space-between">
|
||||
<Text color="orange" fontSize={1}>
|
||||
Balance
|
||||
</Text>
|
||||
<Text
|
||||
color="lightGray"
|
||||
fontSize="14px"
|
||||
mono
|
||||
style={{ cursor: 'pointer' }}
|
||||
onClick={() => {
|
||||
this.copyAddress('string');
|
||||
}}
|
||||
>
|
||||
{this.state.copiedString ? 'copied' : addressText}
|
||||
</Text>
|
||||
<CurrencyPicker
|
||||
api={this.props.api}
|
||||
denomination={denomination}
|
||||
currencies={this.props.state.currencyRates}
|
||||
/>
|
||||
</Row>
|
||||
<Col justifyContent="center" alignItems="center">
|
||||
<Text
|
||||
fontSize="40px"
|
||||
color="orange"
|
||||
style={{ whiteSpace: 'nowrap' }}
|
||||
>
|
||||
{value}
|
||||
</Text>
|
||||
<Text
|
||||
fontSize={1}
|
||||
color="orange"
|
||||
>{`${sats}${unconfirmedString} sats`}</Text>
|
||||
</Col>
|
||||
<Row flexDirection="row-reverse">
|
||||
<Button
|
||||
disabled={sendDisabled}
|
||||
fontSize={1}
|
||||
fontWeight="bold"
|
||||
color={sendDisabled ? 'lighterGray' : 'white'}
|
||||
backgroundColor={sendDisabled ? 'veryLightGray' : 'orange'}
|
||||
style={{ cursor: sendDisabled ? 'default' : 'pointer' }}
|
||||
borderColor="none"
|
||||
borderRadius="24px"
|
||||
height="48px"
|
||||
onClick={() => this.setState({ sending: true })}
|
||||
>
|
||||
Send
|
||||
</Button>
|
||||
<Button
|
||||
mr={3}
|
||||
disabled={this.state.copiedButton}
|
||||
fontSize={1}
|
||||
fontWeight="bold"
|
||||
color={this.state.copiedButton ? 'green' : 'orange'}
|
||||
backgroundColor={
|
||||
this.state.copiedButton ? 'veryLightGreen' : 'midOrange'
|
||||
}
|
||||
style={{
|
||||
cursor: this.state.copiedButton ? 'default' : 'pointer',
|
||||
}}
|
||||
borderColor="none"
|
||||
borderRadius="24px"
|
||||
height="48px"
|
||||
onClick={() => {
|
||||
this.copyAddress('button');
|
||||
}}
|
||||
>
|
||||
{this.state.copiedButton ? 'Address Copied!' : 'Copy Address'}
|
||||
</Button>
|
||||
</Row>
|
||||
</Col>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
@ -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 (
|
||||
<Box display="flex" width="100%" height="100%" alignItems="center" justifyContent="center">
|
||||
<LoadingSpinner
|
||||
width={7}
|
||||
height={7}
|
||||
background="midOrange"
|
||||
foreground="orange"
|
||||
/>
|
||||
</Box>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<Switch>
|
||||
<Route path="/settings">
|
||||
<Col
|
||||
display='flex'
|
||||
flexDirection='column'
|
||||
width={cardWidth}
|
||||
>
|
||||
<Header settings={true} state={this.props.state}/>
|
||||
<Settings state={this.props.state}
|
||||
api={this.props.api}
|
||||
network={this.props.network}
|
||||
/>
|
||||
</Col>
|
||||
</Route>
|
||||
<Route path="/">
|
||||
<Col
|
||||
display='flex'
|
||||
flexDirection='column'
|
||||
width={cardWidth}
|
||||
>
|
||||
<Header settings={false} state={this.props.state}/>
|
||||
{ (!this.props.warning) ? null : <Warning api={this.props.api}/>}
|
||||
<Balance api={this.props.api} state={this.props.state} network={this.props.network}/>
|
||||
<Transactions state={this.props.state} network={this.props.network}/>
|
||||
</Col>
|
||||
</Route>
|
||||
</Switch>
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
@ -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)
|
||||
? <Sigil ship={payee} size={24} color="black" classes={''} icon padding={5}/>
|
||||
: <Box backgroundColor="lighterGray"
|
||||
width="24px"
|
||||
height="24px"
|
||||
textAlign="center"
|
||||
alignItems="center"
|
||||
borderRadius="2px"
|
||||
p={1}
|
||||
><Icon icon="Bitcoin" color="gray"/></Box>;
|
||||
|
||||
return (
|
||||
<>
|
||||
{ this.props.state.broadcastSuccess ?
|
||||
<Sent
|
||||
payee={payee}
|
||||
stopSending={stopSending}
|
||||
denomination={denomination}
|
||||
currencyRates={currencyRates}
|
||||
satsAmount={satsAmount}
|
||||
/> :
|
||||
<Col
|
||||
ref={this.setInvoiceRef}
|
||||
width='100%'
|
||||
backgroundColor='white'
|
||||
borderRadius='48px'
|
||||
mb={5}
|
||||
p={5}
|
||||
>
|
||||
<Col
|
||||
p={5}
|
||||
mt={4}
|
||||
backgroundColor='veryLightGreen'
|
||||
borderRadius='24px'
|
||||
alignItems="center"
|
||||
>
|
||||
<Row>
|
||||
<Text
|
||||
color='green'
|
||||
fontSize='40px'
|
||||
>{satsToCurrency(satsAmount, denomination, currencyRates)}</Text>
|
||||
</Row>
|
||||
<Row>
|
||||
<Text
|
||||
fontWeight="bold"
|
||||
fontSize='16px'
|
||||
color='midGreen'
|
||||
>{`${satsAmount} sats`}</Text>
|
||||
</Row>
|
||||
<Row mt={2}>
|
||||
<Text
|
||||
fontSize='14px'
|
||||
color='midGreen'
|
||||
>{`Fee: ${satsToCurrency(fee, denomination, currencyRates)} (${fee} sats)`}</Text>
|
||||
</Row>
|
||||
<Row mt={4} >
|
||||
<Text fontSize='16px' fontWeight="bold" color="gray">You are paying</Text>
|
||||
</Row>
|
||||
<Row mt={2} alignItems="center">
|
||||
{icon}
|
||||
<Text ml={2}
|
||||
mono
|
||||
color="gray"
|
||||
fontSize='14px'
|
||||
style={{'display': 'block', 'overflow-wrap': 'anywhere'}}
|
||||
>{payee}</Text>
|
||||
</Row>
|
||||
</Col>
|
||||
<Box mt={3}>
|
||||
<Text fontSize='14px' fontWeight='500'>
|
||||
Bridge signed transaction
|
||||
</Text>
|
||||
</Box>
|
||||
<Box mt={1} mb={2}>
|
||||
<Text gray fontSize='14px'>
|
||||
Copy the signed transaction from Bridge
|
||||
</Text>
|
||||
</Box>
|
||||
<Input
|
||||
value={this.state.txHex}
|
||||
fontSize='14px'
|
||||
placeholder='010000000001019e478cc370323ac539097...'
|
||||
autoCapitalize='none'
|
||||
autoCorrect='off'
|
||||
color={inputColor}
|
||||
backgroundColor={inputBg}
|
||||
borderColor={inputBorder}
|
||||
style={{'line-height': '4'}}
|
||||
onChange={this.checkTxHex}
|
||||
/>
|
||||
{ (error !== '') &&
|
||||
<Row>
|
||||
<Error
|
||||
error={error}
|
||||
fontSize='14px'
|
||||
mt={2}/>
|
||||
</Row>
|
||||
}
|
||||
<Row
|
||||
flexDirection='row-reverse'
|
||||
mt={4}
|
||||
alignItems="center"
|
||||
>
|
||||
<Button
|
||||
primary
|
||||
children='Send BTC'
|
||||
mr={3}
|
||||
fontSize={1}
|
||||
borderRadius='24px'
|
||||
border='none'
|
||||
height='48px'
|
||||
onClick={() => this.sendBitcoin(txHex)}
|
||||
disabled={!this.state.ready || error || this.state.broadcasting}
|
||||
color={(this.state.ready && !error && !this.state.broadcasting) ? "white" : "lighterGray"}
|
||||
backgroundColor={(this.state.ready && !error && !this.state.broadcasting) ? "green" : "veryLightGray"}
|
||||
style={{cursor: (this.state.ready && !error) ? "pointer" : "default"}}
|
||||
/>
|
||||
{this.state.broadcasting ? <LoadingSpinner mr={3}/> : null}
|
||||
</Row>
|
||||
</Col>
|
||||
}
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
@ -1,51 +0,0 @@
|
||||
import React, { Component } from 'react';
|
||||
import {
|
||||
Box,
|
||||
Icon,
|
||||
Row,
|
||||
Text,
|
||||
Button,
|
||||
Col,
|
||||
LoadingSpinner,
|
||||
} from '@tlon/indigo-react';
|
||||
import _ from 'lodash';
|
||||
|
||||
import { satsToCurrency } from '../../lib/util.js'
|
||||
import { store } from '../../store';
|
||||
|
||||
export default class CurrencyPicker extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.switchCurrency = this.switchCurrency.bind(this);
|
||||
}
|
||||
|
||||
switchCurrency(){
|
||||
let newCurrency;
|
||||
if (this.props.denomination === 'BTC') {
|
||||
if (this.props.currencies['USD']) {
|
||||
newCurrency = "USD";
|
||||
}
|
||||
} else if (this.props.denomination === 'USD') {
|
||||
newCurrency = "BTC";
|
||||
}
|
||||
let setCurrency = {
|
||||
"put-entry": {
|
||||
desk: window.desk,
|
||||
value: newCurrency,
|
||||
"entry-key": "currency",
|
||||
"bucket-key": "btc-wallet",
|
||||
}
|
||||
}
|
||||
this.props.api.settingsEvent(setCurrency);
|
||||
}
|
||||
|
||||
|
||||
render() {
|
||||
return (
|
||||
<Row style={{cursor: "pointer"}} onClick={this.switchCurrency}>
|
||||
<Icon icon="ChevronDouble" color="orange" pt="2px" pr={1} />
|
||||
<Text color="orange" fontSize={1}>{this.props.denomination}</Text>
|
||||
</Row>
|
||||
);
|
||||
}
|
||||
}
|
@ -1,41 +0,0 @@
|
||||
import React, { Component } from 'react';
|
||||
import { Text } from '@tlon/indigo-react';
|
||||
|
||||
const errorToString = (error) => {
|
||||
if (error === 'cant-pay-ourselves') {
|
||||
return 'Cannot pay ourselves';
|
||||
}
|
||||
if (error === 'no-comets') {
|
||||
return 'Cannot pay comets';
|
||||
}
|
||||
if (error === 'no-dust') {
|
||||
return 'Cannot send dust';
|
||||
}
|
||||
if (error === 'tx-being-signed') {
|
||||
return 'Cannot pay when transaction is being signed';
|
||||
}
|
||||
if (error === 'insufficient-balance') {
|
||||
return 'Insufficient confirmed balance';
|
||||
}
|
||||
if (error === 'broadcast-fail') {
|
||||
return 'Transaction broadcast failed';
|
||||
}
|
||||
if (error === 'invalid-master-ticket') {
|
||||
return 'Invalid master ticket';
|
||||
}
|
||||
if (error === 'invalid-signed') {
|
||||
return 'Invalid signed bitcoin transaction';
|
||||
}
|
||||
}
|
||||
|
||||
export default function Error(props) {
|
||||
const error = errorToString(props.error);
|
||||
|
||||
return(
|
||||
<Text
|
||||
color='red'
|
||||
{...props}>
|
||||
{error}
|
||||
</Text>
|
||||
);
|
||||
}
|
@ -1,99 +0,0 @@
|
||||
import React, { Component } from 'react';
|
||||
import {
|
||||
Box,
|
||||
Icon,
|
||||
Row,
|
||||
Text,
|
||||
Button,
|
||||
Col,
|
||||
StatelessRadioButtonField as RadioButton,
|
||||
Label,
|
||||
} from '@tlon/indigo-react';
|
||||
|
||||
|
||||
export default class FeePicker extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
selected: 'mid'
|
||||
}
|
||||
|
||||
this.select = this.select.bind(this);
|
||||
this.clickDismiss = this.clickDismiss.bind(this);
|
||||
this.setModalRef = this.setModalRef.bind(this);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
document.addEventListener("click", this.clickDismiss);
|
||||
}
|
||||
|
||||
componentWillUnount() {
|
||||
document.removeEventListener("click", this.clickDismiss);
|
||||
}
|
||||
|
||||
setModalRef(n) {
|
||||
this.modalRef = n;
|
||||
}
|
||||
|
||||
clickDismiss(e) {
|
||||
if (this.modalRef && !(this.modalRef.contains(e.target))){
|
||||
this.props.feeDismiss();
|
||||
}
|
||||
}
|
||||
|
||||
select(which) {
|
||||
this.setState({selected: which});
|
||||
this.props.feeSelect(which);
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<Box
|
||||
ref={this.setModalRef}
|
||||
position="absolute" p={4}
|
||||
border="1px solid green" zIndex={10}
|
||||
backgroundColor="white" borderRadius={3}
|
||||
>
|
||||
<Text fontSize={1} color="black" fontWeight="bold" mb={4}>
|
||||
Transaction Speed
|
||||
</Text>
|
||||
<Col mt={4}>
|
||||
<RadioButton
|
||||
name="feeRadio"
|
||||
selected={this.state.selected === 'low'}
|
||||
p="2"
|
||||
onChange={() => {
|
||||
this.select('low');
|
||||
}}
|
||||
>
|
||||
<Label fontSize="14px">Slow: {this.props.feeChoices.low[1]} sats/vbyte ~{this.props.feeChoices.low[0]}m</Label>
|
||||
</RadioButton>
|
||||
|
||||
<RadioButton
|
||||
name="feeRadio"
|
||||
selected={this.state.selected === 'mid'}
|
||||
p="2"
|
||||
onChange={() => {
|
||||
this.select('mid');
|
||||
}}
|
||||
>
|
||||
<Label fontSize="14px">Normal: {this.props.feeChoices.mid[1]} sats/vbyte ~{this.props.feeChoices.mid[0]}m</Label>
|
||||
</RadioButton>
|
||||
|
||||
<RadioButton
|
||||
name="feeRadio"
|
||||
selected={this.state.selected === 'high'}
|
||||
p="2"
|
||||
onChange={() => {
|
||||
this.select('high');
|
||||
}}
|
||||
>
|
||||
<Label fontSize="14px">Fast: {this.props.feeChoices.high[1]} sats/vbyte ~{this.props.feeChoices.high[0]}m</Label>
|
||||
</RadioButton>
|
||||
</Col>
|
||||
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
}
|
@ -1,78 +0,0 @@
|
||||
import React, { Component } from 'react';
|
||||
import {
|
||||
Box,
|
||||
Icon,
|
||||
Row,
|
||||
Text,
|
||||
} from '@tlon/indigo-react';
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
export default class Header extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
}
|
||||
|
||||
|
||||
render() {
|
||||
let icon = this.props.settings ? "X" : "Adjust";
|
||||
let iconColor = this.props.settings ? "black" : "orange";
|
||||
let iconLink = this.props.settings ? "/" : "/settings";
|
||||
|
||||
let connection = null;
|
||||
let badge = null;
|
||||
if (!(this.props.state.provider && this.props.state.provider.connected)) {
|
||||
connection =
|
||||
<Text fontSize={1} color="red" fontWeight="bold" mr={3}>
|
||||
Provider Offline
|
||||
</Text>
|
||||
|
||||
if (!this.props.settings) {
|
||||
badge = <Box borderRadius="50%" width="8px" height="8px" backgroundColor="red" position="absolute" top="0px" right="0px"></Box>
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
return (
|
||||
<Row
|
||||
height={8}
|
||||
width='100%'
|
||||
justifyContent="space-between"
|
||||
alignItems="center"
|
||||
pt={5}
|
||||
pb={5}
|
||||
>
|
||||
<Row alignItems="center" justifyContent="center">
|
||||
<Box backgroundColor="orange"
|
||||
borderRadius={4} mr="12px"
|
||||
width={5}
|
||||
height={5}
|
||||
alignItems="center"
|
||||
justifyContent="center"
|
||||
>
|
||||
<Icon icon="Bitcoin" width={4} p={1} height={4} color="white"/>
|
||||
</Box>
|
||||
<Text fontSize={2} fontWeight="bold" color="orange">
|
||||
Bitcoin
|
||||
</Text>
|
||||
</Row>
|
||||
<Row alignItems="center">
|
||||
{connection}
|
||||
<Link to={iconLink}>
|
||||
<Box backgroundColor="white"
|
||||
borderRadius={4}
|
||||
width={5}
|
||||
height={5}
|
||||
p={2}
|
||||
position="relative"
|
||||
>
|
||||
{badge}
|
||||
<Icon icon={icon} color={iconColor} />
|
||||
</Box>
|
||||
</Link>
|
||||
</Row>
|
||||
</Row>
|
||||
);
|
||||
}
|
||||
}
|
@ -1,291 +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 * as bip39 from 'bip39';
|
||||
|
||||
import Sent from './sent.js'
|
||||
import { patp2dec, isValidPatq, isValidPatp } from 'urbit-ob';
|
||||
|
||||
import { satsToCurrency } from '../../lib/util.js';
|
||||
import Error from './error.js';
|
||||
|
||||
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,
|
||||
};
|
||||
|
||||
export default class Invoice extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
masterTicket: '',
|
||||
ready: false,
|
||||
error: this.props.state.error,
|
||||
sent: false,
|
||||
broadcasting: false,
|
||||
};
|
||||
|
||||
this.checkTicket = this.checkTicket.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);
|
||||
}
|
||||
|
||||
componentDidMount(){
|
||||
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, prevState) {
|
||||
if (this.state.broadcasting) {
|
||||
if (this.state.error !== '') {
|
||||
this.setState({broadcasting: false});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
broadCastTx(psbtHex) {
|
||||
let command = {
|
||||
'broadcast-tx': psbtHex
|
||||
}
|
||||
return this.props.api.btcWalletCommand(command)
|
||||
}
|
||||
|
||||
sendBitcoin(ticket, psbt) {
|
||||
const newPsbt = bitcoin.Psbt.fromBase64(psbt);
|
||||
this.setState({broadcasting: true});
|
||||
kg.generateWallet({ ticket, ship: parseInt(patp2dec('~' + window.ship)) })
|
||||
.then(urbitWallet => {
|
||||
const { xpub } = this.props.network === 'testnet'
|
||||
? urbitWallet.bitcoinTestnet.keys
|
||||
: urbitWallet.bitcoinMainnet.keys;
|
||||
|
||||
const { xprv: zprv } = urbitWallet.bitcoinMainnet.keys;
|
||||
const { xprv: vprv } = urbitWallet.bitcoinTestnet.keys;
|
||||
|
||||
const isTestnet = (this.props.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();
|
||||
|
||||
this.broadCastTx(hex);
|
||||
}
|
||||
catch(e) {
|
||||
this.setState({error: 'invalid-master-ticket', broadcasting: false});
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
|
||||
checkTicket(e){
|
||||
// TODO: port over bridge ticket validation logic
|
||||
let masterTicket = e.target.value;
|
||||
let ready = isValidPatq(masterTicket);
|
||||
let error = (ready) ? '' : 'invalid-master-ticket';
|
||||
this.setState({masterTicket, ready, error});
|
||||
}
|
||||
|
||||
render() {
|
||||
const broadcastSuccess = this.props.state.broadcastSuccess;
|
||||
const { stopSending, payee, denomination, satsAmount, psbt, currencyRates, fee } = this.props;
|
||||
const { sent, error } = 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)
|
||||
? <Sigil ship={payee} size={24} color="black" classes={''} icon padding={5}/>
|
||||
: <Box backgroundColor="lighterGray"
|
||||
width="24px"
|
||||
height="24px"
|
||||
textAlign="center"
|
||||
alignItems="center"
|
||||
borderRadius="2px"
|
||||
p={1}
|
||||
><Icon icon="Bitcoin" color="gray"/></Box>;
|
||||
|
||||
return (
|
||||
<>
|
||||
{ broadcastSuccess ?
|
||||
<Sent
|
||||
payee={payee}
|
||||
stopSending={stopSending}
|
||||
denomination={denomination}
|
||||
currencyRates={currencyRates}
|
||||
satsAmount={satsAmount}
|
||||
/> :
|
||||
<Col
|
||||
ref={this.setInvoiceRef}
|
||||
width='100%'
|
||||
backgroundColor='white'
|
||||
borderRadius='48px'
|
||||
mb={5}
|
||||
p={5}
|
||||
>
|
||||
<Col
|
||||
p={5}
|
||||
mt={4}
|
||||
backgroundColor='veryLightGreen'
|
||||
borderRadius='24px'
|
||||
alignItems="center"
|
||||
>
|
||||
<Row>
|
||||
<Text
|
||||
color='green'
|
||||
fontSize='40px'
|
||||
>{satsToCurrency(satsAmount, denomination, currencyRates)}</Text>
|
||||
</Row>
|
||||
<Row>
|
||||
<Text
|
||||
fontWeight="bold"
|
||||
fontSize='16px'
|
||||
color='midGreen'
|
||||
>{`${satsAmount} sats`}</Text>
|
||||
</Row>
|
||||
<Row mt={2}>
|
||||
<Text
|
||||
fontSize='14px'
|
||||
color='midGreen'
|
||||
>{`Fee: ${satsToCurrency(fee, denomination, currencyRates)} (${fee} sats)`}</Text>
|
||||
</Row>
|
||||
<Row mt={4} >
|
||||
<Text fontSize='16px' fontWeight="bold" color="gray">You are paying</Text>
|
||||
</Row>
|
||||
<Row mt={2} alignItems="center">
|
||||
{icon}
|
||||
<Text ml={2}
|
||||
mono
|
||||
color="gray"
|
||||
fontSize='14px'
|
||||
style={{'display': 'block', 'overflow-wrap': 'anywhere'}}
|
||||
>{payee}</Text>
|
||||
</Row>
|
||||
</Col>
|
||||
<Row mt={3} mb={2} alignItems="center">
|
||||
<Text gray fontSize={1} fontWeight='600' mr={4}>
|
||||
Ticket
|
||||
</Text>
|
||||
<Input
|
||||
value={this.state.masterTicket}
|
||||
fontSize="14px"
|
||||
type="password"
|
||||
name="masterTicket"
|
||||
obscure={value => value.replace(/[^~-]+/g, '••••••')}
|
||||
placeholder="••••••-••••••-••••••-••••••"
|
||||
autoCapitalize="none"
|
||||
autoCorrect="off"
|
||||
color={inputColor}
|
||||
backgroundColor={inputBg}
|
||||
borderColor={inputBorder}
|
||||
onChange={this.checkTicket}
|
||||
/>
|
||||
</Row>
|
||||
{(error !== '') &&
|
||||
<Row>
|
||||
<Error
|
||||
fontSize='14px'
|
||||
color='red'
|
||||
error={error}
|
||||
mt={2}/>
|
||||
</Row>
|
||||
}
|
||||
<Row
|
||||
flexDirection='row-reverse'
|
||||
mt={4}
|
||||
alignItems="center"
|
||||
>
|
||||
<Button
|
||||
primary
|
||||
children='Send BTC'
|
||||
mr={3}
|
||||
fontSize={1}
|
||||
border="none"
|
||||
borderRadius='24px'
|
||||
color={(this.state.ready && !error && !this.state.broadcasting) ? "white" : "lighterGray"}
|
||||
backgroundColor={(this.state.ready && !error && !this.state.broadcasting) ? "green" : "veryLightGray"}
|
||||
height='48px'
|
||||
onClick={() => this.sendBitcoin(this.state.masterTicket, psbt)}
|
||||
disabled={!this.state.ready || error || this.state.broadcasting}
|
||||
style={{cursor: (this.state.ready && !error && !this.state.broadcasting) ? "pointer" : "default"}}
|
||||
/>
|
||||
{ (this.state.broadcasting) ? <LoadingSpinner mr={3}/> : null}
|
||||
</Row>
|
||||
</Col>
|
||||
}
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
@ -1,166 +0,0 @@
|
||||
import React, { Component } from 'react';
|
||||
import {
|
||||
Box,
|
||||
Text,
|
||||
Button,
|
||||
StatelessTextInput,
|
||||
Icon,
|
||||
Row,
|
||||
LoadingSpinner,
|
||||
} from '@tlon/indigo-react';
|
||||
|
||||
import { isValidPatp } from 'urbit-ob';
|
||||
|
||||
export default class ProviderModal extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
potentialProvider: null,
|
||||
checkingProvider: false,
|
||||
providerFailed: false,
|
||||
ready: false,
|
||||
provider: null,
|
||||
connecting: false,
|
||||
};
|
||||
|
||||
this.checkProvider = this.checkProvider.bind(this);
|
||||
this.submitProvider = this.submitProvider.bind(this);
|
||||
}
|
||||
|
||||
checkProvider(e) {
|
||||
// TODO: loading states
|
||||
let provider = e.target.value;
|
||||
let ready = false;
|
||||
let checkingProvider = false;
|
||||
let potentialProvider = this.state.potentialProvider;
|
||||
|
||||
if (isValidPatp(provider)) {
|
||||
let command = {
|
||||
'check-provider': provider,
|
||||
};
|
||||
potentialProvider = provider;
|
||||
checkingProvider = true;
|
||||
this.props.api.btcWalletCommand(command);
|
||||
setTimeout(() => {
|
||||
this.setState({ providerFailed: true, checkingProvider: false });
|
||||
}, 5000);
|
||||
}
|
||||
this.setState({ provider, ready, checkingProvider, potentialProvider });
|
||||
}
|
||||
|
||||
componentDidUpdate() {
|
||||
if (!this.state.ready) {
|
||||
if (this.props.providerPerms[this.state.provider]) {
|
||||
this.setState({
|
||||
ready: true,
|
||||
checkingProvider: false,
|
||||
providerFailed: false,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
submitProvider() {
|
||||
if (this.state.ready) {
|
||||
let command = {
|
||||
'set-provider': this.state.provider,
|
||||
};
|
||||
this.props.api.btcWalletCommand(command);
|
||||
this.setState({ connecting: true });
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
let workingNode = null;
|
||||
let workingColor = null;
|
||||
let workingBg = null;
|
||||
if (this.state.ready) {
|
||||
workingColor = 'green';
|
||||
workingBg = 'veryLightGreen';
|
||||
workingNode = (
|
||||
<Box mt={3}>
|
||||
<Text fontSize="14px" color="green">
|
||||
{this.state.provider} is a working provider node
|
||||
</Text>
|
||||
</Box>
|
||||
);
|
||||
} else if (this.state.providerFailed) {
|
||||
workingColor = 'red';
|
||||
workingBg = 'veryLightRed';
|
||||
workingNode = (
|
||||
<Box mt={3}>
|
||||
<Text fontSize="14px" color="red">
|
||||
{this.state.potentialProvider} is not a working provider node
|
||||
</Text>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Box width="100%" height="100%" padding={3}>
|
||||
<Row>
|
||||
<Icon icon="Bitcoin" mr={2} />
|
||||
<Text fontSize="14px" fontWeight="bold">
|
||||
Step 1 of 2: Set up Bitcoin Provider Node
|
||||
</Text>
|
||||
</Row>
|
||||
<Box mt={3}>
|
||||
<Text fontSize="14px" fontWeight="regular" color="gray">
|
||||
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.
|
||||
<a
|
||||
fontSize="14px"
|
||||
target="_blank"
|
||||
href="https://urbit.org/bitcoin-wallet"
|
||||
rel="noreferrer"
|
||||
>
|
||||
{' '}
|
||||
Learn More
|
||||
</a>
|
||||
</Text>
|
||||
</Box>
|
||||
<Box mt={3} mb={2}>
|
||||
<Text fontSize="14px" fontWeight="500">
|
||||
Provider Node
|
||||
</Text>
|
||||
</Box>
|
||||
<Row alignItems="center">
|
||||
<StatelessTextInput
|
||||
mr={2}
|
||||
width="256px"
|
||||
fontSize="14px"
|
||||
type="text"
|
||||
name="masterTicket"
|
||||
placeholder="e.g. ~zod"
|
||||
autoCapitalize="none"
|
||||
autoCorrect="off"
|
||||
mono
|
||||
backgroundColor={workingBg}
|
||||
color={workingColor}
|
||||
borderColor={workingColor}
|
||||
onChange={this.checkProvider}
|
||||
/>
|
||||
{this.state.checkingProvider ? <LoadingSpinner /> : null}
|
||||
</Row>
|
||||
{workingNode}
|
||||
<Row alignItems="center" mt={3}>
|
||||
<Button
|
||||
mr={2}
|
||||
primary
|
||||
disabled={!this.state.ready}
|
||||
fontSize="14px"
|
||||
style={{ cursor: this.state.ready ? 'pointer' : 'default' }}
|
||||
onClick={() => {
|
||||
this.submitProvider(this.state.provider);
|
||||
}}
|
||||
>
|
||||
Set Peer Node
|
||||
</Button>
|
||||
{this.state.connecting ? <LoadingSpinner /> : null}
|
||||
</Row>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
}
|
@ -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 =
|
||||
<Invoice
|
||||
network={network}
|
||||
api={api}
|
||||
psbt={psbt}
|
||||
fee={fee}
|
||||
currencyRates={currencyRates}
|
||||
stopSending={stopSending}
|
||||
payee={payee}
|
||||
denomination={denomination}
|
||||
satsAmount={satsAmount}
|
||||
state={this.props.state}
|
||||
/>
|
||||
} else if (signMethod === 'bridge') {
|
||||
invoice =
|
||||
<BridgeInvoice
|
||||
state={this.props.state}
|
||||
api={api}
|
||||
psbt={psbt}
|
||||
fee={fee}
|
||||
currencyRates={currencyRates}
|
||||
stopSending={stopSending}
|
||||
payee={payee}
|
||||
denomination={denomination}
|
||||
satsAmount={satsAmount}
|
||||
/>
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{ (signing && psbt) ? invoice :
|
||||
<Col
|
||||
width='100%'
|
||||
backgroundColor='white'
|
||||
borderRadius='48px'
|
||||
mb={5}
|
||||
p={5}
|
||||
>
|
||||
<Col width="100%">
|
||||
<Row
|
||||
justifyContent='space-between'
|
||||
alignItems='center'
|
||||
>
|
||||
<Text highlight color='blue' fontSize={1}>Send BTC</Text>
|
||||
<Text highlight color='blue' fontSize={1}>{value}</Text>
|
||||
<Icon
|
||||
icon='X'
|
||||
cursor='pointer'
|
||||
onClick={() => stopSending()}
|
||||
/>
|
||||
</Row>
|
||||
<Row
|
||||
alignItems='center'
|
||||
mt={6}
|
||||
justifyContent='space-between'>
|
||||
<Row justifyContent="space-between" width='calc(40% - 30px)' alignItems="center">
|
||||
<Text gray fontSize={1} fontWeight='600'>To</Text>
|
||||
{this.state.checkingPatp ?
|
||||
<LoadingSpinner background="midOrange" foreground="orange"/> : null
|
||||
}
|
||||
</Row>
|
||||
<Input
|
||||
autoFocus
|
||||
onFocus={() => {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}
|
||||
/>
|
||||
</Row>
|
||||
{error &&
|
||||
<Row
|
||||
alignItems='center'
|
||||
justifyContent='space-between'>
|
||||
{/* yes this is a hack */}
|
||||
<Box width='calc(40% - 30px)'/>
|
||||
<Error
|
||||
error={error}
|
||||
fontSize='14px'
|
||||
ml={2}
|
||||
mt={2}
|
||||
width='100%' />
|
||||
</Row>
|
||||
}
|
||||
<Row
|
||||
alignItems='center'
|
||||
mt={4}
|
||||
justifyContent='space-between'>
|
||||
<Text
|
||||
gray
|
||||
fontSize={1}
|
||||
fontWeight='600'
|
||||
width="40%"
|
||||
>Amount</Text>
|
||||
<Input
|
||||
onFocus={() => {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)
|
||||
});
|
||||
}}
|
||||
/>
|
||||
<Text color="lighterGray" fontSize={1} ml={3}>{denomination}</Text>
|
||||
</Row>
|
||||
<Row
|
||||
alignItems='center'
|
||||
mt={2}
|
||||
justifyContent='space-between'>
|
||||
{/* yes this is a hack */}
|
||||
<Box width='40%'/>
|
||||
<Input
|
||||
onFocus={() => {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
|
||||
});
|
||||
}}
|
||||
/>
|
||||
<Text color="lightGray" fontSize={1} ml={3}>sats</Text>
|
||||
</Row>
|
||||
<Row mt={4} width="100%" justifyContent="space-between">
|
||||
<Text
|
||||
gray
|
||||
fontSize={1}
|
||||
fontWeight='600'
|
||||
width="40%"
|
||||
>Fee</Text>
|
||||
<Row alignItems="center">
|
||||
<Text mr={2} color="lightGray" fontSize="14px">
|
||||
{this.state.feeChoices[this.state.feeValue][1]} sats/vbyte
|
||||
</Text>
|
||||
<Icon icon="ChevronSouth"
|
||||
fontSize="14px"
|
||||
color="lightGray"
|
||||
onClick={() => {if (!this.state.showModal) this.setState({showModal: true}); }}
|
||||
cursor="pointer"/>
|
||||
</Row>
|
||||
</Row>
|
||||
<Col alignItems="center">
|
||||
{!this.state.showModal ? null :
|
||||
<FeePicker
|
||||
feeChoices={this.state.feeChoices}
|
||||
feeSelect={this.feeSelect}
|
||||
feeDismiss={this.feeDismiss}
|
||||
/>
|
||||
}
|
||||
</Col>
|
||||
<Row mt={4} width="100%"
|
||||
justifyContent="space-between"
|
||||
alignItems='center'
|
||||
>
|
||||
<Text
|
||||
gray
|
||||
fontSize={1}
|
||||
fontWeight='600'
|
||||
width="40%"
|
||||
>Note</Text>
|
||||
<Input
|
||||
onFocus={() => {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,
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</Row>
|
||||
</Col>
|
||||
<Row
|
||||
flexDirection='row-reverse'
|
||||
alignItems="center"
|
||||
mt={4}
|
||||
>
|
||||
<Signer
|
||||
signReady={signReady}
|
||||
choosingSignMethod={choosingSignMethod}
|
||||
signMethod={signMethod}
|
||||
setSignMethod={this.setSignMethod}
|
||||
initPayment={this.initPayment} />
|
||||
{ (!(signing && !error)) ? null :
|
||||
<LoadingSpinner mr={2} background="midOrange" foreground="orange"/>
|
||||
}
|
||||
<Button
|
||||
width='48px'
|
||||
children={
|
||||
<Icon
|
||||
icon={choosingSignMethod ? 'X' : 'Ellipsis'}
|
||||
color={signReady ? 'blue' : 'lighterGray'}
|
||||
/>
|
||||
}
|
||||
fontSize={1}
|
||||
fontWeight='bold'
|
||||
borderRadius='24px'
|
||||
mr={2}
|
||||
height='48px'
|
||||
onClick={() => this.toggleSignMethod(choosingSignMethod)}
|
||||
color={signReady ? 'white' : 'lighterGray'}
|
||||
backgroundColor={signReady ? 'rgba(33, 157, 255, 0.2)' : 'veryLightGray'}
|
||||
disabled={!signReady}
|
||||
border='none'
|
||||
style={{cursor: signReady ? 'pointer' : 'default'}} />
|
||||
</Row>
|
||||
{signMethod === 'masterTicket' &&
|
||||
<Row
|
||||
mt={4}
|
||||
alignItems='center'
|
||||
>
|
||||
<Icon icon='Info' color='yellow' height={4} width={4}/>
|
||||
<Text fontSize="14px" fontWeight="regular" color="gray" ml={2}>
|
||||
We recommend that you sign transactions using Bridge to protect your master ticket.
|
||||
</Text>
|
||||
</Row>
|
||||
}
|
||||
</Col>
|
||||
}
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
@ -1,59 +0,0 @@
|
||||
import React, { Component } from 'react';
|
||||
import {
|
||||
Box,
|
||||
Icon,
|
||||
StatelessTextInput as Input,
|
||||
Row,
|
||||
Center,
|
||||
Text,
|
||||
Button,
|
||||
Col,
|
||||
} from '@tlon/indigo-react';
|
||||
|
||||
import { satsToCurrency } from '../../lib/util.js';
|
||||
|
||||
export default function Sent(props) {
|
||||
const { payee, denomination, satsAmount, stopSending, currencyRates } = props;
|
||||
return (
|
||||
<Col
|
||||
height='400px'
|
||||
width='100%'
|
||||
backgroundColor='orange'
|
||||
borderRadius='48px'
|
||||
mb={5}
|
||||
p={5}
|
||||
>
|
||||
<Row
|
||||
flexDirection='row-reverse'
|
||||
>
|
||||
<Icon
|
||||
color='white'
|
||||
icon='X'
|
||||
cursor='pointer'
|
||||
onClick={stopSending}
|
||||
/>
|
||||
</Row>
|
||||
<Center>
|
||||
<Text
|
||||
style={{'display': 'block', 'overflow-wrap': 'anywhere'}}
|
||||
color='white'>{`You sent BTC to ${payee}`}</Text>
|
||||
</Center>
|
||||
<Center
|
||||
flexDirection='column'
|
||||
flex='1 1 auto'
|
||||
>
|
||||
<Text
|
||||
color='white'
|
||||
fontSize='40px'
|
||||
>
|
||||
{satsToCurrency(satsAmount, denomination, currencyRates)}
|
||||
</Text>
|
||||
<Text
|
||||
color='white'
|
||||
>
|
||||
{`${satsAmount} sats`}
|
||||
</Text>
|
||||
</Center>
|
||||
</Col>
|
||||
);
|
||||
}
|
@ -1,121 +0,0 @@
|
||||
import React, { Component } from 'react';
|
||||
import {
|
||||
Box,
|
||||
Icon,
|
||||
Row,
|
||||
Text,
|
||||
Button,
|
||||
Col,
|
||||
} from '@tlon/indigo-react';
|
||||
|
||||
export default class Settings extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.changeProvider = this.changeProvider.bind(this);
|
||||
this.replaceWallet = this.replaceWallet.bind(this);
|
||||
}
|
||||
|
||||
changeProvider(){
|
||||
this.props.api.btcWalletCommand({'set-provider': null});
|
||||
}
|
||||
|
||||
replaceWallet(){
|
||||
this.props.api.btcWalletCommand({
|
||||
'delete-wallet': this.props.state.wallet,
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
let connColor = "red";
|
||||
let connBackground = "veryLightRed";
|
||||
let conn = 'Offline'
|
||||
let host = '';
|
||||
if (this.props.state.provider){
|
||||
if (this.props.state.provider.connected) conn = 'Connected';
|
||||
if (this.props.state.provider.host) host = this.props.state.provider.host;
|
||||
if (this.props.state.provider.connected && this.props.state.provider.host) {
|
||||
connColor = "orange";
|
||||
connBackground = "lightOrange";
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<Col
|
||||
display="flex"
|
||||
width="100%"
|
||||
p={5}
|
||||
mb={5}
|
||||
borderRadius="48px"
|
||||
backgroundColor="white"
|
||||
>
|
||||
<Row mb="12px">
|
||||
<Text fontSize={1} fontWeight="bold" color="black">
|
||||
XPub Derivation
|
||||
</Text>
|
||||
</Row>
|
||||
<Row borderRadius="12px"
|
||||
backgroundColor="veryLightGray"
|
||||
py={5}
|
||||
px="36px"
|
||||
mb="12px"
|
||||
alignItems="center"
|
||||
justifyContent="space-between"
|
||||
>
|
||||
<Text mono
|
||||
fontSize={1}
|
||||
style={{wordBreak: "break-all"}}
|
||||
color="gray"
|
||||
>
|
||||
{this.props.state.wallet}
|
||||
</Text>
|
||||
</Row>
|
||||
<Row width="100%" mb={5}>
|
||||
<Button children="Replace Wallet"
|
||||
width="100%"
|
||||
fontSize={1}
|
||||
fontWeight="bold"
|
||||
backgroundColor="gray"
|
||||
color="white"
|
||||
borderColor="none"
|
||||
borderRadius="12px"
|
||||
p={4}
|
||||
onClick={this.replaceWallet}
|
||||
/>
|
||||
</Row>
|
||||
<Row mb="12px">
|
||||
<Text fontSize={1} fontWeight="bold" color="black">
|
||||
BTC Node Provider
|
||||
</Text>
|
||||
</Row>
|
||||
<Col mb="12px"
|
||||
py={5}
|
||||
px="36px"
|
||||
borderRadius="12px"
|
||||
backgroundColor={connBackground}
|
||||
alignItems="center"
|
||||
justifyContent="space-between"
|
||||
>
|
||||
<Text fontSize={1} color={connColor} mono>
|
||||
~{host}
|
||||
</Text>
|
||||
<Text fontSize={0} color={connColor}>
|
||||
{conn}
|
||||
</Text>
|
||||
</Col>
|
||||
<Row width="100%">
|
||||
<Button children="Change Provider"
|
||||
width="100%"
|
||||
fontSize={1}
|
||||
fontWeight="bold"
|
||||
backgroundColor="orange"
|
||||
color="white"
|
||||
borderColor="none"
|
||||
borderRadius="12px"
|
||||
p={4}
|
||||
onClick={this.changeProvider}
|
||||
/>
|
||||
</Row>
|
||||
</Col>
|
||||
);
|
||||
}
|
||||
}
|
@ -1,52 +0,0 @@
|
||||
import React, { Component } from 'react';
|
||||
|
||||
import {
|
||||
Box,
|
||||
Button,
|
||||
} from '@tlon/indigo-react';
|
||||
|
||||
export default function Signer(props) {
|
||||
const { signReady, initPayment, choosingSignMethod, signMethod, setSignMethod } = props;
|
||||
|
||||
return (
|
||||
choosingSignMethod ?
|
||||
<Box
|
||||
borderRadius='24px'
|
||||
backgroundColor='rgba(33, 157, 255, 0.2)'
|
||||
>
|
||||
<Button
|
||||
border='none'
|
||||
backgroundColor='transparent'
|
||||
fontWeight='bold'
|
||||
cursor='pointer'
|
||||
color={(signMethod === 'masterTicket') ? 'blue' : 'lightBlue'}
|
||||
height='48px'
|
||||
onClick={() => setSignMethod('masterTicket')}
|
||||
children='Sign with Master Ticket' />
|
||||
<Button
|
||||
border='none'
|
||||
backgroundColor='transparent'
|
||||
fontWeight='bold'
|
||||
cursor='pointer'
|
||||
color={(signMethod === 'bridge') ? 'blue' : 'lightBlue'}
|
||||
height='48px'
|
||||
onClick={() => setSignMethod('bridge')}
|
||||
children='Sign with Bridge' />
|
||||
</Box>
|
||||
:
|
||||
<Button
|
||||
primary
|
||||
children={signMethod === 'bridge' ? 'Sign with Bridge' : 'Sign with Master Ticket'}
|
||||
fontSize={1}
|
||||
fontWeight='bold'
|
||||
borderRadius='24px'
|
||||
height='48px'
|
||||
onClick={initPayment}
|
||||
color={signReady ? 'white' : 'lighterGray'}
|
||||
backgroundColor={signReady ? 'blue' : 'veryLightGray'}
|
||||
disabled={!signReady}
|
||||
border='none'
|
||||
style={{cursor: signReady ? 'pointer' : 'default'}}
|
||||
/>
|
||||
)
|
||||
}
|
@ -1,52 +0,0 @@
|
||||
import React, { Component } from 'react';
|
||||
import { Box } from '@tlon/indigo-react';
|
||||
|
||||
import WalletModal from './walletModal.js'
|
||||
import ProviderModal from './providerModal.js'
|
||||
|
||||
|
||||
export default class StartupModal extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
}
|
||||
|
||||
|
||||
render() {
|
||||
let modal = null;
|
||||
|
||||
if (this.props.state.wallet && this.props.state.provider) {
|
||||
return null;
|
||||
} else if (!this.props.state.provider){
|
||||
modal =
|
||||
<ProviderModal
|
||||
api={this.props.api}
|
||||
providerPerms={this.props.state.providerPerms}
|
||||
/>
|
||||
} else if (!this.props.state.wallet){
|
||||
modal = <WalletModal api={this.props.api} network={this.props.network}/>
|
||||
}
|
||||
return (
|
||||
<Box
|
||||
backgroundColor="scales.black20"
|
||||
left="0px"
|
||||
top="0px"
|
||||
width="100%"
|
||||
height="100%"
|
||||
position="fixed"
|
||||
display="flex"
|
||||
zIndex={10}
|
||||
justifyContent="center"
|
||||
alignItems="center"
|
||||
>
|
||||
<Box display="flex"
|
||||
flexDirection="column"
|
||||
width='400px'
|
||||
backgroundColor="white"
|
||||
borderRadius={3}
|
||||
>
|
||||
{modal}
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
}
|
@ -1,105 +0,0 @@
|
||||
import React, { Component } from 'react';
|
||||
import {
|
||||
Box,
|
||||
Icon,
|
||||
Row,
|
||||
Text,
|
||||
Button,
|
||||
Col,
|
||||
LoadingSpinner,
|
||||
} from '@tlon/indigo-react';
|
||||
import _ from 'lodash';
|
||||
|
||||
import { Sigil } from './sigil.js'
|
||||
import TxAction from './tx-action.js'
|
||||
import TxCounterparty from './tx-counterparty.js'
|
||||
import { satsToCurrency } from '../../lib/util.js'
|
||||
|
||||
export default class Transaction extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
}
|
||||
|
||||
render() {
|
||||
const pending = (!this.props.tx.recvd);
|
||||
|
||||
let weSent = _.find(this.props.tx.inputs, (input) => {
|
||||
return (input.ship === window.ship);
|
||||
});
|
||||
let weRecv = this.props.tx.outputs.every((output) => {
|
||||
return (output.ship === window.ship)
|
||||
});
|
||||
|
||||
let action =
|
||||
(weRecv) ? "recv" :
|
||||
(weSent) ? "sent" : "recv";
|
||||
|
||||
let counterShip = null;
|
||||
let counterAddress = null;
|
||||
let value;
|
||||
let sign;
|
||||
|
||||
if (action === "sent") {
|
||||
let counter = _.find(this.props.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(this.props.tx.outputs, (sum, output) => {
|
||||
if (output.ship === window.ship) {
|
||||
return sum + output.val.value;
|
||||
} else {
|
||||
return sum;
|
||||
}
|
||||
}, 0);
|
||||
|
||||
|
||||
if (weSent && weRecv) {
|
||||
counterAddress = _.get(_.find(this.props.tx.inputs, (input) => {
|
||||
return (input.ship === window.ship);
|
||||
}), 'val.address', null);
|
||||
} else {
|
||||
let counter = _.find(this.props.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, this.props.denom, this.props.rates);
|
||||
|
||||
const failure = Boolean(this.props.tx.failure);
|
||||
if (failure) action = "fail";
|
||||
|
||||
const txid = this.props.tx.txid.dat.slice(2).replaceAll('.','');
|
||||
|
||||
|
||||
return (
|
||||
<Col
|
||||
width='100%'
|
||||
backgroundColor="white"
|
||||
justifyContent="space-between"
|
||||
mb="16px"
|
||||
>
|
||||
<Row justifyContent="space-between" alignItems="center">
|
||||
<TxAction action={action} pending={pending} txid={txid} network={this.props.network}/>
|
||||
<Text fontSize="14px" alignItems="center" color="gray">
|
||||
{sign}{value} sats
|
||||
</Text>
|
||||
</Row>
|
||||
<Box ml="11px" borderLeft="2px solid black" height="4px">
|
||||
</Box>
|
||||
<Row justifyContent="space-between" alignItems="center">
|
||||
<TxCounterparty address={counterAddress} ship={counterShip}/>
|
||||
<Text fontSize="14px">{currencyValue}</Text>
|
||||
</Row>
|
||||
</Col>
|
||||
);
|
||||
}
|
||||
}
|
@ -1,62 +0,0 @@
|
||||
import React, { Component } from 'react';
|
||||
import {
|
||||
Box,
|
||||
Icon,
|
||||
Row,
|
||||
Text,
|
||||
Button,
|
||||
Col,
|
||||
} from '@tlon/indigo-react';
|
||||
|
||||
import Transaction from './transaction.js';
|
||||
|
||||
|
||||
export default class Transactions extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
}
|
||||
|
||||
|
||||
render() {
|
||||
if (!this.props.state.history || this.props.state.history.length <= 0) {
|
||||
return (
|
||||
<Box alignItems="center"
|
||||
display="flex"
|
||||
justifyContent="center"
|
||||
height="340px"
|
||||
width="100%"
|
||||
p={5}
|
||||
mb={5}
|
||||
borderRadius="48px"
|
||||
backgroundColor="white"
|
||||
>
|
||||
<Text color="gray" fontSize={2} fontWeight="bold">No Transactions Yet</Text>
|
||||
</Box>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<Col
|
||||
width='100%'
|
||||
backgroundColor="white"
|
||||
borderRadius="48px"
|
||||
mb={5}
|
||||
p={5}
|
||||
>
|
||||
{
|
||||
this.props.state.history.map((tx, i) => {
|
||||
return(
|
||||
<Transaction
|
||||
tx={tx}
|
||||
key={i}
|
||||
denom={this.props.state.denomination}
|
||||
rates={this.props.state.currencyRates}
|
||||
network={this.props.network}
|
||||
/>
|
||||
);
|
||||
})
|
||||
}
|
||||
</Col>
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user