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

Exchange tokens via mento broker #106

Merged
merged 18 commits into from
Feb 6, 2024
Merged
Show file tree
Hide file tree
Changes from 17 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
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 at least',
aaronmgdr marked this conversation as resolved.
Show resolved Hide resolved
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
82 changes: 58 additions & 24 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 minBuyAmount = 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}`)

// note using getAmountIn here to match way rate is shown in the oracles
async function getQuote(tokenIn: string, tokenOut: string, amount: string) {
const quoteAmountOut = await mento.getAmountIn(tokenIn, tokenOut, amount)
const expectedAmountOut = quoteAmountOut.mul(99).div(100)
aaronmgdr marked this conversation as resolved.
Show resolved Hide resolved
return expectedAmountOut
}

ux.action.start(`Fetching Quote`)

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

if (!check) {
console.log('Cancelled')
return
ux.log('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.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 at least',
expectedAmountToReceive.toString(),
aaronmgdr marked this conversation as resolved.
Show resolved Hide resolved
' CELO'
)
const tx = await mento.swapIn(
stableToken.address,
celoNativeTokenAddress,
sellAmount.toFixed(),
expectedAmountToReceive
)
await displaySendEthersTxViaCK('exchange', tx, kit.connection)
}
}
Loading
Loading