# WebSocket

> NeuroChain gateway WebSocket — /ws 연결, init 메시지, 모든 실시간 이벤트(블록, 트랜잭션, 보류된 이체, AI 결정, 컨트랙트 알림), 잔액 구독, 소켓을 통한 RPC와 적용되는 한도를 설명합니다.

Source: https://docs.nro.world/ko/reference/websocket

Gateway는 REST API와 같은 호스트와 포트에서 **`/ws`** 경로로 WebSocket을 제공합니다:

```
ws://localhost:3000/ws
```

인증은 없습니다. Gateway는 연결된 모든 클라이언트에 체인 및 AI 이벤트를 푸시하며, 클라이언트는 같은 소켓으로 잔액을 구독하고 일련의 RPC 메서드를 호출할 수도 있습니다.

## 연결

```js
const ws = new WebSocket('ws://localhost:3000/ws')

ws.onmessage = (e) => {
  const msg = JSON.parse(e.data)
  switch (msg.type) {
    case 'init':      console.log('height', msg.data.height); break
    case 'new_block': console.log('block', msg.data.height, msg.data.proposer); break
    case 'tx_held':   console.warn('held', msg.data.tx_hash); break
  }
}
```

서버가 보내는 모든 메시지는 `type`이 있는 JSON입니다. 이벤트는 페이로드를 `data`에 담고, `balance`, `rpc_reply`, `error`는 필드를 최상위 수준에 둡니다.

`close` 시에는 백오프를 두고 다시 연결하십시오 — 웹 앱은 지수적으로 늘어나는 지연을 두고 재시도합니다.

## 이벤트

Gateway는 `POLL_NODE_INTERVAL_MS`(기본값 2 000 ms)마다 노드를 폴링하고, 확인한 내용을 이벤트로 변환합니다.

| `type` | 시점 | `data` 필드 |
|--------|------|-------------|
| `init` | 연결 직후 | `height`, `tps`, `finality_ms`, `shard_count`, `validator_count`, `blocks`, `txs`, `total_blocks`, `total_txs` |
| `metrics` | 매 폴링 | `tps`, `height`, `finality_ms` |
| `new_block` | 새 높이가 확인됨 | `GET /api/v1/blocks/:height`가 반환하는 형태의 블록 |
| `validator_update` | 10블록마다 | `nodes`, `total_staked`, `height` |
| `tx` | Gateway가 트랜잭션을 노드로 전달함 | `hash`, `from`, `to`, `value`, `gas`, `gas_price`, `shard`, `timestamp`, `block` |
| `faucet` | Faucet이 주소에 입금함 | `address`, `amount`, `tx_hash` |
| `tx_held` | [Sentinel 정책](/ko/ai/sentinel)이 이체를 보류함 | `tx_hash`, `to`, `value_human` |
| `tx_released` | 보류된 이체가 해제됨 | `tx_hash`, `to`, `value_human` |
| `tx_cancelled` | 보류된 이체가 취소됨 | `tx_hash`, `to`, `value_human` |
| `tx_anomaly` | Sentinel이 제출된 트랜잭션을 이상으로 평가함 | `tx_hash`, `score`, `category` |
| `oracle_decision` | AI 결정이 기록됨 | `model`, `kind`, `subject`, `decision`, `confidence`, `timestamp` |
| `model_voting_update` | AI 모델이 검증자 확인을 기다리는 중 | `models`, `height` |
| `contract_alert` | 컨트랙트 호출량이 비정상적이거나, 점수가 낮은 컨트랙트가 호출되기 시작함 | `address`, `alert`, `severity`, `call_count`, `delta`, `timestamp`, `model` |

::: tip `tx`는 제출 알림입니다
`tx` 이벤트는 gateway가 트랜잭션을 노드에 넘길 때, 즉 블록에 포함되기 전에 발생합니다 — 이 이벤트의 `status` 필드는 확정된 값이 아닙니다. 완결성은 `new_block`, 잔액 구독 또는 `GET /api/v1/transactions/:hash`로 추적하십시오.
:::

컨트랙트 알림은 `SENTINEL_CHECK_INTERVAL_MS`(기본값 30 000 ms)마다 확인됩니다.

## 잔액 구독

```js
ws.send(JSON.stringify({ type: 'subscribe_balance', address: 'neuro:<15 hex>.human' }))
```

Gateway는 즉시 응답하고, 새 블록이 생길 때마다 다시 응답합니다:

```json
{ "type": "balance", "address": "neuro:…", "balance": 50000000, "balance_human": "50", "nonce": 0 }
```

구독은 소켓이 닫히면 종료됩니다.

## 소켓을 통한 RPC

```js
ws.send(JSON.stringify({ type: 'rpc', id: 1, method: 'get_account', params: { address: 'neuro:…' } }))
// → { "type": "rpc_reply", "id": 1, "result": { … }, "error": null }
```

메서드는 REST 엔드포인트와 대응하며 같은 데이터를 반환합니다:

| 그룹 | 메서드 |
|------|--------|
| 체인 | `get_status`, `get_blocks`, `get_block`, `get_block_txs`, `get_txs`, `search_tx`, `get_account` |
| 검증자 | `get_validator`, `get_validators` |
| 이름과 NFT | `human_name_price`, `register_human`, `get_my_nfts`, `get_nft`, `transfer_nft` |
| 거버넌스 | `get_governance_params`, `get_proposals`, `get_proposal`, `create_proposal`, `cast_vote` |
| 볼트 | `get_vault_config`, `list_member_vaults`, `get_vault_proposals`, `create_vault_proposal`, `sign_vault_proposal`, `add_vault_member`, `set_vault_threshold` |
| AI | `get_oracle_log`, `get_oracle_entry`, `get_model_registry`, `get_model_registry_entry`, `register_model` |
| 컨트랙트 | `get_contracts`, `get_contract`, `get_contract_events`, `deploy_contract`, `call_contract`, `update_contract_score`, `get_certificates`, `get_certificate`, `get_contract_certificate`, `certify_contract` |
| 테스트 네트워크 | `faucet` |

쓰기 메서드에는 해당 REST 엔드포인트와 같은 서명 필드(`signature`, `public_key`, `nonce`)가 `params` 안에 필요합니다.

## 한도

| 한도 | 기본값 | 변수 | 초과 시 |
|------|--------|------|---------|
| 메시지 크기 | 64 KB | `WS_MAX_MSG_BYTES` | `{"type":"error","error":"message too large"}` |
| 연결당 잔액 구독 수 | 50 | `WS_MAX_SUBS_PER_CLIENT` | `subscription limit exceeded` |
| 연결당 초당 RPC 호출 수 | 30 | `WS_RPC_MAX_RPS` | `rpc rate limit exceeded` |
| 끊긴 연결 정리 | 30초마다 | `WS_STALE_CHECK_MS` | — |

## Python

```python
import asyncio, json, websockets

async def listen():
    async with websockets.connect("ws://localhost:3000/ws") as ws:
        await ws.send(json.dumps({"type": "subscribe_balance", "address": "neuro:<15 hex>.human"}))
        async for raw in ws:
            msg = json.loads(raw)
            print(msg["type"], msg.get("data", msg))

asyncio.run(listen())
```
