Skip to content
Open
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
4 changes: 1 addition & 3 deletions packages/cli/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,6 @@
"@celo/wallet-hsm-azure": "^8.0.3",
"@celo/wallet-ledger": "^8.0.3",
"@celo/wallet-local": "^8.0.3",
"@ethereumjs/util": "8.0.5",
"@ledgerhq/hw-transport-node-hid": "^6.28.5",
"@oclif/core": "^3.27.0",
"@oclif/plugin-autocomplete": "^3.2.0",
Expand All @@ -74,8 +73,7 @@
"fs-extra": "^8.1.0",
"humanize-duration": "^3.32.1",
"prompts": "^2.0.1",
"viem": "^2.33.2",
"web3": "1.10.4"
"viem": "^2.33.2"
},
"devDependencies": {
"@celo/dev-utils": "workspace:^",
Expand Down
70 changes: 36 additions & 34 deletions packages/cli/src/base.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,10 @@ import http from 'http'
import { tmpdir } from 'os'
import { MethodNotFoundRpcError } from 'viem'
import { privateKeyToAddress } from 'viem/accounts'
import Web3 from 'web3'
import { BaseCommand } from './base'
import Set from './commands/config/set'
import CustomHelp from './help'
import { stripAnsiCodesFromNestedArray, testLocallyWithWeb3Node } from './test-utils/cliUtils'
import { stripAnsiCodesFromNestedArray, testLocallyWithNode } from './test-utils/cliUtils'
import { mockRpcFetch } from './test-utils/mockRpc'
import { CustomFlags } from './utils/command'
import * as config from './utils/config'
Expand Down Expand Up @@ -62,17 +61,17 @@ describe('flags', () => {
describe('--node celo-sepolia', () => {
it('it connects to 11_142_220', async () => {
const command = new BasicCommand(['--node', 'celo-sepolia'], config)
const runnerWeb3 = await command.getWeb3()
const connectdChain = await runnerWeb3.eth.getChainId()
expect(connectdChain).toBe(11_142_220)
const runnerClient = await command.getPublicClient()
const connectdChain = runnerClient.chain
expect(connectdChain.id).toBe(11_142_220)
})
})
describe.each(['celo', 'mainnet'])('--node %s', (node) => {
it('it connects to 42220', async () => {
const command = new BasicCommand(['--node', node], config)
const runnerWeb3 = await command.getWeb3()
const connectdChain = await runnerWeb3.eth.getChainId()
expect(connectdChain).toBe(42220)
const runnerClient = await command.getPublicClient()
const connectdChain = runnerClient.chain
expect(connectdChain.id).toBe(42220)
})
})
describe('--node websockets', () => {
Expand Down Expand Up @@ -105,7 +104,7 @@ jest.mock('../package.json', () => ({
version: '5.2.3',
}))

testWithAnvilL2('BaseCommand', (web3: Web3) => {
testWithAnvilL2('BaseCommand', (provider) => {
const logSpy = jest.spyOn(console, 'log').mockImplementation()

beforeEach(() => {
Expand All @@ -118,7 +117,7 @@ testWithAnvilL2('BaseCommand', (web3: Web3) => {
const storedDerivationPath = readConfig(tmpdir()).derivationPath
console.info('storedDerivationPath', storedDerivationPath)
expect(storedDerivationPath).not.toBe(undefined)
await testLocallyWithWeb3Node(BasicCommand, ['--useLedger'], web3)
await testLocallyWithNode(BasicCommand, ['--useLedger'], provider)
expect(WalletLedgerExports.newLedgerWalletWithSetup).toHaveBeenCalledWith(
expect.anything(),
expect.objectContaining({
Expand All @@ -134,8 +133,8 @@ testWithAnvilL2('BaseCommand', (web3: Web3) => {
it('uses custom derivationPath', async () => {
const storedDerivationPath = readConfig(tmpdir()).derivationPath
const customPath = "m/44'/9000'/0'"
await testLocallyWithWeb3Node(Set, ['--derivationPath', customPath], web3)
await testLocallyWithWeb3Node(BasicCommand, ['--useLedger'], web3)
await testLocallyWithNode(Set, ['--derivationPath', customPath], provider)
await testLocallyWithNode(BasicCommand, ['--useLedger'], provider)
expect(WalletLedgerExports.newLedgerWalletWithSetup).toHaveBeenCalledWith(
expect.anything(),
expect.objectContaining({
Expand All @@ -147,12 +146,12 @@ testWithAnvilL2('BaseCommand', (web3: Web3) => {
baseDerivationPath: customPath,
})
)
await testLocallyWithWeb3Node(Set, ['--derivationPath', storedDerivationPath], web3)
await testLocallyWithNode(Set, ['--derivationPath', storedDerivationPath], provider)
})
})

it('--ledgerAddresses passes derivationPathIndexes to LedgerWallet', async () => {
await testLocallyWithWeb3Node(BasicCommand, ['--useLedger', '--ledgerAddresses', '5'], web3)
await testLocallyWithNode(BasicCommand, ['--useLedger', '--ledgerAddresses', '5'], provider)

expect(WalletLedgerExports.newLedgerWalletWithSetup).toHaveBeenCalledWith(
expect.anything(),
Expand Down Expand Up @@ -197,10 +196,10 @@ testWithAnvilL2('BaseCommand', (web3: Web3) => {

describe('with --ledgerLiveMode', () => {
it('--ledgerAddresses passes changeIndexes to LedgerWallet', async () => {
await testLocallyWithWeb3Node(
await testLocallyWithNode(
BasicCommand,
['--useLedger', '--ledgerLiveMode', '--ledgerAddresses', '5'],
web3
provider
)

expect(WalletLedgerExports.newLedgerWalletWithSetup).toHaveBeenCalledWith(
Expand Down Expand Up @@ -246,10 +245,10 @@ testWithAnvilL2('BaseCommand', (web3: Web3) => {
})
describe('with --ledgerCustomAddresses', () => {
it('passes custom changeIndexes to LedgerWallet', async () => {
await testLocallyWithWeb3Node(
await testLocallyWithNode(
BasicCommand,
['--useLedger', '--ledgerLiveMode', '--ledgerCustomAddresses', '[1,8,9]'],
web3
provider
)

expect(WalletLedgerExports.newLedgerWalletWithSetup).toHaveBeenCalledWith(
Expand Down Expand Up @@ -293,10 +292,10 @@ testWithAnvilL2('BaseCommand', (web3: Web3) => {
})
describe('with --ledgerCustomAddresses', () => {
it('passes custom derivationPathIndexes to LedgerWallet', async () => {
await testLocallyWithWeb3Node(
await testLocallyWithNode(
BasicCommand,
['--useLedger', '--ledgerCustomAddresses', '[1,8,9]'],
web3
provider
)

expect(WalletLedgerExports.newLedgerWalletWithSetup).toHaveBeenCalledWith(
Expand Down Expand Up @@ -341,7 +340,7 @@ testWithAnvilL2('BaseCommand', (web3: Web3) => {

describe('with --from', () => {
it('uses it as the default account', async () => {
await testLocallyWithWeb3Node(
await testLocallyWithNode(
BasicCommand,
[
'--useLedger',
Expand All @@ -350,7 +349,7 @@ testWithAnvilL2('BaseCommand', (web3: Web3) => {
'--from',
'0x1234567890123456789012345678901234567890',
],
web3
provider
)

expect(ViemAccountLedgerExports.ledgerToWalletClient).toHaveBeenCalledWith(
Expand Down Expand Up @@ -381,7 +380,7 @@ testWithAnvilL2('BaseCommand', (web3: Web3) => {
const errorSpy = jest.spyOn(console, 'error').mockImplementation()

await expect(
testLocallyWithWeb3Node(TestErrorCommand, [], web3)
testLocallyWithNode(TestErrorCommand, [], provider)
).rejects.toThrowErrorMatchingInlineSnapshot(
`"Unable to create an RPC Wallet Client, the node is not unlocked. Did you forget to use \`--privateKey\` or \`--useLedger\`?"`
)
Expand All @@ -399,7 +398,7 @@ testWithAnvilL2('BaseCommand', (web3: Web3) => {
const errorSpy = jest.spyOn(console, 'error').mockImplementation()

await expect(
testLocallyWithWeb3Node(TestErrorCommand, [], web3)
testLocallyWithNode(TestErrorCommand, [], provider)
).rejects.toThrowErrorMatchingInlineSnapshot(`"test error"`)

expect(errorSpy.mock.calls).toMatchInlineSnapshot(`
Expand Down Expand Up @@ -432,7 +431,7 @@ testWithAnvilL2('BaseCommand', (web3: Web3) => {
const errorSpy = jest.spyOn(console, 'error').mockImplementation()

await expect(
testLocallyWithWeb3Node(TestErrorCommand, ['--output', 'csv'], web3)
testLocallyWithNode(TestErrorCommand, ['--output', 'csv'], provider)
).rejects.toThrowErrorMatchingInlineSnapshot(`"test error"`)

expect(errorSpy.mock.calls).toMatchInlineSnapshot(`[]`)
Expand All @@ -453,7 +452,7 @@ testWithAnvilL2('BaseCommand', (web3: Web3) => {
throw new Error('Mock connection stop error')
})

await testLocallyWithWeb3Node(TestConnectionStopErrorCommand, [], web3)
await testLocallyWithNode(TestConnectionStopErrorCommand, [], provider)

expect(logSpy.mock.calls).toMatchInlineSnapshot(`
[
Expand Down Expand Up @@ -489,10 +488,10 @@ testWithAnvilL2('BaseCommand', (web3: Web3) => {
}

await expect(
testLocallyWithWeb3Node(
testLocallyWithNode(
TestPrivateKeyCommand,
['--privateKey', privateKey, '--from', wrongFromAddress],
web3
provider
)
).rejects.toThrowErrorMatchingInlineSnapshot(
`"The --from address ${wrongFromAddress} does not match the address derived from the provided private key 0x1Be31A94361a391bBaFB2a4CCd704F57dc04d4bb."`
Expand All @@ -515,10 +514,10 @@ testWithAnvilL2('BaseCommand', (web3: Web3) => {
}

await expect(
testLocallyWithWeb3Node(
testLocallyWithNode(
TestPrivateKeyCommand,
['--privateKey', privateKey, '--from', correctFromAddress],
web3
provider
)
).resolves.not.toThrow()
})
Expand All @@ -538,7 +537,7 @@ testWithAnvilL2('BaseCommand', (web3: Web3) => {
}

await expect(
testLocallyWithWeb3Node(TestPrivateKeyCommand, ['--privateKey', privateKey], web3)
testLocallyWithNode(TestPrivateKeyCommand, ['--privateKey', privateKey], provider)
).resolves.not.toThrow()
})
})
Expand Down Expand Up @@ -687,7 +686,6 @@ testWithAnvilL2('BaseCommand', (web3: Web3) => {
})

delete process.env.TELEMETRY_ENABLED
process.env.TELEMETRY_URL = 'http://localhost:3000/'

const fetchSpy = jest.spyOn(global, 'fetch')

Expand All @@ -697,13 +695,17 @@ testWithAnvilL2('BaseCommand', (web3: Web3) => {
}, 5000) // Higher timeout than the telemetry logic uses
})

server.listen(3000, async () => {
server.listen(0, async () => {
const address = server.address() as { port: number }
const telemetryUrl = `http://localhost:${address.port}/`
process.env.TELEMETRY_URL = telemetryUrl

// Make sure the command actually returns
await expect(TestTelemetryCommand.run([])).resolves.toBe(EXPECTED_COMMAND_RESULT)

expect(fetchSpy.mock.calls.length).toEqual(1)

expect(fetchSpy.mock.calls[0][0]).toMatchInlineSnapshot(`"http://localhost:3000/"`)
expect(fetchSpy.mock.calls[0][0]).toEqual(telemetryUrl)
expect(fetchSpy.mock.calls[0][1]?.body).toMatchInlineSnapshot(`
"
celocli_invocation{success="true", version="5.2.3", command="test:telemetry-timeout"} 1
Expand Down
41 changes: 18 additions & 23 deletions packages/cli/src/base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,9 @@ import {
ETHEREUM_DERIVATION_PATH,
StrongAddress,
} from '@celo/base'
import { ReadOnlyWallet } from '@celo/connect'
import { ContractKit, newKitFromWeb3 } from '@celo/contractkit'
import { type Provider, ReadOnlyWallet } from '@celo/connect'
import { ContractKit, newKitFromProvider } from '@celo/contractkit'
import { getProviderForKit } from '@celo/contractkit/lib/setupForKits'
import { ledgerToWalletClient } from '@celo/viem-account-ledger'
import { AzureHSMWallet } from '@celo/wallet-hsm-azure'
import { AddressValidation, newLedgerWalletWithSetup } from '@celo/wallet-ledger'
Expand All @@ -16,7 +17,6 @@ import { Command, Flags, ux } from '@oclif/core'
import { CLIError } from '@oclif/core/lib/errors'
import { ArgOutput, FlagOutput, Input, ParserOutput } from '@oclif/core/lib/interfaces/parser'
import chalk from 'chalk'
import net from 'net'
import {
createPublicClient,
createWalletClient,
Expand All @@ -30,7 +30,6 @@ import {
import { privateKeyToAccount } from 'viem/accounts'
import { celo, celoSepolia } from 'viem/chains'
import { ipc } from 'viem/node'
import Web3 from 'web3'
import createRpcWalletClient from './packages-to-be/rpc-client'
import { failWith } from './utils/cli'
import { CustomFlags } from './utils/command'
Expand Down Expand Up @@ -143,21 +142,14 @@ export abstract class BaseCommand extends Command {
// useful for the LedgerWalletClient which sometimes needs user input on reads
public isOnlyReadingWallet = false

private _web3: Web3 | null = null
private _provider: Provider | null = null
private _kit: ContractKit | null = null

private publicClient: PublicCeloClient | null = null
private walletClient: WalletCeloClient | null = null
private _parseResult: null | ParserOutput<FlagOutput, FlagOutput> = null
private ledgerTransport: Awaited<ReturnType<(typeof _TransportNodeHid)['open']>> | null = null

async getWeb3() {
if (!this._web3) {
this._web3 = await this.newWeb3()
}
return this._web3
}

get _wallet(): ReadOnlyWallet | undefined {
return this._wallet
}
Expand All @@ -172,17 +164,17 @@ export abstract class BaseCommand extends Command {
return (res.flags && res.flags.node) || getNodeUrl(this.config.configDir)
}

async newWeb3() {
const nodeUrl = await this.getNodeUrl()

return nodeUrl && nodeUrl.endsWith('.ipc')
? new Web3(new Web3.providers.IpcProvider(nodeUrl, net))
: new Web3(nodeUrl)
async newProvider(): Promise<Provider> {
if (!this._provider) {
const nodeUrl = await this.getNodeUrl()
this._provider = getProviderForKit(nodeUrl, undefined)
}
return this._provider
}
Comment on lines +167 to 173
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Preserve BaseCommand.getWeb3 until callers are migrated

This refactor removes the getWeb3() API from BaseCommand, but multiple command files in this commit still invoke this.getWeb3() (e.g. commands/account/lock.ts, commands/account/unlock.ts, and governance commands). That leaves those commands broken as soon as they execute, so this change needs a temporary shim or an atomic migration of all call sites.

Useful? React with 👍 / 👎.


async getKit() {
if (!this._kit) {
this._kit = newKitFromWeb3(await this.getWeb3())
this._kit = newKitFromProvider(await this.newProvider())
}

const res = await this.parse()
Expand Down Expand Up @@ -324,7 +316,10 @@ export abstract class BaseCommand extends Command {
} catch (e) {
let code: number | undefined
try {
const error = JSON.parse((e as any).details) as { code: number; message: string }
const error = JSON.parse((e as Error & { details: string }).details) as {
code: number
message: string
}
code = error.code
} catch (_) {
// noop
Expand All @@ -348,7 +343,7 @@ export abstract class BaseCommand extends Command {
const res = await this.parse(BaseCommand)
const isLedgerLiveMode = res.flags.ledgerLiveMode
const indicesToIterateOver: number[] = res.raw.some(
(value: any) => value.flag === 'ledgerCustomAddresses'
(value) => (value as { flag?: string }).flag === 'ledgerCustomAddresses'
)
? JSON.parse(res.flags.ledgerCustomAddresses)
: Array.from(new Array(res.flags.ledgerAddresses).keys())
Expand Down Expand Up @@ -401,7 +396,7 @@ export abstract class BaseCommand extends Command {
try {
const isLedgerLiveMode = res.flags.ledgerLiveMode
const indicesToIterateOver: number[] = res.raw.some(
(value) => (value as any).flag === 'ledgerCustomAddresses'
(value) => (value as { flag?: string }).flag === 'ledgerCustomAddresses'
)
? JSON.parse(res.flags.ledgerCustomAddresses)
: Array.from(new Array(res.flags.ledgerAddresses).keys())
Expand Down Expand Up @@ -511,7 +506,7 @@ export abstract class BaseCommand extends Command {
return false
}

async finally(arg: Error | undefined): Promise<any> {
async finally(arg: Error | undefined): Promise<void> {
const hideExtraOutput = await this.shouldHideExtraOutput(arg)

try {
Expand Down
Loading
Loading