feat: multichain EVM (Base + Ethereum + Polygon) in one wallet#16
Merged
Conversation
A single secp256k1 key derives the same 0x… address on every EVM
chain, so the canonical pattern is "one signer, multiple bindings."
v0.3.1 hard-coded a single chain at startup; this lifts that:
--evm-chains 8453,1,137
The first id is the primary (used when wallet.evm.* requests omit
chain_id). Per-chain RPC endpoints come from PILOT_EVM_RPC_<chainID>
env vars; the primary chain also accepts --evm-rpc / $PILOT_EVM_RPC
for compatibility with v0.3.1.
## pkg/evm
- Adds ChainPolygonMainnet (137) + native Circle-issued USDC at
0x3c499c… (NOT the bridged USDC.e — only the native contract
exposes EIP-3009 transferWithAuthorization).
- KnownChainIDs helper for callers iterating supported chains.
## pkg/wallet
- New evmByChain map[chainID]*evmBinding alongside the existing
primary `evm *evmBinding`. NewWithEVM stays as a thin wrapper over
the new NewWithEVMs([]EVMConfig).
- Per-chain accessors: EVMChainIDs, HasEVMChain, HasEVMRPCFor,
EVMTokenFor, EVMBalanceFor, EVMMethodFor.
- SatisfyEVMOn(ctx, chainID, contract) is the multichain entry; old
SatisfyEVM delegates to it with the primary chain. Caps still
apply against the wallet-wide spendLog regardless of chain, so a
multichain wallet can't dodge a cap by switching chains.
## pkg/walletipc
- wallet.evm.{address,balance,satisfy,verify} all accept optional
`chain_id`. Omitted/0 means primary.
- New wallet.evm.chains returns the full configured set.
## cmd/wallet
- --evm-chains comma-separated list; preserves the old single-chain
shape (--evm-chain → --evm-chains) under the new flag name.
- Logs one line per chain at startup so an operator can confirm
exactly what landed.
## Version
0.3.1 → 0.3.2. Manifest exposes list adds wallet.evm.chains;
manifest_test confirms the binary's --version constant matches.
## Live-verified
methods=15, same address on every chain, wallet.evm.chains lists
{8453, 1, 137}, address+balance routes by chain_id, unknown chain
errors cleanly.
Codecov Report❌ Patch coverage is 📢 Thoughts on this report? Let us know! |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
A single secp256k1 key derives the same `0x…` address on every EVM chain, so the canonical pattern is "one signer, multiple bindings." v0.3.1 hardcoded a single chain; this lifts that:
```
--evm-chains 8453,1,137
```
The first id is the primary (used when `wallet.evm.*` requests omit `chain_id`). Per-chain RPC endpoints come from `PILOT_EVM_RPC_` env vars.
Adds Polygon native USDC
`0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359` — NOT the bridged USDC.e. Only the native Circle-issued USDC contract exposes EIP-3009 `transferWithAuthorization`.
API
Caps
`SatisfyEVMOn` still checks against the wallet-wide spendLog. A multichain wallet can't dodge a cap by switching chains.
Live-verified
```
methods=15
addr=0xfedc…21ad chain=1 token=0xa0b8…eb48 rpc=false
addr=0xfedc…21ad chain=137 token=0x3c49…3359 rpc=false
addr=0xfedc…21ad chain=8453 token=0x8335…2913 rpc=false
```
Same address on every chain (secp256k1 derivation is chain-agnostic), correct USDC contract per chain, `wallet.evm.chains` lists all three, unknown chain errors cleanly.
Test plan