@@ -1,28 +1,37 @@
1+import React, { useCallback, useMemo, useRef } from 'react';
1import { Just, Nothing } from 'folktale/maybe';
2import { Just, Nothing } from 'folktale/maybe';
2import cn from 'classnames';
3import cn from 'classnames';
3−import React, { useCallback, useState, useEffect } from 'react';
4import * as azimuth from 'azimuth-js';
4import * as azimuth from 'azimuth-js';
5−import * as ob from 'urbit-ob';
6import * as kg from 'urbit-key-generation/dist/index';
5import * as kg from 'urbit-key-generation/dist/index';
7−import { Input, Grid, CheckboxInput } from 'indigo-react';
6+import { Grid, CheckboxInput } from 'indigo-react';
7+import { FORM_ERROR } from 'final-form';
9import { useNetwork } from 'store/network';
9import { useNetwork } from 'store/network';
10import { useWallet } from 'store/wallet';
10import { useWallet } from 'store/wallet';
11import { usePointCursor } from 'store/pointCursor';
11import { usePointCursor } from 'store/pointCursor';
18−} from 'lib/useInputs';
19import * as need from 'lib/need';
13import * as need from 'lib/need';
20import { WALLET_TYPES, urbitWalletFromTicket } from 'lib/wallet';
14import { WALLET_TYPES, urbitWalletFromTicket } from 'lib/wallet';
21import useImpliedPoint from 'lib/useImpliedPoint';
15import useImpliedPoint from 'lib/useImpliedPoint';
22import useLoginView from 'lib/useLoginView';
16import useLoginView from 'lib/useLoginView';
23import patp2dec from 'lib/patp2dec';
17import patp2dec from 'lib/patp2dec';
24−
18+import BridgeForm from 'form/BridgeForm';
19+import Condition from 'form/Condition';
25+ buildCheckboxValidator,
26+ buildTicketValidator,
27+ buildPassphraseValidator,
30+import timeout from 'lib/timeout';
31+import FormError from 'form/FormError';
32+import ContinueButton from './ContinueButton';
25−export default function Ticket({ className }) {
34+export default function Ticket({ className, goHome }) {
26 useLoginView(WALLET_TYPES.TICKET);
35 useLoginView(WALLET_TYPES.TICKET);
28 const { contracts } = useNetwork();
37 const { contracts } = useNetwork();
@@ -30,158 +39,171 @@ export default function Ticket({ className }) {
30 const { setPointCursor } = usePointCursor();
39 const { setPointCursor } = usePointCursor();
31 const impliedPoint = useImpliedPoint();
40 const impliedPoint = useImpliedPoint();
33− // point
42+ const cachedUrbitWallet = useRef(Nothing());
34− const [pointInput, { data: pointName }] = usePointInput({
43+
35− name: 'point',
44+ const validateForm = useCallback(
36− initialValue: impliedPoint || '',
45+ async (values, errors) => {
37− autoFocus: true,
46+ if (errors.point) {
38− });
47+ return errors;
41− const [passphraseInput, { data: passphrase }] = usePassphraseInput({
50+ if (values.useShards) {
42− name: 'passphrase',
51+ if (errors.shard1 || errors.shard2 || errors.shard3) {
43− label: 'Wallet Passphrase',
52+ return errors;
46− const [hasPassphraseInput, { data: hasPassphrase }] = useCheckboxInput({
55+ // shards
47− name: 'has-passphrase',
56+ try {
48− label: 'Passphrase',
57+ const ticket = kg.combine([
49− initialValue: false,
58+ values.shard1,
50− });
59+ values.shard2,
53− const [error, setError] = useState();
62+ const point = patp2dec(values.point);
54− const [deriving, setDeriving] = useState(false);
63+ cachedUrbitWallet.current = await urbitWalletFromTicket(
55− const [ticketInput, { data: ticket, pass: validTicket }] = useTicketInput({
57− label: 'Master Ticket',
63− const [shardsInput, { data: isUsingShards }] = useCheckboxInput({
69− const [shard1Input, { data: shard1, pass: shard1Pass }] = useTicketInput({
74− const [shard2Input, { data: shard2, pass: shard2Pass }] = useTicketInput({
79− const [shard3Input, { data: shard3, pass: shard3Pass }] = useTicketInput({
84− const shardsReady = shard1Pass && shard2Pass && shard3Pass;
86− // TODO: maybe want to do this only on-go, because wallet derivation is slow...
87− const deriveWalletFromTicket = useCallback(async () => {
91− setUrbitWallet(Nothing());
94− !ticket ||
64+ ticket,
95− !pointName ||
65+ point,
96− !ob.isValidPatq(ticket) ||
66+ values.passphrase
97− !ob.isValidPatp(pointName)
67+ );
98− ) {
68+ } catch (error) {
99− setDeriving(false);
69+ console.error(error);
70+ return { [FORM_ERROR]: 'Unable to derive wallet from shards.' };
100− return;
74+ return errors;
77+ await timeout(100); // allow ui events to flush
103− const _contracts = need.contracts(contracts);
80+ const _contracts = need.contracts(contracts);
104− const point = patp2dec(pointName);
81+ const point = patp2dec(values.point);
83+ console.log('computing...');
105− const urbitWallet = await urbitWalletFromTicket(ticket, point, passphrase);
84+ const urbitWallet = await urbitWalletFromTicket(
90+ cachedUrbitWallet.current = urbitWallet;
106− const [isOwner, isTransferProxy] = await Promise.all([
92+ const [isOwner, isTransferProxy] = await Promise.all([
107− azimuth.azimuth.isOwner(
93+ azimuth.azimuth.isOwner(
108− _contracts,
94+ _contracts,
110− urbitWallet.ownership.keys.address
96+ urbitWallet.ownership.keys.address
112− azimuth.azimuth.isTransferProxy(
98+ azimuth.azimuth.isTransferProxy(
113− _contracts,
99+ _contracts,
115− urbitWallet.ownership.keys.address
101+ urbitWallet.ownership.keys.address
105+ if (!isOwner && !isTransferProxy) {
106+ // notify the user, but allow login regardless
108+ // 'This ticket is not the owner of or transfer proxy for this point.'
111+ console.error(error);
112+ return { ticket: 'Unable to derive wallet from ticket.' };
119+ const validate = useMemo(
123+ usePassphrase: buildCheckboxValidator(),
124+ useShards: buildCheckboxValidator(),
125+ point: buildPointValidator(4),
126+ ticket: buildTicketValidator(),
127+ shard1: buildTicketValidator(),
128+ shard2: buildTicketValidator(),
129+ shard3: buildTicketValidator(),
130+ passphrase: buildPassphraseValidator(),
117− ]);
134+ [validateForm]
119− if (!isOwner && !isTransferProxy) {
120− // notify the user, but allow login regardless
122− 'This ticket is not the owner of or transfer proxy for this point.'
126− setUrbitWallet(Just(urbitWallet));
127− setPointCursor(Just(point));
139− const deriveWalletFromShards = useCallback(async () => {
137+ const onValues = useCallback(
140− const s1 = shard1 || undefined;
138+ ({ valid, values }) => {
141− const s2 = shard2 || undefined;
139+ if (valid) {
142− const s3 = shard3 || undefined;
140+ setUrbitWallet(Just(cachedUrbitWallet.current));
143−
141+ setPointCursor(Just(patp2dec(values.point)));
145− const ticket = kg.combine([s1, s2, s3]);
143+ setUrbitWallet(Nothing());
146− const point = patp2dec(pointName);
147− const uhdw = await urbitWalletFromTicket(ticket, point, passphrase);
148− setUrbitWallet(Just(uhdw));
152− }, [passphrase, pointName, setUrbitWallet, shard1, shard2, shard3]);
154− // derive wallet on change
156− if (isUsingShards && shardsReady) {
157− deriveWalletFromShards();
158− } else if (validTicket) {
159− deriveWalletFromTicket();
162− isUsingShards,
146+ [setPointCursor, setUrbitWallet]
165− deriveWalletFromShards,
166− deriveWalletFromTicket,
170 <Grid className={cn('mt4', className)}>
150 <Grid className={cn('mt4', className)}>
171− <Grid.Item full as={Input} {...pointInput} />
151+ <BridgeForm
172−
152+ validate={validate}
173− {!isUsingShards && <Grid.Item full as={Input} {...ticketInput} />}
153+ onValues={onValues}
174− {isUsingShards && (
154+ onSubmit={goHome}
156+ point: impliedPoint || '',
157+ usePasshrase: false,
160+ {({ handleSubmit }) => (
176− <Grid.Item full as={Input} {...shard1Input} />
162+ <Grid.Item full as={PointInput} name="point" />
177− <Grid.Item full as={Input} {...shard2Input} />
163+
178− <Grid.Item full as={Input} {...shard3Input} />
164+ <Condition when="useShards" is={false}>
169+ label="Master Ticket"
173+ <Condition when="useShards" is={true}>
174+ <Grid.Item full as={TicketInput} name="shard1" label="Shard 1" />
175+ <Grid.Item full as={TicketInput} name="shard2" label="Shard 2" />
176+ <Grid.Item full as={TicketInput} name="shard3" label="Shard 3" />
179+ <Condition when="usePassphrase" is={true}>
182+ as={PassphraseInput}
184+ label="Wallet Passphrase"
191+ name="usePassphrase"
201+ <Grid.Item full as={FormError} />
203+ <Grid.Item full as={ContinueButton} handleSubmit={handleSubmit} />
181− {hasPassphrase && <Grid.Item full as={Input} {...passphraseInput} />}
206+ </BridgeForm>
183− <Grid.Item full as={CheckboxInput} {...hasPassphraseInput} />
184− <Grid.Item full as={CheckboxInput} {...shardsInput} />