on a8a2d64 help sign in
bridge/src/lib/tank.js
134 lines 4.0 kB history raw
1// /lib/tank: functions for funding transactions
3import retry from 'async-retry';
4import { toBN } from 'web3-utils';
5import { RETRY_OPTIONS, waitForTransactionConfirm } from './txn';
7//NOTE if accessing this in a localhost configuration fails with "CORS request
8// did not succeed", you might need to visit localhost:3001 or whatever
9// explicitly and tell your browser that's safe to access.
10// https://stackoverflow.com/a/53011185/1334324
11const baseUrl = 'https://localhost:3001';
13function sendRequest(where, what) {
14 return new Promise((resolve, reject) => {
15 fetch(baseUrl + where, {
16 method: 'POST',
17 cache: 'no-cache',
18 headers: {
19 'Content-Type': 'application/json',
20 },
21 body: JSON.stringify(what),
22 })
23 .then(response => {
24 if (response.ok) {
25 resolve(response.json());
26 } else {
27 reject(response);
28 }
29 })
30 .catch(reject);
31 });
34const remainingTransactions = point => {
35 if (typeof point === 'string') point = Number(point);
36 return sendRequest('/point', { point: point });
37};
39const fundTransactions = signedTxs => {
40 return sendRequest('/request', { txs: signedTxs });
41};
43// resolves when address has at least cost balance
44// returns true if gas tank was used, false otherwise
45// askForFunding: callback that takes (address, requiredBalance, currentBalance)
46// and tells the user to get that address the required balance
47// gotFunding: optional callback that stops telling the user to go get funding
48const ensureFundsFor = async (
49 web3,
50 point,
51 address,
52 cost,
53 signedTxs,
54 askForFunding,
55 gotFunding
56) => {
57 const balance = toBN(await web3.eth.getBalance(address));
58 cost = toBN(cost);
60 if (balance.gte(cost)) {
61 console.log(
62 `tank: already have sufficient funds: ${address} proposed ` +
63 `transaction(s) that will cost ${cost.toString()}wei but it ` +
64 `already has ${balance.toString()}wei`
65 );
66 return false;
67 }
69 try {
70 // TODO: if we can't always (easily) provide a point, and the
71 // fundTransactions call is gonna fail anyway, should we maybe
72 // just not bother doing this check in the first place?
73 if (point !== null) {
74 const fundsRemaining = await remainingTransactions(point);
75 if (fundsRemaining < signedTxs.length) {
76 throw new Error('tank: request invalid');
77 }
78 } else {
79 console.log('tank: no point provided. skipping remaining-funds check...');
80 }
82 const res = await fundTransactions(signedTxs);
83 if (!res.success) {
84 throw new Error(`tank: request rejected ${JSON.stringify(res)}`);
85 }
87 await waitForTransactionConfirm(web3, res.txHash);
89 const newBalance = toBN(await web3.eth.getBalance(address));
91 // sanity check
92 if (newBalance.lt(cost)) {
93 throw new Error(
94 `tank: transaction funded but the new balance of ` +
95 `${newBalance.toString()}wei is still less than ` +
96 `${cost.toString()}wei.`
97 );
98 }
100 console.log(
101 `tank: funds have confirmed: ${address} now has ` +
102 `${newBalance.toString()}wei, up from ${balance.toString()}wei`
103 );
104 return true;
105 } catch (error) {
106 console.error(error);
107 await waitForBalance(
108 web3,
109 address,
110 cost.toString(),
111 askForFunding,
112 gotFunding
113 );
114 }
116 return false;
117};
119// returns a promise that resolves when address has at least minBalance
120function waitForBalance(web3, address, minBalance, askForFunding, gotFunding) {
121 minBalance = toBN(minBalance);
122 return retry(async (bail, n) => {
123 const balance = toBN(await web3.eth.getBalance(address));
124 if (balance.gte(minBalance)) {
125 gotFunding && gotFunding();
126 return;
127 } else {
128 askForFunding(address, minBalance.toString(), balance);
129 throw new Error('retrying');
130 }
131 }, RETRY_OPTIONS);
134export { remainingTransactions, fundTransactions, ensureFundsFor };