From d833d832a65f42153f1b0c91eb6bfc9048dce832 Mon Sep 17 00:00:00 2001 From: Jesse van Muijden Date: Wed, 22 Nov 2023 22:40:52 -0400 Subject: [PATCH] feat(kadena-cli): account commands --- .../config/api-extractor-built-in.json | 376 ++++++++++++++++++ .../etc/client-utils-built-in.api.md | 28 ++ packages/libs/client-utils/package.json | 9 +- .../src/built-in/create-principal.ts | 31 ++ .../libs/client-utils/src/built-in/index.ts | 1 + packages/tools/kadena-cli/package.json | 1 + .../src/account/commands/accountCreate.ts | 96 +++++ .../src/account/commands/accountDetails.ts | 39 ++ .../src/account/commands/accountGetBalance.ts | 38 ++ .../account/commands/accountKeysetCreate.ts | 126 ------ .../account/commands/accountTransferCreate.ts | 106 +++++ .../tools/kadena-cli/src/account/index.ts | 24 +- .../tools/kadena-cli/src/prompts/account.ts | 62 +-- .../kadena-cli/src/utils/globalOptions.ts | 44 +- pnpm-lock.yaml | 3 + 15 files changed, 814 insertions(+), 170 deletions(-) create mode 100644 packages/libs/client-utils/config/api-extractor-built-in.json create mode 100644 packages/libs/client-utils/etc/client-utils-built-in.api.md create mode 100644 packages/libs/client-utils/src/built-in/create-principal.ts create mode 100644 packages/libs/client-utils/src/built-in/index.ts create mode 100644 packages/tools/kadena-cli/src/account/commands/accountCreate.ts create mode 100644 packages/tools/kadena-cli/src/account/commands/accountDetails.ts create mode 100644 packages/tools/kadena-cli/src/account/commands/accountGetBalance.ts delete mode 100644 packages/tools/kadena-cli/src/account/commands/accountKeysetCreate.ts create mode 100644 packages/tools/kadena-cli/src/account/commands/accountTransferCreate.ts diff --git a/packages/libs/client-utils/config/api-extractor-built-in.json b/packages/libs/client-utils/config/api-extractor-built-in.json new file mode 100644 index 0000000000..bdad7c0b1e --- /dev/null +++ b/packages/libs/client-utils/config/api-extractor-built-in.json @@ -0,0 +1,376 @@ +/** + * Config file for API Extractor. For more info, please visit: https://api-extractor.com + */ +{ + "$schema": "https://developer.microsoft.com/json-schemas/api-extractor/v7/api-extractor.schema.json", + + /** + * Optionally specifies another JSON config file that this file extends from. This provides a way for + * standard settings to be shared across multiple projects. + * + * If the path starts with "./" or "../", the path is resolved relative to the folder of the file that contains + * the "extends" field. Otherwise, the first path segment is interpreted as an NPM package name, and will be + * resolved using NodeJS require(). + * + * SUPPORTED TOKENS: none + * DEFAULT VALUE: "" + */ + // "extends": "./shared/api-extractor-base.json" + // "extends": "my-package/include/api-extractor-base.json" + + /** + * Determines the "" token that can be used with other config file settings. The project folder + * typically contains the tsconfig.json and package.json config files, but the path is user-defined. + * + * The path is resolved relative to the folder of the config file that contains the setting. + * + * The default value for "projectFolder" is the token "", which means the folder is determined by traversing + * parent folders, starting from the folder containing api-extractor.json, and stopping at the first folder + * that contains a tsconfig.json file. If a tsconfig.json file cannot be found in this way, then an error + * will be reported. + * + * SUPPORTED TOKENS: + * DEFAULT VALUE: "" + */ + // "projectFolder": "..", + + /** + * (REQUIRED) Specifies the .d.ts file to be used as the starting point for analysis. API Extractor + * analyzes the symbols exported by this module. + * + * The file extension must be ".d.ts" and not ".ts". + * + * The path is resolved relative to the folder of the config file that contains the setting; to change this, + * prepend a folder token such as "". + * + * SUPPORTED TOKENS: , , + */ + "mainEntryPointFilePath": "/lib/built-in/index.d.ts", + + /** + * A list of NPM package names whose exports should be treated as part of this package. + * + * For example, suppose that Webpack is used to generate a distributed bundle for the project "library1", + * and another NPM package "library2" is embedded in this bundle. Some types from library2 may become part + * of the exported API for library1, but by default API Extractor would generate a .d.ts rollup that explicitly + * imports library2. To avoid this, we can specify: + * + * "bundledPackages": [ "library2" ], + * + * This would direct API Extractor to embed those types directly in the .d.ts rollup, as if they had been + * local files for library1. + */ + "bundledPackages": [], + + /** + * Determines how the TypeScript compiler engine will be invoked by API Extractor. + */ + "compiler": { + /** + * Specifies the path to the tsconfig.json file to be used by API Extractor when analyzing the project. + * + * The path is resolved relative to the folder of the config file that contains the setting; to change this, + * prepend a folder token such as "". + * + * Note: This setting will be ignored if "overrideTsconfig" is used. + * + * SUPPORTED TOKENS: , , + * DEFAULT VALUE: "/tsconfig.json" + */ + // "tsconfigFilePath": "/tsconfig.json", + /** + * Provides a compiler configuration that will be used instead of reading the tsconfig.json file from disk. + * The object must conform to the TypeScript tsconfig schema: + * + * http://json.schemastore.org/tsconfig + * + * If omitted, then the tsconfig.json file will be read from the "projectFolder". + * + * DEFAULT VALUE: no overrideTsconfig section + */ + // "overrideTsconfig": { + // . . . + // } + /** + * This option causes the compiler to be invoked with the --skipLibCheck option. This option is not recommended + * and may cause API Extractor to produce incomplete or incorrect declarations, but it may be required when + * dependencies contain declarations that are incompatible with the TypeScript engine that API Extractor uses + * for its analysis. Where possible, the underlying issue should be fixed rather than relying on skipLibCheck. + * + * DEFAULT VALUE: false + */ + // "skipLibCheck": true, + }, + + /** + * Configures how the API report file (*.api.md) will be generated. + */ + "apiReport": { + /** + * (REQUIRED) Whether to generate an API report. + */ + "enabled": true, + + /** + * The filename for the API report files. It will be combined with "reportFolder" or "reportTempFolder" to produce + * a full file path. + * + * The file extension should be ".api.md", and the string should not contain a path separator such as "\" or "/". + * + * SUPPORTED TOKENS: , + * DEFAULT VALUE: ".api.md" + */ + "reportFileName": "client-utils-built-in.api.md" + + /** + * Specifies the folder where the API report file is written. The file name portion is determined by + * the "reportFileName" setting. + * + * The API report file is normally tracked by Git. Changes to it can be used to trigger a branch policy, + * e.g. for an API review. + * + * The path is resolved relative to the folder of the config file that contains the setting; to change this, + * prepend a folder token such as "". + * + * SUPPORTED TOKENS: , , + * DEFAULT VALUE: "/temp/" + */ + // "reportFolder": "/temp/", + + /** + * Specifies the folder where the temporary report file is written. The file name portion is determined by + * the "reportFileName" setting. + * + * After the temporary file is written to disk, it is compared with the file in the "reportFolder". + * If they are different, a production build will fail. + * + * The path is resolved relative to the folder of the config file that contains the setting; to change this, + * prepend a folder token such as "". + * + * SUPPORTED TOKENS: , , + * DEFAULT VALUE: "/temp/" + */ + // "reportTempFolder": "/temp/" + }, + + /** + * Configures how the doc model file (*.api.json) will be generated. + */ + "docModel": { + /** + * (REQUIRED) Whether to generate a doc model file. + */ + "enabled": true, + + /** + * The output path for the doc model file. The file extension should be ".api.json". + * + * The path is resolved relative to the folder of the config file that contains the setting; to change this, + * prepend a folder token such as "". + * + * SUPPORTED TOKENS: , , + * DEFAULT VALUE: "/temp/.api.json" + */ + "apiJsonFilePath": "/temp/client-utils-built-in.api.json" + }, + + /** + * Configures how the .d.ts rollup file will be generated. + */ + "dtsRollup": { + /** + * (REQUIRED) Whether to generate the .d.ts rollup file. + */ + "enabled": true, + + /** + * Specifies the output path for a .d.ts rollup file to be generated without any trimming. + * This file will include all declarations that are exported by the main entry point. + * + * If the path is an empty string, then this file will not be written. + * + * The path is resolved relative to the folder of the config file that contains the setting; to change this, + * prepend a folder token such as "". + * + * SUPPORTED TOKENS: , , + * DEFAULT VALUE: "/dist/.d.ts" + */ + "untrimmedFilePath": "/temp/client-utils-built-in.d.ts" + + /** + * Specifies the output path for a .d.ts rollup file to be generated with trimming for an "alpha" release. + * This file will include only declarations that are marked as "@public", "@beta", or "@alpha". + * + * The path is resolved relative to the folder of the config file that contains the setting; to change this, + * prepend a folder token such as "". + * + * SUPPORTED TOKENS: , , + * DEFAULT VALUE: "" + */ + // "alphaTrimmedFilePath": "/dist/-alpha.d.ts", + + /** + * Specifies the output path for a .d.ts rollup file to be generated with trimming for a "beta" release. + * This file will include only declarations that are marked as "@public" or "@beta". + * + * The path is resolved relative to the folder of the config file that contains the setting; to change this, + * prepend a folder token such as "". + * + * SUPPORTED TOKENS: , , + * DEFAULT VALUE: "" + */ + // "betaTrimmedFilePath": "/dist/-beta.d.ts", + + /** + * Specifies the output path for a .d.ts rollup file to be generated with trimming for a "public" release. + * This file will include only declarations that are marked as "@public". + * + * If the path is an empty string, then this file will not be written. + * + * The path is resolved relative to the folder of the config file that contains the setting; to change this, + * prepend a folder token such as "". + * + * SUPPORTED TOKENS: , , + * DEFAULT VALUE: "" + */ + // "publicTrimmedFilePath": "/dist/-public.d.ts", + + /** + * When a declaration is trimmed, by default it will be replaced by a code comment such as + * "Excluded from this release type: exampleMember". Set "omitTrimmingComments" to true to remove the + * declaration completely. + * + * DEFAULT VALUE: false + */ + // "omitTrimmingComments": true + }, + + /** + * Configures how the tsdoc-metadata.json file will be generated. + */ + "tsdocMetadata": { + /** + * Whether to generate the tsdoc-metadata.json file. + * + * DEFAULT VALUE: true + */ + // "enabled": true, + /** + * Specifies where the TSDoc metadata file should be written. + * + * The path is resolved relative to the folder of the config file that contains the setting; to change this, + * prepend a folder token such as "". + * + * The default value is "", which causes the path to be automatically inferred from the "tsdocMetadata", + * "typings" or "main" fields of the project's package.json. If none of these fields are set, the lookup + * falls back to "tsdoc-metadata.json" in the package folder. + * + * SUPPORTED TOKENS: , , + * DEFAULT VALUE: "" + */ + // "tsdocMetadataFilePath": "/dist/tsdoc-metadata.json" + }, + + /** + * Specifies what type of newlines API Extractor should use when writing output files. By default, the output files + * will be written with Windows-style newlines. To use POSIX-style newlines, specify "lf" instead. + * To use the OS's default newline kind, specify "os". + * + * DEFAULT VALUE: "crlf" + */ + // "newlineKind": "crlf", + + /** + * Configures how API Extractor reports error and warning messages produced during analysis. + * + * There are three sources of messages: compiler messages, API Extractor messages, and TSDoc messages. + */ + "messages": { + /** + * Configures handling of diagnostic messages reported by the TypeScript compiler engine while analyzing + * the input .d.ts files. + * + * TypeScript message identifiers start with "TS" followed by an integer. For example: "TS2551" + * + * DEFAULT VALUE: A single "default" entry with logLevel=warning. + */ + "compilerMessageReporting": { + /** + * Configures the default routing for messages that don't match an explicit rule in this table. + */ + "default": { + /** + * Specifies whether the message should be written to the the tool's output log. Note that + * the "addToApiReportFile" property may supersede this option. + * + * Possible values: "error", "warning", "none" + * + * Errors cause the build to fail and return a nonzero exit code. Warnings cause a production build fail + * and return a nonzero exit code. For a non-production build (e.g. when "api-extractor run" includes + * the "--local" option), the warning is displayed but the build will not fail. + * + * DEFAULT VALUE: "warning" + */ + "logLevel": "warning" + + /** + * When addToApiReportFile is true: If API Extractor is configured to write an API report file (.api.md), + * then the message will be written inside that file; otherwise, the message is instead logged according to + * the "logLevel" option. + * + * DEFAULT VALUE: false + */ + // "addToApiReportFile": false + } + + // "TS2551": { + // "logLevel": "warning", + // "addToApiReportFile": true + // }, + // + // . . . + }, + + /** + * Configures handling of messages reported by API Extractor during its analysis. + * + * API Extractor message identifiers start with "ae-". For example: "ae-extra-release-tag" + * + * DEFAULT VALUE: See api-extractor-defaults.json for the complete table of extractorMessageReporting mappings + */ + "extractorMessageReporting": { + "default": { + "logLevel": "warning" + // "addToApiReportFile": false + } + + // "ae-extra-release-tag": { + // "logLevel": "warning", + // "addToApiReportFile": true + // }, + // + // . . . + }, + + /** + * Configures handling of messages reported by the TSDoc parser when analyzing code comments. + * + * TSDoc message identifiers start with "tsdoc-". For example: "tsdoc-link-tag-unescaped-text" + * + * DEFAULT VALUE: A single "default" entry with logLevel=warning. + */ + "tsdocMessageReporting": { + "default": { + "logLevel": "warning" + // "addToApiReportFile": false + } + + // "tsdoc-link-tag-unescaped-text": { + // "logLevel": "warning", + // "addToApiReportFile": true + // }, + // + // . . . + } + } +} diff --git a/packages/libs/client-utils/etc/client-utils-built-in.api.md b/packages/libs/client-utils/etc/client-utils-built-in.api.md new file mode 100644 index 0000000000..acc7670cec --- /dev/null +++ b/packages/libs/client-utils/etc/client-utils-built-in.api.md @@ -0,0 +1,28 @@ +## API Report File for "@kadena/client-utils" + +> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). + +```ts + +import type { ChainId } from '@kadena/client'; +import { ICommandResult } from '@kadena/chainweb-node-client'; +import type { INetworkOptions } from '@kadena/client'; +import type { IPactCommand } from '@kadena/client'; +import { IPactDecimal } from '@kadena/types'; +import { IPactInt } from '@kadena/types'; +import type { ISignFunction } from '@kadena/client'; +import { PactValue } from '@kadena/types'; + +// Warning: (ae-forgotten-export) The symbol "ICreatePrincipalCommandInput" needs to be exported by the entry point index.d.ts +// Warning: (ae-forgotten-export) The symbol "IClientConfig" needs to be exported by the entry point index.d.ts +// Warning: (ae-forgotten-export) The symbol "IEmitterWrapper" needs to be exported by the entry point index.d.ts +// +// @alpha (undocumented) +export const createPrincipalCommand: (inputs: ICreatePrincipalCommandInput, config: Omit) => Promise<() => IEmitterWrapper<[{ +event: "dirtyRead"; +data: ICommandResult; +}], [], Promise | Promise | Promise | Promise | Promise | Promise | Promise | Promise | Promise>>; + +// (No @packageDocumentation comment for this package) + +``` diff --git a/packages/libs/client-utils/package.json b/packages/libs/client-utils/package.json index c6c238e39f..326a8acd07 100644 --- a/packages/libs/client-utils/package.json +++ b/packages/libs/client-utils/package.json @@ -13,12 +13,16 @@ "Albert Groothedde " ], "exports": { + "./built-in": "./lib/built-in/index.js", "./coin": "./lib/coin/index.js", "./core": "./lib/core/index.js" }, "main": "./lib/core/index.js", "typesVersions": { "*": { + "built-in": [ + "./lib/built-in/index.d.ts" + ], "core": [ "./lib/core/index.d.ts" ], @@ -30,15 +34,18 @@ "files": [ "dist", "lib", + "built-in", "coin", "core", "src" ], "scripts": { - "ae": "pnpm run ae:core && pnpm run ae:coin", + "ae": "pnpm run ae:built-in && pnpm run ae:core && pnpm run ae:coin", + "ae:built-in": "api-extractor run --verbose -c ./config/api-extractor-built-in.json", "ae:coin": "api-extractor run --verbose -c ./config/api-extractor-coin.json", "ae:core": "api-extractor run --verbose -c ./config/api-extractor-core.json", "build": "pnpm run pactjs:generate:contract && pnpm run generate-pipe-type && tsc && pnpm run ae", + "dev:ae:built-in": "api-extractor run --local --verbose -c ./config/api-extractor-built-in.json", "dev:ae:coin": "api-extractor run --local --verbose -c ./config/api-extractor-coin.json", "dev:ae:core": "api-extractor run --local --verbose -c ./config/api-extractor-core.json", "dev:postinstall": "pnpm run pactjs:generate:contract", diff --git a/packages/libs/client-utils/src/built-in/create-principal.ts b/packages/libs/client-utils/src/built-in/create-principal.ts new file mode 100644 index 0000000000..917c187d4b --- /dev/null +++ b/packages/libs/client-utils/src/built-in/create-principal.ts @@ -0,0 +1,31 @@ +import type { ChainId } from '@kadena/client'; +import { + addData, + execution, +} from '@kadena/client/fp'; +import { pipe } from 'ramda'; +import { dirtyReadClient } from '../core/client-helpers'; +import type { IClientConfig } from '../core/utils/helpers'; + +interface ICreatePrincipalCommandInput { + keyset: { + keys: string[]; + pred: 'keys-all' | 'keys-two' | 'keys-one'; + }; + gasPayer: { account: string; publicKeys: string[] }; + chainId: ChainId; +} + +/** + * @alpha + */ +export const createPrincipalCommand = async ( + inputs: ICreatePrincipalCommandInput, + config: Omit, +) => + pipe( + () => '(create-principal (read-keyset "ks"))', + execution, + addData('ks', inputs.keyset), + dirtyReadClient(config), + ); diff --git a/packages/libs/client-utils/src/built-in/index.ts b/packages/libs/client-utils/src/built-in/index.ts new file mode 100644 index 0000000000..a1ea58e638 --- /dev/null +++ b/packages/libs/client-utils/src/built-in/index.ts @@ -0,0 +1 @@ +export * from './create-principal'; diff --git a/packages/tools/kadena-cli/package.json b/packages/tools/kadena-cli/package.json index a23647dc7c..28242bc1e5 100644 --- a/packages/tools/kadena-cli/package.json +++ b/packages/tools/kadena-cli/package.json @@ -38,6 +38,7 @@ "dependencies": { "@inquirer/prompts": "^3.0.4", "@kadena/client": "workspace:^", + "@kadena/client-utils": "workspace:^", "@kadena/cryptography-utils": "workspace:*", "@kadena/pactjs": "workspace:*", "@kadena/pactjs-cli": "workspace:^", diff --git a/packages/tools/kadena-cli/src/account/commands/accountCreate.ts b/packages/tools/kadena-cli/src/account/commands/accountCreate.ts new file mode 100644 index 0000000000..c4cfae8b3f --- /dev/null +++ b/packages/tools/kadena-cli/src/account/commands/accountCreate.ts @@ -0,0 +1,96 @@ +import { createSignWithKeypair } from '@kadena/client'; +import { createPrincipalCommand } from '@kadena/client-utils/built-in'; +import { createAccount } from '@kadena/client-utils/coin'; +import type { ChainId } from '@kadena/types'; +import chalk from 'chalk'; +import type { Command } from 'commander'; +import debug from 'debug'; +import { FAUCET_CONSTANTS } from '../../constants/faucet.js'; +import { createCommand } from '../../utils/createCommand.js'; +import { globalOptions } from '../../utils/globalOptions.js'; + +export const createAccountCommand: (program: Command, version: string) => void = + createCommand( + 'create', + 'Create account', + [ + globalOptions.fungible(), + globalOptions.network(), + globalOptions.networkChainId(), + globalOptions.publicKeys(), + globalOptions.predicate(), + ], + async (config) => { + debug('account-create:action')({ config }); + + try { + const createPrincipal = await createPrincipalCommand( + { + keyset: { + pred: config.predicate as 'keys-all' | 'keys-two' | 'keys-one', + keys: config.publicKeysConfig, + }, + gasPayer: { account: 'dummy', publicKeys: [] }, + chainId: config.chainId as ChainId, + }, + { + host: config.networkConfig.networkHost, + defaults: { + networkId: config.networkConfig.networkId, + meta: { + chainId: config.chainId as ChainId, + }, + }, + }, + ); + + const account = await createPrincipal().execute(); + + const signWithKeyPair = createSignWithKeypair({ + publicKey: FAUCET_CONSTANTS.devnetKp.publicKey, + secretKey: FAUCET_CONSTANTS.devnetKp.secretKey, + }); + + const result = await createAccount( + { + account: account as string, + keyset: { + pred: config.predicate as 'keys-all' | 'keys-two' | 'keys-one', + keys: config.publicKeysConfig, + }, + gasPayer: { + account: FAUCET_CONSTANTS.devnetAcct, + publicKeys: [FAUCET_CONSTANTS.devnetKp.publicKey], + }, + chainId: config.chainId as ChainId, + }, + { + host: config.networkConfig.networkHost, + defaults: { + networkId: config.networkConfig.networkId, + meta: { + chainId: config.chainId as ChainId, + }, + }, + sign: signWithKeyPair, + }, + ).execute(); + + if (result === 'Write succeeded') { + console.log( + chalk.green( + `\nCreated account "${account}" on chain ${config.chainId} of "${config.network}".\n`, + ), + ); + } else { + console.log( + chalk.red( + `\nFailed to create the account on "${config.network}".\n`, + ), + ); + } + } catch (e) { + console.log(chalk.red(e.message)); + } + }, + ); diff --git a/packages/tools/kadena-cli/src/account/commands/accountDetails.ts b/packages/tools/kadena-cli/src/account/commands/accountDetails.ts new file mode 100644 index 0000000000..8c74e4ddaf --- /dev/null +++ b/packages/tools/kadena-cli/src/account/commands/accountDetails.ts @@ -0,0 +1,39 @@ +import { details } from '@kadena/client-utils/coin'; +import type { ChainId } from '@kadena/types'; +import chalk from 'chalk'; +import type { Command } from 'commander'; +import debug from 'debug'; +import { createCommand } from '../../utils/createCommand.js'; +import { globalOptions } from '../../utils/globalOptions.js'; + +export const accountDetailsCommand: ( + program: Command, + version: string, +) => void = createCommand( + 'details', + 'Get details of an account', + [ + globalOptions.accountName(), + globalOptions.fungible(), + globalOptions.network(), + globalOptions.networkChainId(), + ], + async (config) => { + debug('account-details:action')({ config }); + + try { + const accountDetails = await details( + config.accountName, + config.networkConfig.networkId, + config.chainId as ChainId, + config.networkConfig.networkHost, + ); + console.log( + chalk.green(`\nDetails of account "${config.accountName}":\n`), + ); + console.log(accountDetails); + } catch (e) { + console.log(chalk.red(e.message)); + } + }, +); diff --git a/packages/tools/kadena-cli/src/account/commands/accountGetBalance.ts b/packages/tools/kadena-cli/src/account/commands/accountGetBalance.ts new file mode 100644 index 0000000000..c519c6e163 --- /dev/null +++ b/packages/tools/kadena-cli/src/account/commands/accountGetBalance.ts @@ -0,0 +1,38 @@ +import { getBalance } from '@kadena/client-utils/coin'; +import type { ChainId } from '@kadena/types'; +import chalk from 'chalk'; +import type { Command } from 'commander'; +import debug from 'debug'; +import { createCommand } from '../../utils/createCommand.js'; +import { globalOptions } from '../../utils/globalOptions.js'; + +export const getBalanceCommand: (program: Command, version: string) => void = + createCommand( + 'get-balance', + 'Get balance of an account', + [ + globalOptions.accountName(), + globalOptions.fungible(), + globalOptions.network(), + globalOptions.networkChainId(), + ], + async (config) => { + debug('account-details:action')({ config }); + + try { + const balance = await getBalance( + config.accountName, + config.networkConfig.networkId, + config.chainId as ChainId, + config.networkConfig.networkHost, + ); + console.log( + chalk.green( + `\nThe balance of "${config.accountName}" on chain ${config.chainId} of ${config.network} is ${balance}\n`, + ), + ); + } catch (e) { + console.log(chalk.red(e.message)); + } + }, + ); diff --git a/packages/tools/kadena-cli/src/account/commands/accountKeysetCreate.ts b/packages/tools/kadena-cli/src/account/commands/accountKeysetCreate.ts deleted file mode 100644 index 353fb936ab..0000000000 --- a/packages/tools/kadena-cli/src/account/commands/accountKeysetCreate.ts +++ /dev/null @@ -1,126 +0,0 @@ -// import type { ChainId } from '@kadena/client'; -// import { createSignWithKeypair } from '@kadena/client'; -// import { -// createPrincipalAccount, -// createPrincipalCommand, -// } from '@kadena/client-utils/coin'; -// import chalk from 'chalk'; -// import { createCommand } from '../../utils/createCommand.js'; -// import { globalOptions } from '../../utils/globalOptions.js'; - -// // eslint-disable-next-line @rushstack/typedef-var -// export const createKeysetCommand = createCommand( -// 'create', -// 'Create account', -// [ -// globalOptions.keyKeyset(), -// globalOptions.networkNetwork(), -// globalOptions.networkChainId(), -// ], -// async (config) => { -// try { -// const publicKeys = config.keysetConfig.publicKeys -// .split(',') -// .map((value: string) => value.trim()) -// .filter((value: string) => value.length); -// for (const keypair of config.keysetConfig.publicKeysFromKeypairs) { -// const keypairConfig = await loadKeypairConfig(keypair); -// publicKeys.push(keypairConfig.publicKey || ''); -// } - -// const createPrincipal = await createPrincipalCommand( -// { -// keyset: { -// pred: config.keysetConfig.predicate as -// | 'keys-all' -// | 'keys-two' -// | 'keys-one', -// keys: publicKeys, -// }, -// gasPayer: { account: 'dummy', publicKeys: [] }, -// chainId: config.chainId as ChainId, -// }, -// { -// host: config.networkConfig.networkHost, -// defaults: { -// networkId: config.networkConfig.networkId, -// meta: { -// chainId: config.chainId as ChainId, -// }, -// }, -// }, -// ); - -// const account = await createPrincipal().execute(); - -// writeAccount({ -// ...config, -// name: config.account, -// account: account as string, -// }); - -// console.log( -// chalk.green(`\nSaved the account configuration "${config.account}".\n`), -// ); - -// // @todo: make gas payer configuration more flexible. -// const gasPayerKeyset = await loadKeysetConfig( -// config.gasPayerConfig.keyset, -// ); -// // @todo: now it is simply assumed that the gas payer account is governed with a keypair -// const gasPayerKeypair = await loadKeypairConfig( -// gasPayerKeyset.publicKeysFromKeypairs.pop() || '', -// ); - -// // @todo: also allow other signing methods -// const signWithKeyPair = createSignWithKeypair({ -// publicKey: gasPayerKeypair.publicKey, -// secretKey: gasPayerKeypair.secretKey, -// }); - -// const c = await createPrincipalAccount( -// { -// keyset: { -// pred: 'keys-all', -// keys: [ -// '1827389ca64cb5c3c77352eb2087c2bb503061d22fb1edcadb5d90ad1dee80f5', -// ], -// }, -// gasPayer: { -// account: 'sender00', -// publicKeys: [gasPayerKeypair.publicKey], -// }, -// chainId: config.chainId as ChainId, -// }, -// { -// host: config.networkConfig.networkHost, -// defaults: { -// networkId: config.networkConfig.networkId, -// meta: { -// chainId: config.chainId as ChainId, -// }, -// }, -// sign: signWithKeyPair, -// }, -// ); - -// const result = await c.execute(); - -// if (result === 'Write succeeded') { -// console.log( -// chalk.green( -// `\nCreated account "${account}" guarded by keyset "${config.keyset}" on chain ${config.chainId} of "${config.network}".\n`, -// ), -// ); -// } else { -// console.log( -// chalk.red( -// `\nFailed to created the account on "${config.network}".\n`, -// ), -// ); -// } -// } catch (e) { -// console.log(chalk.red(e.message)); -// } -// }, -// ); diff --git a/packages/tools/kadena-cli/src/account/commands/accountTransferCreate.ts b/packages/tools/kadena-cli/src/account/commands/accountTransferCreate.ts new file mode 100644 index 0000000000..fe89a1a26a --- /dev/null +++ b/packages/tools/kadena-cli/src/account/commands/accountTransferCreate.ts @@ -0,0 +1,106 @@ +import type { ChainId } from '@kadena/client'; +import { createSignWithKeypair } from '@kadena/client'; +import { createPrincipalCommand } from '@kadena/client-utils/built-in'; +import { transferCreate } from '@kadena/client-utils/coin'; +import chalk from 'chalk'; +import type { Command } from 'commander'; +import debug from 'debug'; +import { FAUCET_CONSTANTS } from '../../constants/faucet.js'; +import { createCommand } from '../../utils/createCommand.js'; +import { globalOptions } from '../../utils/globalOptions.js'; + +export const transferCreateCommand: ( + program: Command, + version: string, +) => void = createCommand( + 'transfer-create', + 'Create and fund account', + [ + globalOptions.amount(), + globalOptions.fungible(), + globalOptions.network(), + globalOptions.networkChainId(), + globalOptions.publicKeys(), + globalOptions.predicate(), + ], + async (config) => { + debug('account-transfer-create:action')({ config }); + + try { + const createPrincipal = await createPrincipalCommand( + { + keyset: { + pred: config.predicate as 'keys-all' | 'keys-two' | 'keys-one', + keys: config.publicKeysConfig, + }, + gasPayer: { account: 'dummy', publicKeys: [] }, + chainId: config.chainId as ChainId, + }, + { + host: config.networkConfig.networkHost, + defaults: { + networkId: config.networkConfig.networkId, + meta: { + chainId: config.chainId as ChainId, + }, + }, + }, + ); + + const account = await createPrincipal().execute(); + + const signWithKeyPair = createSignWithKeypair({ + publicKey: FAUCET_CONSTANTS.devnetKp.publicKey, + secretKey: FAUCET_CONSTANTS.devnetKp.secretKey, + }); + + const result = await transferCreate( + { + sender: { + account: FAUCET_CONSTANTS.devnetAcct, + publicKeys: [FAUCET_CONSTANTS.devnetKp.publicKey], + }, + receiver: { + account: account as string, + keyset: { + pred: config.predicate as 'keys-all' | 'keys-two' | 'keys-one', + keys: config.publicKeysConfig, + }, + }, + gasPayer: { + account: FAUCET_CONSTANTS.devnetAcct, + publicKeys: [FAUCET_CONSTANTS.devnetKp.publicKey], + }, + chainId: config.chainId as ChainId, + amount: config.amount, + }, + { + host: config.networkConfig.networkHost, + defaults: { + networkId: config.networkConfig.networkId, + meta: { + chainId: config.chainId as ChainId, + }, + }, + sign: signWithKeyPair, + }, + ).execute(); + + if (result === 'Write succeeded') { + console.log( + chalk.green( + `\nFunded the account "${account}" on chain ${config.chainId} of "${config.network}".\n`, + ), + ); + } else { + console.log( + chalk.red( + `\nFailed to create and fund the account on "${config.network}".\n`, + ), + ); + } + } catch (e) { + console.log(chalk.red(e.message)); + } + }, +); diff --git a/packages/tools/kadena-cli/src/account/index.ts b/packages/tools/kadena-cli/src/account/index.ts index 0df054946e..01b3df3997 100644 --- a/packages/tools/kadena-cli/src/account/index.ts +++ b/packages/tools/kadena-cli/src/account/index.ts @@ -1,16 +1,18 @@ -// import { fundCommand } from './fundCommand.js'; - import type { Command } from 'commander'; -// import { getBalanceCommand } from './getBalanceCommand.js'; -// import { createKeysetCommand } from './commands/accountKeysetCreate.js'; +import { createAccountCommand } from './commands/accountCreate.js'; +import { accountDetailsCommand } from './commands/accountDetails.js'; +import { getBalanceCommand } from './commands/accountGetBalance.js'; +import { transferCreateCommand } from './commands/accountTransferCreate.js'; -// const SUBCOMMAND_ROOT: 'account' = 'account'; +const SUBCOMMAND_ROOT: 'account' = 'account'; export function accountCommandFactory(program: Command, version: string): void { - // const accountProgram = program - // .command(SUBCOMMAND_ROOT) - // .description(`Tool to manage accounts of fungibles (e.g. 'coin')`); - // createKeysetCommand(accountProgram, version); - // fundCommand(accountProgram, version); - // getBalanceCommand(accountProgram, version); + const accountProgram = program + .command(SUBCOMMAND_ROOT) + .description(`Tool to manage accounts of fungibles (e.g. 'coin')`); + + createAccountCommand(accountProgram, version); + accountDetailsCommand(accountProgram, version); + getBalanceCommand(accountProgram, version); + transferCreateCommand(accountProgram, version); } diff --git a/packages/tools/kadena-cli/src/prompts/account.ts b/packages/tools/kadena-cli/src/prompts/account.ts index 8bde3f9277..7c0755f719 100644 --- a/packages/tools/kadena-cli/src/prompts/account.ts +++ b/packages/tools/kadena-cli/src/prompts/account.ts @@ -1,36 +1,36 @@ -// import { select } from '@inquirer/prompts'; -// import { program } from 'commander'; +import { input, select } from '@inquirer/prompts'; +import type { IPrompt } from '../utils/createOption.js'; -// export async function accountSelectPrompt( -// isOptional: boolean = false, -// ): Promise { -// const existingAccounts: ICustomAccountChoice[] = await getExistingAccounts(); +export const publicKeysPrompt: IPrompt = async () => + await input({ + message: 'Enter zero or more public keys (comma separated).', + }); -// let choices: ICustomAccountChoice[] = existingAccounts.map((account) => ({ -// value: account.value, -// name: account.name, -// })); +export const accountNamePrompt: IPrompt = async () => + await input({ + message: 'Enter an account name.', + }); -// if (isOptional) { -// choices.push({ -// value: 'skip', -// name: 'Account is optional. Continue to next step', -// }); -// } +export const amountPrompt: IPrompt = async () => + await input({ + validate(value) { + return !isNaN(parseFloat(value)); + }, + message: 'Enter an amount.', + }); -// choices.push({ value: 'createNewAccount', name: 'Create a new account' }); +export const fungiblePrompt: IPrompt = async () => + await input({ + default: 'coin', + message: 'Enter the name of a fungible.', + }); -// const selectedAccount = await select({ -// message: 'Select an account', -// choices: choices, -// }); - -// if (selectedAccount === 'createNewAccount') { -// await program.parseAsync(['', '', 'account', 'create']); -// return accountSelectPrompt(isOptional); -// } else if (selectedAccount !== 'skip') { -// return selectedAccount; -// } - -// return 'skip'; -// } +export const predicatePrompt: IPrompt = async () => + await select({ + message: 'Select a keyset predicate.', + choices: [ + { value: 'keys-all', name: 'keys-all' }, + { value: 'keys-any', name: 'keys-any' }, + { value: 'keys-2', name: 'keys-2' }, + ], + }); diff --git a/packages/tools/kadena-cli/src/utils/globalOptions.ts b/packages/tools/kadena-cli/src/utils/globalOptions.ts index e847b8089f..122e004acb 100644 --- a/packages/tools/kadena-cli/src/utils/globalOptions.ts +++ b/packages/tools/kadena-cli/src/utils/globalOptions.ts @@ -1,7 +1,7 @@ import { Option, program } from 'commander'; import { z } from 'zod'; import { - // account, + account, // contract, // devnet, generic, @@ -19,6 +19,48 @@ import { createOption } from './createOption.js'; // eslint-disable-next-line @rushstack/typedef-var export const globalOptions = { + // Account + accountName: createOption({ + key: 'accountName' as const, + prompt: account.accountNamePrompt, + validation: z.string(), + option: new Option('-a, --account-name ', 'Account name'), + }), + publicKeys: createOption({ + key: 'publicKeys' as const, + prompt: account.publicKeysPrompt, + validation: z.string(), + option: new Option( + '-p, --public-keys ', + 'Public keys (comma separated)', + ), + expand: async (publicKeys: string) => { + return publicKeys.split(',').map((value) => value.trim()); + }, + }), + amount: createOption({ + key: 'amount' as const, + prompt: account.amountPrompt, + validation: z + .string({ + /* eslint-disable-next-line @typescript-eslint/naming-convention */ + invalid_type_error: 'Error: -c, --chain-id must be a number', + }) + .min(0), + option: new Option('-a, --amount ', 'Amount'), + }), + fungible: createOption({ + key: 'fungible' as const, + prompt: account.fungiblePrompt, + validation: z.string(), + option: new Option('-f, --fungible ', 'Fungible'), + }), + predicate: createOption({ + key: 'predicate' as const, + prompt: account.predicatePrompt, + validation: z.string(), + option: new Option('-p, --predicate ', 'Keyset predicate'), + }), // Network networkName: createOption({ key: 'network' as const, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index a922eb117b..a34fe67abb 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1820,6 +1820,9 @@ importers: '@kadena/client': specifier: workspace:^ version: link:../../libs/client + '@kadena/client-utils': + specifier: workspace:^ + version: link:../../libs/client-utils '@kadena/cryptography-utils': specifier: workspace:* version: link:../../libs/cryptography-utils