ethers-redispatch-signer

ethers.js RedispatchSigner to redispatch transactions

Usage no npm install needed!

<script type="module">
  import ethersRedispatchSigner from 'https://cdn.skypack.dev/ethers-redispatch-signer';
</script>

README

ethers-redispatch-signer

ethers.Signer subclass which accepts another signer in its constructor and wraps it where if the RedispatchSigner is used to send transactions, the transaction will be queued in the PersistenceAdapter instance associated wtih it, where it will be processed with higher gasPrice values during gas price spikes, until it is eventually mined.

Transaction objects returned by RedispatchSigner#sendTransaction implement an alternative tx.wait() where the return value is always the transaction that was actually mined no matter how many times it was redispatched, or it will be the transaction that was used to cancel the transaction.

await tx.cancel() is implemented to allow you to cancel the transaction. By default it will use a value for gasPrice that is twice the value of the value returned by signer.provider.getGasPrice()

You can also supply await tx.cancel({ multiplier: 3 }) or await tx.cancel({ gasPrice: ethers.utils.parseUnits('1000', 9) }) (those values are examples).

Be sure you do not cancel with a gasPrice which is lower than the exiting tx.gasPrice or cancel will throw.

Usage


const { RedispatchSigner } = require('ethers-redispatch-signer');

const signer = new RedispatchSigner(new JsonRpcProvider('http://localhost:8545').getSigner(0))
const redispatchingContract = contract.connect(signer);

const tx = await redispatchingContract.someFunction();
tx.wait().then((v) => {
  console.log('was cancelled: ' + Boolean(v.data === '0x')); // tx.wait() will resolve with either the cancel tx or the real tx, depending which one gets mined first
});
await tx.cancel();
// both txs will race .. probably this will get cancelled

await cancelTx.wait();

PersistenceAdapter

You can implement your own strategy around persisting the queue used by RedispatchSigner internally.

Create a subclass of PersistenceAdapter from require('@fauxbands/providers/redispatch-signer/persistence-adapter').PersistenceAdapter which implements iterateTransactions(function (watcher) { // logic }) as well as track(watcher) which accepts a TransactionWatcher object.

Create a subclass of RedispatchSigner which implements

static getDefaultPersistenceAdapter() which should return an instance of your new PersistenceAdapter class

When a transaction is sent through your signer, signer.persistence.track(signer.wrapTransaction(tx)) will be called. On every block update, iterateTranscations will be called and each transaction watcher will be passed in as an argument. The default implementation will call await watch.trigger(currentGasPrice)

If you subclass TransactionWatcher, you should implement static newGas(newGasPrice, oldGasPrice) which should return the gas price you want to use with a new transaction, and you should implement trigger(newGasPrice) which is where you should handle gasPrice updates

Further work

  • FilesystemPersistenceAdapter
  • RedisPersistenceAdapter