@@ -1,16 +1,16 @@
1−import { Just, Nothing } from 'folktale/maybe';
1+import Maybe from 'folktale/maybe';
2import React, { useCallback, useState, useEffect } from 'react';
2import React, { useCallback, useState, useEffect } from 'react';
3import * as azimuth from 'azimuth-js';
3import * as azimuth from 'azimuth-js';
4import * as ob from 'urbit-ob';
4import * as ob from 'urbit-ob';
5+import * as kg from 'urbit-key-generation/dist/index';
5−import { Input, AccessoryIcon } from 'indigo-react';
6+import { Input, Grid, CheckboxInput } from 'indigo-react';
7−import View from 'components/View';
9 usePointInput,
9 usePointInput,
10 useTicketInput,
10 useTicketInput,
11 usePassphraseInput,
11 usePassphraseInput,
12} from 'components/Inputs';
13} from 'components/Inputs';
13−import { ForwardButton } from 'components/Buttons';
15import { useNetwork } from 'store/network';
15import { useNetwork } from 'store/network';
16import { useWallet } from 'store/wallet';
16import { useWallet } from 'store/wallet';
@@ -18,123 +18,189 @@ import { usePointCursor } from 'store/pointCursor';
19import * as need from 'lib/need';
19import * as need from 'lib/need';
20import { WALLET_TYPES, urbitWalletFromTicket } from 'lib/wallet';
20import { WALLET_TYPES, urbitWalletFromTicket } from 'lib/wallet';
21−import useWalletType from 'lib/useWalletType';
21+import useImpliedPoint from 'lib/useImpliedPoint';
22−import useResetPointCursor from 'lib/useResetPointCursor';
22+import useLoginView from 'lib/useLoginView';
24−//TODO should be part of InputWithStatus component
25−const INPUT_STATUS = {
26− SPIN: Symbol('SPIN'),
27− GOOD: Symbol('GOOD'),
28− FAIL: Symbol('FAIL'),
31−export default function Ticket({ advanced, loginCompleted }) {
24+export default function Ticket({ className }) {
32− useResetPointCursor();
33− useWalletType(WALLET_TYPES.TICKET);
25+ useLoginView(WALLET_TYPES.TICKET);
34 // globals
26 // globals
35 const { contracts } = useNetwork();
27 const { contracts } = useNetwork();
36− const { wallet, setUrbitWallet } = useWallet();
28+ const { setUrbitWallet } = useWallet();
37 const { setPointCursor } = usePointCursor();
29 const { setPointCursor } = usePointCursor();
30+ const impliedPoint = useImpliedPoint();
40 const pointInput = usePointInput({
35 const pointInput = usePointInput({
41 name: 'point',
36 name: 'point',
42− //TODO deduce point name from URL if we can, prefill input if we found it
37+ initialValue: impliedPoint,
43 autoFocus: true,
38 autoFocus: true,
45 const pointName = pointInput.data;
40 const pointName = pointInput.data;
43+ const passphraseInput = usePassphraseInput({
45+ label: '(Optional) Wallet Passphrase',
47+ const passphrase = passphraseInput.data;
49+ const hasPassphraseInput = useCheckboxInput({
50+ name: 'has-passphrase',
56+ const [error, setError] = useState();
57+ const [deriving, setDeriving] = useState(false);
47 const ticketInput = useTicketInput({
58 const ticketInput = useTicketInput({
48 name: 'ticket',
59 name: 'ticket',
49 label: 'Master Ticket',
60 label: 'Master Ticket',
51 const ticket = ticketInput.data;
64 const ticket = ticketInput.data;
53− const passphraseInput = usePassphraseInput({
66+ // shards
54− name: 'passphrase',
67+ const shardsInput = useCheckboxInput({
55− label: '(Optional) Wallet Passphrase',
68+ name: 'shards',
57− const passphrase = passphraseInput.data;
59− // display state
73+ const shard1Input = useTicketInput({
60− const [ticketStatus, setTicketStatus] = useState(Nothing());
74+ name: 'shard1',
77+ const shard1 = shard1Input.data;
78+ const shard2Input = useTicketInput({
82+ const shard2 = shard2Input.data;
83+ const shard3Input = useTicketInput({
87+ const shard3 = shard3Input.data;
89+ const isUsingShards = !!shardsInput.data;
90+ const shards = [shard1, shard2, shard3];
91+ const shardsReady = shards.filter(x => x !== '').length > 1;
62− //TODO maybe want to do this only on-go, because wallet derivation is slow...
93+ // TODO: maybe want to do this only on-go, because wallet derivation is slow...
63− const verifyTicket = useCallback(
94+ const deriveWalletFromTicket = useCallback(async () => {
64− async (pointName, ticket, passphrase) => {
95+ // clear states
65− setUrbitWallet(Nothing());
98+ setUrbitWallet(Maybe.Nothing());
66− if (!ob.isValidPatq(ticket) || !ob.isValidPatp(pointName)) {
100+ if (!ob.isValidPatq(ticket) || !ob.isValidPatp(pointName)) {
67− setTicketStatus(Nothing());
101+ setDeriving(false);
70− setTicketStatus(Just(INPUT_STATUS.SPIN));
104+
71− const _contracts = need.contracts(contracts);
105+ const _contracts = need.contracts(contracts);
72− const pointNumber = ob.patp2dec(pointName);
106+ const pointNumber = ob.patp2dec(pointName);
73− const uhdw = await urbitWalletFromTicket(ticket, pointName, passphrase);
107+ const urbitWallet = await urbitWalletFromTicket(
112+ const [isOwner, isTransferProxy] = await Promise.all([
74− const isOwner = azimuth.azimuth.isOwner(
113+ azimuth.azimuth.isOwner(
75 _contracts,
114 _contracts,
76 pointNumber,
115 pointNumber,
77− uhdw.ownership.keys.address
116+ urbitWallet.ownership.keys.address
79− const isTransferProxy = azimuth.azimuth.isTransferProxy(
118+ azimuth.azimuth.isTransferProxy(
80 _contracts,
119 _contracts,
81 pointNumber,
120 pointNumber,
82− uhdw.ownership.keys.address
121+ urbitWallet.ownership.keys.address
125+ if (!isOwner && !isTransferProxy) {
127+ 'This ticket is not the owner of or transfer proxy for this point.'
84− setUrbitWallet(Just(uhdw));
129+ setDeriving(false);
85− const newStatus =
130+ return;
86− (await isOwner) || (await isTransferProxy)
131+ }
87− ? INPUT_STATUS.GOOD
132+
88− : INPUT_STATUS.FAIL;
133+ setUrbitWallet(Maybe.Just(urbitWallet));
89− setTicketStatus(Just(newStatus));
134+ setPointCursor(Maybe.Just(ob.patp2dec(pointName)));
146+ const deriveWalletFromShards = useCallback(async () => {
151+ const s1 = shard1 || undefined;
152+ const s2 = shard2 || undefined;
153+ const s3 = shard3 || undefined;
156+ const ticket = kg.combine([s1, s2, s3]);
157+ const uhdw = await urbitWalletFromTicket(ticket, pointName, passphrase);
91− [contracts, setUrbitWallet, setTicketStatus]
158+ setUrbitWallet(Maybe.Just(uhdw));
174+ if (isUsingShards && shardsReady) {
175+ deriveWalletFromShards();
177+ }, [isUsingShards, shardsReady, deriveWalletFromShards]);
179+ // verify ticket if not using shards
94 useEffect(() => {
180 useEffect(() => {
95− verifyTicket(pointName, ticket, passphrase);
181+ if (!isUsingShards) {
96− }, [verifyTicket, pointName, ticket, passphrase]);
182+ deriveWalletFromTicket();
98− const canContinue = () => {
184+ }, [isUsingShards, deriveWalletFromTicket]);
99− // this is our only requirement, since we still want people with
100− // non-standard wallet setups to be able to log in
101− return Just.hasInstance(wallet);
104− const doContinue = () => {
105− setPointCursor(Just(ob.patp2dec(pointName)));
109− //TODO integrate into TicketInput?
110− const displayTicketStatus = ticketStatus.matchWith({
111− Nothing: () => <span />,
113− switch (status.value) {
114− case INPUT_STATUS.SPIN:
115− return <AccessoryIcon.Pending />;
116− case INPUT_STATUS.GOOD:
117− return <AccessoryIcon.Success />;
118− case INPUT_STATUS.FAIL:
119− return <AccessoryIcon.Failure />;
127− <View>
187+ <Grid className={className}>
128− <Input {...pointInput} />
188+ <Grid.Item full as={Input} {...pointInput} />
129− <Input {...ticketInput} accessory={displayTicketStatus} />
189+
190+ {!isUsingShards && <Grid.Item full as={Input} {...ticketInput} />}
193+ <Grid.Item full as={Input} {...shard1Input} />
194+ <Grid.Item full as={Input} {...shard2Input} />
195+ <Grid.Item full as={Input} {...shard3Input} />
198+ {hasPassphraseInput.data && (
130− {advanced && <Input {...passphraseInput} />}
199+ <Grid.Item full as={Input} {...passphraseInput} />
133− className={'mt3'}
202+ <Grid.Item full as={CheckboxInput} {...hasPassphraseInput} />
134− disabled={!canContinue()}
203+ <Grid.Item full as={CheckboxInput} {...shardsInput} />
135− onClick={doContinue}>
204+ </Grid>