@@ -1,7 +1,12 @@
1import lodash from 'lodash'
1import lodash from 'lodash'
2+import { Just, Nothing } from 'folktale/maybe'
3+import Tx from 'ethereumjs-tx'
5+import * as azimuth from 'azimuth-js'
2import * as more from 'more-entropy'
6import * as more from 'more-entropy'
3import * as ob from 'urbit-ob'
7import * as ob from 'urbit-ob'
4import * as kg from '../../../node_modules/urbit-key-generation/dist/index'
8import * as kg from '../../../node_modules/urbit-key-generation/dist/index'
9+import * as tank from './tank'
5import { MAX_GALAXY, MIN_STAR, MAX_STAR, MIN_PLANET,
10import { MAX_GALAXY, MIN_STAR, MAX_STAR, MIN_PLANET,
6 GALAXY_ENTROPY_BITS, STAR_ENTROPY_BITS, PLANET_ENTROPY_BITS,
11 GALAXY_ENTROPY_BITS, STAR_ENTROPY_BITS, PLANET_ENTROPY_BITS,
7 SEED_ENTROPY_BITS,
12 SEED_ENTROPY_BITS,
@@ -11,6 +16,22 @@ import { MAX_GALAXY, MIN_STAR, MAX_STAR, MIN_PLANET,
11import JSZip from 'jszip'
16import JSZip from 'jszip'
12import saveAs from 'file-saver'
17import saveAs from 'file-saver'
21+ sendSignedTransaction,
22+ waitForTransactionConfirm,
23+ isTransactionConfirmed
25+import { BRIDGE_ERROR } from './error'
26+import { attemptSeedDerivation, genKey } from './keys'
29+ addressFromSecp256k1Public,
30+ urbitWalletFromTicket,
14const NEXT_STEP_NUM = 6;
35const NEXT_STEP_NUM = 6;
15const SEED_LENGTH_BYTES = SEED_ENTROPY_BITS / 8
36const SEED_LENGTH_BYTES = SEED_ENTROPY_BITS / 8
@@ -30,6 +51,16 @@ const WALLET_STATES = {
30 TRANSACTIONS: "Sending transactions"
51 TRANSACTIONS: "Sending transactions"
54+const TRANSACTION_STATES = {
55+ GENERATING: 'Generating transactions',
56+ SIGNING: 'Signing transactions',
57+ FUNDING: 'Funding transactions',
58+ CLAIMING: 'Claiming invite',
59+ CONFIGURING: 'Configuring planet',
60+ CLEANING: 'Cleaning up',
33async function generateWallet(point) {
64async function generateWallet(point) {
34 const makeTicket = point => {
65 const makeTicket = point => {
@@ -115,9 +146,267 @@ async function downloadWallet(paper) {
149+async function startTransactions(args) {
150+ let { realPointM, web3M, contractsM, realTicket,
151+ inviteWalletM, realWalletM, setUrbitWallet, updateProgress } = args
153+ if (Nothing.hasInstance(web3M)) {
154+ throw BRIDGE_ERROR.MISSING_WEB3;
156+ const web3 = web3M.value;
158+ if (Nothing.hasInstance(contractsM)) {
159+ throw BRIDGE_ERROR.MISSING_CONTRACTS;
161+ const contracts = contractsM.value;
163+ if (Nothing.hasInstance(inviteWalletM)) {
164+ throw BRIDGE_ERROR.MISSING_WALLET;
166+ const inviteWallet = inviteWalletM.value;
168+ // if (Nothing.hasInstance(realWalletM)) {
169+ // throw BRIDGE_ERROR.MISSING_WALLET;
171+ // const realWallet = realWalletM.value;
173+ if (Nothing.hasInstance(realPointM)) {
174+ throw BRIDGE_ERROR.MISSING_POINT;
176+ const point = realPointM.value;
178+ const inviteAddress = addressFromSecp256k1Public(inviteWallet.publicKey);
179+ console.log('working as', inviteAddress);
181+ console.log('realTicket = ', realTicket)
182+ console.log('realPoint = ', point)
183+ console.log('inviteWallet = ', inviteWallet)
186+ const realWallet = await urbitWalletFromTicket(realTicket, point);
188+ // transfer from invite wallet to new wallet
190+ let transferTx = azimuth.ecliptic.transferPoint(contracts, point, realWallet.ownership.keys.address);
191+ transferTx.gas = 500000; //TODO can maybe be lower?
193+ // ping gas tank with txs if needed
195+ let inviteNonce = await web3.eth.getTransactionCount(inviteAddress);
196+ transferTx.nonce = inviteNonce++;
197+ transferTx.gasPrice = 20000000000; //NOTE we pay the premium for faster ux
198+ transferTx.from = inviteAddress;
200+ //NOTE using web3.eth.accounts.signTransaction is broken (1.0.0-beta51)
201+ let transferStx = new Tx(transferTx);
202+ transferStx.sign(inviteWallet.privateKey);
203+ let rawTransferStx = '0x'+transferStx.serialize().toString('hex');
205+ const transferCost = transferTx.gas * transferTx.gasPrice;
206+ await ensureFundsFor(web3, point, inviteAddress, transferCost, [rawTransferStx], updateProgress);
209+ updateProgress(TRANSACTION_STATES.CLAIMING);
210+ await sendAndAwaitConfirm(web3, [rawTransferStx]);
213+ // we're gonna be operating as the new wallet from here on out, so change
214+ // the relevant values
216+ setUrbitWallet(Just(realWallet));
217+ const newAddress = realWallet.ownership.keys.address;
219+ // configure management proxy
221+ let managementTx = azimuth.ecliptic.setManagementProxy(contracts, point, realWallet.management.keys.address);
222+ managementTx.gas = 200000;
223+ managementTx.nonce = 0;
225+ // configure networking public keys
226+ //TODO feels like more of this logic should live in a lib?
227+ let networkSeed = await attemptSeedDerivation(true, {
228+ walletType: WALLET_NAMES.TICKET,
229+ urbitWallet: Just(realWallet),
230+ pointCursor: Just(point),
231+ pointCache: {[point]: {keyRevisionNumber: 0}}
233+ if (Nothing.hasInstance(networkSeed)) {
234+ throw new Error('wtf network seed not derived');
236+ networkSeed = networkSeed.value;
237+ const networkKeys = kg.deriveNetworkKeys(networkSeed);
239+ let keysTx = azimuth.ecliptic.configureKeys(
242+ addHexPrefix(networkKeys.crypt.public),
243+ addHexPrefix(networkKeys.auth.public),
247+ keysTx.gas = 150000;
250+ //TODO refactor this process into function
252+ let txs = [managementTx, keysTx];
253+ txs = txs.map(tx => {
254+ tx.gasPrice = 20000000000;
255+ tx.from = newAddress;
256+ totalCost = totalCost + (tx.gasPrice * tx.gas);
260+ let rawStxs = txs.map(tx => {
261+ let stx = new Tx(tx);
262+ stx.sign(Buffer.from(realWallet.ownership.keys.private, 'hex'));
263+ return '0x'+stx.serialize().toString('hex');
266+ await ensureFundsFor(web3, point, newAddress, totalCost, rawStxs, updateProgress);
268+ updateProgress(TRANSACTION_STATES.CONFIGURING);
269+ await sendAndAwaitConfirm(web3, rawStxs);
271+ // if non-trivial eth left in invite wallet, transfer to new ownership
272+ updateProgress(TRANSACTION_STATES.CLEANING);
273+ let balance = await web3.eth.getBalance(inviteAddress);
274+ const gasPrice = 20000000000;
275+ const gasLimit = 21000;
276+ const sendEthCost = gasPrice * gasLimit;
277+ if (balance > sendEthCost) {
278+ const value = (balance - sendEthCost);
279+ console.log('sending', value);
285+ nonce: inviteNonce++
287+ let stx = new Tx(tx);
288+ stx.sign(inviteWallet.privateKey);
289+ const rawTx = '0x'+stx.serialize().toString('hex');
290+ web3.eth.sendSignedTransaction(rawTx).catch(err => {
291+ console.log('error sending value tx, who cares', err);
293+ console.log('sent old balance');
296+ // proceed without waiting for confirm
297+ updateProgress(TRANSACTION_STATES.DONE);
298+ setUrbitWallet(Just(realWallet));
299+ //TODO forward to "all done!" screen
302+async function ensureFundsFor(web3, point, address, cost, signedTxs, updateProgress) {
303+ updateProgress(TRANSACTION_STATES.FUNDING);
304+ let balance = await web3.eth.getBalance(address);
306+ if (cost > balance) {
310+ const fundsRemaining = await tank.remainingTransactions(point);
311+ if (fundsRemaining < signedTxs.length) {
312+ throw new Error('request invalid');
315+ const res = await tank.fundTransactions(signedTxs);
317+ throw new Error('request rejected');
319+ await waitForTransactionConfirm(web3, res.txHash);
320+ let newBalance = await web3.eth.getBalance(address);
321+ console.log('funds have confirmed', balance >= cost, balance, newBalance);
326+ console.log('funding failed', e);
327+ await waitForBalance(web3, address, cost, updateProgress);
332+ console.log('already have sufficient funds');
336+// resolves when address has at least minBalance
338+async function waitForBalance(web3, address, minBalance, updateProgress) {
339+ console.log('awaiting balance', address, minBalance);
340+ return new Promise((resolve, reject) => {
341+ let oldBalance = null;
342+ const checkForBalance = async () => {
343+ const balance = await web3.eth.getBalance(address);
344+ if (balance >= minBalance) {
347+ if (balance !== oldBalance) {
348+ askForFunding(address, minBalance, balance, updateProgress);
350+ setTimeout(checkForBalance, 13000);
357+async function sendAndAwaitConfirm(web3, signedTxs) {
358+ await Promise.all(signedTxs.map(tx => {
359+ console.log('sending...');
360+ return new Promise((resolve, reject) => {
361+ web3.eth.sendSignedTransaction(tx).then(res => {
362+ console.log('sent, now waiting for confirm!', res.transactionHash);
363+ waitForTransactionConfirm(web3, res.transactionHash)
365+ }).catch(async err => {
366+ // if there's an error, check if it's because the transaction was
367+ // already confirmed prior to sending.
368+ // this is really only the case in local dev environments.
369+ const txHash = web3.utils.keccak256(tx);
370+ console.log(err.message);
371+ if (err.message.slice(0,54) ===
372+ "Returned error: the tx doesn't have the correct nonce.") {
373+ console.log('nonce error, awaiting confirm', err.message.slice(55));
374+ //TODO max wait time before assume failure?
375+ let res = await waitForTransactionConfirm(web3, txHash);
377+ else reject(new Error('Unexpected tx failure'));
379+ const confirmed = await isTransactionConfirmed(web3, txHash);
380+ console.log('error, but maybe confirmed:', confirmed);
381+ if (confirmed) resolve();
389+function awaitForResult(desired, retryTime, func) {
390+ return new Promise((resolve, reject) => {
391+ const retry = async () => {
392+ const result = await func();
393+ console.log('result vs desired', result, desired);
394+ if (result === desired) resolve();
395+ else setTimeout(retry, retryTime);
401+function askForFunding(address, amount, current, updateProgress) {
402+ updateProgress(`Please make sure ${address} has at least ${amount} wei, we'll continue once that's true. Current balance: ${current}. Waiting`);
119 generateWallet,
406 generateWallet,
120 downloadWallet,
407 downloadWallet,
121 INVITE_STAGES,
409 INVITE_STAGES,
122− WALLET_STATES
410+ WALLET_STATES,