Skip to main content
Looking for Contributors! This Skill needs an implementation.Contributing a Skill involves:
  1. Writing a reference implementation with full functionality
  2. Adding comprehensive tests
  3. Writing documentation with usage examples
See the react-query Skill for an example of a framework integration Skill.Interested? Open an issue or PR at github.com/evmts/voltaire.
Skill — Copyable reference implementation. Use as-is or customize. See Skills Philosophy.
Vue 3 composables for Ethereum development. Similar to the react-query Skill but using Vue’s reactivity system.

Planned Implementation

Composables

// useProvider.ts
import { ref, shallowRef, onMounted, onUnmounted } from 'vue';
import { BrowserProvider } from './BrowserProvider.js';

export function useProvider() {
  const provider = shallowRef(null);
  const accounts = ref([]);
  const chainId = ref(null);
  const isConnected = computed(() => accounts.value.length > 0);

  onMounted(() => {
    try {
      provider.value = BrowserProvider();
      provider.value.getAccounts().then(a => accounts.value = a);
      provider.value.getChainId().then(c => chainId.value = c);

      provider.value.onAccountsChanged(a => accounts.value = a);
      provider.value.onChainChanged(c => chainId.value = c);
    } catch (e) {
      // No provider available
    }
  });

  async function connect() {
    accounts.value = await provider.value?.requestAccounts();
  }

  return { provider, accounts, chainId, isConnected, connect };
}

useBalance

import { ref, watch, computed } from 'vue';

export function useBalance(address, options = {}) {
  const data = ref(null);
  const isLoading = ref(false);
  const error = ref(null);

  const { provider } = useProvider();

  async function fetch() {
    if (!address.value || !provider.value) return;
    isLoading.value = true;
    try {
      data.value = await provider.value.getBalance(address.value);
      error.value = null;
    } catch (e) {
      error.value = e;
    } finally {
      isLoading.value = false;
    }
  }

  watch([address, provider], fetch, { immediate: true });

  // Auto-refresh on new blocks
  if (options.watch) {
    // Set up block subscription
  }

  return { data, isLoading, error, refetch: fetch };
}

useContractRead

export function useContractRead({ address, abi, functionName, args }) {
  const data = ref(null);
  const isLoading = ref(false);
  const error = ref(null);

  const { provider } = useProvider();

  async function fetch() {
    if (!provider.value) return;
    isLoading.value = true;
    try {
      const contract = Contract({ address, abi, provider: provider.value });
      data.value = await contract.read[functionName](...(args.value || []));
      error.value = null;
    } catch (e) {
      error.value = e;
    } finally {
      isLoading.value = false;
    }
  }

  watch([args], fetch, { immediate: true, deep: true });

  return { data, isLoading, error, refetch: fetch };
}

useContractWrite

export function useContractWrite({ address, abi, functionName }) {
  const isLoading = ref(false);
  const error = ref(null);
  const txHash = ref(null);

  const { provider } = useProvider();

  async function write(...args) {
    isLoading.value = true;
    try {
      const contract = Contract({ address, abi, provider: provider.value });
      txHash.value = await contract.write[functionName](...args);
      error.value = null;
      return txHash.value;
    } catch (e) {
      error.value = e;
      throw e;
    } finally {
      isLoading.value = false;
    }
  }

  return { write, isLoading, error, txHash };
}

Example Component

<script setup>
import { useProvider, useBalance, useContractRead } from './composables';

const { accounts, isConnected, connect } = useProvider();
const { data: balance, isLoading: balanceLoading } = useBalance(
  computed(() => accounts.value[0])
);

const { data: tokenBalance } = useContractRead({
  address: USDC_ADDRESS,
  abi: ERC20_ABI,
  functionName: 'balanceOf',
  args: computed(() => [accounts.value[0]])
});
</script>

<template>
  <div v-if="!isConnected">
    <button @click="connect">Connect Wallet</button>
  </div>
  <div v-else>
    <p>Account: {{ accounts[0] }}</p>
    <p>Balance: {{ balance }} ETH</p>
    <p>USDC: {{ tokenBalance }}</p>
  </div>
</template>