on master help sign in
keys: add new reticket hook for L1 points with L2 spawn proxy
bca66f0tomholford
src/lib/constants.ts+4
@@ -219,6 +219,9 @@ const PASSPORT_TOOLTIP =
219const LOCKUP_TOOLTIP =
219const LOCKUP_TOOLTIP =
220 'Stars in a lockup schedule can be withdrawn after a set time duration. The duration is predetermined by the issuer';
220 'Stars in a lockup schedule can be withdrawn after a set time duration. The duration is predetermined by the issuer';
221
221
222+// the initial network key revision is always 1
223+const INITIAL_NETWORK_KEY_REVISION = 1;
224+
222export {
225export {
223 CHECK_BLOCK_EVERY_MS,
226 CHECK_BLOCK_EVERY_MS,
224 DEFAULT_GAS_PRICE_GWEI,
227 DEFAULT_GAS_PRICE_GWEI,
@@ -265,4 +268,5 @@ export {
265 MASTER_TICKET_TOOLTIP,
268 MASTER_TICKET_TOOLTIP,
266 PASSPORT_TOOLTIP,
269 PASSPORT_TOOLTIP,
267 LOCKUP_TOOLTIP,
270 LOCKUP_TOOLTIP,
271+ INITIAL_NETWORK_KEY_REVISION,
268};
272};
src/lib/reticket.js+14−17
@@ -14,13 +14,10 @@ import {
14 sendSignedTransaction,
14 sendSignedTransaction,
15} from './txn';
15} from './txn';
16import getSuggestedGasPrice from './getSuggestedGasPrice';
16import getSuggestedGasPrice from './getSuggestedGasPrice';
17−import { GAS_LIMITS } from './constants';
17+import { GAS_LIMITS, INITIAL_NETWORK_KEY_REVISION } from './constants';
18import { toBN } from 'web3-utils';
18import { toBN } from 'web3-utils';
19import { safeToWei } from './lib';
19import { safeToWei } from './lib';
20−import { isPlanet } from './utils/point';
20+import { isGalaxy, isPlanet } from './utils/point';
21−
22−// the initial network key revision is always 1
23−const INITIAL_NETWORK_KEY_REVISION = 1;
24
21
25export const TRANSACTION_PROGRESS = {
22export const TRANSACTION_PROGRESS = {
26 GENERATING: 0.01,
23 GENERATING: 0.01,
@@ -45,6 +42,7 @@ export async function reticketPointBetweenWallets({
45 nextRevision = INITIAL_NETWORK_KEY_REVISION,
42 nextRevision = INITIAL_NETWORK_KEY_REVISION,
46 txnSigner,
43 txnSigner,
47 txnSender,
44 txnSender,
45+ doSetSpawnProxy = true,
48}) {
46}) {
49 const askForFunding = (address, minBalance, balance) =>
47 const askForFunding = (address, minBalance, balance) =>
50 onUpdate({
48 onUpdate({
@@ -109,8 +107,9 @@ export async function reticketPointBetweenWallets({
109
107
110 // set spawn & voting proxies situationally
108 // set spawn & voting proxies situationally
111
109
110+ // skip this step if reticketing an L1 point with L2-migrated spawn proxy
112 let txs = [transferTmpTx, keysTx, managementTx];
111 let txs = [transferTmpTx, keysTx, managementTx];
113− if (!isPlanet(point)) {
112+ if (!isPlanet(point) && doSetSpawnProxy) {
114 const spawnTx = azimuth.ecliptic.setSpawnProxy(
113 const spawnTx = azimuth.ecliptic.setSpawnProxy(
115 contracts,
114 contracts,
116 point,
115 point,
@@ -118,18 +117,16 @@ export async function reticketPointBetweenWallets({
118 );
117 );
119 spawnTx.gas = GAS_LIMITS.SET_PROXY;
118 spawnTx.gas = GAS_LIMITS.SET_PROXY;
120 txs.push(spawnTx);
119 txs.push(spawnTx);
120+ }
121
121
122− if (
122+ if (isGalaxy(point)) {
123− azimuth.azimuth.getPointSize(point) === azimuth.azimuth.PointSize.Galaxy
124− ) {
125− const votingTx = azimuth.ecliptic.setVotingProxy(
123+ const votingTx = azimuth.ecliptic.setVotingProxy(
126− contracts,
124+ contracts,
127− point,
125+ point,
128− toWallet.voting.keys.address
126+ toWallet.voting.keys.address
129− );
127+ );
130− votingTx.gas = GAS_LIMITS.SET_PROXY;
128+ votingTx.gas = GAS_LIMITS.SET_PROXY;
131− txs.push(votingTx);
129+ txs.push(votingTx);
132− }
133 }
130 }
134
131
135 // transfer configured point to user's new wallet
132 // transfer configured point to user's new wallet
src/lib/types/L2Transaction.ts+1−1
@@ -40,7 +40,7 @@ export interface TransactionData {
40 method: Function;
40 method: Function;
41}
41}
42
42
43−interface ReticketProgressCallback {
43+export interface ReticketProgressCallback {
44 ({
44 ({
45 type,
45 type,
46 state,
46 state,
src/lib/useReticketL2Spawn.tsnew file+122
@@ -0,0 +1,122 @@
1+import { EthAddress, Ship } from '@urbit/roller-api';
2+import { useCallback } from 'react';
3+import Web3 from 'web3';
4+import { ITxData } from '@walletconnect/types';
5+import { INITIAL_NETWORK_KEY_REVISION } from './constants';
6+import { reticketPointBetweenWallets } from './reticket';
7+import BridgeWallet from './types/BridgeWallet';
8+import { ReticketProgressCallback } from './types/L2Transaction';
9+import useRoller from './useRoller';
10+import { registerProxyAddress } from './utils/roller';
11+import { useWallet } from 'store/wallet';
12+import { useWalletConnect } from './useWalletConnect';
13+
14+interface ReticketL2SpawnParams {
15+ fromWallet: BridgeWallet;
16+ fromWalletType: symbol;
17+ fromWalletHdPath: string;
18+ toWallet: UrbitWallet;
19+ point: Ship;
20+ web3: Web3;
21+ contracts: any; // azimuth contracts
22+ networkType: symbol;
23+ onUpdate?: ReticketProgressCallback;
24+ transferEth?: boolean;
25+ nextRevision: number;
26+ to: EthAddress;
27+ txnSigner?: ({
28+ from,
29+ to,
30+ gas,
31+ gasPrice,
32+ value,
33+ data,
34+ nonce,
35+ }: ITxData) => Promise<string>;
36+ txnSender?: ({
37+ from,
38+ to,
39+ gas,
40+ gasPrice,
41+ value,
42+ data,
43+ nonce,
44+ }: ITxData) => Promise<string>;
45+}
46+
47+/**
48+ * This hook is used to reticket points that are still on L1 but have the
49+ * spawn set to L2. It first performs 3-4 L1 transactions (set keys, set
50+ * management / voting proxy, and transfer).
51+ *
52+ * It then sets the spawn proxy using an L2 transaction.
53+ *
54+ * Under the hood, it wraps two existing functions:
55+ * `reticketPointBetweenWallets` for L1, and `registerProxyAddress` for L2.
56+ *
57+ */
58+export const useReticketL2Spawn = () => {
59+ const { api } = useRoller();
60+ const { walletType }: any = useWallet();
61+ const { connector } = useWalletConnect();
62+
63+ const performL2SpawnReticket = useCallback(
64+ async ({
65+ fromWallet,
66+ fromWalletType,
67+ fromWalletHdPath,
68+ toWallet,
69+ point,
70+ web3,
71+ contracts,
72+ networkType,
73+ onUpdate,
74+ transferEth = false,
75+ nextRevision = INITIAL_NETWORK_KEY_REVISION,
76+ to,
77+ txnSigner,
78+ txnSender,
79+ }: ReticketL2SpawnParams) => {
80+ // call reticketPointBetweenWallets, with isL2Spawn set to true; this will
81+ // skip setting the L1 spawn proxy
82+ await reticketPointBetweenWallets({
83+ fromWallet,
84+ fromWalletType,
85+ fromWalletHdPath,
86+ toWallet,
87+ point,
88+ web3,
89+ contracts,
90+ networkType,
91+ onUpdate,
92+ transferEth,
93+ nextRevision,
94+ txnSigner,
95+ txnSender,
96+ doSetSpawnProxy: false,
97+ });
98+
99+ // instead, set spawn proxy on L2
100+ const proxy = 'own';
101+ const nonce = await api.getNonce({ ship: point, proxy });
102+
103+ await registerProxyAddress(
104+ api,
105+ fromWallet,
106+ point,
107+ proxy,
108+ 'spawn',
109+ nonce,
110+ toWallet?.spawn?.keys?.address || to,
111+ walletType,
112+ web3,
113+ connector
114+ );
115+ },
116+ [api, connector, walletType]
117+ );
118+
119+ return {
120+ performL2SpawnReticket,
121+ };
122+};
src/lib/useRoller.ts+1
@@ -771,6 +771,7 @@ export default function useRoller() {
771 nonce = nonce + 1;
771 nonce = nonce + 1;
772 requests.push(registerSpawnRequest);
772 requests.push(registerSpawnRequest);
773 }
773 }
774+
774 // 4. Transfer point
775 // 4. Transfer point
775 progress(TRANSACTION_PROGRESS.TRANSFERRING);
776 progress(TRANSACTION_PROGRESS.TRANSFERRING);
776 const transferTxRequest = await submitL2Transaction({
777 const transferTxRequest = await submitL2Transaction({
src/lib/utils/debug.tsnew file+7
@@ -0,0 +1,7 @@
1+import { isDevelopment } from 'lib/flags';
2+
3+export const debugLog = (msg: string) => {
4+ if (isDevelopment) {
5+ console.log(msg);
6+ }
7+};
src/views/UrbitID/ResetKeys/ResetExecute.js → src/views/UrbitID/ResetKeys/ResetExecute.tsxrenamed+123−72
@@ -7,9 +7,10 @@ import {
7 TRANSACTION_PROGRESS,
7 TRANSACTION_PROGRESS,
8} from 'lib/reticket';
8} from 'lib/reticket';
9import * as need from 'lib/need';
9import * as need from 'lib/need';
10−import { WALLET_TYPES } from 'lib/constants';
10+import { POINT_DOMINIONS, WALLET_TYPES } from 'lib/constants';
11import { convertToInt } from 'lib/convertToInt';
11import { convertToInt } from 'lib/convertToInt';
12import useBlockWindowClose from 'lib/useBlockWindowClose';
12import useBlockWindowClose from 'lib/useBlockWindowClose';
13+import { useReticketL2Spawn } from 'lib/useReticketL2Spawn';
13
14
14import { useNetwork } from 'store/network';
15import { useNetwork } from 'store/network';
15import { useWallet } from 'store/wallet';
16import { useWallet } from 'store/wallet';
@@ -25,8 +26,10 @@ import { RestartButton, ForwardButton } from 'components/Buttons';
25import WarningBox from 'components/WarningBox';
26import WarningBox from 'components/WarningBox';
26import LoadingBar from 'components/LoadingBar';
27import LoadingBar from 'components/LoadingBar';
27import NeedFundsNotice from 'components/NeedFundsNotice';
28import NeedFundsNotice from 'components/NeedFundsNotice';
29+import { debugLog } from 'lib/utils/debug';
30+import { ReticketProgressCallback } from 'lib/types/L2Transaction';
28
31
29−const labelForProgress = progress => {
32+const labelForProgress = (progress: number) => {
30 if (progress <= 0) {
33 if (progress <= 0) {
31 return 'Starting...';
34 return 'Starting...';
32 } else if (progress <= TRANSACTION_PROGRESS.GENERATING) {
35 } else if (progress <= TRANSACTION_PROGRESS.GENERATING) {
@@ -44,9 +47,19 @@ const labelForProgress = progress => {
44 }
47 }
45};
48};
46
49
50+interface ResetExecuteProps {
51+ // a Maybe<UrbitWallet>
52+ newWallet: any;
53+ // a useState setter, accepts Maybe<UrbitWallet>
54+ setNewWallet: (args: any) => {};
55+}
56+
47−export default function ResetExecute({ newWallet, setNewWallet }) {
57+export default function ResetExecute({
58+ newWallet,
59+ setNewWallet,
60+}: ResetExecuteProps) {
48− const { popTo, names, reset } = useHistory();
61+ const { popTo, names, reset }: any = useHistory();
49− const { web3, contracts, networkType } = useNetwork();
62+ const { web3, contracts, networkType }: any = useNetwork();
50 const {
63 const {
51 wallet,
64 wallet,
52 setWalletType,
65 setWalletType,
@@ -54,23 +67,25 @@ export default function ResetExecute({ newWallet, setNewWallet }) {
54 setUrbitWallet,
67 setUrbitWallet,
55 walletType,
68 walletType,
56 walletHdPath,
69 walletHdPath,
57− } = useWallet();
70+ }: any = useWallet();
58− const { performL2Reticket } = useRoller();
71+ const { api, performL2Reticket } = useRoller();
59 const {
72 const {
60− point: { isL2Spawn },
73+ point: { isL2 },
61 } = useRollerStore();
74 } = useRollerStore();
62− const { pointCursor } = usePointCursor();
75+ const { pointCursor }: any = usePointCursor();
63− const { getDetails } = usePointCache();
76+ const { getDetails }: any = usePointCache();
64 const {
77 const {
65 signTransaction: wcSign,
78 signTransaction: wcSign,
66 sendTransaction: wcSend,
79 sendTransaction: wcSend,
67 connector,
80 connector,
68 } = useWalletConnect();
81 } = useWalletConnect();
82+ const { performL2SpawnReticket } = useReticketL2Spawn();
69
83
70− const [generalError, setGeneralError] = useState();
84+ const [generalError, setGeneralError] = useState<Error | undefined>();
71 const [progress, setProgress] = useState(0);
85 const [progress, setProgress] = useState(0);
72− const [needFunds, setNeedFunds] = useState();
86+ const [needFunds, setNeedFunds] = useState<string | boolean | undefined>();
73 const isDone = progress >= 1.0;
87 const isDone = progress >= 1.0;
88+ const point = need.point(pointCursor);
74
89
75 useBlockWindowClose();
90 useBlockWindowClose();
76
91
@@ -79,17 +94,26 @@ export default function ResetExecute({ newWallet, setNewWallet }) {
79 setNewWallet(Nothing());
94 setNewWallet(Nothing());
80 }, [reset, setNewWallet]);
95 }, [reset, setNewWallet]);
81
96
82− const loginAndGoHome = useCallback(() => {
97+ const loginAndGoHome = useCallback(async () => {
83 // set wallet state
98 // set wallet state
84 resetWallet();
99 resetWallet();
85 setWalletType(WALLET_TYPES.TICKET);
100 setWalletType(WALLET_TYPES.TICKET);
86 setUrbitWallet(Just(newWallet.value.wallet));
101 setUrbitWallet(Just(newWallet.value.wallet));
87 // (implicit) pointCursor stays the same
102 // (implicit) pointCursor stays the same
103+ // TODO: do we need to update the point?
104+ // await getAndUpdatePoint(point);
88 // redirect to point
105 // redirect to point
89 popTo(names.POINT);
106 popTo(names.POINT);
90− }, [resetWallet, setWalletType, setUrbitWallet, newWallet, popTo, names]);
107+ }, [
108+ resetWallet,
109+ setWalletType,
110+ setUrbitWallet,
111+ newWallet.value.wallet,
112+ popTo,
113+ names.POINT,
114+ ]);
91
115
92− const handleUpdate = useCallback(
116+ const handleUpdate: ReticketProgressCallback = useCallback(
93 ({ type, state, value }) => {
117 ({ type, state, value }) => {
94 switch (type) {
118 switch (type) {
95 case 'progress':
119 case 'progress':
@@ -106,69 +130,92 @@ export default function ResetExecute({ newWallet, setNewWallet }) {
106 );
130 );
107
131
108 const performReticket = useCallback(async () => {
132 const performReticket = useCallback(async () => {
109− (async () => {
110− // due to react shenanigans we may need to wait for the connector
133+ // due to react shenanigans we may need to wait for the connector
111− if (
134+ if (
112− walletType === WALLET_TYPES.WALLET_CONNECT &&
135+ walletType === WALLET_TYPES.WALLET_CONNECT &&
113− (!connector || !connector.connected)
136+ (!connector || !connector.connected)
114− ) {
137+ ) {
115− setGeneralError(new Error('Awaiting WalletConnect connection...'));
138+ setGeneralError(new Error('Awaiting WalletConnect connection...'));
116− return;
139+ return;
117− }
140+ }
118− setGeneralError();
119
141
120− const point = need.point(pointCursor);
142+ setGeneralError(undefined);
121− const details = need.details(getDetails(point));
122− const networkRevision = convertToInt(details.keyRevisionNumber, 10);
123
143
124− // see also comment in useEthereumTransaction
144+ const l2point = await api.getPoint(point);
125− const txnSigner =
145+ const details = need.details(getDetails(point));
126− walletType === WALLET_TYPES.WALLET_CONNECT ? wcSign : undefined;
146+ const networkRevision = convertToInt(details.keyRevisionNumber, 10);
127− const txnSender =
128− walletType === WALLET_TYPES.WALLET_CONNECT ? wcSend : undefined;
129
147
148+ // see also comment in useEthereumTransaction
149+ const txnSigner =
150+ walletType === WALLET_TYPES.WALLET_CONNECT ? wcSign : undefined;
151+ const txnSender =
152+ walletType === WALLET_TYPES.WALLET_CONNECT ? wcSend : undefined;
153+
130− try {
154+ try {
131− if (isL2Spawn) {
155+ // if L1 point with migrated spawn proxy (dominion = 'spawn')
132− // FIXME: the useEffect is called twice, which is fine since the duplicate
156+ if (l2point.dominion === POINT_DOMINIONS.SPAWN) {
133− // L2 txs will be discarded
157+ debugLog('reticketing L1 point with dominion spawn...');
158+ await performL2SpawnReticket({
159+ fromWallet: need.wallet(wallet),
160+ fromWalletType: walletType,
161+ fromWalletHdPath: walletHdPath,
162+ toWallet: newWallet.value.wallet,
163+ to: newWallet.value.wallet.ownership.keys.address,
164+ point: point,
165+ web3: need.web3(web3),
166+ contracts: need.contracts(contracts),
167+ networkType,
168+ onUpdate: handleUpdate,
169+ nextRevision: networkRevision + 1,
170+ txnSigner,
171+ txnSender,
172+ });
173+ debugLog('done');
174+ // If L2 point
175+ } else if (isL2) {
176+ debugLog('reticketing L2 point ...');
134− await performL2Reticket({
177+ await performL2Reticket({
135− point,
178+ point,
136− to: newWallet.value.wallet.ownership.keys.address,
179+ to: newWallet.value.wallet.ownership.keys.address,
137− manager: newWallet.value.wallet.management.keys.address,
180+ manager: newWallet.value.wallet.management.keys.address,
138− toWallet: newWallet.value.wallet,
181+ toWallet: newWallet.value.wallet,
139− fromWallet: need.wallet(wallet),
182+ fromWallet: need.wallet(wallet),
140− onUpdate: handleUpdate,
183+ onUpdate: handleUpdate,
141− });
184+ });
142− } else
185+ debugLog('done');
186+ // Fallback to L1 point flow
187+ } else debugLog('reticketing L1 point ...');
143− await reticketPointBetweenWallets({
188+ await reticketPointBetweenWallets({
144− fromWallet: need.wallet(wallet),
189+ fromWallet: need.wallet(wallet),
145− fromWalletType: walletType,
190+ fromWalletType: walletType,
146− fromWalletHdPath: walletHdPath,
191+ fromWalletHdPath: walletHdPath,
147− toWallet: newWallet.value.wallet,
192+ toWallet: newWallet.value.wallet,
148− point: point,
193+ point: point,
149− web3: need.web3(web3),
194+ web3: need.web3(web3),
150− contracts: need.contracts(contracts),
195+ contracts: need.contracts(contracts),
151− networkType,
196+ networkType,
152− onUpdate: handleUpdate,
197+ onUpdate: handleUpdate,
153− nextRevision: networkRevision + 1,
198+ nextRevision: networkRevision + 1,
154− txnSigner,
199+ txnSigner,
155− txnSender,
200+ txnSender,
156− });
201+ });
202+ debugLog('done');
157− } catch (err) {
203+ } catch (err) {
158− console.error(err);
204+ console.error(err);
159− setGeneralError(err);
205+ setGeneralError(err);
160− }
206+ }
161− })();
162 }, [
207 }, [
208+ api,
163 connector,
209 connector,
164 contracts,
210 contracts,
165 getDetails,
211 getDetails,
166 handleUpdate,
212 handleUpdate,
167− isL2Spawn,
213+ isL2,
168 networkType,
214 networkType,
169 newWallet.value.wallet,
215 newWallet.value.wallet,
170 performL2Reticket,
216 performL2Reticket,
171− pointCursor,
217+ performL2SpawnReticket,
218+ point,
172 wallet,
219 wallet,
173 walletHdPath,
220 walletHdPath,
174 walletType,
221 walletType,
@@ -177,12 +224,14 @@ export default function ResetExecute({ newWallet, setNewWallet }) {
177 web3,
224 web3,
178 ]);
225 ]);
179
226
180− // start reticketing transactions on mount
181 useEffect(() => {
227 useEffect(() => {
228+ if (!connector) {
229+ return;
230+ }
231+
182 performReticket();
232 performReticket();
183− //NOTE this was using useLifecycle previously (which is useEffect without
233+ // We want to perform the reticket only once, after the WalletConnect
184− // dependencies) and worked fine, but in the wc case we might need to
234+ // connector has finished instantiating and setting up a websocket
185− // wait for the connector to get set due to react state shenanigans.
186 // eslint-disable-next-line react-hooks/exhaustive-deps
235 // eslint-disable-next-line react-hooks/exhaustive-deps
187 }, [connector]);
236 }, [connector]);
188
237
@@ -193,7 +242,9 @@ export default function ResetExecute({ newWallet, setNewWallet }) {
193 return (
242 return (
194 <>
243 <>
195 <Grid.Item full className="mt4">
244 <Grid.Item full className="mt4">
245+ <ErrorText className={''}>
196− <ErrorText>{generalError.message.toString()}</ErrorText>
246+ {generalError.message.toString()}
247+ </ErrorText>
197 </Grid.Item>
248 </Grid.Item>
198 <Grid.Item
249 <Grid.Item
199 full
250 full