genlayer 0.38.8 → 0.38.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.eslintignore +2 -0
- package/.github/workflows/cli-docs.yml +124 -0
- package/.github/workflows/publish.yml +55 -0
- package/.github/workflows/smoke.yml +27 -0
- package/.github/workflows/validate-code.yml +51 -0
- package/.prettierignore +19 -0
- package/.prettierrc +12 -0
- package/.release-it.json +66 -0
- package/CHANGELOG.md +545 -0
- package/CLAUDE.md +55 -0
- package/CONTRIBUTING.md +117 -0
- package/dist/index.js +221 -62
- package/docs/api-references/_meta.json +9 -0
- package/docs/api-references/accounts/_meta.json +3 -0
- package/docs/api-references/accounts/account/create.mdx +19 -0
- package/docs/api-references/accounts/account/export.mdx +19 -0
- package/docs/api-references/accounts/account/import.mdx +22 -0
- package/docs/api-references/accounts/account/list.mdx +15 -0
- package/docs/api-references/accounts/account/lock.mdx +16 -0
- package/docs/api-references/accounts/account/remove.mdx +20 -0
- package/docs/api-references/accounts/account/send.mdx +24 -0
- package/docs/api-references/accounts/account/show.mdx +17 -0
- package/docs/api-references/accounts/account/unlock.mdx +17 -0
- package/docs/api-references/accounts/account/use.mdx +19 -0
- package/docs/api-references/accounts/account.mdx +32 -0
- package/docs/api-references/configuration/_meta.json +4 -0
- package/docs/api-references/configuration/config/get.mdx +21 -0
- package/docs/api-references/configuration/config/reset.mdx +21 -0
- package/docs/api-references/configuration/config/set.mdx +21 -0
- package/docs/api-references/configuration/config.mdx +25 -0
- package/docs/api-references/configuration/network/info.mdx +15 -0
- package/docs/api-references/configuration/network/list.mdx +15 -0
- package/docs/api-references/configuration/network/set.mdx +21 -0
- package/docs/api-references/configuration/network.mdx +25 -0
- package/docs/api-references/contracts/_meta.json +7 -0
- package/docs/api-references/contracts/call.mdx +21 -0
- package/docs/api-references/contracts/code.mdx +20 -0
- package/docs/api-references/contracts/deploy.mdx +17 -0
- package/docs/api-references/contracts/schema.mdx +20 -0
- package/docs/api-references/contracts/write.mdx +21 -0
- package/docs/api-references/environment/_meta.json +7 -0
- package/docs/api-references/environment/init.mdx +20 -0
- package/docs/api-references/environment/new.mdx +21 -0
- package/docs/api-references/environment/stop.mdx +15 -0
- package/docs/api-references/environment/up.mdx +20 -0
- package/docs/api-references/environment/update/ollama.mdx +16 -0
- package/docs/api-references/environment/update.mdx +23 -0
- package/docs/api-references/index.mdx +35 -0
- package/docs/api-references/localnet/_meta.json +3 -0
- package/docs/api-references/localnet/localnet/validators/count.mdx +15 -0
- package/docs/api-references/localnet/localnet/validators/create-random.mdx +16 -0
- package/docs/api-references/localnet/localnet/validators/create.mdx +19 -0
- package/docs/api-references/localnet/localnet/validators/delete.mdx +16 -0
- package/docs/api-references/localnet/localnet/validators/get.mdx +16 -0
- package/docs/api-references/localnet/localnet/validators/update.mdx +23 -0
- package/docs/api-references/localnet/localnet/validators.mdx +28 -0
- package/docs/api-references/localnet/localnet.mdx +23 -0
- package/docs/api-references/staking/_meta.json +3 -0
- package/docs/api-references/staking/staking/active-validators.mdx +18 -0
- package/docs/api-references/staking/staking/banned-validators.mdx +18 -0
- package/docs/api-references/staking/staking/delegation-info.mdx +25 -0
- package/docs/api-references/staking/staking/delegator-claim.mdx +26 -0
- package/docs/api-references/staking/staking/delegator-exit.mdx +26 -0
- package/docs/api-references/staking/staking/delegator-join.mdx +26 -0
- package/docs/api-references/staking/staking/epoch-info.mdx +19 -0
- package/docs/api-references/staking/staking/prime-all.mdx +20 -0
- package/docs/api-references/staking/staking/quarantined-validators.mdx +18 -0
- package/docs/api-references/staking/staking/set-identity.mdx +33 -0
- package/docs/api-references/staking/staking/set-operator.mdx +26 -0
- package/docs/api-references/staking/staking/validator-claim.mdx +24 -0
- package/docs/api-references/staking/staking/validator-deposit.mdx +25 -0
- package/docs/api-references/staking/staking/validator-exit.mdx +25 -0
- package/docs/api-references/staking/staking/validator-history.mdx +29 -0
- package/docs/api-references/staking/staking/validator-info.mdx +25 -0
- package/docs/api-references/staking/staking/validator-join.mdx +22 -0
- package/docs/api-references/staking/staking/validator-prime.mdx +25 -0
- package/docs/api-references/staking/staking/validators.mdx +19 -0
- package/docs/api-references/staking/staking/wizard.mdx +20 -0
- package/docs/api-references/staking/staking.mdx +42 -0
- package/docs/api-references/transactions/_meta.json +6 -0
- package/docs/api-references/transactions/appeal-bond.mdx +20 -0
- package/docs/api-references/transactions/appeal.mdx +21 -0
- package/docs/api-references/transactions/receipt.mdx +25 -0
- package/docs/api-references/transactions/trace.mdx +21 -0
- package/docs/delegator-guide.md +203 -0
- package/docs/validator-guide.md +329 -0
- package/esbuild.config.dev.js +17 -0
- package/esbuild.config.js +22 -0
- package/esbuild.config.prod.js +17 -0
- package/eslint.config.js +60 -0
- package/package.json +2 -11
- package/renovate.json +22 -0
- package/scripts/generate-cli-docs.mjs +68 -5
- package/src/commands/account/create.ts +30 -0
- package/src/commands/account/export.ts +106 -0
- package/src/commands/account/import.ts +135 -0
- package/src/commands/account/index.ts +129 -0
- package/src/commands/account/list.ts +34 -0
- package/src/commands/account/lock.ts +39 -0
- package/src/commands/account/remove.ts +30 -0
- package/src/commands/account/send.ts +162 -0
- package/src/commands/account/show.ts +74 -0
- package/src/commands/account/unlock.ts +56 -0
- package/src/commands/account/use.ts +21 -0
- package/src/commands/config/getSetReset.ts +51 -0
- package/src/commands/config/index.ts +30 -0
- package/src/commands/contracts/call.ts +39 -0
- package/src/commands/contracts/code.ts +33 -0
- package/src/commands/contracts/deploy.ts +161 -0
- package/src/commands/contracts/index.ts +150 -0
- package/src/commands/contracts/schema.ts +31 -0
- package/src/commands/contracts/write.ts +49 -0
- package/src/commands/general/index.ts +45 -0
- package/src/commands/general/init.ts +180 -0
- package/src/commands/general/start.ts +128 -0
- package/src/commands/general/stop.ts +26 -0
- package/src/commands/localnet/index.ts +100 -0
- package/src/commands/localnet/validators.ts +269 -0
- package/src/commands/network/index.ts +29 -0
- package/src/commands/network/setNetwork.ts +77 -0
- package/src/commands/scaffold/index.ts +16 -0
- package/src/commands/scaffold/new.ts +34 -0
- package/src/commands/staking/StakingAction.ts +279 -0
- package/src/commands/staking/delegatorClaim.ts +41 -0
- package/src/commands/staking/delegatorExit.ts +56 -0
- package/src/commands/staking/delegatorJoin.ts +44 -0
- package/src/commands/staking/index.ts +357 -0
- package/src/commands/staking/setIdentity.ts +78 -0
- package/src/commands/staking/setOperator.ts +46 -0
- package/src/commands/staking/stakingInfo.ts +584 -0
- package/src/commands/staking/validatorClaim.ts +43 -0
- package/src/commands/staking/validatorDeposit.ts +48 -0
- package/src/commands/staking/validatorExit.ts +63 -0
- package/src/commands/staking/validatorHistory.ts +300 -0
- package/src/commands/staking/validatorJoin.ts +47 -0
- package/src/commands/staking/validatorPrime.ts +73 -0
- package/src/commands/staking/wizard.ts +809 -0
- package/src/commands/transactions/appeal.ts +83 -0
- package/src/commands/transactions/index.ts +60 -0
- package/src/commands/transactions/receipt.ts +90 -0
- package/src/commands/transactions/trace.ts +42 -0
- package/src/commands/update/index.ts +25 -0
- package/src/commands/update/ollama.ts +103 -0
- package/src/lib/actions/BaseAction.ts +301 -0
- package/src/lib/clients/jsonRpcClient.ts +41 -0
- package/src/lib/clients/system.ts +73 -0
- package/src/lib/config/ConfigFileManager.ts +194 -0
- package/src/lib/config/KeychainManager.ts +89 -0
- package/src/lib/config/simulator.ts +68 -0
- package/src/lib/config/text.ts +2 -0
- package/src/lib/errors/missingRequirement.ts +9 -0
- package/src/lib/errors/versionRequired.ts +9 -0
- package/src/lib/interfaces/ISimulatorService.ts +39 -0
- package/src/lib/services/simulator.ts +386 -0
- package/src/types/node-fetch.d.ts +1 -0
- package/tests/actions/appeal.test.ts +141 -0
- package/tests/actions/call.test.ts +94 -0
- package/tests/actions/code.test.ts +87 -0
- package/tests/actions/create.test.ts +65 -0
- package/tests/actions/deploy.test.ts +420 -0
- package/tests/actions/getSetReset.test.ts +88 -0
- package/tests/actions/init.test.ts +483 -0
- package/tests/actions/lock.test.ts +86 -0
- package/tests/actions/new.test.ts +80 -0
- package/tests/actions/ollama.test.ts +193 -0
- package/tests/actions/receipt.test.ts +261 -0
- package/tests/actions/schema.test.ts +94 -0
- package/tests/actions/setNetwork.test.ts +161 -0
- package/tests/actions/staking.test.ts +280 -0
- package/tests/actions/start.test.ts +257 -0
- package/tests/actions/stop.test.ts +77 -0
- package/tests/actions/unlock.test.ts +139 -0
- package/tests/actions/validators.test.ts +750 -0
- package/tests/actions/write.test.ts +102 -0
- package/tests/commands/account.test.ts +146 -0
- package/tests/commands/appeal.test.ts +97 -0
- package/tests/commands/call.test.ts +78 -0
- package/tests/commands/code.test.ts +69 -0
- package/tests/commands/config.test.ts +54 -0
- package/tests/commands/deploy.test.ts +83 -0
- package/tests/commands/init.test.ts +101 -0
- package/tests/commands/localnet.test.ts +131 -0
- package/tests/commands/network.test.ts +60 -0
- package/tests/commands/new.test.ts +68 -0
- package/tests/commands/parseArg.test.ts +156 -0
- package/tests/commands/receipt.test.ts +142 -0
- package/tests/commands/schema.test.ts +67 -0
- package/tests/commands/staking.test.ts +329 -0
- package/tests/commands/stop.test.ts +27 -0
- package/tests/commands/up.test.ts +105 -0
- package/tests/commands/update.test.ts +45 -0
- package/tests/commands/write.test.ts +76 -0
- package/tests/index.test.ts +56 -0
- package/tests/libs/baseAction.test.ts +535 -0
- package/tests/libs/configFileManager.test.ts +118 -0
- package/tests/libs/jsonRpcClient.test.ts +59 -0
- package/tests/libs/keychainManager.test.ts +156 -0
- package/tests/libs/platformCommands.test.ts +78 -0
- package/tests/libs/system.test.ts +148 -0
- package/tests/services/simulator.test.ts +789 -0
- package/tests/smoke.test.ts +134 -0
- package/tests/utils.ts +13 -0
- package/tsconfig.json +120 -0
- package/vitest.config.ts +13 -0
- package/vitest.smoke.config.ts +17 -0
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
export const localnetCompatibleVersion = "v0.65.0";
|
|
2
|
+
export const DEFAULT_JSON_RPC_URL = "http://localhost:4000/api";
|
|
3
|
+
export const CONTAINERS_NAME_PREFIX = "/genlayer-";
|
|
4
|
+
export const IMAGES_NAME_PREFIX = "yeagerai";
|
|
5
|
+
export const DEFAULT_RUN_SIMULATOR_COMMAND = (location: string, profiles: string) => ({
|
|
6
|
+
darwin: `osascript -e 'tell application "Terminal" to do script "cd \\"${location}\\" && docker compose build && docker compose -p genlayer ${profiles} up"'`,
|
|
7
|
+
win32: `cd /d "${location}" && docker compose build && docker compose -p genlayer ${profiles} up -d`,
|
|
8
|
+
linux: `nohup bash -c 'cd "${location}" && docker compose build && docker compose -p genlayer ${profiles} up -d'`,
|
|
9
|
+
});
|
|
10
|
+
export const DEFAULT_RUN_DOCKER_COMMAND = {
|
|
11
|
+
darwin: "open -a Docker",
|
|
12
|
+
win32: 'start "" "C:\\Program Files\\Docker\\Docker\\Docker Desktop.exe"',
|
|
13
|
+
linux: "sudo systemctl start docker",
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export const VERSION_REQUIREMENTS = {
|
|
17
|
+
docker: "25.0.0",
|
|
18
|
+
node: "18.0.0",
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
export const AVAILABLE_PLATFORMS = ["darwin", "win32", "linux"] as const;
|
|
22
|
+
export type RunningPlatform = (typeof AVAILABLE_PLATFORMS)[number];
|
|
23
|
+
export const STARTING_TIMEOUT_WAIT_CYLCE = 2000;
|
|
24
|
+
export const STARTING_TIMEOUT_ATTEMPTS = 120;
|
|
25
|
+
|
|
26
|
+
export type AiProviders = "ollama" | "openai" | "heuristai" | "geminiai" | "xai";
|
|
27
|
+
export type AiProvidersEnvVars = "ollama" | "OPENAIKEY" | "HEURISTAIAPIKEY" | "GEMINI_API_KEY" | "XAI_API_KEY";
|
|
28
|
+
export type AiProvidersConfigType = {
|
|
29
|
+
[key in AiProviders]: {name: string; hint: string; envVar?: AiProvidersEnvVars; cliOptionValue: string};
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
export const AI_PROVIDERS_CONFIG: AiProvidersConfigType = {
|
|
33
|
+
ollama: {
|
|
34
|
+
name: "Ollama",
|
|
35
|
+
hint: "(By default, this will download and run a local instance of Llama 3)",
|
|
36
|
+
cliOptionValue: "ollama",
|
|
37
|
+
},
|
|
38
|
+
openai: {
|
|
39
|
+
name: "OpenAI",
|
|
40
|
+
hint: "(You will need to provide an OpenAI API key)",
|
|
41
|
+
envVar: "OPENAIKEY",
|
|
42
|
+
cliOptionValue: "openai",
|
|
43
|
+
},
|
|
44
|
+
heuristai: {
|
|
45
|
+
name: "Heurist",
|
|
46
|
+
hint: '(You will need to provide an API key. Get free API credits at https://dev-api-form.heurist.ai/ with referral code: "genlayer")',
|
|
47
|
+
envVar: "HEURISTAIAPIKEY",
|
|
48
|
+
cliOptionValue: "heuristai",
|
|
49
|
+
},
|
|
50
|
+
geminiai: {
|
|
51
|
+
name: "Gemini",
|
|
52
|
+
hint: '(You will need to provide an API key.)',
|
|
53
|
+
envVar: "GEMINI_API_KEY",
|
|
54
|
+
cliOptionValue: "geminiai",
|
|
55
|
+
},
|
|
56
|
+
xai: {
|
|
57
|
+
name: "XAI",
|
|
58
|
+
hint: '(You will need to provide an API key)',
|
|
59
|
+
envVar: "XAI_API_KEY",
|
|
60
|
+
cliOptionValue: "xai",
|
|
61
|
+
},
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
export const GENLAYER_REQUIRED_CONTAINERS = [
|
|
65
|
+
"genlayer-jsonrpc",
|
|
66
|
+
"genlayer-webrequest",
|
|
67
|
+
"genlayer-postgres"
|
|
68
|
+
]
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
export const CLI_DESCRIPTION =
|
|
2
|
+
"GenLayer CLI is a development environment for the GenLayer ecosystem. It allows developers to interact with the protocol by creating accounts, sending transactions, and working with Intelligent Contracts by testing, debugging, and deploying them.";
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export class MissingRequirementError extends Error {
|
|
2
|
+
requirement: string;
|
|
3
|
+
|
|
4
|
+
constructor(requirement: string) {
|
|
5
|
+
super(`${requirement} is not installed. Please install ${requirement}.`);
|
|
6
|
+
this.name = "MissingRequirement";
|
|
7
|
+
this.requirement = requirement;
|
|
8
|
+
}
|
|
9
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export class VersionRequiredError extends Error {
|
|
2
|
+
tool: string;
|
|
3
|
+
|
|
4
|
+
constructor(tool: string, requiredVersion: string) {
|
|
5
|
+
super(`${tool} version ${requiredVersion} or higher is required. Please update ${tool}.`);
|
|
6
|
+
this.name = "VersionRequired";
|
|
7
|
+
this.tool = tool;
|
|
8
|
+
}
|
|
9
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import {AiProviders} from "../config/simulator";
|
|
2
|
+
|
|
3
|
+
export interface ISimulatorService {
|
|
4
|
+
setComposeOptions(headless: boolean, disableOllama?: boolean): void;
|
|
5
|
+
getComposeOptions(): string;
|
|
6
|
+
checkInstallRequirements(): Promise<Record<string, boolean>>;
|
|
7
|
+
checkVersionRequirements(): Promise<Record<string, string>>;
|
|
8
|
+
runSimulator(): Promise<{stdout: string; stderr: string}>;
|
|
9
|
+
waitForSimulatorToBeReady(retries?: number): Promise<WaitForSimulatorToBeReadyResultType>;
|
|
10
|
+
createRandomValidators(numValidators: number, llmProviders: AiProviders[]): Promise<any>;
|
|
11
|
+
deleteAllValidators(): Promise<any>;
|
|
12
|
+
getAiProvidersOptions(withHint: boolean, excludeProviders?: AiProviders[]): Array<{name: string; value: string}>;
|
|
13
|
+
getFrontendUrl(): string;
|
|
14
|
+
openFrontend(): Promise<boolean>;
|
|
15
|
+
stopDockerContainers(): Promise<void>;
|
|
16
|
+
resetDockerContainers(): Promise<void>;
|
|
17
|
+
resetDockerImages(): Promise<void>;
|
|
18
|
+
resetDockerVolumes(): Promise<void>;
|
|
19
|
+
checkCliVersion(): Promise<void>;
|
|
20
|
+
cleanDatabase(): Promise<boolean>;
|
|
21
|
+
addConfigToEnvFile(newConfig: Record<string, string>): void;
|
|
22
|
+
normalizeLocalnetVersion(version: string): string;
|
|
23
|
+
compareVersions(version1: string, version2: string): number;
|
|
24
|
+
ensureDockerRunning(): Promise<void>;
|
|
25
|
+
isLocalnetRunning(): Promise<boolean>;
|
|
26
|
+
setupLocalhostAccess(): void;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
export type WaitForSimulatorToBeReadyResultType = {
|
|
31
|
+
initialized: boolean;
|
|
32
|
+
errorCode?: "TIMEOUT" | "ERROR";
|
|
33
|
+
errorMessage?: string;
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
export type InitializeDatabaseResultType = {
|
|
37
|
+
createResponse: any;
|
|
38
|
+
tablesResponse: any;
|
|
39
|
+
};
|
|
@@ -0,0 +1,386 @@
|
|
|
1
|
+
import Docker, {ContainerInfo} from "dockerode";
|
|
2
|
+
import * as fs from "fs";
|
|
3
|
+
import * as dotenv from "dotenv";
|
|
4
|
+
import * as path from "path";
|
|
5
|
+
import * as semver from "semver";
|
|
6
|
+
import updateCheck from "update-check";
|
|
7
|
+
import { fileURLToPath } from "url";
|
|
8
|
+
import pkg from '../../../package.json'
|
|
9
|
+
|
|
10
|
+
import {rpcClient} from "../clients/jsonRpcClient";
|
|
11
|
+
import {
|
|
12
|
+
DEFAULT_RUN_SIMULATOR_COMMAND,
|
|
13
|
+
DEFAULT_RUN_DOCKER_COMMAND,
|
|
14
|
+
STARTING_TIMEOUT_WAIT_CYLCE,
|
|
15
|
+
STARTING_TIMEOUT_ATTEMPTS,
|
|
16
|
+
AI_PROVIDERS_CONFIG,
|
|
17
|
+
AiProviders,
|
|
18
|
+
VERSION_REQUIREMENTS,
|
|
19
|
+
CONTAINERS_NAME_PREFIX,
|
|
20
|
+
IMAGES_NAME_PREFIX,
|
|
21
|
+
GENLAYER_REQUIRED_CONTAINERS
|
|
22
|
+
} from "../config/simulator";
|
|
23
|
+
import {
|
|
24
|
+
checkCommand,
|
|
25
|
+
getVersion,
|
|
26
|
+
executeCommand,
|
|
27
|
+
openUrl,
|
|
28
|
+
} from "../clients/system";
|
|
29
|
+
import {MissingRequirementError} from "../errors/missingRequirement";
|
|
30
|
+
|
|
31
|
+
import {
|
|
32
|
+
ISimulatorService,
|
|
33
|
+
WaitForSimulatorToBeReadyResultType,
|
|
34
|
+
} from "../interfaces/ISimulatorService";
|
|
35
|
+
import {VersionRequiredError} from "../errors/versionRequired";
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
function sleep(millliseconds: number): Promise<void> {
|
|
39
|
+
return new Promise(resolve => setTimeout(resolve, millliseconds));
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export class SimulatorService implements ISimulatorService {
|
|
43
|
+
private profileOptions: string
|
|
44
|
+
private docker: Docker;
|
|
45
|
+
public location: string;
|
|
46
|
+
|
|
47
|
+
constructor() {
|
|
48
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
49
|
+
this.location = path.resolve(path.dirname(__filename), '..');
|
|
50
|
+
this.profileOptions = "";
|
|
51
|
+
this.docker = new Docker();
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
private readEnvConfigValue(key: string): string {
|
|
55
|
+
const envFilePath = path.join(this.location, ".env");
|
|
56
|
+
// Transform the config string to object
|
|
57
|
+
const envConfig = dotenv.parse(fs.readFileSync(envFilePath, "utf8"));
|
|
58
|
+
return envConfig[key];
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
private async getGenlayerContainers(): Promise<ContainerInfo[]> {
|
|
62
|
+
const containers = await this.docker.listContainers({ all: true });
|
|
63
|
+
return containers.filter(container =>
|
|
64
|
+
container.Names.some(name =>
|
|
65
|
+
name.startsWith(CONTAINERS_NAME_PREFIX)
|
|
66
|
+
)
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
private async stopAndRemoveContainers(remove: boolean = false): Promise<void> {
|
|
71
|
+
const genlayerContainers = await this.getGenlayerContainers();
|
|
72
|
+
|
|
73
|
+
for (const containerInfo of genlayerContainers) {
|
|
74
|
+
const container = this.docker.getContainer(containerInfo.Id);
|
|
75
|
+
if (containerInfo.State === "running") {
|
|
76
|
+
await container.stop();
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const isOllamaContainer = containerInfo.Names.some(name => name.includes("ollama"));
|
|
80
|
+
|
|
81
|
+
if (remove && !isOllamaContainer) {
|
|
82
|
+
await container.remove();
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
public addConfigToEnvFile(newConfig: Record<string, string>): void {
|
|
88
|
+
const envFilePath = path.join(this.location, ".env");
|
|
89
|
+
|
|
90
|
+
// Transform the config string to object
|
|
91
|
+
const envConfig = dotenv.parse(fs.readFileSync(envFilePath, "utf8"));
|
|
92
|
+
Object.keys(newConfig).forEach(key => {
|
|
93
|
+
envConfig[key] = newConfig[key];
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
// Transform the updated config object back into a string
|
|
97
|
+
const updatedConfig = Object.keys(envConfig)
|
|
98
|
+
.map(key => {
|
|
99
|
+
return `${key}=${envConfig[key]}`;
|
|
100
|
+
})
|
|
101
|
+
.join("\n");
|
|
102
|
+
|
|
103
|
+
// Write the new .env file
|
|
104
|
+
fs.writeFileSync(envFilePath, updatedConfig);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
public setComposeOptions(headless: boolean, ollama: boolean = false): void {
|
|
108
|
+
let profiles = [];
|
|
109
|
+
|
|
110
|
+
if (!headless) {
|
|
111
|
+
profiles.push("frontend");
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
if (ollama) {
|
|
115
|
+
profiles.push("ollama");
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
this.profileOptions = profiles.length > 0 ? `--profile ${profiles.join(" --profile ")}` : "";
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
public getComposeOptions(): string {
|
|
122
|
+
return this.profileOptions;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
public async checkCliVersion(): Promise<void> {
|
|
126
|
+
const update = await updateCheck(pkg);
|
|
127
|
+
if (update && update.latest !== pkg.version) {
|
|
128
|
+
console.warn(`\nA new version (${update.latest}) is available! You're using version ${pkg.version}.\nRun npm install -g genlayer to update\n`);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
public async ensureDockerRunning(): Promise<void> {
|
|
133
|
+
await this.docker.ping();
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
public async checkInstallRequirements(): Promise<Record<string, boolean>> {
|
|
137
|
+
const requirementsInstalled = {
|
|
138
|
+
docker: false,
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
try {
|
|
142
|
+
await checkCommand("docker --version", "docker");
|
|
143
|
+
requirementsInstalled.docker = true;
|
|
144
|
+
} catch (error: any) {
|
|
145
|
+
if (!(error instanceof MissingRequirementError)) {
|
|
146
|
+
throw error;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
if (requirementsInstalled.docker) {
|
|
151
|
+
try {
|
|
152
|
+
await this.docker.ping()
|
|
153
|
+
} catch (error: any) {
|
|
154
|
+
await executeCommand(DEFAULT_RUN_DOCKER_COMMAND);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
return requirementsInstalled;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
public async checkVersionRequirements(): Promise<Record<string, string>> {
|
|
162
|
+
const missingVersions = {
|
|
163
|
+
docker: "",
|
|
164
|
+
node: "",
|
|
165
|
+
};
|
|
166
|
+
|
|
167
|
+
try {
|
|
168
|
+
await this.checkVersion(VERSION_REQUIREMENTS.node, "node");
|
|
169
|
+
} catch (error: any) {
|
|
170
|
+
missingVersions.node = VERSION_REQUIREMENTS.node;
|
|
171
|
+
if (!(error instanceof VersionRequiredError)) {
|
|
172
|
+
throw error;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
try {
|
|
177
|
+
await this.checkVersion(VERSION_REQUIREMENTS.docker, "docker");
|
|
178
|
+
} catch (error: any) {
|
|
179
|
+
missingVersions.docker = VERSION_REQUIREMENTS.docker;
|
|
180
|
+
if (!(error instanceof VersionRequiredError)) {
|
|
181
|
+
throw error;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
return missingVersions;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
public async checkVersion(minVersion: string, toolName: string): Promise<void> {
|
|
189
|
+
const version = await getVersion(toolName);
|
|
190
|
+
|
|
191
|
+
if (!semver.satisfies(version, `>=${minVersion}`)) {
|
|
192
|
+
throw new VersionRequiredError(toolName, minVersion);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
public runSimulator(): Promise<{stdout: string; stderr: string}> {
|
|
197
|
+
const commandsByPlatform = DEFAULT_RUN_SIMULATOR_COMMAND(this.location, this.getComposeOptions());
|
|
198
|
+
return executeCommand(commandsByPlatform);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
public async waitForSimulatorToBeReady(
|
|
202
|
+
retries: number = STARTING_TIMEOUT_ATTEMPTS,
|
|
203
|
+
): Promise<WaitForSimulatorToBeReadyResultType> {
|
|
204
|
+
try {
|
|
205
|
+
const response = await rpcClient.request({method: "ping", params: []});
|
|
206
|
+
|
|
207
|
+
//Compatibility with current simulator version
|
|
208
|
+
if (response?.result === "OK" || response?.result?.status === "OK" || response?.result?.data?.status === "OK") {
|
|
209
|
+
return { initialized: true };
|
|
210
|
+
}
|
|
211
|
+
if (retries > 0) {
|
|
212
|
+
await sleep(STARTING_TIMEOUT_WAIT_CYLCE);
|
|
213
|
+
return this.waitForSimulatorToBeReady(retries - 1);
|
|
214
|
+
}
|
|
215
|
+
} catch (error: any) {
|
|
216
|
+
if (
|
|
217
|
+
(error.name === "FetchError" ||
|
|
218
|
+
error.message.includes("Fetch Error") ||
|
|
219
|
+
error.message.includes("ECONNRESET") ||
|
|
220
|
+
error.message.includes("ECONNREFUSED") ||
|
|
221
|
+
error.message.includes("socket hang up")) &&
|
|
222
|
+
retries > 0
|
|
223
|
+
) {
|
|
224
|
+
await sleep(STARTING_TIMEOUT_WAIT_CYLCE * 2);
|
|
225
|
+
return this.waitForSimulatorToBeReady(retries - 1);
|
|
226
|
+
}
|
|
227
|
+
return {initialized: false, errorCode: "ERROR", errorMessage: error.message};
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
return {initialized: false, errorCode: "TIMEOUT"};
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
public createRandomValidators(numValidators: number, llmProviders: AiProviders[]): Promise<any> {
|
|
234
|
+
return rpcClient.request({
|
|
235
|
+
method: "sim_createRandomValidators",
|
|
236
|
+
params: [numValidators, 1, 10, llmProviders],
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
public deleteAllValidators(): Promise<any> {
|
|
241
|
+
return rpcClient.request({method: "sim_deleteAllValidators", params: []});
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
public getAiProvidersOptions(withHint: boolean = true, excludeProviders: AiProviders[] = []): Array<{name: string; value: string}> {
|
|
245
|
+
return Object.values(AI_PROVIDERS_CONFIG)
|
|
246
|
+
.filter(providerConfig => !excludeProviders.includes(providerConfig.cliOptionValue as AiProviders))
|
|
247
|
+
.map(providerConfig => {
|
|
248
|
+
return {
|
|
249
|
+
name: `${providerConfig.name}${withHint ? ` ${providerConfig.hint}` : ""}`,
|
|
250
|
+
value: providerConfig.cliOptionValue,
|
|
251
|
+
};
|
|
252
|
+
});
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
public getFrontendUrl(): string {
|
|
256
|
+
const frontendPort = this.readEnvConfigValue("FRONTEND_PORT");
|
|
257
|
+
return `http://localhost:${frontendPort}`;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
public async openFrontend(): Promise<boolean> {
|
|
261
|
+
await openUrl(this.getFrontendUrl());
|
|
262
|
+
return true;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
public async stopDockerContainers(): Promise<void> {
|
|
266
|
+
await this.stopAndRemoveContainers(false);
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
public async resetDockerContainers(): Promise<void> {
|
|
270
|
+
await this.stopAndRemoveContainers(true);
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
public async resetDockerImages(): Promise<void> {
|
|
274
|
+
const images = await this.docker.listImages();
|
|
275
|
+
const genlayerImages = images.filter(image =>
|
|
276
|
+
image.RepoTags?.some(tag => tag.startsWith(IMAGES_NAME_PREFIX))
|
|
277
|
+
);
|
|
278
|
+
|
|
279
|
+
for (const imageInfo of genlayerImages) {
|
|
280
|
+
const image = this.docker.getImage(imageInfo.Id);
|
|
281
|
+
await image.remove({force: true});
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
public async resetDockerVolumes(): Promise<void> {
|
|
286
|
+
const volumes = await this.docker.listVolumes();
|
|
287
|
+
const genlayerVolumes = volumes.Volumes?.filter(volume =>
|
|
288
|
+
volume.Name.startsWith('genlayer_')
|
|
289
|
+
) || [];
|
|
290
|
+
|
|
291
|
+
for (const volumeInfo of genlayerVolumes) {
|
|
292
|
+
const volume = this.docker.getVolume(volumeInfo.Name);
|
|
293
|
+
await volume.remove({force: true});
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
public async cleanDatabase(): Promise<boolean> {
|
|
298
|
+
|
|
299
|
+
try {
|
|
300
|
+
await rpcClient.request({method: "sim_clearDbTables", params: [['current_state', 'transactions']]});
|
|
301
|
+
}catch (error) {
|
|
302
|
+
console.error(error);
|
|
303
|
+
}
|
|
304
|
+
return true;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
public normalizeLocalnetVersion(version: string) {
|
|
308
|
+
|
|
309
|
+
if (!version.startsWith('v')) {
|
|
310
|
+
version = 'v' + version;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
const versionRegex = /^v(\d+)\.(\d+)\.(\d+)(-.+)?$/;
|
|
314
|
+
const match = version.match(versionRegex);
|
|
315
|
+
|
|
316
|
+
if (!match) {
|
|
317
|
+
console.error('Invalid version format. Expected format: v0.0.0 or v0.0.0-suffix');
|
|
318
|
+
process.exit(1);
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
return version
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
public compareVersions(version1: string, version2: string): number {
|
|
325
|
+
const v1 = version1.replace(/^v/, '');
|
|
326
|
+
const v2 = version2.replace(/^v/, '');
|
|
327
|
+
|
|
328
|
+
const parts1 = v1.split('-')[0].split('.').map(Number);
|
|
329
|
+
const parts2 = v2.split('-')[0].split('.').map(Number);
|
|
330
|
+
|
|
331
|
+
for (let i = 0; i < Math.max(parts1.length, parts2.length); i++) {
|
|
332
|
+
const part1 = parts1[i] || 0;
|
|
333
|
+
const part2 = parts2[i] || 0;
|
|
334
|
+
|
|
335
|
+
if (part1 < part2) return -1;
|
|
336
|
+
if (part1 > part2) return 1;
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
return 0;
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
public setupLocalhostAccess(): void {
|
|
343
|
+
const overridePath = path.join(this.location, 'docker-compose.override.yml');
|
|
344
|
+
const overrideContent = `# Auto-generated by genlayer CLI — enables localhost access for local development
|
|
345
|
+
services:
|
|
346
|
+
jsonrpc:
|
|
347
|
+
volumes:
|
|
348
|
+
- ./config-overrides/genvm-module-web.yaml:/genvm/config/genvm-module-web.yaml:ro
|
|
349
|
+
extra_hosts:
|
|
350
|
+
- "host.docker.internal:host-gateway"
|
|
351
|
+
- "anvil-local:host-gateway"
|
|
352
|
+
`;
|
|
353
|
+
fs.writeFileSync(overridePath, overrideContent);
|
|
354
|
+
|
|
355
|
+
const configDir = path.join(this.location, 'config-overrides');
|
|
356
|
+
if (!fs.existsSync(configDir)) {
|
|
357
|
+
fs.mkdirSync(configDir, { recursive: true });
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
const genvmConfigPath = path.join(configDir, 'genvm-module-web.yaml');
|
|
361
|
+
const genvmConfigContent = `# Auto-generated by genlayer CLI — allows GenVM to reach localhost services
|
|
362
|
+
always_allow_hosts:
|
|
363
|
+
- "localhost"
|
|
364
|
+
- "127.0.0.1"
|
|
365
|
+
- "host.docker.internal"
|
|
366
|
+
- "anvil-local"
|
|
367
|
+
`;
|
|
368
|
+
fs.writeFileSync(genvmConfigPath, genvmConfigContent);
|
|
369
|
+
|
|
370
|
+
console.warn('\x1b[33m⚠ Localhost access enabled — GenVM can reach host services (Anvil, local APIs). Not for production use.\x1b[0m');
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
public async isLocalnetRunning(): Promise<boolean> {
|
|
374
|
+
const genlayerContainers = await this.getGenlayerContainers();
|
|
375
|
+
const runningContainers = genlayerContainers.filter(container => container.State === "running");
|
|
376
|
+
|
|
377
|
+
return GENLAYER_REQUIRED_CONTAINERS.every(requiredContainer =>
|
|
378
|
+
runningContainers.some(container =>
|
|
379
|
+
container.Names.some(name => name.includes(requiredContainer))
|
|
380
|
+
)
|
|
381
|
+
);
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
export default new SimulatorService();
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
declare module "node-fetch";
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import {describe, test, vi, beforeEach, afterEach, expect} from "vitest";
|
|
2
|
+
import {createClient, createAccount} from "genlayer-js";
|
|
3
|
+
import type {TransactionHash} from "genlayer-js/types";
|
|
4
|
+
import {AppealAction} from "../../src/commands/transactions/appeal";
|
|
5
|
+
|
|
6
|
+
vi.mock("genlayer-js", async (importOriginal) => {
|
|
7
|
+
const actual = await importOriginal<typeof import("genlayer-js")>();
|
|
8
|
+
return {
|
|
9
|
+
...actual,
|
|
10
|
+
createClient: vi.fn(),
|
|
11
|
+
createAccount: vi.fn(),
|
|
12
|
+
};
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
describe("AppealAction", () => {
|
|
16
|
+
let appealAction: AppealAction;
|
|
17
|
+
const mockClient = {
|
|
18
|
+
appealTransaction: vi.fn(),
|
|
19
|
+
waitForTransactionReceipt: vi.fn(),
|
|
20
|
+
initializeConsensusSmartContract: vi.fn(),
|
|
21
|
+
getMinAppealBond: vi.fn(),
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
const mockPrivateKey = "mocked_private_key";
|
|
25
|
+
const mockTxId = "0x1234567890123456789012345678901234567890123456789012345678901234" as TransactionHash;
|
|
26
|
+
|
|
27
|
+
beforeEach(() => {
|
|
28
|
+
vi.clearAllMocks();
|
|
29
|
+
vi.mocked(createClient).mockReturnValue(mockClient as any);
|
|
30
|
+
vi.mocked(createAccount).mockReturnValue({privateKey: mockPrivateKey} as any);
|
|
31
|
+
appealAction = new AppealAction();
|
|
32
|
+
vi.spyOn(appealAction as any, "getAccount").mockResolvedValue({privateKey: mockPrivateKey});
|
|
33
|
+
|
|
34
|
+
vi.spyOn(appealAction as any, "startSpinner").mockImplementation(() => {});
|
|
35
|
+
vi.spyOn(appealAction as any, "succeedSpinner").mockImplementation(() => {});
|
|
36
|
+
vi.spyOn(appealAction as any, "failSpinner").mockImplementation(() => {});
|
|
37
|
+
vi.spyOn(appealAction as any, "stopSpinner").mockImplementation(() => {});
|
|
38
|
+
vi.spyOn(appealAction as any, "setSpinnerText").mockImplementation(() => {});
|
|
39
|
+
vi.spyOn(appealAction as any, "logInfo").mockImplementation(() => {});
|
|
40
|
+
vi.spyOn(appealAction as any, "confirmPrompt").mockResolvedValue(undefined);
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
afterEach(() => {
|
|
44
|
+
vi.restoreAllMocks();
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
test("auto-calculates bond and appeals successfully", async () => {
|
|
48
|
+
const mockReceipt = {status: "success"};
|
|
49
|
+
vi.mocked(mockClient.getMinAppealBond).mockResolvedValue(500000000000000000000n);
|
|
50
|
+
vi.mocked(mockClient.appealTransaction).mockResolvedValue("0xhash");
|
|
51
|
+
vi.mocked(mockClient.waitForTransactionReceipt).mockResolvedValue(mockReceipt);
|
|
52
|
+
|
|
53
|
+
await appealAction.appeal({txId: mockTxId});
|
|
54
|
+
|
|
55
|
+
expect(mockClient.getMinAppealBond).toHaveBeenCalledWith({txId: mockTxId});
|
|
56
|
+
expect(mockClient.appealTransaction).toHaveBeenCalledWith({
|
|
57
|
+
txId: mockTxId,
|
|
58
|
+
value: 500000000000000000000n,
|
|
59
|
+
});
|
|
60
|
+
expect(appealAction["succeedSpinner"]).toHaveBeenCalledWith(
|
|
61
|
+
"Appeal successfully executed",
|
|
62
|
+
mockReceipt,
|
|
63
|
+
);
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
test("uses explicit bond when provided", async () => {
|
|
67
|
+
const mockReceipt = {status: "success"};
|
|
68
|
+
vi.mocked(mockClient.appealTransaction).mockResolvedValue("0xhash");
|
|
69
|
+
vi.mocked(mockClient.waitForTransactionReceipt).mockResolvedValue(mockReceipt);
|
|
70
|
+
|
|
71
|
+
await appealAction.appeal({txId: mockTxId, bond: "100gen"});
|
|
72
|
+
|
|
73
|
+
expect(mockClient.getMinAppealBond).not.toHaveBeenCalled();
|
|
74
|
+
expect(mockClient.appealTransaction).toHaveBeenCalledWith({
|
|
75
|
+
txId: mockTxId,
|
|
76
|
+
value: 100000000000000000000n,
|
|
77
|
+
});
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
test("falls back to undefined value when bond calculation fails", async () => {
|
|
81
|
+
const mockReceipt = {status: "success"};
|
|
82
|
+
vi.mocked(mockClient.getMinAppealBond).mockRejectedValue(new Error("not supported"));
|
|
83
|
+
vi.mocked(mockClient.appealTransaction).mockResolvedValue("0xhash");
|
|
84
|
+
vi.mocked(mockClient.waitForTransactionReceipt).mockResolvedValue(mockReceipt);
|
|
85
|
+
|
|
86
|
+
await appealAction.appeal({txId: mockTxId});
|
|
87
|
+
|
|
88
|
+
expect(mockClient.appealTransaction).toHaveBeenCalledWith({
|
|
89
|
+
txId: mockTxId,
|
|
90
|
+
value: undefined,
|
|
91
|
+
});
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
test("handles appealTransaction errors", async () => {
|
|
95
|
+
vi.mocked(mockClient.getMinAppealBond).mockResolvedValue(0n);
|
|
96
|
+
vi.mocked(mockClient.appealTransaction).mockRejectedValue(new Error("Mocked appeal error"));
|
|
97
|
+
|
|
98
|
+
await appealAction.appeal({txId: mockTxId});
|
|
99
|
+
|
|
100
|
+
expect(appealAction["failSpinner"]).toHaveBeenCalledWith(
|
|
101
|
+
"Error during appeal operation",
|
|
102
|
+
expect.any(Error),
|
|
103
|
+
);
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
test("uses custom RPC URL for appeal operations", async () => {
|
|
107
|
+
const rpcUrl = "https://custom-rpc-url.com";
|
|
108
|
+
const mockReceipt = {status: "success"};
|
|
109
|
+
vi.mocked(mockClient.getMinAppealBond).mockResolvedValue(0n);
|
|
110
|
+
vi.mocked(mockClient.appealTransaction).mockResolvedValue("0xhash");
|
|
111
|
+
vi.mocked(mockClient.waitForTransactionReceipt).mockResolvedValue(mockReceipt);
|
|
112
|
+
|
|
113
|
+
await appealAction.appeal({txId: mockTxId, rpc: rpcUrl});
|
|
114
|
+
|
|
115
|
+
expect(createClient).toHaveBeenCalledWith(
|
|
116
|
+
expect.objectContaining({endpoint: rpcUrl}),
|
|
117
|
+
);
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
test("appealBond returns minimum bond", async () => {
|
|
121
|
+
vi.mocked(mockClient.getMinAppealBond).mockResolvedValue(500000000000000000000n);
|
|
122
|
+
|
|
123
|
+
await appealAction.appealBond({txId: mockTxId});
|
|
124
|
+
|
|
125
|
+
expect(mockClient.getMinAppealBond).toHaveBeenCalledWith({txId: mockTxId});
|
|
126
|
+
expect(appealAction["succeedSpinner"]).toHaveBeenCalledWith(
|
|
127
|
+
`Minimum appeal bond: 500 GEN`,
|
|
128
|
+
);
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
test("appealBond handles errors", async () => {
|
|
132
|
+
vi.mocked(mockClient.getMinAppealBond).mockRejectedValue(new Error("not supported"));
|
|
133
|
+
|
|
134
|
+
await appealAction.appealBond({txId: mockTxId});
|
|
135
|
+
|
|
136
|
+
expect(appealAction["failSpinner"]).toHaveBeenCalledWith(
|
|
137
|
+
"Error calculating appeal bond",
|
|
138
|
+
expect.any(Error),
|
|
139
|
+
);
|
|
140
|
+
});
|
|
141
|
+
});
|