butr
Integrations

@mysten/sui

Read state with a Sui client, build transactions with the Transaction builder, sign and execute through the wallet.

@mysten/sui is Sui's official TypeScript SDK. butr discovers and manages the wallet; @mysten/sui owns the RPC client and the transaction builder (Transaction); butr's Sui adapter hands them to the wallet's Wallet Standard features.

Get the wallet

useSelectedWallet("sui") types the connector as a SuiAdapter, so sendTx takes a Transaction and signTransaction resolves { bytes, signature }:

import { useSelectedWallet } from "@usebutr/react";

const wallet = useSelectedWallet("sui");

Read state

The client talks to a Sui full node directly; read balances without routing through the wallet:

import { SuiGrpcClient } from "@mysten/sui/grpc";

const client = new SuiGrpcClient({
  baseUrl: "https://fullnode.testnet.sui.io:443",
  network: "testnet",
});

const { balance } = await client.getBalance({ owner: wallet.account.walletAddress });
const sui = `${Number(balance.balance) / 1_000_000_000} SUI`; // MIST → SUI

Sign a message

signMessage exists when the wallet advertises sui:signPersonalMessage, so check it first:

if (!wallet.connector.signMessage) {
  throw new Error(`${wallet.connector.name} cannot sign messages`);
}
const message = new TextEncoder().encode("Hello from butr + @mysten/sui");
const { signature, signedMessage } = await wallet.connector.signMessage(message, {
  account: wallet.account,
});

Build and send a transaction

Build with the Transaction builder and hand the object to sendTx. The Sui adapter routes it through sui:signAndExecuteTransaction, which signs and executes, and chain pins testnet for this one call without moving the wallet:

import { Transaction } from "@mysten/sui/transactions";
import { SUI_CHAINS } from "@usebutr/core";

if (!wallet.connector.sendTx) {
  throw new Error(`${wallet.connector.name} cannot execute transactions`);
}
const tx = new Transaction();
tx.setSender(wallet.account.walletAddress);
// A no-op self-transfer: split 0 MIST off the gas coin, send it back.
const [coin] = tx.splitCoins(tx.gas, [0]);
tx.transferObjects([coin], wallet.account.walletAddress);

const digest = await wallet.connector.sendTx(tx, {
  account: wallet.account,
  chain: SUI_CHAINS.testnet,
});
// digest → look up on https://suiscan.xyz/testnet/tx/<digest>

Without chain, the adapter uses its current chain, which is mainnet when the wallet advertises it.

sendTx resolves the digest, not signed bytes. For a sign-only flow, signTransaction (present with sui:signTransaction) resolves { bytes, signature }, the two halves executeTransactionBlock needs.

Other features

For a Wallet Standard feature butr does not wrap, narrow the signer and read the feature with getFeature, typed by the shapes @usebutr/sui exports:

import type { SuiSignTransactionFeature } from "@usebutr/sui";
import { getFeature } from "@usebutr/wallet-standard-shared";

const signer = await wallet.connector.getSigner();
if (signer.kind === "wallet-standard") {
  const feature = getFeature<SuiSignTransactionFeature>(
    signer.wallet,
    "sui:signTransaction",
    "signTransaction",
  );
}

Source: apps/demo-with-sui/src/app.tsx in the butr repository. Targets Sui testnet. Run pnpm dev --filter=demo-with-sui → https://usebutr.demo-with-sui.localhost.