diff --git a/src/cli/cli.ts b/src/cli/cli.ts index 9d8bed35..f5e60e96 100644 --- a/src/cli/cli.ts +++ b/src/cli/cli.ts @@ -19,11 +19,7 @@ import { program } from 'commander'; import { - ANT, - ANTRegistry, - ANT_REGISTRY_ID, ArweaveSigner, - Logger, createAoSigner, initANTStateForAddress, spawnANT, @@ -92,6 +88,7 @@ import { PaginationAddressCLIOptions, PaginationCLIOptions, ProcessIdCLIOptions, + ProcessIdWriteActionCLIOptions, RedelegateStakeCLIOptions, SubmitAuctionBidCLIOptions, UpgradeRecordCLIOptions, @@ -100,24 +97,29 @@ import { import { epochInputFromOptions, formatIOWithCommas, + getLoggerFromOptions, + ioProcessIdFromOptions, jwkToAddress, makeCommand, paginationParamsFromOptions, readIOFromOptions, + readableANTFromOptions, redelegateParamsFromOptions, requiredAddressFromOptions, requiredIncreaseCountFromOptions, - requiredInitiatorFromOptions, requiredJwkFromOptions, requiredMIOQuantityFromOptions, requiredNameFromOptions, requiredOperatorStakeFromOptions, + requiredStringArrayFromOptions, + requiredStringFromOptions, requiredTargetAndQuantityFromOptions, requiredVaultIdFromOptions, requiredYearsFromOptions, typeFromOptions, writeActionTagsFromOptions, writeIOFromOptions, + writeableANTFromOptions, yearsFromOptions, } from './utils.js'; @@ -297,6 +299,8 @@ makeCommand({ ), }); +// TODO: Could assert valid arweave addresses at CLI level + makeCommand({ name: 'get-primary-name-request', description: 'Get primary name request', @@ -304,7 +308,7 @@ makeCommand({ action: (o) => readIOFromOptions(o) .getPrimaryNameRequest({ - initiator: requiredInitiatorFromOptions(o), + initiator: requiredStringFromOptions(o, 'initiator'), }) .then( (result) => @@ -448,7 +452,7 @@ makeCommand({ action: updateGatewaySettings, }); -// save-observations +// TODO: save-observations makeCommand({ name: 'increase-operator-stake', @@ -679,7 +683,6 @@ makeCommand({ }), }); -// TODO: Pass debug logger down to spawn/ANTRegistry makeCommand< GlobalCLIOptions & { target?: string; @@ -694,27 +697,18 @@ makeCommand< const signer = createAoSigner(new ArweaveSigner(jwk)); const target = options.target; - const logger = Logger.default; - if (options.debug === true) { - logger.setLogLevel('debug'); - } + // TODO: pass state from any provided ANT state options + const state = initANTStateForAddress(address, target); const antProcessId = await spawnANT({ - state: initANTStateForAddress(address, target), + state, signer, - logger, - }); - - const antRegistry = ANTRegistry.init({ - signer, - processId: ANT_REGISTRY_ID, - }); - await antRegistry.register({ - processId: antProcessId, + logger: getLoggerFromOptions(options), }); return { processId: antProcessId, + state, message: `Spawned ANT process with process ID ${antProcessId}`, }; }, @@ -725,13 +719,344 @@ makeCommand({ description: 'Get the state of an ANT process', options: [optionMap.processId], action: async (options) => { - if (options.processId === undefined) { - throw new Error('--process-id is required'); + return readableANTFromOptions(options).getState(); + }, +}); + +makeCommand({ + name: 'get-ant-info', + description: 'Get the info of an ANT process', + options: [optionMap.processId], + action: async (options) => { + return readableANTFromOptions(options).getInfo(); + }, +}); + +makeCommand< + ProcessIdCLIOptions & { + undername?: string; + } +>({ + name: 'get-ant-record', + description: 'Get a record of an ANT process', + options: [optionMap.processId, optionMap.undername], + action: async (options) => { + if (options.undername === undefined) { + throw new Error('--undername is required'); } + return ( + (await readableANTFromOptions(options).getRecord({ + undername: options.undername, + })) ?? { message: 'No record found' } + ); + }, +}); - return ANT.init({ - processId: options.processId, - }).getState(); +makeCommand({ + name: 'list-ant-records', + description: 'Get the records of an ANT process', + options: [optionMap.processId], + action: async (options) => { + return readableANTFromOptions(options).getRecords(); + }, +}); + +makeCommand({ + name: 'get-ant-owner', + description: 'Get the owner of an ANT process', + options: [optionMap.processId], + action: async (options) => { + return readableANTFromOptions(options).getOwner(); + }, +}); + +makeCommand({ + name: 'list-ant-controllers', + description: 'List the controllers of an ANT process', + options: [optionMap.processId], + action: async (options) => { + return readableANTFromOptions(options).getControllers(); + }, +}); + +makeCommand({ + name: 'get-ant-name', + description: 'Get the name of an ANT process', + options: [optionMap.processId], + action: async (options) => { + return readableANTFromOptions(options).getName(); + }, +}); + +makeCommand({ + name: 'get-ant-ticker', + description: 'Get the ticker of an ANT process', + options: [optionMap.processId], + action: async (options) => { + return readableANTFromOptions(options).getTicker(); + }, +}); + +makeCommand({ + name: 'get-ant-balance', + description: 'Get the balance of an ANT process', + options: [optionMap.processId, optionMap.address], + action: async (options) => { + return readableANTFromOptions(options).getBalance({ + address: requiredAddressFromOptions(options), + }); + }, +}); + +makeCommand({ + name: 'list-ant-balances', + description: 'Get the balances of an ANT process', + options: [optionMap.processId], + action: async (options) => { + return readableANTFromOptions(options).getBalances(); + }, +}); + +makeCommand< + ProcessIdWriteActionCLIOptions & { + target?: string; + } +>({ + name: 'transfer-ant-ownership', + description: 'Transfer ownership of an ANT process', + options: [optionMap.processId, optionMap.target, ...writeActionOptions], + action: async (options) => { + return writeableANTFromOptions(options).transfer( + { + target: requiredStringFromOptions(options, 'target'), + }, + writeActionTagsFromOptions(options), + ); + }, +}); + +makeCommand({ + name: 'add-ant-controller', + description: 'Add a controller to an ANT process', + options: [optionMap.processId, optionMap.controller, ...writeActionOptions], + action: async (options) => { + return writeableANTFromOptions(options).addController( + { + controller: requiredStringFromOptions(options, 'controller'), + }, + writeActionTagsFromOptions(options), + ); + }, +}); + +makeCommand({ + name: 'remove-ant-controller', + description: 'Remove a controller from an ANT process', + options: [optionMap.processId, optionMap.controller, ...writeActionOptions], + action: async (options) => { + return writeableANTFromOptions(options).removeController( + { + controller: requiredStringFromOptions(options, 'controller'), + }, + writeActionTagsFromOptions(options), + ); + }, +}); + +makeCommand< + ProcessIdWriteActionCLIOptions & { + undername?: string; + transactionId?: string; + ttlSeconds?: number; + } +>({ + name: 'set-ant-record', + description: 'Set a record of an ANT process', + options: [ + optionMap.processId, + optionMap.undername, + optionMap.transactionId, + optionMap.ttlSeconds, + ...writeActionOptions, + ], + action: async (options) => { + options.ttlSeconds ??= 3600; + return writeableANTFromOptions(options).setRecord( + { + undername: requiredStringFromOptions(options, 'undername'), + transactionId: requiredStringFromOptions(options, 'transactionId'), + ttlSeconds: options.ttlSeconds, + }, + writeActionTagsFromOptions(options), + ); + }, +}); + +makeCommand({ + name: 'remove-ant-record', + description: 'Remove a record from an ANT process', + options: [optionMap.processId, optionMap.undername, ...writeActionOptions], + action: async (options) => { + return writeableANTFromOptions(options).removeRecord( + { + undername: requiredStringFromOptions(options, 'undername'), + }, + writeActionTagsFromOptions(options), + ); + }, +}); + +makeCommand({ + name: 'set-ant-ticker', + description: 'Set the ticker of an ANT process', + options: [optionMap.processId, optionMap.ticker, ...writeActionOptions], + action: async (options) => { + return writeableANTFromOptions(options).setTicker( + { + ticker: requiredStringFromOptions(options, 'ticker'), + }, + writeActionTagsFromOptions(options), + ); + }, +}); + +makeCommand({ + name: 'set-ant-name', + description: 'Set the name of an ANT process', + options: [optionMap.processId, optionMap.name, ...writeActionOptions], + action: async (options) => { + return writeableANTFromOptions(options).setName( + { + name: requiredStringFromOptions(options, 'name'), + }, + writeActionTagsFromOptions(options), + ); + }, +}); + +makeCommand({ + name: 'set-ant-description', + description: 'Set the description of an ANT process', + options: [optionMap.processId, optionMap.description, ...writeActionOptions], + action: async (options) => { + return writeableANTFromOptions(options).setDescription( + { + description: requiredStringFromOptions(options, 'description'), + }, + writeActionTagsFromOptions(options), + ); + }, +}); + +makeCommand({ + name: 'set-ant-keywords', + description: 'Set the keywords of an ANT process', + options: [optionMap.processId, optionMap.keywords, ...writeActionOptions], + action: async (options) => { + return writeableANTFromOptions(options).setKeywords( + { + keywords: requiredStringArrayFromOptions(options, 'keywords'), + }, + writeActionTagsFromOptions(options), + ); + }, +}); + +makeCommand({ + name: 'set-ant-logo', + description: 'Set the logo of an ANT process', + options: [ + optionMap.processId, + optionMap.transactionId, + ...writeActionOptions, + ], + action: async (options) => { + return writeableANTFromOptions(options).setLogo( + { + txId: requiredStringFromOptions(options, 'transactionId'), + }, + writeActionTagsFromOptions(options), + ); + }, +}); + +makeCommand< + ProcessIdWriteActionCLIOptions & { + name?: string; + } +>({ + name: 'release-name', + description: 'Release the name of an ANT process', + options: [optionMap.processId, optionMap.name, ...writeActionOptions], + action: async (options) => { + return writeableANTFromOptions(options).releaseName( + { + name: requiredStringFromOptions(options, 'name'), + ioProcessId: ioProcessIdFromOptions(options), + }, + writeActionTagsFromOptions(options), + ); + }, +}); + +makeCommand< + ProcessIdWriteActionCLIOptions & { + name?: string; + target?: string; + } +>({ + name: 'reassign-name', + description: 'Reassign the name of an ANT process to another ANT process', + options: [ + optionMap.processId, + optionMap.name, + optionMap.target, + ...writeActionOptions, + ], + action: async (options) => { + return writeableANTFromOptions(options).reassignName( + { + name: requiredStringFromOptions(options, 'name'), + ioProcessId: ioProcessIdFromOptions(options), + antProcessId: requiredStringFromOptions(options, 'target'), + }, + writeActionTagsFromOptions(options), + ); + }, +}); + +makeCommand< + ProcessIdWriteActionCLIOptions & { + name?: string; + target?: string; + } +>({ + name: 'approve-primary-name-request', + description: 'Approve a primary name request', + options: [ + optionMap.processId, + optionMap.name, + optionMap.target, + ...writeActionOptions, + ], +}); + +makeCommand< + ProcessIdWriteActionCLIOptions & { + names?: string[]; + } +>({ + name: 'remove-primary-names', + description: 'Remove primary names', + options: [optionMap.processId, optionMap.names, ...writeActionOptions], + action: async (options) => { + return writeableANTFromOptions(options).removePrimaryNames( + { + names: requiredStringArrayFromOptions(options, 'names'), + ioProcessId: ioProcessIdFromOptions(options), + }, + writeActionTagsFromOptions(options), + ); }, }); diff --git a/src/cli/options.ts b/src/cli/options.ts index ce9c3cfe..0d34c43e 100644 --- a/src/cli/options.ts +++ b/src/cli/options.ts @@ -187,9 +187,43 @@ export const optionMap = { type: 'boolean', }, increaseCount: { - alias: '--increase-count', + alias: '--increase-count ', description: 'Amount to increase the undername count of the record by', }, + undername: { + alias: '--undername ', + description: 'The undername to interact with', + }, + controller: { + alias: '--controller ', + description: 'The controller to interact with', + }, + transactionId: { + alias: '--transaction-id ', + description: 'The transaction ID to interact with', + }, + ttlSeconds: { + alias: '--ttl-seconds ', + description: 'The TTL in seconds for the record', + }, + ticker: { + alias: '--ticker ', + description: 'The ticker for the ANT', + }, + description: { + alias: '--description ', + description: 'The description for the ANT', + }, + keywords: { + alias: '--keywords ', + description: 'The keywords for the ANT', + type: 'array', + }, + names: { + alias: '--names ', + description: 'The names to interact with', + type: 'array', + }, }; export const walletOptions = [ diff --git a/src/cli/types.ts b/src/cli/types.ts index c3bd201a..fea0e0bc 100644 --- a/src/cli/types.ts +++ b/src/cli/types.ts @@ -44,6 +44,10 @@ export type WriteActionCLIOptions = GlobalCLIOptions & { skipConfirmation?: boolean; }; +export type ProcessIdWriteActionCLIOptions = WriteActionCLIOptions & { + processId?: string; +}; + /** * A utility type to transform `number` properties in a type `T` to `string` * properties, while preserving arrays, objects, and other types. @@ -81,7 +85,7 @@ export type AddressCLIOptions = GlobalCLIOptions & CLIOptionsFromAoParams; export type ProcessIdCLIOptions = GlobalCLIOptions & { - processId: string; + processId?: string; }; export type InitiatorCLIOptions = GlobalCLIOptions & diff --git a/src/cli/utils.ts b/src/cli/utils.ts index c02c189b..e4b514e7 100644 --- a/src/cli/utils.ts +++ b/src/cli/utils.ts @@ -19,6 +19,9 @@ import { readFileSync } from 'fs'; import prompts from 'prompts'; import { + ANT, + AoANTRead, + AoANTWrite, AoIORead, AoIOWrite, AoRedelegateStakeParams, @@ -46,6 +49,7 @@ import { JsonSerializable, OperatorStakeCLIOptions, PaginationCLIOptions, + ProcessIdCLIOptions, RedelegateStakeCLIOptions, TransferCLIOptions, UpdateGatewaySettingsCLIOptions, @@ -118,7 +122,7 @@ export function makeCommand({ return appliedCommand; } -function ioProcessIdFromOptions({ +export function ioProcessIdFromOptions({ ioProcessId, dev, }: GlobalCLIOptions): string { @@ -164,6 +168,11 @@ function setLoggerIfDebug(options: GlobalCLIOptions) { } } +export function getLoggerFromOptions(options: GlobalCLIOptions): Logger { + setLoggerIfDebug(options); + return Logger.default; +} + export function readIOFromOptions(options: GlobalCLIOptions): AoIORead { setLoggerIfDebug(options); @@ -463,3 +472,59 @@ export async function confirmationPrompt(message: string): Promise { }); return confirm; } + +export function requiredProcessIdFromOptions( + o: O, +): string { + if (o.processId === undefined) { + throw new Error('--process-id is required'); + } + return o.processId; +} + +export function readableANTFromOptions( + options: ProcessIdCLIOptions, +): AoANTRead { + return ANT.init({ + processId: requiredProcessIdFromOptions(options), + }); +} + +export function writeableANTFromOptions( + options: ProcessIdCLIOptions, + signer?: ContractSigner, +): AoANTWrite { + // TODO: ETH signer, SOL signer, etc. + signer ??= new ArweaveSigner(requiredJwkFromOptions(options)); + + return ANT.init({ + processId: requiredProcessIdFromOptions(options), + signer, + }); +} + +export function requiredStringFromOptions( + options: O, + key: string, +): string { + const value = options[key]; + if (value === undefined) { + throw new Error(`--${key} is required`); + } + return value; +} + +export function requiredStringArrayFromOptions( + options: O, + key: string, +): string[] { + const value = options[key]; + if (value === undefined) { + throw new Error(`--${key} is required`); + } + if (!Array.isArray(value)) { + throw new Error(`--${key} must be an array`); + } + console.log('value', value); + return value; +} diff --git a/src/utils/ao.ts b/src/utils/ao.ts index a2143add..5cc8e89a 100644 --- a/src/utils/ao.ts +++ b/src/utils/ao.ts @@ -45,6 +45,19 @@ export type SpawnANTState = { balances: Record; }; +export type SpawnANTParams = { + signer: AoSigner; + module?: string; + luaCodeTxId?: string; + ao?: AoClient; + scheduler?: string; + state?: SpawnANTState; + stateContractTxId?: string; + antRegistryId?: string; + logger?: Logger; + arweave?: Arweave; +}; + export async function spawnANT({ signer, module = AOS_MODULE_ID, @@ -56,26 +69,7 @@ export async function spawnANT({ antRegistryId = ANT_REGISTRY_ID, logger = Logger.default, arweave = defaultArweave, -}: { - signer: AoSigner; - module?: string; - luaCodeTxId?: string; - ao?: AoClient; - scheduler?: string; - state?: SpawnANTState; - stateContractTxId?: string; - antRegistryId?: string; - logger?: Logger; - arweave?: Arweave; -}): Promise { - const registryClient = ANTRegistry.init({ - process: new AOProcess({ - processId: antRegistryId, - ao, - logger, - }), - signer: signer, - }); +}: SpawnANTParams): Promise { //TODO: cache locally and only fetch if not cached const luaString = (await arweave.transactions.getData(luaCodeTxId, { decode: true, @@ -141,6 +135,14 @@ export async function spawnANT({ }); } + const registryClient = ANTRegistry.init({ + process: new AOProcess({ + processId: antRegistryId, + ao, + logger, + }), + signer: signer, + }); const { id: antRegistrationMsgId } = await registryClient.register({ processId, });