Skip to content
Draft

cct #204

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
2 changes: 1 addition & 1 deletion ccip-cli/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
"typecheck": "tsc --noEmit",
"check": "npm run lint && npm run typecheck",
"build": "npm run clean && tsc -p ./tsconfig.build.json && npm run patch-dist",
"patch-dist": "chmod +x ./dist/index.js && find ./dist -type f -name \"*.js\" -exec sed -i.bkp 's|@chainlink/ccip-sdk/src/.*\\.ts|@chainlink/ccip-sdk|g' {} + && find ./dist -type f -name \"*.bkp\" -delete",
"patch-dist": "chmod +x ./dist/index.js && find ./dist -type f -name \"*.js\" -exec sed -i.bkp -e 's|@chainlink/ccip-sdk/src/token-admin/\\([^/]*\\)/index\\.ts|@chainlink/ccip-sdk/token-admin/\\1|g' -e 's|@chainlink/ccip-sdk/src/token-admin/types\\.ts|@chainlink/ccip-sdk/token-admin/types|g' -e 's|@chainlink/ccip-sdk/src/.*\\.ts|@chainlink/ccip-sdk|g' {} + && find ./dist -type f -name \"*.bkp\" -delete",
"start": "./ccip-cli",
"clean": "rm -rfv ./dist",
"prepare": "npm run build"
Expand Down
24 changes: 24 additions & 0 deletions ccip-cli/src/commands/pool.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
/**
* Pool operations command group.
* Dispatches to subcommands: deploy.
*/

import type { Argv } from 'yargs'

export const command = 'pool'
export const describe =
'Pool operations (deploy, apply-chain-updates, append-remote-pool-addresses, remove-remote-pool-addresses, delete-chain-config, get-config, set-rate-limiter-config, set-rate-limit-admin, transfer-ownership, accept-ownership, execute-ownership-transfer)'

/**
* Yargs builder for the pool command group.
* Loads subcommands from the `pool/` directory.
* @param yargs - Yargs instance.
* @returns Configured yargs instance with subcommands.
*/
export const builder = (yargs: Argv) =>
yargs
.commandDir('pool', {
extensions: [new URL(import.meta.url).pathname.split('.').pop()!],
exclude: /\.test\.[tj]s$/,
})
.demandCommand(1)
134 changes: 134 additions & 0 deletions ccip-cli/src/commands/pool/accept-ownership.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
/**
* Pool accept-ownership subcommand.
* Accepts proposed pool ownership (2-step ownership transfer).
*/

import {
type AcceptOwnershipParams,
type AptosChain,
type Chain,
type EVMChain,
type SolanaChain,
CCIPChainFamilyUnsupportedError,
ChainFamily,
networkInfo,
} from '@chainlink/ccip-sdk/src/index.ts'
import { AptosTokenAdmin } from '@chainlink/ccip-sdk/src/token-admin/aptos/index.ts'
import { EVMTokenAdmin } from '@chainlink/ccip-sdk/src/token-admin/evm/index.ts'
import { SolanaTokenAdmin } from '@chainlink/ccip-sdk/src/token-admin/solana/index.ts'
import type { Argv } from 'yargs'

import type { GlobalOpts } from '../../index.ts'
import { fetchChainsFromRpcs, loadChainWallet } from '../../providers/index.ts'
import { type Ctx, Format } from '../types.ts'
import { getCtx, logParsedError, prettyTable } from '../utils.ts'

export const command = 'accept-ownership'
export const describe = 'Accept proposed pool ownership (2-step ownership transfer)'

/**
* Yargs builder for the pool accept-ownership subcommand.
*/
export const builder = (yargs: Argv) =>
yargs
.option('network', {
alias: 'n',
type: 'string',
demandOption: true,
describe: 'Network: chainId or name (e.g., ethereum-testnet-sepolia)',
})
.option('wallet', {
alias: 'w',
type: 'string',
describe: 'Wallet: ledger[:index] or private key (must be pending/proposed owner)',
})
.option('pool-address', {
type: 'string',
demandOption: true,
describe: 'Pool address',
})
.example([
[
'ccip-cli pool accept-ownership -n sepolia --pool-address 0x...',
'Accept proposed pool ownership',
],
])

/**
* Handler for the pool accept-ownership subcommand.
*/
export async function handler(argv: Awaited<ReturnType<typeof builder>['argv']> & GlobalOpts) {
const [ctx, destroy] = getCtx(argv)
return doAcceptOwnership(ctx, argv)
.catch((err) => {
process.exitCode = 1
if (!logParsedError.call(ctx, err)) ctx.logger.error(err)
})
.finally(destroy)
}

type AcceptOwnershipArgv = Awaited<ReturnType<typeof builder>['argv']> & GlobalOpts

/** Calls acceptOwnership on the appropriate chain-family admin. */
function acceptForChain(chain: Chain, wallet: unknown, params: AcceptOwnershipParams) {
switch (chain.network.family) {
case ChainFamily.EVM: {
const evmChain = chain as EVMChain
const admin = new EVMTokenAdmin(evmChain.provider, evmChain.network, {
logger: evmChain.logger,
})
return admin.acceptOwnership(wallet, params)
}
case ChainFamily.Solana: {
const solanaChain = chain as SolanaChain
const admin = new SolanaTokenAdmin(solanaChain.connection, solanaChain.network, {
logger: solanaChain.logger,
})
return admin.acceptOwnership(wallet, params)
}
case ChainFamily.Aptos: {
const aptosChain = chain as AptosChain
const admin = new AptosTokenAdmin(aptosChain.provider, aptosChain.network, {
logger: aptosChain.logger,
})
return admin.acceptOwnership(wallet, params)
}
default:
throw new CCIPChainFamilyUnsupportedError(chain.network.family)
}
}

async function doAcceptOwnership(ctx: Ctx, argv: AcceptOwnershipArgv) {
const { logger } = ctx
const networkName = networkInfo(argv.network).name
const getChain = fetchChainsFromRpcs(ctx, argv)
const chain = await getChain(networkName)

const params: AcceptOwnershipParams = {
poolAddress: argv.poolAddress,
}

logger.debug(`Accepting ownership: pool=${params.poolAddress}`)

const [, wallet] = await loadChainWallet(chain, argv)
const result = await acceptForChain(chain, wallet, params)

const output: Record<string, string> = {
network: networkName,
poolAddress: params.poolAddress,
txHash: result.txHash,
}

switch (argv.format) {
case Format.json:
logger.log(JSON.stringify(output, null, 2))
return
case Format.log:
logger.log('Ownership accepted, tx:', result.txHash)
return
case Format.pretty:
default:
prettyTable.call(ctx, output)
return
}
}
160 changes: 160 additions & 0 deletions ccip-cli/src/commands/pool/append-remote-pool-addresses.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
/**
* Pool append-remote-pool-addresses subcommand.
* Appends remote pool addresses to a CCIP token pool for a given remote chain.
*/

import {
type AppendRemotePoolAddressesParams,
type AptosChain,
type Chain,
type EVMChain,
type SolanaChain,
CCIPArgumentInvalidError,
CCIPChainFamilyUnsupportedError,
ChainFamily,
networkInfo,
} from '@chainlink/ccip-sdk/src/index.ts'
import { AptosTokenAdmin } from '@chainlink/ccip-sdk/src/token-admin/aptos/index.ts'
import { EVMTokenAdmin } from '@chainlink/ccip-sdk/src/token-admin/evm/index.ts'
import { SolanaTokenAdmin } from '@chainlink/ccip-sdk/src/token-admin/solana/index.ts'
import type { Argv } from 'yargs'

import type { GlobalOpts } from '../../index.ts'
import { fetchChainsFromRpcs, loadChainWallet } from '../../providers/index.ts'
import { type Ctx, Format } from '../types.ts'
import { getCtx, logParsedError, prettyTable } from '../utils.ts'

export const command = 'append-remote-pool-addresses'
export const describe = 'Append remote pool addresses to a CCIP token pool for a given remote chain'

/**
* Yargs builder for the pool append-remote-pool-addresses subcommand.
*/
export const builder = (yargs: Argv) =>
yargs
.option('network', {
alias: 'n',
type: 'string',
describe: 'Network: chainId or name (e.g., ethereum-testnet-sepolia)',
})
.option('wallet', {
alias: 'w',
type: 'string',
describe: 'Wallet: ledger[:index] or private key (must be pool owner)',
})
.option('pool-address', {
type: 'string',
describe: 'Local pool address',
})
.option('remote-chain', {
type: 'string',
describe: 'Remote chain: chainId, name, or selector',
})
.option('remote-pool-addresses', {
type: 'string',
describe: 'Comma-separated list of remote pool addresses',
})
.check((argv) => {
if (!argv.network) throw new CCIPArgumentInvalidError('network', 'required argument missing')
if (!argv.poolAddress)
throw new CCIPArgumentInvalidError('pool-address', 'required argument missing')
if (!argv.remoteChain)
throw new CCIPArgumentInvalidError('remote-chain', 'required argument missing')
if (!argv.remotePoolAddresses)
throw new CCIPArgumentInvalidError('remote-pool-addresses', 'required argument missing')
return true
})
.example([
[
'ccip-cli pool append-remote-pool-addresses -n sepolia --pool-address 0x... --remote-chain avalanche-fuji --remote-pool-addresses 0xaaa,0xbbb',
'Append remote pool addresses for a remote chain',
],
])

/**
* Handler for the pool append-remote-pool-addresses subcommand.
*/
export async function handler(argv: Awaited<ReturnType<typeof builder>['argv']> & GlobalOpts) {
const [ctx, destroy] = getCtx(argv)
return doAppendRemotePoolAddresses(ctx, argv)
.catch((err) => {
process.exitCode = 1
if (!logParsedError.call(ctx, err)) ctx.logger.error(err)
})
.finally(destroy)
}

type AppendArgv = Awaited<ReturnType<typeof builder>['argv']> & GlobalOpts

/** Calls appendRemotePoolAddresses on the appropriate chain-family admin. */
function appendForChain(chain: Chain, wallet: unknown, params: AppendRemotePoolAddressesParams) {
switch (chain.network.family) {
case ChainFamily.EVM: {
const evmChain = chain as EVMChain
const admin = new EVMTokenAdmin(evmChain.provider, evmChain.network, {
logger: evmChain.logger,
})
return admin.appendRemotePoolAddresses(wallet, params)
}
case ChainFamily.Solana: {
const solanaChain = chain as SolanaChain
const admin = new SolanaTokenAdmin(solanaChain.connection, solanaChain.network, {
logger: solanaChain.logger,
})
return admin.appendRemotePoolAddresses(wallet, params)
}
case ChainFamily.Aptos: {
const aptosChain = chain as AptosChain
const admin = new AptosTokenAdmin(aptosChain.provider, aptosChain.network, {
logger: aptosChain.logger,
})
return admin.appendRemotePoolAddresses(wallet, params)
}
default:
throw new CCIPChainFamilyUnsupportedError(chain.network.family)
}
}

async function doAppendRemotePoolAddresses(ctx: Ctx, argv: AppendArgv) {
const { logger } = ctx
const networkName = networkInfo(argv.network!).name
const getChain = fetchChainsFromRpcs(ctx, argv)
const chain = await getChain(networkName)

const remoteChainSelector = networkInfo(argv.remoteChain!).chainSelector
const remotePoolAddresses = argv.remotePoolAddresses!.split(',').map((a) => a.trim())

const params: AppendRemotePoolAddressesParams = {
poolAddress: argv.poolAddress!,
remoteChainSelector,
remotePoolAddresses,
}

logger.debug(
`Appending ${remotePoolAddresses.length} remote pool address(es) for remote chain ${remoteChainSelector}`,
)

const [, wallet] = await loadChainWallet(chain, argv)
const result = await appendForChain(chain, wallet, params)

const output: Record<string, string> = {
network: networkName,
poolAddress: argv.poolAddress!,
remoteChainSelector: String(remoteChainSelector),
addressesAdded: remotePoolAddresses.join(', '),
txHash: result.txHash,
}

switch (argv.format) {
case Format.json:
logger.log(JSON.stringify(output, null, 2))
return
case Format.log:
logger.log('Remote pool addresses appended, tx:', result.txHash)
return
case Format.pretty:
default:
prettyTable.call(ctx, output)
return
}
}
Loading
Loading