Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 50 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -169,11 +169,15 @@ USAGE:
genlayer deploy [options]
genlayer call <contractAddress> <method> [options]
genlayer write <contractAddress> <method> [options]
genlayer estimate-fees [contractAddress] [method] [options]
genlayer schema <contractAddress> [options]

OPTIONS (deploy):
--contract <contractPath> (Optional) Path to the intelligent contract to deploy
--rpc <rpcUrl> RPC URL for the network
--fees <json> Transaction fee options JSON passed to genlayer-js
--fee-value <wei> Explicit fee deposit value
--valid-until <timestamp> Unix timestamp after which the transaction is invalid
--args <args...> Contract arguments (see Argument Types below)

OPTIONS (call):
Expand All @@ -182,23 +186,69 @@ OPTIONS (call):

OPTIONS (write):
--rpc <rpcUrl> RPC URL for the network
--fees <json> Transaction fee options JSON passed to genlayer-js
--fee-value <wei> Explicit fee deposit value
--valid-until <timestamp> Unix timestamp after which the transaction is invalid
--args <args...> Method arguments (see Argument Types below)

OPTIONS (estimate-fees):
--rpc <rpcUrl> RPC URL for the network
--fees <json> Fee estimate options JSON, or a transaction fee object
--include-report Include simulation fee accounting/report in the generated estimate output
--args <args...> Method arguments for simulation-derived estimates

OPTIONS (schema):
--rpc <rpcUrl> RPC URL for the network

EXAMPLES:
genlayer deploy
genlayer deploy --contract ./my_contract.gpy
genlayer deploy --contract ./my_contract.gpy --args "arg1" "arg2" 123
genlayer deploy --contract ./my_contract.gpy --fees '{"distribution":{"leaderTimeunitsAllocation":"100","validatorTimeunitsAllocation":"200","rotations":["0"]}}'
genlayer call 0x123456789abcdef greet --args "Hello World!"
genlayer write 0x123456789abcdef updateValue --args 42
genlayer write 0x123456789abcdef updateValue --fees '{"distribution":{"leaderTimeunitsAllocation":"100","validatorTimeunitsAllocation":"200","rotations":["0"]}}' --args 42
genlayer estimate-fees
genlayer estimate-fees 0x123456789abcdef updateValue --args 42
genlayer write 0x123456789abcdef sendReward --args 0x6857Ed54CbafaA74Fc0357145eC0ee1536ca45A0
genlayer write 0x123456789abcdef setScores --args '[1, 2, 3]'
genlayer write 0x123456789abcdef setConfig --args '{"timeout": 30, "retries": 5}'
genlayer schema 0x123456789abcdef
```

##### Transaction Fee Options

`--fees` accepts the same transaction fee object as `genlayer-js`. Quote large
integer values as strings to preserve precision. `messageAllocations[].messageType`
may be `"internal"`, `"external"`, `0`, or `1`.

For targeted message budgets, the CLI can derive GenVM call keys before passing
the JSON to `genlayer-js`:

```bash
genlayer estimate-fees 0x123456789abcdef settle \
--fees '{"messageAllocations":[{"messageType":"internal","recipient":"0x0000000000000000000000000000000000000001","callKeyMethod":"settle_campaign","budget":"700000"}]}'

genlayer write 0x123456789abcdef sendReward \
--fees '{"messageAllocations":[{"messageType":"external","recipient":"0x0000000000000000000000000000000000000002","callKeySelector":"0xa9059cbb","budget":"210000"}]}'
```

Use `callKeyMethod` for internal GenVM messages, `callKeySelector` for a 4-byte
EVM selector, or `callKeyCalldata` for full external calldata. Explicit
`callKey` is still accepted for advanced cases.

If `--fees` includes a `distribution` and `--fee-value` is omitted, the SDK
derives the fee deposit from FeeManager on network backends, or from
`sim_getFeeConfig` on Studio. Use `--fee-value` only when you need to force an
explicit deposit value.

`estimate-fees` prints the SDK fee preset. Without a contract/method it calls
`estimateTransactionFees`. With a contract/method it uses the SDK target-write
estimation helper so the preset reflects the observed Studio fee accounting
report. Add `--include-report` to use the explicit simulate-and-derive path and
include the simulation fee accounting and execution fee report next to the
preset for reproducible gas-unit debugging.

##### Argument Types

The `--args` option automatically detects and converts values to the correct type:
Expand Down Expand Up @@ -522,4 +572,3 @@ We welcome contributions to GenLayerJS SDK! Whether it's new features, improved
## License

This project is licensed under the ... License - see the [LICENSE](LICENSE) file for details.

8 changes: 4 additions & 4 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@
"dotenv": "^17.0.0",
"ethers": "^6.13.4",
"fs-extra": "^11.3.0",
"genlayer-js": "^1.0.0",
"genlayer-js": "^1.1.8",
"inquirer": "^12.0.0",
"keytar": "^7.9.0",
"node-fetch": "^3.0.0",
Expand Down
7 changes: 6 additions & 1 deletion src/commands/contracts/deploy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,9 @@ import {BaseAction} from "../../lib/actions/BaseAction";
import {pathToFileURL} from "url";
import {TransactionStatus} from "genlayer-js/types";
import {buildSync} from "esbuild";
import {ContractFeeCliOptions, parseTransactionFees, parseValidUntil} from "./fees";

export interface DeployOptions {
export interface DeployOptions extends ContractFeeCliOptions {
contract?: string;
args?: any[];
rpc?: string;
Expand Down Expand Up @@ -131,6 +132,10 @@ export class DeployAction extends BaseAction {

const leaderOnly = false;
const deployParams: any = {code: contractCode, args: options.args, leaderOnly};
const fees = parseTransactionFees(options);
const validUntil = parseValidUntil(options);
if (fees) deployParams.fees = fees;
if (validUntil !== undefined) deployParams.validUntil = validUntil;

this.setSpinnerText("Starting contract deployment...");
this.log("Deployment Parameters:", deployParams);
Expand Down
133 changes: 133 additions & 0 deletions src/commands/contracts/estimateFees.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
import {BaseAction} from "../../lib/actions/BaseAction";
import {ContractFeeCliOptions, parseFeeEstimateOptions} from "./fees";

export interface EstimateFeesOptions extends Pick<ContractFeeCliOptions, "fees"> {
args?: any[];
rpc?: string;
json?: boolean;
includeReport?: boolean;
}

const toTransactionFees = (estimate: Record<string, any>): Record<string, any> => ({
distribution: estimate.distribution,
...(estimate.messageAllocations ? {messageAllocations: estimate.messageAllocations} : {}),
feeValue: estimate.feeValue ?? estimate.fee_value,
});

const toJsonSafe = (value: any): any => {
if (typeof value === "bigint") return value.toString();
if (Array.isArray(value)) return value.map(toJsonSafe);
if (value && typeof value === "object") {
return Object.fromEntries(
Object.entries(value)
.filter(([, item]) => item !== undefined)
.map(([key, item]) => [key, toJsonSafe(item)]),
);
}
return value;
};

const simulationFeeReport = (simulation: Record<string, any>): Record<string, any> | undefined => (
simulation.feeReport ?? simulation.feeAccounting?.execution_fee_report
);

const withSimulationReport = (estimate: unknown, simulation: unknown): unknown => {
if (!simulation || typeof simulation !== "object" || Array.isArray(simulation)) {
return estimate;
}

const simulationRecord = simulation as Record<string, any>;
return {
...(estimate && typeof estimate === "object" && !Array.isArray(estimate)
? estimate as Record<string, any>
: {estimate}),
simulation: {
feeAccounting: simulationRecord.feeAccounting,
feeReport: simulationFeeReport(simulationRecord),
},
};
};

export class EstimateFeesAction extends BaseAction {
constructor() {
super();
}

async estimate({
contractAddress,
method,
args,
rpc,
fees,
json,
includeReport,
}: EstimateFeesOptions & {
contractAddress?: string;
method?: string;
}): Promise<void> {
try {
const client = await this.getClient(rpc, true);
await client.initializeConsensusSmartContract();
const estimateOptions = parseFeeEstimateOptions({fees});

if (!json) this.startSpinner("Estimating transaction fees...");
let estimate: unknown;

if (contractAddress || method) {
if (!contractAddress || !method) {
this.failSpinner("Both contractAddress and method are required for simulation-derived fee estimates.");
return;
}

if (!json) this.setSpinnerText(`Simulating ${method} on ${contractAddress}...`);
if (!includeReport && typeof client.estimateTransactionFeesForWrite === "function") {
estimate = await client.estimateTransactionFeesForWrite({
...(estimateOptions ?? {}),
address: contractAddress as any,
functionName: method,
args: args ?? [],
});
} else {
if (typeof client.simulateWriteContract !== "function") {
this.failSpinner("The active genlayer-js client does not support write simulation.");
return;
}
if (typeof client.estimateTransactionFeesFromSimulation !== "function") {
this.failSpinner("The active genlayer-js client does not support simulation-derived fee estimates.");
return;
}

const initialEstimate = await client.estimateTransactionFees(estimateOptions);
const simulation = await client.simulateWriteContract({
address: contractAddress as any,
functionName: method,
args: args ?? [],
includeReceipt: true,
fees: toTransactionFees(initialEstimate as Record<string, any>),
});
estimate = await client.estimateTransactionFeesFromSimulation({
...(estimateOptions ?? {}),
simulation,
});
if (includeReport) {
estimate = withSimulationReport(estimate, simulation);
}
}
} else {
if (includeReport) {
this.failSpinner("--include-report requires both contractAddress and method.");
return;
}
estimate = await client.estimateTransactionFees(estimateOptions);
}

if (json) {
console.log(JSON.stringify(toJsonSafe(estimate)));
} else {
this.succeedSpinner("Fee estimate generated", toJsonSafe(estimate));
}
} catch (error) {
this.failSpinner("Error estimating transaction fees", error);
}
}
}
Loading
Loading