mirror of
https://github.com/ilyakooo0/urbit.git
synced 2025-01-01 19:46:36 +03:00
Merge branch 'release/next-vere' into jb/next-gen-term
* release/next-vere: (67 commits) pill: all kh: improve code style glob: update to 0v758lj.uf0s5.0nh3m.gunn6.942gj Fix feepicker issues Add exit buttons to invoices Fix issue with change provider button not triggering modal Fix scanning text issues Fix enum, was breaking signer button Fix imports in ExternalInvoice glob: update to 0v4.e52ik.udm4j.6aus5.02b25.vomaj btc-wallet: fix imports aqua: assert pill type Port BTC wallet to Typescript Match edouard's designs Add external (psbt) invoice Fix copy from non-secure context issue Use deSig for isPatp Use deSig rather than concat Add sig to valid patp in send component Just show total main/change addresses scanned ...
This commit is contained in:
commit
148779f4d4
@ -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)
|
||||
|
@ -26,6 +26,6 @@
|
||||
<div id="portal-root"></div>
|
||||
<script src="/~landscape/js/channel.js"></script>
|
||||
<script src="/~landscape/js/session.js"></script>
|
||||
<script src="/~btc/js/bundle/index.3e8bcc150ebd820dd3b2.js"></script>
|
||||
<script src="/~btc/js/bundle/index.050889bac51cbd935dd9.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
@ -5,8 +5,8 @@
|
||||
/- glob, *resource
|
||||
/+ default-agent, verb, dbug
|
||||
|%
|
||||
++ landscape-hash 0v4.qs4qf.3bam7.736sg.0poq8.c6vhq
|
||||
++ btc-wallet-hash 0v7.v4dng.o33qi.kc497.5jc02.ke5es
|
||||
++ landscape-hash 0v3.sdoer.mnnfi.opjrg.npmcj.utr8l
|
||||
++ btc-wallet-hash 0v758lj.uf0s5.0nh3m.gunn6.942gj
|
||||
+$ state-0 [%0 hash=@uv glob=(unit (each glob:glob tid=@ta))]
|
||||
+$ state-1 [%1 =globs:glob]
|
||||
+$ all-states
|
||||
|
@ -24,6 +24,6 @@
|
||||
<div id="portal-root"></div>
|
||||
<script src="/~landscape/js/channel.js"></script>
|
||||
<script src="/~landscape/js/session.js"></script>
|
||||
<script src="/~landscape/js/bundle/index.c1a890b306daa1aee34b.js"></script>
|
||||
<script src="/~landscape/js/bundle/index.60c063d34a42a08ab440.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
@ -5,10 +5,10 @@
|
||||
:- %aqua-events
|
||||
%+ turn
|
||||
^- (list unix-event)
|
||||
:~ [//term/1 %belt %mod %ctl `@c`%e]
|
||||
[//term/1 %belt %mod %ctl `@c`%u]
|
||||
[//term/1 %belt %txt ((list @c) command)]
|
||||
[//term/1 %belt %ret ~]
|
||||
:~ [/d/term/1 %belt %mod %ctl `@c`%e]
|
||||
[/d/term/1 %belt %mod %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)]
|
@ -15,7 +15,7 @@
|
||||
arg=$@(~ [top=path ~])
|
||||
dub=_|
|
||||
==
|
||||
:- %pill
|
||||
:- %boot-pill
|
||||
^- pill:pill
|
||||
:: sys: root path to boot system, `/~me/[desk]/now/sys`
|
||||
::
|
||||
|
@ -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)]~]]
|
||||
::
|
||||
--
|
@ -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 %mod %ctl `@c`%e]
|
||||
[//term/1 %belt %mod %ctl `@c`%u]
|
||||
[//term/1 %belt %txt ((list @c) what)]
|
||||
[//term/1 %belt %ret ~]
|
||||
[/d/term/1 %belt %mod %ctl `@c`%e]
|
||||
[/d/term/1 %belt %mod %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 %mod %ctl (,@c what)]
|
||||
:~ [/d/term/1 %belt %mod %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
|
||||
|
@ -5,12 +5,13 @@
|
||||
::
|
||||
+$ pill
|
||||
$% [%ivory p=(list)]
|
||||
$: %pill
|
||||
nam=term
|
||||
boot-ova=(list)
|
||||
kernel-ova=(list unix-event)
|
||||
userspace-ova=(list unix-event)
|
||||
== ==
|
||||
$: %pill
|
||||
nam=term
|
||||
boot-ova=(list)
|
||||
kernel-ova=(list unix-event)
|
||||
userspace-ova=(list unix-event)
|
||||
==
|
||||
==
|
||||
::
|
||||
+$ unix-event
|
||||
%+ pair wire
|
||||
@ -18,6 +19,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
|
||||
|
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
|
||||
--
|
@ -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]
|
||||
|
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)
|
||||
|
@ -17,6 +17,6 @@
|
||||
:: ;< ~ bind:m (send-hi ~web ~zod)
|
||||
:: ;< ~ bind:m (send-hi ~web ~bus)
|
||||
(pure:m *vase)
|
||||
;< ~ bind:m (breach-aqua i.who)
|
||||
;< ~ bind:m (init-ship i.who)
|
||||
;< ~ bind:m (breach i.who)
|
||||
;< ~ bind:m (init-ship i.who |)
|
||||
loop(who t.who)
|
||||
|
0
pkg/arvo/ted/ph/migrate/end.hoon
Normal file
0
pkg/arvo/ted/ph/migrate/end.hoon
Normal file
@ -4,15 +4,14 @@
|
||||
^- thread:spider
|
||||
|= vase
|
||||
=/ m (strand ,vase)
|
||||
;< ~ bind:m start-simple
|
||||
;< ~ bind:m init-azimuth
|
||||
;< ~ bind:m (spawn-aqua ~zod)
|
||||
;< ~ bind:m (spawn-aqua ~bus)
|
||||
;< ~ bind:m (spawn-aqua ~web)
|
||||
;< ~ bind:m start-azimuth
|
||||
;< ~ bind:m (spawn ~zod)
|
||||
;< ~ bind:m (spawn ~bus)
|
||||
;< ~ bind:m (spawn ~web)
|
||||
::
|
||||
;< ~ bind:m (init-ship ~zod)
|
||||
;< ~ bind:m (init-ship ~bus)
|
||||
;< ~ bind:m (init-ship ~web)
|
||||
;< ~ bind:m (init-ship ~zod |)
|
||||
;< ~ bind:m (init-ship ~bus |)
|
||||
;< ~ bind:m (init-ship ~web |)
|
||||
::
|
||||
;< ~ bind:m (send-hi ~zod ~web)
|
||||
;< ~ bind:m (send-hi ~zod ~bus)
|
||||
|
@ -32,7 +32,7 @@
|
||||
[~zod %graph-1]
|
||||
'graph 1'
|
||||
'desc 1'
|
||||
~
|
||||
`%graph-validator-chat
|
||||
[%group group-rid]
|
||||
'fake'
|
||||
==
|
||||
@ -42,7 +42,7 @@
|
||||
[~bus %graph-2]
|
||||
'graph 2'
|
||||
'desc 2'
|
||||
~
|
||||
`%graph-validator-chat
|
||||
[%group group-rid]
|
||||
'fake'
|
||||
==
|
||||
@ -52,11 +52,19 @@
|
||||
[~web %graph-3]
|
||||
'graph 3'
|
||||
'desc 3'
|
||||
~
|
||||
`%graph-validator-chat
|
||||
[%policy %invite (sy ~zod ~bus ~)]
|
||||
'fake'
|
||||
==
|
||||
::
|
||||
;< ~ bind:m (poke-app ~zod %group-store %group-update-0 [%add-tag group-rid %admin (sy ~bus ~)])
|
||||
;< ~ 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)
|
||||
|
||||
;< ~ bind:m (dojo-thread ~zod %graph-create %graph-view-action create-1)
|
||||
;< ~ bind:m (dojo-thread ~bus %graph-create %graph-view-action create-2)
|
||||
;< ~ bind:m (dojo-thread ~web %graph-create %graph-view-action create-3)
|
||||
@ -71,11 +79,12 @@
|
||||
=/ join-3=action:graph-view
|
||||
[%join [~web %graph-3] ~web]
|
||||
::
|
||||
;< ~ bind:m (dojo-thread ~zod %graph-join %graph-view-action join-2)
|
||||
;< ~ bind:m (dojo-thread ~zod %graph-join %graph-view-action join-3)
|
||||
;< ~ bind:m (dojo-thread ~bus %graph-join %graph-view-action join-1)
|
||||
;< ~ bind:m (dojo-thread ~bus %graph-join %graph-view-action join-3)
|
||||
;< ~ bind:m (sleep ~s10)
|
||||
;< ~ bind:m (poke-app ~zod %group-view %group-view-action join-3)
|
||||
;< ~ bind:m (poke-app ~bus %group-view %group-view-action join-3)
|
||||
;< ~ bind:m (dojo-thread ~web %graph-join %graph-view-action join-1)
|
||||
;< ~ bind:m (dojo-thread ~bus %graph-join %graph-view-action join-1)
|
||||
;< ~ bind:m (dojo-thread ~zod %graph-join %graph-view-action join-2)
|
||||
;< ~ bind:m (dojo-thread ~web %graph-join %graph-view-action join-2)
|
||||
;< ~ bind:m (sleep ~s30)
|
||||
::
|
||||
|
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)
|
||||
|
||||
|
||||
|
@ -31,20 +31,24 @@
|
||||
;< ~ bind:m (start-agent ship %group-store)
|
||||
;< ~ bind:m (start-agent ship %group-pull-hook)
|
||||
;< ~ bind:m (start-agent ship %group-push-hook)
|
||||
;< ~ bind:m (start-agent ship %group-view)
|
||||
;< ~ bind:m (start-agent ship %dm-hook)
|
||||
::
|
||||
;< ~ bind:m (start-agent ship %hark-store)
|
||||
;< ~ bind:m (start-agent ship %hark-graph-hook)
|
||||
;< ~ bind:m (start-agent ship %hark-group-hook)
|
||||
::
|
||||
;< ~ bind:m (start-agent ship %metadata-store)
|
||||
;< ~ bind:m (start-agent ship %metadata-hook)
|
||||
;< ~ bind:m (start-agent ship %metadata-pull-hook)
|
||||
;< ~ bind:m (start-agent ship %metadata-push-hook)
|
||||
::
|
||||
;< ~ bind:m (start-agent ship %invite-store)
|
||||
;< ~ bind:m (start-agent ship %invite-hook)
|
||||
::
|
||||
;< ~ bind:m (start-agent ship %chat-store)
|
||||
;< ~ bind:m (start-agent ship %chat-hook)
|
||||
;< ~ bind:m (start-agent ship %chat-view)
|
||||
::
|
||||
;< ~ bind:m (start-agent ship %contact-store)
|
||||
;< ~ bind:m (start-agent ship %contact-hook)
|
||||
;< ~ bind:m (start-agent ship %contact-view)
|
||||
;< ~ bind:m (start-agent ship %contact-push-hook)
|
||||
;< ~ bind:m (start-agent ship %contact-pull-hook)
|
||||
::
|
||||
;< ~ bind:m (start-agent ship %graph-store)
|
||||
;< ~ bind:m (start-agent ship %graph-push-hook)
|
||||
|
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)
|
||||
|
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 {
|
||||
@ -35,9 +36,12 @@ let devServer = {
|
||||
historyApiFallback: true,
|
||||
};
|
||||
|
||||
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: '',
|
||||
@ -45,17 +49,17 @@ if(urbitrc.URL) {
|
||||
'/~btc/js/bundle/index.*.js': {
|
||||
target: 'http://localhost:9000',
|
||||
pathRewrite: (req, path) => {
|
||||
return '/index.js'
|
||||
}
|
||||
return '/index.js';
|
||||
},
|
||||
},
|
||||
'**': {
|
||||
changeOrigin: true,
|
||||
target: urbitrc.URL,
|
||||
router,
|
||||
// ensure proxy doesn't timeout channels
|
||||
proxyTimeout: 0
|
||||
}
|
||||
}
|
||||
proxyTimeout: 0,
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@ -63,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,
|
||||
@ -96,33 +86,31 @@ 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,
|
||||
plugins: [
|
||||
new UrbitShipPlugin(urbitrc)
|
||||
],
|
||||
plugins: [new UrbitShipPlugin(urbitrc)],
|
||||
watch: true,
|
||||
watchOptions: {
|
||||
poll: true,
|
||||
ignored: '/node_modules/'
|
||||
ignored: '/node_modules/',
|
||||
},
|
||||
output: {
|
||||
filename: 'index.js',
|
||||
chunkFilename: 'index.js',
|
||||
path: path.resolve(__dirname, '../dist'),
|
||||
publicPath: '/',
|
||||
globalObject: 'this'
|
||||
globalObject: 'this',
|
||||
},
|
||||
optimization: {
|
||||
minimize: false,
|
||||
usedExports: true
|
||||
}
|
||||
usedExports: true,
|
||||
},
|
||||
};
|
||||
|
@ -6,55 +6,46 @@ 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()
|
||||
],
|
||||
plugins: [new CleanWebpackPlugin()],
|
||||
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, `../../arvo/app/btc-wallet/js/bundle`),
|
||||
publicPath: '/',
|
||||
},
|
||||
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>
|
||||
<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="/~btc/settings">
|
||||
<Col display="flex" flexDirection="column" width={cardWidth}>
|
||||
<Header settings={true} />
|
||||
<Settings />
|
||||
</Col>
|
||||
</Route>
|
||||
<Route path="/~btc">
|
||||
<Col display="flex" flexDirection="column" width={cardWidth}>
|
||||
<Header settings={false} />
|
||||
{!warning ? null : <Warning />}
|
||||
<Balance />
|
||||
<Transactions />
|
||||
</Col>
|
||||
</Route>
|
||||
</Switch>
|
||||
);
|
||||
};
|
||||
|
||||
export default Body;
|
38
pkg/btc-wallet/src/components/CurrencyPicker.tsx
Normal file
38
pkg/btc-wallet/src/components/CurrencyPicker.tsx
Normal file
@ -0,0 +1,38 @@
|
||||
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': {
|
||||
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 ? '/~btc' : '/~btc/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 as any).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 as any).ship;
|
||||
});
|
||||
let weRecv = tx.outputs.every((output) => {
|
||||
return output.ship === (window as any).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 as any).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 as any).ship) {
|
||||
return sum + output.val.value;
|
||||
} else {
|
||||
return sum;
|
||||
}
|
||||
},
|
||||
0
|
||||
);
|
||||
|
||||
if (weSent && weRecv) {
|
||||
counterAddress = _.get(
|
||||
_.find(tx.inputs, (input) => {
|
||||
return input.ship === (window as any).ship;
|
||||
}),
|
||||
'val.address',
|
||||
null
|
||||
);
|
||||
} else {
|
||||
let counter = _.find(tx.inputs, (input) => {
|
||||
return input.ship !== (window as any).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 as any).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;
|
72
pkg/btc-wallet/src/components/Warning.tsx
Normal file
72
pkg/btc-wallet/src/components/Warning.tsx
Normal file
@ -0,0 +1,72 @@
|
||||
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,
|
||||
'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: false,
|
||||
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: true,
|
||||
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/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 { subscription } from "./js/subscription.js";
|
||||
|
||||
import './css/indigo-static.css';
|
||||
import './css/fonts.css';
|
||||
import './css/custom.css';
|
||||
|
||||
// rebuild x3
|
||||
|
||||
const channel = new window.channel();
|
||||
api.setChannel(window.ship, channel);
|
||||
|
||||
|
||||
if (module.hot) {
|
||||
module.hot.accept()
|
||||
}
|
||||
|
||||
ReactDOM.render((
|
||||
<Root channel={channel}/>
|
||||
), document.querySelectorAll("#root")[0]);
|
23
pkg/btc-wallet/src/index.tsx
Normal file
23
pkg/btc-wallet/src/index.tsx
Normal file
@ -0,0 +1,23 @@
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import { api } from './lib/api';
|
||||
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 (window as any).channel();
|
||||
api.setChannel((window as any).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="/~btc/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="/~btc">
|
||||
<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,50 +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": {
|
||||
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 ? "/~btc" : "/~btc/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'}}
|
||||
/>
|
||||
)
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user