diff --git a/packages/cli/src/commands/account/balance.ts b/packages/cli/src/commands/account/balance.ts index 79eb83567ca..9ab49cf94e3 100644 --- a/packages/cli/src/commands/account/balance.ts +++ b/packages/cli/src/commands/account/balance.ts @@ -1,22 +1,36 @@ import { BaseCommand } from '../../base' -import { printValueMap } from '../../utils/cli' -import { Args } from '../../utils/command' +import { failWith, printValueMap } from '../../utils/cli' +import { Args, Flags } from '../../utils/command' export default class Balance extends BaseCommand { - static description = 'View Celo Dollar and Gold balances for an address' + static description = 'View Celo Stables and CELO balances for an address' static flags = { ...BaseCommand.flags, + erc20Address: Flags.address({ + description: 'Address of generic ERC-20 token to also check balance for', + }), } static args = [Args.address('address')] - static examples = ['balance 0x5409ed021d9299bf6814279a6a1411a7e866a631'] + static examples = [ + 'balance 0x5409ed021d9299bf6814279a6a1411a7e866a631', + 'balance 0x5409ed021d9299bf6814279a6a1411a7e866a631 --erc20Address 0x765DE816845861e75A25fCA122bb6898B8B1282a', + ] async run() { - const { args } = this.parse(Balance) + const { args, flags } = this.parse(Balance) console.log('All balances expressed in units of 10^-18.') printValueMap(await this.kit.getTotalBalance(args.address)) + if (flags.erc20Address) { + try { + const erc20 = await this.kit.contracts.getErc20(flags.erc20Address) + printValueMap({ erc20: await erc20.balanceOf(args.address) }) + } catch { + failWith('Invalid erc20 address') + } + } } } diff --git a/packages/cli/src/commands/exchange/show.ts b/packages/cli/src/commands/exchange/show.ts index 3760e422c34..e6bc69dbd2e 100644 --- a/packages/cli/src/commands/exchange/show.ts +++ b/packages/cli/src/commands/exchange/show.ts @@ -1,4 +1,4 @@ -import { StableTokenInfo } from '@celo/contractkit/src/celo-tokens' +import { StableTokenInfo } from '@celo/contractkit/lib/celo-tokens' import { flags } from '@oclif/command' import { cli } from 'cli-ux' import { BaseCommand } from '../../base' diff --git a/packages/cli/src/commands/transfer/erc20.ts b/packages/cli/src/commands/transfer/erc20.ts new file mode 100644 index 00000000000..b326abff8b6 --- /dev/null +++ b/packages/cli/src/commands/transfer/erc20.ts @@ -0,0 +1,57 @@ +import { Ierc20 } from '@celo/contractkit/lib/generated/IERC20' +import { Erc20Wrapper } from '@celo/contractkit/lib/wrappers/Erc20Wrapper' +import { flags } from '@oclif/command' +import BigNumber from 'bignumber.js' +import { BaseCommand } from '../../base' +import { newCheckBuilder } from '../../utils/checks' +import { displaySendTx, failWith } from '../../utils/cli' +import { Flags } from '../../utils/command' + +export default class TransferErc20 extends BaseCommand { + static description = 'Transfer ERC20 to a specified address' + + static flags = { + ...BaseCommand.flags, + erc20Address: Flags.address({ + required: true, + description: "Custom erc20 to check it's balance too", + }), + from: Flags.address({ + required: true, + description: 'Address of the sender', + }), + to: Flags.address({ + required: true, + description: 'Address of the receiver', + }), + value: flags.string({ + required: true, + description: 'Amount to transfer (in wei)', + }), + } + + static examples = [ + 'erc20 --erc20Address 0x765DE816845861e75A25fCA122bb6898B8B1282a --from 0xa0Af2E71cECc248f4a7fD606F203467B500Dd53B --to 0x5409ed021d9299bf6814279a6a1411a7e866a631 --value 10000000000000000000', + ] + + async run() { + const res = this.parse(TransferErc20) + + const from: string = res.flags.from + const to: string = res.flags.to + const value = new BigNumber(res.flags.value) + + this.kit.defaultAccount = from + let celoToken: Erc20Wrapper + try { + celoToken = await this.kit.contracts.getErc20(res.flags.erc20Address) + // this call allow us to check if it is a valid erc20 + await celoToken.balanceOf(res.flags.from) + } catch { + failWith('Invalid erc20 address') + } + await newCheckBuilder(this).hasEnoughErc20(from, value, res.flags.erc20Address).runChecks() + + await displaySendTx('transfer', celoToken.transfer(to, value.toFixed())) + } +} diff --git a/packages/cli/src/utils/checks.ts b/packages/cli/src/utils/checks.ts index acfe9bb034d..6be69012c9e 100644 --- a/packages/cli/src/utils/checks.ts +++ b/packages/cli/src/utils/checks.ts @@ -328,6 +328,16 @@ class CheckBuilder { ) } + hasEnoughErc20 = (account: Address, value: BigNumber, erc20: Address) => { + const valueInEth = this.kit.connection.web3.utils.fromWei(value.toFixed(), 'ether') + return this.addCheck(`Account has at least ${valueInEth} erc20 token`, () => + this.kit.contracts + .getErc20(erc20) + .then((goldToken) => goldToken.balanceOf(account)) + .then((balance) => balance.gte(value)) + ) + } + exceedsProposalMinDeposit = (deposit: BigNumber) => this.addCheck( `Deposit is greater than or equal to governance proposal minDeposit`, diff --git a/packages/docs/command-line-interface/account.md b/packages/docs/command-line-interface/account.md index a94dd98a692..de0d18a343c 100644 --- a/packages/docs/command-line-interface/account.md +++ b/packages/docs/command-line-interface/account.md @@ -57,19 +57,27 @@ _See code: [src/commands/account/authorize.ts](https://github.com/celo-org/celo- ## `celocli account:balance ADDRESS` -View Celo Dollar and Gold balances for an address +View Celo Stables and CELO balances for an address ``` -View Celo Dollar and Gold balances for an address +View Celo Stables and CELO balances for an address USAGE $ celocli account:balance ADDRESS OPTIONS - --globalHelp View all available global flags + --erc20Address=0xc1912fEE45d61C87Cc5EA59DaE31190FFFFf232d Address of generic ERC-20 + token to also check balance + for -EXAMPLE + --globalHelp View all available global + flags + +EXAMPLES balance 0x5409ed021d9299bf6814279a6a1411a7e866a631 + + balance 0x5409ed021d9299bf6814279a6a1411a7e866a631 --erc20Address + 0x765DE816845861e75A25fCA122bb6898B8B1282a ``` _See code: [src/commands/account/balance.ts](https://github.com/celo-org/celo-monorepo/tree/master/packages/cli/src/commands/account/balance.ts)_ diff --git a/packages/docs/command-line-interface/transfer.md b/packages/docs/command-line-interface/transfer.md index 8e0820a3346..7b4d23c949f 100644 --- a/packages/docs/command-line-interface/transfer.md +++ b/packages/docs/command-line-interface/transfer.md @@ -55,6 +55,40 @@ EXAMPLE _See code: [src/commands/transfer/dollars.ts](https://github.com/celo-org/celo-monorepo/tree/master/packages/cli/src/commands/transfer/dollars.ts)_ +## `celocli transfer:erc20` + +Transfer ERC20 to a specified address + +``` +Transfer ERC20 to a specified address + +USAGE + $ celocli transfer:erc20 + +OPTIONS + --erc20Address=0xc1912fEE45d61C87Cc5EA59DaE31190FFFFf232d (required) Custom erc20 to + check it's balance too + + --from=0xc1912fEE45d61C87Cc5EA59DaE31190FFFFf232d (required) Address of the + sender + + --globalHelp View all available global + flags + + --to=0xc1912fEE45d61C87Cc5EA59DaE31190FFFFf232d (required) Address of the + receiver + + --value=value (required) Amount to + transfer (in wei) + +EXAMPLE + erc20 --erc20Address 0x765DE816845861e75A25fCA122bb6898B8B1282a --from + 0xa0Af2E71cECc248f4a7fD606F203467B500Dd53B --to + 0x5409ed021d9299bf6814279a6a1411a7e866a631 --value 10000000000000000000 +``` + +_See code: [src/commands/transfer/erc20.ts](https://github.com/celo-org/celo-monorepo/tree/master/packages/cli/src/commands/transfer/erc20.ts)_ + ## `celocli transfer:euros` Transfer Celo Euros (cEUR) to a specified address. diff --git a/packages/sdk/contractkit/src/base.ts b/packages/sdk/contractkit/src/base.ts index 37a8c9f77e8..6e6e9f008fb 100644 --- a/packages/sdk/contractkit/src/base.ts +++ b/packages/sdk/contractkit/src/base.ts @@ -6,6 +6,7 @@ export enum CeloContract { DowntimeSlasher = 'DowntimeSlasher', Election = 'Election', EpochRewards = 'EpochRewards', + ERC20 = 'ERC20', Escrow = 'Escrow', Exchange = 'Exchange', ExchangeEUR = 'ExchangeEUR', diff --git a/packages/sdk/contractkit/src/contract-cache.ts b/packages/sdk/contractkit/src/contract-cache.ts index ccd6dbfb7f8..7bb5e2d9b18 100644 --- a/packages/sdk/contractkit/src/contract-cache.ts +++ b/packages/sdk/contractkit/src/contract-cache.ts @@ -1,5 +1,6 @@ import { CeloContract } from './base' import { StableToken } from './celo-tokens' +import { Ierc20 } from './generated/IERC20' import { ContractKit } from './kit' import { AccountsWrapper } from './wrappers/Accounts' import { AttestationsWrapper } from './wrappers/Attestations' @@ -7,6 +8,7 @@ import { BlockchainParametersWrapper } from './wrappers/BlockchainParameters' import { DoubleSigningSlasherWrapper } from './wrappers/DoubleSigningSlasher' import { DowntimeSlasherWrapper } from './wrappers/DowntimeSlasher' import { ElectionWrapper } from './wrappers/Election' +import { Erc20Wrapper } from './wrappers/Erc20Wrapper' // import { EpochRewardsWrapper } from './wrappers/EpochRewards' import { EscrowWrapper } from './wrappers/Escrow' import { ExchangeWrapper } from './wrappers/Exchange' @@ -31,6 +33,7 @@ const WrapperFactories = { [CeloContract.DowntimeSlasher]: DowntimeSlasherWrapper, [CeloContract.Election]: ElectionWrapper, // [CeloContract.EpochRewards]?: EpochRewardsWrapper, + [CeloContract.ERC20]: Erc20Wrapper, [CeloContract.Escrow]: EscrowWrapper, [CeloContract.Exchange]: ExchangeWrapper, [CeloContract.ExchangeEUR]: ExchangeWrapper, @@ -63,6 +66,7 @@ interface WrapperCacheMap { [CeloContract.DowntimeSlasher]?: DowntimeSlasherWrapper [CeloContract.Election]?: ElectionWrapper // [CeloContract.EpochRewards]?: EpochRewardsWrapper + [CeloContract.ERC20]?: Erc20Wrapper [CeloContract.Escrow]?: EscrowWrapper [CeloContract.Exchange]?: ExchangeWrapper [CeloContract.ExchangeEUR]?: ExchangeWrapper @@ -115,6 +119,9 @@ export class WrapperCache { // getEpochRewards() { // return this.getContract(CeloContract.EpochRewards) // } + getErc20(address: string) { + return this.getContract(CeloContract.ERC20, address) + } getEscrow() { return this.getContract(CeloContract.Escrow) } diff --git a/packages/sdk/contractkit/src/web3-contract-cache.ts b/packages/sdk/contractkit/src/web3-contract-cache.ts index 64ea7b76d65..a120d9d7aea 100644 --- a/packages/sdk/contractkit/src/web3-contract-cache.ts +++ b/packages/sdk/contractkit/src/web3-contract-cache.ts @@ -16,6 +16,7 @@ import { newFreezer } from './generated/Freezer' import { newGasPriceMinimum } from './generated/GasPriceMinimum' import { newGoldToken } from './generated/GoldToken' import { newGovernance } from './generated/Governance' +import { newIerc20 } from './generated/IERC20' import { newLockedGold } from './generated/LockedGold' import { newMetaTransactionWallet } from './generated/MetaTransactionWallet' import { newMetaTransactionWalletDeployer } from './generated/MetaTransactionWalletDeployer' @@ -40,6 +41,7 @@ export const ContractFactories = { [CeloContract.DowntimeSlasher]: newDowntimeSlasher, [CeloContract.Election]: newElection, [CeloContract.EpochRewards]: newEpochRewards, + [CeloContract.ERC20]: newIerc20, [CeloContract.Escrow]: newEscrow, [CeloContract.Exchange]: newExchange, [CeloContract.ExchangeEUR]: newExchangeEur, @@ -98,6 +100,9 @@ export class Web3ContractCache { getEpochRewards() { return this.getContract(CeloContract.EpochRewards) } + getErc20(address: string) { + return this.getContract(CeloContract.ERC20, address) + } getEscrow() { return this.getContract(CeloContract.Escrow) }