Documentation

React Hooks Reference

React bindings for Selendra TypeScript SDK v1.0.4

React Hooks Reference

React bindings for @selendrajs/sdk v1.0.4. Provides hooks for connecting to Selendra, querying balances, and managing transactions.

Installation

npm install @selendrajs/sdk

Exports

import {
  // Provider
  SelendraProvider,
  SelendraContext,

  // Hooks
  useSelendra,
  useBalance,
  useTransaction,
  useTransactions,

  // Types
  type SelendraProviderProps,
  type SelendraContextValue,
  type UseSelendraResult,
  type UseBalanceResult,
  type BalanceState,
  type UseTransactionResult,
  type TransactionState,
  type TransactionStatus,
} from "@selendrajs/sdk/react";

SelendraProvider

Context provider that makes the SDK available to all child components.

Props

PropTypeRequiredDescription
configSDKConfigYesSDK configuration
childrenReactNodeYesChild components
autoConnectbooleanNoAuto-connect on mount
onConnected(sdk: SelendraSDK) => voidNoCalled when connected
onDisconnected() => voidNoCalled when disconnected
onError(error: Error) => voidNoCalled on error

Example

import { SelendraProvider } from "@selendrajs/sdk/react";

function App() {
  return (
    <SelendraProvider
      config={{ endpoint: "wss://rpc.selendra.org" }}
      autoConnect
      onConnected={(sdk) =>
        console.log("Connected to", sdk.getConnectionInfo()?.chainName)
      }
      onError={(error) => console.error("Connection error:", error)}
    >
      <MyApp />
    </SelendraProvider>
  );
}

useSelendra

Core hook for accessing the SDK instance and connection state.

Returns

interface UseSelendraResult {
  sdk: SelendraSDK | null; // SDK instance (null if not connected)
  isConnected: boolean; // Whether SDK is connected
  isConnecting: boolean; // Whether SDK is connecting
  error: Error | null; // Connection error if any
  connectionInfo: ConnectionInfo | null; // Chain info (name, token, decimals)
  connect: () => Promise<void>; // Connect to chain
  disconnect: () => Promise<void>; // Disconnect from chain
  reconnect: () => Promise<void>; // Reconnect to chain
}

Example

import { useSelendra } from "@selendrajs/sdk/react";

function ChainInfo() {
  const { sdk, isConnected, isConnecting, connectionInfo, error } =
    useSelendra();

  if (error) return <div>Error: {error.message}</div>;
  if (isConnecting) return <div>Connecting...</div>;
  if (!isConnected) return <div>Not connected</div>;

  return (
    <div>
      <p>Chain: {connectionInfo?.chainName}</p>
      <p>Token: {connectionInfo?.tokenSymbol}</p>
      <p>Decimals: {connectionInfo?.tokenDecimals}</p>
    </div>
  );
}

Querying with SDK

function AccountInfo({ address }: { address: string }) {
  const { sdk, isConnected } = useSelendra();
  const [account, setAccount] = useState(null);

  useEffect(() => {
    if (sdk && isConnected) {
      sdk.pallets.balances.queries.account(address).then(setAccount);
    }
  }, [sdk, isConnected, address]);

  return account ? <div>Free: {account.free.toString()}</div> : null;
}

useBalance

Hook for querying and subscribing to account balances.

Parameters

useBalance(address: string, options?: {
  subscribe?: boolean;  // Subscribe to balance changes (default: false)
})

Returns

interface UseBalanceResult {
  balance: BalanceState | null; // Balance data
  loading: boolean; // Whether loading
  error: Error | null; // Error if any
  refresh: () => Promise<void>; // Refresh balance
  subscribe: () => () => void; // Subscribe to changes
}

interface BalanceState {
  raw: AccountData | null; // Raw chain data
  free: string; // Free balance (formatted)
  reserved: string; // Reserved balance
  total: string; // Total balance
  frozen: string; // Frozen balance
  transferable: string; // Transferable (free - frozen)
  locks: BalanceLock[]; // Balance locks
  reserves: ReserveData[]; // Balance reserves
  existentialDeposit: string; // Existential deposit
  symbol: string; // Token symbol (SEL)
  decimals: number; // Token decimals (18)
}

Example

import { useBalance } from "@selendrajs/sdk/react";

function BalanceDisplay({ address }: { address: string }) {
  const { balance, loading, error, refresh } = useBalance(address);

  if (loading) return <div>Loading...</div>;
  if (error) return <div>Error: {error.message}</div>;
  if (!balance) return null;

  return (
    <div>
      <p>
        Free: {balance.free} {balance.symbol}
      </p>
      <p>
        Reserved: {balance.reserved} {balance.symbol}
      </p>
      <p>
        Total: {balance.total} {balance.symbol}
      </p>
      <p>
        Transferable: {balance.transferable} {balance.symbol}
      </p>
      <button onClick={refresh}>Refresh</button>
    </div>
  );
}

With Subscription

function LiveBalance({ address }: { address: string }) {
  const { balance, subscribe } = useBalance(address, { subscribe: true });

  useEffect(() => {
    const unsubscribe = subscribe();
    return () => unsubscribe();
  }, [subscribe]);

  return balance ? (
    <div>
      Balance: {balance.free} {balance.symbol} (live)
    </div>
  ) : null;
}

useTransaction

Hook for executing single transactions with status tracking.

Returns

interface UseTransactionResult {
  status: TransactionStatus; // 'idle' | 'pending' | 'success' | 'error'
  txHash: string | null; // Transaction hash
  blockHash: string | null; // Block hash (when finalized)
  error: Error | null; // Error if failed
  events: unknown[]; // Transaction events
  isPending: boolean; // status === 'pending'
  isSuccess: boolean; // status === 'success'
  isError: boolean; // status === 'error'
  execute: <T>(tx: (sdk: SelendraSDK) => Promise<T>) => Promise<T | null>;
  reset: () => void; // Reset state for next tx
}

Example

import { useTransaction, useSelendra } from "@selendrajs/sdk/react";

function TransferForm() {
  const { sdk } = useSelendra();
  const { status, txHash, error, execute, reset, isPending, isSuccess } =
    useTransaction();
  const [recipient, setRecipient] = useState("");
  const [amount, setAmount] = useState("");

  const handleTransfer = async () => {
    if (!sdk) return;

    await execute(async (sdk) => {
      const tx = sdk.pallets.balances.manager.transfer({
        dest: recipient,
        value: amount,
      });

      return new Promise((resolve, reject) => {
        tx.signAndSend(signer, {}, (result) => {
          if (result.status.isFinalized) {
            resolve({
              txHash: result.txHash.toHex(),
              blockHash: result.status.asFinalized.toHex(),
              events: result.events,
            });
          }
          if (result.isError) {
            reject(new Error("Transaction failed"));
          }
        });
      });
    });
  };

  return (
    <div>
      <input
        placeholder="Recipient address"
        value={recipient}
        onChange={(e) => setRecipient(e.target.value)}
        disabled={isPending}
      />
      <input
        type="text"
        placeholder="Amount"
        value={amount}
        onChange={(e) => setAmount(e.target.value)}
        disabled={isPending}
      />
      <button onClick={handleTransfer} disabled={isPending}>
        {isPending ? "Sending..." : "Send"}
      </button>

      {isSuccess && (
        <div>
          ✅ Transaction successful!
          <br />
          Hash: {txHash}
        </div>
      )}

      {error && <div>❌ Error: {error.message}</div>}

      {isSuccess && <button onClick={reset}>Reset</button>}
    </div>
  );
}

useTransactions

Hook for managing batch/multiple transactions.

Returns

interface UseTransactionsResult {
  transactions: TransactionState[]; // Array of transaction states
  isAnyPending: boolean; // Any transaction pending
  isAllSuccess: boolean; // All transactions succeeded
  hasAnyError: boolean; // Any transaction failed
  executeBatch: <T>(
    txs: Array<(sdk: SelendraSDK) => Promise<T>>
  ) => Promise<(T | null)[]>;
  reset: () => void; // Reset all states
}

Example

import { useTransactions } from "@selendrajs/sdk/react";

function BatchTransfer() {
  const { transactions, isAnyPending, isAllSuccess, executeBatch, reset } =
    useTransactions();

  const handleBatch = async () => {
    await executeBatch([
      async (sdk) => {
        // First transfer
        const tx = sdk.pallets.balances.manager.transfer({
          dest: "addr1",
          value: "1000000000000000000",
        });
        return await signAndWait(tx, signer);
      },
      async (sdk) => {
        // Second transfer
        const tx = sdk.pallets.balances.manager.transfer({
          dest: "addr2",
          value: "2000000000000000000",
        });
        return await signAndWait(tx, signer);
      },
    ]);
  };

  return (
    <div>
      <button onClick={handleBatch} disabled={isAnyPending}>
        {isAnyPending ? "Processing..." : "Execute Batch"}
      </button>

      {transactions.map((tx, i) => (
        <div key={i}>
          Transaction {i + 1}: {tx.status}
          {tx.txHash && <span> - {tx.txHash}</span>}
        </div>
      ))}

      {isAllSuccess && <div>✅ All transactions completed!</div>}

      <button onClick={reset}>Reset</button>
    </div>
  );
}

Complete Example App

import {
  SelendraProvider,
  useSelendra,
  useBalance,
  useTransaction,
} from "@selendrajs/sdk/react";

// Main App with Provider
function App() {
  return (
    <SelendraProvider
      config={{ endpoint: "wss://rpc.selendra.org" }}
      autoConnect
    >
      <Wallet />
    </SelendraProvider>
  );
}

// Wallet Component
function Wallet() {
  const { isConnected, isConnecting, connectionInfo, error } = useSelendra();
  const [address, setAddress] = useState("");

  if (isConnecting) return <div>Connecting to Selendra...</div>;
  if (error) return <div>Connection Error: {error.message}</div>;
  if (!isConnected) return <div>Not connected</div>;

  return (
    <div>
      <h1>{connectionInfo?.chainName}</h1>

      <input
        placeholder="Enter address"
        value={address}
        onChange={(e) => setAddress(e.target.value)}
      />

      {address && <BalanceCard address={address} />}
    </div>
  );
}

// Balance Card
function BalanceCard({ address }: { address: string }) {
  const { balance, loading, error, refresh } = useBalance(address);

  if (loading) return <div>Loading balance...</div>;
  if (error) return <div>Error: {error.message}</div>;
  if (!balance) return null;

  return (
    <div className="balance-card">
      <h3>Balance</h3>
      <p>
        Free: {balance.free} {balance.symbol}
      </p>
      <p>
        Reserved: {balance.reserved} {balance.symbol}
      </p>
      <p>
        Transferable: {balance.transferable} {balance.symbol}
      </p>
      <button onClick={refresh}>🔄 Refresh</button>
    </div>
  );
}

export default App;

TypeScript Types

// SDK Config
interface SDKConfig {
  endpoint?: string;
  network?: string;
  chainType?: ChainType;
  autoReconnect?: boolean;
  debug?: boolean;
}

// Connection Info
interface ConnectionInfo {
  chainName: string;
  tokenSymbol: string;
  tokenDecimals: number;
  ss58Prefix: number;
}

// Transaction Status
type TransactionStatus = "idle" | "pending" | "success" | "error";

// Balance Lock
interface BalanceLock {
  id: string;
  amount: bigint;
  reasons: string;
}

// Reserve Data
interface ReserveData {
  id: string;
  amount: bigint;
}

Upcoming Hooks

The following hooks are implemented but temporarily disabled pending API updates:

HookPurposeStatus
useStakingStaking operationsComing soon
useGovernanceDemocracy & votingComing soon
useNominationPoolsPool stakingComing soon

These will be re-enabled in a future SDK release.


See Also

Contribute

Found an issue or want to contribute?

Help us improve this documentation by editing this page on GitHub.

Edit this page on GitHub
Selendra - Build on Cambodian Blockchain with Testnet