Skip to content

Contract Interactions

HumanWallet provides functions for interacting with smart contracts, including reading contract state and executing transactions. All write operations are gasless through account abstraction.

Functions Overview

writeContract()

Executes a single contract function call as a gasless transaction.

Function Signature

function writeContract(config: Config, args: WriteContractParameters): Promise<string>

Parameters

config

  • Type: Config
  • Description: Configuration object from createConfig() with authenticated client

args

  • Type: WriteContractParameters
  • Properties:
    • address: Contract address (0x${string})
    • abi: Contract ABI array
    • functionName: Function name to call
    • args: Array of function arguments (optional)
    • value: ETH value to send (optional, defaults to 0n)

Usage Example

import { writeContract } from "@humanwallet/core"
 
// First authenticate user
const { sessionKeyAccount, kernelClient } = await login("alice", config)
 
// Update config with authenticated client
const authenticatedConfig = {
  ...config,
  kernelClient,
  sessionKeyAccount,
}
 
// Execute a token transfer
const hash = await writeContract(authenticatedConfig, {
  address: "0x...", // Token contract address
  abi: [
    {
      name: "transfer",
      type: "function",
      inputs: [
        { name: "to", type: "address" },
        { name: "amount", type: "uint256" },
      ],
      outputs: [{ name: "", type: "bool" }],
      stateMutability: "nonpayable",
    },
  ],
  functionName: "transfer",
  args: ["0x742d35Cc6634C0532925a3b8D52ECC5c5e9fA008", BigInt("1000000000000000000")], // 1 token
})
 
console.log("Transaction hash:", hash)

Advanced Examples

NFT Minting

const mintHash = await writeContract(authenticatedConfig, {
  address: "0x...", // NFT contract
  abi: nftAbi,
  functionName: "mint",
  args: [sessionKeyAccount.address, 1], // mint to self, token ID 1
})

Contract with ETH Value

const donationHash = await writeContract(authenticatedConfig, {
  address: "0x...", // Donation contract
  abi: donationAbi,
  functionName: "donate",
  value: BigInt("1000000000000000000"), // 1 ETH
})

writeContracts()

Executes multiple contract function calls in a single batch transaction.

Function Signature

function writeContracts(config: Config, calls: WriteContractParameters[]): Promise<string>

Parameters

  • config: Configuration object with authenticated client
  • calls: Array of contract call parameters

Usage Example

// Batch multiple operations
const batchHash = await writeContracts(authenticatedConfig, [
  {
    address: "0x...", // Token contract
    abi: tokenAbi,
    functionName: "approve",
    args: ["0x...", BigInt("1000000000000000000")],
  },
  {
    address: "0x...", // DEX contract
    abi: dexAbi,
    functionName: "swap",
    args: [BigInt("1000000000000000000"), BigInt("950000000000000000")],
  },
])
 
console.log("Batch transaction hash:", batchHash)

Benefits of Batching

  • Atomic Operations: All transactions succeed or fail together
  • Gas Efficiency: Single user operation instead of multiple
  • Better UX: One signature for multiple actions
  • Reduced Latency: Faster than sequential transactions

readContract()

Reads data from a smart contract without executing a transaction.

Function Signature

function readContract(config: Config, args: ReadContractParameters): Promise<any>

Parameters

args

  • Type: ReadContractParameters
  • Properties:
    • address: Contract address
    • abi: Contract ABI array
    • functionName: Function name to call
    • args: Array of function arguments (optional)

Usage Examples

Token Balance

const balance = await readContract(config, {
  address: "0x...", // Token contract
  abi: [
    {
      name: "balanceOf",
      type: "function",
      inputs: [{ name: "account", type: "address" }],
      outputs: [{ name: "", type: "uint256" }],
      stateMutability: "view",
    },
  ],
  functionName: "balanceOf",
  args: [sessionKeyAccount.address],
})
 
console.log("Token balance:", balance.toString())

Contract State

const totalSupply = await readContract(config, {
  address: "0x...",
  abi: tokenAbi,
  functionName: "totalSupply",
})
 
const name = await readContract(config, {
  address: "0x...",
  abi: tokenAbi,
  functionName: "name",
})
 
console.log(`${name} total supply:`, totalSupply.toString())

Complex Data Structures

// Reading a struct
const userInfo = await readContract(config, {
  address: "0x...",
  abi: stakingAbi,
  functionName: "getUserInfo",
  args: [sessionKeyAccount.address],
})
 
console.log("User info:", {
  stakedAmount: userInfo[0].toString(),
  rewardDebt: userInfo[1].toString(),
  lastStakeTime: new Date(Number(userInfo[2]) * 1000),
})

signTypedData()

Signs structured data according to EIP-712 standard.

Function Signature

function signTypedData(config: Config, args: SignTypedDataParameters): Promise<Signature>

Parameters

args

  • Type: SignTypedDataParameters
  • Properties:
    • domain: EIP-712 domain separator
    • types: Type definitions for the data
    • primaryType: The primary type being signed
    • message: The actual data to sign

Usage Example

// Sign a permit message for gasless token approval
const signature = await signTypedData(authenticatedConfig, {
  domain: {
    name: "MyToken",
    version: "1",
    chainId: 11155111,
    verifyingContract: "0x...",
  },
  types: {
    Permit: [
      { name: "owner", type: "address" },
      { name: "spender", type: "address" },
      { name: "value", type: "uint256" },
      { name: "nonce", type: "uint256" },
      { name: "deadline", type: "uint256" },
    ],
  },
  primaryType: "Permit",
  message: {
    owner: sessionKeyAccount.address,
    spender: "0x...",
    value: BigInt("1000000000000000000"),
    nonce: BigInt(0),
    deadline: BigInt(Math.floor(Date.now() / 1000) + 3600), // 1 hour
  },
})
 
console.log("Signature:", signature)

Signature Object

The function returns a Signature object with:

interface Signature {
  r: string // First 32 bytes of signature
  s: string // Second 32 bytes of signature
  v: number // Recovery parameter
}

Error Handling

Authentication Errors

try {
  await writeContract(config, contractParams)
} catch (error) {
  if (error.message.includes("Kernel client not initialized")) {
    console.log("User not authenticated - please login first")
    // Redirect to authentication flow
  }
}

Contract Execution Errors

try {
  await writeContract(authenticatedConfig, contractParams)
} catch (error) {
  if (error.message.includes("execution reverted")) {
    console.log("Contract execution failed:", error.message)
    // Handle specific contract errors
  } else if (error.message.includes("insufficient funds")) {
    console.log("Insufficient balance for transaction")
  } else if (error.message.includes("user rejected")) {
    console.log("User cancelled transaction")
  }
}

Read Contract Errors

try {
  const result = await readContract(config, contractParams)
} catch (error) {
  if (error.message.includes("call reverted")) {
    console.log("Contract call reverted - check parameters")
  } else if (error.message.includes("network")) {
    console.log("Network error - please try again")
  }
}

Best Practices

Gas Optimization

// Batch related operations
const operations = [approveTokenOperation, swapTokenOperation, stakeLPTokenOperation]
 
// Execute as single batch instead of 3 separate transactions
await writeContracts(authenticatedConfig, operations)

Error Recovery

async function executeWithRetry(operation: () => Promise<string>, maxRetries = 3) {
  for (let i = 0; i < maxRetries; i++) {
    try {
      return await operation()
    } catch (error) {
      if (i === maxRetries - 1) throw error
 
      // Wait before retry
      await new Promise((resolve) => setTimeout(resolve, 1000 * (i + 1)))
    }
  }
}
 
const hash = await executeWithRetry(() => writeContract(authenticatedConfig, contractParams))

Type Safety

// Define contract types for better TypeScript support
interface TokenContract {
  address: `0x${string}`
  abi: typeof tokenAbi
}
 
async function transferToken(contract: TokenContract, to: `0x${string}`, amount: bigint) {
  return writeContract(authenticatedConfig, {
    address: contract.address,
    abi: contract.abi,
    functionName: "transfer",
    args: [to, amount],
  })
}

Integration Examples

DeFi Application

class DeFiService {
  constructor(private config: Config) {}
 
  async swapTokens(tokenA: string, tokenB: string, amount: bigint) {
    // 1. Check allowance
    const allowance = await readContract(this.config, {
      address: tokenA,
      abi: erc20Abi,
      functionName: "allowance",
      args: [this.config.sessionKeyAccount.address, DEX_CONTRACT],
    })
 
    const operations = []
 
    // 2. Approve if needed
    if (allowance < amount) {
      operations.push({
        address: tokenA,
        abi: erc20Abi,
        functionName: "approve",
        args: [DEX_CONTRACT, amount],
      })
    }
 
    // 3. Execute swap
    operations.push({
      address: DEX_CONTRACT,
      abi: dexAbi,
      functionName: "swapExactTokensForTokens",
      args: [amount, 0n, [tokenA, tokenB], this.config.sessionKeyAccount.address],
    })
 
    // 4. Execute as batch
    return writeContracts(this.config, operations)
  }
}