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