Shifty, the automated ShapeShift AI testing robot
const { delayUnlessShutdown } = require('shutin'); | |
const { depositMethods, settleMethods } = require('../shared/facts'); | |
const { n } = require('../shared/utils'); | |
const { fetchRate, convert } = require('../shared/rates'); | |
const { sample, values, chain, keys } = require('lodash'); | |
const { fetchInventorySnapshot } = require('../shared/db'); | |
const pReduce = require('p-reduce'); | |
const { fetchDepositAddress } = require('../web/backend/utils/address'); | |
const payWith = require('../shared/pay'); | |
const { safePromise } = require('safep'); | |
const { | |
resolvers: { | |
Mutation: { requestQuote }, | |
Query: { quote: fetchQuote }, | |
}, | |
} = require('../web/backend/schema'); | |
const delay = require('delay'); | |
const { canPayWith } = payWith; | |
const config = require('./config').fromEnv(); | |
const FINAL_DEPOSIT_STATES = ['settled', 'settle_fail', 'refunded', 'refund_fail']; | |
const fetchInventoryPerAsset = () => | |
fetchInventorySnapshot().then(snapshot => | |
chain(snapshot) | |
.groupBy('asset') | |
.mapValues(_ => | |
chain(_) | |
.sumBy('amount') | |
.value() | |
) | |
.value() | |
); | |
const shift = async () => { | |
const inventory = await fetchInventoryPerAsset(); | |
const inventoryAsUsd = await pReduce( | |
keys(inventory), | |
async (prev, asset) => ({ | |
...prev, | |
[asset]: await convert(asset, 'USD', inventory[asset]), | |
}), | |
{} | |
); | |
const fundedDepositMethods = chain(depositMethods) | |
.filter(_ => +inventoryAsUsd[_.asset] > +config.amountUsd) | |
.value(); | |
console.log(`${fundedDepositMethods.length} deposit methods can send $${config.amountUsd}`); | |
const possiblePaymentMethods = fundedDepositMethods.filter(canPayWith); | |
console.log( | |
`${possiblePaymentMethods.length} deposit methods are possible to send automatically` | |
); | |
const depositMethod = sample(possiblePaymentMethods); | |
console.log( | |
`Picked the deposit method ${depositMethod.id} (asset ${depositMethod.asset}) by random` | |
); | |
const settleMethod = sample(values(settleMethods).filter(_ => _.id !== depositMethod.id)); | |
console.log( | |
`Picked the settle method ${settleMethod.id} (asset ${settleMethod.asset}) by random` | |
); | |
const paymentDetails = await fetchDepositAddress(settleMethod.id); | |
const depositRate = await fetchRate(depositMethod.asset, 'USD'); | |
const amount = n(1) | |
.div(depositRate) | |
.times(config.amountUsd) | |
.toPrecision(6) | |
.toString(); | |
console.log('Creating quote...'); | |
const createdQuote = await requestQuote(null, { | |
input: { | |
depositMethodId: depositMethod.id, | |
settleMethodId: settleMethod.id, | |
settleAddress: paymentDetails.address, | |
amount, | |
testerId: config.testerId, | |
}, | |
}); | |
const { quoteId } = createdQuote; | |
console.log(`Created quote with id ${quoteId}`); | |
console.log(`Sending payment of ${amount} ${createdQuote.depositAsset}...`); | |
const { tx } = await payWith(depositMethod, { destination: createdQuote.depositAddress, amount }); | |
console.log(`Payment sent! Transaction: ${JSON.stringify(tx)}`); | |
console.log(`Waiting for the deposit to finalize...`); | |
while (true) { | |
const currentQuote = await fetchQuote(null, { quoteId }); | |
const [deposit] = currentQuote.deposits; | |
if (deposit && FINAL_DEPOSIT_STATES.includes(deposit.status)) { | |
console.log( | |
`Deposit reached final state: ${deposit.status}. Reason: ${deposit.reason || '<none>'}` | |
); | |
break; | |
} | |
await delay(5e3); | |
} | |
}; | |
async function worker() { | |
do { | |
const [error] = await safePromise(shift()); | |
if (error) { | |
console.error(`Failed to auto shift: ${error.stack}`); | |
} | |
} while (!(await delayUnlessShutdown(config.tickInterval))); | |
} | |
worker().then(process.exit); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment