From 443430ca4c43498ac65a1a18a8b3669af4603c83 Mon Sep 17 00:00:00 2001 From: Fuxing Loh Date: Sun, 16 Jan 2022 21:02:49 +0800 Subject: [PATCH 1/2] docs(ocean-dbtc-burning-bot): DFIP 2201-A, an automated wallet bot to burn dBTC --- examples/ocean-dbtc-burning-bot/.gitignore | 20 +++ examples/ocean-dbtc-burning-bot/README.md | 29 ++++ examples/ocean-dbtc-burning-bot/build.js | 37 ++++ examples/ocean-dbtc-burning-bot/package.json | 14 ++ examples/ocean-dbtc-burning-bot/src/main.ts | 160 ++++++++++++++++++ examples/ocean-dbtc-burning-bot/tsconfig.json | 16 ++ 6 files changed, 276 insertions(+) create mode 100644 examples/ocean-dbtc-burning-bot/.gitignore create mode 100644 examples/ocean-dbtc-burning-bot/README.md create mode 100644 examples/ocean-dbtc-burning-bot/build.js create mode 100644 examples/ocean-dbtc-burning-bot/package.json create mode 100644 examples/ocean-dbtc-burning-bot/src/main.ts create mode 100644 examples/ocean-dbtc-burning-bot/tsconfig.json diff --git a/examples/ocean-dbtc-burning-bot/.gitignore b/examples/ocean-dbtc-burning-bot/.gitignore new file mode 100644 index 0000000000..4d3c86e9d1 --- /dev/null +++ b/examples/ocean-dbtc-burning-bot/.gitignore @@ -0,0 +1,20 @@ +# Yarn +.yarn/* +!.yarn/patches +!.yarn/releases +!.yarn/plugins +!.yarn/sdks +!.yarn/versions +.pnp.* + +# Node Modules +node_modules +package-lock.json + +# dist & pack +dist +*.tgz +.next +tsconfig.build.tsbuildinfo + + diff --git a/examples/ocean-dbtc-burning-bot/README.md b/examples/ocean-dbtc-burning-bot/README.md new file mode 100644 index 0000000000..60ffb975b1 --- /dev/null +++ b/examples/ocean-dbtc-burning-bot/README.md @@ -0,0 +1,29 @@ +# Ocean dBTC Burning Bot + +> [DFIP 2201-A: Solving unbacked dBTC](https://github.com/DeFiCh/dfips/issues/101) + +Part of DFIP 2201-A requirement; an automated wallet bot to burn dBTC. + +This is a very simple dBTC burning bot that is deployed +on [df1qc8ptw6vc9588w6f53fvcjsjx0fntv3men407a9](https://defiscan.live/address/df1qc8ptw6vc9588w6f53fvcjsjx0fntv3men407a9) +address. + +## Implementation + +> BURN-DFI -> DFI Rewards -> BTC Swap -> BTC Burn + +With a predefined [`main()`](./src/main.ts) as the entry function, this bot take all DFI accumulated from BURN-DFI pool +rewards (3.5% from BTC-DFI). Swapping them from DFI to BTC and then finally sending it to the burn +address `8defichainBurnAddressXXXXXXXdRQkSm`. + +## Deployment + +Deployed on AWS lambda, this bot is compiled with [vercel/ncc](https://github.com/vercel/ncc) via [build.js](./build.js) +which produce a single file that support TypeScript and binary addons (tiny-secp256k1). + +```shell +npm i +npm run build + +# Upload ./dist/main.zip into AWS Lambda +``` diff --git a/examples/ocean-dbtc-burning-bot/build.js b/examples/ocean-dbtc-burning-bot/build.js new file mode 100644 index 0000000000..241b307c81 --- /dev/null +++ b/examples/ocean-dbtc-burning-bot/build.js @@ -0,0 +1,37 @@ +const util = require('util') +const exec = util.promisify(require('child_process').exec) + +function handleExec (res) { + if (res.stderr) { + console.error(res.stderr) + } + + if (res.error) { + console.log(res.error.message) + throw res.error + } +} + +/** + * Build and ZIP for AWS Lambda Execution + */ +async function buildLambda (file = 'main') { + { + const command = `npx --package @vercel/ncc ncc build ./src/${file}.ts --source-map -o ./dist/${file}` + const res = await exec(command, { cwd: __dirname }) + handleExec(res) + } + + { + const res = await exec(`zip -r -j ./dist/${file}.zip ./dist/${file}/*`) + handleExec(res) + } + + console.log(`src/${file}.ts -> dist/${file}.zip`) +} + +buildLambda().catch( + e => { + console.error(e) + } +) diff --git a/examples/ocean-dbtc-burning-bot/package.json b/examples/ocean-dbtc-burning-bot/package.json new file mode 100644 index 0000000000..d216b2b7e8 --- /dev/null +++ b/examples/ocean-dbtc-burning-bot/package.json @@ -0,0 +1,14 @@ +{ + "private": true, + "scripts": { + "build": "node build.js" + }, + "dependencies": { + "@defichain/jellyfish-wallet-classic": "latest", + "@defichain/whale-api-client": "latest", + "@defichain/whale-api-wallet": "latest" + }, + "devDependencies": { + "@vercel/ncc": "latest" + } +} diff --git a/examples/ocean-dbtc-burning-bot/src/main.ts b/examples/ocean-dbtc-burning-bot/src/main.ts new file mode 100644 index 0000000000..edb7d27c7d --- /dev/null +++ b/examples/ocean-dbtc-burning-bot/src/main.ts @@ -0,0 +1,160 @@ +import { WIF } from '@defichain/jellyfish-crypto' +import { WalletClassic } from '@defichain/jellyfish-wallet-classic' +import { WhaleApiClient } from '@defichain/whale-api-client' +import { fromAddress } from '@defichain/jellyfish-address' +import { MainNet } from '@defichain/jellyfish-network' +import { WhaleWalletAccount } from '@defichain/whale-api-wallet' +import { CTransactionSegWit, TransactionSegWit } from '@defichain/jellyfish-transaction' +import { BigNumber } from 'bignumber.js' +import { AddressToken } from '@defichain/whale-api-client/dist/api/address' + +/* eslint-disable @typescript-eslint/no-non-null-assertion */ +const BURN_ADDRESS_SCRIPT = fromAddress('8defichainBurnAddressXXXXXXXdRQkSm', 'mainnet')!.script + +/** + * Initialize WhaleApiClient connected to ocean.defichain.com/v0 + */ +const client = new WhaleApiClient({ + url: 'https://ocean.defichain.com', + version: 'v0' +}) + +// You can `dumpprivkey [address]` with defi-cli, only Bech32 address (df1...) are supported. +const PRIVATE_KEY = 'PRIVATE_KEY_IN_WIF' + +export async function main (): Promise { + const wallet = new WalletClassic(WIF.asEllipticPair(PRIVATE_KEY)) + const sequencer = new BurnProgram(client, wallet) + + // Refill UTXO if balance is getting low + if (await sequencer.accountToUTXO()) { + return + } + + // Burn dBTC by sending to burn address + if (await sequencer.burnDBTC()) { + return + } + + // Swap DFI to BTC + await sequencer.swapDFIDBTC() +} + +export class BurnProgram { + constructor ( + private readonly client: WhaleApiClient, + private readonly wallet: WalletClassic, + private readonly account = new WhaleWalletAccount(client, wallet, MainNet) + ) { + } + + /** + * Refill UTXO when there is less than 1.1 UTXO. + */ + async accountToUTXO (): Promise { + const utxoBalance = await this.getUTXOBalance() + console.log(`UTXO Balance: ${utxoBalance.toFixed()}`) + + if (utxoBalance.gte(new BigNumber('1.1'))) { + return false + } + console.log('Refilling UTXO') + + const script = await this.account.getScript() + const txn = await this.account.withTransactionBuilder().account.accountToUtxos({ + from: script, + balances: [ + { + token: 0, + amount: new BigNumber('1.0') + } + ], + mintingOutputsStart: 2 // 0: DfTx, 1: change, 2: minted utxos (mandated by jellyfish SDK) + }, script) + + await this.send(txn) + return true + } + + /** + * Burn dBTC there is more than 0.1 BTC + */ + async burnDBTC (): Promise { + const dBTC = await this.getTokenBalance('BTC', new BigNumber('0.1')) + if (dBTC === undefined) { + return false + } + console.log(`Burning dBTC: ${dBTC.amount}`) + + const script = await this.account.getScript() + const txn = await this.account.withTransactionBuilder().account.accountToAccount({ + from: script, + to: [{ + script: BURN_ADDRESS_SCRIPT, + balances: [{ + token: parseInt(dBTC.id), + amount: new BigNumber(dBTC.amount) + }] + }] + }, script) + + await this.send(txn) + return true + } + + /** + * Swap DFI to dBTC when there is more than 100 DFI + */ + async swapDFIDBTC (): Promise { + const dfi = await this.getTokenBalance('DFI', new BigNumber('100')) + if (dfi === undefined) { + console.log('DFI Account balance less than 100') + return false + } + console.log(`Swap ${dfi.amount} DFI to dBTC`) + + const script = await this.account.getScript() + const txn = await this.account.withTransactionBuilder().dex.compositeSwap({ + poolSwap: { + fromScript: script, + toScript: script, + fromTokenId: 0, + toTokenId: 2, + fromAmount: new BigNumber(dfi.amount), + maxPrice: new BigNumber('9223372036854775807') + }, + pools: [] + }, script) + + await this.send(txn) + return true + } + + /** + * Get token balance from wallet with minBalance + */ + private async getTokenBalance (symbol: string, minBalance: BigNumber): Promise { + const address = await this.account.getAddress() + const tokens = await this.client.address.listToken(address, 200) + + return tokens.find(token => { + return token.isDAT && token.symbol === symbol && new BigNumber(token.amount).gte(minBalance) + }) + } + + /** + * Get current wallet UTXO balance + */ + private async getUTXOBalance (): Promise { + const address = await this.account.getAddress() + + return new BigNumber(await this.client.address.getBalance(address)) + } + + private async send (txn: TransactionSegWit): Promise { + const hex: string = new CTransactionSegWit(txn).toHex() + const txId: string = await this.client.rawtx.send({ hex: hex }) + + console.log(`Send TxId: ${txId}`) + } +} diff --git a/examples/ocean-dbtc-burning-bot/tsconfig.json b/examples/ocean-dbtc-burning-bot/tsconfig.json new file mode 100644 index 0000000000..8086f6ef17 --- /dev/null +++ b/examples/ocean-dbtc-burning-bot/tsconfig.json @@ -0,0 +1,16 @@ +{ + "compilerOptions": { + "lib": [ + "es2020" + ], + "target": "esnext", + "module": "commonjs", + "moduleResolution": "node", + "strict": true, + "esModuleInterop": true, + "resolveJsonModule": true + }, + "include": [ + "src" + ] +} From be0c4028dea98ef5f7453f2dfcb45b0db0c95f11 Mon Sep 17 00:00:00 2001 From: Fuxing Loh Date: Sun, 16 Jan 2022 21:10:01 +0800 Subject: [PATCH 2/2] eslint ignore examples --- .eslintrc | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.eslintrc b/.eslintrc index e3bf310a56..90adc15d99 100644 --- a/.eslintrc +++ b/.eslintrc @@ -9,7 +9,8 @@ "ignorePatterns": [ "dist", "*.json", - "website" + "website", + "examples" ], "rules": { "curly": [