Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

docs(examples/ocean-dbtc-burning-bot): DFIP 2201-A, an automated wallet bot to burn dBTC #981

Merged
merged 2 commits into from
Jan 17, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .eslintrc
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@
"ignorePatterns": [
"dist",
"*.json",
"website"
"website",
"examples"
],
"rules": {
"curly": [
Expand Down
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
}

ivan-zynesis marked this conversation as resolved.
Show resolved Hide resolved
// 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: []
ivan-zynesis marked this conversation as resolved.
Show resolved Hide resolved
}, 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"
]
}