> ## Documentation Index
> Fetch the complete documentation index at: https://docs.socket.fi/llms.txt
> Use this file to discover all available pages before exploring further.

# SocketFi React SDK: Transactions and Contract Reads

> Submit Soroban contract writes through a hosted approval popup and query on-chain state — all with typed SDK methods and clean UI state management.

The SocketFi React SDK exposes two methods for interacting with Soroban smart contracts: `requestTransaction()` for state-changing operations and `readContract()` for read-only queries. Write operations open a hosted approval popup so the user can review the transaction before it's submitted — read operations execute instantly with no popup required.

## TypeScript types

The SDK ships with full TypeScript definitions. Here are the core types for the transaction methods:

```typescript theme={null}
type TransactionRequest = {
  contractId: string;
  method: string;
  args?: unknown[];
};

type TransactionResult = {
  success: boolean;
  transactionHash?: string;
};

type ReadRequest = {
  contractId: string;
  method: string;
  args?: unknown[];
};

// socketfi.requestTransaction(request: TransactionRequest): Promise<TransactionResult>
// socketfi.readContract(request: ReadRequest): Promise<unknown>
```

## requestTransaction()

Use `requestTransaction()` when you need to write to a Soroban contract. The SDK opens a hosted approval popup showing the user the contract, method, and arguments. After the user approves, the SDK submits the transaction and returns the result.

### Transaction approval flow

```text theme={null}
requestTransaction() called
       ↓
Hosted approval popup opens
       ↓
User reviews contract + method
       ↓
User approves
       ↓
Smart wallet signs & submits
       ↓
Popup closes
       ↓
TransactionResult returned
```

### Transfer tokens example

```typescript theme={null}
import { socketfi } from "../lib/socketfi";

async function transferTokens(recipientXdr: string, amountXdr: string) {
  const result = await socketfi.requestTransaction({
    contractId: "CBXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
    method: "transfer",
    args: [
      recipientXdr, // recipient address encoded as XDR
      amountXdr,    // amount encoded as XDR i128
    ],
  });

  if (result.success) {
    console.log("Transfer confirmed:", result.transactionHash);
  }

  return result;
}
```

## readContract()

Use `readContract()` to query on-chain state without modifying it. Because reads are simulated rather than submitted, no approval popup opens and the user doesn't need to interact.

### Read token balance example

```typescript theme={null}
import { socketfi } from "../lib/socketfi";

async function getBalance(walletAddressXdr: string): Promise<unknown> {
  const balance = await socketfi.readContract({
    contractId: "CBXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
    method: "balance",
    args: [walletAddressXdr], // wallet address encoded as XDR
  });

  console.log("Balance:", balance);
  return balance;
}
```

## UI state management

Production UIs need more than success/error states — they should communicate loading, pending approval, and completed states to the user. The component below demonstrates a complete status-tracking pattern.

```typescript components/TransactionButton.tsx theme={null}
import { useState } from "react";
import { socketfi } from "../lib/socketfi";

type TxStatus = "idle" | "awaiting_approval" | "submitting" | "success" | "error";

type Props = {
  recipientXdr: string;
  amountXdr: string;
  contractId: string;
};

export function TransactionButton({ recipientXdr, amountXdr, contractId }: Props) {
  const [status, setStatus] = useState<TxStatus>("idle");
  const [txHash, setTxHash] = useState<string | null>(null);
  const [errorMessage, setErrorMessage] = useState<string | null>(null);

  async function handleTransfer() {
    setStatus("awaiting_approval");
    setErrorMessage(null);
    setTxHash(null);

    try {
      setStatus("submitting");

      const result = await socketfi.requestTransaction({
        contractId,
        method: "transfer",
        args: [recipientXdr, amountXdr],
      });

      if (result.success) {
        setTxHash(result.transactionHash ?? null);
        setStatus("success");
      } else {
        setErrorMessage("Transaction did not succeed. Please try again.");
        setStatus("error");
      }
    } catch (error: any) {
      if (error?.code === "USER_CANCELLED") {
        setStatus("idle");
        return;
      }
      setErrorMessage(error?.message ?? "Transaction failed. Please try again.");
      setStatus("error");
    }
  }

  const isLoading = status === "awaiting_approval" || status === "submitting";

  return (
    <div>
      <button onClick={handleTransfer} disabled={isLoading}>
        {status === "awaiting_approval" && "Waiting for approval…"}
        {status === "submitting" && "Submitting…"}
        {(status === "idle" || status === "success" || status === "error") &&
          "Send Transfer"}
      </button>

      {status === "success" && txHash && (
        <p>Transaction confirmed: {txHash}</p>
      )}

      {status === "error" && errorMessage && (
        <p role="alert">{errorMessage}</p>
      )}
    </div>
  );
}
```

## Full AuthProvider + transaction flow

This example combines the `AuthProvider` pattern from the Authentication guide with both transaction methods in a single component. It's a good reference for wiring everything together in a real feature.

```typescript components/WalletDashboard.tsx theme={null}
import { useState } from "react";
import { socketfi } from "../lib/socketfi";
import { useSocketFiAuth } from "../lib/AuthProvider";

const TOKEN_CONTRACT = "CBXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX";

export function WalletDashboard() {
  const { user, accessToken } = useSocketFiAuth();
  const [balance, setBalance] = useState<unknown>(null);
  const [txHash, setTxHash] = useState<string | null>(null);
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState<string | null>(null);

  if (!user || !accessToken) {
    return <p>Please sign in first.</p>;
  }

  async function fetchBalance() {
    setLoading(true);
    setError(null);
    try {
      // walletAddressXdr would come from your session or user profile
      const result = await socketfi.readContract({
        contractId: TOKEN_CONTRACT,
        method: "balance",
        args: ["AAAA..."], // wallet address as XDR
      });
      setBalance(result);
    } catch (err: any) {
      setError("Failed to read balance.");
      console.error(err);
    } finally {
      setLoading(false);
    }
  }

  async function sendTransfer() {
    setLoading(true);
    setError(null);
    setTxHash(null);
    try {
      const result = await socketfi.requestTransaction({
        contractId: TOKEN_CONTRACT,
        method: "transfer",
        args: [
          "AAAA...", // recipient address as XDR
          "AAAA...", // amount as XDR i128
        ],
      });
      if (result.success) {
        setTxHash(result.transactionHash ?? null);
      }
    } catch (err: any) {
      if (err?.code !== "USER_CANCELLED") {
        setError(err?.message ?? "Transaction failed.");
        console.error(err);
      }
    } finally {
      setLoading(false);
    }
  }

  return (
    <div>
      <h2>Wallet — {user.id}</h2>

      <button onClick={fetchBalance} disabled={loading}>
        {loading ? "Loading…" : "Refresh Balance"}
      </button>
      {balance !== null && <p>Balance: {String(balance)}</p>}

      <button onClick={sendTransfer} disabled={loading}>
        {loading ? "Waiting…" : "Send Transfer"}
      </button>
      {txHash && <p>Confirmed: {txHash}</p>}

      {error && <p role="alert">{error}</p>}
    </div>
  );
}
```

## Error handling for transactions

Always wrap transaction calls in `try/catch`. The table below lists the errors you're most likely to encounter.

```typescript theme={null}
try {
  const result = await socketfi.requestTransaction({
    contractId,
    method: "transfer",
    args: [recipientXdr, amountXdr],
  });
} catch (error: any) {
  switch (error?.code) {
    case "USER_CANCELLED":
      // User closed the approval popup — silently reset UI
      break;
    case "TRANSACTION_REJECTED":
      // User explicitly rejected in the approval popup
      showToast("Transaction cancelled.");
      break;
    case "TOKEN_EXPIRED":
      // Re-authenticate then retry
      const session = await socketfi.authenticate();
      setSession(session);
      break;
    default:
      console.error("Transaction error:", error);
      showToast("Something went wrong. Please try again.");
  }
}
```

### Transaction error codes reference

| Code                    | Cause                                    | Recommended action                                      |
| ----------------------- | ---------------------------------------- | ------------------------------------------------------- |
| `USER_CANCELLED`        | User closed the approval popup           | Reset UI silently; no error message needed              |
| `TRANSACTION_REJECTED`  | User explicitly rejected the transaction | Let the user know and offer to retry                    |
| `TOKEN_EXPIRED`         | The SocketFi session token has expired   | Call `authenticate()` again, then retry the transaction |
| `AUTHENTICATION_FAILED` | Session could not be re-established      | Prompt the user to sign in again                        |

<Tip>
  Test your transaction flows on `TESTNET` before switching to `MAINNET`. Testnet transactions use test XLM, so mistakes cost nothing.
</Tip>
