Skip to content
Merged
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
5 changes: 5 additions & 0 deletions .changeset/fix-mainnet-default.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'mppx': patch
---

Fixed CLI defaulting to testnet when `--rpc-url` is omitted. The CLI now defaults to Tempo mainnet. Also added `resolveRpcUrl` helper so `MPPX_RPC_URL` and `RPC_URL` env vars are respected consistently across all commands.
19 changes: 11 additions & 8 deletions src/cli/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import {
printResponseHeaders,
prompt,
resolveChain,
resolveRpcUrl,
} from './utils.js'

const packageJson = createRequire(import.meta.url)('../../package.json') as {
Expand Down Expand Up @@ -516,8 +517,9 @@ const account = Cli.create('account', {
? link(`${explorerUrl}/address/${acct.address}`, acct.address)
: acct.address
console.log(pc.dim(`Address ${addrDisplay}`))
resolveChain(c.options)
.then((chain) => createClient({ chain, transport: http(c.options.rpcUrl) }))
const rpcUrl = resolveRpcUrl(c.options.rpcUrl)
resolveChain({ rpcUrl })
.then((chain) => createClient({ chain, transport: http(rpcUrl) }))
.then((client) =>
import('viem/tempo').then(({ Actions }) =>
Actions.faucet.fund(client, { account: acct }).catch(() => {}),
Expand Down Expand Up @@ -629,8 +631,9 @@ const account = Cli.create('account', {
return c.error({ code: 'ACCOUNT_NOT_FOUND', message: 'No account found.', exitCode: 69 })
}
const acct = privateKeyToAccount(key as `0x${string}`)
const chain = await resolveChain(c.options)
const client = createClient({ chain, transport: http(c.options.rpcUrl) })
const rpcUrl = resolveRpcUrl(c.options.rpcUrl)
const chain = await resolveChain({ rpcUrl })
const client = createClient({ chain, transport: http(rpcUrl) })
console.log(`Funding "${accountName}" on ${chainName(chain)}`)
try {
const { Actions } = await import('viem/tempo')
Expand Down Expand Up @@ -711,8 +714,8 @@ const account = Cli.create('account', {
})
}
const address = tempoEntry.wallet_address as Address
const rpcUrl = c.options.rpcUrl ?? (process.env.MPPX_RPC_URL || undefined)
const chain = rpcUrl ? await resolveChain({ rpcUrl }) : tempoMainnet
const rpcUrl = resolveRpcUrl(c.options.rpcUrl)
const chain = await resolveChain({ rpcUrl })
const explorerUrl = chain.blockExplorers?.default?.url
const addrDisplay = explorerUrl
? link(`${explorerUrl}/address/${address}`, address)
Expand Down Expand Up @@ -744,8 +747,8 @@ const account = Cli.create('account', {
return c.error({ code: 'ACCOUNT_NOT_FOUND', message: 'No account found.', exitCode: 69 })
}
const acct = privateKeyToAccount(key as `0x${string}`)
const rpcUrl = c.options.rpcUrl ?? (process.env.MPPX_RPC_URL || undefined)
const chain = rpcUrl ? await resolveChain({ rpcUrl }) : tempoMainnet
const rpcUrl = resolveRpcUrl(c.options.rpcUrl)
const chain = await resolveChain({ rpcUrl })
const explorerUrl = chain.blockExplorers?.default?.url
const addrDisplay = explorerUrl
? link(`${explorerUrl}/address/${acct.address}`, acct.address)
Expand Down
5 changes: 3 additions & 2 deletions src/cli/plugins/tempo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import {
link,
pc,
resolveChain,
resolveRpcUrl,
} from '../utils.js'
import { createPlugin, type Plugin } from './plugin.js'

Expand Down Expand Up @@ -67,7 +68,7 @@ export function tempo() {
useTempoCliSign = true
const tempoEntry = resolveTempoAccount(accountName)
if (tempoEntry) {
const rpcUrl = options.rpcUrl ?? process.env.RPC_URL
const rpcUrl = resolveRpcUrl(options.rpcUrl)
client = createClient({
chain: await resolveChain({ rpcUrl }),
transport: http(rpcUrl),
Expand Down Expand Up @@ -107,7 +108,7 @@ export function tempo() {
} else account = privateKeyToAccount(privateKey as `0x${string}`)

if (!useTempoCliSign && account) {
const rpcUrl = options.rpcUrl ?? process.env.RPC_URL
const rpcUrl = resolveRpcUrl(options.rpcUrl)
client = createClient({
chain: await resolveChain({ rpcUrl }),
transport: http(rpcUrl),
Expand Down
64 changes: 64 additions & 0 deletions src/cli/utils.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import { tempo as tempoMainnet, tempoModerato } from 'viem/chains'
import { afterEach, describe, expect, test } from 'vp/test'

import { resolveChain, resolveRpcUrl } from './utils.js'

describe('resolveRpcUrl', () => {
afterEach(() => {
delete process.env.MPPX_RPC_URL
delete process.env.RPC_URL
})

test('returns explicit value when provided', () => {
process.env.MPPX_RPC_URL = 'https://env.example.com'
expect(resolveRpcUrl('https://explicit.example.com')).toBe('https://explicit.example.com')
})

test('falls back to MPPX_RPC_URL env var', () => {
process.env.MPPX_RPC_URL = 'https://mppx.example.com'
process.env.RPC_URL = 'https://rpc.example.com'
expect(resolveRpcUrl()).toBe('https://mppx.example.com')
})

test('falls back to RPC_URL env var when MPPX_RPC_URL is not set', () => {
process.env.RPC_URL = 'https://rpc.example.com'
expect(resolveRpcUrl()).toBe('https://rpc.example.com')
})

test('returns undefined when nothing is set', () => {
expect(resolveRpcUrl()).toBeUndefined()
})

test('trims whitespace from env vars', () => {
process.env.MPPX_RPC_URL = ' https://mppx.example.com '
expect(resolveRpcUrl()).toBe('https://mppx.example.com')
})

test('skips empty MPPX_RPC_URL and falls back to RPC_URL', () => {
process.env.MPPX_RPC_URL = ' '
process.env.RPC_URL = 'https://rpc.example.com'
expect(resolveRpcUrl()).toBe('https://rpc.example.com')
})
})

describe('resolveChain', () => {
afterEach(() => {
delete process.env.MPPX_RPC_URL
delete process.env.RPC_URL
})

test('defaults to tempo mainnet when no rpcUrl is provided', async () => {
const chain = await resolveChain()
expect(chain.id).toBe(tempoMainnet.id)
})

test('defaults to tempo mainnet when rpcUrl is undefined', async () => {
const chain = await resolveChain({ rpcUrl: undefined })
expect(chain.id).toBe(tempoMainnet.id)
})

test('does not default to testnet', async () => {
const chain = await resolveChain()
expect(chain.id).not.toBe(tempoModerato.id)
})
})
14 changes: 10 additions & 4 deletions src/cli/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -221,17 +221,23 @@ export function fmtBalance(
return `${dec ? `${formatted}.${dec}` : formatted} ${sym}`
}

/** Resolve RPC URL from explicit option, then MPPX_RPC_URL, then RPC_URL env vars. */
export function resolveRpcUrl(explicit?: string | undefined): string | undefined {
return explicit ?? (process.env.MPPX_RPC_URL?.trim() || process.env.RPC_URL?.trim() || undefined)
}

export async function resolveChain(opts: { rpcUrl?: string | undefined } = {}): Promise<Chain> {
if (!opts.rpcUrl) return tempoModerato
const rpcUrl = resolveRpcUrl(opts.rpcUrl)
if (!rpcUrl) return tempoMainnet
const { getChainId } = await import('viem/actions')
const chainId = await getChainId(createClient({ transport: http(opts.rpcUrl) }))
const chainId = await getChainId(createClient({ transport: http(rpcUrl) }))
const allExports = Object.values(await import('viem/chains')) as unknown[]
const candidates = allExports.filter(
(c): c is Chain =>
typeof c === 'object' && c !== null && 'id' in c && (c as Chain).id === chainId,
)
const found = candidates.find((c) => 'serializers' in c && c.serializers) ?? candidates[0]
if (!found) throw new Error(`Unknown chain ID ${chainId} from RPC ${opts.rpcUrl}`)
if (!found) throw new Error(`Unknown chain ID ${chainId} from RPC ${rpcUrl}`)
return found
}

Expand Down Expand Up @@ -306,7 +312,7 @@ export async function fetchBalanceLines(

const mainnetClient = createClient({
chain: tempoMainnet,
transport: http(process.env.MPPX_RPC_URL || undefined),
transport: http(resolveRpcUrl()),
})
const mainnetExplorerUrl = tempoMainnet.blockExplorers?.default?.url
const mainnetResults = await Promise.all(
Expand Down
Loading