Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
05fceb7
refactor(connect,utils): replace web3 with viem
Apr 1, 2026
2d1623c
refactor(contractkit): migrate to viem-native contract interaction
Apr 1, 2026
7a57a73
refactor(explorer,governance,wallets,dev-utils): migrate to viem
Apr 1, 2026
6929a33
refactor(cli): migrate base, utils, and test-utils to viem
Apr 1, 2026
5b3cffd
refactor(cli): migrate account, dkg, election, epochs commands to viem
Apr 1, 2026
f9b5f5d
refactor(cli): migrate governance commands to viem
Apr 1, 2026
289b880
refactor(cli): migrate lockedcelo, multisig, network, oracle, transfe…
Apr 1, 2026
ffd10cc
refactor(cli): migrate releasecelo, validator, validatorgroup command…
Apr 1, 2026
6055e04
fix: BigInt-safe JSON.stringify in SimpleHttpProvider + activate snap…
Apr 1, 2026
ac02f8b
ci: retrigger
Apr 1, 2026
0db402d
fix: correct activate snapshot and increase Validators test timeouts
Apr 1, 2026
0cc9f57
fix(connect,contractkit,utils): correct fee currency, fee math, BigIn…
Jun 11, 2026
61cf848
fix(utils,connect): minor review cleanups from viem migration audit
Jun 11, 2026
09b6d17
chore(wallet-hsm-aws): align viem devDependency with workspace (~2.33.2)
Jun 11, 2026
f1dacee
fix(contractkit): repair IPC provider framing, add ws(s) support and …
Jun 11, 2026
a7e81e8
fix(contractkit): correct wrapper regressions found in viem migration…
Jun 11, 2026
ff57805
chore(cli): untrack runtime-generated governance test fixtures
Jun 11, 2026
62e4672
style(contractkit): apply biome formatting
Jun 11, 2026
cf31a76
test(contractkit): restore weakened test semantics; fix getName block…
Jun 11, 2026
af9439c
docs(changesets): document contractkit breaking changes accurately
Jun 11, 2026
d6b860d
test(connect): assert decodeEventLog value parity for indexed and non…
Jun 11, 2026
57f2774
fix(connect,contractkit): isHash storage-key length; transferWithComm…
Jun 11, 2026
c6193b2
fix(contractkit): canonical selectors in methodIds for tuple-input fu…
Jun 11, 2026
f267b67
fix(wallet-base): keep arbitrary precision when hex-encoding tx quant…
Jun 11, 2026
e6a4a90
fix(connect,explorer,cli): canonical tuple signatures, checksummed pr…
Jun 11, 2026
4b8c12c
fix(cli): dkg output handling and restored election activate assertions
Jun 11, 2026
a104bac
style(cli): biome formatting
Jun 11, 2026
3bfd4da
fix(cli,contractkit): send release-gold revokes from the vote signer …
Jun 11, 2026
9098944
fix(cli): restore on-chain event confirmations for governance commands
Jun 11, 2026
7ac4e0d
style(cli): biome formatting
Jun 11, 2026
82a5f99
fix(cli): use block timestamp for validator deregister and slashing-m…
Jun 11, 2026
ec8ddee
fix(dev-utils,cli): test-infra robustness from migration audit
Jun 11, 2026
a11a21a
fix(wallet-base,cli): audit follow-ups — hex guards, recursive getter…
Jun 11, 2026
5b4fddc
docs(contractkit): document Election.activate eager sequential sending
Jun 11, 2026
02106b0
test(cli): update transfer:dollars snapshot for fixed feeCurrency err…
Jun 11, 2026
dbfb12a
Merge remote-tracking branch 'origin/master' into pahor/viem-combined
Jun 12, 2026
c8994c2
test(cli): stabilize anvil-heavy suites for CI
Jun 12, 2026
11578c1
style(cli): biome formatting for activate.test
Jun 12, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
5 changes: 5 additions & 0 deletions .changeset/fix-ledger-console-noise.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@celo/wallet-ledger': patch
---

Replace `console.info` with `debug` for derivation path logging to avoid noisy test output
5 changes: 5 additions & 0 deletions .changeset/remove-delegate-debug-logs.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@celo/celocli': patch
---

Remove debug console.log statements from lockedcelo:delegate command that were leaking internal values to stdout
25 changes: 25 additions & 0 deletions .changeset/remove-rpc-contract-promievent.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
---
'@celo/connect': major
'@celo/contractkit': major
'@celo/celocli': minor
'@celo/dev-utils': minor
---

**Remove rpc-contract.ts, PromiEvent, and legacy Contract interface from @celo/connect**

- Deleted `rpc-contract.ts`, `promi-event.ts`, and `viem-contract.ts` — replaced with native viem `getContract()` / `GetContractReturnType`
- `CeloTxObject.send()` now returns `Promise<string>` (tx hash) instead of `PromiEvent<CeloTxReceipt>`
- Removed `Connection.createContract()` — use `Connection.getCeloContract()` instead
- Removed `PromiEvent<T>` and `Contract` interfaces from types
- `Connection.getViemContract()` deprecated — delegates to `getCeloContract()`
- `ViemContract<TAbi>` deprecated — use `CeloContract<TAbi>` (viem's `GetContractReturnType`)
- Contract deployment rewritten to use viem's `encodeDeployData` + `connection.sendTransaction()`
- All contractkit wrappers, CLI commands, and test files updated

**Breaking changes in @celo/contractkit**

- `kit.sendTransaction()` now returns `Promise<\`0x${string}\`>` (the transaction hash) instead of a `TransactionResult`; use `kit.connection.viemClient.waitForTransactionReceipt({ hash })` to wait for inclusion
- Removed the deprecated `kit.web3` shim — use `kit.connection.viemClient` (reads) and wrapper methods (writes)
- Removed `kit.isListening()` and `kit.isSyncing()` — use `kit.connection.isListening()` / `kit.connection.isSyncing()`
- Removed the deprecated `kit.gasPriceSuggestionMultiplier` property
- Removed the `CeloToken` type re-export — use `CeloTokenContract`
10 changes: 10 additions & 0 deletions .changeset/remove-web3-shim.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
---
'@celo/connect': major
'@celo/contractkit': major
'@celo/celocli': major
'@celo/explorer': patch
'@celo/governance': patch
'@celo/dev-utils': patch
---

Remove Web3 shim from Connection and migrate contractkit to use viem ABIs with Connection.createContract(). Add backward-compatible kit.web3 shim (deprecated). Add newKitFromProvider() factory function.
5 changes: 5 additions & 0 deletions .changeset/strong-typing-contractkit.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@celo/contractkit': minor
---

**Improved type safety**: Added explicit type annotations to all wrapper methods that previously emitted `CeloTransactionObject<any>` or `Promise<any>` in their declaration files. All `proxySend` and `proxyCall` usages now have explicit return types, eliminating approximately 110 instances of `any` in the public API surface. This provides better IDE autocompletion and compile-time type checking for consumers of `@celo/contractkit`.
16 changes: 16 additions & 0 deletions .changeset/viem-native-migration.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
---
'@celo/connect': minor
'@celo/contractkit': minor
'@celo/explorer': patch
---

**Migrate internal contract interaction from web3-style RPC Contract to viem-native ViemContract**

- Added `ViemContract` type and `createViemTxObject()` function in `@celo/connect`
- Added `Connection.getViemContract()` factory method
- Updated all 36 ContractKit wrappers to use viem-native contract interaction
- Updated `proxyCall`/`proxySend` to accept `ViemContract` + function name strings
- Migrated CLI commands, dev-utils, and explorer to use new API
- Deprecated `Connection.createContract()` (kept for backward compatibility with `.deploy()`)
- Removed the deprecated `kit.web3` shim (use `kit.connection.viemClient` instead)
- Wrapper method signatures remain the same (`CeloTransactionObject` unchanged at this stage)
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
diff --git a/index.js b/index.js
index 5462c1f830bdbe79bf2b1fcfd811cd9799b4dd11..e8fe7e61083d95714bba6f2d4544d0426749a64f 100644
--- a/index.js
+++ b/index.js
@@ -28,14 +28,19 @@ function bufferEq(a, b) {
}

bufferEq.install = function() {
- Buffer.prototype.equal = SlowBuffer.prototype.equal = function equal(that) {
+ Buffer.prototype.equal = function equal(that) {
return bufferEq(this, that);
};
+ if (SlowBuffer) {
+ SlowBuffer.prototype.equal = Buffer.prototype.equal;
+ }
};

var origBufEqual = Buffer.prototype.equal;
-var origSlowBufEqual = SlowBuffer.prototype.equal;
+var origSlowBufEqual = SlowBuffer ? SlowBuffer.prototype.equal : undefined;
bufferEq.restore = function() {
Buffer.prototype.equal = origBufEqual;
- SlowBuffer.prototype.equal = origSlowBufEqual;
+ if (SlowBuffer && origSlowBufEqual) {
+ SlowBuffer.prototype.equal = origSlowBufEqual;
+ }
};
5 changes: 2 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -54,11 +54,10 @@
"typescript": "5.3.3"
},
"resolutions": {
"web3": "1.10.4",
"web3-utils": "1.10.4",
"blind-threshold-bls": "npm:@celo/blind-threshold-bls@1.0.0-beta",
"@types/bn.js": "4.11.6",
"bignumber.js": "9.0.0"
"bignumber.js": "9.0.0",
"buffer-equal-constant-time@npm:1.0.1": "patch:buffer-equal-constant-time@npm%3A1.0.1#~/.yarn/patches/buffer-equal-constant-time-npm-1.0.1-41826f3419.patch"
},
"dependencies": {
"@changesets/cli": "^2.29.5"
Expand Down
1 change: 1 addition & 0 deletions packages/actions/tsconfig-base.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
"compilerOptions": {
"rootDir": "src",
"declaration": true,
"declarationMap": true,
"esModuleInterop": true,
"types": ["node"],
"lib": ["esnext"],
Expand Down
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
Loading
Loading