Somnia Markets / TypeScript SDK
Chains
Every Somnia network as a viem Chain, from one import — so nothing has to
hand-roll a defineChain({...}) or reach for viem/chains again.
import { somniaShannon } from "@somnia-chain/markets-sdk/chains";
chain is a required field of every exchange/client config (it signs writes and
sizes viem's waiting heuristics), and wsRpcUrl is the transport the live tail
runs on — both come straight off these objects.
Moving tokens between these networks is the bridge — same module, same import.
The networks
| Export | Chain id | Native | Blocks | Multicall3 | Explorer |
|---|---|---|---|---|---|
somniaMainnet |
5031 |
SOMI | 100 ms | ✅ | explorer.somnia.network |
somniaShannon |
50312 |
STT | 100 ms | ✅ | shannon-explorer.somnia.network |
somniaTokyo |
50321 |
SOMI | 100 ms | ✅ | — |
hidekiTestnet |
50383 |
STT | 100 ms | ✅ | — |
somniaLocal |
31337 |
STT | — | — | — |
- Shannon (
somniaShannon) is the public testnet and the default target for development.https://dream-rpc.somnia.networkis an alias for the same network and is listed as a secondary endpoint. - Somnia Tokyo (
somniaTokyo) is the Tokyo network on mainnet. It launched as Hideki Mainnet. Its native token is SOMI, the same coin as Somnia mainnet. Somnia mainnet and Somnia Tokyo are the two ends of the mainnet bridge lane. Its definition also namesrpcUrls.sendandrpcUrls.session, which reach a validator directly. The SDK sends transactions and session calls there: see Send and session endpoints. - Hideki Testnet (
hidekiTestnet) is the low-latency Tokyo testnet. If you are measuring latency, measure here. Somnia Tokyo and Hideki Testnet commit transactions every 10 ms and make a ledger block every 100 ms: see Somnia Tokyo and Hideki Testnet block time. Its definition also namesrpcUrls.sendandrpcUrls.session, which reach a validator directly. The SDK sends transactions and session calls there: see Send and session endpoints. somniaLocalis the anvil stack (demo-clob.sh up,DEPLOY_ENV=local). It deliberately presents as Somnia/STT rather than Foundry/Ether: the agent-facing docs read the chain name and native symbol out of this object, and a local stack should still read as Somnia there.
Every definition carries both transports — rpcUrls.default.http and
rpcUrls.default.webSocket — so an exchange can be built from the chain alone:
import { SomniaMarkets } from "@somnia-chain/markets-sdk";
import { hidekiTestnet as chain } from "@somnia-chain/markets-sdk/chains";
const exchange = new SomniaMarkets({
chain,
wsRpcUrl: chain.rpcUrls.default.webSocket[0],
indexerUrl: "/v1/graphql",
});
Somnia Tokyo and Hideki Testnet block time
Somnia Tokyo and Hideki Testnet run the Kenneth consensus layer. Kenneth makes a PBFT block every 10 ms and commits each transaction in one of these blocks. Each chain makes a ledger block every 100 ms, as Somnia mainnet and Shannon do. So a transaction is committed before the ledger block that holds it exists.
The RPC serves only ledger blocks. Block numbers, logs, receipts and the
latest and pending tags all count ledger blocks. So blockTime is 100 in
both definitions. Use 100 ms when you change a time into a number of
blocks. The 10 ms sets how soon a transaction is committed, not how often a
block appears in the RPC.
Do not set blockTime to 10. From viem 2.31, blockTime sets the default
polling interval, and that interval is never shorter than 500 ms. So 10 does
not make a client faster. But code that changes a time into a number of blocks
gets a count that is 10 times too large.
Absences are deliberate
The local chain carries no contracts.multicall3 — nothing is deployed
there to point at. That is not an oversight to be filled in with the canonical
0xcA11bde05977b3631167028862bE2a173976CA11: viem routes batched reads at
whatever address it finds, so a Multicall3 entry with no code behind it turns
every batched read into a failure. An absent field just makes viem fall back to
individual eth_calls. In the same way, only mainnet and Shannon carry
blockExplorers: no public explorer is known for Somnia Tokyo or Hideki
Testnet.
Each network's Multicall3 address is its own, so don't copy one to another:
- Mainnet and Shannon have their own deployments at different addresses,
0x5e44…5a11and0x841b…4223. The presigned "Nick's method" deployment can never land there: contract creation costs ~4,928 gas per deployed byte on Somnia, far over that transaction's fixed 500k limit, and its nonce-0 deployer has already been burned on these networks. - Somnia Tokyo and Hideki Testnet have Multicall3 at the canonical
0xcA11…CA11, because each genesis puts it there. Somnia Tokyo has code there from block 0. Hideki Testnet has code from block 1, since its 2026-09-17 regenesis.
The values here — ids, cadence, Multicall3 addresses — were verified against the
live networks (eth_chainId, block-timestamp deltas over 10k blocks, and a real
getBlockNumber() call on each Multicall3 candidate), not copied from a chain
list.
Resolving a chain id you were handed
Deployment manifests (and NEXT_PUBLIC_CHAIN_ID-style env) carry a plain
number. getSomniaChain bridges that to a Chain, returning null when the
id isn't one of ours:
import { getSomniaChain, defineChain } from "@somnia-chain/markets-sdk/chains";
const chain =
getSomniaChain(deployment.chainId) ??
defineChain({
id: deployment.chainId,
name: "Somnia",
nativeCurrency: { name: "STT", symbol: "STT", decimals: 18 },
rpcUrls: { default: { http: [rpcUrl] } },
});
somniaChains is the same table, keyed by id — iterate it to build a network
switcher. isSomniaChainId(id) is the type guard that narrows a number to
ChainId, enough to index it without a cast. When the id flows the other way —
your code names the network — ChainId is the constant to reach for:
ChainId.somniaShannon is 50312, keyed by the same export names as the
definitions (ChainId.somniaShannon === somniaShannon.id by construction).
Extending one
defineChain and the Chain type are re-exported here, so overriding an
endpoint (a private node, a fork) needs no second import:
import { defineChain, somniaShannon } from "@somnia-chain/markets-sdk/chains";
const forked = defineChain({
...somniaShannon,
id: 31_337,
name: "Somnia Testnet (forked)",
rpcUrls: { default: { http: ["http://127.0.0.1:8545"], webSocket: ["ws://127.0.0.1:8545"] } },
});
Relationship to viem/chains
viem ships two of these networks as somnia and somniaTestnet; our
somniaMainnet and somniaShannon carry the same content under the names the
runbooks use (a test asserts field-for-field parity), and the module adds the
three networks viem cannot ship — Somnia Tokyo, Hideki Testnet and the local
stack are ours, on our cadence. One import covers every target, and a new network
lands when we bring it up rather than when viem next cuts a release.
Each definition lives in its own file under src/chains/definitions/, written the
way viem writes its own, so the two stay diffable. src/chains/bridge/ sits
alongside them with the warp-route registry — see the bridge guide.