diff --git a/package-lock.json b/package-lock.json index cdb50d3..0f02d63 100644 --- a/package-lock.json +++ b/package-lock.json @@ -20,6 +20,7 @@ "ora": "5.4.1", "parse-database-url": "^0.3.0", "progress-string": "^1.2.2", + "prompts": "^2.4.2", "qoa": "^0.2.0", "uuid4": "^2.0.3" }, @@ -5314,8 +5315,6 @@ "version": "3.0.3", "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", - "dev": true, - "peer": true, "engines": { "node": ">=6" } @@ -6580,8 +6579,6 @@ "version": "2.4.2", "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", "integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==", - "dev": true, - "peer": true, "dependencies": { "kleur": "^3.0.3", "sisteransi": "^1.0.5" @@ -7437,9 +7434,7 @@ "node_modules/sisteransi": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", - "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", - "dev": true, - "peer": true + "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==" }, "node_modules/slash": { "version": "3.0.0", @@ -13324,9 +13319,7 @@ "kleur": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", - "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", - "dev": true, - "peer": true + "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==" }, "leven": { "version": "3.1.0", @@ -14291,8 +14284,6 @@ "version": "2.4.2", "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", "integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==", - "dev": true, - "peer": true, "requires": { "kleur": "^3.0.3", "sisteransi": "^1.0.5" @@ -14985,9 +14976,7 @@ "sisteransi": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", - "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", - "dev": true, - "peer": true + "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==" }, "slash": { "version": "3.0.0", diff --git a/package.json b/package.json index 69d8d97..ab48254 100644 --- a/package.json +++ b/package.json @@ -44,6 +44,7 @@ "ora": "5.4.1", "parse-database-url": "^0.3.0", "progress-string": "^1.2.2", + "prompts": "^2.4.2", "qoa": "^0.2.0", "uuid4": "^2.0.3" }, diff --git a/src/api/client.ts b/src/api/client.ts index 11c48ac..4977993 100644 --- a/src/api/client.ts +++ b/src/api/client.ts @@ -14,6 +14,7 @@ import { ResolveEventVersionDataAfterResponse, StringMap, StringKeyMap, + SearchEventVersionsResponse, } from '../types' const formatAuthHeader = (sessionToken: string): StringMap => ({ @@ -148,6 +149,13 @@ async function getContractGroupEvents(group: string): Promise { + const { error, data } = await get(buildUrl(routes.SEARCH_EVENT_VERSIONS), { + name, + }) + return error ? { error } : { events: data || [] } +} + async function resolveEventVersionCursors( givenName: string ): Promise { @@ -174,6 +182,7 @@ export const client = { createContractGroup, getContractGroup, getContractGroupEvents, + searchEventVersions, resolveEventVersionCursors, getEventVersionDataAfter, } diff --git a/src/api/routes.ts b/src/api/routes.ts index ed68bac..2aecdd7 100644 --- a/src/api/routes.ts +++ b/src/api/routes.ts @@ -23,6 +23,7 @@ export const routes = { CREATE_CONTRACT_GROUP: [prefix.CONTRACT, 'group'].join('/'), GET_CONTRACT_GROUP: [prefix.CONTRACT, 'group'].join('/'), GET_CONTRACT_GROUP_EVENTS: [prefix.CONTRACT, 'group', 'events'].join('/'), + SEARCH_EVENT_VERSIONS: [prefix.EVENT_VERSION + 's', 'search'].join('/'), RESOLVE_EVENT_VERSION_CURSORS: [prefix.EVENT_VERSION + 's', 'resolve', 'cursors'].join('/'), GET_EVENT_VERSION_DATA_AFTER: [prefix.EVENT_VERSION + 's', 'data', 'after'].join('/'), } diff --git a/src/cmds/create/group.ts b/src/cmds/create/group.ts index 72d457e..7e4819a 100644 --- a/src/cmds/create/group.ts +++ b/src/cmds/create/group.ts @@ -5,7 +5,8 @@ import { client } from '../../api/client' import { chainIdsSet } from '../../utils/chains' import { isValidContractGroup } from '../../utils/validators' import { resolveAbi } from '../../utils/abi' -import { promptCreateContractGroupDetails } from '../../utils/prompt' +import { getFactoryEvent, promptCreateContractGroupDetails } from '../../utils/prompt' +import chalk from 'chalk' const CMD = 'group' @@ -15,6 +16,7 @@ function addCreateGroupCmd(cmd) { .argument('[group]', 'Name of the contract group in "nsp.ContractName" format', null) .option('--chains ', `The chain ids of the group's future contracts`, null) .option('--abi ', `Path to the group's ABI`, null) + .option('--isFactory ', `Factory contract?`, null) .action(createGroup) } @@ -26,6 +28,7 @@ async function createGroup( opts: { chains: string abi: string + isFactory: boolean } ) { // Get authed user's session token (if any). @@ -40,7 +43,12 @@ async function createGroup( } // Prompt user for inputs if not given directly. - const promptResp = await promptCreateContractGroupDetails(group, opts.chains, opts.abi) + const promptResp = await promptCreateContractGroupDetails( + group, + opts.chains, + opts.abi, + opts.isFactory + ) // Validate contract group structure (e.g. "nsp.ContractName") if (!isValidContractGroup(promptResp.group)) { @@ -66,6 +74,22 @@ async function createGroup( return } + // Get factory event details for factory group. + if (promptResp.isFactory) { + const [factoryEventId, addressProperty] = await getFactoryEvent(promptResp.group) + const [name, version] = factoryEventId?.split('@') || [] + + // Log factory event details (temp formatting). + log(` + ${chalk.cyanBright(contractName)} | Factory group + ${ + factoryEventId + ? `${chalk.green(name.split('.')[2])} | ${chalk.gray(version)} + Address property: ${chalk.gray(addressProperty ? addressProperty : 'No property selected')}` + : chalk.gray('No factory event selected') + }`) + } + // Create empty contract group. const { error: createError } = await client.createContractGroup( sessionToken, diff --git a/src/services/eventVersionServices.ts b/src/services/eventVersionServices.ts new file mode 100644 index 0000000..cdd8a7b --- /dev/null +++ b/src/services/eventVersionServices.ts @@ -0,0 +1,28 @@ +import chalk from 'chalk' +import { client } from '../api/client' +import { logFailure } from '../logger' +import { StringKeyMap } from '../types' + +export const getEventVersions = async (name) => { + const { error, events } = await client.searchEventVersions(name) + if (error) { + logFailure(`Event retreival failed: ${error}`) + return + } + return events +} + +export const formatEventVersions = async (events: StringKeyMap): Promise => { + const cliEvents = events.map((event) => { + const [cliName, version] = event.searchId.split('@') + const formattedEventVersion = chalk.gray( + ` | ${version.slice(0, 10)}${version.length > 10 ? '...' : ''}` + ) + return { + title: cliName + formattedEventVersion, + value: event.searchId, + } + }) + + return cliEvents +} diff --git a/src/types.ts b/src/types.ts index 9262ece..f545323 100644 --- a/src/types.ts +++ b/src/types.ts @@ -97,3 +97,8 @@ export interface Migration { name: string version: string } + +export interface SearchEventVersionsResponse { + error?: string + events?: StringKeyMap +} diff --git a/src/utils/prompt.ts b/src/utils/prompt.ts index 6964330..86af07e 100644 --- a/src/utils/prompt.ts +++ b/src/utils/prompt.ts @@ -1,6 +1,8 @@ import { StringKeyMap } from '../types' import qoa from 'qoa' import chalk from 'chalk' +import prompts from 'prompts' +import { formatEventVersions, getEventVersions } from '../services/eventVersionServices' const EMAIL_PROMPT = { type: 'input', @@ -74,6 +76,14 @@ const CONTRACT_ADDRESSES_PROMPT = { handle: 'addresses', } +const IS_FACTORY_PROMPT = { + type: 'confirm', + name: 'value', + message: 'Factory contract? ', + accept: 'Y', + deny: 'n', +} + function stripWrappedQuotes(val: string): string { if (!val) return val if (val[0] === '"') { @@ -135,7 +145,8 @@ export async function promptNewLiveObjectDetails( export async function promptCreateContractGroupDetails( group?: string, chainIds?: string, - abi?: string + abi?: string, + isFactory?: boolean ): Promise { // Required while (!group) { @@ -152,7 +163,12 @@ export async function promptCreateContractGroupDetails( abi = stripWrappedQuotes((await qoa.prompt([ABI_PROMPT])).abi) } - return { group, chainIds, abi } + // Required + while (isFactory === null) { + isFactory = (await prompts(IS_FACTORY_PROMPT)).value + } + + return { group, chainIds, abi, isFactory } } export async function promptAddContractsDetails( @@ -181,3 +197,57 @@ export async function promptAddContractsDetails( return { addresses, chainId, group, abi } } + +export async function getFactoryEvent(contractGroup: string): Promise { + let factoryEventId + let addressProperty + let cachedResults + let useInitialInput = true + + factoryEventId = ( + await prompts({ + type: 'autocomplete', + name: 'searchId', + message: 'Search for factory event: ', + choices: ['', '', '', '', '', ''], + fallback: ' ', + suggest: async (input, choices) => { + let results = useInitialInput + ? await getEventVersions(contractGroup) + : await getEventVersions(input) + useInitialInput = false + if (!results.length) { + return [] + } + cachedResults = results + const displayResults = await formatEventVersions(results) + return displayResults + }, + }) + ).searchId + + if (!factoryEventId) return [] + + const factoryEvent = cachedResults.find((result) => result.searchId === factoryEventId) + const properties = factoryEvent.addressProperties.map((prop) => { + return { + title: prop, + value: prop, + } + }) + + if (!properties.length) return [factoryEventId] + + addressProperty = ( + await prompts({ + type: 'select', + message: 'Which property is the contract address?', + name: 'property', + symbol: '>', + fallback: 'No properties available', + choices: properties, + }) + ).property + + return [factoryEventId, addressProperty] +}