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