> For the complete documentation index, see [llms.txt](https://docs.hydromancer.xyz/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.hydromancer.xyz/readme/websocket/builder-data/builderapprovednonfundingledgerevents.md).

# builderApprovedNonFundingLedgerEvents

Stream every non-funding ledger update of users who had an active fee approval for your builder at the block.

Every live data message includes both routing identifiers with the same value:

```json
{"type": "builderApprovedNonFundingLedgerEvents", "channel": "builderApprovedNonFundingLedgerEvents"}
```

Other payload fields are omitted above. Existing clients may continue routing on either field.

{% hint style="info" %}
💧New endpoint - this endpoint is not a part of original Hyperliquid API and is added by us for builder convenience.
{% endhint %}

### What it streams

Deposits, withdrawals, transfers, vault activity, liquidations and every other non-funding ledger update of users who held an active fee approval for your builder at the block of the update. Funding is excluded here and served by [`builderApprovedFundings`](/readme/websocket/builder-data/builderapprovedfundings.md).

Each event is delivered **per participant**: a transfer between two users who both approved your builder arrives twice, once with `role: "sender"` and once with `role: "receiver"`, each naming that side's `user`. Events with a single participant carry `role: "user"`. This is the same view [`userNonFundingLedgerEvents`](/readme/websocket/user-data/usernonfundingledgerevents.md) gives each of them, and the `delta` object is the one [`allUserNonFundingLedgerEvents`](/readme/websocket/funding-and-deposits-data/allusernonfundingledgerevents.md) sends for the same event, so the ledger wire shape is shared across channels.

It follows the rules of [`builderApprovedFills`](/readme/websocket/builder-data/builderapprovedfills.md):

* Attribution comes from approval state at the block, not from anything on the event itself, and revoking an approval does not retract past events.
* A user may hold approvals from several builders. Each subscribed builder receives its own copy.
* An approval is treated as revoked only when its max fee rate is set to exactly `0%`.

### Subscribe

```
{
    "type": "subscribe",
    "subscription": {
        "type": "builderApprovedNonFundingLedgerEvents",
        "builder": "0xb84168cf3be63c6b8dad05ff5d755e97432ff80b"
    }
}
```

`builder` is required and must be a 20-byte hex address. It is matched case-insensitively.

### Unsubscribe

```
{
    "type": "unsubscribe",
    "subscription": {
        "type": "builderApprovedNonFundingLedgerEvents",
        "builder": "0xb84168cf3be63c6b8dad05ff5d755e97432ff80b"
    }
}
```

### Ledger event data format

Each entry names the attributed participant and their `role`, the block `time` in milliseconds, the transaction `hash`, the `txIndex` within the block, and the `delta` describing the ledger change. Events are batched per block; both sides of a transfer share one `txIndex` and always arrive in the same message.

{% hint style="info" %}
**This channel is always chunked.** Messages carry `chunk`, `totalChunks` and `batchId` on every connection, whether or not you passed `liveFormat=chunked-v1`, so a large batch never arrives as one oversized frame. Group by `batchId`, order by `chunk`, and you have the block's complete batch when you hold `totalChunks` of them. See [Live Message Chunking](/readme/websocket/live-message-chunking.md).
{% endhint %}

{% hint style="info" %}
**Reconnection Note:** When reconnecting with a session, replay and live events may overlap. Deduplicate using `(time, txIndex, user, role)` - skip entries whose tuple you have already processed. See [Session Management](/readme/websocket/session-management-and-reconnection.md#deduplication) for details.
{% endhint %}

```json
{
  "type": "builderApprovedNonFundingLedgerEvents",
  "channel": "builderApprovedNonFundingLedgerEvents",
  "seq": 1,
  "cursor": "500:1704067200000:3",
  "ledgerEvents": [
    {
      "user": "0x742d35cc6634c0532925a3b844bc9e7595f7f2e2",
      "role": "sender",                 // "user", "sender" or "receiver"
      "time": 1704067200000,            // block time (ms)
      "hash": "0xabc...def",
      "txIndex": 3,
      "delta": {
        "type": "internalTransfer",
        "usdc": "1000.0",
        "user": "0x742d35cc6634c0532925a3b844bc9e7595f7f2e2",
        "destination": "0x123...456",
        "fee": "0.0",
        "users": ["0x742d35cc6634c0532925a3b844bc9e7595f7f2e2"]
      }
    },
    {
      "user": "0x123...456",
      "role": "receiver",
      "time": 1704067200000,
      "hash": "0xabc...def",
      "txIndex": 3,
      "delta": {
        "type": "internalTransfer",
        "usdc": "1000.0",
        "user": "0x742d35cc6634c0532925a3b844bc9e7595f7f2e2",
        "destination": "0x123...456",
        "fee": "0.0",
        "users": ["0x742d35cc6634c0532925a3b844bc9e7595f7f2e2"]
      }
    }
  ]
}
```

The `delta` variants (`deposit`, `withdraw`, `internalTransfer`, `spotTransfer`, `vaultDeposit`, `liquidation`, ...) are documented on [`allUserNonFundingLedgerEvents`](/readme/websocket/funding-and-deposits-data/allusernonfundingledgerevents.md).

### Examples

{% tabs %}
{% tab title="Javascript" %}

```javascript
const WebSocket = require('ws');

const ws = new WebSocket(`wss://api.hydromancer.xyz/ws?token=${process.env.HYDROMANCER_API_KEY}`);

ws.on('message', (data) => {
    const msg = JSON.parse(data);

    if (msg.type === 'connected') {
        ws.send(JSON.stringify({
            type: 'subscribe',
            subscription: {
                type: 'builderApprovedNonFundingLedgerEvents',
                builder: '0xb84168cf3be63c6b8dad05ff5d755e97432ff80b'
            }
        }));
    } else if (msg.type === 'ping') {
        ws.send(JSON.stringify({ type: 'pong' }));
    } else if (msg.type === 'builderApprovedNonFundingLedgerEvents') {
        console.log(`Received ${msg.ledgerEvents.length} approved ledger events`);
    }
});
```

{% endtab %}

{% tab title="Python" %}

```python
import websocket
import json
import os

def on_message(ws, message):
    msg = json.loads(message)

    if msg['type'] == 'connected':
        ws.send(json.dumps({
            "type": "subscribe",
            "subscription": {
                "type": "builderApprovedNonFundingLedgerEvents",
                "builder": "0xb84168cf3be63c6b8dad05ff5d755e97432ff80b"
            }
        }))
    elif msg['type'] == 'ping':
        print("Received ping, sending pong")
        ws.send(json.dumps({'type': 'pong'}))
    elif msg['type'] == 'builderApprovedNonFundingLedgerEvents':
        print(f"Received {len(msg['ledgerEvents'])} approved ledger events")
    elif msg['type'] == 'subscriptionUpdate':
        print(f"Subscription update: {msg}")
    elif msg['type'] == 'error':
        print(f"Error: {msg}")
    else:
        print(f"Unknown message type: {msg}")

ws = websocket.WebSocketApp(
    f"wss://api.hydromancer.xyz/ws?token={os.environ.get('HYDROMANCER_API_KEY')}",
    on_message=on_message
)
ws.run_forever()
```

{% endtab %}
{% endtabs %}

#### Error messages:

```
{
    "type": "error",
    "message": "Builder address required for builderApprovedNonFundingLedgerEvents subscription"
}
```

#### Common errors

1. ```
   Builder address required for builderApprovedNonFundingLedgerEvents subscription
   ```
2. ```
   Invalid address format
   ```
3. ```
   Too many subscriptions - Maximum builder subscriptions per API key
   ```
4. ```
   Invalid API key
   ```
