on master help sign in
bridge/src/lib/utils/roller.ts
404 lines 9.9 kB history raw
1import { Just, Nothing } from 'folktale/maybe';
2import { randomHex } from 'web3-utils';
3import { SignClient as WalletConnect } from '@walletconnect/sign-client/dist/types/client';
4import RollerRPCAPI, {
5 Proxy,
6 Signature,
7 From,
8 EthAddress,
9 AddressParams,
10 Ship,
11 SpawnParams,
12 L2Data,
13 PendingTransaction,
14} from '@urbit/roller-api';
16import { Invite } from 'lib/types/Invite';
17import { signTransactionHash } from 'lib/authToken';
18import { deriveNetworkKeys, CRYPTO_SUITE_VERSION } from 'lib/keys';
19import { addHexPrefix } from 'lib/utils/address';
20import { makeDeterministicTicket, generateWallet } from 'lib/walletgen';
21import { WALLET_TYPES } from 'lib/constants';
22import { L2TransactionArgs, TransactionData } from 'lib/types/L2Transaction';
24export const SECOND = 1000;
25export const MINUTE = SECOND * 60;
26export const HOUR = MINUTE * 60;
28export const padZero = (amount: number) =>
29 `${amount < 10 && amount >= 0 ? '0' : ''}${amount}`;
31export const hasPoint = (point: number) => (invite: Invite) =>
32 invite.planet === point;
34export const getTimeToNextBatch = (nextBatch: number, now: number) => {
35 const toNext = Math.max(nextBatch - now, 0);
36 const hours = Math.floor(toNext / HOUR);
37 const minutes = Math.floor((toNext - hours * HOUR) / MINUTE);
38 const seconds = Math.floor(
39 (toNext - hours * HOUR - minutes * MINUTE) / SECOND
40 );
42 return `${padZero(hours)}h ${padZero(minutes)}m ${padZero(seconds)}s`;
43};
45export const isL2 = (dom?: string) => dom === 'l2';
47export const isL2Spawn = (dom?: string) => dom === 'l2' || dom === 'spawn';
49export const generateInviteWallet = async (point: number, seed: string) => {
50 const ticket = makeDeterministicTicket(point, seed);
51 const inviteWallet = await generateWallet(point, ticket, true);
53 return { ticket, inviteWallet };
54};
56export const generateHashAndSign = async (
57 api: RollerRPCAPI,
58 wallet: any,
59 nonce: number,
60 from: From,
61 type: string,
62 data: any,
63 walletType: symbol,
64 web3: any,
65 connector: WalletConnect | null,
66 isConnected: Function,
67 signPersonalMessage: Function
68) => {
69 if (walletType === WALLET_TYPES.METAMASK) {
70 const hash = await api.prepareForSigning(nonce, from, type, data);
71 let sig;
73 // same issue as seen in getMetamaskAuthToken
74 if (window.ethereum) {
75 sig = window.ethereum.request({
76 method: 'personal_sign',
77 params: [hash, wallet.address],
78 from: wallet.address,
79 });
80 } else {
81 sig = await web3.eth.personal.sign(hash, wallet.address, '');
82 }
84 return sig;
85 } else if (walletType === WALLET_TYPES.WALLET_CONNECT) {
86 if (!connector || !isConnected()) {
87 throw new Error('Awaiting WalletConnect connection...');
88 }
89 const hash = await api.prepareForSigning(nonce, from, type, data);
90 const sig = await signPersonalMessage({
91 message: hash,
92 address: wallet.address,
93 });
94 return sig;
95 } else {
96 const hash = await api.getUnsignedTx(nonce, from, type, data);
97 const sig = await signTransactionHash(hash, wallet.privateKey);
98 return sig;
99 }
100};
102export const configureKeys = async (
103 api: RollerRPCAPI,
104 wallet: any,
105 point: Ship,
106 proxy: string,
107 nonce: number,
108 networkSeed: any,
109 walletType: symbol,
110 web3: any,
111 connector: WalletConnect | null,
112 isConnected: Function,
113 signPersonalMessage: Function,
114 breach?: boolean,
115 force: boolean = false
116) => {
117 const from = {
118 ship: point, //ship to configure keys
119 proxy,
120 };
122 // TODO: do something here?
123 if (Nothing.hasInstance(networkSeed)) {
124 console.log("Network key Error: couldn't derive network keys");
125 // throw new Error("Network key Error: couldn't derive network keys");
126 }
128 const seed = Just.hasInstance(networkSeed)
129 ? networkSeed.value
130 : randomHex(32);
132 const pair = deriveNetworkKeys(seed);
134 const data = {
135 encrypt: addHexPrefix(pair.crypt.public),
136 auth: addHexPrefix(pair.auth.public),
137 cryptoSuite: String(CRYPTO_SUITE_VERSION),
138 breach: breach || false,
139 };
141 const sig = await generateHashAndSign(
142 api,
143 wallet,
144 nonce,
145 from,
146 'configureKeys',
147 data,
148 walletType,
149 web3,
150 connector,
151 signPersonalMessage,
152 isConnected
153 );
154 return api.configureKeys(sig, force, from, wallet.address, data);
155};
157export const transferPointRequest = async (
158 api: RollerRPCAPI,
159 wallet: any,
160 pointToTransfer: Ship,
161 proxy: string,
162 nonce: number,
163 address: string,
164 walletType: symbol,
165 web3: any,
166 connector: WalletConnect | null,
167 isConnected: Function,
168 signPersonalMessage: Function,
169 reset?: boolean,
170 force: boolean = false
171) => {
172 const from = {
173 ship: pointToTransfer,
174 proxy,
175 };
177 const data = {
178 address,
179 reset: reset || false,
180 };
182 const sig = await generateHashAndSign(
183 api,
184 wallet,
185 nonce,
186 from,
187 'transferPoint',
188 data,
189 walletType,
190 web3,
191 connector,
192 signPersonalMessage,
193 isConnected
194 );
196 return api.transferPoint(sig, force, from, wallet.address, data);
197};
199const proxyType = (proxy: Proxy) => {
200 switch (proxy) {
201 case 'manage':
202 return 'setManagementProxy';
203 case 'spawn':
204 return 'setSpawnProxy';
205 case 'transfer':
206 return 'setTransferProxy';
207 default:
208 throw new Error(`Unknown proxyType ${proxy}`);
209 }
210};
212const setProxy = async (
213 api: RollerRPCAPI,
214 proxyAddressType: string,
215 sig: Signature,
216 from: From,
217 address: EthAddress,
218 data: AddressParams,
219 force: boolean = false
220) => {
221 switch (proxyAddressType) {
222 case 'manage':
223 return await api.setManagementProxy(sig, force, from, address, data);
224 case 'spawn':
225 return await api.setSpawnProxy(sig, force, from, address, data);
226 case 'transfer':
227 return await api.setTransferProxy(sig, force, from, address, data);
228 default:
229 throw new Error(`Unknown proxyType ${proxyAddressType}`);
230 }
231};
233export const registerProxyAddress = async (
234 api: RollerRPCAPI,
235 wallet: any, // BridgeWallet
236 _point: Ship,
237 proxy: string,
238 proxyAddressType: string,
239 nonce: number,
240 address: string,
241 walletType: symbol,
242 web3: any,
243 connector: WalletConnect | null,
244 isConnected: Function,
245 signPersonalMessage: Function,
246 force?: boolean
247) => {
248 const from = {
249 ship: _point,
250 proxy,
251 };
252 const data = { address };
254 const sig = await generateHashAndSign(
255 api,
256 wallet,
257 nonce,
258 from,
259 proxyType(proxyAddressType),
260 data,
261 walletType,
262 web3,
263 connector,
264 signPersonalMessage,
265 isConnected
266 );
267 return setProxy(
268 api,
269 proxyAddressType,
270 sig,
271 from,
272 wallet.address,
273 data,
274 force
275 );
276};
278const getDataAndMethod = (args: L2TransactionArgs) => {
279 const {
280 address,
281 api,
282 breach,
283 networkSeed,
284 reset,
285 pointToSpawn,
286 newSponsor,
287 sponsee,
288 ship,
289 } = args;
290 const result: TransactionData = { data: { address }, method: async () => '' };
292 switch (args.type) {
293 case 'transferPoint':
294 result.method = api.transferPoint;
295 result.data.reset = reset || false;
296 return result;
297 case 'spawn':
298 result.method = api.spawn;
299 result.data.ship = pointToSpawn;
300 return result;
301 case 'configureKeys':
302 // TODO: do something here?
303 if (Nothing.hasInstance(networkSeed)) {
304 console.log("Network key Error: couldn't derive network keys");
305 // throw new Error("Network key Error: couldn't derive network keys");
306 }
308 const seed = Just.hasInstance(networkSeed)
309 ? networkSeed.value
310 : randomHex(32);
312 const pair = deriveNetworkKeys(seed);
314 result.data = {
315 encrypt: addHexPrefix(pair.crypt.public),
316 auth: addHexPrefix(pair.auth.public),
317 cryptoSuite: String(CRYPTO_SUITE_VERSION),
318 breach: breach || false,
319 };
320 result.method = api.configureKeys;
321 return result;
322 case 'escape':
323 result.method = api.escape;
324 result.data.ship = newSponsor;
325 delete result.data.address;
326 return result;
327 case 'cancelEscape':
328 result.method = api.cancelEscape;
329 result.data.ship = ship;
330 delete result.data.address;
331 return result;
332 case 'adopt':
333 result.method = api.adopt;
334 result.data.ship = sponsee;
335 delete result.data.address;
336 return result;
337 case 'detach':
338 result.method = api.detach;
339 result.data.ship = sponsee;
340 delete result.data.address;
341 return result;
342 case 'reject':
343 result.method = api.reject;
344 result.data.ship = sponsee;
345 delete result.data.address;
346 return result;
347 }
348};
350export const submitL2Transaction = async (args: L2TransactionArgs) => {
351 const {
352 api,
353 wallet,
354 ship,
355 type,
356 proxy,
357 nonce,
358 walletType,
359 web3,
360 connector,
361 isConnected,
362 signPersonalMessage,
363 } = args;
365 const from = { ship, proxy };
366 const { data, method } = getDataAndMethod(args);
368 const sig = await generateHashAndSign(
369 api,
370 wallet,
371 nonce,
372 from,
373 type,
374 data,
375 walletType,
376 web3,
377 connector,
378 isConnected,
379 signPersonalMessage
380 );
382 return method(sig, false, from, wallet.address, data);
383};
385export const reticketL2Point = async () => {};
387export const hasInvite = (point: number) => (invite: Invite) =>
388 invite.planet === point && invite.ticket.length > 0;
390function isSpawn(tx: L2Data | undefined): tx is SpawnParams {
391 return (tx as SpawnParams) !== undefined;
394function isShipNumber(ship: number | string | undefined): ship is number {
395 return (ship as number) !== undefined;
398export const getPendingSpawns = (pendingTxs: PendingTransaction[]) =>
399 pendingTxs.reduce((acc, pending) => {
400 return isSpawn(pending.rawTx?.tx) &&
401 isShipNumber(pending.rawTx?.tx.data.ship)
402 ? acc.add(pending.rawTx?.tx.data.ship!)
403 : acc;
404 }, new Set<number>());