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

# Dfns

> Integrate RebelFi with Dfns programmable wallet infrastructure

This guide shows how to sign and submit RebelFi transactions using [Dfns](https://www.dfns.co/) as your custody provider. Dfns provides programmable wallets with server-side MPC signing, supporting both EVM and Solana chains.

<Info>
  For Dfns setup, service accounts, and wallet configuration, see the [Dfns Developer Docs](https://docs.dfns.co/).
</Info>

## How It Works

```
1. Plan operation        →  RebelFi returns unsigned transaction(s)
2. Broadcast via Dfns    →  Pass transaction to Dfns broadcastTransaction
3. Dfns signs & sends    →  Server-side MPC signing + on-chain broadcast
4. Poll for confirmation →  Wait for Dfns to confirm the transaction
5. Submit hash           →  Report the on-chain txHash back to RebelFi
```

Unlike custody providers that require you to build and serialize transactions locally, Dfns handles signing server-side. For **EVM**, you pass the transaction fields (`to`, `data`, `value`) as a JSON object. For **Solana**, you pass the serialized unsigned transaction as a hex-encoded string.

## Prerequisites

* A Dfns account with a service account configured
* An Ed25519 key pair for the service account (generated during setup)
* A Dfns wallet on the target network (e.g., `Ethereum`, `Solana`, `Polygon`)
* The wallet address registered with RebelFi
* `@dfns/sdk` and `@dfns/sdk-keysigner` installed: `npm install @dfns/sdk @dfns/sdk-keysigner`

## Setting Up the Dfns Client

Create a Dfns API client using your service account credentials:

```typescript theme={null}
import fs from 'node:fs';
import { DfnsApiClient } from '@dfns/sdk';
import { AsymmetricKeySigner } from '@dfns/sdk-keysigner';

const signer = new AsymmetricKeySigner({
  credId: process.env.DFNS_CRED_ID,
  privateKey: fs.readFileSync(process.env.DFNS_PRIVATE_KEY_PATH, 'utf-8'),
});

const dfns = new DfnsApiClient({
  orgId: process.env.DFNS_ORG_ID,
  authToken: process.env.DFNS_AUTH_TOKEN,
  baseUrl: 'https://api.dfns.io',
  signer,
});
```

## Registering a Wallet

Fetch your Dfns wallet address and register it with RebelFi:

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

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

// Get the wallet address from Dfns
const dfnsWallet = await dfns.wallets.getWallet({
  walletId: process.env.DFNS_WALLET_ID,
});

// Register with RebelFi — detect blockchain from the network
const blockchain = dfnsWallet.network.toLowerCase().includes('solana')
  ? Blockchain.SOLANA
  : Blockchain.ETHEREUM;

const wallet = await rebelfi.wallets.register({
  walletAddress: dfnsWallet.address,
  blockchain,
});
```

<Warning>
  The wallet address you register with RebelFi **must** match the Dfns wallet address. Each Dfns wallet has a single address tied to its network.
</Warning>

## The Dfns Signer

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

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

const POLL_INTERVAL_MS = 3_000;
const MAX_POLL_ATTEMPTS = 120;

const TERMINAL_STATUSES = new Set(['Confirmed', 'Failed', 'Rejected']);

function buildBroadcastBody(tx: UnsignedTransactionDetail) {
  const blockchain = tx.blockchain.toLowerCase();

  if (blockchain === 'solana') {
    // Solana: convert base64 serialized tx to hex
    const hexTx = '0x' + Buffer.from(
      tx.unsignedTransaction.serialized, 'base64'
    ).toString('hex');
    return { kind: 'Transaction' as const, transaction: hexTx };
  }

  // EVM: pass structured transaction fields
  const evm = tx.evmTransaction!;
  return {
    kind: 'Transaction' as const,
    transaction: {
      to: evm.to,
      data: evm.data,
      value: evm.value || '0',
    },
  };
}

async function signAndBroadcast(
  tx: UnsignedTransactionDetail,
  walletId: string,
): Promise<{ txHash: string }> {
  const result = await dfns.wallets.broadcastTransaction({
    walletId,
    body: buildBroadcastBody(tx),
  });

  const dfnsTxId = result.id;

  // Poll until Dfns confirms the transaction on-chain
  for (let i = 0; i < MAX_POLL_ATTEMPTS; i++) {
    await new Promise((r) => setTimeout(r, POLL_INTERVAL_MS));
    const txInfo = await dfns.wallets.getTransaction({
      walletId,
      transactionId: dfnsTxId,
    });

    if (!TERMINAL_STATUSES.has(txInfo.status)) continue;

    if (txInfo.status === 'Confirmed') {
      return { txHash: txInfo.txHash! };
    }

    throw new Error(
      `Dfns transaction ${dfnsTxId} ended with status: ${txInfo.status}`
    );
  }

  throw new Error(`Dfns transaction ${dfnsTxId} timed out`);
}
```

<Note>
  Dfns handles gas estimation and broadcasting automatically. For EVM transactions, you only need to provide `to`, `data`, and `value`. For Solana, you pass the complete serialized transaction and Dfns adds the signature.
</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 Dfns:

```typescript theme={null}
async function supply(
  walletId: number,
  strategyId: number,
  amount: string,
  tokenAddress: string,
  dfnsWalletId: 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 Dfns
  for (const tx of unsignedTxs) {
    console.log(`Signing: ${tx.description} (${tx.blockchain})`);
    const { txHash } = await signAndBroadcast(tx, dfnsWalletId);

    // 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="Broadcast via Dfns">
    For each unsigned transaction, call `broadcastTransaction` on the Dfns wallet. For EVM, pass the `to`, `data`, and `value` fields. For Solana, pass the hex-encoded serialized transaction. Dfns signs with MPC and broadcasts on-chain.
  </Step>

  <Step title="Submit hash to RebelFi">
    Once Dfns confirms the transaction, take the `txHash` 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,
  dfnsWalletId: 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, dfnsWalletId);

    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 transparently. The key difference is how the transaction is passed to Dfns:

|                        | EVM                                 | Solana                              |
| ---------------------- | ----------------------------------- | ----------------------------------- |
| **Transaction format** | JSON object (`to`, `data`, `value`) | Hex-encoded serialized transaction  |
| **Source field**       | `tx.evmTransaction`                 | `tx.unsignedTransaction.serialized` |
| **Typical tx count**   | 2 (approve + deposit)               | 1                                   |
| **Gas handling**       | Dfns estimates automatically        | Included in serialized tx           |

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

## Troubleshooting

<AccordionGroup>
  <Accordion title="Transaction is not hex encoded" icon="circle-exclamation">
    **Cause**: A Solana transaction was sent as a JSON object (EVM format) instead of a hex string, or the hex encoding is missing the `0x` prefix.

    **Solution**: Ensure Solana transactions are converted from base64 to hex with a `0x` prefix: `'0x' + Buffer.from(serialized, 'base64').toString('hex')`.
  </Accordion>

  <Accordion title="Transaction Failed or Rejected" icon="shield">
    **Cause**: Dfns policy engine rejected the transaction, or the transaction failed on-chain.

    **Solution**: Check the `reason` field on the Dfns transaction response. Common causes: insufficient gas funds in the wallet, policy rules blocking the transaction, or a smart contract revert.
  </Accordion>

  <Accordion title="Transaction timed out" icon="clock">
    **Cause**: Dfns transaction took too long to reach a terminal status.

    **Solution**: Check the transaction status in the Dfns dashboard. It may be pending policy approval. Increase `MAX_POLL_ATTEMPTS` if your approval flow takes longer.
  </Accordion>

  <Accordion title="Wrong blockchain detected" icon="shuffle">
    **Cause**: The Dfns wallet network doesn't match the RebelFi strategy's blockchain.

    **Solution**: Ensure your `WALLET_ID` and `STRATEGY_ID` in RebelFi correspond to the same blockchain as your Dfns wallet. A Solana Dfns wallet needs a Solana-registered RebelFi wallet and a Solana strategy.
  </Accordion>
</AccordionGroup>

## Resources

* [Dfns Developer Docs](https://docs.dfns.co/)
* [Dfns SDK (npm)](https://www.npmjs.com/package/@dfns/sdk)
* [Dfns Broadcast API — EVM](https://docs.dfns.co/api-reference/broadcast/evm)
* [Dfns Broadcast API — Solana](https://docs.dfns.co/api-reference/broadcast/solana)
* [RebelFi SDK Reference](/sdk/reference/client)
* [EVM Integration Guide](/sdk/evm-integration)
* [Wallet Profiles](/guides/wallet-profiles)
