on master help sign in
wip nonce store
5a2bc24yosoyubik
package.json+1−1
@@ -119,7 +119,7 @@
119 "pilot-release": "WITH_TEST_STATE=STAR_RELEASE npm run pilot",
119 "pilot-release": "WITH_TEST_STATE=STAR_RELEASE npm run pilot",
120 "pilot-residents": "WITH_TEST_STATE=RESIDENTS npm run pilot",
120 "pilot-residents": "WITH_TEST_STATE=RESIDENTS npm run pilot",
121 "pilot-l2": "BROWSER=NONE WITH_TEST_STATE=L2 HTTPS=false NODE_ENV=development npm-run-all pilot:setup start pilot:cleanup --continue-on-error",
121 "pilot-l2": "BROWSER=NONE WITH_TEST_STATE=L2 HTTPS=false NODE_ENV=development npm-run-all pilot:setup start pilot:cleanup --continue-on-error",
122− "pilot-l2-local": "REACT_APP_STUB_LOCAL=true BROWSER=NONE REACT_APP_L2=true HTTPS=false NODE_ENV=development npm run start --continue-on-error",
122+ "pilot-l2-local": "BROWSER=NONE REACT_APP_L2=true HTTPS=false NODE_ENV=development npm run start --continue-on-error",
123 "pilot-ropsten": "REACT_APP_ROPSTEN=true HTTPS=true npm-run-all start --continue-on-error",
123 "pilot-ropsten": "REACT_APP_ROPSTEN=true HTTPS=true npm-run-all start --continue-on-error",
124 "pilot:cleanup": "pkill -f ganache-cli",
124 "pilot:cleanup": "pkill -f ganache-cli",
125 "lint:check": "eslint 'src/**/*.{js,jsx,ts,tsx}'",
125 "lint:check": "eslint 'src/**/*.{js,jsx,ts,tsx}'",
src/components/L2/Dropdowns/AccountsDropdown.tsx+6−6
@@ -42,12 +42,12 @@ const AccountsDropdown = ({ showMigrate = false }: AccountsDropdownProps) => {
42
42
43 let networkKeysNotSet = true;
43 let networkKeysNotSet = true;
44
44
45− if (point) {
45+ // if (point) {
46− const details = need.details(getDetails(point));
46+ // const details = need.details(getDetails(point));
47− const isStarOrGalaxy = !isPlanet(point);
47+ // const isStarOrGalaxy = !isPlanet(point);
48− const networkRevision = convertToInt(details.keyRevisionNumber, 10);
48+ // const networkRevision = convertToInt(details.keyRevisionNumber, 10);
49− networkKeysNotSet = !currentL2 && isStarOrGalaxy && networkRevision === 0;
49+ // networkKeysNotSet = !currentL2 && isStarOrGalaxy && networkRevision === 0;
50− }
50+ // }
51
51
52 const [showModal, setShowModal] = useState(false);
52 const [showModal, setShowModal] = useState(false);
53
53
src/lib/useRoller.ts+117−62
@@ -1,4 +1,5 @@
1import { useCallback, useEffect, useMemo, useState } from 'react';
1import { useCallback, useEffect, useMemo, useState } from 'react';
2+import _ from 'lodash';
2import * as azimuth from 'azimuth-js';
3import * as azimuth from 'azimuth-js';
3import * as need from 'lib/need';
4import * as need from 'lib/need';
4import * as ob from 'urbit-ob';
5import * as ob from 'urbit-ob';
@@ -7,17 +8,24 @@ import { Just } from 'folktale/maybe';
7import { Invite } from 'lib/types/Invite';
8import { Invite } from 'lib/types/Invite';
8import { convertToInt } from './convertToInt';
9import { convertToInt } from './convertToInt';
9import { ensureHexPrefix } from 'form/formatters';
10import { ensureHexPrefix } from 'form/formatters';
10−import { getOutgoingPoints } from 'views/Points';
11import { isDevelopment, isRopsten } from './flags';
11import { isDevelopment, isRopsten } from './flags';
12−import { isPlanet } from './utils/point';
13import { hasPoint, generateInviteWallet } from './utils/roller';
12import { hasPoint, generateInviteWallet } from './utils/roller';
14import { ROLLER_HOSTS } from './constants';
13import { ROLLER_HOSTS } from './constants';
15import { signTransactionHash } from './authToken';
14import { signTransactionHash } from './authToken';
15+
16import { useNetwork } from 'store/network';
16import { useNetwork } from 'store/network';
17−import { usePointCache } from 'store/pointCache';
18import { usePointCursor } from 'store/pointCursor';
17import { usePointCursor } from 'store/pointCursor';
19import { useRollerStore } from 'store/roller';
18import { useRollerStore } from 'store/roller';
20import { useWallet } from 'store/wallet';
19import { useWallet } from 'store/wallet';
20+import {
21+ getStoredInvites,
22+ setPendingInvites,
23+ setStoredInvites,
24+} from 'store/storage/roller';
25+import { usePointCache } from 'store/pointCache';
26+import { getOutgoingPoints } from 'views/Points';
27+import { getSpawnNonce, getOwnerNonce } from './utils/nonce';
28+import { isPlanet } from './utils/point';
21
29
22import {
30import {
23 deriveNetworkKeys,
31 deriveNetworkKeys,
@@ -28,7 +36,6 @@ import {
28import {
36import {
29 Config,
37 Config,
30 Ship,
38 Ship,
31− L2Point,
32 Proxy,
39 Proxy,
33 RollerRPCAPI,
40 RollerRPCAPI,
34 Options,
41 Options,
@@ -36,6 +43,8 @@ import {
36 UnspawnedPoints,
43 UnspawnedPoints,
37 From,
44 From,
38 Hash,
45 Hash,
46+ SpawnParams,
47+ L2Data,
39} from '@urbit/roller-api';
48} from '@urbit/roller-api';
40
49
41import {
50import {
@@ -43,33 +52,16 @@ import {
43 getTimeToNextBatch,
52 getTimeToNextBatch,
44 spawn,
53 spawn,
45 transferPoint,
54 transferPoint,
55+ setSpawnProxy,
46} from './utils/roller';
56} from './utils/roller';
47
57
48−import {
58+function isSpawn(tx: L2Data | undefined): tx is SpawnParams {
49− getStoredInvites,
59+ return (tx as SpawnParams) !== undefined;
50− setPendingInvites,
60+}
51− setStoredInvites,
52−} from 'store/storage/roller';
53
61
54−const getProxyAndNonce = (
62+function isShipNumber(ship: number | string | undefined): ship is number {
55− point: L2Point,
63+ return (ship as number) !== undefined;
56− address: string
57−): { proxy?: string; nonce?: number } => {
58− return point.ownership?.managementProxy?.address === address
59− ? { proxy: 'manage', nonce: point.ownership?.managementProxy.nonce }
60− : point.ownership?.owner?.address === address
61− ? { proxy: 'own', nonce: point.ownership?.owner.nonce }
62− : point.ownership?.spawnProxy?.address === address
63− ? { proxy: 'spawn', nonce: point.ownership?.spawn?.owner.nonce }
64− : point.ownership?.votingProxy?.address === address
65− ? { proxy: 'vote', nonce: point.ownership?.votingProxy?.owner.nonce }
66− : point.ownership?.transferProxy?.address === address
67− ? {
68− proxy: 'transfer',
69− nonce: point.transferProxy?.votingProxy?.owner.nonce,
70− }
71− : { proxy: undefined, nonce: undefined };
72−};
64+}
73
65
74export default function useRoller() {
66export default function useRoller() {
75 const { wallet, authToken, authMnemonic, urbitWallet }: any = useWallet();
67 const { wallet, authToken, authMnemonic, urbitWallet }: any = useWallet();
@@ -86,6 +78,8 @@ export default function useRoller() {
86 setPendingTransactions,
78 setPendingTransactions,
87 setInvites,
79 setInvites,
88 currentL2,
80 currentL2,
81+ nonces,
82+ increaseNonce,
89 } = useRollerStore();
83 } = useRollerStore();
90 const [config, setConfig] = useState<Config | null>(null);
84 const [config, setConfig] = useState<Config | null>(null);
91
85
@@ -137,6 +131,7 @@ export default function useRoller() {
137 const _wallet = wallet.getOrElse(null);
131 const _wallet = wallet.getOrElse(null);
138 const _authToken = authToken.getOrElse(null);
132 const _authToken = authToken.getOrElse(null);
139 const _details = getDetails(_point);
133 const _details = getDetails(_point);
134+
140 if (!_contracts || !_web3 || !_wallet || !_authToken || !_details) {
135 if (!_contracts || !_web3 || !_wallet || !_authToken || !_details) {
141 // not using need because we want a custom error
136 // not using need because we want a custom error
142 throw new Error('Internal Error: Missing Contracts/Web3/Wallet');
137 throw new Error('Internal Error: Missing Contracts/Web3/Wallet');
@@ -147,9 +142,27 @@ export default function useRoller() {
147 "Auth key Error: A management mnemonic can't create invite codes"
142 "Auth key Error: A management mnemonic can't create invite codes"
148 );
143 );
149 }
144 }
150− const planets: UnspawnedPoints = await api.getUnspawned(_point);
145+ let planets: UnspawnedPoints = await api.getUnspawned(_point);
146+
151− const starInfo = await api.getPoint(_point);
147+ const pendingTxs = await api.getPendingByShip(_point);
148+
149+ const pendingSpawns = _.reduce(
150+ pendingTxs,
151+ (acc, pending) => {
152+ return isSpawn(pending.rawTx?.tx) &&
153+ isShipNumber(pending.rawTx?.tx.data.ship)
154+ ? acc.add(pending.rawTx?.tx.data.ship!)
155+ : acc;
156+ },
157+ new Set<number>()
158+ );
152
159
160+ console.log(pendingTxs, pendingSpawns);
161+
162+ planets = planets.filter((point: number) => !pendingSpawns.has(point));
163+
164+ const starInfo = nonces[_point];
165+ console.log(starInfo);
153 const tickets: {
166 const tickets: {
154 ticket: string;
167 ticket: string;
155 planet: number;
168 planet: number;
@@ -157,37 +170,37 @@ export default function useRoller() {
157 }[] = [];
170 }[] = [];
158 const requests: Promise<string>[] = [];
171 const requests: Promise<string>[] = [];
159
172
160− // TODO: replace with "currentNonce" from store, that would reflect the latest nonce
173+ const operator =
161− // Bridge has used for the star _wallet.address
174+ getSpawnNonce(starInfo, _wallet.address) ||
162− //
163− const { proxy, nonce } = getProxyAndNonce(starInfo, _wallet.address);
175+ getOwnerNonce(starInfo, _wallet.address);
164
176
165− if (!(proxy === 'own' || proxy === 'spawn') || nonce === undefined)
177+ if (operator === undefined)
166 throw new Error("Error: Address doesn't match proxy");
178 throw new Error("Error: Address doesn't match proxy");
167−
179+ console.log('operator', operator);
168 for (let i = 0; i < numInvites && planets[i]; i++) {
180 for (let i = 0; i < numInvites && planets[i]; i++) {
169 const planet = planets[i];
181 const planet = planets[i];
170− const nonceInc = i + nonce;
182+ const nonceInc = i + operator.nonce!;
171
183
172− const { ticket, owner } = await generateInviteWallet(
184+ const { ticket, inviteWallet } = await generateInviteWallet(
173 planet,
185 planet,
174 _authToken
186 _authToken
175 );
187 );
176−
188+ console.log(inviteWallet);
177 const spawnRequest = await spawn(
189 const spawnRequest = await spawn(
178 api,
190 api,
179 _wallet,
191 _wallet,
180 _point,
192 _point,
181− proxy,
193+ operator.proxy!,
182 nonceInc,
194 nonceInc,
183 planet
195 planet
184 );
196 );
197+ increaseNonce(_point, operator.proxy!);
185
198
186 const configureKeysRequest = await configureKeys(
199 const configureKeysRequest = await configureKeys(
187 api,
200 api,
188 _wallet,
201 _wallet,
189 planet,
202 planet,
190− proxy,
203+ operator.proxy!,
191 0,
204 0,
192 _details,
205 _details,
193 authToken,
206 authToken,
@@ -196,20 +209,34 @@ export default function useRoller() {
196 urbitWallet
209 urbitWallet
197 );
210 );
198
211
199− const transferPointRequest = await transferPoint(
212+ const setManagementProxyRequest = await setSpawnProxy(
200 api,
213 api,
201 _wallet,
214 _wallet,
202 planet,
215 planet,
203− proxy,
216+ operator.proxy!,
204 1,
217 1,
205− owner.keys.address
218+ inviteWallet.management.keys.address
206 );
219 );
207
220
221+ const transferPointRequest = await transferPoint(
222+ api,
223+ _wallet,
224+ planet,
225+ operator.proxy!,
226+ 2,
227+ inviteWallet.ownership.keys.address
228+ );
229+
230+ requests.push(
231+ spawnRequest,
208− requests.push(spawnRequest, configureKeysRequest, transferPointRequest);
232+ configureKeysRequest,
233+ setManagementProxyRequest,
234+ transferPointRequest
235+ );
209 tickets.push({
236 tickets.push({
210 ticket,
237 ticket,
211 planet,
238 planet,
212− owner: owner.keys.address,
239+ owner: inviteWallet.ownership.keys.address,
213 });
240 });
214 }
241 }
215
242
@@ -221,6 +248,7 @@ export default function useRoller() {
221 status: 'pending',
248 status: 'pending',
222 }));
249 }));
223
250
251+ console.log('latest update', nonces[_point]);
224 setPendingInvites(_point, pendingInvites);
252 setPendingInvites(_point, pendingInvites);
225 },
253 },
226 [
254 [
@@ -233,6 +261,8 @@ export default function useRoller() {
233 getDetails,
261 getDetails,
234 authMnemonic,
262 authMnemonic,
235 urbitWallet,
263 urbitWallet,
264+ nonces,
265+ increaseNonce,
236 ]
266 ]
237 );
267 );
238
268
@@ -343,7 +373,7 @@ export default function useRoller() {
343 const planet = possibleMissingInvites[i];
373 const planet = possibleMissingInvites[i];
344 if (!availableInvites.find(hasPoint(planet))) {
374 if (!availableInvites.find(hasPoint(planet))) {
345 console.log('MISSING IN AVAILABLE', planet);
375 console.log('MISSING IN AVAILABLE', planet);
346− const { ticket, owner } = await generateInviteWallet(
376+ const { ticket, inviteWallet } = await generateInviteWallet(
347 planet,
377 planet,
348 _authToken
378 _authToken
349 );
379 );
@@ -353,7 +383,7 @@ export default function useRoller() {
353 status: 'available',
383 status: 'available',
354 planet,
384 planet,
355 hash: '',
385 hash: '',
356− owner: owner.keys.address,
386+ owner: inviteWallet.ownership.keys.address,
357 });
387 });
358 }
388 }
359 }
389 }
@@ -371,14 +401,14 @@ export default function useRoller() {
371 }
401 }
372 },
402 },
373 [
403 [
374− api,
404+ // api,
375− pointCursor,
405+ // pointCursor,
376− setInvites,
406+ // setInvites,
377− setPendingTransactions,
407+ // setPendingTransactions,
378− authToken,
408+ // authToken,
379− contracts,
409+ // contracts,
380− controlledPoints,
410+ // controlledPoints,
381− getDetails,
411+ // getDetails,
382 ]
412 ]
383 );
413 );
384
414
@@ -394,6 +424,7 @@ export default function useRoller() {
394 interface AcceptInviteParams {
424 interface AcceptInviteParams {
395 point: Ship;
425 point: Ship;
396 to: EthAddress;
426 to: EthAddress;
427+ manager: EthAddress;
397 fromWallet: any; // TODO: wallet type
428 fromWallet: any; // TODO: wallet type
398 toWallet: any; // TODO: wallet type
429 toWallet: any; // TODO: wallet type
399 }
430 }
@@ -401,6 +432,7 @@ export default function useRoller() {
401 const acceptInvite = async ({
432 const acceptInvite = async ({
402 point,
433 point,
403 to,
434 to,
435+ manager,
404 fromWallet,
436 fromWallet,
405 toWallet,
437 toWallet,
406 }: AcceptInviteParams) => {
438 }: AcceptInviteParams) => {
@@ -408,10 +440,14 @@ export default function useRoller() {
408 console.log('azimuthPoint', azimuthPoint);
440 console.log('azimuthPoint', azimuthPoint);
409 const ownerAddress = azimuthPoint?.ownership?.owner?.address!;
441 const ownerAddress = azimuthPoint?.ownership?.owner?.address!;
410
442
443+ const operator =
444+ azimuthPoint.ownership &&
411− const { nonce } = getProxyAndNonce(azimuthPoint, ownerAddress);
445+ getOwnerNonce(azimuthPoint.ownership, ownerAddress);
446+
412− if (nonce === undefined) {
447+ if (operator === undefined)
413− throw new Error('Nonce unavailable');
448+ throw new Error("Error: Address doesn't match proxy");
414− }
449+
450+ const nonce = operator.nonce!;
415
451
416 // 1. Update networking keys
452 // 1. Update networking keys
417 // TODO: extract this to a useRoller#configureKeys function?
453 // TODO: extract this to a useRoller#configureKeys function?
@@ -445,11 +481,30 @@ export default function useRoller() {
445 ownerAddress,
481 ownerAddress,
446 keysData
482 keysData
447 );
483 );
484+ // TODO: not needed? next time we operate with this point we will login, and get
485+ // the latest nonce (updated with any nonce increase from possible pending txs)
486+ // increaseNonce(point, operator.proxy!);
448
487
488+ // 2. Set Management Proxy
489+ const managementData = { address: manager };
490+ const managementHash: Hash = await api.hashTransaction(
491+ nonce + 1,
492+ fromOwnerProxy,
493+ 'setManagementProxy',
494+ managementData
495+ );
496+
497+ await api.setManagementProxy(
498+ signTransactionHash(managementHash, Buffer.from(keysSigningKey, 'hex')),
499+ fromOwnerProxy,
500+ ownerAddress,
501+ managementData
502+ );
503+
449− // 2. Transfer point
504+ // 3. Transfer point
450 const transferData = { address: to, reset: false };
505 const transferData = { address: to, reset: false };
451 const transferHash: Hash = await api.hashTransaction(
506 const transferHash: Hash = await api.hashTransaction(
452− nonce + 1,
507+ nonce + 2,
453 fromOwnerProxy,
508 fromOwnerProxy,
454 'transferPoint',
509 'transferPoint',
455 transferData
510 transferData
src/lib/utils/nonce.tsnew file+157
@@ -0,0 +1,157 @@
1+import { Ownership } from '@urbit/roller-api';
2+
3+export const getProxyAndNonce = (
4+ owner: Ownership,
5+ address: string
6+): { proxy?: string; nonce?: number } =>
7+ owner.managementProxy?.address === address
8+ ? { proxy: 'manage', nonce: owner.managementProxy.nonce }
9+ : owner.owner?.address === address
10+ ? { proxy: 'own', nonce: owner.owner.nonce }
11+ : owner.spawnProxy?.address === address
12+ ? { proxy: 'spawn', nonce: owner.spawn?.owner.nonce }
13+ : owner.votingProxy?.address === address
14+ ? { proxy: 'vote', nonce: owner.votingProxy?.owner.nonce }
15+ : owner.transferProxy?.address === address
16+ ? {
17+ proxy: 'transfer',
18+ nonce: owner.votingProxy?.nonce,
19+ }
20+ : { proxy: undefined, nonce: undefined };
21+
22+export const getManagementNonce = (
23+ owner: Ownership,
24+ address: string
25+): { nonce?: number; proxy?: string } | undefined =>
26+ owner.managementProxy?.address === address
27+ ? { nonce: owner.managementProxy.nonce, proxy: 'manage' }
28+ : undefined;
29+
30+export const getTransferNonce = (
31+ owner: Ownership,
32+ address: string
33+): { nonce?: number; proxy?: string } | undefined =>
34+ owner.transferProxy?.address === address
35+ ? { nonce: owner.transferProxy.nonce, proxy: 'transfer' }
36+ : undefined;
37+
38+export const getOwnerNonce = (
39+ owner: Ownership,
40+ address: string
41+): { nonce?: number; proxy?: string } | undefined =>
42+ owner.owner?.address === address
43+ ? { nonce: owner.owner.nonce, proxy: 'own' }
44+ : undefined;
45+
46+export const getSpawnNonce = (
47+ owner: Ownership,
48+ address: string
49+): { nonce?: number; proxy?: string } | undefined =>
50+ owner.spawnProxy?.address === address
51+ ? { nonce: owner.spawnProxy.nonce, proxy: 'spawn' }
52+ : undefined;
53+
54+export const getVotingNonce = (
55+ owner: Ownership,
56+ address: string
57+): { nonce?: number; proxy?: string } | undefined =>
58+ owner.votingProxy?.address === address
59+ ? { nonce: owner.votingProxy.nonce, proxy: 'vote' }
60+ : undefined;
61+
62+export const increaseProxyNonce = (
63+ owner: Ownership,
64+ proxy: string
65+): Ownership | undefined =>
66+ 'manage' === proxy
67+ ? {
68+ ...owner,
69+ managementProxy: {
70+ ...owner.managementProxy,
71+ nonce: owner.managementProxy?.nonce
72+ ? owner.managementProxy?.nonce + 1
73+ : 1,
74+ },
75+ }
76+ : 'own' === proxy
77+ ? {
78+ ...owner,
79+ owner: {
80+ ...owner.owner,
81+ nonce: owner.owner?.nonce ? owner.owner?.nonce + 1 : 1,
82+ },
83+ }
84+ : 'spawn' === proxy
85+ ? {
86+ ...owner,
87+ spawnProxy: {
88+ ...owner.spawnProxy,
89+ nonce: owner.spawnProxy?.nonce ? owner.spawnProxy?.nonce + 1 : 1,
90+ },
91+ }
92+ : 'vote' === proxy
93+ ? {
94+ ...owner,
95+ votingProxy: {
96+ ...owner.votingProxy,
97+ nonce: owner.votingProxy?.nonce ? owner.votingProxy?.nonce + 1 : 1,
98+ },
99+ }
100+ : 'transfer' === proxy
101+ ? {
102+ ...owner,
103+ transferProxy: {
104+ ...owner.transferProxy,
105+ nonce: owner.transferProxy?.nonce
106+ ? owner.transferProxy?.nonce + 1
107+ : 1,
108+ },
109+ }
110+ : undefined;
111+
112+export const setProxyNonce = (
113+ owner: Ownership,
114+ proxy: string,
115+ nonce: number
116+): Ownership | undefined =>
117+ 'manage' === proxy
118+ ? {
119+ ...owner,
120+ managementProxy: {
121+ ...owner.managementProxy,
122+ nonce,
123+ },
124+ }
125+ : 'own' === proxy
126+ ? {
127+ ...owner,
128+ owner: {
129+ ...owner.owner,
130+ nonce,
131+ },
132+ }
133+ : 'spawn' === proxy
134+ ? {
135+ ...owner,
136+ spawnProxy: {
137+ ...owner.spawnProxy,
138+ nonce,
139+ },
140+ }
141+ : 'vote' === proxy
142+ ? {
143+ ...owner,
144+ votingProxy: {
145+ ...owner.voteProxy,
146+ nonce,
147+ },
148+ }
149+ : 'transfer' === proxy
150+ ? {
151+ ...owner,
152+ transferProxy: {
153+ ...owner.transferProxy,
154+ nonce,
155+ },
156+ }
157+ : undefined;
src/lib/utils/roller.ts+31−6
@@ -8,10 +8,7 @@ import {
8 CRYPTO_SUITE_VERSION,
8 CRYPTO_SUITE_VERSION,
9} from 'lib/keys';
9} from 'lib/keys';
10import { addHexPrefix } from 'lib/utils/address';
10import { addHexPrefix } from 'lib/utils/address';
11−import {
12− makeDeterministicTicket,
11+import { makeDeterministicTicket, generateWallet } from 'lib/walletgen';
13− generateOwnershipWallet,
14−} from 'lib/walletgen';
15
12
16export const SECOND = 1000;
13export const SECOND = 1000;
17export const MINUTE = SECOND * 60;
14export const MINUTE = SECOND * 60;
@@ -35,9 +32,9 @@ export const isL2 = (dom?: string) => dom === 'l2' || dom === 'spawn';
35
32
36export const generateInviteWallet = async (point: number, seed: string) => {
33export const generateInviteWallet = async (point: number, seed: string) => {
37 const ticket = makeDeterministicTicket(point, seed);
34 const ticket = makeDeterministicTicket(point, seed);
38− const owner = await generateOwnershipWallet(point, ticket);
35+ const inviteWallet = await generateWallet(point, ticket, true);
39
36
40− return { ticket, owner };
37+ return { ticket, inviteWallet };
41};
38};
42
39
43export const spawn = async (
40export const spawn = async (
@@ -136,5 +133,33 @@ export const transferPoint = async (
136 return api.transferPoint(sig, from, _wallet.address, data);
133 return api.transferPoint(sig, from, _wallet.address, data);
137};
134};
138
135
136+export const setSpawnProxy = async (
137+ api: any,
138+ _wallet: any,
139+ _point: number,
140+ proxy: string,
141+ nonce: number,
142+ address: string
143+) => {
144+ const from = {
145+ ship: _point,
146+ proxy,
147+ };
148+ const managementData = { address };
149+ const managementHash = await api.hashTransaction(
150+ nonce,
151+ from,
152+ 'setManagementProxy',
153+ managementData
154+ );
155+
156+ return api.setManagementProxy(
157+ signTransactionHash(managementHash, _wallet.privateKey),
158+ from,
159+ _wallet.address,
160+ managementData
161+ );
162+};
163+
139export const hasPoint = (point: number) => (invite: Invite) =>
164export const hasPoint = (point: number) => (invite: Invite) =>
140 invite.planet === point;
165 invite.planet === point;
src/store/lib/useDetailsStore.js+29−2
@@ -2,10 +2,33 @@ import { useState, useCallback } from 'react';
2import { Just, Nothing } from 'folktale/maybe';
2import { Just, Nothing } from 'folktale/maybe';
3import * as azimuth from 'azimuth-js';
3import * as azimuth from 'azimuth-js';
4
4
5+import useRoller from '../../lib/useRoller';
5import { useNetwork } from '../network';
6import { useNetwork } from '../network';
6
7
8+const toL1Details = point => {
9+ return {
10+ dominion: 'l2',
11+ active: true,
12+ authenticationKey: point.network.keys.auth,
13+ continuityNumber: point.network.keys.rift,
14+ cryptoSuiteVersion: point.network.keys.suite,
15+ encryptionKey: point.network.keys.crypt,
16+ escapeRequested: point.network.escape ? true : false,
17+ escapeRequestedTo: point.network.escape,
18+ hasSponsor: point.network.sponsor.has,
19+ keyRevisionNumber: point.network.keys.life,
20+ managementProxy: point.ownership.managementProxy.address,
21+ owner: point.ownership.owner.address,
22+ spawnProxy: point.ownership.spawnProxy.address,
23+ sponsor: point.network.sponsor.who,
24+ transferProxy: point.ownership.transferProxy.address,
25+ votingProxy: point.ownership.votingProxy.address,
26+ };
27+};
28+
7export default function useDetailsStore() {
29export default function useDetailsStore() {
8 const { contracts } = useNetwork();
30 const { contracts } = useNetwork();
31+ const { api } = useRoller();
9 const [detailsCache, _setDetailsCache] = useState({});
32 const [detailsCache, _setDetailsCache] = useState({});
10
33
11 const addToDetails = useCallback(
34 const addToDetails = useCallback(
@@ -32,12 +55,16 @@ export default function useDetailsStore() {
32 }
55 }
33
56
34 // fetch point details
57 // fetch point details
58+ const l2Point = await api.getPoint(point);
59+ const details =
60+ l2Point && l2Point.dominion === 'l2'
61+ ? toL1Details(l2Point)
35− const details = await azimuth.azimuth.getPoint(_contracts, point);
62+ : await azimuth.azimuth.getPoint(_contracts, point);
36 addToDetails({
63 addToDetails({
37 [point]: details,
64 [point]: details,
38 });
65 });
39 },
66 },
40− [contracts, addToDetails]
67+ [api, contracts, addToDetails]
41 );
68 );
42
69
43 return {
70 return {
src/store/roller.ts+31−1
@@ -1,7 +1,8 @@
1import create from 'zustand';
1import create from 'zustand';
2−import { L2Point, PendingTransaction } from '@urbit/roller-api/build';
2+import { L2Point, PendingTransaction, Ownership } from '@urbit/roller-api';
3
3
4import { HOUR, isL2 } from 'lib/utils/roller';
4import { HOUR, isL2 } from 'lib/utils/roller';
5+import { increaseProxyNonce, setProxyNonce } from 'lib/utils/nonce';
5import { Invite } from 'lib/types/Invite';
6import { Invite } from 'lib/types/Invite';
6
7
7export interface RollerStore {
8export interface RollerStore {
@@ -10,6 +11,17 @@ export interface RollerStore {
10 pendingTransactions: PendingTransaction[];
11 pendingTransactions: PendingTransaction[];
11 currentPoint: L2Point | null;
12 currentPoint: L2Point | null;
12 currentL2: boolean;
13 currentL2: boolean;
14+ nonces: {
15+ [point: number]: Ownership;
16+ };
17+ increaseNonce: (point: number, proxy: string) => void;
18+ setNonce: (
19+ point: number,
20+ owner: Ownership,
21+ proxy: string,
22+ nonce: number
23+ ) => void;
24+ setNonces: (point: number, owner: Ownership) => void;
13 invites: Invite[];
25 invites: Invite[];
14 recentlyCompleted: number;
26 recentlyCompleted: number;
15 setNextBatchTime: (nextBatchTime: number) => void;
27 setNextBatchTime: (nextBatchTime: number) => void;
@@ -27,6 +39,24 @@ export const useRollerStore = create<RollerStore>(set => ({
27 currentL2: false,
39 currentL2: false,
28 invites: [],
40 invites: [],
29 recentlyCompleted: 0,
41 recentlyCompleted: 0,
42+ nonces: {},
43+ increaseNonce: (point: number, proxy: string) =>
44+ set(state => {
45+ const owner = increaseProxyNonce(state.nonces[point], proxy);
46+ console.log(point, proxy, owner);
47+ if (!owner) throw new Error("Can't increase nonce for this proxy");
48+ return { ...state, nonces: { ...state.nonces, [point]: owner } };
49+ }),
50+ setNonce: (point: number, owner: Ownership, proxy: string, nonce: number) =>
51+ set(state => {
52+ const updatedOwner = setProxyNonce(owner, proxy, nonce);
53+ if (!updatedOwner) throw new Error("Can't set nonce for this proxy");
54+ return { ...state, nonces: { ...state.nonces, [point]: updatedOwner } };
55+ }),
56+ setNonces: (point: number, owner: Ownership) =>
57+ set(state => {
58+ return { ...state, nonces: { ...state.nonces, [point]: owner } };
59+ }),
30 setNextBatchTime: (nextBatchTime: number) => set(() => ({ nextBatchTime })),
60 setNextBatchTime: (nextBatchTime: number) => set(() => ({ nextBatchTime })),
31 setNextRoll: (nextRoll: string) => set(() => ({ nextRoll })),
61 setNextRoll: (nextRoll: string) => set(() => ({ nextRoll })),
32 setPendingTransactions: (pendingTransactions: PendingTransaction[]) =>
62 setPendingTransactions: (pendingTransactions: PendingTransaction[]) =>
src/views/Activate/MasterKeyTransfer.tsx+2−6
@@ -30,6 +30,7 @@ const MasterKeyTransfer = () => {
30 await acceptInvite({
30 await acceptInvite({
31 point: derivedPoint.value,
31 point: derivedPoint.value,
32 to: derivedWallet.value.ownership.keys.address,
32 to: derivedWallet.value.ownership.keys.address,
33+ manager: derivedWallet.value.management.keys.address,
33 toWallet: derivedWallet.value,
34 toWallet: derivedWallet.value,
34 fromWallet: inviteWallet.value,
35 fromWallet: inviteWallet.value,
35 });
36 });
@@ -37,12 +38,7 @@ const MasterKeyTransfer = () => {
37 console.error(error);
38 console.error(error);
38 setError(error);
39 setError(error);
39 }
40 }
40− }, [
41− derivedPoint.value,
41+ }, [derivedWallet, acceptInvite, derivedPoint.value, inviteWallet.value]);
42− derivedWallet.value,
43− inviteWallet.value,
44− acceptInvite,
45− ]);
46
42
47 useFadeIn();
43 useFadeIn();
48
44
src/views/Login/Ticket.js+21−14
@@ -22,6 +22,7 @@ import useImpliedPoint from 'lib/useImpliedPoint';
22import useLoginView from 'lib/useLoginView';
22import useLoginView from 'lib/useLoginView';
23import { patp2dec } from 'lib/patp2dec';
23import { patp2dec } from 'lib/patp2dec';
24import { timeout } from 'lib/timeout';
24import { timeout } from 'lib/timeout';
25+import useRoller from 'lib/useRoller';
25
26
26import BridgeForm from 'form/BridgeForm';
27import BridgeForm from 'form/BridgeForm';
27import Condition from 'form/Condition';
28import Condition from 'form/Condition';
@@ -96,6 +97,7 @@ export default function Ticket({ className, goHome }) {
96 const { setPointCursor } = usePointCursor();
97 const { setPointCursor } = usePointCursor();
97 const impliedPoint = useImpliedPoint();
98 const impliedPoint = useImpliedPoint();
98 const didWarn = useRef(false);
99 const didWarn = useRef(false);
100+ const { api, getPoints } = useRoller();
99
101
100 const validateForm = useCallback((values, errors) => {
102 const validateForm = useCallback((values, errors) => {
101 didWarn.current = false;
103 didWarn.current = false;
@@ -134,7 +136,7 @@ export default function Ticket({ className, goHome }) {
134 : values.ticket;
136 : values.ticket;
135
137
136 try {
138 try {
137− const _contracts = need.contracts(contracts);
139+ // const _contracts = need.contracts(contracts);
138 const point = patp2dec(values.point);
140 const point = patp2dec(values.point);
139
141
140 await timeout(16); // allow ui events to flush
142 await timeout(16); // allow ui events to flush
@@ -143,19 +145,24 @@ export default function Ticket({ className, goHome }) {
143 point,
145 point,
144 values.passphrase
146 values.passphrase
145 );
147 );
146−
147− const [isOwner, isTransferProxy] = await Promise.all([
148+ const [ownedPoints, incomingPoints] = await Promise.all([
148− azimuth.azimuth.isOwner(
149− _contracts,
150− point,
151− urbitWallet.ownership.keys.address
149+ getPoints('own', urbitWallet.ownership.keys.address),
152− ),
153− azimuth.azimuth.isTransferProxy(
154− _contracts,
155− point,
156− urbitWallet.ownership.keys.address
150+ getPoints('transfer', urbitWallet.ownership.keys.address),
157− ),
158 ]);
151 ]);
152+ const isOwner = ownedPoints.length !== 0;
153+ const isTransferProxy = incomingPoints.length !== 0;
154+ // const [isOwner, isTransferProxy] = await Promise.all([
155+ // azimuth.azimuth.isOwner(
156+ // _contracts,
157+ // point,
158+ // urbitWallet.ownership.keys.address
159+ // ),
160+ // azimuth.azimuth.isTransferProxy(
161+ // _contracts,
162+ // point,
163+ // urbitWallet.ownership.keys.address
164+ // ),
165+ // ]);
159
166
160 const noPermissions = !isOwner && !isTransferProxy;
167 const noPermissions = !isOwner && !isTransferProxy;
161 // warn the user
168 // warn the user
@@ -178,7 +185,7 @@ export default function Ticket({ className, goHome }) {
178 };
185 };
179 }
186 }
180 },
187 },
181− [contracts, setPointCursor, setUrbitWallet]
188+ [setPointCursor, setUrbitWallet, getPoints]
182 );
189 );
183
190
184 const validate = useMemo(
191 const validate = useMemo(
src/views/Point/Point.js+60−27
@@ -128,6 +128,8 @@ export default function Point() {
128 const { pointCursor } = usePointCursor();
128 const { pointCursor } = usePointCursor();
129 const point = need.point(pointCursor);
129 const point = need.point(pointCursor);
130 const { getDetails } = usePointCache();
130 const { getDetails } = usePointCache();
131+ // sync the current cursor
132+ useSyncExtras([point]);
131
133
132 const { api, getInvites } = useRoller();
134 const { api, getInvites } = useRoller();
133 const {
135 const {
@@ -137,39 +139,69 @@ export default function Point() {
137 setCurrentPoint,
139 setCurrentPoint,
138 setPendingTransactions,
140 setPendingTransactions,
139 currentL2,
141 currentL2,
142+ setNonces,
143+ increaseNonce,
144+ nonces,
140 } = useRollerStore();
145 } = useRollerStore();
141
146
142− const pointSize = azimuth.getPointSize(point);
147+ // const pointSize = azimuth.getPointSize(point);
143− const details = need.details(getDetails(point));
148+ // const details = need.details(getDetails(point));
149+ // const details = getDetails(point);
150+ // console.log(details);
144− const isStarOrGalaxy = pointSize !== azimuth.PointSize.Planet;
151+ // const isStarOrGalaxy = pointSize !== azimuth.PointSize.Planet;
152+ // const networkRevision =
153+ // Just.hasInstance(details) &&
145− const networkRevision = convertToInt(details.keyRevisionNumber, 10);
154+ // convertToInt(need.details(details).keyRevisionNumber, 10);
155+ // console.log(networkRevision);
146− const networkKeysNotSet =
156+ const networkKeysNotSet = false;
147− !currentL2 && isStarOrGalaxy && networkRevision === 0;
157+ // networkRevision && !currentL2 && isStarOrGalaxy && networkRevision === 0;
148
158
149 const [showModal, setShowModal] = useState(networkKeysNotSet);
159 const [showModal, setShowModal] = useState(networkKeysNotSet);
150
160
151− useEffect(() => {
161+ // useEffect(() => {
152− if (networkKeysNotSet) {
162+ // if (networkKeysNotSet) {
153− setShowModal(true);
163+ // setShowModal(true);
154− }
164+ // }
155− }, [networkKeysNotSet]);
165+ // }, [networkKeysNotSet]);
156−
166+
157− useEffect(() => {
167+ useEffect(
168+ () => {
158− const loadL2Info = async () => {
169+ const loadL2Info = async () => {
159− const getTransactions = async () => {
170+ const getTransactions = async () => {
160− const pointInfo = await api.getPoint(Number(point));
171+ const pointInfo = await api.getPoint(Number(point));
172+ const pendingTxs = await api.getPendingByShip(Number(point));
161− if (isDevelopment) {
173+ if (isDevelopment) {
162− console.log('POINT INFO', pointInfo);
174+ console.log('POINT INFO', pointInfo);
163− }
175+ }
176+ setNonces(point, pointInfo.ownership);
177+
178+ for (let index = 0; index < pendingTxs.length; index++) {
179+ const proxy = pendingTxs[index].rawTx?.from?.proxy;
180+ console.log(pointInfo.ownership, proxy);
181+ increaseNonce(point, proxy);
182+ }
164− setCurrentPoint(pointInfo);
183+ setCurrentPoint(pointInfo);
165− getInvites(isL2(pointInfo.dominion));
184+ getInvites(isL2(pointInfo.dominion));
185+ };
186+
187+ getTransactions();
166 };
188 };
167
189
168− getTransactions();
169− };
170−
171− loadL2Info();
190+ loadL2Info();
191+ },
192+ [
193+ // TODO: adding this causes the effect to be called constantly...
194+ // api,
195+ // point,
196+ // authToken,
172− }, [api, point, authToken, setPendingTransactions]); // eslint-disable-line
197+ // setPendingTransactions,
198+ // nonces,
199+ // setCurrentPoint,
200+ // increaseNonce,
201+ // getInvites,
202+ // setNonces,
203+ ]
204+ );
173
205
174 const { isParent, canManage, canSpawn, canVote } = useCurrentPermissions();
206 const { isParent, canManage, canSpawn, canVote } = useCurrentPermissions();
175
207
@@ -256,7 +288,8 @@ export default function Point() {
256 <div className="transaction">
288 <div className="transaction">
257 <Row className="title-row">
289 <Row className="title-row">
258 <div className="title">
290 <div className="title">
291+ {numPending} Planet
259− {numPending} Planet{numPending > 1 ? 's' : ''} Spawned
292+ {numPending > 1 ? 's' : ''} Spawned
260 </div>
293 </div>
261 <div className="rollup-timer">
294 <div className="rollup-timer">
262 <Icon icon="Clock" />
295 <Icon icon="Clock" />
src/views/Points.js+4−2
@@ -172,13 +172,15 @@ export default function Points() {
172 ),
172 ),
173 [getDetails, controlledPoints, _contracts]
173 [getDetails, controlledPoints, _contracts]
174 );
174 );
175−
176 // if we can only interact with a single point, forget about the existence
175 // if we can only interact with a single point, forget about the existence
177 // of this page and jump to the point page.
176 // of this page and jump to the point page.
178 // if there are any pending transfers, incoming or outgoing, stay on this
177 // if there are any pending transfers, incoming or outgoing, stay on this
179 // page, because those can only be completed/cancelled here.
178 // page, because those can only be completed/cancelled here.
180 useEffect(() => {
179 useEffect(() => {
180+ if (
181+ Nothing.hasInstance(outgoingPoints) ||
181− if (outgoingPoints.length < 1 || Nothing.hasInstance(starReleaseDetails)) {
182+ Nothing.hasInstance(starReleaseDetails)
183+ ) {
182 return;
184 return;
183 }
185 }
184 controlledPoints.matchWith({
186 controlledPoints.matchWith({