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

# Parse Onchain Fills

> How to parse equity fill events, detect how an escrow fill closes, and calculate volume and fees onchain

Equity direct fills and escrow fills execute through the DFlow orchestrator program:
`DF1ow4tspfHX9JwWJsAb9epbkA8hmpSEAtxXy1V27QBH`.

## Event Format

The program emits events with Anchor's `emit_cpi!`: it invokes itself, and the event is the data of that inner instruction. Events do not appear in the transaction logs.

To find events, read `meta.innerInstructions[*].instructions[*]` and keep the instructions whose program is the orchestrator and whose data starts with the event tag. The program signs the inner instruction with its event authority PDA, derived from the seed `"__event_authority"`.

Each event's data has this layout:

| Byte(s) | Field | Value |
| - | - | - |
| 0-7 | Anchor event instruction tag | `[228, 69, 165, 46, 81, 203, 154, 29]` |
| 8-15 | Event discriminator | See [Event Discriminators](#event-discriminators) |
| 16+ | Event fields | Borsh-encoded, little-endian |

> [!WARNING]
>
> Read fill data from these events, not from the instructions that produced them.

## Event Discriminators

| Event | Discriminator |
| - | - |
| `RfqOrderEvent` | `[66, 127, 128, 23, 148, 246, 251, 41]` |
| `SwapEvent` | `[64, 198, 205, 232, 38, 8, 113, 226]` |
| `FeeEvent` | `[73, 79, 78, 127, 184, 213, 13, 220]` |
| `FeeWithdrawalEvent` | `[16, 65, 174, 145, 108, 22, 33, 184]` |

## Order Event Types

Each transition of an escrow fill (order) emits exactly one `RfqOrderEvent`. Its `event_type` field records the transition:

| Value | Type | When It Is Emitted |
| - | - | - |
| `1` | Open | The transaction the user signed opens the order. |
| `2` | Fill | A later transaction fills the order and closes it. |
| `3` | Cancel | The account in the `maker` field closes the order at or before its last fillable slot. |
| `4` | Expire | Any account closes the order after its last fillable slot. |

Every order has one Open event and, once a transaction closes it, one terminal event: Fill, Cancel, or Expire.

### Order Event Byte Layout

`RfqOrderEvent` fields, with offsets after the 16-byte prefix. Add 16 for the offset in the instruction data.

| Offset | Field | Type | Open | Fill | Cancel, Expire |
| - | - | - | - | - | - |
| 0 | `event_type` | u8 | `1` | `2` | `3` or `4` |
| 1 | `builder_code` | u16 | Set | Set | Set |
| 3 | `order` | Pubkey | Set | Set | Set |
| 35 | `maker` | Pubkey | Set | Set | Set |
| 67 | `refund_wallet` | Pubkey | Set | Set | Set |
| 99 | `destination_wallet` | Pubkey | Set | Set | Set |
| 131 | `input_mint` | Pubkey | Set | Set | Set |
| 163 | `input_amount` | u64 | Escrowed | Filled | `0` |
| 171 | `output_mint` | Pubkey | Set | Set | Set |
| 203 | `output_amount` | u64 | Required for a full fill | Delivered | `0` |
| 211 | `fee_mint` | Pubkey | Fee mint, or default | Fee mint, or default | Default pubkey |
| 243 | `fee_amount` | u64 | `0` | Charged, or `0` if skipped | `0` |
| 251 | `refund_amount` | u64 | `0` | Input returned | Input returned |

The event fields are 259 bytes, so the full instruction data is 275 bytes.

## Detecting Escrow Fill Outcomes

1. **Parse the Open event** from the transaction the user signed. Read the `order` field, the onchain address of the order.
2. **Watch later transactions** for an `RfqOrderEvent` with the same `order` and `event_type` = `2` (Fill).
3. **Handle the other terminal types**: `event_type` = `3` (Cancel) or `4` (Expire) means the order did not fill.

> [!NOTE]
>
> Fill, Cancel, and Expire transactions include the order's `refund_wallet`, so the app can subscribe to transactions that mention it to detect closes.

## Escrow Fill Volume Calculation

In Fill events, use:

* `input_mint` / `input_amount`
* `output_mint` / `output_amount`

For an escrow fill, one of the two mints is USDC. When USDC is the input, `input_amount` includes the fee, which `refund_amount` returns if the fee was skipped; when USDC is the output, `output_amount` is net of the fee.

A Fill transaction also emits a `SwapEvent` for the same amounts, with `amm` set to the orchestrator program id. Count either the `RfqOrderEvent` or the `SwapEvent` for an escrow fill, not both.

## Escrow Fill Fee Calculation

Fill events carry:

* `fee_mint`
* `fee_amount`

`fee_amount` is the [platform fee](https://pond.dflow.net/equities/equity-fees.md) charged on the fill. The same transaction emits a `FeeEvent` when a fee was paid; its `amount` is the amount that reached the fee account. Sum `fee_amount` for fees charged. For fees received, sum `FeeEvent.amount` over the events whose `account` is the app's fee account.

## Direct Fill Events

A fill (`executionMode: sync`) does not emit `RfqOrderEvent`. Its transaction emits a `SwapEvent` for each leg of the route, with `amm` set to the orchestrator program id for the equity leg, and a `FeeEvent` for each transfer to the fee account: the platform fee and any positive-slippage capture. See [Relevant IDL Fields](#relevant-idl-fields) for both layouts.

## Relevant IDL Fields

`SwapEvent` fields, with offsets after the 16-byte prefix:

| Offset | Field | Type |
| - | - | - |
| 0 | `amm` | Pubkey |
| 32 | `input_mint` | Pubkey |
| 64 | `input_amount` | u64 |
| 72 | `output_mint` | Pubkey |
| 104 | `output_amount` | u64 |

`FeeEvent` fields, with offsets after the 16-byte prefix:

| Offset | Field | Type |
| - | - | - |
| 0 | `account` | Pubkey |
| 32 | `mint` | Pubkey |
| 64 | `amount` | u64 |

`RfqOrderEvent` field meanings:

| Field | Description |
| - | - |
| `builder_code` | The order's builder code, as recorded at open |
| `order` | Onchain address of the order; links all of its events |
| `maker` | Account recorded on the order as its filler |
| `refund_wallet` | The request's `revertWallet` if set, otherwise the user; returned input goes to its token account for `input_mint` |
| `destination_wallet` | Wallet that receives the fill's output |
| `input_mint` | Mint of the order's input |
| `output_mint` | Mint of the order's output |
| `fee_mint` | Mint of the platform fee; the default pubkey when the order has no fee |

## Decode the Events

This example fetches a transaction with `jsonParsed` encoding, which lists each inner instruction's `programId`, and decodes its `RfqOrderEvent` and `FeeEvent` entries.

```typescript theme={null}
import "dotenv/config";

import { PublicKey } from "@solana/web3.js";
import bs58 from "bs58";

const SOLANA_RPC_URL = process.env.SOLANA_RPC_URL ?? "https://api.mainnet-beta.solana.com";

const ORCHESTRATOR_PROGRAM_ID = "DF1ow4tspfHX9JwWJsAb9epbkA8hmpSEAtxXy1V27QBH";
const EVENT_IX_TAG = Buffer.from([228, 69, 165, 46, 81, 203, 154, 29]);
const RFQ_ORDER_EVENT = Buffer.from([66, 127, 128, 23, 148, 246, 251, 41]);
const FEE_EVENT = Buffer.from([73, 79, 78, 127, 184, 213, 13, 220]);
const EVENT_TYPES: Record<number, string> = { 1: "Open", 2: "Fill", 3: "Cancel", 4: "Expire" };

async function getTransaction(signature: string) {
  const response = await fetch(SOLANA_RPC_URL, {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({
      jsonrpc: "2.0",
      id: 1,
      method: "getTransaction",
      params: [
        signature,
        { encoding: "jsonParsed", commitment: "confirmed", maxSupportedTransactionVersion: 1 },
      ],
    }),
  });
  const { result, error } = await response.json();
  if (error) throw new Error(`getTransaction failed: ${error.message}`);
  if (!result) throw new Error(`Transaction ${signature} not found`);
  return result;
}

type RawEvent = { discriminator: Buffer; fields: Buffer };

function collectEvents(tx: any): RawEvent[] {
  const events: RawEvent[] = [];
  for (const group of tx.meta?.innerInstructions ?? []) {
    for (const ix of group.instructions) {
      if (ix.programId !== ORCHESTRATOR_PROGRAM_ID || !ix.data) continue;
      const data = Buffer.from(bs58.decode(ix.data));
      if (data.length < 16 || !data.subarray(0, 8).equals(EVENT_IX_TAG)) continue;
      events.push({ discriminator: data.subarray(8, 16), fields: data.subarray(16) });
    }
  }
  return events;
}

const pubkeyAt = (buf: Buffer, offset: number) =>
  new PublicKey(buf.subarray(offset, offset + 32)).toBase58();

function decodeRfqOrderEvent(fields: Buffer) {
  return {
    eventType: EVENT_TYPES[fields.readUInt8(0)] ?? `Unknown(${fields.readUInt8(0)})`,
    builderCode: fields.readUInt16LE(1),
    order: pubkeyAt(fields, 3),
    refundWallet: pubkeyAt(fields, 67),
    destinationWallet: pubkeyAt(fields, 99),
    inputMint: pubkeyAt(fields, 131),
    inputAmount: fields.readBigUInt64LE(163),
    outputMint: pubkeyAt(fields, 171),
    outputAmount: fields.readBigUInt64LE(203),
    feeMint: pubkeyAt(fields, 211),
    feeAmount: fields.readBigUInt64LE(243),
    refundAmount: fields.readBigUInt64LE(251),
  };
}

function decodeFeeEvent(fields: Buffer) {
  return {
    account: pubkeyAt(fields, 0),
    mint: pubkeyAt(fields, 32),
    amount: fields.readBigUInt64LE(64),
  };
}

const signature = "TRANSACTION_SIGNATURE";
if (signature === "TRANSACTION_SIGNATURE") {
  throw new Error("Replace TRANSACTION_SIGNATURE with an equity transaction signature.");
}

for (const { discriminator, fields } of collectEvents(await getTransaction(signature))) {
  if (discriminator.equals(RFQ_ORDER_EVENT)) console.log(decodeRfqOrderEvent(fields));
  if (discriminator.equals(FEE_EVENT)) console.log(decodeFeeEvent(fields));
}
```
