on 98d950d help sign in
bridge/src/lib/utils/roller.ts
385 lines 9.4 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 === '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) => {
67 if (walletType === WALLET_TYPES.METAMASK) {
68 const hash = await api.prepareForSigning(nonce, from, type, data);
69 let sig;
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 {
79 sig = await web3.eth.personal.sign(hash, wallet.address, '');
80 }
82 return sig;
83 } else if (walletType === WALLET_TYPES.WALLET_CONNECT) {
84 if (!connector || !connector.connected) {
85 throw new Error('Awaiting WalletConnect connection...');
86 }
88 const hash = await api.prepareForSigning(nonce, from, type, data);
89 const sig = await connector.signPersonalMessage([hash, wallet.address]);
90 return sig;
91 } else {
92 const hash = await api.getUnsignedTx(nonce, from, type, data);
93 const sig = await signTransactionHash(hash, wallet.privateKey);
94 return sig;
95 }
96};
98export const configureKeys = async (
99 api: RollerRPCAPI,
100 wallet: any,
101 point: Ship,
102 proxy: string,
103 nonce: number,
104 networkSeed: any,
105 walletType: symbol,
106 web3: any,
107 connector: WalletConnect | null,
108 breach?: boolean,
109 force: boolean = false
110) => {
111 const from = {
112 ship: point, //ship to configure keys
113 proxy,
114 };
116 // TODO: do something here?
117 if (Nothing.hasInstance(networkSeed)) {
118 console.log("Network key Error: couldn't derive network keys");
119 // throw new Error("Network key Error: couldn't derive network keys");
120 }
122 const seed = Just.hasInstance(networkSeed)
123 ? networkSeed.value
124 : randomHex(32);
126 const pair = deriveNetworkKeys(seed);
128 const data = {
129 encrypt: addHexPrefix(pair.crypt.public),
130 auth: addHexPrefix(pair.auth.public),
131 cryptoSuite: String(CRYPTO_SUITE_VERSION),
132 breach: breach || false,
133 };
135 const sig = await generateHashAndSign(
136 api,
137 wallet,
138 nonce,
139 from,
140 'configureKeys',
141 data,
142 walletType,
143 web3,
144 connector
145 );
146 return api.configureKeys(sig, force, from, wallet.address, data);
147};
149export const transferPointRequest = async (
150 api: RollerRPCAPI,
151 wallet: any,
152 pointToTransfer: Ship,
153 proxy: string,
154 nonce: number,
155 address: string,
156 walletType: symbol,
157 web3: any,
158 connector: WalletConnect | null,
159 reset?: boolean,
160 force: boolean = false
161) => {
162 const from = {
163 ship: pointToTransfer,
164 proxy,
165 };
167 const data = {
168 address,
169 reset: reset || false,
170 };
172 const sig = await generateHashAndSign(
173 api,
174 wallet,
175 nonce,
176 from,
177 'transferPoint',
178 data,
179 walletType,
180 web3,
181 connector
182 );
184 return api.transferPoint(sig, force, from, wallet.address, data);
185};
187const proxyType = (proxy: Proxy) => {
188 switch (proxy) {
189 case 'manage':
190 return 'setManagementProxy';
191 case 'spawn':
192 return 'setSpawnProxy';
193 case 'transfer':
194 return 'setTransferProxy';
195 default:
196 throw new Error(`Unknown proxyType ${proxy}`);
197 }
198};
200const setProxy = async (
201 api: RollerRPCAPI,
202 proxyAddressType: string,
203 sig: Signature,
204 from: From,
205 address: EthAddress,
206 data: AddressParams,
207 force: boolean = false
208) => {
209 switch (proxyAddressType) {
210 case 'manage':
211 return await api.setManagementProxy(sig, force, from, address, data);
212 case 'spawn':
213 return await api.setSpawnProxy(sig, force, from, address, data);
214 case 'transfer':
215 return await api.setTransferProxy(sig, force, from, address, data);
216 default:
217 throw new Error(`Unknown proxyType ${proxyAddressType}`);
218 }
219};
221export const registerProxyAddress = async (
222 api: RollerRPCAPI,
223 wallet: any, // BridgeWallet
224 _point: Ship,
225 proxy: string,
226 proxyAddressType: string,
227 nonce: number,
228 address: string,
229 walletType: symbol,
230 web3: any,
231 connector: WalletConnect | null,
232 force?: boolean
233) => {
234 const from = {
235 ship: _point,
236 proxy,
237 };
238 const data = { address };
240 const sig = await generateHashAndSign(
241 api,
242 wallet,
243 nonce,
244 from,
245 proxyType(proxyAddressType),
246 data,
247 walletType,
248 web3,
249 connector
250 );
252 return setProxy(
253 api,
254 proxyAddressType,
255 sig,
256 from,
257 wallet.address,
258 data,
259 force
260 );
261};
263const getDataAndMethod = (args: L2TransactionArgs) => {
264 const {
265 address,
266 api,
267 breach,
268 networkSeed,
269 reset,
270 pointToSpawn,
271 newSponsor,
272 sponsee,
273 ship,
274 } = args;
275 const result: TransactionData = { data: { address }, method: async () => '' };
277 switch (args.type) {
278 case 'transferPoint':
279 result.method = api.transferPoint;
280 result.data.reset = reset || false;
281 return result;
282 case 'spawn':
283 result.method = api.spawn;
284 result.data.ship = pointToSpawn;
285 return result;
286 case 'configureKeys':
287 // TODO: do something here?
288 if (Nothing.hasInstance(networkSeed)) {
289 console.log("Network key Error: couldn't derive network keys");
290 // throw new Error("Network key Error: couldn't derive network keys");
291 }
293 const seed = Just.hasInstance(networkSeed)
294 ? networkSeed.value
295 : randomHex(32);
297 const pair = deriveNetworkKeys(seed);
299 result.data = {
300 encrypt: addHexPrefix(pair.crypt.public),
301 auth: addHexPrefix(pair.auth.public),
302 cryptoSuite: String(CRYPTO_SUITE_VERSION),
303 breach: breach || false,
304 };
305 result.method = api.configureKeys;
306 return result;
307 case 'escape':
308 result.method = api.escape;
309 result.data.ship = newSponsor;
310 delete result.data.address;
311 return result;
312 case 'cancelEscape':
313 result.method = api.cancelEscape;
314 result.data.ship = ship;
315 delete result.data.address;
316 return result;
317 case 'adopt':
318 result.method = api.adopt;
319 result.data.ship = sponsee;
320 delete result.data.address;
321 return result;
322 case 'detach':
323 result.method = api.detach;
324 result.data.ship = sponsee;
325 delete result.data.address;
326 return result;
327 case 'reject':
328 result.method = api.reject;
329 result.data.ship = sponsee;
330 delete result.data.address;
331 return result;
332 }
333};
335export const submitL2Transaction = async (args: L2TransactionArgs) => {
336 const {
337 api,
338 wallet,
339 ship,
340 type,
341 proxy,
342 nonce,
343 walletType,
344 web3,
345 connector,
346 } = args;
348 const from = { ship, proxy };
349 const { data, method } = getDataAndMethod(args);
351 const sig = await generateHashAndSign(
352 api,
353 wallet,
354 nonce,
355 from,
356 type,
357 data,
358 walletType,
359 web3,
360 connector
361 );
363 return method(sig, false, from, wallet.address, data);
364};
366export const reticketL2Point = async () => {};
368export const hasInvite = (point: number) => (invite: Invite) =>
369 invite.planet === point && invite.ticket.length > 0;
371function isSpawn(tx: L2Data | undefined): tx is SpawnParams {
372 return (tx as SpawnParams) !== undefined;
375function isShipNumber(ship: number | string | undefined): ship is number {
376 return (ship as number) !== undefined;
379export const getPendingSpawns = (pendingTxs: PendingTransaction[]) =>
380 pendingTxs.reduce((acc, pending) => {
381 return isSpawn(pending.rawTx?.tx) &&
382 isShipNumber(pending.rawTx?.tx.data.ship)
383 ? acc.add(pending.rawTx?.tx.data.ship!)
384 : acc;
385 }, new Set<number>());