Repository files navigation

GenLayerJS

License: MITDiscordTwitterGitHub star chart

👀 About

GenLayerJS SDK is a TypeScript library designed for developers building decentralized applications (Dapps) on the GenLayer protocol. This SDK provides a comprehensive set of tools to interact with the GenLayer network, including client creation, transaction handling, event subscriptions, and more, all while leveraging the power of Viem as the underlying blockchain client.

Prerequisites

Before installing GenLayerJS SDK, ensure you have the following prerequisites installed:

  • Node.js (>= 16.x)
  • npm (>= 7.x)

🛠️ Installation and Usage

To install the GenLayerJS SDK, use the following command:

$ npm install genlayer-js

Here’s how to initialize the client and connect to the GenLayer Simulator:

Reading a Transaction

import{localnet}from'genlayer-js/chains';import{createClient}from"genlayer-js";constclient=createClient({chain: localnet,});consttransactionHash="0x...";consttransaction=awaitclient.getTransaction({hash: transactionHash})

Waiting for Transaction Receipt

import{localnet}from'genlayer-js/chains';import{createClient}from"genlayer-js";import{TransactionStatus}from"genlayer-js/types";constclient=createClient({chain: localnet,});// Get simplified receipt (default - removes binary data, keeps execution results)constreceipt=awaitclient.waitForTransactionReceipt({hash: "0x...",status: TransactionStatus.FINALIZED,fullTransaction: false// Default - simplified for readability});// Get complete receipt with all fieldsconstfullReceipt=awaitclient.waitForTransactionReceipt({hash: "0x...",status: TransactionStatus.FINALIZED,fullTransaction: true// Complete receipt with all internal data});

Reading a contract

import{localnet}from'genlayer-js/chains';import{createClient}from"genlayer-js";constclient=createClient({chain: localnet,});constresult=awaitclient.readContract({// account: account, Account is optional when reading from contractsaddress: contractAddress,functionName: 'get_complete_storage',args: []stateStatus: "accepted",})

Writing a transaction

import{localnet}from'genlayer-js/chains';import{createClient,createAccount}from"genlayer-js";constclient=createClient({network: localnet,});constaccount=createAccount();consttransactionHash=awaitclient.writeContract({account: account,// using this account for this transactionaddress: contractAddress,functionName: 'account',args: ['new_storage'],value: 0,// value is optional, if you want to send some native token to the contract});constreceipt=awaitclient.waitForTransactionReceipt({hash: txHash,status: TransactionStatus.FINALIZED,// or ACCEPTEDfullTransaction: false// False by default - returns simplified receipt for better readability})

Fee presets for transactions

Apps can build a trusted fee preset once they know the transaction shape, then submit the same preset with the transaction. The user may still override these values in wallet or app UI before signing.

constestimate=awaitclient.estimateTransactionFees({leaderTimeunitsAllocation: 100n,validatorTimeunitsAllocation: 200n,rotations: [0n],});consttxHash=awaitclient.writeContract({
account,address: contractAddress,functionName: "update_storage",args: ["new_storage"],fees: {distribution: estimate.distribution,feeValue: estimate.feeValue,},});

If fees.distribution is provided without feeValue, the SDK derives the fee deposit from FeeManager on network backends, or from sim_getFeeConfig on Studio. Use messageAllocations with estimateTransactionFees for transactions that can emit funded messages.

Use the SDK call-key helpers when targeting a specific emitted message. Internal messages are keyed by the GenVM method name; external EVM messages are keyed by the first 4 bytes of the calldata selector.

import{MessageType,deriveExternalMessageCallKey,deriveInternalMessageCallKey,encodeExternalMessageFeeParams,encodeInternalMessageFeeParams,}from"genlayer-js";constestimate=awaitclient.estimateTransactionFees({messageAllocations: [{messageType: MessageType.Internal,recipient: childContractAddress,callKey: deriveInternalMessageCallKey("settle_campaign"),budget: 700_000n,feeParams: encodeInternalMessageFeeParams({leaderTimeunitsAllocation: 100n,validatorTimeunitsAllocation: 200n,}),},{messageType: MessageType.External,recipient: tokenAddress,callKey: deriveExternalMessageCallKey("0xa9059cbb"),budget: 210_000n,feeParams: encodeExternalMessageFeeParams({gasLimit: 21_000n,maxGasPrice: 10n,}),},],});

You can also pass the same preset to Studio/localnet simulation. On Studio, includeReceipt uses sim_call so the returned object includes the GenVM receipt and fee accounting report:

constrecommended=awaitclient.estimateTransactionFeesForWrite({
account,address: contractAddress,functionName: "update_storage",args: ["new_storage"],});awaitclient.writeContract({
account,address: contractAddress,functionName: "update_storage",args: ["new_storage"],fees: {distribution: recommended.distribution,messageAllocations: recommended.messageAllocations,feeValue: recommended.feeValue,},});

For tests or tools that need to inspect the raw simulation, use the explicit two-step flow:

constsimulation=awaitclient.simulateWriteContract({
account,address: contractAddress,functionName: "update_storage",args: ["new_storage"],fees: {distribution: estimate.distribution,feeValue: estimate.feeValue,},includeReceipt: true,});console.log(simulation.feeAccounting);constrecommended=awaitclient.estimateTransactionFeesFromSimulation({
simulation,});awaitclient.writeContract({
account,address: contractAddress,functionName: "update_storage",args: ["new_storage"],fees: {distribution: recommended.distribution,messageAllocations: recommended.messageAllocations,feeValue: recommended.feeValue,},});

For transactions that are already submitted, use the fee-management helpers:

awaitclient.topUpFees({
txId,value: 1_100n,distribution: {leaderTimeunitsAllocation: 100n,validatorTimeunitsAllocation: 200n,rotations: [0n],},});awaitclient.topUpAndSubmitAppeal({
txId,value: 1_400n,distribution: {appealRounds: 1n,rotations: [0n,0n],},});

topUpFees returns the backend RPC hash. On network backends this is the EVM transaction hash; on Studio/localnet it is the target GenLayer transaction id.

Checking execution results

A transaction can be finalized by consensus but still have a failed execution. Always check txExecutionResult before reading contract state:

import{ExecutionResult,TransactionStatus}from"genlayer-js/types";constreceipt=awaitclient.waitForTransactionReceipt({hash: txHash,status: TransactionStatus.FINALIZED,});if(receipt.txExecutionResultName===ExecutionResult.FINISHED_WITH_RETURN){// Execution succeeded — safe to read stateconstresult=awaitclient.readContract({address: contractAddress,functionName: "get_storage",args: [],});}elseif(receipt.txExecutionResultName===ExecutionResult.FINISHED_WITH_ERROR){// Execution failed — contract state was not modifiedconsole.error("Contract execution failed");}else{// NOT_VOTED — execution hasn't completedconsole.warn("Execution result not yet available");}

Fetching emitted messages and triggered transactions

Transactions can emit messages to other contracts. These messages create new child transactions when processed:

consttx=awaitclient.getTransaction({hash: txHash});// Messages emitted by the contract during executionconsole.log(tx.messages);// [{messageType, recipient, value, data, onAcceptance, saltNonce}, ...]// Child transaction IDs created from those messages (separate call)constchildTxIds=awaitclient.getTriggeredTransactionIds({hash: txHash});console.log(childTxIds);// ["0xabc...", "0xdef..."]

Debugging transaction execution

Use debugTraceTransaction to inspect the full execution trace of a transaction, including return data, errors, and GenVM logs:

consttrace=awaitclient.debugTraceTransaction({hash: txHash,round: 0,// optional, defaults to 0});console.log(trace.result_code);// 0=success, 1=user error, 2=VM errorconsole.log(trace.return_data);// hex-encoded contract return dataconsole.log(trace.stderr);// standard error outputconsole.log(trace.genvm_log);// detailed GenVM execution logs

Using with a wallet provider (MetaMask)

When building a browser dApp, create two clients: one for reads (no wallet needed) and one for writes (signed by the wallet). This follows the standard viem pattern and keeps concerns separated.

import{createClient}from"genlayer-js";import{testnetBradbury}from"genlayer-js/chains";import{TransactionStatus}from"genlayer-js/types";// Read client — talks directly to GenLayer RPC, no wallet neededconstreadClient=createClient({chain: testnetBradbury,});// Write client — signs transactions through the walletconstwriteClient=createClient({chain: testnetBradbury,account: addressas `0x${string}`,// from wallet connectionprovider: window.ethereum,// or from a wallet SDK});// Use readClient for all readsconstresult=awaitreadClient.readContract({address: contractAddress,functionName: "get_storage",args: [],});consttx=awaitreadClient.getTransaction({hash: txHash});// Use writeClient for transactions (MetaMask popup)consttxHash=awaitwriteClient.writeContract({address: contractAddress,functionName: "update_storage",args: ["new_value"],value: BigInt(0),});// Either client can wait for receiptsconstreceipt=awaitreadClient.waitForTransactionReceipt({hash: txHash,status: TransactionStatus.ACCEPTED,});

Switching the wallet to the correct network

When using MetaMask or another browser wallet, the wallet may be connected to a different chain than what your client is configured for. Use client.connect() to switch the wallet to the correct GenLayer network before sending transactions:

import{createClient}from"genlayer-js";import{studionet}from"genlayer-js/chains";constclient=createClient({chain: studionet,account: addressas `0x${string}`,});// Switch MetaMask to the correct chain (adds the network if not present)awaitclient.connect("studionet");// Now transactions will go to the right networkconsttxHash=awaitclient.writeContract({address: contractAddress,functionName: "create_profile",args: ["alice","Hello world"],value: BigInt(0),});

Available networks: "localnet", "studionet", "testnetAsimov", "testnetBradbury".

Note: If the wallet is on the wrong chain when you call writeContract, the SDK will throw a clear error telling you which chain the wallet is on vs. which chain the client expects. Call client.connect() to resolve this.

Staking Operations

The SDK provides staking functionality for validators and delegators on testnet-bradbury (and testnet-asimov).

import{testnetBradbury}from'genlayer-js/chains';import{createClient,createAccount}from"genlayer-js";constaccount=createAccount();constclient=createClient({chain: testnetBradbury,
account,});// Get epoch info (includes timing estimates and inflation data)constepochInfo=awaitclient.getEpochInfo();// {// currentEpoch: 2n,// epochMinDuration: 86400n, // 1 day in seconds// currentEpochStart: Date,// currentEpochEnd: Date | null,// nextEpochEstimate: Date | null,// validatorMinStake: "0.01 GEN",// delegatorMinStake: "42 GEN",// activeValidatorsCount: 6n,// inflation: "1000 GEN", // Total inflation for current epoch// inflationRaw: 1000000000000000000000n,// totalWeight: 500000000000000000000000n, // Total stake weight// totalClaimed: "500 GEN", // Total claimed rewards// }// Get active validatorsconstvalidators=awaitclient.getActiveValidators();// Check if address is a validatorconstisValidator=awaitclient.isValidator("0x...");// Get validator infoconstvalidatorInfo=awaitclient.getValidatorInfo("0x...");// Join as validator (requires account with funds)constresult=awaitclient.validatorJoin({amount: "42000gen"});// Join as delegatorconstdelegateResult=awaitclient.delegatorJoin({validator: "0x...",amount: "42gen",});

🚀 Key Features

  • Client Creation: Easily create and configure a client to connect to GenLayer's network.
  • Transaction Handling: Send and manage transactions on the GenLayer network.
  • Staking: Full staking support for validators and delegators on testnet-bradbury and testnet-asimov.
  • Wallet Integration*: Seamless integration with MetaMask for managing user accounts.
  • Gas Estimation*: Estimate gas fees for executing transactions on GenLayer.

* under development

📖 Documentation

For detailed information on how to use GenLayerJS SDK, please refer to our documentation.

Contributing

We welcome contributions to GenLayerJS SDK! Whether it's new features, improved infrastructure, or better documentation, your input is valuable. Please read our CONTRIBUTING guide for guidelines on how to submit your contributions.

License

This project is licensed under the MIT License - see the LICENSE file for details.

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

Repository files navigation

GenLayerJS

License: MITDiscordTwitterGitHub star chart

👀 About

GenLayerJS SDK is a TypeScript library designed for developers building decentralized applications (Dapps) on the GenLayer protocol. This SDK provides a comprehensive set of tools to interact with the GenLayer network, including client creation, transaction handling, event subscriptions, and more, all while leveraging the power of Viem as the underlying blockchain client.

Prerequisites

Before installing GenLayerJS SDK, ensure you have the following prerequisites installed:

  • Node.js (>= 16.x)
  • npm (>= 7.x)

🛠️ Installation and Usage

To install the GenLayerJS SDK, use the following command:

$ npm install genlayer-js

Here’s how to initialize the client and connect to the GenLayer Simulator:

Reading a Transaction

import{localnet}from'genlayer-js/chains';import{createClient}from"genlayer-js";constclient=createClient({chain: localnet,});consttransactionHash="0x...";consttransaction=awaitclient.getTransaction({hash: transactionHash})

Waiting for Transaction Receipt

import{localnet}from'genlayer-js/chains';import{createClient}from"genlayer-js";import{TransactionStatus}from"genlayer-js/types";constclient=createClient({chain: localnet,});// Get simplified receipt (default - removes binary data, keeps execution results)constreceipt=awaitclient.waitForTransactionReceipt({hash: "0x...",status: TransactionStatus.FINALIZED,fullTransaction: false// Default - simplified for readability});// Get complete receipt with all fieldsconstfullReceipt=awaitclient.waitForTransactionReceipt({hash: "0x...",status: TransactionStatus.FINALIZED,fullTransaction: true// Complete receipt with all internal data});

Reading a contract

import{localnet}from'genlayer-js/chains';import{createClient}from"genlayer-js";constclient=createClient({chain: localnet,});constresult=awaitclient.readContract({// account: account, Account is optional when reading from contractsaddress: contractAddress,functionName: 'get_complete_storage',args: []stateStatus: "accepted",})

Writing a transaction

import{localnet}from'genlayer-js/chains';import{createClient,createAccount}from"genlayer-js";constclient=createClient({network: localnet,});constaccount=createAccount();consttransactionHash=awaitclient.writeContract({account: account,// using this account for this transactionaddress: contractAddress,functionName: 'account',args: ['new_storage'],value: 0,// value is optional, if you want to send some native token to the contract});constreceipt=awaitclient.waitForTransactionReceipt({hash: txHash,status: TransactionStatus.FINALIZED,// or ACCEPTEDfullTransaction: false// False by default - returns simplified receipt for better readability})

Fee presets for transactions

Apps can build a trusted fee preset once they know the transaction shape, then submit the same preset with the transaction. The user may still override these values in wallet or app UI before signing.

constestimate=awaitclient.estimateTransactionFees({leaderTimeunitsAllocation: 100n,validatorTimeunitsAllocation: 200n,rotations: [0n],});consttxHash=awaitclient.writeContract({
account,address: contractAddress,functionName: "update_storage",args: ["new_storage"],fees: {distribution: estimate.distribution,feeValue: estimate.feeValue,},});

If fees.distribution is provided without feeValue, the SDK derives the fee deposit from FeeManager on network backends, or from sim_getFeeConfig on Studio. Use messageAllocations with estimateTransactionFees for transactions that can emit funded messages.

Use the SDK call-key helpers when targeting a specific emitted message. Internal messages are keyed by the GenVM method name; external EVM messages are keyed by the first 4 bytes of the calldata selector.

import{MessageType,deriveExternalMessageCallKey,deriveInternalMessageCallKey,encodeExternalMessageFeeParams,encodeInternalMessageFeeParams,}from"genlayer-js";constestimate=awaitclient.estimateTransactionFees({messageAllocations: [{messageType: MessageType.Internal,recipient: childContractAddress,callKey: deriveInternalMessageCallKey("settle_campaign"),budget: 700_000n,feeParams: encodeInternalMessageFeeParams({leaderTimeunitsAllocation: 100n,validatorTimeunitsAllocation: 200n,}),},{messageType: MessageType.External,recipient: tokenAddress,callKey: deriveExternalMessageCallKey("0xa9059cbb"),budget: 210_000n,feeParams: encodeExternalMessageFeeParams({gasLimit: 21_000n,maxGasPrice: 10n,}),},],});

You can also pass the same preset to Studio/localnet simulation. On Studio, includeReceipt uses sim_call so the returned object includes the GenVM receipt and fee accounting report:

constrecommended=awaitclient.estimateTransactionFeesForWrite({
account,address: contractAddress,functionName: "update_storage",args: ["new_storage"],});awaitclient.writeContract({
account,address: contractAddress,functionName: "update_storage",args: ["new_storage"],fees: {distribution: recommended.distribution,messageAllocations: recommended.messageAllocations,feeValue: recommended.feeValue,},});

For tests or tools that need to inspect the raw simulation, use the explicit two-step flow:

constsimulation=awaitclient.simulateWriteContract({
account,address: contractAddress,functionName: "update_storage",args: ["new_storage"],fees: {distribution: estimate.distribution,feeValue: estimate.feeValue,},includeReceipt: true,});console.log(simulation.feeAccounting);constrecommended=awaitclient.estimateTransactionFeesFromSimulation({
simulation,});awaitclient.writeContract({
account,address: contractAddress,functionName: "update_storage",args: ["new_storage"],fees: {distribution: recommended.distribution,messageAllocations: recommended.messageAllocations,feeValue: recommended.feeValue,},});

For transactions that are already submitted, use the fee-management helpers:

awaitclient.topUpFees({
txId,value: 1_100n,distribution: {leaderTimeunitsAllocation: 100n,validatorTimeunitsAllocation: 200n,rotations: [0n],},});awaitclient.topUpAndSubmitAppeal({
txId,value: 1_400n,distribution: {appealRounds: 1n,rotations: [0n,0n],},});

topUpFees returns the backend RPC hash. On network backends this is the EVM transaction hash; on Studio/localnet it is the target GenLayer transaction id.

Checking execution results

A transaction can be finalized by consensus but still have a failed execution. Always check txExecutionResult before reading contract state:

import{ExecutionResult,TransactionStatus}from"genlayer-js/types";constreceipt=awaitclient.waitForTransactionReceipt({hash: txHash,status: TransactionStatus.FINALIZED,});if(receipt.txExecutionResultName===ExecutionResult.FINISHED_WITH_RETURN){// Execution succeeded — safe to read stateconstresult=awaitclient.readContract({address: contractAddress,functionName: "get_storage",args: [],});}elseif(receipt.txExecutionResultName===ExecutionResult.FINISHED_WITH_ERROR){// Execution failed — contract state was not modifiedconsole.error("Contract execution failed");}else{// NOT_VOTED — execution hasn't completedconsole.warn("Execution result not yet available");}

Fetching emitted messages and triggered transactions

Transactions can emit messages to other contracts. These messages create new child transactions when processed:

consttx=awaitclient.getTransaction({hash: txHash});// Messages emitted by the contract during executionconsole.log(tx.messages);// [{messageType, recipient, value, data, onAcceptance, saltNonce}, ...]// Child transaction IDs created from those messages (separate call)constchildTxIds=awaitclient.getTriggeredTransactionIds({hash: txHash});console.log(childTxIds);// ["0xabc...", "0xdef..."]

Debugging transaction execution

Use debugTraceTransaction to inspect the full execution trace of a transaction, including return data, errors, and GenVM logs:

consttrace=awaitclient.debugTraceTransaction({hash: txHash,round: 0,// optional, defaults to 0});console.log(trace.result_code);// 0=success, 1=user error, 2=VM errorconsole.log(trace.return_data);// hex-encoded contract return dataconsole.log(trace.stderr);// standard error outputconsole.log(trace.genvm_log);// detailed GenVM execution logs

Using with a wallet provider (MetaMask)

When building a browser dApp, create two clients: one for reads (no wallet needed) and one for writes (signed by the wallet). This follows the standard viem pattern and keeps concerns separated.

import{createClient}from"genlayer-js";import{testnetBradbury}from"genlayer-js/chains";import{TransactionStatus}from"genlayer-js/types";// Read client — talks directly to GenLayer RPC, no wallet neededconstreadClient=createClient({chain: testnetBradbury,});// Write client — signs transactions through the walletconstwriteClient=createClient({chain: testnetBradbury,account: addressas `0x${string}`,// from wallet connectionprovider: window.ethereum,// or from a wallet SDK});// Use readClient for all readsconstresult=awaitreadClient.readContract({address: contractAddress,functionName: "get_storage",args: [],});consttx=awaitreadClient.getTransaction({hash: txHash});// Use writeClient for transactions (MetaMask popup)consttxHash=awaitwriteClient.writeContract({address: contractAddress,functionName: "update_storage",args: ["new_value"],value: BigInt(0),});// Either client can wait for receiptsconstreceipt=awaitreadClient.waitForTransactionReceipt({hash: txHash,status: TransactionStatus.ACCEPTED,});

Switching the wallet to the correct network

When using MetaMask or another browser wallet, the wallet may be connected to a different chain than what your client is configured for. Use client.connect() to switch the wallet to the correct GenLayer network before sending transactions:

import{createClient}from"genlayer-js";import{studionet}from"genlayer-js/chains";constclient=createClient({chain: studionet,account: addressas `0x${string}`,});// Switch MetaMask to the correct chain (adds the network if not present)awaitclient.connect("studionet");// Now transactions will go to the right networkconsttxHash=awaitclient.writeContract({address: contractAddress,functionName: "create_profile",args: ["alice","Hello world"],value: BigInt(0),});

Available networks: "localnet", "studionet", "testnetAsimov", "testnetBradbury".

Note: If the wallet is on the wrong chain when you call writeContract, the SDK will throw a clear error telling you which chain the wallet is on vs. which chain the client expects. Call client.connect() to resolve this.

Staking Operations

The SDK provides staking functionality for validators and delegators on testnet-bradbury (and testnet-asimov).

import{testnetBradbury}from'genlayer-js/chains';import{createClient,createAccount}from"genlayer-js";constaccount=createAccount();constclient=createClient({chain: testnetBradbury,
account,});// Get epoch info (includes timing estimates and inflation data)constepochInfo=awaitclient.getEpochInfo();// {// currentEpoch: 2n,// epochMinDuration: 86400n, // 1 day in seconds// currentEpochStart: Date,// currentEpochEnd: Date | null,// nextEpochEstimate: Date | null,// validatorMinStake: "0.01 GEN",// delegatorMinStake: "42 GEN",// activeValidatorsCount: 6n,// inflation: "1000 GEN", // Total inflation for current epoch// inflationRaw: 1000000000000000000000n,// totalWeight: 500000000000000000000000n, // Total stake weight// totalClaimed: "500 GEN", // Total claimed rewards// }// Get active validatorsconstvalidators=awaitclient.getActiveValidators();// Check if address is a validatorconstisValidator=awaitclient.isValidator("0x...");// Get validator infoconstvalidatorInfo=awaitclient.getValidatorInfo("0x...");// Join as validator (requires account with funds)constresult=awaitclient.validatorJoin({amount: "42000gen"});// Join as delegatorconstdelegateResult=awaitclient.delegatorJoin({validator: "0x...",amount: "42gen",});

🚀 Key Features

  • Client Creation: Easily create and configure a client to connect to GenLayer's network.
  • Transaction Handling: Send and manage transactions on the GenLayer network.
  • Staking: Full staking support for validators and delegators on testnet-bradbury and testnet-asimov.
  • Wallet Integration*: Seamless integration with MetaMask for managing user accounts.
  • Gas Estimation*: Estimate gas fees for executing transactions on GenLayer.

* under development

📖 Documentation

For detailed information on how to use GenLayerJS SDK, please refer to our documentation.

Contributing

We welcome contributions to GenLayerJS SDK! Whether it's new features, improved infrastructure, or better documentation, your input is valuable. Please read our CONTRIBUTING guide for guidelines on how to submit your contributions.

License

This project is licensed under the MIT License - see the LICENSE file for details.

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

GenLayerJS

License: MITDiscordTwitterGitHub star chart

👀 About

GenLayerJS SDK is a TypeScript library designed for developers building decentralized applications (Dapps) on the GenLayer protocol. This SDK provides a comprehensive set of tools to interact with the GenLayer network, including client creation, transaction handling, event subscriptions, and more, all while leveraging the power of Viem as the underlying blockchain client.

Prerequisites

Before installing GenLayerJS SDK, ensure you have the following prerequisites installed:

  • Node.js (>= 16.x)
  • npm (>= 7.x)

🛠️ Installation and Usage

To install the GenLayerJS SDK, use the following command:

$ npm install genlayer-js

Here’s how to initialize the client and connect to the GenLayer Simulator:

Reading a Transaction

import{localnet}from'genlayer-js/chains';import{createClient}from"genlayer-js";constclient=createClient({chain: localnet,});consttransactionHash="0x...";consttransaction=awaitclient.getTransaction({hash: transactionHash})

Waiting for Transaction Receipt

import{localnet}from'genlayer-js/chains';import{createClient}from"genlayer-js";import{TransactionStatus}from"genlayer-js/types";constclient=createClient({chain: localnet,});// Get simplified receipt (default - removes binary data, keeps execution results)constreceipt=awaitclient.waitForTransactionReceipt({hash: "0x...",status: TransactionStatus.FINALIZED,fullTransaction: false// Default - simplified for readability});// Get complete receipt with all fieldsconstfullReceipt=awaitclient.waitForTransactionReceipt({hash: "0x...",status: TransactionStatus.FINALIZED,fullTransaction: true// Complete receipt with all internal data});

Reading a contract

import{localnet}from'genlayer-js/chains';import{createClient}from"genlayer-js";constclient=createClient({chain: localnet,});constresult=awaitclient.readContract({// account: account, Account is optional when reading from contractsaddress: contractAddress,functionName: 'get_complete_storage',args: []stateStatus: "accepted",})

Writing a transaction

import{localnet}from'genlayer-js/chains';import{createClient,createAccount}from"genlayer-js";constclient=createClient({network: localnet,});constaccount=createAccount();consttransactionHash=awaitclient.writeContract({account: account,// using this account for this transactionaddress: contractAddress,functionName: 'account',args: ['new_storage'],value: 0,// value is optional, if you want to send some native token to the contract});constreceipt=awaitclient.waitForTransactionReceipt({hash: txHash,status: TransactionStatus.FINALIZED,// or ACCEPTEDfullTransaction: false// False by default - returns simplified receipt for better readability})

Fee presets for transactions

Apps can build a trusted fee preset once they know the transaction shape, then submit the same preset with the transaction. The user may still override these values in wallet or app UI before signing.

constestimate=awaitclient.estimateTransactionFees({leaderTimeunitsAllocation: 100n,validatorTimeunitsAllocation: 200n,rotations: [0n],});consttxHash=awaitclient.writeContract({
account,address: contractAddress,functionName: "update_storage",args: ["new_storage"],fees: {distribution: estimate.distribution,feeValue: estimate.feeValue,},});

If fees.distribution is provided without feeValue, the SDK derives the fee deposit from FeeManager on network backends, or from sim_getFeeConfig on Studio. Use messageAllocations with estimateTransactionFees for transactions that can emit funded messages.

Use the SDK call-key helpers when targeting a specific emitted message. Internal messages are keyed by the GenVM method name; external EVM messages are keyed by the first 4 bytes of the calldata selector.

import{MessageType,deriveExternalMessageCallKey,deriveInternalMessageCallKey,encodeExternalMessageFeeParams,encodeInternalMessageFeeParams,}from"genlayer-js";constestimate=awaitclient.estimateTransactionFees({messageAllocations: [{messageType: MessageType.Internal,recipient: childContractAddress,callKey: deriveInternalMessageCallKey("settle_campaign"),budget: 700_000n,feeParams: encodeInternalMessageFeeParams({leaderTimeunitsAllocation: 100n,validatorTimeunitsAllocation: 200n,}),},{messageType: MessageType.External,recipient: tokenAddress,callKey: deriveExternalMessageCallKey("0xa9059cbb"),budget: 210_000n,feeParams: encodeExternalMessageFeeParams({gasLimit: 21_000n,maxGasPrice: 10n,}),},],});

You can also pass the same preset to Studio/localnet simulation. On Studio, includeReceipt uses sim_call so the returned object includes the GenVM receipt and fee accounting report:

constrecommended=awaitclient.estimateTransactionFeesForWrite({
account,address: contractAddress,functionName: "update_storage",args: ["new_storage"],});awaitclient.writeContract({
account,address: contractAddress,functionName: "update_storage",args: ["new_storage"],fees: {distribution: recommended.distribution,messageAllocations: recommended.messageAllocations,feeValue: recommended.feeValue,},});

For tests or tools that need to inspect the raw simulation, use the explicit two-step flow:

constsimulation=awaitclient.simulateWriteContract({
account,address: contractAddress,functionName: "update_storage",args: ["new_storage"],fees: {distribution: estimate.distribution,feeValue: estimate.feeValue,},includeReceipt: true,});console.log(simulation.feeAccounting);constrecommended=awaitclient.estimateTransactionFeesFromSimulation({
simulation,});awaitclient.writeContract({
account,address: contractAddress,functionName: "update_storage",args: ["new_storage"],fees: {distribution: recommended.distribution,messageAllocations: recommended.messageAllocations,feeValue: recommended.feeValue,},});

For transactions that are already submitted, use the fee-management helpers:

awaitclient.topUpFees({
txId,value: 1_100n,distribution: {leaderTimeunitsAllocation: 100n,validatorTimeunitsAllocation: 200n,rotations: [0n],},});awaitclient.topUpAndSubmitAppeal({
txId,value: 1_400n,distribution: {appealRounds: 1n,rotations: [0n,0n],},});

topUpFees returns the backend RPC hash. On network backends this is the EVM transaction hash; on Studio/localnet it is the target GenLayer transaction id.

Checking execution results

A transaction can be finalized by consensus but still have a failed execution. Always check txExecutionResult before reading contract state:

import{ExecutionResult,TransactionStatus}from"genlayer-js/types";constreceipt=awaitclient.waitForTransactionReceipt({hash: txHash,status: TransactionStatus.FINALIZED,});if(receipt.txExecutionResultName===ExecutionResult.FINISHED_WITH_RETURN){// Execution succeeded — safe to read stateconstresult=awaitclient.readContract({address: contractAddress,functionName: "get_storage",args: [],});}elseif(receipt.txExecutionResultName===ExecutionResult.FINISHED_WITH_ERROR){// Execution failed — contract state was not modifiedconsole.error("Contract execution failed");}else{// NOT_VOTED — execution hasn't completedconsole.warn("Execution result not yet available");}

Fetching emitted messages and triggered transactions

Transactions can emit messages to other contracts. These messages create new child transactions when processed:

consttx=awaitclient.getTransaction({hash: txHash});// Messages emitted by the contract during executionconsole.log(tx.messages);// [{messageType, recipient, value, data, onAcceptance, saltNonce}, ...]// Child transaction IDs created from those messages (separate call)constchildTxIds=awaitclient.getTriggeredTransactionIds({hash: txHash});console.log(childTxIds);// ["0xabc...", "0xdef..."]

Debugging transaction execution

Use debugTraceTransaction to inspect the full execution trace of a transaction, including return data, errors, and GenVM logs:

consttrace=awaitclient.debugTraceTransaction({hash: txHash,round: 0,// optional, defaults to 0});console.log(trace.result_code);// 0=success, 1=user error, 2=VM errorconsole.log(trace.return_data);// hex-encoded contract return dataconsole.log(trace.stderr);// standard error outputconsole.log(trace.genvm_log);// detailed GenVM execution logs

Using with a wallet provider (MetaMask)

When building a browser dApp, create two clients: one for reads (no wallet needed) and one for writes (signed by the wallet). This follows the standard viem pattern and keeps concerns separated.

import{createClient}from"genlayer-js";import{testnetBradbury}from"genlayer-js/chains";import{TransactionStatus}from"genlayer-js/types";// Read client — talks directly to GenLayer RPC, no wallet neededconstreadClient=createClient({chain: testnetBradbury,});// Write client — signs transactions through the walletconstwriteClient=createClient({chain: testnetBradbury,account: addressas `0x${string}`,// from wallet connectionprovider: window.ethereum,// or from a wallet SDK});// Use readClient for all readsconstresult=awaitreadClient.readContract({address: contractAddress,functionName: "get_storage",args: [],});consttx=awaitreadClient.getTransaction({hash: txHash});// Use writeClient for transactions (MetaMask popup)consttxHash=awaitwriteClient.writeContract({address: contractAddress,functionName: "update_storage",args: ["new_value"],value: BigInt(0),});// Either client can wait for receiptsconstreceipt=awaitreadClient.waitForTransactionReceipt({hash: txHash,status: TransactionStatus.ACCEPTED,});

Switching the wallet to the correct network

When using MetaMask or another browser wallet, the wallet may be connected to a different chain than what your client is configured for. Use client.connect() to switch the wallet to the correct GenLayer network before sending transactions:

import{createClient}from"genlayer-js";import{studionet}from"genlayer-js/chains";constclient=createClient({chain: studionet,account: addressas `0x${string}`,});// Switch MetaMask to the correct chain (adds the network if not present)awaitclient.connect("studionet");// Now transactions will go to the right networkconsttxHash=awaitclient.writeContract({address: contractAddress,functionName: "create_profile",args: ["alice","Hello world"],value: BigInt(0),});

Available networks: "localnet", "studionet", "testnetAsimov", "testnetBradbury".

Note: If the wallet is on the wrong chain when you call writeContract, the SDK will throw a clear error telling you which chain the wallet is on vs. which chain the client expects. Call client.connect() to resolve this.

Staking Operations

The SDK provides staking functionality for validators and delegators on testnet-bradbury (and testnet-asimov).

import{testnetBradbury}from'genlayer-js/chains';import{createClient,createAccount}from"genlayer-js";constaccount=createAccount();constclient=createClient({chain: testnetBradbury,
account,});// Get epoch info (includes timing estimates and inflation data)constepochInfo=awaitclient.getEpochInfo();// {// currentEpoch: 2n,// epochMinDuration: 86400n, // 1 day in seconds// currentEpochStart: Date,// currentEpochEnd: Date | null,// nextEpochEstimate: Date | null,// validatorMinStake: "0.01 GEN",// delegatorMinStake: "42 GEN",// activeValidatorsCount: 6n,// inflation: "1000 GEN", // Total inflation for current epoch// inflationRaw: 1000000000000000000000n,// totalWeight: 500000000000000000000000n, // Total stake weight// totalClaimed: "500 GEN", // Total claimed rewards// }// Get active validatorsconstvalidators=awaitclient.getActiveValidators();// Check if address is a validatorconstisValidator=awaitclient.isValidator("0x...");// Get validator infoconstvalidatorInfo=awaitclient.getValidatorInfo("0x...");// Join as validator (requires account with funds)constresult=awaitclient.validatorJoin({amount: "42000gen"});// Join as delegatorconstdelegateResult=awaitclient.delegatorJoin({validator: "0x...",amount: "42gen",});

🚀 Key Features

  • Client Creation: Easily create and configure a client to connect to GenLayer's network.
  • Transaction Handling: Send and manage transactions on the GenLayer network.
  • Staking: Full staking support for validators and delegators on testnet-bradbury and testnet-asimov.
  • Wallet Integration*: Seamless integration with MetaMask for managing user accounts.
  • Gas Estimation*: Estimate gas fees for executing transactions on GenLayer.

* under development

📖 Documentation

For detailed information on how to use GenLayerJS SDK, please refer to our documentation.

Contributing

We welcome contributions to GenLayerJS SDK! Whether it's new features, improved infrastructure, or better documentation, your input is valuable. Please read our CONTRIBUTING guide for guidelines on how to submit your contributions.

License

This project is licensed under the MIT License - see the LICENSE file for details.

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

GenLayerJS

License: MITDiscordTwitterGitHub star chart

👀 About

GenLayerJS SDK is a TypeScript library designed for developers building decentralized applications (Dapps) on the GenLayer protocol. This SDK provides a comprehensive set of tools to interact with the GenLayer network, including client creation, transaction handling, event subscriptions, and more, all while leveraging the power of Viem as the underlying blockchain client.

Prerequisites

Before installing GenLayerJS SDK, ensure you have the following prerequisites installed:

  • Node.js (>= 16.x)
  • npm (>= 7.x)

🛠️ Installation and Usage

To install the GenLayerJS SDK, use the following command:

$ npm install genlayer-js

Here’s how to initialize the client and connect to the GenLayer Simulator:

Reading a Transaction

import{localnet}from'genlayer-js/chains';import{createClient}from"genlayer-js";constclient=createClient({chain: localnet,});consttransactionHash="0x...";consttransaction=awaitclient.getTransaction({hash: transactionHash})

Waiting for Transaction Receipt

import{localnet}from'genlayer-js/chains';import{createClient}from"genlayer-js";import{TransactionStatus}from"genlayer-js/types";constclient=createClient({chain: localnet,});// Get simplified receipt (default - removes binary data, keeps execution results)constreceipt=awaitclient.waitForTransactionReceipt({hash: "0x...",status: TransactionStatus.FINALIZED,fullTransaction: false// Default - simplified for readability});// Get complete receipt with all fieldsconstfullReceipt=awaitclient.waitForTransactionReceipt({hash: "0x...",status: TransactionStatus.FINALIZED,fullTransaction: true// Complete receipt with all internal data});

Reading a contract

import{localnet}from'genlayer-js/chains';import{createClient}from"genlayer-js";constclient=createClient({chain: localnet,});constresult=awaitclient.readContract({// account: account, Account is optional when reading from contractsaddress: contractAddress,functionName: 'get_complete_storage',args: []stateStatus: "accepted",})

Writing a transaction

import{localnet}from'genlayer-js/chains';import{createClient,createAccount}from"genlayer-js";constclient=createClient({network: localnet,});constaccount=createAccount();consttransactionHash=awaitclient.writeContract({account: account,// using this account for this transactionaddress: contractAddress,functionName: 'account',args: ['new_storage'],value: 0,// value is optional, if you want to send some native token to the contract});constreceipt=awaitclient.waitForTransactionReceipt({hash: txHash,status: TransactionStatus.FINALIZED,// or ACCEPTEDfullTransaction: false// False by default - returns simplified receipt for better readability})

Fee presets for transactions

Apps can build a trusted fee preset once they know the transaction shape, then submit the same preset with the transaction. The user may still override these values in wallet or app UI before signing.

constestimate=awaitclient.estimateTransactionFees({leaderTimeunitsAllocation: 100n,validatorTimeunitsAllocation: 200n,rotations: [0n],});consttxHash=awaitclient.writeContract({
account,address: contractAddress,functionName: "update_storage",args: ["new_storage"],fees: {distribution: estimate.distribution,feeValue: estimate.feeValue,},});

If fees.distribution is provided without feeValue, the SDK derives the fee deposit from FeeManager on network backends, or from sim_getFeeConfig on Studio. Use messageAllocations with estimateTransactionFees for transactions that can emit funded messages.

Use the SDK call-key helpers when targeting a specific emitted message. Internal messages are keyed by the GenVM method name; external EVM messages are keyed by the first 4 bytes of the calldata selector.

import{MessageType,deriveExternalMessageCallKey,deriveInternalMessageCallKey,encodeExternalMessageFeeParams,encodeInternalMessageFeeParams,}from"genlayer-js";constestimate=awaitclient.estimateTransactionFees({messageAllocations: [{messageType: MessageType.Internal,recipient: childContractAddress,callKey: deriveInternalMessageCallKey("settle_campaign"),budget: 700_000n,feeParams: encodeInternalMessageFeeParams({leaderTimeunitsAllocation: 100n,validatorTimeunitsAllocation: 200n,}),},{messageType: MessageType.External,recipient: tokenAddress,callKey: deriveExternalMessageCallKey("0xa9059cbb"),budget: 210_000n,feeParams: encodeExternalMessageFeeParams({gasLimit: 21_000n,maxGasPrice: 10n,}),},],});

You can also pass the same preset to Studio/localnet simulation. On Studio, includeReceipt uses sim_call so the returned object includes the GenVM receipt and fee accounting report:

constrecommended=awaitclient.estimateTransactionFeesForWrite({
account,address: contractAddress,functionName: "update_storage",args: ["new_storage"],});awaitclient.writeContract({
account,address: contractAddress,functionName: "update_storage",args: ["new_storage"],fees: {distribution: recommended.distribution,messageAllocations: recommended.messageAllocations,feeValue: recommended.feeValue,},});

For tests or tools that need to inspect the raw simulation, use the explicit two-step flow:

constsimulation=awaitclient.simulateWriteContract({
account,address: contractAddress,functionName: "update_storage",args: ["new_storage"],fees: {distribution: estimate.distribution,feeValue: estimate.feeValue,},includeReceipt: true,});console.log(simulation.feeAccounting);constrecommended=awaitclient.estimateTransactionFeesFromSimulation({
simulation,});awaitclient.writeContract({
account,address: contractAddress,functionName: "update_storage",args: ["new_storage"],fees: {distribution: recommended.distribution,messageAllocations: recommended.messageAllocations,feeValue: recommended.feeValue,},});

For transactions that are already submitted, use the fee-management helpers:

awaitclient.topUpFees({
txId,value: 1_100n,distribution: {leaderTimeunitsAllocation: 100n,validatorTimeunitsAllocation: 200n,rotations: [0n],},});awaitclient.topUpAndSubmitAppeal({
txId,value: 1_400n,distribution: {appealRounds: 1n,rotations: [0n,0n],},});

topUpFees returns the backend RPC hash. On network backends this is the EVM transaction hash; on Studio/localnet it is the target GenLayer transaction id.

Checking execution results

A transaction can be finalized by consensus but still have a failed execution. Always check txExecutionResult before reading contract state:

import{ExecutionResult,TransactionStatus}from"genlayer-js/types";constreceipt=awaitclient.waitForTransactionReceipt({hash: txHash,status: TransactionStatus.FINALIZED,});if(receipt.txExecutionResultName===ExecutionResult.FINISHED_WITH_RETURN){// Execution succeeded — safe to read stateconstresult=awaitclient.readContract({address: contractAddress,functionName: "get_storage",args: [],});}elseif(receipt.txExecutionResultName===ExecutionResult.FINISHED_WITH_ERROR){// Execution failed — contract state was not modifiedconsole.error("Contract execution failed");}else{// NOT_VOTED — execution hasn't completedconsole.warn("Execution result not yet available");}

Fetching emitted messages and triggered transactions

Transactions can emit messages to other contracts. These messages create new child transactions when processed:

consttx=awaitclient.getTransaction({hash: txHash});// Messages emitted by the contract during executionconsole.log(tx.messages);// [{messageType, recipient, value, data, onAcceptance, saltNonce}, ...]// Child transaction IDs created from those messages (separate call)constchildTxIds=awaitclient.getTriggeredTransactionIds({hash: txHash});console.log(childTxIds);// ["0xabc...", "0xdef..."]

Debugging transaction execution

Use debugTraceTransaction to inspect the full execution trace of a transaction, including return data, errors, and GenVM logs:

consttrace=awaitclient.debugTraceTransaction({hash: txHash,round: 0,// optional, defaults to 0});console.log(trace.result_code);// 0=success, 1=user error, 2=VM errorconsole.log(trace.return_data);// hex-encoded contract return dataconsole.log(trace.stderr);// standard error outputconsole.log(trace.genvm_log);// detailed GenVM execution logs

Using with a wallet provider (MetaMask)

When building a browser dApp, create two clients: one for reads (no wallet needed) and one for writes (signed by the wallet). This follows the standard viem pattern and keeps concerns separated.

import{createClient}from"genlayer-js";import{testnetBradbury}from"genlayer-js/chains";import{TransactionStatus}from"genlayer-js/types";// Read client — talks directly to GenLayer RPC, no wallet neededconstreadClient=createClient({chain: testnetBradbury,});// Write client — signs transactions through the walletconstwriteClient=createClient({chain: testnetBradbury,account: addressas `0x${string}`,// from wallet connectionprovider: window.ethereum,// or from a wallet SDK});// Use readClient for all readsconstresult=awaitreadClient.readContract({address: contractAddress,functionName: "get_storage",args: [],});consttx=awaitreadClient.getTransaction({hash: txHash});// Use writeClient for transactions (MetaMask popup)consttxHash=awaitwriteClient.writeContract({address: contractAddress,functionName: "update_storage",args: ["new_value"],value: BigInt(0),});// Either client can wait for receiptsconstreceipt=awaitreadClient.waitForTransactionReceipt({hash: txHash,status: TransactionStatus.ACCEPTED,});

Switching the wallet to the correct network

When using MetaMask or another browser wallet, the wallet may be connected to a different chain than what your client is configured for. Use client.connect() to switch the wallet to the correct GenLayer network before sending transactions:

import{createClient}from"genlayer-js";import{studionet}from"genlayer-js/chains";constclient=createClient({chain: studionet,account: addressas `0x${string}`,});// Switch MetaMask to the correct chain (adds the network if not present)awaitclient.connect("studionet");// Now transactions will go to the right networkconsttxHash=awaitclient.writeContract({address: contractAddress,functionName: "create_profile",args: ["alice","Hello world"],value: BigInt(0),});

Available networks: "localnet", "studionet", "testnetAsimov", "testnetBradbury".

Note: If the wallet is on the wrong chain when you call writeContract, the SDK will throw a clear error telling you which chain the wallet is on vs. which chain the client expects. Call client.connect() to resolve this.

Staking Operations

The SDK provides staking functionality for validators and delegators on testnet-bradbury (and testnet-asimov).

import{testnetBradbury}from'genlayer-js/chains';import{createClient,createAccount}from"genlayer-js";constaccount=createAccount();constclient=createClient({chain: testnetBradbury,
account,});// Get epoch info (includes timing estimates and inflation data)constepochInfo=awaitclient.getEpochInfo();// {// currentEpoch: 2n,// epochMinDuration: 86400n, // 1 day in seconds// currentEpochStart: Date,// currentEpochEnd: Date | null,// nextEpochEstimate: Date | null,// validatorMinStake: "0.01 GEN",// delegatorMinStake: "42 GEN",// activeValidatorsCount: 6n,// inflation: "1000 GEN", // Total inflation for current epoch// inflationRaw: 1000000000000000000000n,// totalWeight: 500000000000000000000000n, // Total stake weight// totalClaimed: "500 GEN", // Total claimed rewards// }// Get active validatorsconstvalidators=awaitclient.getActiveValidators();// Check if address is a validatorconstisValidator=awaitclient.isValidator("0x...");// Get validator infoconstvalidatorInfo=awaitclient.getValidatorInfo("0x...");// Join as validator (requires account with funds)constresult=awaitclient.validatorJoin({amount: "42000gen"});// Join as delegatorconstdelegateResult=awaitclient.delegatorJoin({validator: "0x...",amount: "42gen",});

🚀 Key Features

  • Client Creation: Easily create and configure a client to connect to GenLayer's network.
  • Transaction Handling: Send and manage transactions on the GenLayer network.
  • Staking: Full staking support for validators and delegators on testnet-bradbury and testnet-asimov.
  • Wallet Integration*: Seamless integration with MetaMask for managing user accounts.
  • Gas Estimation*: Estimate gas fees for executing transactions on GenLayer.

* under development

📖 Documentation

For detailed information on how to use GenLayerJS SDK, please refer to our documentation.

Contributing

We welcome contributions to GenLayerJS SDK! Whether it's new features, improved infrastructure, or better documentation, your input is valuable. Please read our CONTRIBUTING guide for guidelines on how to submit your contributions.

License

This project is licensed under the MIT License - see the LICENSE file for details.

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

Repository files navigation

GenLayerJS

License: MITDiscordTwitterGitHub star chart

👀 About

GenLayerJS SDK is a TypeScript library designed for developers building decentralized applications (Dapps) on the GenLayer protocol. This SDK provides a comprehensive set of tools to interact with the GenLayer network, including client creation, transaction handling, event subscriptions, and more, all while leveraging the power of Viem as the underlying blockchain client.

Prerequisites

Before installing GenLayerJS SDK, ensure you have the following prerequisites installed:

  • Node.js (>= 16.x)
  • npm (>= 7.x)

🛠️ Installation and Usage

To install the GenLayerJS SDK, use the following command:

$ npm install genlayer-js

Here’s how to initialize the client and connect to the GenLayer Simulator:

Reading a Transaction

import{localnet}from'genlayer-js/chains';import{createClient}from"genlayer-js";constclient=createClient({chain: localnet,});consttransactionHash="0x...";consttransaction=awaitclient.getTransaction({hash: transactionHash})

Waiting for Transaction Receipt

import{localnet}from'genlayer-js/chains';import{createClient}from"genlayer-js";import{TransactionStatus}from"genlayer-js/types";constclient=createClient({chain: localnet,});// Get simplified receipt (default - removes binary data, keeps execution results)constreceipt=awaitclient.waitForTransactionReceipt({hash: "0x...",status: TransactionStatus.FINALIZED,fullTransaction: false// Default - simplified for readability});// Get complete receipt with all fieldsconstfullReceipt=awaitclient.waitForTransactionReceipt({hash: "0x...",status: TransactionStatus.FINALIZED,fullTransaction: true// Complete receipt with all internal data});

Reading a contract

import{localnet}from'genlayer-js/chains';import{createClient}from"genlayer-js";constclient=createClient({chain: localnet,});constresult=awaitclient.readContract({// account: account, Account is optional when reading from contractsaddress: contractAddress,functionName: 'get_complete_storage',args: []stateStatus: "accepted",})

Writing a transaction

import{localnet}from'genlayer-js/chains';import{createClient,createAccount}from"genlayer-js";constclient=createClient({network: localnet,});constaccount=createAccount();consttransactionHash=awaitclient.writeContract({account: account,// using this account for this transactionaddress: contractAddress,functionName: 'account',args: ['new_storage'],value: 0,// value is optional, if you want to send some native token to the contract});constreceipt=awaitclient.waitForTransactionReceipt({hash: txHash,status: TransactionStatus.FINALIZED,// or ACCEPTEDfullTransaction: false// False by default - returns simplified receipt for better readability})

Fee presets for transactions

Apps can build a trusted fee preset once they know the transaction shape, then submit the same preset with the transaction. The user may still override these values in wallet or app UI before signing.

constestimate=awaitclient.estimateTransactionFees({leaderTimeunitsAllocation: 100n,validatorTimeunitsAllocation: 200n,rotations: [0n],});consttxHash=awaitclient.writeContract({
account,address: contractAddress,functionName: "update_storage",args: ["new_storage"],fees: {distribution: estimate.distribution,feeValue: estimate.feeValue,},});

If fees.distribution is provided without feeValue, the SDK derives the fee deposit from FeeManager on network backends, or from sim_getFeeConfig on Studio. Use messageAllocations with estimateTransactionFees for transactions that can emit funded messages.

Use the SDK call-key helpers when targeting a specific emitted message. Internal messages are keyed by the GenVM method name; external EVM messages are keyed by the first 4 bytes of the calldata selector.

import{MessageType,deriveExternalMessageCallKey,deriveInternalMessageCallKey,encodeExternalMessageFeeParams,encodeInternalMessageFeeParams,}from"genlayer-js";constestimate=awaitclient.estimateTransactionFees({messageAllocations: [{messageType: MessageType.Internal,recipient: childContractAddress,callKey: deriveInternalMessageCallKey("settle_campaign"),budget: 700_000n,feeParams: encodeInternalMessageFeeParams({leaderTimeunitsAllocation: 100n,validatorTimeunitsAllocation: 200n,}),},{messageType: MessageType.External,recipient: tokenAddress,callKey: deriveExternalMessageCallKey("0xa9059cbb"),budget: 210_000n,feeParams: encodeExternalMessageFeeParams({gasLimit: 21_000n,maxGasPrice: 10n,}),},],});

You can also pass the same preset to Studio/localnet simulation. On Studio, includeReceipt uses sim_call so the returned object includes the GenVM receipt and fee accounting report:

constrecommended=awaitclient.estimateTransactionFeesForWrite({
account,address: contractAddress,functionName: "update_storage",args: ["new_storage"],});awaitclient.writeContract({
account,address: contractAddress,functionName: "update_storage",args: ["new_storage"],fees: {distribution: recommended.distribution,messageAllocations: recommended.messageAllocations,feeValue: recommended.feeValue,},});

For tests or tools that need to inspect the raw simulation, use the explicit two-step flow:

constsimulation=awaitclient.simulateWriteContract({
account,address: contractAddress,functionName: "update_storage",args: ["new_storage"],fees: {distribution: estimate.distribution,feeValue: estimate.feeValue,},includeReceipt: true,});console.log(simulation.feeAccounting);constrecommended=awaitclient.estimateTransactionFeesFromSimulation({
simulation,});awaitclient.writeContract({
account,address: contractAddress,functionName: "update_storage",args: ["new_storage"],fees: {distribution: recommended.distribution,messageAllocations: recommended.messageAllocations,feeValue: recommended.feeValue,},});

For transactions that are already submitted, use the fee-management helpers:

awaitclient.topUpFees({
txId,value: 1_100n,distribution: {leaderTimeunitsAllocation: 100n,validatorTimeunitsAllocation: 200n,rotations: [0n],},});awaitclient.topUpAndSubmitAppeal({
txId,value: 1_400n,distribution: {appealRounds: 1n,rotations: [0n,0n],},});

topUpFees returns the backend RPC hash. On network backends this is the EVM transaction hash; on Studio/localnet it is the target GenLayer transaction id.

Checking execution results

A transaction can be finalized by consensus but still have a failed execution. Always check txExecutionResult before reading contract state:

import{ExecutionResult,TransactionStatus}from"genlayer-js/types";constreceipt=awaitclient.waitForTransactionReceipt({hash: txHash,status: TransactionStatus.FINALIZED,});if(receipt.txExecutionResultName===ExecutionResult.FINISHED_WITH_RETURN){// Execution succeeded — safe to read stateconstresult=awaitclient.readContract({address: contractAddress,functionName: "get_storage",args: [],});}elseif(receipt.txExecutionResultName===ExecutionResult.FINISHED_WITH_ERROR){// Execution failed — contract state was not modifiedconsole.error("Contract execution failed");}else{// NOT_VOTED — execution hasn't completedconsole.warn("Execution result not yet available");}

Fetching emitted messages and triggered transactions

Transactions can emit messages to other contracts. These messages create new child transactions when processed:

consttx=awaitclient.getTransaction({hash: txHash});// Messages emitted by the contract during executionconsole.log(tx.messages);// [{messageType, recipient, value, data, onAcceptance, saltNonce}, ...]// Child transaction IDs created from those messages (separate call)constchildTxIds=awaitclient.getTriggeredTransactionIds({hash: txHash});console.log(childTxIds);// ["0xabc...", "0xdef..."]

Debugging transaction execution

Use debugTraceTransaction to inspect the full execution trace of a transaction, including return data, errors, and GenVM logs:

consttrace=awaitclient.debugTraceTransaction({hash: txHash,round: 0,// optional, defaults to 0});console.log(trace.result_code);// 0=success, 1=user error, 2=VM errorconsole.log(trace.return_data);// hex-encoded contract return dataconsole.log(trace.stderr);// standard error outputconsole.log(trace.genvm_log);// detailed GenVM execution logs

Using with a wallet provider (MetaMask)

When building a browser dApp, create two clients: one for reads (no wallet needed) and one for writes (signed by the wallet). This follows the standard viem pattern and keeps concerns separated.

import{createClient}from"genlayer-js";import{testnetBradbury}from"genlayer-js/chains";import{TransactionStatus}from"genlayer-js/types";// Read client — talks directly to GenLayer RPC, no wallet neededconstreadClient=createClient({chain: testnetBradbury,});// Write client — signs transactions through the walletconstwriteClient=createClient({chain: testnetBradbury,account: addressas `0x${string}`,// from wallet connectionprovider: window.ethereum,// or from a wallet SDK});// Use readClient for all readsconstresult=awaitreadClient.readContract({address: contractAddress,functionName: "get_storage",args: [],});consttx=awaitreadClient.getTransaction({hash: txHash});// Use writeClient for transactions (MetaMask popup)consttxHash=awaitwriteClient.writeContract({address: contractAddress,functionName: "update_storage",args: ["new_value"],value: BigInt(0),});// Either client can wait for receiptsconstreceipt=awaitreadClient.waitForTransactionReceipt({hash: txHash,status: TransactionStatus.ACCEPTED,});

Switching the wallet to the correct network

When using MetaMask or another browser wallet, the wallet may be connected to a different chain than what your client is configured for. Use client.connect() to switch the wallet to the correct GenLayer network before sending transactions:

import{createClient}from"genlayer-js";import{studionet}from"genlayer-js/chains";constclient=createClient({chain: studionet,account: addressas `0x${string}`,});// Switch MetaMask to the correct chain (adds the network if not present)awaitclient.connect("studionet");// Now transactions will go to the right networkconsttxHash=awaitclient.writeContract({address: contractAddress,functionName: "create_profile",args: ["alice","Hello world"],value: BigInt(0),});

Available networks: "localnet", "studionet", "testnetAsimov", "testnetBradbury".

Note: If the wallet is on the wrong chain when you call writeContract, the SDK will throw a clear error telling you which chain the wallet is on vs. which chain the client expects. Call client.connect() to resolve this.

Staking Operations

The SDK provides staking functionality for validators and delegators on testnet-bradbury (and testnet-asimov).

import{testnetBradbury}from'genlayer-js/chains';import{createClient,createAccount}from"genlayer-js";constaccount=createAccount();constclient=createClient({chain: testnetBradbury,
account,});// Get epoch info (includes timing estimates and inflation data)constepochInfo=awaitclient.getEpochInfo();// {// currentEpoch: 2n,// epochMinDuration: 86400n, // 1 day in seconds// currentEpochStart: Date,// currentEpochEnd: Date | null,// nextEpochEstimate: Date | null,// validatorMinStake: "0.01 GEN",// delegatorMinStake: "42 GEN",// activeValidatorsCount: 6n,// inflation: "1000 GEN", // Total inflation for current epoch// inflationRaw: 1000000000000000000000n,// totalWeight: 500000000000000000000000n, // Total stake weight// totalClaimed: "500 GEN", // Total claimed rewards// }// Get active validatorsconstvalidators=awaitclient.getActiveValidators();// Check if address is a validatorconstisValidator=awaitclient.isValidator("0x...");// Get validator infoconstvalidatorInfo=awaitclient.getValidatorInfo("0x...");// Join as validator (requires account with funds)constresult=awaitclient.validatorJoin({amount: "42000gen"});// Join as delegatorconstdelegateResult=awaitclient.delegatorJoin({validator: "0x...",amount: "42gen",});

🚀 Key Features

  • Client Creation: Easily create and configure a client to connect to GenLayer's network.
  • Transaction Handling: Send and manage transactions on the GenLayer network.
  • Staking: Full staking support for validators and delegators on testnet-bradbury and testnet-asimov.
  • Wallet Integration*: Seamless integration with MetaMask for managing user accounts.
  • Gas Estimation*: Estimate gas fees for executing transactions on GenLayer.

* under development

📖 Documentation

For detailed information on how to use GenLayerJS SDK, please refer to our documentation.

Contributing

We welcome contributions to GenLayerJS SDK! Whether it's new features, improved infrastructure, or better documentation, your input is valuable. Please read our CONTRIBUTING guide for guidelines on how to submit your contributions.

License

This project is licensed under the MIT License - see the LICENSE file for details.

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

GenLayerJS

License: MITDiscordTwitterGitHub star chart

👀 About

GenLayerJS SDK is a TypeScript library designed for developers building decentralized applications (Dapps) on the GenLayer protocol. This SDK provides a comprehensive set of tools to interact with the GenLayer network, including client creation, transaction handling, event subscriptions, and more, all while leveraging the power of Viem as the underlying blockchain client.

Prerequisites

Before installing GenLayerJS SDK, ensure you have the following prerequisites installed:

  • Node.js (>= 16.x)
  • npm (>= 7.x)

🛠️ Installation and Usage

To install the GenLayerJS SDK, use the following command:

$ npm install genlayer-js

Here’s how to initialize the client and connect to the GenLayer Simulator:

Reading a Transaction

import{localnet}from'genlayer-js/chains';import{createClient}from"genlayer-js";constclient=createClient({chain: localnet,});consttransactionHash="0x...";consttransaction=awaitclient.getTransaction({hash: transactionHash})

Waiting for Transaction Receipt

import{localnet}from'genlayer-js/chains';import{createClient}from"genlayer-js";import{TransactionStatus}from"genlayer-js/types";constclient=createClient({chain: localnet,});// Get simplified receipt (default - removes binary data, keeps execution results)constreceipt=awaitclient.waitForTransactionReceipt({hash: "0x...",status: TransactionStatus.FINALIZED,fullTransaction: false// Default - simplified for readability});// Get complete receipt with all fieldsconstfullReceipt=awaitclient.waitForTransactionReceipt({hash: "0x...",status: TransactionStatus.FINALIZED,fullTransaction: true// Complete receipt with all internal data});

Reading a contract

import{localnet}from'genlayer-js/chains';import{createClient}from"genlayer-js";constclient=createClient({chain: localnet,});constresult=awaitclient.readContract({// account: account, Account is optional when reading from contractsaddress: contractAddress,functionName: 'get_complete_storage',args: []stateStatus: "accepted",})

Writing a transaction

import{localnet}from'genlayer-js/chains';import{createClient,createAccount}from"genlayer-js";constclient=createClient({network: localnet,});constaccount=createAccount();consttransactionHash=awaitclient.writeContract({account: account,// using this account for this transactionaddress: contractAddress,functionName: 'account',args: ['new_storage'],value: 0,// value is optional, if you want to send some native token to the contract});constreceipt=awaitclient.waitForTransactionReceipt({hash: txHash,status: TransactionStatus.FINALIZED,// or ACCEPTEDfullTransaction: false// False by default - returns simplified receipt for better readability})

Fee presets for transactions

Apps can build a trusted fee preset once they know the transaction shape, then submit the same preset with the transaction. The user may still override these values in wallet or app UI before signing.

constestimate=awaitclient.estimateTransactionFees({leaderTimeunitsAllocation: 100n,validatorTimeunitsAllocation: 200n,rotations: [0n],});consttxHash=awaitclient.writeContract({
account,address: contractAddress,functionName: "update_storage",args: ["new_storage"],fees: {distribution: estimate.distribution,feeValue: estimate.feeValue,},});

If fees.distribution is provided without feeValue, the SDK derives the fee deposit from FeeManager on network backends, or from sim_getFeeConfig on Studio. Use messageAllocations with estimateTransactionFees for transactions that can emit funded messages.

Use the SDK call-key helpers when targeting a specific emitted message. Internal messages are keyed by the GenVM method name; external EVM messages are keyed by the first 4 bytes of the calldata selector.

import{MessageType,deriveExternalMessageCallKey,deriveInternalMessageCallKey,encodeExternalMessageFeeParams,encodeInternalMessageFeeParams,}from"genlayer-js";constestimate=awaitclient.estimateTransactionFees({messageAllocations: [{messageType: MessageType.Internal,recipient: childContractAddress,callKey: deriveInternalMessageCallKey("settle_campaign"),budget: 700_000n,feeParams: encodeInternalMessageFeeParams({leaderTimeunitsAllocation: 100n,validatorTimeunitsAllocation: 200n,}),},{messageType: MessageType.External,recipient: tokenAddress,callKey: deriveExternalMessageCallKey("0xa9059cbb"),budget: 210_000n,feeParams: encodeExternalMessageFeeParams({gasLimit: 21_000n,maxGasPrice: 10n,}),},],});

You can also pass the same preset to Studio/localnet simulation. On Studio, includeReceipt uses sim_call so the returned object includes the GenVM receipt and fee accounting report:

constrecommended=awaitclient.estimateTransactionFeesForWrite({
account,address: contractAddress,functionName: "update_storage",args: ["new_storage"],});awaitclient.writeContract({
account,address: contractAddress,functionName: "update_storage",args: ["new_storage"],fees: {distribution: recommended.distribution,messageAllocations: recommended.messageAllocations,feeValue: recommended.feeValue,},});

For tests or tools that need to inspect the raw simulation, use the explicit two-step flow:

constsimulation=awaitclient.simulateWriteContract({
account,address: contractAddress,functionName: "update_storage",args: ["new_storage"],fees: {distribution: estimate.distribution,feeValue: estimate.feeValue,},includeReceipt: true,});console.log(simulation.feeAccounting);constrecommended=awaitclient.estimateTransactionFeesFromSimulation({
simulation,});awaitclient.writeContract({
account,address: contractAddress,functionName: "update_storage",args: ["new_storage"],fees: {distribution: recommended.distribution,messageAllocations: recommended.messageAllocations,feeValue: recommended.feeValue,},});

For transactions that are already submitted, use the fee-management helpers:

awaitclient.topUpFees({
txId,value: 1_100n,distribution: {leaderTimeunitsAllocation: 100n,validatorTimeunitsAllocation: 200n,rotations: [0n],},});awaitclient.topUpAndSubmitAppeal({
txId,value: 1_400n,distribution: {appealRounds: 1n,rotations: [0n,0n],},});

topUpFees returns the backend RPC hash. On network backends this is the EVM transaction hash; on Studio/localnet it is the target GenLayer transaction id.

Checking execution results

A transaction can be finalized by consensus but still have a failed execution. Always check txExecutionResult before reading contract state:

import{ExecutionResult,TransactionStatus}from"genlayer-js/types";constreceipt=awaitclient.waitForTransactionReceipt({hash: txHash,status: TransactionStatus.FINALIZED,});if(receipt.txExecutionResultName===ExecutionResult.FINISHED_WITH_RETURN){// Execution succeeded — safe to read stateconstresult=awaitclient.readContract({address: contractAddress,functionName: "get_storage",args: [],});}elseif(receipt.txExecutionResultName===ExecutionResult.FINISHED_WITH_ERROR){// Execution failed — contract state was not modifiedconsole.error("Contract execution failed");}else{// NOT_VOTED — execution hasn't completedconsole.warn("Execution result not yet available");}

Fetching emitted messages and triggered transactions

Transactions can emit messages to other contracts. These messages create new child transactions when processed:

consttx=awaitclient.getTransaction({hash: txHash});// Messages emitted by the contract during executionconsole.log(tx.messages);// [{messageType, recipient, value, data, onAcceptance, saltNonce}, ...]// Child transaction IDs created from those messages (separate call)constchildTxIds=awaitclient.getTriggeredTransactionIds({hash: txHash});console.log(childTxIds);// ["0xabc...", "0xdef..."]

Debugging transaction execution

Use debugTraceTransaction to inspect the full execution trace of a transaction, including return data, errors, and GenVM logs:

consttrace=awaitclient.debugTraceTransaction({hash: txHash,round: 0,// optional, defaults to 0});console.log(trace.result_code);// 0=success, 1=user error, 2=VM errorconsole.log(trace.return_data);// hex-encoded contract return dataconsole.log(trace.stderr);// standard error outputconsole.log(trace.genvm_log);// detailed GenVM execution logs

Using with a wallet provider (MetaMask)

When building a browser dApp, create two clients: one for reads (no wallet needed) and one for writes (signed by the wallet). This follows the standard viem pattern and keeps concerns separated.

import{createClient}from"genlayer-js";import{testnetBradbury}from"genlayer-js/chains";import{TransactionStatus}from"genlayer-js/types";// Read client — talks directly to GenLayer RPC, no wallet neededconstreadClient=createClient({chain: testnetBradbury,});// Write client — signs transactions through the walletconstwriteClient=createClient({chain: testnetBradbury,account: addressas `0x${string}`,// from wallet connectionprovider: window.ethereum,// or from a wallet SDK});// Use readClient for all readsconstresult=awaitreadClient.readContract({address: contractAddress,functionName: "get_storage",args: [],});consttx=awaitreadClient.getTransaction({hash: txHash});// Use writeClient for transactions (MetaMask popup)consttxHash=awaitwriteClient.writeContract({address: contractAddress,functionName: "update_storage",args: ["new_value"],value: BigInt(0),});// Either client can wait for receiptsconstreceipt=awaitreadClient.waitForTransactionReceipt({hash: txHash,status: TransactionStatus.ACCEPTED,});

Switching the wallet to the correct network

When using MetaMask or another browser wallet, the wallet may be connected to a different chain than what your client is configured for. Use client.connect() to switch the wallet to the correct GenLayer network before sending transactions:

import{createClient}from"genlayer-js";import{studionet}from"genlayer-js/chains";constclient=createClient({chain: studionet,account: addressas `0x${string}`,});// Switch MetaMask to the correct chain (adds the network if not present)awaitclient.connect("studionet");// Now transactions will go to the right networkconsttxHash=awaitclient.writeContract({address: contractAddress,functionName: "create_profile",args: ["alice","Hello world"],value: BigInt(0),});

Available networks: "localnet", "studionet", "testnetAsimov", "testnetBradbury".

Note: If the wallet is on the wrong chain when you call writeContract, the SDK will throw a clear error telling you which chain the wallet is on vs. which chain the client expects. Call client.connect() to resolve this.

Staking Operations

The SDK provides staking functionality for validators and delegators on testnet-bradbury (and testnet-asimov).

import{testnetBradbury}from'genlayer-js/chains';import{createClient,createAccount}from"genlayer-js";constaccount=createAccount();constclient=createClient({chain: testnetBradbury,
account,});// Get epoch info (includes timing estimates and inflation data)constepochInfo=awaitclient.getEpochInfo();// {// currentEpoch: 2n,// epochMinDuration: 86400n, // 1 day in seconds// currentEpochStart: Date,// currentEpochEnd: Date | null,// nextEpochEstimate: Date | null,// validatorMinStake: "0.01 GEN",// delegatorMinStake: "42 GEN",// activeValidatorsCount: 6n,// inflation: "1000 GEN", // Total inflation for current epoch// inflationRaw: 1000000000000000000000n,// totalWeight: 500000000000000000000000n, // Total stake weight// totalClaimed: "500 GEN", // Total claimed rewards// }// Get active validatorsconstvalidators=awaitclient.getActiveValidators();// Check if address is a validatorconstisValidator=awaitclient.isValidator("0x...");// Get validator infoconstvalidatorInfo=awaitclient.getValidatorInfo("0x...");// Join as validator (requires account with funds)constresult=awaitclient.validatorJoin({amount: "42000gen"});// Join as delegatorconstdelegateResult=awaitclient.delegatorJoin({validator: "0x...",amount: "42gen",});

🚀 Key Features

  • Client Creation: Easily create and configure a client to connect to GenLayer's network.
  • Transaction Handling: Send and manage transactions on the GenLayer network.
  • Staking: Full staking support for validators and delegators on testnet-bradbury and testnet-asimov.
  • Wallet Integration*: Seamless integration with MetaMask for managing user accounts.
  • Gas Estimation*: Estimate gas fees for executing transactions on GenLayer.

* under development

📖 Documentation

For detailed information on how to use GenLayerJS SDK, please refer to our documentation.

Contributing

We welcome contributions to GenLayerJS SDK! Whether it's new features, improved infrastructure, or better documentation, your input is valuable. Please read our CONTRIBUTING guide for guidelines on how to submit your contributions.

License

This project is licensed under the MIT License - see the LICENSE file for details.

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

GenLayerJS

License: MITDiscordTwitterGitHub star chart

👀 About

GenLayerJS SDK is a TypeScript library designed for developers building decentralized applications (Dapps) on the GenLayer protocol. This SDK provides a comprehensive set of tools to interact with the GenLayer network, including client creation, transaction handling, event subscriptions, and more, all while leveraging the power of Viem as the underlying blockchain client.

Prerequisites

Before installing GenLayerJS SDK, ensure you have the following prerequisites installed:

  • Node.js (>= 16.x)
  • npm (>= 7.x)

🛠️ Installation and Usage

To install the GenLayerJS SDK, use the following command:

$ npm install genlayer-js

Here’s how to initialize the client and connect to the GenLayer Simulator:

Reading a Transaction

import{localnet}from'genlayer-js/chains';import{createClient}from"genlayer-js";constclient=createClient({chain: localnet,});consttransactionHash="0x...";consttransaction=awaitclient.getTransaction({hash: transactionHash})

Waiting for Transaction Receipt

import{localnet}from'genlayer-js/chains';import{createClient}from"genlayer-js";import{TransactionStatus}from"genlayer-js/types";constclient=createClient({chain: localnet,});// Get simplified receipt (default - removes binary data, keeps execution results)constreceipt=awaitclient.waitForTransactionReceipt({hash: "0x...",status: TransactionStatus.FINALIZED,fullTransaction: false// Default - simplified for readability});// Get complete receipt with all fieldsconstfullReceipt=awaitclient.waitForTransactionReceipt({hash: "0x...",status: TransactionStatus.FINALIZED,fullTransaction: true// Complete receipt with all internal data});

Reading a contract

import{localnet}from'genlayer-js/chains';import{createClient}from"genlayer-js";constclient=createClient({chain: localnet,});constresult=awaitclient.readContract({// account: account, Account is optional when reading from contractsaddress: contractAddress,functionName: 'get_complete_storage',args: []stateStatus: "accepted",})

Writing a transaction

import{localnet}from'genlayer-js/chains';import{createClient,createAccount}from"genlayer-js";constclient=createClient({network: localnet,});constaccount=createAccount();consttransactionHash=awaitclient.writeContract({account: account,// using this account for this transactionaddress: contractAddress,functionName: 'account',args: ['new_storage'],value: 0,// value is optional, if you want to send some native token to the contract});constreceipt=awaitclient.waitForTransactionReceipt({hash: txHash,status: TransactionStatus.FINALIZED,// or ACCEPTEDfullTransaction: false// False by default - returns simplified receipt for better readability})

Fee presets for transactions

Apps can build a trusted fee preset once they know the transaction shape, then submit the same preset with the transaction. The user may still override these values in wallet or app UI before signing.

constestimate=awaitclient.estimateTransactionFees({leaderTimeunitsAllocation: 100n,validatorTimeunitsAllocation: 200n,rotations: [0n],});consttxHash=awaitclient.writeContract({
account,address: contractAddress,functionName: "update_storage",args: ["new_storage"],fees: {distribution: estimate.distribution,feeValue: estimate.feeValue,},});

If fees.distribution is provided without feeValue, the SDK derives the fee deposit from FeeManager on network backends, or from sim_getFeeConfig on Studio. Use messageAllocations with estimateTransactionFees for transactions that can emit funded messages.

Use the SDK call-key helpers when targeting a specific emitted message. Internal messages are keyed by the GenVM method name; external EVM messages are keyed by the first 4 bytes of the calldata selector.

import{MessageType,deriveExternalMessageCallKey,deriveInternalMessageCallKey,encodeExternalMessageFeeParams,encodeInternalMessageFeeParams,}from"genlayer-js";constestimate=awaitclient.estimateTransactionFees({messageAllocations: [{messageType: MessageType.Internal,recipient: childContractAddress,callKey: deriveInternalMessageCallKey("settle_campaign"),budget: 700_000n,feeParams: encodeInternalMessageFeeParams({leaderTimeunitsAllocation: 100n,validatorTimeunitsAllocation: 200n,}),},{messageType: MessageType.External,recipient: tokenAddress,callKey: deriveExternalMessageCallKey("0xa9059cbb"),budget: 210_000n,feeParams: encodeExternalMessageFeeParams({gasLimit: 21_000n,maxGasPrice: 10n,}),},],});

You can also pass the same preset to Studio/localnet simulation. On Studio, includeReceipt uses sim_call so the returned object includes the GenVM receipt and fee accounting report:

constrecommended=awaitclient.estimateTransactionFeesForWrite({
account,address: contractAddress,functionName: "update_storage",args: ["new_storage"],});awaitclient.writeContract({
account,address: contractAddress,functionName: "update_storage",args: ["new_storage"],fees: {distribution: recommended.distribution,messageAllocations: recommended.messageAllocations,feeValue: recommended.feeValue,},});

For tests or tools that need to inspect the raw simulation, use the explicit two-step flow:

constsimulation=awaitclient.simulateWriteContract({
account,address: contractAddress,functionName: "update_storage",args: ["new_storage"],fees: {distribution: estimate.distribution,feeValue: estimate.feeValue,},includeReceipt: true,});console.log(simulation.feeAccounting);constrecommended=awaitclient.estimateTransactionFeesFromSimulation({
simulation,});awaitclient.writeContract({
account,address: contractAddress,functionName: "update_storage",args: ["new_storage"],fees: {distribution: recommended.distribution,messageAllocations: recommended.messageAllocations,feeValue: recommended.feeValue,},});

For transactions that are already submitted, use the fee-management helpers:

awaitclient.topUpFees({
txId,value: 1_100n,distribution: {leaderTimeunitsAllocation: 100n,validatorTimeunitsAllocation: 200n,rotations: [0n],},});awaitclient.topUpAndSubmitAppeal({
txId,value: 1_400n,distribution: {appealRounds: 1n,rotations: [0n,0n],},});

topUpFees returns the backend RPC hash. On network backends this is the EVM transaction hash; on Studio/localnet it is the target GenLayer transaction id.

Checking execution results

A transaction can be finalized by consensus but still have a failed execution. Always check txExecutionResult before reading contract state:

import{ExecutionResult,TransactionStatus}from"genlayer-js/types";constreceipt=awaitclient.waitForTransactionReceipt({hash: txHash,status: TransactionStatus.FINALIZED,});if(receipt.txExecutionResultName===ExecutionResult.FINISHED_WITH_RETURN){// Execution succeeded — safe to read stateconstresult=awaitclient.readContract({address: contractAddress,functionName: "get_storage",args: [],});}elseif(receipt.txExecutionResultName===ExecutionResult.FINISHED_WITH_ERROR){// Execution failed — contract state was not modifiedconsole.error("Contract execution failed");}else{// NOT_VOTED — execution hasn't completedconsole.warn("Execution result not yet available");}

Fetching emitted messages and triggered transactions

Transactions can emit messages to other contracts. These messages create new child transactions when processed:

consttx=awaitclient.getTransaction({hash: txHash});// Messages emitted by the contract during executionconsole.log(tx.messages);// [{messageType, recipient, value, data, onAcceptance, saltNonce}, ...]// Child transaction IDs created from those messages (separate call)constchildTxIds=awaitclient.getTriggeredTransactionIds({hash: txHash});console.log(childTxIds);// ["0xabc...", "0xdef..."]

Debugging transaction execution

Use debugTraceTransaction to inspect the full execution trace of a transaction, including return data, errors, and GenVM logs:

consttrace=awaitclient.debugTraceTransaction({hash: txHash,round: 0,// optional, defaults to 0});console.log(trace.result_code);// 0=success, 1=user error, 2=VM errorconsole.log(trace.return_data);// hex-encoded contract return dataconsole.log(trace.stderr);// standard error outputconsole.log(trace.genvm_log);// detailed GenVM execution logs

Using with a wallet provider (MetaMask)

When building a browser dApp, create two clients: one for reads (no wallet needed) and one for writes (signed by the wallet). This follows the standard viem pattern and keeps concerns separated.

import{createClient}from"genlayer-js";import{testnetBradbury}from"genlayer-js/chains";import{TransactionStatus}from"genlayer-js/types";// Read client — talks directly to GenLayer RPC, no wallet neededconstreadClient=createClient({chain: testnetBradbury,});// Write client — signs transactions through the walletconstwriteClient=createClient({chain: testnetBradbury,account: addressas `0x${string}`,// from wallet connectionprovider: window.ethereum,// or from a wallet SDK});// Use readClient for all readsconstresult=awaitreadClient.readContract({address: contractAddress,functionName: "get_storage",args: [],});consttx=awaitreadClient.getTransaction({hash: txHash});// Use writeClient for transactions (MetaMask popup)consttxHash=awaitwriteClient.writeContract({address: contractAddress,functionName: "update_storage",args: ["new_value"],value: BigInt(0),});// Either client can wait for receiptsconstreceipt=awaitreadClient.waitForTransactionReceipt({hash: txHash,status: TransactionStatus.ACCEPTED,});

Switching the wallet to the correct network

When using MetaMask or another browser wallet, the wallet may be connected to a different chain than what your client is configured for. Use client.connect() to switch the wallet to the correct GenLayer network before sending transactions:

import{createClient}from"genlayer-js";import{studionet}from"genlayer-js/chains";constclient=createClient({chain: studionet,account: addressas `0x${string}`,});// Switch MetaMask to the correct chain (adds the network if not present)awaitclient.connect("studionet");// Now transactions will go to the right networkconsttxHash=awaitclient.writeContract({address: contractAddress,functionName: "create_profile",args: ["alice","Hello world"],value: BigInt(0),});

Available networks: "localnet", "studionet", "testnetAsimov", "testnetBradbury".

Note: If the wallet is on the wrong chain when you call writeContract, the SDK will throw a clear error telling you which chain the wallet is on vs. which chain the client expects. Call client.connect() to resolve this.

Staking Operations

The SDK provides staking functionality for validators and delegators on testnet-bradbury (and testnet-asimov).

import{testnetBradbury}from'genlayer-js/chains';import{createClient,createAccount}from"genlayer-js";constaccount=createAccount();constclient=createClient({chain: testnetBradbury,
account,});// Get epoch info (includes timing estimates and inflation data)constepochInfo=awaitclient.getEpochInfo();// {// currentEpoch: 2n,// epochMinDuration: 86400n, // 1 day in seconds// currentEpochStart: Date,// currentEpochEnd: Date | null,// nextEpochEstimate: Date | null,// validatorMinStake: "0.01 GEN",// delegatorMinStake: "42 GEN",// activeValidatorsCount: 6n,// inflation: "1000 GEN", // Total inflation for current epoch// inflationRaw: 1000000000000000000000n,// totalWeight: 500000000000000000000000n, // Total stake weight// totalClaimed: "500 GEN", // Total claimed rewards// }// Get active validatorsconstvalidators=awaitclient.getActiveValidators();// Check if address is a validatorconstisValidator=awaitclient.isValidator("0x...");// Get validator infoconstvalidatorInfo=awaitclient.getValidatorInfo("0x...");// Join as validator (requires account with funds)constresult=awaitclient.validatorJoin({amount: "42000gen"});// Join as delegatorconstdelegateResult=awaitclient.delegatorJoin({validator: "0x...",amount: "42gen",});

🚀 Key Features

  • Client Creation: Easily create and configure a client to connect to GenLayer's network.
  • Transaction Handling: Send and manage transactions on the GenLayer network.
  • Staking: Full staking support for validators and delegators on testnet-bradbury and testnet-asimov.
  • Wallet Integration*: Seamless integration with MetaMask for managing user accounts.
  • Gas Estimation*: Estimate gas fees for executing transactions on GenLayer.

* under development

📖 Documentation

For detailed information on how to use GenLayerJS SDK, please refer to our documentation.

Contributing

We welcome contributions to GenLayerJS SDK! Whether it's new features, improved infrastructure, or better documentation, your input is valuable. Please read our CONTRIBUTING guide for guidelines on how to submit your contributions.

License

This project is licensed under the MIT License - see the LICENSE file for details.

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

Repository files navigation

GenLayerJS

License: MITDiscordTwitterGitHub star chart

👀 About

GenLayerJS SDK is a TypeScript library designed for developers building decentralized applications (Dapps) on the GenLayer protocol. This SDK provides a comprehensive set of tools to interact with the GenLayer network, including client creation, transaction handling, event subscriptions, and more, all while leveraging the power of Viem as the underlying blockchain client.

Prerequisites

Before installing GenLayerJS SDK, ensure you have the following prerequisites installed:

  • Node.js (>= 16.x)
  • npm (>= 7.x)

🛠️ Installation and Usage

To install the GenLayerJS SDK, use the following command:

$ npm install genlayer-js

Here’s how to initialize the client and connect to the GenLayer Simulator:

Reading a Transaction

import{localnet}from'genlayer-js/chains';import{createClient}from"genlayer-js";constclient=createClient({chain: localnet,});consttransactionHash="0x...";consttransaction=awaitclient.getTransaction({hash: transactionHash})

Waiting for Transaction Receipt

import{localnet}from'genlayer-js/chains';import{createClient}from"genlayer-js";import{TransactionStatus}from"genlayer-js/types";constclient=createClient({chain: localnet,});// Get simplified receipt (default - removes binary data, keeps execution results)constreceipt=awaitclient.waitForTransactionReceipt({hash: "0x...",status: TransactionStatus.FINALIZED,fullTransaction: false// Default - simplified for readability});// Get complete receipt with all fieldsconstfullReceipt=awaitclient.waitForTransactionReceipt({hash: "0x...",status: TransactionStatus.FINALIZED,fullTransaction: true// Complete receipt with all internal data});

Reading a contract

import{localnet}from'genlayer-js/chains';import{createClient}from"genlayer-js";constclient=createClient({chain: localnet,});constresult=awaitclient.readContract({// account: account, Account is optional when reading from contractsaddress: contractAddress,functionName: 'get_complete_storage',args: []stateStatus: "accepted",})

Writing a transaction

import{localnet}from'genlayer-js/chains';import{createClient,createAccount}from"genlayer-js";constclient=createClient({network: localnet,});constaccount=createAccount();consttransactionHash=awaitclient.writeContract({account: account,// using this account for this transactionaddress: contractAddress,functionName: 'account',args: ['new_storage'],value: 0,// value is optional, if you want to send some native token to the contract});constreceipt=awaitclient.waitForTransactionReceipt({hash: txHash,status: TransactionStatus.FINALIZED,// or ACCEPTEDfullTransaction: false// False by default - returns simplified receipt for better readability})

Fee presets for transactions

Apps can build a trusted fee preset once they know the transaction shape, then submit the same preset with the transaction. The user may still override these values in wallet or app UI before signing.

constestimate=awaitclient.estimateTransactionFees({leaderTimeunitsAllocation: 100n,validatorTimeunitsAllocation: 200n,rotations: [0n],});consttxHash=awaitclient.writeContract({
account,address: contractAddress,functionName: "update_storage",args: ["new_storage"],fees: {distribution: estimate.distribution,feeValue: estimate.feeValue,},});

If fees.distribution is provided without feeValue, the SDK derives the fee deposit from FeeManager on network backends, or from sim_getFeeConfig on Studio. Use messageAllocations with estimateTransactionFees for transactions that can emit funded messages.

Use the SDK call-key helpers when targeting a specific emitted message. Internal messages are keyed by the GenVM method name; external EVM messages are keyed by the first 4 bytes of the calldata selector.

import{MessageType,deriveExternalMessageCallKey,deriveInternalMessageCallKey,encodeExternalMessageFeeParams,encodeInternalMessageFeeParams,}from"genlayer-js";constestimate=awaitclient.estimateTransactionFees({messageAllocations: [{messageType: MessageType.Internal,recipient: childContractAddress,callKey: deriveInternalMessageCallKey("settle_campaign"),budget: 700_000n,feeParams: encodeInternalMessageFeeParams({leaderTimeunitsAllocation: 100n,validatorTimeunitsAllocation: 200n,}),},{messageType: MessageType.External,recipient: tokenAddress,callKey: deriveExternalMessageCallKey("0xa9059cbb"),budget: 210_000n,feeParams: encodeExternalMessageFeeParams({gasLimit: 21_000n,maxGasPrice: 10n,}),},],});

You can also pass the same preset to Studio/localnet simulation. On Studio, includeReceipt uses sim_call so the returned object includes the GenVM receipt and fee accounting report:

constrecommended=awaitclient.estimateTransactionFeesForWrite({
account,address: contractAddress,functionName: "update_storage",args: ["new_storage"],});awaitclient.writeContract({
account,address: contractAddress,functionName: "update_storage",args: ["new_storage"],fees: {distribution: recommended.distribution,messageAllocations: recommended.messageAllocations,feeValue: recommended.feeValue,},});

For tests or tools that need to inspect the raw simulation, use the explicit two-step flow:

constsimulation=awaitclient.simulateWriteContract({
account,address: contractAddress,functionName: "update_storage",args: ["new_storage"],fees: {distribution: estimate.distribution,feeValue: estimate.feeValue,},includeReceipt: true,});console.log(simulation.feeAccounting);constrecommended=awaitclient.estimateTransactionFeesFromSimulation({
simulation,});awaitclient.writeContract({
account,address: contractAddress,functionName: "update_storage",args: ["new_storage"],fees: {distribution: recommended.distribution,messageAllocations: recommended.messageAllocations,feeValue: recommended.feeValue,},});

For transactions that are already submitted, use the fee-management helpers:

awaitclient.topUpFees({
txId,value: 1_100n,distribution: {leaderTimeunitsAllocation: 100n,validatorTimeunitsAllocation: 200n,rotations: [0n],},});awaitclient.topUpAndSubmitAppeal({
txId,value: 1_400n,distribution: {appealRounds: 1n,rotations: [0n,0n],},});

topUpFees returns the backend RPC hash. On network backends this is the EVM transaction hash; on Studio/localnet it is the target GenLayer transaction id.

Checking execution results

A transaction can be finalized by consensus but still have a failed execution. Always check txExecutionResult before reading contract state:

import{ExecutionResult,TransactionStatus}from"genlayer-js/types";constreceipt=awaitclient.waitForTransactionReceipt({hash: txHash,status: TransactionStatus.FINALIZED,});if(receipt.txExecutionResultName===ExecutionResult.FINISHED_WITH_RETURN){// Execution succeeded — safe to read stateconstresult=awaitclient.readContract({address: contractAddress,functionName: "get_storage",args: [],});}elseif(receipt.txExecutionResultName===ExecutionResult.FINISHED_WITH_ERROR){// Execution failed — contract state was not modifiedconsole.error("Contract execution failed");}else{// NOT_VOTED — execution hasn't completedconsole.warn("Execution result not yet available");}

Fetching emitted messages and triggered transactions

Transactions can emit messages to other contracts. These messages create new child transactions when processed:

consttx=awaitclient.getTransaction({hash: txHash});// Messages emitted by the contract during executionconsole.log(tx.messages);// [{messageType, recipient, value, data, onAcceptance, saltNonce}, ...]// Child transaction IDs created from those messages (separate call)constchildTxIds=awaitclient.getTriggeredTransactionIds({hash: txHash});console.log(childTxIds);// ["0xabc...", "0xdef..."]

Debugging transaction execution

Use debugTraceTransaction to inspect the full execution trace of a transaction, including return data, errors, and GenVM logs:

consttrace=awaitclient.debugTraceTransaction({hash: txHash,round: 0,// optional, defaults to 0});console.log(trace.result_code);// 0=success, 1=user error, 2=VM errorconsole.log(trace.return_data);// hex-encoded contract return dataconsole.log(trace.stderr);// standard error outputconsole.log(trace.genvm_log);// detailed GenVM execution logs

Using with a wallet provider (MetaMask)

When building a browser dApp, create two clients: one for reads (no wallet needed) and one for writes (signed by the wallet). This follows the standard viem pattern and keeps concerns separated.

import{createClient}from"genlayer-js";import{testnetBradbury}from"genlayer-js/chains";import{TransactionStatus}from"genlayer-js/types";// Read client — talks directly to GenLayer RPC, no wallet neededconstreadClient=createClient({chain: testnetBradbury,});// Write client — signs transactions through the walletconstwriteClient=createClient({chain: testnetBradbury,account: addressas `0x${string}`,// from wallet connectionprovider: window.ethereum,// or from a wallet SDK});// Use readClient for all readsconstresult=awaitreadClient.readContract({address: contractAddress,functionName: "get_storage",args: [],});consttx=awaitreadClient.getTransaction({hash: txHash});// Use writeClient for transactions (MetaMask popup)consttxHash=awaitwriteClient.writeContract({address: contractAddress,functionName: "update_storage",args: ["new_value"],value: BigInt(0),});// Either client can wait for receiptsconstreceipt=awaitreadClient.waitForTransactionReceipt({hash: txHash,status: TransactionStatus.ACCEPTED,});

Switching the wallet to the correct network

When using MetaMask or another browser wallet, the wallet may be connected to a different chain than what your client is configured for. Use client.connect() to switch the wallet to the correct GenLayer network before sending transactions:

import{createClient}from"genlayer-js";import{studionet}from"genlayer-js/chains";constclient=createClient({chain: studionet,account: addressas `0x${string}`,});// Switch MetaMask to the correct chain (adds the network if not present)awaitclient.connect("studionet");// Now transactions will go to the right networkconsttxHash=awaitclient.writeContract({address: contractAddress,functionName: "create_profile",args: ["alice","Hello world"],value: BigInt(0),});

Available networks: "localnet", "studionet", "testnetAsimov", "testnetBradbury".

Note: If the wallet is on the wrong chain when you call writeContract, the SDK will throw a clear error telling you which chain the wallet is on vs. which chain the client expects. Call client.connect() to resolve this.

Staking Operations

The SDK provides staking functionality for validators and delegators on testnet-bradbury (and testnet-asimov).

import{testnetBradbury}from'genlayer-js/chains';import{createClient,createAccount}from"genlayer-js";constaccount=createAccount();constclient=createClient({chain: testnetBradbury,
account,});// Get epoch info (includes timing estimates and inflation data)constepochInfo=awaitclient.getEpochInfo();// {// currentEpoch: 2n,// epochMinDuration: 86400n, // 1 day in seconds// currentEpochStart: Date,// currentEpochEnd: Date | null,// nextEpochEstimate: Date | null,// validatorMinStake: "0.01 GEN",// delegatorMinStake: "42 GEN",// activeValidatorsCount: 6n,// inflation: "1000 GEN", // Total inflation for current epoch// inflationRaw: 1000000000000000000000n,// totalWeight: 500000000000000000000000n, // Total stake weight// totalClaimed: "500 GEN", // Total claimed rewards// }// Get active validatorsconstvalidators=awaitclient.getActiveValidators();// Check if address is a validatorconstisValidator=awaitclient.isValidator("0x...");// Get validator infoconstvalidatorInfo=awaitclient.getValidatorInfo("0x...");// Join as validator (requires account with funds)constresult=awaitclient.validatorJoin({amount: "42000gen"});// Join as delegatorconstdelegateResult=awaitclient.delegatorJoin({validator: "0x...",amount: "42gen",});

🚀 Key Features

  • Client Creation: Easily create and configure a client to connect to GenLayer's network.
  • Transaction Handling: Send and manage transactions on the GenLayer network.
  • Staking: Full staking support for validators and delegators on testnet-bradbury and testnet-asimov.
  • Wallet Integration*: Seamless integration with MetaMask for managing user accounts.
  • Gas Estimation*: Estimate gas fees for executing transactions on GenLayer.

* under development

📖 Documentation

For detailed information on how to use GenLayerJS SDK, please refer to our documentation.

Contributing

We welcome contributions to GenLayerJS SDK! Whether it's new features, improved infrastructure, or better documentation, your input is valuable. Please read our CONTRIBUTING guide for guidelines on how to submit your contributions.

License

This project is licensed under the MIT License - see the LICENSE file for details.

Releases

Packages

Used by

Contributors

Languages