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

# Privy

> Integrate RebelFi with Privy server wallets

This guide shows how to sign and submit RebelFi transactions using [Privy](https://www.privy.io/) server wallets as your custody provider. Privy provides server-side wallet infrastructure with a simple REST API — no SDK dependency required.

<Info>
  For Privy setup, app configuration, and wallet policies, see the [Privy Developer Docs](https://docs.privy.io/).
</Info>

## How It Works

```
1. Plan operation        →  RebelFi returns unsigned transaction(s)
2. Send to Privy         →  POST RPC call with transaction fields
3. Privy signs & sends   →  Server-side signing + on-chain broadcast
4. Get txHash            →  Privy returns the hash immediately
5. Submit hash           →  Report the on-chain txHash back to RebelFi
```

Privy handles signing and broadcasting server-side via a REST API. For **EVM**, you call `eth_sendTransaction` with the transaction fields (`to`, `data`, `value`). For **Solana**, you call `signAndSendTransaction` with the base64-encoded serialized transaction. Both return the transaction hash immediately — no polling required.

## Prerequisites

* A Privy account with server wallets enabled
* An **App ID** and **App Secret** from Dashboard → App settings → Basics
* A Privy server wallet on the target network
* The wallet address registered with RebelFi
* No additional SDK required — uses `fetch` with Basic Auth

## Registering a Wallet

Create a Privy server wallet (or use an existing one) and register its address with RebelFi:

```typescript theme={null}
import { RebelfiClient, Blockchain } from '@rebelfi/sdk';

const PRIVY_API = 'https://api.privy.io';
const privyHeaders = {
  'Authorization': `Basic ${btoa(`${APP_ID}:${APP_SECRET}`)}`,
  'privy-app-id': APP_ID,
  'Content-Type': 'application/json',
};

// Create a new server wallet (or fetch an existing one)
const wallet = await fetch(`${PRIVY_API}/v1/wallets`, {
  method: 'POST',
  headers: privyHeaders,
  body: JSON.stringify({ chain_type: 'solana' }), // or 'ethereum'
}).then((r) => r.json());

console.log(`Privy wallet: ${wallet.id} (${wallet.address})`);

// Register with RebelFi
const rebelfi = new RebelfiClient({ apiKey: process.env.REBELFI_API_KEY });
const registered = await rebelfi.wallets.register({
  walletAddress: wallet.address,
  blockchain: wallet.chain_type === 'solana'
    ? Blockchain.SOLANA
    : Blockchain.ETHEREUM,
});
```

<Warning>
  The wallet address you register with RebelFi **must** match the Privy wallet address. Use the same wallet for all operations.
</Warning>

## The Privy Signer

The core of the integration is a function that takes a RebelFi unsigned transaction and sends it through Privy. It handles both EVM and Solana transactions:

```typescript theme={null}
import type { UnsignedTransactionDetail, EvmTransactionFields } from '@rebelfi/sdk';

const PRIVY_API = 'https://api.privy.io';

// Solana CAIP-2 identifiers (Solana doesn't use numeric chain IDs)
const SOLANA_CAIP2: Record<string, string> = {
  'mainnet-beta': 'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp',
  'devnet': 'solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1',
  'testnet': 'solana:4uhcVJyU9pJkvQyS88uRDiswHXSCkY3z',
};

function buildRpcBody(tx: UnsignedTransactionDetail, evmChainId?: string, solanaNetwork?: string) {
  const blockchain = tx.blockchain.toLowerCase();

  if (blockchain === 'solana') {
    return {
      method: 'signAndSendTransaction',
      caip2: SOLANA_CAIP2[solanaNetwork || 'devnet'],
      params: {
        transaction: tx.unsignedTransaction.serialized, // base64
        encoding: 'base64',
      },
    };
  }

  // EVM
  const evm = tx.evmTransaction!;
  return {
    method: 'eth_sendTransaction',
    caip2: `eip155:${evmChainId || '1'}`,
    params: {
      transaction: {
        to: evm.to,
        data: evm.data,
        value: evm.value || '0x0',
      },
    },
  };
}

async function signAndBroadcast(
  tx: UnsignedTransactionDetail,
  privyWalletId: string,
): Promise<{ txHash: string }> {
  const body = buildRpcBody(tx);

  const res = await fetch(`${PRIVY_API}/v1/wallets/${privyWalletId}/rpc`, {
    method: 'POST',
    headers: privyHeaders,
    body: JSON.stringify(body),
  });

  if (!res.ok) {
    throw new Error(`Privy RPC failed: ${res.status} ${await res.text()}`);
  }

  const result = await res.json();
  const txHash = result.data?.hash;

  if (!txHash) {
    throw new Error(`No hash returned: ${JSON.stringify(result)}`);
  }

  return { txHash };
}
```

<Note>
  Unlike Fireblocks and Dfns, Privy returns the transaction hash **immediately** after broadcasting — no polling loop needed. Privy handles gas estimation and signing server-side. RebelFi's reconciliation tracks on-chain confirmation.
</Note>

## Supply Example

Supply operations may produce multiple transactions (e.g., EVM token approval + deposit, or a single Solana transaction). Sign and submit them sequentially through Privy:

```typescript theme={null}
import { RebelfiClient } from '@rebelfi/sdk';

const rebelfi = new RebelfiClient({ apiKey: process.env.REBELFI_API_KEY });

async function supply(
  walletId: number,
  strategyId: number,
  amount: string,
  tokenAddress: string,
  privyWalletId: string,
) {
  // 1. Plan the supply — RebelFi returns unsigned transactions
  const operation = await rebelfi.operations.supply({
    walletId,
    strategyId,
    amount,
    tokenAddress,
  });
  console.log(`Operation ${operation.operationId} created`);

  // 2. Fetch the unsigned transactions
  const unsignedTxs = await rebelfi.operations.getUnsignedTransactions(
    operation.operationId,
  );
  console.log(`${unsignedTxs.length} transaction(s) to sign`);

  // 3. Sign and submit each transaction via Privy
  for (const tx of unsignedTxs) {
    console.log(`Signing: ${tx.description} (${tx.blockchain})`);
    const { txHash } = await signAndBroadcast(tx, privyWalletId);

    // 4. Report the hash back to RebelFi
    await rebelfi.transactions.submitHash({
      operationId: operation.operationId,
      txHash,
      transactionId: tx.attemptId,
    });
    console.log(`Submitted: ${tx.description} (${txHash})`);
  }

  // 5. Confirm final status
  const final = await rebelfi.operations.get(operation.operationId);
  console.log(`Final status: ${final.status}`);
  return final;
}
```

### What Happens Step by Step

<Steps>
  <Step title="Plan the supply">
    Call `operations.supply()` with your wallet, strategy, amount, and token address. RebelFi returns an operation with status `AWAITING_SIGNATURE`.
  </Step>

  <Step title="Fetch unsigned transactions">
    Call `operations.getUnsignedTransactions()` to get the transactions to sign. For EVM, you'll typically get two (approval + deposit). For Solana, you'll get one.
  </Step>

  <Step title="Send via Privy">
    For each unsigned transaction, POST to Privy's RPC endpoint. For EVM, call `eth_sendTransaction` with the `to`, `data`, and `value` fields. For Solana, call `signAndSendTransaction` with the base64-encoded transaction. Privy signs and broadcasts on-chain, returning the txHash immediately.
  </Step>

  <Step title="Submit hash to RebelFi">
    Take the `txHash` from Privy's response and submit it to RebelFi via `transactions.submitHash()`. Include the `transactionId` to identify which transaction in the operation you're submitting.
  </Step>

  <Step title="Confirm">
    RebelFi monitors the chain and updates the operation status to `CONFIRMED` once all transactions land.
  </Step>
</Steps>

## Unwind Example

Unwinding (withdrawing from a yield strategy) follows the same pattern:

```typescript theme={null}
async function unwind(
  walletId: number,
  strategyId: number,
  amount: string,
  privyWalletId: string,
) {
  const operation = await rebelfi.operations.unwind({
    walletId,
    strategyId,
    amount,
  });

  const unsignedTxs = await rebelfi.operations.getUnsignedTransactions(
    operation.operationId,
  );

  for (const tx of unsignedTxs) {
    const { txHash } = await signAndBroadcast(tx, privyWalletId);

    await rebelfi.transactions.submitHash({
      operationId: operation.operationId,
      txHash,
      transactionId: tx.attemptId,
    });
  }

  return rebelfi.operations.get(operation.operationId);
}
```

<Tip>
  For a full withdrawal, pass `fullWithdrawal: true` instead of `amount`:

  ```typescript theme={null}
  const operation = await rebelfi.operations.unwind({
    walletId,
    strategyId,
    fullWithdrawal: true,
  });
  ```
</Tip>

## EVM vs Solana

The `signAndBroadcast` function handles both chains. The key difference is the RPC method and transaction format:

|                        | EVM                                  | Solana                                |
| ---------------------- | ------------------------------------ | ------------------------------------- |
| **RPC method**         | `eth_sendTransaction`                | `signAndSendTransaction`              |
| **Chain identifier**   | `eip155:<chainId>` (e.g. `eip155:1`) | `solana:<genesis-hash>`               |
| **Transaction format** | JSON object (`to`, `data`, `value`)  | Base64-encoded serialized transaction |
| **Source field**       | `tx.evmTransaction`                  | `tx.unsignedTransaction.serialized`   |
| **Typical tx count**   | 2 (approve + deposit)                | 1                                     |
| **Polling needed**     | No                                   | No                                    |

<Info>
  The `tx.blockchain` field on each unsigned transaction tells you which chain it targets. Use this to route to the correct RPC method.
</Info>

## Authentication

Privy uses **Basic Auth** with your App ID and App Secret. Every request must include:

```
Authorization: Basic <base64(APP_ID:APP_SECRET)>
privy-app-id: <APP_ID>
```

<Warning>
  Keep your App Secret secure. Never expose it in client-side code or public repositories.
</Warning>

## Troubleshooting

<AccordionGroup>
  <Accordion title="401 Unauthorized" icon="lock">
    **Cause**: Invalid or missing authentication headers.

    **Solution**: Ensure both the `Authorization` header (Basic Auth with `APP_ID:APP_SECRET` base64-encoded) and the `privy-app-id` header are set correctly.
  </Accordion>

  <Accordion title="Transaction broadcast but not confirmed" icon="clock">
    **Cause**: Privy returns the txHash immediately upon broadcast. The transaction may still be pending on-chain.

    **Solution**: This is expected behavior. RebelFi monitors the chain and will update the operation status once the transaction is confirmed. You can also check transaction status via `GET /v1/transactions/{transaction_id}`.
  </Accordion>

  <Accordion title="Wrong chain_type for wallet" icon="shuffle">
    **Cause**: Registering a Solana wallet as Ethereum (or vice versa) with RebelFi.

    **Solution**: Check the Privy wallet's `chain_type` field and register with the matching `Blockchain` enum. A Solana Privy wallet must be registered as `Blockchain.SOLANA`.
  </Accordion>

  <Accordion title="Invalid CAIP-2 identifier" icon="circle-exclamation">
    **Cause**: Using an EVM-style chain ID for a Solana wallet, or an incorrect Solana genesis hash.

    **Solution**: For EVM, use `eip155:<chainId>` (e.g. `eip155:11155111` for Sepolia). For Solana, use the full CAIP-2 identifier with the genesis hash (e.g. `solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1` for devnet).
  </Accordion>
</AccordionGroup>

## Resources

* [Privy Developer Docs](https://docs.privy.io/)
* [Privy Server Wallets](https://docs.privy.io/guide/server-wallets)
* [Privy REST API — Wallet RPC](https://docs.privy.io/reference/wallets/rpc)
* [RebelFi SDK Reference](/sdk/reference/client)
* [EVM Integration Guide](/sdk/evm-integration)
* [Wallet Profiles](/guides/wallet-profiles)
