on abbbcbc help sign in
bridge/src/lib/tank.js
121 lines 3.9 kB history raw
1// /lib/tank: functions for funding transactions
3import retry from 'async-retry';
4import { RETRY_OPTIONS, waitForTransactionConfirm } from './txn';
6//NOTE if accessing this in a localhost configuration fails with "CORS request
7// did not succeed", you might need to visit localhost:3001 or whatever
8// explicitly and tell your browser that's safe to access.
9// https://stackoverflow.com/a/53011185/1334324
10const baseUrl = 'https://localhost:3001';
12function sendRequest(where, what) {
13 return new Promise((resolve, reject) => {
14 fetch(baseUrl + where, {
15 method: 'POST',
16 cache: 'no-cache',
17 headers: {
18 'Content-Type': 'application/json',
19 },
20 body: JSON.stringify(what),
21 })
22 .then(response => {
23 if (response.ok) {
24 resolve(response.json());
25 } else {
26 reject(response);
27 }
28 })
29 .catch(reject);
30 });
33const remainingTransactions = point => {
34 if (typeof point === 'string') point = Number(point);
35 return sendRequest('/point', { point: point });
36};
38const fundTransactions = signedTxs => {
39 return sendRequest('/request', { txs: signedTxs });
40};
42// resolves when address has at least cost balance
43// returns true if gas tank was used, false otherwise
44// askForFunding: callback that takes (address, requiredBalance, currentBalance)
45// and tells the user to get that address the required balance
46// gotFunding: optional callback that stops telling the user to go get funding
47const ensureFundsFor = async (
48 web3,
49 point,
50 address,
51 cost,
52 signedTxs,
53 askForFunding,
54 gotFunding
55) => {
56 let balance = await web3.eth.getBalance(address);
58 if (cost > balance) {
59 try {
60 if (point !== null) {
61 const fundsRemaining = await remainingTransactions(point);
62 if (fundsRemaining < signedTxs.length) {
63 throw new Error('tank: request invalid');
64 }
65 } else {
66 console.log('tank: skipping remaining-funds check');
67 //TODO if we can't always (easily) provide a point, and the
68 // fundTransactions call is gonna fail anyway, should we maybe
69 // just not bother doing this check in the first place?
70 }
72 const res = await fundTransactions(signedTxs);
73 if (!res.success) {
74 throw new Error('tank: request rejected');
75 } else {
76 await waitForTransactionConfirm(web3, res.txHash);
77 let newBalance = await web3.eth.getBalance(address);
78 console.log(
79 'tank: funds have confirmed',
80 balance >= cost,
81 balance,
82 newBalance
83 );
84 return true;
85 }
86 } catch (e) {
87 console.log('tank: funding failed', e);
88 await waitForBalance(web3, address, cost, askForFunding, gotFunding);
89 }
90 } else {
91 console.log('tank: already have sufficient funds');
92 }
93 return false;
94};
96// returns a promise that resolves when address has at least minBalance
97function waitForBalance(web3, address, minBalance, askForFunding, gotFunding) {
98 let oldBalance = null;
99 return retry(async (bail, n) => {
100 const balance = await web3.eth.getBalance(address);
101 if (balance >= minBalance) {
102 // if we ever asked for funding, retract that request now
103 if (gotFunding && oldBalance !== null) {
104 gotFunding();
105 }
106 return;
107 } else {
108 if (balance !== oldBalance) {
109 // TODO: minBalance is a `number` type
110 // but we want to display ETH, which will never accept a number
111 // but instead wants string or BN/BigNumber.
112 // this will be heavily refactored to correctly do BN math, but until
113 // then we'll manually convert this number to an integer string
114 askForFunding(address, minBalance.toFixed(), balance);
115 }
116 throw new Error('retrying');
117 }
118 }, RETRY_OPTIONS);
121export { remainingTransactions, fundTransactions, ensureFundsFor };