Skip to content

Commit

Permalink
docs(ocean-dbtc-burning-bot): DFIP 2201-A, an automated wallet bot to…
Browse files Browse the repository at this point in the history
… burn dBTC
  • Loading branch information
fuxingloh committed Jan 16, 2022
1 parent 1fb168e commit 443430c
Show file tree
Hide file tree
Showing 6 changed files with 276 additions and 0 deletions.
20 changes: 20 additions & 0 deletions examples/ocean-dbtc-burning-bot/.gitignore
Original file line number Diff line number Diff line change
@@ -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


29 changes: 29 additions & 0 deletions examples/ocean-dbtc-burning-bot/README.md
Original file line number Diff line number Diff line change
@@ -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
```
37 changes: 37 additions & 0 deletions examples/ocean-dbtc-burning-bot/build.js
Original file line number Diff line number Diff line change
@@ -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)
}
)
14 changes: 14 additions & 0 deletions examples/ocean-dbtc-burning-bot/package.json
Original file line number Diff line number Diff line change
@@ -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"
}
}
160 changes: 160 additions & 0 deletions examples/ocean-dbtc-burning-bot/src/main.ts
Original file line number Diff line number Diff line change
@@ -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<void> {
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<boolean> {
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<boolean> {
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<boolean> {
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<AddressToken | undefined> {
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<BigNumber> {
const address = await this.account.getAddress()

return new BigNumber(await this.client.address.getBalance(address))
}

private async send (txn: TransactionSegWit): Promise<void> {
const hex: string = new CTransactionSegWit(txn).toHex()
const txId: string = await this.client.rawtx.send({ hex: hex })

console.log(`Send TxId: ${txId}`)
}
}
16 changes: 16 additions & 0 deletions examples/ocean-dbtc-burning-bot/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"compilerOptions": {
"lib": [
"es2020"
],
"target": "esnext",
"module": "commonjs",
"moduleResolution": "node",
"strict": true,
"esModuleInterop": true,
"resolveJsonModule": true
},
"include": [
"src"
]
}

0 comments on commit 443430c

Please sign in to comment.