on f941cfa help sign in
bridge/src/lib/utils/roller.ts
373 lines 9.2 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) => {
110 const from = {
111 ship: point, //ship to configure keys
112 proxy,
113 };
115 // TODO: do something here?
116 if (Nothing.hasInstance(networkSeed)) {
117 console.log("Network key Error: couldn't derive network keys");
118 // throw new Error("Network key Error: couldn't derive network keys");
119 }
121 const seed = Just.hasInstance(networkSeed)
122 ? networkSeed.value
123 : randomHex(32);
125 const pair = deriveNetworkKeys(seed);
127 const data = {
128 encrypt: addHexPrefix(pair.crypt.public),
129 auth: addHexPrefix(pair.auth.public),
130 cryptoSuite: String(CRYPTO_SUITE_VERSION),
131 breach: breach || false,
132 };
134 const sig = await generateHashAndSign(
135 api,
136 wallet,
137 nonce,
138 from,
139 'configureKeys',
140 data,
141 walletType,
142 web3,
143 connector
144 );
145 return api.configureKeys(sig, from, wallet.address, data);
146};
148export const transferPointRequest = async (
149 api: RollerRPCAPI,
150 wallet: any,
151 pointToTransfer: Ship,
152 proxy: string,
153 nonce: number,
154 address: string,
155 walletType: symbol,
156 web3: any,
157 connector: WalletConnect | null,
158 reset?: boolean
159) => {
160 const from = {
161 ship: pointToTransfer,
162 proxy,
163 };
165 const data = {
166 address,
167 reset: reset || false,
168 };
170 const sig = await generateHashAndSign(
171 api,
172 wallet,
173 nonce,
174 from,
175 'transferPoint',
176 data,
177 walletType,
178 web3,
179 connector
180 );
182 return api.transferPoint(sig, from, wallet.address, data);
183};
185const proxyType = (proxy: Proxy) => {
186 switch (proxy) {
187 case 'manage':
188 return 'setManagementProxy';
189 case 'spawn':
190 return 'setSpawnProxy';
191 case 'transfer':
192 return 'setTransferProxy';
193 default:
194 throw new Error(`Unknown proxyType ${proxy}`);
195 }
196};
198const setProxy = async (
199 api: RollerRPCAPI,
200 proxyAddressType: string,
201 sig: Signature,
202 from: From,
203 address: EthAddress,
204 data: AddressParams
205) => {
206 switch (proxyAddressType) {
207 case 'manage':
208 return await api.setManagementProxy(sig, from, address, data);
209 case 'spawn':
210 return await api.setSpawnProxy(sig, from, address, data);
211 case 'transfer':
212 return await api.setTransferProxy(sig, from, address, data);
213 default:
214 throw new Error(`Unknown proxyType ${proxyAddressType}`);
215 }
216};
218export const registerProxyAddress = async (
219 api: RollerRPCAPI,
220 wallet: any,
221 _point: Ship,
222 proxy: string,
223 proxyAddressType: string,
224 nonce: number,
225 address: string,
226 walletType: symbol,
227 web3: any,
228 connector: WalletConnect | null
229) => {
230 const from = {
231 ship: _point,
232 proxy,
233 };
234 const data = { address };
236 const sig = await generateHashAndSign(
237 api,
238 wallet,
239 nonce,
240 from,
241 proxyType(proxyAddressType),
242 data,
243 walletType,
244 web3,
245 connector
246 );
248 return setProxy(api, proxyAddressType, sig, from, wallet.address, data);
249};
251const getDataAndMethod = (args: L2TransactionArgs) => {
252 const {
253 address,
254 api,
255 breach,
256 networkSeed,
257 reset,
258 pointToSpawn,
259 newSponsor,
260 sponsee,
261 ship,
262 } = args;
263 const result: TransactionData = { data: { address }, method: () => null };
265 switch (args.type) {
266 case 'transferPoint':
267 result.method = api.transferPoint;
268 result.data.reset = reset || false;
269 return result;
270 case 'spawn':
271 result.method = api.spawn;
272 result.data.ship = pointToSpawn;
273 return result;
274 case 'configureKeys':
275 // TODO: do something here?
276 if (Nothing.hasInstance(networkSeed)) {
277 console.log("Network key Error: couldn't derive network keys");
278 // throw new Error("Network key Error: couldn't derive network keys");
279 }
281 const seed = Just.hasInstance(networkSeed)
282 ? networkSeed.value
283 : randomHex(32);
285 const pair = deriveNetworkKeys(seed);
287 result.data = {
288 encrypt: addHexPrefix(pair.crypt.public),
289 auth: addHexPrefix(pair.auth.public),
290 cryptoSuite: String(CRYPTO_SUITE_VERSION),
291 breach: breach || false,
292 };
293 result.method = api.configureKeys;
294 return result;
295 case 'escape':
296 result.method = api.escape;
297 result.data.ship = newSponsor;
298 delete result.data.address;
299 return result;
300 case 'cancelEscape':
301 result.method = api.cancelEscape;
302 result.data.ship = ship;
303 delete result.data.address;
304 return result;
305 case 'adopt':
306 result.method = api.adopt;
307 result.data.ship = sponsee;
308 delete result.data.address;
309 return result;
310 case 'detach':
311 result.method = api.detach;
312 result.data.ship = sponsee;
313 delete result.data.address;
314 return result;
315 case 'reject':
316 result.method = api.reject;
317 result.data.ship = sponsee;
318 delete result.data.address;
319 return result;
320 }
321};
323export const submitL2Transaction = async (args: L2TransactionArgs) => {
324 const {
325 api,
326 wallet,
327 ship,
328 type,
329 proxy,
330 nonce,
331 walletType,
332 web3,
333 connector,
334 } = args;
336 const from = { ship, proxy };
337 const { data, method } = getDataAndMethod(args);
339 const sig = await generateHashAndSign(
340 api,
341 wallet,
342 nonce,
343 from,
344 type,
345 data,
346 walletType,
347 web3,
348 connector
349 );
351 return method(sig, from, wallet.address, data);
352};
354export const reticketL2Point = async () => {};
356export const hasInvite = (point: number) => (invite: Invite) =>
357 invite.planet === point && invite.ticket.length > 0;
359function isSpawn(tx: L2Data | undefined): tx is SpawnParams {
360 return (tx as SpawnParams) !== undefined;
363function isShipNumber(ship: number | string | undefined): ship is number {
364 return (ship as number) !== undefined;
367export const getPendingSpawns = (pendingTxs: PendingTransaction[]) =>
368 pendingTxs.reduce((acc, pending) => {
369 return isSpawn(pending.rawTx?.tx) &&
370 isShipNumber(pending.rawTx?.tx.data.ship)
371 ? acc.add(pending.rawTx?.tx.data.ship!)
372 : acc;
373 }, new Set<number>());