diff --git a/bouncer/commands/go_bananas.ts b/bouncer/commands/go_bananas.ts new file mode 100755 index 0000000000..a346c00609 --- /dev/null +++ b/bouncer/commands/go_bananas.ts @@ -0,0 +1,201 @@ +#!/usr/bin/env -S pnpm tsx +import axios from 'axios'; +import { Asset, assetDecimals } from '@chainflip-io/cli/.'; +import bitcoin from 'bitcoinjs-lib'; +import { Tapleaf } from 'bitcoinjs-lib/src/types'; +import { blake2AsHex } from '@polkadot/util-crypto'; +import { + asciiStringToBytesArray, + getChainflipApi, + hexStringToBytesArray, + sleep, +} from '../shared/utils'; +import { requestNewSwap } from '../shared/perform_swap'; +import { testSwap } from '../shared/swapping'; +import { sendBtc } from '../shared/send_btc'; +import { createLpPool } from '../shared/create_lp_pool'; +import { provideLiquidity } from '../shared/provide_liquidity'; + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +async function call(method: string, params: any, id: string) { + return axios({ + method: 'post', + baseURL: 'http://localhost:10589', + headers: { 'Content-Type': 'application/json' }, + data: { + jsonrpc: '2.0', + id, + method, + params, + }, + }); +} + +function predictBtcAddress(pubkey: string, salt: number): string { + const saltScript = salt === 0 ? 'OP_0' : bitcoin.script.number.encode(salt).toString('hex'); + const script = bitcoin.script.fromASM(`${saltScript} OP_DROP ${pubkey} OP_CHECKSIG`); + const scriptTree: Tapleaf = { output: script }; + const address = + bitcoin.payments.p2tr({ + internalPubkey: Buffer.from( + 'eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee', + 'hex', + ), + scriptTree, + network: bitcoin.networks.regtest, + }).address ?? ''; + return address; +} + +// how to use this function: +/* +let chainflip = await getChainflipApi(); +let pubkey = ((await chainflip.query.environment.polkadotVaultAccountId()).toJSON()! as string).substring(2); +let salt = ((await chainflip.query.polkadotIngressEgress.channelIdCounter()).toJSON()! as number) + 1; +let dot = predictDotAddress(pubkey, salt); +console.log(dot); */ + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +function predictDotAddress(pubkey: string, salt: number): string { + const bufferSize = 16 + 32 + 2; + const buffer = new Uint8Array(bufferSize); + buffer.set(asciiStringToBytesArray('modlpy/utilisuba'), 0); + buffer.set(hexStringToBytesArray(pubkey), 16); + const littleEndianSalt = new ArrayBuffer(2); + new DataView(littleEndianSalt).setUint16(0, salt, true); + buffer.set(new Uint8Array(littleEndianSalt), 16 + 32); + const result = blake2AsHex(buffer, 256); + return result; +} + +function price2tick(price: number): number { + return Math.round(Math.log(Math.sqrt(price)) / Math.log(Math.sqrt(1.0001))); +} + +async function playLp(asset: string, price: number, liquidity: number) { + const spread = 0.01 * price; + const liquidityFine = liquidity * 1e6; + for (;;) { + const offset = (price * (Math.random() - 0.5)) / 20; + const buyTick = price2tick(price + offset + spread); + const sellTick = price2tick(price + offset - spread); + const result = await Promise.all([ + call( + 'lp_setLimitOrder', + ['Usdc', asset, 1, buyTick, '0x' + BigInt(liquidityFine).toString(16)], + `Buy ${asset}`, + ), + call( + 'lp_setLimitOrder', + [asset, 'Usdc', 1, sellTick, '0x' + BigInt(liquidityFine / price).toString(16)], + `Sell ${asset}`, + ), + ]); + result.forEach((r) => { + if (r.data.error) { + console.log(`Error [${r.data.id}]: ${JSON.stringify(r.data.error)}`); + } + if (r.data.result[0].collected_fees > 0) { + console.log(`Collected ${r.data.result[0].collected_fees / 10 ** 6} USDC in fees`); + } + if (r.data.result[0].bought_amount > 0) { + if (r.data.id === `Buy ${asset}`) { + console.log( + `Bought ${ + r.data.result[0].bought_amount / 10 ** assetDecimals[asset.toUpperCase() as Asset] + } ${asset} for Usdc`, + ); + } else { + console.log(`Bought ${r.data.result[0].bought_amount / 10 ** 6} Usdc for ${asset}`); + } + } + }); + await sleep(12000); + } +} + +async function launchTornado() { + const chainflip = await getChainflipApi(); + const epoch = (await chainflip.query.bitcoinVault.currentVaultEpochAndState()).toJSON()! + .epochIndex as number; + const pubkey = ( + (await chainflip.query.bitcoinVault.vaults(epoch)).toJSON()!.publicKey.current as string + ).substring(2); + const salt = + ((await chainflip.query.bitcoinIngressEgress.channelIdCounter()).toJSON()! as number) + 1; + const btcAddress = predictBtcAddress(pubkey, salt); + // shuffle + const assets: Asset[] = ['ETH', 'USDC', 'FLIP', 'DOT']; + for (let i = 0; i < 10; i++) { + const index1 = Math.floor(Math.random() * assets.length); + const index2 = Math.floor(Math.random() * assets.length); + const temp = assets[index1]; + assets[index1] = assets[index2]; + assets[index2] = temp; + } + let swap = await requestNewSwap(assets[0], 'BTC', btcAddress); + for (let i = 0; i < assets.length - 1; i++) { + swap = await requestNewSwap(assets[i + 1], assets[i], swap.depositAddress); + } + await requestNewSwap('BTC', assets[assets.length - 1], swap.depositAddress); + await sendBtc(btcAddress, 0.01); + console.log(btcAddress); +} + +const swapAmount = new Map([ + ['DOT', '3'], + ['ETH', '0.03'], + ['BTC', '0.006'], + ['USDC', '30'], + ['FLIP', '3'], +]); + +async function playSwapper() { + const assets: Asset[] = ['ETH', 'BTC', 'USDC', 'FLIP', 'DOT']; + for (;;) { + const src = assets.at(Math.floor(Math.random() * assets.length))!; + const dest = assets + .filter((x) => x !== src) + .at(Math.floor(Math.random() * (assets.length - 1)))!; + testSwap(src, dest, undefined, undefined, undefined, swapAmount.get(src)); + await sleep(3000); + } +} + +const price = new Map([ + ['DOT', 10], + ['ETH', 1000], + ['BTC', 10000], + ['USDC', 1], + ['FLIP', 10], +]); + +async function bananas() { + const liquidityUsdc = 10000; + + await Promise.all([ + createLpPool('ETH', price.get('ETH')!), + createLpPool('DOT', price.get('DOT')!), + createLpPool('BTC', price.get('BTC')!), + createLpPool('FLIP', price.get('FLIP')!), + ]); + + await Promise.all([ + provideLiquidity('USDC', 8 * liquidityUsdc), + provideLiquidity('ETH', (2 * liquidityUsdc) / price.get('ETH')!), + provideLiquidity('DOT', (2 * liquidityUsdc) / price.get('DOT')!), + provideLiquidity('BTC', (2 * liquidityUsdc) / price.get('BTC')!), + provideLiquidity('FLIP', (2 * liquidityUsdc) / price.get('FLIP')!), + ]); + await Promise.all([ + playLp('Eth', price.get('ETH')! * 10 ** (6 - 18), liquidityUsdc), + playLp('Btc', price.get('BTC')! * 10 ** (6 - 8), liquidityUsdc), + playLp('Dot', price.get('DOT')! * 10 ** (6 - 10), liquidityUsdc), + playLp('Flip', price.get('FLIP')! * 10 ** (6 - 18), liquidityUsdc), + playSwapper(), + launchTornado(), + ]); +} + +await bananas(); +process.exit(0); diff --git a/bouncer/shared/perform_swap.ts b/bouncer/shared/perform_swap.ts index 4b2d0db102..3109731229 100644 --- a/bouncer/shared/perform_swap.ts +++ b/bouncer/shared/perform_swap.ts @@ -99,6 +99,7 @@ export async function doPerformSwap( tag = '', messageMetadata?: CcmDepositMetadata, senderType = SenderType.Address, + amount?: string, ) { const oldBalance = await getBalance(destAsset, destAddress); @@ -111,7 +112,7 @@ export async function doPerformSwap( : Promise.resolve(); await (senderType === SenderType.Address - ? send(sourceAsset, depositAddress) + ? send(sourceAsset, depositAddress, amount) : sendViaCfTester(sourceAsset, depositAddress)); console.log(`${tag} Funded the address`); @@ -139,6 +140,7 @@ export async function performSwap( swapTag?: string, messageMetadata?: CcmDepositMetadata, senderType = SenderType.Address, + amount?: string, ) { const tag = swapTag ?? ''; @@ -155,7 +157,7 @@ export async function performSwap( tag, messageMetadata, ); - await doPerformSwap(swapParams, tag, messageMetadata, senderType); + await doPerformSwap(swapParams, tag, messageMetadata, senderType, amount); return swapParams; } diff --git a/bouncer/shared/swapping.ts b/bouncer/shared/swapping.ts index 84eb4c83f4..20ba2dd603 100644 --- a/bouncer/shared/swapping.ts +++ b/bouncer/shared/swapping.ts @@ -123,6 +123,7 @@ export async function testSwap( addressType?: BtcAddressType, messageMetadata?: CcmDepositMetadata, tagSuffix?: string, + amount?: string, ) { const { destAddress, tag } = await prepareSwap( sourceAsset, @@ -131,7 +132,7 @@ export async function testSwap( messageMetadata, tagSuffix, ); - return performSwap(sourceAsset, destAsset, destAddress, tag, messageMetadata); + return performSwap(sourceAsset, destAsset, destAddress, tag, messageMetadata, undefined, amount); } async function testSwapViaContract( sourceAsset: Asset,