Skip to content

Typed useWriteContract with ABI inference #35

@satoshai-dev

Description

@satoshai-dev

Summary

Add type-safe contract interactions to useWriteContract by inferring function names and argument types from Clarity contract ABIs (as const objects). Users get autocomplete on function names and type-checked named args without importing ClarityValue constructors. Main feature for v1.

ABI source agnostic — works with any ClarityAbi (Clarinet output, Stacks API, @satoshai/abi-cli, inline). Full type inference requires as const; plain ClarityAbi objects still work at runtime without autocomplete.

API

Typed mode (with ABI)

import { abi } from './abis/amm-pool-v2-01';

const { writeContract } = useWriteContract();

writeContract({
  abi,
  contractAddress: 'SP102V8P0F7JX67ARQ77WEA3D3CFB5XW39REDT0AM',
  contractName: 'amm-pool-v2-01',
  functionName: 'swap',       // autocompletes only public functions
  args: {
    'token-x': 'SP...addr.token-x',  // TraitReference
    'token-y': 'SP...addr.token-y',  // TraitReference
    'amount': 1000000n,               // bigint (uint128)
  },
});

Contract config helper

const ammPool = createContractConfig({
  abi,
  contractAddress: 'SP102V8P0F7JX67ARQ77WEA3D3CFB5XW39REDT0AM',
  contractName: 'amm-pool-v2-01',
});

writeContract({ ...ammPool, functionName: 'swap', args: { ... } });
writeContract({ ...ammPool, functionName: 'add-liquidity', args: { ... } });

Untyped mode (backward compatible)

writeContract({
  contractAddress: 'SP102V8P...',
  contractName: 'amm-pool-v2-01',
  functionName: 'swap',
  functionArgs: [contractPrincipalCV(...), contractPrincipalCV(...), uintCV(1000000n)],
});

When abi is omitted, the current API works as-is. No breaking change.

Clarity → TypeScript type mapping

Clarity ABI type TypeScript type Runtime conversion
uint128 bigint uintCV()
int128 bigint intCV()
bool boolean boolCV()
principal string standardPrincipalCV() / contractPrincipalCV() (auto-detected by . presence)
trait_reference `${string}.${string}` contractPrincipalCV()
none null noneCV()
{ buffer: { length: N } } Uint8Array bufferCV()
{ 'string-ascii': { length: N } } string stringAsciiCV()
{ 'string-utf8': { length: N } } string stringUtf8CV()
{ optional: T } ClarityTypeToTS<T> | null someCV() / noneCV()
{ list: { type: T, length: N } } ClarityTypeToTS<T>[] listCV()
{ tuple: [...] } { [name]: ClarityTypeToTS<type> } tupleCV()
{ response: { ok: T, error: E } } N/A (output only) N/A

Implementation

1. Type utilities — via clarity-abitype

Use clarity-abitype (v0.4.0, MIT, zero deps, type-only) for:

  • ExtractAbiFunctionNames<TAbi, 'public'> — public function name union
  • ExtractAbiFunction<TAbi, TName> — function definition lookup
  • ClarityAbiArgsToPrimitiveTypes — Clarity types → TS primitives

Add kit-specific types:

  • TraitReference`${string}.${string}` template literal
  • TypedWriteContractVariables<TAbi> — overloaded variables type narrowing args by functionName

2. Runtime converter (src/utils/to-clarity-value.ts)

Thin ~30-40 line function: JS natives → ClarityValues, guided by ABI type descriptor. Direct conversion using @stacks/transactions constructors (uintCV, intCV, boolCV, principalCV, etc.), recursive for tuples/lists/optionals.

Clear error messages on type mismatches:

@satoshai/kit: Argument "amount" expects bigint (uint128), got number

3. Contract config helper (src/utils/create-contract-config.ts)

createContractConfig({ abi, contractAddress, contractName }) — typed spread-able object.

4. Hook changes (use-write-contract.ts)

  • When abi + named args present: iterate ABI function args, call toClarityValue for each in order, pass to @stacks/connect
  • When abi absent: pass functionArgs as-is (current behavior)

5. Exports

Export createContractConfig, TraitReference, and relevant type utilities from src/index.ts.

Dependencies

  • clarity-abitype — type-only dev dependency for type-level ABI utilities (zero runtime cost, can be replaced with own types if unmaintained)
  • No new runtime deps — conversion uses @stacks/transactions constructors (already a peer dep)

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions