> ## 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.

# Request Soroban Blockchain Transactions with SocketFi

> Complete guide to requesting Soroban contract transactions — building requests, handling approvals, managing errors, and updating UI state.

Every time a user wants to change on-chain state — send tokens, stake assets, vote on a proposal, deposit into a DeFi protocol — your application calls `socketfi.requestTransaction()`. The SDK takes care of constructing the transaction, presenting an approval UI so the user can review what they are signing, collecting the passkey authorization, and submitting the transaction to the Stellar network. Your app simply waits for the result.

This guide covers when to use `requestTransaction()`, how to build the request object, how to handle the user approval flow, fee handling, loading states, practical examples across common DeFi operations, and thorough error handling.

## When to use requestTransaction()

Use `requestTransaction()` for any operation that **modifies on-chain state**:

* Token transfers and approvals
* DeFi deposits, withdrawals, and claims
* Staking and unstaking
* Governance voting
* NFT minting, transfers, and burns
* Smart wallet policy updates

Use `readContract()` instead for any **read-only** query — balances, metadata, protocol state — that does not need to change anything. Read operations are free, instant, and require no user approval.

## Building the transaction request

Every call to `requestTransaction()` takes three fields:

```typescript theme={null}
type TransactionRequest = {
  contractId: string;   // Soroban contract address
  method: string;       // Contract function name
  args?: unknown[];     // Positional arguments (optional)
};
```

Keep your contract IDs in environment variables so you can swap between testnet and mainnet without code changes:

```bash .env theme={null}
VITE_TOKEN_CONTRACT_ID=CXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
VITE_VAULT_CONTRACT_ID=CXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
```

## The approval flow

When you call `requestTransaction()`, the SDK opens the SocketFi approval UI — a modal or browser sheet depending on the platform. The user sees:

* **Contract** — the Soroban contract address
* **Method** — the function being called
* **Arguments** — the parameters in human-readable form
* **Assets involved** — tokens being moved and amounts
* **Fees** — estimated network and protocol fees

The user either **approves** (the transaction is signed, submitted, and the promise resolves) or **rejects** (the promise rejects with a cancellation error). Your app never handles the raw passkey signature — the SDK manages that entirely.

## Fee handling overview

Every transaction passes through SocketFi's fee engine before execution. Depending on your plan and the transaction type, the outcome is one of:

| Outcome            | Behaviour                                                             |
| ------------------ | --------------------------------------------------------------------- |
| **Collect now**    | Fee is collected at execution time                                    |
| **Deferred**       | Fee is recorded and settled later                                     |
| **Cannot proceed** | Fee limit exceeded — transaction rejected before reaching the network |

Your application does not need to build fee logic. If the fee engine blocks a transaction, `requestTransaction()` rejects with a descriptive error message you can surface to the user.

## Loading states and UI feedback

Transactions take time — the approval UI, passkey prompt, and network submission all happen before the promise resolves. Always disable interactive elements and show a progress indicator while a transaction is in flight.

```typescript src/hooks/useTransaction.ts theme={null}
import { useState } from "react";
import { socketfi } from "../socketfi/client";

type TransactionStatus = "idle" | "approving" | "success" | "error";

export function useTransaction() {
  const [status, setStatus] = useState<TransactionStatus>("idle");
  const [txHash, setTxHash] = useState<string | null>(null);
  const [error, setError] = useState<string | null>(null);

  const request = async (
    contractId: string,
    method: string,
    args?: unknown[]
  ) => {
    setStatus("approving");
    setError(null);
    setTxHash(null);

    try {
      const result = await socketfi.requestTransaction({ contractId, method, args });
      setTxHash(result.transactionHash ?? null);
      setStatus("success");
      return result;
    } catch (err: unknown) {
      setError(err instanceof Error ? err.message : "Transaction failed");
      setStatus("error");
      throw err;
    }
  };

  return { status, txHash, error, request, isLoading: status === "approving" };
}
```

## Practical examples

### Token transfer

```typescript src/services/token.ts theme={null}
import { socketfi } from "../socketfi/client";

export async function transferTokens(
  from: string,
  to: string,
  amount: bigint
): Promise<{ success: boolean; transactionHash?: string }> {
  return socketfi.requestTransaction({
    contractId: import.meta.env.VITE_TOKEN_CONTRACT_ID,
    method: "transfer",
    args: [from, to, amount],
  });
}
```

Use it in a component:

```typescript src/components/SendForm.tsx theme={null}
import { useState } from "react";
import { transferTokens } from "../services/token";
import { useWalletAddress } from "../auth/hooks";

export function SendForm({ recipientAddress }: { recipientAddress: string }) {
  const walletAddress = useWalletAddress();
  const [loading, setLoading] = useState(false);
  const [txHash, setTxHash] = useState<string | null>(null);

  const handleSend = async () => {
    if (!walletAddress) return;
    setLoading(true);
    try {
      const result = await transferTokens(walletAddress, recipientAddress, 500n);
      setTxHash(result.transactionHash ?? null);
    } finally {
      setLoading(false);
    }
  };

  return (
    <div>
      <button onClick={handleSend} disabled={loading}>
        {loading ? "Processing…" : "Send 500 Tokens"}
      </button>
      {txHash && (
        <p>
          ✅ Sent! Transaction:{" "}
          <a href={`https://stellar.expert/explorer/testnet/tx/${txHash}`} target="_blank">
            {txHash.slice(0, 12)}…
          </a>
        </p>
      )}
    </div>
  );
}
```

### Staking

```typescript src/services/staking.ts theme={null}
import { socketfi } from "../socketfi/client";

export async function stakeTokens(walletAddress: string, amount: bigint) {
  return socketfi.requestTransaction({
    contractId: import.meta.env.VITE_STAKING_CONTRACT_ID,
    method: "stake",
    args: [walletAddress, amount],
  });
}

export async function unstakeTokens(walletAddress: string, amount: bigint) {
  return socketfi.requestTransaction({
    contractId: import.meta.env.VITE_STAKING_CONTRACT_ID,
    method: "unstake",
    args: [walletAddress, amount],
  });
}

export async function claimStakingRewards(walletAddress: string) {
  return socketfi.requestTransaction({
    contractId: import.meta.env.VITE_STAKING_CONTRACT_ID,
    method: "claim_rewards",
    args: [walletAddress],
  });
}
```

### DeFi vault deposit

```typescript src/services/vault.ts theme={null}
import { socketfi } from "../socketfi/client";

export async function depositToVault(
  walletAddress: string,
  amount: bigint
): Promise<{ success: boolean; transactionHash?: string }> {
  // Validate inputs before sending to the SDK
  if (amount <= 0n) throw new Error("Deposit amount must be greater than zero");

  return socketfi.requestTransaction({
    contractId: import.meta.env.VITE_VAULT_CONTRACT_ID,
    method: "deposit",
    args: [walletAddress, amount],
  });
}

export async function withdrawFromVault(
  walletAddress: string,
  shares: bigint
): Promise<{ success: boolean; transactionHash?: string }> {
  return socketfi.requestTransaction({
    contractId: import.meta.env.VITE_VAULT_CONTRACT_ID,
    method: "withdraw",
    args: [walletAddress, shares],
  });
}
```

### Governance vote

```typescript src/services/governance.ts theme={null}
import { socketfi } from "../socketfi/client";

export async function castVote(
  voterAddress: string,
  proposalId: string,
  inFavor: boolean
) {
  return socketfi.requestTransaction({
    contractId: import.meta.env.VITE_GOVERNANCE_CONTRACT_ID,
    method: "vote",
    args: [voterAddress, proposalId, inFavor],
  });
}
```

## Error handling

Always wrap `requestTransaction()` in `try/catch`. The promise rejects for several distinct reasons; your UI should handle each one differently.

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

async function executeTransfer(from: string, to: string, amount: bigint) {
  try {
    const result = await socketfi.requestTransaction({
      contractId: TOKEN_CONTRACT_ID,
      method: "transfer",
      args: [from, to, amount],
    });
    showSuccess(`Transaction confirmed: ${result.transactionHash}`);
  } catch (err: unknown) {
    const message = err instanceof Error ? err.message : "";
    handleTransactionError(message);
  }
}

function handleTransactionError(message: string) {
  if (message === "USER_CANCELLED" || message === "TRANSACTION_REJECTED") {
    // User tapped "Reject" in the approval UI — no alarming error needed
    showInfo("Transaction cancelled.");
    return;
  }

  if (message.toLowerCase().includes("policy")) {
    // A wallet policy (e.g. spending limit) blocked the transaction
    showError("This transaction exceeds your wallet's spending limit.");
    return;
  }

  if (message.toLowerCase().includes("fee")) {
    // Fee evaluation failed
    showError("Unable to process fees for this transaction. Please try again.");
    return;
  }

  if (message.toLowerCase().includes("contract")) {
    // The Soroban contract itself reverted
    showError("The contract returned an error. Check your inputs and try again.");
    return;
  }

  // Catch-all for unexpected errors
  showError("Transaction failed. Please try again or contact support.");
}
```

### Error reference

| Error code             | Common cause                                     | Recommended action                                         |
| ---------------------- | ------------------------------------------------ | ---------------------------------------------------------- |
| `USER_CANCELLED`       | User dismissed the passkey prompt before signing | Show a non-alarming dismissible notice                     |
| `TRANSACTION_REJECTED` | User tapped "Reject" in the approval UI          | Show a non-alarming dismissible notice                     |
| Authorization failed   | Passkey signature invalid                        | Ask the user to re-authenticate                            |
| Policy violation       | Wallet spending limit or rule exceeded           | Explain the limit and suggest a smaller amount             |
| Fee validation failed  | Fee engine blocked the transaction               | Retry or contact support                                   |
| Contract error         | Soroban contract reverted                        | Validate arguments; surface the revert reason if available |

## Tracking transactions after submission

Store the `transactionHash` returned on success. You can use it to:

* Display activity history in your app
* Link to a block explorer ([stellar.expert](https://stellar.expert))
* Build receipts or confirmations
* Power support workflows

```typescript src/services/activityLog.ts theme={null}
type ActivityEntry = {
  timestamp: number;
  method: string;
  transactionHash: string;
  status: "success" | "failed";
};

const log: ActivityEntry[] = [];

export function recordTransaction(
  method: string,
  transactionHash: string
) {
  log.push({ timestamp: Date.now(), method, transactionHash, status: "success" });
}
```

## Production tips

* **Validate inputs before calling the SDK.** Check wallet addresses, amounts, and any user-provided data before they reach `requestTransaction()`. A revert from bad inputs still costs the user time and potentially fees.
* **Never assume success.** Network conditions, contract logic, and policies can all cause failures. Always handle the error path.
* **Show clear intent.** Before calling `requestTransaction()`, display a confirmation screen in your own UI that explains what the transaction does, who it affects, and the amounts involved. The SocketFi approval UI is a second line of defence, not a replacement for good UX in your app.
* **Store transaction hashes.** They are your audit trail for debugging, support, and activity feeds.
