Somnia 백서

Somnia

원문

bitpro가 원문에서 글자만 모은 내용이에요. 표·그림·수식은 빠지거나 깨질 수 있어요. 전체 54쪽 중 8쪽

nd in blocks. Each event notification can optionally include the results of read-only simulations run against the same block, so that an application can react to both the event and its derived state at once. There's no additional latency, and no risk of inconsistency. The subscription is served by the RPC node the client is connected to. It is not stored on chain, does not cost [SOMI](/developer/network-info/somi-coin), and disappears when the client disconnects or unsubscribes. For reactions that are part of chain execution rather than client-side, see [on-chain reactivity](/developer/reactivity/reactivity-onchain). ## Example use cases * Drive a UI that updates the moment a token is transferred, without polling balances. * Pipe live swap and liquidity events into an indexer or analytics database. * Attach a `balanceOf` call to every matching Transfer log, so downstream consumers see both the event and the resulting balance atomically. * Build a bot that watches a DEX pool and only receives a message when the pool state has actually changed. ## Quick start 1. Open a WebSocket connection to a Somnia node's API endpoint. 2. Call [`eth_subscribe`](/developer/json-rpc-api#eth_subscribe) using the method `somnia_watch` and a parameters object. The node returns a subscription ID. 3. Read pushed `eth_subscription` notifications on the same connection. The `result` field of each notification is the event payload. 4. To stop, call [`eth_unsubscribe`](/developer/json-rpc-api#eth_unsubscribe) with the subscription ID, or simply close the connection. Minimal JSON-RPC sequence: ```json // -> request { "jsonrpc": "2.0", "id": 1, "method": "eth_subscribe", "params": [ "somnia_watch", { "eth_calls": [] } ] } // <- response { "jsonrpc": "2.0", "id": 1, "result": "0x1234..." } // <- notifications { "jsonrpc": "2.0", "method": "eth_subscription", "params": { "subscription": "0x1234...", "result": { "address": "0x...", "topics": ["0x...", ...], "data": "0x...", "simulationResults": [] } } } ``` ## Using from TypeScript TypeScript and JavaScript apps can use the [`@somnia-chain/reactivity`](https://www.npmjs.com/package/@somnia-chain/reactivity) package. It wraps the WebSocket protocol with Viem and manages subscription/unsubscription, so you don't have to drive `eth_subscribe` directly. (The same package can also create and manage [on-chain reactivity](/developer/reactivity/reactivity-onchain#typescript-package) subscriptions owned by an EOA.) ```bash npm install @somnia-chain/reactivity viem ``` ```typescript import { createPublicClient, defineChain, webSocket } from 'viem'; import { SDK } from '@somnia-chain/reactivity'; const chain = defineChain({ /* see viem docs */ }); const sdk = new SDK({ public: createPublicClient({ chain, transport: webSocket() }), }); const subscription = await sdk.watch({ eventContractSources: ['0x046EDe9564A72571df6F5e44d0405360c0f4dCab'], topicOverrides: ['0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef'], ethCalls: [], onData: (data) => { const event = data.result; console.log(event); }, }); if (subscription instanceof Error) { throw subscription; } // later await subscription.unsubscribe(); ``` The SDK requires the public client to use a WebSocket transport. You don't need a wallet client — off-chain subscriptions don't sign anything. `sdk.subscribe` accepts a `WebsocketSubscriptionInitParams` object that maps to the [Request parameters](#request-parameters) below: | SDK field | RPC field | | ---------------------- | ----------------------------------- | | `eventContractSources` | `address` | | `topicOverrides` | `topics` | | `ethCalls` | `eth_calls` | | `context` | `context` | | `onlyPushChanges` | `push_changes_only` | | `onData` / `onError` | (callback hooks, no RPC equivalent) | For step-by-step walkthroughs, see the [Wildcard Off-Chain Reactivity Tutorial](/developer/reactivity/tutorials/wildcard-off-chain-reactivity-tutorial) and [Off-Chain Reactivity: Filtered Subscriptions tutorial](/developer/reactivity/tutorials/off-chain-reactivity-filtered-subscriptions-tutorial). ## Reference ### Subscription method `somnia_watch`. Subscribed via the standard `eth_subscribe` request over a WebSocket transport. Unsubscribed via `eth_unsubscribe`. ### Request parameters The parameters object follows [`eth_getLogs`](/developer/json-rpc-api#eth_getlogs)-style filter conventions with additions for read-only simulation. | Field | Type | Purpose | | ------------------- | ---------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `address` | `address` or `address[]` | Optional. Matches only logs emitted by one of these addresses. Omit or pass `null` for any address. | | `topics` | `(Hash \| Hash[] \| null)[]` | Optional. Positional topic filter. Entry `i` constrains topic `i` of the log. Each entry can be a single hash, an array of hashes (OR), or `null` (any). Omit for any topics. | | `eth_calls` | array | Required. A list of read-only calls the node runs for each matching log. Pass `[]` if you only want log notifications with no attached simulation. See [eth\_calls](#eth_calls) below. | | `context` | `string` or `string[]` | Optional. Selectors that tell the node which fields of the matching log to append to each simulated call's calldata. See [context](#context) below. | | `push_changes_only` | `bool` | Optional, default false. If true, and `eth_calls` is non-empty, only events whose simulation results differ from the previous event are pushed. | #### eth\_calls Each element is a tuple of `(callRequest, stateOverrides)`, mirroring [`eth_call`](/developer/json-rpc-api#eth_call): * `callRequest` — an `eth_call`-style transaction request object (`to`, `data`, optional `from`, `gas`, etc.). * `stateOverrides` — an optional `eth_call`-style state override set, applied for the duration of the simulation. For every matching log the node executes each call as a read-only simulation against the end-of-transaction state. The raw returned bytes are included in `simulationResults`, in order. The same single-item-or-list shortcut used elsewhere in Ethereum JSON-RPC applies: you can pass just the call request to skip state overrides. #### context When `context` is non-empty, for each simulated call the node appends 32-byte chunks drawn from the matching log to the call's calldata, in the order given. This lets a single simulation template be parameterised by the log that triggered it. Valid selectors: | Selector | What is appended | | --------- | ------------------------------------------------------ | | `address` | The log's emitter address, left-padded to 32 bytes. | | `data` | The log's data bytes (not padded — appended verbatim). | | `topic1` | The log's first topic (`topics[0]`). | | `topic2` | The log's second topic (`topics[1]`). | | `topic3` | The log's third topic (`topics[2]`). | | `topic4` | The log's fourth topic (`topics[3]`). | Note that the 1-based names refer to position within the log, so `topic1` is the first topic (usually the event signature), not the second. Selectors that ask for a topic the log does not have are silently skipped. If `context` is empty or absent, the node runs each `eth_call` exactly as written. #### push\_changes\_only If true and `eth_calls` is non-empty, the node compares the new `simulationResults` against the previous *pushed* event on this subscription. If they are byte-identical, the new event is dropped. The comparison is strictly adjacent — not against any reference value — so a zero result will still be pushed whenever the prior push was non-zero. Use this to avoid re-notifying on a genuinely unchanged view (e.g. a wash transfer that returns a balance to its previous value), not to filter out uninteresting ones. `push_changes_only` has no effect when `eth_calls` is empty, since there is nothing to compare. ### Event payload Each pushed notification has a `result` field shaped as: | Field | Type | Meaning | | ------------------- | --------- | --------------------------------------------------------------------------------------------------------------------------------------------- | | `address` | `address` | The log's emitter. | | `topics` | `Hash[]` | The log's topics, up to four entries. | | `data` | `bytes` | The log's data payload. | | `simulationResults` | `bytes[]` | One entry per `eth_calls` entry, in order. Empty if no `eth_calls` were configured. Each entry is the raw returndata from the simulated call. | Topics and data are exactly as they appear in the committed block, so decoding follows the same rules as the event's Solidity definition. ### Filter matching semantics The `address` and `topics` fields follow the same rules as `eth_getLogs` and `eth_subscribe("logs", ...)`: * A log matches when its emitter matches the `address` filter (or `address` is omitted). * For each topic position, a filter entry of `null` or omitted matches anything, a single hash matches exactly, and an array of hashes matches any member (OR). * The subscription only considers logs from transactions that were included in a block. It does not see pending pool activity. ### Lifetime The subscription lives on the API connection that created it. If the connection closes (client disconnect, node restart, network blip) the subscription is gone and must be recreated. The node does not buffer missed events across disconnects. Subscriptions are not authenticated or owner-scoped in any way. Every client creates and sees only its own subscriptions. ### Historical data Off-chain reactivity only pushes events from the moment the subscription is created onwards. For historical event retrieval, use [`eth_getLogs`](/developer/json-rpc-api#eth_getlogs) or another indexing solution. ## Minimal example Here's how to subscribe to all Wrapped SOMI transfer events on mainnet, checking the token balance of each recipient: ```bash wscat --connect wss://api.infra.mainnet.somnia.network/ws --wait 300 --execute ' { "jsonrpc": "2.0", "id": 1, "method": "eth_subscribe", "params": [ "somnia_watch", { "address": "0x046EDe9564A72571df6F5e44d0405360c0f4dCab", "topics": [ "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef" ], "eth_calls": [ { "to": "0x046EDe9564A72571df6F5e44d0405360c0f4dCab", "data": "0x70a08231" } ], "context": ["topic3"], "push_changes_only": false } ] }' ``` Walking through each field: * `address` — restricts matches to logs emitted by this specific ERC-20 token contract (see [Smart contracts](/developer/smart-contracts) for the canonical mainnet address). * `topics` — the single entry is `keccak256("Transfer(address,address,uint256)")`, so that only ERC-20 Transfer logs match. The log's other two topics (`from` and `to`) are left unconstrained. * `eth_calls` — a single read-only call, back to the token contract. Its `data` is the 4-byte selector `keccak256("balanceOf(address)")`; the 32-byte recipient argument is not written inline, but supplied by `context` below. * `context` — the `topic3` selector instructs the node to append the log's third topic (the Transfer `to`, already left-padded to 32 bytes) to the simulated call's calldata. The result is a well-formed `balanceOf(recipient)` call, even though the subscription template is fixed. * `push_changes_only` — enabling this here would discard events when two successive recipients happened to have the same token balance — not very helpful. On running the command, you'll first receive the subscription ack: ```json {"jsonrpc":"2.0","id":1,"result":"0x..."} ``` And then one `eth_subscription` notification per WSOMI Transfer, as they happen: ```json {"jsonrpc":"2.0","method":"eth_subscription","params":{"subscription":"0x...","result":{"address":"0x046ede9564a72571df6f5e44d0405360c0f4dcab","topics":["0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef","0x...","0x..."],"data":"0x...","simulationResults":["0x..."]}}} ``` Each `simulationResults[0]` is the recipient's WSOMI balance after the transfer, denoted in wei. Close the connection (Ctrl-C) to unsubscribe. # Tutorials ## On-chain * [ERC-20 transfer subscription in Solidity](/developer/reactivity/tutorials/solidity-on-chain-reactivity-tutorial) * [System event subscriptions via TypeScript SDK](/developer/reactivity/tutorials/cron-subscriptions-via-sdk) ## Off-chain * [Wildcard subscription via TypeScript SDK](/developer/reactivity/tutorials/wildcard-off-chain-reactivity-tutorial) * [ERC-20 transfer subscription via TypeScript SDK](/developer/reactivity/tutorials/off-chain-reactivity-filtered-subscriptions-tutorial) # Wildcard Off-Chain Reactivity Tutorial Subscribe to all new logs over WebSocket and receive optional read-only call results with each event This tutorial shows how to create an off-chain Reactivity subscription from TypeScript using the `@somnia-chain/reactivity` SDK. A wildcard subscription omits both address and topic filters, so the node pushes every new log it sees on the WebSocket connection. Wildcard subscriptions are useful for quick testing and exploratory scripts. Production applications should usually add filters, as shown in the [filtered subscriptions tutorial](/developer/reactivity/tutorials/off-chain-reactivity-filtered-subscriptions-tutorial). Off-chain subscriptions: * live only for the WebSocket connection that created them * are not stored on-chain * do not require a wallet client or gas * can include `ethCalls` whose raw return data is delivered with each event For the full protocol reference, see [Off-chain Reactivity](/developer/reactivity/reactivity-offchain). ## Prerequisites You'll need Node.js 20+. Install the SDK and Viem: ```bash npm install @somnia-chain/reactivity viem npm install --save-dev tsx typescript @types/node ``` ## Step 1: Define the Chain This example uses Somnia Testnet. You can switch to mainnet by changing the chain ID, native currency and RPC URLs to the mainnet values from [Network Info](/developer/network-info). ```typescript import { defineChain } from 'viem'; const somniaTestnet = defineChain({ id: 50312, name: 'Somnia Testnet', nativeCurrency: { decimals: 18, name: 'STT', symbol: 'STT', }, rpcUrls: { default: { http: ['https://api.infra.testnet.somnia.network'], webSocket: ['wss://api.infra.testnet.somnia.network/ws'], }, }, }); ``` ## Step 2: Create the SDK Off-chain reactivity requires a public client with a WebSocket transport. You do not need a wallet client because no transaction is signed. ```typescript import { SDK } from '@somnia-chain/reactivity'; import { createPublicClient, webSocket } from 'viem'; const publicClient = createPublicClient({ chain: somniaTestnet, transport: webSocket(somniaTestnet.rpcUrls.default.webSocket[0]), }); const sdk = new SDK({ public: publicClient }); ``` ## Step 3: Subscribe to All Logs Pass `ethCalls: []` when you only want log notifications. Omitting `eventContractSources` and `topicOverrides` makes the subscription wildcard. ```typescript const subscription = await sdk.subscribe({ ethCalls: [], onData: (data) => { const event = data.result; console.log('Emitter:', event.address); console.log('Topics:', event.topics); console.log('Data:', event.data); console.log('Simulation results:', event.simulationResults); }, onError: (error) => { console.error('Subscription error:', error); }, }); if (subscription instanceof Error) { throw subscription; } ``` Each notification's event payload contains: | Field | Meaning | | ------------------- | ------------------------------------------------------ | | `address` | Contract address that emitted the log. | | `topics` | Event topics, with the event signature at `topics[0]`. | | `data` | ABI-encoded non-indexed event data. | | `simulationResults` | Raw return data from configured `ethCalls`, in order. | ## Step 4: Decode a Known Event A wildcard subscription receives many different event types. Decode only logs whose signature matches the ABI you are using. This example recognises ERC-20 `Transfer` logs. ```typescript import { decodeEventLog, erc20Abi, toEventSelector, } from 'viem'; const transferTopic = toEventSelector('Transfer(address,address,uint256)'); function handleEvent(event: { address: `0x${string}`; topics: `0x${string}`[]; data: `0x${string}`; simulationResults: `0x${string}`[]; }) { if (event.topics[0] !== transferTopic) { return; } const decoded = decodeEventLog({ abi: erc20Abi, topics: event.topics, data: event.data, }); console.log('ERC-20 Transfer:', decoded.args); } ``` Then call `handleEvent(data.result)` inside `onData`. ## Full Script Save this as `main.ts`: ```typescript import { SDK } from '@somnia-chain/reactivity'; import { createPublicClient, decodeEventLog, defineChain, erc20Abi, toEventSelector, webSocket, } from 'viem'; const somniaTestnet = defineChain({ id: 50312, name: 'Somnia Testnet', nativeCurrency: { decimals: 18, name: 'STT', symbol: 'STT', }, rpcUrls: { default: { http: ['https://api.infra.testnet.somnia.network'], webSocket: ['wss://api.infra.testnet.somnia.network/ws'], }, }, }); const transferTopic = toEventSelector('Transfer(address,address,uint256)'); const publicClient = createPublicClient({ chain: somniaTestnet, transport: webSocket(somniaTestnet.rpcUrls.default.webSocket[0]), }); const sdk = new SDK({ public: publicClient }); async function main() { const subscription = await sdk.subscribe({ ethCalls: [], onData: (data) => { const event = data.result; console.log('Raw event:', event); if (event.topics[0] !== transferTopic) { return; } const decoded = decodeEventLog({ abi: erc20Abi, topics: event.topics, data: event.data, }); console.log('Decoded ERC-20 Transfer:', decoded.args); }, onError: (error) => { console.error('Subscription error:', error); }, }); if (subscription instanceof Error) { throw subscription; } console.log('Subscribed:', subscription.subscriptionId); process.on('SIGINT', async () => { await subscription.unsubscribe(); process.exit(0); }); } main().catch((error) => { console.error(error); process.exit(1); }); ``` Run it: ```bash npx tsx main.ts ``` ## Next Steps Add `eventContractSources` and `topicOverrides` once you know which contracts and events you want. Add `ethCalls` when you want the node to attach read-only simulation results to each pushed event. # Off-Chain Reactivity: Filtered Subscriptions tutorial This tutorial shows how to narrow an off-chain Reactivity subscription to a specific contract and event. It subscribes to ERC-20 `Transfer` logs and asks the node to run `balanceOf(recipient)` for each matching log, so the pushed notification contains both the event and the recipient's post-transfer balance. For the lower-level protocol reference, see [Off-chain Reactivity](/developer/reactivity/reactivity-offchain). ## What You Will Build The script will: 1. Connect to a Somnia WebSocket RPC endpoint. 2. Subscribe to `Transfer(address,address,uint256)` logs from one ERC-20. 3. Use `context: "topic3"` to append the Transfer recipient to a `balanceOf` `ethCall`. 4. Decode both the event log and the returned balance. 5. Unsubscribe cleanly when the process exits. ## Prerequisites You'll need Node.js 20+. Install the SDK and Viem: ```bash npm install @somnia-chain/reactivity viem npm install --save-dev tsx typescript @types/node ``` ## Step 1: Set Up the SDK This example uses Somnia mainnet. To use testnet, replace the chain ID, native currency and RPC URLs with the testnet values from [Network Info](/developer/network-info). ```typescript import { SDK } from '@somnia-chain/reactivity'; import { createPublicClient, defineChain, webSocket } from 'viem'; const somniaMainnet = defineChain({ id: 5031, name: 'Somnia Main
프로젝트로 돌아가기