Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.goldsky.com/llms.txt

Use this file to discover all available pages before exploring further.

Compose allows you to interact with smart contracts with type safety and flexible wallet options. See wallets for details on the different types of wallets.

Code generation

To interact with smart contracts with full type safety, place your ABI JSON files in the src/contracts/ folder. Compose automatically generates TypeScript classes when you run compose start or compose deploy. You can also manually trigger code generation:
compose codegen
The generated file is written to .compose/generated/index.js. On compose deploy, the CLI bundles this file alongside your tasks so the same typed classes are available in the cloud.

Generated classes

For each ABI file (e.g., src/contracts/BitcoinOracleContract.json), Compose generates a typed class that you can access via evm.contracts:
import { TaskContext } from "compose";

export async function main({ evm, env }: TaskContext) {
  const wallet = await evm.wallet({ name: "my-wallet" });

  // Access the generated contract class via evm.contracts
  const bitcoinOracleContract = new evm.contracts.BitcoinOracleContract(
    env.ORACLE_ADDRESS as `0x${string}`,
    evm.chains.base,
    wallet
  );

  // Convert timestamp and price to bytes32 format
  const timestamp = Date.now();
  const bitcoinPrice = 65000;
  const timestampAsBytes32 = `0x${timestamp.toString(16).padStart(64, "0")}`;
  const priceAsBytes32 = `0x${Math.round(bitcoinPrice * 100).toString(16).padStart(64, "0")}`;

  // Call a state-changing method — returns { hash, receipt, userOpHash? }
  const { hash } = await bitcoinOracleContract.setPrice(
    timestampAsBytes32,
    priceAsBytes32
  );

  // Call a view method — returns the decoded value directly
  const result = await bitcoinOracleContract.getLatestPrice();
}
Contract methods are called directly on the instance without .write or .read suffixes. View/pure functions return their decoded value directly, while state-changing functions return { hash, receipt, userOpHash? } (the userOpHash is present only when gas sponsoring routes the transaction through a bundler).

Decoding event logs

Each generated contract class includes a static decodeEventLog() method for decoding onchain events with full type safety. This is commonly used in tasks triggered by onchain events:
import { TaskContext, OnchainEvent } from "compose";

export async function main({ evm }: TaskContext, payload: OnchainEvent) {
  // Decode using a generated contract class — returns a typed union of all events in the ABI
  const decoded = await evm.contracts.MyContract.decodeEventLog(payload);

  if (decoded.eventName === "Transfer") {
    console.log("Transfer:", decoded.args);
  }

  // Or use the top-level decodeEventLog with any ABI
  const myAbi = [/* ... */];
  const decoded2 = await evm.decodeEventLog(myAbi, payload);
}
You can also use decodeEventLog with a generic type parameter for a single known event type:
const decoded = await evm.contracts.MyContract.decodeEventLog<TransferEventDecoded>(payload);

Direct wallet methods

You can also interact with contracts directly through wallet methods without generated classes. This is useful for one-off calls or contracts you don’t want to check an ABI into the repo for:
import { TaskContext } from "compose";

export async function main({ evm, env }: TaskContext) {
  const wallet = await evm.wallet({ name: "my-wallet" });

  // Write to a contract — returns { hash, receipt, userOpHash? }
  const { hash, receipt } = await wallet.writeContract(
    evm.chains.polygon,
    env.CONTRACT_ADDRESS as `0x${string}`,
    "reportPayouts(bytes32,uint256[])",
    [resultId, payouts]
  );

  // Read from a contract — returns the decoded value directly
  const balance = await wallet.readContract<string>(
    evm.chains.polygon,
    env.CONTRACT_ADDRESS as `0x${string}`,
    "balanceOf(address) returns (uint256)",
    [wallet.address]
  );
}
Integer return types (uint*/int*) are serialized as strings over IPC, so use string as the generic type for readContract when the return is a numeric type. Parse into BigInt if you need to do arithmetic.
See the wallets documentation for the full writeContract, readContract, and sendTransaction signatures, gas handling, confirmations, and reorg protection options.