on 884e294 help sign in
bridge/src/lib/reticket.js
242 lines 6.1 kB history raw
1import * as azimuth from 'azimuth-js';
2import * as tank from './tank';
4import { hexify } from './txn';
5import {
6 deriveNetworkSeedFromUrbitWallet,
7 deriveNetworkKeys,
8 CRYPTO_SUITE_VERSION,
9} from './keys';
10import { addHexPrefix } from './wallet';
11import {
12 sendAndAwaitAllSerial,
13 signTransaction,
14 sendSignedTransaction,
15} from './txn';
16import getSuggestedGasPrice from './getSuggestedGasPrice';
17import { GAS_LIMITS } from './constants';
18import { toWei, toBN } from 'web3-utils';
20// the initial network key revision is always 1
21const INITIAL_NETWORK_KEY_REVISION = 1;
23export const TRANSACTION_PROGRESS = {
24 GENERATING: 0.01,
25 SIGNING: 0.15,
26 FUNDING: 0.3,
27 TRANSFERRING: 0.55,
28 CLEANING: 0.95,
29 DONE: 1.0,
30};
32export async function reticketPointBetweenWallets({
33 fromWallet,
34 fromWalletType,
35 fromWalletHdPath,
36 toWallet,
37 point,
38 web3,
39 contracts,
40 networkType,
41 onUpdate,
42 transferEth = false,
43 nextRevision = INITIAL_NETWORK_KEY_REVISION,
44}) {
45 const askForFunding = (address, minBalance, balance) =>
46 onUpdate({
47 type: 'askFunding',
48 value: { address, minBalance, balance },
49 });
51 const gotFunding = () => onUpdate({ type: 'gotFunding' });
52 const progress = state => onUpdate({ type: 'progress', state });
54 //
55 // generate transactions
56 //
58 progress(TRANSACTION_PROGRESS.GENERATING);
60 // transfer to invite wallet, so it can configure
61 //NOTE doing this in the invite wallet instead of the final wallet makes
62 // failure cases "solvable" by just telling the user to fully retry.
63 // in other words, the invite flow is effectively idempotent.
65 //TODO no harm done if we already owned it, but should still get a bool arg
66 // for skipping this, if it isn't too big a burden for callers
67 const transferTmpTx = azimuth.ecliptic.transferPoint(
68 contracts,
69 point,
70 fromWallet.address,
71 false
72 );
73 transferTmpTx.gas = GAS_LIMITS.TRANSFER;
75 // configure networking public keys
76 const seed = await deriveNetworkSeedFromUrbitWallet(toWallet, nextRevision);
78 const networkSeed = seed.matchWith({
79 Nothing: () => {
80 throw new Error('network seed not derived');
81 },
82 Just: p => p.value,
83 });
85 const networkKeys = deriveNetworkKeys(networkSeed);
87 const keysTx = azimuth.ecliptic.configureKeys(
88 contracts,
89 point,
90 addHexPrefix(networkKeys.crypt.public),
91 addHexPrefix(networkKeys.auth.public),
92 CRYPTO_SUITE_VERSION,
93 /* discontinuous */ false
94 );
95 keysTx.gas = GAS_LIMITS.CONFIGURE_KEYS;
97 // configure management proxy
99 const managementTx = azimuth.ecliptic.setManagementProxy(
100 contracts,
101 point,
102 toWallet.management.keys.address
103 );
104 managementTx.gas = GAS_LIMITS.SET_PROXY;
106 // set spawn & voting proxies situationally
108 let txs = [transferTmpTx, keysTx, managementTx];
109 if (
110 azimuth.azimuth.getPointSize(point) !== azimuth.azimuth.PointSize.Planet
111 ) {
112 const spawnTx = azimuth.ecliptic.setSpawnProxy(
113 contracts,
114 point,
115 toWallet.spawn.keys.address
116 );
117 spawnTx.gas = GAS_LIMITS.SET_PROXY;
118 txs.push(spawnTx);
120 if (
121 azimuth.azimuth.getPointSize(point) === azimuth.azimuth.PointSize.Galaxy
122 ) {
123 const votingTx = azimuth.ecliptic.setVotingProxy(
124 contracts,
125 point,
126 toWallet.voting.keys.address
127 );
128 votingTx.gas = GAS_LIMITS.SET_PROXY;
129 txs.push(votingTx);
130 }
131 }
133 // transfer configured point to user's new wallet
135 const transferFinalTx = azimuth.ecliptic.transferPoint(
136 contracts,
137 point,
138 toWallet.ownership.keys.address,
139 false
140 );
141 transferFinalTx.gas = GAS_LIMITS.TRANSFER;
142 txs.push(transferFinalTx);
144 //
145 // finalizing & signing transactions
146 //
148 progress(TRANSACTION_PROGRESS.SIGNING);
150 const suggestedGasPrice = await getSuggestedGasPrice(networkType);
151 const chainId = await web3.eth.net.getId();
152 const gasPrice = toWei(suggestedGasPrice.toFixed(), 'gwei');
153 const gasPriceBN = toBN(gasPrice);
154 let inviteNonce = await web3.eth.getTransactionCount(fromWallet.address);
155 const totalCost = txs.reduce(
156 (acc, tx) => acc.add(gasPriceBN.mul(toBN(tx.gas))),
157 toBN(0)
158 );
160 let txPairs = await Promise.all(
161 txs.map(async (tx, idx) => {
162 const stx = await signTransaction({
163 wallet: fromWallet,
164 walletType: fromWalletType,
165 walletHdPath: fromWalletHdPath,
166 networkType,
167 txn: tx,
168 nonce: inviteNonce + idx,
169 chainId,
170 gasPrice: suggestedGasPrice.toFixed(),
171 gasLimit: tx.gas,
172 });
173 return {
174 raw: hexify(stx.serialize()),
175 signed: stx,
176 };
177 })
178 );
180 inviteNonce = inviteNonce + txs.length;
182 //
183 // ensuring funding for transactions
184 //
186 progress(TRANSACTION_PROGRESS.FUNDING);
188 const usedTank = await tank.ensureFundsFor(
189 web3,
190 point,
191 fromWallet.address,
192 fromWalletType,
193 totalCost,
194 txPairs.map(p => p.raw),
195 askForFunding,
196 gotFunding
197 );
199 //
200 // sending and awaiting transactions
201 //
203 progress(TRANSACTION_PROGRESS.TRANSFERRING);
205 await sendAndAwaitAllSerial(web3, txPairs.map(p => p.signed), usedTank);
207 //
208 // move leftover eth
209 //
211 progress(TRANSACTION_PROGRESS.CLEANING);
213 // if non-trivial eth left in invite wallet, transfer to new ownership
214 let balance = toBN(await web3.eth.getBalance(fromWallet.address));
215 const gasLimit = GAS_LIMITS.SEND_ETH;
216 const sendEthCost = gasPriceBN.mul(toBN(gasLimit));
217 if (transferEth && balance.gt(sendEthCost)) {
218 const value = balance.sub(sendEthCost);
219 const txn = {
220 to: toWallet.ownership.keys.address,
221 value: value,
222 };
223 const stx = signTransaction({
224 wallet: fromWallet,
225 walletType: fromWalletType,
226 walletHdPath: fromWalletHdPath,
227 txn,
228 chainId,
229 networkType,
230 gasPrice: gasPriceBN,
231 gasLimit,
232 nonce: inviteNonce++,
233 });
234 // let stx = new Tx(tx);
235 // stx.sign(fromWallet.privateKey);
236 sendSignedTransaction(web3, stx).catch(err => {
237 console.log('error sending value tx, who cares', err);
238 });
239 }
241 progress(TRANSACTION_PROGRESS.DONE);