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