Skip to content

Commit

Permalink
Exchange tokens via mento broker (#106)
Browse files Browse the repository at this point in the history
* confirmed swapped https://explorer.celo.org/alfajores/tx/0xa8caba01c5a530f8e187395c01615a7080be4b2ee62b4107689fabb90899b706/state


* something is wrong here why is it calculating price impact as so high? cant be right

* questions for ourselves

* fix quote vs actual discrepency

* numbers make sense when you think about it

* update description for exchange commands to be via mento instead of stability mechanism

* use oclif ux module for feedback rather than loggers


* fetch exchange rates from mento for the exchange:show command

* exchange:gold has been deprecated for a long time. lets remove

* improve ux feedback to users while swapping

* fix quote being off for swaps -- need to invert to oracles rate and use getAmountOut when getting quote for stable to celo swaps

---------

Co-authored-by: Aaron <[email protected]>
  • Loading branch information
aaronmgdr and aaronmgdr authored Feb 6, 2024
1 parent d25bd4a commit 0a3a570
Show file tree
Hide file tree
Showing 16 changed files with 389 additions and 155 deletions.
5 changes: 5 additions & 0 deletions .changeset/empty-beans-sell.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@celo/celocli': major
---

Remove the deprecated exchange:gold command. exchange:celo is a drop in replacement
5 changes: 5 additions & 0 deletions .changeset/light-humans-smell.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@celo/celocli': minor
---

Add support for swapping celo -- cStables via mento broker
6 changes: 4 additions & 2 deletions packages/cli/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
},
"scripts": {
"clean": "tsc -b . --clean",
"dev": "ts-node ./bin/dev.js",
"dev": "yarn build && ts-node ./bin/dev.js",
"build": "tsc -b .",
"docs": "./generate_docs.sh",
"lint": "yarn run --top-level eslint -c .eslintrc.js ",
Expand All @@ -47,6 +47,7 @@
"@celo/wallet-local": "^5.1.1",
"@ethereumjs/util": "8.0.5",
"@ledgerhq/hw-transport-node-hid": "^6.27.4",
"@mento-protocol/mento-sdk": "^0.2.2",
"@oclif/core": "^3.18.1",
"@oclif/plugin-autocomplete": "^3.0.5",
"@oclif/plugin-commands": "^3.1.1",
Expand All @@ -60,6 +61,7 @@
"chalk": "^2.4.2",
"command-exists": "^1.2.9",
"debug": "^4.1.1",
"ethers": "5",
"fs-extra": "^8.1.0",
"humanize-duration": "^3.29.0",
"path": "^0.12.7",
Expand Down Expand Up @@ -130,7 +132,7 @@
"description": "Participate in and view the state of Validator Elections"
},
"exchange": {
"description": "Exchange Celo Dollars and CELO via the stability mechanism"
"description": "Exchange Celo Dollars and CELO via Mento"
},
"governance": {
"description": "Interact with on-chain governance proposals and hotfixes"
Expand Down
76 changes: 52 additions & 24 deletions packages/cli/src/commands/exchange/celo.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,21 @@
import { StableToken } from '@celo/contractkit'
import { stableTokenInfos } from '@celo/contractkit/lib/celo-tokens'
import { Flags } from '@oclif/core'
import { Flags, ux } from '@oclif/core'
import BigNumber from 'bignumber.js'
import { BaseCommand } from '../../base'
import { newCheckBuilder } from '../../utils/checks'
import { displaySendTx, failWith } from '../../utils/cli'
import { binaryPrompt, displaySendEthersTxViaCK, displaySendTx } from '../../utils/cli'
import { CustomFlags } from '../../utils/command'
import { checkNotDangerousExchange } from '../../utils/exchange'
import { enumEntriesDupWithLowercase } from '../../utils/helpers'
import { getMentoBroker } from '../../utils/mento-broker-adaptor'

const largeOrderPercentage = 1
const deppegedPricePercentage = 20
const depeggedPricePercentage = 20

const stableTokenOptions = enumEntriesDupWithLowercase(Object.entries(StableToken))
export default class ExchangeCelo extends BaseCommand {
static description =
'Exchange CELO for StableTokens via the stability mechanism. (Note: this is the equivalent of the old exchange:gold)'
'Exchange CELO for StableTokens via Mento. (Note: this is the equivalent of the old exchange:gold)'

static flags = {
...BaseCommand.flags,
Expand Down Expand Up @@ -49,40 +49,68 @@ export default class ExchangeCelo extends BaseCommand {
const minBuyAmount = res.flags.forAtLeast
const stableToken = stableTokenOptions[res.flags.stableToken]

let exchange
try {
exchange = await kit.contracts.getExchange(stableToken)
} catch {
failWith(`The ${stableToken} token was not deployed yet`)
}

await newCheckBuilder(this).hasEnoughCelo(res.flags.from, sellAmount).runChecks()

const [celoToken, stableTokenAddress, { mento, brokerAddress }] = await Promise.all([
kit.contracts.getGoldToken(),
kit.registry.addressFor(stableTokenInfos[stableToken].contract),
getMentoBroker(kit.connection),
])

async function getQuote(tokenIn: string, tokenOut: string, amount: string) {
const quoteAmountOut = await mento.getAmountOut(tokenIn, tokenOut, amount)
const expectedAmountOut = quoteAmountOut.mul(99).div(100)
return expectedAmountOut
}

ux.action.start(`Fetching Quote`)
const expectedAmountToReceive = await getQuote(
celoToken.address,
stableTokenAddress,
sellAmount.toFixed()
)
ux.action.stop()
if (minBuyAmount.toNumber() === 0) {
const check = await checkNotDangerousExchange(
kit,
sellAmount,
largeOrderPercentage,
deppegedPricePercentage,
true,
new BigNumber(expectedAmountToReceive.toString()),
depeggedPricePercentage,
stableTokenInfos[stableToken]
)

if (!check) {
console.log('Cancelled')
return
ux.info('Cancelled')
ux.exit(0)
}
} else if (expectedAmountToReceive.lt(minBuyAmount.toString())) {
const check = await binaryPrompt(
'Warning: the expected amount to receive is less than the minimum amount to receive. Are you sure you want to continue?',
false
)
if (!check) {
ux.info('Cancelled')
ux.exit(0)
}
}

const celoToken = await kit.contracts.getGoldToken()

await displaySendTx(
'increaseAllowance',
celoToken.increaseAllowance(exchange.address, sellAmount.toFixed())
celoToken.increaseAllowance(brokerAddress, sellAmount.toFixed())
)

const exchangeTx = exchange.exchange(sellAmount.toFixed(), minBuyAmount!.toFixed(), true)
// Set explicit gas based on github.com/celo-org/celo-monorepo/issues/2541
await displaySendTx('exchange', exchangeTx, { gas: 300000 })
ux.log(
'Swapping',
sellAmount.toFixed(),
'CELO for estimated',
expectedAmountToReceive.toString(),
stableToken
)
const tx = await mento.swapIn(
celoToken.address,
stableTokenAddress,
sellAmount.toFixed(),
expectedAmountToReceive
)
await displaySendEthersTxViaCK('exchange', tx, kit.connection)
}
}
2 changes: 1 addition & 1 deletion packages/cli/src/commands/exchange/dollars.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { StableToken } from '@celo/contractkit'
import ExchangeStableBase from '../../exchange-stable-base'
import { CustomFlags } from '../../utils/command'
export default class ExchangeDollars extends ExchangeStableBase {
static description = 'Exchange Celo Dollars for CELO via the stability mechanism'
static description = 'Exchange Celo Dollars for CELO via Mento'

static flags = {
...ExchangeStableBase.flags,
Expand Down
2 changes: 1 addition & 1 deletion packages/cli/src/commands/exchange/euros.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { StableToken } from '@celo/contractkit'
import ExchangeStableBase from '../../exchange-stable-base'
import { CustomFlags } from '../../utils/command'
export default class ExchangeEuros extends ExchangeStableBase {
static description = 'Exchange Celo Euros for CELO via the stability mechanism'
static description = 'Exchange Celo Euros for CELO via Mento'

static flags = {
...ExchangeStableBase.flags,
Expand Down
17 changes: 0 additions & 17 deletions packages/cli/src/commands/exchange/gold.ts

This file was deleted.

2 changes: 1 addition & 1 deletion packages/cli/src/commands/exchange/reals.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { StableToken } from '@celo/contractkit'
import ExchangeStableBase from '../../exchange-stable-base'
import { CustomFlags } from '../../utils/command'
export default class ExchangeEuros extends ExchangeStableBase {
static description = 'Exchange Celo Brazilian Real (cREAL) for CELO via the stability mechanism'
static description = 'Exchange Celo Brazilian Real (cREAL) for CELO via Mento'

static flags = {
...ExchangeStableBase.flags,
Expand Down
25 changes: 10 additions & 15 deletions packages/cli/src/commands/exchange/show.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { StableTokenInfo } from '@celo/contractkit/lib/celo-tokens'
import { Flags, ux } from '@oclif/core'

import { BaseCommand } from '../../base'
import { getExchangeRates } from '../../utils/mento-broker-adaptor'

export default class ExchangeShow extends BaseCommand {
static description = 'Show the current exchange rates offered by the Exchange'
Expand All @@ -23,21 +23,16 @@ export default class ExchangeShow extends BaseCommand {
const { flags: parsedFlags } = await this.parse(ExchangeShow)

ux.action.start('Fetching exchange rates...')
const exchangeAmounts = await kit.celoTokens.forStableCeloToken(
async (info: StableTokenInfo) => {
const exchange = await kit.contracts.getContract(info.exchangeContract)
return {
buy: await exchange.getBuyTokenAmount(parsedFlags.amount as string, true),
sell: await exchange.getBuyTokenAmount(parsedFlags.amount as string, false),
}
}
)
const exchangeAmounts = await getExchangeRates(kit.connection, parsedFlags.amount as string)
ux.action.stop()

Object.entries(exchangeAmounts).forEach((element) => {
this.log(`CELO/${element[0]}:`)
this.log(`${parsedFlags.amount} CELO => ${element[1]!.buy} ${element[0]}`)
this.log(`${parsedFlags.amount} ${element[0]} => ${element[1]!.sell} CELO`)
})
exchangeAmounts
.filter((element) => element !== undefined)
.forEach((element) => {
this.log(`CELO/${element?.symbol}:`)
this.log(`${parsedFlags.amount} CELO => ${element?.buy} ${element?.symbol}`)
this.log(`${parsedFlags.amount} ${element?.symbol} => ${element?.sell} CELO`)
})
ux.exit(0)
}
}
2 changes: 1 addition & 1 deletion packages/cli/src/commands/exchange/stable.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { enumEntriesDupWithLowercase } from '../../utils/helpers'

const stableTokenOptions = enumEntriesDupWithLowercase(Object.entries(StableToken))
export default class ExchangeStable extends ExchangeStableBase {
static description = 'Exchange Stable Token for CELO via the stability mechanism'
static description = 'Exchange Stable Token for CELO via Mento'

static flags = {
...ExchangeStableBase.flags,
Expand Down
84 changes: 59 additions & 25 deletions packages/cli/src/exchange-stable-base.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
import { StableToken } from '@celo/contractkit'
import { CeloContract, StableToken } from '@celo/contractkit'
import { stableTokenInfos } from '@celo/contractkit/lib/celo-tokens'
import { ux } from '@oclif/core'
import BigNumber from 'bignumber.js'
import { BaseCommand } from './base'
import { newCheckBuilder } from './utils/checks'
import { displaySendTx, failWith } from './utils/cli'
import { binaryPrompt, displaySendEthersTxViaCK, displaySendTx } from './utils/cli'
import { CustomFlags } from './utils/command'
import { checkNotDangerousExchange } from './utils/exchange'

const largeOrderPercentage = 1
const deppegedPricePercentage = 20
import { getMentoBroker } from './utils/mento-broker-adaptor'
const depeggedPricePercentage = 20
export default class ExchangeStableBase extends BaseCommand {
static flags = {
...BaseCommand.flags,
Expand All @@ -31,8 +31,8 @@ export default class ExchangeStableBase extends BaseCommand {
async run() {
const kit = await this.getKit()
const res = await this.parse()
const sellAmount = res.flags.value
const minBuyAmount = res.flags.forAtLeast
const sellAmount = res.flags.value as BigNumber
const forAtLeast = res.flags.forAtLeast as BigNumber

if (!this._stableCurrency) {
throw new Error('Stable currency not set')
Expand All @@ -41,38 +41,72 @@ export default class ExchangeStableBase extends BaseCommand {
.hasEnoughStable(res.flags.from, sellAmount, this._stableCurrency)
.runChecks()

let stableToken
let exchange
try {
stableToken = await kit.contracts.getStableToken(this._stableCurrency)
exchange = await kit.contracts.getExchange(this._stableCurrency)
} catch {
failWith(`The ${this._stableCurrency} token was not deployed yet`)
const [stableToken, celoNativeTokenAddress, { mento, brokerAddress }] = await Promise.all([
kit.contracts.getStableToken(this._stableCurrency),
kit.registry.addressFor(CeloContract.GoldToken),
getMentoBroker(kit.connection),
])

ux.debug(`Prepare to exchange ${stableToken.address} for ${celoNativeTokenAddress}`)

async function getQuote(tokenIn: string, tokenOut: string, amount: string) {
const quoteAmountOut = await mento.getAmountOut(tokenIn, tokenOut, amount)
const expectedAmountOut = quoteAmountOut.mul(99).div(100)
return expectedAmountOut
}

if (minBuyAmount.toNumber() === 0) {
ux.action.start(`Fetching Quote`)

const expectedAmountToReceive = await getQuote(
stableToken.address,
celoNativeTokenAddress,
sellAmount.toFixed()
)
ux.action.stop()
if (forAtLeast.toNumber() === 0) {
const check = await checkNotDangerousExchange(
kit,
sellAmount,
largeOrderPercentage,
deppegedPricePercentage,
false,
stableTokenInfos[this._stableCurrency]
new BigNumber(expectedAmountToReceive.toString()),
depeggedPricePercentage,
stableTokenInfos[this._stableCurrency as StableToken],
true
)

if (!check) {
console.log('Cancelled')
return
ux.log('Cancelled')
ux.exit(0)
}
} else if (expectedAmountToReceive.lt(forAtLeast.toString())) {
const check = await binaryPrompt(
'Warning: the expected amount to receive is less than the minimum amount to receive. Are you sure you want to continue?',
false
)
if (!check) {
ux.log('Cancelled')
ux.exit(0)
}
}

await displaySendTx(
'increaseAllowance',
stableToken.increaseAllowance(exchange.address, sellAmount.toFixed())
stableToken.increaseAllowance(brokerAddress, sellAmount.toFixed())
)

const exchangeTx = exchange.exchange(sellAmount.toFixed(), minBuyAmount!.toFixed(), false)
// Set explicit gas based on github.com/celo-org/celo-monorepo/issues/2541
await displaySendTx('exchange', exchangeTx, { gas: 300000 })
ux.info(
'Swapping',
sellAmount.toFixed(),
this._stableCurrency,
'for estimated',
expectedAmountToReceive.toString(),
'CELO'
)
const tx = await mento.swapIn(
stableToken.address,
celoNativeTokenAddress,
sellAmount.toFixed(),
expectedAmountToReceive
)
await displaySendEthersTxViaCK('exchange', tx, kit.connection)
}
}
Loading

0 comments on commit 0a3a570

Please sign in to comment.