npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2025 – Pkg Stats / Ryan Hefner

@klever/connect-transactions

v0.1.1

Published

Transaction building for Klever Connect SDK

Readme

@klever/connect-transactions

Transaction building for Klever Connect SDK - build and sign transactions offline.

Installation

npm install @klever/connect-transactions

Features

  • Chainable API - Fluent transaction building with method chaining for intuitive transaction construction
  • Offline-First - Build transactions without network connectivity using buildProto() for air-gapped signing
  • Online Building - Node-assisted building with automatic fee calculation and nonce management using build()
  • Type Safe - Full TypeScript support with branded types, compile-time validation, and IntelliSense
  • All Transaction Types - Support for 20+ transaction types including transfers, staking, governance, assets, NFTs, marketplace, and smart contracts
  • Fee Management - Automatic fee calculation by node or manual control for offline building
  • Nonce Management - Automatic nonce fetching from the network or manual specification for batch transactions
  • Proto Encoding - Built-in protobuf encoding/decoding compatible with Klever blockchain protocol
  • Multiple Contracts - Support for multiple contracts in a single transaction (e.g., transfer + freeze + delegate)
  • Performance Optimized - Lightning-fast transaction building (~0.003ms) and proto encoding (~0.0002ms)
  • Helper Functions - Convenient helper functions for common operations and amount conversions

Quick Start

Basic Transfer (Node-Assisted)

import { TransactionBuilder } from '@klever/connect-transactions'
import { KleverProvider } from '@klever/connect-provider'

const provider = new KleverProvider({ network: 'mainnet' })

// Build a transfer transaction (node handles nonce and fees)
const tx = await TransactionBuilder.create(provider)
  .sender('klv1...')
  .transfer({
    receiver: 'klv1...',
    amount: '1000000', // 1 KLV
  })
  .build()

// Sign with private key
await tx.sign(privateKey)

// Broadcast
const txHash = await provider.sendRawTransaction(tx.toHex())

Offline Building

import { TransactionBuilder } from '@klever/connect-transactions'

// Build transaction entirely offline (no network required)
const tx = TransactionBuilder.create()
  .sender('klv1...')
  .nonce(123) // Must provide nonce manually
  .transfer({
    receiver: 'klv1...',
    amount: '1000000',
  })
  .buildProto({
    chainId: '100', // Mainnet
    fees: {
      kAppFee: 500000,
      bandwidthFee: 100000,
    },
  })

await tx.sign(privateKey)
// Later: broadcast tx.toHex()

Building Modes Comparison

The SDK provides three distinct building modes, each optimized for different use cases:

| Feature | build() | buildProto() | buildRequest() | | ----------------------------- | ---------------------------- | ------------------------------------ | ------------------------------ | | Network Required | ✅ Yes (provider required) | ❌ No (fully offline) | ❌ No (returns JSON) | | Nonce Management | ✅ Automatic from network | ⚙️ Manual (you provide) | ⚙️ Manual (you provide) | | Fee Calculation | ✅ Automatic by node | ⚙️ Manual (you provide) | ✅ Automatic by node (later) | | Proto Encoding | ✅ Done by node | ✅ Done client-side | ❌ No (JSON object) | | Validation | ✅ Yes (by node) | ⚠️ Limited (client-side) | ❌ No | | Performance | ~100-300ms (network call) | ~0.003ms (instant) | ~0.001ms (instant) | | Best For | Standard dApps, wallets | Hardware wallets, air-gapped signing | Custom integrations, debugging | | Security | Good (requires internet) | Excellent (no network exposure) | Good (inspect before sending) | | Transaction Ready to Sign | ✅ Yes (returns Transaction) | ✅ Yes (returns Transaction) | ❌ No (needs node processing) | | Returns | Promise<Transaction> | Transaction | BuildTransactionRequest |

1. Node-Assisted Building (build())

Best for: Standard applications with internet connectivity, production dApps, wallets

Use when: You want the easiest experience and have network connectivity

Advantages:

  • Automatic nonce fetching from the blockchain
  • Automatic fee calculation based on contract complexity
  • Transaction validation before encoding
  • Error checking and helpful messages
  • Production-ready with minimal code

Disadvantages:

  • Requires internet connection and working provider
  • Slightly slower due to network latency (~100-300ms)
  • Less control over transaction parameters
const provider = new KleverProvider({ network: 'mainnet' })

const tx = await TransactionBuilder.create(provider)
  .sender('klv1...')
  .transfer({ receiver: 'klv1...', amount: '1000000' })
  .build() // Node handles everything

// Transaction is ready to sign and broadcast
await tx.sign(privateKey)
const hash = await provider.sendRawTransaction(tx.toHex())

2. Offline Building (buildProto())

Best for: Air-gapped signing, hardware wallets, maximum security, batch transactions

Use when: Security is paramount, or you're building transactions in advance

Advantages:

  • Zero network latency (~0.003ms building time)
  • Works completely offline (perfect for air-gapped systems)
  • Full control over every transaction parameter
  • Maximum security (no network exposure during signing)
  • Ideal for hardware wallet integration

Disadvantages:

  • Must manually fetch and manage nonce
  • Must manually calculate or estimate fees
  • No automatic validation (easy to create invalid transactions)
  • Requires more code and blockchain knowledge

Fee Estimation:

  • KAppFee: Base fee for contract type (typically 500000-1000000)
  • BandwidthFee: Based on transaction size (typically 100000-500000)
  • Use previous transaction fees as reference or query node's fee table
// Fetch nonce first (can be done earlier)
const account = await provider.getAccount('klv1...')

// Build completely offline
const tx = TransactionBuilder.create()
  .sender('klv1...')
  .nonce(account.nonce)
  .transfer({ receiver: 'klv1...', amount: '1000000' })
  .buildProto({
    chainId: '100', // Mainnet
    fees: {
      kAppFee: 500000, // Base fee
      bandwidthFee: 100000, // Size-based fee
    },
  })

// Sign offline (can be done on air-gapped machine)
await tx.sign(privateKey)

// Later: broadcast to network
const hex = tx.toHex()

3. Request Building (buildRequest())

Best for: Custom integrations, debugging, API development, testing

Use when: You need to inspect or modify the request before building

Advantages:

  • Returns plain JSON object (easy to inspect)
  • Can be modified before sending to node
  • Useful for debugging transaction issues
  • Allows custom processing or logging
  • Can be sent to any node endpoint

Disadvantages:

  • Not ready for signing (needs node processing)
  • Requires additional HTTP call to node
  • Two-step process (build request → send to node)
// Create request object
const request = TransactionBuilder.create()
  .sender('klv1...')
  .transfer({ receiver: 'klv1...', amount: '1000000' })
  .buildRequest()

// Inspect the request
console.log(JSON.stringify(request, null, 2))

// Modify if needed
request.nonce = 150

// Send to node endpoint manually
const response = await fetch('https://api.mainnet.klever.finance/v1.0/transaction/build', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify(request),
})

const data = await response.json()
const tx = Transaction.fromObject(data.result)

// Now ready to sign
await tx.sign(privateKey)

When to Use Each Mode

Use build() if:

  • Building a standard dApp or wallet
  • You have reliable internet connectivity
  • You want automatic nonce and fee management
  • You prefer simpler code with fewer steps

Use buildProto() if:

  • Integrating with hardware wallets (Ledger, Trezor)
  • Building transactions on air-gapped computers
  • Creating batch transactions with sequential nonces
  • Maximum security is required
  • You need predictable build times (no network variance)

Use buildRequest() if:

  • Developing custom blockchain tools
  • Debugging transaction issues
  • Building transaction explorers
  • Need to inspect or modify requests
  • Integrating with custom node infrastructure

Transaction Types

The SDK supports all Klever blockchain transaction types. Each type has a corresponding builder method for easy transaction construction.

Transfer Operations (Type 0)

Transfer KLV, KDA tokens, or NFTs between accounts. Supports royalty payments for NFT transfers.

// Basic KLV transfer
const tx = await TransactionBuilder.create(provider)
  .sender('klv1...')
  .transfer({
    receiver: 'klv1recipient...',
    amount: '1000000', // 1 KLV
  })
  .build()

// Transfer custom KDA token
const tx = await TransactionBuilder.create(provider)
  .sender('klv1...')
  .transfer({
    receiver: 'klv1recipient...',
    amount: '5000000',
    kda: 'MYTOKEN-ABCD', // Custom token
  })
  .build()

// Transfer NFT with royalties
const tx = await TransactionBuilder.create(provider)
  .sender('klv1...')
  .transfer({
    receiver: 'klv1recipient...',
    amount: '1',
    kda: 'NFT-COLLECTION/NONCE-1',
    kdaRoyalties: '100000', // Royalties in KDA
    klvRoyalties: '50000', // Royalties in KLV
  })
  .build()

// Multiple transfers in one transaction
const tx = await TransactionBuilder.create(provider)
  .sender('klv1...')
  .transfer({ receiver: 'klv1alice...', amount: '1000000' })
  .transfer({ receiver: 'klv1bob...', amount: '2000000' })
  .transfer({ receiver: 'klv1charlie...', amount: '3000000', kda: 'MYTOKEN-ABCD' })
  .build()

Staking Operations

Freeze (Type 4)

Lock KLV or KDA tokens to create a staking bucket. Frozen tokens can be delegated to validators or used for governance.

// Freeze KLV for staking
const tx = await TransactionBuilder.create(provider)
  .sender('klv1...')
  .freeze({
    amount: '5000000', // 5 KLV
  })
  .build()

// Freeze custom KDA token
const tx = await TransactionBuilder.create(provider)
  .sender('klv1...')
  .freeze({
    amount: '1000000',
    kda: 'MYTOKEN-ABCD',
  })
  .build()

Unfreeze (Type 5)

Unlock frozen tokens. After unfreezing, tokens enter an unlock period before becoming available.

// Unfreeze KLV bucket
const tx = await TransactionBuilder.create(provider)
  .sender('klv1...')
  .unfreeze({
    kda: 'KLV',
    bucketId: 'bucket-hash-123', // Required for KLV
  })
  .build()

// Unfreeze KDA token
const tx = await TransactionBuilder.create(provider)
  .sender('klv1...')
  .unfreeze({
    kda: 'MYTOKEN-ABCD',
  })
  .build()

Delegate (Type 6)

Assign a frozen bucket to a validator for staking rewards.

// Delegate specific bucket
const tx = await TransactionBuilder.create(provider)
  .sender('klv1...')
  .delegate({
    receiver: 'klv1validator...', // Validator address
    bucketId: 'bucket-hash-123',
  })
  .build()

// Delegate all available buckets
const tx = await TransactionBuilder.create(provider)
  .sender('klv1...')
  .delegate({
    receiver: 'klv1validator...',
  })
  .build()

Undelegate (Type 7)

Remove delegation from a validator, returning the bucket to your control.

const tx = await TransactionBuilder.create(provider)
  .sender('klv1...')
  .undelegate({
    bucketId: 'bucket-hash-123',
  })
  .build()

Withdraw (Type 8)

Withdraw staking rewards, unlocked assets, or other withdrawable amounts.

// Withdraw staking rewards
const tx = await TransactionBuilder.create(provider)
  .sender('klv1...')
  .withdraw({
    withdrawType: 0, // 0 = Staking rewards
  })
  .build()

// Withdraw specific KDA amount
const tx = await TransactionBuilder.create(provider)
  .sender('klv1...')
  .withdraw({
    withdrawType: 0,
    kda: 'MYTOKEN-ABCD',
    amount: '1000000',
  })
  .build()

// Withdraw FPR rewards
const tx = await TransactionBuilder.create(provider)
  .sender('klv1...')
  .withdraw({
    withdrawType: 1, // 1 = FPR rewards
  })
  .build()

Claim (Type 9)

Claim rewards, airdrops, or allocations.

// Claim staking rewards
const tx = await TransactionBuilder.create(provider)
  .sender('klv1...')
  .claim({
    claimType: 0, // 0 = Staking rewards
  })
  .build()

// Claim market rewards
const tx = await TransactionBuilder.create(provider)
  .sender('klv1...')
  .claim({
    claimType: 1, // 1 = Market rewards
  })
  .build()

// Claim specific allocation
const tx = await TransactionBuilder.create(provider)
  .sender('klv1...')
  .claim({
    claimType: 0,
    id: 'allocation-id-123',
  })
  .build()

Asset Management

Create Asset (Type 1)

Create fungible tokens (FT) or non-fungible tokens (NFT).

// Create fungible token
const tx = await TransactionBuilder.create(provider)
  .sender('klv1...')
  .createAsset({
    type: 0, // 0 = Fungible Token
    name: 'My Token',
    ticker: 'MTK',
    ownerAddress: 'klv1...',
    precision: 6, // 6 decimal places
    maxSupply: '1000000000000', // 1 million tokens
    initialSupply: '100000000000', // 100k tokens initially
    properties: {
      canMint: true,
      canBurn: true,
      canPause: true,
      canWipe: false,
      canFreeze: false,
      canChangeOwner: true,
      canAddRoles: true,
    },
  })
  .build()

// Create NFT collection
const tx = await TransactionBuilder.create(provider)
  .sender('klv1...')
  .createAsset({
    type: 1, // 1 = NFT
    name: 'My NFT Collection',
    ticker: 'MYNFT',
    ownerAddress: 'klv1...',
    precision: 0, // NFTs have no decimals
    maxSupply: 0, // Unlimited
    logo: 'https://example.com/logo.png',
    uris: {
      website: 'https://mynft.com',
      twitter: 'https://twitter.com/mynft',
    },
    royalties: {
      address: 'klv1royalty...', // Royalty recipient
      transferPercentage: [{ amount: 0, percentage: 5 }], // 5% royalty
    },
    properties: {
      canMint: true,
      canBurn: false,
      canPause: false,
    },
  })
  .build()

Asset Trigger (Type 11)

Mint, burn, pause, resume, or wipe assets.

// Mint NFT
const tx = await TransactionBuilder.create(provider)
  .sender('klv1...')
  .addContract({
    contractType: 11,
    triggerType: 0, // 0 = Mint
    assetId: 'MYNFT-ABCD',
    receiver: 'klv1recipient...',
    uris: {
      image: 'ipfs://QmXyz...',
      metadata: 'ipfs://QmAbc...',
    },
    mime: 'image/png',
  })
  .build()

// Burn tokens
const tx = await TransactionBuilder.create(provider)
  .sender('klv1...')
  .addContract({
    contractType: 11,
    triggerType: 1, // 1 = Burn
    assetId: 'MYTOKEN-ABCD',
    amount: '1000000',
  })
  .build()

// Pause asset (admin only)
const tx = await TransactionBuilder.create(provider)
  .sender('klv1...')
  .addContract({
    contractType: 11,
    triggerType: 3, // 3 = Pause
    assetId: 'MYTOKEN-ABCD',
  })
  .build()

// Resume asset (admin only)
const tx = await TransactionBuilder.create(provider)
  .sender('klv1...')
  .addContract({
    contractType: 11,
    triggerType: 4, // 4 = Resume
    assetId: 'MYTOKEN-ABCD',
  })
  .build()

Validator Operations

Create Validator (Type 2)

Register a new validator node to participate in network consensus.

const tx = await TransactionBuilder.create(provider)
  .sender('klv1...')
  .createValidator({
    blsPublicKey: '0xabcd1234...', // BLS key for signing
    ownerAddress: 'klv1owner...',
    commission: 10, // 10% commission
    canDelegate: true,
    maxDelegationAmount: '10000000000', // Optional limit
    name: 'My Validator',
    logo: 'https://validator.com/logo.png',
    uris: {
      website: 'https://validator.com',
      twitter: 'https://twitter.com/myvalidator',
    },
  })
  .build()

Validator Config (Type 3)

Update validator configuration (commission, delegation settings, etc.).

const tx = await TransactionBuilder.create(provider)
  .sender('klv1...')
  .addContract({
    contractType: 3,
    blsPublicKey: '0xabcd1234...',
    commission: 8, // Update to 8%
    canDelegate: true,
    rewardAddress: 'klv1rewards...', // Change reward address
  })
  .build()

Unjail (Type 10)

Unjail a validator that was jailed for downtime or misbehavior.

const tx = await TransactionBuilder.create(provider)
  .sender('klv1...')
  .addContract({
    contractType: 10,
    // No additional parameters required
  })
  .build()

Governance

Proposal (Type 13)

Create a governance proposal to change network parameters.

const tx = await TransactionBuilder.create(provider)
  .sender('klv1...')
  .addContract({
    contractType: 13,
    parameters: {
      1: '1000000', // Parameter ID 1: new value
      5: '500000', // Parameter ID 5: new value
    },
    description: 'Increase minimum stake and update rewards',
    epochsDuration: 10, // Voting period: 10 epochs
  })
  .build()

Vote (Type 14)

Vote on an active governance proposal.

// Vote YES
const tx = await TransactionBuilder.create(provider)
  .sender('klv1...')
  .vote({
    proposalId: 5,
    type: 1, // 1 = Yes
    amount: '1000000', // Optional voting weight
  })
  .build()

// Vote NO
const tx = await TransactionBuilder.create(provider)
  .sender('klv1...')
  .vote({
    proposalId: 5,
    type: 2, // 2 = No
  })
  .build()

// Abstain
const tx = await TransactionBuilder.create(provider)
  .sender('klv1...')
  .vote({
    proposalId: 5,
    type: 0, // 0 = Abstain
  })
  .build()

Marketplace

Buy (Type 17)

Purchase items from the marketplace.

const tx = await TransactionBuilder.create(provider)
  .sender('klv1...')
  .addContract({
    contractType: 17,
    buyType: 0, // 0 = Direct buy
    id: 'order-id-123',
    amount: '1', // Quantity
    currencyId: 'KLV',
    currencyAmount: '1000000',
  })
  .build()

Sell (Type 18)

List items for sale on the marketplace.

const tx = await TransactionBuilder.create(provider)
  .sender('klv1...')
  .addContract({
    contractType: 18,
    marketType: 0, // 0 = Fixed price
    marketplaceId: 'marketplace-id',
    assetId: 'MYNFT-ABCD/NONCE-1',
    currencyId: 'KLV',
    price: '1000000',
    endTime: Date.now() + 7 * 24 * 60 * 60 * 1000, // 7 days
  })
  .build()

Cancel Market Order (Type 19)

Cancel a marketplace listing.

const tx = await TransactionBuilder.create(provider)
  .sender('klv1...')
  .addContract({
    contractType: 19,
    orderId: 'order-id-123',
  })
  .build()

Create Marketplace (Type 20)

Create a new marketplace.

const tx = await TransactionBuilder.create(provider)
  .sender('klv1...')
  .addContract({
    contractType: 20,
    name: 'My NFT Marketplace',
    referralAddress: 'klv1referral...',
    referralPercentage: 2, // 2% referral fee
  })
  .build()

ITO (Initial Token Offering)

Config ITO (Type 15)

Configure an Initial Token Offering for your asset.

const tx = await TransactionBuilder.create(provider)
  .sender('klv1...')
  .addContract({
    contractType: 15,
    kda: 'MYTOKEN-ABCD',
    receiverAddress: 'klv1receiver...',
    status: 1, // 1 = Active
    maxAmount: '1000000000',
    packInfo: {
      pack1: {
        packs: [{ amount: '1000', price: '100000' }],
      },
      pack2: {
        packs: [{ amount: '10000', price: '900000' }],
      },
    },
    defaultLimitPerAddress: '100000',
    whitelistStatus: 1,
    startTime: Date.now(),
    endTime: Date.now() + 30 * 24 * 60 * 60 * 1000, // 30 days
  })
  .build()

Smart Contract Operations (Type 63)

Interact with deployed smart contracts.

// Deploy contract
const tx = await TransactionBuilder.create(provider)
  .sender('klv1...')
  .smartContract({
    address: 'klv1000...', // Zero address for deployment
    scType: 0, // 0 = Deploy
  })
  .data(['contractCode', 'initArgs'])
  .build()

// Invoke contract function
const tx = await TransactionBuilder.create(provider)
  .sender('klv1...')
  .smartContract({
    address: 'klv1contract...',
    scType: 1, // 1 = Invoke
    callValue: {
      KLV: '1000000', // Send 1 KLV with call
    },
  })
  .data(['transfer', 'klv1recipient...', '500000'])
  .build()

// Query contract (read-only)
const tx = await TransactionBuilder.create(provider)
  .sender('klv1...')
  .smartContract({
    address: 'klv1contract...',
    scType: 1,
  })
  .data(['getBalance', 'klv1address...'])
  .build()

// Upgrade contract
const tx = await TransactionBuilder.create(provider)
  .sender('klv1...')
  .smartContract({
    address: 'klv1contract...',
    scType: 2, // 2 = Upgrade
  })
  .data(['newContractCode'])
  .build()

Account Management

Set Account Name (Type 12)

Set a human-readable name for your account.

const tx = await TransactionBuilder.create(provider)
  .sender('klv1...')
  .addContract({
    contractType: 12,
    name: 'myaccount',
  })
  .build()

Update Account Permission (Type 22)

Configure multi-signature permissions for your account.

const tx = await TransactionBuilder.create(provider)
  .sender('klv1...')
  .addContract({
    contractType: 22,
    permissions: [
      {
        type: 0, // Owner permission
        permissionName: 'owner',
        threshold: '2', // Require 2 signatures
        operations: '*', // All operations
        signers: [
          { address: 'klv1signer1...', weight: '1' },
          { address: 'klv1signer2...', weight: '1' },
          { address: 'klv1signer3...', weight: '1' },
        ],
      },
    ],
  })
  .build()

Deposit (Type 23)

Deposit assets into contracts or protocols.

const tx = await TransactionBuilder.create(provider)
  .sender('klv1...')
  .addContract({
    contractType: 23,
    depositType: 0, // Deposit type
    kda: 'MYTOKEN-ABCD',
    amount: '1000000',
  })
  .build()

API Reference

TransactionBuilder

Constructor Methods

  • TransactionBuilder.create(provider?) - Create new builder with optional provider
  • new TransactionBuilder(provider?) - Direct instantiation

Configuration Methods

  • .sender(address) - Set transaction sender (required)
  • .nonce(nonce) - Set nonce manually (for offline building)
  • .setChainId(chainId) - Set chain ID (overrides provider's network)
  • .kdaFee(fee) - Pay fees in custom KDA instead of KLV
  • .permissionId(id) - Set permission ID for multi-sig
  • .data(data) - Set transaction data (for smart contracts)
  • .callOptions(options) - Set multiple options at once

Contract Methods

  • .transfer(params) - Add transfer contract
  • .freeze(params) - Add freeze (stake) contract
  • .unfreeze(params) - Add unfreeze (unstake) contract
  • .delegate(params) - Add delegation contract
  • .undelegate(params) - Add undelegation contract
  • .withdraw(params) - Add withdrawal contract
  • .claim(params) - Add claim contract
  • .createAsset(params) - Add asset creation contract
  • .createValidator(params) - Add validator creation contract
  • .vote(params) - Add vote contract
  • .smartContract(params) - Add smart contract call
  • .addContract(contract) - Add any contract by type

Build Methods

  • .build() - Build using node (requires provider)
  • .buildProto(options) - Build offline (no network)
  • .buildRequest() - Build request object for node endpoint

Utility Methods

  • .reset() - Clear all contracts and state
  • .getProvider() - Get provider instance
  • .setProvider(provider) - Set provider instance

Transaction

Instance Methods

  • sign(privateKey) - Sign transaction with private key
  • toHex() - Get hex-encoded proto bytes
  • toBytes() - Get proto bytes as Uint8Array
  • toJSON() - Convert to JSON object
  • isSigned() - Check if transaction is signed
  • getTotalFee() - Get total fee amount
  • getHash() - Get transaction hash
  • getHashBytes() - Get transaction hash as bytes

Static Methods

  • Transaction.fromHex(hex) - Create from hex string
  • Transaction.fromBytes(bytes) - Create from proto bytes
  • Transaction.fromObject(obj) - Create from JSON object
  • Transaction.fromTransaction(tx) - Create copy of transaction

Helper Functions

Transaction Helpers

import {
  createTransfer,
  createFreeze,
  createDelegate,
  // ... and more
} from '@klever/connect-transactions'

// Create transfer request object
const transfer = createTransfer({
  receiver: 'klv1...',
  amount: '1000000',
})

// Use with builder
const tx = await builder.transfer(transfer).build()

Amount Conversion

import { toKLVUnits, fromKLVUnits, toUnits, fromUnits } from '@klever/connect-transactions'

// Convert KLV to smallest units
const amount = toKLVUnits('1.5') // '1500000'

// Convert from smallest units
const klv = fromKLVUnits('1500000') // '1.5'

// Custom precision
const amount = toUnits('1.5', 8) // '150000000' (8 decimals)
const readable = fromUnits('150000000', 8) // '1.5'

Advanced Patterns

Multiple Contracts in One Transaction

The Klever blockchain supports multiple contracts in a single transaction, allowing you to batch operations atomically.

Benefits:

  • Reduced transaction count and fees
  • Atomic execution (all-or-nothing)
  • Simplified transaction management
  • Better UX for complex operations
// Common pattern: Transfer + Freeze + Delegate
const tx = await TransactionBuilder.create(provider)
  .sender('klv1...')
  .transfer({ receiver: 'klv1friend...', amount: '1000000' }) // Send 1 KLV to friend
  .freeze({ amount: '5000000' }) // Freeze 5 KLV for staking
  .delegate({ receiver: 'klv1validator...', bucketId: 'bucket-123' }) // Delegate to validator
  .build()

// Batch transfers (airdrop pattern)
const recipients = [
  { receiver: 'klv1alice...', amount: '1000000' },
  { receiver: 'klv1bob...', amount: '1000000' },
  { receiver: 'klv1charlie...', amount: '1000000' },
]

const builder = TransactionBuilder.create(provider).sender('klv1sender...')

recipients.forEach((recipient) => {
  builder.transfer(recipient)
})

const tx = await builder.build()

// Complex asset management
const tx = await TransactionBuilder.create(provider)
  .sender('klv1...')
  .createAsset({
    type: 0,
    name: 'My Token',
    ticker: 'MTK',
    ownerAddress: 'klv1...',
    precision: 6,
    maxSupply: '1000000000000',
    initialSupply: '100000000000',
  })
  .transfer({ receiver: 'klv1treasury...', amount: '50000000000', kda: 'MTK-ABCD' }) // Send to treasury
  .freeze({ amount: '25000000000', kda: 'MTK-ABCD' }) // Freeze some tokens
  .build()

Custom Fee Configuration

Pay transaction fees in custom KDA tokens instead of KLV.

// Pay fees in custom token
const tx = await TransactionBuilder.create(provider)
  .sender('klv1...')
  .kdaFee({ kda: 'MYTOKEN-ABCD', amount: '1000000' })
  .transfer({ receiver: 'klv1...', amount: '1000000' })
  .build()

// Offline building with custom KDA fee
const tx = TransactionBuilder.create()
  .sender('klv1...')
  .nonce(123)
  .kdaFee({ kda: 'MYTOKEN-ABCD', amount: '1000000' })
  .transfer({ receiver: 'klv1...', amount: '1000000' })
  .buildProto({
    chainId: '100',
    fees: { kAppFee: 0, bandwidthFee: 0 }, // Zeros because using KDA fee
  })

Offline Transaction Signing

For maximum security, build and sign transactions on air-gapped computers.

// Step 1: On connected computer - Fetch account data
const provider = new KleverProvider({ network: 'mainnet' })
const account = await provider.getAccount('klv1...')
console.log('Nonce:', account.nonce)
console.log('Balance:', account.balance)

// Transfer nonce to air-gapped computer

// Step 2: On air-gapped computer - Build and sign transaction
const tx = TransactionBuilder.create()
  .sender('klv1...')
  .nonce(123) // Use fetched nonce
  .transfer({ receiver: 'klv1recipient...', amount: '1000000' })
  .buildProto({
    chainId: '100',
    fees: { kAppFee: 500000, bandwidthFee: 100000 },
  })

await tx.sign(privateKey)
const signedHex = tx.toHex()
console.log('Signed transaction:', signedHex)

// Transfer signedHex back to connected computer

// Step 3: On connected computer - Broadcast transaction
const hash = await provider.sendRawTransaction(signedHex)
console.log('Transaction hash:', hash)

Transaction Batching

Build multiple transactions with sequential nonces for batch processing.

// Fetch current nonce
const account = await provider.getAccount('klv1sender...')
let currentNonce = account.nonce

// Build batch of transactions
const transactions: Transaction[] = []

for (let i = 0; i < 10; i++) {
  const tx = TransactionBuilder.create()
    .sender('klv1sender...')
    .nonce(currentNonce++)
    .transfer({
      receiver: `klv1recipient${i}...`,
      amount: '1000000',
    })
    .buildProto({
      chainId: '100',
      fees: { kAppFee: 500000, bandwidthFee: 100000 },
    })

  await tx.sign(privateKey)
  transactions.push(tx)
}

// Broadcast all transactions
const hashes = await provider.sendRawTransactions(transactions.map((tx) => tx.toHex()))
console.log('Transaction hashes:', hashes)

Nonce Management Strategies

Handle nonces correctly for reliable transaction submission.

// Strategy 1: Automatic (recommended for single transactions)
const tx = await TransactionBuilder.create(provider)
  .sender('klv1...')
  .transfer({ receiver: 'klv1...', amount: '1000000' })
  .build() // Provider fetches nonce automatically

// Strategy 2: Manual with cache (for batch transactions)
class NonceManager {
  private nonceCache = new Map<string, number>()

  async getNextNonce(provider: IProvider, address: string): Promise<number> {
    if (!this.nonceCache.has(address)) {
      const account = await provider.getAccount(address)
      this.nonceCache.set(address, account.nonce)
    }
    const nonce = this.nonceCache.get(address)!
    this.nonceCache.set(address, nonce + 1)
    return nonce
  }

  reset(address: string) {
    this.nonceCache.delete(address)
  }
}

const nonceManager = new NonceManager()

// Build multiple transactions
const tx1 = TransactionBuilder.create()
  .sender('klv1...')
  .nonce(await nonceManager.getNextNonce(provider, 'klv1...'))
  .transfer({ receiver: 'klv1a...', amount: '1000000' })
  .buildProto({ chainId: '100', fees: { kAppFee: 500000, bandwidthFee: 100000 } })

const tx2 = TransactionBuilder.create()
  .sender('klv1...')
  .nonce(await nonceManager.getNextNonce(provider, 'klv1...'))
  .transfer({ receiver: 'klv1b...', amount: '2000000' })
  .buildProto({ chainId: '100', fees: { kAppFee: 500000, bandwidthFee: 100000 } })

// Strategy 3: Retry on nonce error
async function sendWithRetry(tx: Transaction, maxRetries = 3): Promise<string> {
  for (let i = 0; i < maxRetries; i++) {
    try {
      return await provider.sendRawTransaction(tx.toHex())
    } catch (error) {
      if (error.message.includes('nonce')) {
        // Rebuild transaction with fresh nonce
        const account = await provider.getAccount(sender)
        // Rebuild tx with new nonce...
      }
      if (i === maxRetries - 1) throw error
    }
  }
  throw new Error('Max retries exceeded')
}

Reusing Builder Instances

Efficiently reuse builder instances for multiple transactions.

const builder = TransactionBuilder.create(provider)
const sender = 'klv1sender...'

// Build first transaction
const tx1 = await builder
  .sender(sender)
  .transfer({ receiver: 'klv1abc...', amount: '1000000' })
  .build()

await tx1.sign(privateKey)
await provider.sendRawTransaction(tx1.toHex())

// Reset and build second transaction
const tx2 = await builder.reset().sender(sender).freeze({ amount: '5000000' }).build()

await tx2.sign(privateKey)
await provider.sendRawTransaction(tx2.toHex())

// Reuse with different sender
const tx3 = await builder
  .reset()
  .sender('klv1other...')
  .transfer({ receiver: 'klv1...', amount: '500000' })
  .build()

Hardware Wallet Integration Pattern

Build transactions for hardware wallet signing.

// Build unsigned transaction
const tx = TransactionBuilder.create()
  .sender('klv1...')
  .nonce(account.nonce)
  .transfer({ receiver: 'klv1recipient...', amount: '1000000' })
  .buildProto({
    chainId: '100',
    fees: { kAppFee: 500000, bandwidthFee: 100000 },
  })

// Get transaction hash for hardware wallet
const txHash = tx.getHash()
const txHashBytes = tx.getHashBytes()

// Sign with hardware wallet (pseudocode)
const signature = await hardwareWallet.signTransaction(txHashBytes, derivationPath)

// Add signature to transaction
tx.Signature = [signature]

// Broadcast
const hash = await provider.sendRawTransaction(tx.toHex())

Transaction Inspection and Debugging

Inspect transaction details before signing and broadcasting.

// Build transaction
const tx = await TransactionBuilder.create(provider)
  .sender('klv1...')
  .transfer({ receiver: 'klv1...', amount: '1000000' })
  .build()

// Inspect transaction details
console.log('Transaction Hash:', tx.getHash())
console.log('Total Fee:', tx.getTotalFee())
console.log('Is Signed:', tx.isSigned())
console.log('Sender:', tx.RawData?.Sender)
console.log('Nonce:', tx.RawData?.Nonce)
console.log('Contracts:', tx.RawData?.Contract)

// Convert to JSON for logging
const txJson = tx.toJSON()
console.log('Transaction JSON:', JSON.stringify(txJson, null, 2))

// Get proto bytes size
const protoBytes = tx.toBytes()
console.log('Proto size:', protoBytes.length, 'bytes')

// Sign and verify
await tx.sign(privateKey)
console.log('Is Signed:', tx.isSigned())
console.log('Signature:', tx.Signature)

Error Handling

Proper error handling ensures robust transaction processing and better user experience.

Common Errors

import { ValidationError, TransactionError } from '@klever/connect-core'

try {
  const tx = await TransactionBuilder.create(provider)
    .sender('klv1...')
    .transfer({ receiver: 'klv1recipient...', amount: '1000000' })
    .build()

  await tx.sign(privateKey)
  const hash = await provider.sendRawTransaction(tx.toHex())
} catch (error) {
  // Validation errors (caught during building)
  if (error instanceof ValidationError) {
    console.error('Validation failed:', error.message)
    // Examples:
    // - Invalid address format
    // - Negative amounts
    // - Missing required fields
    // - Invalid nonce
    return
  }

  // Transaction errors (caught during signing or broadcasting)
  if (error instanceof TransactionError) {
    console.error('Transaction error:', error.message)
    // Examples:
    // - Signing failed
    // - Insufficient balance
    // - Transaction rejected by network
    return
  }

  // Network errors
  if (error.message.includes('network')) {
    console.error('Network error:', error.message)
    // Retry or notify user
    return
  }

  // Generic error
  console.error('Unexpected error:', error)
}

Validation Before Building

Validate inputs before building transactions to provide better error messages.

import { isValidAddress } from '@klever/connect-core'

function validateTransfer(receiver: string, amount: string): string[] {
  const errors: string[] = []

  if (!isValidAddress(receiver)) {
    errors.push(`Invalid receiver address: ${receiver}`)
  }

  const amountNum = Number(amount)
  if (isNaN(amountNum) || amountNum <= 0) {
    errors.push(`Invalid amount: ${amount}`)
  }

  return errors
}

// Use validation
const errors = validateTransfer(receiver, amount)
if (errors.length > 0) {
  console.error('Validation errors:', errors)
  return
}

// Proceed with transaction
const tx = await TransactionBuilder.create(provider)
  .sender('klv1...')
  .transfer({ receiver, amount })
  .build()

Handling Insufficient Balance

Check balance before creating transactions.

async function safeTransfer(
  provider: IProvider,
  sender: string,
  receiver: string,
  amount: bigint,
): Promise<string> {
  // Check balance
  const balance = await provider.getBalance(sender)

  // Estimate fees (approximate)
  const estimatedFee = 600000n // ~0.6 KLV

  if (balance < amount + estimatedFee) {
    throw new Error(
      `Insufficient balance. Have: ${balance}, Need: ${amount + estimatedFee} (including fees)`,
    )
  }

  // Build and send transaction
  const tx = await TransactionBuilder.create(provider)
    .sender(sender)
    .transfer({ receiver, amount })
    .build()

  await tx.sign(privateKey)
  return await provider.sendRawTransaction(tx.toHex())
}

Retry Logic

Implement retry logic for transient failures.

async function buildWithRetry(
  builder: TransactionBuilder,
  maxRetries = 3,
  delayMs = 1000,
): Promise<Transaction> {
  let lastError: Error | null = null

  for (let attempt = 1; attempt <= maxRetries; attempt++) {
    try {
      return await builder.build()
    } catch (error) {
      lastError = error as Error
      console.warn(`Build attempt ${attempt} failed:`, error.message)

      if (attempt < maxRetries) {
        // Wait before retrying
        await new Promise((resolve) => setTimeout(resolve, delayMs * attempt))
      }
    }
  }

  throw new Error(`Failed after ${maxRetries} attempts: ${lastError?.message}`)
}

// Usage
const tx = await buildWithRetry(
  TransactionBuilder.create(provider)
    .sender('klv1...')
    .transfer({ receiver: 'klv1...', amount: '1000000' }),
)

Graceful Degradation

Fall back to offline building if node is unavailable.

async function buildTransaction(
  provider: IProvider | undefined,
  sender: string,
  receiver: string,
  amount: string,
): Promise<Transaction> {
  if (provider) {
    try {
      // Try node-assisted building
      return await TransactionBuilder.create(provider)
        .sender(sender)
        .transfer({ receiver, amount })
        .build()
    } catch (error) {
      console.warn('Node unavailable, falling back to offline building:', error.message)
    }
  }

  // Fall back to offline building
  // Note: You need to provide nonce and fees
  const nonce = await getNonceFromCache(sender) // Your cache implementation
  return TransactionBuilder.create()
    .sender(sender)
    .nonce(nonce)
    .transfer({ receiver, amount })
    .buildProto({
      chainId: '100',
      fees: { kAppFee: 500000, bandwidthFee: 100000 },
    })
}

Integration Examples

Complete Wallet Integration

Full example of wallet integration with transaction building.

import { KleverProvider } from '@klever/connect-provider'
import { TransactionBuilder, Transaction } from '@klever/connect-transactions'
import { Wallet } from '@klever/connect-wallet'

class WalletService {
  private provider: KleverProvider
  private wallet: Wallet

  constructor(privateKey: string, network: 'mainnet' | 'testnet') {
    this.provider = new KleverProvider({ network })
    this.wallet = new Wallet(privateKey, this.provider)
  }

  async sendKLV(to: string, amount: bigint): Promise<string> {
    // Build transaction
    const tx = await TransactionBuilder.create(this.provider)
      .sender(this.wallet.address)
      .transfer({ receiver: to, amount })
      .build()

    // Sign
    await tx.sign(this.wallet.privateKey)

    // Broadcast
    return await this.provider.sendRawTransaction(tx.toHex())
  }

  async stake(amount: bigint, validatorAddress: string): Promise<string> {
    // Build multi-contract transaction
    const tx = await TransactionBuilder.create(this.provider)
      .sender(this.wallet.address)
      .freeze({ amount })
      .delegate({ receiver: validatorAddress })
      .build()

    await tx.sign(this.wallet.privateKey)
    return await this.provider.sendRawTransaction(tx.toHex())
  }

  async unstake(bucketId: string): Promise<string> {
    const tx = await TransactionBuilder.create(this.provider)
      .sender(this.wallet.address)
      .undelegate({ bucketId })
      .unfreeze({ kda: 'KLV', bucketId })
      .build()

    await tx.sign(this.wallet.privateKey)
    return await this.provider.sendRawTransaction(tx.toHex())
  }

  async getBalance(): Promise<bigint> {
    return await this.provider.getBalance(this.wallet.address)
  }
}

// Usage
const walletService = new WalletService(privateKey, 'mainnet')

// Send KLV
const hash = await walletService.sendKLV('klv1recipient...', 1000000n)
console.log('Transaction hash:', hash)

// Stake and delegate
const stakeHash = await walletService.stake(5000000n, 'klv1validator...')
console.log('Stake transaction:', stakeHash)

React Hook Integration

import { useCallback, useState } from 'react'
import { TransactionBuilder } from '@klever/connect-transactions'
import { useKleverProvider, useWallet } from '@klever/connect-react'

export function useSendTransaction() {
  const provider = useKleverProvider()
  const { address, sign } = useWallet()
  const [loading, setLoading] = useState(false)
  const [error, setError] = useState<Error | null>(null)

  const sendTransaction = useCallback(
    async (receiver: string, amount: string) => {
      if (!provider || !address) {
        throw new Error('Wallet not connected')
      }

      setLoading(true)
      setError(null)

      try {
        // Build transaction
        const tx = await TransactionBuilder.create(provider)
          .sender(address)
          .transfer({ receiver, amount })
          .build()

        // Sign with connected wallet
        const signedTx = await sign(tx)

        // Broadcast
        const hash = await provider.sendRawTransaction(signedTx.toHex())

        return hash
      } catch (err) {
        setError(err as Error)
        throw err
      } finally {
        setLoading(false)
      }
    },
    [provider, address, sign],
  )

  return { sendTransaction, loading, error }
}

// Component usage
function SendButton() {
  const { sendTransaction, loading } = useSendTransaction()

  const handleSend = async () => {
    const hash = await sendTransaction('klv1recipient...', '1000000')
    console.log('Sent:', hash)
  }

  return (
    <button onClick={handleSend} disabled={loading}>
      {loading ? 'Sending...' : 'Send 1 KLV'}
    </button>
  )
}

Transaction Monitoring

Monitor transaction status after broadcasting.

async function sendAndMonitor(
  provider: IProvider,
  tx: Transaction,
  onProgress?: (status: string) => void,
): Promise<void> {
  // Broadcast transaction
  const hash = await provider.sendRawTransaction(tx.toHex())
  console.log('Transaction broadcasted:', hash)

  onProgress?.('pending')

  // Wait for confirmation
  const result = await provider.waitForTransaction(
    hash,
    1, // confirmations
    (status, data) => {
      console.log('Status:', status, 'Data:', data)
      onProgress?.(status)
    },
  )

  if (!result) {
    throw new Error('Transaction failed or timed out')
  }

  console.log('Transaction confirmed!')
  onProgress?.('confirmed')
}

// Usage
const tx = await TransactionBuilder.create(provider)
  .sender('klv1...')
  .transfer({ receiver: 'klv1...', amount: '1000000' })
  .build()

await tx.sign(privateKey)

await sendAndMonitor(tx, provider, (status) => {
  console.log('Current status:', status)
})

Batch Transaction Processor

Process multiple transactions efficiently.

class BatchTransactionProcessor {
  constructor(
    private provider: IProvider,
    private privateKey: PrivateKey,
  ) {}

  async processBatch(operations: Array<{ receiver: string; amount: string }>): Promise<string[]> {
    // Get current nonce
    const sender = await this.getSenderAddress()
    const account = await this.provider.getAccount(sender)
    let nonce = account.nonce

    // Build all transactions
    const transactions: Transaction[] = []

    for (const op of operations) {
      const tx = TransactionBuilder.create()
        .sender(sender)
        .nonce(nonce++)
        .transfer({ receiver: op.receiver, amount: op.amount })
        .buildProto({
          chainId: this.provider.getNetwork().chainId,
          fees: { kAppFee: 500000, bandwidthFee: 100000 },
        })

      await tx.sign(this.privateKey)
      transactions.push(tx)
    }

    // Broadcast all at once
    return await this.provider.sendRawTransactions(transactions.map((tx) => tx.toHex()))
  }

  private async getSenderAddress(): Promise<string> {
    // Derive address from private key
    // Implementation depends on your crypto provider
    return 'klv1...'
  }
}

// Usage
const processor = new BatchTransactionProcessor(provider, privateKey)

const operations = [
  { receiver: 'klv1alice...', amount: '1000000' },
  { receiver: 'klv1bob...', amount: '2000000' },
  { receiver: 'klv1charlie...', amount: '3000000' },
]

const hashes = await processor.processBatch(operations)
console.log('Batch processed:', hashes)

Performance

Benchmarked on Apple M1 Pro, Node.js v20:

  • Transaction building (buildProto): ~0.003ms (335,000 ops/sec)
  • Proto encoding (toBytes): ~0.0002ms (4.1M ops/sec)
  • Signature generation (sign): ~0.5ms (2,000 ops/sec)
  • Node-assisted building (build): ~100-300ms (depends on network latency)

Memory Usage:

  • Single transaction: ~2KB
  • Batch of 100 transactions: ~200KB
  • Proto bytes per transaction: ~200-500 bytes (varies by complexity)

TypeScript Support

Full TypeScript support with type definitions included:

import type { TransferRequest, FreezeRequest, BuildCallOptions } from '@klever/connect-transactions'

License

MIT