# Table of Contents - [Introduction | Somnia Docs](#introduction-somnia-docs) - [Introduction | Concepts | Somnia Docs](#introduction-concepts-somnia-docs) - [Somnia-Mission | Concepts | Somnia Docs](#somnia-mission-concepts-somnia-docs) - [Overview | Concepts | Somnia Docs](#overview-concepts-somnia-docs) - [MultiStream Consensus | Concepts | Somnia Docs](#multistream-consensus-concepts-somnia-docs) - [Somnia's IceDB | Concepts | Somnia Docs](#somnia-s-icedb-concepts-somnia-docs) - [Problem | Concepts | Somnia Docs](#problem-concepts-somnia-docs) - [Accelerated Sequential Execution | Concepts | Somnia Docs](#accelerated-sequential-execution-concepts-somnia-docs) - [Use Cases | Concepts | Somnia Docs](#use-cases-concepts-somnia-docs) - [On-Chain Reactivity | Concepts | Somnia Docs](#on-chain-reactivity-concepts-somnia-docs) - [Security | Concepts | Somnia Docs](#security-concepts-somnia-docs) - [Advanced Compression Techniques | Concepts | Somnia Docs](#advanced-compression-techniques-concepts-somnia-docs) - [Allocation and unlocks | Concepts | Somnia Docs](#allocation-and-unlocks-concepts-somnia-docs) - [Overview | Concepts | Somnia Docs](#overview-concepts-somnia-docs) - [Token Staking and Delegation | Concepts | Somnia Docs](#token-staking-and-delegation-concepts-somnia-docs) - [Conclusion | Concepts | Somnia Docs](#conclusion-concepts-somnia-docs) - [LEGAL | Concepts | Somnia Docs](#legal-concepts-somnia-docs) - [LEGAL DISCLAIMER | Concepts | Somnia Docs](#legal-disclaimer-concepts-somnia-docs) - [Tokens Governance | Concepts | Somnia Docs](#tokens-governance-concepts-somnia-docs) - [Gas Fees | Concepts | Somnia Docs](#gas-fees-concepts-somnia-docs) - [Connect Your Wallet To Mainnet | Somnia Docs](#connect-your-wallet-to-mainnet-somnia-docs) - [Governance | Concepts | Somnia Docs](#governance-concepts-somnia-docs) - [Testnet STT Tokens | Somnia Docs](#testnet-stt-tokens-somnia-docs) - [Getting Started for Mainnet | Somnia Docs](#getting-started-for-mainnet-somnia-docs) - [Audits | Concepts | Somnia Docs](#audits-concepts-somnia-docs) - [MiCAR Whitepaper | Concepts | Somnia Docs](#micar-whitepaper-concepts-somnia-docs) - [SOMI coin | Somnia Docs](#somi-coin-somnia-docs) - [Network Overview (Mainnet / Testnet) | Somnia Docs](#network-overview-mainnet-testnet-somnia-docs) - [Network Info | Somnia Docs](#network-info-somnia-docs) - [Tooling | Somnia Docs](#tooling-somnia-docs) - [Bridging Info | Somnia Docs](#bridging-info-somnia-docs) - [Somnia Reactivity | Somnia Docs](#somnia-reactivity-somnia-docs) - [Subscriptions: The Core Primitive | Somnia Docs](#subscriptions-the-core-primitive-somnia-docs) - [Somnia Data Streams | Somnia Docs](#somnia-data-streams-somnia-docs) - [What is Reactivity? | Somnia Docs](#what-is-reactivity-somnia-docs) - [State Consistency Guarantees | Somnia Docs](#state-consistency-guarantees-somnia-docs) - [AML Compliance | Concepts | Somnia Docs](#aml-compliance-concepts-somnia-docs) - [Push vs Pull: An Architectural Shift | Somnia Docs](#push-vs-pull-an-architectural-shift-somnia-docs) - [Quickstart | Somnia Docs](#quickstart-somnia-docs) - [Airdrop Policy | Concepts | Somnia Docs](#airdrop-policy-concepts-somnia-docs) - [Comparison | Somnia Docs](#comparison-somnia-docs) - [Concepts | Somnia Docs](#concepts-somnia-docs) - [Extending and composing data schemas | Somnia Docs](#extending-and-composing-data-schemas-somnia-docs) - [No, this is not like regular EVM event subscriptions | Somnia Docs](#no-this-is-not-like-regular-evm-event-subscriptions-somnia-docs) - [What is Somnia Data Streams? | Somnia Docs](#what-is-somnia-data-streams-somnia-docs) - [FAQs & Troubleshooting | Somnia Docs](#faqs-troubleshooting-somnia-docs) - [Tutorials | Somnia Docs](#tutorials-somnia-docs) - [Quickstart | Somnia Docs](#quickstart-somnia-docs) - [System Events | Somnia Docs](#system-events-somnia-docs) - [Building DApps | Somnia Docs](#building-dapps-somnia-docs) - [On-chain (Solidity) | Somnia Docs](#on-chain-solidity-somnia-docs) - [Off-Chain (TypeScript) | Somnia Docs](#off-chain-typescript-somnia-docs) - [API Reference | Somnia Docs](#api-reference-somnia-docs) - [Data Provenance and Verification in Streams | Somnia Docs](#data-provenance-and-verification-in-streams-somnia-docs) - [Tutorials | Somnia Docs](#tutorials-somnia-docs) - [Development Frameworks | Somnia Docs](#development-frameworks-somnia-docs) - [Intersection with Somnia Reactivity | Somnia Docs](#intersection-with-somnia-reactivity-somnia-docs) - [Deploy with Thirdweb | Somnia Docs](#deploy-with-thirdweb-somnia-docs) - [Off-Chain Reactivity: Filtered Subscriptions tutorial | Somnia Docs](#off-chain-reactivity-filtered-subscriptions-tutorial-somnia-docs) - [Subscription management | Somnia Docs](#subscription-management-somnia-docs) - [Deploy with RemixIDE | Somnia Docs](#deploy-with-remixide-somnia-docs) - [Security | Somnia Docs](#security-somnia-docs) - [Deployment and Production | Somnia Docs](#deployment-and-production-somnia-docs) - [Ecosystem | Somnia Docs](#ecosystem-somnia-docs) - [Solidity on-chain Reactivity Tutorial | Somnia Docs](#solidity-on-chain-reactivity-tutorial-somnia-docs) - [Tokens and NFTs | Somnia Docs](#tokens-and-nfts-somnia-docs) - [Audit Checklist | Somnia Docs](#audit-checklist-somnia-docs) - [OnRamps | Somnia Docs](#onramps-somnia-docs) - [Deploy with Foundry | Somnia Docs](#deploy-with-foundry-somnia-docs) - [Wildcard Off-Chain Reactivity Tutorial | Somnia Docs](#wildcard-off-chain-reactivity-tutorial-somnia-docs) - [Understanding Schemas, Schema IDs, Data IDs, and Publisher | Somnia Docs](#understanding-schemas-schema-ids-data-ids-and-publisher-somnia-docs) - [Cron subscriptions via SDK | Somnia Docs](#cron-subscriptions-via-sdk-somnia-docs) - [Authenticating with ConnectKit | Somnia Docs](#authenticating-with-connectkit-somnia-docs) - [Smart Contracts | Somnia Docs](#smart-contracts-somnia-docs) - [Verifying via Explorer | Somnia Docs](#verifying-via-explorer-somnia-docs) - [Wallet Integration and Auth | Somnia Docs](#wallet-integration-and-auth-somnia-docs) - [Oracles | Somnia Docs](#oracles-somnia-docs) - [Buy SOMI Using Banxa Checkout | Somnia Docs](#buy-somi-using-banxa-checkout-somnia-docs) - [“Hello World” App | Somnia Docs](#-hello-world-app-somnia-docs) - [Build Your First Schema | Somnia Docs](#build-your-first-schema-somnia-docs) - [The DApp Publisher Proxy Pattern | Somnia Docs](#the-dapp-publisher-proxy-pattern-somnia-docs) - [Data Indexing and Querying | Somnia Docs](#data-indexing-and-querying-somnia-docs) - [Account Abstraction | Somnia Docs](#account-abstraction-somnia-docs) - [Ecosystem Showcase | Somnia Docs](#ecosystem-showcase-somnia-docs) - [Somnia Data vs Event Streams | Somnia Docs](#somnia-data-vs-event-streams-somnia-docs) - [Using the Viem Library | Somnia Docs](#using-the-viem-library-somnia-docs) - [Go-Live Checklist | Somnia Docs](#go-live-checklist-somnia-docs) - [Deploy with Hardhat | Somnia Docs](#deploy-with-hardhat-somnia-docs) - [Create ERC20 Tokens | Somnia Docs](#create-erc20-tokens-somnia-docs) - [Support and Community | Somnia Docs](#support-and-community-somnia-docs) - [Build a Realtime On-Chain Game | Somnia Docs](#build-a-realtime-on-chain-game-somnia-docs) - [Example Applications | Somnia Docs](#example-applications-somnia-docs) - [Meme Coins | Somnia Docs](#meme-coins-somnia-docs) - [NFTs2Me | Somnia Docs](#nfts2me-somnia-docs) - [DAO UI Tutorial p1 | Somnia Docs](#dao-ui-tutorial-p1-somnia-docs) - [Authenticating with RainbowKit | Somnia Docs](#authenticating-with-rainbowkit-somnia-docs) - [APIs | Somnia Docs](#apis-somnia-docs) - [Somnia Domains (.somi) | Somnia Docs](#somnia-domains-somi-somnia-docs) - [RPC | Somnia Docs](#rpc-somnia-docs) - [Wallet Providers | Somnia Docs](#wallet-providers-somnia-docs) - [Account Abstraction | Somnia Docs](#account-abstraction-somnia-docs) - [Smart Wallet App with Thirdweb | Somnia Docs](#smart-wallet-app-with-thirdweb-somnia-docs) - [Explorers | Somnia Docs](#explorers-somnia-docs) - [Gas Configuration | Somnia Docs](#gas-configuration-somnia-docs) - [SDKs | Somnia Docs](#sdks-somnia-docs) - [Elix.fi | Somnia Docs](#elix-fi-somnia-docs) - [Protofire Price Feeds | Somnia Docs](#protofire-price-feeds-somnia-docs) - [Integrate Chainlink Oracles | Somnia Docs](#integrate-chainlink-oracles-somnia-docs) - [On Ramps | Somnia Docs](#on-ramps-somnia-docs) - [Somnia Gas Differences To Ethereum | Somnia Docs](#somnia-gas-differences-to-ethereum-somnia-docs) - [READ Stream Data from a UI (Next.js Example) | Somnia Docs](#read-stream-data-from-a-ui-next-js-example-somnia-docs) - [DAO UI Tutorial p2 | Somnia Docs](#dao-ui-tutorial-p2-somnia-docs) - [Ecosystem Tools | Somnia Docs](#ecosystem-tools-somnia-docs) - [Using Native SOMI/STT | Somnia Docs](#using-native-somi-stt-somnia-docs) - [Safes | Somnia Docs](#safes-somnia-docs) - [Debug Playbook | Somnia Docs](#debug-playbook-somnia-docs) - [Authenticating with MetaMask | Somnia Docs](#authenticating-with-metamask-somnia-docs) - [Tokos | Somnia Docs](#tokos-somnia-docs) - [Building Subgraph UIs (NextJS/Fetch) | Somnia Docs](#building-subgraph-uis-nextjs-fetch-somnia-docs) - [Authenticating with Privy | Somnia Docs](#authenticating-with-privy-somnia-docs) - [Oracles | Somnia Docs](#oracles-somnia-docs) - [Ormi Subgraph | Somnia Docs](#ormi-subgraph-somnia-docs) - [Building Subgraph UIs (Apollo Client) | Somnia Docs](#building-subgraph-uis-apollo-client-somnia-docs) - [Using Verifiable Randomness (VRF) | Somnia Docs](#using-verifiable-randomness-vrf-somnia-docs) - [Node/Infra Security | Somnia Docs](#node-infra-security-somnia-docs) - [Building a Simple DEX on Somnia | Somnia Docs](#building-a-simple-dex-on-somnia-somnia-docs) - [Streams Case Study: Formula 1 | Somnia Docs](#streams-case-study-formula-1-somnia-docs) - [Working with Multiple Publishers in a Shared Stream | Somnia Docs](#working-with-multiple-publishers-in-a-shared-stream-somnia-docs) - [Local Testing and Forking | Somnia Docs](#local-testing-and-forking-somnia-docs) - [Subgraphs | Somnia Docs](#subgraphs-somnia-docs) - [Create ERC721 NFT Collections | Somnia Docs](#create-erc721-nft-collections-somnia-docs) - [Somnia Exchange | Somnia Docs](#somnia-exchange-somnia-docs) - [Using Data APIs (Ormi) | Somnia Docs](#using-data-apis-ormi-somnia-docs) - [DAO Smart Contract | Somnia Docs](#dao-smart-contract-somnia-docs) - [Protofire Subgraph | Somnia Docs](#protofire-subgraph-somnia-docs) - [Responsible Disclosure Policy | Somnia Docs](#responsible-disclosure-policy-somnia-docs) - [Listening to Blockchain Events (WebSocket) | Somnia Docs](#listening-to-blockchain-events-websocket-somnia-docs) - [Managing NFT Metadata with IPFS | Somnia Docs](#managing-nft-metadata-with-ipfs-somnia-docs) --- # Introduction | Somnia Docs ![](https://docs.somnia.network/~gitbook/image?url=https%3A%2F%2Flh7-rt.googleusercontent.com%2Fdocsz%2FAD_4nXc9fRiCwNtm5rL8kdGdHSqaX5DYNy_VHhRWwIp67RZkUVBi2GzkHzDlBet4M5A4SB-0feMUcfZUSHw0QXIikS9pFUsaC9KgQuJP6N1K3jR1A_GrLbYU3vFN4pvk85ls5ttGPYRqdjOel9dcikrMQbqQW4s%3Fkey%3DPd6FhcOWiUEbzWyN_-YJmg&width=768&dpr=3&quality=100&sign=2e6fdc89&sv=2) Somnia is a high-performance, cost-efficient EVM-compatible Layer 1 blockchain capable of processing over 1,000,000 transactions per second (TPS) with sub-second finality. It is suitable for serving millions of users and building real-time mass-consumer applications like games, social applications, metaverses, and more, all fully on-chain. circle-check Somnia Mainnet is LIVE. Visit the [Network Information Page](https://docs.somnia.network/developer/network-info#network-details) for Network details. circle-check **Developers who are deploying Smart Contracts and need Somnia Test Tokens, STT**. Please join the [Discordarrow-up-right](https://discord.com/invite/somnia) . Go to the `#dev-chat` channel, tag the Somnia DevRel, `@emma_odia`and request Test Tokens. You can also use the Faucet: [https://testnet.somnia.network/arrow-up-right](https://testnet.somnia.network/) You can also email `[[email protected]](https://docs.somnia.network/cdn-cgi/l/email-protection) ` with a brief description of what you are building and your GitHub profile. Somnia is supported by [Improbablearrow-up-right](https://www.improbable.io/) and [MSquaredarrow-up-right](https://msquared.io/) . Improbable will develop some of the key technical components of Somnia, including the Blockchain, but the project will require a large and active community to fulfill its vision. [](https://docs.somnia.network/concepts/litepaper/somnia-mission) ![Cover](https://docs.somnia.network/~gitbook/image?url=https%3A%2F%2F2122549367-files.gitbook.io%2F%7E%2Ffiles%2Fv0%2Fb%2Fgitbook-x-prod.appspot.com%2Fo%2Fspaces%252FkYErT9t3BJtpPfejLO6I%252Fuploads%252FxzpEww0H4kHdl1ET3JuX%252Fimages.jpeg%3Falt%3Dmedia%26token%3D93de621b-d65e-4083-9fc9-7d9716814462&width=490&dpr=3&quality=100&sign=3547f832&sv=2) Learn more about Somnia [](https://docs.somnia.network/developer/network-info) ![Cover](https://docs.somnia.network/~gitbook/image?url=https%3A%2F%2F2122549367-files.gitbook.io%2F%7E%2Ffiles%2Fv0%2Fb%2Fgitbook-x-prod.appspot.com%2Fo%2Fspaces%252FkYErT9t3BJtpPfejLO6I%252Fuploads%252FGYkr2ybMOprwXwlW19Nm%252FDiscord%2520Thumbnail.png%3Falt%3Dmedia%26token%3D4b9d7df0-10e9-4e17-9436-82eb3cc93086&width=490&dpr=3&quality=100&sign=77912ce7&sv=2) Start developing on Somnia [](https://docs.somnia.network/get-started/connect-your-wallet-to-mainnet) ![Cover](https://docs.somnia.network/~gitbook/image?url=https%3A%2F%2F2122549367-files.gitbook.io%2F%7E%2Ffiles%2Fv0%2Fb%2Fgitbook-x-prod.appspot.com%2Fo%2Fspaces%252FkYErT9t3BJtpPfejLO6I%252Fuploads%252FgVg3aOZ2HLVFBogxbsXC%252FMainnet-1.png%3Falt%3Dmedia%26token%3D6bcf0ca2-5265-4a20-93e8-6026c80ad2f0&width=490&dpr=3&quality=100&sign=22cf09fc&sv=2) Connect To Mainnet [NextGetting Started for Mainnetchevron-right](https://docs.somnia.network/get-started/getting-started-for-mainnet) Last updated 1 month ago --- # Introduction | Concepts | Somnia Docs ![](https://docs.somnia.network/~gitbook/image?url=https%3A%2F%2Flh7-rt.googleusercontent.com%2Fdocsz%2FAD_4nXc9fRiCwNtm5rL8kdGdHSqaX5DYNy_VHhRWwIp67RZkUVBi2GzkHzDlBet4M5A4SB-0feMUcfZUSHw0QXIikS9pFUsaC9KgQuJP6N1K3jR1A_GrLbYU3vFN4pvk85ls5ttGPYRqdjOel9dcikrMQbqQW4s%3Fkey%3DPd6FhcOWiUEbzWyN_-YJmg&width=768&dpr=3&quality=100&sign=2e6fdc89&sv=2) _Disclaimer: This document is for informational purposes and does not constitute an offer to sell or solicitation to buy any tokens or to participate in any way in Somnia Network. The information provided is accurate to the best of Somnia Foundation’s knowledge as of August 22, 2025. However, future-looking statements involve uncertainties and actual outcomes may differ. Somnia Foundation undertakes to update the information herein as required by law or when materially necessary. Prospective and current participants and/or token holders should conduct their own research in addition to reviewing this disclosure. By participating in Somnia Network or holding SOMI tokens, you acknowledge and accept the risks outlined herein._ Somnia is a high-performance, cost-efficient EVM-compatible Layer 1 blockchain capable of processing over 1,000,000 transactions per second (TPS) with sub-second finality. It is suitable for serving millions of users and building real-time mass-consumer applications like games, social applications, metaverses, and more, all fully on-chain. The Somnia blockchain has achieved 1.05m TPS (ERC-20 swaps) running over 100 nodes distributed globally. During these benchmarks Somnia also achieved 50k uniswaps per second (across one pool) and 300k NFT mints per seconds (one NFT contract). These workloads we do not believe would be achievable by parallel EVM approaches. Somnia has four key innovations in blockchain architecture to achieve this performance level: * [Accelerated Sequential Execution](https://docs.somnia.network/concepts/somnia-blockchain/accelerated-sequential-execution) - through compiled EVM bytecode. * [IceDB](https://docs.somnia.network/concepts/somnia-blockchain/somnias-icedb) - a faster, more predictable database for storing blockchain state. * [MultiStream consensus](https://docs.somnia.network/concepts/somnia-blockchain/multistream-consensus) - a proof-of-stake, partially synchronous BFT protocol inspired by [Autobahn BFTarrow-up-right](https://arxiv.org/pdf/2401.10369) . * [Advanced compression techniques](https://docs.somnia.network/concepts/somnia-blockchain/advanced-compression-techniques) - to deal with increased node-to-node data traffic due to the throughput. Somnia is supported by [Improbablearrow-up-right](https://www.improbable.io/) and [MSquaredarrow-up-right](https://msquared.io/) . Improbable will develop some of the key technical components of Somnia, including the Blockchain, but the project will require a large and active community to fulfil its vision. The Somnia token SOMI is issued by the Somnia Token Co Ltd. [NextSomnia-Missionchevron-right](https://docs.somnia.network/concepts/litepaper/somnia-mission) Last updated 1 month ago --- # Somnia-Mission | Concepts | Somnia Docs To enable mass consumer real-time applications to be built at web2 scale with web3 properties, creating a more open, equitable internet. ### [hashtag](https://docs.somnia.network/concepts/litepaper/somnia-mission#key-objectives) Key Objectives * Applications that can scale to web2 usage levels (millions of CCU, 100,000’s of transactions per second) * Open censorship-resistant systems accessible to anyone with access to the internet * No one counterparty controlling and hoarding value * Composable systems where value is shared among the participants * Free movement of people and assets between platforms so creators and users have freedom of choice A more open and equitable world. ### [hashtag](https://docs.somnia.network/concepts/litepaper/somnia-mission#core-principles) Core Principles #### [hashtag](https://docs.somnia.network/concepts/litepaper/somnia-mission#on-chain-experiences) On-Chain Experiences We are committed to building technology to make fully on-chain, real-time mass-scale applications possible and practical. We believe this is essential for creating a composable, open internet. #### [hashtag](https://docs.somnia.network/concepts/litepaper/somnia-mission#multiverse-over-monolith) Multiverse over Monolith We believe in a collection of applications, each with distinct, interconnected experiences, much like countries within a global community, sharing utilities that enhance mutual growth. #### [hashtag](https://docs.somnia.network/concepts/litepaper/somnia-mission#empowering-composability) Empowering Composability Central to our vision is the principle of composability—the ability for builders to build upon each other’s work, creating a culture of collaboration in which the collective output surpasses the sum of its parts. #### [hashtag](https://docs.somnia.network/concepts/litepaper/somnia-mission#builder-empowerment) Builder Empowerment We will empower builders with the freedom to build sustainable models of engagement and ownership. This includes safeguarding the rights to digital assets and creating an environment where innovation is not stifled by platform constraints. #### [hashtag](https://docs.somnia.network/concepts/litepaper/somnia-mission#accessibility-for-all) Accessibility for All The promise of the virtual society is in its potential for universal access, removing barriers for content creation and participation. Leveling the playing field such that anyone with an internet connection can participate and succeed. [PreviousIntroductionchevron-left](https://docs.somnia.network/concepts) [NextProblemchevron-right](https://docs.somnia.network/concepts/litepaper/problem) Last updated 1 month ago * [Key Objectives](https://docs.somnia.network/concepts/litepaper/somnia-mission#key-objectives) * [Core Principles](https://docs.somnia.network/concepts/litepaper/somnia-mission#core-principles) --- # Overview | Concepts | Somnia Docs The Somnia blockchain has many innovations that enable it to increase performance by several orders of magnitude compared to other EVM chains: * [MultiStream consensus](https://docs.somnia.network/concepts/somnia-blockchain/multistream-consensus) - a proof-of-stake, partially synchronous BFT protocol inspired by [Autobahn BFTarrow-up-right](https://arxiv.org/pdf/2401.10369) . * Independent Data Chains - Each validator operates its own blockchain, or “data chain,” which allows for independent block production. This unique approach eliminates the need for a consensus mechanism within individual data chains, streamlining the data processing workflow. * Consensus Chain - A separate blockchain aggregates the heads of all data chains, employing a modified PBFT algorithm for proof of stake consensus. This structure decouples data production from the consensus process, significantly enhancing overall efficiency. * [Compiled Bytecode](https://docs.somnia.network/concepts/somnia-blockchain/accelerated-sequential-execution) - By translating EVM bytecode to highly optimised native code, Somnia achieves execution speeds close to hand-written C++ contracts, facilitating the execution of millions of transactions per second on a single core. * [Faster and predictable database performance](https://docs.somnia.network/concepts/somnia-blockchain/somnias-icedb) - Somnia has a custom database called IceDB. It employs performance reports for predictable read and write performance as well as a custom database architecture that enables average read/write operations 15-100 nanoseconds with built in snapshotting. * [Advanced Compression Techniques](https://docs.somnia.network/concepts/somnia-blockchain/advanced-compression-techniques) - The Somnia data chain architecture is designed to enable streaming compression in order to maximise data throughput. Somnia combines this with [BLS signaturearrow-up-right](https://www.ietf.org/archive/id/draft-irtf-cfrg-bls-signature-05.html) aggregation in order to achieve extremely high compression ratios, allowing for massive transaction data throughput. This allows theoretical performance above other preported [“limits due to bandwidth”arrow-up-right](https://revelointel.com/lightspeed-how-monad-is-superscaling-the-evm-with-keone-and-kevin-g/) . [PreviousProblemchevron-left](https://docs.somnia.network/concepts/litepaper/problem) [NextMultiStream Consensuschevron-right](https://docs.somnia.network/concepts/somnia-blockchain/multistream-consensus) Last updated 1 month ago --- # MultiStream Consensus | Concepts | Somnia Docs ![](https://docs.somnia.network/~gitbook/image?url=https%3A%2F%2Flh7-rt.googleusercontent.com%2Fdocsz%2FAD_4nXe7Wf4hn7ozZg4s3oMjgJp-CTVgxPCrlCwcnvYhOX9o4yn89hhsPOBrGFqrTj7IN8jtcknxsqDmvFnkylytq9UfICLGSU5ZH1DPZyL9WcX0HDFtUUXGtrvd4N39NKUCTgLR2zBh_Vda4N_nUBF9fqC2cTzL%3Fkey%3DkDSKjjghsV5HdVIUV1RbMw&width=768&dpr=3&quality=100&sign=55b6c150&sv=2) Image showing conesus algorithm In Somnia, every validator publishes their own blockchain, their data chain. This innovation was inspired by the 2024 whitepaper “[Autobahn: Seamless high speed BFTarrow-up-right](https://arxiv.org/pdf/2401.10369) ”. Every data chain is completely independent, and each block in a data chain contains a blob of bytes. Only the owning validator ever adds blocks to their data chain, and there are no safety mechanisms in place to avoid them forking their data chain or proposing invalid blocks. In other words, the data chains have no consensus mechanism at all. Somnia then includes a **consensus chain**, with each consensus block including the current head of every data chain. This chain uses a modified PBFT consensus algorithm and is a typical proof of stake consensus setup. The consensus chain, including the tip of each data chain, provides full security against validators forking their own data chain. Each consensus block then semantically includes all of the transactions in all of the data chains whose tip it advanced. A deterministic pseudorandom ordering of these data chains then provides a single globally ordered stream of bytes to be executed across all data chains. This setup completely decouples the production and distribution of new data (advancements of the data chains) with the consensus algorithm. It has a number of crucial benefits described below in the Advanced Compression Techniques section (e.g. streaming compression), which let this data frontend reach almost a gigabit per second of published transaction data. [PreviousOverviewchevron-left](https://docs.somnia.network/concepts/somnia-blockchain/overview) [NextAccelerated Sequential Executionchevron-right](https://docs.somnia.network/concepts/somnia-blockchain/accelerated-sequential-execution) --- # Somnia's IceDB | Concepts | Somnia Docs Somnia also includes its own database called IceDB. It has three key properties: 1\. Deterministic performance 2\. In-memory cache with read promotions 3\. Built-in snapshotting [hashtag](https://docs.somnia.network/concepts/somnia-blockchain/somnias-icedb#deterministic-performance) **Deterministic Performance** -------------------------------------------------------------------------------------------------------------------------------------------- Most blockchains use an embedded database like [LevelsDBarrow-up-right](https://github.com/google/leveldb) or [RocksDBarrow-up-right](https://rocksdb.org/) . These databases are often optimised for write throughput and eventually flush all written data to disk to maintain a steady RAM load. This means that reads from these databases can hit RAM or many different places on disk. Depending on where the value is stored, the latency difference is enormous, differing by up to 1000x. So, how much gas should a user be charged for a read? Should we assume the worst-case scenario that every read hits disk multiple times? Or should we assume some amount will hit memory? The former drastically limits the speed of your blockchain, and the latter allows an attacker to critically slow down your chain. Could we time the read on each node and charge the user based on how long the request took? Unfortunately not, as this would not be deterministic across nodes (LevelsDB, RocksDB, etc., make no attempt to store data in deterministic places). Therefore, we can not change the gas based on this information. Somnia's IceDb has been built from the ground up to have fully deterministic performance. Every read and write to IceDb returns the result and a "performance report". This report details exactly how many cold cache lines were read from RAM, and exactly how many disk pages were read from the SSD. Due to this information being deterministic, we can charge the user based on the actual load they put on the system. Reads that accessed frequent data (and therefore hit RAM) use less gas, and we can then fit more into the block. [hashtag](https://docs.somnia.network/concepts/somnia-blockchain/somnias-icedb#improved-read-write-cache) **Improved read/write cache** -------------------------------------------------------------------------------------------------------------------------------------------- As mentioned above, databases that persist to the disk often have an in-memory cache which attempts to store frequently accessed data in RAM, to serve frequent data with a much lower latency. Most databases tend to optimise for either read or write. With Somnia, we have created a cache that can do both. This enables the average read/writes of IceDB to be between 15-100 nanoseconds. [hashtag](https://docs.somnia.network/concepts/somnia-blockchain/somnias-icedb#built-in-snapshotting) **Built-in Snapshotting** ------------------------------------------------------------------------------------------------------------------------------------ Blockchains require all nodes to periodically agree on exactly which state is in the blockchain after a given block. Most blockchains use a data structure such as a Merkle tree to retrieve a single-state hash that encapsulates all of the data in the blockchain. Merkle trees allow this hash to be updated with every write with reasonable performance while allowing proofs of a particular piece of state to be generated for parties without access to the database. Most blockchains then store each node in this Merkle tree as standard keys into their database. This makes reads or writes from this tree very expensive, as every single node can result in massive latencies from these embedded databases. The reality, however, is the data structure that these databases use under the hood (a log structured merge tree) already lends itself to a tree formation, with vast sections stored immutably on disk. IceDB utilises this underlying immutable structure to support first class state snapshots, without needing the user space execution engine to store a Merkle tree using the databases' key value abstraction. This massively accelerates the performance of IceDB, and reduces the amount of overhead for each value. [PreviousAccelerated Sequential Executionchevron-left](https://docs.somnia.network/concepts/somnia-blockchain/accelerated-sequential-execution) [NextAdvanced Compression Techniqueschevron-right](https://docs.somnia.network/concepts/somnia-blockchain/advanced-compression-techniques) * [Deterministic Performance](https://docs.somnia.network/concepts/somnia-blockchain/somnias-icedb#deterministic-performance) * [Improved read/write cache](https://docs.somnia.network/concepts/somnia-blockchain/somnias-icedb#improved-read-write-cache) * [Built-in Snapshotting](https://docs.somnia.network/concepts/somnia-blockchain/somnias-icedb#built-in-snapshotting) --- # Problem | Concepts | Somnia Docs Web3 has created a new era of decentralised finance, democratising financial access. However, it has fallen short in generating mass consumer applications and remains largely centred around finance. We believe blockchain technology is a cornerstone for the future of new, open Internet applications. Today, there are limits to what you can build on-chain. Many factors constrain this, from the cost of running applications to the fundamental performance limitations of existing blockchains. We believe that new technologies can unlock a new class of real-time applications that normally must be built on Web2 foundations. Enabling these systems to be built on-chain will allow the free movement of businesses and users between online platforms, creating what we call a true virtual society. We are building Somnia, a fast and cost-effective EVM-based blockchain, to achieve this. Somnia is a layer-one blockchain with full EVM compatibility, capable of processing over 1,000,000 transactions per second with sub-second finality and low fees. This will unlock a new wave of on-chain applications. Initially, we are focusing on gaming, metaverse, and social experiences. The use cases will likely extend far beyond these sectors. We are not even 100% certain of what can be built with the technology we are creating. [hashtag](https://docs.somnia.network/concepts/litepaper/problem#the-current-state) The Current State ---------------------------------------------------------------------------------------------------------- Although many blockchains support the EVM bytecode standard, they all leave much to be desired from a throughput perspective. Below are transactions per second (TPS) figures for a typical UniswapV3 transaction (Dex swaps per second (DPS)), which is 130k gas. As you can see, most chains offer up to ~200 DPS, or ~20M DPS per day. These numbers were taken from [this articlearrow-up-right](https://medium.com/dragonfly-research/the-amm-test-a-no-bs-look-at-l1-performance-4c8c2129d581) : Average DPS Max DPS Finality ETH 9.19 18.38 66s Polygon 47.67 95.33 Variable AVAX 31.65 175.68 3.7s BNB 194.6 194.6 75s These are somewhat similar to real world observed numbers ([taken from Chaininspectarrow-up-right](https://chainspect.app/dashboard) ). Note that Uniswaps are a more complex user interaction and are composed of many transactions. Thus these numbers will be higher than above. Name Max Recorded TPS Block Time Time to finality Base 293 2s 16m BNB Chain 1731 3s 7.5s Polygon 429 2.22s 4m 16s Arbitrum 944 0.25s 16m Ethereum 62.34 12.08 16m Optimism 67.41 2s 16m Avalanche 92.74 2.03s 0s More recent benchmarks look as gas per second. ![](https://docs.somnia.network/~gitbook/image?url=https%3A%2F%2Flh7-rt.googleusercontent.com%2Fdocsz%2FAD_4nXd4aIfcAxT5HfbxKL_DV2lm3p87K61jbnnU3WY1H47J0h1yyj8aUKiS-zDmk78hR-Z9KuqNAT4ziqTUa9Dybf3LXhTqVBl-J7pF_TPzWhBMGcEGUhoU86S1gsHWEatmoJemJCQjiJAamRlmcgo865Ubwruv%3Fkey%3DPd6FhcOWiUEbzWyN_-YJmg&width=768&dpr=3&quality=100&sign=2d3d762a&sv=2) These numbers are taken from [this articlearrow-up-right](https://www.paradigm.xyz/2024/04/reth-perf) . To translate this into DPS, it’s about 6.5 DPS for every 1mg/s. So even the fastest chain on this list has a theoretical limit of 650 DPS (about 3700 TPS). If you go outside of the EVM ecosystem, you can do a lot better. Note that this TPS is not the same as above, as the tests differ. TPS Finality Aptos 30,000 ([citearrow-up-right](https://aptoslabs.medium.com/previewnet-ensuring-scalability-and-reliability-of-the-aptos-network-48f0d210e8fe) ) 0.9s ([citearrow-up-right](https://twitter.com/Aptos/status/1632801717937922052) ) SUI 11,000 – 297,000 ([citearrow-up-right](https://blog.sui.io/sui-performance-update/) ) 480m/s ([citearrow-up-right](https://blog.sui.io/sui-performance-update/) ) Solana 1608 ([citearrow-up-right](https://chainspect.app/dashboard) ) 12.8s ([citearrow-up-right](https://chainspect.app/dashboard) ) Aptos and SUI's real-world live tests have not hit these levels yet. These numbers are based on their benchmarks. The Solana numbers differ from those on the Solana webpage as we are using the real-world observed numbers. Using the same methodology as the article above, Solana’s numbers are significantly worse at 273.34 swaps per second. Despite this performance increase, we believe the EVM is important. There is already a rich ecosystem of developers and content around the EVM. Not being able to access that ecosystem seems like a missed opportunity. These chains had to make that tradeoff to create the needed performance. But what if we could have similar performance on the EVM? The bottlenecks we see today with approaches on blockchain implemented with the EVM today are: * **Execution speed** – The rate at which smart contract code can be executed, and block creation can occur. * **Storage** — Retrieval of storing historical data of a chain. Ethereum [EIP-4844arrow-up-right](https://www.eip4844.com/) was a recent upgrade that significantly improved the cost of storage for ETH and other L2s. However, we still need improvements in reading and writing data to blockchains. * **Bandwidth** — The amount of bandwidth needed to send data between nodes on the network when running at high transaction levels. [PreviousSomnia-Missionchevron-left](https://docs.somnia.network/concepts/litepaper/somnia-mission) [NextOverviewchevron-right](https://docs.somnia.network/concepts/somnia-blockchain/overview) Last updated 6 months ago --- # Accelerated Sequential Execution | Concepts | Somnia Docs [hashtag](https://docs.somnia.network/concepts/somnia-blockchain/accelerated-sequential-execution#the-problem-with-parallel-execution) **The Problem With Parallel Execution** ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- A large number of modern blockchains have attempted to scale their execution using parallelism. This means they execute transactions which are unrelated, on different cores. This can work well when the transactions in a block are unrelated, but breaks down when those transactions modify the same state. The reality, however, is that load spikes mostly happen when there is some event which has caused the spike. This implies a massive correlation between the transactions, which is not normally there in day to day execution. For example: One of Ethereum's biggest load spikes was the Otherside Otherdeed mint. When this happened, the vast majority of all transactions in each block were all modifying the same state ([as they were minting Otherdeedsarrow-up-right](https://decrypt.co/99219/otherside-nft-mint-burned-more-157m-ethereum) ). Parallel execution would not have worked here. A DEX will contain trading on many unrelated asset pairs throughout each day, but their real load spikes will come from volatility on a specific asset pair. These trades will all modify the same state, meaning parallel execution would not have helped. ![](https://docs.somnia.network/~gitbook/image?url=https%3A%2F%2Flh7-rt.googleusercontent.com%2Fdocsz%2FAD_4nXdrbW10aNo4JpAuSDrJdce6xQInL1bEASAzpBr-ZOtxCVxjJyEWKZHJPciH1mpjbq5t5yBMD9tTUO_1pUlEUQQvRhhzdiWjO2Oxwhe8zJJT36uz2Q8uQaqLRg99kCafYu25X1qcpAgFUyVDpygGIIRsahNV%3Fkey%3DkDSKjjghsV5HdVIUV1RbMw&width=768&dpr=3&quality=100&sign=2f130325&sv=2) Parallel works well for many individual apps or accounts, swapping tokens ![](https://docs.somnia.network/~gitbook/image?url=https%3A%2F%2Flh7-rt.googleusercontent.com%2Fdocsz%2FAD_4nXcfVU2BkXvoO29xCqKfv4eZ4u0NrpRs4xM0aAdbco6EtwuQFgUtyUdnNA4WJ_5cY2hYogEzejCNJmq4lLTO09vDOh4rL5vwkuIWguVCBycnbwOCC9au0l7NMAae0FE6-5ki5c_tj2C0uqFAmIJPm5l9bML4%3Fkey%3DkDSKjjghsV5HdVIUV1RbMw&width=768&dpr=3&quality=100&sign=632be666&sv=2) When you have a hot path with many threads touching the same state parallel breaks down In other words, **parallel execution breaks down exactly when you need it.** This observation is why Somnia has opted to make a single core go extremely fast **instead of relying on parallel execution.** ![](https://docs.somnia.network/~gitbook/image?url=https%3A%2F%2Flh7-rt.googleusercontent.com%2Fdocsz%2FAD_4nXewGQiGKlqSgXIyXd4c7zX2F_2_PQDqjF6yF9mlPM3fp0NYLhidvVutP-qK7zgoielDph3jClhmbwy8cHk8Cus3TX555vzowXs-gQqbXSu868WWL29qyHWBZmyQTcBD_90SYZ8KCaDzXuL0_91hXwzwWSgp%3Fkey%3DkDSKjjghsV5HdVIUV1RbMw&width=768&dpr=3&quality=100&sign=e0b49d9a&sv=2) So, how does Somnia make a single core go this fast? [hashtag](https://docs.somnia.network/concepts/somnia-blockchain/accelerated-sequential-execution#evm-compilation) **EVM Compilation** ------------------------------------------------------------------------------------------------------------------------------------------- Somnia executes EVM transactions. It scales by reaching a very high single-core speed instead of attempting to parallelise over multiple cores. The EVM is a relatively simple stack-based architecture. Due to the implicit stack, stack-based bytecodes are often naturally smaller in size than register-based bytecodes, but they contain many redundant operations. There are broadly two ways to execute a VM's bytecode, interpreted or native (which itself can be split into JIT or AOT compilation). The former effectively simulates the VM itself in software, while the latter translates the bytecode to native instructions, which your CPU then executes directly. Ethereum and most EVM blockchains run an interpreted VM to execute EVM bytecode. This is often a relatively naive implementation which keeps its own stack, loops through each operation, and looks up the functionality of each operation in a lookup table. This is very slow compared to native execution. Further, Solidity and other Ethereum compilers often optimise for bytecode size over gas. For example, they will include code to generate large constants instead of including them inline. This ends up including a large amount of runtime computation that can actually be statically resolved at compile time. To exploit these redundancies, Somnia includes its own EVM compiler, which translates EVM bytecode to x86. This approaches near-native speed (native speed being the equivalent functionality handwritten in C++). In benchmarks, this can execute ERC-20 transfers in hundreds of nanoseconds, achieving millions of TPS on a single core. However, there is no free lunch as the compilation process is relatively expensive. For this reason, you would only do this on contracts that are called frequently, falling back to standard interpreted EVM on the rest. [hashtag](https://docs.somnia.network/concepts/somnia-blockchain/accelerated-sequential-execution#hardware-level-parallelism) **Hardware Level Parallelism** ----------------------------------------------------------------------------------------------------------------------------------------------------------------- As mentioned above, "parallel execution", attempting to run unrelated transactions on different cores, breaks down when you need it the most because it cannot handle correlated transactions, which are much more common during load spikes. This type of parallelism is also known as "software parallelism". For this reason, Somnia does not attempt to parallelise unrelated transactions. It does, however, enable the CPU to hardware parallelise each individual transaction. This harnesses the single core parallelism available in modern CPUs to speed up every individual transaction, meaning this technique also works for transactions which modify the same state, helping during the load spikes as well. So, what is hardware-level parallelism? ![](https://docs.somnia.network/~gitbook/image?url=https%3A%2F%2Fcontent.gitbook.com%2Fcontent%2FhEUf5l6MCgn0iJsSVgmd%2Fblobs%2Fk6AbvfKfTZrEImgWrqhS%2Fimage.png&width=768&dpr=3&quality=100&sign=3f254cc4&sv=2) _Diagram showing the difference between sequential execution (left) and hardware parralised execution (right)_ Modern CPU cores give the appearance that they execute assembly instructions in order, implementing control flow primitives by jumping around the assembly. The reality is that they are actually executing these instructions completely out of order, and often in parallel with each other. For example, when reading a value from memory, the CPU will run ahead and work on future computation while waiting for the result. This is all completely invisible to the developer. This can cause massive speedups. Consider the case of an ERC-20 token swap. The program, at a high level, completes the following steps in the first half of it's execution: 1\. Hash the sender account. 2\. Lookup the sender balance using this hash. 3\. Hash the receiver account. 4\. Lookup the receiver balance using this hash. 5\. ... Let's say the speed to hash an account is 150ns, and the speed to lookup the sender balance is 100ns (a single RAM read). If the CPU ran these steps in serial, this would take 150 + 100 + 150 + 100 = 500ns. However, in practice, if these steps are compiled to native code, your CPU hardware would execute 1 and 2 completely in parallel to 3 and 4, causing the execution speed to be 250ns, double the speed. The problem is, interpreting the EVM stops your CPU core from executing assembly in parallel. To really harness the available silicon in your CPU, you need to execute native assembly. Somnia's compiler and database are built to enable hardware-level parallelism of smart contracts. [PreviousMultiStream Consensuschevron-left](https://docs.somnia.network/concepts/somnia-blockchain/multistream-consensus) [NextSomnia's IceDBchevron-right](https://docs.somnia.network/concepts/somnia-blockchain/somnias-icedb) * [The Problem With Parallel Execution](https://docs.somnia.network/concepts/somnia-blockchain/accelerated-sequential-execution#the-problem-with-parallel-execution) * [EVM Compilation](https://docs.somnia.network/concepts/somnia-blockchain/accelerated-sequential-execution#evm-compilation) * [Hardware Level Parallelism](https://docs.somnia.network/concepts/somnia-blockchain/accelerated-sequential-execution#hardware-level-parallelism) --- # Use Cases | Concepts | Somnia Docs [hashtag](https://docs.somnia.network/concepts/somnia-blockchain/use-cases#gaming-fully-on-chain-forever-evolving-games) **Gaming - Fully On-Chain, Forever Evolving Games** --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- Somnia enables fully on-chain games where creators can easily build, modify, and extend games. Developers can create games that live forever on-chain, allowing players to own their in-game assets, modding systems, and worlds that grow continuously without centralized control. [hashtag](https://docs.somnia.network/concepts/somnia-blockchain/use-cases#socialfi-true-ownership-for-social-media-accounts) **SocialFi - True Ownership for Social Media Accounts** ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ Somnia powers full on-chain social media platforms where users own their accounts and data. Creators aren’t locked into one platform and can freely port their content and followers between ecosystems, ensuring freedom and control. [hashtag](https://docs.somnia.network/concepts/somnia-blockchain/use-cases#metaverse-building-interoperable-virtual-socieities) **Metaverse - Building Interoperable Virtual Socieities** ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- Somnia is the backbone for metaverse applications where entire economies and ecosystems are built with on-chain ownership and logic. Developers can create worlds with seamless interoperability, allowing assets, avatars, and experiences to cross over between multiple virtual environments. [hashtag](https://docs.somnia.network/concepts/somnia-blockchain/use-cases#defi-fully-on-chain-limit-order-books-lobs) **DeFi - Fully On-Chain Limit Order Books (LOBs)** ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ Somnia’s performance enables fully on-chain limit order books (LOBs), offering price discovery and order matching similar to centralized exchanges but with full transparency and self-custody. This innovation brings more efficiency and fairness to decentralized finance (DeFi). [hashtag](https://docs.somnia.network/concepts/somnia-blockchain/use-cases#real-time-applications-powering-real-time-large-scale-applications) **Real-Time Applications - Powering Real-Time, Large-Scale Applications** ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- Somnia’s ultra-fast processing and sub-second finality are designed for real-time, mass-consumer applications. This goes beyond the categories above to places we have not imagined, Ultimately any web2 style application can now be built on web3 rails, offering the best of both worlds. We don’t even know all the possibilities yet and would love for builders to help us discover what is now possible when limits are removed. [PreviousSecuritychevron-left](https://docs.somnia.network/concepts/somnia-blockchain/security) [NextOn-Chain Reactivitychevron-right](https://docs.somnia.network/concepts/somnia-blockchain/on-chain-reactivity) * [Gaming - Fully On-Chain, Forever Evolving Games](https://docs.somnia.network/concepts/somnia-blockchain/use-cases#gaming-fully-on-chain-forever-evolving-games) * [SocialFi - True Ownership for Social Media Accounts](https://docs.somnia.network/concepts/somnia-blockchain/use-cases#socialfi-true-ownership-for-social-media-accounts) * [Metaverse - Building Interoperable Virtual Socieities](https://docs.somnia.network/concepts/somnia-blockchain/use-cases#metaverse-building-interoperable-virtual-socieities) * [DeFi - Fully On-Chain Limit Order Books (LOBs)](https://docs.somnia.network/concepts/somnia-blockchain/use-cases#defi-fully-on-chain-limit-order-books-lobs) * [Real-Time Applications - Powering Real-Time, Large-Scale Applications](https://docs.somnia.network/concepts/somnia-blockchain/use-cases#real-time-applications-powering-real-time-large-scale-applications) --- # On-Chain Reactivity | Concepts | Somnia Docs circle-exclamation **Somnia Reactivity is currently only available on TESTNET** [hashtag](https://docs.somnia.network/concepts/somnia-blockchain/on-chain-reactivity#the-missing-link) The Missing Link ---------------------------------------------------------------------------------------------------------------------------- Blockchains have historically had a major limitation: they are passive. They are excellent ledgers, but they are deaf and mute until a user kicks them into action with a transaction. A smart contract cannot "wake up" when a price changes, specific data shows up or when a specific event occurs. [hashtag](https://docs.somnia.network/concepts/somnia-blockchain/on-chain-reactivity#the-usability) The Usability ---------------------------------------------------------------------------------------------------------------------- Imagine the world where you can instantly react to events happening on blockchain: * **DeFi**: Automated responses to price changes, liquidations, or interest accruals. For instance, a lending protocol might need to adjust collateral requirements instantly based on market volatility. * **Gaming**: Real-time updates for player achievements, in-game economies, or multiplayer interactions. Imagine a blockchain game where earning an NFT trophy automatically triggers rewards or level-ups without off-chain servers. * **Business Logic**: Supply chain tracking where an item's arrival (logged as an event) auto-initiates payments or audits. Or in insurance, a smart contract that pays out claims upon verified on-chain data like weather reports. [hashtag](https://docs.somnia.network/concepts/somnia-blockchain/on-chain-reactivity#the-setup) The Setup -------------------------------------------------------------------------------------------------------------- The blockchain is ledger of transaction modifying the common state. Some of these transaction might emit a log, which is defined as an observable event: spinner Traditionally the developer would need to create an infrastructure that constantly monitors the blockchain state and event through RPC calls. Then based on the logic the custom app would create a transaction and post it for the inclusion at the next available block: spinner As expected this created a large overhead for a typical development team. Additionally it did not meet the requirements of decentralisation, trustlessness, immediate reaction and reliable event handling. This is why we saw a plethora of services known as indexers or Web3 hooks: spinner They alleviate a lot of issues related to the infrastructure, at the same time they add new issues related to the domain-specific configuration or language. Still the key requirements are still not met: * indexers are centralised and trustfull * the event handling trasaction can only be included in the next available block * it is not MEV-resistant * it's only as reliable as the indexer provider [hashtag](https://docs.somnia.network/concepts/somnia-blockchain/on-chain-reactivity#the-solution) The Solution -------------------------------------------------------------------------------------------------------------------- Enter **Somnia Native On-Chain Reactivity**. This isn't just an incremental improvement; it's a paradigm shift that enables real-time, trustless and decentralised reactions without relying on external systems: spinner [hashtag](https://docs.somnia.network/concepts/somnia-blockchain/on-chain-reactivity#the-benefits) The Benefits -------------------------------------------------------------------------------------------------------------------- Once an event is defined, it's handling is happening automatically by blockchain nodes: * **Real-Time**: Reaction included in the same block. * **Decentralised**: As decentralised as the blockchain itself. * **Trustlessness**: No trusted party required. * **MEV**: Fully MEV-resistant due to deterministic inclusion of event handlers * **Developer-Friendly**: Familiar paradigms (e.g., subscriptions akin to event listeners in traditional programming) and interface reduce complexity. * **Cost Efficiency**: Optimized gas usage minimize expenses compared to infrastucture required for constant off-chain monitoring. * **Security and Resilience**: Backed by the blockchain itself. Reduces attack surfaces by eliminating external dependencies; nested events allow complex chains without intermediaries. * **Scalability**: Supports high-throughput apps, as seen in Somnia's 1M+ TPS capability, making it ideal for mass-adoption scenarios. * * * ### [hashtag](https://docs.somnia.network/concepts/somnia-blockchain/on-chain-reactivity#comparison-table) Comparison Table Aspect Traditional RPC Indexer/Hooks Somnia Reactivity **Timing** Next block Next block Same block **Decentralization** Developer infra Centralized service Fully decentralized **Trust Model** Self-hosted Trusted third party Trustless **MEV Resistance** ❌ ❌ ✅ **Infrastructure** High complexity Medium complexity Zero external infra **Reliability** Self-managed Service-dependent Blockchain-native [PreviousUse Caseschevron-left](https://docs.somnia.network/concepts/somnia-blockchain/use-cases) [NextOverviewchevron-right](https://docs.somnia.network/concepts/tokenomics/overview) Last updated 1 month ago * [The Missing Link](https://docs.somnia.network/concepts/somnia-blockchain/on-chain-reactivity#the-missing-link) * [The Usability](https://docs.somnia.network/concepts/somnia-blockchain/on-chain-reactivity#the-usability) * [The Setup](https://docs.somnia.network/concepts/somnia-blockchain/on-chain-reactivity#the-setup) * [The Solution](https://docs.somnia.network/concepts/somnia-blockchain/on-chain-reactivity#the-solution) * [The Benefits](https://docs.somnia.network/concepts/somnia-blockchain/on-chain-reactivity#the-benefits) * [Comparison Table](https://docs.somnia.network/concepts/somnia-blockchain/on-chain-reactivity#comparison-table) --- # Security | Concepts | Somnia Docs [hashtag](https://docs.somnia.network/concepts/somnia-blockchain/security#decentralisation) **Decentralisation** --------------------------------------------------------------------------------------------------------------------- Somnia philosophically believes in having sufficiently decentralised services, not maximally decentralised. What this means is that you have enough decentralisation of infrastructure to enable the good properties of decentralisation (increased security, censorship resistance, no single owner/counterparty) whilst not trading off to degrade performance significantly (all decentralisation will inherently decrease performance [see blockchain trilemaarrow-up-right](https://ieeexplore.ieee.org/abstract/document/10549891) ). For Somnia, the main validators of the network will be targeting hardware specs between a Solana and Aptos node. This will allow a large group of participants to join the network but not have sub-par hardware and connectivity. This will ensure the high level of performance needed for real-time mass-consumer applications. There will initially be 100 globally distributed validator nodes. We expect this to grow as the network matures. We also incentivize decentralisation and global footprint for the chain through our tokenomics. [hashtag](https://docs.somnia.network/concepts/somnia-blockchain/security#cuthbert) Cuthbert ------------------------------------------------------------------------------------------------- Cuthbert is a separate implementation of Somnia's execution and database, using third party libraries wherever possible, and without any optimisations. Somnia validators automatically run every transaction through both Somnia and Cuthbert, and they will stop voting or executing if they ever detect a divergence between the two. This means that if the Somnia client has a bug or an issue in its execution or database, that bug would also have to be present in the separate Cuthbert implementation if this bug were to go undetected. Cuthbert will eventually be phased out of Somnia as the system becomes more mature. [hashtag](https://docs.somnia.network/concepts/somnia-blockchain/security#securing-the-network) **Securing The Network** ----------------------------------------------------------------------------------------------------------------------------- As stated in the introduction the network is secured by validators staking tokens to participate in the network. This is a PoS network similar to other major blockchain networks (e.g. ETH). Node providers are subject to slashing if they act maliciously against the network. This will be further explored in our tokenomics. [PreviousAdvanced Compression Techniqueschevron-left](https://docs.somnia.network/concepts/somnia-blockchain/advanced-compression-techniques) [NextUse Caseschevron-right](https://docs.somnia.network/concepts/somnia-blockchain/use-cases) Last updated 7 months ago * [Decentralisation](https://docs.somnia.network/concepts/somnia-blockchain/security#decentralisation) * [Cuthbert](https://docs.somnia.network/concepts/somnia-blockchain/security#cuthbert) * [Securing The Network](https://docs.somnia.network/concepts/somnia-blockchain/security#securing-the-network) --- # Advanced Compression Techniques | Concepts | Somnia Docs When you have a lot of transactions, a lot of data needs to go between nodes ![](https://docs.somnia.network/~gitbook/image?url=https%3A%2F%2Flh7-rt.googleusercontent.com%2Fdocsz%2FAD_4nXc-j1fYvX9rhOUfFhGdomgdhKQyv_X0sWX8hBeOQIWo3HNMGEqBQF6B0TkR33gRYdLvSpsd5iJxeNGazVXtnCW_BQuzlPud6Gf8tgM_UZTU-Cldil3P0G89Ed0rtRj1xm0PV0gUXSAlmOyLvboAziklpmqz%3Fkey%3DkDSKjjghsV5HdVIUV1RbMw&width=768&dpr=3&quality=100&sign=a2d6288c&sv=2) Once you are getting to the world of 100,000’s or even millions of transactions per second you start creating a lot of data. A standard ERC-20 transfer is about 200 bytes when you consider all its parts. Now, imagine we are doing 1 million ERC-20 swaps per second. That’s 190 MBytes/s or 1.5 GBits/s. It's not going to pass over the public Internet, so you are either going to have a very centralised chain or limit your transaction rate. [hashtag](https://docs.somnia.network/concepts/somnia-blockchain/advanced-compression-techniques#power-law-distribution) **Power law distribution** -------------------------------------------------------------------------------------------------------------------------------------------------------- ![](https://docs.somnia.network/~gitbook/image?url=https%3A%2F%2Flh7-rt.googleusercontent.com%2Fdocsz%2FAD_4nXeRqry-9IPGzaY3Ujfo612zu6AwYh3DaUWCrT0-DesSsxOUkHmEiNaA4UKnvzDJfVL_sewKnZXy0xrSMd-RCAF_O9PZAfP3dTYMIFDs0oHrnCI4ZD0zb-jii9vonf8vBb-su2W7WopjgbnI2M71l3UyO8k%3Fkey%3DkDSKjjghsV5HdVIUV1RbMw&width=768&dpr=3&quality=100&sign=9c034bfb&sv=2) Image showing distribution of the power law However, there is a lot of redundant information in those bytes. The number of bits theoretically required to send a piece of information is the logarithm of its probability of occuring. In practice, the probability distribution of which account is making a transaction, or which contract is being executed, or the arguments to the method being called, is very sharp (often a power law). This means that a minority of those accounts or contracts are highly likely to occur in the data to be sent. For example, if a particular contract was being called by 10% of transactions, its address can be encoded in 3.3 bits. That is a 48x compression ratio on the uncompressed 20 byte address. [hashtag](https://docs.somnia.network/concepts/somnia-blockchain/advanced-compression-techniques#streaming-compression) **Streaming Compression** ------------------------------------------------------------------------------------------------------------------------------------------------------ This leads to the question of how we can effectively compress the transaction data moving between nodes. There are broadly two forms of compression: * Block compression * Streaming compression Block compression is given a single block of data, and compresses in a way where the receiver only needs that block of data to decompress it. This is the typical form of compression, such as zip or tar files. It is very convenient because the compressor doesn't need to make assumptions about what other information the client has, or the order that information was sent. Streaming compression is able to assume that the sender and receiver both share an identical history of the data that was compressed and decompressed, and it uses this assumption to build a large amount of internal implicit information which never needs to be sent over the wire. It can say, "Use the address from 3.456 megabytes ago". For this reason, it achieves much better compression ratios than block compression. The downside, though, is that the sender and receiver must share an identical stream of data, and the 'implicit' data cannot be moved across machines or processes without using a lot of bandwidth, meaning the same process must compress the full stream of data (there are ways around this depending on the compression algorithm but it often requires a large amount of CPU time). This poses a problem for a blockchain, which typically has each block proposed by a different machine. It forces blockchains to use block-based compression, if anything, significantly impacting the compression ratios they can achieve. Somnia has a consensus and data availability algorithm designed to support streaming-based compression. Each validator is responsible for publishing their own stream of data to their own blockchain. These are the data chains introduced earlier in this section. The fact that data chains use the same process for publishing this stream unlocks the ability for streaming compression. [hashtag](https://docs.somnia.network/concepts/somnia-blockchain/advanced-compression-techniques#hashes-and-signatures) **Hashes and Signatures** ------------------------------------------------------------------------------------------------------------------------------------------------------ A lot of data in Ethereum transactions have a tight power law distribution, making them very compressible. There are, however two important exceptions: hashes and signatures. By definition, these completely change with a uniform distribution if any bit in the transaction is different, making them completely uncompressable (no two executed Ethereum transactions are identical due to nonces, which avoid replay attacks). Transaction hashes are easy: they are, again by definition, reproducible based on the transaction data itself, so they can simply not be sent (a receiving client is required to recalculate them anyway). Signatures are more challenging. They are required to be sent alongside each transaction. Due to them being uncompressable, this would heavily limit the compression ratios we are able to achieve on transaction data. However, we can aggregate signatures if we use the BLS signature scheme. BLS signatures can aggregate any number of BLS signatures into a single signature, effectively achieving a constant size for any number of transaction signatures. Somnia uses BLS signature aggregation for signature verification speed and, crucially, because it enables a far better compression ratio. ![](https://docs.somnia.network/~gitbook/image?url=https%3A%2F%2Flh7-rt.googleusercontent.com%2Fdocsz%2FAD_4nXfEjCRky-SOm2RJWhdrOKh0Z1cqWrOFAfSLFNSdGn7oDsKHWrPpPIMjcyOmzQXycGwKgiPO7dXESdwDO6eYh7q1zh-0CSFsoLCQsZgHV8IJJ5B-HBDW70_brLGApwNJsHFTZwGBf1n0qnK6YHCwQ9VKbpvv%3Fkey%3DPd6FhcOWiUEbzWyN_-YJmg&width=768&dpr=3&quality=100&sign=f2faca69&sv=2) Image explaining BLS signature scheme This aggregate cryptography is an optional mechanism to submit batches of transactions, where the cost to verify the signatures of all transactions in the batch is similar to the cost of verifying just one transaction. Using large batches this reduces the cost of signature verification by many orders of magnitude. [hashtag](https://docs.somnia.network/concepts/somnia-blockchain/advanced-compression-techniques#bandwidth-symmetry) **Bandwidth Symmetry** ------------------------------------------------------------------------------------------------------------------------------------------------ In a typical blockchain, one validator is responsible for proposing each block and therefore, publishing the transaction data for that 'slot' of time to all of its peers. If the blockchain throughput is X bytes per second, and there are B blocks per second and N peers, the leader must send N \* X / B bytes in 1 / B seconds, requiring an upload speed of N \* X bytes per second (some blockchains will fan out in a tree architecture, which still requires the branching factor multiplied by X ). In comparison, due to Somnia having all peers publish their own shard of data, each peer is responsible for publishing X / (N \* B) bytes in 1 / B seconds, requiring an upload speed of X bytes per second. They also need to download everybody else's data for each block, requiring a download of N \* (X / N) bytes per second, which is just X. Note that no less data is being sent overall, but the bandwidth profile has been amortised to be symmetrical across all peers at all times. No peer ever needs to upload at a faster rate than the bandwidth of the blockchain itself, but they need to do this constantly instead of only when they propose a block. This enables the overall blockchain throughput to reach much closer to the bandwidth of the peers themselves. [PreviousSomnia's IceDBchevron-left](https://docs.somnia.network/concepts/somnia-blockchain/somnias-icedb) [NextSecuritychevron-right](https://docs.somnia.network/concepts/somnia-blockchain/security) * [Power law distribution](https://docs.somnia.network/concepts/somnia-blockchain/advanced-compression-techniques#power-law-distribution) * [Streaming Compression](https://docs.somnia.network/concepts/somnia-blockchain/advanced-compression-techniques#streaming-compression) * [Hashes and Signatures](https://docs.somnia.network/concepts/somnia-blockchain/advanced-compression-techniques#hashes-and-signatures) * [Bandwidth Symmetry](https://docs.somnia.network/concepts/somnia-blockchain/advanced-compression-techniques#bandwidth-symmetry) --- # Allocation and unlocks | Concepts | Somnia Docs [hashtag](https://docs.somnia.network/concepts/tokenomics/allocation-and-unlocks#allocations) Allocations -------------------------------------------------------------------------------------------------------------- ALLOCATION % OF TOKENS NO. OF TOKENS % AT TGE CLIFF (MNTHS) VESTING (MNTHS) 1\. Team 11% 110,000,000 0% 12 48 2\. Launch Partners 15% 150,000,000 0% 12 48 2\. Investors 15.15% 151,500,000 0% 12 36 3\. Advisors 3.58% 35,800,000 0% 12 36 4\. Ecosystem 27.345% 273,450,000 5.075% 0 48 5\. Community 27.925% 279,250,000 10.945% 0 36 ​TOTAL % 100% 1,000,000,000 16.02% * Team - Allocation to early team members and founders * Launch partners - Allocation to early contributors to the Somnia ecosystem * Investors - Allocation to seed investors * Advisors - Allocation to key Advisors for Somnia * Ecosystem - Allocation to be used to develop the Somnia ecosystem and fund the foundation * Community - Allocation for users who contributed to the Somnia ecosystem, validator rewards and liquidity. [hashtag](https://docs.somnia.network/concepts/tokenomics/allocation-and-unlocks#unlock-schedule) Unlock Schedule ---------------------------------------------------------------------------------------------------------------------- The SOMI token unlock schedule chart below illustrates the gradual release of tokens over a period of 48 months post-launch. The circulating supply is segmented into five categories, each contributing to the overall supply incrementally: Community (Light blue): The largest allocation, represented by the light area. This will be used for community initiatives, including liquidity and airdrops. This will have 10.945% unlocked a TGE then a increased unlock at M1 and M2, then monthly vesting linear for 36 months. Ecosystem (Orange): The ecosystem allocation, depicted in orange, reflects the gradual and consistent release of tokens intended to fuel ecosystem development and partnerships. This allocation has linear linear 48 months vesting. 5.075% will be unlocked at TGE Investors (Yellow): Represented by the yellow area, investor tokens start release at month 12 and vest monthly afterwards for 36 months. Advisors (Green): Key advisors to the project have a 12-month cliff with a 36-month linear vest. Launch Partners (Red): The red area illustrates the allocation for launch partners. This allocation has a 12-month cliff with a 48-month linear vest. These are allocated to early contributors to the Somnia ecosystem (eg Improbable). Team (Blue): The team allocation is shown in blue. This has a 12-month cliff with a 48-month linear vest. This gradual increase ensures alignment with the project's long-term goals and incentivizes sustained contribution. The chart demonstrates the planned and strategic distribution of SOMI tokens, ensuring balanced growth and support for various stakeholders in the ecosystem. ![](https://docs.somnia.network/~gitbook/image?url=https%3A%2F%2F1819673643-files.gitbook.io%2F%7E%2Ffiles%2Fv0%2Fb%2Fgitbook-x-prod.appspot.com%2Fo%2Fspaces%252FhEUf5l6MCgn0iJsSVgmd%252Fuploads%252FcAXjnmILmyKx5LSKl8dV%252Fimage.png%3Falt%3Dmedia%26token%3Dc24c7d77-6203-4363-b47f-1b5369532f59&width=768&dpr=3&quality=100&sign=3d839fe2&sv=2) An interactive chart can be found here: [https://unlocks.somnia.network/unlocks.somnia.networkchevron-right](https://unlocks.somnia.network/) [PreviousGas Feeschevron-left](https://docs.somnia.network/concepts/tokenomics/gas-fees) [NextTokens Governancechevron-right](https://docs.somnia.network/concepts/tokenomics/tokens-governance) Last updated 6 months ago * [Allocations](https://docs.somnia.network/concepts/tokenomics/allocation-and-unlocks#allocations) * [Unlock Schedule](https://docs.somnia.network/concepts/tokenomics/allocation-and-unlocks#unlock-schedule) --- # Overview | Concepts | Somnia Docs The Somnia token (SOMI) is the Native token of the Somnia blockchain, with a fixed supply of 1,000,000,000 tokens. [hashtag](https://docs.somnia.network/concepts/tokenomics/overview#token-utility) Token Utility ---------------------------------------------------------------------------------------------------- SOMI is a delegated proof of stake token (dPoS) it is intended for: * Staking functions * Validator staking - To provide validator nodes for the Somnia blockchain, Tokens must be staked. * Delegated staking - Tokens can be delegated to Node Providers to cover their staking costs. * Payment methods * Gas Fees - To use the blockchain, gas fees will be paid in the SOMI token. * Governance: The Token will eventually be used to inform how decisions regarding the Network and its components are made. The details of this are to be determined. [PreviousOn-Chain Reactivitychevron-left](https://docs.somnia.network/concepts/somnia-blockchain/on-chain-reactivity) [NextToken Staking and Delegationchevron-right](https://docs.somnia.network/concepts/tokenomics/token-staking-and-delegation) Last updated 6 months ago --- # Token Staking and Delegation | Concepts | Somnia Docs There are four main entities that play a part in the tokenomics of the Network: Application Owners, Validators, Content Creators and Token Holders. Validators are required to time-lock a fixed amount of Tokens at a designated blockchain address in order to secure their place on the Somnia Network/provide validator nodes as described in the Staking section below. Token Holders can choose to participate in staking, in order to receive rewards from the Fees and/or from the Treasury. These rewards are only available to “staked” Tokens and not to Tokens generally held by Token Holders. This section explores the staking and delegation options under discussion with the Network. [hashtag](https://docs.somnia.network/concepts/tokenomics/token-staking-and-delegation#staking-validators) Staking: Validators ----------------------------------------------------------------------------------------------------------------------------------- * In the Somnia Blockchain, validators must stake Tokens to provide nodes for computation to the network. * Validators must provide a certain level of hardware in order to participate in the network. * The amount of Tokens required to provide a validation node is 5,000,000 SOMI Tokens. * They will be rewarded from gas fees and treasury-based incentives. When Validators cover their staking requirement fully, they earn 100% of the rewards that their service generates. However, Validators can also choose to only partially cover their locking requirements. In this scenario, Validators can delegate the provision of the remaining Token requirement to third-party Token Holders. These third-party Token Holders will be given a percentage of the validator's rewards. This is set by the validator (the delegation rate). Validators can choose to delegate part of the Tokens they are required to fulfill their locking requirements into either the General Validator Pool, or into a Validator-Specific Pool. ### [hashtag](https://docs.somnia.network/concepts/tokenomics/token-staking-and-delegation#delegated-staking-specific-pool-validators) **Delegated Staking Specific Pool: Validators** * Validators can choose to enable the Tokens required to provide a specific Validator’s node to other Token Holders. * Token holders can choose to provide their Tokens to Validators in exchange for a percentage share of the validator's rewards. * The validator sets this percentage called the delegation rate. * When providing their Tokens to Validators, Token Holders must wait 28 days before unstaking, essentially locking their Tokens. * They can choose to emergency unstake at the cost of 50% of their staked Tokens. The forfeited Tokens will then flow back to the Treasury. ### [hashtag](https://docs.somnia.network/concepts/tokenomics/token-staking-and-delegation#delegated-staking-general-pool-validators) **Delegated Staking General Pool: Validators** * Token Holders can choose to delegate to the general Validator pool. * This will allocate their Tokens to all validators currently offering delegated staking. * The Token Holders will receive a percentage of rewards across all Validators. * This spreads the risk of delegation at the cost of potentially less yield. * When staking in the general pool there is no locking period. * The general pool will cover understaked validators first. **What if a validator can’t meet the minimum staking requirements?** In the event that a validator fails to meet the minimum staking requirements, a cooldown period will be provided to the validator to fulfil the staking obligations, typically lasting for one month. **What if a validator has more tokens staked than needed?** Through the general pool and the delegation mechanism, it is possible for a validator to overstake. This means that more Tokens are staked than are required to operate the validator being supplied to the network. In this case, it means there is no time lock to unlock such additional Tokens for that validator. Any user can instantly unlock until the required level of Tokens is reached. As a result of this, the reward split will be further diluted but at the benefit of increased liquidity. [hashtag](https://docs.somnia.network/concepts/tokenomics/token-staking-and-delegation#staking-rewards) **Staking rewards** -------------------------------------------------------------------------------------------------------------------------------- * 50% of all gas fees are distributed to validators as rewards * Validators can offer to share these rewards with delegated stakers. * They do this by setting a delegation rate percentage * Example * Validator has a delegation rate of 80% * The validator gets 100 SOMI in rewards for the Epoch * You are delegating 1M out of the 5M tokens delegated to the validator. This means you have a staking ratio of 0.2 (20%) * The formula to calulate your rewards for the epoch is: epochrewards∗delegationrate∗stakingratioepoch rewards \* delegation rate \* staking ratioepochrewards∗delegationrate∗stakingratio so in this case: 100(epochrewards)∗0.8(delegationrate)∗0.2(stakingratio)\=16tokens100 (epoch rewards) \* 0.8 (delegation rate) \* 0.2 (staking ratio) = 16 tokens100(epochrewards)∗0.8(delegationrate)∗0.2(stakingratio)\=16tokens [PreviousOverviewchevron-left](https://docs.somnia.network/concepts/tokenomics/overview) [NextGas Feeschevron-right](https://docs.somnia.network/concepts/tokenomics/gas-fees) Last updated 6 months ago * [Staking: Validators](https://docs.somnia.network/concepts/tokenomics/token-staking-and-delegation#staking-validators) * [Delegated Staking Specific Pool: Validators](https://docs.somnia.network/concepts/tokenomics/token-staking-and-delegation#delegated-staking-specific-pool-validators) * [Delegated Staking General Pool: Validators](https://docs.somnia.network/concepts/tokenomics/token-staking-and-delegation#delegated-staking-general-pool-validators) * [Staking rewards](https://docs.somnia.network/concepts/tokenomics/token-staking-and-delegation#staking-rewards) --- # Conclusion | Concepts | Somnia Docs We believe in the vision of a world computer. A world where computing is accessible and usable by anyone with an internet connection, where data is shared across a global network that enables composable and interoperable applications. Ethereum started this movement and has provided the backbone for finance. We want to carry that flag for all application classes that can scale and compete with the large-scale web2 market while giving users and builders freedom, ownership and agency. A free and open internet. [PreviousTokens Governancechevron-left](https://docs.somnia.network/concepts/tokenomics/tokens-governance) [NextLEGALchevron-right](https://docs.somnia.network/concepts/miscellaneous/legal) Last updated 1 month ago --- # LEGAL | Concepts | Somnia Docs [LEGAL DISCLAIMERchevron-right](https://docs.somnia.network/concepts/miscellaneous/legal/legal-disclaimer) [AML Compliancechevron-right](https://docs.somnia.network/concepts/miscellaneous/legal/aml-compliance) [Airdrop Policychevron-right](https://docs.somnia.network/concepts/miscellaneous/legal/airdrop-policy) [Governancechevron-right](https://docs.somnia.network/concepts/miscellaneous/legal/governance) [MiCAR Whitepaperchevron-right](https://docs.somnia.network/concepts/miscellaneous/legal/micar-whitepaper) [PreviousConclusionchevron-left](https://docs.somnia.network/concepts/conclusion) [NextLEGAL DISCLAIMERchevron-right](https://docs.somnia.network/concepts/miscellaneous/legal/legal-disclaimer) --- # LEGAL DISCLAIMER | Concepts | Somnia Docs #### [hashtag](https://docs.somnia.network/concepts/miscellaneous/legal/legal-disclaimer#please-read-the-entirety-of-this-legal-disclaimer-section-carefully.-nothing-herein-constitutes-lega) **PLEASE READ THE ENTIRETY OF THIS "LEGAL DISCLAIMER" SECTION CAREFULLY. NOTHING HEREIN CONSTITUTES LEGAL, FINANCIAL, BUSINESS OR TAX ADVICE AND YOU ARE STRONGLY ADVISED TO CONSULT YOUR OWN LEGAL, FINANCIAL, TAX OR OTHER PROFESSIONAL ADVISOR(S) BEFORE ENGAGING IN ANY ACTIVITY IN CONNECTION HEREWITH. NEITHER SOMNIA TOKENCO. LTD. (THE COMPANY), ANY OF THE PROJECT CONTRIBUTORS (THE SOMNIA TEAM) WHO HAVE WORKED ON SOMNIA (AS DEFINED HEREIN) OR PROJECT TO DEVELOP SOMNIA IN ANY WAY WHATSOEVER, ANY DISTRIBUTOR AND/OR VENDOR OF SOMI TOKENS (OR SUCH OTHER RE-NAMED OR SUCCESSOR TICKER CODE OR NAME OF SUCH TOKENS) (THE DISTRIBUTOR), NOR ANY SERVICE PROVIDER SHALL BE LIABLE FOR ANY KIND OF DIRECT OR INDIRECT DAMAGE OR LOSS WHATSOEVER WHICH YOU MAY SUFFER IN CONNECTION WITH ACCESSING THE PAPER, DECK OR MATERIAL RELATING TO SOMI (THE TOKEN DOCUMENTATION) AVAILABLE ON THE WEBSITE AT HTTPS://SOMNIA.NETWORK/ (THE WEBSITE, INCLUDING ANY SUB-DOMAINS THEREON) OR ANY OTHER WEBSITES OR MATERIALS PUBLISHED OR COMMUNICATED BY THE COMPANY OR ITS REPRESENTATIVES FROM TIME TO TIME.** * * * **Project purpose:** You agree that you are acquiring SOMI to participate in Somnia and to obtain services on the ecosystem thereon. The Company, the Distributor and their respective affiliates would develop and contribute to the underlying source code for Somnia. The Company is acting solely as an arms’ length third party in relation to the SOMI distribution, and not in the capacity as a financial advisor or fiduciary of any person with regard to the distribution of SOMI. **Nature of the Token Documentation:** The Token Documentation is a conceptual paper that articulates some of the main design principles and ideas for the creation of a digital token to be known as SOMI. The Token Documentation and the Website are intended for general informational purposes only and do not constitute a prospectus, an offer document, an offer of securities, a solicitation for investment, any offer to sell any product, item, or asset (whether digital or otherwise), or any offer to engage in business with any external individual or entity provided in said documentation. The information herein may not be exhaustive and does not imply any element of, or solicit in any way, a legally-binding or contractual relationship. There is no assurance as to the accuracy or completeness of such information and no representation, warranty or undertaking is or purported to be provided as to the accuracy or completeness of such information. Where the Token Documentation or the Website includes information that has been obtained from third party sources, the Company, the Distributor, their respective affiliates and/or the Somnia team have not independently verified the accuracy or completeness of such information. Further, you acknowledge that the project development roadmap, platform/network functionality are subject to change and that the Token Documentation or the Website may become outdated as a result; and neither the Company nor the Distributor is under any obligation to update or correct this document in connection therewith. **Validity of Token Documentation and Website:** Nothing in the Token Documentation or the Website constitutes any offer by the Company, the Distributor, or the Somnia team to sell any SOMI (as defined herein) nor shall it or any part of it nor the fact of its presentation form the basis of, or be relied upon in connection with, any contract or investment decision. Nothing contained in the Token Documentation or the Website is or may be relied upon as a promise, representation or undertaking as to the future performance of Somnia. The agreement between the Distributor (or any third party) and you, in relation to any distribution or transfer of SOMI, is to be governed only by the separate terms and conditions of such agreement. The information set out in the Token Documentation and the Website is for community discussion only and is not legally binding. No person is bound to enter into any contract or binding legal commitment in relation to the acquisition of SOMI, and no digital asset or other form of payment is to be accepted on the basis of the Token Documentation or the Website. The agreement for distribution of SOMI and/or continued holding of SOMI shall be governed by a separate set of Terms and Conditions or Token Distribution Agreement (as the case may be) setting out the terms of such distribution and/or continued holding of SOMI (the Terms and Conditions), which shall be separately provided to you or made available on the Website. The Terms and Conditions must be read together with the Token Documentation. In the event of any inconsistencies between the Terms and Conditions and the Token Documentation or the Website, the Terms and Conditions shall prevail. **Deemed Representations and Warranties:** By accessing the Token Documentation or the Website (or any part thereof), you shall be deemed to represent and warrant to the Company, the Distributor, their respective affiliates, and the Somnia team as follows: 1. in any decision to acquire any SOMI, you have not relied and shall not rely on any statement set out in the Token Documentation or the Website; 2. you shall at your own expense ensure compliance with all laws, regulatory requirements and restrictions applicable to you (as the case may be); 3. you acknowledge, understand and agree that SOMI may have no value, there is no guarantee or representation of value or liquidity for SOMI, and SOMI is not an investment product nor is it intended for any speculative investment whatsoever; 4. none of the Company, the Distributor, their respective affiliates, and/or the Somnia team shall be responsible for or liable for the value of SOMI, the transferability and/or liquidity of SOMI and/or the availability of any market for SOMI through third parties or otherwise; and 5. you acknowledge, understand and agree that you are not eligible to participate in the distribution of SOMI if you are a citizen, national, resident (tax or otherwise), domiciliary and/or green card or permanent visa holder of a geographic area or country (i) where it is likely that the distribution of SOMI would be construed as the sale of a security (howsoever named), financial service or investment product and/or (ii) where participation in token distributions is prohibited by applicable law, decree, regulation, treaty, or administrative act (including without limitation the United States of America, Canada, and the People's Republic of China); and to this effect you agree to provide all such identity verification document when requested in order for the relevant checks to be carried out. The Company, the Distributor and the Somnia team do not and do not purport to make, and hereby disclaims, all representations, warranties or undertaking to any entity or person (including without limitation warranties as to the accuracy, completeness, timeliness, or reliability of the contents of the Token Documentation or the Website, or any other materials published by the Company or the Distributor). To the maximum extent permitted by law, the Company, the Distributor, their respective affiliates and service providers shall not be liable for any indirect, special, incidental, consequential or other losses of any kind, in tort, contract or otherwise (including, without limitation, any liability arising from default or negligence on the part of any of them, or any loss of revenue, income or profits, and loss of use or data) arising from the use of the Token Documentation or the Website, or any other materials published, or its contents (including without limitation any errors or omissions) or otherwise arising in connection with the same. Prospective acquirors of SOMI should carefully consider and evaluate all risks and uncertainties (including financial and legal risks and uncertainties) associated with the distribution of SOMI, the Company, the Distributor and the Somnia team. **SOMI Token:** SOMI are designed to be utilised, and that is the goal of the SOMI distribution. In particular, it is highlighted that SOMI: 1. does not have any tangible or physical manifestation, and does not have any intrinsic value/pricing (nor does any person make any representation or give any commitment as to its value); 2. is non-refundable, not redeemable for any assets of any entity or organisation, and cannot be exchanged for cash (or its equivalent value in any other digital asset) or any payment obligation by the Company, the Distributor or any of their respective affiliates; 3. does not represent or confer on the token holder any right of any form with respect to the Company, the Distributor (or any of their respective affiliates), or their revenues or assets, including without limitation any right to receive future dividends, revenue, shares, ownership right or stake, share or security, any voting, distribution, redemption, liquidation, proprietary (including all forms of intellectual property or licence rights), right to receive accounts, financial statements or other financial data, the right to requisition or participate in shareholder meetings, the right to nominate a director, or other financial or legal rights or equivalent rights, or intellectual property rights or any other form of participation in or relating to Somnia, the Company, the Distributor and/or their service providers; 4. is not intended to represent any rights under a contract for differences or under any other contract the purpose or intended purpose of which is to secure a profit or avoid a loss; 5. is not intended to be a representation of money (including electronic money), payment instrument, security, commodity, bond, debt instrument, unit in a collective investment or managed investment scheme or any other kind of financial instrument or investment; 6. is not a loan to the Company, the Distributor or any of their respective affiliates, is not intended to represent a debt owed by the Company, the Distributor or any of their respective affiliates, and there is no expectation of profit nor interest payment; and 7. does not provide the token holder with any ownership or other interest in the Company, the Distributor or any of their respective affiliates. Notwithstanding the SOMI distribution, users have no economic or legal right over or beneficial interest in the assets of the Company, the Distributor, or any of their affiliates after the token distribution. For the avoidance of doubt, neither the Company nor the Distributor deals in, or is in the business of buying or selling any virtual asset or digital payment token (including SOMI). Any sale or distribution of tokens would be performed during a restricted initial period solely for the purpose of obtaining project development funds, raising market/brand awareness, as well as community building and social engagement; this is not conducted with any element of repetitiveness or regularity which would constitute a business. To the extent a secondary market or exchange for trading SOMI does develop, it would be run and operated wholly independently of the Company, the Distributor, the distribution of SOMI and Somnia. Neither the Company nor the Distributor will create such secondary markets nor will either entity act as an exchange for SOMI. **Informational purposes only:** The information set out herein is only conceptual, and describes the future development goals for Somnia to be developed. In particular, the project roadmap in the Token Documentation is being shared in order to outline some of the plans of the Somnia team, and is provided solely for INFORMATIONAL PURPOSES and does not constitute any binding commitment. Please do not rely on this information in deciding whether to participate in the token distribution because ultimately, the development, release, and timing of any products, features or functionality remains at the sole discretion of the Company, the Distributor or their respective affiliates, and is subject to change. Further, the Token Documentation or the Website may be amended or replaced from time to time. There are no obligations to update the Token Documentation or the Website, or to provide recipients with access to any information beyond what is provided herein. **Regulatory approval:** No regulatory authority has examined or approved, whether formally or informally, any of the information set out in the Token Documentation or the Website. No such action or assurance has been or will be taken under the laws, regulatory requirements or rules of any jurisdiction. The publication, distribution or dissemination of the Token Documentation or the Website does not imply that the applicable laws, regulatory requirements or rules have been complied with. **Cautionary Note on forward-looking statements:** All statements contained herein, statements made in press releases or in any place accessible by the public and oral statements that may be made by the Company, the Distributor and/or the Somnia team, may constitute forward-looking statements (including statements regarding the intent, belief or current expectations with respect to market conditions, business strategy and plans, financial condition, specific provisions and risk management practices). You are cautioned not to place undue reliance on these forward-looking statements given that these statements involve known and unknown risks, uncertainties and other factors that may cause the actual future results to be materially different from that described by such forward-looking statements, and no independent third party has reviewed the reasonableness of any such statements or assumptions. These forward-looking statements are applicable only as of the date indicated in the Token Documentation, and the Company, the Distributor as well as the Somnia team expressly disclaim any responsibility (whether express or implied) to release any revisions to these forward-looking statements to reflect events after such date. **References to companies and platforms:** The use of any company and/or platform names or trademarks herein (save for those which relate to the Company, the Distributor or their respective affiliates) does not imply any affiliation with, or endorsement by, any third party. References in the Token Documentation or the Website to specific companies and platforms are for illustrative purposes only. **English language:** The Token Documentation and the Website may be translated into a language other than English for reference purpose only and in the event of conflict or ambiguity between the English language version and translated versions of the Token Documentation or the Website, the English language versions shall prevail. You acknowledge that you have read and understood the English language version of the Token Documentation and the Website. **No Distribution:** No part of the Token Documentation or the Website is to be copied, reproduced, distributed or disseminated in any way without the prior written consent of the Company or the Distributor. By attending any presentation on this Token Documentation or by accepting any hard or soft copy of the Token Documentation, you agree to be bound by the foregoing limitations. [PreviousLEGALchevron-left](https://docs.somnia.network/concepts/miscellaneous/legal) [NextAML Compliancechevron-right](https://docs.somnia.network/concepts/miscellaneous/legal/aml-compliance) Last updated 7 months ago --- # Tokens Governance | Concepts | Somnia Docs The Somnia governance is still in it’s early phases and will evolve as the project evolves. This document outlines our current thinking. [hashtag](https://docs.somnia.network/concepts/tokenomics/tokens-governance#somnia-token-governance-overview) Somnia Token Governance Overview --------------------------------------------------------------------------------------------------------------------------------------------------- The Somnia governance system has 5 different groups: * Token house - Token owners that primarily govern allocation of foundation and community tokens * Validator council - Validators in control of hard forks, gas economics and network upgrades. * Developer council - Key developers that oversee the technology roadmap * User assembly - Key users who are used as a check and balance to other groups * Foundation board - Initially in control of the treasury and deployment of new code to the network. Eventually used as a conduit for the other participants and emergency overrides. [hashtag](https://docs.somnia.network/concepts/tokenomics/tokens-governance#token-house-processes) Token house processes ----------------------------------------------------------------------------------------------------------------------------- The token house primarily looks over allocation of the foundation's funds. This is done via proposals. Proposals can be created by any token owner. They are then approved by a majority vote of other token holders. During the bootstrap phase, the token house will be able to propose new allocation of foundation funds. This could include: * Allocation of grants to new project * Allocation of community tokens to new incentive program * Allocation of budget for new marketing program for the year During the Transition phase, token holders will be able to vote on proposals. The foundation board will still have the ultimate decision on allocating funds. During the mature phase the token holders will have the ability to propose and approve allocation of funds. This can be vetoed by the user assembly or foundation board in extreme cases. In general, the board should enact the token holders approved proposals. [hashtag](https://docs.somnia.network/concepts/tokenomics/tokens-governance#progressive-decentralisation-roadmap) Progressive decentralisation roadmap ----------------------------------------------------------------------------------------------------------------------------------------------------------- Phase Duration Overview Bootstrap 0–6 months post‑mainnet Foundation board in control. Other groups formed without any control. Transition 6–24 months Introduction of all governance groups. Beginning of proposal process. Ultimate control still with foundation. Mature Year 2 onward Control is delegated to relevant groups for different decision making. Foundation does have capability for emergency overrides in extreme cases (eg emergency code roll out) [PreviousAllocation and unlockschevron-left](https://docs.somnia.network/concepts/tokenomics/allocation-and-unlocks) [NextConclusionchevron-right](https://docs.somnia.network/concepts/conclusion) Last updated 7 months ago * [Somnia Token Governance Overview](https://docs.somnia.network/concepts/tokenomics/tokens-governance#somnia-token-governance-overview) * [Token house processes](https://docs.somnia.network/concepts/tokenomics/tokens-governance#token-house-processes) * [Progressive decentralisation roadmap](https://docs.somnia.network/concepts/tokenomics/tokens-governance#progressive-decentralisation-roadmap) --- # Gas Fees | Concepts | Somnia Docs Somnia's gas pricing model is designed to balance affordability, predictability, security and sustainability, ensuring an optimal experience for users and developers. Gas fees help prevent network congestion and malicious attacks, compensate validators, efficiently allocate resources and incentivise developers to build within the ecosystem. Users pay fees in SOMI tokens, aligning with network economics and long-term viability. Somnia's model was built based on Ethereum. Most Operation Codes remain the same, however, some required adjustment for two key reasons. First, storage-related opcodes needed to be right-sized since Somnia’s unit gas fees are significantly lower than Ethereum’s, preventing disproportionately cheap storage costs. Second, certain opcodes had gas prices misaligned with the actual compute resources required to execute them, requiring recalibration to maintain a balanced and secure execution environment. [hashtag](https://docs.somnia.network/concepts/tokenomics/gas-fees#pricing-model) **Pricing Model** -------------------------------------------------------------------------------------------------------- Somnia’s model follows Ethereum’s structure: Total Fee = Gas Usage x Gas Unit Price Gas Usage Total gas units consumed by a transaction, determined by the following formula: Gas Usage = Base Gas + Transaction Data + Opcodes 1. Base Gas: All EOA transactions will be subject to a base gas fee of 21,000. This is denoted as a Gtransaction and is, in practice, the cheapest possible transaction cost on the Somnia Network. 2. Transaction Data: Any additional data that is included in a transaction apart from the recipient address, value to be sent and gas parameters is included in this field. Similar to ETH, data is stored in blocks of 32 bytes. Zero-byte and non-zero-byte data costs 160 gas. 3. Opcodes: Operations that can be executed on Smart Contracts. The full Opcodes table can be found [herearrow-up-right](https://docs.google.com/spreadsheets/d/1D0AfQD5zxmDtrWshnHOEdRwVpjR-09tGToReYXOLV3Q/edit?usp=sharing) . 4. Gas Unit Price: The price per gas unit, dynamically adjusted based on account consumption. 1. Somnia applies a dynamic discount curve for applications that achieve higher transaction volumes. As transaction throughput increases, the effective cost per gas unit decreases, incentivizing developers to scale their applications within the ecosystem. ### [hashtag](https://docs.somnia.network/concepts/tokenomics/gas-fees#dynamic-pricing-and-discounts) **Dynamic Pricing & Discounts** circle-info This feature of the gas model will be rolled out later this year Somnia’s compute gas pricing scales with transaction volume: * Base Price: $0.00000000616 per gas unit. * Min gas price: $0.000000000616 per gas unit (at 400 TPS). * Volume Discounts: Gas unit price decreases up to 90% for high-TPS applications. * Step function, decreasing at 0.1, 1, 10, 100 and 400 TPS * Sustained low price from 400 TPS * Measurement timeframe: average TPS in 1 hour. ![](https://docs.somnia.network/~gitbook/image?url=https%3A%2F%2Flh7-rt.googleusercontent.com%2Fdocsz%2FAD_4nXdOxpw44V-VjgJZw6SVjpy9qhRfXNGJ5mrJxgBdqUGllHbcbFYYlEvx0epGEF9I2LRrJU-Knd_cRObhvhYndD2QG8ExF2Ba9jCkXDvQxMg-363zLdsT21ELnctmgVyYedXjB0pV3w%3Fkey%3DowVrc_cdirLGFLQJaBvCiw&width=768&dpr=3&quality=100&sign=37c97dc6&sv=2) P(T) = price per transaction after discount P0 = initial (base) price per transaction T = account cumulative transactions in 1 hour ### [hashtag](https://docs.somnia.network/concepts/tokenomics/gas-fees#storage-fees-single-store-32-bytes) **Storage Fees (Single STORE - 32 Bytes)** circle-info This feature of the gas model will be rolled out later this year Somnia introduces the concept of transient state. This means that a developer can choose not to store data permanently on Somnia. This was explicitly introduced for entertainment products which have a lot of transient states, for instance, the position of a player in a video game. * Transient & Permanent Storage: Users choose between temporary and permanent storage. * Tiered Pricing: Costs scale with duration (e.g., 90% discount for 1-hour storage, no discount for indefinite storage). Create SSTORE (32 bytes) Gas Amount 1 hour 20,000 1 day 40,000 1 month 60,000 1 year 80,000 Indefinite 200,000 TPS Level Gas Price ($) Cost Min Ops (21k) Discount vs Base (%) 0.0 $5.49E-09 $0.00012 0% 0.1 $4.39E-09 $0.00009 20% 1.0 $3.29E-09 $0.00007 40% 10.0 $2.20E-09 $0.00005 60% 100.0 $1.10E-09 $0.00002 80% 400.0 $5.49E-10 $0.00001 90% ### [hashtag](https://docs.somnia.network/concepts/tokenomics/gas-fees#price-increase-function) **Price Increase Function** To ensure smooth network operation, validators can adjust the base fee price through voting, based on block execution time. This dynamically links gas prices to real network utilisation, increasing the base fee as the network gets congested. If a block takes longer than 95ms to execute, validators can vote to double the base fee. If execution is faster than 95ms, they can vote to halve it. However, the base fee cannot fall below the minimum of 21,000 gas. Voting cycles occur every second (10 blocks). Here are the key implementation details: * Price Adjustment Threshold: 95ms. * Price Increase / Decrease: 2x increase, 50% decrease * Minimum Base Fee: 21k * Voting Cycle: 1 sec / 10 blocks ### [hashtag](https://docs.somnia.network/concepts/tokenomics/gas-fees#gas-fee-distribution) **Gas fee distribution** Gas fees spent to use the network are partially distributed to the validators and partially burnt. 50% of all fees are distributed to all validators. This is distributed based on the amount of tokens a validator has staked. It is distributed if the validator was in the working set for the whole epoch. 50% of all fees are burnt. This means the Somnia token is deflationary, as the network is used more, the total supply will decrease. ### [hashtag](https://docs.somnia.network/concepts/tokenomics/gas-fees#additional-features) **Additional Features** * Tipping: Not included initially due to high network efficiency; may be introduced later. [PreviousToken Staking and Delegationchevron-left](https://docs.somnia.network/concepts/tokenomics/token-staking-and-delegation) [NextAllocation and unlockschevron-right](https://docs.somnia.network/concepts/tokenomics/allocation-and-unlocks) Last updated 6 months ago * [Pricing Model](https://docs.somnia.network/concepts/tokenomics/gas-fees#pricing-model) * [Dynamic Pricing & Discounts](https://docs.somnia.network/concepts/tokenomics/gas-fees#dynamic-pricing-and-discounts) * [Storage Fees (Single STORE - 32 Bytes)](https://docs.somnia.network/concepts/tokenomics/gas-fees#storage-fees-single-store-32-bytes) * [Price Increase Function](https://docs.somnia.network/concepts/tokenomics/gas-fees#price-increase-function) * [Gas fee distribution](https://docs.somnia.network/concepts/tokenomics/gas-fees#gas-fee-distribution) * [Additional Features](https://docs.somnia.network/concepts/tokenomics/gas-fees#additional-features) --- # Connect Your Wallet To Mainnet | Somnia Docs circle-check Somnia Mainnet is LIVE. Visit the [Network Information Page](https://docs.somnia.network/developer/network-info#network-details) for Network details. 1. You can create your [MetaMask Wallet arrow-up-right](https://support.metamask.io/getting-started/getting-started-with-metamask/) from here. If you already have a wallet you may skip this step circle-check **Developers who are deploying Smart Contracts and need Somnia Test Tokens, STT**. Please join the [Discordarrow-up-right](https://discord.com/invite/somnia) . Go to the `#dev-chat` channel, tag the Somnia DevRel, `@emma_odia`and request Test Tokens. You can also use the Faucet: [https://testnet.somnia.network/arrow-up-right](https://testnet.somnia.network/) You can also email `[[email protected]](https://docs.somnia.network/cdn-cgi/l/email-protection) ` with a brief description of what you are building and your GitHub profile. 1. Visit [Chainlistarrow-up-right](https://chainlist.org/?search=somnia) and Click on "Connect Wallet": ![](https://docs.somnia.network/~gitbook/image?url=https%3A%2F%2F2122549367-files.gitbook.io%2F%7E%2Ffiles%2Fv0%2Fb%2Fgitbook-x-prod.appspot.com%2Fo%2Fspaces%252FkYErT9t3BJtpPfejLO6I%252Fuploads%252FF3N2EGjehNedhEier24O%252Fchainlist-somnia-1.png%3Falt%3Dmedia%26token%3D0b785fcf-5ce0-436f-8725-52e1a72c5d42&width=768&dpr=3&quality=100&sign=ddbd02&sv=2) 1. First, Connect your Wallet. You may use any desired wallet or Metamask (preferred) ![](https://docs.somnia.network/~gitbook/image?url=https%3A%2F%2F2122549367-files.gitbook.io%2F%7E%2Ffiles%2Fv0%2Fb%2Fgitbook-x-prod.appspot.com%2Fo%2Fspaces%252FkYErT9t3BJtpPfejLO6I%252Fuploads%252FPyMkUXPn4SZv1SWBoMe8%252FScreenshot%25202024-11-14%2520at%25204.45.00%25E2%2580%25AFPM.png%3Falt%3Dmedia%26token%3D90bda0aa-2e05-4e57-926d-0ffa5bd53fc1&width=768&dpr=3&quality=100&sign=f4b2ec75&sv=2) 1. Click on "Add to Metamask" to add Somnia Mainnet to your wallet ![](https://docs.somnia.network/~gitbook/image?url=https%3A%2F%2F2122549367-files.gitbook.io%2F%7E%2Ffiles%2Fv0%2Fb%2Fgitbook-x-prod.appspot.com%2Fo%2Fspaces%252FkYErT9t3BJtpPfejLO6I%252Fuploads%252FvcP2kzMmvhmRucbeJl68%252Fchainlist-somnia-2.png%3Falt%3Dmedia%26token%3D33397d1e-2f33-4196-a655-a259fd9f19c5&width=768&dpr=3&quality=100&sign=1364100e&sv=2) [hashtag](https://docs.somnia.network/get-started/connect-your-wallet-to-mainnet#testnet) Testnet ------------------------------------------------------------------------------------------------------ ![](https://docs.somnia.network/~gitbook/image?url=https%3A%2F%2F2122549367-files.gitbook.io%2F%7E%2Ffiles%2Fv0%2Fb%2Fgitbook-x-prod.appspot.com%2Fo%2Fspaces%252FkYErT9t3BJtpPfejLO6I%252Fuploads%252F8BjGkWmQ42QTQtHve8mq%252Fimage.png%3Falt%3Dmedia%26token%3De59b8cde-90eb-47cd-9745-e50ad6ac77f8&width=768&dpr=3&quality=100&sign=5e5c6f34&sv=2) [PreviousGetting Started for Mainnetchevron-left](https://docs.somnia.network/get-started/getting-started-for-mainnet) [NextBridging Infochevron-right](https://docs.somnia.network/get-started/bridging-info) Last updated 1 month ago --- # Governance | Concepts | Somnia Docs **Corporate Framework:** Somnia operates through a Cayman Islands Foundation structure with British Virgin Islands subsidiary entities. This structure ensures regulatory compliance and operational efficiency across jurisdictions. **Board of Directors:** Four-member Board of Directors provides strategic oversight and holds primary decision-making authority over key business and financial matters. The Board meets quarterly to ensure effective governance and strategic direction. **Supervision:** A Cayman-based legal Supervisor provides independent oversight to the Board of Directors, ensuring governance best practices and regulatory compliance. **Corporate Administration:** Professional corporate secretarial services maintain all corporate records and facilitate Board meetings in accordance with regulatory requirements. **Compliance:** All entities within our structure maintain comprehensive AML/CFT compliance programs, ensuring adherence to applicable international standards and regulatory frameworks across all jurisdictions of operation. [PreviousAirdrop Policychevron-left](https://docs.somnia.network/concepts/miscellaneous/legal/airdrop-policy) [NextMiCAR Whitepaperchevron-right](https://docs.somnia.network/concepts/miscellaneous/legal/micar-whitepaper) Last updated 7 months ago --- # Testnet STT Tokens | Somnia Docs circle-exclamation **Somnia Testnet is meant for developers to build and deploy the first versions of their applications, and there may be bugs.** If you have a question or face an issues, please: * Share your feedback at `[[email protected]](https://docs.somnia.network/cdn-cgi/l/email-protection) ` * Talk to our team in our [Discordarrow-up-right](https://discord.com/invite/Somnia) 1. To Proceed with this step, you should [connect your wallet](https://docs.somnia.network/get-started/connect-your-wallet-to-mainnet) with Somnia Testnet 2. To Request for Somnia Testnet Token, click on the **Get STT** button circle-check **Developers who are deploying Smart Contracts and need Somnia Test Tokens, STT**. Please join the [Discordarrow-up-right](https://discord.com/invite/somnia) . Go to the `#dev-chat` channel, tag the Somnia DevRel, `@emma_odia`and request Test Tokens. You can also use the Faucet: [https://testnet.somnia.network/arrow-up-right](https://testnet.somnia.network/) You can also email `[[email protected]](https://docs.somnia.network/cdn-cgi/l/email-protection) ` with a brief description of what you are building and your GitHub profile. ![](https://docs.somnia.network/~gitbook/image?url=https%3A%2F%2F2122549367-files.gitbook.io%2F%7E%2Ffiles%2Fv0%2Fb%2Fgitbook-x-prod.appspot.com%2Fo%2Fspaces%252FkYErT9t3BJtpPfejLO6I%252Fuploads%252FXYHQLVkyeWEnCtlMUqAA%252Fimage.png%3Falt%3Dmedia%26token%3De4ab30b7-a9aa-412a-a482-88727231517e&width=768&dpr=3&quality=100&sign=8258d775&sv=2) 1. Click on "Get STT" ![](https://docs.somnia.network/~gitbook/image?url=https%3A%2F%2F2122549367-files.gitbook.io%2F%7E%2Ffiles%2Fv0%2Fb%2Fgitbook-x-prod.appspot.com%2Fo%2Fspaces%252FkYErT9t3BJtpPfejLO6I%252Fuploads%252FAVG5oyiaLQ9OorVwQOHN%252FScreenshot%25202024-11-14%2520at%25204.45.23%25E2%2580%25AFPM.png%3Falt%3Dmedia%26token%3Dfcfad990-dbd7-4443-a1dc-36a9d97ba588&width=768&dpr=3&quality=100&sign=e6a890ed&sv=2) 1. You should have it in your wallet ![](https://docs.somnia.network/~gitbook/image?url=https%3A%2F%2F2122549367-files.gitbook.io%2F%7E%2Ffiles%2Fv0%2Fb%2Fgitbook-x-prod.appspot.com%2Fo%2Fspaces%252FkYErT9t3BJtpPfejLO6I%252Fuploads%252FzlMb82sldagQhko88hiJ%252FScreenshot%25202024-11-14%2520at%25204.45.43%25E2%2580%25AFPM.png%3Falt%3Dmedia%26token%3Ddcbebf4b-f8d7-4361-99a3-b6e305c35e25&width=768&dpr=3&quality=100&sign=ca9d67b4&sv=2) 1. You can Select Somnia Testnet and See your tokens ![](https://docs.somnia.network/~gitbook/image?url=https%3A%2F%2F2122549367-files.gitbook.io%2F%7E%2Ffiles%2Fv0%2Fb%2Fgitbook-x-prod.appspot.com%2Fo%2Fspaces%252FkYErT9t3BJtpPfejLO6I%252Fuploads%252FuVwsmj0x9gUVjULJE7fJ%252Fimage.png%3Falt%3Dmedia%26token%3D55324afc-b10b-4c9d-8f0f-dc6facc204a2&width=768&dpr=3&quality=100&sign=3af167fc&sv=2) ### [hashtag](https://docs.somnia.network/get-started/testnet-stt-tokens#try-sending-tokens) Try Sending Tokens 1. Click on Send Tokens ![](https://docs.somnia.network/~gitbook/image?url=https%3A%2F%2F2122549367-files.gitbook.io%2F%7E%2Ffiles%2Fv0%2Fb%2Fgitbook-x-prod.appspot.com%2Fo%2Fspaces%252FkYErT9t3BJtpPfejLO6I%252Fuploads%252FepBeoDzBEm0RJCT1ioNS%252Fimage.png%3Falt%3Dmedia%26token%3Df4cc21fc-2b51-4700-95d6-7e3a0a353232&width=768&dpr=3&quality=100&sign=72cecdbf&sv=2) 1. Send STT tokens to your Desired Wallet address or a random address ![](https://docs.somnia.network/~gitbook/image?url=https%3A%2F%2F2122549367-files.gitbook.io%2F%7E%2Ffiles%2Fv0%2Fb%2Fgitbook-x-prod.appspot.com%2Fo%2Fspaces%252FkYErT9t3BJtpPfejLO6I%252Fuploads%252FX97kRVERH6igvY3ABtj3%252Fimage.png%3Falt%3Dmedia%26token%3Dad69c15e-4b2d-4e9b-9943-606ddffb4e53&width=768&dpr=3&quality=100&sign=3bbbe77f&sv=2) 1. Check our Activity Section in Metamask [PreviousBridging Infochevron-left](https://docs.somnia.network/get-started/bridging-info) [NextNetwork Infochevron-right](https://docs.somnia.network/developer/network-info) Last updated 1 month ago --- # Getting Started for Mainnet | Somnia Docs [hashtag](https://docs.somnia.network/get-started/getting-started-for-mainnet#get-somi-tokens) Get SOMI Tokens ------------------------------------------------------------------------------------------------------------------- Developers and Non-Developers can purchase SOMI Tokens for interacting on Mainnet from the list of exchanges below: CEX DEX [Binancearrow-up-right](https://www.binance.com/en/trade/SOMI_USDT) Quickswap [Gate.ioarrow-up-right](https://www.gate.com/price/somnia-somi) [Bitgetarrow-up-right](https://www.bitgetapp.com/price/somnia) [hashtag](https://docs.somnia.network/get-started/getting-started-for-mainnet#bridge-to-somnia) Bridge to Somnia --------------------------------------------------------------------------------------------------------------------- Developers and Non-Developers can also Bridge their Stablecoins from other Networks to Somnia using LayerZero's STARGATE: [https://stargate.finance/bridgearrow-up-right](https://stargate.finance/bridge) Welcome to Somnia Mainnet. Below is a checklist for you to confirm the migration from Testnet to Mainnet. [hashtag](https://docs.somnia.network/get-started/getting-started-for-mainnet#for-non-developers) For Non-Developers ------------------------------------------------------------------------------------------------------------------------- * check Ensure that you have added the Somnia Mainnet Network to your Wallet. You can use [ChainListarrow-up-right](https://chainlist.org/?search=somnia) . * check Get SOMI Tokens from a list of Exchanges. [hashtag](https://docs.somnia.network/get-started/getting-started-for-mainnet#for-developers) For Developers ----------------------------------------------------------------------------------------------------------------- Conduct the same checks as non-developers, and in addition, the following. * check Add Somnia to the list of Networks in your configuration files for your Smart Contracts * check Using Hardhat: * check Using Forge Deployment: * check See the Page for the list of infrastructure and Dev Tooling available to you on Somnia. [PreviousIntroductionchevron-left](https://docs.somnia.network/) [NextConnect Your Wallet To Mainnetchevron-right](https://docs.somnia.network/get-started/connect-your-wallet-to-mainnet) Last updated 1 month ago * [Get SOMI Tokens](https://docs.somnia.network/get-started/getting-started-for-mainnet#get-somi-tokens) * [Bridge to Somnia](https://docs.somnia.network/get-started/getting-started-for-mainnet#bridge-to-somnia) * [For Non-Developers](https://docs.somnia.network/get-started/getting-started-for-mainnet#for-non-developers) * [For Developers](https://docs.somnia.network/get-started/getting-started-for-mainnet#for-developers) Copy module.exports = { // ... networks: { somnia: { url: "https://api.infra.mainnet.somnia.network", accounts: ["0xPRIVATE_KEY"], // put dev menomonic or PK here, }, }, // ... }; Copy forge create --rpc-url https://api.infra.mainnet.somnia.network --private-key PRIVATE_KEY src/Example.sol:Example --- # Audits | Concepts | Somnia Docs Smart Contract Report file-pdf 2MB [Hacken\_Somnia\_\[SCA\] Somnia \_ Somnia-Contracts \_ Aug2025\_P-2025-1779\_3\_20250829 10\_27 (1).pdf](https://1819673643-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FhEUf5l6MCgn0iJsSVgmd%2Fuploads%2F3zJWvDihw9doxo45FB9X%2FHacken_Somnia_%5BSCA%5D%20Somnia%20_%20Somnia-Contracts%20_%20Aug2025_P-2025-1779_3_20250829%2010_27%20(1).pdf?alt=media&token=be4ef041-ca9d-4ad7-87b0-dbbbc60f7115) PDF downloadDownload[arrow-up-right-from-squareOpen](https://1819673643-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FhEUf5l6MCgn0iJsSVgmd%2Fuploads%2F3zJWvDihw9doxo45FB9X%2FHacken_Somnia_%5BSCA%5D%20Somnia%20_%20Somnia-Contracts%20_%20Aug2025_P-2025-1779_3_20250829%2010_27%20(1).pdf?alt=media&token=be4ef041-ca9d-4ad7-87b0-dbbbc60f7115) L1 Audit Report file-pdf 2MB [Hacken\_Somnia\_\[L1\] Somnia \_ Somnia Network \_ May2025\_P-2025-1650\_3\_20250704 13\_58 (3).pdf](https://1819673643-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FhEUf5l6MCgn0iJsSVgmd%2Fuploads%2F9Fg9QhXC54Drc9TXK5nc%2FHacken_Somnia_%5BL1%5D%20Somnia%20_%20Somnia%20Network%20_%20May2025_P-2025-1650_3_20250704%2013_58%20(3).pdf?alt=media&token=f743c224-24ff-42a1-9086-90f12a87c018) PDF downloadDownload[arrow-up-right-from-squareOpen](https://1819673643-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FhEUf5l6MCgn0iJsSVgmd%2Fuploads%2F9Fg9QhXC54Drc9TXK5nc%2FHacken_Somnia_%5BL1%5D%20Somnia%20_%20Somnia%20Network%20_%20May2025_P-2025-1650_3_20250704%2013_58%20(3).pdf?alt=media&token=f743c224-24ff-42a1-9086-90f12a87c018) [PreviousMiCAR Whitepaperchevron-left](https://docs.somnia.network/concepts/miscellaneous/legal/micar-whitepaper) Last updated 6 months ago --- # MiCAR Whitepaper | Concepts | Somnia Docs file-pdf 488KB [Somnia\_White Paper.pdf](https://1819673643-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FhEUf5l6MCgn0iJsSVgmd%2Fuploads%2FLE7UxEOlt7nyVSWk0ikg%2FSomnia_White%20Paper.pdf?alt=media&token=3cdcb956-6f9b-43ed-8b43-60bdc9e4b2ba) PDF downloadDownload[arrow-up-right-from-squareOpen](https://1819673643-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FhEUf5l6MCgn0iJsSVgmd%2Fuploads%2FLE7UxEOlt7nyVSWk0ikg%2FSomnia_White%20Paper.pdf?alt=media&token=3cdcb956-6f9b-43ed-8b43-60bdc9e4b2ba) [PreviousGovernancechevron-left](https://docs.somnia.network/concepts/miscellaneous/legal/governance) [NextAuditschevron-right](https://docs.somnia.network/concepts/miscellaneous/audits) Last updated 6 months ago --- # SOMI coin | Somnia Docs SOMI is the native coin of the Somnia Network. It is a currency used to pay for transactions, similar to Ether (ETH) on Ethereum and other EVM networks. SOMI is denominated in Wei, the base unit and smallest value that can be expressed in the network. Below is a table showing different denomination and comparison to falimiar terms: Unit Wei Value Exp Ethereum synonym Somi (SOMI) 1,000,000,000,000,000,000 1e18 Ether (ETH) milliSomi 1,000,000,000,000,000, 1e15 microSomi 1,000,000,000,000 1e12 nanoSomi 1,000,000,000 1e9 gWei Wei 1 1 Wei [PreviousNetwork Overview (Mainnet / Testnet)chevron-left](https://docs.somnia.network/developer/network-info/network-overview-mainnet-testnet) [NextSomnia Reactivitychevron-right](https://docs.somnia.network/developer/reactivity) Last updated 27 days ago --- # Network Overview (Mainnet / Testnet) | Somnia Docs Somnia provides two distinct environments for developers and users: **Mainnet** and **Testnet (Shannon)**. Both serve different purposes in the ecosystem, and knowing when to use which is essential for building and deploying applications effectively. * * * ### [hashtag](https://docs.somnia.network/developer/network-info/network-overview-mainnet-testnet#somnia-mainnet) Somnia Mainnet The **Mainnet** is the official production blockchain of Somnia. All transactions on this chain are **final and irreversible** and require **SOMI tokens** as gas. #### [hashtag](https://docs.somnia.network/developer/network-info/network-overview-mainnet-testnet#key-characteristics) Key Characteristics * Real-value environment secured by Somnia’s validator set. * Integrated with wallets, explorers, bridges, and infrastructure providers. * Permanent and immutable transaction history. * Designed for live dApps, end-users, and production-ready deployments. #### [hashtag](https://docs.somnia.network/developer/network-info/network-overview-mainnet-testnet#when-to-use-mainnet) When to Use Mainnet * Deploying **audited and tested smart contracts**. * Running dApps with **real users and assets**. * Managing liquidity, staking, governance, or NFT projects. * Partner integrations requiring **security and finality**. #### [hashtag](https://docs.somnia.network/developer/network-info/network-overview-mainnet-testnet#example) Example Deploy to Somnia Mainnet Copy # Deploying a contract to Somnia Mainnet npx hardhat run scripts/deploy.js --network somnia_mainnet * * * ### [hashtag](https://docs.somnia.network/developer/network-info/network-overview-mainnet-testnet#somnia-testnet-shannon) Somnia Testnet (Shannon) The **Testnet** is a sandbox environment that mirrors mainnet behavior but uses **STT test tokens** with no real-world value. It allows safe experimentation and rapid iteration without financial risk. #### [hashtag](https://docs.somnia.network/developer/network-info/network-overview-mainnet-testnet#key-characteristics-1) Key Characteristics * Transactions use **STT tokens**, available via the faucet. * Close-to-mainnet parameters for realistic testing. * Safe for prototyping, debugging, and QA. * Commonly used in workshops, hackathons, and developer onboarding. #### [hashtag](https://docs.somnia.network/developer/network-info/network-overview-mainnet-testnet#when-to-use-testnet) When to Use Testnet * Learning how to connect and deploy on Somnia. * Prototyping features or building MVPs. * Debugging smart contracts or dApp flows. * Preparing for audits and production deployment. #### [hashtag](https://docs.somnia.network/developer/network-info/network-overview-mainnet-testnet#example-1) Example * * * ### [hashtag](https://docs.somnia.network/developer/network-info/network-overview-mainnet-testnet#quick-comparison) Quick Comparison Feature Mainnet (Production) Testnet (Shannon) Currency SOMI (real value) STT (valueless, faucet) Purpose Production deployments Development & testing Transactions Permanent and irreversible Experimental and disposable Typical Use Live dApps, DeFi, staking, NFT Prototyping, QA, education Risk Financial impact possible No financial risk * * * ### [hashtag](https://docs.somnia.network/developer/network-info/network-overview-mainnet-testnet#best-practices) Best Practices circle-info * Start on Testnet: Validate contracts and flows on Shannon before mainnet. * Audit before launch: Ensure contracts are reviewed and secure. * Use separate configs: Keep `.env` files distinct for testnet and mainnet. * Stay updated: Follow official announcements for upgrades and changes. circle-check Tip: Treat **Testnet** as your safe playground and **Mainnet** as your production stage. Every project should pass through Testnet before moving to Mainnet. [PreviousNetwork Infochevron-left](https://docs.somnia.network/developer/network-info) [NextSOMI coinchevron-right](https://docs.somnia.network/developer/network-info/somi-coin) Last updated 1 month ago * [Somnia Mainnet](https://docs.somnia.network/developer/network-info/network-overview-mainnet-testnet#somnia-mainnet) * [Somnia Testnet (Shannon)](https://docs.somnia.network/developer/network-info/network-overview-mainnet-testnet#somnia-testnet-shannon) * [Quick Comparison](https://docs.somnia.network/developer/network-info/network-overview-mainnet-testnet#quick-comparison) * [Best Practices](https://docs.somnia.network/developer/network-info/network-overview-mainnet-testnet#best-practices) Deploy to Somnia Testnet (Shannon) Copy # Deploying a contract to Somnia Testnet npx hardhat run scripts/deploy.js --network somnia_testnet --- # Network Info | Somnia Docs circle-check **The Somnia Mainnet is LIVE** circle-info **Developers who deploy Smart Contracts on Mainnet require Somnia Tokens, SOMI.** It is a real-world utility token that can be purchased on a list of CEXs and DEXs [here](https://docs.somnia.network/get-started/getting-started-for-mainnet#get-somi-tokens) . circle-check **Developers who are deploying Smart Contracts and need Somnia Test Tokens, STT**. Please join the [Discordarrow-up-right](https://discord.com/invite/somnia) . Go to the `#dev-chat` channel, tag the Somnia DevRel, `@emma_odia`and request Test Tokens. You can also use the Faucet: [https://testnet.somnia.network/arrow-up-right](https://testnet.somnia.network/) You can also email `[[email protected]](https://docs.somnia.network/cdn-cgi/l/email-protection) ` with a brief description of what you are building and your GitHub profile. Network Mainnet Testnet Chain ID 5031 50312 Block Explorer [https://explorer.somnia.networkarrow-up-right](https://explorer.somnia.network/) [https://shannon-explorer.somnia.network/arrow-up-right](https://shannon-explorer.somnia.network/) Symbol SOMI STT RPC [https://api.infra.mainnet.somnia.network/arrow-up-right](https://api.infra.mainnet.somnia.network/) [https://dream-rpc.somnia.network/arrow-up-right](https://vsf-rpc.somnia.network/) MultiCallV3 [0x5e44F178E8cF9B2F5409B6f18ce936aB817C5a11arrow-up-right](https://explorer.somnia.network/address/0x5e44F178E8cF9B2F5409B6f18ce936aB817C5a11) [0x841b8199E6d3Db3C6f264f6C2bd8848b3cA64223arrow-up-right](https://shannon-explorer.somnia.network/address/0x841b8199E6d3Db3C6f264f6C2bd8848b3cA64223) EntryPoint v0.7 [0x0000000071727De22E5E9d8BAf0edAc6f37da032arrow-up-right](https://shannon-explorer.somnia.network/address/0x0000000071727De22E5E9d8BAf0edAc6f37da032) Factory Address [0x4be0ddfebca9a5a4a617dee4dece99e7c862dcebarrow-up-right](https://shannon-explorer.somnia.network/address/0x4bE0ddfebcA9A5A4a617dee4DeCe99E7c862dceb) Alternative Testnet Block Explorer [https://somnia-testnet.socialscan.io/arrow-up-right](https://somnia-testnet.socialscan.io/) Testnet Faucet [https://testnet.somnia.network/arrow-up-right](https://testnet.somnia.network/) Stakely Mainnet Faucet [https://stakely.io/faucet/somnia-somiarrow-up-right](https://stakely.io/faucet/somnia-somi) CreateX 0xD13C575ED5378fd18B100Bd87D5765d9A747358B 0x535822d4b86b2372FBE4fd9d1468318F04A2A640 [hashtag](https://docs.somnia.network/developer/network-info#rpc-providers) RPC Providers ---------------------------------------------------------------------------------------------- RPCs Ankr [https://www.ankr.com/rpc/somniaarrow-up-right](https://www.ankr.com/rpc/somnia/) Public Node [https://somnia.publicnode.comarrow-up-right](https://somnia.publicnode.com/) Stakely [https://somnia-json-rpc.stakely.ioarrow-up-right](https://somnia-json-rpc.stakely.io/) Validation Cloud [https://www.validationcloud.io/somniaarrow-up-right](https://www.validationcloud.io/somnia) [hashtag](https://docs.somnia.network/developer/network-info#faucet-providers) Faucet Providers ---------------------------------------------------------------------------------------------------- Google Cloud Faucet [https://cloud.google.com/application/web3/faucet/somnia/shannonarrow-up-right](https://cloud.google.com/application/web3/faucet/somnia/shannon) Stakely [https://stakely.io/faucet/somnia-testnet-sttarrow-up-right](https://stakely.io/faucet/somnia-testnet-stt) Thirdweb Faucet [https://thirdweb.com/somnia-shannon-testnetarrow-up-right](https://thirdweb.com/somnia-shannon-testnet) #### [hashtag](https://docs.somnia.network/developer/network-info#greater-than-join-the-exclusive-developer-community) \> [Join the Exclusive Developer Communityarrow-up-right](https://somnia.fillout.com/t/vPjjEWFLbQus) #### [hashtag](https://docs.somnia.network/developer/network-info#greater-than-apply-for-the-somnia-network-grants-program) \> [Apply for the Somnia Network Grants Programarrow-up-right](https://somnia.fillout.com/t/5WqTW4iFxCus) [hashtag](https://docs.somnia.network/developer/network-info#community) Community -------------------------------------------------------------------------------------- * [Join us on Discordarrow-up-right](https://discord.com/invite/Somnia) to chat with devs and raise issues * [Follow us on Twitterarrow-up-right](https://twitter.com/Somnia_Network) for updates [hashtag](https://docs.somnia.network/developer/network-info#solidity-resources) Solidity Resources -------------------------------------------------------------------------------------------------------- * [Ethereum Developer arrow-up-right](https://learnweb3.io/degrees/ethereum-developer-degree/) by Learn Web3 * [Solidity by Examplearrow-up-right](https://solidity-by-example.org/) * [CryptoZombiesarrow-up-right](https://cryptozombies.io/en/course) * [Cookbook.devarrow-up-right](https://www.cookbook.dev/search?q=cookbook&categories=Contracts&sort=popular&filter=&page=1) * [Alchemy Unversityarrow-up-right](https://www.alchemy.com/university/courses/solidity) [hashtag](https://docs.somnia.network/developer/network-info#toolkit) Toolkit ---------------------------------------------------------------------------------- * [Hardhat Toolkitarrow-up-right](https://hardhat.org/docs) - Solidity development framework paired with a JavaScript testing framework * [Foundry Toolkitarrow-up-right](https://book.getfoundry.sh/) - Solidity framework for both development and testing. * [Remixarrow-up-right](https://remix.ethereum.org/#lang=en&optimize=false&runs=200&evmVersion=null) - Solidity IDE with interactive features [PreviousTestnet STT Tokenschevron-left](https://docs.somnia.network/get-started/testnet-stt-tokens) [NextNetwork Overview (Mainnet / Testnet)chevron-right](https://docs.somnia.network/developer/network-info/network-overview-mainnet-testnet) Last updated 1 month ago * [RPC Providers](https://docs.somnia.network/developer/network-info#rpc-providers) * [Faucet Providers](https://docs.somnia.network/developer/network-info#faucet-providers) * [Community](https://docs.somnia.network/developer/network-info#community) * [Solidity Resources](https://docs.somnia.network/developer/network-info#solidity-resources) * [Toolkit](https://docs.somnia.network/developer/network-info#toolkit) --- # Tooling | Somnia Docs circle-exclamation **Somnia Reactivity is currently only available on TESTNET** Dive into the following sections to get a better understanding of the Somnia Reactivity tooling [Comparisonchevron-right](https://docs.somnia.network/developer/reactivity/tooling/comparison) [Off-Chain (TypeScript)chevron-right](https://docs.somnia.network/developer/reactivity/tooling/off-chain-typescript) [On-chain (Solidity)chevron-right](https://docs.somnia.network/developer/reactivity/tooling/on-chain-solidity) [Subscription managementchevron-right](https://docs.somnia.network/developer/reactivity/tooling/subscription-management) [PreviousNo, this is not like regular EVM event subscriptionschevron-left](https://docs.somnia.network/developer/reactivity/no-this-is-not-like-regular-evm-event-subscriptions) [NextComparisonchevron-right](https://docs.somnia.network/developer/reactivity/tooling/comparison) Last updated 1 month ago --- # Bridging Info | Somnia Docs Somnia supports cross-chain asset transfers through two official bridge partners: Relay and Stargate Finance. These bridges enable you to move tokens between Somnia and other blockchain networks while maintaining security and minimizing fees. Whether you're bridging stablecoins, ETH, or other supported assets, this guide will walk you through the complete process. [hashtag](https://docs.somnia.network/get-started/bridging-info#prerequisites) Prerequisites ------------------------------------------------------------------------------------------------- * **Wallet Setup**: MetaMask, WalletConnect-compatible wallet, or hardware wallet * **Network Configuration**: Somnia network added to your wallet * **Funded Wallet**: Source chain tokens to cover bridge fees and gas costs * **Basic Understanding**: Familiarity with blockchain transactions and gas fees [hashtag](https://docs.somnia.network/get-started/bridging-info#supported-bridges) Supported Bridges --------------------------------------------------------------------------------------------------------- ### [hashtag](https://docs.somnia.network/get-started/bridging-info#relay-bridge) Relay Bridge Relay is a multichain payments network that has served 5M+ users and processed $5B+ in volume across 85+ networks. It offers instant cross-chain transactions with 99.9% uptime and payments-grade reliability. **Key Features**: * Instant Execution: Cross-chain transfers in 1-10 seconds * 75+ Networks: Extensive blockchain support including Somnia * Predictable Fees: Transparent fee structure with no hidden costs * Enterprise Grade: 99.9% uptime with automatic redundancy systems **Supported Assets**: ETH, USDC, USDT, and other major tokens **Website**: [relay.link/bridgearrow-up-right](https://relay.link/bridge) ### [hashtag](https://docs.somnia.network/get-started/bridging-info#stargate-finance) Stargate Finance Stargate is a fully composable cross-chain bridge built on LayerZero that connects 50+ blockchains. It's the first bridge to solve the "bridging trilemma" by providing instant guaranteed finality, native assets, and unified liquidity. **Key Features**: * Native Assets: Transfer native tokens without wrapped intermediates * Instant Finality: Guaranteed transaction completion * Unified Liquidity: Shared liquidity pools across all chains * LayerZero Powered: Built on robust omnichain infrastructure **Supported Assets**: USDC, USDT, ETH, BTC, and LayerZero OFTs **Website**: [stargate.financearrow-up-right](https://stargate.finance/) [hashtag](https://docs.somnia.network/get-started/bridging-info#using-relay-bridge) Using Relay Bridge ----------------------------------------------------------------------------------------------------------- 1 #### [hashtag](https://docs.somnia.network/get-started/bridging-info#access-relay-bridge) Access Relay Bridge Navigate to [relay.link/bridgearrow-up-right](https://relay.link/bridge) in your web browser. 2 #### [hashtag](https://docs.somnia.network/get-started/bridging-info#connect-your-wallet) Connect Your Wallet Click "Connect Wallet" and select your preferred wallet provider. Approve the connection when prompted. 3 #### [hashtag](https://docs.somnia.network/get-started/bridging-info#configure-bridge-transaction) Configure Bridge Transaction * Source Chain: Select the blockchain you're bridging FROM * Destination Chain: Select Somnia (or your target chain) * Token: Choose the asset you want to bridge * Amount: Enter the amount to transfer\\ ![](https://docs.somnia.network/~gitbook/image?url=https%3A%2F%2F427512505-files.gitbook.io%2F%7E%2Ffiles%2Fv0%2Fb%2Fgitbook-x-prod.appspot.com%2Fo%2Fspaces%252F44oX5XEgjsIITRWz2fbP%252Fuploads%252FyIjtTD8dvQhEhYEbFPqp%252Fimage.png%3Falt%3Dmedia%26token%3Df2c31757-9ecf-459f-a371-6b063fbbc3b3&width=768&dpr=3&quality=100&sign=29620b1&sv=2) 4 #### [hashtag](https://docs.somnia.network/get-started/bridging-info#review-transaction-details) Review Transaction Details Carefully review: * Bridge Fee: Relay's service fee * Gas Fee: Network transaction cost * Estimated Time: Usually 1-10 seconds * Recipient Address: Verify destination address(Your address or if you want to bridge to a different wallet. ![](https://docs.somnia.network/~gitbook/image?url=https%3A%2F%2F427512505-files.gitbook.io%2F%7E%2Ffiles%2Fv0%2Fb%2Fgitbook-x-prod.appspot.com%2Fo%2Fspaces%252F44oX5XEgjsIITRWz2fbP%252Fuploads%252FEfq6xft1t9ubbmoPPLuJ%252Fimage.png%3Falt%3Dmedia%26token%3Daa572d53-89ff-4d29-8343-0f316d5d1527&width=768&dpr=3&quality=100&sign=af01c547&sv=2) 5 #### [hashtag](https://docs.somnia.network/get-started/bridging-info#execute-bridge-transaction) Execute Bridge Transaction Click "Bridge" and confirm the transaction in your wallet. The process typically involves: * Approval Transaction: Authorize Relay to spend your tokens (if required) * Bridge Transaction: Execute the cross-chain transfer * Confirmation: Receive tokens on destination chain 6 #### [hashtag](https://docs.somnia.network/get-started/bridging-info#verify-completion) Verify Completion Check your wallet balance on the destination chain. Relay provides real-time status updates during the bridging process. [hashtag](https://docs.somnia.network/get-started/bridging-info#using-stargate-finance) Using Stargate Finance ------------------------------------------------------------------------------------------------------------------- 1 #### [hashtag](https://docs.somnia.network/get-started/bridging-info#access-stargate-bridge) Access Stargate Bridge Visit [stargate.financearrow-up-right](https://stargate.finance/) and click "Bridge" or "Transfer". 2 #### [hashtag](https://docs.somnia.network/get-started/bridging-info#connect-wallet-and-select-networks) Connect Wallet & Select Networks Connect your wallet and configure: * From: Source blockchain * To: Somnia (or destination chain) * Asset: Select supported token (USDC, USDT, ETH, etc.) ![](https://docs.somnia.network/~gitbook/image?url=https%3A%2F%2F427512505-files.gitbook.io%2F%7E%2Ffiles%2Fv0%2Fb%2Fgitbook-x-prod.appspot.com%2Fo%2Fspaces%252F44oX5XEgjsIITRWz2fbP%252Fuploads%252FKvmRGu0fGzpMlWzyPlGN%252Fimage.png%3Falt%3Dmedia%26token%3D66a534cd-1453-4b19-ac43-5076c86b1ec9&width=300&dpr=3&quality=100&sign=a3d31089&sv=2) 3 #### [hashtag](https://docs.somnia.network/get-started/bridging-info#enter-transfer-details) Enter Transfer Details * Amount: Specify transfer amount * Recipient: Destination wallet address (defaults to your connected wallet) Only applicable if you are bridging to another wallet address.\\ ![](https://docs.somnia.network/~gitbook/image?url=https%3A%2F%2F427512505-files.gitbook.io%2F%7E%2Ffiles%2Fv0%2Fb%2Fgitbook-x-prod.appspot.com%2Fo%2Fspaces%252F44oX5XEgjsIITRWz2fbP%252Fuploads%252FIYzm4EewTNtH3zUaNNOp%252Fimage.png%3Falt%3Dmedia%26token%3D625f507b-5583-4a90-bc24-2382ec10c39e&width=768&dpr=3&quality=100&sign=1bdd2730&sv=2) * Slippage: Set acceptable slippage tolerance (usually 0.1-0.5%) 4 #### [hashtag](https://docs.somnia.network/get-started/bridging-info#review-pool-information) Review Pool Information Stargate displays: * Available Liquidity: Pool depth on destination chain * Bridge Fee: Protocol fee structure * Gas Estimate: Transaction costs * Route: Cross-chain path details 5 #### [hashtag](https://docs.somnia.network/get-started/bridging-info#execute-transfer) Execute Transfer Confirm transaction details and sign with your wallet. 3 Stargate's ΔBridge technology ensures native asset delivery without wrapped tokens. 6 #### [hashtag](https://docs.somnia.network/get-started/bridging-info#monitor-transaction) Monitor Transaction Track your transfer through: * Stargate Interface: Real-time status updates * LayerZero Scan: Cross-chain transaction [explorerarrow-up-right](https://layerzeroscan.com/) * Destination Explorer: Confirm arrival on target chain [hashtag](https://docs.somnia.network/get-started/bridging-info#troubleshooting-common-issues) Troubleshooting Common Issues --------------------------------------------------------------------------------------------------------------------------------- #### [hashtag](https://docs.somnia.network/get-started/bridging-info#transaction-stuck-or-pending) Transaction Stuck or Pending **Symptoms**: Bridge transaction shows "pending" for extended period 1 Check Network Status: Verify source and destination chain health 2 Gas Price Issues: Increase gas price if transaction is stuck 3 Bridge Congestion: Wait for network congestion to clear 4 Contact Support: Reach out to bridge support with transaction hash #### [hashtag](https://docs.somnia.network/get-started/bridging-info#insufficient-liquidity) Insufficient Liquidity **Symptoms**: Bridge shows "insufficient liquidity" error 1 Reduce Amount: Try bridging smaller amounts 2 Wait for Rebalancing: Liquidity pools rebalance automatically 3 Alternative Routes: Use different bridge or route 4 Split Transactions: Break large transfers into smaller ones #### [hashtag](https://docs.somnia.network/get-started/bridging-info#wrong-network-or-address) Wrong Network or Address **Symptoms**: Tokens didn't arrive at expected destination 1 Verify Network: Ensure correct destination network selected 2 Check Address: Confirm recipient address accuracy 3 Network Switch: Switch wallet to destination network 4 Recovery Process: Contact bridge support for recovery options #### [hashtag](https://docs.somnia.network/get-started/bridging-info#high-fees-or-slippage) High Fees or Slippage **Symptoms**: Unexpected high costs or poor exchange rates 1 Timing: Bridge during low network congestion periods 2 Route Optimization: Compare different bridge options 3 Amount Adjustment: Larger amounts often have better rates 4 Alternative Bridges: Consider other bridge providers [hashtag](https://docs.somnia.network/get-started/bridging-info#verification-and-testing) Verification And Testing ----------------------------------------------------------------------------------------------------------------------- #### [hashtag](https://docs.somnia.network/get-started/bridging-info#test-bridge-functionality) Test Bridge Functionality 1 Connect wallet to both source and destination networks 2 Bridge small test amount (e.g., $10-50) 3 Verify tokens arrive within expected timeframe 4 Check token balances on both chains 5 Test bridge interface responsiveness #### [hashtag](https://docs.somnia.network/get-started/bridging-info#confirm-successful-bridge) Confirm Successful Bridge 1 Source Chain: Confirm tokens debited from source wallet 2 Bridge Status: Check bridge interface for completion status 3 Destination Chain: Verify tokens credited to destination wallet 4 Balance Check: Ensure correct amounts received You've successfully learned how to bridge assets to and from Somnia using both Relay and Stargate Finance. These official bridge partners provide secure, fast, and reliable cross-chain transfers with different strengths: * **Relay**: Best for fast payments and swaps across 75+ networks * **Stargate**: Ideal for DeFi composability with native asset transfers Both bridges support Somnia's high-performance infrastructure, enabling seamless integration with the broader multi-chain ecosystem. _For technical support, contact the respective bridge providers or Somnia community channels._ [PreviousConnect Your Wallet To Mainnetchevron-left](https://docs.somnia.network/get-started/connect-your-wallet-to-mainnet) [NextTestnet STT Tokenschevron-right](https://docs.somnia.network/get-started/testnet-stt-tokens) Last updated 1 month ago * [Prerequisites](https://docs.somnia.network/get-started/bridging-info#prerequisites) * [Supported Bridges](https://docs.somnia.network/get-started/bridging-info#supported-bridges) * [Relay Bridge](https://docs.somnia.network/get-started/bridging-info#relay-bridge) * [Stargate Finance](https://docs.somnia.network/get-started/bridging-info#stargate-finance) * [Using Relay Bridge](https://docs.somnia.network/get-started/bridging-info#using-relay-bridge) * [Using Stargate Finance](https://docs.somnia.network/get-started/bridging-info#using-stargate-finance) * [Troubleshooting Common Issues](https://docs.somnia.network/get-started/bridging-info#troubleshooting-common-issues) * [Verification And Testing](https://docs.somnia.network/get-started/bridging-info#verification-and-testing) --- # Somnia Reactivity | Somnia Docs circle-exclamation **Reactivity is currently only available on TESTNET** Somnia Reactivity is a native toolkit for building event-driven dApps on the Somnia chain. Events and blockchain state is pushed directly to your TypeScript or Solidity apps in one atomic notification without polling. #### [hashtag](https://docs.somnia.network/developer/reactivity#key-benefits) Key Benefits * **Real-Time Efficiency**: Notifications include events + state from the same block, slashing RPC calls and latency. * **Cross-Environment**: Seamless for off-chain (WebSocket) and on-chain (EVM invocations). * **Scalable Subscriptions**: Customizable for filters, guarantees, and coalescing to fit your app's needs. TL;DR: Build reactive dApps that respond instantly to on-chain activity, reducing complexity and costs vs. traditional EVM setups. [PreviousSOMI coinchevron-left](https://docs.somnia.network/developer/network-info/somi-coin) [NextWhat is Reactivity?chevron-right](https://docs.somnia.network/developer/reactivity/what-is-reactivity) Last updated 1 month ago --- # Subscriptions: The Core Primitive | Somnia Docs Subscriptions are configurable listeners that define what events to watch and how to deliver notifications. They're the foundation of reactivity—create one, and the chain does the rest. #### [hashtag](https://docs.somnia.network/developer/reactivity/subscriptions-the-core-primitive#key-features) Key Features * **Filters**: Wildcard (\*) for all events, or specify emitters, topics. * **On-chain** * **Costs**: Minimum 32 SOM balance to cover handler invocation costs on-chain (validators execute handlers) + small amount of gas (~21K) to create each subscription * **Options**: * isGuaranteed: Eventual delivery with some block inclusion distance (true/false). * isCoalesced: Batch multiple events into one notification within a block. * Handler Gas params: priorityFeePerGas, maxFeePerGas, gasLimit * **Off-chain** * **Costs**: Cost of running the Somnia node or paying an RPC provider [PreviousQuickstartchevron-left](https://docs.somnia.network/developer/reactivity/quickstart) [NextPush vs Pull: An Architectural Shiftchevron-right](https://docs.somnia.network/developer/reactivity/push-vs-pull-an-architectural-shift) Last updated 1 month ago --- # Somnia Data Streams | Somnia Docs Read, write, and react to structured data or events broadcast on-chain. With a sharp focus on reusability and composability, applications can interoperate and coalesce around commonly agreed-upon data structures that are built around the native reactivity offered by the protocol suite. [PreviousAPI Referencechevron-left](https://docs.somnia.network/developer/reactivity/api-reference) [NextWhat is Somnia Data Streams?chevron-right](https://docs.somnia.network/developer/data-streams/what-is-somnia-data-streams) Last updated 1 month ago --- # What is Reactivity? | Somnia Docs Reactivity is Somnia's event-driven paradigm for dApps. It pushes notifications—combining emitted events and related blockchain state—to subscribers in real-time, enabling "reactive" logic without polling. circle-exclamation **Reactivity is currently only available on TESTNET** #### [hashtag](https://docs.somnia.network/developer/reactivity/what-is-reactivity#core-concepts) Core Concepts * **Events**: Triggers from smart contracts (e.g., Transfer, Approval). * **State**: View calls for contract data fetched at the event's block height. * **Push Delivery**: Chain validators / nodes handle notifications, invoking handlers or WebSocket callbacks directly. * **Subscribers**: Off-chain apps (TypeScript) or on-chain contracts (Solidity). This shifts dApps from reactive querying to proactive responses, like a pub/sub system baked into the blockchain. [PreviousSomnia Reactivitychevron-left](https://docs.somnia.network/developer/reactivity) [NextQuickstartchevron-right](https://docs.somnia.network/developer/reactivity/quickstart) Last updated 1 month ago --- # State Consistency Guarantees | Somnia Docs Somnia ensures notifications deliver events and state that are consistent—sourced from the exact same block. This eliminates race conditions common in pull models. #### [hashtag](https://docs.somnia.network/developer/reactivity/state-consistency-guarantees#how-it-works) How It Works * **Atomic Delivery**: Event + state (via ETH calls) processed in one validator-executed bundle. * **Guarantees**: * Non-coalesced: One notification per event. * Coalesced: Batched, but state reflects the latest in the batch. #### [hashtag](https://docs.somnia.network/developer/reactivity/state-consistency-guarantees#example-impact) Example Impact In a DeFi app, a "Transfer" event pushes the new balance immediately—no extra balanceOf call needed. This makes dApps more reliable and easier to reason about. [PreviousSystem Eventschevron-left](https://docs.somnia.network/developer/reactivity/system-events) [NextNo, this is not like regular EVM event subscriptionschevron-right](https://docs.somnia.network/developer/reactivity/no-this-is-not-like-regular-evm-event-subscriptions) Last updated 2 months ago --- # AML Compliance | Concepts | Somnia Docs **Our Commitment:** Somnia ServiceCo. Ltd., Somnia TokenCo. Ltd., and Somnia NetworkCo. Ltd. (collectively, the "Companies") are committed to maintaining the highest standards of compliance with anti-money laundering (AML), counter-terrorism financing (CFT), and sanctions laws. We implement robust policies and procedures to prevent our services from being misused for illicit purposes. **Policy Overview:** As companies incorporated in the British Virgin Islands, we voluntarily adopt comprehensive AML/CFT compliance measures as a matter of best practice, implementing policies based on international standards and regulatory frameworks. * * * ### [hashtag](https://docs.somnia.network/concepts/miscellaneous/legal/aml-compliance#key-principles) KEY PRINCIPLES **Risk-Based Approach:** We employ a comprehensive risk-based approach to identify, assess, and mitigate money laundering, terrorist financing, and proliferation financing risks across our operations, including: * Customer due diligence assessments * Geographic risk evaluation * Product and service risk analysis * Transaction monitoring **Know Your Customer (KYC):** We conduct thorough customer identification and verification procedures for all clients, including: * Identity verification * Beneficial ownership identification * Sanctions screening * Enhanced due diligence for high-risk customers **Ongoing Monitoring:** We maintain continuous monitoring of business relationships and transactions to: * Detect unusual or suspicious activities * Ensure consistency with customer profiles * Update customer information regularly * Screen against sanctions lists ### [hashtag](https://docs.somnia.network/concepts/miscellaneous/legal/aml-compliance#compliance-framework) COMPLIANCE FRAMEWORK **Prohibited Activities:** We do not engage in business relationships or transactions with: * Individuals or entities on sanctions lists * Politically Exposed Persons (PEPs) without enhanced due diligence * Customers from high-risk jurisdictions without appropriate controls * Any party involved in money laundering, terrorist financing, or other illicit activities **Enhanced Due Diligence:** We apply enhanced due diligence measures for: * High-risk jurisdictions (including but not limited to Burma, Iran, North Korea) * Politically Exposed Persons and their associates * Complex corporate structures * Unusual transaction patterns **Record Keeping:** We maintain comprehensive records of: * Customer identification and verification documents * Transaction records * Compliance documentation * Suspicious activity reports All records are retained for a minimum of five years in secure, confidential systems. ### [hashtag](https://docs.somnia.network/concepts/miscellaneous/legal/aml-compliance#reporting) REPORTING **Suspicious Activity:** We have established procedures for identifying and reporting suspicious activities to relevant authorities. All employees are trained to recognize red flags and report concerns through appropriate channels. **Regulatory Cooperation:** We maintain full cooperation with regulatory authorities and law enforcement agencies, responding promptly to information requests and regulatory inquiries. ### [hashtag](https://docs.somnia.network/concepts/miscellaneous/legal/aml-compliance#training-and-awareness) TRAINING AND AWARENESS All relevant personnel receive regular AML/CFT training to ensure: * Understanding of legal obligations * Recognition of suspicious activities * Proper implementation of compliance procedures * Awareness of emerging risks and threats ### [hashtag](https://docs.somnia.network/concepts/miscellaneous/legal/aml-compliance#contact) CONTACT For compliance-related inquiries or to report suspicious activities: **Money Laundering Reporting Officer (MLRO):** Campbells Regulatory Services Limited Email: [\[email protected\]](https://docs.somnia.network/cdn-cgi/l/email-protection) ### [hashtag](https://docs.somnia.network/concepts/miscellaneous/legal/aml-compliance#regulatory-framework) REGULATORY FRAMEWORK Our compliance program is designed to meet the requirements of applicable laws and regulations, including: * BVI Anti-Money Laundering Regulations * BVI Anti-Money Laundering and Terrorist Financing Code of Practice * International sanctions regimes * FATF Recommendations * * * _We regularly review and update our AML/CFT policies and procedures to ensure they remain effective and compliant with evolving regulatory requirements and industry best practices._ _This policy is effective as of the date of publication and is subject to periodic review and updates. For the most current version, please visit this page from time to time._ [PreviousLEGAL DISCLAIMERchevron-left](https://docs.somnia.network/concepts/miscellaneous/legal/legal-disclaimer) [NextAirdrop Policychevron-right](https://docs.somnia.network/concepts/miscellaneous/legal/airdrop-policy) Last updated 7 months ago * [KEY PRINCIPLES](https://docs.somnia.network/concepts/miscellaneous/legal/aml-compliance#key-principles) * [COMPLIANCE FRAMEWORK](https://docs.somnia.network/concepts/miscellaneous/legal/aml-compliance#compliance-framework) * [REPORTING](https://docs.somnia.network/concepts/miscellaneous/legal/aml-compliance#reporting) * [TRAINING AND AWARENESS](https://docs.somnia.network/concepts/miscellaneous/legal/aml-compliance#training-and-awareness) * [CONTACT](https://docs.somnia.network/concepts/miscellaneous/legal/aml-compliance#contact) * [REGULATORY FRAMEWORK](https://docs.somnia.network/concepts/miscellaneous/legal/aml-compliance#regulatory-framework) --- # Push vs Pull: An Architectural Shift | Somnia Docs Traditional EVM dApps "pull" data via polling (e.g., repeated getLogs or state rpc queries), leading to inefficiency and high rpc costs. Somnia Reactivity's "push" model notifies you proactively, transforming app architecture. #### [hashtag](https://docs.somnia.network/developer/reactivity/push-vs-pull-an-architectural-shift#highlights) Highlights Aspect Pull (Traditional) Push (Somnia Reactivity) Data Fetch Poll RPCs periodically Passive notifications Latency Seconds to minutes (poll interval) Near-instant (block time) RPC Calls High (loops, retries) Minimal (one sub setup) Complexity Manage loops, error handling Simple callback/handler Use Cases Basic event listening Real-time reactions, auto-updates #### [hashtag](https://docs.somnia.network/developer/reactivity/push-vs-pull-an-architectural-shift#why-it-matters) Why It Matters * **Simplified Front-Ends**: No more `setInterval` for balances—push updates UIs directly. * **Efficient Indexers**: Push to DBs instead of scanning blocks. * **Cost Savings**: Avoid redundant queries. Let the chain push changes to you and build realtime blockchain applications [PreviousSubscriptions: The Core Primitivechevron-left](https://docs.somnia.network/developer/reactivity/subscriptions-the-core-primitive) [NextSystem Eventschevron-right](https://docs.somnia.network/developer/reactivity/system-events) Last updated 1 month ago --- # Quickstart | Somnia Docs circle-exclamation **Reactivity is currently only available on TESTNET** ### [hashtag](https://docs.somnia.network/developer/reactivity/quickstart#off-chain-typescript) Off-chain (TypeScript) #### [hashtag](https://docs.somnia.network/developer/reactivity/quickstart#sdk-installation) 📦 SDK Installation Copy npm i @somnia-chain/reactivity #### [hashtag](https://docs.somnia.network/developer/reactivity/quickstart#plugging-into-the-sdk) 🔌 Plugging into the SDK You'll need `viem` installed for the public and or wallet client. Install it with `npm i viem`. Copy import { createPublicClient, createWalletClient, http, defineChain } from 'viem' import { SDK } from '@somnia-chain/reactivity' // Example: Public client (required for reading data) const chain = defineChain() // see viem docs for defining a chain const publicClient = createPublicClient({ chain, transport: http(), }) // Optional: Wallet client for writes const walletClient = createWalletClient({ account, chain, transport: http(), }) const sdk = new SDK({ public: publicClient, wallet: walletClient, // Omit if not executing transactions on-chain }) #### [hashtag](https://docs.somnia.network/developer/reactivity/quickstart#activating-websocket-reactivity-subscriptions) 📡 Activating Websocket Reactivity Subscriptions Use WebSocket subscriptions for real-time updates to contract event and state updates atomically. Define params and subscribe — the SDK handles the rest via WebSockets. ### [hashtag](https://docs.somnia.network/developer/reactivity/quickstart#on-chain-solidity-handlers) On-chain (Solidity handlers) Developers can build Solidity smart contracts that get invoked when other contracts emit events—allowing smart contracts to "react" to what's happening on-chain. In order to achieve this, we need two things: 1. A Somnia event handler smart contract (standard Solidity syntax). 2. A valid subscription with funds to pay for Solidity handler invocations. Creators of on-chain subscriptions are required to hold minimum balances (currently 32 SOM) that pay for handler invocations executed by validators. #### [hashtag](https://docs.somnia.network/developer/reactivity/quickstart#creating-the-handler-smart-contract) Creating the Handler Smart Contract Very basic contract with the `@somnia-chain/reactivity-contracts` npm package installed Once the handler is complete, deploy it using Foundry or Hardhat, and note the address — this will be required for creating a subscription. #### [hashtag](https://docs.somnia.network/developer/reactivity/quickstart#setting-up-an-on-chain-subscription-using-the-sdk) Setting Up an On-Chain Subscription (Using the SDK) The following uses the TypeScript SDK to create and pay for a subscription that will invoke a handler contract for events emitted by other smart contracts. Another approach would be for the subscribing smart contract to directly hold the required SOM balance and have the logic for creating the subscription baked into one place, but that may not always be optimal. [PreviousWhat is Reactivity?chevron-left](https://docs.somnia.network/developer/reactivity/what-is-reactivity) [NextSubscriptions: The Core Primitivechevron-right](https://docs.somnia.network/developer/reactivity/subscriptions-the-core-primitive) Last updated 23 hours ago * [Off-chain (TypeScript)](https://docs.somnia.network/developer/reactivity/quickstart#off-chain-typescript) * [On-chain (Solidity handlers)](https://docs.somnia.network/developer/reactivity/quickstart#on-chain-solidity-handlers) Copy import { SDK, SubscriptionInitParams, SubscriptionCallback } from '@somnia-chain/reactivity' const initParams: SubscriptionInitParams = { ethCalls: [], // State to read when events are emitted onData: (data: SubscriptionCallback) => console.log('Received:', data), } const subscription = await sdk.subscribe(initParams) Copy pragma solidity ^0.8.20; import { SomniaEventHandler } from "@somnia-chain/reactivity-contracts/contracts/SomniaEventHandler.sol"; contract ExampleEventHandler is SomniaEventHandler { function _onEvent( address emitter, bytes32[] calldata eventTopics, bytes calldata data ) internal override { // Execute your logic here // Be careful about emitting events to avoid infinite loops } } Copy import { SDK } from '@somnia-chain/reactivity'; import { parseGwei } from 'viem'; // Initialize the SDK const sdk = new SDK({ public: publicClient, wallet: walletClient, }) // Create a Solidity subscription // This is an example of a wildcard subscription to all events // We do not need to supply SOM—the chain ensures min balance await sdk.createSoliditySubscription({ handlerContractAddress: '0x123...', priorityFeePerGas: parseGwei('2'), // 2 gwei — minimum recommended for validators to process maxFeePerGas: parseGwei('10'), // 10 gwei — max you're willing to pay (base + priority) gasLimit: 2_000_000n, // Minimum recommended for state changes, increase for complex logic isGuaranteed: true, isCoalesced: false, }); --- # Airdrop Policy | Concepts | Somnia Docs This Airdrop Policy ("Policy") governs the distribution of SOMI tokens ("Tokens") by Somnia TokenCo Ltd. ("Company," "we," "us," or "our") to eligible participants ("Participants", “you”, “your”) through our token airdrop program ("Airdrop"). By participating in the Airdrop, you agree to be bound by this Policy and our Terms of Service. * * * ### [hashtag](https://docs.somnia.network/concepts/miscellaneous/legal/airdrop-policy#disclaimer-this-policy-governs-your-participation-in-the-airdrop-and-constitutes-a-legally-binding-a) **DISCLAIMER: THIS POLICY GOVERNS YOUR PARTICIPATION IN THE AIRDROP AND CONSTITUTES A LEGALLY BINDING AGREEMENT BETWEEN YOU AND US. PLEASE READ THIS POLICY CAREFULLY. YOUR PARTICIPATION IN THE AIRDROP SHALL CONSTITUTE YOUR ACCEPTANCE TO THIS AIRDROP POLICY IN ITS ENTIRETY.** * * * **By participating in the Airdrop, you acknowledge that:** 1. Somnia assumes no responsibility for your Participation and the consequences thereof; 2. Somnia does not offer any professional advice or recommendations regarding the Airdrop or tokens. The information provided is for informational purpose only, nothing in this Policy shall be construed as being professional advice; 3. This Policy does not establish any fiduciary relationship between Somnia and Participants. Somnia disclaims any fiduciary duty or liability to the maximum extent permitted by law; 4. You are making an independent decision in respect of your participation and your participation is at your own risk; and 5. It is your responsibility to seek professional legal, tax or other advice before participating in the Airdrop. ### [hashtag](https://docs.somnia.network/concepts/miscellaneous/legal/airdrop-policy#eligibility) **ELIGIBILITY** **Who can participate:** 1. Participants must be at least 18 years of age and not a Prohibited Person. A “Prohibited Person” is any person or entity that is (a) the subject of any economic or trade sanctions administered or enforced by any governmental authority, including any person designated on any list of prohibited or restricted parties by any governmental authority, such as the European Union (“EU”) Consolidated List of Persons, the United Kingdom (“UK”) Consolidated List of Financial Sanctions Targets, the United States (“U.S.”) Treasury Department’s list of Specially Designated Nationals, and the U.S. Department of Commerce Denied Persons or Entity Lists; (b) located in, incorporated in, or otherwise organized or established in, or resident of, any country, territory, or jurisdiction that is the subject of comprehensive country-wide or regional economic sanctions or embargoes or has been designated as “terrorist supporting” by the United Nations (“UN”) or any governmental authority of the EU, UK, or the U.S., including the Office of Foreign Assets Control (“OFAC”) of the U.S. Treasury Department or the Office of Financial Sanctions (“OFSI”) of HM Treasury of the UK (each such country, territory, or jurisdiction, a “Prohibited Geo”); (c) owned or controlled by such persons or entities described in (a)-(b); or (d) accessing the Airdrop on behalf of persons or entities described in (a)-(c). You agree not to use virtual private networks or other tools to circumvent these restrictions. Any such deliberate circumvention, or attempted circumvention, of our controls may permanently disqualify you from participation in the airdrop, as determined in our discretion. 2. Participants must hold an EVM-compatible wallet. 3. Participants are required to fulfill all the verification steps provided in this Policy or as may be communicated by us from time to time. 4. This Airdrop is not available to residents of the United Kingdom (UK) or United States of America (USA). Participants must not use virtual private networks or similar technology to bypass geographic restrictions. 5. Participants are responsible for ensuring compliance with the local laws and regulations in their jurisdiction. 6. If you are participating on behalf of an entity, you represent that you have full authority and proper authorization to bind that entity to this Policy. **Account verification requirements:** * Participants must connect their EVM-compatible wallet to our platform to verify eligibility. * The authenticity of Participants shall be verified to ensure that the Participant is not a bot or using an automated account and is a legal person. ### [hashtag](https://docs.somnia.network/concepts/miscellaneous/legal/airdrop-policy#claiming) CLAIMING To be able to claim the Airdrop, you will need to complete the account verification requirements provided above. Failure to complete the authorisation may be considered as forfeiture of Tokens. Participants must claim their tokens within the specified timeframe. Unclaimed tokens after the deadline will be forfeited. The claiming deadline will be announced separately. Somnia reserves the right to determine, at its sole discretion, both your eligibility to receive Airdrop and the specific number of Tokens allocated to each Participant based on their previous contributions. > Somnia is under no obligation to communicate eligibility requirements to participants or potential participants before, during, or after the Airdrop claim period begins. The Participant shall ensure their eligibility to claim in accordance with this Policy. You agree and acknowledge that you may not pursue any legal claims against Somnia or its representatives if any Airdrop Tokens remain unclaimed due to: 1. technical glitches beyond Somnia’s control; 2. issues with smart contract; 3. inability to pay gas fees; 4. wallet incompatibilities; 5. loss of access to a wallet or the key; 6. any other circumstances beyond Somnia’s control. ### [hashtag](https://docs.somnia.network/concepts/miscellaneous/legal/airdrop-policy#technical-requirements) **TECHNICAL REQUIREMENTS** **Wallet and Network Requirements:** For a successful Airdrop claim, the Participant shall abide by the following requirements: 1. The Participant must have an EVM-compatible wallet 2. Somnia chain must be added to the wallet 3. SOMI token contract must be added to the wallet 4. Stable internet connection is required for the claiming process **Gas Fees and Transaction Costs:** * Participants are responsible for all gas fees and transaction costs associated with claiming and managing their tokens; * You must ensure that there is sufficient funds in your wallet to cover any such transaction fees. ### [hashtag](https://docs.somnia.network/concepts/miscellaneous/legal/airdrop-policy#anti-fraud-and-security-measures) ANTI FRAUD AND SECURITY MEASURES **Prohibited Activities and Disqualifications:** Including but not limited to, the following acts are prohibited and may result in disqualification of the Participant from the Airdrop: * Use of bots, automated scripts, or any artificial means to participate; * Creation or use of multiple accounts to increase allocation; * Any form of fraud, manipulation, or abuse of the airdrop system * Violation of our terms of service or community guidelines **Enforcement:** Somnia reserves the sole and absolute right to investigate any Participant or their wallets that appear to be malicious, fraudulent, operated by bots, abusing the system, or is found to be conducting any prohibited activities provided above. Somnia reserves the sole and absolute right to disqualify and/or blacklist any Participant or potential Participant and their wallet it deems ineligible for the Airdrop. Disqualification decisions shall be final and not subject to appeal. ### [hashtag](https://docs.somnia.network/concepts/miscellaneous/legal/airdrop-policy#other-terms-and-conditions) **OTHER TERMS AND CONDITIONS** **Token Value Disclaimer** > The airdropped tokens may have no monetary value. There is no guarantee that these tokens will have any market value, utility, or liquidity. The Participant must participate at their own risk. We make no representations about the future market value of the Token **Tax and Regulatory Responsibilities:** You are responsible for any taxes or reporting obligations arising from the receipt of these Tokens and are fully responsible for compliance with all applicable laws, regulations, and tax requirements in your jurisdiction. All information provided in connection with the Airdrop or otherwise provided by Somnia is for informational purposes only and is not and should not be construed as professional tax or legal advice regarding the Airdrop or Tokens. Participants must consult their own tax or legal advisor. **Not an investment advice, DYOR:** The Token is not intended to constitute securities of any form or any other form of investment in any jurisdiction. This Policy is for informational purposes only and does not constitute a prospectus, offer document of any sort, or investment solicitation. No financial regulatory authority has examined or approved of this Policy. **Program Modifications:** Somnia reserves the absolute right to cancel, modify, suspend, or terminate the airdrop program at any time without prior notice and without any liability. We may change the eligibility criteria, distribution amounts, unlock conditions, or any other aspects of the program. Somnia is not liable to provide any compensation to the Participant for program changes or cancellations. **Privacy and Data:** All data collection and processing related to this Airdrop is governed by our Privacy Policy available at: [https://somnia.network/privacy-policyarrow-up-right](https://somnia.network/privacy-policy) **User Disclaimer:** The Airdrop is provided on an “as is'” and “as available” basis. Somnia expressly disclaims all warranties of any kind, whether express, implied, or statutory, including the implied warranties of merchantability, fitness for a particular purpose, title, and non-infringement. We do not guarantee that the Airdrop will be uninterrupted, timely, secure, error-free, or meet your specific requirements. You agree and acknowledge that your participation in the Airdrop does not violate any applicable laws, including without limitation, applicable economic and trade sanctions and export control laws and regulations, such as those administered and enforced by the EU, OFSI, OFAC, the U.S. Department of State, the U.S. Department of Commerce, the UN Security Council, and other relevant authorities. You also agree that you will not sell, assign or transfer control of any Tokens you receive in the Airdrop to a person or wallet address that would violate these terms if claimed directly by such person or wallet. **Limitation of Liability:** Our liability shall be limited to the fullest extent permitted by law. You release us and our team from any liability related to the Airdrop or Token use, including lost funds, third-party wallet security breaches, market value fluctuations, technical errors, or geo-blocking. **Indemnification:** The Participant agrees to defend, indemnify and hold harmless, Somnia and its representatives against any claims, losses, or legal costs arising from your Participation in the Airdrop program or breach of this Policy. **Dispute Disclaimer:** In no event shall Somnia or any Somnia person be held liable in connection with or for any claims, losses, damages, or other liabilities, whether in contract, tort, or otherwise, arising out of or in connection with the airdrop or the receipt of any tokens. By participating in this airdrop, you agree to resolve all disputes by individual arbitration and expressly waive your right to engage in class actions, class arbitration and representative actions. **Final Terms:** By participating in this airdrop, you acknowledge that you have read, understood, and agree to be bound by this Policy. You participate entirely at your own risk and acknowledge that Somnia makes no representations or warranties regarding the tokens or their future value. You represent and warrant that all information provided during the Airdrop process is true, accurate, and complete. We reserve the right to modify, add, or remove any section of this Policy from time to time or terminate the Airdrop or this Policy at any time with or without notice. It is your responsibility to keep yourself updated with the changes to this Policy. Continued participation shall constitute your acceptance of any modifications. [PreviousAML Compliancechevron-left](https://docs.somnia.network/concepts/miscellaneous/legal/aml-compliance) [NextGovernancechevron-right](https://docs.somnia.network/concepts/miscellaneous/legal/governance) Last updated 7 months ago * [DISCLAIMER: THIS POLICY GOVERNS YOUR PARTICIPATION IN THE AIRDROP AND CONSTITUTES A LEGALLY BINDING AGREEMENT BETWEEN YOU AND US. PLEASE READ THIS POLICY CAREFULLY. YOUR PARTICIPATION IN THE AIRDROP SHALL CONSTITUTE YOUR ACCEPTANCE TO THIS AIRDROP POLICY IN ITS ENTIRETY.](https://docs.somnia.network/concepts/miscellaneous/legal/airdrop-policy#disclaimer-this-policy-governs-your-participation-in-the-airdrop-and-constitutes-a-legally-binding-a) * [ELIGIBILITY](https://docs.somnia.network/concepts/miscellaneous/legal/airdrop-policy#eligibility) * [CLAIMING](https://docs.somnia.network/concepts/miscellaneous/legal/airdrop-policy#claiming) * [TECHNICAL REQUIREMENTS](https://docs.somnia.network/concepts/miscellaneous/legal/airdrop-policy#technical-requirements) * [ANTI FRAUD AND SECURITY MEASURES](https://docs.somnia.network/concepts/miscellaneous/legal/airdrop-policy#anti-fraud-and-security-measures) * [OTHER TERMS AND CONDITIONS](https://docs.somnia.network/concepts/miscellaneous/legal/airdrop-policy#other-terms-and-conditions) --- # Comparison | Somnia Docs Somnia Reactivity supports two subscription modes: off-chain (via WebSocket in TypeScript) for flexible, external app integration, and on-chain (via Solidity handlers) for automated, trustless blockchain reactions. Choose based on your dApp's needs—off-chain for UIs/backends, on-chain for DeFi/automation. #### [hashtag](https://docs.somnia.network/developer/reactivity/tooling/comparison#comparison-table) Comparison Table Aspect Off-Chain (WebSocket/TypeScript) On-Chain (Solidity/EVM) Delivery Mechanism WebSocket push to your app/server Direct EVM invocation of handler contract Execution Environment Off-chain (Node.js, browser) On-chain (Somnia validators execute) Gas / Costs None per notification (pay for node or rpc provider) Pays gas per invocation (from min 32 SOM balance) Latency Near-real-time (block time + network) Block time (atomic with chain state) State Access Include ETH view calls in sub; flexible queries Limited to handler logic; no external calls;Solidity contract does its own external view calls Use Cases Front-ends (live UIs), backends (DB updates), integrations DeFi (auto-compound), NFTs (reactive mints), oracles Reliability Options Can specify callback only executed when state changes Can specify if you want delivery regardless if block inclusion distance > 0, can also coalece Setup Complexity Install SDK, subscribe callback Deploy handler contract, create/fund sub Security App-level (e.g., auth WebSockets) Blockchain-level (reentrancy risks in handlers) Scalability Handles high volume off-chain Limited by gas/block; use coalescing for batches #### [hashtag](https://docs.somnia.network/developer/reactivity/tooling/comparison#when-to-use-off-chain-subscriptions) When to Use Off-Chain Subscriptions * **Pros**: No gas per event, easy integration with web apps, full access to external data/APIs. * **Cons**: Relies on your app being online * **Example**: Real-time dashboard updating on Transfer events without chain writes. #### [hashtag](https://docs.somnia.network/developer/reactivity/tooling/comparison#when-to-use-on-chain-subscriptions) When to Use On-Chain Subscriptions * **Pros**: Fully decentralized reactions; executes automatically on-chain. * **Cons**: Gas costs accumulate; potential for loops if handlers emit events. * **Example**: Smart contract that auto-swaps tokens on price oracle updates. #### [hashtag](https://docs.somnia.network/developer/reactivity/tooling/comparison#hybrid-approaches) Hybrid Approaches Combine both: Use off-chain for monitoring/UI, trigger on-chain actions via transactions when needed. [PreviousToolingchevron-left](https://docs.somnia.network/developer/reactivity/tooling) [NextOff-Chain (TypeScript)chevron-right](https://docs.somnia.network/developer/reactivity/tooling/off-chain-typescript) Last updated 1 month ago --- # Concepts | Somnia Docs [Understanding Schemas, Schema IDs, Data IDs, and Publisherchevron-right](https://docs.somnia.network/developer/data-streams/concepts/understanding-schemas-schema-ids-data-ids-and-publisher) [Extending and composing data schemaschevron-right](https://docs.somnia.network/developer/data-streams/concepts/extending-and-composing-data-schemas) [Somnia Data vs Event Streamschevron-right](https://docs.somnia.network/developer/data-streams/concepts/somnia-data-vs-event-streams) [Intersection with Somnia Reactivitychevron-right](https://docs.somnia.network/developer/data-streams/concepts/intersection-with-somnia-reactivity) [Data Provenance and Verification in Streamschevron-right](https://docs.somnia.network/developer/data-streams/concepts/data-provenance-and-verification-in-streams) [PreviousQuickstartchevron-left](https://docs.somnia.network/developer/data-streams/quickstart) [NextUnderstanding Schemas, Schema IDs, Data IDs, and Publisherchevron-right](https://docs.somnia.network/developer/data-streams/concepts/understanding-schemas-schema-ids-data-ids-and-publisher) --- # Extending and composing data schemas | Somnia Docs New schemas can extend other schemas by setting a parent schema ID. Remember, you can take any raw schema string and compute a schema ID from it. When registering a new schema that builds upon and extends another, you would specify the raw schema string for the new schema as well as specifying the optional parent schema ID. The parent schema ID will be critical later for deserialising data written to chain. For schemas that do not extend other schemas (when nothing is available), then one does not need to specify a parent schema ID or can optionally specify the zero value for the bytes32 solidity type. For maximum composability, all schemas should be public. ### [hashtag](https://docs.somnia.network/developer/data-streams/concepts/extending-and-composing-data-schemas#extension-in-practice-example-1) Extension in practice (Example 1) Copy import { SDK } from "@somnia-chain/streams" const sdk = new SDK({ public: getPublicClient(), wallet: getWalletClient(), }) // The parent schema here will be the GPS schema from the quick start guide const gpsSchema = `uint64 timestamp, int32 latitude, int32 longitude, int32 altitude, uint32 accuracy, bytes32 entityId, uint256 nonce` const parentSchemaId = await sdk.streams.computeSchemaId(gpsSchema) // Lets extend the gps schema and add F1 data since every car will have a gps position const formulaOneSchema = `uint256 driverNumber` // We can also extend the gps schema for FR data i.e. aircraft identifier const flightRadarSchema = `bytes32 ICAO24` await sdk.streams.registerDataSchemas([\ { schemaName: "gps", schema: gpsSchema },\ { schemaName: "f1", schema: formulaOneSchema, parentSchemaId }, // F1 extends GPS\ { schemaName: "FR", schema: flightRadarSchema, parentSchemaId },// FR extends GPS\ ]) The typescript code shows how two new schemas re-use the GPS schema in order to append an additional field ### [hashtag](https://docs.somnia.network/developer/data-streams/concepts/extending-and-composing-data-schemas#extension-in-practice-example-2) Extension in practice (Example 2) Versioned schemas Client's that are reading data associated with the derived schemas, use the SDK to get the fully decoded data since data is retrieved by schema ID (See `getByKey` from the quick start guide). Essentially the SDK does a number of the following pseudo steps: 1. Fetch schema and recursively fetch parent schema until the end of the chain is reached 2. Join all schemas together seperated by comma 3. Spin up the decoder and pass through the raw data stored on-chain 4. Return the decoded data to the caller [PreviousUnderstanding Schemas, Schema IDs, Data IDs, and Publisherchevron-left](https://docs.somnia.network/developer/data-streams/concepts/understanding-schemas-schema-ids-data-ids-and-publisher) [NextSomnia Data vs Event Streamschevron-right](https://docs.somnia.network/developer/data-streams/concepts/somnia-data-vs-event-streams) Last updated 1 month ago * [Extension in practice (Example 1)](https://docs.somnia.network/developer/data-streams/concepts/extending-and-composing-data-schemas#extension-in-practice-example-1) * [Extension in practice (Example 2)](https://docs.somnia.network/developer/data-streams/concepts/extending-and-composing-data-schemas#extension-in-practice-example-2) Copy import { SDK } from "@somnia-chain/streams" const sdk = new SDK({ public: getPublicClient(), wallet: getWalletClient(), }) const versionSchema = `uint16 version` const parentSchemaId = await sdk.streams.computeSchemaId(versionSchema) // Now lets register a person schema with expectation there will be many versions of the person schema const personSchema = `uint8 age` await sdk.streams.registerDataSchemas([\ { schemaName: "version", schema: versionSchema },\ { schemaName: "person", schema: personSchema, parentSchemaId }\ ]) --- # No, this is not like regular EVM event subscriptions | Somnia Docs Think event subscriptions are old news? On Ethereum or other EVM chains, they're just events, no state, and no on-chain reactions. Somnia's push subscriptions deliver state along side event data, something other EVMs cannot offer. #### [hashtag](https://docs.somnia.network/developer/reactivity/no-this-is-not-like-regular-evm-event-subscriptions#chain-comparison) Chain Comparison * **Other Chains**: `eth_subscribe` gives events only—you still pull state separately, risking inconsistency. * **Somnia**: Pushes event + state atomically; invokes Solidity handlers directly. #### [hashtag](https://docs.somnia.network/developer/reactivity/no-this-is-not-like-regular-evm-event-subscriptions#code-comparison) Code Comparison **Ethereum (Pull)**: Copy web3.eth.subscribe('logs', { address: '0x...' }, (err, log) => { // Now pull state manually contract.methods.balanceOf(...).call(); }); **Somnia (Push)**: Copy sdk.subscribe({ ethCalls: ['balanceOf'], onData: (data) => { // Event + state delivered with `data` }); [PreviousState Consistency Guaranteeschevron-left](https://docs.somnia.network/developer/reactivity/state-consistency-guarantees) [NextToolingchevron-right](https://docs.somnia.network/developer/reactivity/tooling) Last updated 1 month ago --- # What is Somnia Data Streams? | Somnia Docs Somnia Data Streams is a structured data layer for EVM chains. Somnia Data streams enable developers to build applications that both emit EVM event logs and write data to the Somnia chain without Solidity. This means developers do not need to know Solidity to build applications using Somnia Data Streams. Somnia Data streams allow parsing schema data into contract storage, where developers define a schema (a typed, ordered layout of fields), then publish and subscribe to data that conforms to that schema. Think of reading data from Streams as an emitted event, but with an SDK: publishers write strongly-typed records; subscribers read them by schema and publisher, and decode to rich objects. * * * [hashtag](https://docs.somnia.network/developer/data-streams/what-is-somnia-data-streams#why-streams) Why Streams? ----------------------------------------------------------------------------------------------------------------------- Traditional approaches each have trade-offs: * Contract events are great for signaling, but untyped at the app level (you still write your own ABI and decoders across projects). Events are also hard to stitch into reusable data models. * Custom contract storage is powerful but heavyweight, and you maintain the whole schema logic, CRUD, indexing patterns, and migrations. * Off-chain DB and proofs are flexible but brittle; either centralized or require extra machinery. * Oracles are useful for external data, but not a generic modeling layer for app-originated data. Streams solves this by standardizing: 1. Schemas (the “data ABI”) 2. Publish/Subscribe primitives (SDK, not boilerplate contracts) 3. Deterministic IDs (schemaId, dataId) and provenance (publisher address) This results in interoperable, verifiable, composable data with minimal app code. * * * [hashtag](https://docs.somnia.network/developer/data-streams/what-is-somnia-data-streams#when-to-use-streams) When to Use Streams -------------------------------------------------------------------------------------------------------------------------------------- Use Streams when you need: * Typed, shareable data across apps (chat messages, GPS, player stats, telemetry) * Multiple publishers writing the same kind of record * A standard decode flow with minimal custom indexing * You need instant push to clients (Streams also works well with polling; you can add WS if desired) Avoid Streams if: * You need complex transactional logic/state machines tightly bound to contract invariants (build a contract) * You must store large blobs (store off-chain, publish references/URIs in Streams) * * * [hashtag](https://docs.somnia.network/developer/data-streams/what-is-somnia-data-streams#definition-of-terms) Definition of Terms -------------------------------------------------------------------------------------------------------------------------------------- * **Schema**: a canonical string describing fields in order, e.g. uint64 timestamp, bytes32 roomId, string content, string senderName, address sender The exact string determines a schemaId (hash). * **Publisher**: The signer that writes data. EOA or Smart Contract that writes data under a schema. Readers trust provenance by address. * **Subscriber**: reader that fetches all records for a (schemaId, publisher) pair. * **Data ID (dataId)**: developer-chosen 32-byte key per record (helps with lookups, dedup, pagination). Pick dataIds with predictable structure to enable point lookups or pagination seeds. E.g: * Game: toHex('matchId-index', { size: 32 }) * Chat: toHex('room-timestamp', { size: 32 }) * GPS: toHex('device-epoch', { size: 32 }) * **Encoder**: converts typed values ⇄ bytes according to the schema. * **schemaId**: computed from the schema string. Treat it like a contract address for data shape. #### [hashtag](https://docs.somnia.network/developer/data-streams/what-is-somnia-data-streams#data-flow) Data flow You can have multiple publishers writing under the same schema; subscribers can aggregate them if desired. * * * [hashtag](https://docs.somnia.network/developer/data-streams/what-is-somnia-data-streams#the-schema-your-data-abi) The Schema: Your “Data ABI” --------------------------------------------------------------------------------------------------------------------------------------------------- A schema is a compact, ordered list of typed fields. The exact string determines the computed `schemaId` Even whitespace and order matter. Design guidance * Put stable fields first (e.g., timestamp, entityId, type). * Prefer fixed-width ints (e.g., uint64 for timestamps). * Use bytes32 for keys/IDs (room, device, etc.). * Use string for human-readable info (names, messages), but keep it short for gas efficiency. * * * ### [hashtag](https://docs.somnia.network/developer/data-streams/what-is-somnia-data-streams#data-writing-patterns) Data Writing Patterns * Single Publisher One server wallet publishes; User Interaces can read the schema using `getByKey` , `getAtIndex` , `getAllPublisherDataForSchema`. * Multi-Publisher Many devices publish under a shared schema. Your API aggregates across a list of publisher addresses. * Derived Views Build REST endpoints that query Streams and derive higher-level views (e.g., “latest per room”). * * * [hashtag](https://docs.somnia.network/developer/data-streams/what-is-somnia-data-streams#quickstart-in-5-minutes) Quickstart in 5 Minutes ---------------------------------------------------------------------------------------------------------------------------------------------- You’ll register a schema, publish one message, and read it back — just to feel the flow. ### [hashtag](https://docs.somnia.network/developer/data-streams/what-is-somnia-data-streams#install) Install ### [hashtag](https://docs.somnia.network/developer/data-streams/what-is-somnia-data-streams#set-up-env) Set up Env ### [hashtag](https://docs.somnia.network/developer/data-streams/what-is-somnia-data-streams#define-chain) Define Chain ### [hashtag](https://docs.somnia.network/developer/data-streams/what-is-somnia-data-streams#define-client) Define Client ### [hashtag](https://docs.somnia.network/developer/data-streams/what-is-somnia-data-streams#schema) Schema #### [hashtag](https://docs.somnia.network/developer/data-streams/what-is-somnia-data-streams#register-schema-optional-but-recommended) Register Schema (optional but recommended) chevron-rightscripts/register.ts[hashtag](https://docs.somnia.network/developer/data-streams/what-is-somnia-data-streams#scripts-register.ts) ### [hashtag](https://docs.somnia.network/developer/data-streams/what-is-somnia-data-streams#publish-write) Publish (Write) ### [hashtag](https://docs.somnia.network/developer/data-streams/what-is-somnia-data-streams#read-data) Read Data That’s your first end-to-end loop. * * * [hashtag](https://docs.somnia.network/developer/data-streams/what-is-somnia-data-streams#faqs) FAQs -------------------------------------------------------------------------------------------------------- Q: Do I need to register a schema? A: Registration is optional but recommended. You can publish to an unregistered schema (readers just need the exact string to decode). Registration helps discoverability and tooling. Q: Can I change a schema later? A: Changing order or types yields a new schemaId. Plan for versioning (run v1 + v2 together). Q: How do I page data? A: Use structured dataIds, or build a thin index off-chain that records block numbers / tx hashes per record. Q: How does Streams differ from subgraphs? A: Streams defines how you write/read structured records with an SDK. Subgraphs (or other indexers) sit on top to query across many publishers, paginate, and filter efficiently. Q: How do I handle large payloads? A: Store the payload elsewhere (IPFS, Arweave, S3) and put the URI + hash in Streams. Optionally encrypt off-chain. [PreviousSomnia Data Streamschevron-left](https://docs.somnia.network/developer/data-streams) [NextQuickstartchevron-right](https://docs.somnia.network/developer/data-streams/quickstart) Last updated 1 month ago * [Why Streams?](https://docs.somnia.network/developer/data-streams/what-is-somnia-data-streams#why-streams) * [When to Use Streams](https://docs.somnia.network/developer/data-streams/what-is-somnia-data-streams#when-to-use-streams) * [Definition of Terms](https://docs.somnia.network/developer/data-streams/what-is-somnia-data-streams#definition-of-terms) * [The Schema: Your “Data ABI”](https://docs.somnia.network/developer/data-streams/what-is-somnia-data-streams#the-schema-your-data-abi) * [Data Writing Patterns](https://docs.somnia.network/developer/data-streams/what-is-somnia-data-streams#data-writing-patterns) * [Quickstart in 5 Minutes](https://docs.somnia.network/developer/data-streams/what-is-somnia-data-streams#quickstart-in-5-minutes) * [Install](https://docs.somnia.network/developer/data-streams/what-is-somnia-data-streams#install) * [Set up Env](https://docs.somnia.network/developer/data-streams/what-is-somnia-data-streams#set-up-env) * [Define Chain](https://docs.somnia.network/developer/data-streams/what-is-somnia-data-streams#define-chain) * [Define Client](https://docs.somnia.network/developer/data-streams/what-is-somnia-data-streams#define-client) * [Schema](https://docs.somnia.network/developer/data-streams/what-is-somnia-data-streams#schema) * [Publish (Write)](https://docs.somnia.network/developer/data-streams/what-is-somnia-data-streams#publish-write) * [Read Data](https://docs.somnia.network/developer/data-streams/what-is-somnia-data-streams#read-data) * [FAQs](https://docs.somnia.network/developer/data-streams/what-is-somnia-data-streams#faqs) Copy +-------------+ publishData(payload) +--------------------+ | Publisher | --------------------------------> | Somnia Streams L1 | | (wallet) | | (on-chain data) | +-------------+ +--------------------+ ^ | | getAllPublisherDataForSchema | v +-------------+ +-----------+ | Subscriber | <------------------------------------ | Reader | | (frontend) | | (SDK) | +-------------+ +-----------+ Copy npm i @somnia-chain/streams viem Copy RPC_URL=https://dream-rpc.somnia.network PRIVATE_KEY=0xYOUR_FUNDED_PRIVATE_KEY Copy // lib/chain.ts import { defineChain } from 'viem' export const somniaTestnet = defineChain({ id: 50312, name: 'Somnia Testnet', network: 'somnia-testnet', nativeCurrency: { name: 'STT', symbol: 'STT', decimals: 18 }, rpcUrls: { default: { http: ['https://dream-rpc.somnia.network'] }, public: { http: ['https://dream-rpc.somnia.network'] } }, } as const) Copy // lib/clients.ts import { createPublicClient, createWalletClient, http } from 'viem' import { privateKeyToAccount } from 'viem/accounts' import { somniaTestnet } from './chain' const RPC = process.env.RPC_URL as string const PK = process.env.PRIVATE_KEY as `0x${string}` export const publicClient = createPublicClient({ chain: somniaTestnet, transport: http(RPC) }) export const walletClient = createWalletClient({ account: privateKeyToAccount(PK), chain: somniaTestnet, transport: http(RPC) }) Copy // lib/schema.ts export const chatSchema = 'uint64 timestamp, bytes32 roomId, string content, string senderName, address sender' Copy import 'dotenv/config' import { SDK, zeroBytes32 } from '@somnia-chain/streams' import { publicClient, walletClient } from '../lib/clients' import { chatSchema } from '../lib/schema' import { waitForTransactionReceipt } from 'viem/actions' async function main() { const sdk = new SDK({ public: publicClient, wallet: walletClient }) const id = await sdk.streams.computeSchemaId(chatSchema) const exists = await sdk.streams.isSchemaRegistered(id) if (!exists) { const tx = await sdk.streams.registerSchema(chatSchema, zeroBytes32) await waitForTransactionReceipt(publicClient, { hash: tx }) } console.log('schemaId:', id) } main() Copy // scripts/publish-one.ts import 'dotenv/config' import { SDK, SchemaEncoder } from '@somnia-chain/streams' import { publicClient, walletClient } from '../lib/clients' import { chatSchema } from '../lib/schema' import { toHex, type Hex } from 'viem' import { waitForTransactionReceipt } from 'viem/actions' async function main() { const sdk = new SDK({ public: publicClient, wallet: walletClient }) const schemaId = await sdk.streams.computeSchemaId(chatSchema) const enc = new SchemaEncoder(chatSchema) const payload: Hex = enc.encodeData([\ { name: 'timestamp', value: Date.now().toString(), type: 'uint64' },\ { name: 'roomId', value: toHex('general', { size: 32 }), type: 'bytes32' },\ { name: 'content', value: 'Hello Somnia!', type: 'string' },\ { name: 'senderName', value: 'Alice', type: 'string' },\ { name: 'sender', value: walletClient.account!.address, type: 'address' },\ ]) const dataId = toHex(`general-${Date.now()}`, { size: 32 }) const tx = await sdk.streams.setAndEmitEvents( [{ id: dataId, schemaId, data }], [{ id: CHAT_EVENT_ID, argumentTopics: topics.slice(1), data: eventData }] ) if (!tx) throw new Error('Failed to setAndEmitEvents') await waitForTransactionReceipt(getPublicHttpClient(), { hash: tx }) return { txHash: tx } } main() Copy // scripts/read-all.ts import 'dotenv/config' import { SDK } from '@somnia-chain/streams' import { publicClient } from '../lib/clients' import { chatSchema } from '../lib/schema' import { toHex } from 'viem' type Field = { name: string; type: string; value: any } const val = (f: Field) => f?.value?.value ?? f?.value async function main() { const sdk = new SDK({ public: publicClient }) const schemaId = await sdk.streams.computeSchemaId(chatSchema) const publisher = process.env.PUBLISHER as `0x${string}` || '0xYOUR_PUBLISHER_ADDR' const rows = (await sdk.streams.getAllPublisherDataForSchema(schemaId, publisher)) as Field[][] const want = toHex('general', { size: 32 }).toLowerCase() for (const r of rows || []) { const ts = Number(val(r[0])) const ms = String(ts).length <= 10 ? ts * 1000 : ts if (String(val(r[1])).toLowerCase() !== want) continue console.log({ time: new Date(ms).toLocaleString(), content: String(val(r[2])), senderName: String(val(r[3])), sender: String(val(r[4])), }) } } main() --- # FAQs & Troubleshooting | Somnia Docs ### [hashtag](https://docs.somnia.network/developer/reactivity/faqs-and-troubleshooting#faqs) FAQs circle-exclamation **Somnia Reactivity is currently only available on TESTNET** #### [hashtag](https://docs.somnia.network/developer/reactivity/faqs-and-troubleshooting#how-do-i-fetch-historical-data) How Do I Fetch Historical Data? To get event and state data from before an application subscription was created there are a few approaches: * Use a traditional indexer or tooling such as a subgraph * Build a custom indexer which starts at an earlier block and persists data received from Somnia reactivity into a DB that can be queried from the chain * Directly query historical data from the chain from within your application (generally inefficient) ### [hashtag](https://docs.somnia.network/developer/reactivity/faqs-and-troubleshooting#troubleshooting) Troubleshooting #### [hashtag](https://docs.somnia.network/developer/reactivity/faqs-and-troubleshooting#issues-starting-websocket-subscriptions) Issues starting websocket subscriptions Two core reasons for this: 1. The chain definition for the Somnia testnet or mainnet does not contain a wss url 2. The wss url does not support the reactivity feature set or the rpc provider is having issues #### [hashtag](https://docs.somnia.network/developer/reactivity/faqs-and-troubleshooting#too-many-active-websocket-subscriptions) Too many active websocket subscriptions Often seen in React applications where `useEffect` is not being used correctly or not used at all to start an event subscription leading to attempts to create a subscription every time the page renders #### [hashtag](https://docs.somnia.network/developer/reactivity/faqs-and-troubleshooting#solidity-handler-not-being-invoked) Solidity handler not being invoked Three core reasons for this: 1. Invalid implementation of `SomniaEventHandler` interface 2. No active subscription 3. Insufficient subscription balance [PreviousGas Configurationchevron-left](https://docs.somnia.network/developer/reactivity/gas-configuration) [NextAPI Referencechevron-right](https://docs.somnia.network/developer/reactivity/api-reference) Last updated 1 month ago * [FAQs](https://docs.somnia.network/developer/reactivity/faqs-and-troubleshooting#faqs) * [Troubleshooting](https://docs.somnia.network/developer/reactivity/faqs-and-troubleshooting#troubleshooting) --- # Tutorials | Somnia Docs [“Hello World” Appchevron-right](https://docs.somnia.network/developer/data-streams/tutorials/hello-world-app) [Build Your First Schemachevron-right](https://docs.somnia.network/developer/data-streams/tutorials/build-your-first-schema) [Streams Case Study: Formula 1chevron-right](https://docs.somnia.network/developer/data-streams/tutorials/streams-case-study-formula-1) [READ Stream Data from a UI (Next.js Example)chevron-right](https://docs.somnia.network/developer/data-streams/tutorials/read-stream-data-from-a-ui-next.js-example) [Integrate Chainlink Oracleschevron-right](https://docs.somnia.network/developer/data-streams/tutorials/integrate-chainlink-oracles) [Working with Multiple Publishers in a Shared Streamchevron-right](https://docs.somnia.network/developer/data-streams/tutorials/working-with-multiple-publishers-in-a-shared-stream) [The DApp Publisher Proxy Patternchevron-right](https://docs.somnia.network/developer/data-streams/tutorials/the-dapp-publisher-proxy-pattern) [Build a Minimal On-Chain Chat Appchevron-right](https://docs.somnia.network/developer/data-streams/tutorials/build-a-minimal-on-chain-chat-app) [Build a Realtime On-Chain Gamechevron-right](https://docs.somnia.network/developer/data-streams/tutorials/build-a-realtime-on-chain-game) [PreviousSDK Methods Guidechevron-left](https://docs.somnia.network/developer/data-streams/sdk-methods-guide) [Next“Hello World” Appchevron-right](https://docs.somnia.network/developer/data-streams/tutorials/hello-world-app) --- # Quickstart | Somnia Docs [hashtag](https://docs.somnia.network/developer/data-streams/quickstart#pre-requisites) Pre-requisites ----------------------------------------------------------------------------------------------------------- A typescript environment with [`viem`arrow-up-right](https://viem.sh/) and [`@somnia-chain/streams`arrow-up-right](https://www.npmjs.com/package/@somnia-chain/streams) installed [hashtag](https://docs.somnia.network/developer/data-streams/quickstart#steps) Steps ----------------------------------------------------------------------------------------- ### [hashtag](https://docs.somnia.network/developer/data-streams/quickstart#id-1.-define-your-schema-as-a-string-and-plug-it-into-the-schema-encoder) 1\. Define your schema as a string and plug it into the schema encoder Copy import { SDK, zeroBytes32, SchemaEncoder } from "@somnia-chain/streams" const gpsSchema = `uint64 timestamp, int32 latitude, int32 longitude, int32 altitude, uint32 accuracy, bytes32 entityId, uint256 nonce` const schemaEncoder = new SchemaEncoder(gpsSchema) `schemaEncoder` can now be used to encode data for broadcast and also decode data when reading it from Somnia Data Stream SDK. ### [hashtag](https://docs.somnia.network/developer/data-streams/quickstart#id-2.-compute-your-unique-schema-identifier-from-the-schema) 2\. Compute your unique schema identifier from the schema Copy const sdk = new SDK({ public: getPublicClient(), wallet: getWalletClient(), }) const schemaId = await sdk.streams.computeSchemaId(gpsSchema) console.log(`Schema ID ${schemaId}`) All data broadcast with the Somnia Data Stream SDK write mechanism must be linked to a schema ID so that we know how to decode the data on read. ### [hashtag](https://docs.somnia.network/developer/data-streams/quickstart#id-3.-encode-the-data-you-want-to-store-that-is-compatible-with-the-schema) 3\. Encode the data you want to store that is compatible with the schema The value returned is a raw hex encoded bytes value that can be broadcast on-chain via the Somnia Data Stream SDK. ### [hashtag](https://docs.somnia.network/developer/data-streams/quickstart#id-4.-publish-data-with-our-without-a-public-schema) 4\. Publish data (with our without a public schema) `set` has the following parameter `dataStreams` which is a list of data points being written to chain `dataStreams` has the `DataStream[]` type: ### [hashtag](https://docs.somnia.network/developer/data-streams/quickstart#id-5.-direct-data-read-without-reactivity) 5\. Direct data read without reactivity This last step shows how you request data from Somnia data streams filtering on: 1. Schema ID 2. Address of the account that wrote the data to chain 1. This could be an EOA or another smart contract The response from `getByKey` will be the data published but decoded for the specified schema. Note: where the schema ID is associated with a public data schema that has been registered on-chain, the SDK will automatically decode the raw data published on-chain and return that decoded data removing the need for the decoder. If the schema is not public, the schema decoder will be required outside of the SDK and you will instead get raw bytes from the chain. Example: Further filters can be applied client side to the data in order to filter for specifics within the data. GitBook also allows you to set up a bi-directional sync with an existing repository on GitHub or GitLab. Setting up Git Sync allows you and your team to write content in GitBook or in code, and never have to worry about your content becoming out of sync. [PreviousWhat is Somnia Data Streams?chevron-left](https://docs.somnia.network/developer/data-streams/what-is-somnia-data-streams) [NextConceptschevron-right](https://docs.somnia.network/developer/data-streams/concepts) Last updated 1 month ago * [Pre-requisites](https://docs.somnia.network/developer/data-streams/quickstart#pre-requisites) * [Steps](https://docs.somnia.network/developer/data-streams/quickstart#steps) * [1\. Define your schema as a string and plug it into the schema encoder](https://docs.somnia.network/developer/data-streams/quickstart#id-1.-define-your-schema-as-a-string-and-plug-it-into-the-schema-encoder) * [2\. Compute your unique schema identifier from the schema](https://docs.somnia.network/developer/data-streams/quickstart#id-2.-compute-your-unique-schema-identifier-from-the-schema) * [3\. Encode the data you want to store that is compatible with the schema](https://docs.somnia.network/developer/data-streams/quickstart#id-3.-encode-the-data-you-want-to-store-that-is-compatible-with-the-schema) * [4\. Publish data (with our without a public schema)](https://docs.somnia.network/developer/data-streams/quickstart#id-4.-publish-data-with-our-without-a-public-schema) * [5\. Direct data read without reactivity](https://docs.somnia.network/developer/data-streams/quickstart#id-5.-direct-data-read-without-reactivity) Copy const encodedData: Hex = schemaEncoder.encodeData([\ { name: "timestamp", value: Date.now().toString(), type: "uint64" },\ { name: "latitude", value: "51509865", type: "int32" },\ { name: "longitude", value: "-0118092", type: "int32" },\ { name: "altitude", value: "0", type: "int32" },\ { name: "accuracy", value: "0", type: "uint32" },\ { name: "entityId", value: zeroBytes32, type: "bytes32" }, // object providing GPS data\ { name: "nonce", value: "0", type: "uint256" },\ ]) Copy const publishTxHash = await sdk.streams.set([{\ id: toHex("london", { size: 32 }),\ schemaId: computedGpsSchemaId,\ data: encodedData,\ }]) Copy type Hex = `0x{string}` type DataStream = { id: Hex // Unique data key for the publisher schemaId: Hex // Computed from the raw schema string data: Hex // From step 3, raw bytes data formated as a hex string } Copy const data = await sdk.streams.getByKey( computedGpsSchemaId, publisherWalletAddress, dataKey ) Copy if (data) { schemaEncoder.decode(data) } --- # System Events | Somnia Docs There are two events that are generated by the system, this is represented in Solidity as: Copy event BlockTick(uint64 indexed blockNumber); event BlockTick(uint64 indexed epochNumber, uint64 indexed blockNumber); event Schedule(uint256 indexed timestampMillis); You can subscribe to those events as any other. The system will generate those events for every block and match with any subscriptions. circle-info Remember to set the `emitter` field to `SOMNIA_REACTIVITY_PRECOMPILE_ADDRESS`. This will make sure that your handler will only respond to system events. ### [hashtag](https://docs.somnia.network/developer/reactivity/system-events#block-tick-event) Block Tick Event If `blockNumber` is provided then this event will trigger at the specific block. Otherwise this will be triggered at every block, ~10 times per second. This example will tick at every single block: Copy ISomniaReactivityPrecompile.SubscriptionData memory subscriptionData = ISomniaReactivityPrecompile .SubscriptionData({ eventTopics: [BlockTick.selector, bytes32(0), bytes32(0), bytes32(0)], emitter: SomniaExtensions.SOMNIA_REACTIVITY_PRECOMPILE_ADDRESS, handlerContractAddress: address(this), handlerFunctionSelector: ISomniaEventHandler.onEvent.selector, /*...*/ }); ### [hashtag](https://docs.somnia.network/developer/reactivity/system-events#epoch-tick-event) Epoch Tick Event If `epochNumber` is provided then this event will trigger at the beginning of the specific epoch. Otherwise this will be triggered at every epoch, roughly every ~5 minutes. This example will tick at every single epoch: ### [hashtag](https://docs.somnia.network/developer/reactivity/system-events#schedule-event) Schedule Event This event is useful for scheduling actions in the future. Few things to remember: * The provided timestamp must be in the future, minimum is next second from the current block * The subscription to `Schedule` is one-off and will be deleted after triggering * The timestamp is expressed in milliseconds (see [https://currentmillis.com/arrow-up-right](https://currentmillis.com/) for handy calculations) his example will tick on Nov 11 2026 11:11:11.011 : [PreviousPush vs Pull: An Architectural Shiftchevron-left](https://docs.somnia.network/developer/reactivity/push-vs-pull-an-architectural-shift) [NextState Consistency Guaranteeschevron-right](https://docs.somnia.network/developer/reactivity/state-consistency-guarantees) Last updated 1 day ago * [Block Tick Event](https://docs.somnia.network/developer/reactivity/system-events#block-tick-event) * [Epoch Tick Event](https://docs.somnia.network/developer/reactivity/system-events#epoch-tick-event) * [Schedule Event](https://docs.somnia.network/developer/reactivity/system-events#schedule-event) Copy ISomniaReactivityPrecompile.SubscriptionData memory subscriptionData = ISomniaReactivityPrecompile .SubscriptionData({ eventTopics: [EpochTick.selector, bytes32(0), bytes32(0), bytes32(0)], emitter: SomniaExtensions.SOMNIA_REACTIVITY_PRECOMPILE_ADDRESS, handlerContractAddress: address(this), handlerFunctionSelector: ISomniaEventHandler.onEvent.selector, /*...*/ }); Copy ISomniaReactivityPrecompile.SubscriptionData memory subscriptionData = ISomniaReactivityPrecompile .SubscriptionData({ eventTopics: [Schedule.selector, 1794395471011, bytes32(0), bytes32(0)], emitter: SomniaExtensions.SOMNIA_REACTIVITY_PRECOMPILE_ADDRESS, handlerContractAddress: address(this), handlerFunctionSelector: ISomniaEventHandler.onEvent.selector, /*...*/ }); --- # Building DApps | Somnia Docs [Tokens and NFTschevron-right](https://docs.somnia.network/developer/building-dapps/tokens-and-nfts) [Wallet Integration and Authchevron-right](https://docs.somnia.network/developer/building-dapps/wallet-integration-and-auth) [OnRampschevron-right](https://docs.somnia.network/developer/building-dapps/onramps) [Account Abstractionchevron-right](https://docs.somnia.network/developer/building-dapps/account-abstraction) [Data Indexing and Queryingchevron-right](https://docs.somnia.network/developer/building-dapps/data-indexing-and-querying) [Oracleschevron-right](https://docs.somnia.network/developer/building-dapps/oracles) [Example Applicationschevron-right](https://docs.somnia.network/developer/building-dapps/example-applications) [PreviousDebug Playbookchevron-left](https://docs.somnia.network/developer/development-frameworks/debug-playbook) [NextTokens and NFTschevron-right](https://docs.somnia.network/developer/building-dapps/tokens-and-nfts) --- # On-chain (Solidity) | Somnia Docs ### [hashtag](https://docs.somnia.network/developer/reactivity/tooling/on-chain-solidity#somnia-reactivity-precompile) Somnia Reactivity Precompile The Somnia Reactivity Precompile is located at address `0x0100`. It provides an interface for managing event subscriptions. #### [hashtag](https://docs.somnia.network/developer/reactivity/tooling/on-chain-solidity#interface) Interface The interface for the precompile is defined in `ISomniaReactivityPrecompile.sol`: Copy interface ISomniaReactivityPrecompile { struct SubscriptionData { bytes32[4] eventTopics; // Topic filter (0x0 for wildcard) address origin; // Origin (tx.origin) filter (address(0) for wildcard) address caller; // Caller (msg.sender) filter (address(0) for wildcard) address emitter; // Contract emitting the event (address(0) for wildcard) address handlerContractAddress; // Address of the contract to handle the event bytes4 handlerFunctionSelector; // Function selector in the handler contract uint64 priorityFeePerGas; // Extra fee to prioritize handling, in nanoSomi uint64 maxFeePerGas; // Max fee willing to pay, in nanoSomi uint64 gasLimit; // Maximum gas that will be provisioned per subscription callback bool isGuaranteed; // If true, moves to next block if current is full bool isCoalesced; // If true, multiple events can be coalesced } event SubscriptionCreated(uint64 indexed subscriptionId, address indexed owner, SubscriptionData subscriptionData); event SubscriptionRemoved(uint64 indexed subscriptionId, address indexed owner); function subscribe(SubscriptionData calldata subscriptionData) external returns (uint256 subscriptionId); function unsubscribe(uint256 subscriptionId) external; function getSubscriptionInfo(uint256 subscriptionId) external view returns (SubscriptionData memory subscriptionData, address owner); } [PreviousOff-Chain (TypeScript)chevron-left](https://docs.somnia.network/developer/reactivity/tooling/off-chain-typescript) [NextSubscription managementchevron-right](https://docs.somnia.network/developer/reactivity/tooling/subscription-management) Last updated 6 days ago --- # Off-Chain (TypeScript) | Somnia Docs ### [hashtag](https://docs.somnia.network/developer/reactivity/tooling/off-chain-typescript#overview) Overview Tooling is as follows: * TypeScript SDK - Compatible in NodeJS, Browser, JavaScript and Typescript environments * React library (future) - Native hooks and react APIs for getting started with subscriptions following React best practives See the quick start guide for getting started with the SDK [Quickstartchevron-right](https://docs.somnia.network/developer/reactivity/quickstart) [PreviousComparisonchevron-left](https://docs.somnia.network/developer/reactivity/tooling/comparison) [NextOn-chain (Solidity)chevron-right](https://docs.somnia.network/developer/reactivity/tooling/on-chain-solidity) Last updated 2 months ago --- # API Reference | Somnia Docs circle-exclamation **Somnia Reactivity is currently only available on TESTNET** ### [hashtag](https://docs.somnia.network/developer/reactivity/api-reference#off-chain-typescript) Off-chain (TypeScript) #### [hashtag](https://docs.somnia.network/developer/reactivity/api-reference#websocket-subscription-initialization-params) WebSocket Subscription Initialization Params Copy /** * @property The notification result data containing information about the event topic, data and view results */ export type SubscriptionCallback = { result: { topics: Hex[], data: Hex, simulationResults: Hex[] } } /** * @property ethCalls Fixed set of ETH calls that must be executed before onData callback is triggered. Multicall3 is recommended. Can be an empty array * @property context Event sourced selectors to be added to the data field of ETH calls, possible values: topic0, topic1, topic2, topic3, data and address * @property onData Callback for a successful reactivity notification * @property onError Callback for a failed attempt * @property eventContractSources Alternative contract event source(s) (any on somnia) that will be emitting the logs specified by topicOverrides * @property topicOverrides Optional filter for specifying topics of interest, otherwise wildcard filter is applied (all events watched) * @property onlyPushChanges Whether the data should be pushed to the subscriber only if eth_call results are different from the previous */ export type WebsocketSubscriptionInitParams = { ethCalls: EthCall[] context?: string onData: (data: SubscriptionCallback) => void onError?: (error: Error) => void eventContractSources?: Address[] topicOverrides?: Hex[] onlyPushChanges?: boolean } An object of type `WebsocketSubscriptionInitParams` is the only required argument to the `subscribe` function required to create a subscription to a Somnia node to become notified about event + state changes that take place on-chain Example: #### [hashtag](https://docs.somnia.network/developer/reactivity/api-reference#solidity-subscription-creation-from-sdk) Solidity Subscription Creation from SDK Example: #### [hashtag](https://docs.somnia.network/developer/reactivity/api-reference#solidity-subscription-info-query-from-sdk) Solidity subscription info query from SDK Example: [PreviousFAQs & Troubleshootingchevron-left](https://docs.somnia.network/developer/reactivity/faqs-and-troubleshooting) [NextSomnia Data Streamschevron-right](https://docs.somnia.network/developer/data-streams) Last updated 1 day ago Copy const subscription = await sdk.subscribe({ ethCalls: [], // State to read when events are emitted onData: (data: SubscriptionCallback) => console.log('Received:', data), }) Copy /** * @property eventTopics Optional event filters * @property origin Optional tx.origin filter * @property caller Optional msg.sender filter * @property emitter Optional contract event emitter filter * @property handlerContractAddress Contract that will handle subscription callback * @property handlerFunctionSelector Optional override for specifying callback handler function * @property priorityFeePerGas Additional priority fee that will be paid per gas consumed by callback * @property maxFeePerGas Maximum fee per gas the subscriber is willing to pay (base fee + priority fee) * @property gasLimit Maximum gas that will be provisioned per subscription callback * @property isGuaranteed Whether event notification must be delivered regardless of block inclusion distance from emission * @property isCoalesced Whether multiple events can be coalesced into a single handling call per block */ export type SoliditySubscriptionData = { eventTopics?: Hex[]; origin?: Address; caller?: Address; emitter?: Address; handlerContractAddress: Address; handlerFunctionSelector?: Hex; priorityFeePerGas: bigint; maxFeePerGas: bigint; gasLimit: bigint; isGuaranteed: boolean; isCoalesced: boolean; } Copy await sdk.createSoliditySubscription({ handlerContractAddress: '0x123...', priorityFeePerGas: 10n, maxFeePerGas: 20n, gasLimit: 500_000n, isGuaranteed: true, isCoalesced: false, }); Copy export type SoliditySubscriptionInfo = { subscriptionData: SoliditySubscriptionData, owner: Address } Copy const subscriptionId = 1n; const subscriptionInfo: SoliditySubscriptionInfo = await sdk.getSubscriptionInfo( subscriptionId ); --- # Data Provenance and Verification in Streams | Somnia Docs When consuming data from any source, especially in a decentralized environment, the most critical question is: **"Can I trust this data?"** This question is not just about the data's content, but its _origin_. How do you know that data claiming to be from a trusted oracle, a specific device, or another user _actually_ came from them and not from an imposter? This is the challenge of **Data Provenance**. In Somnia Data Streams, provenance is not an optional feature or a "best practice". It is a fundamental, cryptographic guarantee built into the core smart contract. This article explains how Streams ensures authenticity via publisher signatures and how you can verify data origin. [hashtag](https://docs.somnia.network/developer/data-streams/concepts/data-provenance-and-verification-in-streams#the-cryptographic-guarantee-msg.sender-as-provenance) The Cryptographic Guarantee: `msg.sender` as Provenance ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ The trust layer of Somnia Streams is elegantly simple. It does not rely on complex off-chain signature checking or data fields like `senderName`. Instead, it leverages the most basic and secure primitive of the EVM: `msg.sender`. All data published to Streams is stored in the core `Streams` smart contract. The data storage mapping has a specific structure: #### [hashtag](https://docs.somnia.network/developer/data-streams/concepts/data-provenance-and-verification-in-streams#conceptual-contract-storage) **Conceptual Contract Storage** Copy // mapping: schemaId => publisherAddress => dataId => data mapping(bytes32 => mapping(address => mapping(bytes32 => bytes))) public dsstore; When a publisher calls `sdk.streams.set(...)` or `sdk.streams.setAndEmitEvents(...)`, their wallet signs a transaction. The `Streams` smart contract receives this transaction and identifies the signer's address via the `msg.sender` variable. The contract then stores the data _at the_ `_msg.sender_`_'s address_ within the schema's mapping. **This is the cryptographic guarantee.** It is **impossible** for `0xPublisher_A` to send a transaction that writes data into the slot for `0xPublisher_B`. They cannot fake their `msg.sender`. The data is automatically and immutably tied to the address of the account that paid the gas to publish it. * An attacker **cannot** write data as if it came from a trusted oracle. * A user **cannot** send a chat message pretending to be another user. * Data integrity is linked directly to wallet security. [hashtag](https://docs.somnia.network/developer/data-streams/concepts/data-provenance-and-verification-in-streams#verification-is-implicit-in-the-read-operation) Verification Is Implicit in the Read Operation --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- Because the `publisher` address is a fundamental key in the storage mapping, you don't need to perform complex "verification" steps. **Verification is implicit in the read operation.** When you use the SDK to read data, you must specify which publisher you are interested in: * `sdk.streams.getByKey(schemaId, publisher, key)` * `sdk.streams.getAllPublisherDataForSchema(schemaId, publisher)` When you call `getAllPublisherDataForSchema(schemaId, '0xTRUSTED_ORACLE_ADDRESS')`, you are not _filtering_ data. You are asking the smart contract to retrieve data from the specific storage slot that _only_ `0xTRUSTED_ORACLE_ADDRESS` could have written to. If an imposter (`0xIMPOSTER_ADDRESS`) publishes data using the same `schemaId`, their data is stored in a completely different location (`dsstore[schemaId]['0xIMPOSTER_ADDRESS']`). It will never be returned when you query for the trusted address. [hashtag](https://docs.somnia.network/developer/data-streams/concepts/data-provenance-and-verification-in-streams#deliverable-building-a-verification-script) Deliverable: Building a Verification Script -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- Let's build a utility to prove this concept. **Scenario:** We have a shared `oraclePrice` schema. Two different, trusted oracles (`0xOracle_A` and `0xOracle_B`) publish prices to it. We will build a script that verifies the origin of data and proves that an `imposter` cannot pollute their feeds. ### [hashtag](https://docs.somnia.network/developer/data-streams/concepts/data-provenance-and-verification-in-streams#project-setup) **Project Setup** We will use the same project setup as the "[Multi-Publisher Aggregatorarrow-up-right](https://emre-gitbook.gitbook.io/emre-gitbook-docs/data-streams/working-with-multiple-publishers-in-a-shared-stream#tutorial-building-a-multi-publisher-aggregator-app) " tutorial. You will need a `.env` file with at least one private key to act as a publisher, and we will simulate the other addresses. [`**src/lib/clients.ts**`arrow-up-right](https://emre-gitbook.gitbook.io/emre-gitbook-docs/data-streams/working-with-multiple-publishers-in-a-shared-stream#chain-and-client-configuration) (No changes needed from the previous tutorial. We just need `publicClient`.) `**src/lib/schema.ts**` ### [hashtag](https://docs.somnia.network/developer/data-streams/concepts/data-provenance-and-verification-in-streams#the-verification-script) **The Verification Script** This script will not publish data. We will assume our two trusted oracles (`PUBLISHER_1_PK` and `PUBLISHER_2_PK` from the previous tutorial) have already published data using the `oraclePriceSchema`. Our script will: 1. Define a list of `TRUSTED_ORACLES`. 2. Define an `IMPOSTER_ORACLE` (a random address that has _not_ published). 3. Create a `verifyPublisher` function that fetches data _only_ for a specific publisher address. 4. Run verification for all addresses and show that data is only returned for the correct publishers. `**src/scripts/verifyOrigin.ts**` ### [hashtag](https://docs.somnia.network/developer/data-streams/concepts/data-provenance-and-verification-in-streams#expected-output) **Expected Output** To run this, first publish some data (using the script from the previous tutorial, but adapted for `oraclePriceSchema`) from both `PUBLISHER_1_PK` and `PUBLISHER_2_PK`. Then, run the verification script. You will see an output similar to this: [hashtag](https://docs.somnia.network/developer/data-streams/concepts/data-provenance-and-verification-in-streams#conclusion-key-takeaways) Conclusion: Key Takeaways -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- * **Provenance is Built-In:** Data provenance in Somnia Streams is not an optional feature; it is a core cryptographic guarantee of the `Streams` smart contract, enforced by `msg.sender`. * **Verification is Implicit:** You verify data origin every time you perform a read operation with `getAllPublisherDataForSchema` or `getByKey`. The `publisher` address acts as the ultimate verification key. * **Trust Layer:** This architecture creates a robust trust layer. Your application logic can be certain that any data returned for a specific publisher was, without question, signed and submitted by that publisher's wallet. [PreviousIntersection with Somnia Reactivitychevron-left](https://docs.somnia.network/developer/data-streams/concepts/intersection-with-somnia-reactivity) [NextSDK Methods Guidechevron-right](https://docs.somnia.network/developer/data-streams/sdk-methods-guide) Last updated 1 month ago * [The Cryptographic Guarantee: msg.sender as Provenance](https://docs.somnia.network/developer/data-streams/concepts/data-provenance-and-verification-in-streams#the-cryptographic-guarantee-msg.sender-as-provenance) * [Verification Is Implicit in the Read Operation](https://docs.somnia.network/developer/data-streams/concepts/data-provenance-and-verification-in-streams#verification-is-implicit-in-the-read-operation) * [Deliverable: Building a Verification Script](https://docs.somnia.network/developer/data-streams/concepts/data-provenance-and-verification-in-streams#deliverable-building-a-verification-script) * [Project Setup](https://docs.somnia.network/developer/data-streams/concepts/data-provenance-and-verification-in-streams#project-setup) * [The Verification Script](https://docs.somnia.network/developer/data-streams/concepts/data-provenance-and-verification-in-streams#the-verification-script) * [Expected Output](https://docs.somnia.network/developer/data-streams/concepts/data-provenance-and-verification-in-streams#expected-output) * [Conclusion: Key Takeaways](https://docs.somnia.network/developer/data-streams/concepts/data-provenance-and-verification-in-streams#conclusion-key-takeaways) Copy export const oraclePriceSchema = 'uint256 price, uint64 timestamp' Copy import 'dotenv/config' import { SDK, SchemaDecodedItem } from '@somnia-chain/streams' import { publicClient } from '../lib/clients' // Assuming you have clients.ts from previous tutorial import { oraclePriceSchema } from '../lib/schema' import { Address, createWalletClient, http } from 'viem' import { privateKeyToAccount } from 'viem/accounts' // --- Setup: Define our trusted and untrusted addresses --- function getEnv(key: string): string { const value = process.env[key] if (!value) throw new Error(`Missing environment variable: ${key}`) return value } // These are the addresses we trust for this schema. // We get them from our .env file for this example. const TRUSTED_ORACLES: Address[] = [\ privateKeyToAccount(getEnv('PUBLISHER_1_PK') as `0x${string}`).address,\ privateKeyToAccount(getEnv('PUBLISHER_2_PK') as `0x${string}`).address,\ ] // This is a random, untrusted address. const IMPOSTER_ORACLE: Address = '0x1234567890123456789012345678901234567890' // --- Helper Functions --- // Helper to decode the oracle data function decodePriceRecord(row: SchemaDecodedItem[]): { price: bigint, timestamp: number } { const val = (field: any) => field?.value?.value ?? field?.value ?? '' return { price: BigInt(val(row[0])), timestamp: Number(val(r[1])), } } /** * Verification Utility * Fetches data for a *single* publisher to verify its origin. */ async function verifyPublisher(sdk: SDK, schemaId: `0x${string}`, publisherAddress: Address) { console.log(`\n--- Verifying Publisher: ${publisherAddress} ---`) try { const data = await sdk.streams.getAllPublisherDataForSchema(schemaId, publisherAddress) if (!data || data.length === 0) { console.log('[VERIFIED] No data found for this publisher.') return } const records = (data as SchemaDecodedItem[][]).map(decodePriceRecord) console.log(`[VERIFIED] Found ${records.length} record(s) cryptographically signed by this publisher:`) records.forEach(record => { console.log(` - Price: ${record.price}, Time: ${new Date(record.timestamp).toISOString()}`) }) } catch (error: any) { console.error(`Error during verification: ${error.message}`) } } // --- Main Execution --- async function main() { const sdk = new SDK({ public: publicClient }) const schemaId = await sdk.streams.computeSchemaId(oraclePriceSchema) if (!schemaId) throw new Error('Could not compute schemaId') console.log('Starting Data Provenance Verification...') console.log(`Schema: oraclePriceSchema (${schemaId})`) // 1. Verify our trusted oracles for (const oracleAddress of TRUSTED_ORACLES) { await verifyPublisher(sdk, schemaId, oracleAddress) } // 2. Verify the imposter // This will securely return NO data, even if the imposter // published data to the same schemaId under their *own* address. await verifyPublisher(sdk, schemaId, IMPOSTER_ORACLE) } main().catch((e) => { console.error(e) process.exit(1) }) Copy # Add to package.json "verify": "ts-node src/scripts/verifyOrigin.ts" # Run it npm run verify Copy Starting Data Provenance Verification... Schema: oraclePriceSchema (0x...) --- Verifying Publisher: 0xPublisher1Address... --- [VERIFIED] Found 2 record(s) cryptographically signed by this publisher: - Price: 3200, Time: 2025-10-31T12:30:00.000Z - Price: 3201, Time: 2025-10-31T12:31:00.000Z --- Verifying Publisher: 0xPublisher2Address... --- [VERIFIED] Found 1 record(s) cryptographically signed by this publisher: - Price: 3199, Time: 2025-10-31T12:30:30.000Z --- Verifying Publisher: 0x1234567890123456789012345678901234567890 --- [VERIFIED] No data found for this publisher. --- # Tutorials | Somnia Docs circle-exclamation **Somnia Reactivity is currently only available on TESTNET** [PreviousSubscription managementchevron-left](https://docs.somnia.network/developer/reactivity/tooling/subscription-management) [NextWildcard Off-Chain Reactivity Tutorialchevron-right](https://docs.somnia.network/developer/reactivity/tutorials/wildcard-off-chain-reactivity-tutorial) Last updated 1 month ago --- # Development Frameworks | Somnia Docs Developing a robust decentralized application (dApp) requires a complex stack of technologies. Software frameworks streamline this process by bundling essential features or offering flexible plugin architectures to customize your toolkit. These frameworks provide immediate, out-of-the-box utility, including: * Local Environments: Tools to instantly launch a local blockchain instance. * Development Suite: Utilities for compiling and testing smart contracts. * Integrated Frontend: Add-ons that allow for client-side development within the same repository. * Deployment Management: Configurations for connecting to networks (local or public) and deploying contracts. [PreviousSmart Contractschevron-left](https://docs.somnia.network/developer/smart-contracts) [NextLocal Testing and Forkingchevron-right](https://docs.somnia.network/developer/development-frameworks/local-testing-and-forking) Last updated 1 month ago --- # Intersection with Somnia Reactivity | Somnia Docs [hashtag](https://docs.somnia.network/developer/data-streams/concepts/intersection-with-somnia-reactivity#reactivity-background) Reactivity background ----------------------------------------------------------------------------------------------------------------------------------------------------------- For detailed information about reactivity please visit the Reactivity docs: [](https://docs.somnia.network/developer/reactivity#welcome-to-the-somnia-reactivity-docs) Reactivity [hashtag](https://docs.somnia.network/developer/data-streams/concepts/intersection-with-somnia-reactivity#writing-data-events-and-reacting) Writing data, events and reacting ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- When an ERC20 transfer takes place, balance state is updated and an event is emitted. The ERC20 transfer scenario is very common in smart contracts i.e. publishing state and emitting an event (also known as a log). Somnia Data Streams offers you the tooling to do this without the requirements of having to write your own custom Solidity contract. It also allows you to take advantage of existing schemas for publishing data yielding composibility benefits for applications. Example: Copy import { SDK } from "@somnia-chain/streams" import { zeroAddress, erc721Abi } from "viem" // Use WebSocket transport in the public client for subscription tasks // For the SDK instance that executes transactions, stick with htttp const sdk = new SDK({ public: getPublicClient(), wallet: getWalletClient(), }) // Encode view function calls to be executed when an event takes place const ethCalls = [{\ to: "0x23B66B772AE29708a884cca2f9dec0e0c278bA2c",\ data: encodeFunctionData({\ abi: erc721Abi,\ functionName: "balanceOf",\ args: ["0x3dC360e0389683cA0341a11Fc3bC26252b5AF9bA"]\ })\ }] // Start a subsciption const subscription = await sdk.streams.subscribe({ ethCalls, onData: (data) => { const decodedLog = decodeEventLog({ abi: fireworkABI, topics: data.result.topics, data: data.result.data, }); const decodedFunctionResult = decodeFunctionResult({ abi: erc721Abi, functionName: 'balanceOf', data: data.result.simulationResults[0], }); console.log("Decoded event", decodedLog); console.log("Decoded function call result", decodedFunctionResult); } }) // Write data and emit events that will trigger the above callback! const dataStreams = [{\ id,\ schemaId: driverSchemaId,\ data: encodedData\ }] const eventStreams = [{\ id: somniaStreamsEventId,\ argumentTopics,\ data\ }] const setAndEmitEventsTxHash = await sdk.streams.setAndEmitEvents( dataStreams, eventStreams ) Writing data and emitting events will trigger a call back to subscribers that care about a specified event emitted from the Somnia Data Streams protocol (or any contract for that matter) without having the need to poll the chain. It follows the observer pattern meaning push rather than pull which is always a more efficient paradigm. [PreviousSomnia Data vs Event Streamschevron-left](https://docs.somnia.network/developer/data-streams/concepts/somnia-data-vs-event-streams) [NextData Provenance and Verification in Streamschevron-right](https://docs.somnia.network/developer/data-streams/concepts/data-provenance-and-verification-in-streams) Last updated 1 month ago * [Reactivity background](https://docs.somnia.network/developer/data-streams/concepts/intersection-with-somnia-reactivity#reactivity-background) * [Writing data, events and reacting](https://docs.somnia.network/developer/data-streams/concepts/intersection-with-somnia-reactivity#writing-data-events-and-reacting) --- # Deploy with Thirdweb | Somnia Docs [Thirdwebarrow-up-right](https://thirdweb.com/) is a complete web3 development framework that offers everything you need to connect your apps or games to the Somnia network. Its service allows developers to build, manage, and analyze their Web3 applications. This tutorial will guide you through deploying a Smart contract to the Somnia Devnet using Thirdweb’s command-line tool (\`thirdweb deploy\`). Thirdweb simplifies deployment and interaction with smart contracts on Somnia. [hashtag](https://docs.somnia.network/developer/development-frameworks/deploy-with-thirdweb#prerequisites) Prerequisites ----------------------------------------------------------------------------------------------------------------------------- * This guide is not an introduction to Solidity Programming; you are expected to understand Basic Solidity Programming. * To complete this guide, you will need MetaMask installed and the Somnia DevNet added to the list of Networks. If you have yet to install MetaMask, please follow this guide to Connect Your Wallet. * Thirdweb CLI: Install globally with: [hashtag](https://docs.somnia.network/developer/development-frameworks/deploy-with-thirdweb#set-up-the-project) Set Up the Project --------------------------------------------------------------------------------------------------------------------------------------- First, create a new folder for your project and initialize it. [hashtag](https://docs.somnia.network/developer/development-frameworks/deploy-with-thirdweb#write-the-smart-contract) Write the Smart Contract --------------------------------------------------------------------------------------------------------------------------------------------------- You can write your Smart Contract using the [Remix IDE](https://docs.somnia.network/developer/development-frameworks/deploy-with-remixide) to ensure it works. Create a file `**OpenGreeter.sol**` and add the following code: chevron-rightOpenGreeter.sol[hashtag](https://docs.somnia.network/developer/development-frameworks/deploy-with-thirdweb#opengreeter.sol) This is a simple Greeter Smart Contract that any address can call the `**changeName**` function. The Smart Contract has two functions: `**changeName**` - This function allows anyone to change the name variable. It stores the old name, updates the name variable, and emits the `**NameChanged**` event with the old and new names. `**greet**` - This function returns a greeting message that includes the current name. It uses abi.encodePacked to concatenate strings efficiently. [hashtag](https://docs.somnia.network/developer/development-frameworks/deploy-with-thirdweb#deploy-the-smart-contract-using-thirdweb) Deploy the Smart Contract using Thirdweb. ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ First, go to Thirdweb and create a profile. After you have completed the onboarding process, create a Project. ![](https://docs.somnia.network/~gitbook/image?url=https%3A%2F%2F2122549367-files.gitbook.io%2F%7E%2Ffiles%2Fv0%2Fb%2Fgitbook-x-prod.appspot.com%2Fo%2Fspaces%252FkYErT9t3BJtpPfejLO6I%252Fuploads%252Fu9gOGCPPoSlIjfJuC1Zw%252Fthirdweb1.png%3Falt%3Dmedia%26token%3Ddff88e99-035e-4c73-b436-2a466bd44d60&width=768&dpr=3&quality=100&sign=892469d0&sv=2) Go to the project **settings.** ![](https://docs.somnia.network/~gitbook/image?url=https%3A%2F%2F2122549367-files.gitbook.io%2F%7E%2Ffiles%2Fv0%2Fb%2Fgitbook-x-prod.appspot.com%2Fo%2Fspaces%252FkYErT9t3BJtpPfejLO6I%252Fuploads%252FXVHbePUtYbJknW7QN7iR%252Fthirdweb2.png%3Falt%3Dmedia%26token%3Dc25bdefa-6641-49f7-bf6a-f1bf2eb8af96&width=768&dpr=3&quality=100&sign=5fe901d2&sv=2) **C**opy your secret key, and keep it safe. The secret key will be used to deploy the Smart Contract. ![](https://docs.somnia.network/~gitbook/image?url=https%3A%2F%2F2122549367-files.gitbook.io%2F%7E%2Ffiles%2Fv0%2Fb%2Fgitbook-x-prod.appspot.com%2Fo%2Fspaces%252FkYErT9t3BJtpPfejLO6I%252Fuploads%252FTILjmzz4OzFHDnBZK2kv%252FThirdweb3.png%3Falt%3Dmedia%26token%3De6f5c61f-0f70-482a-a4e2-4e92bdc2f1c7&width=768&dpr=3&quality=100&sign=cb59bd8b&sv=2) Go to the terminal and paste the following command to deploy the Smart Contract: Select the `**solc**` option to be `**true**` in the prompts on Terminal. ![](https://docs.somnia.network/~gitbook/image?url=https%3A%2F%2F2122549367-files.gitbook.io%2F%7E%2Ffiles%2Fv0%2Fb%2Fgitbook-x-prod.appspot.com%2Fo%2Fspaces%252FkYErT9t3BJtpPfejLO6I%252Fuploads%252FvXFViXZTG6BkZqil6k4J%252FThirdweb4.png%3Falt%3Dmedia%26token%3D33616b54-cb0a-4613-afff-55a368ca4976&width=768&dpr=3&quality=100&sign=4893772&sv=2) Click the link to open the User Interface in your Browser to deploy the Smart Contract. ![](https://docs.somnia.network/~gitbook/image?url=https%3A%2F%2F2122549367-files.gitbook.io%2F%7E%2Ffiles%2Fv0%2Fb%2Fgitbook-x-prod.appspot.com%2Fo%2Fspaces%252FkYErT9t3BJtpPfejLO6I%252Fuploads%252FXxSQyhItIF4aZ9kOMiT0%252FThirdweb5.png%3Falt%3Dmedia%26token%3D0c09ee56-d67c-41a6-bdbd-cb692880754a&width=768&dpr=3&quality=100&sign=b359cd53&sv=2) Enter an initialName. Select the Network as Somnia Devnet. Check the option to import it to the list of Contracts in your Thirdweb Dashboard. Click on `**Deploy Now**` and approve the Metamask prompts. ![](https://docs.somnia.network/~gitbook/image?url=https%3A%2F%2F2122549367-files.gitbook.io%2F%7E%2Ffiles%2Fv0%2Fb%2Fgitbook-x-prod.appspot.com%2Fo%2Fspaces%252FkYErT9t3BJtpPfejLO6I%252Fuploads%252F0OW54Q2s1vPVFab1onbx%252FThirdweb6.png%3Falt%3Dmedia%26token%3D9f3ee7f7-8c8e-4943-bf10-d87df11ff83b&width=768&dpr=3&quality=100&sign=a719ef39&sv=2) ![](https://docs.somnia.network/~gitbook/image?url=https%3A%2F%2F2122549367-files.gitbook.io%2F%7E%2Ffiles%2Fv0%2Fb%2Fgitbook-x-prod.appspot.com%2Fo%2Fspaces%252FkYErT9t3BJtpPfejLO6I%252Fuploads%252FhqmD2JWR9VzDUU4utLaG%252FThirdweb7.png%3Falt%3Dmedia%26token%3D8cc44893-b356-4a21-89de-ac768441dbb5&width=768&dpr=3&quality=100&sign=68ad98f6&sv=2) Your Smart Contract is deployed, and you can view it on your Thirdweb Dashboard. ![](https://docs.somnia.network/~gitbook/image?url=https%3A%2F%2F2122549367-files.gitbook.io%2F%7E%2Ffiles%2Fv0%2Fb%2Fgitbook-x-prod.appspot.com%2Fo%2Fspaces%252FkYErT9t3BJtpPfejLO6I%252Fuploads%252FgM5hUd8zY2McGaA39jRu%252FThirdweb8.png%3Falt%3Dmedia%26token%3Dc345e5aa-bf2d-491b-bc98-9238a0eeac43&width=768&dpr=3&quality=100&sign=b7b77859&sv=2) Visit the Explorer section to simulate interactions with your deployed Smart Contract and carry out actual transactions. ![](https://docs.somnia.network/~gitbook/image?url=https%3A%2F%2F2122549367-files.gitbook.io%2F%7E%2Ffiles%2Fv0%2Fb%2Fgitbook-x-prod.appspot.com%2Fo%2Fspaces%252FkYErT9t3BJtpPfejLO6I%252Fuploads%252FO8BaWj5O3A1sqAd9UghX%252FThirdweb9.png%3Falt%3Dmedia%26token%3D50477081-fda6-43c1-9d08-7e470e53b33a&width=768&dpr=3&quality=100&sign=cb35813e&sv=2) Congratulations. 🎉 You have deployed your Smart Contract to the Somnia Network using Thirdweb. 🎉 [PreviousDeploy with RemixIDEchevron-left](https://docs.somnia.network/developer/development-frameworks/deploy-with-remixide) [NextDeploy with Foundrychevron-right](https://docs.somnia.network/developer/development-frameworks/deploy-with-foundry) Last updated 1 month ago * [Prerequisites](https://docs.somnia.network/developer/development-frameworks/deploy-with-thirdweb#prerequisites) * [Set Up the Project](https://docs.somnia.network/developer/development-frameworks/deploy-with-thirdweb#set-up-the-project) * [Write the Smart Contract](https://docs.somnia.network/developer/development-frameworks/deploy-with-thirdweb#write-the-smart-contract) * [Deploy the Smart Contract using Thirdweb.](https://docs.somnia.network/developer/development-frameworks/deploy-with-thirdweb#deploy-the-smart-contract-using-thirdweb) Copy npm thirdweb install Copy mkdir somnia-thirdweb-example cd somnia-thirdweb-example Copy // SPDX-License-Identifier: MIT pragma solidity ^0.8.28; contract OpenGreeter { string public name; address public owner; event NameChanged(string oldName, string newName); constructor(string memory _initialName) { name = _initialName; owner = msg.sender; } function changeName(string memory _newName) public { string memory oldName = name; name = _newName; emit NameChanged(oldName, _newName); } function greet() external view returns (string memory) { return string(abi.encodePacked("Hello, ", name, "!")); } } Copy npx thirdweb deploy -k your_secret_key --- # Off-Chain Reactivity: Filtered Subscriptions tutorial | Somnia Docs This tutorial builds on basic off-chain reactivity by adding filters to your WebSocket subscriptions. While wildcard (all events) is great for quick testing and seeing reactivity in action, it's often too verbose for production—flooding logs with irrelevant data. Instead, use filters to target specific emitters or events, making your app more efficient and focused. We'll subscribe to Transfer events from a specific ERC20 contract, include a view call (e.g., balanceOf), and enable `onlyPushChanges` to notify only on state changes. This is ideal for real-time UIs or monitoring without noise. #### [hashtag](https://docs.somnia.network/developer/reactivity/tutorials/off-chain-reactivity-filtered-subscriptions-tutorial#overview) Overview Off-chain subscriptions push filtered events + state via WebSockets to TypeScript apps. Filters reduce volume: * **eventContractSources**: Limit to specific emitter addresses. * **topicOverrides**: Filter by event topics (e.g., keccak256 signatures like Transfer's `0xddf...`). * **onlyPushChanges**: Skip notifications if ethCalls results match the previous one. * **ethCalls**: Optional view calls for bundled state. * **onError**: Handle failures gracefully. No gas costs; runs off-chain. Prerequisites: * Same as wildcard tutorial: Node.js, `npm i @somnia-chain/reactivity viem`. * Know your target contract/event (e.g., ERC20 Transfer). #### [hashtag](https://docs.somnia.network/developer/reactivity/tutorials/off-chain-reactivity-filtered-subscriptions-tutorial#key-objectives) Key Objectives 1. **Set Up Chain and SDK**: Configure viem and initialize SDK. 2. **Define Filters and ETH Calls**: Specify emitters, topics, and views. 3. **Create Filtered Subscription**: Use params for targeted listening. 4. **Decode and Handle Data**: Parse with viem; add error handling. 5. **Run and Optimize**: Test with `onlyPushChanges`. #### [hashtag](https://docs.somnia.network/developer/reactivity/tutorials/off-chain-reactivity-filtered-subscriptions-tutorial#step-1-install-dependencies) Step 1: Install Dependencies #### [hashtag](https://docs.somnia.network/developer/reactivity/tutorials/off-chain-reactivity-filtered-subscriptions-tutorial#step-2-define-the-somnia-chain) Step 2: Define the Somnia Chain (Reuse from wildcard tutorial.) #### [hashtag](https://docs.somnia.network/developer/reactivity/tutorials/off-chain-reactivity-filtered-subscriptions-tutorial#step-3-initialize-the-sdk) Step 3: Initialize the SDK #### [hashtag](https://docs.somnia.network/developer/reactivity/tutorials/off-chain-reactivity-filtered-subscriptions-tutorial#step-4-define-eth-calls-and-filters) Step 4: Define ETH Calls and Filters * **ethCalls**: Query balanceOf on an ERC20. * **eventContractSources**: Array of emitter addresses (e.g., one ERC20 contract). * **topicOverrides**: Hex for event signatures (e.g., Transfer: `0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef`). #### [hashtag](https://docs.somnia.network/developer/reactivity/tutorials/off-chain-reactivity-filtered-subscriptions-tutorial#step-5-create-the-filtered-subscription) Step 5: Create the Filtered Subscription Include `onlyPushChanges: true` to notify only on balance changes. Add `onError` for robustness. #### [hashtag](https://docs.somnia.network/developer/reactivity/tutorials/off-chain-reactivity-filtered-subscriptions-tutorial#step-6-decode-data-in-the-callback) Step 6: Decode Data in the Callback Parse the event (Transfer) and view result (balanceOf). #### [hashtag](https://docs.somnia.network/developer/reactivity/tutorials/off-chain-reactivity-filtered-subscriptions-tutorial#step-7-put-it-all-together-and-run) Step 7: Put It All Together and Run Full script (`main.ts`): Run: `ts-node main.ts`. #### [hashtag](https://docs.somnia.network/developer/reactivity/tutorials/off-chain-reactivity-filtered-subscriptions-tutorial#testing) Testing 1. Run the script. 2. Trigger a Transfer on the filtered contract (e.g., send tokens). 3. See notifications only on relevant events/changes. * If too quiet: Remove `onlyPushChanges` or broaden filters. * Prod Tip: Start with wildcard for debugging, then add filters. #### [hashtag](https://docs.somnia.network/developer/reactivity/tutorials/off-chain-reactivity-filtered-subscriptions-tutorial#troubleshooting) Troubleshooting * **No Notifications?** Verify topics/address (use explorers for keccak). Check WS connection. * **Errors?** Handle in onError; common: Invalid filters or RPC issues. * **Too Many?** Tighten topicOverrides or eventContractSources. #### [hashtag](https://docs.somnia.network/developer/reactivity/tutorials/off-chain-reactivity-filtered-subscriptions-tutorial#next-steps) Next Steps * Multi-emitters: Add more to eventContractSources. * Custom Events: Compute topics for your ABI. * Advanced: Combine with React for live UIs. * Compare to On-Chain: On-Chain Tutorial. [PreviousWildcard Off-Chain Reactivity Tutorialchevron-left](https://docs.somnia.network/developer/reactivity/tutorials/wildcard-off-chain-reactivity-tutorial) [NextSolidity on-chain Reactivity Tutorialchevron-right](https://docs.somnia.network/developer/reactivity/tutorials/solidity-on-chain-reactivity-tutorial) Last updated 1 month ago Copy npm i @somnia-chain/reactivity viem Copy import { defineChain } from 'viem'; const somniaTestnet = defineChain({ id: 50312, name: 'Somnia Testnet', // ... full config as before }); Copy import { SDK } from '@somnia-chain/reactivity'; import { createPublicClient, webSocket } from 'viem'; const publicClient = createPublicClient({ chain: somniaTestnet, transport: webSocket(), }); const sdk = new SDK({ public: publicClient }); Copy import { encodeFunctionData, erc20Abi, keccak256, toHex } from 'viem'; // Example: Transfer topic (keccak256('Transfer(address,address,uint256)')) const transferTopic = keccak256(toHex('Transfer(address,address,uint256)')); const ethCall = { to: '0xExampleERC20Address', // Your target ERC20 data: encodeFunctionData({ abi: erc20Abi, functionName: 'balanceOf', args: ['0xYourWalletAddress'], // Monitor this balance }), }; const filters = { eventContractSources: ['0xExampleERC20Address'], // Filter to this emitter topicOverrides: [transferTopic], // Only Transfer events }; Copy const subscription = await sdk.subscribe({ ethCalls: [ethCall], // Bundled state query ...filters, // From Step 4 onlyPushChanges: true, // Efficient: Skip if balance unchanged onData: (data) => { console.log('Filtered Notification:', data); // Decoding here (Step 6) }, onError: (error) => { console.error('Subscription Error:', error.message); // Retry logic or alert }, }); // Unsubscribe: subscription.unsubscribe(); Copy import { decodeEventLog, decodeFunctionResult } from 'viem'; // Inside onData: const decodedLog = decodeEventLog({ abi: erc20Abi, topics: data.result.topics, data: data.result.data, }); const decodedBalance = decodeFunctionResult({ abi: erc20Abi, functionName: 'balanceOf', data: data.result.simulationResults[0], }); console.log('Decoded Transfer:', decodedLog.args); // { from, to, value } console.log('New Balance:', decodedBalance); Copy // Imports... async function main() { // Chain, client, SDK from Steps 2-3... // ethCall and filters from Step 4... const subscription = await sdk.subscribe({ ethCalls: [ethCall], ...filters, onlyPushChanges: true, onData: (data) => { // Decoding from Step 6... }, onError: (error) => console.error(error), }); // Run indefinitely or unsubscribe on signal } main().catch(console.error); --- # Subscription management | Somnia Docs circle-exclamation **Reactivity is currently only available on TESTNET** Manage your reactivity subscriptions efficiently using the SDK, or do the same via Solidity by directly accessing the Somnia Reactivity Precompile. This covers creation, listing, querying, and cancellation for both off-chain (WebSocket) and on-chain (Solidity) types. Off-chain subscriptions are local to your app; on-chain are chain-managed and require funding (min 32 STT). #### [hashtag](https://docs.somnia.network/developer/reactivity/tooling/subscription-management#off-chain-websocket-subscriptions) Off-Chain (WebSocket) Subscriptions Off-chain subs use WebSockets for push notifications. Management is app-side—no chain queries needed. **Creating a Subscription** Use `sdk.subscribe()` to start listening. Returns an object with `unsubscribe()`. Copy import { SDK, SubscriptionCallback } from '@somnia-chain/reactivity'; const subscription = await sdk.subscribe({ ethCalls: [], // Optional: ETH view calls onData: (data: SubscriptionCallback) => { console.log('Event:', data); }, // Other filters: eventTopics, origin, etc. }); // Store subscription for later management **Unsubscribing** Call the returned method to stop. **Tips** * Track subs in your app state (e.g., array of subscription objects). * No listing/querying via SDK—handle locally as they're not persisted on-chain. #### [hashtag](https://docs.somnia.network/developer/reactivity/tooling/subscription-management#on-chain-solidity-subscriptions) On-Chain (Solidity) Subscriptions On-chain subs invoke handlers via EVM. Managed by the chain; owner must fund. **Subscription Data Structure** **Creating a Subscription** Returns tx hash on success. **Getting Subscription Info** Fetch details by ID. **Cancelling a Subscription** Returns txn hash on success. Only owner can cancel. #### [hashtag](https://docs.somnia.network/developer/reactivity/tooling/subscription-management#best-practices) Best Practices * **Funding**: Ensure owner has 32+ STT; subs pause if low. * **Error Handling**: Always check for Error instances. * **Monitoring**: For on-chain, periodically list and query to monitor status. * **Security**: Use private keys securely; avoid over-provisioning gas. For full SDK reference, see API Reference. ### [hashtag](https://docs.somnia.network/developer/reactivity/tooling/subscription-management#solidity-subscription-management) Solidity Subscription management #### [hashtag](https://docs.somnia.network/developer/reactivity/tooling/subscription-management#creating-subscriptions) Creating Subscriptions Whoever calls the `subscribe` function becomes the owner of the subscription. The owner can be EOA or a smart contract. In either case, the owner is required to hold a miniumum amount of STT and is responsible for paying the gas fees associated with handling events. The `SubscriptionData` struct defines the criteria for the event subscription and how it should be handled: * **eventTopics**: An array of 4 bytes32 values representing the event topics to filter by. Use `bytes32(0)` for wildcards. * **origin**: Filters by the transaction origin (`tx.origin`). Use `address(0)` for any origin. * **caller**: Filters by the message sender (`msg.sender`). Use `address(0)` for any caller. * **emitter**: The address of the contract emitting the event. Use `address(0)` for any emitter. * **handlerContractAddress**: The address of the contract that will be called when a matching event occurs. * **handlerFunctionSelector**: The 4-byte function selector of the method to call on the handler contract. * **priorityFeePerGas**: Additional gas fee paid to validators to prioritize this event handling. This is expressed in nanoSTT (gwei equivalent). * **maxFeePerGas**: The maximum total gas fee (base + priority) the subscriber is willing to pay. This is expressed in nanoSTT (gwei equivalent). * **gasLimit**: The maximum gas that will be provisioned per subscription callback * **isGuaranteed**: If `true`, the event handling is guaranteed to execute, potentially moving to the next block if the current block is full. * **isCoalesced**: If `true`, multiple matching events in the same block can be coalesced into a single handler call (implementation dependent). A subscription can be handled by any smart contract (no special op codes). Additionally, the optional function selector can be used, that is prefixed to the event data when calling the handler contract. **Handling Events** When an event matching the subscription criteria is emitted, the Somnia Reactivity Precompile will invoke the specified handler contract and function. The handler contract can implement a function that matches the `handlerFunctionSelector` specified in the subscription. This function will be called with the event data when a matching event occurs. The owner of the subscription is charged the gas fees specified in the subscription for each event handled. The two fields indicate that the call the to handler has been initialized by the precompile: * **msg.sender**: The Somnia Reactivity Precompile address (`0x0100`). * **tx.origin**: The owner of the subscription. #### [hashtag](https://docs.somnia.network/developer/reactivity/tooling/subscription-management#examples) Examples **1\. Fully On-Chain Subscription** You can create subscriptions directly from another smart contract. This is useful for creating autonomous agents or protocols that react to network activity. **Key Snippet:** [PreviousOn-chain (Solidity)chevron-left](https://docs.somnia.network/developer/reactivity/tooling/on-chain-solidity) [NextTutorialschevron-right](https://docs.somnia.network/developer/reactivity/tutorials) Last updated 6 days ago Copy subscription.unsubscribe(); Copy export type SoliditySubscriptionData = { eventTopics?: Hex[]; // Optional filters origin?: Address; caller?: Address; emitter?: Address; handlerContractAddress: Address; // Required handlerFunctionSelector?: Hex; // Optional override priorityFeePerGas: bigint; maxFeePerGas: bigint; gasLimit: bigint; isGuaranteed: boolean; isCoalesced: boolean; }; export type SoliditySubscriptionInfo = { subscriptionData: SoliditySubscriptionData, owner: Address }; Copy const subData: SoliditySubscriptionData = { handlerContractAddress: '0x123...', priorityFeePerGas: parseGwei('2'), maxFeePerGas: parseGwei('10'), gasLimit: 500_000n, isGuaranteed: true, isCoalesced: false, // Add filters as needed }; const txHash = await sdk.createSoliditySubscription(subData); if (txHash instanceof Error) { console.error(txHash.message); } else { console.log('Created:', txHash); } Copy const info = await sdk.getSubscriptionInfo(123n); // bigint ID if (info instanceof Error) { console.error(info.message); } else { console.log('Info:', info); } Copy const txHash = await sdk.cancelSoliditySubscription(123n); if (txHash instanceof Error) { console.error(txHash.message); } else { console.log('Canceled:', txHash); } Copy ISomniaReactivityPrecompile.SubscriptionData memory subscriptionData = ISomniaReactivityPrecompile.SubscriptionData({ eventTopics: [Transfer.selector, bytes32(0), bytes32(0), bytes32(0)], origin: address(0), caller: address(0), emitter: address(tokenAddress), handlerContractAddress: address(this), handlerFunctionSelector: ISomniaEventHandler.onEvent.selector, priorityFeePerGas: parseGwei('2'), maxFeePerGas: parseGwei('10'), isGuaranteed: true, isCoalesced: false }); uint256 subscriptionId = somniaReactivityPrecompile.subscribe(subscriptionData); --- # Deploy with RemixIDE | Somnia Docs The Somnia mission is to enable the building of mass-consumer real-time applications. As a Developer, you must understand the quick steps to deploy your first Smart Contract on the Somnia Network. This guide will teach you how to connect to and deploy your first Smart Contract to the Somia Network using the Remix IDE. circle-check Somnia Mainnet is LIVE. To deploy on Somnia Mainnet, you will need SOMI Tokens. Please refer to the [guide](https://docs.somnia.network/get-started/getting-started-for-mainnet) on Moving from Testnet to Mainnet. [hashtag](https://docs.somnia.network/developer/development-frameworks/deploy-with-remixide#pre-requisites) Pre-requisites: -------------------------------------------------------------------------------------------------------------------------------- 1. This guide is not an introduction to Solidity Programming; you are expected to understand Basic Solidity Programming. 2. To complete this guide, you will need MetaMask installed and the Somnia Network added to the list of Networks. If you have yet to install MetaMask, please follow this guide to [Connect Your Wallet](https://docs.somnia.network/get-started/connect-your-wallet-to-mainnet) . [Remixarrow-up-right](https://remix.ethereum.org/) is an IDE for Smart Contract development, which includes compilation, deployment, testing, and debugging. It makes it easy for developers to create, debug, and deploy Smart Contracts to the Somnia Network. In this example, we will deploy a Greeter Smart contract, where we can update the state of the Contract to say “Hello” + Name. [hashtag](https://docs.somnia.network/developer/development-frameworks/deploy-with-remixide#connect-to-somnia-testnet) Connect to Somnia Testnet ----------------------------------------------------------------------------------------------------------------------------------------------------- Ensure you are logged into your MetaMask, connected to the Somnia Testnet, and have some STT Tokens. [Get STT Tokens](https://docs.somnia.network/get-started/testnet-stt-tokens) from the faucet. [hashtag](https://docs.somnia.network/developer/development-frameworks/deploy-with-remixide#create-the-smart-contract) Create the Smart Contract ----------------------------------------------------------------------------------------------------------------------------------------------------- Go to the Remix IDE and create a new file. Paste the Smart Contract below: [hashtag](https://docs.somnia.network/developer/development-frameworks/deploy-with-remixide#compile-the-smart-contract) Compile the Smart Contract ------------------------------------------------------------------------------------------------------------------------------------------------------- On the left tab, click the “Solidity Compiler” menu item and then the “ Compile Greeter.sol” button. This will compile the Solidity file and convert the Solidity code into machine-readable bytecode. ![](https://docs.somnia.network/~gitbook/image?url=https%3A%2F%2F2122549367-files.gitbook.io%2F%7E%2Ffiles%2Fv0%2Fb%2Fgitbook-x-prod.appspot.com%2Fo%2Fspaces%252FkYErT9t3BJtpPfejLO6I%252Fuploads%252FdKcbUUf1Opv1MdtaSVl0%252Fgreeter1.png%3Falt%3Dmedia%26token%3D016a067e-3187-47dc-a148-6ddbf09fb71c&width=768&dpr=3&quality=100&sign=8fa6d84e&sv=2) [hashtag](https://docs.somnia.network/developer/development-frameworks/deploy-with-remixide#deploy-the-smart-contract) Deploy the Smart Contract ----------------------------------------------------------------------------------------------------------------------------------------------------- The Smart Contract has been created and compiled into ByteCode, and the ABI has also been created. The next step is to deploy the Smart Contract to the Somnia DevNet so that you can perform READ and WRITE operations. On the left tab, click the “Deploy and run transactions” menu item. To deploy the Smart Contract, we will require a wallet connection. In the Environment dropdown, select the option: “Injected Provider - MetaMask”. Then select the MetaMask account where you have STT Tokens. In the “DEPLOY” field, enter a value for the “\_INITIALNAME” variable, and click deploy. When prompted, approve the Contract deployment on your MetaMask. ![](https://docs.somnia.network/~gitbook/image?url=https%3A%2F%2F2122549367-files.gitbook.io%2F%7E%2Ffiles%2Fv0%2Fb%2Fgitbook-x-prod.appspot.com%2Fo%2Fspaces%252FkYErT9t3BJtpPfejLO6I%252Fuploads%252FbZnJ394qflm4jauGRoaT%252FGreeter3.png%3Falt%3Dmedia%26token%3D24e2ccd9-b1de-41a4-aa6b-fa71f0dc72c5&width=768&dpr=3&quality=100&sign=4e167d01&sv=2) Look at the terminal for the response and the deployed Smart Contract address. You can interact with the Smart Contract via the Remix IDE. Send a transaction to change the name. ![](https://docs.somnia.network/~gitbook/image?url=https%3A%2F%2F2122549367-files.gitbook.io%2F%7E%2Ffiles%2Fv0%2Fb%2Fgitbook-x-prod.appspot.com%2Fo%2Fspaces%252FkYErT9t3BJtpPfejLO6I%252Fuploads%252F3nbALtkWIA8XUaKdnsdO%252FGreeter4.png%3Falt%3Dmedia%26token%3D225da565-48df-46bb-ab1b-b056d10e13a4&width=768&dpr=3&quality=100&sign=16f35edd&sv=2) Congratulations. 🎉 You have deployed your first Smart Contract to the Somnia Network. 🎉 [PreviousLocal Testing and Forkingchevron-left](https://docs.somnia.network/developer/development-frameworks/local-testing-and-forking) [NextDeploy with Thirdwebchevron-right](https://docs.somnia.network/developer/development-frameworks/deploy-with-thirdweb) Last updated 1 month ago * [Pre-requisites:](https://docs.somnia.network/developer/development-frameworks/deploy-with-remixide#pre-requisites) * [Connect to Somnia Testnet](https://docs.somnia.network/developer/development-frameworks/deploy-with-remixide#connect-to-somnia-testnet) * [Create the Smart Contract](https://docs.somnia.network/developer/development-frameworks/deploy-with-remixide#create-the-smart-contract) * [Compile the Smart Contract](https://docs.somnia.network/developer/development-frameworks/deploy-with-remixide#compile-the-smart-contract) * [Deploy the Smart Contract](https://docs.somnia.network/developer/development-frameworks/deploy-with-remixide#deploy-the-smart-contract) Copy // SPDX-License-Identifier: MIT pragma solidity ^0.8.22; contract Greeter { string public name; address public owner; event NameChanged(string oldName, string newName); modifier onlyOwner() { require(msg.sender == owner, "Only the owner can perform this action"); _; } constructor(string memory _initialName) { name = _initialName; owner = msg.sender; } function changeName(string memory _newName) external onlyOwner { string memory oldName = name; name = _newName; emit NameChanged(oldName, _newName); } function greet() external view returns (string memory) { return string(abi.encodePacked("Hello, ", name, "!")); } } --- # Security | Somnia Docs [Smart Contract Security 101chevron-right](https://docs.somnia.network/developer/security/smart-contract-security-101) [Audit Checklistchevron-right](https://docs.somnia.network/developer/security/audit-checklist) [Node/Infra Securitychevron-right](https://docs.somnia.network/developer/security/node-infra-security) [Responsible Disclosure Policychevron-right](https://docs.somnia.network/developer/security/responsible-disclosure-policy) [PreviousBuilding a Simple DEX on Somniachevron-left](https://docs.somnia.network/developer/building-dapps/example-applications/building-a-simple-dex-on-somnia) [NextSmart Contract Security 101chevron-right](https://docs.somnia.network/developer/security/smart-contract-security-101) --- # Deployment and Production | Somnia Docs [Go-Live Checklistchevron-right](https://docs.somnia.network/developer/deployment-and-production/go-live-checklist) [Explorer API Health and Monitoringchevron-right](https://docs.somnia.network/developer/deployment-and-production/explorer-api-health-and-monitoring) [Somnia Gas Differences To Ethereumchevron-right](https://docs.somnia.network/developer/deployment-and-production/somnia-gas-differences-to-ethereum) [Ecosystemchevron-right](https://docs.somnia.network/developer/deployment-and-production/ecosystem) [Support and Communitychevron-right](https://docs.somnia.network/developer/deployment-and-production/support-and-community) [PreviousResponsible Disclosure Policychevron-left](https://docs.somnia.network/developer/security/responsible-disclosure-policy) [NextGo-Live Checklistchevron-right](https://docs.somnia.network/developer/deployment-and-production/go-live-checklist) --- # Ecosystem | Somnia Docs [Ecosystem Showcasechevron-right](https://docs.somnia.network/developer/deployment-and-production/ecosystem/ecosystem-showcase) [Ecosystem Toolschevron-right](https://docs.somnia.network/developer/deployment-and-production/ecosystem/ecosystem-tools) [PreviousSomnia Gas Differences To Ethereumchevron-left](https://docs.somnia.network/developer/deployment-and-production/somnia-gas-differences-to-ethereum) [NextEcosystem Showcasechevron-right](https://docs.somnia.network/developer/deployment-and-production/ecosystem/ecosystem-showcase) --- # Solidity on-chain Reactivity Tutorial | Somnia Docs This tutorial guides you through building on-chain reactivity on Somnia. You'll create a smart contract that reacts to events from other contracts automatically—invoked by chain validators. Subscriptions trigger handler logic directly in the EVM. ### [hashtag](https://docs.somnia.network/developer/reactivity/tutorials/solidity-on-chain-reactivity-tutorial#overview) Overview On-chain reactivity lets Solidity contracts "subscribe" to events emitted by other contracts. When an event fires, your handler contract gets called with the event data, enabling automated business logic like auto-swaps or updates. This is powered by Somnia's native push mechanism, funded by a minimum SOM balance (currently 32 SOM) held by the subscription owner. Key benefits: * Atomic: Event + state from the same block (state reads have to be handled by your own contract unlike off-chain subscriptions). * Decentralized: Runs on-chain without off-chain servers removing liveness assumptions. * Efficient: Pay only for invocations via gas params. Prerequisites: * Solidity basics (e.g., Remix, Hardhat, or Foundry). * Somnia testnet wallet with 32+ SOM (faucet: [https://docs.somnia.network/developer/network-infoarrow-up-right](https://docs.somnia.network/developer/network-info) ). * TypeScript SDK for subscription management (install: `npm i @somnia-chain/reactivity`) or manage the subscription directly with the precompile contract on-chain #### [hashtag](https://docs.somnia.network/developer/reactivity/tutorials/solidity-on-chain-reactivity-tutorial#key-objectives) Key Objectives 1. **Create a SomniaEventHandler Contract**: Inherit from the abstract contract and override `_onEvent` virtual function with your logic. 2. **Deploy the Handler**: Use your tool of choice (e.g., Remix or Hardhat). 3. **Create a Subscription**: Use the SDK to set up and fund the sub (owner must hold min SOM) or setup the subscription in Solidity. 4. **Handle Callbacks**: The chain invokes your handler on matching filters based on subscription configuration. #### [hashtag](https://docs.somnia.network/developer/reactivity/tutorials/solidity-on-chain-reactivity-tutorial#step-1-install-dependencies) Step 1: Install Dependencies Install the reactivity contracts package for the `SomniaEventHandler` abstract contract: This provides the interface to import. (Public Foundry repo coming soon for easier Forge integration.) #### [hashtag](https://docs.somnia.network/developer/reactivity/tutorials/solidity-on-chain-reactivity-tutorial#step-2-create-the-handler-contract) Step 2: Create the Handler Contract Inherit from `SomniaEventHandler` and implement `_onEvent`. This is where your business logic goes—e.g., update state or call other contracts. Example: A simple handler that logs or reacts to any event (wildcard). * **Customization**: Filter in the subscription (Step 4) or add checks in `_onEvent` (e.g., if `emitter == specificAddress`). * **Warnings**: Keep gas usage low; handlers run in validator context. Test for reentrancy. #### [hashtag](https://docs.somnia.network/developer/reactivity/tutorials/solidity-on-chain-reactivity-tutorial#step-3-deploy-the-handler) Step 3: Deploy the Handler * **Using Remix**: Paste code, compile, deploy to Somnia testnet RPC ([https://docs.somnia.network/developer/network-infoarrow-up-right](https://docs.somnia.network/developer/network-info) ). * **Using Hardhat**: Set up a project, add the contract, and deploy script. Example Hardhat deploy script (`scripts/deploy.ts`): Run: `npx hardhat run scripts/deploy.ts --network somniaTestnet` (configure networks in `hardhat.config.ts`). Note the deployed address (e.g., `0x123...`). #### [hashtag](https://docs.somnia.network/developer/reactivity/tutorials/solidity-on-chain-reactivity-tutorial#step-4-create-and-manage-the-subscription) Step 4: Create and Manage the Subscription Use the TypeScript SDK to create the subscription. The caller becomes the owner and must hold 32+ SOM. * **Funding**: Chain enforces min SOM; top up if needed. * **Filters:** See API reference for more filters that can be passed to createSoliditySubscription #### [hashtag](https://docs.somnia.network/developer/reactivity/tutorials/solidity-on-chain-reactivity-tutorial#step-5-test-the-callback) Step 5: Test the Callback 1. Deploy an emitter contract that emits events (e.g., simple ERC20 with Transfer). 2. Trigger an event (e.g., transfer tokens). 3. Check your handler: Use explorers or logs to see `_onEvent` executed (e.g., your `ReactedToEvent` emitted). #### [hashtag](https://docs.somnia.network/developer/reactivity/tutorials/solidity-on-chain-reactivity-tutorial#troubleshooting) Troubleshooting * **No Invocation?** Verify sub ID (via `sdk.getSubscriptionInfo`), filters match, and balance funded. * **Gas Errors?** Increase `gasLimit` or optimize handler. * **Cancel**: `await sdk.cancelSoliditySubscription(subId);` (get ID from listing). #### [hashtag](https://docs.somnia.network/developer/reactivity/tutorials/solidity-on-chain-reactivity-tutorial#next-steps) Next Steps * Add filters for targeted reactivity. * Integrate with DeFi/NFT logic. * Explore hybrid: Off-chain monitoring + on-chain actions. [PreviousOff-Chain Reactivity: Filtered Subscriptions tutorialchevron-left](https://docs.somnia.network/developer/reactivity/tutorials/off-chain-reactivity-filtered-subscriptions-tutorial) [NextCron subscriptions via SDKchevron-right](https://docs.somnia.network/developer/reactivity/tutorials/cron-subscriptions-via-sdk) Last updated 1 day ago Copy npm i @somnia-chain/reactivity-contracts Copy pragma solidity ^0.8.20; import { SomniaEventHandler } from "@somnia-chain/reactivity-contracts/contracts/SomniaEventHandler.sol"; contract MyEventHandler is SomniaEventHandler { event ReactedToEvent(address emitter, bytes32 topic); function _onEvent( address emitter, bytes32[] calldata eventTopics, bytes calldata data ) internal override { // Your business logic here // Example: Emit a new event or update storage emit ReactedToEvent(emitter, eventTopics[0]); // Be cautious: Avoid reentrancy or infinite loops (e.g., don't emit events that trigger this handler) } } Copy import { ethers } from "hardhat"; async function main() { const Handler = await ethers.getContractFactory("MyEventHandler"); const handler = await Handler.deploy(); await handler.deployed(); console.log("Handler deployed to:", handler.address); } main().catch((error) => { console.error(error); process.exitCode = 1; }); Copy import { SDK } from '@somnia-chain/reactivity'; import { somniaTestnet } from 'viem/chains'; import { privateKeyToAccount } from 'viem/accounts'; import { createPublicClient, createWalletClient, http } from 'viem'; // Initialize SDK with the required clients const sdk = new SDK({ public: createPublicClient({ chain: somniaTestnet, transport: http() }), wallet: createWalletClient({ account: privateKeyToAccount(process.env.PRIVATE_KEY), chain: somniaTestnet, transport: http(), }) }); const subData = { handlerContractAddress: '0xYourDeployedHandlerAddress', priorityFeePerGas: parseGwei('2'), maxFeePerGas: parseGwei('10'), gasLimit: 500_000n, // Adjust based on handler complexity isGuaranteed: true, // Retry on failure isCoalesced: false, // One call per event // Optional filters: eventTopics: ['0x...'], emitter: '0xTargetContract' }; const txHash = await sdk.createSoliditySubscription(subData); if (txHash instanceof Error) { console.error('Creation failed:', txHash.message); } else { console.log('Subscription created! Tx:', txHash); } --- # Tokens and NFTs | Somnia Docs Tokens and NFTs form the backbone of value and ownership on Somnia. This section walks you through how to **create, deploy, and interact** with ERC-20, ERC-721, and ERC-1155 contracts on the Somnia Network. You’ll learn how to: * Deploy fungible tokens (ERC-20) and configure supply and minting logic * Create and manage NFTs (ERC-721 / ERC-1155) for digital assets and collectibles * Integrate token interactions into your dApps and subgraphs * Understand token standards and best practices for gas optimization > Whether you’re launching a governance token, building an NFT marketplace, or gamifying user engagement, Somnia’s speed and scalability make asset creation seamless. [PreviousBuilding DAppschevron-left](https://docs.somnia.network/developer/building-dapps) [NextCreate ERC20 Tokenschevron-right](https://docs.somnia.network/developer/building-dapps/tokens-and-nfts/create-erc20-tokens) Last updated 1 month ago --- # Audit Checklist | Somnia Docs This Self-Review Audit Checklist is the mandatory, internal quality assurance process that every smart contract must undergo before deployment to any public or private blockchain environment. Its purpose is to catch common, critical, and complex security vulnerabilities early, significantly reducing the risk of exploits, financial loss, and costly post-deployment fixes. This process consists of two primary phases: a Manual Pre-Deployment Checklist and Automated Static Analysis Tooling. [hashtag](https://docs.somnia.network/developer/security/audit-checklist#phase-1-manual-pre-deployment-checklist) Phase 1: Manual Pre-Deployment Checklist --------------------------------------------------------------------------------------------------------------------------------------------------------------- The development team must manually review and verify that the contract adheres to the following security, logic, and best-practice requirements. **1.1. Security Vulnerability Checks** Item Requirement Verification Steps **Reentrancy Protection** All functions that send Ether or tokens to external addresses must follow the Checks-Effects-Interactions pattern. Verify that state variables are updated before any external calls are made. Use `transfer`/`send` methods or reentrancy guards where necessary. **Access Control** Critical state-changing functions (e.g., `setOwner`, `pause`, `upgrade`, `mint`) must be guarded by proper access modifiers (e.g., `onlyOwner`, `onlyRole`). Check that function visibility is correctly set (e.g., `internal`, `external`, `private`). **Integer Overflow/Underflow** All arithmetic operations, especially those based on user input, must be safe. For Solidity >= 0.8.0, verify the compiler's default overflow checks are not disabled. For older versions, ensure the use of SafeMath libraries. **Denial-of-Service (DoS)** Operations must not iterate over unbounded arrays or map sizes that could be arbitrarily inflated by a malicious user, leading to excessive gas costs. Check all loops to ensure iteration counts are fixed or restricted. **External Call Security** All interactions with unknown or untrusted external contracts are handled safely. Ensure that results of external calls are checked and fail gracefully if necessary. Use call wrappers to limit reentrancy risk. **Visibility** State variables and functions intended for internal use must be declared `private` or `internal`. Review all function and variable declarations for accidental public exposure. **1.2. Logic and Functional Checks** Item Requirement Verification Steps **Functional Specification** The contract logic precisely matches the intended business logic and all requirements documented in the functional specification. Verify contract against all use cases and edge cases defined in the project scope. **State Transitions** The contract's state (e.g., token balances, operational phase) transitions correctly and predictably. Trace critical functions (`transfer`, `claim`, `lock`) to ensure state variables update correctly. **Error Handling** All potential failure points are handled gracefully with clear, descriptive error messages. Ensure that `require()` or `revert()` statements are used everywhere necessary and that custom error codes are defined and leveraged. **Event Emission** All critical state changes and value transfers must emit an appropriate `Event` to allow for off-chain monitoring, indexing, and UI responsiveness. Verify that an `Event` is emitted for every action that changes a user-facing state. [hashtag](https://docs.somnia.network/developer/security/audit-checklist#phase-2-automated-static-analysis-tools) Phase 2: Automated Static Analysis Tools --------------------------------------------------------------------------------------------------------------------------------------------------------------- Static analysis is a mandatory, automated review step that must be completed using approved tools before deployment. **2.1. Mandatory Tooling** The following static analysis tools must be executed against the final version of the smart contract code: Tool Purpose Output Review Requirement [**Slither**arrow-up-right](https://github.com/crytic/slither) Detects various security vulnerabilities (e.g., reentrancy, unprotected calls, misuses of `msg.sender`) and code optimization issues. Must be executed with all security and efficiency detectors enabled. [**Mythril**arrow-up-right](https://github.com/ConsenSysDiligence/mythril) Performs symbolic execution to find potential execution paths that could lead to vulnerabilities (e.g., integer overflows, assertion failures). Must be run using its security analysis modes (e.g., full scan, execution path analysis). [**Solhint**arrow-up-right](https://github.com/protofire/solhint) Enforces code style and security best practices, ensuring clean and maintainable code. Must pass all configured security and style rulesets without warnings. **2.2. Warning Triage and Sign-off** chevron-rightHigh/Critical Severity Warnings[hashtag](https://docs.somnia.network/developer/security/audit-checklist#high-critical-severity-warnings) Any issue categorized as High or Critical by the static analysis tools must be fixed immediately. The deployment process cannot proceed until these are resolved and the tools run clean. chevron-rightMedium/Low Severity Warnings[hashtag](https://docs.somnia.network/developer/security/audit-checklist#medium-low-severity-warnings) These must be reviewed by a lead developer. They must either be: * Fixed: If the issue is a genuine vulnerability or best practice deviation. * Justified: If the tool's warning is a false positive or the logic is intentionally implemented in that manner, a clear, documented justification must be added to a dedicated "Audit Waivers" log. chevron-rightReport Retention[hashtag](https://docs.somnia.network/developer/security/audit-checklist#report-retention) The final, clean static analysis reports must be saved and archived as part of the pre-deployment documentation. circle-check **Congratulations!** You've mastered the audit checklist for both manual and automatic process. Continue to learn prevention strategies and secure coding patterns. [PreviousSmart Contract Security 101chevron-left](https://docs.somnia.network/developer/security/smart-contract-security-101) [NextNode/Infra Securitychevron-right](https://docs.somnia.network/developer/security/node-infra-security) Last updated 1 month ago * [Phase 1: Manual Pre-Deployment Checklist](https://docs.somnia.network/developer/security/audit-checklist#phase-1-manual-pre-deployment-checklist) * [Phase 2: Automated Static Analysis Tools](https://docs.somnia.network/developer/security/audit-checklist#phase-2-automated-static-analysis-tools) --- # OnRamps | Somnia Docs OnRamps make it easy for users to move from the traditional financial system into the Somnia ecosystem by purchasing tokens directly with their local currency. This section covers how to integrate **Banxa**, Somnia’s trusted onramping service, to enable seamless and secure fiat-to-crypto transactions within your dApps. You’ll learn how to: * Embed Banxa’s checkout flow in your application * Support multiple payment methods (credit/debit card, bank transfer, etc.) * Customize the onramp experience to match your app’s branding * Streamline user onboarding with a direct path from fiat to onchain activity > Banxa provides developers and users with a regulated, global, and developer-friendly solution for accessing the Somnia network — making your application more inclusive and easier to adopt. [PreviousAuthenticating with RainbowKitchevron-left](https://docs.somnia.network/developer/building-dapps/wallet-integration-and-auth/authenticating-with-rainbowkit) [NextBuy SOMI Using Banxa Checkoutchevron-right](https://docs.somnia.network/developer/building-dapps/onramps/buy-somi-using-banxa-checkout) Last updated 1 month ago --- # Deploy with Foundry | Somnia Docs Somnia empowers developers to build applications for mass adoption. Foundry is a tool for building Smart Contracts for mass adoption, making it easy for developers to create and deploy Smart Contracts to the Somnia Network. [Foundryarrow-up-right](https://book.getfoundry.sh/) is a blazing fast, portable and modular toolkit for EVM application development written in Rust. This guide will teach you how to deploy a “Voting” Smart Contract to the Somia Network using Foundry. circle-check Somnia Mainnet is LIVE. To deploy on Somnia Mainnet, you will need SOMI Tokens. Please refer to the [guide](https://docs.somnia.network/get-started/getting-started-for-mainnet) on Moving from Testnet to Mainnet. [hashtag](https://docs.somnia.network/developer/development-frameworks/deploy-with-foundry#pre-requisites) Pre-requisites: ------------------------------------------------------------------------------------------------------------------------------- 1. This guide is not an introduction to Solidity Programming; you are expected to understand Basic Solidity Programming. 2. To complete this guide, you will need MetaMask installed and the Somnia Network added to the list of Networks. If you have yet to install MetaMask, please follow this guide to [Connect Your Wallet](https://docs.somnia.network/get-started/connect-your-wallet-to-mainnet) . 3. Foundry is installed and set up on your local machine. See [Guidearrow-up-right](https://getfoundry.sh/) [hashtag](https://docs.somnia.network/developer/development-frameworks/deploy-with-foundry#initialise-foundry-project) Initialise Foundry Project ------------------------------------------------------------------------------------------------------------------------------------------------------ To start a new project with Foundry, run the command: Copy forge init BallotVoting This creates a new directory `hello_foundry` from the default template. Open `BallotVoting` directory, and the open the `src` directory where you will find a default `Counter.sol` solidity file. Delete the `Counter.sol` file. [hashtag](https://docs.somnia.network/developer/development-frameworks/deploy-with-foundry#create-the-smart-contract) Create the Smart Contract ---------------------------------------------------------------------------------------------------------------------------------------------------- Create a new file inside the `src` directory and name it `BallotVoting.sol` and paste the following code: [hashtag](https://docs.somnia.network/developer/development-frameworks/deploy-with-foundry#compile-the-smart-contract) Compile the Smart Contract ------------------------------------------------------------------------------------------------------------------------------------------------------ Compiling the Smart Contract will convert the Solidity code into machine-readable bytecode. To compile the Smart Contract, run the command: It will return the response: You can learn more about parsing arguments using flags by reading the [Foundry bookarrow-up-right](https://book.getfoundry.sh/reference/forge/forge-build) . [hashtag](https://docs.somnia.network/developer/development-frameworks/deploy-with-foundry#deploy-contract) Deploy Contract. --------------------------------------------------------------------------------------------------------------------------------- Deploying Smart Contracts to the Somnia Network is very straightforward. All you need the RPC URL and the Private Key from an Ethereum address which contains some STT tokens to pay for Gas during deployment. You can get some STT Tokens from the Somnia [Faucetarrow-up-right](https://devnet.somnia.network/) . Follow this [guidearrow-up-right](https://support.metamask.io/managing-my-wallet/secret-recovery-phrase-and-private-keys/how-to-export-an-accounts-private-key/) to get your Private Key on MetaMask. To deploy the Smart Contract, run this command in the terminal: You will see a status response: Copy the Transaction hash and paste it into the Somnia Network [Explorerarrow-up-right](https://somnia-devnet.socialscan.io/) . You will find the deployed Smart Contract address. Congratulations. 🎉 You have deployed your “BallotVoting” Smart Contract to the Somnia Network using Foundry. 🎉 > NOTE: Contract Verification on SomniaScan is COMING SOON! [PreviousDeploy with Thirdwebchevron-left](https://docs.somnia.network/developer/development-frameworks/deploy-with-thirdweb) [NextDeploy with Hardhatchevron-right](https://docs.somnia.network/developer/development-frameworks/deploy-with-hardhat) Last updated 1 month ago * [Pre-requisites:](https://docs.somnia.network/developer/development-frameworks/deploy-with-foundry#pre-requisites) * [Initialise Foundry Project](https://docs.somnia.network/developer/development-frameworks/deploy-with-foundry#initialise-foundry-project) * [Create the Smart Contract](https://docs.somnia.network/developer/development-frameworks/deploy-with-foundry#create-the-smart-contract) * [Compile the Smart Contract](https://docs.somnia.network/developer/development-frameworks/deploy-with-foundry#compile-the-smart-contract) * [Deploy Contract.](https://docs.somnia.network/developer/development-frameworks/deploy-with-foundry#deploy-contract) Copy // SPDX-License-Identifier: MIT pragma solidity ^0.8.28; contract BallotVoting { struct Ballot { string name; string[] options; mapping(uint256 => uint256) votes; mapping(address => bool) hasVoted; bool active; uint256 totalVotes; } uint256 public ballotCount; mapping(uint256 => Ballot) public ballots; event BallotCreated(uint256 indexed ballotId, string name, string[] options); event VoteCast(uint256 indexed ballotId, address indexed voter, uint256 optionIndex); event BallotClosed(uint256 indexed ballotId); function createBallot(string memory name, string[] memory options) public { require(options.length > 1, "Ballot must have at least two options"); ballotCount++; Ballot storage ballot = ballots[ballotCount]; ballot.name = name; ballot.options = options; ballot.active = true; emit BallotCreated(ballotCount, name, options); } function vote(uint256 ballotId, uint256 optionIndex) public { Ballot storage ballot = ballots[ballotId]; require(ballot.active, "This ballot is closed"); require(!ballot.hasVoted[msg.sender], "You have already voted"); require(optionIndex < ballot.options.length, "Invalid option index"); ballot.votes[optionIndex]++; ballot.hasVoted[msg.sender] = true; ballot.totalVotes++; emit VoteCast(ballotId, msg.sender, optionIndex); } function closeBallot(uint256 ballotId) public { Ballot storage ballot = ballots[ballotId]; require(ballot.active, "Ballot is already closed"); ballot.active = false; emit BallotClosed(ballotId); } function getBallotDetails(uint256 ballotId) public view returns ( string memory name, string[] memory options, bool active, uint256 totalVotes ) { Ballot storage ballot = ballots[ballotId]; return (ballot.name, ballot.options, ballot.active, ballot.totalVotes); } function getBallotResults(uint256 ballotId) public view returns (uint256[] memory results) { Ballot storage ballot = ballots[ballotId]; uint256[] memory voteCounts = new uint256[](ballot.options.length); for (uint256 i = 0; i < ballot.options.length; i++) { voteCounts[i] = ballot.votes[i]; } return voteCounts; } } Copy forge build Copy [⠊] Compiling... [⠢] Compiling 27 files with Solc 0.8.28 [⠆] Solc 0.8.28 finished in 2.22s Compiler run successful! Copy forge create --rpc-url https://dream-rpc.somnia.network --private-key PRIVATE_KEY src/BallotVoting.sol:BallotVoting Copy [⠊] Compiling... No files changed, compilation skipped Deployer: 0xb6e4fa6ff2873480590c68D9Aa991e5BB14Dbf03 Deployed to: 0x46639fB6Ce28FceC29993Fc0201Cd5B6fb1b7b16 Transaction hash: 0xb3f8fe0443acae4efdb6d642bbadbb66797ae1dcde2c864d5c00a56302fb9a34 --- # Wildcard Off-Chain Reactivity Tutorial | Somnia Docs This tutorial shows how to set up an off-chain subscription in TypeScript to listen for _all_ events emitted on the Somnia blockchain (wildcard mode). Notifications will push event data plus results from Solidity view calls (e.g., querying contract state like balances) in a single atomic payload. This reduces RPC roundtrips compared to traditional event listening + separate state fetches. We'll use the Reactivity SDK for WebSocket subscriptions and viem for chain setup, ABI handling, and decoding. For familiarity, we'll subscribe to all events but decode a common one (ERC20 Transfer) in the callback. Whilst most developers are unlikely to use reactivity in this way in production, there are scenarios where this will be useful: * Testing reactivity before applying more filters (see our other tutorials) * Building an indexer which scrapes required information from the chain into a secondary database that would serve other applications that want large volumes of historical chain data #### [hashtag](https://docs.somnia.network/developer/reactivity/tutorials/wildcard-off-chain-reactivity-tutorial#overview) Overview Off-chain reactivity uses WebSockets to push notifications to your TypeScript app. Key features: * **Wildcard Listening**: Catch every event without filters. * **Bundled State**: Include ETH view calls executed at the event's block height. * **Real-Time**: Low-latency updates for UIs, backends, or scripts. * **No Gas Costs**: Off-chain, so free per notification (after setup). This enables reactive apps like live dashboards or automated alerts. Prerequisites: * Node.js 20+ * Somnia testnet access (RPC: https://dream-rpc.somnia.network) * Install dependencies: `npm i @somnia-chain/reactivity viem` #### [hashtag](https://docs.somnia.network/developer/reactivity/tutorials/wildcard-off-chain-reactivity-tutorial#key-objectives) Key Objectives 1. **Set Up the Chain and SDK**: Configure viem for Somnia Testnet and initialize the SDK. 2. **Define ETH Calls**: Specify view functions to run on events (e.g., balanceOf). 3. **Create the Subscription**: Start a wildcard WebSocket sub with a data callback. 4. **Decode Notifications**: Use viem to parse event logs and function results. 5. **Run and Test**: Handle incoming data in real-time. #### [hashtag](https://docs.somnia.network/developer/reactivity/tutorials/wildcard-off-chain-reactivity-tutorial#step-1-install-dependencies) Step 1: Install Dependencies #### [hashtag](https://docs.somnia.network/developer/reactivity/tutorials/wildcard-off-chain-reactivity-tutorial#step-2-define-the-somnia-chain) Step 2: Define the Somnia Chain Use viem's `defineChain` to configure the testnet. #### [hashtag](https://docs.somnia.network/developer/reactivity/tutorials/wildcard-off-chain-reactivity-tutorial#step-3-initialize-the-sdk) Step 3: Initialize the SDK Create a public client with WebSocket transport and pass it to the SDK. #### [hashtag](https://docs.somnia.network/developer/reactivity/tutorials/wildcard-off-chain-reactivity-tutorial#step-4-define-eth-calls) Step 4: Define ETH Calls Specify view calls to execute when events emit. Here, we query an ERC721 balanceOf (adjust addresses as needed). #### [hashtag](https://docs.somnia.network/developer/reactivity/tutorials/wildcard-off-chain-reactivity-tutorial#step-5-create-the-wildcard-subscription) Step 5: Create the Wildcard Subscription Subscribe with `ethCalls` and an `onData` callback. Omit filters for wildcard (all events). * **Unsubscribe Later**: `subscription.unsubscribe();` #### [hashtag](https://docs.somnia.network/developer/reactivity/tutorials/wildcard-off-chain-reactivity-tutorial#step-6-decode-data-in-the-callback) Step 6: Decode Data in the Callback Use viem to decode the event log and function results. For example, assuming an ERC20 Transfer event (use `erc20Abi` from viem). * **Notes**: `data.result` contains `topics`, `data` (event payload), and `simulationResults` (view call outputs). Handle errors if decoding fails (e.g., non-matching ABI). #### [hashtag](https://docs.somnia.network/developer/reactivity/tutorials/wildcard-off-chain-reactivity-tutorial#step-7-put-it-all-together-and-run) Step 7: Put It All Together and Run Full script (`main.ts`): Run: `ts-node main.ts` (install ts-node if needed). #### [hashtag](https://docs.somnia.network/developer/reactivity/tutorials/wildcard-off-chain-reactivity-tutorial#testing) Testing 1. Run the script. 2. Trigger events on Somnia Testnet (e.g., transfer ERC20 tokens via a wallet). 3. Watch console for decoded notifications. * If no events: Deploy a test contract and emit manually. #### [hashtag](https://docs.somnia.network/developer/reactivity/tutorials/wildcard-off-chain-reactivity-tutorial#troubleshooting) Troubleshooting * **No Data?** Ensure WebSocket RPC is connected; check filters (none for wildcard). * **Decoding Errors?** Verify ABI matches the event/contract. * **Connection Issues?** Use HTTP fallback if WS fails, but prefer WS for reactivity. #### [hashtag](https://docs.somnia.network/developer/reactivity/tutorials/wildcard-off-chain-reactivity-tutorial#next-steps) Next Steps * Add filters (e.g., `eventTopics: ['0xddf...']` for Transfer keccak). * Integrate with React (future hooks). * Handle multiple ethCalls/decodes. * Explore on-chain version: On-Chain Tutorial. [PreviousTutorialschevron-left](https://docs.somnia.network/developer/reactivity/tutorials) [NextOff-Chain Reactivity: Filtered Subscriptions tutorialchevron-right](https://docs.somnia.network/developer/reactivity/tutorials/off-chain-reactivity-filtered-subscriptions-tutorial) Last updated 1 month ago Copy npm i @somnia-chain/reactivity viem Copy import { defineChain } from 'viem'; const somniaTestnet = defineChain({ id: 50312, name: 'Somnia Testnet', network: 'testnet', nativeCurrency: { decimals: 18, name: 'STT', symbol: 'STT', }, rpcUrls: { default: { http: ['https://dream-rpc.somnia.network'], webSocket: ['ws://api.infra.testnet.somnia.network/ws'], }, public: { http: ['https://dream-rpc.somnia.network'], webSocket: ['ws://api.infra.testnet.somnia.network/ws'], }, }, }); Copy import { SDK } from '@somnia-chain/reactivity'; import { createPublicClient, webSocket } from 'viem'; const publicClient = createPublicClient({ chain: somniaTestnet, transport: webSocket(), }); const sdk = new SDK({ public: publicClient }); Copy import { encodeFunctionData, erc721Abi } from 'viem'; const ethCall = { to: '0x23B66B772AE29708a884cca2f9dec0e0c278bA2c', // Example Somnia ERC721 contract data: encodeFunctionData({ abi: erc721Abi, functionName: 'balanceOf', args: ['0x3dC360e0389683cA0341a11Fc3bC26252b5AF9bA'], // Example owner address }), }; Copy const subscription = await sdk.subscribe({ ethCalls: [ethCall], // Array of calls; add more if needed onData: (data) => { console.log('Raw Notification:', data); // Decoding happens here (Step 6) }, }); Copy import { decodeEventLog, decodeFunctionResult, erc20Abi } from 'viem'; // Inside onData: const decodedLog = decodeEventLog({ abi: erc20Abi, // Or your custom ABI topics: data.result.topics, data: data.result.data, }); const decodedFunctionResult = decodeFunctionResult({ abi: erc721Abi, // Match the call's ABI functionName: 'balanceOf', data: data.result.simulationResults[0], // First call's result }); console.log('Decoded Event:', decodedLog); // e.g., { eventName: 'Transfer', args: { from, to, value } } console.log('Decoded Balance:', decodedFunctionResult); // e.g., 42n Copy // Imports from above... async function main() { // Chain, client, SDK setup from Steps 2-3... // EthCall from Step 4... const subscription = await sdk.subscribe({ ethCalls: [ethCall], onData: (data) => { // Decoding from Step 6... }, }); // Keep running (e.g., for a server) or unsubscribe after testing } main().catch(console.error); --- # Understanding Schemas, Schema IDs, Data IDs, and Publisher | Somnia Docs Somnia Data Streams uses a schema-driven architecture to store and manage blockchain data. Every piece of information stored on the network, whether it’s a chat message, leaderboard score, or todo item, follows a structured schema, is identified by a Schema ID, written with a Data ID, and associated with a Publisher. In this guide, you’ll learn the difference between Schemas and Schema IDs, how Data IDs uniquely identify records, and how Publishers own and manage their data streams. * Schemas define the structure of your data. * Data IDs uniquely identify individual records. * Publisher determines who owns or controls the data stream. By the end, you’ll understand how to organize, reference, and manage your application’s data on Somnia. [hashtag](https://docs.somnia.network/developer/data-streams/concepts/understanding-schemas-schema-ids-data-ids-and-publisher#what-are-schemas) What Are Schemas? ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- A Schema defines the structure and types of the data you want to store onchain. It’s like a blueprint for how your application’s data is encoded, stored, and decoded. A Schema ID, on the other hand, is a unique deterministic hash computed from that schema definition. When you register or compute a schema, the SDK automatically generates a unique hash (Schema ID) that permanently represents that schema definition. A schema describes the structure of your data, much like a table in a relational database defines its columns. #### [hashtag](https://docs.somnia.network/developer/data-streams/concepts/understanding-schemas-schema-ids-data-ids-and-publisher#example-defining-a-schema) Example: Defining a Schema Copy const userSchema = ` uint64 timestamp, string username, string bio, address owner ` This schema tells the Somnia Data Streams system how your data is structured and typed. [hashtag](https://docs.somnia.network/developer/data-streams/concepts/understanding-schemas-schema-ids-data-ids-and-publisher#schema-id-the-unique-identifier) Schema ID: The Unique Identifier ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- A Schema ID is derived from your schema using a hashing algorithm. It uniquely represents this structure onchain, ensuring consistency and integrity. You can compute its Schema ID before even deploying it onchain. Example Output: triangle-exclamation This hash (schemaId) uniquely identifies the schema onchain. If you change even one character in the schema definition, the Schema ID will change. The Schema ID is the hash that ensures the same structure is used everywhere, preventing mismatched or corrupted data. [hashtag](https://docs.somnia.network/developer/data-streams/concepts/understanding-schemas-schema-ids-data-ids-and-publisher#registering-a-schema) Registering a Schema ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- To make the schema usable onchain, it has to be registered by calling the `registerDataSchemas()` method. This ensures other nodes and apps can decode your data correctly: `id` is a string. human human-readable identifier`ignoreExistingSchemas` is for telling the SDK not to worry about already registered schemas. Once registered, any publisher can use this Schema ID to store or retrieve data encoded according to this structure. The schema defines structure. The Schema ID becomes its permanent onchain reference. Concept Database Equivalent Description Schema Table Definition Defines data fields and types Schema ID Table Hash Uniquely identifies that schema definition For instance: `Schema → CREATE TABLE Users (id INT, name TEXT)` `Schema ID → 0x9f3a...a7c (hash of the above definition)` [hashtag](https://docs.somnia.network/developer/data-streams/concepts/understanding-schemas-schema-ids-data-ids-and-publisher#what-are-data-ids) What Are Data IDs? ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ Every record written to Somnia (e.g., a single message, transaction, or post) must have a Data ID, a unique key representing that entry. It uniquely identifies a specific record (or row). The Data ID ensures that: * Each entry can be updated or replaced deterministically. * Developers can reference or fetch a specific record by key. * Duplicate writes can be prevented. #### [hashtag](https://docs.somnia.network/developer/data-streams/concepts/understanding-schemas-schema-ids-data-ids-and-publisher#example-creating-a-data-id) Example: Creating a Data ID A Data ID can be created by hashing a string, typically by combining context and timestamp. Example Output: You can now use this ID to publish structured data to the blockchain. A Data ID ensures every record written is unique and can be referenced or updated deterministically. #### [hashtag](https://docs.somnia.network/developer/data-streams/concepts/understanding-schemas-schema-ids-data-ids-and-publisher#example-writing-data-with-a-schema-and-data-id) Example: Writing Data with a Schema and Data ID Think of a Data ID like a primary key in a SQL table. Data ID (Primary Key) username bio 0x1234abcd... Emmanuel Blockchain Developer If you write another record with the same Data ID, it updates the existing entry rather than duplicating it, thereby maintaining data integrity. `schemaId` defines how to encode/decode the data, and `dataId` identifies which record this is. The data itself is encoded and written to the blockchain [hashtag](https://docs.somnia.network/developer/data-streams/concepts/understanding-schemas-schema-ids-data-ids-and-publisher#what-are-publishers) What Are Publishers? ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- A Publisher is any wallet address that sends data to Somnia Streams. Each publisher maintains its own isolated namespace for all schema-based data it writes. This means: * Data from two different publishers never conflict. * Apps can filter or query data from a specific publisher. * Publishers serve as the data owners for all records they create. #### [hashtag](https://docs.somnia.network/developer/data-streams/concepts/understanding-schemas-schema-ids-data-ids-and-publisher#example-getting-a-publisher-address) Example: Getting a Publisher Address If you’re using a connected wallet, your publisher is automatically derived using the `createWalletClient` from viem: Where `publisher = wallet.account.address` When reading data, you can specify which publisher’s records to fetch: Example Output: This retrieves all data published under that schema by that particular address. Think of Publishers like individual database owners. Each one maintains their own “tables” (schemas) and “records” (data entries) under their unique namespace. Publisher (Wallet) Schema ID Data ID Description 0x123...abc Schema A Data 1 Paul’s todos 0x789...def Schema A Data 2 Emmanuel’s todos [hashtag](https://docs.somnia.network/developer/data-streams/concepts/understanding-schemas-schema-ids-data-ids-and-publisher#putting-it-all-together) Putting It All Together ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- When you publish data on Somnia, three identifiers always work together: Concept Role Example Schema ID Identifies schema hash 0x5e4bce54... Data ID Identifies record 0x75736572... Publisher Identifies sender 0x3dC360e038... These three make your data verifiable, queryable, and uniquely name-spaced across the blockchain. These form the foundation of the Somnia Data Streams architecture: * The Schema tells the system what kind of data this is. * The Schema ID ensures it’s stored consistently across the network. * The Data ID identifies which record this is. * The Publisher records who wrote it. [hashtag](https://docs.somnia.network/developer/data-streams/concepts/understanding-schemas-schema-ids-data-ids-and-publisher#example-use-case-chat-messages) Example Use Case: Chat Messages -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- Here’s how they interact in a real-world scenario, a decentralized chat room. ### [hashtag](https://docs.somnia.network/developer/data-streams/concepts/understanding-schemas-schema-ids-data-ids-and-publisher#step-1-define-schema) Step 1: Define Schema ### [hashtag](https://docs.somnia.network/developer/data-streams/concepts/understanding-schemas-schema-ids-data-ids-and-publisher#step-2-compute-schema-id) Step 2: Compute Schema ID ### [hashtag](https://docs.somnia.network/developer/data-streams/concepts/understanding-schemas-schema-ids-data-ids-and-publisher#step-3-generate-data-id-for-each-message) Step 3: Generate Data ID for each message ### [hashtag](https://docs.somnia.network/developer/data-streams/concepts/understanding-schemas-schema-ids-data-ids-and-publisher#step-4-publish-message) Step 4: Publish Message Now each message: * Conforms to a schema * Is identified by a Schema ID * Is stored under a unique Data ID * Is published by a specific Publisher ### [hashtag](https://docs.somnia.network/developer/data-streams/concepts/understanding-schemas-schema-ids-data-ids-and-publisher#common-pitfalls) Common Pitfalls Mistake Description Fix Reusing Data IDs incorrectly Causes overwrites of older records Use unique IDs like title-timestamp Forgetting to register schema Data won’t decode properly Always call registerDataSchemas() once Mixing publisher data Leads to incomplete reads Query by the correct publisher address and consider aggregating many publishers under a single contract address ### [hashtag](https://docs.somnia.network/developer/data-streams/concepts/understanding-schemas-schema-ids-data-ids-and-publisher#conclusion) Conclusion Now that you understand Schemas, Data IDs, and Publishers, you’re ready to build your own data model for decentralized apps and query live data across multiple publishers [PreviousConceptschevron-left](https://docs.somnia.network/developer/data-streams/concepts) [NextExtending and composing data schemaschevron-right](https://docs.somnia.network/developer/data-streams/concepts/extending-and-composing-data-schemas) Last updated 1 month ago * [What Are Schemas?](https://docs.somnia.network/developer/data-streams/concepts/understanding-schemas-schema-ids-data-ids-and-publisher#what-are-schemas) * [Schema ID: The Unique Identifier](https://docs.somnia.network/developer/data-streams/concepts/understanding-schemas-schema-ids-data-ids-and-publisher#schema-id-the-unique-identifier) * [Registering a Schema](https://docs.somnia.network/developer/data-streams/concepts/understanding-schemas-schema-ids-data-ids-and-publisher#registering-a-schema) * [What Are Data IDs?](https://docs.somnia.network/developer/data-streams/concepts/understanding-schemas-schema-ids-data-ids-and-publisher#what-are-data-ids) * [What Are Publishers?](https://docs.somnia.network/developer/data-streams/concepts/understanding-schemas-schema-ids-data-ids-and-publisher#what-are-publishers) * [Putting It All Together](https://docs.somnia.network/developer/data-streams/concepts/understanding-schemas-schema-ids-data-ids-and-publisher#putting-it-all-together) * [Example Use Case: Chat Messages](https://docs.somnia.network/developer/data-streams/concepts/understanding-schemas-schema-ids-data-ids-and-publisher#example-use-case-chat-messages) * [Step 1: Define Schema](https://docs.somnia.network/developer/data-streams/concepts/understanding-schemas-schema-ids-data-ids-and-publisher#step-1-define-schema) * [Step 2: Compute Schema ID](https://docs.somnia.network/developer/data-streams/concepts/understanding-schemas-schema-ids-data-ids-and-publisher#step-2-compute-schema-id) * [Step 3: Generate Data ID for each message](https://docs.somnia.network/developer/data-streams/concepts/understanding-schemas-schema-ids-data-ids-and-publisher#step-3-generate-data-id-for-each-message) * [Step 4: Publish Message](https://docs.somnia.network/developer/data-streams/concepts/understanding-schemas-schema-ids-data-ids-and-publisher#step-4-publish-message) * [Common Pitfalls](https://docs.somnia.network/developer/data-streams/concepts/understanding-schemas-schema-ids-data-ids-and-publisher#common-pitfalls) * [Conclusion](https://docs.somnia.network/developer/data-streams/concepts/understanding-schemas-schema-ids-data-ids-and-publisher#conclusion) Copy import { SDK } from '@somnia-chain/streams' import { getSdk } from './clients' const sdk = getSdk() const schemaId = await sdk.streams.computeSchemaId(userSchema) console.log("Computed Schema ID:", schemaId) Copy Computed Schema ID: 0x5e4bce54a39b42b5b8a235b5d9e27e7031e39b65d7a42a6e0ac5e8b2c79e17b0 Copy import { zeroBytes32 } from '@somnia-chain/streams' const ignoreExistingSchemas = true await sdk.streams.registerDataSchemas([\ { schemaName: "MySchema", schema: userSchema, parentSchemaId: zeroBytes32 }\ ], ignoreExistingSchemas) Copy import { toHex } from 'viem' const dataId = toHex(`username-${Date.now()}`, { size: 32 }) console.log("Data ID:", dataId) Copy Data ID: 0x757365726e616d652d31373239303239323435 Copy import { SchemaEncoder } from '@somnia-chain/streams' const encoder = new SchemaEncoder(userSchema) const encodedData = encoder.encodeData([\ { name: 'timestamp', value: Date.now().toString(), type: 'uint64' },\ { name: 'username', value: 'Victory', type: 'string' },\ { name: 'bio', value: 'Blockchain Developer', type: 'string' },\ { name: 'owner', value: '0xYourWalletAddress', type: 'address' },\ ]) await sdk.streams.set([\ { id: dataId, schemaId, data: encodedData }\ ]) Copy const { ... createWalletClient, } = require("viem"); const { privateKeyToAccount } = require("viem/accounts"); // Create wallet client const walletClient = createWalletClient({ account: privateKeyToAccount(process.env.PRIVATE_KEY), chain: dreamChain, transport: http(dreamChain.rpcUrls.default.http[0]), }); // Initialize SDK const sdk = new SDK({ ... wallet: walletClient, }); const encodedData = schemaEncoder.encodeData([\ ...\ { name: "sender", value: wallet.account.address, type: "address" },\ ]); Copy const messages = await sdk.streams.getAllPublisherDataForSchema(schemaId, publisherAddress) Copy [\ { timestamp: 1729302920, username: "Victory", bio: "Blockchain Developer" }\ ] Copy const chatSchema = ` uint64 timestamp, bytes32 roomId, string content, string senderName, address sender ` Copy const schemaId = await sdk.streams.computeSchemaId(chatSchema) Copy const dataId = toHex(`${roomName}-${Date.now()}`, { size: 32 }) Copy const encoded = encoder.encodeData([\ { name: 'timestamp', value: Date.now().toString(), type: 'uint64' },\ { name: 'roomId', value: toHex(roomName, { size: 32 }), type: 'bytes32' },\ { name: 'content', value: 'Hello world!', type: 'string' },\ { name: 'senderName', value: 'Victory', type: 'string' },\ { name: 'sender', value: publisherAddress, type: 'address' }\ ]) await sdk.streams.set([{ id: dataId, schemaId, data: encoded }]) --- # Cron subscriptions via SDK | Somnia Docs Starting from `@somnia-chain/[[email protected]](https://docs.somnia.network/cdn-cgi/l/email-protection) `, the typescript SDK introduces two new convenience functions to streamline creating subscriptions for [system-generated events](https://docs.somnia.network/developer/reactivity/system-events) : `BlockTick` and `Schedule`. These functions reduce boilerplate by handling the underlying `SubscriptionData` structure and precompile interactions for you, while still allowing customization of gas fees, guarantees, and other parameters. For a deeper understanding of how these system events work under the hood (including event signatures and behaviors), refer to the [System Events documentation](https://docs.somnia.network/developer/reactivity/system-events) . ### [hashtag](https://docs.somnia.network/developer/reactivity/tutorials/cron-subscriptions-via-sdk#block-tick-subscription) Block Tick Subscription The `BlockTick` event triggers at the start of every block if no specific block number is provided, or at a targeted block if specified. #### [hashtag](https://docs.somnia.network/developer/reactivity/tutorials/cron-subscriptions-via-sdk#using-the-sdk) Using the SDK Use `createOnchainBlockTickSubscription` to set up the subscription with minimal code: Copy import { SDK } from '@somnia-chain/reactivity'; // Assuming you have an instance of SomniaReactivity SDK const reactivity = new SDK(/* your config */); async function setupBlockTick() { try { const txHash = await reactivity.createOnchainBlockTickSubscription({ // Optional: Specify a future block number; omit for every block // blockNumber: BigInt(123456789), handlerContractAddress: '0xYourHandlerContractAddress', // Optional: Override default handler selector (defaults to onEvent) // handlerFunctionSelector: '0xYourSelector', priorityFeePerGas: BigInt(1000000000), // 1 nanoSomi maxFeePerGas: BigInt(20000000000), // 20 nanoSomi gasLimit: BigInt(100000), isGuaranteed: true, // Ensure delivery even if delayed isCoalesced: false // Handle each event separately }); console.log('Subscription created with tx hash:', txHash); } catch (error) { console.error('Error creating subscription:', error); } } setupBlockTick(); This returns the transaction hash on success or an error if the subscription fails. #### [hashtag](https://docs.somnia.network/developer/reactivity/tutorials/cron-subscriptions-via-sdk#equivalent-in-solidity) Equivalent in Solidity For comparison, here's the lower-level Solidity equivalent (as in the core docs): ### [hashtag](https://docs.somnia.network/developer/reactivity/tutorials/cron-subscriptions-via-sdk#schedule-event-one-off-cron-job) Schedule Event (One-Off Cron Job) The `Schedule` event is ideal for one-time future actions. Key notes: * Timestamp must be in the future (at least 12 seconds from n ow). * It's a one-off subscription—automatically deleted after triggering. * Use milliseconds (e.g., via [currentmillis.comarrow-up-right](https://currentmillis.com/) ). #### [hashtag](https://docs.somnia.network/developer/reactivity/tutorials/cron-subscriptions-via-sdk#using-the-sdk-1) Using the SDK Use `scheduleOnchainCronJob` for a simple setup: This returns the transaction hash on success or an error if the subscription fails. #### [hashtag](https://docs.somnia.network/developer/reactivity/tutorials/cron-subscriptions-via-sdk#equivalent-in-solidity-1) Equivalent in Solidity For comparison, here's the lower-level Solidity equivalent (as in the core docs): circle-info These SDK functions default the `emitter` to `SOMNIA_REACTIVITY_PRECOMPILE_ADDRESS` and handle event topics automatically, ensuring your handler only responds to genuine system events. To get started, update your typescript SDK package to `@somnia-chain/[[email protected]](https://docs.somnia.network/cdn-cgi/l/email-protection) ` or later and integrate these functions into your dApp. If you need more advanced customizations (e.g., additional filters like origin or caller), you can still use the full `SoliditySubscriptionData` type directly with the `createSoliditySubscription` SDK function (scheduleOnchainCronJob is calling that internally). See [Solidity on-chain Reactivity Tutorial](https://docs.somnia.network/developer/reactivity/tutorials/solidity-on-chain-reactivity-tutorial) for a tutorial on how to create a regular subscription to any event emitted by any smart contract. [PreviousSolidity on-chain Reactivity Tutorialchevron-left](https://docs.somnia.network/developer/reactivity/tutorials/solidity-on-chain-reactivity-tutorial) [NextGas Configurationchevron-right](https://docs.somnia.network/developer/reactivity/gas-configuration) Last updated 1 month ago * [Block Tick Subscription](https://docs.somnia.network/developer/reactivity/tutorials/cron-subscriptions-via-sdk#block-tick-subscription) * [Schedule Event (One-Off Cron Job)](https://docs.somnia.network/developer/reactivity/tutorials/cron-subscriptions-via-sdk#schedule-event-one-off-cron-job) Copy ISomniaReactivityPrecompile.SubscriptionData memory subscriptionData = ISomniaReactivityPrecompile .SubscriptionData({ eventTopics: [BlockTick.selector, bytes32(0), bytes32(0), bytes32(0)], // Or specify blockNumber in topics[1] emitter: SomniaExtensions.SOMNIA_REACTIVITY_PRECOMPILE_ADDRESS, handlerContractAddress: address(this), handlerFunctionSelector: ISomniaEventHandler.onEvent.selector, /* Add gas params, isGuaranteed, isCoalesced here */ }); // Then call subscribe(subscriptionData) on the precompile Copy import { SDK } from '@somnia-chain/reactivity'; // Assuming you have an instance of SomniaReactivity const reactivity = new SDK(/* your config */); async function setupSchedule() { try { const txHash = await reactivity.scheduleOnchainCronJob({ timestampMs: 1794395471011, // e.g., Nov 11, 2026, 11:11:11.011 handlerContractAddress: '0xYourHandlerContractAddress', // Optional: Override default handler selector (defaults to onEvent) // handlerFunctionSelector: '0xYourSelector', priorityFeePerGas: BigInt(1000000000), // 1 nanoSomi maxFeePerGas: BigInt(20000000000), // 2 nanoSomi gasLimit: BigInt(100000), isGuaranteed: true, // Ensure delivery even if delayed isCoalesced: false // N/A for one-off, but included for consistency }); console.log('Cron job scheduled with tx hash:', txHash); } catch (error) { console.error('Error scheduling cron job:', error); } } setupSchedule(); Copy ISomniaReactivityPrecompile.SubscriptionData memory subscriptionData = ISomniaReactivityPrecompile .SubscriptionData({ eventTopics: [Schedule.selector, bytes32(uint256(1794395471011)), bytes32(0), bytes32(0)], emitter: SomniaExtensions.SOMNIA_REACTIVITY_PRECOMPILE_ADDRESS, handlerContractAddress: address(this), handlerFunctionSelector: ISomniaEventHandler.onEvent.selector, /* Add gas params, isGuaranteed, isCoalesced here */ }); // Then call subscribe(subscriptionData) on the precompile --- # Authenticating with ConnectKit | Somnia Docs In this guide, we'll integrate [ConnectKitarrow-up-right](https://docs.family.co/connectkit) with the Somnia Network in a Next.js application. This will enable users to connect their wallets seamlessly, facilitating interactions with the Somnia blockchain. [hashtag](https://docs.somnia.network/developer/building-dapps/wallet-integration-and-auth/authenticating-with-connectkit#prerequisites) Prerequisites ----------------------------------------------------------------------------------------------------------------------------------------------------------- Before we begin, ensure you have the following: 1. This guide is not an introduction to JavaScript Programming; you are expected to understand JavaScript. 2. To complete this guide, you will need MetaMask installed and the Somnia DevNet added to the list of Networks. If you have yet to install MetaMask, please follow the [Connect Your Wallet guide](https://docs.somnia.network/get-started/connect-your-wallet-to-mainnet) . 3. Familiarity with React and Next.js is assumed. [hashtag](https://docs.somnia.network/developer/building-dapps/wallet-integration-and-auth/authenticating-with-connectkit#create-the-next.js-project) Create the Next.js Project ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- Open your terminal and run the following commands to set up a new Next.js project: Copy npx create-next-app@latest somnia-connectkit cd somnia-connectkit Install the required Dependencies, which are `**wagmi**`, `**viem**`, `**@tanstack/react-query**`, and `**connectkit**`. Run the following command: Copy npm install wagmi viem @tanstack/react-query connectkit [hashtag](https://docs.somnia.network/developer/building-dapps/wallet-integration-and-auth/authenticating-with-connectkit#set-up-providers-in-next.js) Set Up Providers in Next.js --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- We'll set up several providers to manage the application's state and facilitate interactions with the blockchain. Create a `**components**` directory in the app folder. Inside the components directory, create a file named `**ClientProvider.tsx**` with the following content: In the app directory, locate the `**layout.tsx**` file and update it as follows: [hashtag](https://docs.somnia.network/developer/building-dapps/wallet-integration-and-auth/authenticating-with-connectkit#build-the-home-page) Build the Home Page ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- We'll create a simple home page that allows users to connect their wallets and displays their address upon connection. In the app directory, locate the `**page.tsx**` file and update it as follows: To run the application, start the Development Server by running the following command: Open your browser and navigate to `**http://localhost:3000**`. You should see the ConnectKit button, which allows users to connect their wallets to the Somnia network. [hashtag](https://docs.somnia.network/developer/building-dapps/wallet-integration-and-auth/authenticating-with-connectkit#conclusion) Conclusion ----------------------------------------------------------------------------------------------------------------------------------------------------- You've successfully integrated ConnectKit with the Somnia Network in a Next.js application. This setup provides a foundation for building decentralized applications on Somnia, enabling seamless wallet connections and interactions with the Somnia Network. For further exploration, consider adding features such as interacting with smart contracts, displaying user balances, or implementing transaction functionalities. If you encounter any issues or need assistance, join the [Somnia Developer Discordarrow-up-right](https://discord.gg/somnia) . [PreviousAuthenticating with MetaMaskchevron-left](https://docs.somnia.network/developer/building-dapps/wallet-integration-and-auth/authenticating-with-metamask) [NextAuthenticating with Privychevron-right](https://docs.somnia.network/developer/building-dapps/wallet-integration-and-auth/authenticating-with-privy) Last updated 5 months ago * [Prerequisites](https://docs.somnia.network/developer/building-dapps/wallet-integration-and-auth/authenticating-with-connectkit#prerequisites) * [Create the Next.js Project](https://docs.somnia.network/developer/building-dapps/wallet-integration-and-auth/authenticating-with-connectkit#create-the-next.js-project) * [Set Up Providers in Next.js](https://docs.somnia.network/developer/building-dapps/wallet-integration-and-auth/authenticating-with-connectkit#set-up-providers-in-next.js) * [Build the Home Page](https://docs.somnia.network/developer/building-dapps/wallet-integration-and-auth/authenticating-with-connectkit#build-the-home-page) * [Conclusion](https://docs.somnia.network/developer/building-dapps/wallet-integration-and-auth/authenticating-with-connectkit#conclusion) Copy 'use client'; import { WagmiConfig, createConfig } from 'wagmi'; import { ConnectKitProvider, getDefaultConfig } from 'connectkit'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import { somniaTestnet } from 'viem/chains'; const queryClient = new QueryClient(); const config = createConfig( getDefaultConfig({ autoConnect: true, appName: 'Somnia DApp', chains: [somniaTestnet], }) ); export default function ClientProvider({ children }) { return ( {children} ); } Copy import ClientProvider from './components/ClientProvider'; export default function RootLayout({ children }) { return ( Somnia DApp {children} ); } Copy 'use client'; import { useAccount } from 'wagmi'; import { ConnectKitButton } from 'connectkit'; export default function Home() { const { address, isConnected } = useAccount(); return (
Hello, world! {/* Connect Button */}
{/* Show Wallet Address */} {isConnected && (

Connected as: {address}

)}
); } Copy npm run dev --- # Smart Contracts | Somnia Docs Contract Address MultiCallV3 [0x5e44F178E8cF9B2F5409B6f18ce936aB817C5a11arrow-up-right](https://explorer.somnia.network/address/0x5e44F178E8cF9B2F5409B6f18ce936aB817C5a11) WSOMI [0x046EDe9564A72571df6F5e44d0405360c0f4dCabarrow-up-right](https://explorer.somnia.network/token/0x046EDe9564A72571df6F5e44d0405360c0f4dCab) USDC [0x28bec7e30e6faee657a03e19bf1128aad7632a00arrow-up-right](https://explorer.somnia.network/address/0x28BEc7E30E6faee657a03e19Bf1128AaD7632A00) WETH [0x936Ab8C674bcb567CD5dEB85D8A216494704E9D8arrow-up-right](https://explorer.somnia.network/token/0x936Ab8C674bcb567CD5dEB85D8A216494704E9D8) USDT [0x67B302E35Aef5EEE8c32D934F5856869EF428330arrow-up-right](https://explorer.somnia.network/token/0x67B302E35Aef5EEE8c32D934F5856869EF428330) [hashtag](https://docs.somnia.network/developer/smart-contracts#omnichain-somi-deployments) Omnichain SOMI Deployments --------------------------------------------------------------------------------------------------------------------------- Mainnet Address Type BNB Chain [0xa9616e5e23ec1582c2828b025becf3ef610e266farrow-up-right](https://bscscan.com/token/0xa9616e5e23ec1582c2828b025becf3ef610e266f) OFT Base [0x47636b3188774a3E7273D85A537b9bA4Ee7b253arrow-up-right](https://basescan.org/token/0x47636b3188774a3E7273D85A537b9bA4Ee7b2535) OFT Ethereum [0x1B0F6590d21dc02B92ad3A7D00F8884dC4f1aed9arrow-up-right](https://etherscan.io/token/0x1B0F6590d21dc02B92ad3A7D00F8884dC4f1aed9) OFT Somnia [0xC3D4E9Ac47D7f37bB07C2f8355Bb4940DEA3bbC3arrow-up-right](https://explorer.somnia.network/address/0xC3D4E9Ac47D7f37bB07C2f8355Bb4940DEA3bbC3) NativeOFTAdapter [hashtag](https://docs.somnia.network/developer/smart-contracts#layerzero-contracts) LayerZero Contracts ------------------------------------------------------------------------------------------------------------- * `**chainKey**` **:** `somnia` * `**stage**` **:** `mainnet` * `**EID**` **:** `30380` Contract Address endpointV2 (main entrypoint) [0x6F475642a6e85809B1c36Fa62763669b1b48DD5Barrow-up-right](https://explorer.somnia.network/address/0x6F475642a6e85809B1c36Fa62763669b1b48DD5B) sendUln302 [0xC39161c743D0307EB9BCc9FEF03eeb9Dc4802de7arrow-up-right](https://explorer.somnia.network/address/0xC39161c743D0307EB9BCc9FEF03eeb9Dc4802de7) receiveUln302 [0xe1844c5D63a9543023008D332Bd3d2e6f1FE1043arrow-up-right](https://explorer.somnia.network/address/0xC39161c743D0307EB9BCc9FEF03eeb9Dc4802de7) executor [0x4208D6E27538189bB48E603D6123A94b8Abe0A0barrow-up-right](https://explorer.somnia.network/address/0x4208D6E27538189bB48E603D6123A94b8Abe0A0b) deadDVN [0x6788f52439ACA6BFF597d3eeC2DC9a44B8FEE842arrow-up-right](https://explorer.somnia.network/address/0x6788f52439ACA6BFF597d3eeC2DC9a44B8FEE842) [hashtag](https://docs.somnia.network/developer/smart-contracts#oracles) Oracles ------------------------------------------------------------------------------------- ### [hashtag](https://docs.somnia.network/developer/smart-contracts#dia-mainnet-price-feeds) DIA - Mainnet [Price Feeds](https://docs.somnia.network/developer/building-dapps/oracles/dia-price-feeds) Contract Address Oracle [0xbA0E0750A56e995506CA458b2BdD752754CF39C4arrow-up-right](https://explorer.somnia.network/address/0xbA0E0750A56e995506CA458b2BdD752754CF39C4) Gas Wallet [0x3073d2E61ecb6E4BF4273Af83d53eDAE099ea04aarrow-up-right](https://mainnet.somnia.w3us.site/address/0x3073d2E61ecb6E4BF4273Af83d53eDAE099ea04a) USDT [0x936C4F07fD4d01485849ee0EE2Cdcea2373ba267arrow-up-right](https://mainnet.somnia.w3us.site/address/0x936C4F07fD4d01485849ee0EE2Cdcea2373ba267) USDC [0x5D4266f4DD721c1cD8367FEb23E4940d17C83C93arrow-up-right](https://explorer.somnia.network/address/0x5D4266f4DD721c1cD8367FEb23E4940d17C83C93) BTC [0xb12e1d47b0022fA577c455E7df2Ca9943D0152bEarrow-up-right](https://explorer.somnia.network/address/0xb12e1d47b0022fA577c455E7df2Ca9943D0152bE) ARB [0x6a96a0232402c2BC027a12C73f763b604c9F77a6arrow-up-right](https://explorer.somnia.network/address/0x6a96a0232402c2BC027a12C73f763b604c9F77a6) SOL [0xa4a3a8B729939E2a79dCd9079cee7d84b0d96234arrow-up-right](https://mainnet.somnia.w3us.site/address/0xa4a3a8B729939E2a79dCd9079cee7d84b0d96234) WETH [0x4E5A9Ebc4D48d7dB65bCde4Ab9CBBE89Da2Add52arrow-up-right](https://explorer.somnia.network/address/0x4E5A9Ebc4D48d7dB65bCde4Ab9CBBE89Da2Add52) SOMI [0x1f5f46B0DABEf8806a1f33772522ED683Ba64E27arrow-up-right](https://explorer.somnia.network/address/0x1f5f46B0DABEf8806a1f33772522ED683Ba64E27) ### [hashtag](https://docs.somnia.network/developer/smart-contracts#protofire-mainnet-vrf-smart-contracts) Protofire - Mainnet [VRF Smart Contracts](https://docs.somnia.network/developer/building-dapps/oracles/using-verifiable-randomness-vrf) Contract Address VRFV2PlusWrapper [0x606b2B36516AB7479D1445Ec14B6B39B44901bf8arrow-up-right](https://explorer.somnia.network/address/0x606b2B36516AB7479D1445Ec14B6B39B44901bf8) LINK Token [0x0a4Db7035284566F6f676991ED418140dC01A2aaarrow-up-right](https://explorer.somnia.network/address/0x0a4Db7035284566F6f676991ED418140dC01A2aa) LINK/NATIVE oracle [0xEBD41881413dD76F42DF2902ee865099af9099B4arrow-up-right](https://explorer.somnia.network/address/0xEBD41881413dD76F42DF2902ee865099af9099B4) [PreviousBuild a Realtime On-Chain Gamechevron-left](https://docs.somnia.network/developer/data-streams/tutorials/build-a-realtime-on-chain-game) [NextDevelopment Frameworkschevron-right](https://docs.somnia.network/developer/development-frameworks) Last updated 1 month ago * [Omnichain SOMI Deployments](https://docs.somnia.network/developer/smart-contracts#omnichain-somi-deployments) * [LayerZero Contracts](https://docs.somnia.network/developer/smart-contracts#layerzero-contracts) * [Oracles](https://docs.somnia.network/developer/smart-contracts#oracles) * [DIA - Mainnet Price Feeds](https://docs.somnia.network/developer/smart-contracts#dia-mainnet-price-feeds) * [Protofire - Mainnet VRF Smart Contracts](https://docs.somnia.network/developer/smart-contracts#protofire-mainnet-vrf-smart-contracts) --- # Verifying via Explorer | Somnia Docs This documentation provides complete guidance for verifying your smart contracts using our web interface, API endpoints, Hardhat, and Foundry. ### [hashtag](https://docs.somnia.network/developer/development-frameworks/verifying-via-explorer#how-it-works) How It Works Somnia Explorer operates a comprehensive API server that includes **all stable Solidity compiler versions** (excluding nightly builds). Our verification system supports multiple methods to accommodate different development workflows: * Single File Verification: For simple contracts contained in one source file * Multi-Part File Verification: For complex contracts with multiple dependencies * Standard JSON Input: The recommended method with complete compilation metadata * API Verification: Submit verification requests programmatically through REST endpoints * Hardhat Verification: Verify contracts directly from your Hardhat environment using the Somnia Explorer API * Foundry Verification: Verify contracts from the command line with Foundry’s `forge verify-contract` command ### [hashtag](https://docs.somnia.network/developer/development-frameworks/verifying-via-explorer#verification-process) Verification Process 1. Input Processing: We receive your contract information through web interface or API 2. Compilation: Our system compiles your source code using the specified compiler version 3. Response Generation: Verification results are returned with contract details circle-info Recommendation: We strongly recommend using Standard JSON Input as it provides the most comprehensive information about your code, including optimizer settings and complete compilation metadata. ### [hashtag](https://docs.somnia.network/developer/development-frameworks/verifying-via-explorer#id-1.-explorer-ui) 1\. Explorer UI The Somnia Explorer web interface provides an intuitive way to verify your smart contracts. The verification process is designed to be user-friendly while maintaining technical accuracy. #### [hashtag](https://docs.somnia.network/developer/development-frameworks/verifying-via-explorer#getting-started) Getting Started ![](https://docs.somnia.network/~gitbook/image?url=https%3A%2F%2F1861192046-files.gitbook.io%2F%7E%2Ffiles%2Fv0%2Fb%2Fgitbook-x-prod.appspot.com%2Fo%2Fspaces%252FFxD3cqyyFDzHJ0nfCPfy%252Fuploads%252FMXuYXhu4mR2Zugzgqo8e%252Fimage.png%3Falt%3Dmedia%26token%3Dba4b6612-a486-4724-9579-ccf8b8a9b5a9&width=768&dpr=3&quality=100&sign=dc3bbf4b&sv=2) * Navigate to the Verify Contract page on Somnia Explorer * Enter the contract address you want to verify * Select the appropriate compiler type from the available options #### [hashtag](https://docs.somnia.network/developer/development-frameworks/verifying-via-explorer#solidity-single-file) Solidity (Single File) Perfect for simple contracts that don't have external dependencies or complex import structures. ![](https://docs.somnia.network/~gitbook/image?url=https%3A%2F%2F1861192046-files.gitbook.io%2F%7E%2Ffiles%2Fv0%2Fb%2Fgitbook-x-prod.appspot.com%2Fo%2Fspaces%252FFxD3cqyyFDzHJ0nfCPfy%252Fuploads%252FFjPOSJht0mt8ZTyTqPsE%252Fimage%25201.png%3Falt%3Dmedia%26token%3D203de58e-982d-49a4-9682-d9bab270505d&width=768&dpr=3&quality=100&sign=70145639&sv=2) 1 #### [hashtag](https://docs.somnia.network/developer/development-frameworks/verifying-via-explorer#single-file-verification-steps) Single-file verification — Steps * Contract Code: Copy and paste your complete smart contract source code into the text area * Compiler version: Choose the Solidity compiler version used during development * Optimization Settings: * Enable optimization if it was used during compilation (default: disabled) * Set the optimization runs value (default: 200) * EVM Version: Select the appropriate EVM version (default: Prague) * License Type: Specify the open source license type for your contract * Constructor Arguments: Enter constructor parameters if your contract was deployed with initialization arguments * Submit: Click "Verify and Publish" to complete verification #### [hashtag](https://docs.somnia.network/developer/development-frameworks/verifying-via-explorer#solidity-multi-part-files) Solidity (Multi-Part Files) Ideal for complex contracts with multiple source files, libraries, and dependencies. ![](https://docs.somnia.network/~gitbook/image?url=https%3A%2F%2F1861192046-files.gitbook.io%2F%7E%2Ffiles%2Fv0%2Fb%2Fgitbook-x-prod.appspot.com%2Fo%2Fspaces%252FFxD3cqyyFDzHJ0nfCPfy%252Fuploads%252Fg3gvdQDAbnj6gookKVjV%252Fimage%25202.png%3Falt%3Dmedia%26token%3D2d3ad116-1511-46d5-a6c3-3a1aa0ad0ece&width=768&dpr=3&quality=100&sign=e95c30c3&sv=2) 1 #### [hashtag](https://docs.somnia.network/developer/development-frameworks/verifying-via-explorer#multi-file-verification-steps) Multi-file verification — Steps * File Upload: Upload all source files required for compilation * Supported formats: `.sol`, `.json` * Include all imported contracts and dependencies * Compiler version: Choose the Solidity compiler version used during development * Optimization Settings: * Enable optimization if it was used during compilation (default: disabled) * Set the optimization runs value (default: 200) * EVM Version: Select the appropriate EVM version (default: Prague) * License Type: Specify the open source license type for your contract * Constructor Arguments: Enter constructor parameters if your contract was deployed with initialization arguments * Submit: Click "Verify and Publish" to complete verification #### [hashtag](https://docs.somnia.network/developer/development-frameworks/verifying-via-explorer#solidity-standard-json-input) Solidity (Standard-JSON-Input) The most comprehensive verification method, recommended for production contracts. ![](https://docs.somnia.network/~gitbook/image?url=https%3A%2F%2F1861192046-files.gitbook.io%2F%7E%2Ffiles%2Fv0%2Fb%2Fgitbook-x-prod.appspot.com%2Fo%2Fspaces%252FFxD3cqyyFDzHJ0nfCPfy%252Fuploads%252FjX91luBTLBEvbDNa0pba%252Fimage%25203.png%3Falt%3Dmedia%26token%3Dbb65a698-3352-432b-b939-86c0c1a8a879&width=768&dpr=3&quality=100&sign=e3311242&sv=2) 1 #### [hashtag](https://docs.somnia.network/developer/development-frameworks/verifying-via-explorer#standard-json-input-steps) Standard JSON input — Steps * JSON File Upload: Upload your complete Standard JSON input file * Supported formats: `.json` * Should include all compilation settings, optimizer configuration, and source mappings * Compiler version: Choose the Solidity compiler version used during development * License Type: Specify the open source license type for your contract * Constructor Arguments: Enter constructor parameters if your contract was deployed with initialization arguments * Submit: Click "Verify and Publish" to complete verification ### [hashtag](https://docs.somnia.network/developer/development-frameworks/verifying-via-explorer#id-2.-api-verification) 2\. API Verification #### [hashtag](https://docs.somnia.network/developer/development-frameworks/verifying-via-explorer#base-configuration) Base Configuration API Base URL [https://explorer-somnia.api.xangle.ioarrow-up-right](https://explorer-somnia.api.xangle.io/) Required Header `X-Chain: SOMNIA` #### [hashtag](https://docs.somnia.network/developer/development-frameworks/verifying-via-explorer#endpoints) Endpoints **Single File Contract Verification** POST `/api/contract/verify/single-file` Content Type: `application/json` Description: Verify a smart contract using a single Solidity source file. Request Body Response **Multi-File Contract Verification** POST `/api/contract/verify/multi-file` Content Type: `multipart/form-data` Description: Verify a smart contract using multiple source files. Request Body Response **JSON Input Contract Verification** POST `/api/contract/verify/json` Content Type: `multipart/form-data` Description: Verify a smart contract using multiple source files (Standard JSON input). Request Body Response ### [hashtag](https://docs.somnia.network/developer/development-frameworks/verifying-via-explorer#id-3.-hardhat-verification) 3\. Hardhat Verification Hardhat provides a convenient way to verify smart contracts directly from your development environment. By integrating with the Somnia Explorer API, you can submit your contract source code and metadata without leaving your workflow. Base Configuration In your `hardhat.config.js` (or `hardhat.config.ts`), configure the Somnia mainnet settings: * `networks`: Use public RPC or a private RPC you manage for Somnia mainnet * `etherscan.apiKey`: Normally used for Etherscan, but for Somnia an empty value is sufficient * `customChains`: Points Hardhat to the Somnia Explorer API and browser endpoints Verification Command * ``: The address of your deployed contract * `[constructorArgs...]`: Any constructor parameters passed during deployment 1 #### [hashtag](https://docs.somnia.network/developer/development-frameworks/verifying-via-explorer#hardhat-example-workflow) Hardhat example workflow 1. Deploy your contract on Somnia mainnet using Hardhat 2. Copy the deployed address from the deployment output 3. Run the verify command with the address and constructor arguments 4. The verified source code will appear on Somnia Explorer ### [hashtag](https://docs.somnia.network/developer/development-frameworks/verifying-via-explorer#id-4.-foundry-verification) 4\. Foundry Verification Foundry offers a streamlined way to verify your smart contracts directly from the command line. By using the built-in `forge verify-contract` command, you can connect to Somnia Explorer and publish your contract’s source code automatically. Verification Command * `-rpc-url`: Use public RPC or a private RPC you manage for Somnia mainnet * `-verifier`: Use Etherscan * `-verifier-url`: API endpoint of Somnia Explorer contract verification * `
`: The deployed contract address * `[contractFile]:[contractName]`: The Solidity file and the specific contract name to verify 1 #### [hashtag](https://docs.somnia.network/developer/development-frameworks/verifying-via-explorer#foundry-example-workflow) Foundry example workflow 1. Deploy your contract to Somnia mainnet using Foundry or another deployment method 2. Copy the deployed address from the deployment output 3. Run the verify command with the contract address, source file, and contract name 4. Once complete, your verified source code will be visible on Somnia Explorer [PreviousUsing the Viem Librarychevron-left](https://docs.somnia.network/developer/development-frameworks/using-the-viem-library) [NextDebug Playbookchevron-right](https://docs.somnia.network/developer/development-frameworks/debug-playbook) Last updated 1 month ago * [How It Works](https://docs.somnia.network/developer/development-frameworks/verifying-via-explorer#how-it-works) * [Verification Process](https://docs.somnia.network/developer/development-frameworks/verifying-via-explorer#verification-process) * [1\. Explorer UI](https://docs.somnia.network/developer/development-frameworks/verifying-via-explorer#id-1.-explorer-ui) * [2\. API Verification](https://docs.somnia.network/developer/development-frameworks/verifying-via-explorer#id-2.-api-verification) * [3\. Hardhat Verification](https://docs.somnia.network/developer/development-frameworks/verifying-via-explorer#id-3.-hardhat-verification) * [4\. Foundry Verification](https://docs.somnia.network/developer/development-frameworks/verifying-via-explorer#id-4.-foundry-verification) Copy { "mainNetType": "SOMNIA", "evmVersion": "string", "contractAddress": "string", "solcVersion": "string", "optimizationEnabled": boolean, "runs": number, "viaIR": boolean, "constructorArgs": "string", "sourceCode": "string" } Copy { "STS": "Y", "CTRTADDR": "string", "MSG": "string", "VA": number, "NM": "string", "BTCD": "string", "ABI": "string" } Copy { "mainNetType": "SOMNIA", "evmVersion": "string", "contractAddress": "string", "solcVersion": "string", "optimizationEnabled": boolean, "runs": number, "viaIR": boolean, "constructorArgs": "string", "sourceFileList": [\ "string($binary)"\ ] } Copy { "STS": "Y", "CTRTADDR": "string", "MSG": "string", "VA": number, "NM": "string", "BTCD": "string", "ABI": "string" } Copy { "mainNetType": "SOMNIA", "evmVersion": "string", "contractAddress": "string", "solcVersion": "string", "optimizationEnabled": boolean, "runs": number, "viaIR": boolean, "constructorArgs": "string", "sourceFileList": [\ "string($binary)"\ ] } Copy { "STS": "Y", "CTRTADDR": "string", "MSG": "string", "VA": number, "NM": "string", "BTCD": "string", "ABI": "string" } Copy const config: HardhatUserConfig = { solidity: "v0.8.30", // replace if necessary networks: { 'somnia-mainnet': { url: {public rpc or your own rpc url} }, }, etherscan: { apiKey: { 'somnia-mainnet': 'empty' }, customChains: [\ {\ network: "somnia-mainnet",\ chainId: 5031,\ urls: {\ apiURL: "https://verify-contract.xangle.io/somnia/api",\ browserURL: "https://somnia-explorer.xangle.io"\ }\ }\ ] } }; Copy npx hardhat verify \ --network somnia-mainnet \
\ [...constructorArgs] Copy forge verify-contract \ --rpc-url {public rpc or your own rpc url} \ --verifier etherscan \ --verifier-url ‘https://verify-contract.xangle.io/somnia/api’ \
\ [contractFile]:[contractName] --- # Wallet Integration and Auth | Somnia Docs Seamless onboarding and intuitive wallet experiences are at the heart of successful dApps. Somnia supports a range of modern wallet integration tools to help you deliver smooth, secure, and gas-efficient user journeys — from first interaction to advanced transactions. In this section, you’ll learn how to: * [Integrate **Privy** for embedded, Web2-friendly wallet flows](https://docs.somnia.network/developer/building-dapps/wallet-integration-and-auth/authenticating-with-privy) * [Use **RainbowKit** to offer a beautiful, customizable wallet connection UI](https://docs.somnia.network/developer/building-dapps/wallet-integration-and-auth/authenticating-with-rainbowkit) * [Leverage **ConnectKit** for flexible and extensible wallet integrations](https://docs.somnia.network/developer/building-dapps/wallet-integration-and-auth/authenticating-with-connectkit) > Whether you're onboarding crypto-first users or Web2 newcomers, these wallet SDKs give you the tools to build dApps that are simple, secure, and delightful to use. [PreviousUsing Native SOMI/STTchevron-left](https://docs.somnia.network/developer/building-dapps/tokens-and-nfts/using-native-somi-stt) [NextAuthenticating with MetaMaskchevron-right](https://docs.somnia.network/developer/building-dapps/wallet-integration-and-auth/authenticating-with-metamask) Last updated 1 month ago --- # Oracles | Somnia Docs Oracles bridge the gap between onchain and offchain worlds. They bring external data like prices, randomness, and real-world events directly into your smart contracts. In this section, you’ll explore: * [How to integrate **DIA price feeds** on Somnia](https://docs.somnia.network/developer/building-dapps/oracles/dia-price-feeds) * [How to implement **Protofire Price Feeds**](https://docs.somnia.network/developer/building-dapps/oracles/protofire-price-feeds) * [How to implement **Verifiable Randomness (VRF)** via **Protofire + Chainlink**](https://docs.somnia.network/developer/building-dapps/oracles/using-verifiable-randomness-vrf) * Use cases for oracles in DeFi, gaming, prediction markets, and more > If your dApp depends on real-world data, randomness, or secure external inputs — this is where to start. [PreviousListening to Blockchain Events (WebSocket)chevron-left](https://docs.somnia.network/developer/building-dapps/data-indexing-and-querying/listening-to-blockchain-events-websocket) [NextDIA Price Feedschevron-right](https://docs.somnia.network/developer/building-dapps/oracles/dia-price-feeds) Last updated 1 month ago --- # Buy SOMI Using Banxa Checkout | Somnia Docs You can easily buy SOMI tokens using Banxa, a trusted fiat to crypto gateway partner. This guide walks you through purchasing SOMI directly from the [Banxa Checkout Portalarrow-up-right](https://checkout.banxa.com/) . * * * Banxa lets you buy cryptocurrency with local payment methods, including credit/debit cards, bank transfers, and mobile wallets. In this guide, you’ll learn how to: * Access the Banxa checkout page * Select SOMI as the token to purchase * Complete payment securely * Receive SOMI in your wallet * * * [hashtag](https://docs.somnia.network/developer/building-dapps/onramps/buy-somi-using-banxa-checkout#prerequisites) Prerequisites -------------------------------------------------------------------------------------------------------------------------------------- Before you start, ensure you have: * Your wallet address handy, this is where SOMI will be sent. * A valid payment method supported in your region (Visa, Mastercard, bank transfer, etc.). * * * [hashtag](https://docs.somnia.network/developer/building-dapps/onramps/buy-somi-using-banxa-checkout#step-1-visit-the-banxa-checkout-page) Step 1 — Visit the Banxa Checkout Page -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- Go to [https://checkout.banxa.com/arrow-up-right](https://checkout.banxa.com/) . You’ll see a simple interface to select your fiat currency and cryptocurrency. * * * [hashtag](https://docs.somnia.network/developer/building-dapps/onramps/buy-somi-using-banxa-checkout#undefined) ![](https://docs.somnia.network/~gitbook/image?url=https%3A%2F%2F2122549367-files.gitbook.io%2F%7E%2Ffiles%2Fv0%2Fb%2Fgitbook-x-prod.appspot.com%2Fo%2Fspaces%252FkYErT9t3BJtpPfejLO6I%252Fuploads%252FAnhZH77hTM9sZ7kphUcD%252FBanxa-1.png%3Falt%3Dmedia%26token%3Dcc530b33-4026-42d1-bdf0-9b8cb3535e59&width=300&dpr=3&quality=100&sign=b8bcf932&sv=2) ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- [hashtag](https://docs.somnia.network/developer/building-dapps/onramps/buy-somi-using-banxa-checkout#step-2-choose-your-purchase-options) Step 2 — Choose Your Purchase Options ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ * In the “You Pay” field, enter how much fiat (e.g. USD, EUR, NGN) you want to spend. * In the “You Get” field, select SOMI as the token. * Select the network as Somnia Network. Banxa will automatically show the conversion rate, fees, and delivery estimate. * * * [hashtag](https://docs.somnia.network/developer/building-dapps/onramps/buy-somi-using-banxa-checkout#step-3-enter-your-wallet-address) Step 3 — Enter Your Wallet Address ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ Copy your wallet address from your Somnia-compatible wallet (e.g. MetaMask) and paste it into the “Wallet Address” field. triangle-exclamation _**Triple check the address before proceeding. Funds sent to the wrong address cannot be recovered.**_ * * * [hashtag](https://docs.somnia.network/developer/building-dapps/onramps/buy-somi-using-banxa-checkout#step-4-complete-kyc-verification) Step 4 — Complete KYC Verification ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ Banxa complies with international regulations and may ask you to verify your identity. Typical verification steps include: * Uploading a government-issued ID * Taking a selfie * Confirming your billing address This process only needs to be done once per account. ![](https://docs.somnia.network/~gitbook/image?url=https%3A%2F%2F2122549367-files.gitbook.io%2F%7E%2Ffiles%2Fv0%2Fb%2Fgitbook-x-prod.appspot.com%2Fo%2Fspaces%252FkYErT9t3BJtpPfejLO6I%252Fuploads%252FDlOUu0Y1ZUpGRRDcbS5f%252FBanxa-2.png%3Falt%3Dmedia%26token%3D8cc99731-c962-4ffa-b8ed-4d2e101c42ff&width=300&dpr=3&quality=100&sign=d52bde00&sv=2) * * * [hashtag](https://docs.somnia.network/developer/building-dapps/onramps/buy-somi-using-banxa-checkout#step-5-select-a-payment-method) Step 5 — Select a Payment Method -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- Choose one of the available payment options for your country: * Credit/Debit Card * Bank Transfer * Apple Pay / Google Pay (where supported) Follow the on-screen instructions to complete the payment. * * * [hashtag](https://docs.somnia.network/developer/building-dapps/onramps/buy-somi-using-banxa-checkout#step-6-receive-somi-in-your-wallet) Step 6 — Receive SOMI in Your Wallet ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- Once payment is confirmed: * Banxa will process the transaction. * SOMI tokens will be sent directly to your wallet on the Somnia Network. * You’ll receive a confirmation email once the transfer is complete. This may take a few minutes, depending on network congestion and payment method. * * * [hashtag](https://docs.somnia.network/developer/building-dapps/onramps/buy-somi-using-banxa-checkout#need-help) Need Help? ------------------------------------------------------------------------------------------------------------------------------- If your transaction is delayed or you need assistance, you can contact: * Banxa Support: [support.banxa.comarrow-up-right](https://support.banxa.com/) * Somnia Community: [Discord → #supportarrow-up-right](https://discord.gg/somnia) * * * [hashtag](https://docs.somnia.network/developer/building-dapps/onramps/buy-somi-using-banxa-checkout#disclaimers) Disclaimers ---------------------------------------------------------------------------------------------------------------------------------- * Banxa is a third party provider. Always ensure you are using the official [checkout.banxa.comarrow-up-right](https://checkout.banxa.com/) link. * Transaction fees, limits, and verification steps vary by country and payment method. * SOMI purchases are non reversible once processed. [PreviousOnRampschevron-left](https://docs.somnia.network/developer/building-dapps/onramps) [NextAccount Abstractionchevron-right](https://docs.somnia.network/developer/building-dapps/account-abstraction) Last updated 4 months ago * [Prerequisites](https://docs.somnia.network/developer/building-dapps/onramps/buy-somi-using-banxa-checkout#prerequisites) * [Step 1 — Visit the Banxa Checkout Page](https://docs.somnia.network/developer/building-dapps/onramps/buy-somi-using-banxa-checkout#step-1-visit-the-banxa-checkout-page) * [](https://docs.somnia.network/developer/building-dapps/onramps/buy-somi-using-banxa-checkout#undefined) * [Step 2 — Choose Your Purchase Options](https://docs.somnia.network/developer/building-dapps/onramps/buy-somi-using-banxa-checkout#step-2-choose-your-purchase-options) * [Step 3 — Enter Your Wallet Address](https://docs.somnia.network/developer/building-dapps/onramps/buy-somi-using-banxa-checkout#step-3-enter-your-wallet-address) * [Step 4 — Complete KYC Verification](https://docs.somnia.network/developer/building-dapps/onramps/buy-somi-using-banxa-checkout#step-4-complete-kyc-verification) * [Step 5 — Select a Payment Method](https://docs.somnia.network/developer/building-dapps/onramps/buy-somi-using-banxa-checkout#step-5-select-a-payment-method) * [Step 6 — Receive SOMI in Your Wallet](https://docs.somnia.network/developer/building-dapps/onramps/buy-somi-using-banxa-checkout#step-6-receive-somi-in-your-wallet) * [Need Help?](https://docs.somnia.network/developer/building-dapps/onramps/buy-somi-using-banxa-checkout#need-help) * [Disclaimers](https://docs.somnia.network/developer/building-dapps/onramps/buy-somi-using-banxa-checkout#disclaimers) --- # “Hello World” App | Somnia Docs If you’ve ever wanted to see your data travel onchain in real time, this is the simplest way to begin. In this guide, we’ll build and run a Hello World Publisher and Subscriber using the Somnia Data Streams SDK. It demonstrates how to define a schema, publish onchain data, and read it in real time. Somnia Data Streams enables developers to store, retrieve, and react to real-time blockchain data without needing to build indexers or manually poll the chain. Each app works around three key ideas: 1. Schemas – define the data format. 2. Data IDs – uniquely identify each record. 3. Publishers – wallet addresses that own and post data. Your app can write (“publish”) data using one account, and another app (or user) can “subscribe” to read or monitor that data stream. This “Hello World” project demonstrates exactly how that works. [hashtag](https://docs.somnia.network/developer/data-streams/tutorials/hello-world-app#prerequisites) Prerequisites ------------------------------------------------------------------------------------------------------------------------ Before you begin: * Node.js 20+ * A Somnia Testnet wallet with STT test tokens * `.env` file containing your wallet credentials [hashtag](https://docs.somnia.network/developer/data-streams/tutorials/hello-world-app#project-setup) Project Setup ------------------------------------------------------------------------------------------------------------------------ Create a Project Directory and install dependencies [@somnia-chain/streamsarrow-up-right](https://www.npmjs.com/package/@somnia-chain/streams) and [viemarrow-up-right](https://viem.sh/) : Copy npm i @somnia-chain/streams viem dotenv Now create a .env file to hold your test wallet’s private key: [hashtag](https://docs.somnia.network/developer/data-streams/tutorials/hello-world-app#project-overview) Project Overview ------------------------------------------------------------------------------------------------------------------------------ The project contains four files: File Description publisher.js Sends “Hello World” messages to Somnia Data Streams subscriber.js Reads and displays those messages dream-chain.js Configures the Somnia Dream testnet connection package.json Handles dependencies and npm scripts [hashtag](https://docs.somnia.network/developer/data-streams/tutorials/hello-world-app#network-configuration) Network Configuration ---------------------------------------------------------------------------------------------------------------------------------------- The file `dream-chain.js` defines the blockchain network connection. This allows both publisher and subscriber scripts to easily reference the same testnet environment. [hashtag](https://docs.somnia.network/developer/data-streams/tutorials/hello-world-app#hello-world-publisher) Hello World Publisher ---------------------------------------------------------------------------------------------------------------------------------------- The publisher connects to the blockchain, registers a schema if necessary, and sends a “Hello World” message every few seconds. This function connects to Somnia Dream Testnet using your wallet and computes the schema ID for the message structure. It then registers the schema if not already registered. The \`encodeData\` method encodes each message as a structured data packet, and it then publishes data to the chain using sdk.streams.set(). Each transaction is a verifiable, timestamped on-chain record. * * * [hashtag](https://docs.somnia.network/developer/data-streams/tutorials/hello-world-app#hello-world-subscriber) Hello World Subscriber ------------------------------------------------------------------------------------------------------------------------------------------ The subscriber listens for any messages published under the same schema and publisher address. It uses a simple polling mechanism, executed every 3 seconds, to fetch and decode updates. This function computes the same Schema ID used by the publisher. It polls the blockchain for all messages from that publisher and decodes data according to the schema fields. Then, it displays any new messages with timestamps and sender addresses. * * * [hashtag](https://docs.somnia.network/developer/data-streams/tutorials/hello-world-app#run-the-app) Run the App -------------------------------------------------------------------------------------------------------------------- Run both scripts in separate terminals: and then You’ll see Publisher Output: Subscriber Output Congratulations 🎉 You’ve just published and read blockchain data using Somnia Data Streams! This is the foundation for real-time decentralized apps, chat apps, dashboards, IoT feeds, leaderboards, and more. * * * [hashtag](https://docs.somnia.network/developer/data-streams/tutorials/hello-world-app#conclusion) Conclusion ------------------------------------------------------------------------------------------------------------------ You’ve just learned how to: * Define and compute a schema and schema ID * Register it on the Somnia Testnet * Publish and subscribe to on-chain structured data * Decode and render blockchain messages in real time This simple 'Hello World' app is your first step toward building real-time, decentralized applications on Somnia. [PreviousTutorialschevron-left](https://docs.somnia.network/developer/data-streams/tutorials) [NextBuild Your First Schemachevron-right](https://docs.somnia.network/developer/data-streams/tutorials/build-your-first-schema) Last updated 1 month ago * [Prerequisites](https://docs.somnia.network/developer/data-streams/tutorials/hello-world-app#prerequisites) * [Project Setup](https://docs.somnia.network/developer/data-streams/tutorials/hello-world-app#project-setup) * [Project Overview](https://docs.somnia.network/developer/data-streams/tutorials/hello-world-app#project-overview) * [Network Configuration](https://docs.somnia.network/developer/data-streams/tutorials/hello-world-app#network-configuration) * [Hello World Publisher](https://docs.somnia.network/developer/data-streams/tutorials/hello-world-app#hello-world-publisher) * [Hello World Subscriber](https://docs.somnia.network/developer/data-streams/tutorials/hello-world-app#hello-world-subscriber) * [Run the App](https://docs.somnia.network/developer/data-streams/tutorials/hello-world-app#run-the-app) * [Conclusion](https://docs.somnia.network/developer/data-streams/tutorials/hello-world-app#conclusion) Copy PRIVATE_KEY=0xYOUR_PRIVATE_KEY PUBLIC_KEY=0xYOUR_PUBLIC_ADDRESS Copy const { defineChain } = require("viem"); const dreamChain = defineChain({ id: 50312, name: "Somnia Dream", network: "somnia-dream", nativeCurrency: { name: "STT", symbol: "STT", decimals: 18 }, rpcUrls: { default: { http: ["https://dream-rpc.somnia.network"] }, }, }); module.exports = { dreamChain }; Copy const { SDK, SchemaEncoder, zeroBytes32 } = require("@somnia-chain/streams") const { createPublicClient, http, createWalletClient, toHex } = require("viem") const { privateKeyToAccount } = require("viem/accounts") const { waitForTransactionReceipt } = require("viem/actions") const { dreamChain } = require("./dream-chain") require("dotenv").config() async function main() { const publicClient = createPublicClient({ chain: dreamChain, transport: http() }) const walletClient = createWalletClient({ account: privateKeyToAccount(process.env.PRIVATE_KEY), chain: dreamChain, transport: http(), }) const sdk = new SDK({ public: publicClient, wallet: walletClient }) // 1️⃣ Define schema const helloSchema = `string message, uint256 timestamp, address sender` const schemaId = await sdk.streams.computeSchemaId(helloSchema) console.log("Schema ID:", schemaId) // 2️⃣ Safer schema registration const ignoreAlreadyRegistered = true try { const txHash = await sdk.streams.registerDataSchemas( [\ {\ schemaName: 'hello_world',\ schema: helloSchema,\ parentSchemaId: zeroBytes32\ },\ ], ignoreAlreadyRegistered ) if (txHash) { await waitForTransactionReceipt(publicClient, { hash: txHash }) console.log(`✅ Schema registered or confirmed, Tx: ${txHash}`) } else { console.log('ℹ️ Schema already registered — no action required.') } } catch (err) { // fallback: if the SDK doesn’t support the flag yet if (String(err).includes('SchemaAlreadyRegistered')) { console.log('⚠️ Schema already registered. Continuing...') } else { throw err } } // 3️⃣ Publish messages const encoder = new SchemaEncoder(helloSchema) let count = 0 setInterval(async () => { count++ const data = encoder.encodeData([\ { name: 'message', value: `Hello World #${count}`, type: 'string' },\ { name: 'timestamp', value: BigInt(Math.floor(Date.now() / 1000)), type: 'uint256' },\ { name: 'sender', value: walletClient.account.address, type: 'address' },\ ]) const dataStreams = [{ id: toHex(`hello-${count}`, { size: 32 }), schemaId, data }] const tx = await sdk.streams.set(dataStreams) console.log(`✅ Published: Hello World #${count} (Tx: ${tx})`) }, 3000) } main() Copy const { SDK, SchemaEncoder } = require("@somnia-chain/streams"); const { createPublicClient, http } = require("viem"); const { dreamChain } = require("./dream-chain"); require('dotenv').config(); async function main() { const publisherWallet = process.env.PUBLISHER_WALLET; const publicClient = createPublicClient({ chain: dreamChain, transport: http() }); const sdk = new SDK({ public: publicClient }); const helloSchema = `string message, uint256 timestamp, address sender`; const schemaId = await sdk.streams.computeSchemaId(helloSchema); const schemaEncoder = new SchemaEncoder(helloSchema); const result = new Set(); setInterval(async () => { const allData = await sdk.streams.getAllPublisherDataForSchema(schemaId, publisherWallet); for (const dataItem of allData) { const fields = dataItem.data ?? dataItem; let message = "", timestamp = "", sender = ""; for (const field of fields) { const val = field.value?.value ?? field.value; if (field.name === "message") message = val; if (field.name === "timestamp") timestamp = val.toString(); if (field.name === "sender") sender = val; } const id = `${timestamp}-${message}`; if (!result.has(id)) { result.add(id); console.log(`🆕 ${message} from ${sender} at ${new Date(Number(timestamp) * 1000).toLocaleTimeString()}`); } } }, 3000); } main(); Copy npm run publisher Copy npm run subscriber Copy Schema ID: 0x27c30fa6547c34518f2de6a268b29ac3b54e51c98f8d0ef6018bbec9153e9742 ⚠️ Schema already registered. Continuing... ✅ Published: Hello World #1 (Tx: 0xf21ad71a6c7aa54c171ad38b79ef417e8488fd750ce00c1357918b7c7fa5c951) ✅ Published: Hello World #2 (Tx: 0xe999b0381ba9d937d85eb558fefe214fa4e572767c4e698c6e31588ff0e68f0a) Copy 🆕 Hello World #2 from 0xb6e4fa6ff2873480590c68D9Aa991e5BB14Dbf03 at 2:24:04 PM 🆕 Hello World #3 from 0xb6e4fa6ff2873480590c68D9Aa991e5BB14Dbf03 at 2:24:07 PM --- # Build Your First Schema | Somnia Docs Before you can publish or read structured data on the Somnia Network using Somnia Data Streams, you must first define a Schema. A schema acts as the blueprint or data contract between your publisher and all subscribers who wish to interpret your data correctly. In the Somnia Data Streams system, every schema is expressed as a canonical string. A strict, ordered list of fields with Solidity compatible types. For example, a chat application schema: Copy uint64 timestamp, bytes32 roomId, string content, string senderName, address sender This simple definition: * Establishes how data should be encoded and decoded on-chain. * Produces a unique schemaId derived from its exact string representation. * Enables multiple publishers and readers to exchange data consistently, without needing to redeploy contracts or agree on custom ABIs. Each schema you define becomes a typed, reusable data model, similar to a table definition in a database or an ABI for events, but far simpler. Once created, schemas can be: * Reused across many applications. * Extended to create hierarchical data definitions (e.g., “GPS coordinates” → “Vehicle telemetry”). * Versioned by creating new schemas when structure changes occur. This tutorial will walk you through building, registering, and validating your first schema step by step. [hashtag](https://docs.somnia.network/developer/data-streams/tutorials/build-your-first-schema#prerequisites) Prerequisites -------------------------------------------------------------------------------------------------------------------------------- Before continuing, ensure you have the following: 1. Node.js 20+ 2. TypeScript configured in your project 3. `.env.local` file for environment variables Add your credentials to .env.local: Copy RPC_URL=https://dream-rpc.somnia.network PRIVATE_KEY=0xYOUR_FUNDED_PRIVATE_KEY 4. A Funded Testnet Account. You’ll need an address with test tokens on the Somnia Testnet to register schemas or publish data. triangle-exclamation NOTE: The Private Key is only required if connecting a Private Key via a Viem wallet account. Important: Never expose your private key to a client-side environment. Keep it in server scripts or backend environments only. * * * [hashtag](https://docs.somnia.network/developer/data-streams/tutorials/build-your-first-schema#what-youll-build) What You’ll Build --------------------------------------------------------------------------------------------------------------------------------------- In this tutorial, you will: * Create a canonical schema string (your “data ABI”) * Compute the schema ID * Register your schema on-chain (idempotently) * Validate your schema with a simple encode/decode test We’ll use a chat message schema as a running example: This schema represents a single chat message, which can be used later to build a full on-chain chat application. * * * [hashtag](https://docs.somnia.network/developer/data-streams/tutorials/build-your-first-schema#project-setup) Project Setup -------------------------------------------------------------------------------------------------------------------------------- ### [hashtag](https://docs.somnia.network/developer/data-streams/tutorials/build-your-first-schema#install-dependencies) Install dependencies ### [hashtag](https://docs.somnia.network/developer/data-streams/tutorials/build-your-first-schema#define-chain-configuration) Define Chain configuration ### [hashtag](https://docs.somnia.network/developer/data-streams/tutorials/build-your-first-schema#set-up-your-clients) Set up your clients * * * [hashtag](https://docs.somnia.network/developer/data-streams/tutorials/build-your-first-schema#define-the-schema-string) Define the Schema String ------------------------------------------------------------------------------------------------------------------------------------------------------ Field order matters, and ensure to always use Solidity-compatible types. It is important to keep the `string` fields short to minimize gas. Note that changing type or order creates a new schema ID. * * * [hashtag](https://docs.somnia.network/developer/data-streams/tutorials/build-your-first-schema#compute-the-schemaid) Compute the schemaId ---------------------------------------------------------------------------------------------------------------------------------------------- The SDK computes a unique hash of the schema string. This `schemaId` is your permanent identifier. Anyone using the same schema string will derive the same ID _\[confirm with Vincent for correctness\]_. * * * [hashtag](https://docs.somnia.network/developer/data-streams/tutorials/build-your-first-schema#register-the-schema) Register the Schema -------------------------------------------------------------------------------------------------------------------------------------------- Registration makes your schema discoverable and reusable by others. _\[confirm with Vincent for correctness\]_. `isSchemaRegistered()` checks chain state. `registerSchema()` publishes the schema definition to Streams. Thus, the transaction is idempotent, meaning that it is safe to re-run. * * * [hashtag](https://docs.somnia.network/developer/data-streams/tutorials/build-your-first-schema#encode-and-decode-a-sample-payload) Encode and Decode a Sample Payload -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- Test your schema locally before publishing any data. `encodeData()` serializes the payload according to the schema definition. `decodeData()` restores readable field values from the encoded hex. This step ensures your schema fields align correctly. * * * [hashtag](https://docs.somnia.network/developer/data-streams/tutorials/build-your-first-schema#conclusion) Conclusion -------------------------------------------------------------------------------------------------------------------------- You’ve just built and registered your first schema on Somnia Data Streams. Your schema now acts as a public data contract between any publisher and subscriber that wants to communicate using this structure. [Previous“Hello World” Appchevron-left](https://docs.somnia.network/developer/data-streams/tutorials/hello-world-app) [NextStreams Case Study: Formula 1chevron-right](https://docs.somnia.network/developer/data-streams/tutorials/streams-case-study-formula-1) Last updated 1 month ago * [Prerequisites](https://docs.somnia.network/developer/data-streams/tutorials/build-your-first-schema#prerequisites) * [What You’ll Build](https://docs.somnia.network/developer/data-streams/tutorials/build-your-first-schema#what-youll-build) * [Project Setup](https://docs.somnia.network/developer/data-streams/tutorials/build-your-first-schema#project-setup) * [Install dependencies](https://docs.somnia.network/developer/data-streams/tutorials/build-your-first-schema#install-dependencies) * [Define Chain configuration](https://docs.somnia.network/developer/data-streams/tutorials/build-your-first-schema#define-chain-configuration) * [Set up your clients](https://docs.somnia.network/developer/data-streams/tutorials/build-your-first-schema#set-up-your-clients) * [Define the Schema String](https://docs.somnia.network/developer/data-streams/tutorials/build-your-first-schema#define-the-schema-string) * [Compute the schemaId](https://docs.somnia.network/developer/data-streams/tutorials/build-your-first-schema#compute-the-schemaid) * [Register the Schema](https://docs.somnia.network/developer/data-streams/tutorials/build-your-first-schema#register-the-schema) * [Encode and Decode a Sample Payload](https://docs.somnia.network/developer/data-streams/tutorials/build-your-first-schema#encode-and-decode-a-sample-payload) * [Conclusion](https://docs.somnia.network/developer/data-streams/tutorials/build-your-first-schema#conclusion) Copy uint64 timestamp, bytes32 roomId, string content, string senderName, address sender Copy npm i @somnia-chain/streams viem npm i -D @types/node Copy // src/lib/chain.ts import { defineChain } from 'viem' export const somniaTestnet = defineChain({ id: 50312, name: 'Somnia Testnet', network: 'somnia-testnet', nativeCurrency: { name: 'STT', symbol: 'STT', decimals: 18 }, rpcUrls: { default: { http: ['https://dream-rpc.somnia.network'] }, public: { http: ['https://dream-rpc.somnia.network'] }, }, }) Copy // src/lib/clients.ts import { createPublicClient, createWalletClient, http } from 'viem' import { privateKeyToAccount } from 'viem/accounts' import { somniaTestnet } from './chain' function need(key: 'RPC_URL' | 'PRIVATE_KEY') { const v = process.env[key] if (!v) throw new Error(`Missing ${key} in .env.local`) return v } export const publicClient = createPublicClient({ chain: somniaTestnet, transport: http(need('RPC_URL')), }) export const walletClient = createWalletClient({ account: privateKeyToAccount(need('PRIVATE_KEY') as `0x${string}`), chain: somniaTestnet, transport: http(need('RPC_URL')), }) Copy // src/lib/chatSchema.ts export const chatSchema = 'uint64 timestamp, bytes32 roomId, string content, string senderName, address sender' Copy // scripts/compute-schema-id.ts import 'dotenv/config' import { SDK } from '@somnia-chain/streams' import { publicClient } from '../src/lib/clients' import { chatSchema } from '../src/lib/chatSchema' async function main() { const sdk = new SDK({ public: publicClient }) const id = await sdk.streams.computeSchemaId(chatSchema) console.log('Schema ID:', id) } main().catch((e) => { console.error(e) process.exit(1) }) Copy // scripts/register-schema.ts import 'dotenv/config' import { SDK, zeroBytes32 } from '@somnia-chain/streams' import { publicClient, walletClient } from '../src/lib/clients' import { chatSchema } from '../src/lib/chatSchema' import { waitForTransactionReceipt } from 'viem/actions' async function main() { const sdk = new SDK({ public: publicClient, wallet: walletClient }) const id = await sdk.streams.computeSchemaId(chatSchema) const isRegistered = await sdk.streams.isSchemaRegistered(id) if (isRegistered) { console.log('Schema already registered.') return } const txHash = await sdk.streams.registerDataSchemas({ schemaName: "chat", schema: chatSchema }) console.log('Register tx:', txHash) const receipt = await waitForTransactionReceipt(publicClient, { hash: txHash }) console.log('Registered in block:', receipt.blockNumber) } main().catch((e) => { console.error(e) process.exit(1) }) Copy // scripts/encode-decode.ts import 'dotenv/config' import { SchemaEncoder } from '@somnia-chain/streams' import { toHex, type Hex } from 'viem' import { chatSchema } from '../src/lib/chatSchema' const encoder = new SchemaEncoder(chatSchema) const encodedData: Hex = encoder.encodeData([\ { name: 'timestamp', value: Date.now().toString(), type: 'uint64' },\ { name: 'roomId', value: toHex('general', { size: 32 }), type: 'bytes32' },\ { name: 'content', value: 'Hello Somnia!', type: 'string' },\ { name: 'senderName', value: 'Victory', type: 'string' },\ { name: 'sender', value: '0x0000000000000000000000000000000000000001', type: 'address' },\ ]) console.log('Encoded:', encodedData) console.log('Decoded:', encoder.decodeData(encodedData)) --- # The DApp Publisher Proxy Pattern | Somnia Docs In the "[Working with Multiple Publishersarrow-up-right](https://www.google.com/search?q=httpsa://emre-gitbook.gitbook.io/emre-gitbook-docs/data-streams/working-with-multiple-publishers-in-a-shared-stream) " tutorial, you learned the standard pattern for building an aggregator: 1. Maintain a list of all known publisher addresses. 2. Loop through this list. 3. Call `sdk.streams.getAllPublisherDataForSchema()` for each address. 4. Merge and sort the results on the client side. This pattern is simple and effective for a known, manageable number of publishers (e.g., 50 IoT sensors from a single company). **But what happens at a massive scale?** [hashtag](https://docs.somnia.network/developer/data-streams/tutorials/the-dapp-publisher-proxy-pattern#the-problem-the-10-000-publisher-scenario) The Problem: The 10,000-Publisher Scenario -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- Imagine you are building a popular on-chain game. You have a `leaderboardSchema` and 10,000 players actively publishing their scores. If you use the standard aggregator pattern, your "global leaderboard" DApp would need to: 1. Somehow find all 10,000 player addresses. 2. Perform **10,000 separate read calls** (`getAllPublisherDataForSchema`) to the Somnia RPC node. This is not scalable, fast, or efficient. It creates an enormous (and slow) data-fetching burden on your application. [hashtag](https://docs.somnia.network/developer/data-streams/tutorials/the-dapp-publisher-proxy-pattern#the-solution-the-dapp-publisher-proxy) The Solution: The DApp Publisher Proxy ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ This is an advanced architecture that inverts the model to solve the read-scalability problem. Instead of having 10,000 publishers write to Streams _directly_, they all write to **your DApp's smart contract**, which then publishes to Streams on their behalf. **The Flow:** 1. **User (Publisher):** Calls a function on your DApp's contract (e.g., `myGame.submitScore(100)`). The `msg.sender` is the user's address. 2. **DApp Contract (The Proxy):** Internally, your `submitScore` function: * Adds the user's address (`msg.sender`) _into the data payload_ to preserve provenance. * Calls `somniaStreams.esstores(...)` using its _own_ contract address. 3. **Somnia Data Streams:** Records the data. To the Streams contract, the **only publisher** is your DApp Contract's address. The Result: Your global leaderboard aggregator now only needs to make one single read call to fetch all 10,000 players' data: `sdk.streams.getAllPublisherDataForSchema(schemaId, YOUR_DAPP_CONTRACT_ADDRESS)` This is massively scalable and efficient for read-heavy applications. [hashtag](https://docs.somnia.network/developer/data-streams/tutorials/the-dapp-publisher-proxy-pattern#tutorial-building-a-gameleaderboard-proxy) Tutorial: Building a `GameLeaderboard` Proxy ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- Let's build a conceptual example of this pattern. [hashtag](https://docs.somnia.network/developer/data-streams/tutorials/the-dapp-publisher-proxy-pattern#what-youll-build) What You'll Build ------------------------------------------------------------------------------------------------------------------------------------------------ 1. **A new Schema** that _includes_ the original publisher's address. 2. **A** `**GameLeaderboard.sol**` smart contract that acts as the proxy. 3. **A Client Script** that writes to the _proxy contract_ instead of Streams. 4. **A new Aggregator** that reads from the _proxy contract's_ address. [hashtag](https://docs.somnia.network/developer/data-streams/tutorials/the-dapp-publisher-proxy-pattern#the-schema-solving-for-provenance) The Schema (Solving for Provenance) ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- Since the `msg.sender` to the Streams contract will always be our _proxy contract_, we lose the built-in provenance. We must re-create it by adding the original player's address to the schema itself. `**src/lib/schema.ts**` [hashtag](https://docs.somnia.network/developer/data-streams/tutorials/the-dapp-publisher-proxy-pattern#the-proxy-smart-contract-solidity) The Proxy Smart Contract (Solidity) ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- This is a new smart contract you would write and deploy for your DApp. It acts as the gatekeeper. circle-info **SDK set() vs. Contract esstores()** This example uses the low-level contract function esstores(). When you use sdk.streams.set() in your client-side code, the SDK is calling the esstores() function on the Somnia Streams contract "under the hood." This proxy contract is simply calling that same function directly. `**src/contracts/GameLeaderboard.sol**` [hashtag](https://docs.somnia.network/developer/data-streams/tutorials/the-dapp-publisher-proxy-pattern#the-client-script-publishing-to-the-proxy) The Client Script (Publishing to the Proxy) --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- The client-side logic changes. The user no longer needs the Streams SDK to publish, but rather a way to call your DApp's `submitScore` function. `**src/scripts/publishScore.ts**` [hashtag](https://docs.somnia.network/developer/data-streams/tutorials/the-dapp-publisher-proxy-pattern#the-aggregator-script-simple-scalable-reads) The Aggregator Script (Simple, Scalable Reads) -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- This is the pay-off. The aggregator script is now _dramatically_ simpler and more scalable. It only needs to know the single DApp contract address. `**src/scripts/readLeaderboard.ts**` [hashtag](https://docs.somnia.network/developer/data-streams/tutorials/the-dapp-publisher-proxy-pattern#trade-offs-and-considerations) Trade-Offs & Considerations ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- This pattern is powerful, but it's important to understand the trade-offs. **Feature** **Standard Pattern (Multi-Publisher)** **Proxy Pattern (Single Publisher)** **Read Scalability** **Low.** Requires N read calls (N = # of publishers). **High.** Requires 1 read call, regardless of publisher count. **Publisher Gas Cost** **Low.** 1 transaction (`streams.set`). **High.** 1 transaction + 1 internal transaction. User pays more gas. **Provenance** **Automatic & Implicit.** `msg.sender` is the user. **Manual.** Must be built into the schema (`address player`). **Complexity** **Simple.** Requires only the SDK. **Complex.** Requires writing, deploying, and maintaining a custom smart contract. #### [hashtag](https://docs.somnia.network/developer/data-streams/tutorials/the-dapp-publisher-proxy-pattern#conclusion) Conclusion The **DApp Publisher Proxy** is an advanced but essential pattern for any Somnia Data Streams application that needs to scale to thousands or millions of publishers (e.t., games, social media, large IoT networks). It simplifies the data aggregation logic from `**N+1**` read calls down to `**1**`, at the cost of higher gas fees for publishers and increased development complexity. For most DApps, we recommend starting with the simpler "Multi-Publisher Aggregator" pattern. When your application's read performance becomes a bottleneck due to a high number of publishers, you can evolve to this proxy pattern to achieve massive read scalability. [PreviousWorking with Multiple Publishers in a Shared Streamchevron-left](https://docs.somnia.network/developer/data-streams/tutorials/working-with-multiple-publishers-in-a-shared-stream) [NextBuild a Minimal On-Chain Chat Appchevron-right](https://docs.somnia.network/developer/data-streams/tutorials/build-a-minimal-on-chain-chat-app) Last updated 1 month ago * [The Problem: The 10,000-Publisher Scenario](https://docs.somnia.network/developer/data-streams/tutorials/the-dapp-publisher-proxy-pattern#the-problem-the-10-000-publisher-scenario) * [The Solution: The DApp Publisher Proxy](https://docs.somnia.network/developer/data-streams/tutorials/the-dapp-publisher-proxy-pattern#the-solution-the-dapp-publisher-proxy) * [Tutorial: Building a GameLeaderboard Proxy](https://docs.somnia.network/developer/data-streams/tutorials/the-dapp-publisher-proxy-pattern#tutorial-building-a-gameleaderboard-proxy) * [What You'll Build](https://docs.somnia.network/developer/data-streams/tutorials/the-dapp-publisher-proxy-pattern#what-youll-build) * [The Schema (Solving for Provenance)](https://docs.somnia.network/developer/data-streams/tutorials/the-dapp-publisher-proxy-pattern#the-schema-solving-for-provenance) * [The Proxy Smart Contract (Solidity)](https://docs.somnia.network/developer/data-streams/tutorials/the-dapp-publisher-proxy-pattern#the-proxy-smart-contract-solidity) * [The Client Script (Publishing to the Proxy)](https://docs.somnia.network/developer/data-streams/tutorials/the-dapp-publisher-proxy-pattern#the-client-script-publishing-to-the-proxy) * [The Aggregator Script (Simple, Scalable Reads)](https://docs.somnia.network/developer/data-streams/tutorials/the-dapp-publisher-proxy-pattern#the-aggregator-script-simple-scalable-reads) * [Trade-Offs & Considerations](https://docs.somnia.network/developer/data-streams/tutorials/the-dapp-publisher-proxy-pattern#trade-offs-and-considerations) Copy // Schema: 'uint64 timestamp, address player, uint256 score' export const leaderboardSchema = 'uint64 timestamp, address player, uint256 score' Copy // SPDX-License-Identifier: MIT pragma solidity ^0.8.20; // A simplified interface for the Somnia Streams contract interface IStreams { struct DataStream { bytes32 id; bytes32 schemaId; bytes data; } // This is the correct low-level function name function esstores(DataStream[] calldata streams) external; } /** * @title GameLeaderboard * This contract is a DApp Publisher Proxy. * Users call submitScore() here. * This contract then calls somniaStreams.esstores() as a single publisher. */ contract GameLeaderboard { IStreams public immutable somniaStreams; bytes32 public immutable leaderboardSchemaId; event ScoreSubmitted(address indexed player, uint256 score); /** * @param _streamsAddress The deployed address of the Somnia Streams contract * (e.g., 0x6AB397FF662e42312c003175DCD76EfF69D048Fc on Somnia Testnet). * @param _schemaId The pre-computed schemaId for 'uint64 timestamp, address player, uint256 score'. */ constructor(address _streamsAddress, bytes32 _schemaId) { somniaStreams = IStreams(_streamsAddress); leaderboardSchemaId = _schemaId; } /** * @notice Players call this function to submit their score. * @param score The player's score. */ function submitScore(uint256 score) external { // 1. Get the original publisher's address address player = msg.sender; uint64 timestamp = uint64(block.timestamp); // 2. Encode the data payload to match the schema // Schema: 'uint64 timestamp, address player, uint256 score' bytes memory data = abi.encode(timestamp, player, score); // 3. Create a unique dataId (e.g., hash of player and time) bytes32 dataId = keccak256(abi.encodePacked(player, timestamp)); // 4. Prepare the DataStream struct IStreams.DataStream[] memory d = new IStreams.DataStream[](1); d[0] = IStreams.DataStream({ id: dataId, schemaId: leaderboardSchemaId, data: data }); // 5. Call Somnia Streams. The `msg.sender` for this call // is THIS contract (GameLeaderboard). somniaStreams.esstores(d); // 6. Emit a DApp-specific event for good measure emit ScoreSubmitted(player, score); } } Copy import 'dotenv/config' import { createWalletClient, http, createPublicClient, parseAbi } from 'viem' import { privateKeyToAccount } from 'viem/accounts' import { somniaTestnet } from '../lib/chain' // From previous tutorials import { waitForTransactionReceipt } from 'viem/actions' // --- DApp Contract Setup --- // This is the address you get after deploying GameLeaderboard.sol const DAPP_CONTRACT_ADDRESS = '0x...' // Your deployed GameLeaderboard contract address // A minimal ABI for our GameLeaderboard contract const DAPP_ABI = parseAbi([\ 'function submitScore(uint256 score) external',\ ]) // --- --- --- function getEnv(key: string): string { const value = process.env[key] if (!value) throw new Error(`Missing environment variable: ${key}`) return value } // We can use any publisher wallet const walletClient = createWalletClient({ account: privateKeyToAccount(getEnv('PUBLISHER_1_PK') as `0x${string}`), chain: somniaTestnet, transport: http(getEnv('RPC_URL')), }) const publicClient = createPublicClient({ chain: somniaTestnet, transport: http(getEnv('RPC_URL')), }) async function main() { const newScore = Math.floor(Math.random() * 10000) console.log(`Player ${walletClient.account.address} submitting score: ${newScore}...`) try { const { request } = await publicClient.simulateContract({ account: walletClient.account, address: DAPP_CONTRACT_ADDRESS, abi: DAPP_ABI, functionName: 'submitScore', args: [BigInt(newScore)], }) const txHash = await walletClient.writeContract(request) console.log(`Transaction sent, hash: ${txHash}`) await waitForTransactionReceipt(publicClient, { hash: txHash }) console.log('Score submitted successfully!') } catch (e: any) { console.error(`Failed to submit score: ${e.message}`) } } main().catch(console.error) Copy import 'dotenv/config' import { SDK, SchemaDecodedItem } from '@somnia-chain/streams' import { createPublicClient, http } from 'viem' import { somniaTestnet } from '../lib/chain' import { leaderboardSchema } from '../libL/schema' // Our new schema // --- DApp Contract Setup --- const DAPP_CONTRACT_ADDRESS = '0x...' // Your deployed GameLeaderboard contract address // --- --- --- function getEnv(key: string): string { const value = process.env[key] if (!value) throw new Error(`Missing environment variable: ${key}`) return value } const publicClient = createPublicClient({ chain: somniaTestnet, transport: http(getEnv('RPC_URL')), }) // Helper to decode the leaderboard data interface ScoreRecord { timestamp: number player: `0x${string}` score: bigint } function decodeScoreRecord(row: SchemaDecodedItem[]): ScoreRecord { const val = (field: any) => field?.value?.value ?? field?.value ?? '' return { timestamp: Number(val(row[0])), player: val(row[1]) as `0x${string}`, score: BigInt(val(row[2])), } } async function main() { // The aggregator only needs a public client const sdk = new SDK({ public: publicClient }) const schemaId = await sdk.streams.computeSchemaId(leaderboardSchema) if (!schemaId) throw new Error('Could not compute schemaId') console.log('--- Global Leaderboard Aggregator ---') console.log(`Reading all data from proxy: ${DAPP_CONTRACT_ADDRESS}\n`) // 1. Make ONE call to get all data for the DApp const data = await sdk.streams.getAllPublisherDataForSchema( schemaId, DAPP_CONTRACT_ADDRESS ) if (!data || data.length === 0) { console.log('No scores found.') return } // 2. Decode and sort the records const allScores = (data as SchemaDecodedItem[][]).map(decodeScoreRecord) allScores.sort((a, b) => (b.score > a.score ? 1 : -1)) // Sort descending by score // 3. Display the leaderboard console.log(`Total scores found: ${allScores.length}\n`) allScores.forEach((record, index) => { console.log( `#${index + 1}: Player ${record.player} - Score: ${record.score} (at ${new Date(record.timestamp).toISOString()})` ) }) } main().catch(console.error) --- # Data Indexing and Querying | Somnia Docs Subgraphs allow your dApp to efficiently index and query onchain data from the Somnia Network. Whether you're building dashboards, tracking events, or querying user actions, Subgraphs provide a scalable way to make your smart contract data accessible and searchable. In this section, you’ll learn: * [How to create and deploy subgraphs on Somnia using **ORMI**](https://docs.somnia.network/developer/building-dapps/data-indexing-and-querying/ormi-subgraph) * [How to build with **Protofire’s subgraph infrastructure**](https://docs.somnia.network/developer/building-dapps/data-indexing-and-querying/protofire-subgraph) * [Frontend integrations for building responsive UIs from your subgraph data](https://docs.somnia.network/developer/building-dapps/data-indexing-and-querying/building-subgraph-uis-nextjs-fetch) > Subgraphs are essential for any developer building real-time dApps, analytics dashboards, or event-driven logic on Somnia. [PreviousSmart Wallet App with Thirdwebchevron-left](https://docs.somnia.network/developer/building-dapps/account-abstraction/smart-wallet-app-with-thirdweb) [NextOrmi Subgraphchevron-right](https://docs.somnia.network/developer/building-dapps/data-indexing-and-querying/ormi-subgraph) Last updated 4 months ago --- # Account Abstraction | Somnia Docs Account Abstraction (AA) revolutionizes how users interact with blockchain applications by making wallets **smarter, simpler, and more programmable**. In this section, you’ll explore how to implement **Smart Contract Accounts (SCAs)** on Somnia using modern tooling like **Thirdweb** and **Privy**, and learn how to enable **gasless transactions** and **session keys** for better UX. You’ll learn how to: * Create and manage smart contract wallets * Implement user operations via ERC-4337-style flows * Enable sponsored and gasless transactions * Simplify onboarding through smart wallets and relayers > Account Abstraction bridges the gap between Web2 simplicity and Web3 ownership — empowering developers to build dApps users actually love to use. [PreviousBuy SOMI Using Banxa Checkoutchevron-left](https://docs.somnia.network/developer/building-dapps/onramps/buy-somi-using-banxa-checkout) [NextGasless Transactions with Thirdwchevron-right](https://docs.somnia.network/developer/building-dapps/account-abstraction/gasless-transactions-with-thirdw) Last updated 4 months ago --- # Ecosystem Showcase | Somnia Docs [Elix.fichevron-right](https://docs.somnia.network/developer/deployment-and-production/ecosystem/ecosystem-showcase/elix.fi) [Meme Coinschevron-right](https://docs.somnia.network/developer/deployment-and-production/ecosystem/ecosystem-showcase/meme-coins) [NFTs2Mechevron-right](https://docs.somnia.network/developer/deployment-and-production/ecosystem/ecosystem-showcase/nfts2me) [Somnia Domains (.somi)chevron-right](https://docs.somnia.network/developer/deployment-and-production/ecosystem/ecosystem-showcase/somnia-domains-.somi) [Somnia Exchangechevron-right](https://docs.somnia.network/developer/deployment-and-production/ecosystem/ecosystem-showcase/somnia-exchange) [Tokoschevron-right](https://docs.somnia.network/developer/deployment-and-production/ecosystem/ecosystem-showcase/tokos) [PreviousEcosystemchevron-left](https://docs.somnia.network/developer/deployment-and-production/ecosystem) [NextElix.fichevron-right](https://docs.somnia.network/developer/deployment-and-production/ecosystem/ecosystem-showcase/elix.fi) --- # Somnia Data vs Event Streams | Somnia Docs ### [hashtag](https://docs.somnia.network/developer/data-streams/concepts/somnia-data-vs-event-streams#tl-dr) tl;dr * Data Streams: Raw bytes calldata written to chain with contextual information on how to parse the data using a public or private `data schema` * Event Streams: [EVM logsarrow-up-right](https://docs.chainstack.com/docs/ethereum-logs-tutorial-series-logs-and-filters) emitted by the Somnia Streams protocol. Protocol users register and `event schema` that can be referenced they want to emit an event that others can `subscribe` to with Somnia streams reactivity Both data and event streams can be done without knowing Solidity and without deploying any smart contracts ### [hashtag](https://docs.somnia.network/developer/data-streams/concepts/somnia-data-vs-event-streams#typescript-sdk-interface) TypeScript SDK interface Copy /** * @param somniaStreamsEventId The identifier of a registered event schema within Somnia streams protocol or null if using a custom event source * @param ethCalls Fixed set of ETH calls that must be executed before onData callback is triggered. Multicall3 is recommended. Can be an empty array * @param context Event sourced selectors to be added to the data field of ETH calls, possible values: topic0, topic1, topic2, topic3, topic4, data and address * @param onData Callback for a successful reactivity notification * @param onError Callback for a failed attempt * @param eventContractSource Alternative contract event source (any on somnia) that will be emitting the logs specified by topicOverrides * @param topicOverrides Optional when using Somnia streams as an event source but mandatory when using a different event source * @param onlyPushChanges Whether the data should be pushed to the subscriber only if eth_call results are different from the previous */ export type SubscriptionInitParams = { somniaStreamsEventId?: string ethCalls: EthCall[] context?: string onData: (data: any) => void onError?: (error: Error) => void eventContractSource?: Address topicOverrides?: Hex[] onlyPushChanges: boolean } export interface StreamsInterface { // Write set(d: DataStream[]): Promise; emitEvents(e: EventStream[]): Promise; setAndEmitEvents(d: DataStream[], e: EventStream[]): Promise; // Manage registerDataSchemas(registrations: DataSchemaRegistration[]): Promise; registerEventSchemas(ids: string[], schemas: EventSchema[]): Promise; manageEventEmittersForRegisteredStreamsEvent( streamsEventId: string, emitter: Address, isEmitter: boolean ): Promise; // Read getByKey(schemaId: SchemaID, publisher: Address, key: Hex): Promise; getAtIndex(schemaId: SchemaID, publisher: Address, idx: bigint): Promise; getBetweenRange( schemaId: SchemaID, publisher: Address, startIndex: bigint, endIndex: bigint ): Promise; getAllPublisherDataForSchema( schemaReference: SchemaReference, publisher: Address ): Promise; getLastPublishedDataForSchema( schemaId: SchemaID, publisher: Address ): Promise; totalPublisherDataForSchema(schemaId: SchemaID, publisher: Address): Promise; isDataSchemaRegistered(schemaId: SchemaID): Promise; computeSchemaId(schema: string): Promise; parentSchemaId(schemaId: SchemaID): Promise; schemaIdToId(schemaId: SchemaID): Promise; idToSchemaId(id: string): Promise; getAllSchemas(): Promise; getEventSchemasById(ids: string[]): Promise; // Helper deserialiseRawData( rawData: Hex[], parentSchemaId: Hex, schemaLookup: { schema: string; schemaId: Hex; } | null ): Promise; // Subscribe subscribe(initParams: SubscriptionInitParams): Promise<{ subscriptionId: string, unsubscribe: () => void } | undefined>; // Protocol getSomniaDataStreamsProtocolInfo(): Promise; } [PreviousExtending and composing data schemaschevron-left](https://docs.somnia.network/developer/data-streams/concepts/extending-and-composing-data-schemas) [NextIntersection with Somnia Reactivitychevron-right](https://docs.somnia.network/developer/data-streams/concepts/intersection-with-somnia-reactivity) Last updated 1 month ago * [tl;dr](https://docs.somnia.network/developer/data-streams/concepts/somnia-data-vs-event-streams#tl-dr) * [TypeScript SDK interface](https://docs.somnia.network/developer/data-streams/concepts/somnia-data-vs-event-streams#typescript-sdk-interface) --- # Using the Viem Library | Somnia Docs Somnia empowers developers to build applications for mass adoption. Smart Contracts deployed on Somnia will require front-end user interfaces to interact with them. These front-end user interfaces will require middleware libraries to establish a connection to the Somnia Network and enable interaction with Smart Contracts. In this Guide, you will learn how to use the Viem Library to establish a connection between your deployed Smart Contracts on Somnia Network and your Front-end User application. You will also learn how to perform READ and WRITE operations using Viem. [Viemarrow-up-right](https://viem.sh/) is a TypeScript interface for Ethereum that provides low-level stateless primitives for interacting with Ethereum. circle-check Somnia Mainnet is LIVE. To deploy on Somnia Mainnet, you will need SOMI Tokens. Please refer to the [guide](https://docs.somnia.network/get-started/getting-started-for-mainnet) on Moving from Testnet to Mainnet. [hashtag](https://docs.somnia.network/developer/development-frameworks/using-the-viem-library#how-does-viem-enable-ui-interaction) How does Viem enable UI interaction? ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- When a Smart Contract is programmed using any development tool such as RemixIDE, Hardhat or Foundry, the Smart Contract undergoes a “compilation” stage. Compiling a Smart Contract, among other things will convert the Solidity code into machine-readable bytecode. An ABI file is also produced when a Smart Contrac is compiled. ABI stand for Application Binary Interface. You can think of an ABI like the Interface that make it possible for a User Interface to connect with the Smart Contract functions in a way similar to how an API makes it possible to to connect a UI and Backend server in web2. ![](https://docs.somnia.network/~gitbook/image?url=https%3A%2F%2Flh7-rt.googleusercontent.com%2Fdocsz%2FAD_4nXfaRoTuAtASsyoeB7jqiU2urm-g50ESwc_Ty4iH2h7DlLcwQak0uXG-eew1-J40_S4Cr0pJwGPxgWzjCjfo4_sHX8aV20snkM5w1dcJUeGpJx8igiTMGUqatgy1OpaYTnbINqXV6A%3Fkey%3DHxFSDQZc_LGgImSmo9E6Cd6w&width=768&dpr=3&quality=100&sign=ac6bc4c2&sv=2) [hashtag](https://docs.somnia.network/developer/development-frameworks/using-the-viem-library#example-smart-contract) Example Smart Contract ------------------------------------------------------------------------------------------------------------------------------------------------- Here is an example \`Greeter.sol\` Smart Contract: [hashtag](https://docs.somnia.network/developer/development-frameworks/using-the-viem-library#example-abi) Example ABI --------------------------------------------------------------------------------------------------------------------------- When the Greeter Smart Contract is compiled, below is its ABI: chevron-rightABI[hashtag](https://docs.somnia.network/developer/development-frameworks/using-the-viem-library#abi) [https://gist.github.com/emmaodia/bdb9b84998b1e4f3f19d0ae27c541e63arrow-up-right](https://gist.github.com/emmaodia/bdb9b84998b1e4f3f19d0ae27c541e63) The ABI is an array of JSON objects containing the constructor, event, and four functions in the Smart Contract. Using a Library such as Viem, you can perform READ and WRITE operations, for each of the ABI objects. You can READ the events, and other “view” only methods. You can perform WRITE operations on the “changeName” function. A cursory look at each ABI method will help you understand the function and what can be accomplished by interacting with the method. For example: [hashtag](https://docs.somnia.network/developer/development-frameworks/using-the-viem-library#how-to-use-viem) How to use Viem. ------------------------------------------------------------------------------------------------------------------------------------ To use Viem, it has to be installed in the project directory where you want to perform READ and WRITE operations. First, create a directory and initialize a new project using npm. Initialize a project in the directory by running the command: Install Viem by running the following command. [hashtag](https://docs.somnia.network/developer/development-frameworks/using-the-viem-library#set-up-viem) Set Up Viem --------------------------------------------------------------------------------------------------------------------------- To connect to the deployed Example Greeter Smart Contract using Viem, it is necessary to have access to the Smart Contract’s ABI and its Contract Address. Viem sets up a “`transport`” infrastructure to connect with a node in the EVM Network and the deployed Smart Contracts. We will use some Viem methods to connect to your Smart Contract deployed on the Somnia Network. Viem has a \`createPublicClient\` and a \`createWalletClient\` method. The PublicClient is used to perform READ operations, while the WalletClient is used to perform WRITE operations. Create a new file `index.js` Import the method classes from the Library: The `http` is the transport protocol for interacting with the Node of the Somnia Blockchain via RPC. It uses the default Somnia RPC URL: [`https://dream-rpc.somnia.network`arrow-up-right](https://dream-rpc.somnia.network/) . In the future developers can use RPC providers to avoid rate limiting. [hashtag](https://docs.somnia.network/developer/development-frameworks/using-the-viem-library#set-up-publicclient) Set up PublicClient ------------------------------------------------------------------------------------------------------------------------------------------- We will start with setting up the `publicClient` to read `view` only methods. Set up a `**publicClient**` where the default Transport is `http` and `chain` is `SOMNIA` network created using the `defineChain` method. [hashtag](https://docs.somnia.network/developer/development-frameworks/using-the-viem-library#consume-actions) Consume Actions ----------------------------------------------------------------------------------------------------------------------------------- Now that you have a Client set up, you can interact with Somnia Blockchain and consume Actions! An example will be to call the `greet` method on the deployed Smart Contract. To do this, we have to create a file name `abi.js` and add the exported ABI in the file. In the `index.js` we can import the ABI file and start calling methods on the deployed Smart Contract. Import the ABI: Set Contract Address > This is an example Greeter Smart Contract deployed on Somnia Testnet Write a Function \`interactWithContract\`: Open your terminal and run the following: You will see the response from the Smart Contract logged into the Console! ![](https://docs.somnia.network/~gitbook/image?url=https%3A%2F%2Flh7-rt.googleusercontent.com%2Fdocsz%2FAD_4nXeerGko1tZ3XeNPJPAiHOIw4DsOM8hL4HSurOu3-CgEP7dQeAUc_rbGJdXRx2jYX16DxCCUslCrEFQmmOmZJ5MDAB3-iWw8h2j4pWbbucO0YUO4Wp8i9xJ6JcI6zFUvztoPdUgbPw%3Fkey%3DHxFSDQZc_LGgImSmo9E6Cd6w&width=768&dpr=3&quality=100&sign=8b8b0cb2&sv=2) Congratulations, you have successfully performed a READ operation on your Smart Contract deployed on Somnia. [hashtag](https://docs.somnia.network/developer/development-frameworks/using-the-viem-library#set-up-wallet-client) Set up Wallet Client --------------------------------------------------------------------------------------------------------------------------------------------- To perform a write operation, we will parse the \`createWalletClient\` method to a \`walletClient\` variable. It is important to understand that carrying out WRITE operations changes the state of the Blockchain, unlike READ operations, where you read the state of the Blockchain. So, to perform WRITE operations, a user will have to spend Gas, and to be able to spend Gas, a user will have to parse his Private Key from an EOA to give the Library masked permission to carry out transactions on behalf of the user. To read the Private Key from an EOA, we will use a Viem method: Then, create a variable `walletClient` > The variable \`$YOUR\_PRIVATE\_KEY\` variable can be parsed using a dotenv file. After sending a WRITE operation, we also have to be able to read the transaction to see the state changes. We will rely on a READ method to read a transaction, \`waitForTransactionReceipt\`. Update the \`interactWithContract\` function with the code below: Save the file and run the node command to see your responses logged into the console. Congratulations, you have successfully performed a WRITE operation on your Smart Contract deployed on Somnia. 🎉 [PreviousDeploy with Hardhatchevron-left](https://docs.somnia.network/developer/development-frameworks/deploy-with-hardhat) [NextVerifying via Explorerchevron-right](https://docs.somnia.network/developer/development-frameworks/verifying-via-explorer) Last updated 1 month ago * [How does Viem enable UI interaction?](https://docs.somnia.network/developer/development-frameworks/using-the-viem-library#how-does-viem-enable-ui-interaction) * [Example Smart Contract](https://docs.somnia.network/developer/development-frameworks/using-the-viem-library#example-smart-contract) * [Example ABI](https://docs.somnia.network/developer/development-frameworks/using-the-viem-library#example-abi) * [How to use Viem.](https://docs.somnia.network/developer/development-frameworks/using-the-viem-library#how-to-use-viem) * [Set Up Viem](https://docs.somnia.network/developer/development-frameworks/using-the-viem-library#set-up-viem) * [Set up PublicClient](https://docs.somnia.network/developer/development-frameworks/using-the-viem-library#set-up-publicclient) * [Consume Actions](https://docs.somnia.network/developer/development-frameworks/using-the-viem-library#consume-actions) * [Set up Wallet Client](https://docs.somnia.network/developer/development-frameworks/using-the-viem-library#set-up-wallet-client) Copy // SPDX-License-Identifier: MIT pragma solidity ^0.8.22; contract Greeter { string public name; address public owner; event NameChanged(string oldName, string newName); modifier onlyOwner() { require(msg.sender == owner, "Only the owner can perform this action"); _; } constructor(string memory _initialName) { name = _initialName; owner = msg.sender; } function changeName(string memory _newName) external onlyOwner { string memory oldName = name; name = _newName; emit NameChanged(oldName, _newName); } function greet() external view returns (string memory) { return string(abi.encodePacked("Hello, ", name, "!")); } } Copy { "inputs": [ --->specifies it is an input, i.e. a WRITE function\ {\ "internalType": "string", ---> the data type\ "name": "_newName", ---> params name\ "type": "string" ---> data type\ }\ ], "name": "changeName", ---> function name "outputs": [], ---> it does not have a return property "stateMutability": "nonpayable", ---> It changes the Blockchain State without Token exchange, it simply stores information. "type": "function" ---> It is a function. }, Copy mkdir viem-example && cd viem-example Copy npm init -y Copy npm i viem Copy import { createPublicClient, createWalletClient, http } from "viem"; Copy import { somniaTestnet } from "viem/chains" Copy const publicClient = createPublicClient({ chain: somniaTestnet, transport: http(), }) Copy export const ABI = [//...ABI here] Copy import { ABI } from "./abi.js"; Copy const CONTRACT_ADDRESS = "0x2e7f682863a9dcb32dd298ccf8724603728d0edd"; Copy const interactWithContract = async () => { try { console.log("Reading message from the contract..."); // Read the "greet" function const greeting = await publicClient.readContract({ address: CONTRACT_ADDRESS, abi: ABI, functionName: "greet", }); console.log("Current greeting:", greeting); } catch (error) { console.error("Error interacting with the contract:", error); } }; interactWithContract(); Copy node index.js Copy import { privateKeyToAccount } from "viem/accounts"; Copy const walletClient = createWalletClient({ account: privateKeyToAccount($YOUR_PRIVATE_KEY), chain: somniaTestnet, transport: http(), }); Copy // Write to the "changeName" function const txHash = await walletClient.writeContract({ address: CONTRACT_ADDRESS, abi: ABI, functionName: "changeName", args: ["Emmanuel!"], }); console.log("Transaction sent. Hash:", txHash); console.log("Waiting for transaction confirmation..."); // Wait for the transaction to be confirmed const receipt = await publicClient.waitForTransactionReceipt({ hash: txHash }); console.log("Transaction confirmed. Receipt:", receipt); // Read the updated "greet" function const updatedGreeting = await publicClient.readContract({ address: CONTRACT_ADDRESS, abi: ABI, functionName: "greet", }); console.log("Updated greeting:", updatedGreeting); Copy node index.js --- # Go-Live Checklist | Somnia Docs This page provides a **checklist for deploying Somnia applications to production (Mainnet)**. It ensures that all necessary environment variables, contract addresses, allowlists, and rollback strategies are correctly configured before launch. circle-exclamation Never commit secrets to git. Use encrypted secret storage or CI secret managers for production deployments. * * * ### [hashtag](https://docs.somnia.network/developer/deployment-and-production/go-live-checklist#environment-variables) Environment Variables * Store RPC URLs, private keys, and API keys in `.env` files (never commit to git). * Define `SOMNIA_RPC_MAINNET` and `SOMNIA_RPC_TESTNET` for switching between environments. * Configure block explorer API keys (for contract verification). * Separate `.env.production` vs `.env.development`. * Double-check secrets with `printenv | grep SOMNIA`. Example `.env.production`: .env.production Copy SOMNIA_RPC_MAINNET=https://api.infra.mainnet.somnia.network/ PRIVATE_KEY=0xabc123... EXPLORER_API_KEY=... ALLOWED_ORIGIN=https://yourapp.com * * * ### [hashtag](https://docs.somnia.network/developer/deployment-and-production/go-live-checklist#contract-addresses) Contract Addresses * Verify all **core Somnia system contracts** (e.g., wrapped SOMI, multicall, registry). * Update deployed contract addresses in `.env` or config files. * Confirm that addresses match **Mainnet deployments** (not testnet). * Cross-check with block explorer for correct bytecode & verification. Example `config/addresses.json`: * * * ### [hashtag](https://docs.somnia.network/developer/deployment-and-production/go-live-checklist#allowlists) Allowlists * Maintain allowlists for admin roles, multisigs, and privileged addresses. * Use multisig wallets ([Safearrow-up-right](https://safe.somnia.network/welcome) ) for critical roles (owner, pauser, upgrader). * Double-check allowlist in contracts (no dev/test keys). * Store allowlist in version control (JSON/YAML). Example `allowlist.json`: * * * ### [hashtag](https://docs.somnia.network/developer/deployment-and-production/go-live-checklist#rollback-plan) Rollback Plan Having a clear rollback strategy is essential if something goes wrong during or after deployment. This should include both **technical measures** and **operational procedures**. #### [hashtag](https://docs.somnia.network/developer/deployment-and-production/go-live-checklist#technical-rollback-steps) Technical Rollback Steps * **Pause contracts** – ensure critical contracts implement a `pause()` function for emergencies. * **Feature flags** – enable/disable risky features without redeployment. * **Upgradable contracts** – if using proxies, keep previous implementation verified and ready. * **Role revocation** – remove compromised keys or revoke admin privileges quickly. * **Migration scripts** – maintain scripts to redeploy or roll forward to a safe version. #### [hashtag](https://docs.somnia.network/developer/deployment-and-production/go-live-checklist#operational-rollback-steps) Operational Rollback Steps * **Communication plan** – inform users immediately via Discord, Twitter, or status page. * **Validator coordination** – notify Somnia infra providers/validators in case of critical incidents. * **Emergency access** – keep a secure list of multisig signers available for urgent transactions. * **Data backups** – keep copies of config files, [subgrapharrow-up-right](https://subgraph.somnia.network/) schemas, and off-chain DBs. * **Post-mortem process** – document the issue, resolution, and future prevention steps. * * * ### [hashtag](https://docs.somnia.network/developer/deployment-and-production/go-live-checklist#best-practices) Best Practices * Always test on **Shannon Testnet** before mainnet deployment. * Pin dependencies & compiler versions for reproducibility. * Run **audit checklist** (see Smart Contract Security 101). * Monitor deployed contracts with logging & health checks. * Communicate launch windows to your community in advance. [PreviousDeployment and Productionchevron-left](https://docs.somnia.network/developer/deployment-and-production) [NextExplorer API Health and Monitoringchevron-right](https://docs.somnia.network/developer/deployment-and-production/explorer-api-health-and-monitoring) Last updated 1 month ago * [Environment Variables](https://docs.somnia.network/developer/deployment-and-production/go-live-checklist#environment-variables) * [Contract Addresses](https://docs.somnia.network/developer/deployment-and-production/go-live-checklist#contract-addresses) * [Allowlists](https://docs.somnia.network/developer/deployment-and-production/go-live-checklist#allowlists) * [Rollback Plan](https://docs.somnia.network/developer/deployment-and-production/go-live-checklist#rollback-plan) * [Best Practices](https://docs.somnia.network/developer/deployment-and-production/go-live-checklist#best-practices) config/addresses.json Copy { "network": "mainnet", "dao": "0x1234...", "token": "0xabcd...", "subgraph": "https://subgraph.somnia.network/dashboard/subgraph/..." } allowlist.json Copy { "admins": ["0xAdmin1...", "0xAdmin2..."], "oracles": ["0xOracle1..."], "relayers": ["0xRelayer1..."] } --- # Deploy with Hardhat | Somnia Docs Various developer tools can be used to build on Somnia to enable the Somnia mission of empowering developers to build Mass applications. One such development tool is Hardhat. [Hardhatarrow-up-right](https://hardhat.org/) is a development environment for the EVM i.e. Somnia. It consists of different components for editing, compiling, debugging, and deploying your smart contracts and dApps, all working together to create a complete development environment. This guide will teach you how to deploy a “Buy Me Coffee” Smart Contract to the Somia Network using Hardhat Development tools. circle-check Somnia Mainnet is LIVE. To deploy on Somnia Mainnet, you will need SOMI Tokens. Please refer to the [guide](https://docs.somnia.network/get-started/getting-started-for-mainnet) on Moving from Testnet to Mainnet. [hashtag](https://docs.somnia.network/developer/development-frameworks/deploy-with-hardhat#pre-requisites) Pre-requisites ------------------------------------------------------------------------------------------------------------------------------ 1. This guide is not an introduction to Solidity Programming; you are expected to understand Basic Solidity Programming. 2. To complete this guide, you will need MetaMask installed and the Somnia Network added to the list of Networks. If you have yet to install MetaMask, please follow this guide to [Connect Your Wallet](https://docs.somnia.network/get-started/connect-your-wallet-to-mainnet) . 3. Hardhat is installed and set up on your local machine. See [Guidearrow-up-right](https://hardhat.org/hardhat-runner/docs/getting-started#installation) . [hashtag](https://docs.somnia.network/developer/development-frameworks/deploy-with-hardhat#initialise-hardhat-project) Initialise Hardhat Project ------------------------------------------------------------------------------------------------------------------------------------------------------ Start a new Hardhat project by running the following command in your Terminal: This will give you a series of prompts. Select the option to “Create a TypeScript Project (with Viem)” ![](https://docs.somnia.network/~gitbook/image?url=https%3A%2F%2F2122549367-files.gitbook.io%2F%7E%2Ffiles%2Fv0%2Fb%2Fgitbook-x-prod.appspot.com%2Fo%2Fspaces%252FkYErT9t3BJtpPfejLO6I%252Fuploads%252F1d19cgBgLMTfHs1n3h9V%252FHardhat1.png%3Falt%3Dmedia%26token%3D39635e08-e00e-4080-8946-efc6176246c4&width=768&dpr=3&quality=100&sign=168ff72b&sv=2) This will install the required dependencies for your project. Once the installation is complete, open the project directory and check the directories where you will find the \`contracts\` directory. This is where the Smart Contract will be added. [hashtag](https://docs.somnia.network/developer/development-frameworks/deploy-with-hardhat#create-the-smart-contract) Create the Smart Contract ---------------------------------------------------------------------------------------------------------------------------------------------------- Open the Smart Contracts folder and delete the default `Lock.sol` file. Create a new file, `BuyMeCoffee.sol` and paste the following code: [hashtag](https://docs.somnia.network/developer/development-frameworks/deploy-with-hardhat#compile-the-smart-contract) Compile the Smart Contract ------------------------------------------------------------------------------------------------------------------------------------------------------ To compile your contracts, you need to customize the Solidity compiler options, open the `hardhat.config.js` file and ensure the Solidity version is `0.8.28` and then run the command: It will return the response: This will compile the Solidity file and convert the Solidity code into machine-readable bytecode. By default, the compiled _artifacts_ will be saved in the newly created `artifacts` directory. The next step is to deploy the contracts to the Somnia Network. In Hardhat, deployments are defined through **Ignition Modules**. These modules are abstractions that describe a deployment, specifically, JavaScript functions that process the file you want to deploy. Open the `ignition` directory inside the project root's directory, then enter the directory named `modules`. Delete the `Lock.ts` file. Create a `deploy.ts` file and paste the following code: [hashtag](https://docs.somnia.network/developer/development-frameworks/deploy-with-hardhat#deploy-contract) Deploy Contract -------------------------------------------------------------------------------------------------------------------------------- Open the `hardhat.config.js` file and update the network information by adding Somnia Network to the list of networks. Copy your Wallet Address Private Key from MetaMask, and add it to the **accounts** section. Ensure there are enough STT Token in the Wallet Address to pay for Gas. You can get some from the Somnia [Faucetarrow-up-right](https://devnet.somnia.network/) . > The "**0xPRIVATE\_KEY**" is used to sign the Transaction from your EOA without permission. When deploying the smart contract, you must ensure the EOA that owns the Private Key is funded with enough STT Tokens to pay for gas. Follow this [guidearrow-up-right](https://support.metamask.io/managing-my-wallet/secret-recovery-phrase-and-private-keys/how-to-export-an-accounts-private-key/) > to get your Private Key on MetaMask. Open a new terminal and deploy the smart contract to the Somnia Network. Run the command: You will see a confirmation message asking if you want to deploy to the Somnia Network. Answer by hitting “**y**” on your keyboard. This will confirm the deployment of the Smart Contract to the Somnia Network. ![](https://docs.somnia.network/~gitbook/image?url=https%3A%2F%2F2122549367-files.gitbook.io%2F%7E%2Ffiles%2Fv0%2Fb%2Fgitbook-x-prod.appspot.com%2Fo%2Fspaces%252FkYErT9t3BJtpPfejLO6I%252Fuploads%252F1WJLxutkGqpORdMMV2iO%252FHardhat2.png%3Falt%3Dmedia%26token%3Dfb2b88e6-c5ee-4521-9185-f8a8b89b8b4a&width=768&dpr=3&quality=100&sign=b4f6d0da&sv=2) Congratulations. 🎉 You have deployed your “BuyMeCoffee” Smart Contract to the Somnia Network using Hardhat. 🎉 [hashtag](https://docs.somnia.network/developer/development-frameworks/deploy-with-hardhat#verify-your-smart-contract) **Verify Your Smart Contract** ---------------------------------------------------------------------------------------------------------------------------------------------------------- After deploying your contract, you can verify it using the [Hardhat Verify pluginarrow-up-right](https://hardhat.org/hardhat-runner/plugins/nomicfoundation-hardhat-verify) . This allows your source code to be visible and validated on the [Somnia Explorerarrow-up-right](https://shannon-explorer.somnia.network/) . #### [hashtag](https://docs.somnia.network/developer/development-frameworks/deploy-with-hardhat#update-hardhat.config.tsadd-the-following-to-your-config-file) Update `hardhat.config.ts`Add the following to your config file: > Store your private key in a `.env` file and import it securely to avoid hardcoding. After deploying your contract, run the Verify command. Copy the deployed address and run: Example for a contract with one string constructor arg: Visit the [Somnia Explorerarrow-up-right](https://shannon-explorer.somnia.network/) and search for your contract address. If successful, the source code will appear under the **“Contract”** tab and show as **verified**. ![](https://docs.somnia.network/~gitbook/image?url=https%3A%2F%2F2122549367-files.gitbook.io%2F%7E%2Ffiles%2Fv0%2Fb%2Fgitbook-x-prod.appspot.com%2Fo%2Fspaces%252FkYErT9t3BJtpPfejLO6I%252Fuploads%252FcN2gHJzFCQwqoaqicN3T%252FVerifiedSC-1.png%3Falt%3Dmedia%26token%3D5d8d8372-1646-4897-ac80-a3081c35efaa&width=768&dpr=3&quality=100&sign=384ab739&sv=2) ![](https://docs.somnia.network/~gitbook/image?url=https%3A%2F%2F2122549367-files.gitbook.io%2F%7E%2Ffiles%2Fv0%2Fb%2Fgitbook-x-prod.appspot.com%2Fo%2Fspaces%252FkYErT9t3BJtpPfejLO6I%252Fuploads%252F2u9IKT2RJTl02qpVVc2S%252FVerified-SC2.png%3Falt%3Dmedia%26token%3Da61d90b4-21a0-49a5-b18f-f38f9f149d9e&width=768&dpr=3&quality=100&sign=dccbfd4d&sv=2) The verified Smart Contracts contain the Source Code, which anyone can review for bugs and malicious code. Users can also connect with and interact with the Verified Smart Contract. [PreviousDeploy with Foundrychevron-left](https://docs.somnia.network/developer/development-frameworks/deploy-with-foundry) [NextUsing the Viem Librarychevron-right](https://docs.somnia.network/developer/development-frameworks/using-the-viem-library) Last updated 1 month ago * [Pre-requisites](https://docs.somnia.network/developer/development-frameworks/deploy-with-hardhat#pre-requisites) * [Initialise Hardhat Project](https://docs.somnia.network/developer/development-frameworks/deploy-with-hardhat#initialise-hardhat-project) * [Create the Smart Contract](https://docs.somnia.network/developer/development-frameworks/deploy-with-hardhat#create-the-smart-contract) * [Compile the Smart Contract](https://docs.somnia.network/developer/development-frameworks/deploy-with-hardhat#compile-the-smart-contract) * [Deploy Contract](https://docs.somnia.network/developer/development-frameworks/deploy-with-hardhat#deploy-contract) * [Verify Your Smart Contract](https://docs.somnia.network/developer/development-frameworks/deploy-with-hardhat#verify-your-smart-contract) Copy npx hardhat init Copy // SPDX-License-Identifier: MIT pragma solidity ^0.8.28; contract BuyMeCoffee { event CoffeeBought( address indexed supporter, uint256 amount, string message, uint256 timestamp ); address public owner; struct Contribution { address supporter; uint256 amount; string message; uint256 timestamp; } Contribution[] public contributions; constructor() { owner = msg.sender; } function buyCoffee(string memory message) external payable { require(msg.value > 0, "Amount must be greater than zero."); contributions.push( Contribution(msg.sender, msg.value, message, block.timestamp) ); emit CoffeeBought(msg.sender, msg.value, message, block.timestamp); } function withdraw() external { require(msg.sender == owner, "Only the owner can withdraw funds."); payable(owner).transfer(address(this).balance); } function getContributions() external view returns (Contribution[] memory) { return contributions; } function setOwner(address newOwner) external { require(msg.sender == owner, "Only the owner can set a new owner."); owner = newOwner; } } Copy npx hardhat compile Copy Compiling... Compiled 1 contract successfully Copy import { buildModule } from "@nomicfoundation/hardhat-ignition/modules"; const BuyMeCoffee = buildModule("BuyMeCoffee", (m) => { const contract = m.contract("BuyMeCoffee"); return { contract }; }); module.exports = BuyMeCoffee; Copy module.exports = { // ... networks: { somnia: { url: "https://dream-rpc.somnia.network", accounts: ["0xPRIVATE_KEY"], // put dev menomonic or PK here, }, }, // ... }; Copy npx hardhat ignition deploy ./ignition/modules/deploy.ts --network somnia Copy import { HardhatUserConfig } from "hardhat/config"; import "@nomicfoundation/hardhat-toolbox"; const config: HardhatUserConfig = { solidity: "0.8.28", networks: { somnia: { url: "https://dream-rpc.somnia.network", accounts: ["YOUR_PRIVATE_KEY"], }, }, sourcify: { enabled: false, }, etherscan: { apiKey: { somnia: "empty", }, customChains: [\ {\ network: "somnia",\ chainId: 50312,\ urls: {\ apiURL: "https://shannon-explorer.somnia.network/api",\ browserURL: "https://shannon-explorer.somnia.network",\ },\ },\ ], }, }; Copy npx hardhat verify --network somnia DEPLOYED_CONTRACT_ADDRESS "ConstructorArgument1" ... Copy npx hardhat verify --network somnia 0xYourContractAddress "YourDeployerWalletAddress" --- # Create ERC20 Tokens | Somnia Docs The Somnia mission is to enable the development of mass-consumer real-time applications. To achieve this as a developer, you will need to build applications that are Token enabled, as this is a requirement for many Blockchain applications. This guide will teach you how to connect to and deploy your ERC20 Smart Contract to the Somia Network using the [Remix IDEarrow-up-right](https://remix.ethereum.org/) . circle-check Somnia Mainnet is LIVE. To deploy on Somnia Mainnet, you will need SOMI Tokens. Please refer to the [guide](https://docs.somnia.network/get-started/getting-started-for-mainnet) on Moving from Testnet to Mainnet. [hashtag](https://docs.somnia.network/developer/building-dapps/tokens-and-nfts/create-erc20-tokens#pre-requisite) Pre-requisite ------------------------------------------------------------------------------------------------------------------------------------ 1. This guide is not an introduction to Solidity Programming; you are expected to understand Basic Solidity Programming. 2. To complete this guide, you will need MetaMask installed and the Somnia Network added to the list of Networks. If you have yet to install MetaMask, please follow this guide to [Connect Your Wallet](https://docs.somnia.network/get-started/connect-your-wallet-to-mainnet) . Somnia Network is an EVM-compatible Layer 1 Blockchain. This means that critical implementation on Ethereum is available on Somnia, with higher throughput and faster finality. Smart Contract that follows the [ERC-20 standardarrow-up-right](https://eips.ethereum.org/EIPS/eip-20) is an ERC-20 token; these Smart Contracts are often referred to as Token Contracts. ERC-20 tokens provide functionality to * Transfer tokens * Allow others to transfer tokens on behalf of the token holder It is important to note that ERC20 Smart Contracts are different from the Native Somnia Contracts, which are used to pay gas fees when transacting on Somnia. In the following steps, we will create an ERC20 Token by following the EIP Standard and also demonstrate the option to use a Library to create the ERC20 Token. [hashtag](https://docs.somnia.network/developer/building-dapps/tokens-and-nfts/create-erc20-tokens#ierc-20) IERC-20 ------------------------------------------------------------------------------------------------------------------------ According to the EIP Standard, certain Smart Contract methods must be implemented adhering to the standard so that other Smart Contracts can interact with the deployed Smart Contracts and call the method. To achieve this, we will use the Solidity Interface Type to create the Smart Contract standard. In Solidity, an interface is a special contract that defines a set of function signatures without implementation. It acts as a "blueprint" for other contracts, ensuring they adhere to a specific structure. Interfaces are crucial for creating standards, such as the ERC-20 token standards, allowing different contracts to interact seamlessly within the EVM ecosystem. Create an Interface for the ERC20 Token. Copy and paste the code below into a file named `IERC20.sol` [hashtag](https://docs.somnia.network/developer/building-dapps/tokens-and-nfts/create-erc20-tokens#erc-20-token-contract) ERC-20 Token Contract ---------------------------------------------------------------------------------------------------------------------------------------------------- With the Interface created and all the Token standard methods implemented, the next step is to import the Interface into the ERC20 Smart Contract implementation. Create a file called `ERC20.sol` and paste the code below into it. We have implemented the various requirements for the ERC20 Token Standard: `**constructor**`: Initializes the token's basic properties. Accepts the token's `name`, `symbol`, and the number of `decimals` as parameters and sets these values as the token's immutable metadata. Example: `ERC20("MyToken", "MTK", 18)` initializes a token named `MyToken` with the symbol `MTK` and 18 decimal places (the standard for ERC-20). ### [hashtag](https://docs.somnia.network/developer/building-dapps/tokens-and-nfts/create-erc20-tokens#functions) Functions `**transfer**`: Moves a specified `amount` of tokens from the _sender_ to a _recipient_. It checks if the sender has enough balance and deducts the amount from the sender's balance, and adds it to the recipient's. It also **emits a Transfer event** to log the transaction and returns \`**true**\` if the transfer is successful. `**approve**`: It allows a _spender_ to spend up to a certain amount of tokens on behalf of the _owner_. It sets the `allowance` for the _spender_ to the specified `amount`. **The spender is usually another Smart Contract**. It then **emits an Approval event** to record the spender's `allowance`. It returns **true** if the `approval` is successful. A common use case is to enable spending through third-party contracts like decentralized exchanges (DEXs). `**transferFrom**`: It allows a _spender_ to transfer tokens on behalf of another account. It checks if the _sender_ has an approved `allowance` for the spender and ensures it is sufficient for the _amount_ and deducts the amount from the `allowance` and the sender's _balance_ and adds the _amount_ to the recipient's balance. It also **emits a Transfer event** to log the transaction. It returns \`**true**\` if the transfer is successful. A common use case is DEXs or smart contracts to handle token transactions on behalf of users. `**_mint**`: Creates new tokens and adds them to a specified account's _balance_. Calling the method increases the recipient's `**balanceOf**` by the specified amount. It also increases the `**totalSupply**` of the ERC20 Tokens by the same amount. A transfer event with the from address as address(0) (indicating tokens are created). `**_burn**`: Destroys a specified number of _tokens_ from an account's _balance_. It reduces the account's `**balanceOf**` and decreases the `**totalSupply**` by the amount. It emits a Transfer event with the to address as address(0) (indicating tokens are burned). It is typically implemented in token-burning mechanisms to reduce supply, increasing scarcity. `**mint**`: Public wrapper for `**_mint**`. It calls `**_mint**` to create new tokens for a specified to address. Allows the contract owner or authorized accounts to mint new tokens. `**burn**`: Public wrapper for `**_burn**`. Calls \_burn to destroy tokens from a specified from address. ### [hashtag](https://docs.somnia.network/developer/building-dapps/tokens-and-nfts/create-erc20-tokens#events) Events `**Transfer**`: Logs token transfers, including minting and burning events. Parameters: from (sender), to (recipient), value (amount). `**Approval**`: Logs approvals of allowances for spenders. Parameters: owner, spender, value (allowance amount). [hashtag](https://docs.somnia.network/developer/building-dapps/tokens-and-nfts/create-erc20-tokens#compile-smart-contract) Compile Smart Contract ------------------------------------------------------------------------------------------------------------------------------------------------------ The ERC20 Token is now ready to be deployed to the Somnia Blockchain. On the left tab, click the “Solidity Compiler” menu item and then the “ Compile ERC20.sol” button. This will compile the Solidity file and convert the Solidity code into machine-readable bytecode. ![](https://docs.somnia.network/~gitbook/image?url=https%3A%2F%2F2122549367-files.gitbook.io%2F%7E%2Ffiles%2Fv0%2Fb%2Fgitbook-x-prod.appspot.com%2Fo%2Fspaces%252FkYErT9t3BJtpPfejLO6I%252Fuploads%252FySYqHAeTuffcUc6GyICA%252FERC20-1.png%3Falt%3Dmedia%26token%3Dcafc024f-0b65-4a57-ad4b-a572e0f101f7&width=768&dpr=3&quality=100&sign=26bc40cc&sv=2) [hashtag](https://docs.somnia.network/developer/building-dapps/tokens-and-nfts/create-erc20-tokens#deploy-smart-contract) Deploy Smart Contract ---------------------------------------------------------------------------------------------------------------------------------------------------- The Smart Contract has been created and compiled into ByteCode, and the ABI has also been created. The next step is to deploy the Smart Contract to the Somnia DevNet so that you can perform READ and WRITE operations. On the left tab, click the “Deploy and run transactions” menu item. To deploy the Smart Contract, we will require a wallet connection. In the Environment dropdown, select the option: “Injected Provider - MetaMask”. Then select the MetaMask account where you have STT Tokens. In the “DEPLOY” field, enter the property values for the ERC20 Token: * “\_NAME” - type string * “\_SYMBOL” - type string * “\_DECIMALS” - type uint8 Click Deploy. When prompted, approve the Contract deployment on your MetaMask. ![](https://docs.somnia.network/~gitbook/image?url=https%3A%2F%2F2122549367-files.gitbook.io%2F%7E%2Ffiles%2Fv0%2Fb%2Fgitbook-x-prod.appspot.com%2Fo%2Fspaces%252FkYErT9t3BJtpPfejLO6I%252Fuploads%252F7Bnmh0xRi9fTFDuYwZ1u%252FERC20-2.png%3Falt%3Dmedia%26token%3D68d16050-9d93-4da7-9581-6a9d5943c762&width=768&dpr=3&quality=100&sign=54bbd915&sv=2) Look at the terminal for the response and the deployed Smart Contract address. You can interact with the Smart Contract via the Remix IDE. Send a transaction to change the name. ![](https://docs.somnia.network/~gitbook/image?url=https%3A%2F%2F2122549367-files.gitbook.io%2F%7E%2Ffiles%2Fv0%2Fb%2Fgitbook-x-prod.appspot.com%2Fo%2Fspaces%252FkYErT9t3BJtpPfejLO6I%252Fuploads%252FY6AD65aY4S9Sjhm1jMZX%252FERC20-3.png%3Falt%3Dmedia%26token%3D056082b4-b772-4da9-b855-f07cf6c60a2b&width=768&dpr=3&quality=100&sign=87857390&sv=2) Congratulations. 🎉 You have deployed an ERC20 Smart Contract to the Somnia Network. 🎉 * * * ### [hashtag](https://docs.somnia.network/developer/building-dapps/tokens-and-nfts/create-erc20-tokens#openzeppelin) OpenZeppelin As was mentioned at the beginning of this Tutorial, there is the option to use a Library to create the ERC20 Token. The OpenZeppelin Smart Contract Library can be used to create an ERC20 Token, and developers can rely on the Smart Contracts wizard to specify particular properties for the created Token. See an example below: The process for deploying this Smart Contract implementation built with OpenZeppelin is the same as Steps 3 and 4 above. [PreviousTokens and NFTschevron-left](https://docs.somnia.network/developer/building-dapps/tokens-and-nfts) [NextCreate ERC721 NFT Collectionschevron-right](https://docs.somnia.network/developer/building-dapps/tokens-and-nfts/create-erc721-nft-collections) Last updated 1 month ago * [Pre-requisite](https://docs.somnia.network/developer/building-dapps/tokens-and-nfts/create-erc20-tokens#pre-requisite) * [IERC-20](https://docs.somnia.network/developer/building-dapps/tokens-and-nfts/create-erc20-tokens#ierc-20) * [ERC-20 Token Contract](https://docs.somnia.network/developer/building-dapps/tokens-and-nfts/create-erc20-tokens#erc-20-token-contract) * [Functions](https://docs.somnia.network/developer/building-dapps/tokens-and-nfts/create-erc20-tokens#functions) * [Events](https://docs.somnia.network/developer/building-dapps/tokens-and-nfts/create-erc20-tokens#events) * [Compile Smart Contract](https://docs.somnia.network/developer/building-dapps/tokens-and-nfts/create-erc20-tokens#compile-smart-contract) * [Deploy Smart Contract](https://docs.somnia.network/developer/building-dapps/tokens-and-nfts/create-erc20-tokens#deploy-smart-contract) * [OpenZeppelin](https://docs.somnia.network/developer/building-dapps/tokens-and-nfts/create-erc20-tokens#openzeppelin) Copy // SPDX-License-Identifier: MIT pragma solidity ^0.8.22; interface IERC20 { function totalSupply() external view returns (uint256); function balanceOf(address account) external view returns (uint256); function transfer(address recipient, uint256 amount) external returns (bool); function allowance(address owner, address spender) external view returns (uint256); function approve(address spender, uint256 amount) external returns (bool); function transferFrom(address sender, address recipient, uint256 amount) external returns (bool); } Copy // SPDX-License-Identifier: MIT pragma solidity ^0.8.22; import "./IERC20.sol"; contract ERC20 is IERC20 { event Transfer(address indexed from, address indexed to, uint256 value); event Approval( address indexed owner, address indexed spender, uint256 value ); uint256 public totalSupply; mapping(address => uint256) public balanceOf; mapping(address => mapping(address => uint256)) public allowance; string public name; string public symbol; uint8 public decimals; constructor(string memory _name, string memory _symbol, uint8 _decimals) { name = _name; symbol = _symbol; decimals = _decimals; } function transfer(address recipient, uint256 amount) external returns (bool) { balanceOf[msg.sender] -= amount; balanceOf[recipient] += amount; emit Transfer(msg.sender, recipient, amount); return true; } function approve(address spender, uint256 amount) external returns (bool) { allowance[msg.sender][spender] = amount; emit Approval(msg.sender, spender, amount); return true; } function transferFrom(address sender, address recipient, uint256 amount) external returns (bool) { allowance[sender][msg.sender] -= amount; balanceOf[sender] -= amount; balanceOf[recipient] += amount; emit Transfer(sender, recipient, amount); return true; } function _mint(address to, uint256 amount) internal { balanceOf[to] += amount; totalSupply += amount; emit Transfer(address(0), to, amount); } function _burn(address from, uint256 amount) internal { balanceOf[from] -= amount; totalSupply -= amount; emit Transfer(from, address(0), amount); } function mint(address to, uint256 amount) external { _mint(to, amount); } function burn(address from, uint256 amount) external { _burn(from, amount); } } Copy // SPDX-License-Identifier: MIT pragma solidity ^0.8.22; import "@openzeppelin/[email protected]/token/ERC20/ERC20.sol"; import "@openzeppelin/[email protected]/token/ERC20/extensions/ERC20Burnable.sol"; import "@openzeppelin/[email protected]/access/Ownable.sol"; contract MyToken is ERC20, ERC20Burnable, Ownable { constructor(address initialOwner) ERC20("MyToken", "MTK") Ownable() {} function mint(address to, uint256 amount) public onlyOwner { _mint(to, amount); } } --- # Support and Community | Somnia Docs [General FAQschevron-right](https://docs.somnia.network/developer/deployment-and-production/support-and-community/general-faqs) [Developer FAQschevron-right](https://docs.somnia.network/developer/deployment-and-production/support-and-community/developer-faqs) [PreviousAPIschevron-left](https://docs.somnia.network/developer/deployment-and-production/ecosystem/ecosystem-tools/apis) [NextGeneral FAQschevron-right](https://docs.somnia.network/developer/deployment-and-production/support-and-community/general-faqs) --- # Build a Realtime On-Chain Game | Somnia Docs This tutorial shows how to build a Tap-to-Play Onchain Game using Somnia Data Streams, where every player’s tap is written directly to the blockchain and the leaderboard updates in realtime. Each tap is stored onchain as a structured data record following a schema. The game uses MetaMask for wallet identity and Somnia Streams SDK to: * Store tap events onchain using `sdk.streams.set()` * Retrieve and rank all players from onchain data By the end of this guide, you’ll have: - A working Next.js app - Onchain data storage using Somnia Data Streams - A live leaderboard that reads blockchain state - MetaMask integration for identity and transaction signing * * * [hashtag](https://docs.somnia.network/developer/data-streams/tutorials/build-a-realtime-on-chain-game#prerequisites) Prerequisites --------------------------------------------------------------------------------------------------------------------------------------- * Node.js 20+ * A funded Somnia Testnet wallet. Kindly get some from the [Faucetarrow-up-right](https://testnet.somnia.network/) * Basic familiarity with TypeScript and Next.js * * * [hashtag](https://docs.somnia.network/developer/data-streams/tutorials/build-a-realtime-on-chain-game#project-setup) Project Setup --------------------------------------------------------------------------------------------------------------------------------------- Initialize a new Next.js app and install dependencies. Create the app by creating a directory where the app will live Copy npx create-next-app@latest somnia-chat --ts --app --no-tailwind cd somnia-chat Install the [Somnia Streamsarrow-up-right](https://www.npmjs.com/package/@somnia-chain/streams) and ViemJS dependencies Create a .env.local file for storing secrets and environmental variables triangle-exclamation Never expose PRIVATE\_KEY to the browser. Keep all publishing code in API routes or server code only. NOTE: You can connect a Privy Wallet (or equivalent) to the SDK, avoiding the need entirely for private keys. * * * [hashtag](https://docs.somnia.network/developer/data-streams/tutorials/build-a-realtime-on-chain-game#chain-configuration) Define Tap Schema ------------------------------------------------------------------------------------------------------------------------------------------------- Create a file `lib/schema.ts`: This schema defines the structure of each tap event: * `timestamp`: when the tap occurred * `player`: who tapped (wallet address) * A `nonce` will be added when the schema is deployed to ensures each record is unique The Schema ID will be automatically computed by the SDK from this schema. * * * [hashtag](https://docs.somnia.network/developer/data-streams/tutorials/build-a-realtime-on-chain-game#setup-clients) Setup Clients --------------------------------------------------------------------------------------------------------------------------------------- Create `lib/serverClient.ts` for server-side reads: Create `lib/clients.ts` for client-side access: * * * [hashtag](https://docs.somnia.network/developer/data-streams/tutorials/build-a-realtime-on-chain-game#writing-tap-data-onchain) Writing Tap Data Onchain ------------------------------------------------------------------------------------------------------------------------------------------------------------- Each tap is recorded onchain with the `sdk.streams.set()` method. In this section, we’ll walk through how each part of the `sendTap()` logic works from wallet connection to writing structured schema data onchain. * * * ### [hashtag](https://docs.somnia.network/developer/data-streams/tutorials/build-a-realtime-on-chain-game#set-up-state-variables) **Set up state variables** We’ll start by tracking a number of states, such as: * the connected wallet address * the wallet client (MetaMask connection) * and a few helper states for loading, cooldowns, and errors. These ensure that you can access the connected wallet address (`address`) and track transaction state (`pending`). It also prevents spam taps with a 1-second cooldown (`cooldownMs`) * * * ### [hashtag](https://docs.somnia.network/developer/data-streams/tutorials/build-a-realtime-on-chain-game#connect-metamask) **Connect MetaMask** We use the browser’s `window.ethereum` API to connect to MetaMask. Once connected, we create a **wallet client** that Somnia’s SDK can use for signing transactions. `createWalletClient` from Viem wraps MetaMask into a signer object that the Somnia SDK can use. This is how the UI and the blockchain are bridged securely. * * * ### [hashtag](https://docs.somnia.network/developer/data-streams/tutorials/build-a-realtime-on-chain-game#initialize-the-sdk) **Initialize the SDK** The **Somnia Data Streams SDK** provides methods to compute schema IDs, encode structured data, and publish to the blockchain. We initialize it with both the **public client** (for chain access) and the **wallet client** (for signing transactions). This gives you full read/write access to the Somnia Streams contract on Somnia Testnet. * * * ### [hashtag](https://docs.somnia.network/developer/data-streams/tutorials/build-a-realtime-on-chain-game#compute-the-schema-id) **Compute the Schema ID** Schemas define how the onchain data is structured. In this case, the tap schema looks like this: Before writing data, we must compute its **unique Schema ID**: This produces a deterministic ID derived from the schema text, ensuring that any app using the same schema can read or decode your data. * * * ### [hashtag](https://docs.somnia.network/developer/data-streams/tutorials/build-a-realtime-on-chain-game#register-the-schema) **Register the Schema** If this schema wasn’t registered yet, we register it once. It’s safe to call this before sending the first message. ### [hashtag](https://docs.somnia.network/developer/data-streams/tutorials/build-a-realtime-on-chain-game#encode-the-data) **Encode the Data** Somnia Streams stores structured data using its `SchemaEncoder` class. We create an encoder and provide each field according to the schema definition. This converts your JavaScript values into the precise binary format that can be stored onchain and later decoded. * * * ### [hashtag](https://docs.somnia.network/developer/data-streams/tutorials/build-a-realtime-on-chain-game#generate-a-unique-data-id) **Generate a Unique Data ID** Each record needs a **unique identifier** within the schema. We use the `keccak256` hash of the player’s address and timestamp to ensure that it is packed into 32 bits of data. This ensures no two taps collide, even if the same player taps rapidly. * * * ### [hashtag](https://docs.somnia.network/developer/data-streams/tutorials/build-a-realtime-on-chain-game#store-the-tap-onchain) **Store the Tap Onchain** Finally, we push the structured data to the blockchain using: The `set()` method writes one or more records (called _Data Streams_) to the chain. Each record is cryptographically signed by the player’s wallet, and gets stored on Somnia’s decentralized data infrastructure. It can also be retrieved instantly using the same schema * * * ### [hashtag](https://docs.somnia.network/developer/data-streams/tutorials/build-a-realtime-on-chain-game#manage-cooldowns-and-feedback) **Manage Cooldowns and Feedback** After the tap is sent, we apply a 1-second cooldown to avoid flooding transactions and reset the pending state. This gives players a smooth UX while maintaining blockchain transaction integrity. * * * #### [hashtag](https://docs.somnia.network/developer/data-streams/tutorials/build-a-realtime-on-chain-game#putting-it-all-together) Putting It All Together Here’s the complete `sendTap()` method with all steps combined: ### [hashtag](https://docs.somnia.network/developer/data-streams/tutorials/build-a-realtime-on-chain-game#complete-page.tsx-code) Complete \`page.tsx\` Code chevron-rightpage.tsx[hashtag](https://docs.somnia.network/developer/data-streams/tutorials/build-a-realtime-on-chain-game#page.tsx) * * * [hashtag](https://docs.somnia.network/developer/data-streams/tutorials/build-a-realtime-on-chain-game#reading-leaderboard-data-onchain) Reading Leaderboard Data Onchain ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- The leaderboard is calculated server-side by reading all tap data stored onchain. Create a `lib/store.ts` file and add the following code: The leaderboard logic begins inside the `getLeaderboard()` function, where we use the **SDK** to read structured tap data directly from the blockchain. First, the function initializes the SDK with a **server-compatible public client**, which allows read-only access to the chain without a connected wallet. The next step computes the `schemaId` by passing our `tapSchema` to `sdk.streams.computeSchemaId()`. This produces a deterministic identifier that ensures we’re always referencing the correct data structure. Once the `schemaId` is known, the core operation happens through `sdk.streams.getAllPublisherDataForSchema(schemaId, publisher)`. This method queries the blockchain for all records written by the specified publisher under that schema. Each returned record is an array of fields that align with the schema’s definition, in this case `[timestamp, player]`. The helper function `val()` is then used to unwrap nested field values (`f?.value?.value`) from the SDK’s response format, giving us clean, readable values. `getAllPublisherDataForSchema` acts like a decentralized “SELECT \* FROM” query, fetching all onchain data tied to a schema and publisher, while the rest of the function transforms that raw blockchain data into a structured leaderboard the app can display. * * * Creat a api route to retrieve Leaderboard score. Create the file `app/api/leaderboard/route.ts` This endpoint imports the `getLeaderboard()` function from `lib/store.ts`, which handles the heavy lifting of querying Somnia Data Streams, and then exposes that onchain data as a clean, JSON-formatted response for your application. The client simply fetches the leaderboard via `/api/leaderboard`. The page.tsx fetches /api/leaderboard every few seconds to stay updated. * * * Every tap executes a real blockchain transaction: Field Description timestamp Time of the tap player Wallet address of the player When the `set()` call succeeds, Somnia Data Streams stores the record and indexes it under your publisher’s address. Any application (including yours) can then read this data and build dashboards, analytics, or game leaderboards. * * * [hashtag](https://docs.somnia.network/developer/data-streams/tutorials/build-a-realtime-on-chain-game#run-the-app) Run the App ----------------------------------------------------------------------------------------------------------------------------------- Open [http://localhost:3000arrow-up-right](http://localhost:3000/) and connect your MetaMask wallet. Click 🖱️ Tap to send onchain transactions, and watch your leaderboard update live. * * * [hashtag](https://docs.somnia.network/developer/data-streams/tutorials/build-a-realtime-on-chain-game#conclusion) Conclusion --------------------------------------------------------------------------------------------------------------------------------- You’ve built a fully onchain game where player interactions are stored via Somnia Data Streams and leaderboard rankings are derived from immutable blockchain data. MetaMask provides secure, user-friendly authentication. This same pattern powers realtime Web3 experiences, from social apps to competitive games, using Somnia’s high-performance onchain data infrastructure. [PreviousBuild a Minimal On-Chain Chat Appchevron-left](https://docs.somnia.network/developer/data-streams/tutorials/build-a-minimal-on-chain-chat-app) [NextSmart Contractschevron-right](https://docs.somnia.network/developer/smart-contracts) Last updated 1 month ago * [Prerequisites](https://docs.somnia.network/developer/data-streams/tutorials/build-a-realtime-on-chain-game#prerequisites) * [Project Setup](https://docs.somnia.network/developer/data-streams/tutorials/build-a-realtime-on-chain-game#project-setup) * [Define Tap Schema](https://docs.somnia.network/developer/data-streams/tutorials/build-a-realtime-on-chain-game#chain-configuration) * [Setup Clients](https://docs.somnia.network/developer/data-streams/tutorials/build-a-realtime-on-chain-game#setup-clients) * [Writing Tap Data Onchain](https://docs.somnia.network/developer/data-streams/tutorials/build-a-realtime-on-chain-game#writing-tap-data-onchain) * [Set up state variables](https://docs.somnia.network/developer/data-streams/tutorials/build-a-realtime-on-chain-game#set-up-state-variables) * [Connect MetaMask](https://docs.somnia.network/developer/data-streams/tutorials/build-a-realtime-on-chain-game#connect-metamask) * [Initialize the SDK](https://docs.somnia.network/developer/data-streams/tutorials/build-a-realtime-on-chain-game#initialize-the-sdk) * [Compute the Schema ID](https://docs.somnia.network/developer/data-streams/tutorials/build-a-realtime-on-chain-game#compute-the-schema-id) * [Register the Schema](https://docs.somnia.network/developer/data-streams/tutorials/build-a-realtime-on-chain-game#register-the-schema) * [Encode the Data](https://docs.somnia.network/developer/data-streams/tutorials/build-a-realtime-on-chain-game#encode-the-data) * [Generate a Unique Data ID](https://docs.somnia.network/developer/data-streams/tutorials/build-a-realtime-on-chain-game#generate-a-unique-data-id) * [Store the Tap Onchain](https://docs.somnia.network/developer/data-streams/tutorials/build-a-realtime-on-chain-game#store-the-tap-onchain) * [Manage Cooldowns and Feedback](https://docs.somnia.network/developer/data-streams/tutorials/build-a-realtime-on-chain-game#manage-cooldowns-and-feedback) * [Complete \`page.tsx\` Code](https://docs.somnia.network/developer/data-streams/tutorials/build-a-realtime-on-chain-game#complete-page.tsx-code) * [Reading Leaderboard Data Onchain](https://docs.somnia.network/developer/data-streams/tutorials/build-a-realtime-on-chain-game#reading-leaderboard-data-onchain) * [Run the App](https://docs.somnia.network/developer/data-streams/tutorials/build-a-realtime-on-chain-game#run-the-app) * [Conclusion](https://docs.somnia.network/developer/data-streams/tutorials/build-a-realtime-on-chain-game#conclusion) Copy npm i @somnia-chain/streams viem Copy NEXT_PUBLIC_PUBLISHER_ADDRESS=0xb6e4fa6ff2873480590c68D9Aa991e5BB14Dbf03 NEXT_PUBLIC_RPC_URL=https://dream-rpc.somnia.network Copy // lib/schema.ts export const tapSchema = 'uint64 timestamp, address player' Copy // lib/serverClient.ts import { createPublicClient, http } from 'viem' import { somniaTestnet } from 'viem/chains' export function getServerPublicClient() { return createPublicClient({ chain: somniaTestnet, transport: http(process.env.RPC_URL || 'https://dream-rpc.somnia.network'), }) } Copy // lib/clients.ts 'use client' import { createPublicClient, http } from 'viem' import { somniaTestnet } from 'viem/chains' export function getPublicHttpClient() { return createPublicClient({ chain: somniaTestnet, transport: http(process.env.NEXT_PUBLIC_RPC_URL || 'https://dream-rpc.somnia.network'), }) } Copy const [address, setAddress] = useState('') const [walletClient, setWalletClient] = useState(null) const [cooldownMs, setCooldownMs] = useState(0) const [pending, setPending] = useState(false) const [error, setError] = useState('') Copy async function connectWallet() { if (typeof window !== "undefined" && window.ethereum !== undefined) try { await window.ethereum.request({ method: "eth_requestAccounts" }); const walletClient = createWalletClient({ chain: somniaDream, transport: custom(window.ethereum), }); const [account] = await walletClient.getAddresses(); setWalletClient(walletClient) setAddress(account) } catch (e: any) { setError(e?.message || String(e)) } setWalletClient(wallet) } Copy const sdk = new SDK({ public: getPublicHttpClient(), wallet: walletClient, }) Copy tapSchema = 'uint64 timestamp, address player' Copy const schemaId = await sdk.streams.computeSchemaId(tapSchema) Copy // Register schema const schemaId = await sdk.streams.computeSchemaId(chatSchema) const isRegistered = await sdk.streams.isDataSchemaRegistered(schemaId) if (!isRegistered) { const ignoreAlreadyRegistered = true const txHash = await sdk.streams.registerDataSchemas( [{ schemaName: 'tap', schema: tapSchema, parentSchemaId: zeroBytes32 }], ignoreAlreadyRegistered ) if (!txHash) throw new Error('Failed to register schema') await waitForTransactionReceipt(getPublicHttpClient(), { hash: txHash }) } Copy const encoder = new SchemaEncoder(tapSchema) const now = BigInt(Date.now()) const data = encoder.encodeData([\ { name: 'timestamp', value: now, type: 'uint64' },\ { name: 'player', value: address, type: 'address' },\ ]) Copy const id = keccak256(toHex(`${address}-${Number(nonce)}`)) Copy await sdk.streams.set([{ id, schemaId, data }]) Copy setCooldownMs(1000) setPending(false) Copy async function sendTap() { if (!walletClient || !address) return setPending(true) const sdk = new SDK({ public: getPublicHttpClient(), wallet: walletClient }) const schemaId = await sdk.streams.computeSchemaId(tapSchema) const encoder = new SchemaEncoder(tapSchema) const now = BigInt(Date.now()) const data = encoder.encodeData([\ { name: 'timestamp', value: now, type: 'uint64' },\ { name: 'player', value: address, type: 'address' },\ ]) const id = keccak256(toHex(`${address}-${Number(now)}`)) await sdk.streams.set([{ id, schemaId, data }]) setCooldownMs(1000) setPending(false) } Copy 'use client' import { useState, useEffect, useRef } from 'react' import { SDK, SchemaEncoder } from '@somnia-chain/streams' import { getPublicHttpClient } from '@/lib/clients' import { tapSchema } from '@/lib/schema' import { keccak256, toHex, createWalletClient, custom } from 'viem' import { somniaTestnet } from 'viem/chains' export default function Page() { const [address, setAddress] = useState('') const [walletClient, setWalletClient] = useState(null) const [leaderboard, setLeaderboard] = useState<{ address: string; count: number }[]>([]) const [cooldownMs, setCooldownMs] = useState(0) const [pending, setPending] = useState(false) const [error, setError] = useState('') const lastNonce = useRef(0) async function connectWallet() { const accounts = await window.ethereum.request({ method: 'eth_requestAccounts' }) const wallet = createWalletClient({ chain: somniaTestnet, transport: custom(window.ethereum), }) setAddress(accounts[0]) setWalletClient(wallet) } async function sendTap() { if (!walletClient || !address) return setPending(true) const sdk = new SDK({ public: getPublicHttpClient(), wallet: walletClient }) const schemaId = await sdk.streams.computeSchemaId(tapSchema) const encoder = new SchemaEncoder(tapSchema) const now = BigInt(Date.now()) const data = encoder.encodeData([\ { name: 'timestamp', value: now, type: 'uint64' },\ { name: 'player', value: address, type: 'address' },\ { name: 'nonce', value: BigInt(lastNonce.current++), type: 'uint256' },\ ]) const id = keccak256(toHex(`${address}-${Number(now)}`)) await sdk.streams.set([{ id, schemaId, data }]) setCooldownMs(1000) setPending(false) } return (

🚀 Somnia Tap Game

{!address ? ( ) : (

Connected: {address.slice(0, 6)}...{address.slice(-4)}

)} {error &&

{error}

}
) } function Leaderboard({ leaderboard }: { leaderboard: { address: string; count: number }[] }) { if (!leaderboard.length) return

No taps yet

return (
    {leaderboard.map((p, i) => (
  1. #{i + 1} {p.address} — {p.count} taps
  2. ))}
) } Copy lib/store.ts import { SDK } from '@somnia-chain/streams' import { getServerPublicClient } from './serverClient' import { tapSchema } from './schema' const publisher = process.env.NEXT_PUBLIC_PUBLISHER_ADDRESS || '0x0000000000000000000000000000000000000000' const val = (f: any) => f?.value?.value ?? f?.value export async function getLeaderboard() { const sdk = new SDK({ public: getServerPublicClient() }) const schemaId = await sdk.streams.computeSchemaId(tapSchema) const rows = await sdk.streams.getAllPublisherDataForSchema(schemaId, publisher) if (!Array.isArray(rows)) return [] const counts = new Map() for (const row of rows) { const player = String(val(row[1]) ?? '').toLowerCase() if (!player.startsWith('0x')) continue counts.set(player, (counts.get(player) || 0) + 1) } return Array.from(counts.entries()) .map(([address, count]) => ({ address, count })) .sort((a, b) => b.count - a.count) } Copy import { NextResponse } from 'next/server' import { getLeaderboard } from '@/lib/store' export async function GET() { const leaderboard = await getLeaderboard() return NextResponse.json({ leaderboard }) } Copy npm run dev --- # Example Applications | Somnia Docs This section showcases **hands-on examples** of real applications built on Somnia, from small experimental dApps to full scale and production ready projects. Each example walks you through architecture, deployment, and integration patterns, helping you understand **how all the pieces fit together:** contracts, subgraphs, oracles, APIs, and SDKs. You’ll find examples like: * Token swap dApps (DEX) * DAO Smart Contract * DAO User Interface > These examples are meant to inspire and guide you. Use them as blueprints, remix them, or extend them — and build the next great dApp on Somnia. [PreviousUsing Verifiable Randomness (VRF)chevron-left](https://docs.somnia.network/developer/building-dapps/oracles/using-verifiable-randomness-vrf) [NextDAO Smart Contractchevron-right](https://docs.somnia.network/developer/building-dapps/example-applications/dao-smart-contract) Last updated 1 month ago --- # Meme Coins | Somnia Docs Launch and trade meme coins on Somnia Network. Earn passive income with Revenue Key NFTs. 1 [hashtag](https://docs.somnia.network/developer/deployment-and-production/ecosystem/ecosystem-showcase/meme-coins#buying-meme-coins) Buying Meme Coins ----------------------------------------------------------------------------------------------------------------------------------------------------------- 1 #### [hashtag](https://docs.somnia.network/developer/deployment-and-production/ecosystem/ecosystem-showcase/meme-coins#connect-wallet) Connect Wallet Visit [somnia.memearrow-up-right](https://somnia.meme/) and connect your Web3 wallet. 2 #### [hashtag](https://docs.somnia.network/developer/deployment-and-production/ecosystem/ecosystem-showcase/meme-coins#browse-tokens) Browse Tokens Explore available meme coins on the platform. 3 #### [hashtag](https://docs.somnia.network/developer/deployment-and-production/ecosystem/ecosystem-showcase/meme-coins#select-amount) Select Amount Choose how much you want to buy. 4 #### [hashtag](https://docs.somnia.network/developer/deployment-and-production/ecosystem/ecosystem-showcase/meme-coins#execute-trade) Execute Trade Confirm transaction in your wallet. [hashtag](https://docs.somnia.network/developer/deployment-and-production/ecosystem/ecosystem-showcase/meme-coins#selling-meme-coins) Selling Meme Coins ------------------------------------------------------------------------------------------------------------------------------------------------------------- 1 #### [hashtag](https://docs.somnia.network/developer/deployment-and-production/ecosystem/ecosystem-showcase/meme-coins#access-portfolio) Access Portfolio Go to your token holdings. 2 #### [hashtag](https://docs.somnia.network/developer/deployment-and-production/ecosystem/ecosystem-showcase/meme-coins#choose-token) Choose Token Select the meme coin you want to sell. 3 #### [hashtag](https://docs.somnia.network/developer/deployment-and-production/ecosystem/ecosystem-showcase/meme-coins#set-amount) Set Amount Enter how much to sell. 4 #### [hashtag](https://docs.somnia.network/developer/deployment-and-production/ecosystem/ecosystem-showcase/meme-coins#confirm-sale) Confirm Sale Complete the transaction. [hashtag](https://docs.somnia.network/developer/deployment-and-production/ecosystem/ecosystem-showcase/meme-coins#how-to-launch-your-own-meme-coin) How to Launch Your Own Meme Coin ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- #### [hashtag](https://docs.somnia.network/developer/deployment-and-production/ecosystem/ecosystem-showcase/meme-coins#step-by-step-launch-process) Step-by-Step Launch Process 1 #### [hashtag](https://docs.somnia.network/developer/deployment-and-production/ecosystem/ecosystem-showcase/meme-coins#access-launch-page) Access Launch Page Click "Launch Token" on [somnia.memearrow-up-right](https://somnia.meme/) 2 #### [hashtag](https://docs.somnia.network/developer/deployment-and-production/ecosystem/ecosystem-showcase/meme-coins#set-token-details) Set Token Details * Token name (e.g., "DogeCoin2024") * Token symbol (e.g., "DOGE24") * Total supply * Token description 3 #### [hashtag](https://docs.somnia.network/developer/deployment-and-production/ecosystem/ecosystem-showcase/meme-coins#configure-parameters) Configure Parameters * Initial price * Liquidity settings * Trading parameters 4 #### [hashtag](https://docs.somnia.network/developer/deployment-and-production/ecosystem/ecosystem-showcase/meme-coins#deploy) Deploy Pay deployment fees and confirm transaction. 5 #### [hashtag](https://docs.somnia.network/developer/deployment-and-production/ecosystem/ecosystem-showcase/meme-coins#your-token-goes-live) Your Token Goes Live Start trading immediately on the platform. [hashtag](https://docs.somnia.network/developer/deployment-and-production/ecosystem/ecosystem-showcase/meme-coins#bonus-revenue-keys-for-passive-income) Bonus: Revenue Keys for Passive Income ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- #### [hashtag](https://docs.somnia.network/developer/deployment-and-production/ecosystem/ecosystem-showcase/meme-coins#earn-from-platform-activity) Earn from Platform Activity Get Revenue Key NFTs to earn passive income from all platform trading fees: 1 * **Purple Key**: 28% revenue share, 1,000 supply * **Blue Key**: 32% revenue share, 1,333 supply * **Yellow Key**: 15% revenue share, 9,000 supply - **FREE** (complete 10 trades + launch a token) 1 #### [hashtag](https://docs.somnia.network/developer/deployment-and-production/ecosystem/ecosystem-showcase/meme-coins#how-to-get-keys) How to Get Keys 1 #### [hashtag](https://docs.somnia.network/developer/deployment-and-production/ecosystem/ecosystem-showcase/meme-coins#buy-blue-key) Buy Blue Key Visit [somnia.meme/mintarrow-up-right](https://somnia.meme/mint) (max 10 per transaction) 1 2 #### [hashtag](https://docs.somnia.network/developer/deployment-and-production/ecosystem/ecosystem-showcase/meme-coins#earn-yellow-key) Earn Yellow Key Trade 10 times + launch 1 token = FREE key 1 3 #### [hashtag](https://docs.somnia.network/developer/deployment-and-production/ecosystem/ecosystem-showcase/meme-coins#claim-rewards) Claim Rewards Visit [portal.somnia.memearrow-up-right](https://portal.somnia.meme/) anytime 2 _Image placeholder: Revenue Keys overview_ [hashtag](https://docs.somnia.network/developer/deployment-and-production/ecosystem/ecosystem-showcase/meme-coins#faq) FAQ ------------------------------------------------------------------------------------------------------------------------------- chevron-rightHow much does it cost to launch a meme coin?[hashtag](https://docs.somnia.network/developer/deployment-and-production/ecosystem/ecosystem-showcase/meme-coins#how-much-does-it-cost-to-launch-a-meme-coin) 3.5 SOMI + gas chevron-rightCan I trade immediately after launching?[hashtag](https://docs.somnia.network/developer/deployment-and-production/ecosystem/ecosystem-showcase/meme-coins#can-i-trade-immediately-after-launching) Yes! Your token goes live instantly and can be traded right away. chevron-rightWhat wallets work with Somnia Meme?[hashtag](https://docs.somnia.network/developer/deployment-and-production/ecosystem/ecosystem-showcase/meme-coins#what-wallets-work-with-somnia-meme) MetaMask, WalletConnect, and other Web3 wallets compatible with Somnia Network. chevron-rightDo I need Revenue Keys to trade or launch?[hashtag](https://docs.somnia.network/developer/deployment-and-production/ecosystem/ecosystem-showcase/meme-coins#do-i-need-revenue-keys-to-trade-or-launch) No! Keys are optional for earning passive income from platform fees. * * * [PreviousElix.fichevron-left](https://docs.somnia.network/developer/deployment-and-production/ecosystem/ecosystem-showcase/elix.fi) [NextNFTs2Mechevron-right](https://docs.somnia.network/developer/deployment-and-production/ecosystem/ecosystem-showcase/nfts2me) Last updated 4 months ago * [Buying Meme Coins](https://docs.somnia.network/developer/deployment-and-production/ecosystem/ecosystem-showcase/meme-coins#buying-meme-coins) * [Selling Meme Coins](https://docs.somnia.network/developer/deployment-and-production/ecosystem/ecosystem-showcase/meme-coins#selling-meme-coins) * [How to Launch Your Own Meme Coin](https://docs.somnia.network/developer/deployment-and-production/ecosystem/ecosystem-showcase/meme-coins#how-to-launch-your-own-meme-coin) * [Bonus: Revenue Keys for Passive Income](https://docs.somnia.network/developer/deployment-and-production/ecosystem/ecosystem-showcase/meme-coins#bonus-revenue-keys-for-passive-income) * [FAQ](https://docs.somnia.network/developer/deployment-and-production/ecosystem/ecosystem-showcase/meme-coins#faq) --- # NFTs2Me | Somnia Docs NFTs2Me is a comprehensive no-code toolkit for creating, deploying, and managing NFT collections on any EVM blockchain. The platform offers everything from simple editions to complex generative art collections, all with advanced features like enforced royalties and token gating. [hashtag](https://docs.somnia.network/developer/deployment-and-production/ecosystem/ecosystem-showcase/nfts2me#create-section-features) Create Section Features -------------------------------------------------------------------------------------------------------------------------------------------------------------------- #### [hashtag](https://docs.somnia.network/developer/deployment-and-production/ecosystem/ecosystem-showcase/nfts2me#editions-artwork-multiple-nfts) Editions (Artwork - Multiple NFTs) The easiest way to start your NFT journey. Create collections where all NFTs share the same artwork. 3 1 #### [hashtag](https://docs.somnia.network/developer/deployment-and-production/ecosystem/ecosystem-showcase/nfts2me#create-an-edition) Create an Edition * Connect your wallet to NFTs2Me * Click "Editions" from the create menu * Set project name, token symbol, and description * Upload artwork or generate using AI * Set minting fee and edition size * Deploy to your chosen blockchain #### [hashtag](https://docs.somnia.network/developer/deployment-and-production/ecosystem/ecosystem-showcase/nfts2me#drop-multiple-artworks-multiple-nfts) Drop (Multiple Artworks - Multiple NFTs) Perfect when you have specific images for each NFT. Assign different artwork to each token in your collection. **Key Features:** * Upload multiple unique artworks * Individual metadata for each NFT * Customizable rarity traits * Batch upload support #### [hashtag](https://docs.somnia.network/developer/deployment-and-production/ecosystem/ecosystem-showcase/nfts2me#generative-art-combination-of-artworks) Generative Art (Combination of Artworks) Advanced mode for creating thousands of unique NFTs by combining image layers. 2 1 #### [hashtag](https://docs.somnia.network/developer/deployment-and-production/ecosystem/ecosystem-showcase/nfts2me#prepare-files) Prepare files * Create or upload PSD files with layers 2 #### [hashtag](https://docs.somnia.network/developer/deployment-and-production/ecosystem/ecosystem-showcase/nfts2me#configure-rarities) Configure rarities * Set rarity percentages for each trait 3 #### [hashtag](https://docs.somnia.network/developer/deployment-and-production/ecosystem/ecosystem-showcase/nfts2me#configure-combinations) Configure combinations * Configure layer combinations 4 #### [hashtag](https://docs.somnia.network/developer/deployment-and-production/ecosystem/ecosystem-showcase/nfts2me#auto-background-and-preview) Auto-background and preview * Generate auto-backgrounds * Preview combinations in real-time 5 #### [hashtag](https://docs.somnia.network/developer/deployment-and-production/ecosystem/ecosystem-showcase/nfts2me#generate-collection) Generate collection * Generate complete collection **Features:** * Drag-and-drop layer management * Rarity configuration per trait * Unlimited preview generations * Auto-background generation * PSD file support [hashtag](https://docs.somnia.network/developer/deployment-and-production/ecosystem/ecosystem-showcase/nfts2me#dashboard) Dashboard ---------------------------------------------------------------------------------------------------------------------------------------- Powerful control center for managing your NFT collections with advanced features. 1 **Dashboard Capabilities:** * Multi-phase sales management * Airdrops and whitelists * Snapshot tools * Revenue tracking * Collection analytics * Minting page customization * * * Visit [nfts2me.comarrow-up-right](https://nfts2me.com/) to start creating your NFT collection. [PreviousMeme Coinschevron-left](https://docs.somnia.network/developer/deployment-and-production/ecosystem/ecosystem-showcase/meme-coins) [NextSomnia Domains (.somi)chevron-right](https://docs.somnia.network/developer/deployment-and-production/ecosystem/ecosystem-showcase/somnia-domains-.somi) Last updated 4 months ago * [Create Section Features](https://docs.somnia.network/developer/deployment-and-production/ecosystem/ecosystem-showcase/nfts2me#create-section-features) * [Dashboard](https://docs.somnia.network/developer/deployment-and-production/ecosystem/ecosystem-showcase/nfts2me#dashboard) --- # DAO UI Tutorial p1 | Somnia Docs Somnia empowers developers to build applications for mass adoption. Smart Contracts deployed on the Somnia Blockchain will sometimes require building a User Interface. This guide will teach you how to build a user interface for a [DAO Smart Contract](https://docs.somnia.network/developer/building-dapps/example-applications/dao-smart-contract) using Next.js and React Context. It is divided into three parts. At the end of this guide, you’ll learn how to: 1. Initialize a Next.js project. 2. Set up a global state using the Context API ([`**useContext**`arrow-up-right](https://react.dev/reference/react/useContext) hook). 3. Add a global NavBar in `**_app.js**` so it appears on every page. You will have a basic skeleton of a DApp, ready for **READ/WRITE** operations and UI components —topics we’ll cover in the subsequent articles. * * * [hashtag](https://docs.somnia.network/developer/building-dapps/example-applications/dao-ui-tutorial-p1#pre-requisites) Pre-requisites: ------------------------------------------------------------------------------------------------------------------------------------------- 1. This guide is not an introduction to JavaScript Programming; you are expected to understand JavaScript. 2. To complete this guide, you will need MetaMask installed and the Somnia Network added to the list of Networks. If you have yet to install MetaMask, please follow this guide to [Connect Your Walletarrow-up-right](https://codex.somnia.network/get-started/connect-your-wallet) . [hashtag](https://docs.somnia.network/developer/building-dapps/example-applications/dao-ui-tutorial-p1#create-your-next.js-project) Create Your Next.js Project -------------------------------------------------------------------------------------------------------------------------------------------------------------------- To create a NextJS project, run the command: Copy npx create-next-app my-dapp-ui Accept the prompts and change directory into the folder after the build is completed. This gives you a minimal Next.js setup with a pages folder (holding your routes), a public folder (for static assets), and config files. #### [hashtag](https://docs.somnia.network/developer/building-dapps/example-applications/dao-ui-tutorial-p1#optional-add-tailwind-css) (Optional) Add Tailwind CSS If you plan to style your app with Tailwind, install and configure it now: Then edit your tailwind.config.js: Finally, include Tailwind in styles/globals.css: * * * [hashtag](https://docs.somnia.network/developer/building-dapps/example-applications/dao-ui-tutorial-p1#setting-up-a-react-context-for-global-state) Setting Up a React Context for Global State ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- In many DApps, including this one, developers will manage **Wallet connection** and **State** globally so each page and component in the project can access it without repetitive code. This can be achieved by a [Contextarrow-up-right](https://react.dev/learn/passing-data-deeply-with-context) following React patterns. * Create a folder and name it `**contexts**` at the project root or inside `**pages**` directory. * Inside it, create a file called `**walletcontext.js**` add the following code to the file: chevron-rightwalletcontext.js[hashtag](https://docs.somnia.network/developer/building-dapps/example-applications/dao-ui-tutorial-p1#walletcontext.js) We parse three class methods from React: `**createContext**`, `**useContext**`, and`**useState**` `createContext` is used to create a [contextarrow-up-right](https://react.dev/learn/passing-data-deeply-with-context) that components can provide or read. In this example, we assign `createContext` to the `**WalletContext**` variable and call the `**Provider**` method on each State and Function to make them available throughout the application. `**useWallet()**` is a custom hook, so any page or component can do: to access the global wallet state i.e. any of the `**Wallet.Provider**` methods `**connectToMetaMask()**` triggers the MetaMask connection flow. `**WalletProvider**` manages **State** and **Methods** in the application. * * * [hashtag](https://docs.somnia.network/developer/building-dapps/example-applications/dao-ui-tutorial-p1#creating-a-global-navbar-in-_app.js) Creating a Global NavBar in \_app.js ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- Next.js automatically uses `**pages/_app.js**` to initialize every page. We will wrap the entire app in the `**WalletProvider**` inside `_app.js` and inject a `**NavBar**` menu that appears site-wide in the application #### [hashtag](https://docs.somnia.network/developer/building-dapps/example-applications/dao-ui-tutorial-p1#create-the-_app.js-and-add-the-code) Create the `_app.js` and add the code: `****` wraps the entire `****` tree so that every page can share the same wallet state. `****` is placed above `**
**`, so it’s visible on all pages. We give `**
**` a pt-16 to avoid content hiding behind a fixed navbar. [hashtag](https://docs.somnia.network/developer/building-dapps/example-applications/dao-ui-tutorial-p1#navbar) NavBar -------------------------------------------------------------------------------------------------------------------------- Create a sub directory **components** and add a file called `**navbar.js**` Add the code: It uses `**useWallet()**` to read the global `connected` and `address`states, and the `**disconnectWallet**` function. The truncated `address` is displayed if a user is logged into the App or “Not connected” otherwise. A **Logout** button calls `**disconnectWallet()**` to reset the global state. * * * [hashtag](https://docs.somnia.network/developer/building-dapps/example-applications/dao-ui-tutorial-p1#test-your-setup) Test Your Setup -------------------------------------------------------------------------------------------------------------------------------------------- Start the dev server: Open **http://localhost:3000** in a Web Browser. You should see your NavBar at the top. ![](https://docs.somnia.network/~gitbook/image?url=https%3A%2F%2F2122549367-files.gitbook.io%2F%7E%2Ffiles%2Fv0%2Fb%2Fgitbook-x-prod.appspot.com%2Fo%2Fspaces%252FkYErT9t3BJtpPfejLO6I%252Fuploads%252Fl9fqbfW5Kr8EGo0TI4H1%252FNot_Logged_In.png%3Falt%3Dmedia%26token%3D7ed9498d-59ea-404a-89d8-e2e196073877&width=768&dpr=3&quality=100&sign=21694a46&sv=2) Not Logged In ![](https://docs.somnia.network/~gitbook/image?url=https%3A%2F%2F2122549367-files.gitbook.io%2F%7E%2Ffiles%2Fv0%2Fb%2Fgitbook-x-prod.appspot.com%2Fo%2Fspaces%252FkYErT9t3BJtpPfejLO6I%252Fuploads%252FyctcgnYDLdzAshYqW54b%252FLogged_In.png%3Falt%3Dmedia%26token%3D6f8b2631-921e-444a-a11d-0e8c91dd42f1&width=768&dpr=3&quality=100&sign=66425b8b&sv=2) Logged In Because we haven’t built any advanced pages yet, you will see a blank home page. The important part is that your **WalletContext** and global **NavBar** are in place and ready for the next steps. * * * ### [hashtag](https://docs.somnia.network/developer/building-dapps/example-applications/dao-ui-tutorial-p1#id-5.-next-steps) 5\. Next Steps * Article 2 shows you how to implement READ/WRITE operations (e.g., deposit, create proposals, vote, etc.) across different Next.js pages—using the same WalletContext to handle contract calls. * Article 3 will focus on UI components, like forms, buttons, and event handling, tying it all together into a polished user interface. Congratulations! You have a clean foundation, a Next.js project configured with Tailwind, a global context to manage wallet states, and a NavBar appearing across all routes. This sets the stage for adding contract interactions and advanced UI flows in the subsequent articles. Happy building! [PreviousDAO Smart Contractchevron-left](https://docs.somnia.network/developer/building-dapps/example-applications/dao-smart-contract) [NextDAO UI Tutorial p2chevron-right](https://docs.somnia.network/developer/building-dapps/example-applications/dao-ui-tutorial-p2) Last updated 10 months ago * [Pre-requisites:](https://docs.somnia.network/developer/building-dapps/example-applications/dao-ui-tutorial-p1#pre-requisites) * [Create Your Next.js Project](https://docs.somnia.network/developer/building-dapps/example-applications/dao-ui-tutorial-p1#create-your-next.js-project) * [Setting Up a React Context for Global State](https://docs.somnia.network/developer/building-dapps/example-applications/dao-ui-tutorial-p1#setting-up-a-react-context-for-global-state) * [Creating a Global NavBar in \_app.js](https://docs.somnia.network/developer/building-dapps/example-applications/dao-ui-tutorial-p1#creating-a-global-navbar-in-_app.js) * [NavBar](https://docs.somnia.network/developer/building-dapps/example-applications/dao-ui-tutorial-p1#navbar) * [Test Your Setup](https://docs.somnia.network/developer/building-dapps/example-applications/dao-ui-tutorial-p1#test-your-setup) * [5\. Next Steps](https://docs.somnia.network/developer/building-dapps/example-applications/dao-ui-tutorial-p1#id-5.-next-steps) Copy npm install -D tailwindcss postcss autoprefixer npx tailwindcss init -p Copy module.exports = { content: [\ "./pages/**/*.{js,ts,jsx,tsx}",\ "./components/**/*.{js,ts,jsx,tsx}",\ ], theme: { extend: {}, }, plugins: [], } Copy @tailwind base; @tailwind components; @tailwind utilities; Copy import { createContext, useContext, useState } from "react"; const WalletContext = createContext(); export function WalletProvider({ children }) { const [connected, setConnected] = useState(false); const [address, setAddress] = useState(""); async function connectToMetaMask() { if (typeof window !== "undefined" && window.ethereum) { try { await window.ethereum.request({ method: "eth_requestAccounts" }); // For simplicity, get the first address const [userAddress] = window.ethereum.selectedAddress ? [window.ethereum.selectedAddress] : []; setAddress(userAddress); setConnected(true); } catch (err) { console.error("User denied account access:", err); } } else { console.log("MetaMask is not installed!"); } } function disconnectWallet() { setConnected(false); setAddress(""); } // Return the context provider return ( {children} ); } export function useWallet() { return useContext(WalletContext); } Copy const { connected, ... } = useWallet() Copy import "../styles/globals.css"; import { WalletProvider } from "../contexts/walletcontext"; import NavBar from "../components/navbar"; function MyApp({ Component, pageProps }) { return (
); } export default MyApp; Copy import { useWallet } from "../contexts/walletcontext"; import Link from "next/link"; export default function NavBar() { const { connected, address, disconnectWallet } = useWallet(); return ( ); } Copy npm run dev --- # Authenticating with RainbowKit | Somnia Docs In this guide, we'll integrate [RainbowKitarrow-up-right](https://www.rainbowkit.com/docs/introduction) with the Somnia Network in a Next.js application. This will enable users to connect their wallets seamlessly, facilitating interactions with the Somnia blockchain. [hashtag](https://docs.somnia.network/developer/building-dapps/wallet-integration-and-auth/authenticating-with-rainbowkit#prerequisites) Prerequisites ----------------------------------------------------------------------------------------------------------------------------------------------------------- Before we begin, ensure you have the following: 1. This guide is not an introduction to JavaScript Programming; you are expected to understand JavaScript. 2. To complete this guide, you will need MetaMask installed and the Somnia DevNet added to the list of Networks. If you have yet to install MetaMask, please follow this guide to [Connect Your Wallet guide](https://docs.somnia.network/get-started/connect-your-wallet-to-mainnet) . 3. Familiarity with React and Next.js is assumed. [hashtag](https://docs.somnia.network/developer/building-dapps/wallet-integration-and-auth/authenticating-with-rainbowkit#create-the-next.js-project) Create the Next.js Project ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- Open your terminal and run the following commands to set up a new Next.js project: Copy npx create-next-app@latest somnia-rainbowkit cd somnia-rainbowkit Install the required Dependencies, which are `**wagmi**`, `**viem**`, @tanstack/react-query, and rainbowkit. Run the following command: Copy npm install wagmi viem @tanstack/react-query rainbowkit ### [hashtag](https://docs.somnia.network/developer/building-dapps/wallet-integration-and-auth/authenticating-with-rainbowkit#set-up-providers-in-next.js) Set Up Providers in Next.js We'll set up several providers to manage the application's state and facilitate interactions with the blockchain. Create a `**components**` directory in the app folder. Inside the components directory, create a file named `**ClientProvider.tsx**` with the following content: > Every Rainbowkit dApp relies on **WalletConnect** and needs to obtain a `**projectId**` from WalletConnect Cloud. Get one [herearrow-up-right](https://cloud.walletconnect.com/) > . In the app directory, locate the `**layout.tsx**` file and update it as follows: ### [hashtag](https://docs.somnia.network/developer/building-dapps/wallet-integration-and-auth/authenticating-with-rainbowkit#build-the-home-page) Build the Home Page We'll create a simple home page that allows users to connect their wallets and displays their address upon connection. In the app directory, locate the `**page.tsx**` file and update it as follows: ### [hashtag](https://docs.somnia.network/developer/building-dapps/wallet-integration-and-auth/authenticating-with-rainbowkit#run-the-application) Run the Application Start the Development Server by running the following command: Open your browser and navigate to http://localhost:3000. You should see the RainbowKit button, allowing users to connect their wallets to the Somnia network. [hashtag](https://docs.somnia.network/developer/building-dapps/wallet-integration-and-auth/authenticating-with-rainbowkit#conclusion) Conclusion ----------------------------------------------------------------------------------------------------------------------------------------------------- You've successfully integrated RainbowKit with the Somnia Network in a Next.js application. This setup provides a foundation for building decentralized applications on Somnia, enabling seamless wallet connections and interactions with the Somnia Network. For further exploration, consider adding features such as interacting with smart contracts, displaying user balances, or implementing transaction functionalities. If you encounter any issues or need assistance, join the [Somnia Developer Discordarrow-up-right](https://discord.gg/somnia) . [PreviousAuthenticating with Privychevron-left](https://docs.somnia.network/developer/building-dapps/wallet-integration-and-auth/authenticating-with-privy) [NextOnRampschevron-right](https://docs.somnia.network/developer/building-dapps/onramps) Last updated 5 months ago * [Prerequisites](https://docs.somnia.network/developer/building-dapps/wallet-integration-and-auth/authenticating-with-rainbowkit#prerequisites) * [Create the Next.js Project](https://docs.somnia.network/developer/building-dapps/wallet-integration-and-auth/authenticating-with-rainbowkit#create-the-next.js-project) * [Set Up Providers in Next.js](https://docs.somnia.network/developer/building-dapps/wallet-integration-and-auth/authenticating-with-rainbowkit#set-up-providers-in-next.js) * [Build the Home Page](https://docs.somnia.network/developer/building-dapps/wallet-integration-and-auth/authenticating-with-rainbowkit#build-the-home-page) * [Run the Application](https://docs.somnia.network/developer/building-dapps/wallet-integration-and-auth/authenticating-with-rainbowkit#run-the-application) * [Conclusion](https://docs.somnia.network/developer/building-dapps/wallet-integration-and-auth/authenticating-with-rainbowkit#conclusion) Copy 'use client'; import { WagmiProvider } from "wagmi"; import { RainbowKitProvider, getDefaultConfig, } from "@rainbow-me/rainbowkit"; import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; import { somniaTestnet } from "viem/chains"; const queryClient = new QueryClient(); const config = getDefaultConfig({ appName: "Somnia Example App", projectId: "Get_WalletConnect_ID", chains: [somniaTestnet], ssr: true, }); export default function ClientProvider({ children }) { return ( {children} ); } Copy import ClientProvider from './components/ClientProvider'; export default function RootLayout({ children }) { return ( Somnia DApp {children} ); } Copy 'use client'; import { useAccount } from 'wagmi'; import { ConnectButton } from "@rainbow-me/rainbowkit"; export default function Home() { const { address, isConnected } = useAccount(); return (
{isConnected && (

Connected as: {address}

)}
); } Copy npm run dev --- # APIs | Somnia Docs [hashtag](https://docs.somnia.network/developer/deployment-and-production/ecosystem/ecosystem-tools/apis#ormi) [Ormiarrow-up-right](https://ormilabs.com/) ---------------------------------------------------------------------------------------------------------------------------------------------------------------- Ormi is the only unified Web3 data layer to supercharge live, historical, and AI-powered blockchain data applications ### [hashtag](https://docs.somnia.network/developer/deployment-and-production/ecosystem/ecosystem-tools/apis#resources) Resources * [API Listarrow-up-right](https://subgraph.somnia.network/) * [Documentation](https://docs.somnia.network/developer/building-dapps/data-indexing-and-querying/using-data-apis-ormi) [PreviousAccount Abstractionchevron-left](https://docs.somnia.network/developer/deployment-and-production/ecosystem/ecosystem-tools/account-abstraction) [NextSupport and Communitychevron-right](https://docs.somnia.network/developer/deployment-and-production/support-and-community) Last updated 6 months ago * [Ormi](https://docs.somnia.network/developer/deployment-and-production/ecosystem/ecosystem-tools/apis#ormi) * [Resources](https://docs.somnia.network/developer/deployment-and-production/ecosystem/ecosystem-tools/apis#resources) --- # Somnia Domains (.somi) | Somnia Docs [hashtag](https://docs.somnia.network/developer/deployment-and-production/ecosystem/ecosystem-showcase/somnia-domains-.somi#somnia-domains-.somi) Somnia Domains (.somi) ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- [hashtag](https://docs.somnia.network/developer/deployment-and-production/ecosystem/ecosystem-showcase/somnia-domains-.somi#id-1.-for-users) 1\. For Users --------------------------------------------------------------------------------------------------------------------------------------------------------------- ### [hashtag](https://docs.somnia.network/developer/deployment-and-production/ecosystem/ecosystem-showcase/somnia-domains-.somi#how-to-claim-a-domain) How to Claim a Domain? ![](https://docs.somnia.network/~gitbook/image?url=https%3A%2F%2F1861192046-files.gitbook.io%2F%7E%2Ffiles%2Fv0%2Fb%2Fgitbook-x-prod.appspot.com%2Fo%2Fspaces%252FFxD3cqyyFDzHJ0nfCPfy%252Fuploads%252FfwLeZD5ZZaXx6rMkgBQc%252FEkran%2520Resmi%25202025-10-20%252002.09.07.png%3Falt%3Dmedia%26token%3D5025bb87-7cda-494e-8760-f4e389462b12&width=768&dpr=3&quality=100&sign=ccaf55b0&sv=2) 1. Go to [somnia.domainsarrow-up-right](https://somnia.domains/) and connect your wallet. 2. Enter the domain you wish to claim in the “Domain Name” field. 3. If the domain address has not been reserved or claimed, you can claim it. 4. Claim periods of 1, 2, or 3 years are available. If you wish to extend it further, you can extend the period by clicking the “Renew” button in the “Domain Management” menu. 5. Domains that are not renewed before their expiration date will automatically expire and become available for others to claim. ### [hashtag](https://docs.somnia.network/developer/deployment-and-production/ecosystem/ecosystem-showcase/somnia-domains-.somi#how-to-set-primary-domain) How to Set Primary Domain? ![](https://docs.somnia.network/~gitbook/image?url=https%3A%2F%2F1861192046-files.gitbook.io%2F%7E%2Ffiles%2Fv0%2Fb%2Fgitbook-x-prod.appspot.com%2Fo%2Fspaces%252FFxD3cqyyFDzHJ0nfCPfy%252Fuploads%252F0PC3aXdsEZgOHGlfnQ3i%252FEkran%2520Resmi%25202025-10-20%252002.31.22.png%3Falt%3Dmedia%26token%3Dd19eca22-95af-423b-be86-d3e0a3e3cf01&width=768&dpr=3&quality=100&sign=89dc7e4f&sv=2) 1. You can view the domains you own from the “Domain Management” menu. 2. Here, you can set your desired domain as the Primary Domain. 3. Your primary domain is the domain that dApps can access via API. You can remove it or change it to another domain at any time. [hashtag](https://docs.somnia.network/developer/deployment-and-production/ecosystem/ecosystem-showcase/somnia-domains-.somi#id-2.-for-developers) 2\. For Developers ------------------------------------------------------------------------------------------------------------------------------------------------------------------------- **.somi API** You can retrieve the primary domain data of wallets using somi's API. This allows users to see their Primary Domains instead of their wallet addresses in the frontend. ### [hashtag](https://docs.somnia.network/developer/deployment-and-production/ecosystem/ecosystem-showcase/somnia-domains-.somi#what-does-the-api-returns) What does the API returns? You can display the `wallet` or `primaryDomain` strings in your frontend using the `hasPrimary` boolean value. [PreviousNFTs2Mechevron-left](https://docs.somnia.network/developer/deployment-and-production/ecosystem/ecosystem-showcase/nfts2me) [NextSomnia Exchangechevron-right](https://docs.somnia.network/developer/deployment-and-production/ecosystem/ecosystem-showcase/somnia-exchange) Last updated 4 months ago * [Somnia Domains (.somi)](https://docs.somnia.network/developer/deployment-and-production/ecosystem/ecosystem-showcase/somnia-domains-.somi#somnia-domains-.somi) * [1\. For Users](https://docs.somnia.network/developer/deployment-and-production/ecosystem/ecosystem-showcase/somnia-domains-.somi#id-1.-for-users) * [How to Claim a Domain?](https://docs.somnia.network/developer/deployment-and-production/ecosystem/ecosystem-showcase/somnia-domains-.somi#how-to-claim-a-domain) * [How to Set Primary Domain?](https://docs.somnia.network/developer/deployment-and-production/ecosystem/ecosystem-showcase/somnia-domains-.somi#how-to-set-primary-domain) * [2\. For Developers](https://docs.somnia.network/developer/deployment-and-production/ecosystem/ecosystem-showcase/somnia-domains-.somi#id-2.-for-developers) * [What does the API returns?](https://docs.somnia.network/developer/deployment-and-production/ecosystem/ecosystem-showcase/somnia-domains-.somi#what-does-the-api-returns) Copy https://api.somnia.domains/api/primary-domain/{walletAddress} Copy { "wallet":"0x4F40da1a67b891Ec7cCD54F809AFDD72CCc07BEC", "primaryDomain":"primary.somi", "hasPrimary":true } --- # RPC | Somnia Docs [hashtag](https://docs.somnia.network/developer/deployment-and-production/ecosystem/ecosystem-tools/rpc#ankr) [Ankrarrow-up-right](https://ankr.com/) ----------------------------------------------------------------------------------------------------------------------------------------------------------- Ankr provides scalable RPC and API services to interact with the Somnia blockchain efficiently. Developers can use Ankr's enterprise-grade infrastructure to enhance dApp performance. ### [hashtag](https://docs.somnia.network/developer/deployment-and-production/ecosystem/ecosystem-tools/rpc#resources) Resources * [Ankr RPC for Somniaarrow-up-right](https://www.ankr.com/rpc/somnia/) * * * [hashtag](https://docs.somnia.network/developer/deployment-and-production/ecosystem/ecosystem-tools/rpc#public-node) Public Node ------------------------------------------------------------------------------------------------------------------------------------- Public Node by All Nodes provides fast, free, and privacy-first RPC endpoints for Somnia Network. ### [hashtag](https://docs.somnia.network/developer/deployment-and-production/ecosystem/ecosystem-tools/rpc#resources-1) Resources * [Public Node RPC for Somniaarrow-up-right](https://somnia.publicnode.com/) * * * [hashtag](https://docs.somnia.network/developer/deployment-and-production/ecosystem/ecosystem-tools/rpc#stakely) Stakely ----------------------------------------------------------------------------------------------------------------------------- Stakely is an enterprise-grade infrastructure providerr, trusted by developers and institutions. ### [hashtag](https://docs.somnia.network/developer/deployment-and-production/ecosystem/ecosystem-tools/rpc#resources-2) Resources * [Stakely RPC for Somniaarrow-up-right](https://somnia-json-rpc.stakely.io/) * * * [hashtag](https://docs.somnia.network/developer/deployment-and-production/ecosystem/ecosystem-tools/rpc#validation-cloud) Validation Cloud ----------------------------------------------------------------------------------------------------------------------------------------------- Validation Cloud is the world’s fastest node provider according to Compare Nodes. With 50 million compute units available for use without a credit card and a scale tier that never imposes rate limits, Validation Cloud is built to support the most rigorous, low-latency workloads. Somnia Testnet & Somnia Mainnet are supported. ### [hashtag](https://docs.somnia.network/developer/deployment-and-production/ecosystem/ecosystem-tools/rpc#resources-3) Resources * [Linkarrow-up-right](https://www.validationcloud.io/somnia) [PreviousEcosystem Toolschevron-left](https://docs.somnia.network/developer/deployment-and-production/ecosystem/ecosystem-tools) [NextOracleschevron-right](https://docs.somnia.network/developer/deployment-and-production/ecosystem/ecosystem-tools/oracles) Last updated 5 months ago * [Ankr](https://docs.somnia.network/developer/deployment-and-production/ecosystem/ecosystem-tools/rpc#ankr) * [Resources](https://docs.somnia.network/developer/deployment-and-production/ecosystem/ecosystem-tools/rpc#resources) * [Public Node](https://docs.somnia.network/developer/deployment-and-production/ecosystem/ecosystem-tools/rpc#public-node) * [Resources](https://docs.somnia.network/developer/deployment-and-production/ecosystem/ecosystem-tools/rpc#resources-1) * [Stakely](https://docs.somnia.network/developer/deployment-and-production/ecosystem/ecosystem-tools/rpc#stakely) * [Resources](https://docs.somnia.network/developer/deployment-and-production/ecosystem/ecosystem-tools/rpc#resources-2) * [Validation Cloud](https://docs.somnia.network/developer/deployment-and-production/ecosystem/ecosystem-tools/rpc#validation-cloud) * [Resources](https://docs.somnia.network/developer/deployment-and-production/ecosystem/ecosystem-tools/rpc#resources-3) --- # Wallet Providers | Somnia Docs [hashtag](https://docs.somnia.network/developer/deployment-and-production/ecosystem/ecosystem-tools/wallet-providers#privy) [Privyarrow-up-right](https://docs.privy.io/welcome) -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- Privy is a secure, embeddable wallet infrastructure provider that allows developers to authenticate users, manage sessions, and provide seamless wallet experiences within dApps. ### [hashtag](https://docs.somnia.network/developer/deployment-and-production/ecosystem/ecosystem-tools/wallet-providers#resources) Resources * [Documentation](https://docs.somnia.network/developer/building-dapps/wallet-integration-and-auth/authenticating-with-privy) * * * [hashtag](https://docs.somnia.network/developer/deployment-and-production/ecosystem/ecosystem-tools/wallet-providers#sequence) [Sequencearrow-up-right](https://sequence.build/) -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- Sequence is a smart contract wallet & account abstraction provider that enables gasless transactions and seamless user onboarding to dApps. ### [hashtag](https://docs.somnia.network/developer/deployment-and-production/ecosystem/ecosystem-tools/wallet-providers#resources-1) Resources * [Documentationarrow-up-right](https://sequence.build/landing) * * * [hashtag](https://docs.somnia.network/developer/deployment-and-production/ecosystem/ecosystem-tools/wallet-providers#thirdweb) [Thirdwebarrow-up-right](https://thirdweb.com/) ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ Thirdweb simplifies smart contract deployment, account abstraction, and Web3 development on Somnia. It offers gasless transactions, token management, and SDKs for seamless dApp building. ### [hashtag](https://docs.somnia.network/developer/deployment-and-production/ecosystem/ecosystem-tools/wallet-providers#resources-2) Resources * [Thirdweb Somnia Docsarrow-up-right](https://thirdweb.com/somnia) * Documentation [PreviousSubgraphschevron-left](https://docs.somnia.network/developer/deployment-and-production/ecosystem/ecosystem-tools/subgraphs) [NextSafeschevron-right](https://docs.somnia.network/developer/deployment-and-production/ecosystem/ecosystem-tools/safes) Last updated 5 months ago * [Privy](https://docs.somnia.network/developer/deployment-and-production/ecosystem/ecosystem-tools/wallet-providers#privy) * [Resources](https://docs.somnia.network/developer/deployment-and-production/ecosystem/ecosystem-tools/wallet-providers#resources) * [Sequence](https://docs.somnia.network/developer/deployment-and-production/ecosystem/ecosystem-tools/wallet-providers#sequence) * [Resources](https://docs.somnia.network/developer/deployment-and-production/ecosystem/ecosystem-tools/wallet-providers#resources-1) * [Thirdweb](https://docs.somnia.network/developer/deployment-and-production/ecosystem/ecosystem-tools/wallet-providers#thirdweb) * [Resources](https://docs.somnia.network/developer/deployment-and-production/ecosystem/ecosystem-tools/wallet-providers#resources-2) --- # Account Abstraction | Somnia Docs [hashtag](https://docs.somnia.network/developer/deployment-and-production/ecosystem/ecosystem-tools/account-abstraction#pimlico) Pimlico --------------------------------------------------------------------------------------------------------------------------------------------- Pimlico, the world's most advanced ERC-4337 account abstraction infrastructure platform. This guide will help you understand Pimlico's ecosystem and get you started with building applications using our tools and services. ### [hashtag](https://docs.somnia.network/developer/deployment-and-production/ecosystem/ecosystem-tools/account-abstraction#resources) Resources * [Tutorialarrow-up-right](https://docs.pimlico.io/guides/tutorials/tutorial-1) [PreviousOn Rampschevron-left](https://docs.somnia.network/developer/deployment-and-production/ecosystem/ecosystem-tools/on-ramps) [NextAPIschevron-right](https://docs.somnia.network/developer/deployment-and-production/ecosystem/ecosystem-tools/apis) Last updated 1 month ago * [Pimlico](https://docs.somnia.network/developer/deployment-and-production/ecosystem/ecosystem-tools/account-abstraction#pimlico) * [Resources](https://docs.somnia.network/developer/deployment-and-production/ecosystem/ecosystem-tools/account-abstraction#resources) --- # Smart Wallet App with Thirdweb | Somnia Docs The Somnia mission is to enable the development of mass-consumer real-time applications. The Somnia Network allows developers to build a unique experience by implementing Smart Contract Wallets with gasless transactions via Account Abstraction (ERC-4337). In this tutorial, we'll use the [Thirdweb React SDKarrow-up-right](https://portal.thirdweb.com/react/v5) to: * Connect Smart Wallets (Account Abstraction) * Read Wallet Balance * Send STT Tokens [hashtag](https://docs.somnia.network/developer/building-dapps/account-abstraction/smart-wallet-app-with-thirdweb#pre-requisites) Pre-requisites ----------------------------------------------------------------------------------------------------------------------------------------------------- Before we start, ensure you have: * Basic knowledge of React * A [Thirdwebarrow-up-right](https://thirdweb.com/) account & Client ID * Node.js & npm installed [hashtag](https://docs.somnia.network/developer/building-dapps/account-abstraction/smart-wallet-app-with-thirdweb#install-dependencies) Install Dependencies ----------------------------------------------------------------------------------------------------------------------------------------------------------------- Run the following command to set up your project: Copy npx create-next-app@latest somnia-thirdweb cd somnia-thirdweb npm install thirdweb ethers viem dotenv This installs: `thirdweb` → The Thirdweb React SDK. `ethers` → To interact with blockchain transactions. `dotenv` → To securely store API keys. [hashtag](https://docs.somnia.network/developer/building-dapps/account-abstraction/smart-wallet-app-with-thirdweb#create-the-thirdweb-client) Create the Thirdweb Client ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- The Thirdweb client allows the app to communicate with the blockchain. Create a `**client.ts**` file and add: circle-info Get your Client ID: Register at thirdweb.com/dashboard. 💡 [hashtag](https://docs.somnia.network/developer/building-dapps/account-abstraction/smart-wallet-app-with-thirdweb#add-environment-variables) Add Environment Variables --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- Store API keys in a .env.local file: Restart Next.js after modifying .env.local [hashtag](https://docs.somnia.network/developer/building-dapps/account-abstraction/smart-wallet-app-with-thirdweb#build-the-account-abstraction-app) Build the Account Abstraction App ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- To ensure Thirdweb Components are available throughout the app, wrap the children's components inside ThirdwebProvider. Modify `**layout.ts**`: [hashtag](https://docs.somnia.network/developer/building-dapps/account-abstraction/smart-wallet-app-with-thirdweb#create-and-connect-smart-contract-wallet) Create & Connect Smart Contract Wallet ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- The `**useActiveAccount**` hook allows us to detect the connected Smart Wallet Account. We use `**ConnectButton**` to handle authentication and connection to the blockchain. This button will connect the user's Smart Contract Wallet and authenticate the user with Thirdweb. After connection, it will also display the wallet address. ![](https://docs.somnia.network/~gitbook/image?url=https%3A%2F%2F2122549367-files.gitbook.io%2F%7E%2Ffiles%2Fv0%2Fb%2Fgitbook-x-prod.appspot.com%2Fo%2Fspaces%252FkYErT9t3BJtpPfejLO6I%252Fuploads%252FbxG90nEcMeuXf9kutKQr%252Fconnect-button.png%3Falt%3Dmedia%26token%3D09e7cc83-a09d-443d-85f6-a2b616189cb8&width=768&dpr=3&quality=100&sign=942ed536&sv=2) click Connect ![](https://docs.somnia.network/~gitbook/image?url=https%3A%2F%2F2122549367-files.gitbook.io%2F%7E%2Ffiles%2Fv0%2Fb%2Fgitbook-x-prod.appspot.com%2Fo%2Fspaces%252FkYErT9t3BJtpPfejLO6I%252Fuploads%252FVCF2GxRUflnhi1WGcd9T%252Fconnected-button.png%3Falt%3Dmedia%26token%3Db53519f9-a194-466e-8041-d7f4187e2518&width=768&dpr=3&quality=100&sign=8b93ab62&sv=2) Connected Wallet To show the connected address, we add the following UI component: [hashtag](https://docs.somnia.network/developer/building-dapps/account-abstraction/smart-wallet-app-with-thirdweb#token-transfer) Token Transfer ----------------------------------------------------------------------------------------------------------------------------------------------------- The `**useSendTransaction**` hook is used to send STT tokens to another address. The function `**sendTokens**` will check that the Smart Account is connected and then send 0.01 STT tokens to a recipient address. First, copy your Smart Wallet Address and request for Tokens on Discord in the **dev-chat,** you can also Transfer some from your EOA. Log transaction success or failure messages to the console. [hashtag](https://docs.somnia.network/developer/building-dapps/account-abstraction/smart-wallet-app-with-thirdweb#button-integration) Button Integration ------------------------------------------------------------------------------------------------------------------------------------------------------------- The button UI provides a clear interaction for sending STT tokens. The button shows a loading state (Sending...) when the transaction is pending and displays a success or failure message once the transaction is complete. chevron-rightComplete Code[hashtag](https://docs.somnia.network/developer/building-dapps/account-abstraction/smart-wallet-app-with-thirdweb#complete-code) The full implementation includes wallet connection, balance retrieval, and token transfer. ![](https://docs.somnia.network/~gitbook/image?url=https%3A%2F%2F2122549367-files.gitbook.io%2F%7E%2Ffiles%2Fv0%2Fb%2Fgitbook-x-prod.appspot.com%2Fo%2Fspaces%252FkYErT9t3BJtpPfejLO6I%252Fuploads%252FwKc8sH2NricEjaR4sN5z%252FWallet.png%3Falt%3Dmedia%26token%3De00eb509-954e-45ab-90ea-48583cde0a63&width=768&dpr=3&quality=100&sign=80fb0159&sv=2) Smart Contract Wallet [hashtag](https://docs.somnia.network/developer/building-dapps/account-abstraction/smart-wallet-app-with-thirdweb#conclusion) Conclusion --------------------------------------------------------------------------------------------------------------------------------------------- Congratulations! 🎉 You have successfully connected a smart contract wallet using Thirdweb. Read wallet balances and Transferred STT tokens on Somnia Testnet. You can explore additional features such as gasless transactions, NFT integration, and DeFi applications. [PreviousGasless Transactions with Thirdwchevron-left](https://docs.somnia.network/developer/building-dapps/account-abstraction/gasless-transactions-with-thirdw) [NextData Indexing and Queryingchevron-right](https://docs.somnia.network/developer/building-dapps/data-indexing-and-querying) Last updated 10 months ago * [Pre-requisites](https://docs.somnia.network/developer/building-dapps/account-abstraction/smart-wallet-app-with-thirdweb#pre-requisites) * [Install Dependencies](https://docs.somnia.network/developer/building-dapps/account-abstraction/smart-wallet-app-with-thirdweb#install-dependencies) * [Create the Thirdweb Client](https://docs.somnia.network/developer/building-dapps/account-abstraction/smart-wallet-app-with-thirdweb#create-the-thirdweb-client) * [Add Environment Variables](https://docs.somnia.network/developer/building-dapps/account-abstraction/smart-wallet-app-with-thirdweb#add-environment-variables) * [Build the Account Abstraction App](https://docs.somnia.network/developer/building-dapps/account-abstraction/smart-wallet-app-with-thirdweb#build-the-account-abstraction-app) * [Create & Connect Smart Contract Wallet](https://docs.somnia.network/developer/building-dapps/account-abstraction/smart-wallet-app-with-thirdweb#create-and-connect-smart-contract-wallet) * [Token Transfer](https://docs.somnia.network/developer/building-dapps/account-abstraction/smart-wallet-app-with-thirdweb#token-transfer) * [Button Integration](https://docs.somnia.network/developer/building-dapps/account-abstraction/smart-wallet-app-with-thirdweb#button-integration) * [Conclusion](https://docs.somnia.network/developer/building-dapps/account-abstraction/smart-wallet-app-with-thirdweb#conclusion) Copy import { createThirdwebClient } from "thirdweb"; export const client = createThirdwebClient({ clientId: process.env.NEXT_PUBLIC_THIRDWEB_CLIENT_ID as string, // Replace with your actual Client ID }); Copy NEXT_PUBLIC_THIRDWEB_CLIENT_ID=your-client-id-here NEXT_PUBLIC_SOMNIA_RPC_URL=https://dream-rpc.somnia.network/ Copy import { ThirdwebProvider } from 'thirdweb/react'; {children} Copy import { useActiveAccount } from "thirdweb/react"; const smartAccount = useActiveAccount(); Copy {smartAccount ? (

Connected as: {smartAccount.address}

{message &&

{message}

}
) : (

Please connect your wallet.

)} Copy import { useSendTransaction } from "thirdweb/react"; import { ethers } from "ethers"; const { mutate: sendTransaction, isPending } = useSendTransaction(); const sendTokens = async () => { if (!smartAccount) { setMessage("No smart account connected."); return; } console.log("Sending 0.01 STT from:", smartAccount.address); sendTransaction( { to: "0xb6e4fa6ff2873480590c68D9Aa991e5BB14Dbf03", value: ethers.parseUnits("0.01", 18), chain: somniaTestnet, client, }, { onSuccess: (receipt) => { console.log("Transaction Success:", receipt); setMessage(`Sent 0.01 STT! TX: ${receipt.transactionHash}`); }, onError: (error) => { console.error("Transaction Failed:", error); setMessage("Transaction failed! Check console."); }, } ); }; Copy const [message, setMessage] = useState(''); {message &&

{message}

} Copy 'use client'; import { useState } from 'react'; import { ConnectButton, useActiveAccount, useSendTransaction, } from 'thirdweb/react'; import { ethers } from 'ethers'; import { client } from './client'; import { somniaTestnet } from 'viem/chain'; export default function Home() { const [message, setMessage] = useState(''); const smartAccount = useActiveAccount(); // Get connected account const { mutate: sendTransaction, isPending } = useSendTransaction(); const sendTokens = async () => { if (!smartAccount) { setMessage('No smart account connected.'); return; } console.log('🚀 Sending 0.01 STT from:', smartAccount.address); sendTransaction( { to: '0xb6e4fa6ff2873480590c68D9Aa991e5BB14Dbf03', // Replace value: ethers.parseUnits('0.01', 18), chain: somniaTestnet, client, }, { onSuccess: (receipt) => { console.log('Transaction Success:', receipt); setMessage(`Sent 0.01 STT! TX: ${receipt.transactionHash}`); }, onError: (error) => { console.error('Transaction Failed:', error); setMessage('Transaction failed! Check console.'); }, } ); }; return (
{smartAccount ? (

Connected as: {smartAccount.address}

{message &&

{message}

}
) : (

Please connect your wallet.

)}
); } --- # Explorers | Somnia Docs [hashtag](https://docs.somnia.network/developer/deployment-and-production/ecosystem/ecosystem-tools/explorers#blockscout) Blockscout ----------------------------------------------------------------------------------------------------------------------------------------- Blockscout provides blockchain analytics & explorer services for Somnia. Developers and users can access on-chain transaction data, addresses, and smart contracts. ### [hashtag](https://docs.somnia.network/developer/deployment-and-production/ecosystem/ecosystem-tools/explorers#resources) Resources * [Blockscout Explorerarrow-up-right](http://shannon-explorer.somnia.network/) [PreviousSafeschevron-left](https://docs.somnia.network/developer/deployment-and-production/ecosystem/ecosystem-tools/safes) [NextSDKschevron-right](https://docs.somnia.network/developer/deployment-and-production/ecosystem/ecosystem-tools/sdks) Last updated 10 months ago * [Blockscout](https://docs.somnia.network/developer/deployment-and-production/ecosystem/ecosystem-tools/explorers#blockscout) * [Resources](https://docs.somnia.network/developer/deployment-and-production/ecosystem/ecosystem-tools/explorers#resources) --- # Gas Configuration | Somnia Docs Properly configuring gas parameters is critical for on-chain reactivity subscriptions. If gas values are too low, validators will silently skip your subscription — no error, no warning, just nothing happens. triangle-exclamation **This is the #1 cause of "reactivity not working."** Most developers set up their contracts and subscriptions correctly, but use gas values that are too low for validators to process. [hashtag](https://docs.somnia.network/developer/reactivity/gas-configuration#understanding-the-parameters) Understanding the Parameters -------------------------------------------------------------------------------------------------------------------------------------------- On-chain reactivity subscriptions require three gas parameters: Parameter Description Unit `priorityFeePerGas` Tip paid to validators to prioritize your handler execution gwei (nanoSomi) `maxFeePerGas` Maximum total fee per gas (base fee + priority fee) gwei (nanoSomi) `gasLimit` Maximum gas provisioned per handler invocation gas units ### [hashtag](https://docs.somnia.network/developer/reactivity/gas-configuration#how-they-work-together) How They Work Together 1 ### [hashtag](https://docs.somnia.network/developer/reactivity/gas-configuration#priorityfeepergas) priorityFeePerGas This is essentially the "tip" for validators. The default value is 0 nanoSomi. Increase it to make sure your handler is executed before others. 2 ### [hashtag](https://docs.somnia.network/developer/reactivity/gas-configuration#maxfeepergas) maxFeePerGas The ceiling on what you'll pay per gas unit. The minimum is `baseFee + priorityFeePerGas` , where the base fee in Somnia is 6 nanoSomi. Setting this too low will cause your handler invocation to fail in peak times. 3 ### [hashtag](https://docs.somnia.network/developer/reactivity/gas-configuration#gaslimit) gasLimit How much gas your `_onEvent` handler is allowed to consume. If your handler runs out of gas, it reverts. [hashtag](https://docs.somnia.network/developer/reactivity/gas-configuration#recommended-values) Recommended Values ------------------------------------------------------------------------------------------------------------------------ ### [hashtag](https://docs.somnia.network/developer/reactivity/gas-configuration#standard-use-cases) Standard Use Cases Copy import { parseGwei } from 'viem'; await sdk.createSoliditySubscription({ handlerContractAddress: '0x...', emitter: '0x...', eventTopics: [eventSignature], priorityFeePerGas: parseGwei('0'), // 0 gwei — typically, no priority is required maxFeePerGas: parseGwei('10'), // 10 gwei — comfortable ceiling gasLimit: 2_000_000n, // Sufficient for simple state updates isGuaranteed: true, isCoalesced: false, }); ### [hashtag](https://docs.somnia.network/developer/reactivity/gas-configuration#by-handler-complexity) By Handler Complexity Handler Type `priorityFeePerGas` `maxFeePerGas` `gasLimit` Example **Simple** (state updates, emit event) `parseGwei('0')` `parseGwei('10')` `2_000_000n` Counter, token reward **Medium** (cross-contract calls) `parseGwei('0')` `parseGwei('10')` `3_000_000n` Game logic with external calls **Complex** (multiple external calls, loops) `parseGwei('10')` `parseGwei('20')` `10_000_000n` Settlement, multi-step workflows ### [hashtag](https://docs.somnia.network/developer/reactivity/gas-configuration#quick-reference-table-raw-bigint-values) Quick Reference Table (Raw BigInt Values) If you prefer raw values instead of `parseGwei()`: Level `priorityFeePerGas` `maxFeePerGas` `gasLimit` Minimum recommended `0n` `10_000_000_000n` 2`_000_000n` Comfortable `0n` `10_000_000_000n` 3`_000_000n` High priority `10_000_000_000n` `20_000_000_000n` `10_000_000n` [hashtag](https://docs.somnia.network/developer/reactivity/gas-configuration#common-mistakes) Common Mistakes ------------------------------------------------------------------------------------------------------------------ ### [hashtag](https://docs.somnia.network/developer/reactivity/gas-configuration#using-wei-instead-of-gwei) Using wei instead of gwei circle-exclamation `10n` is **10 wei** = 0.00000001 gwei. This is 200 million times less than the recommended 2 gwei. Always use `parseGwei()` to avoid unit confusion. See [SOMI coin](https://docs.somnia.network/developer/network-info/somi-coin) for details on how this is calculated. ### [hashtag](https://docs.somnia.network/developer/reactivity/gas-configuration#computing-from-gasprice-with-too-small-divisors) Computing from gasPrice with too-small divisors ### [hashtag](https://docs.somnia.network/developer/reactivity/gas-configuration#setting-gaslimit-too-low) Setting gasLimit too low Somnia operates on a different gas model to Ethereum. One of the key differences is that the 1,000,000 gas reserve is required for any storage operations, see [Storage EVM operations](https://docs.somnia.network/developer/deployment-and-production/somnia-gas-differences-to-ethereum#storage-evm-operations) . It is safe to up your gas limit to meet the reserve requirements. If your handler reverts due to out-of-gas, the subscription still charges you but the state change doesn't happen. ### [hashtag](https://docs.somnia.network/developer/reactivity/gas-configuration#forgetting-to-recreate-subscription-after-redeploying) Forgetting to recreate subscription after redeploying Subscriptions are tied to specific contract addresses. If you redeploy your contract, you get a new address. The old subscription won't trigger for the new contract. [hashtag](https://docs.somnia.network/developer/reactivity/gas-configuration#cost-estimation) Cost Estimation ------------------------------------------------------------------------------------------------------------------ The subscription owner pays for each handler invocation. The cost per invocation is: Where `effectiveGasPrice` is at most `maxFeePerGas` and at least `baseFee + priorityFeePerGas`. ### [hashtag](https://docs.somnia.network/developer/reactivity/gas-configuration#example) Example For a simple handler using ~50,000 gas at 10 gwei max fee: The subscription owner must maintain at least **32 SOMI** balance. This is not spent — it's a minimum holding requirement. Actual costs are deducted per invocation. [hashtag](https://docs.somnia.network/developer/reactivity/gas-configuration#debugging-gas-issues) Debugging Gas Issues ---------------------------------------------------------------------------------------------------------------------------- If your subscription was created successfully but the handler is never invoked: 1 ### [hashtag](https://docs.somnia.network/developer/reactivity/gas-configuration#check-subscription-info) Check subscription info Verify that `priorityFeePerGas` is at least `2000000000` (2 gwei). 2 ### [hashtag](https://docs.somnia.network/developer/reactivity/gas-configuration#test-with-a-cli-script-first) Test with a CLI script first Before debugging frontend issues, confirm reactivity works via a Hardhat script: 3 ### [hashtag](https://docs.somnia.network/developer/reactivity/gas-configuration#look-for-validator-transactions) Look for validator transactions On-chain reactivity is executed by validators from the address `0x0000000000000000000000000000000000000100`. Check the block explorer for transactions from this address to your handler contract after your event was emitted. [hashtag](https://docs.somnia.network/developer/reactivity/gas-configuration#summary) Summary -------------------------------------------------------------------------------------------------- Do Don't Use `parseGwei('2')` for priority fee Use raw small numbers like `10n` or `100n` Use `parseGwei('10')` for max fee Compute from `gasPrice` with arbitrary divisors Set gasLimit based on handler complexity Use a one-size-fits-all low gasLimit Recreate subscription after redeploying Assume old subscription works with new contract Test via CLI before building frontend Debug reactivity issues through the browser [PreviousCron subscriptions via SDKchevron-left](https://docs.somnia.network/developer/reactivity/tutorials/cron-subscriptions-via-sdk) [NextFAQs & Troubleshootingchevron-right](https://docs.somnia.network/developer/reactivity/faqs-and-troubleshooting) Last updated 27 days ago * [Understanding the Parameters](https://docs.somnia.network/developer/reactivity/gas-configuration#understanding-the-parameters) * [How They Work Together](https://docs.somnia.network/developer/reactivity/gas-configuration#how-they-work-together) * [priorityFeePerGas](https://docs.somnia.network/developer/reactivity/gas-configuration#priorityfeepergas) * [maxFeePerGas](https://docs.somnia.network/developer/reactivity/gas-configuration#maxfeepergas) * [gasLimit](https://docs.somnia.network/developer/reactivity/gas-configuration#gaslimit) * [Recommended Values](https://docs.somnia.network/developer/reactivity/gas-configuration#recommended-values) * [Standard Use Cases](https://docs.somnia.network/developer/reactivity/gas-configuration#standard-use-cases) * [By Handler Complexity](https://docs.somnia.network/developer/reactivity/gas-configuration#by-handler-complexity) * [Quick Reference Table (Raw BigInt Values)](https://docs.somnia.network/developer/reactivity/gas-configuration#quick-reference-table-raw-bigint-values) * [Common Mistakes](https://docs.somnia.network/developer/reactivity/gas-configuration#common-mistakes) * [Using wei instead of gwei](https://docs.somnia.network/developer/reactivity/gas-configuration#using-wei-instead-of-gwei) * [Computing from gasPrice with too-small divisors](https://docs.somnia.network/developer/reactivity/gas-configuration#computing-from-gasprice-with-too-small-divisors) * [Setting gasLimit too low](https://docs.somnia.network/developer/reactivity/gas-configuration#setting-gaslimit-too-low) * [Forgetting to recreate subscription after redeploying](https://docs.somnia.network/developer/reactivity/gas-configuration#forgetting-to-recreate-subscription-after-redeploying) * [Cost Estimation](https://docs.somnia.network/developer/reactivity/gas-configuration#cost-estimation) * [Example](https://docs.somnia.network/developer/reactivity/gas-configuration#example) * [Debugging Gas Issues](https://docs.somnia.network/developer/reactivity/gas-configuration#debugging-gas-issues) * [Check subscription info](https://docs.somnia.network/developer/reactivity/gas-configuration#check-subscription-info) * [Test with a CLI script first](https://docs.somnia.network/developer/reactivity/gas-configuration#test-with-a-cli-script-first) * [Look for validator transactions](https://docs.somnia.network/developer/reactivity/gas-configuration#look-for-validator-transactions) * [Summary](https://docs.somnia.network/developer/reactivity/gas-configuration#summary) Copy // WRONG — these are 10 wei and 20 wei, essentially zero priorityFeePerGas: 10n, maxFeePerGas: 20n, // CORRECT — these are 2 gwei and 10 gwei priorityFeePerGas: parseGwei('2'), // = 2_000_000_000n maxFeePerGas: parseGwei('10'), // = 10_000_000_000n Copy // RISKY — gas price fluctuates and dividing by 10 may yield too-low values const gasPrice = await publicClient.getGasPrice(); priorityFeePerGas: gasPrice / 10n, // SAFER — use fixed proven values priorityFeePerGas: parseGwei('2'), Copy // TOO LOW for any storage operations gasLimit: 100_000n, // SAFE for most handlers gasLimit: 2_000_000n, // SAFE for complex handlers with external calls gasLimit: 10_000_000n, Copy Deploy contract → address A Create subscription → emitter: A, handler: A ✅ Redeploy contract → address B Old subscription still points to A → ❌ won't work Must create NEW subscription → emitter: B, handler: B ✅ Copy cost = gasUsed × effectiveGasPrice Copy cost ≈ 50,000 × 10 gwei = 500,000 gwei = 0.0005 SOM per invocation Copy const info = await sdk.getSubscriptionInfo(subscriptionId); console.log(JSON.stringify(info, (_, v) => typeof v === 'bigint' ? v.toString() : v, 2)); Copy // 1. Call your contract function that emits the event const tx = await contract.myFunction(); await tx.wait(); // 2. Poll for state change for (let i = 0; i < 15; i++) { await new Promise(r => setTimeout(r, 2000)); const result = await contract.myStateVariable(); if (result !== previousValue) { console.log('Reactivity worked!'); break; } } --- # SDKs | Somnia Docs [hashtag](https://docs.somnia.network/developer/deployment-and-production/ecosystem/ecosystem-tools/sdks#sequence) [Sequencearrow-up-right](https://sequence.build/) -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- Sequence is a smart contract wallet & account abstraction provider that enables gasless transactions and seamless user onboarding to dApps. ### [hashtag](https://docs.somnia.network/developer/deployment-and-production/ecosystem/ecosystem-tools/sdks#resources) Resources * [Documentationarrow-up-right](https://sequence.build/landing) * * * [hashtag](https://docs.somnia.network/developer/deployment-and-production/ecosystem/ecosystem-tools/sdks#thirdweb) [Thirdwebarrow-up-right](https://thirdweb.com/) ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ Thirdweb simplifies smart contract deployment, account abstraction, and Web3 development on Somnia. It offers gasless transactions, token management, and SDKs for seamless dApp building. ### [hashtag](https://docs.somnia.network/developer/deployment-and-production/ecosystem/ecosystem-tools/sdks#resources-1) Resources * [Thirdweb Somnia Docsarrow-up-right](https://thirdweb.com/somnia-shannon-testnet) * [Deploy Smart Contracts on Somnia](https://docs.somnia.network/developer/development-frameworks/deploy-with-thirdweb) * [Account Abstraction with Somnia](https://docs.somnia.network/developer/building-dapps/account-abstraction/smart-wallet-app-with-thirdweb) * [Thirdweb Faucetarrow-up-right](https://thirdweb.com/somnia-shannon-testnet) [PreviousExplorerschevron-left](https://docs.somnia.network/developer/deployment-and-production/ecosystem/ecosystem-tools/explorers) [NextOn Rampschevron-right](https://docs.somnia.network/developer/deployment-and-production/ecosystem/ecosystem-tools/on-ramps) Last updated 1 month ago * [Sequence](https://docs.somnia.network/developer/deployment-and-production/ecosystem/ecosystem-tools/sdks#sequence) * [Resources](https://docs.somnia.network/developer/deployment-and-production/ecosystem/ecosystem-tools/sdks#resources) * [Thirdweb](https://docs.somnia.network/developer/deployment-and-production/ecosystem/ecosystem-tools/sdks#thirdweb) * [Resources](https://docs.somnia.network/developer/deployment-and-production/ecosystem/ecosystem-tools/sdks#resources-1) --- # Elix.fi | Somnia Docs [hashtag](https://docs.somnia.network/developer/deployment-and-production/ecosystem/ecosystem-showcase/elix.fi#id-1.-for-users) 1\. For Users -------------------------------------------------------------------------------------------------------------------------------------------------- Elix is a CLOB (Central Limit Order Book) / AMM hybrid decentralized exchange (DEX) built on the Somnia Network, designed to bring the performance and trading experience of a centralized exchange (CEX) fully on-chain. ### [hashtag](https://docs.somnia.network/developer/deployment-and-production/ecosystem/ecosystem-showcase/elix.fi#how-to-trade-on-elix) How to Trade on Elix? ![](https://docs.somnia.network/~gitbook/image?url=https%3A%2F%2F1861192046-files.gitbook.io%2F%7E%2Ffiles%2Fv0%2Fb%2Fgitbook-x-prod.appspot.com%2Fo%2Fspaces%252FFxD3cqyyFDzHJ0nfCPfy%252Fuploads%252FLnbfsJrRMIK5k3lCSXwl%252FEkran%2520Resmi%25202025-10-22%252018.52.12.png%3Falt%3Dmedia%26token%3D8a7d0e56-31ff-4ef2-af67-ef1e25b260e2&width=768&dpr=3&quality=100&sign=1a934cbf&sv=2) 1. **Go to the** [**Elix App**arrow-up-right](https://app.elix.fi/trade) **:** Connect your wallet to the Elix trading platform. 2. **Select Market:** Choose the trading pair you are interested in, for example, WETH/USDC. 3. **Choose Order Type:** You can place a **Market Order** to trade at the current best price, or a **Limit Order** to specify the exact price at which you want to buy or sell. 4. **Enter Details:** For a limit order, enter the price and the amount you wish to trade. The interface will show you the total cost and fees. 5. **Place Order:** Submit your order. Limit orders will sit on the order book until the market price reaches your specified price, at which point they will be automatically executed. ### [hashtag](https://docs.somnia.network/developer/deployment-and-production/ecosystem/ecosystem-showcase/elix.fi#how-to-earn-yield-on-elix) How to Earn Yield on Elix? ![](https://docs.somnia.network/~gitbook/image?url=https%3A%2F%2F1861192046-files.gitbook.io%2F%7E%2Ffiles%2Fv0%2Fb%2Fgitbook-x-prod.appspot.com%2Fo%2Fspaces%252FFxD3cqyyFDzHJ0nfCPfy%252Fuploads%252F6UUO431ge80AMAXKD9A4%252FEkran%2520Resmi%25202025-10-22%252019.02.08.png%3Falt%3Dmedia%26token%3Dca710905-367c-4149-97f7-573896d1f14a&width=768&dpr=3&quality=100&sign=fa51ea20&sv=2) Elix offers innovative yield opportunities for liquidity providers through its "Earn" page. 1. **Navigate to Earn Page:** Find the "Earn" section in the Elix app. 2. **Explore Vaults:** Browse the available vaults. Each vault has a different strategy and risk profile, targeting specific token pairs. 3. **Deposit Liquidity:** Choose a vault that fits your strategy and deposit your assets. By depositing, you become a liquidity provider and start earning fees from trades, and potentially other rewards. ![](https://docs.somnia.network/~gitbook/image?url=https%3A%2F%2F1861192046-files.gitbook.io%2F%7E%2Ffiles%2Fv0%2Fb%2Fgitbook-x-prod.appspot.com%2Fo%2Fspaces%252FFxD3cqyyFDzHJ0nfCPfy%252Fuploads%252FozLkUsj8Ydl2iSn4ScdQ%252FEkran%2520Resmi%25202025-10-22%252019.01.54.png%3Falt%3Dmedia%26token%3D051a949e-23b8-422c-a3b0-b1ccaf944bd0&width=768&dpr=3&quality=100&sign=94c4ea2f&sv=2) [hashtag](https://docs.somnia.network/developer/deployment-and-production/ecosystem/ecosystem-showcase/elix.fi#id-2.-for-developers) 2\. For Developers ------------------------------------------------------------------------------------------------------------------------------------------------------------ Elix's revolutionary feature is that liquidity is not locked. Liquidity providers can use their funds in other protocols until their orders are matched. This opens up immense possibilities for "composable" DeFi strategies. **Core Concepts for Integration** * **Capital Efficiency & Composability:** Elix’s core innovation is that liquidity is not locked in the order book. As a developer, this means your users can use assets **such as aTokens supplied to** [**Tokos**arrow-up-right](https://app.tokos.fi/) as collateral to provide liquidity on Elix. Users can earn both lending interest from [Tokosarrow-up-right](https://app.tokos.fi/) and a share of trading fees from Elix simultaneously. * **Smart Hooks (Programmable Orders):** Elix allows liquidity providers to post arbitrary Smart Contracts (_Hooks_) as offers. These programmable orders can execute custom logic directly on-chain. This unlocks new opportunities for automated trading, dynamic pricing, and cross-protocol yield strategies. * **Off-Chain Order Posting, On-Chain Settlement:** To achieve CEX-level performance, Elix can utilize off-chain services for order matching while ensuring that all settlements and fund transfers are executed transparently and securely on the Somnia blockchain. Developers can build bots and strategies that interact with this high-frequency matching engine. For more information, please check [Elix.fi Docsarrow-up-right](https://docs.elix.fi/) . [PreviousEcosystem Showcasechevron-left](https://docs.somnia.network/developer/deployment-and-production/ecosystem/ecosystem-showcase) [NextMeme Coinschevron-right](https://docs.somnia.network/developer/deployment-and-production/ecosystem/ecosystem-showcase/meme-coins) Last updated 4 months ago * [1\. For Users](https://docs.somnia.network/developer/deployment-and-production/ecosystem/ecosystem-showcase/elix.fi#id-1.-for-users) * [How to Trade on Elix?](https://docs.somnia.network/developer/deployment-and-production/ecosystem/ecosystem-showcase/elix.fi#how-to-trade-on-elix) * [How to Earn Yield on Elix?](https://docs.somnia.network/developer/deployment-and-production/ecosystem/ecosystem-showcase/elix.fi#how-to-earn-yield-on-elix) * [2\. For Developers](https://docs.somnia.network/developer/deployment-and-production/ecosystem/ecosystem-showcase/elix.fi#id-2.-for-developers) --- # Protofire Price Feeds | Somnia Docs [hashtag](https://docs.somnia.network/developer/building-dapps/oracles/protofire-price-feeds#somnia-mainnet-price-feeds) Somnia Mainnet Price Feeds -------------------------------------------------------------------------------------------------------------------------------------------------------- **Asset Pair** **OCR Aggregator** **Proxy (read‑only)** USDC / USD [0x4b74EcA574Ce996448b485100e4FFf84866911dFarrow-up-right](https://explorer.somnia.network/address/0x4b74EcA574Ce996448b485100e4FFf84866911dF) [0x843B6812E9Aa67b3773675d2836646BCbd216642arrow-up-right](https://explorer.somnia.network/address/0x843B6812E9Aa67b3773675d2836646BCbd216642) ETH / USD [0xa3060dd6Bb56EdfB2E0d78c88ef63A974a392D36arrow-up-right](https://explorer.somnia.network/address/0xa3060dd6Bb56EdfB2E0d78c88ef63A974a392D36) [0xeC25a820A6F194118ef8274216a7F225Da019526arrow-up-right](https://explorer.somnia.network/address/0xeC25a820A6F194118ef8274216a7F225Da019526) BTC / USD [0x3cBdF7F02956c8e946192Bff64bb2Dd470dd589Carrow-up-right](https://explorer.somnia.network/address/0x3cBdF7F02956c8e946192Bff64bb2Dd470dd589C) [0xa57d637618252669fD859B1F4C7bE6F52Bef67edarrow-up-right](https://explorer.somnia.network/address/0xa57d637618252669fD859B1F4C7bE6F52Bef67ed) [hashtag](https://docs.somnia.network/developer/building-dapps/oracles/protofire-price-feeds#somnia-testnet-price-feeds) Somnia Testnet Price Feeds -------------------------------------------------------------------------------------------------------------------------------------------------------- **Token Pair** **Contract Address** USDC/USD [0xa2515C9480e62B510065917136B08F3f7ad743B4arrow-up-right](http://shannon-explorer.somnia.network/address/0xa2515C9480e62B510065917136B08F3f7ad743B4) ETH/USD [0xd9132c1d762D432672493F640a63B758891B449earrow-up-right](http://shannon-explorer.somnia.network/address/0xd9132c1d762D432672493F640a63B758891B449e) BTC/USD [0x8CeE6c58b8CbD8afdEaF14e6fCA0876765e161fEarrow-up-right](http://shannon-explorer.somnia.network/address/0x8CeE6c58b8CbD8afdEaF14e6fCA0876765e161fE) Use any of these when deploying the PriceConsumer Smart Contract for your dApp. [hashtag](https://docs.somnia.network/developer/building-dapps/oracles/protofire-price-feeds#what-are-oracles-and-why-do-they-matter) What Are Oracles and Why Do They Matter ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- This service is powered by Protofire, an infrastructure provider that integrates decentralized oracle networks for Somnia. Learn more at [protofire.ioarrow-up-right](https://protofire.io/services/solution/oracle-integration) . Oracles are critical infrastructure in the blockchain ecosystem that enable Smart Contracts to interact with real-world data. Blockchains are deterministic by design and cannot fetch off-chain information directly. This creates a need for oracles, which are trusted data feeds that can push external data, like market prices, sports scores, or weather conditions, into Smart Contracts. Chainlink is the most widely adopted decentralized oracle network. It allows developers to access reliable data feeds that are resistant to manipulation and downtime. In this tutorial, we focus on Protofire Chainlink Price Feeds, which provide real-time market prices for assets like ETH, BTC, and USDC on Somnia Smart Contracts that rely on accurate pricing (e.g., lending, trading, insurance) benefit immensely from using decentralized oracles like Protofire. Oracles unlock use cases that were previously impossible due to blockchain isolation. In this guide, we will build a live crypto price tracker that displays BTC/USD, ETH/USD, and USDC/USD using Protofire Chainlink Price Feeds deployed on the Somnia Testnet. [hashtag](https://docs.somnia.network/developer/building-dapps/oracles/protofire-price-feeds#prerequisites) Prerequisites ------------------------------------------------------------------------------------------------------------------------------ 1. This guide is not an introduction to JavaScript Programming; you are expected to understand JavaScript. 2. Basic knowledge of React & Next.js. ### [hashtag](https://docs.somnia.network/developer/building-dapps/oracles/protofire-price-feeds#solidity-contract-chainlink-oracle-consumer) Solidity Contract (Chainlink Oracle Consumer) ### [hashtag](https://docs.somnia.network/developer/building-dapps/oracles/protofire-price-feeds#code-breakdown) Code Breakdown Pulls in the AggregatorV3Interface from the Chainlink library. This interface allows interaction with Chainlink oracle contracts for Price Feeds. The contract's constructor runs once when the contract is deployed. It takes the address of the Protofire Chainlink Price Feed contract and stores it. Instantiates the interface with the provided address, enabling function calls to the external oracle. `getLatestPrice()` is a public function which is callable from outside the contract. It does not modify blockchain state and returns the latest price from the Chainlink feed. It calls the Chainlink function `**latestRoundData()**` which returns a 5-value tuple: This function extracts only the price and ignores the rest using commas. It returns the latest price as an int256. `getDecimals()` is a helper function to return the number of decimals used by the price feed. Ensures consumers of the contract know how to scale the price properly. [hashtag](https://docs.somnia.network/developer/building-dapps/oracles/protofire-price-feeds#how-price-oracle-latestrounddata-works) How Price Oracle latestRoundData() works ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- It's helpful to understand how the Price Feed data is structured. The function `latestRoundData()` from the Chainlink Aggregator interface returns the following tuple: * `roundId`: The current round number for the feed * `price`: The actual price value (e.g. ETH/USD = 1900.42 × 10^8) * `startedAt`: Timestamp when this round started * `timeStamp`: When the answer was last updated * `answeredInRound`: The round in which the answer was submitted Most price consumer contracts use only `price`, but for more robust designs, `timeStamp` can be checked to ensure the price is recent. Additionally, Protofire Chainlink Price Feeds often return prices with 8 decimals. This means the raw price value needs to be normalized by dividing it by 10 \*\* decimals. This is essential because Solidity doesn't support floating-point math. Prices are represented using fixed-point math. For example, if ETH/USD is $1,940.82 and the feed uses 8 decimals, the returned price would be 194082000000. You must divide by 10 \*\* 8 to display $1940.82 in your UI. Always use the `getDecimals()` method provided by the Aggregator interface to dynamically adjust for different feeds that may use 6, 8, or 18 decimals depending on the asset. [hashtag](https://docs.somnia.network/developer/building-dapps/oracles/protofire-price-feeds#deploy-with-hardhat) Deploy with Hardhat ------------------------------------------------------------------------------------------------------------------------------------------ Update ignition/modules/deploy.js: Deploy using: [hashtag](https://docs.somnia.network/developer/building-dapps/oracles/protofire-price-feeds#building-the-ui) Building the UI ---------------------------------------------------------------------------------------------------------------------------------- Now that we’ve deployed the contract and confirmed it fetches live price data from Somnia’s Protofire Oracles, let’s bring it to life with a clean and responsive UI. We’ll use React and Viem.js to build a real-time dashboard that displays crypto prices with auto-refresh and token selection. Start by creating a new Next.js app. Then, install required dependencies. Add imports to the `index.js` file * useEffect, useState: React hooks for lifecycle and state management. * createPublicClient: Creates a read-only client to interact with the blockchain. * http: Defines the transport layer for the client (uses Somnia RPC). * parseAbi: Parses the contract's ABI. * formatUnits: Converts big numbers (like token prices) to a human readable format.s. #### [hashtag](https://docs.somnia.network/developer/building-dapps/oracles/protofire-price-feeds#create-a-viem-client-for-somnia) Create a Viem Client for Somnia Creates a blockchain client configured for Somnia Testnet using its RPC URL and allows reading smart contract data without needing a wallet or signer. #### [hashtag](https://docs.somnia.network/developer/building-dapps/oracles/protofire-price-feeds#declare-a-variable-for-the-deployed-smart-contracts-for-your-price-feed-addresses) Declare a variable for the deployed Smart Contracts for your Price Feed Addresses This will map token pairs to their corresponding Chainlink oracle contract addresses deployed on Somnia. Parse the ABI #### [hashtag](https://docs.somnia.network/developer/building-dapps/oracles/protofire-price-feeds#set-up-the-state) Set up the State The `price` state will store the formatted token price and `selectedToken` will track which token is selected from the dropdown (default: ETH). #### [hashtag](https://docs.somnia.network/developer/building-dapps/oracles/protofire-price-feeds#fetch-the-latest-price) Fetch the Latest Price Reads the price and its decimal precision from the Chainlink oracle contract. The function declaration uses `Promise.all()` to optimize performance by fetching both at once and formats the price to 2 decimal places for display. #### [hashtag](https://docs.somnia.network/developer/building-dapps/oracles/protofire-price-feeds#fetch-price-on-load-and-every-10-seconds) Fetch Price on Load & Every 10 Seconds The `useEffect` hook runs `fetchPrice()`, once on component mount and every time `selectedToken` changes. The hook also refreshes price data every 10 seconds Live Price Display in the `return` statement. [hashtag](https://docs.somnia.network/developer/building-dapps/oracles/protofire-price-feeds#complete-code) Complete Code ------------------------------------------------------------------------------------------------------------------------------ chevron-rightindex.js[hashtag](https://docs.somnia.network/developer/building-dapps/oracles/protofire-price-feeds#index.js) [hashtag](https://docs.somnia.network/developer/building-dapps/oracles/protofire-price-feeds#conclusion) Conclusion ------------------------------------------------------------------------------------------------------------------------ The Protofire Oracle integration on Somnia provides developers with reliable, on-chain price feeds for key assets like ETH, BTC, and USDC. Using verified oracles and standardized data formats enables accurate, real-time pricing essential for building GaemFi, DeFi, trading, and financial applications. [PreviousDIA Price Feedschevron-left](https://docs.somnia.network/developer/building-dapps/oracles/dia-price-feeds) [NextUsing Verifiable Randomness (VRF)chevron-right](https://docs.somnia.network/developer/building-dapps/oracles/using-verifiable-randomness-vrf) Last updated 5 months ago * [Somnia Mainnet Price Feeds](https://docs.somnia.network/developer/building-dapps/oracles/protofire-price-feeds#somnia-mainnet-price-feeds) * [Somnia Testnet Price Feeds](https://docs.somnia.network/developer/building-dapps/oracles/protofire-price-feeds#somnia-testnet-price-feeds) * [What Are Oracles and Why Do They Matter](https://docs.somnia.network/developer/building-dapps/oracles/protofire-price-feeds#what-are-oracles-and-why-do-they-matter) * [Prerequisites](https://docs.somnia.network/developer/building-dapps/oracles/protofire-price-feeds#prerequisites) * [Solidity Contract (Chainlink Oracle Consumer)](https://docs.somnia.network/developer/building-dapps/oracles/protofire-price-feeds#solidity-contract-chainlink-oracle-consumer) * [Code Breakdown](https://docs.somnia.network/developer/building-dapps/oracles/protofire-price-feeds#code-breakdown) * [How Price Oracle latestRoundData() works](https://docs.somnia.network/developer/building-dapps/oracles/protofire-price-feeds#how-price-oracle-latestrounddata-works) * [Deploy with Hardhat](https://docs.somnia.network/developer/building-dapps/oracles/protofire-price-feeds#deploy-with-hardhat) * [Building the UI](https://docs.somnia.network/developer/building-dapps/oracles/protofire-price-feeds#building-the-ui) * [Complete Code](https://docs.somnia.network/developer/building-dapps/oracles/protofire-price-feeds#complete-code) * [Conclusion](https://docs.somnia.network/developer/building-dapps/oracles/protofire-price-feeds#conclusion) Copy // SPDX-License-Identifier: MIT pragma solidity ^0.8.7; import "@chainlink/contracts/src/v0.8/interfaces/AggregatorV3Interface.sol"; contract PriceConsumer { AggregatorV3Interface internal priceFeed; constructor(address _priceFeed) { priceFeed = AggregatorV3Interface(_priceFeed); } /** * Returns the latest price */ function getLatestPrice() public view returns (int256) { ( /* uint80 roundID */, int256 price, /* uint startedAt */, /* uint timeStamp */, /* uint80 answeredInRound */ ) = priceFeed.latestRoundData(); return price; } /** * Returns price decimals */ function getDecimals() public view returns (uint8) { return priceFeed.decimals(); } } Copy import `@chainlink/contracts/src/v0.8/interfaces/AggregatorV3Interface.sol Copy constructor(address _priceFeed) { } Copy priceFeed = AggregatorV3Interface(_priceFeed); Copy function getLatestPrice() public view returns (int256) { ( /* uint80 roundID */, int256 price, /* uint startedAt */, /* uint timeStamp */, /* uint80 answeredInRound */ ) = priceFeed.latestRoundData(); return price; } Copy (uint80 roundId, int256 price, uint startedAt, uint timeStamp, uint80 answeredInRound) Copy function getDecimals() public view returns (uint8) { return priceFeed.decimals(); } Copy (uint80 roundId, int256 answer, uint startedAt, uint timeStamp, uint80 answeredInRound) Copy import { buildModule } from "@nomicfoundation/hardhat-ignition/modules"; const PriceConsumerModule = buildModule("PriceConsumerModule", (m) => { // Replace this with the correct feed address for your chosen pair const feedAddress = m.getParameter( "feedAddress", "0xd9132c1d762D432672493F640a63B758891B449e" // Example: ETH/USD on Somnia ); const priceConsumer = m.contract("PriceConsumer", [feedAddress]); return { priceConsumer }; }); export default PriceConsumerModule; Copy npx hardhat ignition deploy ./ignition/modules/Lock.js --network somnia Copy npx create-next-app@latest somnia-protofire-example cd somnia-protofire-example Copy npm install viem Copy import { useEffect, useState } from 'react'; import { createPublicClient, http, parseAbi, formatUnits } from 'viem'; import { somniaTestnet } from 'viem/chains'; Copy const client = createPublicClient({ chain: somniaTestnet, transport: http(), }); Copy const FEEDS = { //Testnet Price Feeds ETH: '0x604CF5063eC760A78d1C089AA55dFf29B90937f9', BTC: '0x3dF17dbaa3BA861D03772b501ADB343B4326C676', USDC: '0xA4a08Eb26f85A53d40E3f908B406b2a69B1A2441', }; Copy const abi = parseAbi([\ 'function getLatestPrice() view returns (int256)',\ 'function getDecimals() view returns (uint8)',\ ]); Copy export default function PriceWidget() { const [price, setPrice] = useState(''); const [selectedToken, setSelectedToken] = useState<'ETH' | 'BTC' | 'USDC'>('ETH'); Copy const fetchPrice = async () => { const contractAddress = FEEDS[selectedToken]; const [rawPrice, decimals] = await Promise.all([\ client.readContract({ address: contractAddress, abi, functionName: 'getLatestPrice' }),\ client.readContract({ address: contractAddress, abi, functionName: 'getDecimals' }),\ ]); const normalized = formatUnits(rawPrice, decimals); setPrice(parseFloat(normalized).toFixed(2)); }; Copy useEffect(() => { fetchPrice(); const interval = setInterval(fetchPrice, 10000); return () => clearInterval(interval); }, [selectedToken]); Copy return ( ...

${price}

... ) Copy import { useEffect, useState } from 'react'; import { createPublicClient, http, parseAbi, formatUnits } from 'viem'; import { somniaTestnet } from 'viem/chains'; const client = createPublicClient({ chain: somniaTestnet, transport: http(), }); const FEEDS = { ETH: '0x604CF5063eC760A78d1C089AA55dFf29B90937f9', BTC: '0x3dF17dbaa3BA861D03772b501ADB343B4326C676', USDC: '0xA4a08Eb26f85A53d40E3f908B406b2a69B1A2441', }; const abi = parseAbi([\ 'function getLatestPrice() view returns (int256)',\ 'function getDecimals() view returns (uint8)',\ ]); export default function PriceWidget() { const [price, setPrice] = useState(''); const [selectedToken, setSelectedToken] = useState<'ETH' | 'BTC' | 'USDC'>( 'ETH' ); const fetchPrice = async () => { const contractAddress = FEEDS[selectedToken]; const [rawPrice, decimals] = await Promise.all([\ client.readContract({\ address: contractAddress,\ abi,\ functionName: 'getLatestPrice',\ }),\ client.readContract({\ address: contractAddress,\ abi,\ functionName: 'getDecimals',\ }),\ ]); const normalized = formatUnits(rawPrice, decimals); setPrice(parseFloat(normalized).toFixed(2)); }; useEffect(() => { fetchPrice(); const interval = setInterval(fetchPrice, 10000); return () => clearInterval(interval); }, [selectedToken]); return (

{selectedToken}/USD on Somnia

${price}

); } --- # Integrate Chainlink Oracles | Somnia Docs Somnia Data Streams provides a powerful, on-chain, and composable storage layer. [Chainlink Oraclesarrow-up-right](https://docs.chain.link/data-feeds/price-feeds/addresses?page=1&testnetPage=1&networkType=testnet&search=&testnetSearch=) provide secure, reliable, and decentralized external data feeds. When you combine them, you unlock a powerful new capability: **creating historical, queryable, on-chain data streams from real-world data.** Chainlink Price Feeds are designed to provide the _latest_ price of an asset. They are not designed to provide a queryable history. You cannot easily ask a Price Feed, "What was the price of ETH 48 hours ago?" By integrating Chainlink with Somnia Streams, you can build a "snapshot bot" that reads from Chainlink at regular intervals and appends the price to a Somnia Data Stream. This creates a permanent, verifiable, and historical on-chain feed that any other DApp or user can read and trust. [hashtag](https://docs.somnia.network/developer/data-streams/tutorials/integrate-chainlink-oracles#objectives-and-deliverable) **Objectives & Deliverable** ---------------------------------------------------------------------------------------------------------------------------------------------------------------- * **Objective:** Fetch off-chain data (a price feed) with Chainlink and store it historically via Somnia Streams. * **Key Takeaway:** Combining external "truth" sources with Somnia's composable storage to create new, valuable on-chain data products. * **Deliverable:** A hybrid "Snapshot Bot" that reads from Chainlink on the Sepolia testnet and publishes to a historical price feed on the Somnia Testnet. [hashtag](https://docs.somnia.network/developer/data-streams/tutorials/integrate-chainlink-oracles#what-youll-build) What You'll Build ------------------------------------------------------------------------------------------------------------------------------------------- 1. **A New Schema:** A `priceFeedSchema` to store price data. 2. **A Chainlink Reader:** A script using `viem` to read the `latestRoundData` from Chainlink's ETH/USD feed on the Sepolia testnet. 3. **A Snapshot Bot:** A script that reads from Chainlink (Sepolia) and writes to Somnia Data Streams (Somnia Testnet). 4. **A History Reader:** A script to read our new historical price feed from Somnia Data Streams. This tutorial demonstrates a true hybrid-chain application. [hashtag](https://docs.somnia.network/developer/data-streams/tutorials/integrate-chainlink-oracles#prerequisites) Prerequisites ------------------------------------------------------------------------------------------------------------------------------------ * Node.js 20+. * `@somnia-chain/streams`, `viem`, and `dotenv` installed. * A wallet with Somnia Testnet tokens (for publishing) and Sepolia testnet ETH (for gas, though we are only reading, so a public RPC is fine). [hashtag](https://docs.somnia.network/developer/data-streams/tutorials/integrate-chainlink-oracles#environment-setup) **Environment Setup** ------------------------------------------------------------------------------------------------------------------------------------------------ Create a `.env` file. You will need RPC URLs for **both** chains and a private key for the Somnia Testnet (to pay for publishing). [hashtag](https://docs.somnia.network/developer/data-streams/tutorials/integrate-chainlink-oracles#project-setup) Project Setup ------------------------------------------------------------------------------------------------------------------------------------ Set up your project with `viem` and the Streams SDK. [hashtag](https://docs.somnia.network/developer/data-streams/tutorials/integrate-chainlink-oracles#chain-configuration) **Chain Configuration** ---------------------------------------------------------------------------------------------------------------------------------------------------- We need to define both chains we are interacting with. `**src/lib/chain.ts**` [hashtag](https://docs.somnia.network/developer/data-streams/tutorials/integrate-chainlink-oracles#client-configuration) **Client Configuration** ------------------------------------------------------------------------------------------------------------------------------------------------------ We will create two separate clients: * A **Somnia SDK client** (with a wallet) to _write_ data. * A **Sepolia Public Client** (read-only) to _read_ from Chainlink. `**src/lib/clients.ts**` [hashtag](https://docs.somnia.network/developer/data-streams/tutorials/integrate-chainlink-oracles#define-the-price-feed-schema) Define the Price Feed Schema ------------------------------------------------------------------------------------------------------------------------------------------------------------------ Our schema will store the core data from Chainlink's feed. `**src/lib/schema.ts**` * `timestamp`: The `updatedAt` time from Chainlink. * `price`: The `answer` (e.g., ETH price). * `roundId`: The Chainlink round ID, to prevent duplicates. * `pair`: A string to identify the feed (e.g., "ETH/USD"). [hashtag](https://docs.somnia.network/developer/data-streams/tutorials/integrate-chainlink-oracles#create-the-chainlink-reader) Create the Chainlink Reader ---------------------------------------------------------------------------------------------------------------------------------------------------------------- Let's create a dedicated file to handle fetching data from Chainlink. We will use the `ETH/USD` feed on Sepolia. `**src/lib/chainlinkReader.ts**` [hashtag](https://docs.somnia.network/developer/data-streams/tutorials/integrate-chainlink-oracles#build-the-snapshot-bot-the-hybrid-app) Build the Snapshot Bot (The Hybrid App) -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- This is the core of our project. This script will: 1. Fetch the latest price from Chainlink (using our module). 2. Encode this data using our `priceFeedSchema`. 3. Publish the data to Somnia Data Streams. `**src/scripts/snapshotBot.ts**` **To run your bot:** Add a script to `package.json`: `"snapshot": "ts-node src/scripts/snapshotBot.ts"` Run it: `npm run snapshot` You can run this script multiple times. It will only add new data if Chainlink's `roundId` has changed. [hashtag](https://docs.somnia.network/developer/data-streams/tutorials/integrate-chainlink-oracles#read-your-historical-price-feed) Read Your Historical Price Feed ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ Now for the payoff. Let's create a script that reads our new on-chain history from Somnia Streams. `**src/scripts/readHistory.ts**` **To read the history:** Add to `package.json`: `"history": "ts-node src/scripts/readHistory.ts"` Run it: `npm run history` **Expected Output:** [hashtag](https://docs.somnia.network/developer/data-streams/tutorials/integrate-chainlink-oracles#conclusion-key-takeaways) Conclusion: Key Takeaways ----------------------------------------------------------------------------------------------------------------------------------------------------------- You have successfully built a hybrid, cross-chain application. * You combined an **external "truth source"** (Chainlink) with Somnia's **composable storage layer** (Somnia Data Streams). * You created a new, valuable, on-chain data product: a **historical, queryable price feed** that any dApp on Somnia can now read from and trust. * You demonstrated the power of the `publisher` address as a verifiable source. Any dApp can now consume your feed, knowing it was published by _your_ trusted bot. This pattern can be extended to any external data source: weather, sports results, IoT data, and more. You can run the `snapshotBot.ts` script as a cron job or serverless function to create a truly autonomous, on-chain oracle. [PreviousREAD Stream Data from a UI (Next.js Example)chevron-left](https://docs.somnia.network/developer/data-streams/tutorials/read-stream-data-from-a-ui-next.js-example) [NextWorking with Multiple Publishers in a Shared Streamchevron-right](https://docs.somnia.network/developer/data-streams/tutorials/working-with-multiple-publishers-in-a-shared-stream) Last updated 1 month ago * [Objectives & Deliverable](https://docs.somnia.network/developer/data-streams/tutorials/integrate-chainlink-oracles#objectives-and-deliverable) * [What You'll Build](https://docs.somnia.network/developer/data-streams/tutorials/integrate-chainlink-oracles#what-youll-build) * [Prerequisites](https://docs.somnia.network/developer/data-streams/tutorials/integrate-chainlink-oracles#prerequisites) * [Environment Setup](https://docs.somnia.network/developer/data-streams/tutorials/integrate-chainlink-oracles#environment-setup) * [Project Setup](https://docs.somnia.network/developer/data-streams/tutorials/integrate-chainlink-oracles#project-setup) * [Chain Configuration](https://docs.somnia.network/developer/data-streams/tutorials/integrate-chainlink-oracles#chain-configuration) * [Client Configuration](https://docs.somnia.network/developer/data-streams/tutorials/integrate-chainlink-oracles#client-configuration) * [Define the Price Feed Schema](https://docs.somnia.network/developer/data-streams/tutorials/integrate-chainlink-oracles#define-the-price-feed-schema) * [Create the Chainlink Reader](https://docs.somnia.network/developer/data-streams/tutorials/integrate-chainlink-oracles#create-the-chainlink-reader) * [Build the Snapshot Bot (The Hybrid App)](https://docs.somnia.network/developer/data-streams/tutorials/integrate-chainlink-oracles#build-the-snapshot-bot-the-hybrid-app) * [Read Your Historical Price Feed](https://docs.somnia.network/developer/data-streams/tutorials/integrate-chainlink-oracles#read-your-historical-price-feed) * [Conclusion: Key Takeaways](https://docs.somnia.network/developer/data-streams/tutorials/integrate-chainlink-oracles#conclusion-key-takeaways) Copy # .env RPC_URL_SOMNIA=[https://dream-rpc.somnia.network] RPC_URL_SEPOLIA=[https://sepolia.drpc.org] PRIVATE_KEY_SOMNIA=0xYOUR_SOMNIA_PRIVATE_KEY Copy npm i @somnia-chain/streams viem dotenv npm i -D @types/node typescript ts-node Copy import { defineChain } from 'viem' import { sepolia as sepoliaBase } from 'viem/chains' // 1. Somnia Testnet export const somniaTestnet = defineChain({ id: 50312, name: 'Somnia Testnet', network: 'somnia-testnet', nativeCurrency: { name: 'STT', symbol: 'STT', decimals: 18 }, rpcUrls: { default: { http: [process.env.RPC_URL_SOMNIA || ''] }, public: { http: [process.env.RPC_URL_SOMNIA || ''] }, }, } as const) // 2. Sepolia Testnet (for Chainlink) export const sepolia = sepoliaBase Copy import 'dotenv/config' import { createPublicClient, createWalletClient, http } from 'viem' import { privateKeyToAccount } from 'viem/accounts' import { SDK } from '@somnia-chain/streams' import { somniaTestnet, sepolia } from './chain' function getEnv(key: string): string { const value = process.env[key] if (!value) throw new Error(`Missing environment variable: ${key}`) return value } // === Client 1: Somnia SDK (Read/Write) === const somniaWalletClient = createWalletClient({ account: privateKeyToAccount(getEnv('PRIVATE_KEY_SOMNIA') as `0x${string}`), chain: somniaTestnet, transport: http(getEnv('RPC_URL_SOMNIA')), }) const somniaPublicClient = createPublicClient({ chain: somniaTestnet, transport: http(getEnv('RPC_URL_SOMNIA')), }) export const somniaSdk = new SDK({ public: somniaPublicClient, wallet: somniaWalletClient, }) // === Client 2: Sepolia Public Client (Read-Only) === export const sepoliaPublicClient = createPublicClient({ chain: sepolia, transport: http(getEnv('RPC_URL_SEPOLIA')), }) Copy // This schema will store historical price snapshots export const priceFeedSchema = 'uint64 timestamp, int256 price, uint80 roundId, string pair' Copy import { parseAbi, Address } from 'viem' import { sepoliaPublicClient } from './clients' // Chainlink ETH/USD Feed on Sepolia Testnet const CHAINLINK_FEED_ADDRESS: Address = '0x694AA1769357215DE4FAC081bf1f309aDC325306' // Minimal ABI for AggregatorV3Interface const CHAINLINK_ABI = parseAbi([\ 'function latestRoundData() external view returns (uint80 roundId, int256 answer, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound)',\ 'function decimals() external view returns (uint8)',\ ]) export interface PriceData { roundId: bigint price: bigint timestamp: bigint decimals: number } /** * Fetches the latest price data from the Chainlink ETH/USD feed on Sepolia. */ export async function fetchLatestPrice(): Promise { console.log('Fetching latest price from Chainlink on Sepolia...') try { const [roundData, decimals] = await Promise.all([\ sepoliaPublicClient.readContract({\ address: CHAINLINK_FEED_ADDRESS,\ abi: CHAINLINK_ABI,\ functionName: 'latestRoundData',\ }),\ sepoliaPublicClient.readContract({\ address: CHAINLINK_FEED_ADDRESS,\ abi: CHAINLINK_ABI,\ functionName: 'decimals',\ })\ ]) const [roundId, answer, , updatedAt] = roundData console.log(`Chainlink data received: Round ${roundId}, Price ${answer}`) return { roundId, price: answer, timestamp: updatedAt, decimals, } } catch (error: any) { console.error(`Failed to read from Chainlink: ${error.message}`) throw error } } Copy import 'dotenv/config' import { somniaSdk } from '../lib/clients' import { priceFeedSchema } from '../lib/schema' import { fetchLatestPrice } from '../lib/chainlinkReader' import { SchemaEncoder, zeroBytes32 } from '@somnia-chain/streams' import { toHex, Hex } from 'viem' import { waitForTransactionReceipt } from 'viem/actions' const PAIR_NAME = "ETH/USD" async function main() { console.log('--- Starting Snapshot Bot ---') // 1. Initialize SDK and Encoder const sdk = somniaSdk const encoder = new SchemaEncoder(priceFeedSchema) const publisherAddress = sdk.wallet.account?.address if (!publisherAddress) throw new Error('Wallet client not initialized.') // 2. Compute Schema ID and Register (idempotent) const schemaId = await sdk.streams.computeSchemaId(priceFeedSchema) if (!schemaId) throw new Error('Could not compute schemaId') const ignoreAlreadyRegisteredSchemas = true const regTx = await sdk.streams.registerDataSchemas([\ { id: 'price-feed-v1', schema: priceFeedSchema }\ ], ignoreAlreadyRegisteredSchemas) if (!regTx) throw new Error('Failed to register schema') await waitForTransactionReceipt(sdk.public, { hash: regTx }) // 3. Fetch data from Chainlink const priceData = await fetchLatestPrice() // 4. Encode data for Somnia Streams const encodedData: Hex = encoder.encodeData([\ { name: 'timestamp', value: priceData.timestamp.toString(), type: 'uint64' },\ { name: 'price', value: priceData.price.toString(), type: 'int256' },\ { name: 'roundId', value: priceData.roundId.toString(), type: 'uint80' },\ { name: 'pair', value: PAIR_NAME, type: 'string' },\ ]) // 5. Create a unique Data ID (using the roundId to prevent duplicates) const dataId = toHex(`price-${PAIR_NAME}-${priceData.roundId}`, { size: 32 }) // 6. Publish to Somnia Data Streams console.log(`Publishing price data to Somnia Streams...`) const txHash = await sdk.streams.set([\ { id: dataId, schemaId, data: encodedData }\ ]) if (!txHash) throw new Error('Failed to publish to Streams') await waitForTransactionReceipt(sdk.public, { hash: txHash }) console.log('\n--- Snapshot Complete! ---') console.log(` Publisher: ${publisherAddress}`) console.log(` Schema ID: ${schemaId}`) console.log(` Data ID: ${dataId}`) console.log(` Tx Hash: ${txHash}`) } main().catch((e) => { console.error(e) process.exit(1) }) Copy import 'dotenv/config' import { somniaSdk } from '../lib/clients' import { priceFeedSchema } from '../lib/schema' import { SchemaDecodedItem } from '@somnia-chain/streams' // Helper to decode the SDK's output interface PriceRecord { timestamp: number price: bigint roundId: bigint pair: string } function decodePriceRecord(row: SchemaDecodedItem[]): PriceRecord { const val = (field: any) => field?.value?.value ?? field?.value ?? '' return { timestamp: Number(val(row[0])), price: BigInt(val(row[1])), roundId: BigInt(val(row[2])), pair: String(val(row[3])), } } async function main() { console.log('--- Reading Historical Price Feed from Somnia Streams ---') const sdk = somniaSdk // Use the *publisher address* from your .env file const publisherAddress = sdk.wallet.account?.address if (!publisherAddress) throw new Error('Wallet client not initialized.') const schemaId = await sdk.streams.computeSchemaId(priceFeedSchema) if (!schemaId) throw new Error('Could not compute schemaId') console.log(`Reading all data for publisher: ${publisherAddress}`) console.log(`Schema: ${schemaId}\n`) // Fetch all data for this schema and publisher const data = await sdk.streams.getAllPublisherDataForSchema(schemaId, publisherAddress) if (!data || data.length === 0) { console.log('No price history found. Run the snapshot bot first.') return } const records = (data as SchemaDecodedItem[][]).map(decodePriceRecord) // Sort by timestamp records.sort((a, b) => a.timestamp - b.timestamp) console.log(`Found ${records.length} historical price points:\n`) records.forEach(record => { // We assume the decimals are 8 for this ETH/USD feed const priceFloat = Number(record.price) / 10**8 console.log( `[${new Date(record.timestamp * 1000).toISOString()}] ${record.pair} - $${priceFloat.toFixed(2)} (Round: ${record.roundId})` ) }) } main().catch((e) => { console.error(e) process.exit(1) }) Copy --- Reading Historical Price Feed from Somnia Streams --- ... Found 3 historical price points: [2025-11-06T14:30:00.000Z] ETH/USD - $3344.50 (Round: 110...) [2025-11-06T14:35:00.000Z] ETH/USD - $3362.12 (Round: 111...) [2025-11-06T14:40:00.000Z] ETH/USD - $3343.90 (Round: 112...) --- # On Ramps | Somnia Docs [hashtag](https://docs.somnia.network/developer/deployment-and-production/ecosystem/ecosystem-tools/on-ramps#banxa) [Banxaarrow-up-right](https://banxa.com/) ------------------------------------------------------------------------------------------------------------------------------------------------------------------- Seamlessly integrate crypto-fiat exchange, transfer, and compliance solutions to expand globally and unlock new revenue streams. ### [hashtag](https://docs.somnia.network/developer/deployment-and-production/ecosystem/ecosystem-tools/on-ramps#resources) Resources [Documentationarrow-up-right](https://docs.banxa.com/docs/tutorial) [PreviousSDKschevron-left](https://docs.somnia.network/developer/deployment-and-production/ecosystem/ecosystem-tools/sdks) [NextAccount Abstractionchevron-right](https://docs.somnia.network/developer/deployment-and-production/ecosystem/ecosystem-tools/account-abstraction) Last updated 5 months ago * [Banxa](https://docs.somnia.network/developer/deployment-and-production/ecosystem/ecosystem-tools/on-ramps#banxa) * [Resources](https://docs.somnia.network/developer/deployment-and-production/ecosystem/ecosystem-tools/on-ramps#resources) --- # Somnia Gas Differences To Ethereum | Somnia Docs Somnia's gas costs work very differently to Ethereum's. For background: EVM gas fees are designed to approximate the load placed on the blockchain. They are used to charge users in proportion to the system resources they consume. A transaction's gas fee (in ETH or SOMI) is calculated as `number of units of gas * price per unit of gas`. The number of units of gas is just the sum of the gas costs of the transaction's operations. Because the price of a unit of gas is variable, the absolute cost of an operation in units of gas does not ultimately matter. What matters is the relative difference between operations' gas costs. In other words, if you doubled the gas costs for all operations, and halved the price per unit of gas, there would be no functional change. In regards to determining gas costs, there are two fundamental differences between Ethereum and Somnia: 1. Somnia's architecture and implementation has drastically reduced the real-world performance cost of most EVM operations, but not all. A few operations, such as third-party library precompiles, are just as performant on Ethereum as on Somnia - both use the same code. This relative difference requires us to adjust the relative gas costs of these operations. We could either reduce the gas costs of all operations that Somnia has improved, or increase the gas costs of all operations that Somnia has _not_ improved - the result is equivalent. Somnia does the latter, as it fits better into the Ethereum developer ecosystem. 2. Ethereum charges a flat gas cost to read any existing value from storage for the first time in a transaction. This ends up hitting a database whose latency for this read can vary by multiple orders of magnitude. This gas cost inaccuracy is a big problem when scaling these blockchains, as it requires them to leave massive amounts of unused buffer in their execution budget to account for these unpredictable latencies. Somnia has built a new database, IceDb, that is designed to fix this problem. Only the (rare) reads which actually take a long time have a high gas cost; reading from cached state is far cheaper. These two factors mean that Somnia requires different gas semantics to Ethereum. The differences are detailed in this document. [hashtag](https://docs.somnia.network/developer/deployment-and-production/somnia-gas-differences-to-ethereum#precompiles) Precompiles ------------------------------------------------------------------------------------------------------------------------------------------ These precompiles have flat multiples applied to [the same gas calculation that Ethereum usesarrow-up-right](https://www.evm.codes/precompiled) . * `ecRecover (0x01)` costs **50** times the Ethereum calculated gas usage. (i.e. **150,000** gas instead of **3000** gas.) * `SHA2-256 (0x02)` costs **50** times the Ethereum calculated gas usage. * `RIPEMD-160 (0x03)` costs **10** times the Ethereum calculated gas usage. * `modexp (0x05)` costs **10** times the Ethereum calculated gas usage. * `ecAdd (0x06)` costs **50** times the Ethereum calculated gas usage. * `ecMul (0x07)` costs **10** times the Ethereum calculated gas usage. * `ecPairing (0x08)` costs **250** times the Ethereum calculated gas usage. * `blake2f (0x09)` costs **10** times the Ethereum calculated gas usage. * `point evaluation (0x0a)` costs **50** times the Ethereum calculated gas usage. BLS operations: * `BLS12_G1ADD (0x0b)` costs **220** times the Ethereum calculated gas usage. * `BLS12_G1MSM (0x0c)` costs **110** times the Ethereum calculated gas usage. * `BLS12_G2ADD (0x0d)` costs **330** times the Ethereum calculated gas usage. * `BLS12_G2MSM (0x0e)` costs **50** times the Ethereum calculated gas usage. * `BLS12_PAIRING_CHECK (0x0f)` costs **50** times the Ethereum calculated gas usage. * `BLS12_MAP_FP_TO_G1 (0x10)` costs **120** times the Ethereum calculated gas usage. * `BLS12_MAP_FP2_TO_G2 (0x11)` costs **50** times the Ethereum calculated gas usage. [hashtag](https://docs.somnia.network/developer/deployment-and-production/somnia-gas-differences-to-ethereum#non-storage-evm-operations) Non-storage EVM operations ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ * `SELFBALANCE` costs **305** gas instead of **5** gas. * `KECCAK256` costs `1250 + 300 * minimum_word_size` gas instead of `30 + 6 * minimum_word_size` gas. * `ADDMOD` costs **358** gas instead of **8** gas. * `MULMOD` costs **358** gas instead of **8** gas. [hashtag](https://docs.somnia.network/developer/deployment-and-production/somnia-gas-differences-to-ethereum#storage-evm-operations) Storage EVM operations ---------------------------------------------------------------------------------------------------------------------------------------------------------------- Ethereum uses an [access list based gas modelarrow-up-right](https://www.evm.codes/about#access_list) for all storage operations, to charge extra gas depending on if the value being accessed has already been accessed _in the same transaction_, and if it is creating or deleting that state. As discussed above, Somnia instead charges extra gas based on a more accurate model of the real world latency it takes to read or write the value in question: ### [hashtag](https://docs.somnia.network/developer/deployment-and-production/somnia-gas-differences-to-ethereum#sload) `SLOAD` * If the storage slot key is in the set of most recently accessed **128 million** contract slot keys, **no extra gas is charged** (on top of the static op gas fee, which is **100** gas). * If the key does not exist, the access requires **at least 1,000,000 gas remaining**, but that gas is **not charged**, so there is no extra cost. * Otherwise, the read costs an additional **1,000,000** gas. ### [hashtag](https://docs.somnia.network/developer/deployment-and-production/somnia-gas-differences-to-ethereum#sstore) `SSTORE` * If the storage slot key is in the set of most recently accessed **128 million** contract slot keys, **no extra gas is charged** (on top of the static op gas fee, which is **100** gas). * If the key does not exist and is being _written to zero_, the access requires **at least 1,000,000 gas remaining**, but that gas is **not charged**, so there is no extra cost. * If the key does not exist and is being _written to a non-zero value_, the access requires **at least 1,000,000 gas remaining**, but the caller is charged **200,000** gas. * Otherwise, the write costs an additional **1,000,000** gas. ### [hashtag](https://docs.somnia.network/developer/deployment-and-production/somnia-gas-differences-to-ethereum#account-operations) Account operations The following EVM operations access account state: * `BALANCE` * `EXTCODESIZE` * `EXTCODECOPY` * `EXTCODEHASH` * `CALL` * `CALLCODE` * `DELEGATECALL` * `STATICCALL` * `CREATE` * `CREATE2` * `SELFDESTRUCT` These have an additional storage gas fee (in place of Ethereum's dynamic access list gas fee): * If the account is in the set of most recently accessed **32 million** accounts, **no extra gas is charged**. * If the account does not exist and is being _read_, the access requires **at least 1,000,000 gas remaining**, but that gas is **not charged**, so there is no extra cost. * If the account does not exist and is being _created_, the access requires **at least 1,000,000 gas remaining**, but the caller is charged **400,000** gas for the new account. * Otherwise, the access costs **1,000,000** gas. [hashtag](https://docs.somnia.network/developer/deployment-and-production/somnia-gas-differences-to-ethereum#evm-logs) EVM Logs ------------------------------------------------------------------------------------------------------------------------------------ Somnia charges an increased amount of gas for EVM logs due to the historical storage requirements. Ethereum charges: `375 + 375 * topic_count + 8 * size + memory_expansion_cost` gas Somnia charges: `3200 + 5120 * topic_count + 160 * size + memory_expansion_cost` gas To illustrate these changes with the five different EVM log operations: EVM Op Ethereum gas Somnia gas LOG0 with 32 bytes of data 631 8,320 LOG1 with 32 bytes of data 1,006 13,440 LOG2 with 32 bytes of data 1,381 18,560 LOG3 with 32 bytes of data 1,756 23,680 LOG4 with 32 bytes of data 2,131 28,800 [hashtag](https://docs.somnia.network/developer/deployment-and-production/somnia-gas-differences-to-ethereum#contract-bytecode-deployment-cost) Contract bytecode deployment cost -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- Ethereum charges **200** gas per byte of deployed bytecode. Somnia charges **3125** gas per byte of deployed bytecode. [hashtag](https://docs.somnia.network/developer/deployment-and-production/somnia-gas-differences-to-ethereum#eip-7702-delegation-authorisations) EIP-7702 delegation authorisations ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- Ethereum charges **25,000** gas per authorisation in a [Set Code transactionarrow-up-right](https://eips.ethereum.org/EIPS/eip-7702) . **12,500** gas is refunded if account creation isn't required. Somnia charges **1,570,000** gas per authorisation in a Set Code transaction. **400,000** gas is refunded if account creation isn't required. [PreviousExplorer API Health and Monitoringchevron-left](https://docs.somnia.network/developer/deployment-and-production/explorer-api-health-and-monitoring) [NextEcosystemchevron-right](https://docs.somnia.network/developer/deployment-and-production/ecosystem) Last updated 14 days ago * [Precompiles](https://docs.somnia.network/developer/deployment-and-production/somnia-gas-differences-to-ethereum#precompiles) * [Non-storage EVM operations](https://docs.somnia.network/developer/deployment-and-production/somnia-gas-differences-to-ethereum#non-storage-evm-operations) * [Storage EVM operations](https://docs.somnia.network/developer/deployment-and-production/somnia-gas-differences-to-ethereum#storage-evm-operations) * [SLOAD](https://docs.somnia.network/developer/deployment-and-production/somnia-gas-differences-to-ethereum#sload) * [SSTORE](https://docs.somnia.network/developer/deployment-and-production/somnia-gas-differences-to-ethereum#sstore) * [Account operations](https://docs.somnia.network/developer/deployment-and-production/somnia-gas-differences-to-ethereum#account-operations) * [EVM Logs](https://docs.somnia.network/developer/deployment-and-production/somnia-gas-differences-to-ethereum#evm-logs) * [Contract bytecode deployment cost](https://docs.somnia.network/developer/deployment-and-production/somnia-gas-differences-to-ethereum#contract-bytecode-deployment-cost) * [EIP-7702 delegation authorisations](https://docs.somnia.network/developer/deployment-and-production/somnia-gas-differences-to-ethereum#eip-7702-delegation-authorisations) --- # READ Stream Data from a UI (Next.js Example) | Somnia Docs In this guide, you’ll learn how to read data published to Somnia Data Streams directly from a Next.js frontend, the same way you’d use readContract with Viem. We’ll build a simple HelloWorld schema and use it to demonstrate all the READ methods in the Somnia Data Streams SDK, from fetching the latest message to retrieving complete datasets or schema metadata. * * * [hashtag](https://docs.somnia.network/developer/data-streams/tutorials/read-stream-data-from-a-ui-next.js-example#prerequisites) Prerequisites --------------------------------------------------------------------------------------------------------------------------------------------------- Before we begin, make sure you have: Copy npm i @somnia-chain/streams viem Also ensure: * Node.js 20+ * A Somnia Testnet wallet with STT test tokens * `.env` file containing your wallet credentials: * A working Next.js app (npx create-next-app somnia-streams-read) * Access to a publisher address and schema ID (or one you’ve created earlier) * * * [hashtag](https://docs.somnia.network/developer/data-streams/tutorials/read-stream-data-from-a-ui-next.js-example#set-up-the-sdk-and-client) Set up the SDK and Client --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- We’ll initialize the SDK using Viem’s createPublicClient to communicate with Somnia’s blockchain. Copy // lib/store.ts import { SDK } from '@somnia-chain/streams' import { createPublicClient, http } from 'viem' import { somniaTestnet } from 'viem/chains' const publicClient = createPublicClient({ chain: somniaTestnet, transport: http(), }) export const sdk = new SDK(publicClient) This sets up the data-reading connection between your frontend and the Somnia testnet. Think of it as the Streams version of [readContract()arrow-up-right](https://viem.sh/docs/contract/readContract#readcontract) ; it lets you pull structured data (not just variables) directly from the blockchain. * * * [hashtag](https://docs.somnia.network/developer/data-streams/tutorials/read-stream-data-from-a-ui-next.js-example#define-schema-and-publisher) Define Schema and Publisher ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- A schema describes the structure of data stored in Streams, just like how a smart contract defines the structure of state variables. If you don’t have the schema ID handy, you can generate it from its definition: This ensures that you’re referencing the same schema ID under which the data was published. * * * [hashtag](https://docs.somnia.network/developer/data-streams/tutorials/read-stream-data-from-a-ui-next.js-example#fetch-latest-hello-world-message) Fetch Latest “Hello World” Message ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- This is the most common use case: getting the most recent data point. For example, displaying the latest sensor reading or chat message. This method retrieves the newest record from that schema-publisher combination. It’s useful when: * You’re showing a live dashboard * You need real-time data polling * You want to auto-refresh a view (e.g., “Last Updated at…”) * * * [hashtag](https://docs.somnia.network/developer/data-streams/tutorials/read-stream-data-from-a-ui-next.js-example#fetch-by-key-e.g.-message-id) Fetch by Key (e.g., message ID) ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ Each record can have a unique key, such as a message ID, sensor UUID, or user reference. When you know that key, you can fetch the exact record. When to use: * Fetching a message by its ID (e.g., “message #45a1”) * Retrieving a transaction or sensor entry when you know its hash * Building a detail view (e.g., /message/\[id\] route in Next.js) Think of it like calling readContract for one item by ID. * * * [hashtag](https://docs.somnia.network/developer/data-streams/tutorials/read-stream-data-from-a-ui-next.js-example#fetch-by-index-sequential-logs) Fetch by Index (Sequential Logs) --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- In sequential datasets such as logs, chat history, and telemetry, each record is indexed numerically. You can fetch a specific record by its position: When to use: * When looping through entries in order (0, 1, 2, ...) * To replay logs sequentially * To test pagination logic Example: getAtIndex(schemaId, publisher, 0n) retrieves the very first message. * * * [hashtag](https://docs.somnia.network/developer/data-streams/tutorials/read-stream-data-from-a-ui-next.js-example#fetch-a-range-of-records-paginated-view) Fetch a Range of Records (Paginated View) --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- You can fetch multiple entries at once using index ranges. This is perfect for pagination or time-series queries. Example Use Cases: * Displaying the last 10 chat messages: getBetweenRange(schemaId, publisher, 0n, 10n) * Loading older telemetry data * Implementing infinite scroll circle-info Tip: Treat start and end like array indices (inclusive start, exclusive end). `start` is inclusive and `end` is exclusive. * * * [hashtag](https://docs.somnia.network/developer/data-streams/tutorials/read-stream-data-from-a-ui-next.js-example#fetch-all-publisher-data-for-a-schema) Fetch All Publisher Data for a Schema --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- If you want to retrieve all content a publisher has ever posted to a given schema, use this. When to use: * Generating analytics or trend charts * Migrating or syncing full datasets * Debugging data integrity or history You can think of this as: “Give me the entire dataset under this schema from this publisher.” It’s the Streams equivalent of querying all events from a contract. circle-info This should be used for small data sets. For larger, paginated reading, `getBetweenRange` is recommended not to overwhelm the node returning the data. * * * [hashtag](https://docs.somnia.network/developer/data-streams/tutorials/read-stream-data-from-a-ui-next.js-example#count-total-entries) Count Total Entries --------------------------------------------------------------------------------------------------------------------------------------------------------------- Sometimes, you just want to know how many entries exist. When to use: * To know the total record count for pagination * To display dataset stats (“42 entries recorded”) * To monitor the growth of a stream This helps determine boundaries for getBetweenRange() or detect when new data arrives. * * * [hashtag](https://docs.somnia.network/developer/data-streams/tutorials/read-stream-data-from-a-ui-next.js-example#inspect-schema-metadata) Inspect Schema Metadata ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- Schemas define structure, and sometimes you’ll want to validate or inspect them before reading data. First, check that a schema exists when publishing a new schema: This is critical to ensure your app doesn’t attempt to query a non-existent or unregistered schema — useful for user-facing dashboards. * * * [hashtag](https://docs.somnia.network/developer/data-streams/tutorials/read-stream-data-from-a-ui-next.js-example#retrieve-full-schema-information) Retrieve Full Schema Information ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- This method retrieves both the base schema and its extended structure, if any. It automatically resolves inherited schemas, so you get the full picture of what fields exist. Example output: This is important when you’re visualizing or decoding raw stream data, you can use the schema structure to parse fields correctly (timestamp, string, address, etc.). * * * [hashtag](https://docs.somnia.network/developer/data-streams/tutorials/read-stream-data-from-a-ui-next.js-example#example-next.js-app) Example Next.js App --------------------------------------------------------------------------------------------------------------------------------------------------------------- Now let’s render our fetched data in the UI. ### [hashtag](https://docs.somnia.network/developer/data-streams/tutorials/read-stream-data-from-a-ui-next.js-example#project-setup) Project Setup * * * ### [hashtag](https://docs.somnia.network/developer/data-streams/tutorials/read-stream-data-from-a-ui-next.js-example#folder-structure) Folder Structure * * * ### [hashtag](https://docs.somnia.network/developer/data-streams/tutorials/read-stream-data-from-a-ui-next.js-example#lib-store.ts) lib/store.ts Sets up the Somnia SDK and connects to the testnet. * * * ### [hashtag](https://docs.somnia.network/developer/data-streams/tutorials/read-stream-data-from-a-ui-next.js-example#lib-schema.ts) lib/schema.ts Defines your schema and publisher. If you don’t know your schema ID yet, you can compute it later using: * * * ### [hashtag](https://docs.somnia.network/developer/data-streams/tutorials/read-stream-data-from-a-ui-next.js-example#lib-read.ts) lib/read.ts Implements read helpers for your API and UI. * * * ### [hashtag](https://docs.somnia.network/developer/data-streams/tutorials/read-stream-data-from-a-ui-next.js-example#app-api-latest-route.ts) app/api/latest/route.ts A serverless route to fetch the latest message (you can add more routes for range or schema info). * * * ### [hashtag](https://docs.somnia.network/developer/data-streams/tutorials/read-stream-data-from-a-ui-next.js-example#components-streamviewer.tsx) components/StreamViewer.tsx A live component with interactive buttons for fetching data. chevron-rightStreamViewer.tsx[hashtag](https://docs.somnia.network/developer/data-streams/tutorials/read-stream-data-from-a-ui-next.js-example#streamviewer.tsx) * * * ### [hashtag](https://docs.somnia.network/developer/data-streams/tutorials/read-stream-data-from-a-ui-next.js-example#components-schemainfo.tsx) components/SchemaInfo.tsx Displays the schema metadata. * * * ### [hashtag](https://docs.somnia.network/developer/data-streams/tutorials/read-stream-data-from-a-ui-next.js-example#app-page.tsx) app/page.tsx Main dashboard combining both components. * * * ### [hashtag](https://docs.somnia.network/developer/data-streams/tutorials/read-stream-data-from-a-ui-next.js-example#app-layout.tsx) app/layout.tsx Wraps the layout globally. * * * ### [hashtag](https://docs.somnia.network/developer/data-streams/tutorials/read-stream-data-from-a-ui-next.js-example#run-the-app) Run the App Visit http://localhost:3000 to open your dashboard. You’ll see a **“Fetch Latest Message”** button that retrieves data via `/api/latest` and a **Schema Info** section (ready to expand) * * * [hashtag](https://docs.somnia.network/developer/data-streams/tutorials/read-stream-data-from-a-ui-next.js-example#summary-table) Summary Table --------------------------------------------------------------------------------------------------------------------------------------------------- Method Purpose Example getByKey Fetch a record by unique ID getByKey(schemaId, publisher, dataId) getAtIndex Fetch record at position getAtIndex(schemaId, publisher, 0n) getBetweenRange Retrieve records in range getBetweenRange(schemaId, publisher, 0n, 10n) getAllPublisherDataForSchema Fetch all data by publisher getAllPublisherDataForSchema(schemaRef, publisher) getLastPublishedDataForSchema Latest record only getLastPublishedDataForSchema(schemaId, publisher) totalPublisherDataForSchema Count of entries totalPublisherDataForSchema(schemaId, publisher) isDataSchemaRegistered Check if schema exists isDataSchemaRegistered(schemaId) schemaIdToId / idToSchemaId Convert between Hex and readable Useful for UI & schema mapping getSchemaFromSchemaId Inspect full schema definition Retrieves base + extended schema [PreviousStreams Case Study: Formula 1chevron-left](https://docs.somnia.network/developer/data-streams/tutorials/streams-case-study-formula-1) [NextIntegrate Chainlink Oracleschevron-right](https://docs.somnia.network/developer/data-streams/tutorials/integrate-chainlink-oracles) Last updated 1 month ago * [Prerequisites](https://docs.somnia.network/developer/data-streams/tutorials/read-stream-data-from-a-ui-next.js-example#prerequisites) * [Set up the SDK and Client](https://docs.somnia.network/developer/data-streams/tutorials/read-stream-data-from-a-ui-next.js-example#set-up-the-sdk-and-client) * [Define Schema and Publisher](https://docs.somnia.network/developer/data-streams/tutorials/read-stream-data-from-a-ui-next.js-example#define-schema-and-publisher) * [Fetch Latest “Hello World” Message](https://docs.somnia.network/developer/data-streams/tutorials/read-stream-data-from-a-ui-next.js-example#fetch-latest-hello-world-message) * [Fetch by Key (e.g., message ID)](https://docs.somnia.network/developer/data-streams/tutorials/read-stream-data-from-a-ui-next.js-example#fetch-by-key-e.g.-message-id) * [Fetch by Index (Sequential Logs)](https://docs.somnia.network/developer/data-streams/tutorials/read-stream-data-from-a-ui-next.js-example#fetch-by-index-sequential-logs) * [Fetch a Range of Records (Paginated View)](https://docs.somnia.network/developer/data-streams/tutorials/read-stream-data-from-a-ui-next.js-example#fetch-a-range-of-records-paginated-view) * [Fetch All Publisher Data for a Schema](https://docs.somnia.network/developer/data-streams/tutorials/read-stream-data-from-a-ui-next.js-example#fetch-all-publisher-data-for-a-schema) * [Count Total Entries](https://docs.somnia.network/developer/data-streams/tutorials/read-stream-data-from-a-ui-next.js-example#count-total-entries) * [Inspect Schema Metadata](https://docs.somnia.network/developer/data-streams/tutorials/read-stream-data-from-a-ui-next.js-example#inspect-schema-metadata) * [Retrieve Full Schema Information](https://docs.somnia.network/developer/data-streams/tutorials/read-stream-data-from-a-ui-next.js-example#retrieve-full-schema-information) * [Example Next.js App](https://docs.somnia.network/developer/data-streams/tutorials/read-stream-data-from-a-ui-next.js-example#example-next.js-app) * [Project Setup](https://docs.somnia.network/developer/data-streams/tutorials/read-stream-data-from-a-ui-next.js-example#project-setup) * [Folder Structure](https://docs.somnia.network/developer/data-streams/tutorials/read-stream-data-from-a-ui-next.js-example#folder-structure) * [lib/store.ts](https://docs.somnia.network/developer/data-streams/tutorials/read-stream-data-from-a-ui-next.js-example#lib-store.ts) * [lib/schema.ts](https://docs.somnia.network/developer/data-streams/tutorials/read-stream-data-from-a-ui-next.js-example#lib-schema.ts) * [lib/read.ts](https://docs.somnia.network/developer/data-streams/tutorials/read-stream-data-from-a-ui-next.js-example#lib-read.ts) * [app/api/latest/route.ts](https://docs.somnia.network/developer/data-streams/tutorials/read-stream-data-from-a-ui-next.js-example#app-api-latest-route.ts) * [components/StreamViewer.tsx](https://docs.somnia.network/developer/data-streams/tutorials/read-stream-data-from-a-ui-next.js-example#components-streamviewer.tsx) * [components/SchemaInfo.tsx](https://docs.somnia.network/developer/data-streams/tutorials/read-stream-data-from-a-ui-next.js-example#components-schemainfo.tsx) * [app/page.tsx](https://docs.somnia.network/developer/data-streams/tutorials/read-stream-data-from-a-ui-next.js-example#app-page.tsx) * [app/layout.tsx](https://docs.somnia.network/developer/data-streams/tutorials/read-stream-data-from-a-ui-next.js-example#app-layout.tsx) * [Run the App](https://docs.somnia.network/developer/data-streams/tutorials/read-stream-data-from-a-ui-next.js-example#run-the-app) * [Summary Table](https://docs.somnia.network/developer/data-streams/tutorials/read-stream-data-from-a-ui-next.js-example#summary-table) Copy // lib/schema.ts export const helloWorldSchema = 'uint64 timestamp, string message' export const schemaId = '0xabc123...' // Example Schema ID export const publisher = '0xF9D3...E5aC' // Example Publisher Address Copy const computedId = await sdk.streams.computeSchemaId(helloWorldSchema) console.log('Computed Schema ID:', computedId) Copy // lib/read.ts import { sdk } from './store' import { schemaId, publisher } from './schema' export async function getLatestMessage() { const latest = await sdk.streams.getLastPublishedDataForSchema(schemaId, publisher) console.log('Latest data:', latest) return latest } Copy export async function getMessageById(messageKey: `0x${string}`) { const msg = await sdk.streams.getByKey(schemaId, publisher, messageKey) console.log('Message by key:', msg) return msg } Copy export async function getMessageAtIndex(index: bigint) { const record = await sdk.streams.getAtIndex(schemaId, publisher, index) console.log(`Record at index ${index}:`, record) return record } Copy export async function getMessagesInRange(start: bigint, end: bigint) { const records = await sdk.streams.getBetweenRange(schemaId, publisher, start, end) console.log('Records in range:', records) return records } Copy export async function getAllPublisherData() { const allData = await sdk.streams.getAllPublisherDataForSchema(schemaId, publisher) console.log('All publisher data:', allData) return allData } Copy export async function getTotalEntries() { const total = await sdk.streams.totalPublisherDataForSchema(schemaId, publisher) console.log(`Total entries: ${total}`) return Number(total) } Copy const ignoreAlreadyRegistered = true try { const txHash = await sdk.streams.registerDataSchemas( [\ {\ schemaName: 'hello_world',\ schema: helloSchema,\ parentSchemaId: zeroBytes32\ },\ ], ignoreAlreadyRegistered ) if (txHash) { await waitForTransactionReceipt(publicClient, { hash: txHash }) console.log(`Schema registered or confirmed, Tx: ${txHash}`) } else { console.log('Schema already registered — no action required.') } } catch (err) { // fallback: if the SDK doesn’t support the flag yet if (String(err).includes('SchemaAlreadyRegistered')) { console.log('Schema already registered. Continuing...') } else { throw err } } Copy const schemaInfo = await sdk.streams.getSchemaFromSchemaId(schemaId) console.log('Schema Info:', schemaInfo) Copy { baseSchema: 'uint64 timestamp, string message', finalSchema: 'uint64 timestamp, string message', schemaId: '0xabc123...' } Copy npx create-next-app somnia-streams-reader --typescript cd somnia-streams-reader npm install @somnia-chain/streams viem npm install -D tailwindcss postcss autoprefixer npx tailwindcss init -p Copy somnia-streams-reader/ ├── app/ │ ├── api/ │ │ └── latest/route.ts │ ├── page.tsx │ ├── layout.tsx │ └── globals.css ├── components/ │ ├── StreamViewer.tsx │ └── SchemaInfo.tsx ├── lib/ │ ├── store.ts │ ├── schema.ts │ └── read.ts ├── tailwind.config.js └── package.json Copy import { SDK } from "@somnia-chain/streams" import { createPublicClient, http } from "viem" import { somniaTestnet } from "viem/chains" const publicClient = createPublicClient({ chain: somniaTestnet, transport: http(), }) export const sdk = new SDK(publicClient) Copy export const helloWorldSchema = "uint64 timestamp, string message" export const schemaId = "0xabc123..." // replace with actual schemaId export const publisher = "0xF9D3...E5aC" // replace with actual publisher Copy const computed = await sdk.streams.computeSchemaId(helloWorldSchema) console.log("Schema ID:", computed) Copy import { sdk } from "./store" import { schemaId, publisher } from "./schema" export async function getLatestMessage() { return await sdk.streams.getLastPublishedDataForSchema(schemaId, publisher) } export async function getMessagesInRange(start: bigint, end: bigint) { return await sdk.streams.getBetweenRange(schemaId, publisher, start, end) } export async function getSchemaInfo() { return await sdk.streams.getSchemaFromSchemaId(schemaId) } Copy import { NextResponse } from "next/server" import { getLatestMessage } from "@/lib/read" export async function GET() { const data = await getLatestMessage() return NextResponse.json({ data }) } Copy "use client" import { useState } from "react" export default function StreamViewer() { const [data, setData] = useState(null) const [loading, setLoading] = useState(false) const fetchLatest = async () => { setLoading(true) try { const res = await fetch("/api/latest") const { data } = await res.json() setData(data) } catch (err) { console.error(err) } finally { setLoading(false) } } return (

HelloWorld Stream Reader

{data && (
              {JSON.stringify(data, null, 2)}
            
)}
) } Copy "use client" import { useState } from "react" export default function SchemaInfo() { const [info, setInfo] = useState(null) const fetchInfo = async () => { const res = await fetch("/api/latest") // just for demo; replace with /api/schema if separate route const { data } = await res.json() setInfo(data) } return (

Schema Information

{info && (
              {JSON.stringify(info, null, 2)}
            
)}
) } Copy import StreamViewer from "@/components/StreamViewer" import SchemaInfo from "@/components/SchemaInfo" export default function Home() { return (

🛰️ Somnia Data Streams Reader

) } Copy import "./globals.css" export const metadata = { title: "Somnia Streams Reader", description: "Read on-chain data from Somnia Data Streams", } export default function RootLayout({ children, }: { children: React.ReactNode }) { return ( {children} ) } Copy npm run dev --- # DAO UI Tutorial p2 | Somnia Docs This guide will focus exclusively on implementing Read Operations, which fetches data from your deployed [DAO Smart Contract](https://docs.somnia.network/developer/building-dapps/example-applications/dao-smart-contract) . By the end of this article, you’ll be able to: 1. Understand how to read data from your smart contract using [viem](https://docs.somnia.network/developer/development-frameworks/using-the-viem-library) . 2. Implement functions to fetch the total number of proposals and specific proposal details. 3. Integrate these READ operations into your Next.js pages to display dynamic data. Prerequisite: Ensure you’ve completed [Part 1](https://docs.somnia.network/developer/building-dapps/example-applications/dao-ui-tutorial-p1) of this series, where you initialized a Next.js project, set up a `**WalletContext**` for global state management, and added a global `**NavBar**`. * * * [hashtag](https://docs.somnia.network/developer/building-dapps/example-applications/dao-ui-tutorial-p2#understand-read-operations) Understand READ Operations ------------------------------------------------------------------------------------------------------------------------------------------------------------------ In decentralized applications (dApps), Read Operations involve fetching data from the blockchain without altering its state. In the example DAO Smart Contract, this is crucial for displaying dynamic information such as: * **Total Number of Proposals**: How many proposals have been created. * **Proposal Details**: Information about a specific proposal, including its description, votes, and execution status. These operations are read-only and do not require the user to sign any transactions, making them free of gas costs. We’ll use the viem library to interact with our smart contract and perform these READ operations. * * * [hashtag](https://docs.somnia.network/developer/building-dapps/example-applications/dao-ui-tutorial-p2#expand-walletcontext.js-for-read-operations) Expand walletcontext.js for Read Operations ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- `**walletcontext.js**` is the central hub for managing wallet connections and interacting with your Smart Contract. We’ll add two primary READ functions: 1. `**fetchTotalProposals()**`: Retrieves the total number of proposals created. 2. `**fetchProposal(proposalId)**`: Fetches details of a specific proposal by its ID. ### [hashtag](https://docs.somnia.network/developer/building-dapps/example-applications/dao-ui-tutorial-p2#fetch-total-proposals) Fetch Total Proposals Functionality: This function calls the `**totalProposals**` method in your smart contract to determine how many proposals have been created so far. chevron-rightcontexts/walletcontext.js[hashtag](https://docs.somnia.network/developer/building-dapps/example-applications/dao-ui-tutorial-p2#contexts-walletcontext.js) `**fetchTotalProposals()**` uses `**publicClient.readContract**` to call the `**totalProposals**` function in the Smart Contract. This function returns a `BigInt` representing the total number of proposals. `**fetchProposal(proposalId)**` calls the proposals mapping in your contract to retrieve details of a specific proposal by its `ID`. It returns a struct containing the proposal's **description**, **deadline**, **votes**, **execution status**, and **proposer**. * * * [hashtag](https://docs.somnia.network/developer/building-dapps/example-applications/dao-ui-tutorial-p2#integrate-read-operations-into-pages) Integrate Read Operations into Pages -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- With the read functions in place, let’s integrate them into Next.js pages to display dynamic data. ### [hashtag](https://docs.somnia.network/developer/building-dapps/example-applications/dao-ui-tutorial-p2#home-page) Home Page Update the `index.js` page to show the total number of proposals created in your DAO on the home page. chevron-rightpages/index.js[hashtag](https://docs.somnia.network/developer/building-dapps/example-applications/dao-ui-tutorial-p2#pages-index.js) Here we set the `totalProposals` state variable to store the fetched total number of proposals. The `**ConnectButton**` implements the MetaMask authentication. The useWallet hook parse the function from `**WalletContext.**` The `**useEffect**` Hook is applied on component mount, `**fetchTotalProposals()**` is then called to retrieve the total number of proposals from the Smart Contract. The page displays a loading message until totalProposals is fetched. Once fetched, it displays the total number of proposals. Users will have to click the **ConnectButton** to connect their wallets for WRITE operations. See part 3. * * * ### [hashtag](https://docs.somnia.network/developer/building-dapps/example-applications/dao-ui-tutorial-p2#fetch-proposal-page) Fetch-Proposal Page This page allow users to input a proposal ID, fetch its details, and display them. Additionally, on the page users are provided options for voting on or executing the proposal. Implementation: chevron-rightpages/fetch-proposal.js[hashtag](https://docs.somnia.network/developer/building-dapps/example-applications/dao-ui-tutorial-p2#pages-fetch-proposal.js) The following React states are implemented * `proposalId`: Stores the user-inputted proposal ID. * `proposalData`: Stores the fetched proposal details. * `error`: Captures any errors during fetch, vote, or execute operations. The `**handleSubmit**` function is used to validate the input and connection status. It then calls the `**fetchProposal(proposalId)**` to retrieve proposal details. We use a **Form** element for users to input a proposal ID and fetch its details. The **Error** Display is implemented to show any errors that occur during operations. The **Proposal Details** displays the fetched proposal information in a styled card. The card contains **Vote** and **Execute b**uttons for users to vote **YES/NO** or execute the proposal if eligible. * * * [hashtag](https://docs.somnia.network/developer/building-dapps/example-applications/dao-ui-tutorial-p2#edge-cases-and-errors) Edge Cases and Errors -------------------------------------------------------------------------------------------------------------------------------------------------------- For better UX, consider adding loading indicators while fetching data or awaiting transaction confirmations. Example: chevron-rightpages/fetch-proposal.js[hashtag](https://docs.somnia.network/developer/building-dapps/example-applications/dao-ui-tutorial-p2#pages-fetch-proposal.js-1) * * * [hashtag](https://docs.somnia.network/developer/building-dapps/example-applications/dao-ui-tutorial-p2#test-read-operations) Test Read Operations ------------------------------------------------------------------------------------------------------------------------------------------------------ ### [hashtag](https://docs.somnia.network/developer/building-dapps/example-applications/dao-ui-tutorial-p2#populate-some-data) Populate Some Data Before testing read operations, make sure there are some proposals created: 1. Load your Smart Contract on the [Remix IDE](https://docs.somnia.network/developer/building-dapps/tokens-and-nfts/create-erc20-tokens) . 2. Deposit 0.001 ETH to gain voting power. 3. Create one or more proposals via the Create Proposal page. ### [hashtag](https://docs.somnia.network/developer/building-dapps/example-applications/dao-ui-tutorial-p2#verify-read-operations) Verify Read Operations Run your application using the command: Your application will be running on `**localhost:3000**` in your web browser. Check for the following in the User Interface: 1. Total Proposals: On the Home page, verify that the total number of proposals matches the number you’ve created via Remix IDE. 2. Fetch Proposal Details: - Navigate to the Fetch-Proposal page. - Input a valid proposalId (e.g., 0 for the first proposal). - Verify that all proposal details are accurately displayed. Monitor the browser console for any errors or logs that can help in debugging. * * * [hashtag](https://docs.somnia.network/developer/building-dapps/example-applications/dao-ui-tutorial-p2#conclusion-and-next-steps) Conclusion and Next Steps ---------------------------------------------------------------------------------------------------------------------------------------------------------------- In Part 2, you successfully implemented Read Operations in your DAO front end: * `**fetchTotalProposals()**`: Displayed the total number of proposals on the Home page. * **fetchProposal(proposalId)**: Retrieved and displayed specific proposal details on the Fetch-Proposal page. #### [hashtag](https://docs.somnia.network/developer/building-dapps/example-applications/dao-ui-tutorial-p2#whats-next) What's Next? Stay tuned for Part 3 of this series, where we’ll dive into building UI Components—crafting forms, buttons, and enhancing event handling to create a more polished and user-friendly interface for your DAO dApp. * * * Congratulations! You’ve now built a robust foundation for reading data from your DAO smart contract within your Next.js front end. Keep experimenting and enhancing your dApp’s capabilities in the upcoming sections! [PreviousDAO UI Tutorial p1chevron-left](https://docs.somnia.network/developer/building-dapps/example-applications/dao-ui-tutorial-p1) [NextDAO UI Tutorial p3chevron-right](https://docs.somnia.network/developer/building-dapps/example-applications/dao-ui-tutorial-p3) Last updated 1 month ago * [Understand READ Operations](https://docs.somnia.network/developer/building-dapps/example-applications/dao-ui-tutorial-p2#understand-read-operations) * [Expand walletcontext.js for Read Operations](https://docs.somnia.network/developer/building-dapps/example-applications/dao-ui-tutorial-p2#expand-walletcontext.js-for-read-operations) * [Fetch Total Proposals](https://docs.somnia.network/developer/building-dapps/example-applications/dao-ui-tutorial-p2#fetch-total-proposals) * [Integrate Read Operations into Pages](https://docs.somnia.network/developer/building-dapps/example-applications/dao-ui-tutorial-p2#integrate-read-operations-into-pages) * [Home Page](https://docs.somnia.network/developer/building-dapps/example-applications/dao-ui-tutorial-p2#home-page) * [Fetch-Proposal Page](https://docs.somnia.network/developer/building-dapps/example-applications/dao-ui-tutorial-p2#fetch-proposal-page) * [Edge Cases and Errors](https://docs.somnia.network/developer/building-dapps/example-applications/dao-ui-tutorial-p2#edge-cases-and-errors) * [Test Read Operations](https://docs.somnia.network/developer/building-dapps/example-applications/dao-ui-tutorial-p2#test-read-operations) * [Populate Some Data](https://docs.somnia.network/developer/building-dapps/example-applications/dao-ui-tutorial-p2#populate-some-data) * [Verify Read Operations](https://docs.somnia.network/developer/building-dapps/example-applications/dao-ui-tutorial-p2#verify-read-operations) * [Conclusion and Next Steps](https://docs.somnia.network/developer/building-dapps/example-applications/dao-ui-tutorial-p2#conclusion-and-next-steps) Copy import { createContext, useContext, useState } from "react"; import { defineChain, createPublicClient, createWalletClient, http, custom, parseEther, } from "viem"; import { ABI } from "../../abi"; // Adjust the path as necessary // Define Somnia Chain const SOMNIA = defineChain({ id: 50312, name: "Somnia Testnet", nativeCurrency: { decimals: 18, name: "Ether", symbol: "STT", }, rpcUrls: { default: { http: ["https://dream-rpc.somnia.network"], }, }, blockExplorers: { default: { name: "Explorer", url: "https://somnia-devnet.socialscan.io" }, }, }); // Create a public client for read operations const publicClient = createPublicClient({ chain: SOMNIA, transport: http(), }); const WalletContext = createContext(); export function WalletProvider({ children }) { // ---------- STATE ------------ const [connected, setConnected] = useState(false); const [address, setAddress] = useState(""); const [client, setClient] = useState(null); // Fetch Total Proposals async function fetchTotalProposals() { try { const result = await publicClient.readContract({ address: "0x7be249A360DB86E2Cf538A6893f37aFd89C70Ab4", abi: ABI, functionName: "totalProposals", }); return result; // Returns a BigInt } catch (error) { console.error("Error fetching totalProposals:", error); throw error; } } // Fetch Proposal Details async function fetchProposal(proposalId) { try { const result = await publicClient.readContract({ address: "0x7be249A360DB86E2Cf538A6893f37aFd89C70Ab4", abi: ABI, functionName: "proposals", args: [parseInt(proposalId)], }); console.log(result); return result; // Returns the Proposal struct } catch (error) { console.error("Error fetching proposal:", error); throw error; } } // Provider's value return ( {children} ); } // Custom hook to consume context export function useWallet() { return useContext(WalletContext); } Copy import { useState, useEffect } from "react"; import ConnectButton from "../components/connectbutton"; import { useWallet } from "../contexts/walletcontext"; export default function Home() { const { fetchTotalProposals } = useWallet(); const [totalProposals, setTotalProposals] = useState(null); useEffect(() => { async function loadData() { try { const count = await fetchTotalProposals(); setTotalProposals(count); } catch (error) { console.error("Failed to fetch total proposals:", error); } } loadData(); }, [fetchTotalProposals]); return (
{/* The NavBar is already rendered in _app.js */}

Welcome to MyDAO

{totalProposals !== null ? (

Total proposals created: {totalProposals.toString()}

) : (

Loading total proposals...

)}
); } Copy import { useState, useEffect } from "react"; import { useRouter } from "next/router"; import { useWallet } from "../contexts/walletcontext"; import { Button, Card, Label, TextInput } from "flowbite-react"; // Optional Flowbite imports export default function FetchProposalPage() { const [proposalId, setProposalId] = useState(""); const [proposalData, setProposalData] = useState(null); const [error, setError] = useState(""); const { connected, fetchProposal, voteOnProposal, executeProposal } = useWallet(); const handleSubmit = async (e) => { e.preventDefault(); setError(""); // Clear previous errors if (!connected) { alert("You must connect your wallet first!"); return; } if (!proposalId.trim()) { setError("Please enter a proposal ID."); return; } try { // Fetch the proposal from the contract const result = await fetchProposal(proposalId); console.log("Fetched Proposal:", result); setProposalData(result); } catch (err) { console.error("Error fetching proposal:", err); setError("Failed to fetch proposal. Check console for details."); } }; useEffect(() => { if (proposalData !== null) { console.log("Updated Proposal Data:", proposalData); } }, [proposalData]); return (

Fetch a Proposal

{/* Form to input Proposal ID */}
{/* Display Errors */} {error &&
{error}
} {/* Display Proposal Details */} {proposalData && (

Proposal #{proposalId}

  • Description: {proposalData[0]}
  • Deadline: {new Date(proposalData[1] * 1000).toLocaleString()}
  • Yes Votes: {proposalData[2].toString()}
  • No Votes: {proposalData[3].toString()}
  • Executed: {proposalData[4] ? "Yes" : "No"}
  • Proposer: {proposalData[5]}
)} ); } Copy const [loading, setLoading] = useState(false); // In handleSubmit const handleSubmit = async (e) => { e.preventDefault(); setError(""); setLoading(true); // ... rest of the code setLoading(false); }; // In the button Copy npm run dev --- # Ecosystem Tools | Somnia Docs Somnia is built with a robust ecosystem of infrastructure providers that enable developers, projects, and businesses to integrate seamlessly with the network. With Somnia's ecosystem of infrastructure partners, developers have access to scalable RPCs, smart contract tools, oracles, account abstraction, identity solutions, and analytics. Each partner provides critical tooling, APIs, and services to power decentralized applications (dApps) on Somnia. Below is an overview of our key Infrastructure Partners and resources to get started. circle-check **Developers who are deploying Smart Contracts and need Somnia Test Tokens, STT. Please join the** [**Discord**arrow-up-right](https://discord.com/invite/somnia) **. Go to the #dev-chat channel, tag the Somnia DevRel,** `**@emma_odia**`**and request Test Tokens. You can also email** `**[[email protected]](https://docs.somnia.network/cdn-cgi/l/email-protection) **` **with a brief description of what you are building and your GitHub profile.** Service Provider [RPC](https://docs.somnia.network/developer/deployment-and-production/ecosystem/ecosystem-tools/rpc) Ankr, Public Node, Stakely, Validation Cloud [Oracles](https://docs.somnia.network/developer/deployment-and-production/ecosystem/ecosystem-tools/oracles) DIA [Subgraphs](https://docs.somnia.network/developer/deployment-and-production/ecosystem/ecosystem-tools/subgraphs) Protofire, Ormi [Wallet Provider](https://docs.somnia.network/developer/deployment-and-production/ecosystem/ecosystem-tools/wallet-providers) Privy, Thirdweb [Safes](https://docs.somnia.network/developer/deployment-and-production/ecosystem/ecosystem-tools/safes) Palmera DAO [Explorers](https://docs.somnia.network/developer/deployment-and-production/ecosystem/ecosystem-tools/explorers) Block Scout [SDKs](https://docs.somnia.network/developer/deployment-and-production/ecosystem/ecosystem-tools/sdks) Sequence, Thirdweb [On Ramps](https://docs.somnia.network/developer/deployment-and-production/ecosystem/ecosystem-tools/on-ramps) Banxa [APIs](https://docs.somnia.network/developer/deployment-and-production/ecosystem/ecosystem-tools/apis) Ormi [Account Abstraction](https://docs.somnia.network/developer/deployment-and-production/ecosystem/ecosystem-tools/account-abstraction) Pimlico [PreviousTokoschevron-left](https://docs.somnia.network/developer/deployment-and-production/ecosystem/ecosystem-showcase/tokos) [NextRPCchevron-right](https://docs.somnia.network/developer/deployment-and-production/ecosystem/ecosystem-tools/rpc) Last updated 1 month ago --- # Using Native SOMI/STT | Somnia Docs SOMI is the native token of the Somnia Network, similar to ETH on Ethereum. Unlike ERC20 tokens, SOMI is built into the protocol itself and does not have a contract address. circle-exclamation Kindly note that the Native Token for Somnia Testnet is STT. This multi-part guide shows how to use SOMI for: * Payments * Escrow * Donations & Tipping * Sponsored gas via Account Abstraction [hashtag](https://docs.somnia.network/developer/building-dapps/tokens-and-nfts/using-native-somi-stt#use-somi-for-payments-in-smart-contracts) Use SOMI for Payments in Smart Contracts -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- A simple contract that accepts exact SOMI payments: Use `msg.value` to access the native token sent in a transaction. No ERC20 functions are needed. To withdraw collected SOMI: Deploy using Hardhat Ignition or Viem and test with a `sendTransaction` call. chevron-rightExample.sol[hashtag](https://docs.somnia.network/developer/building-dapps/tokens-and-nfts/using-native-somi-stt#example.sol) [hashtag](https://docs.somnia.network/developer/building-dapps/tokens-and-nfts/using-native-somi-stt#build-an-somi-escrow-contract) Build an SOMI Escrow Contract ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- A secure escrow contract allows a buyer to deposit SOMI and later release or refund: Release funds to the seller: Deploy with Hardhat Ignition and pass SOMI as `value` during deployment. chevron-rightExample.sol[hashtag](https://docs.somnia.network/developer/building-dapps/tokens-and-nfts/using-native-somi-stt#example.sol-1) [hashtag](https://docs.somnia.network/developer/building-dapps/tokens-and-nfts/using-native-somi-stt#somi-tip-jar) SOMI Tip Jar ------------------------------------------------------------------------------------------------------------------------------------ Allow any wallet to send tips directly: Withdraw all tips: chevron-rightExample.sol[hashtag](https://docs.somnia.network/developer/building-dapps/tokens-and-nfts/using-native-somi-stt#example.sol-2) [hashtag](https://docs.somnia.network/developer/building-dapps/tokens-and-nfts/using-native-somi-stt#frontend-tip) Frontend tip ------------------------------------------------------------------------------------------------------------------------------------ [hashtag](https://docs.somnia.network/developer/building-dapps/tokens-and-nfts/using-native-somi-stt#sponsor-somi-transactions-with-account-abstraction) Sponsor SOMI Transactions with Account Abstraction ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- Using a smart account + relayer (e.g. via Privy, Thirdweb), the dApp can cover gas fees: The Smart Contract function can execute mint or other logic as usual. The paymaster or relayer pays SOMI. [hashtag](https://docs.somnia.network/developer/building-dapps/tokens-and-nfts/using-native-somi-stt#conclusion) Conclusion -------------------------------------------------------------------------------------------------------------------------------- * **SOMI is native** and used via `msg.value`, `.transfer()`, and `payable`. * **There is no contract address for SOMI (STT for Testnet)**. * You can integrate SOMI into any Solidity or Viem app with no ERC20 logic. * Account Abstraction enables gasless dApps using SOMI as sponsor currency. [PreviousManaging NFT Metadata with IPFSchevron-left](https://docs.somnia.network/developer/building-dapps/tokens-and-nfts/managing-nft-metadata-with-ipfs) [NextWallet Integration and Authchevron-right](https://docs.somnia.network/developer/building-dapps/wallet-integration-and-auth) Last updated 5 months ago * [Use SOMI for Payments in Smart Contracts](https://docs.somnia.network/developer/building-dapps/tokens-and-nfts/using-native-somi-stt#use-somi-for-payments-in-smart-contracts) * [Build an SOMI Escrow Contract](https://docs.somnia.network/developer/building-dapps/tokens-and-nfts/using-native-somi-stt#build-an-somi-escrow-contract) * [SOMI Tip Jar](https://docs.somnia.network/developer/building-dapps/tokens-and-nfts/using-native-somi-stt#somi-tip-jar) * [Frontend tip](https://docs.somnia.network/developer/building-dapps/tokens-and-nfts/using-native-somi-stt#frontend-tip) * [Sponsor SOMI Transactions with Account Abstraction](https://docs.somnia.network/developer/building-dapps/tokens-and-nfts/using-native-somi-stt#sponsor-somi-transactions-with-account-abstraction) * [Conclusion](https://docs.somnia.network/developer/building-dapps/tokens-and-nfts/using-native-somi-stt#conclusion) Copy function payToAccess() external payable { require(msg.value == 0.01 ether, "Must send exactly 0.01 SOMI"); } Copy function withdraw() external onlyOwner { payable(owner).transfer(address(this).balance); } Copy // SPDX-License-Identifier: MIT pragma solidity ^0.8.20; contract SOMIPayment { address public owner; constructor() { owner = msg.sender; } // Modifier to restrict access to the contract owner modifier onlyOwner() { require(msg.sender == owner, "Only owner can call this"); _; } // User must send exactly 0.01 SOMI to access this feature function payToAccess() external payable { require(msg.value == 0.01 ether, "Must send exactly 0.01 SOMI"); // Logic for access: mint token, grant download, emit event, etc. } // Withdraw collected SOMI to owner function withdraw() external onlyOwner { payable(owner).transfer(address(this).balance); } } Copy constructor(address payable _seller) payable { buyer = msg.sender; seller = _seller; amount = msg.value; } Copy function release() external onlyBuyer { seller.transfer(amount); } Copy // SPDX-License-Identifier: MIT pragma solidity ^0.8.20; contract SOMIEscrow { address public buyer; address payable public seller; uint256 public amount; bool public isDeposited; constructor(address payable _seller) payable { buyer = msg.sender; seller = _seller; amount = msg.value; require(amount > 0, "Must deposit SOMI"); isDeposited = true; } modifier onlyBuyer() { require(msg.sender == buyer, "Only buyer can call this"); _; } function release() external onlyBuyer { require(isDeposited, "No funds to release"); isDeposited = false; seller.transfer(amount); } function refund() external onlyBuyer { require(isDeposited, "No funds to refund"); isDeposited = false; payable(buyer).transfer(amount); } } Copy receive() external payable { emit Tipped(msg.sender, msg.value); } Copy function withdraw() external onlyOwner { payable(owner).transfer(address(this).balance); } Copy // SPDX-License-Identifier: MIT pragma solidity ^0.8.20; contract SOMITipJar { address public owner; event Tipped(address indexed from, uint256 amount); event Withdrawn(address indexed to, uint256 amount); constructor() { owner = msg.sender; } receive() external payable { emit Tipped(msg.sender, msg.value); } function withdraw() external { require(msg.sender == owner, "Only owner can withdraw"); uint256 balance = address(this).balance; require(balance > 0, "No tips available"); payable(owner).transfer(balance); emit Withdrawn(owner, balance); } } Copy await walletClient.sendTransaction({ to: '0xTipJarAddress', value: parseEther('0.05'), }); Copy await sendTransaction({ to: contractAddress, data: mintFunctionEncoded, value: 0n, // user sends no SOMI }); --- # Safes | Somnia Docs [hashtag](https://docs.somnia.network/developer/deployment-and-production/ecosystem/ecosystem-tools/safes#palmera-dao) [Palmera DAOarrow-up-right](https://www.palmeradao.xyz/) ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- Palmera is a multi-safe and multi-chain treasury management platform designed to simplify on-chain financial operations. It offers a unified dashboard for managing multiple Safes across various supported chains, enhancing financial transparency and control for DAOs and organizations. ### [hashtag](https://docs.somnia.network/developer/deployment-and-production/ecosystem/ecosystem-tools/safes#resources) Resources * [Documentationarrow-up-right](https://docs.palmeradao.xyz/palmera) [PreviousWallet Providerschevron-left](https://docs.somnia.network/developer/deployment-and-production/ecosystem/ecosystem-tools/wallet-providers) [NextExplorerschevron-right](https://docs.somnia.network/developer/deployment-and-production/ecosystem/ecosystem-tools/explorers) Last updated 12 months ago * [Palmera DAO](https://docs.somnia.network/developer/deployment-and-production/ecosystem/ecosystem-tools/safes#palmera-dao) * [Resources](https://docs.somnia.network/developer/deployment-and-production/ecosystem/ecosystem-tools/safes#resources) --- # Debug Playbook | Somnia Docs [hashtag](https://docs.somnia.network/developer/development-frameworks/debug-playbook#id-1.-revert-decoding) 1\. Revert Decoding ------------------------------------------------------------------------------------------------------------------------------------- Debugging smart contracts on the **Somnia Network** requires an understanding of how and why transactions fail. Every reverted transaction carries encoded data that can reveal the root cause of failure, whether it’s due to logic errors, insufficient gas, failed access checks, or internal Solidity panics. ### [hashtag](https://docs.somnia.network/developer/development-frameworks/debug-playbook#id-1.1-anatomy-of-a-revert) 1.1 Anatomy of a Revert When a transaction fails on Somnia, the EVM halts execution and returns **revert data**, an ABI-encoded payload that follows one of these three formats: Type Selector Description Example `Error(string)` `0x08c379a0` Standard revert reason with message `require(balance > 0, "Zero balance");` `Panic(uint256)` `0x4e487b71` Internal error (e.g., overflow, div/0, invalid enum) `assert(x > 0);` Custom Errors Function selector of custom error `error Unauthorized(address caller);` On Somnia, these behave identically to Ethereum but may differ in **gas costs** and **stack trace length** depending on the validator node configuration. ### [hashtag](https://docs.somnia.network/developer/development-frameworks/debug-playbook#id-1.2-catching-and-displaying-reverts-in-hardhat) 1.2 Catching and Displaying Reverts in Hardhat When running tests or scripts on Somnia, wrap calls in `try/catch` to capture the revert reason. example.ts Copy try { await treasury.withdraw(1000); } catch (error: any) { console.log('Revert reason:', error.reason || error.message); console.log('Full error data:', error.data || error.error?.data); } In **Chai matchers**: If your contract uses **custom errors**, Hardhat will not automatically print the name. Decode it manually: ### [hashtag](https://docs.somnia.network/developer/development-frameworks/debug-playbook#id-1.3-decoding-panic-codes) 1.3 Decoding Panic Codes Internal Solidity panics correspond to low-level EVM exceptions. Somnia propagates these codes like any other EVM chain. Panic Code Description Typical Cause `0x01` Assertion failed Logic invariant broken `0x11` Arithmetic overflow/underflow Unchecked math operation `0x12` Division by zero Incorrect math division `0x21` Invalid enum conversion Out-of-bounds value `0x31` Storage array index out of bounds Bad loop or mapping access `0x32` Memory array index out of bounds Corrupt array operation To detect Panic errors dynamically: ### [hashtag](https://docs.somnia.network/developer/development-frameworks/debug-playbook#id-1.4-advanced-revert-inspection-with-hardhat-traces) 1.4 Advanced Revert Inspection with Hardhat Traces Hardhat’s tracing layer can reveal the full execution path of a revert. You’ll see nested calls, gas usage per function, and exactly where the failure occurred. This is invaluable for multi-contract interactions like on-chain governance or liquidity management. Example output: ### [hashtag](https://docs.somnia.network/developer/development-frameworks/debug-playbook#id-1.5-custom-error-decoding-for-verified-contracts) 1.5 Custom Error Decoding for Verified Contracts If a Somnia contract is verified on the explorer, you can fetch its ABI dynamically to decode errors programmatically: Then use `iface.parseError(error.data)` to decode reverts directly from on-chain logs or transactions. * * * [hashtag](https://docs.somnia.network/developer/development-frameworks/debug-playbook#id-2.-common-error-patterns-on-somnia) 2\. Common Error Patterns on Somnia --------------------------------------------------------------------------------------------------------------------------------------------------------------------- Even experienced developers encounter recurring issues. Below are the **most common EVM-level errors** observed when deploying or testing on Somnia Testnet (Shannon) and Mainnet. Error Type Cause Fix `execution reverted` Fallback revert with no message Add explicit revert messages or decode ABI data `out of gas` Gas exhausted mid-call Use `estimateGas()` or increase gas limit `invalid opcode` Calling a non-existent function Validate ABI and deployed bytecode `nonce too low` Pending transaction not mined yet Wait for confirmation or reset nonce `replacement underpriced` Gas bump too small Raise gas price by 10–20% `static call violation` State-changing call via `eth_call` Use `.sendTransaction()` instead ### [hashtag](https://docs.somnia.network/developer/development-frameworks/debug-playbook#id-2.1-example-catching-a-custom-error-in-somnia-treasury-contract) 2.1 Example: Catching a Custom Error in Somnia Treasury Contract Decoding in JS: ### [hashtag](https://docs.somnia.network/developer/development-frameworks/debug-playbook#id-2.2-handling-complex-contract-interactions) 2.2 Handling Complex Contract Interactions When interacting with multi-layered DeFi protocols or bridging modules on Somnia, reverts can originate **several calls deep**. Use Hardhat’s trace or Foundry’s `-vvvv` verbosity to see the full stack. Foundry example: This reveals each opcode execution, event emission, and revert reason. ### [hashtag](https://docs.somnia.network/developer/development-frameworks/debug-playbook#id-2.3-invalid-abi-or-proxy-conflicts) 2.3 Invalid ABI or Proxy Conflicts Many Somnia projects use **upgradeable proxies**. Reverts from a proxy may originate in the implementation contract. If you get a generic `execution reverted`, verify you’re using the correct implementation ABI: * * * [hashtag](https://docs.somnia.network/developer/development-frameworks/debug-playbook#id-3.-transaction-simulation) 3\. Transaction Simulation --------------------------------------------------------------------------------------------------------------------------------------------------- Simulating transactions allows developers to predict revert causes, estimate gas usage, and test behaviors **without risking real SOMI or STT**. ### [hashtag](https://docs.somnia.network/developer/development-frameworks/debug-playbook#id-3.1-fork-somnia-networks-locally) 3.1 Fork Somnia Networks Locally Create a local fork of Somnia Mainnet or Shannon Testnet: Or in configuration: This mirrors on-chain state locally, so you can safely replay any transaction. ### [hashtag](https://docs.somnia.network/developer/development-frameworks/debug-playbook#id-3.2-using-callstatic-for-dry-run-simulation) 3.2 Using callStatic for Dry-Run Simulation `callStatic` runs a transaction without broadcasting or altering state. ### [hashtag](https://docs.somnia.network/developer/development-frameworks/debug-playbook#id-3.3-using-eth_call-manually) 3.3 Using eth\_call Manually For raw RPC simulation: If the call reverts, inspect `error.data` to decode it with the ABI. ### [hashtag](https://docs.somnia.network/developer/development-frameworks/debug-playbook#id-3.4-impersonating-accounts-for-privileged-actions) 3.4 Impersonating Accounts for Privileged Actions Simulate admin or contract-controlled operations: Stop impersonation when finished: ### [hashtag](https://docs.somnia.network/developer/development-frameworks/debug-playbook#id-3.5-snapshot-and-rollback-control) 3.5 Snapshot and Rollback Control Snapshots let you test different outcomes quickly: This resets the blockchain to its previous state instantly. ### [hashtag](https://docs.somnia.network/developer/development-frameworks/debug-playbook#id-3.6-simulating-on-chain-transactions) 3.6 Simulating On-Chain Transactions If you have a failed transaction hash from Somnia Mainnet: This reproduces the failure locally and lets you inspect the revert reason directly in Hardhat. ### [hashtag](https://docs.somnia.network/developer/development-frameworks/debug-playbook#id-3.7-advanced-fork-testing-with-foundry) 3.7 Advanced Fork Testing with Foundry You can use cheatcodes like: ### [hashtag](https://docs.somnia.network/developer/development-frameworks/debug-playbook#id-3.8-gas-profiling-and-cost-analysis) 3.8 Gas Profiling and Cost Analysis Somnia gas costs can differ from Ethereum due to consensus differences. Always estimate gas usage per function: Compare against Shannon and Mainnet to identify anomalies. ### [hashtag](https://docs.somnia.network/developer/development-frameworks/debug-playbook#id-3.9-full-transaction-lifecycle-test) 3.9 Full Transaction Lifecycle Test 1 #### [hashtag](https://docs.somnia.network/developer/development-frameworks/debug-playbook#fork-somnia-testnet) Fork Somnia Testnet Create a fork of the testnet to reproduce on-chain state locally. 2 #### [hashtag](https://docs.somnia.network/developer/development-frameworks/debug-playbook#impersonate-the-deployer-account) Impersonate the deployer account Use impersonation to perform privileged actions and reproduce behavior. 3 #### [hashtag](https://docs.somnia.network/developer/development-frameworks/debug-playbook#run-callstatic-to-simulate-critical-functions) Run callStatic to simulate critical functions Dry-run core functions to inspect return values and revert reasons. 4 #### [hashtag](https://docs.somnia.network/developer/development-frameworks/debug-playbook#capture-reverts-and-decode-with-abi) Capture reverts and decode with ABI Decode revert data, custom errors, and panic codes to get actionable context. 5 #### [hashtag](https://docs.somnia.network/developer/development-frameworks/debug-playbook#use-snapshot-revert-to-iterate-quickly) Use snapshot/revert to iterate quickly Take snapshots to test multiple scenarios and revert between them. 6 #### [hashtag](https://docs.somnia.network/developer/development-frameworks/debug-playbook#once-clean-deploy-and-verify-on-testnet) Once clean, deploy and verify on testnet After local verification, deploy to the testnet and verify behavior. 7 #### [hashtag](https://docs.somnia.network/developer/development-frameworks/debug-playbook#run-same-steps-on-mainnet-fork-before-release) Run same steps on mainnet fork before release Final validation on a mainnet fork ensures production parity. * * * circle-info Summary * Always **decode revert data** rather than relying on generic error strings. * Decode **custom errors** to get structured failure context. * Use **forked local environments** for safe and realistic debugging. * Combine **callStatic**, **trace**, and **snapshot/revert** for fast iteration. * Validate gas behavior across testnet and mainnet for accurate production cost. Debugging on Somnia means understanding the EVM intimately. Every revert, panic, and trace is a clue—decode them, simulate safely, and ship with confidence. [PreviousVerifying via Explorerchevron-left](https://docs.somnia.network/developer/development-frameworks/verifying-via-explorer) [NextBuilding DAppschevron-right](https://docs.somnia.network/developer/building-dapps) Last updated 4 months ago * [1\. Revert Decoding](https://docs.somnia.network/developer/development-frameworks/debug-playbook#id-1.-revert-decoding) * [1.1 Anatomy of a Revert](https://docs.somnia.network/developer/development-frameworks/debug-playbook#id-1.1-anatomy-of-a-revert) * [1.2 Catching and Displaying Reverts in Hardhat](https://docs.somnia.network/developer/development-frameworks/debug-playbook#id-1.2-catching-and-displaying-reverts-in-hardhat) * [1.3 Decoding Panic Codes](https://docs.somnia.network/developer/development-frameworks/debug-playbook#id-1.3-decoding-panic-codes) * [1.4 Advanced Revert Inspection with Hardhat Traces](https://docs.somnia.network/developer/development-frameworks/debug-playbook#id-1.4-advanced-revert-inspection-with-hardhat-traces) * [1.5 Custom Error Decoding for Verified Contracts](https://docs.somnia.network/developer/development-frameworks/debug-playbook#id-1.5-custom-error-decoding-for-verified-contracts) * [2\. Common Error Patterns on Somnia](https://docs.somnia.network/developer/development-frameworks/debug-playbook#id-2.-common-error-patterns-on-somnia) * [2.1 Example: Catching a Custom Error in Somnia Treasury Contract](https://docs.somnia.network/developer/development-frameworks/debug-playbook#id-2.1-example-catching-a-custom-error-in-somnia-treasury-contract) * [2.2 Handling Complex Contract Interactions](https://docs.somnia.network/developer/development-frameworks/debug-playbook#id-2.2-handling-complex-contract-interactions) * [2.3 Invalid ABI or Proxy Conflicts](https://docs.somnia.network/developer/development-frameworks/debug-playbook#id-2.3-invalid-abi-or-proxy-conflicts) * [3\. Transaction Simulation](https://docs.somnia.network/developer/development-frameworks/debug-playbook#id-3.-transaction-simulation) * [3.1 Fork Somnia Networks Locally](https://docs.somnia.network/developer/development-frameworks/debug-playbook#id-3.1-fork-somnia-networks-locally) * [3.2 Using callStatic for Dry-Run Simulation](https://docs.somnia.network/developer/development-frameworks/debug-playbook#id-3.2-using-callstatic-for-dry-run-simulation) * [3.3 Using eth\_call Manually](https://docs.somnia.network/developer/development-frameworks/debug-playbook#id-3.3-using-eth_call-manually) * [3.4 Impersonating Accounts for Privileged Actions](https://docs.somnia.network/developer/development-frameworks/debug-playbook#id-3.4-impersonating-accounts-for-privileged-actions) * [3.5 Snapshot and Rollback Control](https://docs.somnia.network/developer/development-frameworks/debug-playbook#id-3.5-snapshot-and-rollback-control) * [3.6 Simulating On-Chain Transactions](https://docs.somnia.network/developer/development-frameworks/debug-playbook#id-3.6-simulating-on-chain-transactions) * [3.7 Advanced Fork Testing with Foundry](https://docs.somnia.network/developer/development-frameworks/debug-playbook#id-3.7-advanced-fork-testing-with-foundry) * [3.8 Gas Profiling and Cost Analysis](https://docs.somnia.network/developer/development-frameworks/debug-playbook#id-3.8-gas-profiling-and-cost-analysis) * [3.9 Full Transaction Lifecycle Test](https://docs.somnia.network/developer/development-frameworks/debug-playbook#id-3.9-full-transaction-lifecycle-test) chai-example.ts Copy await expect(treasury.connect(user).withdraw(1000)) .to.be.revertedWith('Insufficient funds'); decode-custom-error.ts Copy const iface = new ethers.utils.Interface(contractABI); try { await treasury.connect(attacker).withdraw(9999); } catch (error: any) { const data = error.data || error.error?.data; if (data) { const decoded = iface.parseError(data); console.log(`Custom Error: ${decoded.name}`); console.log('Arguments:', decoded.args); } } detect-panic.ts Copy if (error.data?.startsWith('0x4e487b71')) { const code = parseInt(error.data.slice(10), 16); console.log('Panic Code:', `0x${code.toString(16)}`); } trace Copy npx hardhat test --trace Copy CALL treasury.withdraw └─ CALL token.transfer -> reverted with reason: 'Insufficient balance' fetch-abi.ts Copy import axios from 'axios'; const abiURL = `https://explorer.somnia.network/api?module=contract&action=getabi&address=${address}`; const { data } = await axios.get(abiURL); const iface = new ethers.utils.Interface(JSON.parse(data.result)); Treasury.sol Copy error Unauthorized(address caller); function mint(address to, uint amount) external { if (msg.sender != owner) revert Unauthorized(msg.sender); _mint(to, amount); } catch-unauthorized.ts Copy try { await treasury.connect(randomUser).mint(addr, 100); } catch (e: any) { const iface = new ethers.utils.Interface(['error Unauthorized(address caller)']); const decoded = iface.parseError(e.data); console.log('Unauthorized address:', decoded.args[0]); } foundry-verbosity Copy forge test -vvvv get-impl.ts Copy const implAddr = await provider.getStorageAt(proxyAddress, '0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc'); const iface = new ethers.utils.Interface(implementationABI); hardhat-fork Copy npx hardhat node --fork https://api.infra.mainnet.somnia.network hardhat-config.ts Copy networks: { hardhat: { forking: { url: process.env.SOMNIA_RPC_TESTNET, blockNumber: 123456, //example } } } callstatic.ts Copy try { const result = await treasury.callStatic.withdraw(1000); console.log('Call successful:', result); } catch (error: any) { console.log('Simulation failed with reason:', error.reason); } eth-call.ts Copy const tx = { to: contract.address, data: contract.interface.encodeFunctionData('stake', [amount]) }; const result = await provider.call(tx); console.log('Returned data:', result); impersonate.ts Copy await network.provider.request({ method: 'hardhat_impersonateAccount', params: ['0xAdminAddress'] }); const admin = await ethers.getSigner('0xAdminAddress'); await treasury.connect(admin).setFee(5); stop-impersonate.ts Copy await network.provider.request({ method: 'hardhat_stopImpersonatingAccount', params: ['0xAdminAddress'] }); snapshot.ts Copy const snapshot = await network.provider.send('evm_snapshot', []); await treasury.mint(100); await network.provider.send('evm_revert', [snapshot]); replay-tx.ts Copy const tx = await provider.getTransaction('0x123...'); await provider.call({ to: tx.to!, data: tx.data }); anvil-forge Copy anvil --fork-url https://dream-rpc.somnia.network --fork-block-number 3456789 forge test -vvvv foundry-cheatcodes.sol Copy vm.startPrank(admin); contract.withdraw(1000); vm.stopPrank(); estimate-gas.ts Copy const gas = await contract.estimateGas.executeTrade(orderId); console.log('Estimated gas:', gas.toString()); --- # Authenticating with MetaMask | Somnia Docs Somnia empowers developers to build applications for mass adoption. Developers who deploy their Smart Contracts on Somnia, will require a User Interface to Connect to the Smart Contract. To enable users connect via the User Interface, it is necessary to set up an authentication process where only authorized users can access the functionality on the deployed Smart Contracts, for example, to carry out WRITE operations. [MetaMaskarrow-up-right](https://docs.metamask.io/) is a wallet library that developers can use to build login functionality for applications on the Somnia Network. circle-check Somnia Mainnet is LIVE. To deploy on Somnia Mainnet, you will need SOMI Tokens. Please refer to the [guide](https://docs.somnia.network/get-started/getting-started-for-mainnet) on Moving from Testnet to Mainnet. In this guide, you will learn how to use the MetaMask Library to set up authentication for your User Interface App and connect to the Somnia Network. We will build a simple NextJS application to walk through the process. [hashtag](https://docs.somnia.network/developer/building-dapps/wallet-integration-and-auth/authenticating-with-metamask#start-a-nextjs-project) Start a NextJS Project --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- Run the command below to start a NextJS project: Copy npx create-next-app metamask-example Select Typescript, TailWind CSS, and Page Router in the build options. Change the directory into the project folder. Delete the code inside of the `
` tags and replace them with the following: Copy

Hello, World!

[hashtag](https://docs.somnia.network/developer/building-dapps/wallet-integration-and-auth/authenticating-with-metamask#install-viem) Install Viem ------------------------------------------------------------------------------------------------------------------------------------------------------- Copy npm i viem [Viemarrow-up-right](https://viem.sh/) is a TypeScript interface for Ethereum that provides low-level stateless primitives for interacting with Ethereum. Viem sets up a “`transport`” infrastructure to connect with a node in the EVM Network and the deployed Smart Contracts. We will use some ViemJS methods to connect to your Smart Contract deployed on the Somnia Network. ViemJS has a \`createPublicClient\` and a \`createWalletClient\` method. The PublicClient is used to perform READ operations, while the WalletClient is used to perform WRITE operations. [hashtag](https://docs.somnia.network/developer/building-dapps/wallet-integration-and-auth/authenticating-with-metamask#import-methods) Import Methods ----------------------------------------------------------------------------------------------------------------------------------------------------------- The next step is to set up the React State methods and the ViemJS methods that we will require: The `http` is the transport protocol for interacting with the Node of the Somnia Blockchain via RPC. It uses the default Somnia RPC URL: [`https://dream-rpc.somnia.network`arrow-up-right](https://dream-rpc.somnia.network/) . In the future developers can use RPC providers to avoid rate limiting. [hashtag](https://docs.somnia.network/developer/building-dapps/wallet-integration-and-auth/authenticating-with-metamask#import-somnia) Import Somnia --------------------------------------------------------------------------------------------------------------------------------------------------------- Import Somnia Testnet [hashtag](https://docs.somnia.network/developer/building-dapps/wallet-integration-and-auth/authenticating-with-metamask#declare-react-states) Declare React States ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- State allows us to manage changing data in the User Interface. For this example application, we are going to manage two states: * When we can read the User's Address * When a User is connected (Authorization) Add the states inside the export statement: Now that the States are declared, we can declare a function to handle the MetaMask authentication process on Somnia Network. [hashtag](https://docs.somnia.network/developer/building-dapps/wallet-integration-and-auth/authenticating-with-metamask#connect-metamask-function) Connect MetaMask Function --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- Add the function below inside the export statement: [hashtag](https://docs.somnia.network/developer/building-dapps/wallet-integration-and-auth/authenticating-with-metamask#update-the-ui) Update the UI --------------------------------------------------------------------------------------------------------------------------------------------------------- MetaMask connection is set up, and the final step is to test the connection via the User Interface. Update the `

Hello, World!

` in the return statement to the following: Open your terminal and run the following command to start the app: Go to `localhost:3000` in your Web Browser to interact with the app and connect to Somnia Network via MetaMask. You can read more about using Viem to interact with the deployed Smart Contract methods on Somnia Network [here](https://docs.somnia.network/developer/development-frameworks/using-the-viem-library) . Congratulations, you have successfully connected from MetaMask to Somnia Network. 🎉 [PreviousWallet Integration and Authchevron-left](https://docs.somnia.network/developer/building-dapps/wallet-integration-and-auth) [NextAuthenticating with ConnectKitchevron-right](https://docs.somnia.network/developer/building-dapps/wallet-integration-and-auth/authenticating-with-connectkit) Last updated 6 months ago * [Start a NextJS Project](https://docs.somnia.network/developer/building-dapps/wallet-integration-and-auth/authenticating-with-metamask#start-a-nextjs-project) * [Install Viem](https://docs.somnia.network/developer/building-dapps/wallet-integration-and-auth/authenticating-with-metamask#install-viem) * [Import Methods](https://docs.somnia.network/developer/building-dapps/wallet-integration-and-auth/authenticating-with-metamask#import-methods) * [Import Somnia](https://docs.somnia.network/developer/building-dapps/wallet-integration-and-auth/authenticating-with-metamask#import-somnia) * [Declare React States](https://docs.somnia.network/developer/building-dapps/wallet-integration-and-auth/authenticating-with-metamask#declare-react-states) * [Connect MetaMask Function](https://docs.somnia.network/developer/building-dapps/wallet-integration-and-auth/authenticating-with-metamask#connect-metamask-function) * [Update the UI](https://docs.somnia.network/developer/building-dapps/wallet-integration-and-auth/authenticating-with-metamask#update-the-ui) Copy import { useState } from "react"; import { createPublicClient, http, createWalletClient, } from "viem"; Copy import { somniaTestnet } from "viem/chains"; Copy const [address, setAddress] = useState(""); const [connected, setConnected] = useState(false); Copy const connectToMetaMask = async () => { if (typeof window !== "undefined" && window.ethereum !== undefined) { try { await window.ethereum.request({ method: "eth_requestAccounts" }); const walletClient = createWalletClient({ chain: SOMNIA, transport: custom(window.ethereum), }); const [userAddress] = await walletClient.getAddresses(); setClient(walletClient); setAddress(userAddress); setConnected(true); console.log("Connected account:", userAddress); } catch (error) { console.error("User denied account access:", error); } } else { console.log( "MetaMask is not installed or not running in a browser environment!" ); } }; Copy {!connected ? ( ) : (

Connected as: {address}

)} Copy npm run dev --- # Tokos | Somnia Docs [hashtag](https://docs.somnia.network/developer/deployment-and-production/ecosystem/ecosystem-showcase/tokos#tokos) Tokos ------------------------------------------------------------------------------------------------------------------------------ Tokos is a decentralized liquidity protocol operating on the Somnia network that enables users to earn interest income by staking their digital assets or borrow funds by providing collateral. The protocol operates in a decentralized and transparent manner. [hashtag](https://docs.somnia.network/developer/deployment-and-production/ecosystem/ecosystem-showcase/tokos#how-to-supply-assets-and-earn-interest) How to Supply Assets and Earn Interest? ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ![](https://docs.somnia.network/~gitbook/image?url=https%3A%2F%2F1861192046-files.gitbook.io%2F%7E%2Ffiles%2Fv0%2Fb%2Fgitbook-x-prod.appspot.com%2Fo%2Fspaces%252FFxD3cqyyFDzHJ0nfCPfy%252Fuploads%252FzP5UbCatcGMuJcw9tDwY%252FAds%25C4%25B1z%2520tasar%25C4%25B1m%2520%2818%29.png%3Falt%3Dmedia%26token%3D0b16eff8-1fe5-4455-8bae-9acf353cd892&width=768&dpr=3&quality=100&sign=fa6e1254&sv=2) 1. **Go to the** [**Tokos App**arrow-up-right](https://app.tokos.fi/) **:** Connect your wallet to the Tokos lending platform. 2. **Select an Asset:** Choose the digital asset you wish to supply from the list of available markets. 3. **Enter Amount:** Specify the amount of the asset you want to supply to the liquidity pool. 4. **Approve & Supply:** First, approve the Tokos smart contract to spend your tokens, then submit the supply transaction. 5. **Start Earning:** Once your transaction is confirmed, you will start earning interest on your supplied assets. You will receive a corresponding amount of aTokens (e.g., aUSDC), which represent your share in the pool and accrue interest in real-time. [hashtag](https://docs.somnia.network/developer/deployment-and-production/ecosystem/ecosystem-showcase/tokos#how-to-borrow-assets) How to Borrow Assets? ------------------------------------------------------------------------------------------------------------------------------------------------------------- ![](https://docs.somnia.network/~gitbook/image?url=https%3A%2F%2F1861192046-files.gitbook.io%2F%7E%2Ffiles%2Fv0%2Fb%2Fgitbook-x-prod.appspot.com%2Fo%2Fspaces%252FFxD3cqyyFDzHJ0nfCPfy%252Fuploads%252FecbSRy6Qo28Bowx24fPO%252FAds%25C4%25B1z%2520tasar%25C4%25B1m%2520%2819%29.png%3Falt%3Dmedia%26token%3Dba953de7-3d76-4b55-ab51-f8a230fa2bab&width=768&dpr=3&quality=100&sign=b8e0e9d5&sv=2) 1. **Supply Collateral:** Before you can borrow, you must supply assets to be used as collateral. 2. **Enable as Collateral:** In your supply dashboard, make sure the asset you want to use as collateral is enabled. 3. **Choose Asset to Borrow:** Select the asset you wish to borrow from the "Borrow" section. 4. **Specify Amount:** Enter the amount you want to borrow. The interface will show your "Health Factor," which indicates the safety of your loan. A lower Health Factor increases your risk of liquidation. 5. **Confirm Transaction:** Submit the borrow transaction. The borrowed assets will be transferred to your wallet. [hashtag](https://docs.somnia.network/developer/deployment-and-production/ecosystem/ecosystem-showcase/tokos#id-2.-for-developers) 2\. For Developers ---------------------------------------------------------------------------------------------------------------------------------------------------------- Tokos provides a base layer for developers to build their own DeFi applications. By interacting with the protocol's smart contracts, you can integrate borrowing/lending functions into your own platforms. You can access the Tokos smart contracts via the [linkarrow-up-right](https://docs.tokos.fi/deployed-contracts) . [PreviousSomnia Exchangechevron-left](https://docs.somnia.network/developer/deployment-and-production/ecosystem/ecosystem-showcase/somnia-exchange) [NextEcosystem Toolschevron-right](https://docs.somnia.network/developer/deployment-and-production/ecosystem/ecosystem-tools) Last updated 4 months ago * [Tokos](https://docs.somnia.network/developer/deployment-and-production/ecosystem/ecosystem-showcase/tokos#tokos) * [How to Supply Assets and Earn Interest?](https://docs.somnia.network/developer/deployment-and-production/ecosystem/ecosystem-showcase/tokos#how-to-supply-assets-and-earn-interest) * [How to Borrow Assets?](https://docs.somnia.network/developer/deployment-and-production/ecosystem/ecosystem-showcase/tokos#how-to-borrow-assets) * [2\. For Developers](https://docs.somnia.network/developer/deployment-and-production/ecosystem/ecosystem-showcase/tokos#id-2.-for-developers) --- # Building Subgraph UIs (NextJS/Fetch) | Somnia Docs [Subgraphsarrow-up-right](https://somnia.chain.love/) allow developers to efficiently query Somnia blockchain data using GraphQL, making it easy to index and retrieve real-time blockchain activity. In this tutorial, you’ll learn how to: * Fetch blockchain data from a Subgraph API * Fix CORS errors using a NextJS API route * Display token transfers in a real-time UI By the end of this guide, you'll have a fully functional UI that fetches and displays token transfers from Somnia’s Subgraph API. [hashtag](https://docs.somnia.network/developer/building-dapps/data-indexing-and-querying/building-subgraph-uis-nextjs-fetch#prerequisites) Prerequisites -------------------------------------------------------------------------------------------------------------------------------------------------------------- * Basic knowledge of React & Next.js. * A deployed Subgraph API on Somnia ([or use an existing onearrow-up-right](https://somnia.chain.love/graph) ). * Account on [https://somnia.chain.lovearrow-up-right](https://somnia.chain.love/) see [guide](https://docs.somnia.network/developer/building-dapps/data-indexing-and-querying/protofire-subgraph) . [hashtag](https://docs.somnia.network/developer/building-dapps/data-indexing-and-querying/building-subgraph-uis-nextjs-fetch#create-a-nextjs-project) Create a NextJS Project ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- Start by creating a new Next.js app. Copy npx create-next-app@latest somnia-subgraph-ui cd somnia-subgraph-ui Then, install required dependencies. Copy npm install thirdweb react-query graphql [hashtag](https://docs.somnia.network/developer/building-dapps/data-indexing-and-querying/building-subgraph-uis-nextjs-fetch#define-the-subgraph-api-in-environment-variables) Define the Subgraph API in Environment Variables ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ Create a .env.local file in the root folder. circle-info 💡 Note: Restart your development server after modifying .env.local: [hashtag](https://docs.somnia.network/developer/building-dapps/data-indexing-and-querying/building-subgraph-uis-nextjs-fetch#create-a-nextjs-api-route-for-the-subgraph) Create a NextJS API Route for the Subgraph ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ Since the Somnia Subgraph API has CORS restrictions, we’ll use a NextJS API route to act as a proxy. Inside the app directory create the folder paths `**api/proxy**` and add a file `**route.ts**` Update the `**route.ts**` file with the following code: This code allows your frontend to make requests without triggering CORS errors. [hashtag](https://docs.somnia.network/developer/building-dapps/data-indexing-and-querying/building-subgraph-uis-nextjs-fetch#fetch-and-display-token-transfers-in-the-ui) Fetch and Display Token Transfers in the UI -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- Now that we have the API set up, let’s build a React component that: * Sends the GraphQL query using fetch() * Stores the fetched data using useState() * Displays the token transfers in a simple UI To fetch the latest 10 token transfers, we’ll use this GraphQL query: This code fetches the last 10 transfers (first: 10) and orders them by timestamp (latest first). It retrieves wallet addresses (from, to) and the amount transferred (value) and includes the transaction hash (used to generate an explorer link). [hashtag](https://docs.somnia.network/developer/building-dapps/data-indexing-and-querying/building-subgraph-uis-nextjs-fetch#fetch-and-display-data-in-a-react-component) Fetch and Display Data in a React Component -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- Now, let’s integrate the query into our NextJS frontend. Create a folder `**components**` and add a file `**TokenTransfer.ts**` We use `**useState()**` to store transfer data and `**useEffect()**` to fetch it when the component loads. Once data is fetched, we render it inside the UI. The UI shows a loading message while data is being fetched. When data is ready, it displays the latest 10 token transfers. It formats transaction values (value / 1e18) to show the correct STT amount and provides a link to view each transaction on Somnia Explorer. [hashtag](https://docs.somnia.network/developer/building-dapps/data-indexing-and-querying/building-subgraph-uis-nextjs-fetch#add-the-component-to-the-nextjs-page) Add the Component to the NextJS Page ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ Update the page.tsx file: Restart the development server: ![](https://docs.somnia.network/~gitbook/image?url=https%3A%2F%2F2122549367-files.gitbook.io%2F%7E%2Ffiles%2Fv0%2Fb%2Fgitbook-x-prod.appspot.com%2Fo%2Fspaces%252FkYErT9t3BJtpPfejLO6I%252Fuploads%252FXbWJRdvQ4SHjCpp9uaBX%252FTokenTransfers.png%3Falt%3Dmedia%26token%3Dcafd3254-32a7-44ed-8481-6f47afa28e59&width=768&dpr=3&quality=100&sign=26ff9a1d&sv=2) Now your NextJS UI dynamically fetches and displays token transfers from Somnia’s Subgraph! 🔥 [PreviousProtofire Subgraphchevron-left](https://docs.somnia.network/developer/building-dapps/data-indexing-and-querying/protofire-subgraph) [NextBuilding Subgraph UIs (Apollo Client)chevron-right](https://docs.somnia.network/developer/building-dapps/data-indexing-and-querying/building-subgraph-uis-apollo-client) Last updated 12 months ago * [Prerequisites](https://docs.somnia.network/developer/building-dapps/data-indexing-and-querying/building-subgraph-uis-nextjs-fetch#prerequisites) * [Create a NextJS Project](https://docs.somnia.network/developer/building-dapps/data-indexing-and-querying/building-subgraph-uis-nextjs-fetch#create-a-nextjs-project) * [Define the Subgraph API in Environment Variables](https://docs.somnia.network/developer/building-dapps/data-indexing-and-querying/building-subgraph-uis-nextjs-fetch#define-the-subgraph-api-in-environment-variables) * [Create a NextJS API Route for the Subgraph](https://docs.somnia.network/developer/building-dapps/data-indexing-and-querying/building-subgraph-uis-nextjs-fetch#create-a-nextjs-api-route-for-the-subgraph) * [Fetch and Display Token Transfers in the UI](https://docs.somnia.network/developer/building-dapps/data-indexing-and-querying/building-subgraph-uis-nextjs-fetch#fetch-and-display-token-transfers-in-the-ui) * [Fetch and Display Data in a React Component](https://docs.somnia.network/developer/building-dapps/data-indexing-and-querying/building-subgraph-uis-nextjs-fetch#fetch-and-display-data-in-a-react-component) * [Add the Component to the NextJS Page](https://docs.somnia.network/developer/building-dapps/data-indexing-and-querying/building-subgraph-uis-nextjs-fetch#add-the-component-to-the-nextjs-page) Copy NEXT_PUBLIC_SUBGRAPH_URL=https://proxy.somnia.chain.love/subgraphs/name/somnia-testnet/test-mytoken NEXT_PUBLIC_SUBGRAPH_CLIENT_ID=YOUR_CLIENT_ID Copy npm run dev Copy import { NextResponse } from "next/server"; const SUBGRAPH_URL = process.env.NEXT_PUBLIC_SUBGRAPH_URL as string; const CLIENT_ID = process.env.NEXT_PUBLIC_SUBGRAPH_CLIENT_ID as string; export async function POST(req: Request) { try { const body = await req.json(); const response = await fetch(SUBGRAPH_URL, { method: "POST", headers: { "Content-Type": "application/json", "Client-ID": CLIENT_ID, // ✅ Pass the Subgraph Client ID }, body: JSON.stringify(body), }); const data = await response.json(); return NextResponse.json(data); } catch (error) { console.error("Proxy Error:", error); return NextResponse.json({ error: "Failed to fetch from Subgraph" }, { status: 500 }); } } Copy { transfers(first: 10, orderBy: blockTimestamp, orderDirection: desc) { id from to value blockTimestamp transactionHash } } Copy "use client"; import { useEffect, useState } from "react"; export default function TokenTransfers() { // Store transfers in state const [transfers, setTransfers] = useState([]); // Track loading state const [loading, setLoading] = useState(true); Next, we fetch the token transfer data when the component loads. useEffect(() => { async function fetchTransfers() { setLoading(true); // Show loading state const query = ` { transfers(first: 10, orderBy: blockTimestamp, orderDirection: desc) { id from to value blockTimestamp transactionHash } } `; const response = await fetch("/api/proxy", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ query }), }); const { data } = await response.json(); setTransfers(data.transfers || []); // Store results in state setLoading(false); // Hide loading state } fetchTransfers(); }, []); Copy return (

Latest Token Transfers

{loading ? (

Loading transfers...

) : (
    {transfers.map((transfer) => (
  • From: {transfer.from}

    To: {transfer.to}

    Value: {parseFloat(transfer.value) / 1e18} STT

    TX:{" "} View Transaction

  • ))}
)}
); } Copy "use client"; import TokenTransfers from "../components/TokenTransfers"; export default function Home() { return (

Welcome to MyToken Dashboard

); } Copy npm run dev --- # Authenticating with Privy | Somnia Docs [Privyarrow-up-right](https://docs.privy.io/) is a secure, embeddable wallet infrastructure provider that allows developers to authenticate users, manage sessions, and provide seamless wallet experiences within dApps. Privy embedded wallets can be made interoperable across apps. Somnia has adopted the global wallets setup to foster a cross-app ecosystem where users can easily port their wallets from one app to another in the Somnia Ecosystem. Using **global wallets**, users can seamlessly move assets between different apps and easily prove ownership of, sign messages, or send transactions with their existing wallets. Developers do not have to worry that users will generate a new wallet to sign into different applications. Kindly read more [herearrow-up-right](https://docs.privy.io/wallets/global-wallets/overview) . This guide will integrate Privy with the Somnia Testnet, enabling users to create and connect wallets effortlessly. ![](https://docs.somnia.network/~gitbook/image?url=https%3A%2F%2F2122549367-files.gitbook.io%2F%7E%2Ffiles%2Fv0%2Fb%2Fgitbook-x-prod.appspot.com%2Fo%2Fspaces%252FkYErT9t3BJtpPfejLO6I%252Fuploads%252Fg9NXZ2uShPKKpmPnqFGs%252FSomnia-ProviderApp.png%3Falt%3Dmedia%26token%3Deecad0bb-b8f7-4eb0-a221-e64ed795c3a0&width=768&dpr=3&quality=100&sign=414d2118&sv=2) [hashtag](https://docs.somnia.network/developer/building-dapps/wallet-integration-and-auth/authenticating-with-privy#prerequisites) Prerequisites ------------------------------------------------------------------------------------------------------------------------------------------------------ * This guide is not an introduction to JavaScript Programming; you are expected to understand JavaScript. * To complete this guide, sign up for [Privyarrow-up-right](https://dashboard.privy.io/) and get an AppID and get the Somnia Provider AppID. * Familiarity with React and Next.js is assumed. [hashtag](https://docs.somnia.network/developer/building-dapps/wallet-integration-and-auth/authenticating-with-privy#installation) Installation ---------------------------------------------------------------------------------------------------------------------------------------------------- ### [hashtag](https://docs.somnia.network/developer/building-dapps/wallet-integration-and-auth/authenticating-with-privy#create-the-next.js-project) Create the Next.js Project Open your terminal and run the following commands to set up a new Next.js project: Install the necessary packages [hashtag](https://docs.somnia.network/developer/building-dapps/wallet-integration-and-auth/authenticating-with-privy#set-up-privyprovider) Set Up PrivyProvider -------------------------------------------------------------------------------------------------------------------------------------------------------------------- Go to [https://dashboard.privy.io/arrow-up-right](https://dashboard.privy.io/) to set up an account. Click "`**New App**`" to create a new application that will connect to the Somnia Provider AppID. ![](https://docs.somnia.network/~gitbook/image?url=https%3A%2F%2F2122549367-files.gitbook.io%2F%7E%2Ffiles%2Fv0%2Fb%2Fgitbook-x-prod.appspot.com%2Fo%2Fspaces%252FkYErT9t3BJtpPfejLO6I%252Fuploads%252FqyA5RwjgOn7hgjHDMWgb%252FPrivy-Dashboard.png%3Falt%3Dmedia%26token%3D3a209129-c947-4df7-b666-e6c6a55d92ec&width=768&dpr=3&quality=100&sign=f2266fa&sv=2) Open the newly created app and in the left side navigation menu navigate to: `**User Management >>>> Global Wallet >>>> Integrations**` Click the toggle to turn ON the Somnia Provider App. ![](https://docs.somnia.network/~gitbook/image?url=https%3A%2F%2F2122549367-files.gitbook.io%2F%7E%2Ffiles%2Fv0%2Fb%2Fgitbook-x-prod.appspot.com%2Fo%2Fspaces%252FkYErT9t3BJtpPfejLO6I%252Fuploads%252FmU5FdWAqHhKD5k1r5k7K%252FSomniaAppID.png%3Falt%3Dmedia%26token%3D6dfc2377-f20c-4dbd-90c2-75ca0dd9d3ae&width=768&dpr=3&quality=100&sign=cfa7f844&sv=2) Wrap your application `**layout.ts**` file with PrivyProvider and supply your PrivateKey from Privy and the Somnia Provider App ID to the `**loginMethods**`: Add your environment variable in .env.local: [hashtag](https://docs.somnia.network/developer/building-dapps/wallet-integration-and-auth/authenticating-with-privy#privy-hooks) Privy Hooks -------------------------------------------------------------------------------------------------------------------------------------------------- These hooks make it easy to authenticate users, manage wallets, and interact with the Somnia Network using Privy Global Wallet [hashtag](https://docs.somnia.network/developer/building-dapps/wallet-integration-and-auth/authenticating-with-privy#authenticate) Authenticate ---------------------------------------------------------------------------------------------------------------------------------------------------- Use the provided hooks to authenticate users and access their wallets. chevron-rightpage.tsx[hashtag](https://docs.somnia.network/developer/building-dapps/wallet-integration-and-auth/authenticating-with-privy#page.tsx) [hashtag](https://docs.somnia.network/developer/building-dapps/wallet-integration-and-auth/authenticating-with-privy#send-transactions) Send Transactions -------------------------------------------------------------------------------------------------------------------------------------------------------------- Once authenticated, use the `**useSendTransaction**` hook from `useCrossAppAccount` method to interact with Somnia Testnet: [hashtag](https://docs.somnia.network/developer/building-dapps/wallet-integration-and-auth/authenticating-with-privy#complete-code) Complete Code ------------------------------------------------------------------------------------------------------------------------------------------------------ chevron-rightComplete `**page.tsx**` code[hashtag](https://docs.somnia.network/developer/building-dapps/wallet-integration-and-auth/authenticating-with-privy#complete-page.tsx-code) By using Privy Global Wallet on the Somnia Testnet, developers can offer a seamless onboarding and wallet experience. This setup is ideal for onboarding Web2 users into Web3 with embedded wallets, abstracting away traditional wallet complexities. [PreviousAuthenticating with ConnectKitchevron-left](https://docs.somnia.network/developer/building-dapps/wallet-integration-and-auth/authenticating-with-connectkit) [NextAuthenticating with RainbowKitchevron-right](https://docs.somnia.network/developer/building-dapps/wallet-integration-and-auth/authenticating-with-rainbowkit) Last updated 9 months ago * [Prerequisites](https://docs.somnia.network/developer/building-dapps/wallet-integration-and-auth/authenticating-with-privy#prerequisites) * [Installation](https://docs.somnia.network/developer/building-dapps/wallet-integration-and-auth/authenticating-with-privy#installation) * [Create the Next.js Project](https://docs.somnia.network/developer/building-dapps/wallet-integration-and-auth/authenticating-with-privy#create-the-next.js-project) * [Set Up PrivyProvider](https://docs.somnia.network/developer/building-dapps/wallet-integration-and-auth/authenticating-with-privy#set-up-privyprovider) * [Privy Hooks](https://docs.somnia.network/developer/building-dapps/wallet-integration-and-auth/authenticating-with-privy#privy-hooks) * [Authenticate](https://docs.somnia.network/developer/building-dapps/wallet-integration-and-auth/authenticating-with-privy#authenticate) * [Send Transactions](https://docs.somnia.network/developer/building-dapps/wallet-integration-and-auth/authenticating-with-privy#send-transactions) * [Complete Code](https://docs.somnia.network/developer/building-dapps/wallet-integration-and-auth/authenticating-with-privy#complete-code) Copy npx create-next-app@latest somnia-privy cd somnia-privy Copy npm install @privy-io/react-auth viem Copy 'use client'; import { PrivyProvider } from '@privy-io/react-auth'; import { somniaTestnet } from 'viem/chains'; export default function RootLayout({ children, }: Readonly<{ children: React.ReactNode; }>) { return ( {children} ); } Copy NEXT_PUBLIC_PRIVY_APP_ID=your-privy-app-id Copy import { useCrossAppAccounts, usePrivy } from '@privy-io/react-auth'; Copy export default function Home() { const { loginWithCrossAppAccount } = useCrossAppAccounts(); const { ready, authenticated, user, logout } = usePrivy(); const disableLogin = !ready || (ready && authenticated); const [loginError, setLoginError] = useState(null); const [walletAddress, setWalletAddress] = useState(null); const providerAppId = 'cm8d9yzp2013kkr612h8ymoq8'; const startCrossAppLogin = async () => { try { setLoginError(null); const result = await loginWithCrossAppAccount({ appId: providerAppId, }); setWalletAddress(result.wallet?.address) console.log( 'Logged in via global wallet:', result, ); } catch (err) { console.warn('Cross-app login failed:', err); setLoginError('Failed to log in with Global Wallet.'); } }; ...... {!ready ? (

Loading...

) : authenticated ? ( {walletAddress ? (

Connected as: {walletAddress}

) : (

No wallet address found.

)} ) : ( <> {loginError &&

{loginError}

} )} } Copy const { sendTransaction } = useCrossAppAccounts(); ...... const sendSTT = async () => { if (!walletAddress) return; const txn = { to: '0xb6e4fa6ff2873480590c68D9Aa991e5BB14Dbf03', value: 1000000000000000, chainId: 50312, }; try { const tx = await sendTransaction(txn, { address: walletAddress }); console.log('TX Sent:', tx); } catch (err) { console.error('TXN Failed:', err); } }; ...... Copy 'use client'; import { usePrivy, useCrossAppAccounts, } from '@privy-io/react-auth'; import { useEffect, useState } from 'react'; import { createPublicClient, http, formatEther } from 'viem'; import { somniaTestnet } from 'viem/chains'; export default function Home() { const { ready, authenticated, user, logout } = usePrivy(); const { loginWithCrossAppAccount, sendTransaction } = useCrossAppAccounts(); const [loginError, setLoginError] = useState(null); const [hydrated, setHydrated] = useState(false); const [walletAddress, setWalletAddress] = useState(null); const [balance, setBalance] = useState(''); const providerAppId = 'cm8d9yzp2013kkr612h8ymoq8'; const client = createPublicClient({ chain: somniaTestnet, transport: http(), }); const startCrossAppLogin = async () => { try { setLoginError(null); const result = await loginWithCrossAppAccount({ appId: providerAppId, }); console.log( 'Logged in via global wallet:', result, ); } catch (err) { console.warn('Cross-app login failed:', err); setLoginError('Failed to log in with Global Wallet.'); } }; useEffect(() => { if (authenticated) { const globalWallet = user?.linkedAccounts?.find( (account) => account.type === 'cross_app' && account.providerApp?.id === providerAppId ); console.log(globalWallet); const wallet = globalWallet?.smartWallets?.[0]; console.log(wallet); if (wallet?.address) { setWalletAddress(wallet.address); setHydrated(true); fetchBalance(wallet.address); } else if (user?.wallet?.address) { setWalletAddress(user.wallet.address); setHydrated(true); fetchBalance(user.wallet.address); } else { setHydrated(true); } } }, [authenticated, user]); const fetchBalance = async (address: string) => { try { const result = await client.getBalance({ address: address as `0x${string}`, }); const formatted = parseFloat(formatEther(result)).toFixed(3); setBalance(formatted); } catch (err) { console.error('Failed to fetch balance:', err); } }; const sendSTT = async () => { if (!walletAddress) return; console.log(walletAddress); const txn = { to: '0xb6e4fa6ff2873480590c68D9Aa991e5BB14Dbf03', value: 1000000000000000, chainId: 50312, }; try { const tx = await sendTransaction(txn, { address: walletAddress }); console.log('TX Sent:', tx); if (walletAddress) fetchBalance(walletAddress); } catch (err) { console.error('TXN Failed:', err); } }; return (
{!ready ? (

Loading...

) : !authenticated ? ( <> {loginError &&

{loginError}

} ) : hydrated ? (
{walletAddress ? (

Connected as: {walletAddress}

) : (

No wallet address found.

)}

Balance: {balance ? `${balance} STT` : 'Loading...'}

) : (

🔄 Logging in... Please wait

)}
); } --- # Oracles | Somnia Docs [hashtag](https://docs.somnia.network/developer/deployment-and-production/ecosystem/ecosystem-tools/oracles#dia) [DIAarrow-up-right](https://www.diadata.org/) -------------------------------------------------------------------------------------------------------------------------------------------------------------------- DIA (Decentralized Information Asset) provides real-time on-chain oracles and trusted price feeds for decentralized applications on Somnia. This is critical for DeFi, lending, and trading protocols. ### [hashtag](https://docs.somnia.network/developer/deployment-and-production/ecosystem/ecosystem-tools/oracles#resources) Resources * [Somnia Price Oraclesarrow-up-right](https://docs.diadata.org/use-nexus-product/how-to-dia-nexus-oracles/access-the-oracle/somnia-price-oracles) * [How to Integrate DIA Price Feeds](https://docs.somnia.network/developer/building-dapps/oracles/dia-price-feeds) * * * [hashtag](https://docs.somnia.network/developer/deployment-and-production/ecosystem/ecosystem-tools/oracles#protofire) [Protofirearrow-up-right](http://protofire.io/) ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- Protofire deploys custom, compatible oracles using the same data providers and node operators, allowing protocols to connect to their network without modifying Smart Contracts. ### [hashtag](https://docs.somnia.network/developer/deployment-and-production/ecosystem/ecosystem-tools/oracles#resources-1) Resources * [Price Feeds](https://docs.somnia.network/developer/building-dapps/oracles/protofire-price-feeds) * [Verifiable Randomness Function](https://docs.somnia.network/developer/building-dapps/oracles/using-verifiable-randomness-vrf) [PreviousRPCchevron-left](https://docs.somnia.network/developer/deployment-and-production/ecosystem/ecosystem-tools/rpc) [NextSubgraphschevron-right](https://docs.somnia.network/developer/deployment-and-production/ecosystem/ecosystem-tools/subgraphs) Last updated 1 month ago * [DIA](https://docs.somnia.network/developer/deployment-and-production/ecosystem/ecosystem-tools/oracles#dia) * [Resources](https://docs.somnia.network/developer/deployment-and-production/ecosystem/ecosystem-tools/oracles#resources) * [Protofire](https://docs.somnia.network/developer/deployment-and-production/ecosystem/ecosystem-tools/oracles#protofire) * [Resources](https://docs.somnia.network/developer/deployment-and-production/ecosystem/ecosystem-tools/oracles#resources-1) --- # Ormi Subgraph | Somnia Docs The Graph is a decentralized indexing protocol that allows developers to query blockchain data using GraphQL. Instead of parsing complex raw logs and events directly from smart contracts, developers can build subgraphs that transform onchain activity into structured, queryable datasets. This tutorial demonstrates how to deploy a subgraph on the Somnia Testnet using [Ormiarrow-up-right](https://ormilabs.com/) , a powerful gateway that simplifies subgraph deployment through a hosted Graph Node and IPFS infrastructure. [hashtag](https://docs.somnia.network/developer/building-dapps/data-indexing-and-querying/ormi-subgraph#prerequisites) Prerequisites ----------------------------------------------------------------------------------------------------------------------------------------- * GraphQL is installed and set up on your local machine. * A verified smart contract address deployed on Somnia. * An Ormi account and Private Key. [hashtag](https://docs.somnia.network/developer/building-dapps/data-indexing-and-querying/ormi-subgraph#install-graph-cli-globally) Install Graph CLI globally ------------------------------------------------------------------------------------------------------------------------------------------------------------------- [hashtag](https://docs.somnia.network/developer/building-dapps/data-indexing-and-querying/ormi-subgraph#initialize-your-subgraph) Initialize Your Subgraph --------------------------------------------------------------------------------------------------------------------------------------------------------------- The Graph services rely on the existence of a deployed Smart Contract with onchain activity. The subgraph will be created based on indexing the events emitted from the Smart Contract. To set up a subgraph service for an example contract called `**MyToken**` run the following command to scaffold a new subgraph project: `**mytoken**` is the folder that contains the subgraph files. Replace `0xYourTokenAddress` with your actual deployed Smart Contract address on Somnia. This command will generate the following files: * `subgraph.yaml`Defines the data sources and events to index * `schema.graphql` Structure of your data * `src/mytoken.ts`TypeScript logic to handle events [hashtag](https://docs.somnia.network/developer/building-dapps/data-indexing-and-querying/ormi-subgraph#define-the-subgraph-schema) Define the Subgraph Schema ------------------------------------------------------------------------------------------------------------------------------------------------------------------- For the example`MyToken` Contract, which is an ERC20 Token, Edit `schema.graphql` to index all the Transfer events emitted from the Smart Contract. [hashtag](https://docs.somnia.network/developer/building-dapps/data-indexing-and-querying/ormi-subgraph#build-the-subgraph) Build the Subgraph --------------------------------------------------------------------------------------------------------------------------------------------------- After customizing your schema and mapping logic, build the subgraph by running the command: This will generate the necessary `artifacts` for deployment. [hashtag](https://docs.somnia.network/developer/building-dapps/data-indexing-and-querying/ormi-subgraph#deploy-using-ormi) Deploy Using Ormi ------------------------------------------------------------------------------------------------------------------------------------------------- Open the Somnia Ormi website [https://subgraph.somnia.network/arrow-up-right](https://subgraph.somnia.network/) and create an account. ![](https://docs.somnia.network/~gitbook/image?url=https%3A%2F%2F2122549367-files.gitbook.io%2F%7E%2Ffiles%2Fv0%2Fb%2Fgitbook-x-prod.appspot.com%2Fo%2Fspaces%252FkYErT9t3BJtpPfejLO6I%252Fuploads%252FAm7Rs07ZpEQcTVVmaI4q%252FOrmi-Login.png%3Falt%3Dmedia%26token%3D7e677985-72ea-446a-8697-8dc96ac2371c&width=768&dpr=3&quality=100&sign=a60c831f&sv=2) On the left navigation menu, click the "key" icon to access your `privateKey.` ![](https://docs.somnia.network/~gitbook/image?url=https%3A%2F%2F2122549367-files.gitbook.io%2F%7E%2Ffiles%2Fv0%2Fb%2Fgitbook-x-prod.appspot.com%2Fo%2Fspaces%252FkYErT9t3BJtpPfejLO6I%252Fuploads%252FOxEJLWk2vP58mYRizSZh%252FOrmi-pKey.png%3Falt%3Dmedia%26token%3D2cf1a5d2-fbe7-4c0a-b27c-89d8950c909d&width=768&dpr=3&quality=100&sign=c7b45bde&sv=2) Deploy your subgraph to Ormi’s hosted infrastructure with the following command: Replace yourPrivateKey with your Somnia Ormi account private key. Once deployed, Ormi will return a GraphQL endpoint where you can begin querying your subgraph. Return to the dashboard to find your list of deployed subgraphs. ![](https://docs.somnia.network/~gitbook/image?url=https%3A%2F%2F2122549367-files.gitbook.io%2F%7E%2Ffiles%2Fv0%2Fb%2Fgitbook-x-prod.appspot.com%2Fo%2Fspaces%252FkYErT9t3BJtpPfejLO6I%252Fuploads%252F9oMGwE0EgK52fFmIQeZm%252FOrmi-dash.png%3Falt%3Dmedia%26token%3D549aaf6c-5999-4fc5-be3b-10de24c9dc74&width=768&dpr=3&quality=100&sign=ec5b46c4&sv=2) Open the deployed subgraph in the explorer to interact with it: ![](https://docs.somnia.network/~gitbook/image?url=https%3A%2F%2F2122549367-files.gitbook.io%2F%7E%2Ffiles%2Fv0%2Fb%2Fgitbook-x-prod.appspot.com%2Fo%2Fspaces%252FkYErT9t3BJtpPfejLO6I%252Fuploads%252FMzzQXDCRdUcvrpiLQkW4%252Formi-explorer.png%3Falt%3Dmedia%26token%3D8b9f0dcf-7424-49c1-a3d1-510d6a89e324&width=768&dpr=3&quality=100&sign=bee5bc55&sv=2) [hashtag](https://docs.somnia.network/developer/building-dapps/data-indexing-and-querying/ormi-subgraph#conclusion) Conclusion ----------------------------------------------------------------------------------------------------------------------------------- You have successfully deployed a subgraph to index events emitted from your Smart Contract. To challenge yourself even further, you can extend your build: * Expand your schema and mapping logic to cover more events. * Connect your subgraph to a frontend UI or analytics dashboard. For more information, visit the Ormi [docsarrow-up-right](https://docs.ormilabs.com/dedicated-env/somnia/subgraphs/overview) . [PreviousData Indexing and Queryingchevron-left](https://docs.somnia.network/developer/building-dapps/data-indexing-and-querying) [NextProtofire Subgraphchevron-right](https://docs.somnia.network/developer/building-dapps/data-indexing-and-querying/protofire-subgraph) Last updated 5 months ago * [Prerequisites](https://docs.somnia.network/developer/building-dapps/data-indexing-and-querying/ormi-subgraph#prerequisites) * [Install Graph CLI globally](https://docs.somnia.network/developer/building-dapps/data-indexing-and-querying/ormi-subgraph#install-graph-cli-globally) * [Initialize Your Subgraph](https://docs.somnia.network/developer/building-dapps/data-indexing-and-querying/ormi-subgraph#initialize-your-subgraph) * [Define the Subgraph Schema](https://docs.somnia.network/developer/building-dapps/data-indexing-and-querying/ormi-subgraph#define-the-subgraph-schema) * [Build the Subgraph](https://docs.somnia.network/developer/building-dapps/data-indexing-and-querying/ormi-subgraph#build-the-subgraph) * [Deploy Using Ormi](https://docs.somnia.network/developer/building-dapps/data-indexing-and-querying/ormi-subgraph#deploy-using-ormi) * [Conclusion](https://docs.somnia.network/developer/building-dapps/data-indexing-and-querying/ormi-subgraph#conclusion) Copy npm install -g @graphprotocol/graph-cli Copy graph init --contract-name MyToken --from-contract 0xYourTokenAddress --network somnia-testnet mytoken Copy type Transfer @entity(immutable: true) { id: Bytes! from: Bytes! to: Bytes! value: BigInt! blockNumber: BigInt! blockTimestamp: BigInt! transactionHash: Bytes! } Copy graph codegen && graph build Copy graph deploy mytoken --node https://api.subgraph.somnia.network/deploy --ipfs https://api.subgraph.somnia.network/ipfs --deploy-key yourORMIPrivateKey --- # Building Subgraph UIs (Apollo Client) | Somnia Docs This guide will teach you how to create a minimal, functional UI that queries blockchain data from a Somnia subgraph using Next.js, Apollo Client, and GraphQL. [hashtag](https://docs.somnia.network/developer/building-dapps/data-indexing-and-querying/building-subgraph-uis-apollo-client#prerequisites) Prerequisites --------------------------------------------------------------------------------------------------------------------------------------------------------------- * Basic knowledge of React and Next.js * Node.js 20+ * A deployed subgraph on Somnia (we'll use [SomFliparrow-up-right](https://shannon-explorer.somnia.network/address/0x014F851965F281d6112FC7F6dfe8c331C413Eb9b) as an example) [hashtag](https://docs.somnia.network/developer/building-dapps/data-indexing-and-querying/building-subgraph-uis-apollo-client#what-youll-build) What You'll Build ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- A clean, minimal interface that: * Displays all coin flip results with pagination * Shows a live feed that auto-refreshes every 5 seconds [hashtag](https://docs.somnia.network/developer/building-dapps/data-indexing-and-querying/building-subgraph-uis-apollo-client#create-a-next.js-project) Create a Next.js Project ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- Start by creating a new Next.js application with TypeScript and TailwindCSS: Copy npx create-next-app@latest somnia-subgraph-ui --typescript --tailwind --app cd somnia-subgraph-ui Install the required GraphQL dependencies: Copy npm install @apollo/client graphql [hashtag](https://docs.somnia.network/developer/building-dapps/data-indexing-and-querying/building-subgraph-uis-apollo-client#understand-the-architecture) Understand the Architecture ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- Before we code, let's understand how the pieces fit together: [hashtag](https://docs.somnia.network/developer/building-dapps/data-indexing-and-querying/building-subgraph-uis-apollo-client#set-up-apollo-client) Set Up Apollo Client ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- Apollo Client is a comprehensive GraphQL client that manages data fetching, caching, and state management. Create a `lib` directory and create a file `apollo-client.ts` The `URI` is the endpoint where your subgraph is hosted, and `InMemoryCache` will store the query results in memory for fast access [hashtag](https://docs.somnia.network/developer/building-dapps/data-indexing-and-querying/building-subgraph-uis-apollo-client#create-the-apollo-provider-wrapper) Create the Apollo Provider Wrapper --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- React components need access to the Apollo Client. We'll create a wrapper component that provides this access to the entire app. Create a `components` directory and create a file `ApolloWrapper.tsx`. ApolloProvider: Makes the Apollo Client available to all child components [hashtag](https://docs.somnia.network/developer/building-dapps/data-indexing-and-querying/building-subgraph-uis-apollo-client#update-app-layout.tsx) Update app/layout.tsx ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- [hashtag](https://docs.somnia.network/developer/building-dapps/data-indexing-and-querying/building-subgraph-uis-apollo-client#create-graphql-queries) Create GraphQL Queries --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- GraphQL queries define exactly what data you want from the subgraph. Let's create queries for our two main features. In the `lib` directory create a `queries.ts` file. chevron-rightqueries.ts[hashtag](https://docs.somnia.network/developer/building-dapps/data-indexing-and-querying/building-subgraph-uis-apollo-client#queries.ts) Note the following: * `gql` is the template literal tag that parses GraphQL queries * Variables start with $ and have types (Int!, String!, etc.) * ! means the field is required (non-nullable) [hashtag](https://docs.somnia.network/developer/building-dapps/data-indexing-and-querying/building-subgraph-uis-apollo-client#build-the-all-flips-component) Build the All Flips Component ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- Let's build the All Flips component step by step, understanding each part in detail. ### [hashtag](https://docs.somnia.network/developer/building-dapps/data-indexing-and-querying/building-subgraph-uis-apollo-client#set-up-the-component-file) Set Up the Component File In the `components` directory create `AllFlips.tsx` file and add the following imports. ### [hashtag](https://docs.somnia.network/developer/building-dapps/data-indexing-and-querying/building-subgraph-uis-apollo-client#create-utility-functions) Create Utility Functions Add these helper functions at the top of your component: ### [hashtag](https://docs.somnia.network/developer/building-dapps/data-indexing-and-querying/building-subgraph-uis-apollo-client#component-function-and-state-management) Component Function and State Management `the number ofpage` tracks the current page number (starting at 0), which is used to calculate the number of results to skip. It updates when the user clicks the Previous/Next button. ### [hashtag](https://docs.somnia.network/developer/building-dapps/data-indexing-and-querying/building-subgraph-uis-apollo-client#execute-the-graphql-query) Execute the GraphQL Query The `useQuery` function is set to `loading: true` while fetching `data` and the `data` contains the query results when successful. ### [hashtag](https://docs.somnia.network/developer/building-dapps/data-indexing-and-querying/building-subgraph-uis-apollo-client#handle-query-states) Handle Query States This prevents rendering errors and andles edge cases gracefully ### [hashtag](https://docs.somnia.network/developer/building-dapps/data-indexing-and-querying/building-subgraph-uis-apollo-client#render-the-table-view) Render the Table View chevron-rightAllFlips.tsx - Table View[hashtag](https://docs.somnia.network/developer/building-dapps/data-indexing-and-querying/building-subgraph-uis-apollo-client#allflips.tsx-table-view) [hashtag](https://docs.somnia.network/developer/building-dapps/data-indexing-and-querying/building-subgraph-uis-apollo-client#build-the-live-feed-component) Build the Live Feed Component ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- Now let's build the Live Feed component that automatically refreshes to show new flips. ### [hashtag](https://docs.somnia.network/developer/building-dapps/data-indexing-and-querying/building-subgraph-uis-apollo-client#set-up-the-component) Set Up the Component In the `components` directory create a `LiveFeed.tsx` file and update the imports. ### [hashtag](https://docs.somnia.network/developer/building-dapps/data-indexing-and-querying/building-subgraph-uis-apollo-client#create-utility-functions-1) Create Utility Functions ### [hashtag](https://docs.somnia.network/developer/building-dapps/data-indexing-and-querying/building-subgraph-uis-apollo-client#component-function-with-auto-refresh) Component Function with Auto-Refresh The `pollInterval` automatically re-executes the query every 5 seconds. New flips appear without user interaction with Apollo Client handling the refresh logic. You can set to 0 or remove to disable auto-refresh ### [hashtag](https://docs.somnia.network/developer/building-dapps/data-indexing-and-querying/building-subgraph-uis-apollo-client#handle-query-states-1) Handle Query States ### [hashtag](https://docs.somnia.network/developer/building-dapps/data-indexing-and-querying/building-subgraph-uis-apollo-client#complete-live-feed-component) Complete Live Feed Component chevron-rightLiveFeed.tsx[hashtag](https://docs.somnia.network/developer/building-dapps/data-indexing-and-querying/building-subgraph-uis-apollo-client#livefeed.tsx) The key differences from the AllFlips page are that there is no pagination (shows most recent only), and it auto-refreshes with pollInterval, with a visual emphasis on win/loss status. [hashtag](https://docs.somnia.network/developer/building-dapps/data-indexing-and-querying/building-subgraph-uis-apollo-client#update-the-main-page.tsx) Update the Main Page.tsx ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- chevron-rightpage.tsx[hashtag](https://docs.somnia.network/developer/building-dapps/data-indexing-and-querying/building-subgraph-uis-apollo-client#page.tsx) [hashtag](https://docs.somnia.network/developer/building-dapps/data-indexing-and-querying/building-subgraph-uis-apollo-client#run-your-application) Run Your Application ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- Visit `http://localhost:3000` to see your UI in action. [PreviousBuilding Subgraph UIs (NextJS/Fetch)chevron-left](https://docs.somnia.network/developer/building-dapps/data-indexing-and-querying/building-subgraph-uis-nextjs-fetch) [NextUsing Data APIs (Ormi)chevron-right](https://docs.somnia.network/developer/building-dapps/data-indexing-and-querying/using-data-apis-ormi) Last updated 1 month ago * [Prerequisites](https://docs.somnia.network/developer/building-dapps/data-indexing-and-querying/building-subgraph-uis-apollo-client#prerequisites) * [What You'll Build](https://docs.somnia.network/developer/building-dapps/data-indexing-and-querying/building-subgraph-uis-apollo-client#what-youll-build) * [Create a Next.js Project](https://docs.somnia.network/developer/building-dapps/data-indexing-and-querying/building-subgraph-uis-apollo-client#create-a-next.js-project) * [Understand the Architecture](https://docs.somnia.network/developer/building-dapps/data-indexing-and-querying/building-subgraph-uis-apollo-client#understand-the-architecture) * [Set Up Apollo Client](https://docs.somnia.network/developer/building-dapps/data-indexing-and-querying/building-subgraph-uis-apollo-client#set-up-apollo-client) * [Create the Apollo Provider Wrapper](https://docs.somnia.network/developer/building-dapps/data-indexing-and-querying/building-subgraph-uis-apollo-client#create-the-apollo-provider-wrapper) * [Update app/layout.tsx](https://docs.somnia.network/developer/building-dapps/data-indexing-and-querying/building-subgraph-uis-apollo-client#update-app-layout.tsx) * [Create GraphQL Queries](https://docs.somnia.network/developer/building-dapps/data-indexing-and-querying/building-subgraph-uis-apollo-client#create-graphql-queries) * [Build the All Flips Component](https://docs.somnia.network/developer/building-dapps/data-indexing-and-querying/building-subgraph-uis-apollo-client#build-the-all-flips-component) * [Set Up the Component File](https://docs.somnia.network/developer/building-dapps/data-indexing-and-querying/building-subgraph-uis-apollo-client#set-up-the-component-file) * [Create Utility Functions](https://docs.somnia.network/developer/building-dapps/data-indexing-and-querying/building-subgraph-uis-apollo-client#create-utility-functions) * [Component Function and State Management](https://docs.somnia.network/developer/building-dapps/data-indexing-and-querying/building-subgraph-uis-apollo-client#component-function-and-state-management) * [Execute the GraphQL Query](https://docs.somnia.network/developer/building-dapps/data-indexing-and-querying/building-subgraph-uis-apollo-client#execute-the-graphql-query) * [Handle Query States](https://docs.somnia.network/developer/building-dapps/data-indexing-and-querying/building-subgraph-uis-apollo-client#handle-query-states) * [Render the Table View](https://docs.somnia.network/developer/building-dapps/data-indexing-and-querying/building-subgraph-uis-apollo-client#render-the-table-view) * [Build the Live Feed Component](https://docs.somnia.network/developer/building-dapps/data-indexing-and-querying/building-subgraph-uis-apollo-client#build-the-live-feed-component) * [Set Up the Component](https://docs.somnia.network/developer/building-dapps/data-indexing-and-querying/building-subgraph-uis-apollo-client#set-up-the-component) * [Create Utility Functions](https://docs.somnia.network/developer/building-dapps/data-indexing-and-querying/building-subgraph-uis-apollo-client#create-utility-functions-1) * [Component Function with Auto-Refresh](https://docs.somnia.network/developer/building-dapps/data-indexing-and-querying/building-subgraph-uis-apollo-client#component-function-with-auto-refresh) * [Handle Query States](https://docs.somnia.network/developer/building-dapps/data-indexing-and-querying/building-subgraph-uis-apollo-client#handle-query-states-1) * [Complete Live Feed Component](https://docs.somnia.network/developer/building-dapps/data-indexing-and-querying/building-subgraph-uis-apollo-client#complete-live-feed-component) * [Update the Main Page.tsx](https://docs.somnia.network/developer/building-dapps/data-indexing-and-querying/building-subgraph-uis-apollo-client#update-the-main-page.tsx) * [Run Your Application](https://docs.somnia.network/developer/building-dapps/data-indexing-and-querying/building-subgraph-uis-apollo-client#run-your-application) Copy User Interface (React Components) ↓ Apollo Client (GraphQL Client) ↓ GraphQL Queries ↓ Somnia Subgraph API ↓ Blockchain Data Copy import { ApolloClient, InMemoryCache } from '@apollo/client'; const client = new ApolloClient({ // The URI of your subgraph endpoint uri: 'https://proxy.somnia.chain.love/subgraphs/name/somnia-testnet/SomFlip', // Apollo's caching layer - stores query results cache: new InMemoryCache(), }); export default client; Copy 'use client'; // Next.js 13+ directive for client-side components import { ApolloProvider } from '@apollo/client'; import client from '@/lib/apollo-client'; // This component wraps your app with Apollo's context provider export default function ApolloWrapper({ children }: { children: React.ReactNode }) { return ( {children} ); } Copy import ApolloWrapper from '@/components/ApolloWrapper'; export default function RootLayout({ children, }: { children: React.ReactNode; }) { return ( {children} ); } Copy import { gql } from '@apollo/client'; // Query for paginated flip results export const GET_FLIP_RESULTS = gql` query GetFlipResults( $first: Int!, # Number of results to fetch $skip: Int!, # Number of results to skip (for pagination) $orderBy: String!, # Field to sort by $orderDirection: String! # 'asc' or 'desc' ) { flipResults( first: $first skip: $skip orderBy: $orderBy orderDirection: $orderDirection ) { id # Unique identifier player # Wallet address of player betAmount # Amount bet (in wei) choice # Player's choice: HEADS or TAILS result # Actual result: HEADS or TAILS payout # Amount won (0 if lost) blockNumber # Block when flip occurred blockTimestamp # Unix timestamp transactionHash # Transaction hash on blockchain } } `; // Query for recent flips (live feed) export const GET_RECENT_FLIPS = gql` query GetRecentFlips($first: Int!) { flipResults( first: $first orderBy: blockTimestamp orderDirection: desc # Most recent first ) { id player betAmount choice result payout blockTimestamp transactionHash } } `; Copy 'use client'; import { useState } from 'react'; import { useQuery } from '@apollo/client'; import { GET_FLIP_RESULTS } from '@/lib/queries'; Copy / Shortens long blockchain addresses for display // Example: "0x1234567890abcdef" becomes "0x1234...cdef" const truncateHash = (hash: string) => { return `${hash.slice(0, 6)}...${hash.slice(-4)}`; }; // Converts wei (smallest unit) to ether (display unit) // 1 ether = 1,000,000,000,000,000,000 wei (10^18) const formatEther = (wei: string) => { const ether = parseFloat(wei) / 1e18; return ether.toFixed(4); // Show 4 decimal places }; // Converts Unix timestamp to readable date // Blockchain stores time as seconds since Jan 1, 1970 const formatTime = (timestamp: string) => { const milliseconds = parseInt(timestamp) * 1000; const date = new Date(milliseconds); return date.toLocaleString(); }; Copy export default function AllFlips() { // Track which page of results we're viewing const [page, setPage] = useState(0); const itemsPerPage = 30; Copy const { loading, error, data } = useQuery(GET_FLIP_RESULTS, { variables: { first: itemsPerPage, // How many results to fetch skip: page * itemsPerPage, // How many to skip orderBy: 'blockTimestamp', // Sort by time orderDirection: 'desc', // Newest first }, }); Copy // Show loading spinner while fetching if (loading) { return
Loading...
; } // Show error message if query failed if (error) { return (
Error: {error.message}
); } // Check if we have results if (!data?.flipResults?.length) { return
No flips found
; } Copy return (
{/* Makes table scrollable on mobile */} {data.flipResults.map((flip: any) => ( {/* Player address - truncated for readability */} {/* Bet amount - converted from wei to ether */} {/* Player's choice - color coded */} {/* Actual result - same color coding */} {/* Payout - green if won, gray if lost */} {/* Timestamp - converted to readable date */} ))}
Player Bet Choice Result Payout Time
{truncateHash(flip.player)} {formatEther(flip.betAmount)} STT {flip.choice} {flip.result} {flip.payout !== '0' ? `+${formatEther(flip.payout)}` : '0' } STT {formatTime(flip.blockTimestamp)}
{/* Previous button */} {/* Current page indicator */} Page {page + 1} {/* Next button */}
); } Copy 'use client'; import { useQuery } from '@apollo/client'; import { GET_RECENT_FLIPS } from '@/lib/queries'; Copy const truncateHash = (hash: string) => { return `${hash.slice(0, 6)}...${hash.slice(-4)}`; }; const formatEther = (wei: string) => { return (parseFloat(wei) / 1e18).toFixed(4); }; Copy export default function LiveFeed() { // Execute query with automatic polling const { loading, error, data } = useQuery(GET_RECENT_FLIPS, { variables: { first: 10 // Get 10 most recent flips }, pollInterval: 5000, // Refresh every 5 seconds (5000ms) }); Copy // Same loading/error handling as AllFlips if (loading) { return
Loading...
; } if (error) { return
Error: {error.message}
; } if (!data?.flipResults?.length) { return
No recent flips
; } Copy 'use client'; import { useQuery } from '@apollo/client'; import { GET_RECENT_FLIPS } from '@/lib/queries'; const truncateHash = (hash: string) => `${hash.slice(0, 6)}...${hash.slice(-4)}`; const formatEther = (wei: string) => (parseFloat(wei) / 1e18).toFixed(4); export default function LiveFeed() { const { loading, error, data } = useQuery(GET_RECENT_FLIPS, { variables: { first: 10 }, pollInterval: 5000, }); if (loading) return
Loading...
; if (error) return
Error: {error.message}
; if (!data?.flipResults?.length) return
No recent flips
; return (
{data.flipResults.map((flip: any) => { const won = flip.payout !== '0'; return (
{truncateHash(flip.player)} bet {formatEther(flip.betAmount)} STT
{flip.choice} {flip.result}
{won ? `Won ${formatEther(flip.payout)} STT` : 'Lost'}
); })}

Auto-refreshing every 5 seconds

); } Copy 'use client'; import { useState } from 'react'; import AllFlips from '@/components/AllFlips'; import LiveFeed from '@/components/LiveFeed'; export default function Home() { const [activeTab, setActiveTab] = useState('allFlips'); return (

SomFlip

{/* Tab Navigation */}
{/* Conditional Rendering Based on Active Tab */} {activeTab === 'allFlips' ? : }
); } Copy npm run dev --- # Using Verifiable Randomness (VRF) | Somnia Docs Protofire Chainlink’s Verifiable Random Function (VRF) allows developers to securely request random numbers in a tamper-proof and auditable way. It is ideal for gaming, NFT mints, and lotteries. This tutorial walks you through integrating Protofire's Chainlink VRF v2.5 on Somnia Network, using native STT (Somnia Token) as the payment currency. [hashtag](https://docs.somnia.network/developer/building-dapps/oracles/using-verifiable-randomness-vrf#mainnet-vrf-smart-contracts) Mainnet VRF Smart Contracts -------------------------------------------------------------------------------------------------------------------------------------------------------------------- Contract Address VRFV2PlusWrapper [0x606b2B36516AB7479D1445Ec14B6B39B44901bf8arrow-up-right](https://explorer.somnia.network/address/0x606b2B36516AB7479D1445Ec14B6B39B44901bf8) LINK Token [0x0a4Db7035284566F6f676991ED418140dC01A2aaarrow-up-right](https://explorer.somnia.network/address/0x0a4Db7035284566F6f676991ED418140dC01A2aa) LINK/NATIVE oracle [0xEBD41881413dD76F42DF2902ee865099af9099B4arrow-up-right](https://explorer.somnia.network/address/0xEBD41881413dD76F42DF2902ee865099af9099B4) [hashtag](https://docs.somnia.network/developer/building-dapps/oracles/using-verifiable-randomness-vrf#testnet-vrf-smart-contracts) Testnet VRF Smart Contracts -------------------------------------------------------------------------------------------------------------------------------------------------------------------- Contract Address VRFV2PlusWrapper [0x763cC914d5CA79B04dC4787aC14CcAd780a16BD2arrow-up-right](https://shannon-explorer.somnia.network/address/0x763cC914d5CA79B04dC4787aC14CcAd780a16BD2) LINK Token [0x30C75a2badF9b12733e831fcb5315C8f54e96f6darrow-up-right](https://shannon-explorer.somnia.network/address/0x30C75a2badF9b12733e831fcb5315C8f54e96f6d) LINK/NATIVE oracle [0xEc00df0e834AB878135b6554bb7438A2Ff66563barrow-up-right](https://shannon-explorer.somnia.network/address/0xEc00df0e834AB878135b6554bb7438A2Ff66563b) [hashtag](https://docs.somnia.network/developer/building-dapps/oracles/using-verifiable-randomness-vrf#understanding-vrf-and-why-it-matters) Understanding VRF and Why It Matters -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- Randomness is essential for many blockchain applications, such as Games, Lotteries, Raffles, and NFT drops, but blockchains are deterministic by nature. This means every node must produce the same output given the same inputs. If you try to use on-chain data like `block.timestamp` or `blockhash` as a random source, miners/validators can manipulate these values to influence the outcome. This is where VRF comes in. ### [hashtag](https://docs.somnia.network/developer/building-dapps/oracles/using-verifiable-randomness-vrf#what-is-vrf) What is VRF? A Verifiable Random Function (VRF) is a cryptographic method of generating random numbers along with a proof that the result was not tampered with. When using Protofire Chainlink VRF: 1. You request a random number from the VRF service. 2. Protofire Chainlink’s decentralized oracle network generates a random value off-chain along with a cryptographic proof. 3. The proof is verified on-chain before the value is returned to your contract. This ensures tamper-proof randomness and publicly verifiable results. Where the outcomes are fair. ### [hashtag](https://docs.somnia.network/developer/building-dapps/oracles/using-verifiable-randomness-vrf#why-is-vrf-important-on-blockchain) Why is VRF important on blockchain? Without VRF, randomness in blockchain apps can be gamed. With VRF: * No single party can manipulate the results * Users can independently verify the randomness * Applications gain trust from players, participants, and investors Requesting VRF Data using Protofire Chainlink services relies on two methods: Subscription and Direct Funding In the Subscription method, Chainlink VRF requests receive funding from subscription accounts. The [Subscription Managerarrow-up-right](https://vrf.chain.link/) lets you create an account and pre-pay for your use of Chainlink VRF requests. You can learn more about the subscription method by referencing the Chainlink [documentationarrow-up-right](https://docs.chain.link/vrf/v2-5/overview/subscription) . The Direct Funding method doesn't require a subscription and is optimal for one-off requests for randomness. This method also works best for applications where your end-users must pay the fees for VRF because the cost of the request is determined at request time. [Learn morearrow-up-right](https://docs.chain.link/vrf/v2-5/overview/direct-funding) . In this guide, we will build a Smart Contract called `RandomNumberConsumer` that: * Inherits the Protofire Chainlink VRF Wrapper. * Requests 3 secure random numbers * Pays for randomness using native STT (no LINK subscription required) * Emits events and exposes functions to retrieve the randomness * Handles overpayments and pending request checks [hashtag](https://docs.somnia.network/developer/building-dapps/oracles/using-verifiable-randomness-vrf#prerequisites) Prerequisites ---------------------------------------------------------------------------------------------------------------------------------------- Before getting started: * You are familiar with Solidity (v0.8+) * You have the VRF Wrapper address for [Protofire ChainLink VRF Wrapperarrow-up-right](https://shannon-explorer.somnia.network/address/0x763cC914d5CA79B04dC4787aC14CcAd780a16BD2) [hashtag](https://docs.somnia.network/developer/building-dapps/oracles/using-verifiable-randomness-vrf#tl-dr) TL;DR ------------------------------------------------------------------------------------------------------------------------ 1. Owner calls `requestRandomNumber()` and sends enough STT `(msg.value)` to cover the fee. 2. Contract uses **VRF Wrapper** to request 3 random words (in native STT). 3. When VRF is ready, the wrapper calls `fulfillRandomWords`, the contract: 1. verifies the request, 2. stores the 3 words, 3. toggles `fulfilled = true`, 4. emits RandomNumberFulfilled. 4. User Interfaces and Scripts can read `getLatestRandomWord()` or poll `getRequestStatus()`. chevron-rightEXAMPLE - RandomNumberConsumer.sol[hashtag](https://docs.somnia.network/developer/building-dapps/oracles/using-verifiable-randomness-vrf#example-randomnumberconsumer.sol) [hashtag](https://docs.somnia.network/developer/building-dapps/oracles/using-verifiable-randomness-vrf#code-breakdown) Code Breakdown ------------------------------------------------------------------------------------------------------------------------------------------ `VRFV2PlusWrapperConsumerBase` gives you the glue code for requesting randomness from the VRF Wrapper and receiving the callback `(fulfillRandomWords)`. It also exposes the wrapper instance `i_vrfV2PlusWrapper`. `ConfirmedOwner` is a lightweight ownership module; it lets you restrict actions to the contract owner via onlyOwner. [hashtag](https://docs.somnia.network/developer/building-dapps/oracles/using-verifiable-randomness-vrf#state-variables) State Variables -------------------------------------------------------------------------------------------------------------------------------------------- `latestRequestId` tracks the most recent VRF request ID. Used to make sure the fulfillment we receive matches the last request. `latestRandomWord` stores the three random words returned by VRF for the latest request. `fulfilled` marks whether the latest request has finished (prevents overlapping requests and makes UI/state checks easy). [hashtag](https://docs.somnia.network/developer/building-dapps/oracles/using-verifiable-randomness-vrf#vrf-request-parameters-constants) VRF Request Parameters (constants) -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- `CALLBACK_GAS_LIMIT` is the max gas VRF can use when calling your `fulfillRandomWords`. Must be large enough for your logic. This example uses headroom for 3 words. `REQUEST_CONFIRMATIONS` is how many blocks to wait before fulfillment (trade-off between speed and reorg safety). `NUM_WORDS` This is how many random numbers you want per request. Here, it’s 3. [hashtag](https://docs.somnia.network/developer/building-dapps/oracles/using-verifiable-randomness-vrf#events) Events -------------------------------------------------------------------------------------------------------------------------- `RandomNumberRequested` is emitted right after submitting a VRF request. Includes the paid cost in native STT. `RandomNumberFulfilled` is emitted when VRF returns the result, with the three random words. Events make it easy to monitor behavior from explorers, indexers, or frontends. [hashtag](https://docs.somnia.network/developer/building-dapps/oracles/using-verifiable-randomness-vrf#custom-errors) Custom errors ---------------------------------------------------------------------------------------------------------------------------------------- `InsufficientPayment` is thrown if msg.value doesn’t cover the VRF native fee at request time. `RequestAlreadyPending` is thrown if you try to request again while the previous request hasn’t been fulfilled. Errors are cheaper than require("string") and clearer to reason about. [hashtag](https://docs.somnia.network/developer/building-dapps/oracles/using-verifiable-randomness-vrf#constructor) Constructor ------------------------------------------------------------------------------------------------------------------------------------ Takes the VRF V2+ Wrapper address (the on-chain contract that mediates VRF requests) and initializes ownership to the deployer. [hashtag](https://docs.somnia.network/developer/building-dapps/oracles/using-verifiable-randomness-vrf#function-requesting-randomness) Function Requesting Randomness -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- The `requestRandomNumber()` function implements safeguards and processes to ensure reliable VRF operation. First, it enforces safety by preventing spam or overlapping requests, which ensures a predictable user experience and maintains simpler state management. The function then calculates the exact payment required by calling `getRequestPrice()` to determine how much STT the wrapper currently needs, rejecting any transaction with insufficient payment. To specify the payment method, it encodes `nativePayment: true` in the request parameters, instructing the wrapper to charge in native STT tokens rather than LINK. Once validated, the function submits the request through `requestRandomnessPayInNative()`, which initiates the VRF request to Chainlink while storing the returned requestId and marking the fulfilled status as false to track the pending request. Finally, the function implements automatic refund logic that returns any excess funds to the user if they overpaid, ensuring users never lose funds due to price variations. [hashtag](https://docs.somnia.network/developer/building-dapps/oracles/using-verifiable-randomness-vrf#read-operations) READ OPERATIONS -------------------------------------------------------------------------------------------------------------------------------------------- ### [hashtag](https://docs.somnia.network/developer/building-dapps/oracles/using-verifiable-randomness-vrf#vrf-callback-fulfillment) VRF callback (fulfillment) Called by the VRF Wrapper (not by you) and validates that we actually received words, and the `requestId` matches the latest request (guards against stale/foreign callbacks). It then stores the 3 words and flips `fulfilled = true` and emits a completion event. If you need game logic, derive from these random words inside this function or store and consume later. ### [hashtag](https://docs.somnia.network/developer/building-dapps/oracles/using-verifiable-randomness-vrf#getrequeststatus) getRequestStatus() For frontends/monitoring: see the last `request ID`, whether it’s still `pending`, and whether it was `fulfilled`. ### [hashtag](https://docs.somnia.network/developer/building-dapps/oracles/using-verifiable-randomness-vrf#getlatestrandomword) getLatestRandomWord() Returns the three words from the most recent fulfilled request, which is actually a string on numbers for example: `93869141573160465677701763703933181905260360385351294458479680637737009096153` ### [hashtag](https://docs.somnia.network/developer/building-dapps/oracles/using-verifiable-randomness-vrf#pricing-helper) Pricing Helper Asks the wrapper how much STT (in wei) you need right now for a request with your chosen `CALLBACK_GAS_LIMIT` and `NUM_WORDS`. Use this in your UI or scripts to fill `msg.value`. [hashtag](https://docs.somnia.network/developer/building-dapps/oracles/using-verifiable-randomness-vrf#conclusion) Conclusion ---------------------------------------------------------------------------------------------------------------------------------- You've successfully built a secure random number generator on Somnia using Chainlink VRF v2.5. Your `RandomNumberConsumer` Smart Contract provides tamper proof randomness with native STT payment, automatic refunds, and proper request management, everything needed for production use. ### [hashtag](https://docs.somnia.network/developer/building-dapps/oracles/using-verifiable-randomness-vrf#real-world-vrf-use-cases) Real-World VRF Use Cases VRF powers a wide range of blockchain applications where fairness is critical. In gaming, it enables trustworthy dice rolls, loot drops, critical hit calculations, and procedurally generated maps. For NFT collections, VRF ensures unbiased trait assignment during minting, metadata reveals, and rarity distribution. This is crucial when traits can be worth thousands. Lotteries and raffles benefit from transparent winner selection, whether for small community giveaways or million dollar prize pools. DeFi protocols use VRF for random liquidator selection, fair distribution, and variable reward mechanisms, while DAO governance applications include jury selection for disputes, randomized proposal ordering, and representative sampling for surveys. With VRF integrated, you're ready to build applications where fairness is cryptographically guaranteed, not just promised. Whether for games, NFTs, or DeFi protocols, your users can verify that randomness is truly random and build trust through mathematics, not faith. [PreviousProtofire Price Feedschevron-left](https://docs.somnia.network/developer/building-dapps/oracles/protofire-price-feeds) [NextExample Applicationschevron-right](https://docs.somnia.network/developer/building-dapps/example-applications) Last updated 1 month ago * [Mainnet VRF Smart Contracts](https://docs.somnia.network/developer/building-dapps/oracles/using-verifiable-randomness-vrf#mainnet-vrf-smart-contracts) * [Testnet VRF Smart Contracts](https://docs.somnia.network/developer/building-dapps/oracles/using-verifiable-randomness-vrf#testnet-vrf-smart-contracts) * [Understanding VRF and Why It Matters](https://docs.somnia.network/developer/building-dapps/oracles/using-verifiable-randomness-vrf#understanding-vrf-and-why-it-matters) * [What is VRF?](https://docs.somnia.network/developer/building-dapps/oracles/using-verifiable-randomness-vrf#what-is-vrf) * [Why is VRF important on blockchain?](https://docs.somnia.network/developer/building-dapps/oracles/using-verifiable-randomness-vrf#why-is-vrf-important-on-blockchain) * [Prerequisites](https://docs.somnia.network/developer/building-dapps/oracles/using-verifiable-randomness-vrf#prerequisites) * [TL;DR](https://docs.somnia.network/developer/building-dapps/oracles/using-verifiable-randomness-vrf#tl-dr) * [Code Breakdown](https://docs.somnia.network/developer/building-dapps/oracles/using-verifiable-randomness-vrf#code-breakdown) * [State Variables](https://docs.somnia.network/developer/building-dapps/oracles/using-verifiable-randomness-vrf#state-variables) * [VRF Request Parameters (constants)](https://docs.somnia.network/developer/building-dapps/oracles/using-verifiable-randomness-vrf#vrf-request-parameters-constants) * [Events](https://docs.somnia.network/developer/building-dapps/oracles/using-verifiable-randomness-vrf#events) * [Custom errors](https://docs.somnia.network/developer/building-dapps/oracles/using-verifiable-randomness-vrf#custom-errors) * [Constructor](https://docs.somnia.network/developer/building-dapps/oracles/using-verifiable-randomness-vrf#constructor) * [Function Requesting Randomness](https://docs.somnia.network/developer/building-dapps/oracles/using-verifiable-randomness-vrf#function-requesting-randomness) * [READ OPERATIONS](https://docs.somnia.network/developer/building-dapps/oracles/using-verifiable-randomness-vrf#read-operations) * [VRF callback (fulfillment)](https://docs.somnia.network/developer/building-dapps/oracles/using-verifiable-randomness-vrf#vrf-callback-fulfillment) * [getRequestStatus()](https://docs.somnia.network/developer/building-dapps/oracles/using-verifiable-randomness-vrf#getrequeststatus) * [getLatestRandomWord()](https://docs.somnia.network/developer/building-dapps/oracles/using-verifiable-randomness-vrf#getlatestrandomword) * [Pricing Helper](https://docs.somnia.network/developer/building-dapps/oracles/using-verifiable-randomness-vrf#pricing-helper) * [Conclusion](https://docs.somnia.network/developer/building-dapps/oracles/using-verifiable-randomness-vrf#conclusion) * [Real-World VRF Use Cases](https://docs.somnia.network/developer/building-dapps/oracles/using-verifiable-randomness-vrf#real-world-vrf-use-cases) Copy // SPDX-License-Identifier: MIT pragma solidity ^0.8.19; import {VRFConsumerBaseV2Plus} from "@chainlink/[email protected]/src/v0.8/vrf/dev/VRFConsumerBaseV2Plus.sol"; import {VRFV2PlusClient} from "@chainlink/[email protected]/src/v0.8/vrf/dev/libraries/VRFV2PlusClient.sol"; import {VRFV2PlusWrapperConsumerBase} from "@chainlink/[email protected]/src/v0.8/vrf/dev/VRFV2PlusWrapperConsumerBase.sol"; import {ConfirmedOwner} from "@chainlink/[email protected]/src/v0.8/shared/access/ConfirmedOwner.sol"; contract RandomNumberConsumer is VRFV2PlusWrapperConsumerBase, ConfirmedOwner { uint256 public latestRequestId; uint256[] public latestRandomWord; bool public fulfilled; uint32 public constant CALLBACK_GAS_LIMIT = 2_100_000; uint16 public constant REQUEST_CONFIRMATIONS = 3; uint32 public constant NUM_WORDS = 3; event RandomNumberRequested(uint256 indexed requestId, address indexed requester, uint256 paid); event RandomNumberFulfilled(uint256 indexed requestId, uint256[] randomWord); error InsufficientPayment(uint256 required, uint256 sent); error RequestAlreadyPending(); constructor(address wrapper) ConfirmedOwner(msg.sender) VRFV2PlusWrapperConsumerBase(wrapper) {} function requestRandomNumber() external payable onlyOwner { // Check if there's already a pending request if (latestRequestId != 0 && !fulfilled) { revert RequestAlreadyPending(); } // Calculate the required payment uint256 requestPrice = getRequestPrice(); if (msg.value < requestPrice) { revert InsufficientPayment(requestPrice, msg.value); } // Prepare the extra arguments for native payment VRFV2PlusClient.ExtraArgsV1 memory extraArgs = VRFV2PlusClient.ExtraArgsV1({ nativePayment: true }); bytes memory args = VRFV2PlusClient._argsToBytes(extraArgs); // Request randomness (uint256 requestId, uint256 paid) = requestRandomnessPayInNative( CALLBACK_GAS_LIMIT, REQUEST_CONFIRMATIONS, NUM_WORDS, args ); latestRequestId = requestId; fulfilled = false; emit RandomNumberRequested(requestId, msg.sender, paid); // Refund excess payment if (msg.value > paid) { (bool success, ) = msg.sender.call{value: msg.value - paid}(""); require(success, "Refund failed"); } } // This will be called by the VRF Wrapper function fulfillRandomWords(uint256 requestId, uint256[] memory randomWords) internal override { require(randomWords.length > 0, "No random word returned"); require(requestId == latestRequestId, "Unexpected request ID"); latestRandomWord = randomWords; fulfilled = true; emit RandomNumberFulfilled(requestId, randomWords); } function getRequestStatus() external view returns ( uint256 requestId, bool isPending, bool isFulfilled ) { return ( latestRequestId, latestRequestId != 0 && !fulfilled, fulfilled ); } function getLatestRandomWord() external view returns (uint256[] memory) { require(fulfilled, "No fulfilled request yet"); return latestRandomWord; } /** * @notice Get the current price for a VRF request in native tokens * @return The price in wei for requesting random numbers */ function getRequestPrice() public view returns (uint256) { return i_vrfV2PlusWrapper.calculateRequestPriceNative(CALLBACK_GAS_LIMIT, NUM_WORDS); } /** * @notice Withdraw any excess native tokens from the contract * @dev Only callable by owner, useful for recovering overpayments */ function withdraw() external onlyOwner { uint256 balance = address(this).balance; require(balance > 0, "No balance to withdraw"); (bool success, ) = owner().call{value: balance}(""); require(success, "Withdrawal failed"); } // Allow contract to receive STT for native payment receive() external payable {} } Copy contract RandomNumberConsumer is VRFV2PlusWrapperConsumerBase, ConfirmedOwner Copy uint256 public latestRequestId; uint256[] public latestRandomWord; bool public fulfilled; Copy uint32 public constant CALLBACK_GAS_LIMIT = 2_100_000; uint16 public constant REQUEST_CONFIRMATIONS = 3; uint32 public constant NUM_WORDS = 3; Copy event RandomNumberRequested(uint256 indexed requestId, address indexed requester, uint256 paid); event RandomNumberFulfilled(uint256 indexed requestId, uint256[] randomWord); Copy error InsufficientPayment(uint256 required, uint256 sent); error RequestAlreadyPending(); Copy constructor(address wrapper) ConfirmedOwner(msg.sender) VRFV2PlusWrapperConsumerBase(wrapper) {} Copy function requestRandomNumber() external payable onlyOwner { // 1) block overlapping requests if (latestRequestId != 0 && !fulfilled) revert RequestAlreadyPending(); // 2) compute required fee and validate payment uint256 requestPrice = getRequestPrice(); if (msg.value < requestPrice) revert InsufficientPayment(requestPrice, msg.value); // 3) signal native payment to the wrapper bytes memory args = VRFV2PlusClient._argsToBytes( VRFV2PlusClient.ExtraArgsV1({ nativePayment: true }) ); // 4) submit request (uses native STT) (uint256 requestId, uint256 paid) = requestRandomnessPayInNative( CALLBACK_GAS_LIMIT, REQUEST_CONFIRMATIONS, NUM_WORDS, args ); latestRequestId = requestId; fulfilled = false; emit RandomNumberRequested(requestId, msg.sender, paid); // 5) refund any excess back to caller if (msg.value > paid) { (bool ok, ) = msg.sender.call{ value: msg.value - paid }(""); require(ok, "Refund failed"); } } Copy function fulfillRandomWords( uint256 requestId, uint256[] memory randomWords ) internal override { require(randomWords.length > 0, "No random word returned"); require(requestId == latestRequestId, "Unexpected request ID"); latestRandomWord = randomWords; // stores 3 words fulfilled = true; emit RandomNumberFulfilled(requestId, randomWords); } Copy function getRequestStatus() external view returns (uint256 requestId, bool isPending, bool isFulfilled) { return (latestRequestId, latestRequestId != 0 && !fulfilled, fulfilled); } Copy function getLatestRandomWord() external view returns (uint256[] memory) { require(fulfilled, "No fulfilled request yet"); return latestRandomWord; } Copy function getRequestPrice() public view returns (uint256) { return i_vrfV2PlusWrapper.calculateRequestPriceNative(CALLBACK_GAS_LIMIT, NUM_WORDS); } --- # Node/Infra Security | Somnia Docs [hashtag](https://docs.somnia.network/developer/security/node-infra-security#secure-rpc-key-management-and-environment-configuration-for-somnia-developers) Secure RPC Key Management and Environment Configuration for Somnia Developers ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- This comprehensive guide teaches developers how to securely manage RPC keys, private keys, and environment variables when building applications on the Somnia blockchain. If you're deploying smart contracts, building dApps, or integrating with Somnia. Proper security practices are essential to protect your assets and maintain service reliability. By following this tutorial, you'll implement industry-standard security measures with practical code examples that seamlessly integrate into your development workflow. [hashtag](https://docs.somnia.network/developer/security/node-infra-security#prerequisites) Prerequisites -------------------------------------------------------------------------------------------------------------- Before starting this guide, ensure you have: * Basic knowledge of blockchain development and EVM concepts * Node.js (20+) installed * A code editor (VS Code recommended) * A Somnia wallet with Somnia Token (STT) for testing * Familiarity with environment variables and package managers * Basic understanding of Git and version control [hashtag](https://docs.somnia.network/developer/security/node-infra-security#rpc-key-security-fundamentals) RPC Key Security Fundamentals ---------------------------------------------------------------------------------------------------------------------------------------------- RPC (Remote Procedure Call) keys and endpoints allow your application to interact with the blockchain. Using them securely is paramount. A publicly accessible key, especially one with write permissions, can be exploited by an attacker to drain wallets or cause network congestion. #### [hashtag](https://docs.somnia.network/developer/security/node-infra-security#using-ankr-provider) Using Ankr Provider ❌ **Bad Practice:** ✅ **Good Practice:** [hashtag](https://docs.somnia.network/developer/security/node-infra-security#environment-variable-management) Environment Variable Management -------------------------------------------------------------------------------------------------------------------------------------------------- #### [hashtag](https://docs.somnia.network/developer/security/node-infra-security#private-rpc-endpoints) Private RPC Endpoints While public endpoints are convenient for basic queries, they are prone to unreliability and congestion during high-traffic events. **Private RPCs are premium services and perform significantly better than the public RPC**, offering more speed and reliability through dedicated connections. #### [hashtag](https://docs.somnia.network/developer/security/node-infra-security#environment-variable-best-practices) Environment Variable Best Practices A .env file is a standard way to manage environment-specific configuration: #### [hashtag](https://docs.somnia.network/developer/security/node-infra-security#never-commit-.env-files) Never Commit .env Files The .env file should be added to your .gitignore file. #### [hashtag](https://docs.somnia.network/developer/security/node-infra-security#create-separate-environment-files) Create Separate Environment Files Use separate configuration files for different environments. #### [hashtag](https://docs.somnia.network/developer/security/node-infra-security#reference-keys-in-code) Reference Keys in Code Always reference environment variables rather than hardcoding sensitive keys. #### [hashtag](https://docs.somnia.network/developer/security/node-infra-security#environment-variable-testing-for-applications) Environment Variable Testing for Applications **Note:** This testing approach is designed for application projects only, not system-wide configurations. [hashtag](https://docs.somnia.network/developer/security/node-infra-security#implementation-examples) Implementation Examples ---------------------------------------------------------------------------------------------------------------------------------- #### [hashtag](https://docs.somnia.network/developer/security/node-infra-security#complete-project-setup) Complete Project Setup #### [hashtag](https://docs.somnia.network/developer/security/node-infra-security#secure-contract-interaction) Secure Contract Interaction [hashtag](https://docs.somnia.network/developer/security/node-infra-security#rpc-key-management) RPC Key Management ------------------------------------------------------------------------------------------------------------------------ #### [hashtag](https://docs.somnia.network/developer/security/node-infra-security#ip-whitelisting) IP Whitelisting If your RPC provider supports it, restrict access to your API key by creating an allowlist of trusted IP addresses. #### [hashtag](https://docs.somnia.network/developer/security/node-infra-security#key-rotation-and-expiration) Key Rotation and Expiration Regularly rotate your RPC keys and immediately revoke any that are no longer in use. chevron-rightManual key rotation implementation[hashtag](https://docs.somnia.network/developer/security/node-infra-security#manual-key-rotation-implementation) #### [hashtag](https://docs.somnia.network/developer/security/node-infra-security#secrets-management-for-production) Secrets Management for Production For production environments, use a dedicated secrets management platform. [hashtag](https://docs.somnia.network/developer/security/node-infra-security#private-key-security) Private Key Security ---------------------------------------------------------------------------------------------------------------------------- Private keys authorize all transactions on a blockchain and should be protected with the utmost vigilance. #### [hashtag](https://docs.somnia.network/developer/security/node-infra-security#secure-key-generation) Secure Key Generation Use reputable tools that follow industry standards for cryptographically random key generation. #### [hashtag](https://docs.somnia.network/developer/security/node-infra-security#access-control) Access Control Private keys should never be shared. For team access, use multisig wallets or role-based access control. [hashtag](https://docs.somnia.network/developer/security/node-infra-security#error-handling-and-logging) Error Handling and Logging ---------------------------------------------------------------------------------------------------------------------------------------- Proper error handling and logging are crucial for maintaining security and debugging issues in production environments. When implementing logging for blockchain applications, it's essential to balance transparency with security, ensuring that sensitive information like private keys and API secrets are never exposed in logs. #### [hashtag](https://docs.somnia.network/developer/security/node-infra-security#secure-logging-practices) Secure Logging Practices chevron-rightSecure logging implementation[hashtag](https://docs.somnia.network/developer/security/node-infra-security#secure-logging-implementation) #### [hashtag](https://docs.somnia.network/developer/security/node-infra-security#error-recovery-strategies) Error Recovery Strategies [hashtag](https://docs.somnia.network/developer/security/node-infra-security#security-checklist) Security Checklist ------------------------------------------------------------------------------------------------------------------------ * Store all sensitive keys in environment variables * Use private RPC endpoints for better performance and reliability * Implement IP whitelisting where supported * Set up regular key rotation schedules * Use hardware wallets for high-value operations * Implement proper error handling and logging * Never commit secrets to version control * Use role-based access control for team environments * Monitor and audit key usage regularly * Test environment variable loading in application context [hashtag](https://docs.somnia.network/developer/security/node-infra-security#conclusion) Conclusion -------------------------------------------------------------------------------------------------------- By following these security practices, you'll significantly reduce the risk of key compromise and ensure your Somnia blockchain applications operate securely and reliably. Security is an ongoing process, and you should regularly review and update your practices as new threats emerge and best practices evolve. [PreviousAudit Checklistchevron-left](https://docs.somnia.network/developer/security/audit-checklist) [NextResponsible Disclosure Policychevron-right](https://docs.somnia.network/developer/security/responsible-disclosure-policy) Last updated 1 month ago * [Secure RPC Key Management and Environment Configuration for Somnia Developers](https://docs.somnia.network/developer/security/node-infra-security#secure-rpc-key-management-and-environment-configuration-for-somnia-developers) * [Prerequisites](https://docs.somnia.network/developer/security/node-infra-security#prerequisites) * [RPC Key Security Fundamentals](https://docs.somnia.network/developer/security/node-infra-security#rpc-key-security-fundamentals) * [Environment Variable Management](https://docs.somnia.network/developer/security/node-infra-security#environment-variable-management) * [Implementation Examples](https://docs.somnia.network/developer/security/node-infra-security#implementation-examples) * [RPC Key Management](https://docs.somnia.network/developer/security/node-infra-security#rpc-key-management) * [Private Key Security](https://docs.somnia.network/developer/security/node-infra-security#private-key-security) * [Error Handling and Logging](https://docs.somnia.network/developer/security/node-infra-security#error-handling-and-logging) * [Security Checklist](https://docs.somnia.network/developer/security/node-infra-security#security-checklist) * [Conclusion](https://docs.somnia.network/developer/security/node-infra-security#conclusion) Copy // Do not call your api Provider directly in your script with the api-keys! // Setup provider AnkrProvider const provider = new AnkrProvider('https://rpc.ankr.com/somnia_testnet/your-private-key'); Copy // Create a .env file SOMNIA_ANKR_RPC_URL=https://rpc.ankr.com/somnia_testnet/your-private-key // Use environment variables // Setup provider AnkrProvider const provider = new AnkrProvider(process.env.SOMNIA_ANKR_RPC_URL); Copy // Example configuration for private endpoint const config = { testnet: { url: process.env.SOMNIA_TESTNET_RPC_URL, accounts: [process.env.TESTNET_PRIVATE_KEY] } }; Copy # .env file SOMNIA_TESTNET_RPC_URL=https://rpc.ankr.com/somnia_testnet/your-private-key TESTNET_PRIVATE_KEY=your_private_key_here NODE_ENV=development Copy # .gitignore .env .env.local .env.*.local node_modules/ dist/ Copy # Project structure ├── .env.example # Template file (safe to commit) ├── .env.development # Development secrets ├── .env.test # Test environment ├── .env.staging # Staging environment └── .env.production # Production secrets (never commit) Copy // config/environment.js const dotenv = require('dotenv'); const path = require('path'); const environment = process.env.NODE_ENV || 'development'; const envFile = `.env.${environment}`; dotenv.config({ path: path.resolve(process.cwd(), envFile) }); module.exports = { rpcUrl: process.env.SOMNIA_RPC_URL, privateKey: process.env.PRIVATE_KEY, environment }; Copy // utils/blockchain.js const { ethers } = require('ethers'); const config = require('../config/environment'); class BlockchainService { constructor() { this.provider = new ethers.JsonRpcProvider(config.rpcUrl); this.wallet = new ethers.Wallet(config.privateKey, this.provider); } async getBalance(address) { return await this.provider.getBalance(address); } async sendTransaction(to, value) { const tx = { to, value: ethers.parseEther(value.toString()) }; return await this.wallet.sendTransaction(tx); } } module.exports = BlockchainService; Copy // test-env.js - For application projects only require('dotenv').config(); const testEnvironmentVariables = () => { const requiredVars = [\ 'SOMNIA_TESTNET_RPC_URL',\ 'TESTNET_PRIVATE_KEY'\ ]; const missing = requiredVars.filter(varName => !process.env[varName]); if (missing.length > 0) { console.error('Missing required environment variables:', missing); process.exit(1); } console.log('All required environment variables are loaded'); }; testEnvironmentVariables(); Copy # 1. Initialize project npm init -y npm install ethers dotenv npm install -D nodemon # 2. Create environment template echo "SOMNIA_RPC_URL=https://rpc.ankr.com/somnia_testnet/your-key-here" > .env.example echo "PRIVATE_KEY=your-private-key-here" >> .env.example echo "CONTRACT_ADDRESS=0x..." >> .env.example # 3. Add to .gitignore echo ".env*" >> .gitignore echo "!.env.example" >> .gitignore Copy // contracts/SomniaContract.js const { ethers } = require('ethers'); const config = require('../config/environment'); class SomniaContract { constructor(contractAddress, abi) { this.provider = new ethers.JsonRpcProvider(config.rpcUrl); this.wallet = new ethers.Wallet(config.privateKey, this.provider); this.contract = new ethers.Contract(contractAddress, abi, this.wallet); } async safeCall(methodName, ...args) { try { // Estimate gas first const gasEstimate = await this.contract[methodName].estimateGas(...args); // Add 20% buffer const gasLimit = gasEstimate * 120n / 100n; const tx = await this.contract[methodName](...args, { gasLimit }); console.log(`Transaction sent: ${tx.hash}`); const receipt = await tx.wait(); console.log(`Transaction confirmed: ${receipt.transactionHash}`); return receipt; } catch (error) { console.error('Transaction failed:', error.message); throw error; } } } module.exports = SomniaContract; Copy # Example: Configure IP allowlist in your provider dashboard # Allowed IPs: 203.0.113.1, 203.0.113.2 # This ensures only requests from your servers can use the key Copy // Manual key rotation implementation // Note: RPC providers typically require manual key generation through their dashboard // This implementation helps manage the rotation process once you have new keys const updateEnvironmentVariable = async (key, value) => { // Update .env file or environment configuration const fs = require('fs').promises; const envPath = '.env'; try { let envContent = await fs.readFile(envPath, 'utf8'); const regex = new RegExp(`^${key}=.*$`, 'm'); if (regex.test(envContent)) { envContent = envContent.replace(regex, `${key}=${value}`); } else { envContent += `\n${key}=${value}`; } await fs.writeFile(envPath, envContent); console.log(`Updated ${key} in environment file`); } catch (error) { console.error('Failed to update environment variable:', error); throw error; } }; // Manual key rotation helper const rotateApiKey = async (newKey) => { try { // Validate the new key format if (!newKey || typeof newKey !== 'string') { throw new Error('Invalid API key provided'); } // Store old key for reference const oldKey = process.env.SOMNIA_TESTNET_RPC_URL; console.log('Rotating API key...'); // Update environment variable await updateEnvironmentVariable('SOMNIA_TESTNET_RPC_URL', newKey); console.log('API key rotated successfully'); console.log('Please manually revoke the old key in your RPC provider dashboard'); console.log('Old key (first 10 chars):', oldKey?.substring(0, 10) + '...'); } catch (error) { console.error('Key rotation failed:', error); } }; // Key rotation reminder system const setupRotationReminder = () => { const NINETY_DAYS = 90 * 24 * 60 * 60 * 1000; setInterval(() => { console.log('\n🔑 SECURITY REMINDER: Consider rotating your RPC API keys'); console.log('1. Generate new key in your RPC provider dashboard'); console.log('2. Call rotateApiKey(newKey) with the new key'); console.log('3. Manually revoke old key in provider dashboard\n'); }, NINETY_DAYS); }; // Usage example: // rotateApiKey('https://rpc.ankr.com/somnia_testnet/your-new-private-key'); // setupRotationReminder(); Copy // AWS Secrets Manager example const AWS = require('aws-sdk'); const secretsManager = new AWS.SecretsManager(); const getRpcKey = async () => { const secret = await secretsManager.getSecretValue({ SecretId: 'somnia-rpc-key' }).promise(); return JSON.parse(secret.SecretString).rpcUrl; }; Copy // Example: Secure key generation with ethers.js const { Wallet } = require('ethers'); const { randomBytes } = require('crypto'); // Generate cryptographically secure random wallet const generateSecureWallet = () => { const randomWallet = Wallet.createRandom(); return { address: randomWallet.address, privateKey: randomWallet.privateKey, mnemonic: randomWallet.mnemonic.phrase }; }; Copy // Example: Role-based access pattern class SecureWalletManager { constructor() { this.roles = new Map(); this.permissions = { 'admin': ['deploy', 'transfer', 'read'], 'developer': ['deploy', 'read'], 'viewer': ['read'] }; } assignRole(address, role) { this.roles.set(address, role); } canExecute(address, action) { const role = this.roles.get(address); return this.permissions[role]?.includes(action) || false; } } Copy // Secure logging implementation const winston = require('winston'); // Create logger with security considerations const logger = winston.createLogger({ level: 'info', format: winston.format.combine( winston.format.timestamp(), winston.format.errors({ stack: true }), winston.format.json(), // Custom format to redact sensitive information winston.format.printf(({ timestamp, level, message, ...meta }) => { // Redact sensitive data const sanitized = JSON.stringify(meta).replace( /(private_key|api_key|secret)":\s*"[^"]+"/gi, '$1": "[REDACTED]"' ); return `${timestamp} [${level}]: ${message} ${sanitized}`; }) ), transports: [\ new winston.transports.File({ filename: 'error.log', level: 'error' }),\ new winston.transports.File({ filename: 'combined.log' })\ ] }); // Error handling for RPC calls const safeRpcCall = async (provider, method, params) => { try { const result = await provider.send(method, params); logger.info('RPC call successful', { method, success: true }); return result; } catch (error) { // Log error without exposing sensitive information logger.error('RPC call failed', { method, error: error.message, code: error.code }); throw new Error(`RPC call failed: ${error.message}`); } }; Copy // Implement retry logic with exponential backoff const retryRpcCall = async (provider, method, params, maxRetries = 3) => { for (let attempt = 1; attempt <= maxRetries; attempt++) { try { return await safeRpcCall(provider, method, params); } catch (error) { if (attempt === maxRetries) { logger.error('Max retries exceeded', { method, attempts: attempt }); throw error; } const delay = Math.pow(2, attempt) * 1000; // Exponential backoff logger.warn('Retrying RPC call', { method, attempt, delay }); await new Promise(resolve => setTimeout(resolve, delay)); } } }; --- # Building a Simple DEX on Somnia | Somnia Docs This tutorial will guide you through building a simple Decentralized Exchange (DEX) on Somnia, inspired by Uniswap V2's core mechanics. We'll implement the essential components: Liquidity Pools, Automated Market Maker (AMM) logic, and Token Swapping functionality. circle-check Somnia Mainnet is LIVE. To deploy on Somnia Mainnet, you will need SOMI Tokens. Please refer to the [guide](https://docs.somnia.network/get-started/getting-started-for-mainnet) on Moving from Testnet to Mainnet. [hashtag](https://docs.somnia.network/developer/building-dapps/example-applications/building-a-simple-dex-on-somnia#prerequisites) Prerequisites ----------------------------------------------------------------------------------------------------------------------------------------------------- 1. This guide is not an introduction to Solidity Programming; you are expected to understand Basic Solidity Programming. 2. You can deploy the Smart Contracts using our [Hardhat](https://docs.somnia.network/developer/development-frameworks/deploy-with-hardhat) or [Foundry](https://docs.somnia.network/developer/development-frameworks/deploy-with-foundry) guides. [hashtag](https://docs.somnia.network/developer/building-dapps/example-applications/building-a-simple-dex-on-somnia#core-concepts) Core Concepts ----------------------------------------------------------------------------------------------------------------------------------------------------- ### [hashtag](https://docs.somnia.network/developer/building-dapps/example-applications/building-a-simple-dex-on-somnia#automated-market-maker-amm) Automated Market Maker (AMM) At the core of an AMM is a Liquidity Pool, a Smart Contract that holds reserves of two (or more) tokens (e.g., STT and USDC). Users trade directly against this pool instead of with other users. Most AMMs (like Uniswap v2) use the constant product formula: x⋅y=kx \\cdot y = kx⋅y=k * `x` = amount of Token A in the pool * `y` = amount of Token B in the pool * `k` = constant (must remain unchanged) This ensures price adjustment based on supply and demand. If a user wants to buy Token A with Token B: * They send Token B into the Pool * The Smart Contract calculates how much Token A to send out to maintain `x * y = k` * As more Token A is withdrawn, its price increases (slippage) Anyone can deposit an equal value of both tokens into the pool to become a Liquidity Provider (LP) and earn a share of the trading fees (e.g., 0.3%). > #### > > [hashtag](https://docs.somnia.network/developer/building-dapps/example-applications/building-a-simple-dex-on-somnia#amms-are-fully-decentralized-with-no-need-for-counterparties-and-are-open-and-permissionless-to-use) > > AMMs are fully decentralized with no need for counterparties and are Open and permissionless to use and contribute liquidity. ### [hashtag](https://docs.somnia.network/developer/building-dapps/example-applications/building-a-simple-dex-on-somnia#liquidity-pools) Liquidity Pools Pairs of tokens locked in Smart Contracts that facilitate trading without traditional order books. [hashtag](https://docs.somnia.network/developer/building-dapps/example-applications/building-a-simple-dex-on-somnia#smart-contract-architecture) Smart Contract Architecture --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- We'll build three main contracts: 1. `SomniaFactory`: Creates and manages pair contracts 2. `SomniaPair`: Individual liquidity pool for token pairs 3. `SomniaRouter`: User-facing contract for swaps and liquidity management [hashtag](https://docs.somnia.network/developer/building-dapps/example-applications/building-a-simple-dex-on-somnia#implementation) Implementation ------------------------------------------------------------------------------------------------------------------------------------------------------- ### [hashtag](https://docs.somnia.network/developer/building-dapps/example-applications/building-a-simple-dex-on-somnia#erc-20-interface) ERC-20 Interface First, let's define the ERC-20 interface we'll use. chevron-rightSomniaPair.sol[hashtag](https://docs.somnia.network/developer/building-dapps/example-applications/building-a-simple-dex-on-somnia#somniapair.sol) ### [hashtag](https://docs.somnia.network/developer/building-dapps/example-applications/building-a-simple-dex-on-somnia#somniapair-contract) SomniaPair Contract The pair contract manages individual Liquidity Pools. chevron-rightSomniaPair.sol[hashtag](https://docs.somnia.network/developer/building-dapps/example-applications/building-a-simple-dex-on-somnia#somniapair.sol-1) ### [hashtag](https://docs.somnia.network/developer/building-dapps/example-applications/building-a-simple-dex-on-somnia#somniafactory-contract) SomniaFactory Contract The factory creates and tracks all pair contracts. chevron-rightSomniaFactory.sol[hashtag](https://docs.somnia.network/developer/building-dapps/example-applications/building-a-simple-dex-on-somnia#somniafactory.sol) ### [hashtag](https://docs.somnia.network/developer/building-dapps/example-applications/building-a-simple-dex-on-somnia#somniarouter-contract) SomniaRouter Contract The router provides user-friendly functions for swapping and liquidity management. chevron-rightSomniaRouter.sol[hashtag](https://docs.somnia.network/developer/building-dapps/example-applications/building-a-simple-dex-on-somnia#somniarouter.sol) You can deploy the Smart Contracts in the order they have been created above. Create `Token A` and `Token B` or, for example, use `wSTT` and `USDC` Token Pairs. The next step will be to create a Pool Pair for `Token A` and `Token B`. Deploy the Pair and Add Liquidity. Then users will be able to create `SWAP` using the `Router` Smart Contract. ### [hashtag](https://docs.somnia.network/developer/building-dapps/example-applications/building-a-simple-dex-on-somnia#usage-example) Usage Example chevron-rightAdd Liquidity[hashtag](https://docs.somnia.network/developer/building-dapps/example-applications/building-a-simple-dex-on-somnia#add-liquidity) chevron-rightSwap Tokens[hashtag](https://docs.somnia.network/developer/building-dapps/example-applications/building-a-simple-dex-on-somnia#swap-tokens) chevron-rightRemove Liquidity[hashtag](https://docs.somnia.network/developer/building-dapps/example-applications/building-a-simple-dex-on-somnia#remove-liquidity) ### [hashtag](https://docs.somnia.network/developer/building-dapps/example-applications/building-a-simple-dex-on-somnia#test-your-dex) Test Your DEX Create a test script to verify functionality: chevron-rightTestDEX.sol[hashtag](https://docs.somnia.network/developer/building-dapps/example-applications/building-a-simple-dex-on-somnia#testdex.sol) [hashtag](https://docs.somnia.network/developer/building-dapps/example-applications/building-a-simple-dex-on-somnia#conclusion) Conclusion ----------------------------------------------------------------------------------------------------------------------------------------------- This tutorial has walked you through implementing a fully functional decentralized exchange (DEX)on Somnia, demonstrating the core Smart Contract architecture that powers Automated Market Makers - AMM. By building these contracts from scratch, you've gained hands-on experience with the fundamental mechanics of DEXs: 1. How Liquidity Pools maintain token reserves. 2. How the Constant Product Formula enables permissionless trading. 3. How router contracts abstract complex operations into user-friendly interfaces. The implementation covers essential features including liquidity provision with LP token minting, atomic swaps with built-in slippage protection, and multi-hop routing for indirect trading pairs. While this represents a complete Smart Contract foundation, production deployments would benefit from additional features such as concentrated liquidity (as seen in Uniswap V3), dynamic fee tiers, flash loan functionality, and comprehensive governance mechanisms. The modular architecture we've implemented makes these enhancements straightforward to integrate. As you continue developing on Somnia, remember that the Smart Contracts presented here are just one layer of a complete DEX ecosystem; You'll need to consider frontend interfaces, liquidity incentives, and integration with other DeFi protocols to create a thriving exchange. Most importantly, ensure thorough testing on Somnia's testnet and seek professional security audits before deploying any contracts handling real value. This foundation provides you with the knowledge and code necessary to contribute to Somnia's DeFi ecosystem, whether by deploying your own DEX or building innovative features on top of existing protocols. [PreviousDAO UI Tutorial p3chevron-left](https://docs.somnia.network/developer/building-dapps/example-applications/dao-ui-tutorial-p3) [NextSecuritychevron-right](https://docs.somnia.network/developer/security) Last updated 1 month ago * [Prerequisites](https://docs.somnia.network/developer/building-dapps/example-applications/building-a-simple-dex-on-somnia#prerequisites) * [Core Concepts](https://docs.somnia.network/developer/building-dapps/example-applications/building-a-simple-dex-on-somnia#core-concepts) * [Automated Market Maker (AMM)](https://docs.somnia.network/developer/building-dapps/example-applications/building-a-simple-dex-on-somnia#automated-market-maker-amm) * [Liquidity Pools](https://docs.somnia.network/developer/building-dapps/example-applications/building-a-simple-dex-on-somnia#liquidity-pools) * [Smart Contract Architecture](https://docs.somnia.network/developer/building-dapps/example-applications/building-a-simple-dex-on-somnia#smart-contract-architecture) * [Implementation](https://docs.somnia.network/developer/building-dapps/example-applications/building-a-simple-dex-on-somnia#implementation) * [ERC-20 Interface](https://docs.somnia.network/developer/building-dapps/example-applications/building-a-simple-dex-on-somnia#erc-20-interface) * [SomniaPair Contract](https://docs.somnia.network/developer/building-dapps/example-applications/building-a-simple-dex-on-somnia#somniapair-contract) * [SomniaFactory Contract](https://docs.somnia.network/developer/building-dapps/example-applications/building-a-simple-dex-on-somnia#somniafactory-contract) * [SomniaRouter Contract](https://docs.somnia.network/developer/building-dapps/example-applications/building-a-simple-dex-on-somnia#somniarouter-contract) * [Usage Example](https://docs.somnia.network/developer/building-dapps/example-applications/building-a-simple-dex-on-somnia#usage-example) * [Test Your DEX](https://docs.somnia.network/developer/building-dapps/example-applications/building-a-simple-dex-on-somnia#test-your-dex) * [Conclusion](https://docs.somnia.network/developer/building-dapps/example-applications/building-a-simple-dex-on-somnia#conclusion) Copy // IERC20.sol pragma solidity ^0.8.0; interface IERC20 { function totalSupply() external view returns (uint256); function balanceOf(address account) external view returns (uint256); function transfer(address recipient, uint256 amount) external returns (bool); function allowance(address owner, address spender) external view returns (uint256); function approve(address spender, uint256 amount) external returns (bool); function transferFrom(address sender, address recipient, uint256 amount) external returns (bool); event Transfer(address indexed from, address indexed to, uint256 value); event Approval(address indexed owner, address indexed spender, uint256 value); } Copy // SomniaPair.sol pragma solidity ^0.8.0; import "./IERC20.sol"; contract SomniaPair is IERC20 { uint256 public constant MINIMUM_LIQUIDITY = 10**3; address public factory; address public token0; address public token1; uint112 private reserve0; uint112 private reserve1; uint32 private blockTimestampLast; uint256 public kLast; uint256 private unlocked = 1; modifier lock() { require(unlocked == 1, 'LOCKED'); unlocked = 0; _; unlocked = 1; } // ERC-20 Implementation string public constant name = "Somnia LP Token"; string public constant symbol = "SLP"; uint8 public constant decimals = 18; uint256 public totalSupply; mapping(address => uint256) public balanceOf; mapping(address => mapping(address => uint256)) public allowance; event Mint(address indexed sender, uint256 amount0, uint256 amount1); event Burn(address indexed sender, uint256 amount0, uint256 amount1, address indexed to); event Swap( address indexed sender, uint256 amount0In, uint256 amount1In, uint256 amount0Out, uint256 amount1Out, address indexed to ); event Sync(uint112 reserve0, uint112 reserve1); constructor() { factory = msg.sender; } function initialize(address _token0, address _token1) external { require(msg.sender == factory, 'FORBIDDEN'); token0 = _token0; token1 = _token1; } function getReserves() public view returns (uint112 _reserve0, uint112 _reserve1, uint32 _blockTimestampLast) { _reserve0 = reserve0; _reserve1 = reserve1; _blockTimestampLast = blockTimestampLast; } function _safeTransfer(address token, address to, uint256 value) private { (bool success, bytes memory data) = token.call(abi.encodeWithSelector(IERC20.transfer.selector, to, value)); require(success && (data.length == 0 || abi.decode(data, (bool))), 'TRANSFER_FAILED'); } function _update(uint256 balance0, uint256 balance1, uint112 _reserve0, uint112 _reserve1) private { require(balance0 <= type(uint112).max && balance1 <= type(uint112).max, 'OVERFLOW'); uint32 blockTimestamp = uint32(block.timestamp % 2**32); reserve0 = uint112(balance0); reserve1 = uint112(balance1); blockTimestampLast = blockTimestamp; emit Sync(reserve0, reserve1); } function mint(address to) external lock returns (uint256 liquidity) { (uint112 _reserve0, uint112 _reserve1,) = getReserves(); uint256 balance0 = IERC20(token0).balanceOf(address(this)); uint256 balance1 = IERC20(token1).balanceOf(address(this)); uint256 amount0 = balance0 - _reserve0; uint256 amount1 = balance1 - _reserve1; uint256 _totalSupply = totalSupply; if (_totalSupply == 0) { liquidity = sqrt(amount0 * amount1) - MINIMUM_LIQUIDITY; _mint(address(0), MINIMUM_LIQUIDITY); // permanently lock the first MINIMUM_LIQUIDITY tokens } else { liquidity = min(amount0 * _totalSupply / _reserve0, amount1 * _totalSupply / _reserve1); } require(liquidity > 0, 'INSUFFICIENT_LIQUIDITY_MINTED'); _mint(to, liquidity); _update(balance0, balance1, _reserve0, _reserve1); kLast = uint256(reserve0) * reserve1; emit Mint(msg.sender, amount0, amount1); } function burn(address to) external lock returns (uint256 amount0, uint256 amount1) { (uint112 _reserve0, uint112 _reserve1,) = getReserves(); address _token0 = token0; address _token1 = token1; uint256 balance0 = IERC20(_token0).balanceOf(address(this)); uint256 balance1 = IERC20(_token1).balanceOf(address(this)); uint256 liquidity = balanceOf[address(this)]; uint256 _totalSupply = totalSupply; amount0 = liquidity * balance0 / _totalSupply; amount1 = liquidity * balance1 / _totalSupply; require(amount0 > 0 && amount1 > 0, 'INSUFFICIENT_LIQUIDITY_BURNED'); _burn(address(this), liquidity); _safeTransfer(_token0, to, amount0); _safeTransfer(_token1, to, amount1); balance0 = IERC20(_token0).balanceOf(address(this)); balance1 = IERC20(_token1).balanceOf(address(this)); _update(balance0, balance1, _reserve0, _reserve1); kLast = uint256(reserve0) * reserve1; emit Burn(msg.sender, amount0, amount1, to); } function swap(uint256 amount0Out, uint256 amount1Out, address to, bytes calldata data) external lock { require(amount0Out > 0 || amount1Out > 0, 'INSUFFICIENT_OUTPUT_AMOUNT'); (uint112 _reserve0, uint112 _reserve1,) = getReserves(); require(amount0Out < _reserve0 && amount1Out < _reserve1, 'INSUFFICIENT_LIQUIDITY'); uint256 balance0; uint256 balance1; { address _token0 = token0; address _token1 = token1; require(to != _token0 && to != _token1, 'INVALID_TO'); if (amount0Out > 0) _safeTransfer(_token0, to, amount0Out); if (amount1Out > 0) _safeTransfer(_token1, to, amount1Out); balance0 = IERC20(_token0).balanceOf(address(this)); balance1 = IERC20(_token1).balanceOf(address(this)); } uint256 amount0In = balance0 > _reserve0 - amount0Out ? balance0 - (_reserve0 - amount0Out) : 0; uint256 amount1In = balance1 > _reserve1 - amount1Out ? balance1 - (_reserve1 - amount1Out) : 0; require(amount0In > 0 || amount1In > 0, 'INSUFFICIENT_INPUT_AMOUNT'); { uint256 balance0Adjusted = balance0 * 1000 - amount0In * 3; uint256 balance1Adjusted = balance1 * 1000 - amount1In * 3; require(balance0Adjusted * balance1Adjusted >= uint256(_reserve0) * _reserve1 * 1000**2, 'K'); } _update(balance0, balance1, _reserve0, _reserve1); emit Swap(msg.sender, amount0In, amount1In, amount0Out, amount1Out, to); } // Helper functions function sqrt(uint256 y) internal pure returns (uint256 z) { if (y > 3) { z = y; uint256 x = y / 2 + 1; while (x < z) { z = x; x = (y / x + x) / 2; } } else if (y != 0) { z = 1; } } function min(uint256 x, uint256 y) internal pure returns (uint256 z) { z = x < y ? x : y; } // ERC-20 functions function _mint(address to, uint256 value) internal { totalSupply += value; balanceOf[to] += value; emit Transfer(address(0), to, value); } function _burn(address from, uint256 value) internal { balanceOf[from] -= value; totalSupply -= value; emit Transfer(from, address(0), value); } function approve(address spender, uint256 value) external returns (bool) { allowance[msg.sender][spender] = value; emit Approval(msg.sender, spender, value); return true; } function transfer(address to, uint256 value) external returns (bool) { balanceOf[msg.sender] -= value; balanceOf[to] += value; emit Transfer(msg.sender, to, value); return true; } function transferFrom(address from, address to, uint256 value) external returns (bool) { if (allowance[from][msg.sender] != type(uint256).max) { allowance[from][msg.sender] -= value; } balanceOf[from] -= value; balanceOf[to] += value; emit Transfer(from, to, value); return true; } } Copy // SomniaFactory.sol pragma solidity ^0.8.0; import "./SomniaPair.sol"; contract SomniaFactory { mapping(address => mapping(address => address)) public getPair; address[] public allPairs; event PairCreated(address indexed token0, address indexed token1, address pair, uint256); function allPairsLength() external view returns (uint256) { return allPairs.length; } function createPair(address tokenA, address tokenB) external returns (address pair) { require(tokenA != tokenB, 'IDENTICAL_ADDRESSES'); (address token0, address token1) = tokenA < tokenB ? (tokenA, tokenB) : (tokenB, tokenA); require(token0 != address(0), 'ZERO_ADDRESS'); require(getPair[token0][token1] == address(0), 'PAIR_EXISTS'); bytes memory bytecode = type(SomniaPair).creationCode; bytes32 salt = keccak256(abi.encodePacked(token0, token1)); assembly { pair := create2(0, add(bytecode, 32), mload(bytecode), salt) } SomniaPair(pair).initialize(token0, token1); getPair[token0][token1] = pair; getPair[token1][token0] = pair; allPairs.push(pair); emit PairCreated(token0, token1, pair, allPairs.length); } } Copy // SomniaRouter.sol pragma solidity ^0.8.0; import "./IERC20.sol"; import "./SomniaPair.sol"; import "./SomniaFactory.sol"; contract SomniaRouter { address public immutable factory; modifier ensure(uint256 deadline) { require(deadline >= block.timestamp, 'EXPIRED'); _; } constructor(address _factory) { factory = _factory; } // Add liquidity function addLiquidity( address tokenA, address tokenB, uint256 amountADesired, uint256 amountBDesired, uint256 amountAMin, uint256 amountBMin, address to, uint256 deadline ) external ensure(deadline) returns (uint256 amountA, uint256 amountB, uint256 liquidity) { (amountA, amountB) = _addLiquidity(tokenA, tokenB, amountADesired, amountBDesired, amountAMin, amountBMin); address pair = pairFor(tokenA, tokenB); _safeTransferFrom(tokenA, msg.sender, pair, amountA); _safeTransferFrom(tokenB, msg.sender, pair, amountB); liquidity = SomniaPair(pair).mint(to); } function _addLiquidity( address tokenA, address tokenB, uint256 amountADesired, uint256 amountBDesired, uint256 amountAMin, uint256 amountBMin ) internal returns (uint256 amountA, uint256 amountB) { if (SomniaFactory(factory).getPair(tokenA, tokenB) == address(0)) { SomniaFactory(factory).createPair(tokenA, tokenB); } (uint256 reserveA, uint256 reserveB) = getReserves(tokenA, tokenB); if (reserveA == 0 && reserveB == 0) { (amountA, amountB) = (amountADesired, amountBDesired); } else { uint256 amountBOptimal = quote(amountADesired, reserveA, reserveB); if (amountBOptimal <= amountBDesired) { require(amountBOptimal >= amountBMin, 'INSUFFICIENT_B_AMOUNT'); (amountA, amountB) = (amountADesired, amountBOptimal); } else { uint256 amountAOptimal = quote(amountBDesired, reserveB, reserveA); assert(amountAOptimal <= amountADesired); require(amountAOptimal >= amountAMin, 'INSUFFICIENT_A_AMOUNT'); (amountA, amountB) = (amountAOptimal, amountBDesired); } } } // Remove liquidity function removeLiquidity( address tokenA, address tokenB, uint256 liquidity, uint256 amountAMin, uint256 amountBMin, address to, uint256 deadline ) public ensure(deadline) returns (uint256 amountA, uint256 amountB) { address pair = pairFor(tokenA, tokenB); SomniaPair(pair).transferFrom(msg.sender, pair, liquidity); (uint256 amount0, uint256 amount1) = SomniaPair(pair).burn(to); (address token0,) = sortTokens(tokenA, tokenB); (amountA, amountB) = tokenA == token0 ? (amount0, amount1) : (amount1, amount0); require(amountA >= amountAMin, 'INSUFFICIENT_A_AMOUNT'); require(amountB >= amountBMin, 'INSUFFICIENT_B_AMOUNT'); } // Swap functions function swapExactTokensForTokens( uint256 amountIn, uint256 amountOutMin, address[] calldata path, address to, uint256 deadline ) external ensure(deadline) returns (uint256[] memory amounts) { amounts = getAmountsOut(amountIn, path); require(amounts[amounts.length - 1] >= amountOutMin, 'INSUFFICIENT_OUTPUT_AMOUNT'); _safeTransferFrom( path[0], msg.sender, pairFor(path[0], path[1]), amounts[0] ); _swap(amounts, path, to); } function swapTokensForExactTokens( uint256 amountOut, uint256 amountInMax, address[] calldata path, address to, uint256 deadline ) external ensure(deadline) returns (uint256[] memory amounts) { amounts = getAmountsIn(amountOut, path); require(amounts[0] <= amountInMax, 'EXCESSIVE_INPUT_AMOUNT'); _safeTransferFrom( path[0], msg.sender, pairFor(path[0], path[1]), amounts[0] ); _swap(amounts, path, to); } // Internal functions function _swap(uint256[] memory amounts, address[] memory path, address _to) internal { for (uint256 i; i < path.length - 1; i++) { (address input, address output) = (path[i], path[i + 1]); (address token0,) = sortTokens(input, output); uint256 amountOut = amounts[i + 1]; (uint256 amount0Out, uint256 amount1Out) = input == token0 ? (uint256(0), amountOut) : (amountOut, uint256(0)); address to = i < path.length - 2 ? pairFor(output, path[i + 2]) : _to; SomniaPair(pairFor(input, output)).swap( amount0Out, amount1Out, to, new bytes(0) ); } } // Library functions function sortTokens(address tokenA, address tokenB) internal pure returns (address token0, address token1) { require(tokenA != tokenB, 'IDENTICAL_ADDRESSES'); (token0, token1) = tokenA < tokenB ? (tokenA, tokenB) : (tokenB, tokenA); require(token0 != address(0), 'ZERO_ADDRESS'); } function pairFor(address tokenA, address tokenB) internal view returns (address pair) { (address token0, address token1) = sortTokens(tokenA, tokenB); pair = SomniaFactory(factory).getPair(token0, token1); require(pair != address(0), 'PAIR_DOES_NOT_EXIST'); } function getReserves(address tokenA, address tokenB) internal view returns (uint256 reserveA, uint256 reserveB) { (address token0,) = sortTokens(tokenA, tokenB); (uint256 reserve0, uint256 reserve1,) = SomniaPair(pairFor(tokenA, tokenB)).getReserves(); (reserveA, reserveB) = tokenA == token0 ? (reserve0, reserve1) : (reserve1, reserve0); } function quote(uint256 amountA, uint256 reserveA, uint256 reserveB) internal pure returns (uint256 amountB) { require(amountA > 0, 'INSUFFICIENT_AMOUNT'); require(reserveA > 0 && reserveB > 0, 'INSUFFICIENT_LIQUIDITY'); amountB = amountA * reserveB / reserveA; } function getAmountOut(uint256 amountIn, uint256 reserveIn, uint256 reserveOut) internal pure returns (uint256 amountOut) { require(amountIn > 0, 'INSUFFICIENT_INPUT_AMOUNT'); require(reserveIn > 0 && reserveOut > 0, 'INSUFFICIENT_LIQUIDITY'); uint256 amountInWithFee = amountIn * 997; uint256 numerator = amountInWithFee * reserveOut; uint256 denominator = reserveIn * 1000 + amountInWithFee; amountOut = numerator / denominator; } function getAmountIn(uint256 amountOut, uint256 reserveIn, uint256 reserveOut) internal pure returns (uint256 amountIn) { require(amountOut > 0, 'INSUFFICIENT_OUTPUT_AMOUNT'); require(reserveIn > 0 && reserveOut > 0, 'INSUFFICIENT_LIQUIDITY'); uint256 numerator = reserveIn * amountOut * 1000; uint256 denominator = (reserveOut - amountOut) * 997; amountIn = (numerator / denominator) + 1; } function getAmountsOut(uint256 amountIn, address[] memory path) public view returns (uint256[] memory amounts) { require(path.length >= 2, 'INVALID_PATH'); amounts = new uint256[](path.length); amounts[0] = amountIn; for (uint256 i; i < path.length - 1; i++) { (uint256 reserveIn, uint256 reserveOut) = getReserves(path[i], path[i + 1]); amounts[i + 1] = getAmountOut(amounts[i], reserveIn, reserveOut); } } function getAmountsIn(uint256 amountOut, address[] memory path) public view returns (uint256[] memory amounts) { require(path.length >= 2, 'INVALID_PATH'); amounts = new uint256[](path.length); amounts[amounts.length - 1] = amountOut; for (uint256 i = path.length - 1; i > 0; i--) { (uint256 reserveIn, uint256 reserveOut) = getReserves(path[i - 1], path[i]); amounts[i - 1] = getAmountIn(amounts[i], reserveIn, reserveOut); } } function _safeTransferFrom(address token, address from, address to, uint256 value) private { (bool success, bytes memory data) = token.call(abi.encodeWithSelector(IERC20.transferFrom.selector, from, to, value)); require(success && (data.length == 0 || abi.decode(data, (bool))), 'TRANSFER_FROM_FAILED'); } } Copy // Approve router to spend tokens tokenA.approve(router.address, amountA); tokenB.approve(router.address, amountB); // Add liquidity router.addLiquidity( tokenA.address, tokenB.address, amountA, amountB, minAmountA, minAmountB, userAddress, deadline ); Copy // Approve router to spend input token tokenA.approve(router.address, amountIn); // Swap exact tokens for tokens address[] memory path = new address[](2); path[0] = tokenA.address; path[1] = tokenB.address; router.swapExactTokensForTokens( amountIn, minAmountOut, path, userAddress, deadline ); Copy // Approve router to spend LP tokens pair.approve(router.address, lpTokenAmount); // Remove liquidity router.removeLiquidity( tokenA.address, tokenB.address, lpTokenAmount, minAmountA, minAmountB, userAddress, deadline ); Copy pragma solidity ^0.8.0; import "./SomniaFactory.sol"; import "./SomniaRouter.sol"; import "./IERC20.sol"; contract TestDEX { SomniaFactory public factory; SomniaRouter public router; constructor() { factory = new SomniaFactory(); router = new SomniaRouter(address(factory)); } function testCreatePair(address tokenA, address tokenB) external returns (address) { return factory.createPair(tokenA, tokenB); } function testAddLiquidity( address tokenA, address tokenB, uint256 amountA, uint256 amountB ) external { IERC20(tokenA).transferFrom(msg.sender, address(this), amountA); IERC20(tokenB).transferFrom(msg.sender, address(this), amountB); IERC20(tokenA).approve(address(router), amountA); IERC20(tokenB).approve(address(router), amountB); router.addLiquidity( tokenA, tokenB, amountA, amountB, 0, 0, msg.sender, block.timestamp + 3600 ); } } --- # Streams Case Study: Formula 1 | Somnia Docs ### [hashtag](https://docs.somnia.network/developer/data-streams/tutorials/streams-case-study-formula-1#schemas) Schemas Driver schema Copy uint32 number, string name, string abbreviation, string teamName, string teamColor Cartesian 3D coordinates schema Copy int256 x, int256 y, int256 z The driver schema can extend the cartesian coordinates since the 3D coordinates will be used widely for other applications. Again this promotes re-usability of schemas. ### [hashtag](https://docs.somnia.network/developer/data-streams/tutorials/streams-case-study-formula-1#schema-registration-and-re-use) Schema registration and re-use Copy const { SDK, zeroBytes32, SchemaEncoder } = require("@somnia-chain/streams"); const { createPublicClient, http, createWalletClient, toHex, defineChain, } = require("viem"); const { privateKeyToAccount } = require("viem/accounts"); const dreamChain = defineChain({ id: 50312, name: "Somnia Testnet", network: "testnet", nativeCurrency: { decimals: 18, name: "STT", symbol: "STT", }, rpcUrls: { default: { http: [\ "https://dream-rpc.somnia.network",\ ], }, public: { http: [\ "https://dream-rpc.somnia.network",\ ], }, }, }) async function main() { // Connect to the blockchain to read data with the public client const publicClient = createPublicClient({ chain: dreamChain, transport: http(), }) const walletClient = createWalletClient({ account: privateKeyToAccount(process.env.PRIVATE_KEY), chain: dreamChain, transport: http(), }) // Connect to the SDK const sdk = new SDK({ public: publicClient, wallet: walletClient, }) // Setup the schemas const coordinatesSchema = `int256 x, int256 y, int256 z` const driverSchema = `uint32 number, string name, string abbreviation, string teamName, string teamColor` // Derive Etherbase schema metadata const coordinatesSchemaId = await sdk.streams.computeSchemaId( coordinatesSchema ) if (!coordinatesSchemaId) { throw new Error("Unable to compute the schema ID for the coordinates schema") } const driverSchemaId = await sdk.streams.computeSchemaId( driverSchema ) if (!driverSchemaId) { throw new Error("Unable to compute the schema ID for the driver schema") } const extendedSchema = `${driverSchema}, ${coordinatesSchema}` console.log("Schemas in use", { coordinatesSchemaId, driverSchemaId, coordinatesSchema, driverSchema, extendedSchema }) const isCoordinatesSchemaRegistered = await sdk.streams.isDataSchemaRegistered(coordinatesSchemaId) if (!isCoordinatesSchemaRegistered) { // We want to publish the driver schema but we need to publish the coordinates schema first before it can be extended const registerCoordinatesSchemaTxHash = await sdk.streams.registerDataSchemas([\ { schemaName: "coords", schema: coordinatesSchema }\ ]) if (!registerCoordinatesSchemaTxHash) { throw new Error("Failed to register coordinates schema") } console.log("Registered coordinates schema on-chain", { registerCoordinatesSchemaTxHash }) await publicClient.waitForTransactionReceipt({ hash: registerCoordinatesSchemaTxHash }) } const isDriverSchemaRegistered = await sdk.streams.isDataSchemaRegistered(driverSchemaId) if (!isDriverSchemaRegistered) { // Now, publish the driver schema but extend the coordinates schema! const registerDriverSchemaTxHash = sdk.streams.registerDataSchemas([\ { schemaName: "driver", schema: driverSchema, parentSchemaId: coordinatesSchemaId }\ ]) if (!registerDriverSchemaTxHash) { throw new Error("Failed to register schema on-chain") } console.log("Registered driver schema on-chain", { registerDriverSchemaTxHash, }) await publicClient.waitForTransactionReceipt({ hash: registerDriverSchemaTxHash }) } // Publish some data!! const schemaEncoder = new SchemaEncoder(extendedSchema) const encodedData = schemaEncoder.encodeData([\ { name: "number", value: "44", type: "uint32" },\ { name: "name", value: "Lewis Hamilton", type: "string" },\ { name: "abbreviation", value: "HAM", type: "string" },\ { name: "teamName", value: "Ferrari", type: "string" },\ { name: "teamColor", value: "#F91536", type: "string" },\ { name: "x", value: "-1513", type: "int256" },\ { name: "y", value: "0", type: "int256" },\ { name: "z", value: "955", type: "int256" },\ ]) console.log("encodedData", encodedData) const dataStreams = [{\ // Data id: DRIVER number - index will be a helpful lookup later and references ./data/f1-coordinates.js Cube 4 coordinates (driver 44) - F1 telemetry data\ id: toHex(`44-0`, { size: 32 }),\ schemaId: driverSchemaId,\ data: encodedData\ }] const publishTxHash = await sdk.streams.set(dataStreams) console.log("\nPublish Tx Hash", publishTxHash) } [PreviousBuild Your First Schemachevron-left](https://docs.somnia.network/developer/data-streams/tutorials/build-your-first-schema) [NextREAD Stream Data from a UI (Next.js Example)chevron-right](https://docs.somnia.network/developer/data-streams/tutorials/read-stream-data-from-a-ui-next.js-example) Last updated 1 month ago * [Schemas](https://docs.somnia.network/developer/data-streams/tutorials/streams-case-study-formula-1#schemas) * [Schema registration and re-use](https://docs.somnia.network/developer/data-streams/tutorials/streams-case-study-formula-1#schema-registration-and-re-use) --- # Working with Multiple Publishers in a Shared Stream | Somnia Docs The core architecture of Somnia Data Streams decouples data schemas from publishers. This allows multiple different accounts (or devices) to publish data using the **same schema**. The data conforms to the same data structure, regardless of who published it. This model is perfect for multi-source data scenarios, such as: * **Multi-User Chat:** Multiple users sending messages under the same `chatMessage` schema. * **IoT (Internet of Things):** Hundreds of sensors submitting data under the same `telemetry` schema. * **Gaming:** All players in a game publishing their positions and scores under the same `playerUpdate` schema. In this tutorial, we will demonstrate how to build an "aggregator" application that collects and merges data from two different "devices" (two separate wallet accounts) publishing to the same "telemetry" schema. [hashtag](https://docs.somnia.network/developer/data-streams/tutorials/working-with-multiple-publishers-in-a-shared-stream#prerequisites) Prerequisites ------------------------------------------------------------------------------------------------------------------------------------------------------------ * Node.js 20+ * `@somnia-chain/streams` and `viem` libraries installed * An `RPC_URL` for access to the Somnia Testnet * **Two (2)** funded Somnia Testnet wallets for publishing data. [hashtag](https://docs.somnia.network/developer/data-streams/tutorials/working-with-multiple-publishers-in-a-shared-stream#what-youll-build) What You’ll Build ------------------------------------------------------------------------------------------------------------------------------------------------------------------- In this tutorial, we will build two main components: 1. A **Publisher Script** that simulates two different wallets sending data to the same telemetry schema. 2. An **Aggregator Script** that fetches _all_ data from a specified list of publishers, merges them into a single list, and sorts them by timestamp. [hashtag](https://docs.somnia.network/developer/data-streams/tutorials/working-with-multiple-publishers-in-a-shared-stream#project-setup) Project Setup ------------------------------------------------------------------------------------------------------------------------------------------------------------ Create a new directory for your application and install the necessary packages. Create a `tsconfig.json` file in your project root: [hashtag](https://docs.somnia.network/developer/data-streams/tutorials/working-with-multiple-publishers-in-a-shared-stream#configure-environment-variables) Configure Environment Variables ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ Create a `.env` file in your project root. For this tutorial, we will need **two** different private keys. circle-info **IMPORTANT:** Never expose private keys in client-side (browser) code or public repositories. The scripts in this tutorial are intended to be run server-side. [hashtag](https://docs.somnia.network/developer/data-streams/tutorials/working-with-multiple-publishers-in-a-shared-stream#chain-and-client-configuration) Chain and Client Configuration ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- Create a folder named `src/lib` and set up your `chain.ts` and `clients.ts` files. `**src/lib/chain.ts**` `**src/lib/clients.ts**` [hashtag](https://docs.somnia.network/developer/data-streams/tutorials/working-with-multiple-publishers-in-a-shared-stream#define-the-shared-schema) Define the Shared Schema ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- Let's define the common schema that all publishers will use. `**src/lib/schema.ts**` [hashtag](https://docs.somnia.network/developer/data-streams/tutorials/working-with-multiple-publishers-in-a-shared-stream#create-the-publisher-script) Create the Publisher Script ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- Now, let's create a script that simulates how two different publishers will send data to this schema. This script will take which publisher to use as a command-line argument. `**src/scripts/publishData.ts**` **To run this script:** Add the following scripts to your `package.json` file: Now, open two different terminals and send data from each: Repeat this a few times to build up a dataset from both publishers. [hashtag](https://docs.somnia.network/developer/data-streams/tutorials/working-with-multiple-publishers-in-a-shared-stream#create-the-aggregator-script) Create the Aggregator Script ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ Now that we have published our data, we can write the "aggregator" script that collects, merges, and sorts all data from these two (or more) publishers. `**src/scripts/aggregateData.ts**` **To run this script:** Add the script to your `package.json` file: And run it: [hashtag](https://docs.somnia.network/developer/data-streams/tutorials/working-with-multiple-publishers-in-a-shared-stream#conclusion) Conclusion ------------------------------------------------------------------------------------------------------------------------------------------------------ In this tutorial, you learned how to manage a multi-publisher architecture with Somnia Data Streams. * **Publisher Side:** The logic remained unchanged. Each publisher independently published its data using its wallet and the `sdk.streams.set()` method. * **Aggregator Side:** This is where the main logic came in. 1. We maintained a list of publishers we were interested in. 2. We fetched the data for each publisher separately using the `getAllPublisherDataForSchema` method. 3. We combined the incoming data into a single array (`allRecords.push(...)`). 4. Finally, we sorted all the data on the client-side to display them in a meaningful order (e.g., by timestamp). This pattern can be scaled to support any number of publishers and provides a robust foundation for building decentralized, multi-source applications. [PreviousIntegrate Chainlink Oracleschevron-left](https://docs.somnia.network/developer/data-streams/tutorials/integrate-chainlink-oracles) [NextThe DApp Publisher Proxy Patternchevron-right](https://docs.somnia.network/developer/data-streams/tutorials/the-dapp-publisher-proxy-pattern) Last updated 1 month ago * [Prerequisites](https://docs.somnia.network/developer/data-streams/tutorials/working-with-multiple-publishers-in-a-shared-stream#prerequisites) * [What You’ll Build](https://docs.somnia.network/developer/data-streams/tutorials/working-with-multiple-publishers-in-a-shared-stream#what-youll-build) * [Project Setup](https://docs.somnia.network/developer/data-streams/tutorials/working-with-multiple-publishers-in-a-shared-stream#project-setup) * [Configure Environment Variables](https://docs.somnia.network/developer/data-streams/tutorials/working-with-multiple-publishers-in-a-shared-stream#configure-environment-variables) * [Chain and Client Configuration](https://docs.somnia.network/developer/data-streams/tutorials/working-with-multiple-publishers-in-a-shared-stream#chain-and-client-configuration) * [Define the Shared Schema](https://docs.somnia.network/developer/data-streams/tutorials/working-with-multiple-publishers-in-a-shared-stream#define-the-shared-schema) * [Create the Publisher Script](https://docs.somnia.network/developer/data-streams/tutorials/working-with-multiple-publishers-in-a-shared-stream#create-the-publisher-script) * [Create the Aggregator Script](https://docs.somnia.network/developer/data-streams/tutorials/working-with-multiple-publishers-in-a-shared-stream#create-the-aggregator-script) * [Conclusion](https://docs.somnia.network/developer/data-streams/tutorials/working-with-multiple-publishers-in-a-shared-stream#conclusion) Copy mkdir somnia-aggregator cd somnia-aggregator npm init -y npm i @somnia-chain/streams viem dotenv npm i -D @types/node typescript ts-node Copy { "compilerOptions": { "target": "ES2020", "module": "commonjs", "strict": true, "esModuleInterop": true, "skipLibCheck": true, "forceConsistentCasingInFileNames": true, "outDir": "./dist" }, "include": ["src/**/*"] } Copy # .env RPC_URL=https://dream-rpc.somnia.network/ # Simulates two different devices/publishers PUBLISHER_1_PK=0xPUBLISHER_ONE_PRIVATE_KEY PUBLISHER_2_PK=0xPUBLISHER_TWO_PRIVATE_KEY Copy import { defineChain } from 'viem' export const somniaTestnet = defineChain({ id: 50312, name: 'Somnia Testnet', network: 'somnia-testnet', nativeCurrency: { name: 'STT', symbol: 'STT', decimals: 18 }, rpcUrls: { default: { http: ['[https://dream-rpc.somnia.network]'] }, public: { http: ['[https://dream-rpc.somnia.network]'] }, }, } as const) Copy import 'dotenv/config' import { createPublicClient, createWalletClient, http, PublicClient } from 'viem' import { privateKeyToAccount, PrivateKeyAccount } from 'viem/accounts' import { somniaTestnet } from './chain' function getEnv(key: string): string { const value = process.env[key] if (!value) { throw new Error(`Missing environment variable: ${key}`) } return value } // A single Public Client for read operations export const publicClient: PublicClient = createPublicClient({ chain: somniaTestnet, transport: http(getEnv('RPC_URL')), }) // Two different Wallet Clients for simulation export const walletClient1 = createWalletClient({ account: privateKeyToAccount(getEnv('PUBLISHER_1_PK') as `0x${string}`), chain: somniaTestnet, transport: http(getEnv('RPC_URL')), }) export const walletClient2 = createWalletClient({ account: privateKeyToAccount(getEnv('PUBLISHER_2_PK') as `0x${string}`), chain: somniaTestnet, transport: http(getEnv('RPC_URL')), }) Copy // This schema will be used by multiple devices export const telemetrySchema = 'uint64 timestamp, string deviceId, int32 x, int32 y, uint32 speed' Copy import 'dotenv/config' import { SDK, SchemaEncoder, zeroBytes32 } from '@somnia-chain/streams' import { publicClient, walletClient1, walletClient2 } from '../lib/clients' import { telemetrySchema } from '../lib/schema' import { toHex, Hex, WalletClient } from 'viem' import { waitForTransactionReceipt } from 'viem/actions' // Select which publisher to use async function getPublisher(): Promise<{ client: WalletClient, deviceId: string }> { const arg = process.argv[2] // 'p1' or 'p2' if (arg === 'p2') { console.log('Using Publisher 2 (Device B)') return { client: walletClient2, deviceId: 'device-b-002' } } console.log('Using Publisher 1 (Device A)') return { client: walletClient1, deviceId: 'device-a-001' } } // Helper function to encode the data function encodeTelemetry(encoder: SchemaEncoder, deviceId: string): Hex { const now = Date.now().toString() return encoder.encodeData([\ { name: "timestamp", value: now, type: "uint64" },\ { name: "deviceId", value: deviceId, type: "string" },\ { name: "x", value: Math.floor(Math.random() * 1000).toString(), type: "int32" },\ { name: "y", value: Math.floor(Math.random() * 1000).toString(), type: "int32" },\ { name: "speed", value: Math.floor(Math.random() * 120).toString(), type: "uint32" },\ ]) } async function main() { const { client, deviceId } = await getPublisher() const publisherAddress = client.account.address console.log(`Publisher Address: ${publisherAddress}`) const sdk = new SDK({ public: publicClient, wallet: client }) const encoder = new SchemaEncoder(telemetrySchema) // 1. Compute the Schema ID const schemaId = await sdk.streams.computeSchemaId(telemetrySchema) if (!schemaId) throw new Error('Could not compute schemaId') console.log(`Schema ID: ${schemaId}`) // 2. Register Schema (Updated for new API) console.log('Registering schema (if not already registered)...') const ignoreAlreadyRegisteredSchemas = true const regTx = await sdk.streams.registerDataSchemas([\ { \ schemaName: 'telemetry', // Updated: 'id' is now 'schemaName'\ schema: telemetrySchema, \ parentSchemaId: zeroBytes32 \ }\ ], ignoreAlreadyRegisteredSchemas) if (regTx) { console.log('Schema registration transaction sent:', regTx) await waitForTransactionReceipt(publicClient, { hash: regTx }) console.log('Schema registered successfully!') } else { console.log('Schema was already registered. No transaction sent.') } // 3. Encode the data const encodedData = encodeTelemetry(encoder, deviceId) // 4. Publish the data // We make the dataId unique with a timestamp and device ID const dataId = toHex(`${deviceId}-${Date.now()}`, { size: 32 }) const txHash = await sdk.streams.set([\ { id: dataId, schemaId, data: encodedData }\ ]) if (!txHash) throw new Error('Failed to publish data') console.log(`Publishing data... Tx: ${txHash}`) await waitForTransactionReceipt(publicClient, { hash: txHash }) console.log('Data published successfully!') } main().catch((e) => { console.error(e) process.exit(1) }) Copy "scripts": { "publish:p1": "ts-node src/scripts/publishData.ts p1", "publish:p2": "ts-node src/scripts/publishData.ts p2" } Copy # Terminal 1 npm run publish:p1 # Terminal 2 npm run publish:p2 Copy import 'dotenv/config' import { SDK, SchemaDecodedItem } from '@somnia-chain/streams' import { publicClient, walletClient1, walletClient2 } from '../lib/clients' import { telemetrySchema } from '../lib/schema' import { Address } from 'viem' // LIST OF PUBLISHERS TO TRACK // You could also fetch this list dynamically (e.g., from a contract or database). const TRACKED_PUBLISHERS: Address[] = [\ walletClient1.account.address,\ walletClient2.account.address,\ ] // Helper function to convert SDK data into a cleaner object // (Similar to the 'val' function in the Minimal On-Chain Chat App Tutorial) function decodeTelemetryRecord(row: SchemaDecodedItem[]): TelemetryRecord { const val = (field: any) => field?.value?.value ?? field?.value ?? '' return { timestamp: Number(val(row[0])), deviceId: String(val(row[1])), x: Number(val(row[2])), y: Number(val(row[3])), speed: Number(val(row[4])), } } // Type definition for our data interface TelemetryRecord { timestamp: number deviceId: string x: number y: number speed: number publisher?: Address // We will add this field later } async function main() { // The aggregator doesn't need to write data, so it only uses the publicClient const sdk = new SDK({ public: publicClient }) const schemaId = await sdk.streams.computeSchemaId(telemetrySchema) if (!schemaId) throw new Error('Could not compute schemaId') console.log(`Aggregator started. Tracking ${TRACKED_PUBLISHERS.length} publishers...`) console.log(`Schema ID: ${schemaId}\n`) const allRecords: TelemetryRecord[] = [] // 1. Loop through each publisher for (const publisherAddress of TRACKED_PUBLISHERS) { console.log(`--- Fetching data for ${publisherAddress} ---`) // 2. Fetch all data for the publisher based on the schema // Note: The SDK automatically decodes the data if the schema is registered const data = await sdk.streams.getAllPublisherDataForSchema(schemaId, publisherAddress) if (!data || data.length === 0) { console.log('No data found for this publisher.\n') continue } // 3. Transform the data and add the 'publisher' field const records: TelemetryRecord[] = (data as SchemaDecodedItem[][]).map(row => ({ ...decodeTelemetryRecord(row), publisher: publisherAddress // To know where the data came from })) console.log(`Found ${records.length} records.`) // 4. Add all records to the main list allRecords.push(...records) } // 5. Sort all data by timestamp console.log('\n--- Aggregation Complete ---') console.log(`Total records fetched: ${allRecords.length}`) allRecords.sort((a, b) => a.timestamp - b.timestamp) // 6. Display the result console.log('\n--- Combined and Sorted Telemetry Log ---') allRecords.forEach(record => { console.log( `[${new Date(record.timestamp).toISOString()}] [${record.publisher}] - Device: ${record.deviceId}, Speed: ${record.speed}` ) }) } main().catch((e) => { console.error(e) process.exit(1) }) Copy "scripts": { "publish:p1": "ts-node src/scripts/publishData.ts p1", "publish:p2": "ts-node src/scripts/publishData.ts p2", "aggregate": "ts-node src/scripts/aggregateData.ts" } Copy npm run aggregate --- # Local Testing and Forking | Somnia Docs circle-info This page shows how to spin up a local EVM node with **Hardhat** (and optionally **Anvil**) and fork **Somnia Testnet (Shannon)** or **Somnia Mainnet** for realistic testing. * * * [hashtag](https://docs.somnia.network/developer/development-frameworks/local-testing-and-forking#prerequisites) Prerequisites ---------------------------------------------------------------------------------------------------------------------------------- * Node.js 20+ * Hardhat (or Foundry/Anvil if you prefer) * A `.env` file with Somnia RPCs: .env (example) Copy # .env (example) SOMNIA_RPC_MAINNET=https://api.infra.mainnet.somnia.network/ SOMNIA_RPC_TESTNET=https://dream-rpc.somnia.network/ # Optional: pin block numbers for reproducible forks FORK_BLOCK_MAINNET= FORK_BLOCK_TESTNET= > Do not commit real keys/tokens. Use environment variables. * * * [hashtag](https://docs.somnia.network/developer/development-frameworks/local-testing-and-forking#quick-start-hardhat) Quick Start (Hardhat) ------------------------------------------------------------------------------------------------------------------------------------------------ 1 Install packages Copy npm i -D hardhat @nomicfoundation/hardhat-ethers ethers dotenv npx hardhat # if project not initialized yet cp .env.example .env || true 2 hardhat.config.ts Copy import { HardhatUserConfig } from "hardhat/config"; import "@nomicfoundation/hardhat-ethers"; import * as dotenv from "dotenv"; dotenv.config(); const cfgFromEnv = { mainnetUrl: process.env.SOMNIA_RPC_MAINNET || "https://api.infra.mainnet.somnia.network/", testnetUrl: process.env.SOMNIA_RPC_TESTNET || "https://dream-rpc.somnia.network/", forkMainnetBlock: process.env.FORK_BLOCK_MAINNET ? Number(process.env.FORK_BLOCK_MAINNET) : undefined, forkTestnetBlock: process.env.FORK_BLOCK_TESTNET ? Number(process.env.FORK_BLOCK_TESTNET) : undefined, }; const config: HardhatUserConfig = { solidity: "0.8.19", networks: { hardhat: { chainId: 31337, }, localhost: { url: "http://127.0.0.1:8545", chainId: 31337, }, somnia_testnet: { url: cfgFromEnv.testnetUrl, chainId: 50312, }, somnia_mainnet: { url: cfgFromEnv.mainnetUrl, chainId: 5031, }, }, }; export default config; * * * [hashtag](https://docs.somnia.network/developer/development-frameworks/local-testing-and-forking#testing-your-dapp-locally) Testing Your DApp Locally ---------------------------------------------------------------------------------------------------------------------------------------------------------- Once your Hardhat environment is set up, you can write and run tests for your smart contracts. This ensures your code works as expected before deploying it. **Create a Simple Smart Contract** First, create a basic smart contract to test. The `Counter.sol` contract below includes fundamental functions for incrementing a counter and retrieving its current value. Save this file in your project's `contracts` directory. ### [hashtag](https://docs.somnia.network/developer/development-frameworks/local-testing-and-forking#write-a-test-file) **Write a Test File** Next, write a test file to verify the functionality of your smart contract. The `Counter.test.ts` file below deploys the `Counter` contract on a local test network, calls the `increment` function, and checks whether the outcome is as expected. Save this file in your project's `test` directory. ### [hashtag](https://docs.somnia.network/developer/development-frameworks/local-testing-and-forking#run-your-tests) **Run Your Tests** To run your tests, navigate to your project's root directory in the terminal and use the following command. This command will execute your test scripts using Hardhat's built-in test network and display the results. * * * [hashtag](https://docs.somnia.network/developer/development-frameworks/local-testing-and-forking#forking-somnia-testnet-vs-mainnet) Forking Somnia (Testnet vs Mainnet) ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- _Forking is the process of copying the state of a live network, like Somnia Mainnet or Testnet, at a specific block and creating a simulation of it on your local machine. This powerful feature allows you to test how your contract will interact with other deployed contracts on the live network (such as a DEX, oracle, or NFT marketplace) using real-world data, but without any of the risk or cost._ You can fork Shannon Testnet for faster iteration or Mainnet for production-like state. For deterministic CI, always pin a blockNumber. Fork Testnet (Shannon) Fork Mainnet > Testnet (STT) is ideal for validating flows and cheaper RPC limits; Mainnet (SOMI) reflects real contract/state and gas rules. * * * [hashtag](https://docs.somnia.network/developer/development-frameworks/local-testing-and-forking#handy-rpc-tricks-hardhat) Handy RPC Tricks (Hardhat) ---------------------------------------------------------------------------------------------------------------------------------------------------------- These RPC methods provided by Hardhat Network allow you to manipulate the blockchain state for advanced testing scenarios. ### [hashtag](https://docs.somnia.network/developer/development-frameworks/local-testing-and-forking#impersonate-an-account) **Impersonate an account** This allows you to execute transactions from any wallet address on the forked chain, which is perfect for testing functions with admin privileges or interacting with contracts using an account that holds a large amount of tokens ("whale"). ### [hashtag](https://docs.somnia.network/developer/development-frameworks/local-testing-and-forking#time-travel) **Time travel** This feature lets you change the timestamp of future blocks. It's incredibly useful for testing time-dependent smart contract logic, such as vesting schedules, lock-up periods, or any functionality that relies on `block.timestamp`. ### [hashtag](https://docs.somnia.network/developer/development-frameworks/local-testing-and-forking#snapshot-and-revert) **Snapshot and Revert** This allows you to save the current state of the blockchain and later restore it instantly. It's an efficient way to isolate your tests, ensuring that each test case starts from the same clean state without needing to restart the local node. ### [hashtag](https://docs.somnia.network/developer/development-frameworks/local-testing-and-forking#reset-fork) **Reset fork** This command resets the local Hardhat node to a fresh state forked from the blockchain. You can use it to switch to a different block number or even a different RPC endpoint without restarting your entire testing process. * * * [hashtag](https://docs.somnia.network/developer/development-frameworks/local-testing-and-forking#alternative-anvil-foundry) Alternative: Anvil (Foundry) ------------------------------------------------------------------------------------------------------------------------------------------------------------- Fork Testnet Fork Mainnet Notes Point your app/tests to `http://127.0.0.1:8546`. Anvil JSON-RPC is Hardhat-compatible for most calls (`evm_*`). Replace `hardhat_*` with Anvil equivalents where needed. * * * [hashtag](https://docs.somnia.network/developer/development-frameworks/local-testing-and-forking#tips-and-best-practices) Tips & Best Practices ---------------------------------------------------------------------------------------------------------------------------------------------------- * Always pin block numbers in forks for reproducibility. * Prefer `localhost` network when you need state persistence across multiple test files. * Keep Anvil/Hardhat on separate ports if you run both simultaneously. * Use labels/comments in tests to describe assumptions tied to a specific fork block. * Avoid draining RPC rate limits: cache fixtures, use snapshots, and fork testnet for most flows. * * * [hashtag](https://docs.somnia.network/developer/development-frameworks/local-testing-and-forking#common-issues) Common Issues ---------------------------------------------------------------------------------------------------------------------------------- chevron-rightI**nsufficient funds for gas**[hashtag](https://docs.somnia.network/developer/development-frameworks/local-testing-and-forking#insufficient-funds-for-gas) Top up native balance with \`hardhat\_setBalance\`. chevron-rightN**once too high / replacement underpriced**[hashtag](https://docs.somnia.network/developer/development-frameworks/local-testing-and-forking#nonce-too-high-replacement-underpriced) Reset account state (new snapshot) or use a fresh signer. chevron-right**Contract already deployed at address**[hashtag](https://docs.somnia.network/developer/development-frameworks/local-testing-and-forking#contract-already-deployed-at-address) On persistent localhost, restart node or change deployer nonce. [PreviousDevelopment Frameworkschevron-left](https://docs.somnia.network/developer/development-frameworks) [NextDeploy with RemixIDEchevron-right](https://docs.somnia.network/developer/development-frameworks/deploy-with-remixide) Last updated 1 month ago * [Prerequisites](https://docs.somnia.network/developer/development-frameworks/local-testing-and-forking#prerequisites) * [Quick Start (Hardhat)](https://docs.somnia.network/developer/development-frameworks/local-testing-and-forking#quick-start-hardhat) * [Testing Your DApp Locally](https://docs.somnia.network/developer/development-frameworks/local-testing-and-forking#testing-your-dapp-locally) * [Write a Test File](https://docs.somnia.network/developer/development-frameworks/local-testing-and-forking#write-a-test-file) * [Run Your Tests](https://docs.somnia.network/developer/development-frameworks/local-testing-and-forking#run-your-tests) * [Forking Somnia (Testnet vs Mainnet)](https://docs.somnia.network/developer/development-frameworks/local-testing-and-forking#forking-somnia-testnet-vs-mainnet) * [Handy RPC Tricks (Hardhat)](https://docs.somnia.network/developer/development-frameworks/local-testing-and-forking#handy-rpc-tricks-hardhat) * [Impersonate an account](https://docs.somnia.network/developer/development-frameworks/local-testing-and-forking#impersonate-an-account) * [Time travel](https://docs.somnia.network/developer/development-frameworks/local-testing-and-forking#time-travel) * [Snapshot and Revert](https://docs.somnia.network/developer/development-frameworks/local-testing-and-forking#snapshot-and-revert) * [Reset fork](https://docs.somnia.network/developer/development-frameworks/local-testing-and-forking#reset-fork) * [Alternative: Anvil (Foundry)](https://docs.somnia.network/developer/development-frameworks/local-testing-and-forking#alternative-anvil-foundry) * [Tips & Best Practices](https://docs.somnia.network/developer/development-frameworks/local-testing-and-forking#tips-and-best-practices) * [Common Issues](https://docs.somnia.network/developer/development-frameworks/local-testing-and-forking#common-issues) contracts/Counter.sol Copy // SPDX-License-Identifier: MIT pragma solidity ^0.8.19; contract Counter { uint256 private count; event CountedTo(uint256 number); function getCount() public view returns (uint256) { return count; } function increment() public { count += 1; emit CountedTo(count); } } test/Counter.test.ts Copy import { expect } from "chai"; import { ethers } from "hardhat"; describe("Counter Contract", function () { it("Should increment the count by 1", async function () { const Counter = await ethers.getContractFactory("Counter"); const counter = await Counter.deploy(); await counter.waitForDeployment(); expect(await counter.getCount()).to.equal(0); const tx = await counter.increment(); await tx.wait(); expect(await counter.getCount()).to.equal(1); }); it("Should emit a CountedTo event", async function () { const Counter = await ethers.getContractFactory("Counter"); const counter = await Counter.deploy(); await counter.waitForDeployment(); await expect(counter.increment()).to.emit(counter, "CountedTo").withArgs(1); }); }); Copy # Run tests on the default in-process Hardhat network npx hardhat test # Or, start a local node in a separate terminal npx hardhat node # Then run tests against it npx hardhat test --network localhost Copy hardhat: { forking: { url: process.env.SOMNIA_RPC_TESTNET!, blockNumber: process.env.FORK_BLOCK_TESTNET ? Number(process.env.FORK_BLOCK_TESTNET) : undefined, }, } Copy # in-process fork npx hardhat test # persistent node npx hardhat node --fork $SOMNIA_RPC_TESTNET ${FORK_BLOCK_TESTNET:+--fork-block-number $FORK_BLOCK_TESTNET} Copy hardhat: { forking: { url: process.env.SOMNIA_RPC_MAINNET!, blockNumber: process.env.FORK_BLOCK_MAINNET ? Number(process.env.FORK_BLOCK_MAINNET) : undefined, }, } Copy # in-process fork npx hardhat test # persistent node npx hardhat node --fork $SOMNIA_RPC_MAINNET ${FORK_BLOCK_MAINNET:+--fork-block-number $FORK_BLOCK_MAINNET} impersonate.ts Copy import { ethers, network } from "hardhat"; async function main() { const target = "0xYourSomniaAddress"; // account to impersonate await network.provider.request({ method: "hardhat_impersonateAccount", params: [target] }); const signer = await ethers.getSigner(target); await network.provider.send("hardhat_setBalance", [\ target,\ "0x152d02c7e14af6800000" // 1000 ether in wei\ ]); console.log("Impersonating:", await signer.getAddress()); } main().catch((e) => { console.error(e); process.exit(1); }); Copy await network.provider.send("evm_setNextBlockTimestamp", [Math.floor(Date.now()/1000) + 3600]); await network.provider.send("evm_mine"); Copy const id = await network.provider.send("evm_snapshot"); // ... run actions ... await network.provider.send("evm_revert", [id]); Copy await network.provider.request({ method: "hardhat_reset", params: [{ forking: { url: process.env.SOMNIA_RPC_TESTNET!, blockNumber: Number(process.env.FORK_BLOCK_TESTNET||0) || undefined } }] }); Copy anvil --fork-url $SOMNIA_RPC_TESTNET ${FORK_BLOCK_TESTNET:+--fork-block-number $FORK_BLOCK_TESTNET} --port 8546 Copy anvil --fork-url $SOMNIA_RPC_MAINNET ${FORK_BLOCK_MAINNET:+--fork-block-number $FORK_BLOCK_MAINNET} --port 8546 --- # Subgraphs | Somnia Docs [hashtag](https://docs.somnia.network/developer/deployment-and-production/ecosystem/ecosystem-tools/subgraphs#protofire) [Protofirearrow-up-right](http://protofire.io/) ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ Protofire deploys custom, compatible oracles using the same data providers and node operators, allowing protocols to connect to their network without modifying Smart Contracts. ### [hashtag](https://docs.somnia.network/developer/deployment-and-production/ecosystem/ecosystem-tools/subgraphs#resources) Resources * [Create Subgraphsarrow-up-right](http://somnia.chain.love/) * [Documentation](https://docs.somnia.network/developer/building-dapps/data-indexing-and-querying/protofire-subgraph) * * * [hashtag](https://docs.somnia.network/developer/deployment-and-production/ecosystem/ecosystem-tools/subgraphs#ormi) [Ormiarrow-up-right](https://ormilabs.com/) --------------------------------------------------------------------------------------------------------------------------------------------------------------------- Ormi is the only unified Web3 data layer to supercharge live, historical, and AI-powered blockchain data applications ### [hashtag](https://docs.somnia.network/developer/deployment-and-production/ecosystem/ecosystem-tools/subgraphs#resources-1) Resources * [Create Subgraphsarrow-up-right](https://subgraph.somnia.network/) * [Documentation](https://docs.somnia.network/developer/building-dapps/data-indexing-and-querying/ormi-subgraph) [PreviousOracleschevron-left](https://docs.somnia.network/developer/deployment-and-production/ecosystem/ecosystem-tools/oracles) [NextWallet Providerschevron-right](https://docs.somnia.network/developer/deployment-and-production/ecosystem/ecosystem-tools/wallet-providers) Last updated 6 months ago * [Protofire](https://docs.somnia.network/developer/deployment-and-production/ecosystem/ecosystem-tools/subgraphs#protofire) * [Resources](https://docs.somnia.network/developer/deployment-and-production/ecosystem/ecosystem-tools/subgraphs#resources) * [Ormi](https://docs.somnia.network/developer/deployment-and-production/ecosystem/ecosystem-tools/subgraphs#ormi) * [Resources](https://docs.somnia.network/developer/deployment-and-production/ecosystem/ecosystem-tools/subgraphs#resources-1) --- # Create ERC721 NFT Collections | Somnia Docs ERC721 is the EVM compatible standard for Non Fungible Tokens, NFTs. NFTS are digital assets where each token is unique. Unlike ERC20 (fungible) tokens that are interchangeable, ERC721 tokens represent distinct items such as game assets, collectibles, tickets, certificates, or onchain identities. Somnia, being EVM-compatible, supports the ERC721 standard natively. ERC721 has the following functionalities: * Every token has a distinct `tokenId` and (optionally) distinct metadata, making it unique. * Wallets can own, transfer, and approve NFTs using a common interface. * NFTs are composable; therefore, Wallets, marketplaces, and dApps understand ERC-721 uniformly, enabling easy listing, trading, and display. * ERC721 has a clean base that can be extended with metadata, enumeration, royalties (ERC-2981), permit (EIP-4494), etc. This guide will teach you how to connect to and deploy your ERC20 Smart Contract to the Somia Network using Hardhat. [hashtag](https://docs.somnia.network/developer/building-dapps/tokens-and-nfts/create-erc721-nft-collections#pre-requisite) Pre-requisite ---------------------------------------------------------------------------------------------------------------------------------------------- * This guide is not an introduction to Solidity Programming; you are expected to have a basic understanding of Solidity Programming. * To complete this guide, you will need MetaMask installed and the Somnia Network added to the list of Networks. If you have yet to install MetaMask, please follow this guide to [Connect Your Wallet](https://docs.somnia.network/get-started/connect-your-wallet-to-mainnet) . The Smart Contract is minimal, production-friendly ERC-721 without royalties (no ERC-2981). Per-token tokenURI set during mint (works great with IPFS). It also demonstrates how to deploy, mint, and verify on Somnia networks. [hashtag](https://docs.somnia.network/developer/building-dapps/tokens-and-nfts/create-erc721-nft-collections#erc721-smart-contract) ERC721 Smart Contract -------------------------------------------------------------------------------------------------------------------------------------------------------------- chevron-right`NFTTest.sol`[hashtag](https://docs.somnia.network/developer/building-dapps/tokens-and-nfts/create-erc721-nft-collections#nfttest.sol) Copy // SPDX-License-Identifier: MIT // Compatible with OpenZeppelin Contracts ^5.4.0 pragma solidity ^0.8.27; import {ERC721} from "@openzeppelin/contracts/token/ERC721/ERC721.sol"; import {ERC721URIStorage} from "@openzeppelin/contracts/token/ERC721/extensions/ERC721URIStorage.sol"; import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; contract NFTTest is ERC721, ERC721URIStorage, Ownable { uint256 private _nextTokenId; constructor(address initialOwner) ERC721("NFTTest", "NFTT") Ownable(initialOwner) {} function _baseURI() internal pure override returns (string memory) { return "https://ipfs.io"; } function safeMint(address to, string memory uri) public onlyOwner returns (uint256) { uint256 tokenId = _nextTokenId++; _safeMint(to, tokenId); _setTokenURI(tokenId, uri); return tokenId; } function tokenURI(uint256 tokenId) public view override(ERC721, ERC721URIStorage) returns (string memory) { return super.tokenURI(tokenId); } function supportsInterface(bytes4 interfaceId) public view override(ERC721, ERC721URIStorage) returns (bool) { return super.supportsInterface(interfaceId); } } [hashtag](https://docs.somnia.network/developer/building-dapps/tokens-and-nfts/create-erc721-nft-collections#code-breakdown) Code Breakdown ------------------------------------------------------------------------------------------------------------------------------------------------ Below is a breakdown explanation of the code: ### [hashtag](https://docs.somnia.network/developer/building-dapps/tokens-and-nfts/create-erc721-nft-collections#imports) Imports `ERC721` is the Core NFT standard that has all the methods that control `ownership`, `transfers`, `approvals`, and `metadata` hook. `ERC721URIStorage` adds per-token URI storage via `_setTokenURI` and overrides `tokenURI`. ERC721URIStorage extends ERC721 to store URIs per token (costs storage gas, but very flexible). `Ownable` is a simple access control library that enables the Smart Contract owner to set one account as the “owner”. ### [hashtag](https://docs.somnia.network/developer/building-dapps/tokens-and-nfts/create-erc721-nft-collections#contract-declaration) Contract Declaration `_nextTokenId` is an Internal counter for new token IDs. Starts at `0` by default (since not set), so your first mint is `tokenId = 0`. ### [hashtag](https://docs.somnia.network/developer/building-dapps/tokens-and-nfts/create-erc721-nft-collections#constructor) Constructor Calls `ERC721` constructor with collection name "NFTTest" and symbol "NFTT". Initializes Ownable with `initialOwner` the address allowed to mint tokens. In this instance, the body is empty because all setup is done via parent constructors. This ERC-721 contract follows a straightforward ownership and metadata model designed for simplicity and marketplace compatibility. The contract owner is the sole minter, and each mint assigns the next sequential identifier—beginning at 0—to the specified recipient. This ensures that token IDs remain predictable (0, 1, 2, …) and facilitates aligning your metadata files with minted tokens. ### [hashtag](https://docs.somnia.network/developer/building-dapps/tokens-and-nfts/create-erc721-nft-collections#base-uri) Base URI Returns a prefix used by `ERC721.tokenURI` when a token’s stored URI is relative (e.g., "/ipfs//1.json"). In this contract you set full URIs at mint (commonly ipfs://...), which are returned as-is by `ERC721URIStorage`. So this Base URI only matters if you mint with relative paths. An example Base URI when using IPFS is then `https://ipfs.io/` ### [hashtag](https://docs.somnia.network/developer/building-dapps/tokens-and-nfts/create-erc721-nft-collections#minting) Minting In this contract, minting is restricted by `onlyOwner`, ensuring that only the contract owner can create new tokens. Token IDs are assigned sequentially using `_nextTokenId++`, starting from 0 and incrementing by one with each mint. The `_safeMint` function adds an extra layer of safety by checking if the recipient is a contract and, if so, requiring it to implement `IERC721Receiver` to prevent tokens from being locked. For metadata, `_setTokenURI` stores the exact URI string for each token, such as `ipfs:///1.json`, making it easy to reference unique files. The function also emits and returns the new `tokenId` upon minting. However, it’s worth noting that storing a full string per token consumes more gas; for larger drops with sequential files, a cheaper alternative is to use a base URI combined with the token ID rather than storing individual URIs. ### [hashtag](https://docs.somnia.network/developer/building-dapps/tokens-and-nfts/create-erc721-nft-collections#required-overrides) Required overrides Because both ERC721 and ERC721URIStorage define tokenURI, Solidity requires you to pick which implementation to use. `super.tokenURI(tokenId)` resolves to ERC721URIStorage’s version, which: * Returns the stored URI if present. * Otherwise falls back to ERC721’s baseURI and tokenId behavior. Also resolves multiple inheritance for supportsInterface (ERC165) and ensures the contract correctly reports ERC721 and metadata support. Transfers and approvals behave exactly as the ERC721 standard specifies. Use `safeTransferFrom` by default, so the contract checks that recipients can handle NFTs, which prevents tokens from being sent to incompatible contracts. `transferFrom` is available when you are certain the recipient can accept NFTs without the receiver check, and approvals can be granted either per token with approve or globally with `setApprovalForAll`. Interface support is advertised through supportsInterface, allowing wallets, marketplaces, and indexers to recognize ERC721 core and metadata compatibility automatically. A few best practices will keep deployments robust. Favor the `ipfs://` scheme for `metadata` and media to avoid locking yourself to a single HTTP gateway. If you expect very large drops, remember that `ERC721URIStorage` stores a full string per token, which is convenient but more expensive at scale; collections with sequential filenames can reduce costs by adopting a `baseURI` and `tokenId` pattern instead of per token URI storage. [hashtag](https://docs.somnia.network/developer/building-dapps/tokens-and-nfts/create-erc721-nft-collections#initialize-hardhat) Initialize Hardhat -------------------------------------------------------------------------------------------------------------------------------------------------------- Create `.env`: chevron-right`hardhat.config.ts`[hashtag](https://docs.somnia.network/developer/building-dapps/tokens-and-nfts/create-erc721-nft-collections#hardhat.config.ts) Add the `Smart Contract` to the Contract directory [hashtag](https://docs.somnia.network/developer/building-dapps/tokens-and-nfts/create-erc721-nft-collections#deploy-with-ignition) Deploy with Ignition ------------------------------------------------------------------------------------------------------------------------------------------------------------ Create `ignition/modules/NFTTest.ts`: Deploy: Copy the **deployed address** from the output. * * * [hashtag](https://docs.somnia.network/developer/building-dapps/tokens-and-nfts/create-erc721-nft-collections#verify-smart-contract) Verify Smart Contract -------------------------------------------------------------------------------------------------------------------------------------------------------------- > Ensure compiler version, optimizer, and constructor args match. * * * ### [hashtag](https://docs.somnia.network/developer/building-dapps/tokens-and-nfts/create-erc721-nft-collections#mint-your-collection) Mint your collection We’ll mint by calling `safeMint(to, uri)` **10 times**, matching your uploaded metadata files. chevron-right`scripts/mint.ts`[hashtag](https://docs.somnia.network/developer/building-dapps/tokens-and-nfts/create-erc721-nft-collections#scripts-mint.ts) Run the project: * * * [hashtag](https://docs.somnia.network/developer/building-dapps/tokens-and-nfts/create-erc721-nft-collections#inspect-token-metadata) Inspect Token metadata ---------------------------------------------------------------------------------------------------------------------------------------------------------------- Read a token’s URI (e.g., tokenId `0`) and open it in your browser. `scripts/read-uri.ts`: Open the printed URL (it should be `https://ipfs.io/ipfs//0.json`) in your browser to confirm JSON and `image` render correctly. Congratulations. 🎉 You have deployed your first ERC721 Smart Contract to the Somnia Network. 🎉 [PreviousCreate ERC20 Tokenschevron-left](https://docs.somnia.network/developer/building-dapps/tokens-and-nfts/create-erc20-tokens) [NextManaging NFT Metadata with IPFSchevron-right](https://docs.somnia.network/developer/building-dapps/tokens-and-nfts/managing-nft-metadata-with-ipfs) Last updated 5 months ago * [Pre-requisite](https://docs.somnia.network/developer/building-dapps/tokens-and-nfts/create-erc721-nft-collections#pre-requisite) * [ERC721 Smart Contract](https://docs.somnia.network/developer/building-dapps/tokens-and-nfts/create-erc721-nft-collections#erc721-smart-contract) * [Code Breakdown](https://docs.somnia.network/developer/building-dapps/tokens-and-nfts/create-erc721-nft-collections#code-breakdown) * [Imports](https://docs.somnia.network/developer/building-dapps/tokens-and-nfts/create-erc721-nft-collections#imports) * [Contract Declaration](https://docs.somnia.network/developer/building-dapps/tokens-and-nfts/create-erc721-nft-collections#contract-declaration) * [Constructor](https://docs.somnia.network/developer/building-dapps/tokens-and-nfts/create-erc721-nft-collections#constructor) * [Base URI](https://docs.somnia.network/developer/building-dapps/tokens-and-nfts/create-erc721-nft-collections#base-uri) * [Minting](https://docs.somnia.network/developer/building-dapps/tokens-and-nfts/create-erc721-nft-collections#minting) * [Required overrides](https://docs.somnia.network/developer/building-dapps/tokens-and-nfts/create-erc721-nft-collections#required-overrides) * [Initialize Hardhat](https://docs.somnia.network/developer/building-dapps/tokens-and-nfts/create-erc721-nft-collections#initialize-hardhat) * [Deploy with Ignition](https://docs.somnia.network/developer/building-dapps/tokens-and-nfts/create-erc721-nft-collections#deploy-with-ignition) * [Verify Smart Contract](https://docs.somnia.network/developer/building-dapps/tokens-and-nfts/create-erc721-nft-collections#verify-smart-contract) * [Mint your collection](https://docs.somnia.network/developer/building-dapps/tokens-and-nfts/create-erc721-nft-collections#mint-your-collection) * [Inspect Token metadata](https://docs.somnia.network/developer/building-dapps/tokens-and-nfts/create-erc721-nft-collections#inspect-token-metadata) Copy import {ERC721} from "@openzeppelin/contracts/token/ERC721/ERC721.sol"; import {ERC721URIStorage} from "@openzeppelin/contracts/token/ERC721/extensions/ERC721URIStorage.sol"; import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; Copy contract NFTTest is ERC721, ERC721URIStorage, Ownable { uint256 private _nextTokenId; ... } Copy constructor(address initialOwner) ERC721("NFTTest", "NFTT") Ownable(initialOwner) {} Copy function _baseURI() internal pure override returns (string memory) { return "https://ipfs.io"; } Copy function safeMint(address to, string memory uri) public onlyOwner returns (uint256) { uint256 tokenId = _nextTokenId++; _safeMint(to, tokenId); _setTokenURI(tokenId, uri); return tokenId; } Copy function tokenURI(uint256 tokenId) public view override(ERC721, ERC721URIStorage) returns (string memory) { return super.tokenURI(tokenId); } Copy function supportsInterface(bytes4 interfaceId) public view override(ERC721, ERC721URIStorage) returns (bool) { return super.supportsInterface(interfaceId); } Copy mkdir somnia-nft && cd somnia-nft npm init -y npm install --save-dev hardhat typescript ts-node @types/node npx hardhat npm install @openzeppelin/contracts npm install --save-dev @nomicfoundation/hardhat-verify @nomicfoundation/hardhat-ignition @nomicfoundation/hardhat-ignition-ethers Copy PRIVATE_KEY=0xYourPrivateKey SOMNIA_RPC_HTTPS=https://dream-rpc.somnia.network Copy import "dotenv/config"; import "@nomicfoundation/hardhat-verify"; import "@nomicfoundation/hardhat-ignition-ethers"; import { HardhatUserConfig } from "hardhat/config"; const config: HardhatUserConfig = { solidity: "0.8.28", networks: { somnia: { url: process.env.SOMNIA_RPC_HTTPS!, accounts: [process.env.PRIVATE_KEY!], }, }, sourcify: { enabled: false }, etherscan: { apiKey: { somnia: process.env.SOMNIA_EXPLORER_API_KEY || "" }, customChains: [\ {\ network: "somnia",\ chainId: 50312,\ urls: {\ apiURL: "https://shannon-explorer.somnia.network/api",\ browserURL: "https://shannon-explorer.somnia.network",\ },\ },\ ], }, }; export default config; Copy import { buildModule } from "@nomicfoundation/hardhat-ignition/modules"; const NFTTestModule = buildModule("NFTTestModule", (m) => { const initialOwner = m.getParameter("initialOwner", "0xYourOwnerAddress"); const nft = m.contract("NFTTest", [initialOwner]); return { nft }; }); export default NFTTestModule; Copy npx hardhat ignition deploy ignition/modules/NFTTest.ts --network somnia Copy npx hardhat verify --network somnia 0xYourOwnerAddress Copy import { ethers } from "hardhat"; async function main() { const contractAddr = ""; const nft = await ethers.getContractAt("NFTTest", contractAddr); const owner = (await ethers.getSigners())[0]; // Example: 10 tokens at /ipfs//{0..9}.json const CID = ""; for (let i = 0; i < 10; i++) { const uri = `/ipfs/${CID}/${i}.json`; const tx = await nft.safeMint(owner.address, uri); console.log(`Mint tx ${i}:`, tx.hash); await tx.wait(); } const lastId = await nft.callStatic.safeMint(owner.address, `/ipfs/${CID}/999.json`).catch(()=>null); console.log("Minted 10 tokens. Next simulated ID (no state change):", lastId ?? "N/A"); } main().catch((e) => { console.error(e); process.exit(1); }); Copy npx hardhat run scripts/mint.ts --network somnia Copy import { ethers } from "hardhat"; async function main() { const contractAddr = ""; const nft = await ethers.getContractAt("NFTTest", contractAddr); const uri = await nft.tokenURI(0); console.log("tokenURI(0):", uri); } main().catch(console.error); Copy npx hardhat run scripts/read-uri.ts --network somnia --- # Somnia Exchange | Somnia Docs Somnia Exchange is a gamified decentralized exchange (DEX) on Somnia Network. Trade tokens, provide liquidity, and earn rewards through gaming features. [hashtag](https://docs.somnia.network/developer/deployment-and-production/ecosystem/ecosystem-showcase/somnia-exchange#how-to-trade-tokens) How to Trade Tokens -------------------------------------------------------------------------------------------------------------------------------------------------------------------- ### [hashtag](https://docs.somnia.network/developer/deployment-and-production/ecosystem/ecosystem-showcase/somnia-exchange#token-swapping) Token Swapping 1 #### [hashtag](https://docs.somnia.network/developer/deployment-and-production/ecosystem/ecosystem-showcase/somnia-exchange#connect-wallet) Connect Wallet Visit [somnia.exchangearrow-up-right](https://somnia.exchange/) and connect your Web3 wallet. 2 #### [hashtag](https://docs.somnia.network/developer/deployment-and-production/ecosystem/ecosystem-showcase/somnia-exchange#select-tokens) Select Tokens Choose "From" and "To" tokens for your swap. 3 #### [hashtag](https://docs.somnia.network/developer/deployment-and-production/ecosystem/ecosystem-showcase/somnia-exchange#enter-amount) Enter Amount Input the amount you want to trade. 4 #### [hashtag](https://docs.somnia.network/developer/deployment-and-production/ecosystem/ecosystem-showcase/somnia-exchange#review-trade) Review Trade Check exchange rate and slippage tolerance. 5 #### [hashtag](https://docs.somnia.network/developer/deployment-and-production/ecosystem/ecosystem-showcase/somnia-exchange#execute-swap) Execute Swap Confirm transaction in your wallet. [hashtag](https://docs.somnia.network/developer/deployment-and-production/ecosystem/ecosystem-showcase/somnia-exchange#available-trading-pairs) Available Trading Pairs ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- * **SOMI/USDC.e**: Main trading pair with highest liquidity * **USDC.e/NIA**: Secondary major pair * **SOMI/NIA**: Alternative SOMI trading option * **Various Meme Tokens**: CHICK, JELLU, BOB, and others _Available trading pairs list_ ![](https://docs.somnia.network/~gitbook/image?url=https%3A%2F%2F2122549367-files.gitbook.io%2F%7E%2Ffiles%2Fv0%2Fb%2Fgitbook-x-prod.appspot.com%2Fo%2Fspaces%252FkYErT9t3BJtpPfejLO6I%252Fuploads%252FAamZxECkcnmUdjEKUNBl%252FSomnia-Exchane.png%3Falt%3Dmedia%26token%3D11434f3c-1bf7-4cc1-872e-535cfae33c60&width=768&dpr=3&quality=100&sign=5998e8ab&sv=2) [hashtag](https://docs.somnia.network/developer/deployment-and-production/ecosystem/ecosystem-showcase/somnia-exchange#how-to-add-liquidity-pools) How to Add Liquidity Pools ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- #### [hashtag](https://docs.somnia.network/developer/deployment-and-production/ecosystem/ecosystem-showcase/somnia-exchange#step-by-step-liquidity-provision) Step-by-Step Liquidity Provision 1 #### [hashtag](https://docs.somnia.network/developer/deployment-and-production/ecosystem/ecosystem-showcase/somnia-exchange#access-pools) Access Pools Go to [somnia.exchange/poolsarrow-up-right](https://somnia.exchange/pools) 2 #### [hashtag](https://docs.somnia.network/developer/deployment-and-production/ecosystem/ecosystem-showcase/somnia-exchange#choose-pool) Choose Pool Select existing pool or create new pair.\\ 3 #### [hashtag](https://docs.somnia.network/developer/deployment-and-production/ecosystem/ecosystem-showcase/somnia-exchange#add-liquidity) Add Liquidity Click "Add Liquidity" on desired pool. 4 #### [hashtag](https://docs.somnia.network/developer/deployment-and-production/ecosystem/ecosystem-showcase/somnia-exchange#deposit-tokens) Deposit Tokens Provide equal value of both tokens in the pair. 5 #### [hashtag](https://docs.somnia.network/developer/deployment-and-production/ecosystem/ecosystem-showcase/somnia-exchange#confirm-transaction) Confirm Transaction Approve token spending and confirm liquidity addition. 6 #### [hashtag](https://docs.somnia.network/developer/deployment-and-production/ecosystem/ecosystem-showcase/somnia-exchange#receive-lp-tokens) Receive LP Tokens Get LP tokens representing your pool share. [hashtag](https://docs.somnia.network/developer/deployment-and-production/ecosystem/ecosystem-showcase/somnia-exchange#liquidity-rewards) Liquidity Rewards ---------------------------------------------------------------------------------------------------------------------------------------------------------------- * **Trading Fees**: Earn 0.3% of all trades in your pool 4 * **Liquidity Points**: Participate in points program for additional rewards 1 * **Pool Multipliers**: Different pools have varying reward multipliers based on risk 1 [hashtag](https://docs.somnia.network/developer/deployment-and-production/ecosystem/ecosystem-showcase/somnia-exchange#portfolio-management) Portfolio Management ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- #### [hashtag](https://docs.somnia.network/developer/deployment-and-production/ecosystem/ecosystem-showcase/somnia-exchange#track-your-holdings) Track Your Holdings 1 #### [hashtag](https://docs.somnia.network/developer/deployment-and-production/ecosystem/ecosystem-showcase/somnia-exchange#connect-wallet-1) Connect Wallet Link your wallet to view portfolio. 2 #### [hashtag](https://docs.somnia.network/developer/deployment-and-production/ecosystem/ecosystem-showcase/somnia-exchange#view-balances) View Balances See all token holdings and values. 3 #### [hashtag](https://docs.somnia.network/developer/deployment-and-production/ecosystem/ecosystem-showcase/somnia-exchange#transaction-history) Transaction History Review all trading and liquidity activities. [hashtag](https://docs.somnia.network/developer/deployment-and-production/ecosystem/ecosystem-showcase/somnia-exchange#play-game-features) Play Game Features ------------------------------------------------------------------------------------------------------------------------------------------------------------------ #### [hashtag](https://docs.somnia.network/developer/deployment-and-production/ecosystem/ecosystem-showcase/somnia-exchange#gamified-trading-experience) Gamified Trading Experience Access the gaming features at [game.somnia.exchangearrow-up-right](https://game.somnia.exchange/) #### [hashtag](https://docs.somnia.network/developer/deployment-and-production/ecosystem/ecosystem-showcase/somnia-exchange#energy-stations) Energy Stations 1 #### [hashtag](https://docs.somnia.network/developer/deployment-and-production/ecosystem/ecosystem-showcase/somnia-exchange#stake-lp-tokens) Stake LP Tokens Deposit your LP tokens in Energy Stations 2 2 #### [hashtag](https://docs.somnia.network/developer/deployment-and-production/ecosystem/ecosystem-showcase/somnia-exchange#generate-energy) Generate Energy Earn energy points from staked positions. 3 #### [hashtag](https://docs.somnia.network/developer/deployment-and-production/ecosystem/ecosystem-showcase/somnia-exchange#unlock-rewards) Unlock Rewards Use energy to access special features and bonuses. 4 #### [hashtag](https://docs.somnia.network/developer/deployment-and-production/ecosystem/ecosystem-showcase/somnia-exchange#multiple-pools) Multiple Pools Choose from 2 different energy station pools 2 _Image placeholder: Gaming features and energy stations_ ### [hashtag](https://docs.somnia.network/developer/deployment-and-production/ecosystem/ecosystem-showcase/somnia-exchange#gaming-benefits) Gaming Benefits * **Enhanced Rewards**: Earn additional tokens through gaming mechanics * **NFT Rewards**: Collect NFTs through trading activities 3 * **Feed & Energy System**: Power up your trading with gaming elements 3 * **Competitive Elements**: Participate in trading contests and leaderboards [hashtag](https://docs.somnia.network/developer/deployment-and-production/ecosystem/ecosystem-showcase/somnia-exchange#how-to-start-gaming) How to Start Gaming -------------------------------------------------------------------------------------------------------------------------------------------------------------------- 1 #### [hashtag](https://docs.somnia.network/developer/deployment-and-production/ecosystem/ecosystem-showcase/somnia-exchange#connect-wallet-2) Connect Wallet Link your wallet to the gaming platform. 2 #### [hashtag](https://docs.somnia.network/developer/deployment-and-production/ecosystem/ecosystem-showcase/somnia-exchange#provide-liquidity) Provide Liquidity Add liquidity to earn LP tokens. 3 #### [hashtag](https://docs.somnia.network/developer/deployment-and-production/ecosystem/ecosystem-showcase/somnia-exchange#stake-in-energy-stations) Stake in Energy Stations Deposit LP tokens to generate energy. 4 #### [hashtag](https://docs.somnia.network/developer/deployment-and-production/ecosystem/ecosystem-showcase/somnia-exchange#participate-in-activities) Participate in Activities Use energy for gaming features and rewards. * * * Start trading at [somnia.exchangearrow-up-right](https://somnia.exchange/) | Gaming at [game.somnia.exchangearrow-up-right](https://game.somnia.exchange/) [PreviousSomnia Domains (.somi)chevron-left](https://docs.somnia.network/developer/deployment-and-production/ecosystem/ecosystem-showcase/somnia-domains-.somi) [NextTokoschevron-right](https://docs.somnia.network/developer/deployment-and-production/ecosystem/ecosystem-showcase/tokos) Last updated 4 months ago * [How to Trade Tokens](https://docs.somnia.network/developer/deployment-and-production/ecosystem/ecosystem-showcase/somnia-exchange#how-to-trade-tokens) * [Token Swapping](https://docs.somnia.network/developer/deployment-and-production/ecosystem/ecosystem-showcase/somnia-exchange#token-swapping) * [Available Trading Pairs](https://docs.somnia.network/developer/deployment-and-production/ecosystem/ecosystem-showcase/somnia-exchange#available-trading-pairs) * [How to Add Liquidity Pools](https://docs.somnia.network/developer/deployment-and-production/ecosystem/ecosystem-showcase/somnia-exchange#how-to-add-liquidity-pools) * [Liquidity Rewards](https://docs.somnia.network/developer/deployment-and-production/ecosystem/ecosystem-showcase/somnia-exchange#liquidity-rewards) * [Portfolio Management](https://docs.somnia.network/developer/deployment-and-production/ecosystem/ecosystem-showcase/somnia-exchange#portfolio-management) * [Play Game Features](https://docs.somnia.network/developer/deployment-and-production/ecosystem/ecosystem-showcase/somnia-exchange#play-game-features) * [Gaming Benefits](https://docs.somnia.network/developer/deployment-and-production/ecosystem/ecosystem-showcase/somnia-exchange#gaming-benefits) * [How to Start Gaming](https://docs.somnia.network/developer/deployment-and-production/ecosystem/ecosystem-showcase/somnia-exchange#how-to-start-gaming) --- # Using Data APIs (Ormi) | Somnia Docs The Somnia mission is to enable the building of mass-consumer real-time applications. As a Developer, you need to understand how to interact with onchain data to build UIs. This guide will teach you how to build a Token Balance dApp that fetches and displays ERC20 token balances from the Somnia Network using Next.js and the Ormi Data APIs. [hashtag](https://docs.somnia.network/developer/building-dapps/data-indexing-and-querying/using-data-apis-ormi#prerequisites) Prerequisites ------------------------------------------------------------------------------------------------------------------------------------------------ To complete this guide, you will need: * Basic understanding of React and TypeScript * An Ormi API key. Get one at [arrow-up-right](https://ormi.xyz/) [https://subgraph.somnia.network/dashboard/apiarrow-up-right](https://subgraph.somnia.network/dashboard/api) . [hashtag](https://docs.somnia.network/developer/building-dapps/data-indexing-and-querying/using-data-apis-ormi#what-is-ormi-data-api) What is Ormi Data API? ----------------------------------------------------------------------------------------------------------------------------------------------------------------- [Ormiarrow-up-right](https://docs.ormilabs.com/dedicated-env/somnia/data-apis/overview) provides a unified crypto data infrastructure for live and historical blockchain data. The Data APIs allow developers to query blockchain data without running their own nodes, making it easy to build data-rich applications on the Somnia Network. #### [hashtag](https://docs.somnia.network/developer/building-dapps/data-indexing-and-querying/using-data-apis-ormi#api-base-url) API Base URL The Ormi Data API for Somnia Network uses the following base URL: #### [hashtag](https://docs.somnia.network/developer/building-dapps/data-indexing-and-querying/using-data-apis-ormi#api-endpoints) API Endpoints The API follows a RESTful structure. For fetching ERC20 token balances, the endpoint structure is: Where: * `somnia` - The network identifier * `v1` - API version * `{walletAddress}` - The wallet address you want to query * `balance/erc20` - Specifies that you want ERC-20 token balances #### [hashtag](https://docs.somnia.network/developer/building-dapps/data-indexing-and-querying/using-data-apis-ormi#authentication) Authentication The Ormi API requires authentication using a Bearer token. Every request must include an Authorization header: **Important**: Never expose your API key in client-side code. Always make API calls from a server-side route to keep your key secure. #### [hashtag](https://docs.somnia.network/developer/building-dapps/data-indexing-and-querying/using-data-apis-ormi#example-api-request) Example API Request Here's an example of how to make a direct API call using curl: [hashtag](https://docs.somnia.network/developer/building-dapps/data-indexing-and-querying/using-data-apis-ormi#set-up-the-project) Set up the Project ---------------------------------------------------------------------------------------------------------------------------------------------------------- Create a new Next.js application with TypeScript and Tailwind CSS: [hashtag](https://docs.somnia.network/developer/building-dapps/data-indexing-and-querying/using-data-apis-ormi#create-the-type-definitions) Create the Type Definitions ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- First, we need to define TypeScript interfaces for the API response. Update `app/page.tsx`: [hashtag](https://docs.somnia.network/developer/building-dapps/data-indexing-and-querying/using-data-apis-ormi#build-the-user-interface) Build the User Interface ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- Now, let's create the main component with an input field and button. Update your `app/page.tsx`: [hashtag](https://docs.somnia.network/developer/building-dapps/data-indexing-and-querying/using-data-apis-ormi#create-a-.env-file) Create a `.env` file ------------------------------------------------------------------------------------------------------------------------------------------------------------ The `.env` is for keeping secrets such as the Ormi API KEY. Add the API KEY: [hashtag](https://docs.somnia.network/developer/building-dapps/data-indexing-and-querying/using-data-apis-ormi#create-the-api-route) Create the API Route -------------------------------------------------------------------------------------------------------------------------------------------------------------- To avoid CORS issues and keep your API key secure, we'll create an API route. Create a directory and a new file `app/api/balance/route.ts`: Important: Replace `YOUR_API_KEY_HERE` with your actual Ormi API key. [hashtag](https://docs.somnia.network/developer/building-dapps/data-indexing-and-querying/using-data-apis-ormi#implement-the-fetch-function) Implement the Fetch Function ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ Add the fetch function to handle form submission. Update your `app/page.tsx`: Don't forget to add the onSubmit handler to your form: [hashtag](https://docs.somnia.network/developer/building-dapps/data-indexing-and-querying/using-data-apis-ormi#display-the-results) Display the Results ------------------------------------------------------------------------------------------------------------------------------------------------------------ Add error handling and a table to display the token balances. Add this code after your form in `app/page.tsx`: ### [hashtag](https://docs.somnia.network/developer/building-dapps/data-indexing-and-querying/using-data-apis-ormi#test-your-dapp) Test Your dApp Start the development server: Open [http://localhost:3000arrow-up-right](http://localhost:3000/) in your browser. Enter a wallet address that has tokens on Somnia Network. Example test address: `0xC4890Bc98273424a18626772F266C35bf57FA56A` Look at the browser for the response and the displayed token balances. You can click on any contract address to view it in the Shannon Explorer. [hashtag](https://docs.somnia.network/developer/building-dapps/data-indexing-and-querying/using-data-apis-ormi#complete-code) Complete Code ------------------------------------------------------------------------------------------------------------------------------------------------ chevron-rightpage.tsx[hashtag](https://docs.somnia.network/developer/building-dapps/data-indexing-and-querying/using-data-apis-ormi#page.tsx) chevron-rightroute.ts[hashtag](https://docs.somnia.network/developer/building-dapps/data-indexing-and-querying/using-data-apis-ormi#route.ts) [hashtag](https://docs.somnia.network/developer/building-dapps/data-indexing-and-querying/using-data-apis-ormi#congratulations) Congratulations ---------------------------------------------------------------------------------------------------------------------------------------------------- You have built your first API enabled dApp on the Somnia Network! Now that you have a working Token Balance dApp, you can extend it by using other Ormi API [endpointsarrow-up-right](https://subgraphs.somnia.network/) . [PreviousBuilding Subgraph UIs (Apollo Client)chevron-left](https://docs.somnia.network/developer/building-dapps/data-indexing-and-querying/building-subgraph-uis-apollo-client) [NextListening to Blockchain Events (WebSocket)chevron-right](https://docs.somnia.network/developer/building-dapps/data-indexing-and-querying/listening-to-blockchain-events-websocket) Last updated 5 months ago * [Prerequisites](https://docs.somnia.network/developer/building-dapps/data-indexing-and-querying/using-data-apis-ormi#prerequisites) * [What is Ormi Data API?](https://docs.somnia.network/developer/building-dapps/data-indexing-and-querying/using-data-apis-ormi#what-is-ormi-data-api) * [Set up the Project](https://docs.somnia.network/developer/building-dapps/data-indexing-and-querying/using-data-apis-ormi#set-up-the-project) * [Create the Type Definitions](https://docs.somnia.network/developer/building-dapps/data-indexing-and-querying/using-data-apis-ormi#create-the-type-definitions) * [Build the User Interface](https://docs.somnia.network/developer/building-dapps/data-indexing-and-querying/using-data-apis-ormi#build-the-user-interface) * [Create a .env file](https://docs.somnia.network/developer/building-dapps/data-indexing-and-querying/using-data-apis-ormi#create-a-.env-file) * [Create the API Route](https://docs.somnia.network/developer/building-dapps/data-indexing-and-querying/using-data-apis-ormi#create-the-api-route) * [Implement the Fetch Function](https://docs.somnia.network/developer/building-dapps/data-indexing-and-querying/using-data-apis-ormi#implement-the-fetch-function) * [Display the Results](https://docs.somnia.network/developer/building-dapps/data-indexing-and-querying/using-data-apis-ormi#display-the-results) * [Test Your dApp](https://docs.somnia.network/developer/building-dapps/data-indexing-and-querying/using-data-apis-ormi#test-your-dapp) * [Complete Code](https://docs.somnia.network/developer/building-dapps/data-indexing-and-querying/using-data-apis-ormi#complete-code) * [Congratulations](https://docs.somnia.network/developer/building-dapps/data-indexing-and-querying/using-data-apis-ormi#congratulations) Copy https://api.subgraph.somnia.network/public_api/data_api Copy /somnia/v1/address/{walletAddress}/balance/erc20 Copy Authorization: Bearer YOUR_API_KEY Copy curl -X GET "https://api.subgraph.somnia.network/public_api/data_api/somnia/v1/address/0xYOUR_WALLET_ADDRESS/balance/erc20" \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -H "Accept: application/json" Copy npx create-next-app@latest somnia-balance-demo --typescript --tailwind --app cd somnia-balance-demo Copy 'use client' import { useState, FormEvent } from 'react' // Type definitions for the API response interface TokenBalance { balance: string contract: { address: string decimals: number erc_type: string logoUri: string | null name: string symbol: string } raw_balance: string } interface BalanceResponse { erc20TokenBalances: TokenBalance[] resultCount: number } Copy export default function Home() { const [walletAddress, setWalletAddress] = useState('') const [loading, setLoading] = useState(false) const [data, setData] = useState(null) const [error, setError] = useState('') return (

Somnia Network Balance Demo

setWalletAddress(e.target.value)} placeholder="Enter wallet address (0x...)" className="flex-1 px-4 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500" />
) } Copy PRIVATE_KEY=YOUR_API_KEY_HERE Copy import { NextRequest, NextResponse } from 'next/server' export async function GET(request: NextRequest) { try { const searchParams = request.nextUrl.searchParams; const walletAddress = searchParams.get('address'); if (!walletAddress) { return NextResponse.json( { error: 'Wallet address is required' }, { status: 400 } ) } const apiKey = process.env.PRIVATE_KEY const baseUrl = 'https://api.subgraph.somnia.network/public_api/data_api' const response = await fetch( `${baseUrl}/somnia/v1/address/${walletAddress}/balance/erc20`, { headers: { 'Authorization': `Bearer ${apiKey}`, 'Content-Type': 'application/json', 'Accept': 'application/json', }, } ) const data = await response.json() if (!response.ok) { return NextResponse.json( { error: 'Failed to fetch data from Ormi API', details: data }, { status: response.status } ) } return NextResponse.json(data) } catch (error) { console.error('API Error:', error) return NextResponse.json( { error: 'Internal server error' }, { status: 500 } ) } } Copy const fetchBalance = async (e: FormEvent) => { e.preventDefault() if (!walletAddress) { setError('Please enter a wallet address') return } setLoading(true) setError('') setData(null) try { const response = await fetch(`/api/balance?address=${walletAddress}`, { headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ walletAddress }), }) const result = await response.json() if (!response.ok) { throw new Error(result.error || 'Failed to fetch balance') } setData(result) } catch (err) { setError(err instanceof Error ? err.message : 'An error occurred') } finally { setLoading(false) } } Copy
Copy {error && (

{error}

)} {data && data.erc20TokenBalances.length > 0 && (

Token Balances ({data.resultCount} tokens)

{data.erc20TokenBalances.map((token, index) => ( ))}
Name Symbol Balance Contract Address
{token.contract.name || 'Unknown'} {token.contract.symbol || '-'} {parseFloat(token.balance).toLocaleString()} {token.contract.address.slice(0, 6)}...{token.contract.address.slice(-4)}
)} {data && data.erc20TokenBalances.length === 0 && (

No ERC-20 tokens found for this address

)} Copy npm run dev Copy 'use client'; import { useState, FormEvent } from 'react'; // Type definitions for the API response interface TokenBalance { balance: string; contract: { address: string; decimals: number; erc_type: string; logoUri: string | null; name: string; symbol: string; }; raw_balance: string; } interface BalanceResponse { erc20TokenBalances: TokenBalance[]; resultCount: number; } export default function Home() { const [walletAddress, setWalletAddress] = useState(''); const [loading, setLoading] = useState(false); const [data, setData] = useState(null); const [error, setError] = useState(''); const fetchBalance = async (e: FormEvent) => { e.preventDefault(); if (!walletAddress) { setError('Please enter a wallet address'); return; } setLoading(true); setError(''); setData(null); try { const response = await fetch(`/api/balance?address=${walletAddress}`, { headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ walletAddress }), }); const result = await response.json(); if (!response.ok) { throw new Error(result.error || 'Failed to fetch balance'); } setData(result); } catch (err) { setError(err instanceof Error ? err.message : 'An error occurred'); } finally { setLoading(false); } }; return (

Somnia Network Balance Demo

setWalletAddress(e.target.value)} placeholder='Enter wallet address (0x...)' className='flex-1 px-4 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 text-gray-500' />
{error && (

{error}

)} {data && data.erc20TokenBalances.length > 0 && (

Token Balances ({data.resultCount} tokens)

{data.erc20TokenBalances.map((token, index) => ( ))}
Name Symbol Balance Contract Address
{token.contract.name || 'Unknown'} {token.contract.symbol || '-'} {parseFloat(token.balance).toLocaleString()} {token.contract.address.slice(0, 6)}... {token.contract.address.slice(-4)}
)} {data && data.erc20TokenBalances.length === 0 && (

No ERC-20 tokens found for this address

)}
); } Copy import { NextRequest, NextResponse } from 'next/server'; export async function GET(request: NextRequest) { try { const searchParams = request.nextUrl.searchParams; const walletAddress = searchParams.get('address'); if (!walletAddress) { return NextResponse.json( { error: 'Wallet address is required' }, { status: 400 } ); } const apiKey = process.env.PRIVATE_KEY; const baseUrl = 'https://api.subgraph.somnia.network/public_api/data_api'; const response = await fetch( `${baseUrl}/somnia/v1/address/${walletAddress}/balance/erc20`, { headers: { Authorization: `Bearer ${apiKey}`, 'Content-Type': 'application/json', Accept: 'application/json', }, } ); const data = await response.json(); if (!response.ok) { return NextResponse.json( { error: 'Failed to fetch data from Ormi API', details: data }, { status: response.status } ); } return NextResponse.json(data); } catch (error) { console.error('API Error:', error); return NextResponse.json( { error: 'Internal server error' }, { status: 500 } ); } } --- # DAO Smart Contract | Somnia Docs Decentralized Autonomous Organizations (DAOs) are an innovative way to organize communities where decisions are made collectively without centralized authority. In this tutorial, we’ll explore a simple DAO implemented in Solidity. By the end, you’ll understand how to deploy and interact with this contract. circle-check Somnia Mainnet is LIVE. To deploy on Somnia Mainnet, you will need SOMI Tokens. Please refer to the [guide](https://docs.somnia.network/get-started/getting-started-for-mainnet) on Moving from Testnet to Mainnet. [hashtag](https://docs.somnia.network/developer/building-dapps/example-applications/dao-smart-contract#example-use-case-dao-implementation-in-gaming) Example Use Case: DAO Implementation in Gaming --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- DAOs can be particularly impactful in gaming environments. Imagine a massive multiplayer online game (MMO) with a shared in-game economy. A DAO can be used to manage a treasury funded by player contributions, allowing players to propose and vote on game updates, community events, or rewards. For example: 1. In-Game Treasury Management: Players deposit some of their in-game earnings into a DAO treasury. Proposals for using these funds—such as hosting tournaments or funding new content—are created and voted on. 2. Player-Driven Governance: Gamers vote on new features like maps, characters, or weapons, giving them a direct say in the game's evolution. 3. Community Rewards: DAOs could allocate funds to reward top-performing players or teams, enhancing engagement and competition. This decentralized approach ensures that game updates align with player interests, creating a more engaging and community-driven gaming experience. [hashtag](https://docs.somnia.network/developer/building-dapps/example-applications/dao-smart-contract#prerequisites) Prerequisites ---------------------------------------------------------------------------------------------------------------------------------------- Before starting, ensure you have: 1. This guide is not an introduction to Solidity Programming; you are expected to understand Basic Solidity Programming. 2. To complete this guide, you will need MetaMask installed and the Somnia Network added to the list of Networks. If you have yet to install MetaMask, please follow this guide to Connect Your Wallet. 3. You can deploy the Smart Contracts using our Hardhat or Foundry guides. [hashtag](https://docs.somnia.network/developer/building-dapps/example-applications/dao-smart-contract#overview-of-the-dao-contract) Overview of the DAO Contract ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- ![](https://docs.somnia.network/~gitbook/image?url=https%3A%2F%2Flh7-rt.googleusercontent.com%2Fdocsz%2FAD_4nXdJhAoATXHidSpCiF3u9C42iFnofuVqoMNCc3QyfzWhFAlxRCtwFtPbxfDexiWAdKTjUprX56sA5e3YtxI-jwfmVsTPkDoucVvkCCpQKLJHPTSD6uGa1xkioMbOfbSjy0IO_ERmeQ%3Fkey%3D96mDMjIvPGrXt0Tzk68UXspT&width=768&dpr=3&quality=100&sign=23ad0fa6&sv=2) The provided DAO contract allows users to: 1. Deposit funds to gain voting power. 2. Create proposals. 3. Vote on proposals. 4. Execute proposals if they pass. The key features of the contract include: * Proposal Struct: Stores details of proposals. * Voting Mechanism: Allows weighted voting based on deposited funds. * Execution Logic: Ensures proposals are executed only if approved. [hashtag](https://docs.somnia.network/developer/building-dapps/example-applications/dao-smart-contract#setting-up-the-development-environment) Setting Up the Development Environment ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ Follow the [Hardhat](https://docs.somnia.network/developer/development-frameworks/deploy-with-hardhat) or [Foundry](https://docs.somnia.network/developer/development-frameworks/deploy-with-foundry) guides. [hashtag](https://docs.somnia.network/developer/building-dapps/example-applications/dao-smart-contract#create-the-smart-contract) Create the Smart Contract ---------------------------------------------------------------------------------------------------------------------------------------------------------------- Create a new file named DAO.sol in the contracts folder and copy the provided contract code. chevron-rightDAO.sol[hashtag](https://docs.somnia.network/developer/building-dapps/example-applications/dao-smart-contract#dao.sol) Let’s break down the contract into its main components: #### [hashtag](https://docs.somnia.network/developer/building-dapps/example-applications/dao-smart-contract#mappings) Mappings Mappings are used to store structured data efficiently: 1. `**proposals**` * Stores all proposals created in the DAO. * It represents the Proposal `struct` containing details like description, deadline, votes, and proposer. 1. `**votingPower**` * Tracks the voting power of each address. * Voting power increases when users deposit funds into the DAO. 1. `**hasVoted**` * Tracks whether a specific address has voted on a specific proposal. * Prevents double voting. #### [hashtag](https://docs.somnia.network/developer/building-dapps/example-applications/dao-smart-contract#functions) Functions The contract includes several key functions: 1. `**Constructor**` * Sets the deployer as the owner of the contract. 1. `**deposit**` * Allows users to deposit STT Tokens to gain voting power. * Increases their votingPower by the amount deposited. 1. `**createProposal**` * Allows users with voting power to create new proposals. * Adds the proposal to the proposals mapping. 1. `**vote**` * Allows users to cast a vote on a proposal. * Updates the `**yesVotes**` or `**noVotes**` in the Proposal struct based on the user's choice. * Prevents double voting by using the `**hasVoted**` mapping. 1. `**executeProposal**` * Executes a proposal if it passes (more yes votes than no votes). * Transfers a fixed amount of ETH to the proposer as an example of execution logic. * Ensures proposals cannot be executed multiple times. #### [hashtag](https://docs.somnia.network/developer/building-dapps/example-applications/dao-smart-contract#key-variables) Key Variables 1. `**totalProposals**` * Tracks the total number of proposals created. 1. `**votingDuration**` * Sets the default duration for voting on proposals. 1. `**owner**` * Stores the address of the contract owner. * Used for functions that require administrative control. Understanding these components shows how the DAO enables decentralized governance while maintaining transparency and fairness. [hashtag](https://docs.somnia.network/developer/building-dapps/example-applications/dao-smart-contract#deploy-the-smart-contract-to-somnia) Deploy the Smart Contract to Somnia ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ Follow the [Hardhat](https://docs.somnia.network/developer/development-frameworks/deploy-with-hardhat) or [Foundry](https://docs.somnia.network/developer/development-frameworks/deploy-with-foundry) guides. First, compile the Smart Contract to Bytecode by running the Hardhat or Foundry compile instructions. This is an example deployment script using Hardhat. Create a file in the `**/ignition/module**` folder and name it `**deploy.js**` Before running the deploy command, add Somnia Network to the `**hardhat.config.js**` file: Ensure that the deploying address has enough STT Tokens. You can get STT Tokens from the [Faucetarrow-up-right](https://devnet.somnia.network/) . Run the deployment script: Congratulations. 🎉 You have successfully deployed the DAO Smart Contract. 🎉 [hashtag](https://docs.somnia.network/developer/building-dapps/example-applications/dao-smart-contract#interacting-with-the-contract) Interacting with the Contract ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ Use the Hardhat console or scripts to interact with the contract. #### [hashtag](https://docs.somnia.network/developer/building-dapps/example-applications/dao-smart-contract#id-1.-deposit-funds) 1\. Deposit Funds Call the deposit function to gain voting power: #### [hashtag](https://docs.somnia.network/developer/building-dapps/example-applications/dao-smart-contract#id-2.-create-a-proposal) 2\. Create a Proposal Create a new proposal by calling createProposal: #### [hashtag](https://docs.somnia.network/developer/building-dapps/example-applications/dao-smart-contract#id-3.-vote-on-a-proposal) 3\. Vote on a Proposal Vote on a proposal by specifying its ID and your support (true for yes, false for no): #### [hashtag](https://docs.somnia.network/developer/building-dapps/example-applications/dao-smart-contract#id-4.-execute-a-proposal) 4\. Execute a Proposal After the voting deadline, execute the proposal if it has majority votes: [hashtag](https://docs.somnia.network/developer/building-dapps/example-applications/dao-smart-contract#testing-the-contract) Testing the Contract ------------------------------------------------------------------------------------------------------------------------------------------------------ #### [hashtag](https://docs.somnia.network/developer/building-dapps/example-applications/dao-smart-contract#writing-tests) Writing Tests Create a test file DAO.test.js in the test folder. Run the tests: [hashtag](https://docs.somnia.network/developer/building-dapps/example-applications/dao-smart-contract#enhance-the-dao) Enhance the DAO -------------------------------------------------------------------------------------------------------------------------------------------- You can expand this DAO contract by: 1. Adding Governance Tokens: Reward participants with tokens for voting or executing proposals. Follow the ERC20 Token Guide here. 2. Implementing Quorums: Require a minimum number of votes for proposals to pass. 3. Flexible Voting Power: Allow dynamic voting power allocation. [hashtag](https://docs.somnia.network/developer/building-dapps/example-applications/dao-smart-contract#conclusion) Conclusion ---------------------------------------------------------------------------------------------------------------------------------- This tutorial provided a foundational understanding of building and deploying a simple DAO on Somnia. Experiment with enhancements to create more complex governance structures. DAOs are a powerful tool for decentralized decision-making, and the possibilities for innovation are limitless! [PreviousExample Applicationschevron-left](https://docs.somnia.network/developer/building-dapps/example-applications) [NextDAO UI Tutorial p1chevron-right](https://docs.somnia.network/developer/building-dapps/example-applications/dao-ui-tutorial-p1) Last updated 1 month ago * [Example Use Case: DAO Implementation in Gaming](https://docs.somnia.network/developer/building-dapps/example-applications/dao-smart-contract#example-use-case-dao-implementation-in-gaming) * [Prerequisites](https://docs.somnia.network/developer/building-dapps/example-applications/dao-smart-contract#prerequisites) * [Overview of the DAO Contract](https://docs.somnia.network/developer/building-dapps/example-applications/dao-smart-contract#overview-of-the-dao-contract) * [Setting Up the Development Environment](https://docs.somnia.network/developer/building-dapps/example-applications/dao-smart-contract#setting-up-the-development-environment) * [Create the Smart Contract](https://docs.somnia.network/developer/building-dapps/example-applications/dao-smart-contract#create-the-smart-contract) * [Deploy the Smart Contract to Somnia](https://docs.somnia.network/developer/building-dapps/example-applications/dao-smart-contract#deploy-the-smart-contract-to-somnia) * [Interacting with the Contract](https://docs.somnia.network/developer/building-dapps/example-applications/dao-smart-contract#interacting-with-the-contract) * [Testing the Contract](https://docs.somnia.network/developer/building-dapps/example-applications/dao-smart-contract#testing-the-contract) * [Enhance the DAO](https://docs.somnia.network/developer/building-dapps/example-applications/dao-smart-contract#enhance-the-dao) * [Conclusion](https://docs.somnia.network/developer/building-dapps/example-applications/dao-smart-contract#conclusion) Copy // SPDX-License-Identifier: MIT pragma solidity ^0.8.28; contract DAO { struct Proposal { string description; // Proposal details uint256 deadline; // Voting deadline uint256 yesVotes; // Votes in favor uint256 noVotes; // Votes against bool executed; // Whether the proposal has been executed address proposer; // Address of the proposer } mapping(uint256 => Proposal) public proposals; mapping(address => uint256) public votingPower; mapping(uint256 => mapping(address => bool)) public hasVoted; uint256 public totalProposals; uint256 public votingDuration = 10 minutes; address public owner; modifier onlyOwner() { require(msg.sender == owner, "Not the owner"); _; } constructor() { owner = msg.sender; } function deposit() external payable { require(msg.value == 0.001 ether, "Must deposit STT"); votingPower[msg.sender] += msg.value; } function createProposal(string calldata description) external { require(votingPower[msg.sender] > 0, "No voting power"); proposals[totalProposals] = Proposal({ description: description, deadline: block.timestamp + votingDuration, yesVotes: 0, noVotes: 0, executed: false, proposer: msg.sender }); totalProposals++; } function vote(uint256 proposalId, bool support) external { Proposal storage proposal = proposals[proposalId]; require(block.timestamp < proposal.deadline, "Voting has ended"); require(!hasVoted[proposalId][msg.sender], "Already voted"); require(votingPower[msg.sender] > 0, "No voting power"); hasVoted[proposalId][msg.sender] = true; if (support) { proposal.yesVotes += votingPower[msg.sender]; } else { proposal.noVotes += votingPower[msg.sender]; } } function executeProposal(uint256 proposalId) external { Proposal storage proposal = proposals[proposalId]; require(block.timestamp >= proposal.deadline, "Voting still active"); require(!proposal.executed, "Proposal already executed"); require(proposal.yesVotes > proposal.noVotes, "Proposal did not pass"); proposal.executed = true; // Logic for proposal execution // Example: transfer STT to proposer as a reward for successful vote pass payable(proposal.proposer).transfer(0.001 ether); } } Copy mapping(uint256 => Proposal) public proposals; Copy struct Proposal { string description; // Proposal details uint256 deadline; // Voting deadline uint256 yesVotes; // Votes in favor uint256 noVotes; // Votes against bool executed; // Whether the proposal has been executed address proposer; // Address of the proposer } Copy mapping(address => uint256) public votingPower; Copy mapping(uint256 => mapping(address => bool)) public hasVoted; Copy constructor() { owner = msg.sender; } Copy function deposit() external payable { require(msg.value >= 0.001 ether, "Minimum deposit is 0.001 STT"); votingPower[msg.sender] += msg.value; } Copy function createProposal(string calldata description) external { require(votingPower[msg.sender] > 0, "No voting power"); proposals[totalProposals] = Proposal({ description: description, deadline: block.timestamp + votingDuration, yesVotes: 0, noVotes: 0, executed: false, proposer: msg.sender }); totalProposals++; } Copy function vote(uint256 proposalId, bool support) external { Proposal storage proposal = proposals[proposalId]; require(block.timestamp < proposal.deadline, "Voting has ended"); require(!hasVoted[proposalId][msg.sender], "Already voted"); require(votingPower[msg.sender] > 0, "No voting power"); hasVoted[proposalId][msg.sender] = true; if (support) { proposal.yesVotes += votingPower[msg.sender]; } else { proposal.noVotes += votingPower[msg.sender]; } } Copy function executeProposal(uint256 proposalId) external { Proposal storage proposal = proposals[proposalId]; require(block.timestamp >= proposal.deadline, "Voting still active"); require(!proposal.executed, "Proposal already executed"); require(proposal.yesVotes > proposal.noVotes, "Proposal did not pass"); proposal.executed = true; payable(proposal.proposer).transfer(0.001 ether); } Copy uint256 public totalProposals; Copy uint256 public votingDuration = 10 minutes; Copy address public owner; Copy import { buildModule } from "@nomicfoundation/hardhat-ignition/modules"; const dao = buildModule("DAO", (m) => { const contract = m.contract("DAO"); return { contract }; }); module.exports = dao; Copy const config = { solidity: "0.8.28", networks: { somnia: { url: "https://dream-rpc.somnia.network", accounts: ["YOUR_PRIVATE_KEY"], }, }, }; Copy npx hardhat ignition deploy ./ignition/modules/deploy.ts --network somnia Copy await dao.deposit({ value: ethers.utils.parseEther("0.001") }); Copy await dao.createProposal("Fund development of new feature"); Copy await dao.vote(0, true); // Vote ‘yes’ on proposal 0 Copy await dao.executeProposal(0); Copy const { expect } = require("chai"); const { ethers } = require("hardhat"); describe("DAO", function () { let dao; let owner, addr1; beforeEach(async function () { const DAO = await ethers.getContractFactory("DAO"); dao = await DAO.deploy(); [owner, addr1] = await ethers.getSigners(); }); it("Should allow deposits and update voting power", async function () { await dao.connect(addr1).deposit({ value: ethers.utils.parseEther("0.001") }); expect(await dao.votingPower(addr1.address)).to.equal(ethers.utils.parseEther("0.001")); }); it("Should allow proposal creation", async function () { await dao.connect(addr1).deposit({ value: ethers.utils.parseEther("0.001") }); await dao.connect(addr1).createProposal("Test Proposal"); const proposal = await dao.proposals(0); expect(proposal.description).to.equal("Test Proposal"); }); }); Copy npx hardhat test --- # Protofire Subgraph | Somnia Docs The blockchain is an ever-growing database of transactions and Smart Contract events. Developers use subgraphs, an indexing solution provided by the Graph protocol, to retrieve and analyze this data efficiently. A graph in this context represents the structure of blockchain data, including token transfers, contract events, and user interactions. A subgraph is a customized indexing service that listens to blockchain transactions and structures them in a way that can be easily queried using GraphQL. [hashtag](https://docs.somnia.network/developer/building-dapps/data-indexing-and-querying/protofire-subgraph#prerequisites) Prerequisites ---------------------------------------------------------------------------------------------------------------------------------------------- * This guide is not an introduction to Solidity Programming; you are expected to understand Basic Solidity Programming. * GraphQL is installed and set up on your local machine. [hashtag](https://docs.somnia.network/developer/building-dapps/data-indexing-and-querying/protofire-subgraph#deploy-a-simple-erc20-token-on-somnia) Deploy a Simple ERC20 Token on Somnia ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- We will deploy a basic ERC20 token on the Somnia network using Hardhat. Ensure you have Hardhat, OpenZeppelin, and dotenv installed: [hashtag](https://docs.somnia.network/developer/building-dapps/data-indexing-and-querying/protofire-subgraph#create-an-erc20-token-contract) Create an ERC20 Token Contract -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- Create a new Solidity file: `contracts/MyToken.sol` and update it [hashtag](https://docs.somnia.network/developer/building-dapps/data-indexing-and-querying/protofire-subgraph#create-a-deployment-script) Create a Deployment Script ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ Create a new file in `ignition/modules/MyTokenModule.ts` [hashtag](https://docs.somnia.network/developer/building-dapps/data-indexing-and-querying/protofire-subgraph#deploy-the-smart-contract) Deploy the Smart Contract ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- Open the hardhat.config.js file and update the network information by adding Somnia Network to the list of networks. Copy your Wallet Address Private Key from MetaMask, and add it to the accounts section. Ensure there are enough STT Token in the Wallet Address to pay for Gas. You can get some from the Somnia Faucet. Open a new terminal and deploy the smart contract to the Somnia Network. Run the command: This will deploy the ERC20 contract to the Somnia network and return the deployed contract address. [hashtag](https://docs.somnia.network/developer/building-dapps/data-indexing-and-querying/protofire-subgraph#simulate-on-chain-activity) Simulate On-Chain Activity ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ Once deployed, we will create a script to generate multiple transactions on the blockchain. Create a new file `scripts/interact.js` chevron-right`interact.js`[hashtag](https://docs.somnia.network/developer/building-dapps/data-indexing-and-querying/protofire-subgraph#interact.js) Create an `.env` file to hold sensitive informations such as the private keys #### [hashtag](https://docs.somnia.network/developer/building-dapps/data-indexing-and-querying/protofire-subgraph#run-the-script) Run the Script This will generate several on-chain transactions for our subgraph to index. [hashtag](https://docs.somnia.network/developer/building-dapps/data-indexing-and-querying/protofire-subgraph#deploy-a-subgraph-on-somnia) Deploy a Subgraph on Somnia -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- Go to [https://somnia.chain.love/arrow-up-right](https://somnia.chain.love/) and connect your Wallet. ![](https://docs.somnia.network/~gitbook/image?url=https%3A%2F%2Flh7-rt.googleusercontent.com%2Fdocsz%2FAD_4nXcOA43Zg4tecvciwgPQxb-dlcBwhPkTGY0POkIQ8JaUdKcaheg-AwetWTR1yFdYSGw_A2c_fm-xuQba6mVDitI_rD7wuazgEsjBjUjxT8-0ELLl9CW0JFlzNuPLHlZYXKy7VP16_A%3Fkey%3DZDOrGANolV8eE-n5bJ-_qrWy&width=768&dpr=3&quality=100&sign=d68fa60d&sv=2) First, you need to create a private key for deploying subgraphs. To do so, please go to [Somnia Protofire Servicearrow-up-right](https://somnia.chain.love/) and create an Account. You are now able to create subgraphs. Click the create button and enter the required details. ![](https://docs.somnia.network/~gitbook/image?url=https%3A%2F%2Flh7-rt.googleusercontent.com%2Fdocsz%2FAD_4nXdbWSqj7ddwv0yFj8FlHdDGGlJmeqmRakTRUNX2LUCUHlqPjcK2eSBdRwnwwZ5pq8T-3op10MJwdknOn-KfCDbsJ4nuI7Uu39obkcb8gYfVTlRhrt1s4IJ3U7VNgEnMW0mMN7hK6w%3Fkey%3DZDOrGANolV8eE-n5bJ-_qrWy&width=768&dpr=3&quality=100&sign=2a1b5bb0&sv=2) ![](https://docs.somnia.network/~gitbook/image?url=https%3A%2F%2Flh7-rt.googleusercontent.com%2Fdocsz%2FAD_4nXex3AIr44-pbEiLznhnQE0xhOYHgtOmwRyu8HwXjFWsTQLM9JtyFD10YPZdDxY0qFNVC2JUqt_29q8VtSmV0emS8VVOZrF4VfYUUxmqQM3yF-_c29E9vQaRfvVoV7Guu6AkI8mgUw%3Fkey%3DZDOrGANolV8eE-n5bJ-_qrWy&width=768&dpr=3&quality=100&sign=8816fda2&sv=2) After initialising the subgraph on [https://somnia.chain.love/arrow-up-right](https://somnia.chain.love/) the next step is to create and deploy the subgraph via the terminal. [hashtag](https://docs.somnia.network/developer/building-dapps/data-indexing-and-querying/protofire-subgraph#initialize-the-subgraph) Initialize the Subgraph ------------------------------------------------------------------------------------------------------------------------------------------------------------------ ![](https://docs.somnia.network/~gitbook/image?url=https%3A%2F%2Flh7-rt.googleusercontent.com%2Fdocsz%2FAD_4nXeFtlgKbMC4sCBfwGl1xI76iHiOxsc7jKZtg9BUeMGiTsormp5HRYnreVlo4cpuZe23eESQT9pgbCdf9dQ4lKw_-fjsBwPLd-DZd-o_Gc3BA9VxjE95i05yuXDacPmNBbwsEvvvbA%3Fkey%3DZDOrGANolV8eE-n5bJ-_qrWy&width=768&dpr=3&quality=100&sign=cca3407e&sv=2) Then, update networks.json to use Somnia’s RPC [hashtag](https://docs.somnia.network/developer/building-dapps/data-indexing-and-querying/protofire-subgraph#define-the-subgraph-schema) Define the Subgraph Schema ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ 📁 Edit schema.graphql [hashtag](https://docs.somnia.network/developer/building-dapps/data-indexing-and-querying/protofire-subgraph#build-the-subgraph) Build the Subgraph -------------------------------------------------------------------------------------------------------------------------------------------------------- [hashtag](https://docs.somnia.network/developer/building-dapps/data-indexing-and-querying/protofire-subgraph#deploy-the-subgraph) Deploy the Subgraph ---------------------------------------------------------------------------------------------------------------------------------------------------------- ![](https://docs.somnia.network/~gitbook/image?url=https%3A%2F%2Flh7-rt.googleusercontent.com%2Fdocsz%2FAD_4nXfc-Mu-wBk_mvxbW4Rz6ODZK_RtWPtTxDjKcldwtudapplfGjxVMZTGS_xzX73VOp_XcRCiicQsA-q7yXG0x8Tz6vyCeTU1Lw08Kmi8rbZlWapYgskmfWXgHo3t2Rgypy3x3cfd0A%3Fkey%3DZDOrGANolV8eE-n5bJ-_qrWy&width=768&dpr=3&quality=100&sign=edb2231&sv=2) [hashtag](https://docs.somnia.network/developer/building-dapps/data-indexing-and-querying/protofire-subgraph#query-the-subgraph) Query the Subgraph -------------------------------------------------------------------------------------------------------------------------------------------------------- Once your subgraph is deployed and indexing blockchain data on Somnia, you can retrieve information using GraphQL queries. These queries allow you to efficiently access structured data such as token transfers, approvals, and contract interactions without having to scan the blockchain manually. Developers can query indexed blockchain data in real time using the Graph Explorer or a GraphQL client. This enables DApps, analytics dashboards, and automated systems to interact more efficiently with blockchain events. This section demonstrates how to write and execute GraphQL queries to fetch blockchain data indexed by the subgraph. Go to [https://somnia.chain.love/graph/17arrow-up-right](https://somnia.chain.love/graph/17) ### [hashtag](https://docs.somnia.network/developer/building-dapps/data-indexing-and-querying/protofire-subgraph#fetch-latest-transfers) Fetch Latest Transfers ### [hashtag](https://docs.somnia.network/developer/building-dapps/data-indexing-and-querying/protofire-subgraph#get-transfers-by-address) Get Transfers by Address ### [hashtag](https://docs.somnia.network/developer/building-dapps/data-indexing-and-querying/protofire-subgraph#get-transfers-in-a-time-range) Get Transfers in a Time Range [hashtag](https://docs.somnia.network/developer/building-dapps/data-indexing-and-querying/protofire-subgraph#conclusion) Conclusion ---------------------------------------------------------------------------------------------------------------------------------------- This tutorial provides a complete pipeline for indexing blockchain data on Somnia using The Graph! 🔥 [PreviousOrmi Subgraphchevron-left](https://docs.somnia.network/developer/building-dapps/data-indexing-and-querying/ormi-subgraph) [NextBuilding Subgraph UIs (NextJS/Fetch)chevron-right](https://docs.somnia.network/developer/building-dapps/data-indexing-and-querying/building-subgraph-uis-nextjs-fetch) Last updated 5 months ago * [Prerequisites](https://docs.somnia.network/developer/building-dapps/data-indexing-and-querying/protofire-subgraph#prerequisites) * [Deploy a Simple ERC20 Token on Somnia](https://docs.somnia.network/developer/building-dapps/data-indexing-and-querying/protofire-subgraph#deploy-a-simple-erc20-token-on-somnia) * [Create an ERC20 Token Contract](https://docs.somnia.network/developer/building-dapps/data-indexing-and-querying/protofire-subgraph#create-an-erc20-token-contract) * [Create a Deployment Script](https://docs.somnia.network/developer/building-dapps/data-indexing-and-querying/protofire-subgraph#create-a-deployment-script) * [Deploy the Smart Contract](https://docs.somnia.network/developer/building-dapps/data-indexing-and-querying/protofire-subgraph#deploy-the-smart-contract) * [Simulate On-Chain Activity](https://docs.somnia.network/developer/building-dapps/data-indexing-and-querying/protofire-subgraph#simulate-on-chain-activity) * [Deploy a Subgraph on Somnia](https://docs.somnia.network/developer/building-dapps/data-indexing-and-querying/protofire-subgraph#deploy-a-subgraph-on-somnia) * [Initialize the Subgraph](https://docs.somnia.network/developer/building-dapps/data-indexing-and-querying/protofire-subgraph#initialize-the-subgraph) * [Define the Subgraph Schema](https://docs.somnia.network/developer/building-dapps/data-indexing-and-querying/protofire-subgraph#define-the-subgraph-schema) * [Build the Subgraph](https://docs.somnia.network/developer/building-dapps/data-indexing-and-querying/protofire-subgraph#build-the-subgraph) * [Deploy the Subgraph](https://docs.somnia.network/developer/building-dapps/data-indexing-and-querying/protofire-subgraph#deploy-the-subgraph) * [Query the Subgraph](https://docs.somnia.network/developer/building-dapps/data-indexing-and-querying/protofire-subgraph#query-the-subgraph) * [Fetch Latest Transfers](https://docs.somnia.network/developer/building-dapps/data-indexing-and-querying/protofire-subgraph#fetch-latest-transfers) * [Get Transfers by Address](https://docs.somnia.network/developer/building-dapps/data-indexing-and-querying/protofire-subgraph#get-transfers-by-address) * [Get Transfers in a Time Range](https://docs.somnia.network/developer/building-dapps/data-indexing-and-querying/protofire-subgraph#get-transfers-in-a-time-range) * [Conclusion](https://docs.somnia.network/developer/building-dapps/data-indexing-and-querying/protofire-subgraph#conclusion) Copy npm install -g @graphprotocol/graph-cli Copy npm install --save-dev hardhat @nomicfoundation/hardhat-ignition-ethers @openzeppelin/contracts dotenv ethers Copy // SPDX-License-Identifier: MIT pragma solidity ^0.8.25; import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; contract MyToken is ERC20 { constructor(uint256 initialSupply) ERC20("MyToken", "MTK") { _mint(msg.sender, initialSupply * 10**decimals()); } function mint(address to, uint256 amount) external { _mint(to, amount); } function burn(uint256 amount) external { _burn(msg.sender, amount); } } Copy import { buildModule } from "@nomicfoundation/hardhat-ignition/modules"; export default buildModule("MyTokenModule", (m) => { const initialSupply = m.getParameter("initialSupply", 1000000n * 10n ** 18n); const myToken = m.contract("MyToken", [initialSupply]); return { myToken }; }); Copy module.exports = { // ... networks: { somniaTestnet: { url: "https://dream-rpc.somnia.network", accounts: ["0xPRIVATE_KEY"], // put dev menomonic or PK here, }, }, // ... }; Copy npx hardhat ignition deploy ./ignition/modules/MyTokenModule.ts --network somniaTestnet Copy require("dotenv").config(); const { ethers } = require("hardhat"); async function main() { // Connect to Somnia RPC const provider = new ethers.JsonRpcProvider(process.env.SOMNIA_RPC_URL); // Load wallets from .env const deployer = new ethers.Wallet(process.env.PRIVATE_KEY_1, provider); const user1 = new ethers.Wallet(process.env.PRIVATE_KEY_2, provider); const user2 = new ethers.Wallet(process.env.PRIVATE_KEY_3, provider); const user3 = new ethers.Wallet(process.env.PRIVATE_KEY_4, provider); const user4 = new ethers.Wallet(process.env.PRIVATE_KEY_5, provider); const contractAddress = "0xBF9516ADc5263d277E2505d4e141F7159B103d33"; // Replace with your deployed contract address const abi = [\ "function transfer(address to, uint256 amount) external returns (bool)",\ "function mint(address to, uint256 amount) external",\ "function burn(uint256 amount) external",\ ]; // Attach to the deployed ERC20 contract const token = new ethers.Contract(contractAddress, abi, provider); console.log("🏁 Starting Token Transactions Simulation on Somnia..."); // Simulate Transfers const transfers = [\ { from: deployer, to: user1.address, amount: "1000" },\ { from: deployer, to: user2.address, amount: "1000" },\ { from: user1, to: user2.address, amount: "50" },\ { from: user2, to: user3.address, amount: "30" },\ { from: user3, to: user4.address, amount: "10" },\ { from: user4, to: deployer.address, amount: "5" },\ { from: deployer, to: user2.address, amount: "100" },\ { from: user1, to: user3.address, amount: "70" },\ { from: user2, to: user4.address, amount: "40" },\ ]; for (const tx of transfers) { const { from, to, amount } = tx; const txResponse = await token.connect(from).transfer(to, ethers.parseUnits(amount, 18)); await txResponse.wait(); console.log(`✅ ${from.address} sent ${amount} MTK to ${to}`); } // Simulate Minting const mintAmount1 = ethers.parseUnits("500", 18); const mintTx1 = await token.connect(deployer).mint(user1.address, mintAmount1); await mintTx1.wait(); console.log(`✅ Minted ${ethers.formatUnits(mintAmount1, 18)} MTK to User1!`); const mintAmount2 = ethers.parseUnits("300", 18); const mintTx2 = await token.connect(deployer).mint(user2.address, mintAmount2); await mintTx2.wait(); console.log(`✅ Minted ${ethers.formatUnits(mintAmount2, 18)} MTK to User2!`); // Simulate Burning const burnAmount1 = ethers.parseUnits("50", 18); const burnTx1 = await token.connect(user1).burn(burnAmount1); await burnTx1.wait(); console.log(`🔥 User1 burned ${ethers.formatUnits(burnAmount1, 18)} MTK!`); const burnAmount2 = ethers.parseUnits("100", 18); const burnTx2 = await token.connect(user2).burn(burnAmount2); await burnTx2.wait(); console.log(`🔥 User2 burned ${ethers.formatUnits(burnAmount2, 18)} MTK!`); console.log("🏁 Simulation Complete on Somnia!"); } main() .then(() => process.exit(0)) .catch((error) => { console.error(error); process.exit(1); }); Copy SOMNIA_RPC_URL=https://dream-rpc.somnia.network PRIVATE_KEY_1=0x... PRIVATE_KEY_2=0x... PRIVATE_KEY_3=0x... PRIVATE_KEY_4=0x... PRIVATE_KEY_5=0x... Copy node scripts/interact.js Copy graph init --contract-name MyToken --from-contract 0xYourTokenAddress --network somnia-testnet mytoken Copy { "somnia-testnet": { "network": "Somnia Testnet", "rpc": "https://dream-rpc.somnia.network", "startBlock": 12345678 } } Copy type Transfer @entity(immutable: true) { id: Bytes! from: Bytes! to: Bytes! value: BigInt! blockNumber: BigInt! blockTimestamp: BigInt! transactionHash: Bytes! } Copy graph codegen graph build Copy graph deploy --node https://proxy.somnia.chain.love/graph/somnia-testnet --version-label 0.0.1 somnia-testnet/test-mytoken --access-token=your_token_from_somnia_chain_love Copy { transfers(first: 10, orderBy: blockTimestamp, orderDirection: desc) { id from to value blockTimestamp transactionHash } } Copy { transfers(where: { from: "0xUserWalletAddress" }) { id from to value } } Copy { transfers(where: { blockTimestamp_gte: "1700000000", blockTimestamp_lte: "1710000000" }) { id from to value } } --- # Responsible Disclosure Policy | Somnia Docs This page serves as the unified communication hub for **Somnia developers, contributors, and security researchers**. It combines two essential areas: * **Developer Contact and Support:** How to reach the Somnia DevRel and technical teams. * **Responsible Disclosure:** How to report security vulnerabilities, contribute improvements, and participate in the future bounty ecosystem. * * * [hashtag](https://docs.somnia.network/developer/security/responsible-disclosure-policy#developer-contact-and-support) Developer Contact and Support -------------------------------------------------------------------------------------------------------------------------------------------------------- The Somnia developer community operates across several communication channels to provide quick technical assistance, feedback exchange, and support for integrations or bug reports. #### [hashtag](https://docs.somnia.network/developer/security/responsible-disclosure-policy#active-support-channels) Active Support Channels * **Telegram (DevRel Team):** * [@emreyetharrow-up-right](https://t.me/emreyeth) * [@PromiseGameFiarrow-up-right](https://t.me/PromiseGameFi) * [@emmaodiaarrow-up-right](https://t.me/emmaodia) * **Discord:** * Join the official [Somnia arrow-up-right](https://discord.gg/somnia) server. * For technical questions, use the `#dev-support` or `#dev-chat` channel. * To report issues privately, open a **support ticket** under “Bug Reports” * **Email:** Send an email to [**\[email protected\]**envelope](https://docs.somnia.network/cdn-cgi/l/email-protection#97f3f2e1f2fbf8e7f2e5e4d7e4f8faf9fef6b9f9f2e3e0f8e5fc) for official inquiries, integration help, or collaboration requests. circle-info Response time varies based on the request type, but DevRel aims to reply within **24 hours**. #### [hashtag](https://docs.somnia.network/developer/security/responsible-disclosure-policy#types-of-support-requests) Types of Support Requests Category Description Preferred Channel **Integration Help** RPC, SDK, and Smart Contract setup assistance Discord / Email **Docs Contribution** Reporting outdated or missing developer docs GitHub PR / Email **Bug Report** Contract, SDK, or explorer bugs Discord Ticket / Email **Partnership Inquiry** Technical collaborations or integration ideas Email * * * [hashtag](https://docs.somnia.network/developer/security/responsible-disclosure-policy#responsible-disclosure) Responsible Disclosure ------------------------------------------------------------------------------------------------------------------------------------------ Somnia encourages ethical researchers and contributors to responsibly disclose vulnerabilities or security risks found in the ecosystem. Even though a formal bounty system is not yet live, this framework ensures findings are handled safely and recognized appropriately. * * * [hashtag](https://docs.somnia.network/developer/security/responsible-disclosure-policy#technical-disclosure-guidelines) Technical Disclosure Guidelines ------------------------------------------------------------------------------------------------------------------------------------------------------------ All vulnerability reports should follow a clear, reproducible structure for fast triage and validation. #### [hashtag](https://docs.somnia.network/developer/security/responsible-disclosure-policy#required-report-template) **Required Report Template** chevron-rightExample[hashtag](https://docs.somnia.network/developer/security/responsible-disclosure-policy#example) * * * [hashtag](https://docs.somnia.network/developer/security/responsible-disclosure-policy#contribution-pathways-for-developers) Contribution Pathways for Developers ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- Somnia invites developers to contribute beyond bug reporting. Follow these pathways to get involved. 1 #### [hashtag](https://docs.somnia.network/developer/security/responsible-disclosure-policy#documentation-contributions) Documentation Contributions * Suggest edits or add missing examples in tutorials. * Create new pages under categories like _Debugging_, _Testing_, or _Security_. 2 #### [hashtag](https://docs.somnia.network/developer/security/responsible-disclosure-policy#testing-best-practices) Testing Best Practices * Always test exploits or stress scenarios on **Shannon Testnet**, not on Mainnet. * Use local forks with Hardhat or Foundry for reproducibility. * * * [hashtag](https://docs.somnia.network/developer/security/responsible-disclosure-policy#somnia-report-lifecycle) Somnia Report Lifecycle -------------------------------------------------------------------------------------------------------------------------------------------- 1 #### [hashtag](https://docs.somnia.network/developer/security/responsible-disclosure-policy#submission) Submission Researcher submits a report via email, Discord, or Telegram. 2 #### [hashtag](https://docs.somnia.network/developer/security/responsible-disclosure-policy#verification) Verification Somnia DevRel reproduces the issue and collects context. 3 #### [hashtag](https://docs.somnia.network/developer/security/responsible-disclosure-policy#escalation) Escalation Valid issues are passed to Somnia Core Security. 4 #### [hashtag](https://docs.somnia.network/developer/security/responsible-disclosure-policy#patch-deployment) Patch Deployment Fix rolled out to Shannon Testnet, then Mainnet. (Based on where is it.) 5 #### [hashtag](https://docs.somnia.network/developer/security/responsible-disclosure-policy#acknowledgment) Acknowledgment Researcher credited publicly in Somnia Docs and Discord. For multi-party vulnerabilities (e.g., involving validators or external oracles), coordinated disclosure will be handled privately. * * * [hashtag](https://docs.somnia.network/developer/security/responsible-disclosure-policy#ethical-rules) Ethical Rules ------------------------------------------------------------------------------------------------------------------------ circle-exclamation * Do **not** exploit vulnerabilities on Mainnet. * Do **not** disrupt network services or RPC endpoints. * Do **not** engage in social engineering or phishing. * Always disclose vulnerabilities privately and responsibly. Researchers acting in good faith will **not face any penalties** and will be publicly recognized for their ethical contributions. * * * [hashtag](https://docs.somnia.network/developer/security/responsible-disclosure-policy#summary) Summary ------------------------------------------------------------------------------------------------------------ * Use **Telegram, Discord, or Email** to reach Somnia’s DevRel and security teams. * Follow the **Responsible Disclosure template** for structured vulnerability reports. * Contribute improvements via **Pull Requests** or documentation updates. * Future bounty and recognition programs will expand as Somnia Mainnet evolves. [PreviousNode/Infra Securitychevron-left](https://docs.somnia.network/developer/security/node-infra-security) [NextDeployment and Productionchevron-right](https://docs.somnia.network/developer/deployment-and-production) Last updated 4 months ago * [Developer Contact and Support](https://docs.somnia.network/developer/security/responsible-disclosure-policy#developer-contact-and-support) * [Responsible Disclosure](https://docs.somnia.network/developer/security/responsible-disclosure-policy#responsible-disclosure) * [Technical Disclosure Guidelines](https://docs.somnia.network/developer/security/responsible-disclosure-policy#technical-disclosure-guidelines) * [Contribution Pathways for Developers](https://docs.somnia.network/developer/security/responsible-disclosure-policy#contribution-pathways-for-developers) * [Somnia Report Lifecycle](https://docs.somnia.network/developer/security/responsible-disclosure-policy#somnia-report-lifecycle) * [Ethical Rules](https://docs.somnia.network/developer/security/responsible-disclosure-policy#ethical-rules) * [Summary](https://docs.somnia.network/developer/security/responsible-disclosure-policy#summary) Vulnerability Report Template Copy # Vulnerability Report — Somnia Network ## Summary Brief description of the issue. ## Impact Potential risks if exploited. ## Steps to Reproduce 1. Step-by-step actions. 2. Include RPC endpoint, contract address, and network (Mainnet or Shannon Testnet). ## Expected vs Actual Behavior Explain the difference in observed vs intended behavior. ## Proof of Concept (PoC) Include transaction hash, minimal code snippet, or call trace. ## Suggested Fix (Optional) Provide insights or improvement recommendations. ## Contact Telegram / Discord handle / Email. Example Vulnerability Report Copy # Vulnerability Report — Somnia Bridge Contract ## Summary Bridge contract mishandles token decimals in cross-chain conversion. ## Impact Potential underflow on tokens with decimals < 18. ## Steps to Reproduce 1. Deploy ERC20 with 6 decimals. 2. Execute `bridgeToSomnia(token, 1000000)`. 3. Observe incorrect amount on destination. ## Expected vs Actual Expected: normalized 1 token. Actual: 0.000001 tokens received. ## Suggested Fix Add decimal normalization logic. ## Proof of Concept Testnet Tx: `0x92b...4fe1` ## Contact @emreyeth (Telegram) --- # Listening to Blockchain Events (WebSocket) | Somnia Docs This guide teaches developers how to create WebSocket connections to listen for smart contract events on the Somnia network in real-time. We'll use a simple Greeting contract as an example to demonstrate the core concepts. [hashtag](https://docs.somnia.network/developer/building-dapps/data-indexing-and-querying/listening-to-blockchain-events-websocket#resources) Resources ------------------------------------------------------------------------------------------------------------------------------------------------------------ Somnia Mainnet WebSocket [wss://api.infra.mainnet.somnia.network/wsarrow-up-right](wss://api.infra.mainnet.somnia.network/ws) Somnia Testnet WebSocket [wss://dream-rpc.somnia.network/wsarrow-up-right](wss://dream-rpc.somnia.network/ws) [hashtag](https://docs.somnia.network/developer/building-dapps/data-indexing-and-querying/listening-to-blockchain-events-websocket#example-script) Example Script ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- chevron-rightwebsocket-listener.js[hashtag](https://docs.somnia.network/developer/building-dapps/data-indexing-and-querying/listening-to-blockchain-events-websocket#websocket-listener.js) Copy // const { ethers } = require("ethers"); import { ethers } from "ethers"; // Configuration const wsUrl = "wss://dream-rpc.somnia.network/ws"; const contractAddress = "0xADA7b2953E7d670092644d37b6a39BAE3237beD7"; // Replace with your contract address // Contract ABI const abi = [\ {\ anonymous: false,\ inputs: [\ { indexed: true, internalType: "string", name: "oldGreeting", type: "string" },\ { indexed: true, internalType: "string", name: "newGreeting", type: "string" },\ ],\ name: "GreetingSet",\ type: "event",\ },\ {\ inputs: [],\ name: "getGreeting",\ outputs: [{ internalType: "string", name: "", type: "string" }],\ stateMutability: "view",\ type: "function",\ },\ ]; async function listen() { // Create WebSocket provider and contract const provider = new ethers.WebSocketProvider(wsUrl); await provider._waitUntilReady(); const contract = new ethers.Contract(contractAddress, abi, provider); console.log("Listening for events...\n"); // Event filter const filter = { address: contractAddress, topics: [ethers.id("GreetingSet(string,string)")], }; // Listen for events provider.on(filter, async (log) => { try { const greeting = await contract.getGreeting(); console.log(`New greeting: "${greeting}"`); } catch (error) { console.error("Error:", error.message); } }); // Keep connection alive setInterval(async () => { try { await provider.getBlockNumber(); } catch (error) { console.error("Connection error"); } }, 30000); // Handle shutdown process.on("SIGINT", () => { provider.destroy(); process.exit(0); }); } // Start listening listen().catch(console.error); [hashtag](https://docs.somnia.network/developer/building-dapps/data-indexing-and-querying/listening-to-blockchain-events-websocket#prerequisites) Prerequisites -------------------------------------------------------------------------------------------------------------------------------------------------------------------- * Node.js 20+ * Basic understanding of JavaScript * A deployed smart contract on Somnia network [hashtag](https://docs.somnia.network/developer/building-dapps/data-indexing-and-querying/listening-to-blockchain-events-websocket#what-are-websockets) What are WebSockets? --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- WebSockets are a communication protocol that provides bidirectional communication channels over a single TCP connection. Unlike traditional HTTP requests, WebSockets maintain a persistent connection between the client and server. ### [hashtag](https://docs.somnia.network/developer/building-dapps/data-indexing-and-querying/listening-to-blockchain-events-websocket#how-websockets-work) **How WebSockets Work** WebSockets begin with a connection establishment phase where the client initiates a WebSocket handshake through an HTTP upgrade request. Once established, the connection stays open as a persistent channel between client and server. This enables bidirectional communication where both client and server can send messages at any time without waiting for requests. The protocol maintains low latency since there's no need to establish new connections for each message exchange. This design is highly efficient with minimal protocol overhead compared to traditional HTTP polling approaches. **WebSocket Connection Lifecycle** ### [hashtag](https://docs.somnia.network/developer/building-dapps/data-indexing-and-querying/listening-to-blockchain-events-websocket#websocket-vs-http-polling) WebSocket vs HTTP Polling #### [hashtag](https://docs.somnia.network/developer/building-dapps/data-indexing-and-querying/listening-to-blockchain-events-websocket#http-polling-approach) **HTTP Polling Approach** The problem with polling is that Polling wastes bandwidth by constantly checking for updates even when none exist, leading to unnecessary network traffic. This approach introduces delays of up to the polling interval (5 seconds in our example), meaning users might wait several seconds to see new events that have already occurred. Additionally, the server must process these unnecessary requests repeatedly, increasing computational load and infrastructure costs. For blockchain applications, this translates to higher costs for RPC providers who often charge based on the number of requests made. #### [hashtag](https://docs.somnia.network/developer/building-dapps/data-indexing-and-querying/listening-to-blockchain-events-websocket#websocket-approach) **WebSocket Approach** WebSockets provide real-time updates measured in milliseconds rather than seconds, ensuring users receive notifications instantly when events occur. This eliminates wasted requests since the server only sends data when there are actual updates, significantly reducing bandwidth usage. The reduced request frequency leads to lower server load and better resource utilization. For users, this translates to a superior experience with immediate feedback, while developers benefit from reduced infrastructure costs. ### [hashtag](https://docs.somnia.network/developer/building-dapps/data-indexing-and-querying/listening-to-blockchain-events-websocket#blockchain-events-and-websockets) Blockchain Events and WebSockets Smart contracts emit events when important state changes occur. These events are included in transaction receipts and stored in blockchain logs. The event flow begins when a user calls a Smart Contract function through a Transaction. During execution, the contract updates its Internal State and emits Events containing relevant data about the changes. These Transactions and their associated Events are then included in a new block by Validators. Once the Block is finalized, Nodes across the network broadcast it to their peers. WebSocket connections instantly notify connected clients about these new events, enabling real-time reactions to Blockchain state changes. ### [hashtag](https://docs.somnia.network/developer/building-dapps/data-indexing-and-querying/listening-to-blockchain-events-websocket#example-use-cases-for-websocket-event-listening) **Example Use Cases for WebSocket Event Listening** DeFi Applications * Monitor price updates on DEX swaps NFT Marketplaces * Live bidding updates in auctions Gaming DApps * Real-time game state updates DAOs and Governance * Live voting updates Supply Chain * Product status updates ### [hashtag](https://docs.somnia.network/developer/building-dapps/data-indexing-and-querying/listening-to-blockchain-events-websocket#indexed-parameters-in-events) Indexed Parameters in Events When you mark an event parameter as `indexed` in Solidity, it becomes part of the event's topics rather than the data section. This enables efficient filtering but changes how you access the data. Non-Indexed String (accessible directly): Indexed String (hashed for filtering): **Why Use Indexed Parameters?** `Indexed` parameters enable efficient filtering by allowing nodes to quickly find specific events without scanning through all logs in a block. They provide gas optimization since topics are more gas-efficient for filtering operations compared to parsing event data. Additionally, nodes can index and search these parameters significantly faster, improving overall query performance when applications need to find specific events based on parameter values. **The Trade-off** For strings and bytes, indexing means: * Can filter events by this parameter efficiently * Cannot retrieve the actual value from the event log * Must query contract state to get the current value This is why, in our example, when we receive a `GreetingSet` event with indexed string parameters, we call contract.getGreeting() to retrieve the actual greeting text. > The pattern for listening to blockchain events via WebSocket follows these principles: > > The process begins by establishing a connection to the blockchain node's WebSocket endpoint, ensuring a persistent communication channel. Once connected, you create a filter that defines which events from which contracts to monitor, allowing precise event targeting. Next, you set up listener functions that register callbacks to execute when specific events occur. When events are received, your handler functions process the event data according to your application's needs. Throughout the connection lifetime, you must maintain the connection with periodic activity to prevent timeouts. Finally, when your application terminates, ensure a clean shutdown by properly closing all connections and removing event listeners. [hashtag](https://docs.somnia.network/developer/building-dapps/data-indexing-and-querying/listening-to-blockchain-events-websocket#code-breakdown) Code Breakdown ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- ### [hashtag](https://docs.somnia.network/developer/building-dapps/data-indexing-and-querying/listening-to-blockchain-events-websocket#connect-to-somnia-websocket) Connect to Somnia WebSocket The WebSocket URL for Somnia mainnet is `wss://dream-rpc.somnia.network/ws`. This creates a persistent connection to the network. #### [hashtag](https://docs.somnia.network/developer/building-dapps/data-indexing-and-querying/listening-to-blockchain-events-websocket#create-contract-instance) Create Contract Instance You need: * `Contract address`: The deployed address on Somnia. * `ABI`: At minimum, include the events you want to listen for. * `Provider`: The WebSocket connection. ### [hashtag](https://docs.somnia.network/developer/building-dapps/data-indexing-and-querying/listening-to-blockchain-events-websocket#define-event-filter) Define Event Filter The filter specifies: * Which contract to monitor * Which event signature to listen for ### [hashtag](https://docs.somnia.network/developer/building-dapps/data-indexing-and-querying/listening-to-blockchain-events-websocket#set-up-event-listener) Set Up Event Listener When an event is detected: 1. The callback receives the log data 2. Query the contract for the current state 3. Process/display the data as needed ### [hashtag](https://docs.somnia.network/developer/building-dapps/data-indexing-and-querying/listening-to-blockchain-events-websocket#maintain-connection) Maintain Connection This is because WebSocket connections can timeout. Send periodic requests to keep the connection alive. ### [hashtag](https://docs.somnia.network/developer/building-dapps/data-indexing-and-querying/listening-to-blockchain-events-websocket#update-the-abi) Update the ABI Include only the events and functions you need: ### [hashtag](https://docs.somnia.network/developer/building-dapps/data-indexing-and-querying/listening-to-blockchain-events-websocket#update-the-event-filter) Update the Event Filter Change the event signature to match your event: ### [hashtag](https://docs.somnia.network/developer/building-dapps/data-indexing-and-querying/listening-to-blockchain-events-websocket#handle-event-data) Handle Event Data Process the event based on your needs: [hashtag](https://docs.somnia.network/developer/building-dapps/data-indexing-and-querying/listening-to-blockchain-events-websocket#common-patterns) Common Patterns ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ ### [hashtag](https://docs.somnia.network/developer/building-dapps/data-indexing-and-querying/listening-to-blockchain-events-websocket#multiple-events) Multiple Events Listen for multiple events from the same contract: ### [hashtag](https://docs.somnia.network/developer/building-dapps/data-indexing-and-querying/listening-to-blockchain-events-websocket#error-recovery) Error Recovery Add reconnection logic for production applications: ### [hashtag](https://docs.somnia.network/developer/building-dapps/data-indexing-and-querying/listening-to-blockchain-events-websocket#event-history) Event History Get recent events on startup: [hashtag](https://docs.somnia.network/developer/building-dapps/data-indexing-and-querying/listening-to-blockchain-events-websocket#test-your-websocket-connection) Test Your WebSocket Connection ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ 1. Deploy your Smart Contract to Somnia network. 2. Run the listener in one terminal: 1. Trigger events from another script or dApp 2. Observe real-time updates in your listener ### [hashtag](https://docs.somnia.network/developer/building-dapps/data-indexing-and-querying/listening-to-blockchain-events-websocket#conclusion) Conclusion WebSocket connections provide real-time event monitoring for smart contracts on Somnia. This guide demonstrated: 1. Connecting to Somnia's WebSocket endpoint 2. Listening for specific contract events 3. Handling indexed parameters correctly 4. Maintaining stable connections 5. Adapting the pattern for any smart contract With this foundation, you can build responsive dApps that react instantly to blockchain events without polling. [PreviousUsing Data APIs (Ormi)chevron-left](https://docs.somnia.network/developer/building-dapps/data-indexing-and-querying/using-data-apis-ormi) [NextOracleschevron-right](https://docs.somnia.network/developer/building-dapps/oracles) Last updated 1 month ago * [Resources](https://docs.somnia.network/developer/building-dapps/data-indexing-and-querying/listening-to-blockchain-events-websocket#resources) * [Example Script](https://docs.somnia.network/developer/building-dapps/data-indexing-and-querying/listening-to-blockchain-events-websocket#example-script) * [Prerequisites](https://docs.somnia.network/developer/building-dapps/data-indexing-and-querying/listening-to-blockchain-events-websocket#prerequisites) * [What are WebSockets?](https://docs.somnia.network/developer/building-dapps/data-indexing-and-querying/listening-to-blockchain-events-websocket#what-are-websockets) * [How WebSockets Work](https://docs.somnia.network/developer/building-dapps/data-indexing-and-querying/listening-to-blockchain-events-websocket#how-websockets-work) * [WebSocket vs HTTP Polling](https://docs.somnia.network/developer/building-dapps/data-indexing-and-querying/listening-to-blockchain-events-websocket#websocket-vs-http-polling) * [Blockchain Events and WebSockets](https://docs.somnia.network/developer/building-dapps/data-indexing-and-querying/listening-to-blockchain-events-websocket#blockchain-events-and-websockets) * [Example Use Cases for WebSocket Event Listening](https://docs.somnia.network/developer/building-dapps/data-indexing-and-querying/listening-to-blockchain-events-websocket#example-use-cases-for-websocket-event-listening) * [Indexed Parameters in Events](https://docs.somnia.network/developer/building-dapps/data-indexing-and-querying/listening-to-blockchain-events-websocket#indexed-parameters-in-events) * [Code Breakdown](https://docs.somnia.network/developer/building-dapps/data-indexing-and-querying/listening-to-blockchain-events-websocket#code-breakdown) * [Connect to Somnia WebSocket](https://docs.somnia.network/developer/building-dapps/data-indexing-and-querying/listening-to-blockchain-events-websocket#connect-to-somnia-websocket) * [Define Event Filter](https://docs.somnia.network/developer/building-dapps/data-indexing-and-querying/listening-to-blockchain-events-websocket#define-event-filter) * [Set Up Event Listener](https://docs.somnia.network/developer/building-dapps/data-indexing-and-querying/listening-to-blockchain-events-websocket#set-up-event-listener) * [Maintain Connection](https://docs.somnia.network/developer/building-dapps/data-indexing-and-querying/listening-to-blockchain-events-websocket#maintain-connection) * [Update the ABI](https://docs.somnia.network/developer/building-dapps/data-indexing-and-querying/listening-to-blockchain-events-websocket#update-the-abi) * [Update the Event Filter](https://docs.somnia.network/developer/building-dapps/data-indexing-and-querying/listening-to-blockchain-events-websocket#update-the-event-filter) * [Handle Event Data](https://docs.somnia.network/developer/building-dapps/data-indexing-and-querying/listening-to-blockchain-events-websocket#handle-event-data) * [Common Patterns](https://docs.somnia.network/developer/building-dapps/data-indexing-and-querying/listening-to-blockchain-events-websocket#common-patterns) * [Multiple Events](https://docs.somnia.network/developer/building-dapps/data-indexing-and-querying/listening-to-blockchain-events-websocket#multiple-events) * [Error Recovery](https://docs.somnia.network/developer/building-dapps/data-indexing-and-querying/listening-to-blockchain-events-websocket#error-recovery) * [Event History](https://docs.somnia.network/developer/building-dapps/data-indexing-and-querying/listening-to-blockchain-events-websocket#event-history) * [Test Your WebSocket Connection](https://docs.somnia.network/developer/building-dapps/data-indexing-and-querying/listening-to-blockchain-events-websocket#test-your-websocket-connection) * [Conclusion](https://docs.somnia.network/developer/building-dapps/data-indexing-and-querying/listening-to-blockchain-events-websocket#conclusion) Copy Client Server | | |----Connection Request-->| |<---Connection Accept----| | | |<===Open Connection====> | | | |----Send Message-------->| |<---Receive Message------| |<---Push Notification----| |----Send Message-------->| | | |<===Open Connection====> | | | |----Close Connection---->| Copy // Inefficient: Constantly asking "Any updates?" setInterval(async () => { const response = await fetch('https://api.example.com/events'); const data = await response.json(); if (data.hasNewEvents) { console.log('New event:', data.events); } }, 5000); // Check every 5 seconds Copy // Efficient: Server pushes updates immediately const ws = new WebSocket('wss://api.example.com/events'); ws.on('message', (data) => { console.log('New event:', data); // Instant notification }); Copy event MessageSent(string message); // Can read 'message' directly from logs Copy event MessageSent(string indexed message); // 'message' is hashed, cannot read directly Copy const wsUrl = 'wss://dream-rpc.somnia.network/ws'; //change url for Mainnet const provider = new ethers.WebSocketProvider(wsUrl); await provider._waitUntilReady(); Copy const contract = new ethers.Contract(contractAddress, abi, provider); Copy const filter = { address: contractAddress, topics: [ethers.id("GreetingSet(string,string)")] }; Copy provider.on(filter, async (log) => { // Handle the event const greeting = await contract.getGreeting(); console.log(`New greeting: "${greeting}"`); }); Copy setInterval(async () => { await provider.getBlockNumber(); }, 30000); Copy const abi = [\ // Your event definition\ {\ "anonymous": false,\ "inputs": [\ // Your event parameters\ ],\ "name": "YourEventName",\ "type": "event"\ },\ // Any read functions you need\ {\ "inputs": [],\ "name": "yourReadFunction",\ "outputs": [/* outputs */],\ "stateMutability": "view",\ "type": "function"\ }\ ]; Copy const filter = { address: contractAddress, topics: [ethers.id("YourEventName(type1,type2)")] }; Copy provider.on(filter, async (log) => { // For non-indexed parameters, you can parse the log const parsedLog = contract.interface.parseLog(log); // For indexed strings, query the contract state const currentState = await contract.yourReadFunction(); // Process your data console.log('Event detected:', currentState); }); Copy // Listen for Event1 provider.on({ address: contractAddress, topics: [ethers.id("Event1(...)")] }, handleEvent1); // Listen for Event2 provider.on({ address: contractAddress, topics: [ethers.id("Event2(...)")] }, handleEvent2); Copy async function connectWithRetry() { let retries = 0; while (retries < 5) { try { await listen(); break; } catch (error) { console.log(`Retry ${++retries}/5...`); await new Promise(r => setTimeout(r, 5000)); } } } Copy // Get last 100 blocks of events const currentBlock = await provider.getBlockNumber(); const events = await contract.queryFilter('YourEventName', currentBlock - 100, currentBlock); events.forEach(event => { console.log('Historical event:', event); }); Copy node websocket-listener.js --- # Managing NFT Metadata with IPFS | Somnia Docs In this guide, you will rely on an IPFS workflow for ERC721 collections on Somnia. This guide will walk you through how - You will prepare and upload artwork to IPFS via Pinata. - Generate clean, wallet/marketplace-friendly metadata JSON - Upload the metadata to IPFS via Pinata Cloud - Deploy a Solidity contract that stores the metadata URIs onchain (for each token) - Mint your NFTs. All metadata resides on IPFS, while only the URIs (pointers) are stored onchain. Please note that this guide provides a Smart Contract option for fully onchain metadata. [hashtag](https://docs.somnia.network/developer/building-dapps/tokens-and-nfts/managing-nft-metadata-with-ipfs#concepts-you-should-know) Concepts You Should Know ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- * [IPFSarrow-up-right](https://ipfs.tech/) (InterPlanetary File System) is a “Content Addressed Storage Network”. Files are addressed by their CID (content identifier). If any bit changes, the CID changes, and it is great for integrity and verifiability. * [Pinataarrow-up-right](https://pinata.cloud/) : Pinata pins your files to IPFS and keeps them available. You’ll get CIDs (content identifiers) that never change for the same content. * TokenURI: an ERC721 method that returns a URL (often an ipfs:// URI) pointing to a JSON file describing the NFT token (name, description, image, attributes, etc.). * Per token URI vs `baseURI`: * Per token URI (this guide): store the full JSON URI onchain for each token with ERC721URIStorage. Easiest and most flexible. * `baseURI` pattern: compute tokenURI = baseURI + tokenId (no per token storage). Cheaper for large drops, but requires sequential naming. [hashtag](https://docs.somnia.network/developer/building-dapps/tokens-and-nfts/managing-nft-metadata-with-ipfs#prerequisites) Prerequisites ------------------------------------------------------------------------------------------------------------------------------------------------ * MetaMask is configured for a Somnia RPC and has sufficient funds for gas * Node.js 20+ (only if you’ll run the helper scripts below) * Pinata account and a JWT (recommended): Get your JWT on Pinata * `Pinata Dashboard → API Keys → New Key → choose Admin/Scoped as needed → copy JWT` [hashtag](https://docs.somnia.network/developer/building-dapps/tokens-and-nfts/managing-nft-metadata-with-ipfs#prepare-images) Prepare Images -------------------------------------------------------------------------------------------------------------------------------------------------- If your art varies wildly in size or format, normalize once for a consistent user experience. Create an `assets` directory for your images. Run the node script below, targeting the `assets` directory to give uniformity to your images. Install the dependencies: Run the script: [hashtag](https://docs.somnia.network/developer/building-dapps/tokens-and-nfts/managing-nft-metadata-with-ipfs#recommended-folder-structure) Recommended Folder Structure ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ * `images/` contain consistent dimensions and formats (e.g., 1024×1024 PNG). * `metadata/` contain one JSON per token ID, matching the filename (e.g., 7.json for tokenId 7). [hashtag](https://docs.somnia.network/developer/building-dapps/tokens-and-nfts/managing-nft-metadata-with-ipfs#upload-images-to-ipfs-via-pinata) Upload Images to IPFS via Pinata -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- Install Pinata SDK: Initialise Pinata using your JWT credentials: Create a `.env` file in your project root: Upload the images directory and get the root CID: Run the following command to upload the images: Copy the CID into `.env` as `IMAGES_CID=....` [hashtag](https://docs.somnia.network/developer/building-dapps/tokens-and-nfts/managing-nft-metadata-with-ipfs#generate-metadata-json) Generate Metadata JSON ------------------------------------------------------------------------------------------------------------------------------------------------------------------ Each \*.json should follow the de facto standards: Install `fast-glob` library: Run the script below to generate a file per image: To create the metadata files, run the command: [hashtag](https://docs.somnia.network/developer/building-dapps/tokens-and-nfts/managing-nft-metadata-with-ipfs#upload-metadata-to-ipfs) Upload Metadata to IPFS -------------------------------------------------------------------------------------------------------------------------------------------------------------------- Upload the metadata folder to generate the `METADATA_CID` Run the command: Your tokenURI format is now: `ipfs:///.json` Make sure the script uploads `assets/metadata` and records the printed Metadata CID; you’ll mint with: `ipfs:///.json` [hashtag](https://docs.somnia.network/developer/building-dapps/tokens-and-nfts/managing-nft-metadata-with-ipfs#nft-smart-contract) NFT Smart Contract ---------------------------------------------------------------------------------------------------------------------------------------------------------- We’ll use `ERC721URIStorage` Smart Contract and mint with full URIs. Paste this into Remix as `NFTTest.sol` chevron-rightNFTTest.sol[hashtag](https://docs.somnia.network/developer/building-dapps/tokens-and-nfts/managing-nft-metadata-with-ipfs#nfttest.sol) [hashtag](https://docs.somnia.network/developer/building-dapps/tokens-and-nfts/managing-nft-metadata-with-ipfs#contract-walkthrough) Contract Walkthrough -------------------------------------------------------------------------------------------------------------------------------------------------------------- ### [hashtag](https://docs.somnia.network/developer/building-dapps/tokens-and-nfts/managing-nft-metadata-with-ipfs#imports) Imports * `ERC721` is the core NFT logic (ownership, transfers, approvals). * `ERC721URIStorage` adds per-token storage for URIs via `_setTokenURI`. * `Ownable` simple admin model; exposes onlyOwner for minting control. `ERC721URIStorage` is the most straightforward way to store a token’s exact URI. For large drops, consider using a baseURI pattern to save storage gas; otherwise, this is perfect for flexible, explicit URIs. State (\_nextTokenId) is the sequential ID counter starting at 0. First mint → tokenId = 0, then 1, 2, … etc ### [hashtag](https://docs.somnia.network/developer/building-dapps/tokens-and-nfts/managing-nft-metadata-with-ipfs#constructor) Constructor Initializes collection name/symbol and sets the owner to `initialOwner` (the only account allowed to mint). ### [hashtag](https://docs.somnia.network/developer/building-dapps/tokens-and-nfts/managing-nft-metadata-with-ipfs#baseuri) \_baseURI() Returns https://ipfs.io. This is only used if you mint with relative paths (e.g., /ipfs/CID/7.json). If you mint with absolute ipfs://… URIs (recommended), this value is ignored. ### [hashtag](https://docs.somnia.network/developer/building-dapps/tokens-and-nfts/managing-nft-metadata-with-ipfs#safemint-to-uri) safeMint(to, uri): Owner-only mint. Creates a new token ID, mints safely (checking receiver contracts implement IERC721Receiver), and stores the exact uri string for that token via \_setTokenURI. ### [hashtag](https://docs.somnia.network/developer/building-dapps/tokens-and-nfts/managing-nft-metadata-with-ipfs#compile-and-deploy-using-remix.-follow-this-guide) Compile and Deploy using Remix. Follow this [guide](https://docs.somnia.network/developer/building-dapps/tokens-and-nfts/create-erc20-tokens) Copy the deployed contract address. [hashtag](https://docs.somnia.network/developer/building-dapps/tokens-and-nfts/managing-nft-metadata-with-ipfs#how-to-mint-nfts-per-token-uris) How To Mint NFTs (Per Token URIs) -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- n Remix (Deployed Contracts): * Call `safeMint(to, uri)` where: * `to` is the recipient address (can be yours). * `uri` is `ipfs:///.json` (e.g., `ipfs://bafy.../0.json`). Then call `tokenURI(0)` to confirm the stored URI. If you prefer scripts for multiple mints, you can write a script to Batch Mint using Hardhat, for example: [hashtag](https://docs.somnia.network/developer/building-dapps/tokens-and-nfts/managing-nft-metadata-with-ipfs#validation) Validation ------------------------------------------------------------------------------------------------------------------------------------------ To validate everything end to end, first do quick gateway smoke tests by opening `https://ipfs.io/ipfs//0.png` and `https://ipfs.io/ipfs//0.json` in a browser to confirm the image and JSON are reachable. Next, call `tokenURI(0)` on your contract and verify it returns `ipfs:///0.json`. Finally, check in a wallet or marketplace UI that the NFT renders correctly using the `ipfs://` image URI embedded in the JSON. [hashtag](https://docs.somnia.network/developer/building-dapps/tokens-and-nfts/managing-nft-metadata-with-ipfs#metadataoptional-fully-onchain-metadata) MetadataOptional Fully Onchain Metadata ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- chevron-rightSmart Contract[hashtag](https://docs.somnia.network/developer/building-dapps/tokens-and-nfts/managing-nft-metadata-with-ipfs#smart-contract) If your art is small (SVG) or you only need text metadata, you can **encode metadata JSON on-chain**: **Trade-offs** * No external dependencies; URIs are immutable, always available. * Higher gas (long strings), limited to compact media (SVG/text). For large images, prefer IPFS. [PreviousCreate ERC721 NFT Collectionschevron-left](https://docs.somnia.network/developer/building-dapps/tokens-and-nfts/create-erc721-nft-collections) [NextUsing Native SOMI/STTchevron-right](https://docs.somnia.network/developer/building-dapps/tokens-and-nfts/using-native-somi-stt) Last updated 1 month ago * [Concepts You Should Know](https://docs.somnia.network/developer/building-dapps/tokens-and-nfts/managing-nft-metadata-with-ipfs#concepts-you-should-know) * [Prerequisites](https://docs.somnia.network/developer/building-dapps/tokens-and-nfts/managing-nft-metadata-with-ipfs#prerequisites) * [Prepare Images](https://docs.somnia.network/developer/building-dapps/tokens-and-nfts/managing-nft-metadata-with-ipfs#prepare-images) * [Recommended Folder Structure](https://docs.somnia.network/developer/building-dapps/tokens-and-nfts/managing-nft-metadata-with-ipfs#recommended-folder-structure) * [Upload Images to IPFS via Pinata](https://docs.somnia.network/developer/building-dapps/tokens-and-nfts/managing-nft-metadata-with-ipfs#upload-images-to-ipfs-via-pinata) * [Generate Metadata JSON](https://docs.somnia.network/developer/building-dapps/tokens-and-nfts/managing-nft-metadata-with-ipfs#generate-metadata-json) * [Upload Metadata to IPFS](https://docs.somnia.network/developer/building-dapps/tokens-and-nfts/managing-nft-metadata-with-ipfs#upload-metadata-to-ipfs) * [NFT Smart Contract](https://docs.somnia.network/developer/building-dapps/tokens-and-nfts/managing-nft-metadata-with-ipfs#nft-smart-contract) * [Contract Walkthrough](https://docs.somnia.network/developer/building-dapps/tokens-and-nfts/managing-nft-metadata-with-ipfs#contract-walkthrough) * [Imports](https://docs.somnia.network/developer/building-dapps/tokens-and-nfts/managing-nft-metadata-with-ipfs#imports) * [Constructor](https://docs.somnia.network/developer/building-dapps/tokens-and-nfts/managing-nft-metadata-with-ipfs#constructor) * [\_baseURI()](https://docs.somnia.network/developer/building-dapps/tokens-and-nfts/managing-nft-metadata-with-ipfs#baseuri) * [safeMint(to, uri):](https://docs.somnia.network/developer/building-dapps/tokens-and-nfts/managing-nft-metadata-with-ipfs#safemint-to-uri) * [Compile and Deploy using Remix. Follow this guide](https://docs.somnia.network/developer/building-dapps/tokens-and-nfts/managing-nft-metadata-with-ipfs#compile-and-deploy-using-remix.-follow-this-guide) * [How To Mint NFTs (Per Token URIs)](https://docs.somnia.network/developer/building-dapps/tokens-and-nfts/managing-nft-metadata-with-ipfs#how-to-mint-nfts-per-token-uris) * [Validation](https://docs.somnia.network/developer/building-dapps/tokens-and-nfts/managing-nft-metadata-with-ipfs#validation) * [MetadataOptional Fully Onchain Metadata](https://docs.somnia.network/developer/building-dapps/tokens-and-nfts/managing-nft-metadata-with-ipfs#metadataoptional-fully-onchain-metadata) Copy npm i sharp fast-glob Copy // scripts/resize.js import fg from "fast-glob"; import sharp from "sharp"; import { mkdirSync } from "fs"; import path from "path"; const INPUT = "assets/raw-images"; // put your original images here const OUTPUT = "assets/images"; mkdirSync(OUTPUT, { recursive: true }); const files = (await fg(`${INPUT}/*.{png,jpg,jpeg,webp}`)).sort(); for (let i = 0; i < files.length; i++) { const out = path.join(OUTPUT, `${i}.png`); await sharp(files[i]).resize(1024, 1024, { fit: "cover" }).toFile(out); } console.log("Normalized images written to", OUTPUT); Copy project/ assets/ images/ 0.png 1.png 2.png ... metadata/ 0.json 1.json 2.json ... scripts/ resize.js pinata.ts (Pinata SDK init) upload-images.ts (pin images folder → IMAGES_CID) make-metadata.ts (generate JSON → uses IMAGES_CID) upload-metadata.ts (pin metadata folder → METADATA_CID) Copy npm i pinata dotenv Copy // pinata.ts import 'dotenv/config' import { PinataSDK } from 'pinata' export const pinata = new PinataSDK({ pinataJwt: process.env.PINATA_JWT!, // from Pinata “API Keys” pinataGateway: process.env.PINATA_GATEWAY!, // e.g. myxyz.mypinata.cloud }) Copy PINATA_JWT=eyJhbGciOi... # your Pinata JWT (keep secret) PINATA_GATEWAY=myxyz.mypinata.cloud IMAGES_CID= # leave blank until you upload images Copy // scripts/upload-images.ts import 'dotenv/config' import { pinata } from './pinata' import fs from 'node:fs/promises' import path from 'node:path' import { File } from 'node:buffer' const DIR = 'assets/images' async function main() { const names = (await fs.readdir(DIR)) .filter(n => n.match(/\.(png|jpg|jpeg|webp)$/i)) .sort((a, b) => Number(a.split('.')[0]) - Number(b.split('.')[0])) const files: File[] = [] for (const name of names) { const bytes = await fs.readFile(path.join(DIR, name)) files.push(new File([bytes], name)) // uploaded as a single folder } const res = await pinata.upload.public.fileArray(files) // res.cid is the directory root CID console.log('Images CID:', res.cid) } main().catch(console.error) Copy node dist/scripts/upload-images.js # => Images CID: bafybe... Copy { "name": "NFTTest #0", "description": "A clean ERC-721 on Somnia with per-token metadata.", "image": "ipfs://IMAGES_CID/0.png", "external_url": "https://your-site.example", "attributes": [\ { "trait_type": "Edition", "value": 0 }\ ] } Copy npm i fs-extra fast-glob Copy / scripts/make-metadata.js import fg from "fast-glob"; import { writeJSON, mkdirs } from "fs-extra"; import path from "path"; const IMAGES_CID = process.env.IMAGES_CID || "bafy..."; // paste from Step 2 const OUT_DIR = "assets/metadata"; sync function main() { await mkdirs(OUT_DIR) const imgs = (await fg('assets/images/*.{png,jpg,jpeg,webp}')).sort((a, b) => Number(path.basename(a).split('.')[0]) - Number(path.basename(b).split('.')[0]) ) for (const p of imgs) { const id = Number(path.basename(p).split('.')[0]) const json = { name: `NFTTest #${id}`, description: 'ERC-721 on Somnia with Pinata-hosted IPFS metadata.', image: `ipfs://${IMAGES_CID}/${id}.png`, attributes: [{ trait_type: 'Edition', value: id }] } await writeJSON(path.join(OUT_DIR, `${id}.json`), json, { spaces: 2 }) } console.log('Metadata written to', OUT_DIR) } main().catch(console.error) Copy node dist/scripts/make-metadata.js Copy // scripts/upload-metadata.ts import { pinata } from './pinata' import fs from 'node:fs/promises' import path from 'node:path' import { File } from 'node:buffer' const DIR = 'assets/metadata' async function main() { const names = (await fs.readdir(DIR)) .filter(n => n.endsWith('.json')) .sort((a, b) => Number(a.split('.')[0]) - Number(b.split('.')[0])) const files: File[] = [] for (const name of names) { const bytes = await fs.readFile(path.join(DIR, name)) files.push(new File([bytes], name, { type: 'application/json' })) } const res = await pinata.upload.public.fileArray(files) console.log('Metadata CID:', res.cid) } main().catch(console.error) Copy node dist/scripts/upload-metadata.js # => Metadata CID: bafybe... Copy // SPDX-License-Identifier: MIT // Compatible with OpenZeppelin Contracts ^5.4.0 pragma solidity ^0.8.27; import {ERC721} from "@openzeppelin/contracts/token/ERC721/ERC721.sol"; import {ERC721URIStorage} from "@openzeppelin/contracts/token/ERC721/extensions/ERC721URIStorage.sol"; import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; contract NFTTest is ERC721, ERC721URIStorage, Ownable { uint256 private _nextTokenId; constructor(address initialOwner) ERC721("NFTTest", "NFTT") Ownable(initialOwner) {} function _baseURI() internal pure override returns (string memory) { return "https://ipfs.io"; // only affects relative paths } function safeMint(address to, string memory uri) public onlyOwner returns (uint256) { uint256 tokenId = _nextTokenId++; _safeMint(to, tokenId); _setTokenURI(tokenId, uri); // store full URI (e.g., ipfs://CID/0.json) return tokenId; } function tokenURI(uint256 tokenId) public view override(ERC721, ERC721URIStorage) returns (string memory) { return super.tokenURI(tokenId); } function supportsInterface(bytes4 interfaceId) public view override(ERC721, ERC721URIStorage) returns (bool) { return super.supportsInterface(interfaceId); } } Copy scripts/mint.ts (Hardhat) import { ethers } from "hardhat"; const CONTRACT = "0xYourDeployedAddress"; const RECEIVER = "0xReceiver"; const METADATA_CID = "bafy..."; async function main() { const nft = await ethers.getContractAt("NFTTest", CONTRACT); for (let id = 0; id < 10; id++) { const uri = `ipfs://${METADATA_CID}/${id}.json`; const tx = await nft.safeMint(RECEIVER, uri); await tx.wait(); console.log(`Minted #${id} → ${uri}`); } } main().catch(console.error); Copy // SPDX-License-Identifier: MIT pragma solidity ^0.8.27; import {ERC721} from "@openzeppelin/contracts/token/ERC721/ERC721.sol"; import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; import {Strings} from "@openzeppelin/contracts/utils/Strings.sol"; import {Base64} from "@openzeppelin/contracts/utils/Base64.sol"; contract OnchainMeta is ERC721, Ownable { using Strings for uint256; uint256 private _id; constructor(address owner_) ERC721("Onchain", "ONC") Ownable(owner_) {} function mint(address to) external onlyOwner returns (uint256) { uint256 tokenId = _id++; _safeMint(to, tokenId); return tokenId; } function tokenURI(uint256 tokenId) public view override returns (string memory) { _requireOwned(tokenId); // Example: trivial, static image via SVG data URI string memory image = string( abi.encodePacked( "data:image/svg+xml;base64,", Base64.encode( bytes( string( abi.encodePacked( "", "", "#", tokenId.toString(), "" ) ) ) ) ) ); bytes memory json = abi.encodePacked( '{"name":"Onchain #', tokenId.toString(), '","description":"Fully on-chain metadata","image":"', image, '"}' ); return string(abi.encodePacked( "data:application/json;base64,", Base64.encode(json) )); } } ---