grimoire

grimoire show @kontor/kontor-sdk contract-interactions

Contract Interactions and WIT Specifications

Type-safe contract method invocation using WebAssembly Interface Types

contractswittype-safetywebassemblytype-system

Contract Interactions and WIT Specifications

Overview

The contract interaction system provides type-safe method invocation for smart contracts using WebAssembly Interface Types (WIT) specifications. This architecture ensures compile-time type safety when calling contract methods, automatically validating function names, parameters, and return types based on the contract's WIT definition.

WIT specifications serve as the type schema for contracts, defining the interface between your application and deployed smart contracts. The type system extracts and transforms WIT definitions into TypeScript types, enabling full IDE support and catching errors before runtime.

Key Concepts

WebAssembly Interface Types (WIT)

WIT is a schema definition language that describes the public interface of WebAssembly modules. In this context, WIT specifications define:

The WIT specification acts as a contract between your frontend code and the smart contract, ensuring both sides agree on the interface.

Type-Safe Contract Function Names

The ContractFunctionName type extracts valid function names from a WIT specification based on the desired state mutability:

export type ContractFunctionName<
  wit extends Wit | readonly unknown[] = Wit,
  mutability extends WitStateMutability = WitStateMutability,
> =
  ExtractWitFunctionNames<
    wit extends Wit ? wit : Wit,
    mutability
  > extends infer functionName extends string
    ? [functionName] extends [never]
      ? string
      : functionName
    : string;

This type:

Type-Safe Function Arguments

The ContractFunctionArgs type derives the expected argument types for a specific contract function:

export type ContractFunctionArgs<
  wit extends Wit | readonly unknown[] = Wit,
  mutability extends WitStateMutability = WitStateMutability,
  functionName extends ContractFunctionName<
    wit,
    mutability
  > = ContractFunctionName<wit, mutability>,
> =
  WitParametersToPrimitiveTypes<
    ExtractWitFunction<
      wit extends Wit ? wit : Wit,
      functionName,
      mutability
    >["inputs"],
    "inputs"
  > extends infer args
    ? [args] extends [never]
      ? readonly unknown[]
      : args
    : readonly unknown[];

This transforms WIT parameter definitions into TypeScript tuple types, converting WIT types to their TypeScript equivalents.

Type Widening for Runtime Flexibility

The Widen type provides controlled type widening to handle runtime values while maintaining type safety:

export type Widen<T> =
  // preserve unknown / any
  | ([unknown] extends [T] ? unknown : never)
  // preserve functions
  | (T extends (...args: any[]) => any ? T : never)
  // bigint-ish -> bigint
  | (T extends ResolvedRegister["bigIntType"] ? bigint : never)
  // booleans -> boolean
  | (T extends boolean ? boolean : never)
  // int-ish -> number
  | (T extends ResolvedRegister["intType"] ? number : never)
  // decimal -> canonical tuple or number (NO string)
  | (T extends ResolvedRegister["decimalType"]
      ? ResolvedRegister["decimalType"]
      : never)
  // addresses -> registered address type
  | (T extends ResolvedRegister["addressType"]
      ? ResolvedRegister["addressType"]
      : never)
  // plain strings (non-address)
  | (T extends string
      ? T extends ResolvedRegister["addressType"]
        ? never
        : string
      : never)
  // empty tuple
  | (T extends readonly [] ? readonly [] : never)
  // object / record types (but not arrays/tuples)
  | (T extends readonly any[]
      ? never
      : T extends Record<string, unknown>
        ? { [K in keyof T]: Widen<T[K]> }
        : never)
  // tuple/array-like types: recursively widen elements
  | (T extends { length: number }
      ? {
          [K in keyof T]: Widen<T[K]>;
        } extends infer V extends readonly unknown[]
        ? readonly [...V]
        : never
      : never);

This type:

Contract Function Parameters

The ContractFunctionParameters type combines all necessary information for a contract call:

export type ContractFunctionParameters<
  wit extends Wit | readonly unknown[] = Wit,
  mutability extends WitStateMutability = WitStateMutability,
  functionName extends ContractFunctionName<
    wit,
    mutability
  > = ContractFunctionName<wit, mutability>,
  args extends ContractFunctionArgs<
    wit,
    mutability,
    functionName
  > = ContractFunctionArgs<wit, mutability, functionName>,
  allFunctionNames = ContractFunctionName<wit, mutability>,
  allArgs = ContractFunctionArgs<wit, mutability, functionName>,
> = {
  wit: wit;
  functionName:
    | allFunctionNames
    | (functionName extends allFunctionNames ? functionName : never);
  contractAddress: ResolvedRegister["contractAddress"];
} & (readonly [] extends allArgs
  ? { args?: allArgs | undefined }
  : { args: args });

This type:

Contract Function Return Types

The ContractFunctionReturnType type derives the expected return type:

export type ContractFunctionReturnType<
  wit extends Wit | readonly unknown[] = Wit,
  mutability extends WitStateMutability = WitStateMutability,
  functionName extends ContractFunctionName<
    wit,
    mutability
  > = ContractFunctionName<wit, mutability>,
  args extends ContractFunctionArgs<
    wit,
    mutability,
    functionName
  > = ContractFunctionArgs<wit, mutability, functionName>,
> = wit extends Wit
  ? Wit extends wit
    ? unknown
    : WitParametersToPrimitiveTypes<
          ExtractWitFunctionForArgs<
            wit,
            mutability,
            functionName,
            args
          >["outputs"]
        > extends infer types
      ? types extends readonly []
        ? void
        : types extends readonly [infer type]
          ? type
          : types
      : never
  : unknown;

This type:

Function Overload Resolution

The ExtractWitFunctionForArgs type handles function overloading by matching arguments:

export type ExtractWitFunctionForArgs<
  wit extends Wit,
  mutability extends WitStateMutability,
  functionName extends ContractFunctionName<wit, mutability>,
  args extends ContractFunctionArgs<wit, mutability, functionName>,
> =
  ExtractWitFunction<
    wit,
    functionName,
    mutability
  > extends infer witFunction extends WitFunction
    ? IsUnion<witFunction> extends true
      ? UnionToTuple<witFunction> extends infer witFunctions extends
          readonly WitFunction[]

This type:

Type System Architecture

Registered Types Integration

The contract type system integrates with the global type registry through ResolvedRegister:

These registered types ensure consistency across the entire application and allow customization of type representations.

WIT Parameter Transformation

The WitParametersToPrimitiveTypes utility (referenced but not shown) transforms WIT parameter definitions into TypeScript types:

  1. Parses WIT parameter specifications
  2. Maps WIT primitive types to TypeScript primitives
  3. Handles complex types (records, variants, lists)
  4. Preserves type names and optional parameters
  5. Generates tuple types maintaining parameter order

State Mutability Filtering

Functions are categorized by state mutability:

The type system uses mutability to filter available functions and apply appropriate type constraints.

Usage Patterns

Basic Contract Interaction

import type { ContractFunctionParameters, ContractFunctionReturnType } from './sdk/types/contract';
import type { MyContractWit } from './contracts/my-contract.wit';

// Type-safe function call parameters
type TransferParams = ContractFunctionParameters<
  MyContractWit,
  'write',
  'transfer'
>;

// Expected return type
type TransferResult = ContractFunctionReturnType<
  MyContractWit,
  'write',
  'transfer'
>;

const params: TransferParams = {
  wit: myContractWit,
  functionName: 'transfer', // Autocomplete available
  contractAddress: '0x123...',
  args: [recipient, amount] // Type-checked tuple
};

View Function with No Arguments

type GetBalanceParams = ContractFunctionParameters<
  MyContractWit,
  'view',
  'getBalance'
>;

const params: GetBalanceParams = {
  wit: myContractWit,
  functionName: 'getBalance',
  contractAddress: '0x123...',
  // args is optional when function takes no parameters
};

Handling Multiple Return Values

// Function returning multiple values
type MultiReturnResult = ContractFunctionReturnType<
  MyContractWit,
  'view',
  'getAccountInfo'
>;
// Result type: [balance: bigint, nonce: number, isActive: boolean]

const [balance, nonce, isActive] = await contract.call(params);

Generic Contract Interaction

function callContract<
  TWit extends Wit,
  TMutability extends WitStateMutability,
  TFunctionName extends ContractFunctionName<TWit, TMutability>
>(
  params: ContractFunctionParameters<TWit, TMutability, TFunctionName>
): Promise<ContractFunctionReturnType<TWit, TMutability, TFunctionName>> {
  // Implementation with full type safety
}

Best Practices

Always Use WIT Specifications

Define WIT specifications for all contracts to enable type safety:

// Define WIT specification
const tokenWit = {
  functions: [
    {
      name: 'transfer',
      inputs: [{ name: 'to', type: 'address' }, { name: 'amount', type: 'u256' }],
      outputs: [{ name: 'success', type: 'bool' }],
      mutability: 'write'
    }
  ]
} as const satisfies Wit;

Leverage Type Narrowing

Use specific mutability types to restrict available functions:

// Only allow read-only functions
type ViewFunctions = ContractFunctionName<MyContractWit, 'view'>;

// Only allow state-changing functions
type WriteFunctions = ContractFunctionName<MyContractWit, 'write'>;

Handle Type Widening Appropriately

Use Widen when accepting user input or runtime values:

import type { Widen, UnionWiden } from './sdk/types/contract';

// Accept wider types for user input
function submitTransaction(
  args: Widen<ContractFunctionArgs<MyContractWit, 'write', 'transfer'>>
) {
  // Implementation
}

Related Type Utilities

The contract interaction system relies on several utility types:

These utilities work together to provide comprehensive type safety throughout the contract interaction lifecycle.

Type Safety Benefits

  1. Compile-time validation: Catch invalid function names and arguments before runtime
  2. IDE autocomplete: Full IntelliSense support for function names and parameters
  3. Refactoring safety: Changes to WIT specifications propagate through the type system
  4. Documentation: Types serve as inline documentation of contract interfaces
  5. Version compatibility: Type mismatches reveal contract version incompatibilities

Related Files

For deeper understanding of the contract interaction system: