-
Notifications
You must be signed in to change notification settings - Fork 27
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
64 changed files
with
4,067 additions
and
298 deletions.
There are no files selected for viewing
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
import { collectResponses, getQuestionKeys, processProject, } from '../utils/helpers.js'; | ||
import { processZodErrors } from '../utils/processZodErrors.js'; | ||
import { FundQuestions, fundQuestions } from './fundQuestions.js'; | ||
import { makeFundRequest } from './makeFundRequest.js'; | ||
import { Option } from 'commander'; | ||
import { ZodError } from 'zod'; | ||
export function fundCommand(program, version) { | ||
program | ||
.command('fund') | ||
.description('fund an account on a devnet or testnet') | ||
.option('-r, --receiver <receiver>', 'Receiver (k:) wallet address') | ||
.addOption(new Option('-c, --chainId <number>', 'Chain to retrieve from (default 1)').argParser((value) => parseInt(value, 10))) | ||
.addOption(new Option('-n, --network <network>', 'Network to retrieve from')) | ||
.option('-nid, --networkId <networkId>', 'Kadena network Id (e.g. "testnet04")') | ||
.option('-p, --project <project>', 'project file to use') | ||
.action(async (args) => { | ||
try { | ||
let projectArgs = {}; | ||
if (args.project !== undefined) { | ||
projectArgs = await processProject(args.project, getQuestionKeys(fundQuestions)); | ||
} | ||
const responses = await collectResponses({ ...projectArgs, ...args }, fundQuestions); | ||
const requestArgs = { | ||
...args, | ||
...responses, | ||
}; | ||
FundQuestions.parse(requestArgs); | ||
await makeFundRequest(requestArgs); | ||
} | ||
catch (e) { | ||
if (e instanceof ZodError) { | ||
processZodErrors(program, e, args); | ||
return; | ||
} | ||
throw e; | ||
} | ||
}); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,58 @@ | ||
import { getExistingNetworks } from '../utils/helpers.js'; | ||
import { input, select } from '@inquirer/prompts'; | ||
import { z } from 'zod'; | ||
// eslint-disable-next-line @rushstack/typedef-var | ||
export const FundQuestions = z.object({ | ||
receiver: z | ||
.string() | ||
.min(60, { message: 'Wallet must be 60 or more characters long' }) | ||
.startsWith('k:', { message: 'Wallet should start with k:' }), | ||
chainId: z | ||
.number({ | ||
/* eslint-disable-next-line @typescript-eslint/naming-convention */ | ||
invalid_type_error: 'Error: -c, --chain must be a number', | ||
}) | ||
.min(0) | ||
.max(19), | ||
network: z.enum(['testnet', 'devnet']), | ||
networkId: z.string({}), | ||
project: z.string({}).optional(), | ||
}); | ||
export const fundQuestions = [ | ||
{ | ||
key: 'receiver', | ||
prompt: async (config, prevAnswers, args) => { | ||
const answer = await select({ | ||
message: 'Which account to use?', | ||
choices: [{ value: undefined, name: `don't select from list` }], | ||
}); | ||
if (answer !== undefined) { | ||
return answer; | ||
} | ||
return await input({ | ||
message: 'Enter the k:receiver wallet address that will receive the funds:', | ||
}); | ||
}, | ||
}, | ||
{ | ||
key: 'network', | ||
prompt: async () => await select({ | ||
message: 'Choose your network', | ||
choices: getExistingNetworks(), | ||
}), | ||
}, | ||
{ | ||
key: 'chainId', | ||
prompt: async (config) => parseInt(await input({ | ||
message: 'Enter chainId (0-19)', | ||
}), 10), | ||
}, | ||
{ | ||
key: 'networkId', | ||
prompt: async (config, previousAnswers) => { | ||
return await input({ | ||
message: `Enter ${previousAnswers.network} network Id (e.g. "${previousAnswers.network}04")`, | ||
}); | ||
}, | ||
}, | ||
]; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
import { fundCommand } from './fundCommand.js'; | ||
const SUBCOMMAND_ROOT = 'account'; | ||
export function accountCommandFactory(program, version) { | ||
const accountProgram = program | ||
.command(SUBCOMMAND_ROOT) | ||
.description(`Tool to manage accounts of fungibles (e.g. 'coin')`); | ||
fundCommand(accountProgram, version); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,86 @@ | ||
import { createSignWithKeypair, isSignedTransaction, Pact, } from '@kadena/client'; | ||
import { FAUCET_CONSTANTS } from '../constants/faucet.js'; | ||
import { accountExists } from '../utils/chainHelpers.js'; | ||
import { pollStatus, submit } from '../utils/client.js'; | ||
import { clearCLI } from '../utils/helpers.js'; | ||
import chalk from 'chalk'; | ||
import { stdout } from 'process'; | ||
async function fundTestNet({ receiver, chainId, networkId, }) { | ||
const { faucetOpKP, faucetAcct, faucetOpAcct } = FAUCET_CONSTANTS; | ||
const amount = { | ||
decimal: '20.0', | ||
}; | ||
if (await accountExists(receiver, chainId.toString(), networkId)) { | ||
console.log('not implemented'); | ||
/* | ||
[{ | ||
"hash": "Y50WGUPcoKArTWlWyjGDB_qGLDDngAl0yB3Oq9CZ0pE", | ||
"sigs": [{ | ||
"sig": "7b648de73e8850b0e047121b7758142fa2b02db969dfc818cdce0a433b45afb2a062a2464e1d785e537ff03f6849f42b8774ea19961e3b6ac7c52f4e45354009" | ||
}, | ||
{ | ||
"sig": "46f2510c8dcedfbc6dc50d1d8efec7c20d3418e9a22cea28fd6f6d1e08774b4903880b5f8a71401e94c9f609f41913f225c35930b3489bc3308a7bfee3997407" | ||
} | ||
], | ||
"cmd": "{\"networkId\":\"testnet04\",\"payload\":{\"exec\":{\"data\":{\"fund-keyset\":{\"pred\":\"keys-all\",\"keys\":[\"cd61b5ca94717bd5f18c08e66b382d37542597190b1df3b63f883126bf4d13c6\"]}},\"code\":\"(user.coin-faucet.create-and-request-coin \\\"k: cd61b5ca94717bd5f18c08e66b382d37542597190b1df3b63f883126bf4d13c6\\\" (read-keyset 'fund-keyset) 20.0)\"}},\"signers\":[{\"clist\":[{\"name\":\"coin.GAS\",\"args\":[]}],\"pubKey\":\"dc28d70fceb519b61b4a797876a3dee07de78cebd6eddc171aef92f9a95d706e\"},{\"clist\":[{\"name\":\"coin.TRANSFER\",\"args\":[\"coin-faucet\",\"k: cd61b5ca94717bd5f18c08e66b382d37542597190b1df3b63f883126bf4d13c6\",20]}],\"pubKey\":\"f3af819e58d2c85a91c5ac0dadfb89e931670f49f384a10e5c33c7c776b7caea\"}],\"meta\":{\"creationTime\":1695132604,\"ttl\":28800,\"gasLimit\":10000,\"chainId\":\"1\",\"gasPrice\":0.00001,\"sender\":\"faucet-operation\"},\"nonce\":\"\\\"2023-09-19T14:10:18.898Z\\\"\"}" | ||
}] | ||
*/ | ||
} | ||
const transaction = Pact.builder | ||
.execution(Pact.modules['user.coin-faucet']['request-coin'](receiver, amount)) | ||
// eslint-disable-next-line @typescript-eslint/no-explicit-any | ||
.addSigner(faucetOpKP.publicKey, (withCap) => [withCap('coin.GAS')]) | ||
// eslint-disable-next-line @typescript-eslint/no-explicit-any | ||
.addSigner(faucetOpKP.publicKey, (withCap) => [ | ||
withCap('coin.TRANSFER', faucetAcct, receiver, amount), | ||
]) | ||
.setMeta({ | ||
senderAccount: faucetOpAcct, | ||
chainId: chainId.toString(), | ||
}) | ||
.setNetworkId(networkId) | ||
.createTransaction(); | ||
const signedTx = await createSignWithKeypair({ | ||
publicKey: faucetOpKP.publicKey, | ||
secretKey: faucetOpKP.secretKey, | ||
})(transaction); | ||
try { | ||
if (isSignedTransaction(signedTx)) { | ||
const transactionDescriptor = await submit(signedTx); | ||
clearCLI(); | ||
console.log(chalk.green(`Submitted transaction - ${transactionDescriptor.requestKey}`)); | ||
stdout.write(chalk.yellow(`Processing transaction ${transactionDescriptor.requestKey}`)); | ||
await pollStatus(transactionDescriptor, { | ||
onPoll() { | ||
stdout.write(chalk.yellow(`.`)); | ||
}, | ||
}); | ||
console.log(chalk.green(`Funding of wallet ${receiver} with txId: ${transactionDescriptor.requestKey} succesful`)); | ||
} | ||
else { | ||
clearCLI(); | ||
console.log(chalk.yellow(`unsigned - ${signedTx}`)); | ||
throw new Error('Failed to sign transaction'); | ||
} | ||
} | ||
catch (e) { | ||
clearCLI(); | ||
console.error(chalk.red(`Failed to fund account: ${e}`)); | ||
throw new Error(`Failed to fund account: ${e}`); | ||
} | ||
} | ||
async function fundDevNet({ receiver }) { | ||
// todo - implement | ||
} | ||
export async function makeFundRequest(args) { | ||
const { network } = args; | ||
switch (network) { | ||
case 'testnet': | ||
return fundTestNet(args); | ||
case 'devnet': | ||
return fundDevNet(args); | ||
default: | ||
throw new Error(`Unsupported network: ${network}`); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,96 @@ | ||
import { configDefaults, projectPrefix, projectRootPath, } from '../constants/config.js'; | ||
import { defaultNetworksPath } from '../constants/networks.js'; | ||
import { PathExists, writeFile } from '../utils/filesystem.js'; | ||
import { mergeConfigs, sanitizeFilename } from '../utils/helpers.js'; | ||
import chalk from 'chalk'; | ||
import { readFileSync } from 'fs'; | ||
import yaml from 'js-yaml'; | ||
import path from 'path'; | ||
/** | ||
* Writes config to a file. | ||
* | ||
* @param {TConfigOptions} options - The set of configuration options. | ||
* @param {string} options.projectName - The name of the project. | ||
* @param {string} options.network - The network (e.g., 'mainnet', 'testnet') or custom network. | ||
* @param {number} options.chainId - The ID representing the chain. | ||
* @returns {void} - No return value; the function writes directly to a file. | ||
*/ | ||
export function writeProjectConfig(options) { | ||
const { projectName } = options; | ||
const projectFilePath = path.join(projectRootPath, `/${projectPrefix}${sanitizeFilename(projectName).toLowerCase()}.yaml`); | ||
const existingConfig = PathExists(projectFilePath) | ||
? yaml.load(readFileSync(projectFilePath, 'utf8')) | ||
: { ...configDefaults }; | ||
const projectConfig = mergeConfigs(existingConfig, options); | ||
writeFile(projectFilePath, yaml.dump(projectConfig), 'utf8'); | ||
} | ||
/** | ||
* Displays the general configuration in a formatted manner. | ||
* | ||
* @param {TConfigOptions} config - The general configuration to display. | ||
*/ | ||
export function displayGeneralConfig(config) { | ||
const log = console.log; | ||
const formatLength = 80; // Maximum width for the display | ||
const displaySeparator = () => { | ||
log(chalk.green('-'.padEnd(formatLength, '-'))); | ||
}; | ||
const formatConfig = (key, value) => { | ||
const valueDisplay = value !== undefined && value.toString().trim() !== '' | ||
? chalk.green(value.toString()) | ||
: chalk.red('Not Set'); | ||
const keyValue = `${key}: ${valueDisplay}`; | ||
const remainingWidth = formatLength - keyValue.length > 0 ? formatLength - keyValue.length : 0; | ||
return ` ${keyValue}${' '.repeat(remainingWidth)} `; | ||
}; | ||
displaySeparator(); | ||
log(formatConfig('Project Name', config.projectName)); | ||
log(formatConfig('Network', config.network)); | ||
log(formatConfig('Chain-ID', config.chainId)); | ||
displaySeparator(); | ||
} | ||
/** | ||
* Loads and returns the current configuration from the default root path. | ||
* | ||
* @returns {IDefaultConfigOptions} - The parsed configuration object. | ||
*/ | ||
export function getProjectConfig(projectName) { | ||
const projectConfigPath = path.join(projectRootPath, `${projectName}.yaml`); | ||
try { | ||
return yaml.load(readFileSync(projectConfigPath, 'utf8')); | ||
} | ||
catch (e) { | ||
throw new Error(`Project config file '${projectName}' not found`); | ||
} | ||
} | ||
/** | ||
* Retrieves the current network configuration for the given project name. | ||
* | ||
* @function | ||
* @export | ||
* @param {string} projectName - The name of the project for which the network configuration is to be retrieved. | ||
* | ||
* @returns {TConfigOptions} The network configuration options for the provided project name. | ||
* | ||
* @throws Will throw an error if the network configuration file is not found or any error occurs during loading the network configuration. | ||
*/ | ||
export function getCurrentNetworkConfigForProject(projectName) { | ||
const projectConfig = getProjectConfig(projectName); | ||
const networkConfigPath = path.join(defaultNetworksPath, `/${projectConfig.network}.yaml`); | ||
try { | ||
return yaml.load(readFileSync(networkConfigPath, 'utf8')); | ||
} | ||
catch (e) { | ||
console.log(chalk.red(`error loading network config: ${e}`)); | ||
throw Error('Network config file not found'); | ||
} | ||
} | ||
function combineConfigs(projectConfig, networkConfig) { | ||
return { ...projectConfig, ...networkConfig }; | ||
} | ||
export function getCombinedConfig(projectName) { | ||
const projectConfig = getProjectConfig(projectName); | ||
const networkConfig = getCurrentNetworkConfigForProject(projectName); | ||
return combineConfigs(projectConfig, networkConfig); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,71 @@ | ||
import { capitalizeFirstLetter, getExistingNetworks, isAlphanumeric, isNumeric, } from '../utils/helpers.js'; | ||
import { input, select } from '@inquirer/prompts'; | ||
import { z } from 'zod'; | ||
// eslint-disable-next-line @rushstack/typedef-var | ||
export const ConfigOptions = z.object({ | ||
projectName: z.string(), | ||
network: z.string(), | ||
chainId: z | ||
.number({ | ||
/* eslint-disable-next-line @typescript-eslint/naming-convention */ | ||
invalid_type_error: 'Error: -c, --chain must be a number', | ||
}) | ||
.min(0) | ||
.max(19), | ||
}); | ||
export async function askForNetwork() { | ||
const existingNetworks = getExistingNetworks(); | ||
existingNetworks | ||
.filter((v, i, a) => a.findIndex((v2) => v2.name === v.name) === i) | ||
.map((network) => { | ||
return { | ||
value: network.value, | ||
name: capitalizeFirstLetter(network.value), | ||
}; | ||
}); | ||
const networkChoice = await select({ | ||
message: 'Select an existing network', | ||
choices: existingNetworks, | ||
}); | ||
return networkChoice.toLowerCase(); | ||
} | ||
export const configQuestions = [ | ||
{ | ||
key: 'projectName', | ||
prompt: async () => await input({ | ||
validate: function (input) { | ||
if (input === '') { | ||
return 'Network name cannot be empty! Please enter something.'; | ||
} | ||
if (!isAlphanumeric(input)) { | ||
return 'Project name must be alphanumeric! Please enter a valid projectname.'; | ||
} | ||
return true; | ||
}, | ||
message: 'Enter your project name', | ||
}), | ||
}, | ||
{ | ||
key: 'network', | ||
prompt: async () => await askForNetwork(), | ||
}, | ||
{ | ||
key: 'chainId', | ||
prompt: async () => { | ||
const chainID = await input({ | ||
default: '0', | ||
validate: function (input) { | ||
if (input === '') { | ||
return 'ChainId cannot be empty! Please enter a number.'; | ||
} | ||
if (!isNumeric(input)) { | ||
return 'ChainId must be numeric! Please enter a valid chain.'; | ||
} | ||
return true; | ||
}, | ||
message: 'Enter chainId (0-19)', | ||
}); | ||
return parseInt(chainID, 10); | ||
}, | ||
}, | ||
]; |
Oops, something went wrong.