on master help sign in
fix: move presentation logic around reticketing into relevant components
commit0705a409affbc198b26a05a903fd532aa4680ffb authorMatt Condon (shrugs) <mattgcondon@gmail.com> dateWed 17 Jul 2019, 15:23 (7 years ago) parentb16048c
5 files changed+272−253
src/lib/invite.js−235 src/lib/reticket.js+217 src/views/Activate/PassportTransfer.js+29−8 src/views/Admin/Reticket/ReticketExecute.js+26−9 src/views/SentTransaction.js−1
src/lib/invite.js−235
@@ -1,52 +1,8 @@
1−import Tx from 'ethereumjs-tx';
2−import * as azimuth from 'azimuth-js';
31import * as wg from './walletgen';
4−import * as tank from './tank';
52
63import JSZip from 'jszip';
74import saveAs from 'file-saver';
85
9−import { hexify } from './txn';
10−import { deriveNetworkSeedFromUrbitWallet, deriveNetworkKeys } from './keys';
11−import { addHexPrefix } from './wallet';
12−import { sendAndAwaitAll } from './txn';
13−import {
14− GAS_LIMITS,
15− DEFAULT_GAS_PRICE_GWEI,
16− CRYPTO_SUITE_VERSION,
17−} from './constants';
18−import { toWei } from 'web3-utils';
19−
20−// the initial network key revision is always 1
21−const INITIAL_NETWORK_KEY_REVISION = 1;
22−
23−const TRANSACTION_STATES = {
24− GENERATING: {
25− label: 'Generating Transactions...',
26− progress: 0.0,
27− },
28− SIGNING: {
29− label: 'Signing Transactions...',
30− progress: 0.15,
31− },
32− FUNDING: {
33− label: 'Funding Transactions...',
34− progress: 0.3,
35− },
36− TRANSFERRING: {
37− label: 'Transferring Point...',
38− progress: 0.55,
39− },
40− CLEANING: {
41− label: 'Cleaning Up...',
42− progress: 0.95,
43− },
44− DONE: {
45− label: 'Done',
46− progress: 1.0,
47− },
48−};
49−
506export async function generateWallet(point) {
517 const ticket = await wg.makeTicket(point);
528 const wallet = await wg.generateWallet(point, ticket);
@@ -83,194 +39,3 @@ export async function downloadWallet(paper) {
8339 });
8440 });
8541}
86−
87−export async function reticketPointBetweenWallets({
88− fromWallet,
89− toWallet,
90− point,
91− web3,
92− contracts,
93− onUpdate,
94− transferEth = false,
95−}) {
96− const askForFunding = (address, minBalance, balance) =>
97− onUpdate({
98− type: 'askFunding',
99− value: { address, minBalance, balance },
100− });
101−
102− const gotFunding = () => onUpdate({ type: 'gotFunding' });
103− const progress = state => onUpdate({ type: 'progress', state });
104−
105− //
106− // generate transactions
107− //
108−
109− progress(TRANSACTION_STATES.GENERATING);
110−
111− // transfer to invite wallet, so it can configure
112− //NOTE doing this in the invite wallet instead of the final wallet makes
113− // failure cases "solvable" by just telling the user to fully retry.
114− // in other words, the invite flow is effectively idempotent.
115−
116− //TODO no harm done if we already owned it, but should still get a bool arg
117− // for skipping this, if it isn't too big a burden for callers
118− const transferTmpTx = azimuth.ecliptic.transferPoint(
119− contracts,
120− point,
121− fromWallet.address,
122− false
123− );
124− transferTmpTx.gas = GAS_LIMITS.TRANSFER;
125−
126− // configure networking public keys
127− const seed = await deriveNetworkSeedFromUrbitWallet(
128− toWallet,
129− INITIAL_NETWORK_KEY_REVISION
130− );
131−
132− const networkSeed = seed.matchWith({
133− Nothing: () => {
134− throw new Error('network seed not derived');
135− },
136− Just: p => p.value,
137− });
138−
139− const networkKeys = deriveNetworkKeys(networkSeed);
140−
141− const keysTx = azimuth.ecliptic.configureKeys(
142− contracts,
143− point,
144− addHexPrefix(networkKeys.crypt.public),
145− addHexPrefix(networkKeys.auth.public),
146− CRYPTO_SUITE_VERSION,
147− /* discontinuous */ false
148− );
149− keysTx.gas = GAS_LIMITS.CONFIGURE_KEYS;
150−
151− // configure management proxy
152−
153− const managementTx = azimuth.ecliptic.setManagementProxy(
154− contracts,
155− point,
156− toWallet.management.keys.address
157− );
158− managementTx.gas = GAS_LIMITS.SET_PROXY;
159−
160− // set spawn & voting proxies situationally
161−
162− let txs = [transferTmpTx, keysTx, managementTx];
163− if (
164− azimuth.azimuth.getPointSize(point) !== azimuth.azimuth.PointSize.Planet
165− ) {
166− const spawnTx = azimuth.ecliptic.setSpawnProxy(
167− contracts,
168− point,
169− toWallet.spawn.keys.address
170− );
171− spawnTx.gas = GAS_LIMITS.SET_PROXY;
172− txs.push(spawnTx);
173−
174− if (
175− azimuth.azimuth.getPointSize(point) === azimuth.azimuth.PointSize.Galaxy
176− ) {
177− const votingTx = azimuth.ecliptic.setVotingProxy(
178− contracts,
179− point,
180− toWallet.voting.keys.address
181− );
182− votingTx.gas = GAS_LIMITS.SET_PROXY;
183− txs.push(votingTx);
184− }
185− }
186−
187− // transfer configured point to user's new wallet
188−
189− const transferFinalTx = azimuth.ecliptic.transferPoint(
190− contracts,
191− point,
192− toWallet.ownership.keys.address,
193− false
194− );
195− transferFinalTx.gas = GAS_LIMITS.TRANSFER;
196− txs.push(transferFinalTx);
197−
198− //
199− // finalizing & signing transactions
200− //
201−
202− progress(TRANSACTION_STATES.SIGNING);
203−
204− let totalCost = 0;
205− let inviteNonce = await web3.eth.getTransactionCount(fromWallet.address);
206− txs = txs.map(tx => {
207− tx.from = fromWallet.address;
208− tx.nonce = inviteNonce++;
209− tx.gasPrice = parseInt(toWei(DEFAULT_GAS_PRICE_GWEI.toFixed(), 'gwei'), 10);
210− totalCost = totalCost + tx.gasPrice * tx.gas;
211− return tx;
212− });
213−
214− let txPairs = txs.map(tx => {
215− let stx = new Tx(tx);
216− stx.sign(Buffer.from(fromWallet.privateKey, 'hex'));
217− return {
218− raw: hexify(stx.serialize()),
219− signed: stx,
220− };
221− });
222−
223− //
224− // ensuring funding for transactions
225− //
226−
227− progress(TRANSACTION_STATES.FUNDING);
228−
229− const usedTank = await tank.ensureFundsFor(
230− web3,
231− point,
232− fromWallet.address,
233− totalCost,
234− txPairs.map(p => p.raw),
235− askForFunding,
236− gotFunding
237− );
238−
239− //
240− // sending and awaiting transactions
241− //
242−
243− progress(TRANSACTION_STATES.TRANSFERRING);
244−
245− await sendAndAwaitAll(web3, txPairs.map(p => p.signed), usedTank);
246−
247− //
248− // move leftover eth
249− //
250−
251− progress(TRANSACTION_STATES.CLEANING);
252−
253− // if non-trivial eth left in invite wallet, transfer to new ownership
254− let balance = await web3.eth.getBalance(fromWallet.address);
255− const gasPrice = parseInt(toWei(DEFAULT_GAS_PRICE_GWEI.toFixed(), 'gwei'));
256− const gasLimit = GAS_LIMITS.SEND_ETH;
257− const sendEthCost = gasPrice * gasLimit;
258− if (transferEth && balance > sendEthCost) {
259− const value = balance - sendEthCost;
260− const tx = {
261− to: toWallet.ownership.keys.address,
262− value: value,
263− gasPrice: gasPrice,
264− gas: gasLimit,
265− nonce: inviteNonce++,
266− };
267− let stx = new Tx(tx);
268− stx.sign(fromWallet.privateKey);
269− const rawTx = hexify(stx.serialize());
270− web3.eth.sendSignedTransaction(rawTx).catch(err => {
271− console.log('error sending value tx, who cares', err);
272− });
273− }
274−
275− progress(TRANSACTION_STATES.DONE);
276−}
src/lib/reticket.jsnew file+217
@@ -0,0 +1,217 @@
1+import Tx from 'ethereumjs-tx';
2+import * as azimuth from 'azimuth-js';
3+import * as tank from './tank';
4+
5+import { hexify } from './txn';
6+import { deriveNetworkSeedFromUrbitWallet, deriveNetworkKeys } from './keys';
7+import { addHexPrefix } from './wallet';
8+import { sendAndAwaitAll } from './txn';
9+import {
10+ GAS_LIMITS,
11+ DEFAULT_GAS_PRICE_GWEI,
12+ CRYPTO_SUITE_VERSION,
13+} from './constants';
14+import { toWei } from 'web3-utils';
15+
16+// the initial network key revision is always 1
17+const INITIAL_NETWORK_KEY_REVISION = 1;
18+
19+export const TRANSACTION_PROGRESS = {
20+ GENERATING: 0.01,
21+ SIGNING: 0.15,
22+ FUNDING: 0.3,
23+ TRANSFERRING: 0.55,
24+ CLEANING: 0.95,
25+ DONE: 1.0,
26+};
27+
28+export async function reticketPointBetweenWallets({
29+ fromWallet,
30+ toWallet,
31+ point,
32+ web3,
33+ contracts,
34+ onUpdate,
35+ transferEth = false,
36+}) {
37+ const askForFunding = (address, minBalance, balance) =>
38+ onUpdate({
39+ type: 'askFunding',
40+ value: { address, minBalance, balance },
41+ });
42+
43+ const gotFunding = () => onUpdate({ type: 'gotFunding' });
44+ const progress = state => onUpdate({ type: 'progress', state });
45+
46+ //
47+ // generate transactions
48+ //
49+
50+ progress(TRANSACTION_PROGRESS.GENERATING);
51+
52+ // transfer to invite wallet, so it can configure
53+ //NOTE doing this in the invite wallet instead of the final wallet makes
54+ // failure cases "solvable" by just telling the user to fully retry.
55+ // in other words, the invite flow is effectively idempotent.
56+
57+ //TODO no harm done if we already owned it, but should still get a bool arg
58+ // for skipping this, if it isn't too big a burden for callers
59+ const transferTmpTx = azimuth.ecliptic.transferPoint(
60+ contracts,
61+ point,
62+ fromWallet.address,
63+ false
64+ );
65+ transferTmpTx.gas = GAS_LIMITS.TRANSFER;
66+
67+ // configure networking public keys
68+ const seed = await deriveNetworkSeedFromUrbitWallet(
69+ toWallet,
70+ INITIAL_NETWORK_KEY_REVISION
71+ );
72+
73+ const networkSeed = seed.matchWith({
74+ Nothing: () => {
75+ throw new Error('network seed not derived');
76+ },
77+ Just: p => p.value,
78+ });
79+
80+ const networkKeys = deriveNetworkKeys(networkSeed);
81+
82+ const keysTx = azimuth.ecliptic.configureKeys(
83+ contracts,
84+ point,
85+ addHexPrefix(networkKeys.crypt.public),
86+ addHexPrefix(networkKeys.auth.public),
87+ CRYPTO_SUITE_VERSION,
88+ /* discontinuous */ false
89+ );
90+ keysTx.gas = GAS_LIMITS.CONFIGURE_KEYS;
91+
92+ // configure management proxy
93+
94+ const managementTx = azimuth.ecliptic.setManagementProxy(
95+ contracts,
96+ point,
97+ toWallet.management.keys.address
98+ );
99+ managementTx.gas = GAS_LIMITS.SET_PROXY;
100+
101+ // set spawn & voting proxies situationally
102+
103+ let txs = [transferTmpTx, keysTx, managementTx];
104+ if (
105+ azimuth.azimuth.getPointSize(point) !== azimuth.azimuth.PointSize.Planet
106+ ) {
107+ const spawnTx = azimuth.ecliptic.setSpawnProxy(
108+ contracts,
109+ point,
110+ toWallet.spawn.keys.address
111+ );
112+ spawnTx.gas = GAS_LIMITS.SET_PROXY;
113+ txs.push(spawnTx);
114+
115+ if (
116+ azimuth.azimuth.getPointSize(point) === azimuth.azimuth.PointSize.Galaxy
117+ ) {
118+ const votingTx = azimuth.ecliptic.setVotingProxy(
119+ contracts,
120+ point,
121+ toWallet.voting.keys.address
122+ );
123+ votingTx.gas = GAS_LIMITS.SET_PROXY;
124+ txs.push(votingTx);
125+ }
126+ }
127+
128+ // transfer configured point to user's new wallet
129+
130+ const transferFinalTx = azimuth.ecliptic.transferPoint(
131+ contracts,
132+ point,
133+ toWallet.ownership.keys.address,
134+ false
135+ );
136+ transferFinalTx.gas = GAS_LIMITS.TRANSFER;
137+ txs.push(transferFinalTx);
138+
139+ //
140+ // finalizing & signing transactions
141+ //
142+
143+ progress(TRANSACTION_PROGRESS.SIGNING);
144+
145+ let totalCost = 0;
146+ let inviteNonce = await web3.eth.getTransactionCount(fromWallet.address);
147+ txs = txs.map(tx => {
148+ tx.from = fromWallet.address;
149+ tx.nonce = inviteNonce++;
150+ tx.gasPrice = parseInt(toWei(DEFAULT_GAS_PRICE_GWEI.toFixed(), 'gwei'), 10);
151+ totalCost = totalCost + tx.gasPrice * tx.gas;
152+ return tx;
153+ });
154+
155+ let txPairs = txs.map(tx => {
156+ let stx = new Tx(tx);
157+ stx.sign(Buffer.from(fromWallet.privateKey, 'hex'));
158+ return {
159+ raw: hexify(stx.serialize()),
160+ signed: stx,
161+ };
162+ });
163+
164+ //
165+ // ensuring funding for transactions
166+ //
167+
168+ progress(TRANSACTION_PROGRESS.FUNDING);
169+
170+ const usedTank = await tank.ensureFundsFor(
171+ web3,
172+ point,
173+ fromWallet.address,
174+ totalCost,
175+ txPairs.map(p => p.raw),
176+ askForFunding,
177+ gotFunding
178+ );
179+
180+ //
181+ // sending and awaiting transactions
182+ //
183+
184+ progress(TRANSACTION_PROGRESS.TRANSFERRING);
185+
186+ await sendAndAwaitAll(web3, txPairs.map(p => p.signed), usedTank);
187+
188+ //
189+ // move leftover eth
190+ //
191+
192+ progress(TRANSACTION_PROGRESS.CLEANING);
193+
194+ // if non-trivial eth left in invite wallet, transfer to new ownership
195+ let balance = await web3.eth.getBalance(fromWallet.address);
196+ const gasPrice = parseInt(toWei(DEFAULT_GAS_PRICE_GWEI.toFixed(), 'gwei'));
197+ const gasLimit = GAS_LIMITS.SEND_ETH;
198+ const sendEthCost = gasPrice * gasLimit;
199+ if (transferEth && balance > sendEthCost) {
200+ const value = balance - sendEthCost;
201+ const tx = {
202+ to: toWallet.ownership.keys.address,
203+ value: value,
204+ gasPrice: gasPrice,
205+ gas: gasLimit,
206+ nonce: inviteNonce++,
207+ };
208+ let stx = new Tx(tx);
209+ stx.sign(fromWallet.privateKey);
210+ const rawTx = hexify(stx.serialize());
211+ web3.eth.sendSignedTransaction(rawTx).catch(err => {
212+ console.log('error sending value tx, who cares', err);
213+ });
214+ }
215+
216+ progress(TRANSACTION_PROGRESS.DONE);
217+}
src/views/Activate/PassportTransfer.js+29−8
@@ -8,7 +8,10 @@ import { usePointCursor } from 'store/pointCursor';
88
99import * as need from 'lib/need';
1010import useLifecycle from 'lib/useLifecycle';
11−import { reticketPointBetweenWallets } from 'lib/invite';
11+import {
12+ reticketPointBetweenWallets,
13+ TRANSACTION_PROGRESS,
14+} from 'lib/reticket';
1215import timeout from 'lib/timeout';
1316import { fromWei } from 'lib/txn';
1417
@@ -20,6 +23,24 @@ import { RestartButton } from 'components/Buttons';
2023import { useActivateFlow } from './ActivateFlow';
2124import PassportView from './PassportView';
2225
26+const labelForProgress = progress => {
27+ if (progress <= 0) {
28+ return 'Verify Passport';
29+ } else if (progress <= TRANSACTION_PROGRESS.GENERATING) {
30+ return 'Generating Transactions...';
31+ } else if (progress <= TRANSACTION_PROGRESS.SIGNING) {
32+ return 'Signing Transactions...';
33+ } else if (progress <= TRANSACTION_PROGRESS.FUNDING) {
34+ return 'Funding Transactions...';
35+ } else if (progress <= TRANSACTION_PROGRESS.TRANSFERRING) {
36+ return 'Transferring Point...';
37+ } else if (progress <= TRANSACTION_PROGRESS.CLEANING) {
38+ return 'Cleaning Up...';
39+ } else if (progress <= TRANSACTION_PROGRESS.DONE) {
40+ return 'Done';
41+ }
42+};
43+
2344export default function PassportTransfer({ className, resetActivateRouter }) {
2445 const { replaceWith, names } = useHistory();
2546 const { setUrbitWallet } = useWallet();
@@ -32,10 +53,7 @@ export default function PassportTransfer({ className, resetActivateRouter }) {
3253 reset: resetActivateFlow,
3354 } = useActivateFlow();
3455 const [generalError, setGeneralError] = useState();
35− const [{ label, progress }, setState] = useState({
36− label: 'Verify Passport',
37− progress: 0,
38− });
56+ const [progress, setProcesss] = useState(0);
3957 const [needFunds, setNeedFunds] = useState();
4058
4159 const goToLogin = useCallback(
@@ -69,7 +87,7 @@ export default function PassportTransfer({ className, resetActivateRouter }) {
6987 ({ type, state, value }) => {
7088 switch (type) {
7189 case 'progress':
72− return setState(state);
90+ return setProcesss(state);
7391 case 'askFunding':
7492 return setNeedFunds(value);
7593 case 'gotFunding':
@@ -78,7 +96,7 @@ export default function PassportTransfer({ className, resetActivateRouter }) {
7896 console.error(`Unknown update: ${type}`);
7997 }
8098 },
81− [setState, setNeedFunds]
99+ [setProcesss, setNeedFunds]
82100 );
83101
84102 const claimPoint = useCallback(async () => {
@@ -172,7 +190,10 @@ export default function PassportTransfer({ className, resetActivateRouter }) {
172190 };
173191
174192 return (
175− <PassportView className={className} header={label} step={3}>
193+ <PassportView
194+ className={className}
195+ header={labelForProgress(progress)}
196+ step={3}>
176197 <Grid>
177198 <Grid.Item full as={Grid} className="mt3" gap={3}>
178199 <Grid.Item full as={LoadingBar} progress={progress} />
src/views/Admin/Reticket/ReticketExecute.js+26−9
@@ -2,7 +2,10 @@ import React, { useState, useCallback } from 'react';
22import { Just, Nothing } from 'folktale/maybe';
33import { Grid, H4, Text, ErrorText } from 'indigo-react';
44
5−import { reticketPointBetweenWallets } from 'lib/invite';
5+import {
6+ reticketPointBetweenWallets,
7+ TRANSACTION_PROGRESS,
8+} from 'lib/reticket';
69import { fromWei } from 'lib/txn';
710import * as need from 'lib/need';
811import useLifecycle from 'lib/useLifecycle';
@@ -18,7 +21,24 @@ import LoadingBar from 'components/LoadingBar';
1821import Highlighted from 'components/Highlighted';
1922import { WALLET_TYPES } from 'lib/wallet';
2023
21−// TODO: maybe we can merge PassportTransfer here?
24+const labelForProgress = progress => {
25+ if (progress <= 0) {
26+ return 'Starting...';
27+ } else if (progress <= TRANSACTION_PROGRESS.GENERATING) {
28+ return 'Generating Transactions...';
29+ } else if (progress <= TRANSACTION_PROGRESS.SIGNING) {
30+ return 'Signing Transactions...';
31+ } else if (progress <= TRANSACTION_PROGRESS.FUNDING) {
32+ return 'Funding Transactions...';
33+ } else if (progress <= TRANSACTION_PROGRESS.TRANSFERRING) {
34+ return 'Transferring Point...';
35+ } else if (progress <= TRANSACTION_PROGRESS.CLEANING) {
36+ return 'Cleaning Up...';
37+ } else if (progress <= TRANSACTION_PROGRESS.DONE) {
38+ return 'Done';
39+ }
40+};
41+
2242export default function ReticketExecute({ newWallet, setNewWallet }) {
2343 const { popTo, names, reset } = useHistory();
2444 const { web3, contracts } = useNetwork();
@@ -26,10 +46,7 @@ export default function ReticketExecute({ newWallet, setNewWallet }) {
2646 const { pointCursor } = usePointCursor();
2747
2848 const [generalError, setGeneralError] = useState();
29− const [{ label, progress }, setState] = useState({
30− label: 'Starting...',
31− progress: 0,
32− });
49+ const [progress, setProgress] = useState(0);
3350 const [needFunds, setNeedFunds] = useState();
3451 const isDone = progress >= 1.0;
3552
@@ -70,7 +87,7 @@ export default function ReticketExecute({ newWallet, setNewWallet }) {
7087 ({ type, state, value }) => {
7188 switch (type) {
7289 case 'progress':
73− return setState(state);
90+ return setProgress(state);
7491 case 'askFunding':
7592 return setNeedFunds(value);
7693 case 'gotFunding':
@@ -79,7 +96,7 @@ export default function ReticketExecute({ newWallet, setNewWallet }) {
7996 console.error(`Unknown update: ${type}`);
8097 }
8198 },
82− [setState, setNeedFunds]
99+ [setProgress, setNeedFunds]
83100 );
84101
85102 const renderAdditionalInfo = () => {
@@ -124,7 +141,7 @@ export default function ReticketExecute({ newWallet, setNewWallet }) {
124141 return (
125142 <Grid gap={4} className="mt4">
126143 <Grid.Item full as={H4}>
127− {label}
144+ {labelForProgress(progress)}
128145 </Grid.Item>
129146
130147 {isDone ? (
src/views/SentTransaction.js−1
@@ -1,4 +1,3 @@
1−import Maybe from 'folktale/maybe';
21import React from 'react';
32import * as need from '../lib/need';
43import { H1, H3, P, Warning, Anchor } from '../components/old/Base';