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.
Before installing GenLayerJS SDK, ensure you have the following prerequisites installed:
- Node.js (>= 16.x)
- npm (>= 7.x)
To install the GenLayerJS SDK, use the following command:
$ npm install genlayer-jsHere’s how to initialize the client and connect to the GenLayer Simulator:
import{localnet}from'genlayer-js/chains';import{createClient}from"genlayer-js";constclient=createClient({chain: localnet,});consttransactionHash="0x...";consttransaction=awaitclient.getTransaction({hash: transactionHash})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});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",})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})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.
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");}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..."]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 logsWhen 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,});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. Callclient.connect()to resolve this.
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",});- 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
For detailed information on how to use GenLayerJS SDK, please refer to our documentation.
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.
This project is licensed under the MIT License - see the LICENSE file for details.