on bf97291 help sign in
bridge/src/lib/utils/roller.ts
440 lines 10 kB history raw
1import { Just, Nothing } from 'folktale/maybe';
2import { randomHex } from 'web3-utils';
3import WalletConnect from '@walletconnect/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 === '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) => {
67 if (walletType === WALLET_TYPES.METAMASK) {
68 const hash = await api.prepareForSigning(nonce, from, type, data);
69 const sig = await web3.eth.personal.sign(hash, wallet.address, '');
70 return sig;
71 } else if (walletType === WALLET_TYPES.WALLET_CONNECT) {
72 if (!connector || !connector.connected) {
73 throw new Error('Awaiting WalletConnect connection...');
74 }
76 const hash = await api.prepareForSigning(nonce, from, type, data);
77 const sig = await connector.signPersonalMessage([hash, wallet.address]);
78 return sig;
79 } else {
80 const hash = await api.getUnsignedTx(nonce, from, type, data);
81 const sig = await signTransactionHash(hash, wallet.privateKey);
82 return sig;
83 }
84};
86export const spawn = async (
87 api: RollerRPCAPI,
88 wallet: any,
89 _point: number,
90 proxy: string,
91 nonce: number,
92 planet: number,
93 walletType: symbol,
94 web3: any,
95 connector: WalletConnect | null
96) => {
97 const from = {
98 ship: _point, //ship that is spawning the planet
99 proxy,
100 };
102 const data = {
103 address: wallet.address,
104 ship: planet, // ship to spawn
105 };
107 const sig = await generateHashAndSign(
108 api,
109 wallet,
110 nonce,
111 from,
112 'spawn',
113 data,
114 walletType,
115 web3,
116 connector
117 );
118 return api.spawn(sig, from, wallet.address, data);
119};
121export const configureKeys = async (
122 api: RollerRPCAPI,
123 wallet: any,
124 point: Ship,
125 proxy: string,
126 nonce: number,
127 networkSeed: any,
128 walletType: symbol,
129 web3: any,
130 connector: WalletConnect | null,
131 breach?: boolean
132) => {
133 const from = {
134 ship: point, //ship to configure keys
135 proxy,
136 };
138 // TODO: do something here?
139 if (Nothing.hasInstance(networkSeed)) {
140 console.log("Network key Error: couldn't derive network keys");
141 // throw new Error("Network key Error: couldn't derive network keys");
142 }
144 const seed = Just.hasInstance(networkSeed)
145 ? networkSeed.value
146 : randomHex(32);
148 const pair = deriveNetworkKeys(seed);
150 const data = {
151 encrypt: addHexPrefix(pair.crypt.public),
152 auth: addHexPrefix(pair.auth.public),
153 cryptoSuite: String(CRYPTO_SUITE_VERSION),
154 breach: breach || false,
155 };
157 const sig = await generateHashAndSign(
158 api,
159 wallet,
160 nonce,
161 from,
162 'configureKeys',
163 data,
164 walletType,
165 web3,
166 connector
167 );
168 return api.configureKeys(sig, from, wallet.address, data);
169};
171export const transferPointRequest = async (
172 api: RollerRPCAPI,
173 wallet: any,
174 pointToTransfer: Ship,
175 proxy: string,
176 nonce: number,
177 address: string,
178 walletType: symbol,
179 web3: any,
180 connector: WalletConnect | null,
181 reset?: boolean
182) => {
183 const from = {
184 ship: pointToTransfer,
185 proxy,
186 };
188 const data = {
189 address,
190 reset: reset || false,
191 };
193 const sig = await generateHashAndSign(
194 api,
195 wallet,
196 nonce,
197 from,
198 'transferPoint',
199 data,
200 walletType,
201 web3,
202 connector
203 );
205 return api.transferPoint(sig, from, wallet.address, data);
206};
208export const adopt = async (
209 api: RollerRPCAPI,
210 _wallet: any,
211 sponsor: Ship,
212 proxy: string,
213 nonce: number,
214 ship: Ship
215) => {
216 const from = {
217 ship: sponsor, // ship that is adopting the planet
218 proxy,
219 };
221 const data = {
222 ship, // ship to adopt
223 };
225 const hash = await api.getUnsignedTx(nonce, from, 'detach', data);
226 const sSig = signTransactionHash(hash, _wallet.privateKey);
227 return api.adopt(sSig, from, _wallet.address, data);
228};
230export const detach = async (
231 api: RollerRPCAPI,
232 _wallet: any,
233 sponsor: Ship,
234 proxy: string,
235 nonce: number,
236 ship: Ship
237) => {
238 const from = {
239 ship: sponsor, // ship that is detaching the planet
240 proxy,
241 };
243 const data = {
244 ship, // ship to detach
245 };
247 const hash = await api.getUnsignedTx(nonce, from, 'detach', data);
248 const sSig = signTransactionHash(hash, _wallet.privateKey);
249 return api.detach(sSig, from, _wallet.address, data);
250};
252export const reject = async (
253 api: RollerRPCAPI,
254 _wallet: any,
255 sponsor: Ship,
256 proxy: string,
257 nonce: number,
258 ship: Ship
259) => {
260 const from = {
261 ship: sponsor, // ship that is rejecting the planet
262 proxy,
263 };
265 const data = {
266 ship, // ship to reject
267 };
269 const hash = await api.getUnsignedTx(nonce, from, 'detach', data);
270 const sSig = signTransactionHash(hash, _wallet.privateKey);
271 return api.reject(sSig, from, _wallet.address, data);
272};
274const proxyType = (proxy: Proxy) => {
275 switch (proxy) {
276 case 'manage':
277 return 'setManagementProxy';
278 case 'spawn':
279 return 'setSpawnProxy';
280 case 'transfer':
281 return 'setTransferProxy';
282 default:
283 throw new Error(`Unknown proxyType ${proxy}`);
284 }
285};
287const setProxy = async (
288 api: RollerRPCAPI,
289 proxyAddressType: string,
290 sig: Signature,
291 from: From,
292 address: EthAddress,
293 data: AddressParams
294) => {
295 switch (proxyAddressType) {
296 case 'manage':
297 return await api.setManagementProxy(sig, from, address, data);
298 case 'spawn':
299 return await api.setSpawnProxy(sig, from, address, data);
300 case 'transfer':
301 return await api.setTransferProxy(sig, from, address, data);
302 default:
303 throw new Error(`Unknown proxyType ${proxyAddressType}`);
304 }
305};
307export const registerProxyAddress = async (
308 api: RollerRPCAPI,
309 wallet: any,
310 _point: Ship,
311 proxy: string,
312 proxyAddressType: string,
313 nonce: number,
314 address: string,
315 walletType: symbol,
316 web3: any,
317 connector: WalletConnect | null
318) => {
319 const from = {
320 ship: _point,
321 proxy,
322 };
323 const data = { address };
325 const sig = await generateHashAndSign(
326 api,
327 wallet,
328 nonce,
329 from,
330 proxyType(proxyAddressType),
331 data,
332 walletType,
333 web3,
334 connector
335 );
337 return setProxy(api, proxyAddressType, sig, from, wallet.address, data);
338};
340const getDataAndMethod = (args: L2TransactionArgs) => {
341 const {
342 address,
343 api,
344 breach,
345 networkSeed,
346 reset,
347 pointToSpawn,
348 newSponsor,
349 } = args;
350 const result: TransactionData = { data: { address }, method: () => null };
352 switch (args.type) {
353 case 'transferPoint':
354 result.method = api.transferPoint;
355 result.data.reset = reset || false;
356 return result;
357 case 'spawn':
358 result.method = api.spawn;
359 result.data.ship = pointToSpawn;
360 return result;
361 case 'configureKeys':
362 // TODO: do something here?
363 if (Nothing.hasInstance(networkSeed)) {
364 console.log("Network key Error: couldn't derive network keys");
365 // throw new Error("Network key Error: couldn't derive network keys");
366 }
368 const seed = Just.hasInstance(networkSeed)
369 ? networkSeed.value
370 : randomHex(32);
372 const pair = deriveNetworkKeys(seed);
374 result.data = {
375 encrypt: addHexPrefix(pair.crypt.public),
376 auth: addHexPrefix(pair.auth.public),
377 cryptoSuite: String(CRYPTO_SUITE_VERSION),
378 breach: breach || false,
379 };
380 result.method = api.configureKeys;
381 return result;
382 case 'escape':
383 result.method = api.escape;
384 result.data.ship = newSponsor;
385 delete result.data.address;
386 return result;
387 }
388};
390export const submitL2Transaction = async (args: L2TransactionArgs) => {
391 const {
392 api,
393 wallet,
394 ship,
395 type,
396 proxy,
397 nonce,
398 walletType,
399 web3,
400 connector,
401 } = args;
403 const from = { ship, proxy };
404 const { data, method } = getDataAndMethod(args);
406 const sig = await generateHashAndSign(
407 api,
408 wallet,
409 nonce,
410 from,
411 type,
412 data,
413 walletType,
414 web3,
415 connector
416 );
418 return method(sig, from, wallet.address, data);
419};
421export const reticketL2Point = async () => {};
423export const hasInvite = (point: number) => (invite: Invite) =>
424 invite.planet === point && invite.ticket.length > 0;
426function isSpawn(tx: L2Data | undefined): tx is SpawnParams {
427 return (tx as SpawnParams) !== undefined;
430function isShipNumber(ship: number | string | undefined): ship is number {
431 return (ship as number) !== undefined;
434export const getPendingSpawns = (pendingTxs: PendingTransaction[]) =>
435 pendingTxs.reduce((acc, pending) => {
436 return isSpawn(pending.rawTx?.tx) &&
437 isShipNumber(pending.rawTx?.tx.data.ship)
438 ? acc.add(pending.rawTx?.tx.data.ship!)
439 : acc;
440 }, new Set<number>());