mcpField
W
wagmi-development

wagmi-development

github.com/wevm開発6.8k94 回インストール今日更新
リポジトリ

wagmi-development について

wagmi-developmentは、Wagmiの機能開発向けスキルで、Viemベースのアクションを扱い、Wagmiの設定アクションは対象外です。coreアクション、TanStack Queryオプション、ReactおよびVueバインディングの実装パターンに加え、テスト、エクスポート、検証手順を示します。

主な機能

  1. 01Viemをラップするcoreアクションの実装
  2. 02TanStack Queryオプションの追加
  3. 03ReactとVueのバインディング作成
  4. 04実行時テストと型テストの作成
  5. 05パッケージのインデックスから追加機能を公開
  6. 06フォーマットと型チェックの実行

活用シーン

  1. 01Wagmi coreにViemベースのアクションを追加
  2. 02TanStack Query経由でアクションを公開
  3. 03ReactフックまたはVueコンポーザブルを実装
  4. 04アクションの型と動作を検証

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/wevm/wagmi --skill wagmi-development

インストール後、Claude Code で/wagmi-developmentを入力して読み込まれたか確認します。

詳細

Wagmi の開発

Wagmi の機能を追加するためのフルスタックパターンです。このスキルでは、Viem ベースのアクションのみを扱います(Wagmi の設定アクションは対象外です)。

レイヤーの概要

  1. コアアクション(packages/core/src/actions/)- Viem をラップした基本機能
  2. クエリーオプション(packages/core/src/query/)- TanStack Query との統合
  3. フレームワークバインディング - React(packages/react/src/hooks/)、Vue(packages/vue/src/composables/)

1. コアアクション

構造
import {
  type MyActionErrorType as viem_MyActionErrorType,
  type MyActionParameters as viem_MyActionParameters,
  type MyActionReturnType as viem_MyActionReturnType,
  myAction as viem_myAction,
} from 'viem/actions'

import type { Config } from '../createConfig.js'
import type { ChainIdParameter, ConnectorParameter } from '../types/properties.js'
import type { Compute } from '../types/utils.js'
import { getAction } from '../utils/getAction.js'

export type MyActionParameters<config extends Config = Config> = Compute<
  ChainIdParameter<config> & viem_MyActionParameters
>

export type MyActionReturnType = viem_MyActionReturnType

export type MyActionErrorType = viem_MyActionErrorType

/** https://wagmi.sh/core/api/actions/myAction */
export async function myAction<config extends Config>(
  config: config,
  parameters: MyActionParameters<config>,
): Promise<MyActionReturnType> {
  const { chainId, ...rest } = parameters
  const client = config.getClient({ chainId })
  const action = getAction(client, viem_myAction, 'myAction')
  return action(rest)
}
主なルール
  • Viem のインポート:viem_ をプレフィックスとして付けます(例:viem_getBalance)
  • クライアントへのアクセス:
    • 読み取り専用:config.getClient({ chainId })
    • ウォレット:await getConnectorClient(config, { chainId, connector, account })
    • 混合:アカウントには getConnectorClient、アクションには getClient を使用します(estimateGas.ts を参照)
  • パラメーター:必ず ChainIdParameter<config> を追加します。ウォレットアクションには ConnectorParameter も追加します。
  • 型パラメーター:型推論のため、Viem の型パラメーターに合わせます。リテラル推論には const 修飾子を使用します(abi、args)。
  • スプレッド:Viem のアクションを呼び出す際は、Wagmi 固有のプロパティ(chainId、connector)を省きます。
テスト

実行時テスト(action.test.ts):

import { abi, address, config } from '@wagmi/test'
import { expect, test } from 'vitest'
import { myAction } from './myAction.js'

test('default', async () => {
  await expect(myAction(config, { /* required params */ })).resolves.toMatchInlineSnapshot(`...`)
})

test('parameters: chainId', async () => { /* test chainId param */ })

test('behavior: error case', async () => { /* test error handling */ })

型テスト(action.test-d.ts)- アクションに型推論がある場合のみ:

import { config } from '@wagmi/test'
import { expectTypeOf, test } from 'vitest'
import { myAction } from './myAction.js'

test('default', async () => {
  const result = await myAction(config, { /* params */ })
  expectTypeOf(result).toEqualTypeOf<ExpectedType>()
})

型ベンチマーク(action.bench-d.ts)- アクションに型推論がある場合のみ:

import { attest } from '@ark/attest'
import { test } from 'vitest'
import type { MyActionParameters } from './myAction.js'

test('default', () => {
  type Result = MyActionParameters<typeof abi.erc20, 'balanceOf'>
  const res = {} as Result
  attest.instantiations([12345, 'instantiations'])
  attest(res.args).type.toString.snap(`readonly [account: \`0x\${string}\`]`)
})

ウォレットアクションのテスト:事前に接続し、テスト後に切断します:

test('default', async () => {
  await connect(config, { connector })
  await expect(myAction(config, { /* params */ })).resolves.toMatchInlineSnapshot(`...`)
  await disconnect(config, { connector })
})

2. クエリーオプション

TanStack Query 用のクエリー(読み取り専用)またはミューテーション(ウォレット)オプションです。

クエリーの構造
import {
  type MyActionErrorType,
  type MyActionParameters,
  type MyActionReturnType,
  myAction,
} from '../actions/myAction.js'
import type { Config } from '../createConfig.js'
import type { ScopeKeyParameter } from '../types/properties.js'
import type { QueryOptions, QueryParameter } from '../types/query.js'
import type { Compute, ExactPartial } from '../types/utils.js'
import { filterQueryOptions, structuralSharing } from './utils.js'

export type MyActionOptions<
  config extends Config,
  selectData = MyActionData,
> = Compute<ExactPartial<MyActionParameters<config>> & ScopeKeyParameter> &
  QueryParameter<MyActionQueryFnData, MyActionErrorType, selectData, MyActionQueryKey<config>>

export function myActionQueryOptions<
  config extends Config,
  selectData = MyActionData,
>(
  config: config,
  options: MyActionOptions<config, selectData> = {},
): MyActionQueryOptions<config, selectData> {
  return {
    ...options.query,
    enabled: Boolean(options.requiredParam && (options.query?.enabled ?? true)),
    queryFn: async (context) => {
      const [, { scopeKey: _, ...parameters }] = context.queryKey
      if (!parameters.requiredParam) throw new Error('requiredParam is required')
      const result = await myAction(config, {
        ...(parameters as MyActionParameters),
        requiredParam: parameters.requiredParam,
      })
      return result ?? null
    },
    queryKey: myActionQueryKey(options),
    structuralSharing, // include when returning complex objects/arrays
  }
}

export type MyActionQueryFnData = Compute<MyActionReturnType>
export type MyActionData = MyActionQueryFnData

export function myActionQueryKey<config extends Config>(
  options: Compute<ExactPartial<MyActionParameters<config>> & ScopeKeyParameter> = {},
) {
  return ['myAction', filterQueryOptions(options)] as const
}

export type MyActionQueryKey<config extends Config> = ReturnType<typeof myActionQueryKey<config>>

export type MyActionQueryOptions<
  config extends Config,
  selectData = MyActionData,
> = QueryOptions<MyActionQueryFnData, MyActionErrorType, selectData, MyActionQueryKey<config>>
ミューテーションの構造
import type { MutationOptions, MutationParameter } from '../types/query.js'

export type MyActionOptions<config extends Config, context = unknown> = MutationParameter<
  MyActionData,
  MyActionErrorType,
  MyActionVariables<config>,
  context
>

export function myActionMutationOptions<config extends Config, context>(
  config: config,
  options: MyActionOptions<config, context> = {},
): MyActionMutationOptions<config> {
  return {
    ...options.mutation,
    mutationFn: async (variables) => {
      return myAction(config, variables)
    },
    mutationKey: ['myAction'],
  }
}

export type MyActionMutationOptions<config extends Config> = MutationOptions<
  MyActionData,
  MyActionErrorType,
  MyActionVariables<config>
>
主なルール
  • ExactPartial と UnionExactPartial:単純な型には ExactPartial、複雑なユニオン(コントラクトアクション)には UnionExactPartial を使用します。
  • enabled:必須パラメーターが truthy かどうかに基づいて設定します。
  • structuralSharing:アクションがオブジェクトや配列を返す場合に含めます。
  • filterQueryOptions:シリアライズできない一般的なプロパティをフィルタリングします。onReplaced のようなプロパティはクエリーキーから手動で除外します。
  • クエリーキー:常に ['actionName', filterQueryOptions(options)] とします。
テスト
import { config } from '@wagmi/test'
import { expect, test } from 'vitest'
import { myActionQueryOptions } from './myAction.js'

test('default', () => {
  expect(myActionQueryOptions(config, {})).toMatchInlineSnapshot(`
    {
      "enabled": false,
      "queryFn": [Function],
      "queryKey": ["myAction", {}],
    }
  `)
})

test('enabled', () => {
  expect(myActionQueryOptions(config, { requiredParam: 'value' }).enabled).toBe(true)
})

test('queryFn: calls query fn', async () => {
  const options = myActionQueryOptions(config, { requiredParam: 'value' })
  const result = await options.queryFn({ queryKey: options.queryKey } as any)
  expect(result).toMatchInlineSnapshot(`...`)
})

3. フレームワークバインディング

React クエリーフック
'use client'
import type { Config, MyActionErrorType, ResolvedRegister } from '@wagmi/core'
import type { Compute } from '@wagmi/core/internal'
import {
  type MyActionData,
  type MyActionOptions,
  myActionQueryOptions,
} from '@wagmi/core/query'
import type { ConfigParameter } from '../types/properties.js'
import { type UseQueryReturnType, useQuery } from '../utils/query.js'
import { useChainId } from './useChainId.js'
import { useConfig } from './useConfig.js'

export type UseMyActionParameters<
  config extends Config = Config,
  selectData = MyActionData,
> = Compute<MyActionOptions<config, selectData> & ConfigParameter<config>>

export type UseMyActionReturnType<selectData = MyActionData> =
  UseQueryReturnType<selectData, MyActionErrorType>

/** https://wagmi.sh/react/api/hooks/useMyAction */
export function useMyAction<
  config extends Config = ResolvedRegister['config'],
  selectData = MyActionData,
>(
  parameters: UseMyActionParameters<config, selectData> = {},
): UseMyActionReturnType<selectData> {
  const config = useConfig(parameters)
  const chainId = useChainId({ config })
  const options = myActionQueryOptions(config, {
    ...parameters,
    chainId: parameters.chainId ?? chainId,
    query: parameters.query,
  })
  return useQuery(options)
}
React ミューテーションフック
'use client'
import { useMutation } from '@tanstack/react-query'
import type { Config, ResolvedRegister, MyActionErrorType } from '@wagmi/core'
import {
  type MyActionData,
  type MyActionMutate,
  type MyActionMutateAsync,
  type MyActionOptions,
  type MyActionVariables,
  myActionMutationOptions,
} from '@wagmi/core/query'
import type { ConfigParameter } from '../types/properties.js'
import type { UseMutationReturnType } from '../utils/query.js'
import { useConfig } from './useConfig.js'

export type UseMyActionParameters<config extends Config = Config, context = unknown> =
  MyActionOptions<config, context> & ConfigParameter<config>

export type UseMyActionReturnType<config extends Config = Config, context = unknown> =
  UseMutationReturnType<
    MyActionData,
    MyActionErrorType,
    MyActionVariables<config>,
    context,
    MyActionMutate<config, context>,
    MyActionMutateAsync<config, context>
  >

/** https://wagmi.sh/react/api/hooks/useMyAction */
export function useMyAction<
  config extends Config = ResolvedRegister['config'],
  context = unknown,
>(
  parameters: UseMyActionParameters<config, context> = {},
): UseMyActionReturnType<config, context> {
  const config = useConfig(parameters)
  const options = myActionMutationOptions(config, parameters)
  const mutation = useMutation(options)
  type Return = UseMyActionReturnType<config, context>
  return {
    ...mutation,
    mutate: mutation.mutate as Return['mutate'],
    mutateAsync: mutation.mutateAsync as Return['mutateAsync'],
  }
}
Vue コンポーザブル(クエリー)
import type { Config, MyActionErrorType, ResolvedRegister } from '@wagmi/core'
import type { Compute } from '@wagmi/core/internal'
import {
  type MyActionData,
  type MyActionOptions,
  myActionQueryOptions,
} from '@wagmi/core/query'
import { computed } from 'vue'
import type { ConfigParameter } from '../types/properties.js'
import type { DeepMaybeRef } from '../types/ref.js'
import { deepUnref } from '../utils/cloneDeep.js'
import { type UseQueryReturnType, useQuery } from '../utils/query.js'
import { useChainId } from './useChainId.js'
import { useConfig } from './useConfig.js'

export type UseMyActionParameters<
  config extends Config = Config,
  selectData = MyActionData,
> = Compute<DeepMaybeRef<MyActionOptions<config, selectData> & ConfigParameter<config>>>

export type UseMyActionReturnType<selectData = MyActionData> =
  UseQueryReturnType<selectData, MyActionErrorType>

/** https://wagmi.sh/vue/api/composables/useMyAction */
export function useMyAction<
  config extends Config = ResolvedRegister['config'],
  selectData = MyActionData,
>(
  parameters: UseMyActionParameters<config, selectData> = {},
): UseMyActionReturnType<selectData> {
  const params = computed(() => deepUnref(parameters))
  const config = useConfig(params)
  const chainId = useChainId({ config })
  const options = computed(() =>
    myActionQueryOptions(config as any, {
      ...params.value,
      chainId: params.value.chainId ?? chainId.value,
      query: params.value.query,
    }),
  )
  return useQuery(options as any) as any
}
フレームワークのルール
ルールReactVue
先頭のディレクティブ'use client'なし
パラメーターのラッパーCompute<...>Compute<DeepMaybeRef<...>>
リアクティビティ直接computed() + deepUnref()
ドキュメント URLwagmi.sh/react/api/hooks/wagmi.sh/vue/api/composables/

共通ルール:

  • ResolvedRegister['config']:関数シグネチャでのみ使用し、型定義では使用しません。
  • フックに enabled/structuralSharing を含めません。これらは queryOptions で処理します。
テスト

クエリーフックの test-d.ts:

import { abi } from '@wagmi/test'
import { expectTypeOf, test } from 'vitest'
import { useMyAction } from './useMyAction.js'

test('select data', () => {
  const result = useMyAction({
    /* params */
    query: {
      select(data) {
        expectTypeOf(data).toEqualTypeOf<ExpectedDataType>()
        return data
      },
    },
  })
  expectTypeOf(result.data).toEqualTypeOf<ExpectedDataType>()
})

ミューテーションフックの test-d.ts:

import { expectTypeOf, test } from 'vitest'
import { useMyAction } from './useMyAction.js'

test('context', () => {
  const { mutate } = useMyAction({
    mutation: {
      onMutate(variables) {
        expectTypeOf(variables).toMatchTypeOf<{ /* expected shape */ }>()
        return { foo: 'bar' }
      },
      onError(error, variables, context) { /* test types */ },
      onSuccess(data, variables, context) { /* test types */ },
      onSettled(data, error, variables, context) { /* test types */ },
    },
  })

  mutate({ /* params */ }, {
    onSuccess(data, variables, context) { /* test inference */ },
  })
})

エクスポート

各パッケージの exports/index.ts に追加します:

// packages/core/src/exports/index.ts
export {
  type MyActionParameters,
  type MyActionReturnType,
  type MyActionErrorType,
  myAction,
} from '../actions/myAction.js'

// packages/core/src/exports/query.ts
export {
  type MyActionData,
  type MyActionOptions,
  type MyActionQueryFnData,
  type MyActionQueryKey,
  type MyActionQueryOptions,
  myActionQueryKey,
  myActionQueryOptions,
} from '../query/myAction.js'

// packages/react/src/exports/index.ts
export {
  type UseMyActionParameters,
  type UseMyActionReturnType,
  useMyAction,
} from '../hooks/useMyAction.js'

検証

# Format
pnpm format

# Type check (all or filtered)
pnpm check:types
pnpm --filter @wagmi/core check:types
pnpm --filter wagmi check:types

# Test (all or filtered)
pnpm test
pnpm test --project core
pnpm test --project react

# Update test snapshots
pnpm vitest -u

# Type benchmarks
pnpm bench:types

# Viem version mismatch in test snapshots
pnpm version:update:viem

# Build (all or filtered)
pnpm run clean && pnpm build
pnpm --filter @wagmi/core build

コメント