-
Notifications
You must be signed in to change notification settings - Fork 37
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
docs(ocean-dbtc-burning-bot): DFIP 2201-A, an automated wallet bot to…
… burn dBTC
- Loading branch information
Showing
6 changed files
with
276 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
|
||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | ||
} | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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" | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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}`) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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" | ||
] | ||
} |