on master help sign in
Merge branch 'master' into hm/invites-refactor
c635e69Hunter Miller
package-lock.json+8−3
@@ -4720,6 +4720,11 @@
4720 "resolved": "https://registry.npmjs.org/@ledgerhq/logs/-/logs-6.2.0.tgz",
4720 "resolved": "https://registry.npmjs.org/@ledgerhq/logs/-/logs-6.2.0.tgz",
4721 "integrity": "sha512-SLyFyD7ElMhgKWPYedFGCT/ilcbGPgL5hXXYHxOM79Fs5fWi0zaUpt5oGqGMsOAAFaMa9/rbun0pokzPhEFz8A=="
4721 "integrity": "sha512-SLyFyD7ElMhgKWPYedFGCT/ilcbGPgL5hXXYHxOM79Fs5fWi0zaUpt5oGqGMsOAAFaMa9/rbun0pokzPhEFz8A=="
4722 },
4722 },
4723+ "@metamask/detect-provider": {
4724+ "version": "1.2.0",
4725+ "resolved": "https://registry.npmjs.org/@metamask/detect-provider/-/detect-provider-1.2.0.tgz",
4726+ "integrity": "sha512-ocA76vt+8D0thgXZ7LxFPyqw3H7988qblgzddTDA6B8a/yU0uKV42QR/DhA+Jh11rJjxW0jKvwb5htA6krNZDQ=="
4727+ },
4723 "@microsoft/fetch-event-source": {
4728 "@microsoft/fetch-event-source": {
4724 "version": "2.0.1",
4729 "version": "2.0.1",
4725 "resolved": "https://registry.npmjs.org/@microsoft/fetch-event-source/-/fetch-event-source-2.0.1.tgz",
4730 "resolved": "https://registry.npmjs.org/@microsoft/fetch-event-source/-/fetch-event-source-2.0.1.tgz",
@@ -7775,9 +7780,9 @@
7775 }
7780 }
7776 },
7781 },
7777 "@urbit/roller-api": {
7782 "@urbit/roller-api": {
7778− "version": "0.1.14",
7783+ "version": "0.1.15",
7779− "resolved": "https://registry.npmjs.org/@urbit/roller-api/-/roller-api-0.1.14.tgz",
7784+ "resolved": "https://registry.npmjs.org/@urbit/roller-api/-/roller-api-0.1.15.tgz",
7780− "integrity": "sha512-8I26tia2n6+7IbSl7pVhW7PSaDVFH+QA4Y4RJvgnYpdQDTpry4cXFynvsiTtlU5p97csBd+hXdNub7JDon4LKg==",
7785+ "integrity": "sha512-pP+XtpKOlSdx25+/lHPRtCihLSlVnOXLUk6igA2tCFMdo0cgrH1fCREoT3GlqkwhJQBq+VY08gnyD7Kn58i/Ug==",
7781 "requires": {
7786 "requires": {
7782 "@open-rpc/client-js": "1.7.1",
7787 "@open-rpc/client-js": "1.7.1",
7783 "@open-rpc/meta-schema": "1.14.2",
7788 "@open-rpc/meta-schema": "1.14.2",
package.json+3−2
@@ -1,6 +1,6 @@
1{
1{
2 "name": "bridge",
2 "name": "bridge",
3− "version": "3.0.0b",
3+ "version": "3.0.0",
4 "description": "An application for interacting with Azimuth",
4 "description": "An application for interacting with Azimuth",
5 "repository": {
5 "repository": {
6 "type": "git",
6 "type": "git",
@@ -18,6 +18,7 @@
18 "@ethereumjs/tx": "^3.3.0",
18 "@ethereumjs/tx": "^3.3.0",
19 "@ledgerhq/hw-app-eth": "^6.5.0",
19 "@ledgerhq/hw-app-eth": "^6.5.0",
20 "@ledgerhq/hw-transport-u2f": "^5.36.0-deprecated",
20 "@ledgerhq/hw-transport-u2f": "^5.36.0-deprecated",
21+ "@metamask/detect-provider": "^1.2.0",
21 "@reach/disclosure": "^0.15.0",
22 "@reach/disclosure": "^0.15.0",
22 "@reach/menu-button": "^0.15.1",
23 "@reach/menu-button": "^0.15.1",
23 "@reach/tabs": "^0.15.0",
24 "@reach/tabs": "^0.15.0",
@@ -32,7 +33,7 @@
32 "@types/styled-components": "^5.1.10",
33 "@types/styled-components": "^5.1.10",
33 "@types/styled-system": "^5.1.11",
34 "@types/styled-system": "^5.1.11",
34 "@types/styled-system__css": "^5.0.15",
35 "@types/styled-system__css": "^5.0.15",
35− "@urbit/roller-api": "^0.1.14",
36+ "@urbit/roller-api": "^0.1.15",
36 "@walletconnect/client": "^1.7.1",
37 "@walletconnect/client": "^1.7.1",
37 "@walletconnect/qrcode-modal": "^1.7.1",
38 "@walletconnect/qrcode-modal": "^1.7.1",
38 "@welldone-software/why-did-you-render": "^3.2.3",
39 "@welldone-software/why-did-you-render": "^3.2.3",
src/Bridge.js+8
@@ -25,6 +25,7 @@ import Modal from 'components/L2/Modal';
25import { useRollerStore } from 'store/rollerStore';
25import { useRollerStore } from 'store/rollerStore';
26import { Box } from '@tlon/indigo-react';
26import { Box } from '@tlon/indigo-react';
27import LoadingOverlay from 'components/L2/LoadingOverlay';
27import LoadingOverlay from 'components/L2/LoadingOverlay';
28+import { useRollerPoller } from 'lib/useRollerPoller';
28
29
29const INITIAL_NETWORK_TYPE = isRopsten
30const INITIAL_NETWORK_TYPE = isRopsten
30 ? NETWORK_TYPES.ROPSTEN
31 ? NETWORK_TYPES.ROPSTEN
@@ -72,6 +73,12 @@ function useInitialRoutes() {
72 return [{ key: ROUTE_NAMES.LOGIN }];
73 return [{ key: ROUTE_NAMES.LOGIN }];
73}
74}
74
75
76+function Poller() {
77+ useRollerPoller();
78+
79+ return null;
80+}
81+
75function Bridge() {
82function Bridge() {
76 const initialRoutes = useInitialRoutes();
83 const initialRoutes = useInitialRoutes();
77 const { loading, modalText, setModalText } = useRollerStore();
84 const { loading, modalText, setModalText } = useRollerStore();
@@ -105,6 +112,7 @@ function Bridge() {
105 <Box width="280px">{modalText}</Box>
112 <Box width="280px">{modalText}</Box>
106 </Modal>
113 </Modal>
107 <LoadingOverlay loading={loading} />
114 <LoadingOverlay loading={loading} />
115+ <Poller />
108 </Provider>
116 </Provider>
109 </WithErrorBoundary>
117 </WithErrorBoundary>
110 );
118 );
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+36−2
@@ -1,4 +1,16 @@
1−import RollerRPCAPI, { EthAddress, Proxy, Ship } from '@urbit/roller-api';
1+import RollerRPCAPI, {
2+ Adopt,
3+ CancelEscape,
4+ ConfigureKeys,
5+ Detach,
6+ Escape,
7+ EthAddress,
8+ Proxy,
9+ Reject,
10+ Ship,
11+ Spawn,
12+ TransferPoint,
13+} from '@urbit/roller-api';
2import WalletConnect from '@walletconnect/client';
14import WalletConnect from '@walletconnect/client';
3import BridgeWallet from './BridgeWallet';
15import BridgeWallet from './BridgeWallet';
4
16
@@ -37,7 +49,28 @@ export interface L2TransactionArgs extends SendL2Params {
37
49
38export interface TransactionData {
50export interface TransactionData {
39 data: any;
51 data: any;
40− method: Function;
52+ method:
53+ | Spawn
54+ | TransferPoint
55+ | ConfigureKeys
56+ | Escape
57+ | CancelEscape
58+ | Adopt
59+ | Detach
60+ | Reject
61+ | (() => Promise<string>);
62+}
63+
64+export interface ReticketProgressCallback {
65+ ({
66+ type,
67+ state,
68+ value,
69+ }: {
70+ type: string;
71+ state: number;
72+ value?: string;
73+ }): void;
41}
74}
42
75
43export interface ReticketParams {
76export interface ReticketParams {
@@ -46,4 +79,5 @@ export interface ReticketParams {
46 manager: EthAddress;
79 manager: EthAddress;
47 fromWallet: BridgeWallet;
80 fromWallet: BridgeWallet;
48 toWallet: UrbitWallet;
81 toWallet: UrbitWallet;
82+ onUpdate?: ReticketProgressCallback;
49}
83}
src/lib/types/Point.ts+4
@@ -69,6 +69,7 @@ interface PointConstructorParams {
69 details: L1Point;
69 details: L1Point;
70 address: string;
70 address: string;
71 l2Quota?: number;
71 l2Quota?: number;
72+ l2Allowance?: number;
72 isPlaceholder?: boolean;
73 isPlaceholder?: boolean;
73}
74}
74
75
@@ -77,6 +78,7 @@ export default class Point {
77 patp: string;
78 patp: string;
78 layer: 1 | 2;
79 layer: 1 | 2;
79 l2Quota: number;
80 l2Quota: number;
81+ l2Allowance: number;
80 active: boolean;
82 active: boolean;
81 authenticationKey: string;
83 authenticationKey: string;
82 continuityNumber: string;
84 continuityNumber: string;
@@ -132,6 +134,7 @@ export default class Point {
132 details,
134 details,
133 address,
135 address,
134 l2Quota = 0,
136 l2Quota = 0,
137+ l2Allowance = 0,
135 isPlaceholder = false,
138 isPlaceholder = false,
136 }: PointConstructorParams) {
139 }: PointConstructorParams) {
137 this.value = value;
140 this.value = value;
@@ -139,6 +142,7 @@ export default class Point {
139
142
140 this.layer = details.layer || 1;
143 this.layer = details.layer || 1;
141 this.l2Quota = l2Quota;
144 this.l2Quota = l2Quota;
145+ this.l2Allowance = l2Allowance;
142 this.isL2Spawn = Boolean(details.isL2Spawn);
146 this.isL2Spawn = Boolean(details.isL2Spawn);
143 this.active = details.active;
147 this.active = details.active;
144 this.authenticationKey = details.authenticationKey;
148 this.authenticationKey = details.authenticationKey;
src/lib/useReticketL2Spawn.tsnew file+134
@@ -0,0 +1,134 @@
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+ // In most TX sending cases, we would use the fromWallet to sign and send
101+ // the TX. However, because we are transferring this point during the
102+ // reticket, we will instead sign the final set spawn proxy TX with the
103+ // receiving wallet's private key.
104+
105+ // `registerProxyAddress` expects a toWallet object shaped like a
106+ // BridgeWallet with `address` and `privateKey` properties.
107+ const toWalletStub = {
108+ address: toWallet.ownership.keys.address,
109+ privateKey: Buffer.from(toWallet.ownership.keys.private, 'hex'),
110+ };
111+ const proxy = 'own';
112+ const nonce = await api.getNonce({ ship: point, proxy });
113+
114+ await registerProxyAddress(
115+ api,
116+ toWalletStub,
117+ point,
118+ proxy,
119+ 'spawn',
120+ nonce,
121+ toWallet?.spawn?.keys?.address || to,
122+ walletType,
123+ web3,
124+ connector,
125+ true
126+ );
127+ },
128+ [api, connector, walletType]
129+ );
130+
131+ return {
132+ performL2SpawnReticket,
133+ };
134+};
src/lib/useRoller.ts+20−39
@@ -27,7 +27,6 @@ import {
27 generateInviteWallet,
27 generateInviteWallet,
28 getPendingSpawns,
28 getPendingSpawns,
29 submitL2Transaction,
29 submitL2Transaction,
30− getTimeToNextBatch,
31 registerProxyAddress,
30 registerProxyAddress,
32 isL2Spawn,
31 isL2Spawn,
33} from './utils/roller';
32} from './utils/roller';
@@ -44,15 +43,13 @@ import {
44} from '@urbit/roller-api';
43} from '@urbit/roller-api';
45
44
46import Point, { PointField, Points } from './types/Point';
45import Point, { PointField, Points } from './types/Point';
47−import { useTimerStore } from 'store/timerStore';
48import { ReticketParams, SendL2Params } from './types/L2Transaction';
46import { ReticketParams, SendL2Params } from './types/L2Transaction';
49import { L1Point } from './types/L1Point';
47import { L1Point } from './types/L1Point';
50import { ddmmmYYYY } from './utils/date';
48import { ddmmmYYYY } from './utils/date';
51import { showNotification } from './utils/notifications';
49import { showNotification } from './utils/notifications';
52import { useWalletConnect } from './useWalletConnect';
50import { useWalletConnect } from './useWalletConnect';
53import { PendingL1Txn } from './types/PendingL1Transaction';
51import { PendingL1Txn } from './types/PendingL1Transaction';
54−
52+import { TRANSACTION_PROGRESS } from './reticket';
55−const ONE_SECOND = 1000;
56
53
57interface UpdateParams {
54interface UpdateParams {
58 point: number;
55 point: number;
@@ -89,10 +86,7 @@ export default function useRoller() {
89 const allPoints: any = usePointCache();
86 const allPoints: any = usePointCache();
90 const getDetails = allPoints?.getDetails;
87 const getDetails = allPoints?.getDetails;
91
88
92− const { setNextRoll } = useTimerStore();
93−
94 const {
89 const {
95− nextBatchTime,
96 point,
90 point,
97 points,
91 points,
98 pointList,
92 pointList,
@@ -162,7 +156,7 @@ export default function useRoller() {
162 );
156 );
163
157
164 const fetchConfig = useCallback(async () => {
158 const fetchConfig = useCallback(async () => {
165− getNextQuotaTime(1);
159+ getNextQuotaTime(Date.now() + 1000);
166 api
160 api
167 .getRollerConfig()
161 .getRollerConfig()
168 .then(config => {
162 .then(config => {
@@ -182,12 +176,12 @@ export default function useRoller() {
182 if (point.l2Quota <= 0) {
176 if (point.l2Quota <= 0) {
183 setModalText(
177 setModalText(
184 `You have reached your weekly L2 transaction limit. You will get another
178 `You have reached your weekly L2 transaction limit. You will get another
185− ${config?.rollerQuota} transactions on ${ddmmmYYYY(nextQuotaTime)}.`
179+ ${point.l2Allowance} transactions on ${ddmmmYYYY(nextQuotaTime)}.`
186 );
180 );
187 }
181 }
188
182
189 return point.l2Quota <= 0;
183 return point.l2Quota <= 0;
190− }, [config, nextQuotaTime, point, setModalText]);
184+ }, [nextQuotaTime, point, setModalText]);
191
185
192 const initPoint = useCallback(
186 const initPoint = useCallback(
193 async (point: string | number) => {
187 async (point: string | number) => {
@@ -201,9 +195,9 @@ export default function useRoller() {
201 const pointNum = Number(point);
195 const pointNum = Number(point);
202 try {
196 try {
203 const rawDetails = await api.getPoint(pointNum);
197 const rawDetails = await api.getPoint(pointNum);
204− const l2Quota = isL2Spawn(rawDetails?.dominion)
198+ const isL2 = isL2Spawn(rawDetails?.dominion);
205− ? await api.getRemainingQuota(pointNum)
199+ const l2Quota = isL2 ? await api.getRemainingQuota(pointNum) : 0;
206− : 0;
200+ const l2Allowance = isL2 ? await api.getAllowance(pointNum) : 0;
207
201
208 const details = isL2Spawn(rawDetails?.dominion)
202 const details = isL2Spawn(rawDetails?.dominion)
209 ? toL1Details(rawDetails)
203 ? toL1Details(rawDetails)
@@ -214,6 +208,7 @@ export default function useRoller() {
214 details,
208 details,
215 address: _wallet.address,
209 address: _wallet.address,
216 l2Quota,
210 l2Quota,
211+ l2Allowance,
217 });
212 });
218 } catch (e) {
213 } catch (e) {
219 console.warn(e);
214 console.warn(e);
@@ -697,15 +692,20 @@ export default function useRoller() {
697 manager,
692 manager,
698 fromWallet,
693 fromWallet,
699 toWallet,
694 toWallet,
695+ onUpdate,
700 }: ReticketParams) => {
696 }: ReticketParams) => {
701 const azimuthPoint = await api.getPoint(point);
697 const azimuthPoint = await api.getPoint(point);
702 const proxy = 'own';
698 const proxy = 'own';
703 const _web3 = web3.getOrElse(null);
699 const _web3 = web3.getOrElse(null);
704 let nonce = await api.getNonce({ ship: point, proxy });
700 let nonce = await api.getNonce({ ship: point, proxy });
701+ const progress = onUpdate
702+ ? (state: number) => onUpdate({ type: 'progress', state })
703+ : () => {};
705
704
706 let requests = [];
705 let requests = [];
707
706
708 // 1. Update networking keys
707 // 1. Update networking keys
708+ progress(TRANSACTION_PROGRESS.GENERATING);
709 const networkRevision = convertToInt(azimuthPoint.network.keys.life, 10);
709 const networkRevision = convertToInt(azimuthPoint.network.keys.life, 10);
710 const nextRevision = networkRevision + 1;
710 const nextRevision = networkRevision + 1;
711 const networkSeed = await deriveNetworkSeedFromUrbitWallet(
711 const networkSeed = await deriveNetworkSeedFromUrbitWallet(
@@ -728,6 +728,7 @@ export default function useRoller() {
728 requests.push(configureKeysRequest);
728 requests.push(configureKeysRequest);
729
729
730 // 2. Set Management Proxy
730 // 2. Set Management Proxy
731+ progress(TRANSACTION_PROGRESS.SIGNING);
731 const registerMgmtRequest = await registerProxyAddress(
732 const registerMgmtRequest = await registerProxyAddress(
732 api,
733 api,
733 fromWallet,
734 fromWallet,
@@ -744,6 +745,7 @@ export default function useRoller() {
744 requests.push(registerMgmtRequest);
745 requests.push(registerMgmtRequest);
745
746
746 // 3. Set Spawn Proxy
747 // 3. Set Spawn Proxy
748+ progress(TRANSACTION_PROGRESS.FUNDING);
747 if (!isPlanet(Number(point))) {
749 if (!isPlanet(Number(point))) {
748 const registerSpawnRequest = await registerProxyAddress(
750 const registerSpawnRequest = await registerProxyAddress(
749 api,
751 api,
@@ -760,7 +762,9 @@ export default function useRoller() {
760 nonce = nonce + 1;
762 nonce = nonce + 1;
761 requests.push(registerSpawnRequest);
763 requests.push(registerSpawnRequest);
762 }
764 }
765+
763 // 4. Transfer point
766 // 4. Transfer point
767+ progress(TRANSACTION_PROGRESS.TRANSFERRING);
764 const transferTxRequest = await submitL2Transaction({
768 const transferTxRequest = await submitL2Transaction({
765 api,
769 api,
766 wallet: fromWallet,
770 wallet: fromWallet,
@@ -775,7 +779,10 @@ export default function useRoller() {
775 });
779 });
776 requests.push(transferTxRequest);
780 requests.push(transferTxRequest);
777
781
782+ progress(TRANSACTION_PROGRESS.CLEANING);
778 const hashes = await Promise.all(requests);
783 const hashes = await Promise.all(requests);
784+
785+ progress(TRANSACTION_PROGRESS.DONE);
779 return hashes;
786 return hashes;
780 };
787 };
781
788
@@ -1034,32 +1041,6 @@ export default function useRoller() {
1034 fetchConfig();
1041 fetchConfig();
1035 }, [config, fetchConfig]);
1042 }, [config, fetchConfig]);
1036
1043
1037− useEffect(() => {
1038− const time = ONE_SECOND;
1039−
1040− const interval = setInterval(() => {
1041− const nextRoll = getTimeToNextBatch(nextBatchTime, new Date().getTime());
1042− setNextRoll(nextRoll);
1043−
1044− if (nextBatchTime - ONE_SECOND <= new Date().getTime()) {
1045− api.getRollerConfig().then(response => {
1046− setNextBatchTime(response.nextBatch);
1047− });
1048−
1049− getPendingTransactions();
1050−
1051− setTimeout(() => {
1052− if (!point.isDefault) {
1053− console.log('getting invites from batch time');
1054− getInvites(); // This will also get pending txns
1055− }
1056− }, TEN_SECONDS); // Should this be more like a minute?
1057− }
1058− }, time);
1059−
1060− return () => clearInterval(interval);
1061− }, [point, nextBatchTime, getPendingTransactions, getInvites]); // eslint-disable-line react-hooks/exhaustive-deps
1062−
1063 return {
1044 return {
1064 api,
1045 api,
1065 changeSponsorship,
1046 changeSponsorship,
src/lib/useRollerPoller.tsnew file+42
@@ -0,0 +1,42 @@
1+import { useEffect, useState } from 'react';
2+import { useRollerStore } from 'store/rollerStore';
3+import { useTimerStore } from 'store/timerStore';
4+import { ONE_SECOND, TEN_SECONDS } from './constants';
5+import useRoller from './useRoller';
6+import { getTimeToNextBatch } from './utils/roller';
7+
8+export function useRollerPoller() {
9+ const { nextBatchTime, setNextBatchTime, setNextRoll } = useTimerStore();
10+ const { point } = useRollerStore();
11+ const { api, getInvites, getPendingTransactions } = useRoller();
12+ const [time, setTime] = useState(ONE_SECOND);
13+
14+ useEffect(() => {
15+ const interval = setInterval(async () => {
16+ const nextRoll = getTimeToNextBatch(nextBatchTime, new Date().getTime());
17+ setNextRoll(nextRoll);
18+
19+ if (nextBatchTime - ONE_SECOND <= new Date().getTime()) {
20+ const response = await api.getRollerConfig();
21+
22+ if (response.nextBatch === nextBatchTime) {
23+ // exponentially back off or cap at polling every minute
24+ setTime(Math.min(60 * ONE_SECOND, time + time));
25+ return;
26+ }
27+
28+ setTime(ONE_SECOND); // reset the timer
29+ setNextBatchTime(response.nextBatch);
30+ getPendingTransactions();
31+
32+ setTimeout(() => {
33+ if (!point.isDefault) {
34+ getInvites(); // This will also get pending txns
35+ }
36+ }, TEN_SECONDS); // Should this be more like a minute?
37+ }
38+ }, time);
39+
40+ return () => clearInterval(interval);
41+ }, [time, point, nextBatchTime, getPendingTransactions, getInvites]); // eslint-disable-line react-hooks/exhaustive-deps
42+}
src/lib/utils/roller.ts+38−14
@@ -66,7 +66,19 @@ export const generateHashAndSign = async (
66) => {
66) => {
67 if (walletType === WALLET_TYPES.METAMASK) {
67 if (walletType === WALLET_TYPES.METAMASK) {
68 const hash = await api.prepareForSigning(nonce, from, type, data);
68 const hash = await api.prepareForSigning(nonce, from, type, data);
69+ let sig;
70+
71+ // same issue as seen in getMetamaskAuthToken
72+ if (window.ethereum) {
73+ sig = window.ethereum.request({
74+ method: 'personal_sign',
75+ params: [hash, wallet.address],
76+ from: wallet.address,
77+ });
78+ } else {
69− const sig = await web3.eth.personal.sign(hash, wallet.address, '');
79+ sig = await web3.eth.personal.sign(hash, wallet.address, '');
80+ }
81+
70 return sig;
82 return sig;
71 } else if (walletType === WALLET_TYPES.WALLET_CONNECT) {
83 } else if (walletType === WALLET_TYPES.WALLET_CONNECT) {
72 if (!connector || !connector.connected) {
84 if (!connector || !connector.connected) {
@@ -93,7 +105,8 @@ export const configureKeys = async (
93 walletType: symbol,
105 walletType: symbol,
94 web3: any,
106 web3: any,
95 connector: WalletConnect | null,
107 connector: WalletConnect | null,
96− breach?: boolean
108+ breach?: boolean,
109+ force: boolean = false
97) => {
110) => {
98 const from = {
111 const from = {
99 ship: point, //ship to configure keys
112 ship: point, //ship to configure keys
@@ -130,7 +143,7 @@ export const configureKeys = async (
130 web3,
143 web3,
131 connector
144 connector
132 );
145 );
133− return api.configureKeys(sig, from, wallet.address, data);
146+ return api.configureKeys(sig, force, from, wallet.address, data);
134};
147};
135
148
136export const transferPointRequest = async (
149export const transferPointRequest = async (
@@ -143,7 +156,8 @@ export const transferPointRequest = async (
143 walletType: symbol,
156 walletType: symbol,
144 web3: any,
157 web3: any,
145 connector: WalletConnect | null,
158 connector: WalletConnect | null,
146− reset?: boolean
159+ reset?: boolean,
160+ force: boolean = false
147) => {
161) => {
148 const from = {
162 const from = {
149 ship: pointToTransfer,
163 ship: pointToTransfer,
@@ -167,7 +181,7 @@ export const transferPointRequest = async (
167 connector
181 connector
168 );
182 );
169
183
170− return api.transferPoint(sig, from, wallet.address, data);
184+ return api.transferPoint(sig, force, from, wallet.address, data);
171};
185};
172
186
173const proxyType = (proxy: Proxy) => {
187const proxyType = (proxy: Proxy) => {
@@ -189,15 +203,16 @@ const setProxy = async (
189 sig: Signature,
203 sig: Signature,
190 from: From,
204 from: From,
191 address: EthAddress,
205 address: EthAddress,
192− data: AddressParams
206+ data: AddressParams,
207+ force: boolean = false
193) => {
208) => {
194 switch (proxyAddressType) {
209 switch (proxyAddressType) {
195 case 'manage':
210 case 'manage':
196− return await api.setManagementProxy(sig, from, address, data);
211+ return await api.setManagementProxy(sig, force, from, address, data);
197 case 'spawn':
212 case 'spawn':
198− return await api.setSpawnProxy(sig, from, address, data);
213+ return await api.setSpawnProxy(sig, force, from, address, data);
199 case 'transfer':
214 case 'transfer':
200− return await api.setTransferProxy(sig, from, address, data);
215+ return await api.setTransferProxy(sig, force, from, address, data);
201 default:
216 default:
202 throw new Error(`Unknown proxyType ${proxyAddressType}`);
217 throw new Error(`Unknown proxyType ${proxyAddressType}`);
203 }
218 }
@@ -205,7 +220,7 @@ const setProxy = async (
205
220
206export const registerProxyAddress = async (
221export const registerProxyAddress = async (
207 api: RollerRPCAPI,
222 api: RollerRPCAPI,
208− wallet: any,
223+ wallet: any, // BridgeWallet
209 _point: Ship,
224 _point: Ship,
210 proxy: string,
225 proxy: string,
211 proxyAddressType: string,
226 proxyAddressType: string,
@@ -213,7 +228,8 @@ export const registerProxyAddress = async (
213 address: string,
228 address: string,
214 walletType: symbol,
229 walletType: symbol,
215 web3: any,
230 web3: any,
216− connector: WalletConnect | null
231+ connector: WalletConnect | null,
232+ force?: boolean
217) => {
233) => {
218 const from = {
234 const from = {
219 ship: _point,
235 ship: _point,
@@ -233,7 +249,15 @@ export const registerProxyAddress = async (
233 connector
249 connector
234 );
250 );
235
251
252+ return setProxy(
253+ api,
236− return setProxy(api, proxyAddressType, sig, from, wallet.address, data);
254+ proxyAddressType,
255+ sig,
256+ from,
257+ wallet.address,
258+ data,
259+ force
260+ );
237};
261};
238
262
239const getDataAndMethod = (args: L2TransactionArgs) => {
263const getDataAndMethod = (args: L2TransactionArgs) => {
@@ -248,7 +272,7 @@ const getDataAndMethod = (args: L2TransactionArgs) => {
248 sponsee,
272 sponsee,
249 ship,
273 ship,
250 } = args;
274 } = args;
251− const result: TransactionData = { data: { address }, method: () => null };
275+ const result: TransactionData = { data: { address }, method: async () => '' };
252
276
253 switch (args.type) {
277 switch (args.type) {
254 case 'transferPoint':
278 case 'transferPoint':
@@ -336,7 +360,7 @@ export const submitL2Transaction = async (args: L2TransactionArgs) => {
336 connector
360 connector
337 );
361 );
338
362
339− return method(sig, from, wallet.address, data);
363+ return method(sig, false, from, wallet.address, data);
340};
364};
341
365
342export const reticketL2Point = async () => {};
366export const reticketL2Point = async () => {};
src/store/lib/useControlledPointsStore.js+3−3
@@ -10,7 +10,7 @@ import { useNetwork } from '../network';
10import { ONE_MINUTE } from 'lib/constants';
10import { ONE_MINUTE } from 'lib/constants';
11
11
12export default function useControlledPointsStore() {
12export default function useControlledPointsStore() {
13− const { contracts } = useNetwork();
13+ const { contracts, web3 } = useNetwork();
14 const { wallet } = useWallet();
14 const { wallet } = useWallet();
15 const { getPoints, getPointsDetails } = useRoller();
15 const { getPoints, getPointsDetails } = useRoller();
16
16
@@ -19,7 +19,7 @@ export default function useControlledPointsStore() {
19 const syncControlledPoints = useCallback(async () => {
19 const syncControlledPoints = useCallback(async () => {
20 const _contracts = contracts.getOrElse(null);
20 const _contracts = contracts.getOrElse(null);
21 const _wallet = wallet.getOrElse(null);
21 const _wallet = wallet.getOrElse(null);
22− if (!_contracts || !_wallet) {
22+ if (!web3 || Nothing.hasInstance(web3) || !_contracts || !_wallet) {
23 return;
23 return;
24 }
24 }
25
25
@@ -115,7 +115,7 @@ export default function useControlledPointsStore() {
115 console.error('failed to fetch controlled points', error);
115 console.error('failed to fetch controlled points', error);
116 _setControlledPoints(Just(Result.Error(JSON.stringify(error))));
116 _setControlledPoints(Just(Result.Error(JSON.stringify(error))));
117 }
117 }
118− }, [contracts, wallet, getPoints, getPointsDetails]);
118+ }, [web3, contracts, wallet, getPoints, getPointsDetails]);
119
119
120 // sync controlled points whenever wallet or contracts changes
120 // sync controlled points whenever wallet or contracts changes
121 useEffect(() => {
121 useEffect(() => {
src/store/network.js+3−1
@@ -16,6 +16,7 @@ import { isDevelopment } from '../lib/flags';
16import { Grid } from 'indigo-react';
16import { Grid } from 'indigo-react';
17import View from 'components/View';
17import View from 'components/View';
18import Blinky from 'components/Blinky';
18import Blinky from 'components/Blinky';
19+import detectEthereumProvider from '@metamask/detect-provider';
19
20
20function _useNetwork(initialNetworkType = null) {
21function _useNetwork(initialNetworkType = null) {
21 const [networkType, setNetworkType] = useState(initialNetworkType);
22 const [networkType, setNetworkType] = useState(initialNetworkType);
@@ -26,7 +27,8 @@ function _useNetwork(initialNetworkType = null) {
26
27
27 useEffect(() => {
28 useEffect(() => {
28 (async () => {
29 (async () => {
29− if (window.ethereum) {
30+ const provider = await detectEthereumProvider();
31+ if (provider) {
30 try {
32 try {
31 setMetamask(true);
33 setMetamask(true);
32 setNetworkType(chainIdToNetworkType(window.ethereum.chainId));
34 setNetworkType(chainIdToNetworkType(window.ethereum.chainId));
src/views/Invite/Cohort.tsx+2−3
@@ -72,7 +72,6 @@ export default function InviteCohort() {
72 const { nextRoll } = useTimerStore();
72 const { nextRoll } = useTimerStore();
73
73
74 const {
74 const {
75− config,
76 ls,
75 ls,
77 generateInviteCodes,
76 generateInviteCodes,
78 getPendingTransactions,
77 getPendingTransactions,
@@ -379,7 +378,7 @@ export default function InviteCohort() {
379 You can generate up to
378 You can generate up to
380 <strong>{` ${point.l2Quota} `}</strong>
379 <strong>{` ${point.l2Quota} `}</strong>
381 invites. You will be able to generate another
380 invites. You will be able to generate another
382− <strong>{` ${config?.rollerQuota}`}</strong> invites on
381+ <strong>{` ${point.l2Allowance}`}</strong> invites on
383 <strong>{` ${ddmmmYYYY(nextQuotaTime)}`}</strong>.
382 <strong>{` ${ddmmmYYYY(nextQuotaTime)}`}</strong>.
384 </Box>
383 </Box>
385 )}
384 )}
@@ -498,7 +497,7 @@ export default function InviteCohort() {
498 <Box className="invite-limit-message">
497 <Box className="invite-limit-message">
499 You have hit your weekly invite limit. You will be able to generate
498 You have hit your weekly invite limit. You will be able to generate
500 another
499 another
501− <strong>{` ${config?.rollerQuota}`}</strong> invites on
500+ <strong>{` ${point.l2Allowance}`}</strong> invites on
502 <strong>{` ${ddmmmYYYY(nextQuotaTime)}`}</strong>.
501 <strong>{` ${ddmmmYYYY(nextQuotaTime)}`}</strong>.
503 </Box>
502 </Box>
504 ) : null}
503 ) : null}
src/views/Login/LoginSelector.tsx+10−5
@@ -79,14 +79,19 @@ export default function LoginSelector({
79
79
80 const selectMetamask = useCallback(async () => {
80 const selectMetamask = useCallback(async () => {
81 try {
81 try {
82+ if (!window.ethereum) {
83+ throw new Error(
84+ 'Device does not have an Ethereum provider available (is Metamask or Brave Wallet installed?)'
85+ );
86+ }
82 setMetamask(true);
87 setMetamask(true);
83 setMetamaskSelected(true);
88 setMetamaskSelected(true);
84− const accounts = await (window as any).ethereum.send(
89+ const accounts = await window.ethereum.request({
85− 'eth_requestAccounts'
90+ method: 'eth_requestAccounts',
86− );
91+ });
87− const wallet = new MetamaskWallet(accounts.result[0]);
92+ const wallet = new MetamaskWallet(accounts[0]);
88
93
89− const web3 = new Web3((window as any).ethereum);
94+ const web3 = new Web3(window.ethereum);
90 const authToken = await getAuthToken({
95 const authToken = await getAuthToken({
91 address: wallet.address,
96 address: wallet.address,
92 walletType: WALLET_TYPES.METAMASK,
97 walletType: WALLET_TYPES.METAMASK,
src/views/Login/Metamask.js+3−1
@@ -33,7 +33,9 @@ export default function Metamask({ className, goHome }) {
33 const onSubmit = useCallback(async () => {
33 const onSubmit = useCallback(async () => {
34 try {
34 try {
35 setMetamask(true);
35 setMetamask(true);
36− const accounts = await window.ethereum.send('eth_requestAccounts');
36+ const accounts = await window.ethereum.request({
37+ method: 'eth_requestAccounts',
38+ });
37 const wallet = new MetamaskWallet(accounts.result[0]);
39 const wallet = new MetamaskWallet(accounts.result[0]);
38
40
39 const web3 = new Web3(window.ethereum);
41 const web3 = new Web3(window.ethereum);
src/views/Points.scss+11
@@ -15,3 +15,14 @@
15 font-family: 'Inter';
15 font-family: 'Inter';
16 }
16 }
17}
17}
18+
19+div.migrated-info-box {
20+ margin-top: 30px;
21+ font-size: 14px;
22+ display: flex;
23+ flex-flow: row nowrap;
24+
25+ span.message {
26+ padding-left: 5px;
27+ }
28+}
src/views/Points.tsx+17−13
@@ -28,11 +28,12 @@ import L2PointHeader from 'components/L2/Headers/L2PointHeader';
28import IncomingPoint from 'components/L2/Points/IncomingPoint';
28import IncomingPoint from 'components/L2/Points/IncomingPoint';
29import LoadingOverlay from 'components/L2/LoadingOverlay';
29import LoadingOverlay from 'components/L2/LoadingOverlay';
30import PointList from 'components/L2/PointList';
30import PointList from 'components/L2/PointList';
31−import { Box, Text } from '@tlon/indigo-react';
31+import { Box, Icon, Text } from '@tlon/indigo-react';
32
32
33import './Points.scss';
33import './Points.scss';
34import { StarReleaseButton } from './Points/StarReleaseButton';
34import { StarReleaseButton } from './Points/StarReleaseButton';
35import { maybeGetResult } from 'lib/maybeGetResult';
35import { maybeGetResult } from 'lib/maybeGetResult';
36+import { isGalaxy, isStar } from 'lib/utils/point';
36
37
37export const isLocked = (details: any, contracts: any) =>
38export const isLocked = (details: any, contracts: any) =>
38 details.owner === contracts.linearSR ||
39 details.owner === contracts.linearSR ||
@@ -172,6 +173,11 @@ export default function Points() {
172 const displayEmptyState =
173 const displayEmptyState =
173 !loading && incomingPoints.length === 0 && allPoints.length === 0;
174 !loading && incomingPoints.length === 0 && allPoints.length === 0;
174
175
176+ // If the user has any stars or galaxies, give a polite heads up that
177+ // it can take some time for the roller be aware of L1 TXs
178+ const showMigrateNotice = !displayEmptyState &&
179+ allPoints.some(p => isStar(p.value) || isGalaxy(p.value));
180+
175 const starReleasing = starReleaseDetails
181 const starReleasing = starReleaseDetails
176 .map((s: any) => (s ? s.total > 0 : false))
182 .map((s: any) => (s ? s.total > 0 : false))
177 .getOrElse(false);
183 .getOrElse(false);
@@ -324,19 +330,17 @@ export default function Points() {
324 </Grid.Item>
330 </Grid.Item>
325 )}
331 )}
326
332
327− {/* {processingPoints.length > 0 && (
333+ {showMigrateNotice && (
328− <Grid.Item full as={Grid} gap={1} className="mv6">
334+ <Grid.Item full as={Box} className={'migrated-info-box'}>
329− <Grid.Item full as={H5}>
335+ <span className={'icon'}>
330− Awaiting L2 Rollup
336+ <Icon icon="Info" />
331− </Grid.Item>
337+ </span>
332− <Grid.Item
338+ <span className={'message'}>
333− full
339+ Not seeing a recently-migrated Layer 2 point? It may take a few
334− as={PointList}
340+ minutes for the public roller to be aware of the transaction.
335− points={processingPoints}
341+ </span>
336− processing
337− />
338 </Grid.Item>
342 </Grid.Item>
339− )} */}
343+ )}
340
344
341 <Footer>
345 <Footer>
342 <Grid>
346 <Grid>
src/views/UrbitID/ResetKeys/ResetExecute.js → src/views/UrbitID/ResetKeys/ResetExecute.tsxrenamed+140−77
@@ -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,9 @@ 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 { ReticketProgressCallback } from 'lib/types/L2Transaction';
28
30
29−const labelForProgress = progress => {
31+const labelForProgress = (progress: number) => {
30 if (progress <= 0) {
32 if (progress <= 0) {
31 return 'Starting...';
33 return 'Starting...';
32 } else if (progress <= TRANSACTION_PROGRESS.GENERATING) {
34 } else if (progress <= TRANSACTION_PROGRESS.GENERATING) {
@@ -44,9 +46,19 @@ const labelForProgress = progress => {
44 }
46 }
45};
47};
46
48
49+interface ResetExecuteProps {
50+ // a Maybe<UrbitWallet>
51+ newWallet: any;
52+ // a useState setter, accepts Maybe<UrbitWallet>
53+ setNewWallet: (args: any) => {};
54+}
55+
47−export default function ResetExecute({ newWallet, setNewWallet }) {
56+export default function ResetExecute({
57+ newWallet,
58+ setNewWallet,
59+}: ResetExecuteProps) {
48− const { popTo, names, reset } = useHistory();
60+ const { popTo, names, reset }: any = useHistory();
49− const { web3, contracts, networkType } = useNetwork();
61+ const { web3, contracts, networkType }: any = useNetwork();
50 const {
62 const {
51 wallet,
63 wallet,
52 setWalletType,
64 setWalletType,
@@ -54,94 +66,34 @@ export default function ResetExecute({ newWallet, setNewWallet }) {
54 setUrbitWallet,
66 setUrbitWallet,
55 walletType,
67 walletType,
56 walletHdPath,
68 walletHdPath,
57− } = useWallet();
69+ }: any = useWallet();
58− const { performL2Reticket } = useRoller();
70+ const { api, performL2Reticket } = useRoller();
59 const {
71 const {
60 point: { isL2 },
72 point: { isL2 },
61 } = useRollerStore();
73 } = useRollerStore();
62− const { pointCursor } = usePointCursor();
74+ const { pointCursor }: any = usePointCursor();
63− const { getDetails } = usePointCache();
75+ const { getDetails }: any = usePointCache();
64 const {
76 const {
65 signTransaction: wcSign,
77 signTransaction: wcSign,
66 sendTransaction: wcSend,
78 sendTransaction: wcSend,
67 connector,
79 connector,
68 } = useWalletConnect();
80 } = useWalletConnect();
81+ const { performL2SpawnReticket } = useReticketL2Spawn();
69
82
70− const [generalError, setGeneralError] = useState();
83+ const [generalError, setGeneralError] = useState<Error | undefined>();
71 const [progress, setProgress] = useState(0);
84 const [progress, setProgress] = useState(0);
72− const [needFunds, setNeedFunds] = useState();
85+ const [needFunds, setNeedFunds] = useState<string | boolean | undefined>();
73 const isDone = progress >= 1.0;
86 const isDone = progress >= 1.0;
87+ const point = need.point(pointCursor);
74
88
75 useBlockWindowClose();
89 useBlockWindowClose();
76
90
77− // start reticketing transactions on mount
78− useEffect(() => {
79− (async () => {
80− // due to react shenanigans we may need to wait for the connector
81− if (
82− walletType === WALLET_TYPES.WALLET_CONNECT &&
83− (!connector || !connector.connected)
84− ) {
85− setGeneralError(new Error('Awaiting WalletConnect connection...'));
86− return;
87− }
88− setGeneralError();
89−
90− const point = need.point(pointCursor);
91− const details = need.details(getDetails(point));
92− const networkRevision = convertToInt(details.keyRevisionNumber, 10);
93−
94− // see also comment in useEthereumTransaction
95− const txnSigner =
96− walletType === WALLET_TYPES.WALLET_CONNECT ? wcSign : undefined;
97− const txnSender =
98− walletType === WALLET_TYPES.WALLET_CONNECT ? wcSend : undefined;
99−
100− try {
101− if (isL2) {
102− console.log(newWallet.value);
103− // FIXME: the useEffect is called twice, which is fine since the duplicate
104− // L2 txs will be discarded
105− //
106− await performL2Reticket({
107− point,
108− to: newWallet.value.wallet.ownership.keys.address,
109− manager: newWallet.value.wallet.management.keys.address,
110− toWallet: newWallet.value.wallet,
111− fromWallet: need.wallet(wallet),
112− });
113− } else
114− await reticketPointBetweenWallets({
115− fromWallet: need.wallet(wallet),
116− fromWalletType: walletType,
117− fromWalletHdPath: walletHdPath,
118− toWallet: newWallet.value.wallet,
119− point: point,
120− web3: need.web3(web3),
121− contracts: need.contracts(contracts),
122− networkType,
123− onUpdate: handleUpdate,
124− nextRevision: networkRevision + 1,
125− txnSigner,
126− txnSender,
127− });
128− } catch (err) {
129− console.error(err);
130− setGeneralError(err);
131− }
132− })();
133− //NOTE this was using useLifecycle previously (which is useEffect without
134− // dependencies) and worked fine, but in the wc case we might need to
135− // wait for the connector to get set due to react state shenanigans.
136− // eslint-disable-next-line react-hooks/exhaustive-deps
137− }, [connector]);
138−
139 const goToRestart = useCallback(() => {
91 const goToRestart = useCallback(() => {
140 reset();
92 reset();
141 setNewWallet(Nothing());
93 setNewWallet(Nothing());
142 }, [reset, setNewWallet]);
94 }, [reset, setNewWallet]);
143
95
144− const loginAndGoHome = useCallback(() => {
96+ const loginAndGoHome = useCallback(async () => {
145 // set wallet state
97 // set wallet state
146 resetWallet();
98 resetWallet();
147 setWalletType(WALLET_TYPES.TICKET);
99 setWalletType(WALLET_TYPES.TICKET);
@@ -149,9 +101,16 @@ export default function ResetExecute({ newWallet, setNewWallet }) {
149 // (implicit) pointCursor stays the same
101 // (implicit) pointCursor stays the same
150 // redirect to point
102 // redirect to point
151 popTo(names.POINT);
103 popTo(names.POINT);
152− }, [resetWallet, setWalletType, setUrbitWallet, newWallet, popTo, names]);
104+ }, [
105+ resetWallet,
106+ setWalletType,
107+ setUrbitWallet,
108+ newWallet.value.wallet,
109+ popTo,
110+ names.POINT,
111+ ]);
153
112
154− const handleUpdate = useCallback(
113+ const handleUpdate: ReticketProgressCallback = useCallback(
155 ({ type, state, value }) => {
114 ({ type, state, value }) => {
156 switch (type) {
115 switch (type) {
157 case 'progress':
116 case 'progress':
@@ -167,6 +126,108 @@ export default function ResetExecute({ newWallet, setNewWallet }) {
167 [setProgress, setNeedFunds]
126 [setProgress, setNeedFunds]
168 );
127 );
169
128
129+ const performReticket = useCallback(async () => {
130+ // due to react shenanigans we may need to wait for the connector
131+ if (
132+ walletType === WALLET_TYPES.WALLET_CONNECT &&
133+ (!connector || !connector.connected)
134+ ) {
135+ setGeneralError(new Error('Awaiting WalletConnect connection...'));
136+ return;
137+ }
138+
139+ setGeneralError(undefined);
140+
141+ const l2point = await api.getPoint(point);
142+ const details = need.details(getDetails(point));
143+ const networkRevision = convertToInt(details.keyRevisionNumber, 10);
144+
145+ // see also comment in useEthereumTransaction
146+ const txnSigner =
147+ walletType === WALLET_TYPES.WALLET_CONNECT ? wcSign : undefined;
148+ const txnSender =
149+ walletType === WALLET_TYPES.WALLET_CONNECT ? wcSend : undefined;
150+
151+ try {
152+ // if L1 point with migrated spawn proxy (dominion = 'spawn')
153+ if (l2point.dominion === POINT_DOMINIONS.SPAWN) {
154+ await performL2SpawnReticket({
155+ fromWallet: need.wallet(wallet),
156+ fromWalletType: walletType,
157+ fromWalletHdPath: walletHdPath,
158+ toWallet: newWallet.value.wallet,
159+ to: newWallet.value.wallet.ownership.keys.address,
160+ point: point,
161+ web3: need.web3(web3),
162+ contracts: need.contracts(contracts),
163+ networkType,
164+ onUpdate: handleUpdate,
165+ nextRevision: networkRevision + 1,
166+ txnSigner,
167+ txnSender,
168+ });
169+ // If L2 point
170+ } else if (isL2) {
171+ await performL2Reticket({
172+ point,
173+ to: newWallet.value.wallet.ownership.keys.address,
174+ manager: newWallet.value.wallet.management.keys.address,
175+ toWallet: newWallet.value.wallet,
176+ fromWallet: need.wallet(wallet),
177+ onUpdate: handleUpdate,
178+ });
179+ // Fallback to L1 point flow
180+ } else {
181+ await reticketPointBetweenWallets({
182+ fromWallet: need.wallet(wallet),
183+ fromWalletType: walletType,
184+ fromWalletHdPath: walletHdPath,
185+ toWallet: newWallet.value.wallet,
186+ point: point,
187+ web3: need.web3(web3),
188+ contracts: need.contracts(contracts),
189+ networkType,
190+ onUpdate: handleUpdate,
191+ nextRevision: networkRevision + 1,
192+ txnSigner,
193+ txnSender,
194+ });
195+ }
196+ } catch (err) {
197+ console.error(err);
198+ setGeneralError(err);
199+ }
200+ }, [
201+ api,
202+ connector,
203+ contracts,
204+ getDetails,
205+ handleUpdate,
206+ isL2,
207+ networkType,
208+ newWallet.value.wallet,
209+ performL2Reticket,
210+ performL2SpawnReticket,
211+ point,
212+ wallet,
213+ walletHdPath,
214+ walletType,
215+ wcSend,
216+ wcSign,
217+ web3,
218+ ]);
219+
220+ useEffect(() => {
221+ if (!connector) {
222+ return;
223+ }
224+
225+ performReticket();
226+ // We want to perform the reticket only once, after the WalletConnect
227+ // connector has finished instantiating and setting up a websocket
228+ // eslint-disable-next-line react-hooks/exhaustive-deps
229+ }, [connector]);
230+
170 const renderAdditionalInfo = () => {
231 const renderAdditionalInfo = () => {
171 if (generalError) {
232 if (generalError) {
172 console.log(generalError);
233 console.log(generalError);
@@ -174,7 +235,9 @@ export default function ResetExecute({ newWallet, setNewWallet }) {
174 return (
235 return (
175 <>
236 <>
176 <Grid.Item full className="mt4">
237 <Grid.Item full className="mt4">
238+ <ErrorText className={''}>
177− <ErrorText>{generalError.message.toString()}</ErrorText>
239+ {generalError.message.toString()}
240+ </ErrorText>
178 </Grid.Item>
241 </Grid.Item>
179 <Grid.Item
242 <Grid.Item
180 full
243 full