on e75792f help sign in
bridge/src/lib/invite.js
312 lines 8.3 kB history raw
1import { Just } from 'folktale/maybe';
2import Tx from 'ethereumjs-tx';
4import * as azimuth from 'azimuth-js';
5import * as kg from 'urbit-key-generation/dist';
6import * as wg from './walletgen';
7import * as tank from './tank';
9import JSZip from 'jszip';
10import saveAs from 'file-saver';
12import { sendSignedTransaction, hexify } from './txn';
13import { attemptNetworkSeedDerivation } from './keys';
14import { addHexPrefix, WALLET_TYPES } from './wallet';
16const INVITE_STAGES = {
17 INVITE_LOGIN: 'invite login',
18 INVITE_WALLET: 'invite wallet',
19 INVITE_VERIFY: 'invite verify',
20 INVITE_TRANSACTIONS: 'invite transactions',
21};
23const WALLET_STATES = {
24 UNLOCKING: 'Unlocking invite wallet',
25 GENERATING: 'Generating your wallet',
26 RENDERING: 'Creating paper collateral',
27 PAPER_READY: 'Download your wallet',
28 DOWNLOADED: 'Wallet downloaded',
29 TRANSACTIONS: 'Sending transactions',
30};
32const TRANSACTION_STATES = {
33 GENERATING: {
34 label: 'Generating Transactions...',
35 progress: 0.0,
36 },
37 SIGNING: {
38 label: 'Signing Transactions...',
39 progress: 0.15,
40 },
41 FUNDING_INVITE: {
42 label: 'Funding Invite Wallet...',
43 progress: 0.3,
44 },
45 CLAIMING: {
46 label: 'Claiming Invite...',
47 progress: 0.55,
48 },
49 FUNDING_RECIPIENT: {
50 label: 'Funding Recipient Wallet...',
51 progress: 0.65,
52 },
53 CONFIGURING: {
54 label: 'Configuring Point...',
55 progress: 0.85,
56 },
57 CLEANING: {
58 label: 'Cleaning Up...',
59 progress: 0.95,
60 },
61 DONE: {
62 label: 'Done',
63 progress: 1.0,
64 },
65};
67async function generateWallet(point) {
68 const ticket = await wg.makeTicket(point);
69 const wallet = await wg.generateWallet(point, ticket);
70 return wallet;
73//TODO should be moved to lib/walletgen
74async function downloadWallet(paper) {
75 return new Promise((resolve, reject) => {
76 const zip = new JSZip();
77 //TODO the categories here aren't explained in bridge at all...
79 const bin0 = paper.filter(item => item.bin === '0');
80 const bin1 = paper.filter(item => item.bin === '1');
81 const bin2 = paper.filter(item => item.bin === '2');
82 const bin3 = paper.filter(item => item.bin === '3');
83 const bin4 = paper.filter(item => item.bin === '4');
85 const bin0Folder = zip.folder('0. Public');
86 const bin1Folder = zip.folder('1. Very High Friction Custody');
87 const bin2Folder = zip.folder('2. High Friction Custody');
88 const bin3Folder = zip.folder('3. Medium Friction Custody');
89 const bin4Folder = zip.folder('4. Low Friction Custody');
91 bin0.forEach(item => bin0Folder.file(`${item.pageTitle}.png`, item.png));
92 bin1.forEach(item => bin1Folder.file(`${item.pageTitle}.png`, item.png));
93 bin2.forEach(item => bin2Folder.file(`${item.pageTitle}.png`, item.png));
94 bin3.forEach(item => bin3Folder.file(`${item.pageTitle}.png`, item.png));
95 bin4.forEach(item => bin4Folder.file(`${item.pageTitle}.png`, item.png));
97 zip.generateAsync({ type: 'blob' }).then(content => {
98 saveAs(content, 'urbit-wallet.zip');
99 resolve(true);
100 });
101 });
104async function claimPointFromInvite({
105 inviteWallet,
106 wallet,
107 point,
108 web3,
109 contracts,
110 onUpdate,
111}) {
112 const askForFunding = (address, minBalance, balance) =>
113 onUpdate({
114 type: 'askFunding',
115 value: { address, minBalance, balance },
116 });
118 const gotFunding = () => onUpdate({ type: 'gotFunding' });
119 const progress = state => onUpdate({ type: 'progress', state });
121 const inviteAddress = inviteWallet.address;
123 progress(TRANSACTION_STATES.GENERATING);
125 // transfer from invite wallet to new wallet
126 const transferTx = azimuth.ecliptic.transferPoint(
127 contracts,
128 point,
129 wallet.ownership.keys.address
130 );
131 transferTx.gas = 500000; //TODO can maybe be lower?
133 // ping gas tank with txs if needed
134 let inviteNonce = await web3.eth.getTransactionCount(inviteAddress);
135 transferTx.nonce = inviteNonce++;
136 transferTx.gasPrice = 20000000000; //NOTE we pay the premium for faster ux
137 transferTx.from = inviteAddress;
139 progress(TRANSACTION_STATES.SIGNING);
140 // NOTE: using web3.eth.accounts.signTransaction is broken (1.0.0-beta51)
141 const transferStx = new Tx(transferTx);
142 transferStx.sign(inviteWallet.privateKey);
143 const rawTransferStx = hexify(transferStx.serialize());
144 const transferCost = transferTx.gas * transferTx.gasPrice;
146 progress(TRANSACTION_STATES.FUNDING_INVITE);
148 let usedTank = await tank.ensureFundsFor(
149 web3,
150 point,
151 inviteAddress,
152 transferCost,
153 [rawTransferStx],
154 askForFunding,
155 gotFunding
156 );
158 progress(TRANSACTION_STATES.CLAIMING);
160 // send transaction
161 await sendAndAwaitConfirm(web3, [Just(transferStx)], usedTank);
163 //
164 // we're gonna be operating as the new wallet from here on out, so change
165 // the relevant values
166 const newAddress = wallet.ownership.keys.address;
168 // configure management proxy
169 const managementTx = azimuth.ecliptic.setManagementProxy(
170 contracts,
171 point,
172 wallet.management.keys.address
173 );
174 managementTx.gas = 200000;
175 managementTx.nonce = 0;
177 // configure networking public keys
178 //TODO feels like more of this logic should live in a lib?
179 const seed = await attemptNetworkSeedDerivation(true, {
180 walletType: WALLET_TYPES.TICKET,
181 urbitWallet: Just(wallet),
182 pointCursor: Just(point),
183 pointCache: { [point]: { keyRevisionNumber: 0 } },
184 });
186 const networkSeed = seed.matchWith({
187 Nothing: () => {
188 throw new Error('network seed not derived');
189 },
190 Just: p => p.value,
191 });
193 const networkKeys = kg.deriveNetworkKeys(networkSeed);
195 let keysTx = azimuth.ecliptic.configureKeys(
196 contracts,
197 point,
198 addHexPrefix(networkKeys.crypt.public),
199 addHexPrefix(networkKeys.auth.public),
200 1,
201 false
202 );
203 keysTx.gas = 150000;
204 keysTx.nonce = 1;
206 let txs = [managementTx, keysTx];
208 if (
209 azimuth.azimuth.getPointSize(point) !== azimuth.azimuth.PointSize.Planet
210 ) {
211 let spawnTx = azimuth.ecliptic.setSpawnProxy(
212 contracts,
213 point,
214 wallet.spawn.keys.address
215 );
216 spawnTx.gas = 200000;
217 spawnTx.nonce = 2;
218 txs.push(spawnTx);
220 if (
221 azimuth.azimuth.getPointSize(point) === azimuth.azimuth.PointSize.Galaxy
222 ) {
223 let votingTx = azimuth.ecliptic.setVotingProxy(
224 contracts,
225 point,
226 wallet.voting.keys.address
227 );
228 votingTx.gas = 200000;
229 votingTx.nonce = 3;
230 txs.push(votingTx);
231 }
232 }
234 let totalCost = 0;
235 txs = txs.map(tx => {
236 tx.gasPrice = 20000000000;
237 tx.from = newAddress;
238 totalCost = totalCost + tx.gasPrice * tx.gas;
239 return tx;
240 });
242 let txPairs = txs.map(tx => {
243 let stx = new Tx(tx);
244 stx.sign(Buffer.from(wallet.ownership.keys.private, 'hex'));
245 return {
246 raw: '0x' + stx.serialize().toString('hex'),
247 signed: stx,
248 };
249 });
251 progress(TRANSACTION_STATES.FUNDING_RECIPIENT);
253 usedTank = await tank.ensureFundsFor(
254 web3,
255 point,
256 newAddress,
257 totalCost,
258 txPairs.map(p => p.raw),
259 askForFunding
260 );
262 progress(TRANSACTION_STATES.CONFIGURING);
264 await sendAndAwaitConfirm(web3, txPairs.map(p => Just(p.signed)), usedTank);
266 progress(TRANSACTION_STATES.CLEANING);
268 // if non-trivial eth left in invite wallet, transfer to new ownership
269 let balance = await web3.eth.getBalance(inviteAddress);
270 const gasPrice = 20000000000;
271 const gasLimit = 21000;
272 const sendEthCost = gasPrice * gasLimit;
273 if (balance > sendEthCost) {
274 const value = balance - sendEthCost;
275 console.log('sending', value);
276 const tx = {
277 to: newAddress,
278 value: value,
279 gasPrice: gasPrice,
280 gas: gasLimit,
281 nonce: inviteNonce++,
282 };
283 let stx = new Tx(tx);
284 stx.sign(inviteWallet.privateKey);
285 const rawTx = hexify(stx.serialize());
286 web3.eth.sendSignedTransaction(rawTx).catch(err => {
287 console.log('error sending value tx, who cares', err);
288 });
289 console.log('sent old balance');
290 }
292 progress(TRANSACTION_STATES.DONE);
295async function sendAndAwaitConfirm(web3, signedTxs, usedTank) {
296 await Promise.all(
297 signedTxs.map(tx => {
298 return new Promise((resolve, reject) => {
299 sendSignedTransaction(web3, tx, usedTank, resolve);
300 });
301 })
302 );
305export {
306 generateWallet,
307 downloadWallet,
308 claimPointFromInvite,
309 INVITE_STAGES,
310 WALLET_STATES,
311 TRANSACTION_STATES,
312};