# WebAssembly (Wasm) WebAssembly is a way to run applications in programming languages other than JavaScript as web pages. Essentially, Wasm is just a virtual machine that runs on all modern browsers, but whereas in the past you were required to use JavaScript to run code in a web page, Wasm makes it possible to run code in browsers with programming languages other than JavaScript. The WebAssembly Virtual Machine, or Wasm for short, is proven to be faster than any alternative virtual machine because of technology peculiarities. The use of WebAssembly enables Gear’s smart contracts to compile directly into machine code and run at near-native speeds. Higher speeds means lower transaction costs and higher efficiency. All Gear programs and smart-contract are run as WebAssembly programs. This means for example, that developers can bring their applications to the web and achieve full performance with the apps’ full set of capabilities - that they’d typically have when running native on Windows or Mac - in a web browser. The developers won’t actually have to write the Wasm code directly either. Instead, they’d use Wasm as a compilation target for programs written in other languages. The main problem that Wasm solves is the inability to use programming languages other than JavaScript on the web. Although JavaScript is a great programming language, it wasn’t designed to be super fast in large applications. What’s game changing about Wasm is that it brings the performance of native applications, written in other programming languages, to the web in a way that's completely secure. Wasm should give significant speed increases in two main areas. First, it should significantly increase application start up speed. In fact, applications that are already using Wasm have been able to cut application start up time in half, and as more optimizations are made, it will only continue to increase startup speed further. This will allow huge applications to load up very, very quickly. Secondly, Wasm enables significant benefits in throughput too, which means that once code is compiled, it will run much faster - making applications more efficient and responsive which will significantly improve user experience. WebAssembly has the following advantages: * Wasm is extremely fast, efficient and portable. Code can be executed at near-native speed across different platforms. * Wasm is also readable and debuggable. Although WebAssembly is a low-level language, it does have a human-readable text format that allows code to be written, viewed and debugged by hand. * It’s also extremely secure as it’s run in a safe, sandboxed environment and like other web code, it will enforce the browsers same-origin and permissionless security policies. Wasm format allows Gear Protocol’s developers write applications in Rust today along with C#/C++, Go, and Javascript in the future. # Actor Model One of the key and distinguished features of the Gear Protocol is the **Actor model** for message-passing communications. Actor model framework enables asynchronous messaging and parallel computation which drastically increases the achievable speed and allows building more complex dApps in an easier way. Asynchronous format of dApps written on the Gear Protocol makes them cross-chain compatible by default. In concurrent computing systems, “message-passing communication” means that programs communicate by exchanging messages. This has its advantages over “shared memory communication” as message passing is easier to understand than shared memory concurrency as it’s more robust and has better performance characteristics. The principle of the Actor model approach is that programs never share any state and just exchange messages between each other. With the Actor model, a system consists of simultaneously functioning objects that communicate with each other exclusively by messaging. While an ordinary Actor model does not guarantee message ordering, the Gear Protocol provides some additional guarantees that the order of messages between two particular programs is preserved. Actors are isolated from each other and do not share memory. They have a state and the only way to change it is by receiving a message. The Actor model guarantees high scalability and high fault tolerance. As applications become more computationally intensive over time in Web3, the Actor model will naturally allow advancements in CPU technology to support this continued scale. Actor [#actor] An Actor in the Actor model is an atomic computational unit that can send and receive messages. With Gear, any instance in the Gear Protocol is an Actor - a program (smart contract) or a user that sends messages to a program. Every Actor has an internal private state, also users have a mailbox. Communication is asynchronous, messages are popped out from the mailbox and allocated to message processing streams where they’re then processed in cycles. When an actor receives and processes a message, the response can be the following: * Send a message to another actor * Create another actor * Change its own internal state Actors are independent, they never share any state and just exchange messages with each other. Using the Actor model approach provides a way to implement Actor-based concurrency inside smart contract logic. It can utilize various language constructs for asynchronous programming (Futures and async-await in Rust). Async/await support [#asyncawait-support] Unlike classes, Actors allow only one task to access their mutable state at a time, which makes it safe for code in multiple tasks to interact with the same instance of an actor. Asynchronous functions significantly streamline concurrency management, but they do not handle the possibility of deadlocks or state corruption. To avoid deadlocks or state corruption, async functions should avoid calling functions that may block their thread. To achieve it, they use an await expression. Currently, the lack of normal support of async/await patterns in the typical smart contracts code brings a lot of problems for smart contract developers. Actually, achieving better control in a smart contract program flow is actually more or less possible by adding handmade functions (like in Solidity smart contracts). But the problem with many functions in a contract is that one can easily get confused - which function can be called at which stage in the contract's lifetime. It brings unnecessary complexity for developers writing smart contracts using domain specific languages. Gear natively provides arbitrary async/await syntax for any programs. It greatly simplifies development and testing and reduces the likelihood of errors in smart contract development. The Gear Protocol's API also allows synchronous messages by simply not using await expressions if the logic of the program requires it. # Technology Aspect Gear Protocol, built on [Substrate](/docs/vara-network/about/technology/substrate), facilitates swift dApp development. It is the most developer-friendly way to implement programs and smart contracts with arbitrary logic and of any complexity. Vara benefits from Gear Protocol's unique tech stack that includes the Actor Model, Persistent Memory and the WebAssembly virtual machine (Wasm). Actor Model [#actor-model] In the [Actor Model](/docs/vara-network/about/technology/actor-model) of communication, programs and users are known as "actors." Actors maintain their state privacy and communicate through messages, enhancing network security. All communications are asynchronous but logically guaranteed to be handled, which increases the achievable network speed and allows the building of more sophisticated dApps. Persistent Memory [#persistent-memory] The [Persistent Memory](/docs/vara-network/about/technology/persist-memory) concept entails that programs do not rely on shared storage; instead, their complete state is consistently stored within their respective memory spaces. Clever and effective memory virtualization techniques allow tracking memory access and ensuring that only required pages are persisted and loaded when needed. It removes many complexities, streamlining the development process while mapping running programs and their states to more closely resemble real-life operating system primitives. Wasm [#wasm] [WebAssembly](/docs/vara-network/about/technology/WASM) allows the running of any bytecode in a sandboxed environment. Smart contracts in Vara are run as Wasm programs (Actors). Since Wasm can be compiled from a variety of common programming languages, it considerably lowers the barrier to entry for developers arriving from the Web2 space. Enabling native speed for deterministic cross-platform computations, Wasm is Web3 development unlocked. Network Upgrade [#network-upgrade] Like all software systems, blockchains require periodic updates for security patches and other code changes. In the early days, blockchains had to implement forks to make updates, causing instability and price fluctuations. Coordinating updates within a decentralized community proved challenging. Vara, built on the Substrate blockchain development framework, offers a solution with forkless runtime upgrades. Substrate enables the deployment of enhanced runtime capabilities, introducing breaking changes without a hard fork. This is possible because the runtime definition is a component of a Substrate chain's state, allowing network participants to update it through an extrinsic. Gear Capabilities [#gear-capabilities] Gear Protocol offers a versatile platform for building next-generation Web3 applications. Gear's true strength lies in its broader capabilities: * **Sovereign Layer-1 Network** tailored to developer's specific needs, escaping the limitations of pre-defined protocols, enabling complete control over governance, consensus mechanisms, and fee structures. * **Scalable Layer-2 Solutions** seamlessly integrating with popular Layer-1 networks (like Ethereum), offloading computationally intensive tasks, significantly reducing on-chain costs and boosting transaction throughput. * **Parallel Processing Powerhouse** - Gear scales naturally with hardware advancements, thanks to its unique parallel processing architecture. Run demanding applications on standard computers, ensuring cost-effectiveness and accessibility. * **Modular Microservices Mastery** - allows to deploy and manage individual microservices on single nodes or custom networks, optimize resource allocation, simplify maintenance, and achieve fine-grained control over the application's functionality. * Unleash the potential of **Zero-Knowledge Proofs** (ZKPs) with Gear's high-performance infrastructure. Develop privacy-preserving applications, including ZKML (Zero-Knowledge Machine Learning), without sacrificing scalability or performance. * **Familiar Development Environment** - leverages developers existing skillset supporting widely adopted programming languages like Rust, C, and C++, allowing developers to seamlessly transition to the Gear ecosystem without mastering new complexities. * **Collaborative Community** - becoming a part of a thriving developer community enables anyone access a wealth of resources and support, including a growing library of pre-built programs and smart contracts examples, tools, and documentation. Contribute to the development of a vibrant and collaborative ecosystem. * Potential integration with other Substrate-based networks. Substrate framework simplifies the creation of custom blockchains for specific applications. Substrate provides extensive functionality out-of-the-box and allows one to focus on creating a custom engine on top of the protocol. The majority of developers and inspirers of the Gear Protocol were directly involved in creating the Substrate technology. Anyone with a standard computer can run a [Gear node](/docs/vara-network/node) today and always. For additional details, refer to the [Gear Whitepaper](https://whitepaper.gear.foundation). # Persistent Memory Another distinguished feature of the Gear Protocol is the Persistent memory approach. It is what makes development easier, removes a lot of complexity and makes protocol memory management match real-life hardware and operating systems. Programs running in Gear Networks don’t use storage but rather their full state is persisted which ensures much less API surface for blockchain context. It avoids domain-specific language features as well as allows using much more complex language constructs — persisted boxed closures, futures compositors, etc. The Gear Protocol uses clever memory virtualization techniques (despite vanilla Wasm does not), memory allocation and deallocation are first-class syscalls of the protocol. Memory access is also tracked and only required pages are loaded/stored. That allows heap-allocated stack frames of smart contracts stored in the blockchain’s state (typically found in futures and their compositors) to be seamlessly persisted and invoked when needed, preserving their state upon request. Program code is stored as an immutable Wasm blob. Each program has a fixed amount of memory which persists between message-handling (so-called static area). Gear instance holds individual memory space per program and guarantees its persistence. A program can read and write only within its own memory space and has no access to the memory space of other programs. Individual memory space is reserved for a program during its initialization and does not require an additional fee (it is included in the program's initialization fee). A program can allocate the required amount of memory in blocks of 64KB. Each memory block allocation requires a gas fee. Each page (64KB) is stored separately on the distributed database backend, but at the run time, Gear node constructs continuous runtime memory and allows programs to run on it without reloads. Memory parallelism [#memory-parallelism] Individual isolated memory space per program allows parallelization of message processing on a Gear node. Number of parallel processing streams equals the number of CPU cores. Each stream processes messages intended for a defined set of programs. It relates to messages sent from other programs or from outside (user’s transactions). For example, given a message queue containing messages targeted to 100 different programs, Gear node runs on a network where 2 threads of processing are configured. Gear engine uses a runtime-defined number of streams (equal to number of CPU cores on a typical validator machine), divides total amount of targeted programs to number of streams and creates a message pool for each stream (50 programs per stream). Programs are distributed to separate streams and each message appears in a stream where its targeted program is defined. So, all messages addressed to a particular program appear in a single processing stream. In each cycle a targeted program can have more than one message and one stream processes messages for plenty of programs. The result of message processing is a set of new messages from each stream that is added to the message queue, then the cycle repeats. The resultant messages generated during message processing are usually sent to another address (return to origin or to the next program). alt text # State transition Each system follows the rules according to which the state of the system evolves. As the network processes new input data, the state is advanced according to state transition rules. This input data is packed in granular pieces of information called transactions. Gear nodes maintain and synchronize a transaction pool which contains all those new transactions. When any node (validator or not) receives a transaction, the node propagates the transaction to all connected nodes. For advanced reading how the transaction pool operates, refer to [Substrate Documentation](https://substrate.dev/docs/en/knowledgebase/learn-substrate/tx-pool). When a Gear validator node comes to produce a new block, some (or all) transactions from the pool are merged into a block and the network undergoes a state transition via this block. Transactions that were not taken in the last block remain in the pool until the next block is produced. A Gear Network supports the following types of transactions: 1. **Upload a program** - user uploads new program(s) - smart-contracts 2. **Upload a code** - user uploads a code to create programs from it later 3. **Create a program** - program or user creates a program from the previously uploaded code 4. **Send a message** - program or user fills the message queue 5. **Send a reply** - similar to the message but can be sent only as a reply to the received message 6. **Claim a value from mailbox** - user requests to transfer value associated with the message in his mailbox 7. **Dequeue messages** - validators (block producers) dequeue multiple messages, running associated programs 8. **Balance transfers** - Gear engine performs user-program-validator balance transfers Message processing is performed in the reserved space of the block construction/import time. It is guaranteed that message processing will be executed in every block, and at least at some particular rate determined by current instance settings. Upload a program [#upload-a-program] Designated authorities (or any user for public implementation) of Gear network can propose a new program saved to the state. For public networks, a balance associated with a program is also provided. This new balance then constitutes the initial balance (Existential Deposit). Upload a code [#upload-a-code] One can upload a Wasm code without initialization. This code cannot be executed as a program but it is saved to the storage and can be used for creating new programs from it. Both users and programs can create programs from uploaded code. Create a program [#create-a-program] Both users and programs can create new programs from the uploaded code. The code can be reused many times to produce several programs. Send a message [#send-a-message] End-users interact with programs via sending messages to a Gear Network. Messages sent to a Gear Network fill up the global message queue. This queue can be viewed as a runtime-driven transaction queue but with the guarantee that any message accepted into it will eventually be processed. Putting a message in the queue is not free and therefore a message is guaranteed to be dispatched. Programs also exchange messages with each other. The result of the received message can be another message (reply) addressed to another program or a user or a designated behavior to be used for the next message it receives. A program can also send a message the execution result of which will be a creation of another program. Send a reply [#send-a-reply] The reply is similar to the message described above with the only difference that the reply can be sent only as a response to the received message. Users can send the reply to the message contained in their mailboxes. Claim a value from mailbox [#claim-a-value-from-mailbox] After receiving a message with an associated value, the user has to claim this value to transfer it from the message to the user. Unclaimed value is to be returned to the sender after the message leaves the mailbox after its gas limit exhausts. Dequeue messages [#dequeue-messages] Validators can choose which messages to dequeue when it's their turn to produce the next block. It eliminates the need of each particular validator to maintain the full memory state. Dequeuing occurs only at the end of each block. During dequeuing, new messages can be generated. They can also be processed in this phase, but also can stay in the queue for the next block (and another validator). Balance transfers [#balance-transfers] Regular balance transfers can be performed in two ways. The first way is to use correspondent extrinsics (e.g. `transfer`, `setBalance`, etc.) from the Substrate `Balances` pallet. The second way is to send a message from one user to another with associated value. Messages, blocks and events lifecycle [#messages-blocks-and-events-lifecycle] The picture below illustrates the eternal lifecycle of Gear machinery. As the actor model for communications dictates, nothing is shared, there are only messages. Messages with destination "system" end up in the event log to be inspected in the user space. alt text # Gear Network State As any blockchain system, Gear maintains distributed state. Runtime code compiled to WebAssembly becomes part of the blockchain’s storage state. Gear ensures one of the defining features - **forkless runtime upgrades**. The state is also guaranteed to be finalized if a finality gadget is used. Storage state consists of the following components: * **Programs and memory** (includes program’s code and private memory) * **Message queue** (global message queue of the network) * **Accounts** (network accounts and their balances) Programs [#programs] Programs are first-class citizens in the Gear network state. Program code is stored as an immutable Wasm blob. Each program has a fixed amount of memory which persists between message-handling (so-called static area). Programs can allocate more memory from the memory pool provided by a Gear instance. A particular program can read and write only within exclusively allocated to its memory. Memory [#memory] Gear instance holds individual memory space per program and guarantees it's persistence. A program can read and write only within its own memory space and has no access to the memory space of other programs. Individual memory space is reserved for a program during its initialization and does not require an additional fee (included in the program initialization fee). A program can allocate the required amount of memory in blocks of 64KB. Each memory block allocation requires a gas fee. Each page (64KB) is stored separately on the distributed database backend, but at the run time, Gear node constructs continuous runtime memory and allows programs to run on it without reloads. Message queue [#message-queue] Gear instance holds a global message queue. Using Gear node, users can send transactions with one or several messages to a particular program(s). This fills up the message queue. During block construction, messages are dequeued and routed to the particular program. Accounts [#accounts] For a public network, protection against DOS attacks always requires gas/fee for transaction processing. Gear provides a balance module that allows to store user and program balances and pay a transaction fee. In general, a particular Gear network instance can be defined as both permissioned and permissionless, public blockchain. In the permissioned scenario, no balance module is required. # Vara.eth Welcome to Vara.eth [#welcome-to-varaeth] Vara.eth brings **high-performance computation** directly to Ethereum. Build apps that were previously impossible on-chain — with parallel execution, 2 GB memory per program, and users who never pay gas. Where do you want to start? [#where-do-you-want-to-start] **Modern development runs on AI agents.** Whether you're shipping a production dApp or prototyping an idea, tell Claude Code, Codex, or Cursor what you need — [Vara.eth Agent Skills](/docs/vara-eth/setup/agent-skills) walk the agent through Sails contracts, `ethexe` deployment, ABI, and frontend with verified, documentation-backed patterns. **Available right now.** Deploy your first program in 5 minutes → [Quick Start](/docs/vara-eth/build/quickstart) I'm new here [#im-new-here] Start with **[What is Vara.eth?](/docs/vara-eth/start-here/what-is-vara-eth)** — understand the big picture in 3 minutes. I want to build [#i-want-to-build] Jump to **[Pick Your Path](/docs/vara-eth/start-here/pick-your-path)** — choose your developer journey based on what you want to create. Why should I care? [#why-should-i-care] Read **[Why It Matters](/docs/vara-eth/start-here/why-it-matters)** — see what becomes possible: reverse gas, pre-confirmations, parallel execution. *** **Vara.eth is proven live on Ethereum — start building today.** The TestNet and MainNet campaigns ran the full stack end to end. [One of Us](https://github.com/gear-foundation/one-of-us) drew 931 members — one click each, no gas paid by users; [Robo Miner](https://github.com/gear-foundation/robo-miner) put AI agents into a live on-chain arena. Both are open source and work as reference applications. Those campaigns are complete, and the Gear-operated public endpoints (Ethereum RPC, Beacon, and the Vara.eth validator network) are no longer served. Everything else is unchanged — the Router and wVARA contracts stay deployed and verifiable on Ethereum Mainnet and Hoodi, and the whole stack is open source. Point `ethexe` at any Ethereum RPC provider, or [run your own node](/docs/vara-eth/setup/local-node), and reproduce the entire flow. You can also build with [Agent Skills](/docs/vara-eth/setup/agent-skills). # Overview Welcome to Vara Network’s documentation portal. This is the primary source of information for everyone who is looking for detailed information about Vara. Here you will find guides and developer's documentation that will enable you to dive deeper into developing programs and smart contracts on the Gear Protocol and running them on Vara in a decentralized manner. This Wiki outlines foundational information, provides general technology overviews, and highlights key advantages of Vara’s technology. It includes guidance on implementing and running your programs, setting up a Vara node, and becoming a nominator or validator. Additionally, it offers full API details and examples. Subscribe to the [@GearVaraDev](https://t.me/GearVaraDev) Telegram channel to get the latest releases and updates directly from the development team. What is the Vara Network? [#what-is-the-vara-network] **[Vara Network](https://vara.network/)** is a stand-alone layer-1 decentralized network built and running on top of **[Gear Protocol](https://gear-tech.io/)**. Alongside other blockchains that offer fast and inexpensive transactions, Vara possesses several unique features: a simple yet powerful framework for writing programs based on the Actor model, automation through deferred messages, gasless and signless transactions, built-in actors to extend program capabilities, and more. For more detailed information on these features, read the next article - [Distinctive Features](/docs/vara-network/about/features). Many developers choose Vara for its ease of implementing arbitrary and advanced logic for their web3 projects. Web3 projects are particularly inspired by the ability to implement web2-like scenarios, automate interactions with users and between programs, and create an enhanced user experience that is unattainable on other blockchain platforms due to the specifics of their technology. The fast and scalable Vara Network, which is non-fork upgradable, provides an optimal platform for next-gen gaming, financial-based applications, and various experimental features, among others. Vara accommodates a wide range of modern use cases, making it an ideal environment for running diverse applications. Vara Network employs an inclusive and reliable Nominated Proof-of-Stake (NPoS) mechanism to select validators for its consensus protocol. This mechanism selects validators based on their stakes and the funds nominated by other token holders. Running a node or supporting the network as a nominator is incentivized. This approach fosters a more democratic and decentralized process, reducing the risk of centralization, where only the wealthiest entities could become validators. The vast majority of tokens are held by the community. Vara empowers its community to steer the network's evolution through its on-chain governance mechanism. To ensure decentralization, longevity, and flexibility, Vara adopts the widely accepted OpenGov framework as its native governance model. This framework enables any VARA token holder to participate in Vara’s decentralized governance, influencing future updates or modifications to the network. The primary features of the Vara Network include: * The most advanced platform for developing and running decentralized applications. * Fast and low transaction cost truly decentralized L1. * Secure, efficient, and scalable environment for new features and optimizations. * A robust [NPoS](/docs/vara-network/staking) mechanism for network security and decentralization. * [Governance](/docs/vara-network/governance) by community members - token holders. * A stable and upgradable network, facilitated by Substrate. * A well-thought-out [token economy](/docs/vara-network/tokenomics) that ensures the long-term sustainability of the network and benefits for its community. Vara is currently in the **BETA phase**. While the network is live, it is not guaranteed to be entirely bug-free. However, thanks to **fork-less upgrades**, the network will evolve continuously while ensuring all user data is preserved. Built for the Agentic Era [#built-for-the-agentic-era] **Modern software development runs on AI agents.** Whether you're shipping a production dApp or prototyping an idea, you describe what you need and the agent — Claude Code, Codex, Cursor — writes the code. Vara is built for this loop, with a complete, open-source skill pack you can use right now. **[Vara Skills](/docs/vara-network/api/tooling/vara-skills)** gives the agent the exact APIs, real imports, and verified patterns for every step — spec, Sails contract, IDL, frontend, tests, and deployment. No hallucinated symbols, no broken builds. You describe the dApp; the agent ships it. Agents don't stop at building. The **[Trading Skill](/docs/vara-network/api/tooling/trading-skill)** lets an agent trade VARA and other spot pairs on centralized exchanges — Gate.io and Coinbase — with guided onboarding, local-only keys, risk limits, and dry-run-first safety. This is available **here and now**, not on a roadmap. For Vara.eth (Ethereum-anchored programs), see [Vara.eth Agent Skills](/docs/vara-eth/setup/agent-skills). What is the Gear Protocol? [#what-is-the-gear-protocol] The Gear Protocol is a [Substrate](https://substrate.io/)-based programming platform that enables anyone to develop and run dApps in Vara Network as well as other custom networks or environments powered by the Gear Protocol’s runtime and technology. The Gear Protocol is the most developer-friendly and cost-effective way to implement and run programs and smart contracts with arbitrary logic and of any complexity. Gear programs run under [WebAssembly](https://webassembly.org/) VM and they can be compiled from many popular programming languages, such as Rust, C, C++ and many more. The protocol ensures minimal, intuitive, and sufficient APIs for implementing programs and running them on multiple environments that support Gear runtime without needing to rewrite them. From a technological standpoint, the Gear Protocol is distinguished by fundamentally new features that enable anyone to create asynchronous programs with a completely new and previously unattainable user experience. These features include the Actor model for communication, the persistent memory concept, and the WebAssembly execution environment. The protocol dictates the structure and behavior of actors as communicating entities. The Gear Protocol assists with the transition to mass use of Web3 technologies by enabling the running of innovative dApps, microservices, middleware and open APIs. All code, libraries, and tools are available on [GitHub](https://github.com/gear-tech/gear). Feel free to use the tools and libraries, log issues as you find them, or create pull requests for your bug-bears or features. As the Gear Protocol is developing and growing, contributions are more than welcome! How to Start Developing on Vara Network? [#how-to-start-developing-on-vara-network] 1. Learn program development using the [developer documentation](/docs/developing/build). 2. Explore and try a batch of ready-made programs on [GitHub](https://github.com/gear-foundation). How to engage with the Vara community [#how-to-engage-with-the-vara-community] X (ex. Twitter) [#x-ex-twitter] Follow Vara Network on X for regular updates: [@VaraNetwork](https://twitter.com/VaraNetwork) Telegram [#telegram] Join Vara's official Telegram channel: [@VaraNetwork\_Global](https://t.me/VaraNetwork_Global) Discord [#discord] General information, announcements, technical and non-technical conversation are found on the [Discord server](https://discord.gg/x8ZeSy6S6K). Medium [#medium] Get smart with Vara's regular articles on [Medium](https://medium.com/@VaraNetwork). GitHub [#github] dApp examples and developer tools are found on [Gear Foundation's GitHub](https://github.com/gear-foundation). Gear Protocol's code, libraries, and tools are available on [Gear's GitHub](https://github.com/gear-tech/gear) # Quick Start This guide provides a general overview of running programs on the [Vara Network](https://vara.network/). It guides you through how to write a program, compile it to Wasm and deploy it to the network. Prerequisites [#prerequisites] System dependencies [#system-dependencies] * **Linux:** install build tools according to your distribution.\ For Ubuntu: ```bash sudo apt install -y binaryen build-essential clang cmake curl git jq libssl-dev pkg-config ``` * **macOS:** install Xcode Command Line Tools: ```bash xcode-select --install ``` * **Windows:** install **Build Tools for Visual Studio**.\ Download: [https://visualstudio.microsoft.com/downloads/?q=build+tools](https://visualstudio.microsoft.com/downloads/?q=build+tools) Rust toolchain [#rust-toolchain] Install Rust using [Rustup](https://rustup.rs/): ```bash curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh ``` If you just installed Rust and see `command not found: rustup` or `cargo`, your terminal needs to reload the environment. **Run this command to update your PATH:** ```bash source "$HOME/.cargo/env" ``` Alternatively, simply restart your terminal (or VS Code) to apply the changes. Wasm target [#wasm-target] Add the Wasm target: ```bash rustup target add wasm32v1-none ``` Starting from Gear **v1.8.0**, you must use the `wasm32v1-none` target to build programs for the Vara Network. **Note:** The old `wasm32-unknown-unknown` target is now considered legacy and should only be used for older projects. Creating your first Vara program [#creating-your-first-vara-program] To get started, install the `sails-cli` tool using the following command: ```bash cargo install sails-cli ``` After installation, you can create a new Vara project named `vara-app` by running: ```bash cargo sails new vara-app ``` Your `vara-app` directory tree should look like this: ``` vara-app │ ├── app │ └── src │ └── lib.rs │ ├── client │ └── ... │ ├── src │ └── lib.rs │ ├── tests │ └── gtest.rs │ ├── build.rs │ ├── Cargo.lock │ ├── Cargo.toml │ └── README.md ``` In `Cargo.toml`, the essential libraries required for building your first project have been included, for example: ```rust title="vara-app/Cargo.toml" [package] name = "vara-app" version = "0.1.0" edition = "2024" [dependencies] sails-rs = "1.0.0" vara-app-app = { version = "0.1.0", path = "app" } [workspace] resolver = "3" members = ["app", "client"] [workspace.package] version = "0.1.0" edition = "2024" [build-dependencies] sails-rs = { version = "1.0.0", features = ["build"] } vara-app-app = { version = "0.1.0", path = "app" } [dev-dependencies] sails-rs = { version = "1.0.0", features = ["gtest", "gclient"] } tokio = { version = "1.47.1", features = ["rt", "macros"] } vara-app-app = { path = "app" } vara-app-client = { path = "client" } ``` Let's move on to the main code: This Rust code defines a simple program for the Vara Network, now updated with a new structure. The program consists of a `VaraAppProgram` struct with a method to instantiate it and another method to return a `VaraAppService` struct. The `VaraAppService` struct has a service method that returns the string *"Hello from VaraApp!"*. This example demonstrates the basic structure and functionality of a Vara program using the `sails_rs` library. ```rust title="vara-app/app/src/lib.rs" #![no_std] use sails_rs::prelude::*; struct Service(()); impl Service { pub fn new() -> Self { Self(()) } } #[sails_rs::service] impl Service { // Service's method (command) #[export] pub fn do_something(&mut self) -> String { "Hello from Service!".to_string() } } #[derive(Default)] pub struct Program(()); #[sails_rs::program] impl Program { // Program's constructor pub fn new() -> Self { Self(()) } // Exposed service pub fn service(&self) -> Service { Service::new() } } ``` Build your program with a single command: ```bash cargo build --release ``` After a successful build, you can execute your tests to verify that everything is functioning correctly: ```bash cargo test --release ``` If everything has been executed successfully, your working directory should now contain a `target` directory structured as follows: ``` vara-app ├── ... ├── target ├── ... └── wasm32-gear └── release ├── vara_app.wasm <---- this is our built .wasm file ├── vara_app.opt.wasm <---- this is optimized .wasm file └── vara_app.idl <---- this is our application interface .idl file ``` * `vara_app.wasm` is the output Wasm binary built from source files * `vara_app.opt.wasm` is the optimized Wasm aimed to be uploaded to the blockchain\ (Optimization include reducing the file size and improving performance) * `vara_app.idl` is the Interface Definition Language (IDL) file that describes the application's interface, defining the structure and methods callable on the `vara_app` program. It's essential for interacting with the application on the blockchain, specifying available functions and their inputs and outputs. This file is crucial for developers and clients to ensure they use the correct method signatures and data types when interacting with the deployed program. Deploy your program to Vara Mainnet [#deploy-your-program-to-vara-mainnet] Gear provides an application for developers (Gear Idea) that implements all of the possibilities of interaction with programs on Vara Network, available at [idea.gear-tech.io](https://idea.gear-tech.io). Building a dApp used to mean an extra step - spin up on a testnet, then move everything to mainnet. That step is gone. Gear Idea hands every developer **100 free VARA per wallet, once**, so you can deploy, test and cover your first transactions directly on mainnet. Real network, real conditions, nothing to pay to start. Create account [#create-account] 1. Download the Polkadot extension for your browser via [https://polkadot.js.org/extension/](https://polkadot.js.org/extension/). This extension manages accounts and allows the signing of transactions with those accounts. It is a secure tool that allows injecting your accounts into any Substrate-based dapp. It does not perform wallet functions, e.g send funds. 2. Once downloaded, click + button to create a new account: Add account 3. Make sure you save your 12-word mnemonic seed securely. Create account 4. Select the network that will be used for this account - choose "Allow to use on any chain". Provide any name to this account and password and click "Add the account with the generated seed" to complete account registration. Finalizing account 5. Go to **[idea.gear-tech.io](https://idea.gear-tech.io)**. You will be prompted to grant access to your account for the application, click "Yes, allow this application access". Allow access 6. Make sure you are connected to the `Vara Network Mainnet`. The network name is at the bottom left corner of the page. Network name 7. You may switch the network by clicking on the network name. Switch network 8. Click the `Connect` button on the top-right to select an account that will be connected to Vara. Connect account 9. In accordance with the Actor model, programs are uploaded to a network via messages. Vara node charges a gas fee during message processing. Your account balance needs to have enough funds to upload a program to Vara Mainnet. If your wallet is new, claim the one-time developer grant of **100 VARA** - click the following button: Get balance A notification about successful balance replenishment will appear after passing captcha at the bottom of the window. You can also see the current account balance next to the account name in the upper right corner. The grant is available once per wallet and is enough to deploy a program and cover your first transactions. Transfer balance Upload program [#upload-program] 1. When your account balance is sufficient, click the Upload program button to open a popup window for uploading your new program. Upload program button 2. In the popup, click the Select file button and navigate to the `.opt.wasm` file we have pointed to above Upload new program 3. After uploading the `.opt.wasm` file, you need to upload the `IDL` file. Click the Select file button and navigate to the `.idl` file referenced earlier. Upload idl button 4. Specify the program name, click the Calculate Gas button to set the gas limit automatically, and then click the Submit button. Upload program form 5. Sign the `gear.uploadProgram` transaction to Vara. It is recommended to set the checkbox `Remember my password for the next 15 minutes` for your convenience. Sign transaction 6. Once your program is uploaded, go to the `Programs` section, find your program, and select it. Recently uploaded programs 7. Click on your program to see more information, such as `Metadata/Sails`, `Messages`, `Events`, and the `Vouchers` pane. Main page program Send message to a program [#send-message-to-a-program] 1. Send your newly uploaded program a message to see how it responds! Click the Send message button. 2. Click the Calculate Gas button to set the gas limit automatically, then click the Send Message button. Send form 3. Sign the `gear.sendMessage` transaction as it is shown in step 5 of the section **Upload Program**. 4. After your message has been successfully processed, you will see corresponding notifications: Log You have just sent a `DoSomething` command to your program! 5. Go back to your program by either clicking the Cancel button or using the browser's back button. 6. In the message pane, you can view the corresponding response. Log 7. Click on the message response to see more information. The expected "**Hello from VaraApp!**" response can be seen in the `Payload`. Log *** Further reading [#further-reading] For more info about writing programs for Vara and the specifics behind the program implementation, refer to [this article](/docs/developing/build/introduction). # Upload Program Following the principles of Actor model for communication, uploading a program in the network is just one of the specific types of transactions that contain a Wasm file as a payload. Uploading a new program (smart-contract) to the blockchain takes place by calling the custom extrinsic `gear.uploadProgram(code, salt, initPayload, gasLimit, value)`. Where: * `code: Bytes` - binary Wasm code * `salt: Bytes` - the random data that is added to the hashing process to force their uniqueness * `initPayload: Bytes`- the init message payload that will be processed by the init() function during program initialization * `gasLimit: u64` - the amount of gas that users are willing to spend on processing the upload of a new program * `value: u128` - the value that will be transferred to a balance of the newly created account for the program Program Balance [#program-balance] For each created program, amount of VARA equal to [existential deposit](https://docs.substrate.io/reference/glossary/#existential-deposit) (currently 1 VARA) is locked as a fixed deposit at the program's balance and cannot be used throughout the program's lifetime. This deposit can only be withdrawn upon the program's termination through the `gr_exit` function or program' initialization failure. At the time of a program’s initialization, the protocol deducts the existential deposit amount from the initiator’s balance, establishing the program’s minimal balance. It is also possible to allocate more than the ED value during the program's initialization. When a program is created by another program, the system verifies whether the initiating program has a sufficient balance to cover the required deposit. Program upload events [#program-upload-events] > Note: while extrinsics represent information from the outside world, events represent information from the chain. Extrinsics can trigger events. The extrinsic that was called to upload a program triggers a series of events. Learn more about events [here](/docs/developing/api/events#events-types). How to upload [#how-to-upload] There are several ways to upload a program: Upload via Gear Idea [#upload-via-gear-idea] The easiest way to upload the program is to use the “Upload program” option in the Gear Idea portal - **[idea.gear-tech.io](https://idea.gear-tech.io)**. img alt Via Gear-JS library [#via-gear-js-library] The Gear-JS library provides a simple and intuitive way to connect Vara Component APIs, including interaction with programs. More details: [Gear API](/docs/developing/api). Via gear-program [#via-gear-program] `gear-program` is the command line (CLI) utility for interacting with the blockchain network. Refer to [gear-program GitHub repo](https://github.com/gear-tech/gear-program) for details. # Consistency and Reliability One of the key features of the Gear Protocol is its use of the Actor model for message-passing communication. The Actor model framework enables asynchronous messaging and parallel computation, which drastically improves the achievable speed and scalability of dApps. In the Actor model, programs do not share state and instead communicate with each other through messages. If a program sends an asynchronous message to another program, it has to wait for a reply from the other program before it can proceed with the next operation. When a program interacts with another program, the transaction becomes "distributed." A distributed transaction is a set of operations performed across multiple databases or, in the case of the Gear Protocol, across multiple actors with their own states. Distributed transactions must possess these properties: * Atomicity - all data changes are treated as if they were a single operation. That is, either all of the modifications are made, or none of them are made; * Consistency - this property implies that when a transaction begins and ends, the state of data is consistent. For example, transactions on the Ethereum blockchain are atomic, meaning that if a transaction fails due to an error, all of its effects on the global state are rolled back as if the transaction never occurred. Many blockchain applications rely on the atomicity of transactions, but this can be a problem when building asynchronous applications using the programming paradigm used on Ethereum, as it may encounter the problem of not being able to recover program state after a failed transaction. Consider a simple token exchange where a user wants to swap tokens A for tokens B in a liquidity pool. The swap program (hereinafter referred to as the 'contract') would send a message to the token A contract and a message to the token B contract. If one of these messages succeeds and the other fails for some reason, the state of the token A contract would be changed while the state of the token B contract would remain unchanged. This can cause inconsistencies in the state of the data and make it difficult to recover from failed transactions. As a result, it is important to consider different programming paradigms for implementing distributed transactions. Let's look at different programming methods using the example of a token exchange. Splitting a token swap transaction into 3 separate transactions [#splitting-a-token-swap-transaction-into-3-separate-transactions] Consider the following situation: we have a liquidity pool of token A and token B, and also a user who wants to exchange his tokens A for tokens B. `Step 1` : A user sends a `MakeOrder` message to the swap contract. During that transaction the contract sends a message to the fungible token contract. The result of executing this message can be a success or a failure. The worst case scenario is having a lack of gas when processing a message in the token contract or in the subsequent execution of the swap contract. However, since the token contract supports idempotency, the user can simply restart the transaction and complete it.
img alt
`Step 2`: A user sends an `ExecuteOrder` message to the swap contract. The swap contract just calculates the amount of tokens a user will receive and saves the new state of the liquidity poll.
img alt
`Step 3`: A user sends a `Withdraw` message to the swap contract and receives tokens B. The situation here is the same as in the first step.
img alt
It is possible to execute a swap in one transaction. To resolve the problem of atomicity we can use the following patterns here: * `2 PC - 2 Phase Commit protocol` (And also its extension - 3 phase commit protocol); * `Saga Pattern`. Two phase commit protocol [#two-phase-commit-protocol] **Theory**: Use a coordinator that sends messages to participants. The `two-phase commit protocol` has two parts: the `prepare` phase and the `commit` phase. **Preparation phase:** During the preparation phase, the coordinator and participants perform the following dialog: * `Coordinator`: The coordinator directs each participant database server to prepare to commit the transaction. * `Participants`: Every participant notifies the coordinator whether it can commit to its transaction branch. * `Coordinator`: The coordinator, based on the response from each participant, decides whether to commit or roll back the transaction. It decides to commit only if all participants indicate that they can commit to their transaction branches. If any participant indicates that it is not ready to commit to its transaction branch (or if it does not respond), the coordinator decides to end the global transaction. **Commit phase:** During the commit phase, the coordinator and participants perform the following dialog: * `Coordinator`: The coordinator writes the commit record or rollback record to the coordinator's logical log and then directs each participant to either commit or roll back the transaction. * `Participants`: If the coordinator issued a commit message, the participants commit the transaction by writing the commit record to the logical log and then sending a message to the coordinator acknowledging that the transaction was committed. If the coordinator issued a rollback message, the participants roll back the transaction but do not send an acknowledgment to the coordinator. * `Coordinator`: If the coordinator issues a message to commit the transaction, it waits to receive acknowledgment from each participant before it ends the global transaction. If the coordinator issued a message to roll back the transaction, it does not wait for acknowledgments from the participants. In the context of a token swap contract example, one may utilize it as follows: The scenario involves an account seeking to swap their tokens (referred to as tokenA) for alternative tokens (tokenB) by leveraging the liquidity pool within the swap contract. In that case the swap contract is a coordinator contract and tokens contracts are participants. The swap contract makes the following steps: **Prepare phase** * `Swap contract:` Swap contract sends the messages to token contracts to prepare transfer tokens (Messages can be sent in parallel). In fact, token contracts must lock funds at this stage. * `Token contract`: Token contracts make all necessary checks, and in case of success, lock funds and reply to the swap contract that they are ready to make a transaction. * `Swap contract`: Swap contract handles the messages from the token contracts and decides whether to commit or abort the global transaction. receives tokens B. The situation here is the same as in the first step.
img alt
**Commit phase** * `Swap contract`: If token contracts confirm their readiness to execute the transaction, the swap contract sends them a message to commit the state. Otherwise, the swap contract tells them to abort the transaction. * `Token contract`: Token contracts finally change their state and send replies to the swap contract; * \`Swap contracts: Swap contract handles the messages from the token contracts and saves the result about transaction execution.
img alt
Of course, all that workflow handles the case when the gas runs out during the message execution. `Pros:` * Messages can be sent in parallel; * If cases with a lack of gas are taken into account, then the data consistency is achieved. `Cons:` * The participants have to wait for the message from the coordinator, they can’t commit or abort themselves; * The coordinator plays an important role: if it fails to send the message then all participants go to the blocked state (in our example: the funds in token contracts are blocked). Three phase commit protocol. [#three-phase-commit-protocol] **Theory**: It is similar to two-phase commit protocol but it tries to solve the problems with blocking the state of participants and to give the participants the opportunity to recover their states themselves. **Prepare phase:** The same steps of two phase commit protocol are followed here: * `Coordinator`: The coordinator sends a prepare message to all participants and waits for replies; * `Participants`: If the participants are ready to commit a transaction they send the ready message, otherwise they send no message to the coordinator. * `Coordinator`: Based on replies the coordinator decides either to go to the next state or not. If any of the participants respond with no message or if any of the participants fails to respond within a defined time, the coordinator sends an abort message to every participant. It is important to highlight the differences from two phase commit protocol: * The coordinator limits the response time from the participant. This can be implemented by sending a message with an indicated amount of gas or indicated number of blocks the coordinator is ready to wait; * If the coordinator fails at this state, then the participants are able to abort the transaction (i.e. unlock their state) using delayed messages. So, in that phase, the timeout cases abort. **Prepare-to-commit phase:** * `Coordinator`: The coordinator sends a prepare-to-commit message to all participants and gets acknowledgements from everyone; * `Participants`: Participants: Receiving a prepare-to-commit message, a participant knows that the unanimous decision was to commit. As mentioned in the preparation phase, if a participant fails to receive this message in time, then it aborts. However, if a participant receives an abort message, then it can immediately abort the transaction. The possible problem: the coordinator fails when sending a prepare-to-commit to participants. So some participants are in Phase 2, others are in Phase 1. It's a disaster because the first group will commit, the second group will abort in case of timeout. This phase must ensure that If one of the participants has received a precommit message, they can all commit. If the coordinator falls, any of the participants, being at the second stage, can become the coordinator itself and continue the transaction. * `Coordinator`: Having received acknowledgements from all the participants, the coordinator goes to the next phase. The three-phase commit protocol accomplishes two things: 1. Enables use of a `recovery coordinator` (it can be a coordinator itself that starts a new transaction, or a participant). If a coordinator died, a recovery coordinator can query a participant. * If the participant is found to be in phase 2, that means that every participant has completed phase 1 and voted on the outcome. The completion of phase 1 is guaranteed. It is possible that some participants may have received commit requests (phase 3). The recovery coordinator can safely resume at phase 2. * If the participant was in phase 1, that means NO participant has started commits or aborts. The protocol can start at the beginning. * If the participant was in phase 3, the coordinator can continue in phase 3 – and make sure everyone gets the commit/abort request 2. Every phase can now time out – there is no indefinite wait as in the two-phase commit protocol. * `Phase 1`: Participant aborts if it doesn’t hear from a coordinator in time; Coordinator sends aborts to all if it doesn’t hear from any participant. * `Phase 2`: If a participant times out waiting for a coordinator, elect a new coordinator. Back to the swap contract. **Preparation phase**: The following cases are possible: * all token contracts receive the message; * the swap contract fails to wait for response from any token contract * the swap contract fails itself. In the case of a fall, if a transaction isn't restarted, the swap contract will not move to the second phase and the token contracts will unlock their state using delayed messages.
img alt
**Pre-Commit phase**: At this stage, there can be a failure in the swap contract or in the token contract only due to the lack of gas. To address this issue, gas reservation can be utilized as follows: * The swap contract receives the information about error in its `handle_signal`; * Using gas reservation (so, it’s necessary to care about gas reservations before), the swap contract sends a message to itself to restart the transaction from the second phase. (The same logic can also be used in the `preparation phase`).
img alt
**Commit phase**: Like the previous stage, failure can occur solely due to the absence of gas. In this situation, it is not as critical, as all participants can commit to the process. Saga pattern [#saga-pattern] **Theory**: A `saga` is a sequence of local transactions. Each local transaction updates the database and publishes a message or event to trigger the next local transaction in the saga. If a local transaction fails because it violates a business rule then the saga executes a series of compensating transactions that undo the changes that were made by the preceding local transactions. Thus, Saga consists of multiple steps whereas `2PC` acts like a single request. There are two ways of coordination sagas: * `Choreography` - each local transaction publishes domain events that trigger local transactions in other services; * `Orchestration` - an orchestrator (object) tells the participants what local transactions to execute. Consider the `orchestration based Saga`, where an orchestrator (swap contract) manages the entire operation from a central point. The swap operation consists of the following steps: 1. Swap contract receives a message to exchange tokens in the liquidity pool. So, it must transfer tokens A from the account to its address and then transfer tokens B to the user. 2. It creates the first task: transfer tokens from the user to the swap contract. It also creates a compensating transaction for the first task: transfer tokens from the swap contract back to the user. The second task is to transfer tokens from the swap contract to the user. 3. It starts executing the first task. If the execution fails, it cancels the transaction. If it’s successful, the swap contract executes the second task; 4. If the execution of the second task is successful, the transaction is completed. Otherwise, the swap contract executes the compensation transaction for the first task.
img alt
It is important to note that compensatory transactions should not fail due to any logical error. They can only fall due to lack of gas. If this happens, then it is needed to restart the transaction again or use the gas reservation. The `idempotency` of the token contract guarantees that the transaction will be completed to the end without any duplicate transactions. # Overview This Wiki, along with the [Vara Educational Hub](https://vara.network/education-hub), serves as a primary source of knowledge for getting started with writing programs using the Gear Protocol and uploading them to the Vara Network. For a quick overview of writing and running programs on Vara, refer to [this guide](/docs/vara-network/quick-start). It walks through writing a program, compiling it to Wasm, and deploying it to the Vara Network within just 5 minutes. Stay up to date with the latest releases and updates from the development team by subscribing to our Telegram channel: [@GearVaraDev](https://t.me/GearVaraDev). Sails Library (High-Level Development) [#sails-library-high-level-development] The [Sails library](/docs/developing/build/sails) is the recommended starting point for developing applications on the Vara Network. It eliminates the need to write low-level boilerplate code with `gstd`, enabling developers to concentrate on solving business problems. Additionally, Sails provides developers with automatically generated client code in Rust or TypeScript, making it easier to interface with their deployed Vara applications. Gear Library (Low-Level Development) [#gear-library-low-level-development] Messages are the main interface for communications between Actors (users and programs). Each Vara program contains code for processing incoming messages. During message processing, programs can send messages to other programs and users, including replies to the initial message. The Gear Protocol's library `gstd` provides necessary and sufficient custom functions and methods, including arbitrary async/await syntax for developing programs. The next articles in this section cover various aspects of the Gear library, executable and state functions, data decoding/encoding, asynchronous programming, program testing, and more. Vara Node [#vara-node] Vara Node provides a runtime that assists program developers in implementation and testing (personal node) while also enabling participation in the Vara Network as validators. Anyone can either compile a Vara node or download the prebuilt binary (nightly build). From a functional standpoint, it doesn't matter whether it was downloaded or built manually. The Node can be built, installed, and run on Linux, Mac, and Windows operating systems. For detailed instructions on installing and running the Vara Node, as well as keeping it operational, backing up, restoring, and troubleshooting, refer to the guide [here](/docs/vara-network/node). Gear-JS API [#gear-js-api] The Gear-JS API provides a range of utilities, libraries, and tools that enable JavaScript applications to interact with programs running on the Vara Network via queries to a Vara Node. The basic API is implemented on the Substrate layer and remains consistent across all Substrate-based networks. It enables connection to a local or remote node, getting node info, subscribing to events, etc. The main capabilities of the Gear-JS API include working with the program's metadata, calculating gas required for network operations, uploading programs to the network, sending a message or a reply to a program, reading a program's state, getting messages from the user's mailbox, and more. To learn more about implementing a web application to interact with programs on the Vara Network, check out the [Gear-JS API](/docs/developing/api). Also, take a look at the [NFT frontend application](/docs/vara-network/examples/NFTs/nft-marketplace/nft-application) example, which describes how to implement a frontend application that interacts with the program on the blockchain. Gear Idea [#gear-idea] **[Gear Idea](https://idea.gear-tech.io/)** is a convenient tool that allows program developers to easily and swiftly upload programs to the Vara Network directly through their browser without additional configuration. For development and testing purposes, Gear Idea can be connected to any supported Gear-based network, including a private network or a localhost Node. This application encompasses all interactions with programs on Vara Network, such as uploading new programs, sending messages to contracts, and receiving responses. Gear Idea also facilitates account and balance management, observation of transaction events, working with various wallets, and more. Program Examples [#program-examples] For developers' convenience, the Gear Foundation provides numerous [examples](/docs/vara-network/examples) for various use cases. These examples serve as useful references for getting acquainted with writing programs on the Gear Protocol or as a foundation for developing custom dApps. They can be utilized as they are or modified to suit specific scenarios. The source code is available on [GitHub](https://github.com/gear-foundation). # Introduction What is a Vara program? [#what-is-a-vara-program] A Vara program (sometimes referred to as a smart contract) is a piece of code compiled to Wasm, deployed to the blockchain, and can be executed by anyone who sends a message to it. The program can store data, receive and send messages, and perform any other actions allowed by the Gear Protocol. Every program should have a set of exported functions that can be called by the Gear Protocol. These functions are called entry points. The Gear Protocol has a set of predefined entry points used to initialize the program, handle incoming messages, and process replies to previously sent messages. However, it's impossible to implement any business logic without using imported functions that form the API for the program. The Gear Protocol has a set of predefined API functions that can be used by any program. These include low-level functions allowing developers to load incoming message payloads, send messages, and perform other actions. Additionally, there are higher-level libraries that enable convenient interaction with the Gear Protocol. Predefined entry points (exported functions) [#predefined-entry-points-exported-functions] handle() [#handle] The central exported function of the Vara program is `handle()`. It is called every time the program receives an incoming message. Below is the Rust example code: ```rust #[no_mangle] extern "C" fn handle() { // Execute this code during explicitly incoming message } ``` This function is obligatory to be defined in the program. It establishes the main business logic of the program. For example: * Checking the incoming message and performing actions based on the message type. * Sending a message to another program. * Sending a reply to the message that was received by the program. init() [#init] As with any other program intended to be executed in some environment, a Vara program has its lifecycle. It is initialized, receives messages, and can be terminated. By defining the optional `init()` function, one can perform actions during program initialization, such as storing data in the program's memory. The `init()` function is invoked only once at program initialization. ```rust #[no_mangle] extern "C" fn init() { // Execute this code during program initialization } ``` If there is no `init()` function in the program, the program will be initialized without any custom actions. handle_reply() [#handle_reply] The third most important function is `handle_reply()`. It is called when the program receives a reply to the message that was sent by the program. For example, you can check the reply and perform some actions depending on the reply type. ```rust #[no_mangle] extern "C" fn handle_reply() { // Execute this code during handling reply on the previously sent message } ``` There is no need to define the `handle_reply()` function if the program doesn't intend to receive replies. In this case, the program will ignore all incoming replies. The reply message is similar to the ordinary message, but it has some differences: * The reply message is sent to the user or program that has sent the original message. The destination of the reply message cannot be set explicitly. * Only one reply message per execution can be sent. We get an error if we try to send more than one reply message. * Even if the program execution is failed (for example, panic is called or the gas limit is exceeded), the reply message will be sent anyway. * The reply message is processed in `handle_reply()` function instead of `handle()` function for the ordinary message. We have mentioned this above but it is worth underlining it again. API functions (imported functions) [#api-functions-imported-functions] There are a lot of imported functions that can be used by the Vara program. They are called API functions. These functions are provided by the runtime that executes the Gear program. The most convenient way to use these functions is to use the Gear standard library called [`gstd`](https://docs.rs/gstd/latest/gstd/). It is a set of high-level functions that are implemented on top of the low-level API functions. More details about the Gear Protocol's standard library can be found in the [Gear Library](/docs/developing/build/gstd) section. Basic stages of the Vara program lifecycle [#basic-stages-of-the-vara-program-lifecycle] The typical lifecycle of a Vara program involves utilizing the Rust programming language for example. However, it's essential to note that the same principles apply to any other programming language that can be compiled into WebAssembly (Wasm). 1. Write the program code. [#1-write-the-program-code] You can find the minimal example in the [Getting Started](/docs/vara-network/quick-start) section. It is a simple program that stores the counter, can increment and decrement it and returns the current value of the counter. More advanced examples can be found in the Gear Foundation organization on GitHub: [https://github.com/gear-foundation](https://github.com/gear-foundation) 2. Test the program. [#2-test-the-program] The Vara developer community recommends using the [`gtest`](https://docs.gear.rs/gtest/) crate for testing Vara programs as it enables program unit tests to be written and run in the local environment. The more advanced way to test the program is to use the [`gclient`](https://docs.gear.rs/gclient/) crate that allows you to run the program in the blockchain network. It is useful when you need to test the program in a real environment. You can find more details about testing in the [Program Testing](/docs/developing/build/testing) section. 3. Compile the program into Wasm. [#3-compile-the-program-into-wasm] The Vara developer community recommends using the [`gear-wasm-builder`](https://docs.gear.rs/gear_wasm_builder/) crate in a custom build script `build.rs`. Add it to the `[build-dependencies]` section in the `Cargo.toml` file: ```toml [build-dependencies] gear-wasm-builder = { git = "https://github.com/gear-tech/gear.git", tag = "v1.1.1", features = ["wasm-opt"] } ``` And add the following code to the `build.rs` file: ```rust fn main() { gear_wasm_builder::build(); } ``` The compiled Wasm files can be found in the `target/wasm32-unknown-unknown/release` directory. 4. Deploy the program to the blockchain. [#4-deploy-the-program-to-the-blockchain] Program deployment is a process of storing the program's Wasm code on the blockchain and its initialization. The user pays a fee for the deployment transaction. The program is deployed to the blockchain only once. After that, it can be executed by anyone by sending a message to it. If initialization fails (for example, the program panics in the `init()` function), the program is not deployed and the user gets an error. {/* Also, it is important to underline that someone should pay rent for keeping the program in the blockchain after a free period that is equal to 5 million blocks (it is about 2 months for networks with 1 block per second production). It is possible to add funds for rent using the [`pay_program_rent`](https://docs.gear.rs/pallet_gear/pallet/struct.Pallet.html#method.pay_program_rent) extrinsic (by the user) or with the [`gstd::exec::pay_program_rent`](https://docs.gear.rs/gstd/exec/fn.pay_program_rent.html) API function (by the program). If the rent is not paid, the program state changes to pause, its persistent memory is removed from the storage and the program can't be executed. The program can be resumed by uploading its memory pages to the blockchain and paying the rent. */} More details about the program deployment can be found in the [Upload Program](/docs/developing/build/deploy) section. 5. Execute the program. [#5-execute-the-program] The program can be executed by sending a message to it. The message can be sent by the user or by another program. The user pays a fee for the message execution. The program can send a reply to the message. The reply is sent to the user or program that has sent the original message. 6. Terminate the program. [#6-terminate-the-program] The program can be terminated by calling the [`gstd::exec::exit`](https://docs.rs/gstd/latest/gstd/exec/fn.exit.html) function. Also, the program is paused if the rent is not paid. The program can't be executed after termination. Program key features [#program-key-features] Vara programs have a lot of features that make them unique. Let's explore the most important of them. State function [#state-function] Vara programs can store the state in persistent memory. Anyone can read this memory from the blockchain. To make state reading more convenient, Vara programs can define the `state()` function. ```rust #[no_mangle] extern "C" fn state() { msg::reply(any_encodable_data, 0).expect("Failed to share state"); } ``` This function is stored in the blockchain in the same Wasm blob with `handle()` and `init()` functions. But unlike them, it is not executed using extrinsic and doesn't affect the blockchain state. It can be executed for free by any node with a fully synchronized blockchain state. There is a dedicated [`read_state`](https://docs.gear.rs/pallet_gear_rpc/trait.GearApiServer.html#tymethod.read_state) RPC call for this. The data returned by the `state()` function can be converted to any convenient representation by using a state-conversion program. This is a separate program compiled into Wasm and dedicated to being executed on the off-chain runner. It should contain a set of meta-functions that accept the data returned by the `state()` function and return the data in a convenient format. There is a dedicated [`read_state_using_wasm`](https://docs.gear.rs/pallet_gear_rpc/trait.GearApiServer.html#tymethod.read_state_using_wasm) RPC call for reading the program state using the state-conversion program. More details about state functions can be found in the [State Functions](/docs/developing/build/gstd/state) section. Asynchronous programming [#asynchronous-programming] In some cases, it is more convenient to express some concepts in an asynchronous programming style. For example, when you need to wait for a reply from another program or wait for a certain time. Under the hood, the `async`/`await` syntax is a kind of syntactic sugar that generates a state machine around [`gstd::exec::wait`](https://docs.rs/gstd/latest/gstd/exec/fn.wait.html) and [`gstd::exec::wake`](https://docs.rs/gstd/latest/gstd/exec/fn.wake.html) functions. The state machine is stored in the program's persistent memory. Note that in case of using async functions, you are to declare the `async main()` function with `#[async_main]` attribute instead of the `handle()` function: ```rust #[gstd::async_main] async fn main() { // Async code here } ``` The initialization function can also be declared as an async function: ```rust #[gstd::async_init] async fn init() { // Async init code here } ``` More details about asynchronous programming can be found in the [Asynchronous Programming](/docs/developing/build/gstd/async-programing) section. Creating programs from programs [#creating-programs-from-programs] Both users and programs are actors in terms of the Gear programming model. Therefore, any actor can create a new program and deploy it to the blockchain. The only prerequisite is that the code of the program should be stored in the blockchain. This can be done by using the [`upload_code`](https://docs.gear.rs/pallet_gear/pallet/struct.Pallet.html#method.upload_code) extrinsic that returns an identifier of the uploaded code. The code can be uploaded only once, then it can be used for creating multiple programs. There are several helper functions for creating programs from programs in the [`gstd::prog`](https://docs.rs/gstd/latest/gstd/prog/index.html) module. More details about creating programs from programs can be found in the [Create Program](/docs/developing/build/gstd/create) section. Gas reservation [#gas-reservation] Vara programs use gas for measuring the complexity of the program execution. The user pays a fee for the gas used by the program. Some part of the gas limit may be reserved during the current execution to be spent later. This gas reserving mechanism can be used to shift the burden of paying for program execution from one user to another. Also, it makes it possible to run some deferred actions using delayed messages described below. You can find more details about gas reservation in the [Gas Reservation](/docs/developing/build/gstd/gas-reservation) section. Delayed messages [#delayed-messages] Vara's programs can send messages to other actors during the current execution and after some time. This mechanism can be used to implement deferred actions. Use functions with `*_delayed` suffix from [`gstd::msg`](https://docs.rs/gstd/latest/gstd/msg/index.html) module to send a delayed message to a program or user. The message will be sent after the specified number of blocks. More details about delayed messages can be found in the [Delayed Messages](/docs/developing/build/gstd/delayed-messages) section. System signals [#system-signals] Sometimes the system that executes the program should communicate with it in some manner. For example, the program should be notified when the rent is not paid. This can be done by using system signals. The `handle_signal()` function should be declared in the program to handle system signals. It is executed when the program receives a system signal. ```rust #[no_mangle] extern "C" fn handle_signal() { // Handle system signal here } ``` You can find more details about system signals in the [System Signals](/docs/developing/build/gstd/system-signals) section. Reply deposit [#reply-deposit] Usually, the reply sender pays a gas fee for the reply message execution. However, sometimes it is more convenient to shift this burden to the program that receives the reply. This can be done by using the reply deposit mechanism. The reply deposit is a part of the gas limit reserved during the current execution to be spent later. The reserved gas can be used to pay for the reply message execution. To do this, the program should call the [`gstd::exec::reply_deposit`](https://docs.rs/gstd/latest/gstd/exec/fn.reply_deposit.html) function. This function provides a gas deposit from the current message to handle the reply message on the given message ID. This message ID should be sent within the execution. Once the destination actor or system sends a reply to it, the gas limit is ignored; if the program provides a deposit, only it will be used for the execution of `handle_reply`. More details about reply deposit can be found in the [Reply Deposit](/docs/developing/build/gstd/reply-deposit) section. # Program Rent Vara Network utilizes a rent-based program management system. When developers upload a program to the network, an expiration date is assigned. The expiration period is measured in blocks. After the expiration date, the program is automatically removed from storage unless the owner chooses to extend its life by paying rent. The owner must indicate the number of additional blocks they want to pay for and provide the rent in utility tokens to keep the program active beyond its initial expiration date. The current initial rent period is **5,000,000 blocks**, which is approximately **173 days** on the Vara Network. *Note: This value is a network parameter and may be adjusted via governance.* After uploading a program, a similar event can be observed: `gear.ProgramChanged` ```json { "id": "0xde76e4cf663ff825d94944d6f060204e83fbb5e24f8dfdbbdc25842df4f4135d", "change": { "Active": { "expiration": "12,834,248" } } } ``` How to Extend the Rent of the Program [#how-to-extend-the-rent-of-the-program] To extend the rent period of a program, call the special extrinsic `gear.payProgramRent(programId, blockCount)`. [See more](/docs/developing/api/program-rent) Restoring a Deleted Program [#restoring-a-deleted-program] Since the blockchain stores all states for the entire history, it is possible to restore the program's state to the previous block before it was deleted. Reasons for Using the Program Rent System on Vara [#reasons-for-using-the-program-rent-system-on-vara] * Optimization and efficient resource usage * Stimulating utility token usage # Testing with gclient `gclient` is intended to be used as a tool for testing Vara programs with a real blockchain network. It allows sending extrinsics and RPCs by connecting to the network. `gclient` is recommended for end-to-end testing to ensure the program works as expected in the real blockchain world. It is essential to note that testing with `gclient` requires a running node as the second part of the test suite. `gclient` interacts with the node over the WebSocket protocol. Depending on the testing purpose, `gclient` can communicate with either a local or a remote node. Using a **local node in developer mode** is the best choice for initial debugging and continuous integration. Testing with `gclient` is slower than `gtest` and produces more build artifacts, making it better suited as the final step in quality control. However, `gclient` provides the most accurate test results. Import gclient Library [#import-gclient-library] To use the `gclient` library, import it into the `Cargo.toml` file in the `[dev-dependencies]` block. Also, add some external crates that are used together with `gclient`: ```toml [package] name = "first-gear-app" version = "0.1.0" authors = ["Your Name"] edition = "2021" [dependencies] gstd = { git = "https://github.com/gear-tech/gear.git", tag = "v1.1.1", features = ["debug"] } [build-dependencies] gear-wasm-builder = { git = "https://github.com/gear-tech/gear.git", tag = "v1.1.1" } [dev-dependencies] gclient = { git = "https://github.com/gear-tech/gear.git", tag = "v1.1.1" } tokio = { version = "1", features = ["full"] } ``` Running the Node [#running-the-node] The best way is to download the latest node binary for your operating system from [https://get.gear.rs](https://get.gear.rs). Then unpack the package and run the node. Here and below, the node is assumed to be running in developer mode. Terminal: ```bash curl https://get.gear.rs/gear-v1.1.1-x86_64-unknown-linux-gnu.tar.xz | tar xJ ``` or **Linux x86-64**: [gear-v1.1.1-x86\_64-unknown-linux-gnu.tar.xz](https://get.gear.rs/gear-v1.1.1-x86_64-unknown-linux-gnu.tar.xz) You can try to run the node: ``` ❯ ./gear --version gear 1.1.1-33ee05d5aab ``` Terminal: ```bash curl https://get.gear.rs/gear-v1.1.1-aarch64-apple-darwin.tar.xz | tar xJ ``` or **macOS ARM**: [gear-v1.1.1-aarch64-apple-darwin.tar.xz](https://get.gear.rs/gear-v1.1.1-aarch64-apple-darwin.tar.xz) You can try to run the node: ``` ❯ ./gear --version gear 1.1.1-33ee05d5aab ``` Terminal: ```bash curl https://get.gear.rs/gear-v1.1.1-x86_64-apple-darwin.tar.xz | tar xJ ``` or **macOS x86-64**: [gear-v1.1.1-x86\_64-apple-darwin.tar.xz](https://get.gear.rs/gear-v1.1.1-x86_64-apple-darwin.tar.xz) You can try to run the node: ``` ❯ ./gear --version gear 1.1.1-33ee05d5aab ``` Terminal: ```bash curl -O https://get.gear.rs/gear-v1.1.1-x86_64-pc-windows-msvc.zip ``` or **Windows x86-64**: [gear-v1.1.1-x86\_64-pc-windows-msvc.zip](https://get.gear.rs/gear-v1.1.1-x86_64-pc-windows-msvc.zip) Unzip the downloaded package then you can try to run the node: ``` ❯ gear.exe --version gear 1.1.1-33ee05d5aab ``` Open the second terminal window and run tests using `cargo` as described in the [previous section](/docs/developing/build/testing#building-a-program-in-test-mode). Simple Example [#simple-example] Add an end-to-end test to the `first-gear-app` introduced in the [Getting Started](/docs/vara-network/quick-start#creating-your-first-vara-program) section. Add the `tests` directory next to the `src` directory and create the `end2end.rs` file in it. ``` └── first-gear-app ├── Cargo.toml ├── src │ └── lib.rs └── tests └── end2end.rs ``` `end2end.rs`: ```rust use gclient::{EventProcessor, GearApi, Result}; const WASM_PATH: &str = "./target/wasm32-unknown-unknown/release/first_gear_app.opt.wasm"; #[tokio::test] #[ignore] async fn test_example() -> Result<()> { // Create API instance let api = GearApi::dev().await?; // Subscribe to events let mut listener = api.subscribe().await?; // Check that blocks are still running assert!(listener.blocks_running().await?); // Calculate gas amount needed for initialization let gas_info = api .calculate_upload_gas( None, gclient::code_from_os(WASM_PATH)?, vec![], 0, true, ) .await?; // Upload and init the program let (message_id, program_id, _hash) = api .upload_program_bytes_by_path( WASM_PATH, gclient::now_micros().to_le_bytes(), vec![], gas_info.min_limit, 0, ) .await?; assert!(listener.message_processed(message_id).await?.succeed()); let payload = b"PING".to_vec(); // Calculate gas amount needed for handling the message let gas_info = api .calculate_handle_gas(None, program_id, payload.clone(), 0, true) .await?; // Send the PING message let (message_id, _hash) = api .send_message_bytes(program_id, payload, gas_info.min_limit, 0) .await?; assert!(listener.message_processed(message_id).await?.succeed()); Ok(()) } ``` Run the following command and wait for all tests to be green: ```shell cargo test --release -- --include-ignored ``` It is recommended to mark tests with `gclient` with the `#[ignore]` attribute to separate their slow execution from the rest. To execute ignored tests with Cargo, add the `--include-ignored` flag after a double dash (`--`) as shown above. More Details About gclient [#more-details-about-gclient] Please refer to the [`gclient` docs](https://docs.gear.rs/gclient/) for more information about its capabilities and use cases. # Testing with gtest `gtest` simulates a real network by providing mockups of the user, program, balances, mailbox, etc. Since it does not include parts of the actual blockchain, it is fast and lightweight. However, as a model of the blockchain network, `gtest` cannot be a complete reflection of the actual network. `gtest` is excellent for unit and integration testing and is also helpful for debugging Vara program logic. Running tests based on `gtest` only requires the Rust compiler, making its use in continuous integration predictable and robust. Import gtest Library [#import-gtest-library] To use the `gtest` library, import it into the `Cargo.toml` file in the `[dev-dependencies]` block to fetch and compile it for tests only: ```toml [package] name = "first-gear-app" version = "0.1.0" authors = ["Your Name"] edition = "2021" [dependencies] gstd = { git = "https://github.com/gear-tech/gear.git", tag = "v1.1.1" } [build-dependencies] gear-wasm-builder = { git = "https://github.com/gear-tech/gear.git", tag = "v1.1.1" } [dev-dependencies] gtest = { git = "https://github.com/gear-tech/gear.git", tag = "v1.1.1" } ``` gtest Capabilities [#gtest-capabilities] * Initialization of the common environment for running programs: ```rust // This emulates node's and chain's behavior. // By default, sets: // - current block equals 0 // - current timestamp equals UNIX timestamp of your system. // - minimal message id equal 0x010000.. // - minimal program id equal 0x010000.. let sys = System::new(); ``` * Program initialization: ```rust // Initialization of program structure from file. // Takes as arguments reference to the related `System` and the path to wasm binary relative to the root of the crate where the test was written. // Sets free program id from the related `System` to this program. For this case it equals 0x010000.. // Next program initialized without id specification will have id 0x020000.. and so on. let _ = Program::from_file( &sys, "./target/wasm32-unknown-unknown/release/demo_ping.wasm", ); // Also, use the `Program::current()` function to load the current program. let _ = Program::current(&sys); // Check the id of the program by calling `id()` function. // It returns `ProgramId` type value. let ping_pong_id = ping_pong.id(); // Manually specify the id of the program using `from_file_with_id` constructor. let _ = ProgramBuilder::from_file("./target/wasm32-unknown-unknown/release/demo_ping.wasm") .with_id(105) .build(&sys); ``` * Getting the program from the system: ```rust // Retrieve the object from the system by id. let _ = sys.get_program(105); ``` * Initialization of styled `env_logger`: ```rust // Initialization of styled `env_logger` to print logs (only from `gwasm` by default) into stdout. // To specify printed logs, set the env variable `RUST_LOG`: // `RUST_LOG="target_1=logging_level,target_2=logging_level" cargo test` // Vara programs use `gwasm` target with `debug` logging level sys.init_logger(); ``` * Sending messages: ```rust // To send a message to the program, call `send()` or `send_bytes()` (or `send_with_value` and `send_bytes_with_value` if you need to send a message with attached funds). // Both methods require the sender id as the first argument and the payload as the second. // The first method requires payload to be CODEC Encodable, while the second requires payload to implement `AsRef<[u8]>`. // First message to the initialized program structure is always the init message. let res = program.send_bytes(100001, "INIT MESSAGE"); ``` * Processing the result of the program execution: ```rust // Sending functions return `RunResult` structure containing the final result of the processing message and others created during execution. // It has 4 main functions: // Returns the reference to the Vec produced to users messages. assert!(res.log().is_empty()); // Returns bool indicating if there was panic during the execution of the main message. assert!(!res.main_failed()); // Returns bool indicating if there was panic during the execution of the created messages during the main execution. assert!(!res.others_failed()); // Returns bool indicating if logs contain a given log. assert!(!res.contains(&Log::builder())); // Build a log for assertion using `Log` structure with its builders. // All fields are optional. Assertions with Logs from core are made on the Some(..) fields. // Constructor for success log. let _ = Log::builder(); // Constructor for error reply log. Note that error reply never contains payload. let _ = Log::error_builder(); // Send a new message after the program has been initialized. let res = ping_pong.send_bytes(100001, "PING"); // Other fields are set optionally by `dest()`, `source()`, `payload()`, `payload_bytes()`. // The logic for `payload()` and `payload_bytes()` is the same as for `send()` and `send_bytes()`. let log = Log::builder() .source(ping_pong_id) .dest(100001) .payload_bytes("PONG"); assert!(res.contains(&log)); let wrong_log = Log::builder().source(100001); assert!(!res.contains(&wrong_log)); // Log also has `From` implementations from (ID, T) and from (ID, ID, T), // where ID: Into, T: AsRef<[u8]>. // Examples: let x = Log::builder().dest(5).payload_bytes("A"); let x_from: Log = (5, "A").into(); assert_eq!(x, x_from); let y = Log::builder().dest(5).source(15).payload_bytes("A"); let y_from: Log = (15, 5, "A").into(); assert_eq!(y, y_from); assert!(!res.contains(&(ping_pong_id, ping_pong_id, "PONG"))); assert!(res.contains(&(1, 100001, "PONG"))); ``` * Spending blocks: ```rust // Control time in the system by spending blocks. // It adds the amount of blocks passed as arguments to the current block of the system. // Note that 1 block in Vara network is 1 sec duration. sys.spend_blocks(150); ``` {/* - Reading the program state: ```rust // Read the program state using `meta_state()` or `meta_state_with_bytes()`. // These methods require the payload as the input argument. // The first method requires payload to be CODEC Encodable, while the second requires payload to implement `AsRef<[u8]>`. // Example: #[derive(Encode, Decode, TypeInfo)] pub struct ContractState { a: u128, b: u128, } pub enum State { A, B, } pub enum StateReply { A(u128), B(u128), } #[no_mangle] unsafe extern "C" fn meta_state() -> *mut [i32; 2] { let query: State = msg::load().expect("Unable to decode `State`"); let encoded = match query { State::A => StateReply::A(STATE.a), State::B => StateReply::B(STATE.b), }.encode(); gstd::util::to_leak_ptr(encoded) } // Send a query from gtest: let reply: StateReply = self .meta_state(&State::A) .expect("Meta_state failed"); let expected_reply = StateReply::A(10); assert_eq!(reply,expected_reply); // If `meta_state` function doesn't require input payloads, use `meta_state_empty` or `meta_state_empty_with_bytes` functions without any arguments. ``` */} * Balance: ```rust // To send a message with value, mint balance for the message sender: let user_id = 42; sys.mint_to(user_id, 5000); assert_eq!(sys.balance_of(user_id), 5000); // To give the balance to the program, use the `mint` method: let prog = Program::current(&sys); prog.mint(1000); assert_eq!(prog.balance(), 1000); ``` # Program Testing Basics [#basics] Vara uses the standard testing mechanism for Rust programs: building and running testing executables using `cargo`. Following the basic concepts and testing methods described in the [Rustbook](https://doc.rust-lang.org/book/ch11-00-testing.html), tests can be organized into two main categories: **unit tests** and **integration tests**. **Unit tests** enable testing of each unit of code in isolation from the rest of the code. This helps quickly identify where the code works as expected and where it does not. Unit tests should be placed in the `src` directory in each file containing the code they test. Even when code units work correctly, it is crucial to test if several library parts work together correctly. For **integration tests**, a separate `tests` directory is required at the top level of the project directory, next to `src`. Multiple test files can be created in this directory, and `cargo` will compile each file as an individual crate. How to Test a Program [#how-to-test-a-program] There are at least two ways to test and debug Vara programs. The first is off-chain testing using the low-level [`gtest`](https://docs.gear.rs/gtest/) crate. This approach is recommended for unit and integration tests. The second is on-chain testing with the higher-level [`gclient`](https://docs.gear.rs/gclient/) crate, which is well-suited for end-to-end testing. Although `gclient` is oriented towards end-to-end testing, tests can be written as unit or integration tests in Rust. It is recommended to use an integration-like approach with separate test files in the `tests` directory when utilizing the `gclient` crate. Building a Program in Test Mode [#building-a-program-in-test-mode] First, ensure a compiled Wasm file of the program to be tested. Refer to [Getting Started](/docs/vara-network/quick-start) for additional details. 1. Usually, the following command is used for the regular compilation of Vara programs: ```bash cd ~/gear/contracts/first-gear-app/ cargo build --release ``` The nightly compiler is required if some unstable features have been used: ```bash cargo +nightly build --release ``` 2. The minimal command for running tests is: ```bash cargo test ``` The nightly compiler is required if the program uses unstable Rust features, and the compiler will prompt to enable `nightly` if necessary: ```bash cargo +nightly test ``` Build tests in release mode to run faster: ```bash cargo test --release ``` Dig Deeper [#dig-deeper] The following sections outline testing a Vara program using both the `gtest` and `gclient` crates. # Keystore Manager Command-line tools for developers remain an expansive subject. While these may not appeal to non-technical, modern browser users, command-line tools continue to play a pivotal role universally. This prevalence is partly because developing and maintaining user interfaces is time-consuming. For the [Vara Network](https://vara.network), the command-line tool, [`gcli`](https://crates.io/crates/gcli), has provided comprehensive functionality for interacting with the network. However, to simplify and manage growth, the keystore component has been separated and developed into a standalone, lightweight keyring implementation. [`Gring`](https://crates.io/crates/gring) is this command-line keyring implementation, designed specifically for managing Vara Network keypairs. ```text $ gring Gear keyring Usage: gring [OPTIONS] Commands: add Imports a keystore file generated by polakdot-js extension new Generate a new key list List all keys in keyring [aliases: l] use Use the provided key as primary key sign Sign a message verify Verify a message help Print this message or the help of the given subcommand(s) Options: -v, --verbose... The verbosity level -h, --help Print help ``` Keyring 101 [#keyring-101] > A keyring shares similarities with a blockchain wallet, offering greater flexibility. For blockchain wallet browser extensions, the initial step involves creating an account, remembering, and securely saving the passphrase to avoid future loss. Subsequently, the wallet generates additional keypairs from the root account upon request. A critical point often overlooked is the loss of the passphrase for the root account results in the loss of all keypairs within the wallet. In contrast, keyrings operate differently; each keypair in a keyring possesses its own passphrase. Losing one keypair does not compromise the security of others. | | Keyring | Wallet | | --------------------- | ------- | ------ | | Multiple Keypairs | Yes | Yes | | Standalone Passphrase | Yes | No | **Keyring** aligns with the basic concept of browser extension blockchain wallets but is more straightforward and flexible. Keyring's primary function is to generate and store keypairs on disk, encrypting and decrypting them with cryptography. Keystore 101 [#keystore-101] > A **keyring** is a ring of **keystores**, analogous to how a blockchain comprises a series of blocks. A keystore secures the private keys of keypairs through encryption. For `gring`, adherence to the encryption standard of the **Polkadot-JS wallet** is maintained, facilitating the sharing of keystore files between the two systems. How it works [#how-it-works] This chapter delves into the technical specifics of `gring`, illustrated with examples. Encrypt keypair into keystore [#encrypt-keypair-into-keystore] ```bash $ gring new -p password my-first-wallet ``` This command creates a keystore named `my-first-keystore` using the passphrase `password`. Delving into the details, the gring program performs the following actions: 1. Generates a random \[schnorrkel]\[[https://github.com/w3f/schnorrkel](https://github.com/w3f/schnorrkel)] keypair. 2. Encodes the keypair into a specific byte format termed **keypair-info**. ```text Keypair Info: [PAIR_HEADER, SECRET_KEY, DIVIDER, PUBLIC_KEY] ``` 3. Encrypt the `passphrase` with \[scrypt]\[[https://en.wikipedia.org/wiki/Scrypt](https://en.wikipedia.org/wiki/Scrypt)] to get a password for the secret box. 4. Pack the `keypair-info` in \[NaCl Secretbox]\[[https://en.wikipedia.org/wiki/NaCl\_(software)](https://en.wikipedia.org/wiki/NaCl_\(software\))] with the passwored from `3.` After all, the encrypted keypair will be like: | slot | 1 | 2 | 3 | | ----- | ------------- | ------------ | -------------------------------------------------------- | | bytes | scrypt config | random nonce | secret box (keypaire-info, nonce, encrypted\_passphrase) | Generally, developers include additional information about the keypair in the keystore file, rather than just the raw encoded data, to enhance human readability. In **Polkadot-js** or **`gring`**, this is accomplished as follows: ```json { "encoded": "" "encoding": { "content": ["pkcs8", "sr25519"], "ty": ["scrypt", "xsalsa20-poly1305"], "version": "3" }, "address": "", "meta": { "name": "" } } ``` Decrypt Keypair from Keystore [#decrypt-keypair-from-keystore] This section describes the inverse process of section `1.` It becomes straightforward when the correct passphrase and corresponding keystore file are available, particularly the `encoded` field in your `keystore.json`. 1. Decode the `encoded` field using `base64` to retrieve the **secret box**. 2. Extract the first 32 bytes of the encoded data, which represent the scrypt configuration. 3. Use the scrypt configuration and your passphrase to derive the **secret box** `password`. 4. Decode the `nonce` from the subsequent encoded data. 5. Unlock the secret box using the correct `nonce` and `password`. This completes the process. Polkadot-JS adopts a similar approach, allowing keystore files to be shared with gring. And more ... [#and-more-] Within the Substrate ecosystem, there was no existing tool fulfilling this need, prompting the development of `gring`. `gring` serves as a command within `gcli`, sharing keystore paths. Ideally, `gring` can be utilized to manage Vara keys, and `gcli` can be used for interactions with the Vara network using stored keypairs. # Connect Account In general, sending messages to programs, including uploading them on-chain, requires a connected Vara account with enough tokens for gas fees. To start using the IDEA portal, click the `Connect` button at the top right, select a wallet, and choose an account. If you don't have an account yet, refer to the next article. Connect account You can easily switch between your accounts and supported wallets via the currently connected account button. Get free VARA [#get-free-vara] Every developer gets a one-time grant of **100 VARA** per wallet, directly on Vara Mainnet. Click the `Get Balance` button, solve the captcha, and the tokens will appear in your balance. That is enough to deploy a program, test it and cover your first transactions - real network, real conditions, nothing to pay to start. Get balance Transfer balance [#transfer-balance] The `Transfer Balance` option allows you to transfer VARA tokens from the currently connected account to any other specified account. Specify the account address in Vara or HEX format, the amount of tokens, and sign the transaction. Transfer balance # Gear IDEA Overview The Gear IDEA portal is a convenient tool designed to familiarize users with the Gear platform. It offers program and smart contract developers the easiest and fastest way to work with their programs on Vara Network directly in their browser without additional configuration. This application supports all interactions with programs on the network, including uploading programs and codes, reading program states, sending messages, and more. Additionally, it allows managing accounts, balances, events, vouchers, and more. You can start experimenting right now at **[https://idea.gear-tech.io/](https://idea.gear-tech.io/)**. Idea main screen IDEA components and microservices [#idea-components-and-microservices] [frontend](https://github.com/gear-tech/gear-js/tree/main/idea/frontend) React application that provides the user interface for working with programs running on Vara network. [indexer](https://github.com/gear-tech/gear-js/tree/master/idea/indexer) Microservice is responsible for blockchain indexing and storing information about programs and their messages as well as for storing programs metadata. [test-balance](https://github.com/gear-tech/gear-js/tree/main/idea/test-balance) Microservice distributes the one-time 100 VARA developer grant. [api-gateway](https://github.com/gear-tech/gear-js/tree/main/idea/api-gateway) Microservice provides any interaction between `indexer` / `test-balance` services and an external user # UI Overview Network Switcher [#network-switcher] Use the `Network Switcher` to select Vara Mainnet (selected by default). The portal can also connect to your locally launched developer node. Idea switch network Programs [#programs] The `Programs` view is the default view of the Idea portal. The list of programs is displayed as tiles where you can instantly perform basic operations: send a message to a program, read its state, or issue a [voucher](/docs/developing/api/vouchers) for a specified account(s) to enable interaction with the program without worrying about gas fees. Each program's tile contains its name, network address, status, and the date it was uploaded and initialized. Click on the program's name to access additional details. Idea main screen Upload Program [#upload-program] This option allows you to upload a program to the network. Select program's .wasm and .meta files, click the `Calculate gas` and `Upload program` buttons. You will be prompted to sign the transaction of the program initialization. Navigate to a program and click the `Read State` button to get accces to the program's storage data. Codes [#codes] The `Codes` view lists not yet initialized programs. These are WASM binaries you can upload on-chain without initialization, meaning you can create (initialize) a new active program from this code later as many times as needed. The list is organized as code tiles where you can instantly create a new initialized program in the network and find all programs previously created from this code. Click on the code's name to access additional details. Idea Codes Upload Code [#upload-code] This option allows you to upload your program without program's initialization in the network. It can be used to initialize one or several instances of an identical program later. Messages [#messages] All communications between actors in the Vara network can be found in the `Messages` view. It shows each message ID, date, and the direction of the message in relation to the program - `to` or `from` the particular program. Click on the message's name to access additional details about the message. Idea Messages Send Message [#send-message] This option allows you to send a message to a program. Copy destination program's address where to send a message and provide necessary details in the Payload section, click the “Calculate gas” and “Send message” buttons. You will be prompted to sign the transaction for the message sending. Navigate to necessary messages using search by program id, message id or applying filters. Explorer [#explorer] The `Explorer` view allows you to get details about recent network events, search by block hash or block number, or apply filters by event types. Events are shown for the current user session only. Idea Explorer Mailbox [#mailbox] [`Mailbox`](/docs/developing/build/gstd/mailbox) contains messages sent from programs to the currently connected account. Vouchers [#vouchers] The list of issued vouchers and their properties is available in the `Vouchers` view. It shows the voucher status, amount of tokens available in the voucher, issue date, ID, and the account address to which the voucher was issued. You can issue a new voucher via the `Create Voucher` button, search by voucher ID, or apply filters by voucher status or beneficiary. Idea Vouchers # Client Generation TypeScript client libraries are generated by the **Sails CLI** (`sails-cli`) using the `cargo sails client-js` command. It reads a Sails IDL file and produces a strongly typed `.ts` client that mirrors the program's services, functions, queries, and events — ensuring consistency between client and on-chain programs. Installation [#installation] `cargo sails client-js` ships as part of `sails-cli`. Install it via Cargo: ```bash cargo install sails-cli ``` Or install a prebuilt binary with `cargo-binstall`: ```bash cargo binstall sails-cli ``` Generating a TypeScript Client Library Using an IDL File [#generating-a-typescript-client-library-using-an-idl-file] To generate a TypeScript client library, run: ```bash cargo sails client-js path/to/sails.idl path/to/out/lib.ts ``` The output path is positional and optional. When omitted, the IDL filename is reused with the extension swapped from `.idl` to `.ts`: ```bash # Produces path/to/sails.ts next to the IDL file. cargo sails client-js path/to/sails.idl ``` The command always produces a single TypeScript file containing the `Program` class plus its type definitions — no scaffolded project, no auxiliary files. Using the Generated Library [#using-the-generated-library] Creating an Instance [#creating-an-instance] First, connect to the chain using `@gear-js/api`: ```javascript import { GearApi } from '@gear-js/api'; const api = await GearApi.create(); ``` For further details, refer to the [Gear-JS](/docs/developing/api) API section. Next, import the `Program` class from the generated file and create an instance: ```javascript import { Program } from './lib'; const program = new Program(api); // If the program is already deployed, provide its ID const programId = '0x...'; const program = new Program(api, programId); ``` The `Program` class includes all the functions defined in the IDL file. Methods of the Program Class [#methods-of-the-program-class] The `Program` class contains several types of methods: * Query methods * Message methods * Constructor methods * Event subscription methods Query Methods [#query-methods] Query methods are used to query the program's state. These methods accept the required arguments for the function call and return the result. Additionally, they accept optional parameters: `originAddress` (the caller's account address, defaulting to a zero address if not provided), `value` (an optional amount of tokens for function execution), and `atBlock` (to query the program state at a specific block). ```javascript const alice = '0xd43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d'; const result = await program.serviceName.queryFnName(arg1, arg2, alice); console.log(result); ``` Message Methods [#message-methods] Message methods are used to send messages to the program. These methods accept the required arguments for sending the message and return a [Transaction Builder](/docs/developing/sails-js/transaction-builder), which contains methods for building and sending the transaction. ```javascript const transaction = program.serviceName.functionName(arg1, arg2); // ## Set the account sending the message // Using a KeyringPair instance import { Keyring } from '@polkadot/api'; const keyring = new Keyring({ type: 'sr25519' }); const pair = keyring.addFromUri('//Alice'); transaction.withAccount(pair); // Or using an address and signer options (common in frontend applications with connected wallets) import { web3FromSource, web3Accounts } from '@polkadot/extension-dapp'; const allAccounts = await web3Accounts(); const account = allAccounts[0]; const injector = await web3FromSource(account.meta.source); transaction.withAccount(account.address, { signer: injector.signer }); // ## Set the value of the message transaction.withValue(BigInt(10 * 1e12)); // 10 VARA // ## Calculate gas // Optionally, you can provide two arguments: // The first argument, `allowOtherPanics`, either allows or forbids panics in other programs (default: false). // The second argument, `increaseGas`, is the percentage to increase the gas limit (default: 0). await transaction.calculateGas(); // Alternatively, use `withGas` to set the gas limit manually: transaction.withGas(100000n); // ## Send the transaction // `signAndSend` returns the message ID, block hash, and a `response` function for retrieving the program's response. const { msgId, blockHash, response } = await transaction.signAndSend(); const result = await response(); console.log(result); ``` Constructor Methods [#constructor-methods] Constructor methods, postfixed with `CtorFromCode` and `CtorFromCodeId` in the `Program` class, are used to deploy the program on the chain. These methods accept either the Wasm code bytes or the code ID and return a [Transaction Builder](/docs/developing/sails-js/transaction-builder) just like message methods. ```javascript const code = fs.readFileSync('path/to/program.wasm'); // Or use the fetch function to retrieve the code in frontend environments const transaction = program.newCtorFromCode(code); // The same methods as message methods can be used to build and send the transaction. ``` Event Subscription Methods [#event-subscription-methods] Event subscription methods allow subscribing to specific events emitted by the program. ```javascript program.subscribeToSomeEvent((data) => { console.log(data); }); ``` {/* ## React Hooks Generating the library with the `--with-hooks` flag creates custom React hooks that simplify interaction with a `sails-js` program. These hooks are wrappers around the generic hooks provided by the `@gear-js/react-hooks` library. They are generated based on the `Program` class in `lib.ts`, tailored to the specific types and names derived from it. Refer to the `@gear-js/react-hooks` [README](https://github.com/gear-tech/gear-js/tree/main/utils/gear-hooks#sails) on GitHub for more details on these hooks. ### `useProgram` Initializes the program with the provided parameters. ```jsx import { useProgram } from './hooks'; const { data: program } = useProgram({ id: '0x...' }); ``` ### `useSend` for `serviceName` and `functionName` Transaction Sends a transaction to a specified service and function. ```jsx import { useProgram, useSendAdminMintTransaction } from './hooks'; const { data: program } = useProgram({ id: '0x...' }); const { sendTransaction } = useSendAdminMintTransaction({ program }); ``` ### `usePrepare` for `serviceName` and `functionName` Transaction Prepares a transaction for a specified service and function. ```jsx import { useProgram, usePrepareAdminMintTransaction } from './hooks'; const { data: program } = useProgram({ id: '0x...' }); const { prepareTransaction } = usePrepareAdminMintTransaction({ program }); ``` ### `use` for `serviceName` and `functionName` Query Queries a specified service and function. ```jsx import { useProgram, useErc20BalanceOfQuery } from './hooks'; const { data: program } = useProgram({ id: '0x...' }); const { data } = useErc20BalanceOfQuery({ program, args: ['0x...'] }); ``` ### `use` for `serviceName` and `functionName` Event Subscribes to events from a specified service and event. ```jsx import { useProgram, useAdminMintedEvent } from './hooks'; const { data: program } = useProgram({ id: '0x...' }); const { data } = useAdminMintedEvent({ program, onData: (value) => console.log(value) }); ``` */} Example: The Demo Project [#example-the-demo-project] The following example demonstrates how the generated `lib.ts` can be used in conjunction with `@gear-js/api` to upload a WASM binary of an application to the chain, create and submit a service command to the deployed application, and receive its response. This example is part of the Sails GitHub repository and can be viewed [here](https://github.com/gear-tech/sails/tree/master/js/example). Although the project includes multiple services, this example focuses on interacting with the `pingPong` service. The relevant portion of the IDL looks like this: ```rust service PingPong { Ping : (input: str) -> result (str, str); }; ``` The script first initializes the `GearApi` and creates a keyring to retrieve Alice’s account using the `@polkadot/api` library, which is used for signing transactions. It then reads the compiled WASM application and deploys it to the network using `newCtorFromCode` from the generated `lib.ts`, which creates a `TransactionBuilder` for constructing the deployment transaction. After deployment, the script interacts with the program by invoking the `ping` function from the `pingPong` service, constructing and sending a new transaction. Finally, the program's reply is awaited and logged to the console. ```jsx import { GearApi } from '@gear-js/api'; import { Keyring } from '@polkadot/api'; import { Program } from './lib.js'; import { readFileSync } from 'fs'; const main = async () => { const api = await GearApi.create(); const keyring = new Keyring({ type: 'sr25519', ss58Format: 137 }); const alice = keyring.addFromUri('//Alice'); const program = new Program(api); // Deploy the program const code = readFileSync('../../target/wasm32-unknown-unknown/release/demo.opt.wasm'); const ctorBuilder = await program.newCtorFromCode(code, null, null).withAccount(alice).calculateGas(); const { blockHash, msgId, txHash } = await ctorBuilder.signAndSend(); console.log( `\nProgram deployed. \n\tprogram id ${program.programId}, \n\tblock hash: ${blockHash}, \n\ttx hash: ${txHash}, \n\tinit message id: ${msgId}`, ); // Call the program const pingBuilder = await program.pingPong.ping('ping').withAccount(alice).calculateGas(); const { blockHash: blockHashPing, msgId: msgIdPing, txHash: txHashPing, response } = await pingBuilder.signAndSend(); console.log( `\nPing message sent. \n\tBlock hash: ${blockHashPing}, \n\ttx hash: ${txHashPing}, \n\tmessage id: ${msgIdPing}`, ); const reply = await response(); console.log(`\nProgram replied: \n\t${JSON.stringify(reply)}`); }; main() .then(() => process.exit(0)) .catch((error) => { console.log(error); process.exit(1); }); ``` # Sails-JS The Sails-JS library is a powerful tool designed to help developers interact with their [Sails](/docs/developing/build/sails/) applications on Vara Network. It facilitates seamless communication between your TypeScript projects and on-chain programs by providing two primary methods for generating client libraries from a Sails IDL (Interface Definition Language), while relying on the [Gear-JS API](/docs/developing/api) library for low-level communication with Vara nodes and network interactions: 1. **Parsing IDL with the Sails Class** Use the `.parseIdl` method of the `Sails` class to parse a string containing the IDL description of your Sails application during runtime. This method instantiates a corresponding `Sails` object, which you can use to interact directly with your deployed application. This approach offers maximal control over the interaction with your application. *Why Choose This Method?* * **Fine-Grained Control**: Developers may want to manage low-level operations or handle side effects of the library's abstractions themselves. Using the raw `Sails` object gives you the flexibility to customize interactions as needed. Parsing the IDL at runtime can be also be advantageous if you need to work with multiple IDL files. 2. **Generating Client Libraries with `cargo sails client-js`** The Sails CLI (`sails-cli`) provides the `cargo sails client-js` command, which generates a TypeScript client library from a given IDL file. The tool produces a single `.ts` file you can import into your TypeScript project. This generated library provides strongly typed classes and methods corresponding to your application's services, functions, queries, and events, making it easier and faster to interact with your deployed application. *Why Choose This Method?* * **Ease of Use**: The auto-generated client library simplifies the development process, allowing you to get started quickly without worrying about low-level details. * **Improved Developer Experience**: By providing abstractions over complex operations, it enables you to focus on building features rather than managing underlying mechanisms. Installation [#installation] The `sails-js` library requires the `@gear-js/api` and `@polkadot/api` packages to be installed. To install `sails-js` using npm, run the following command: npm pnpm yarn bun ```bash npm install sails-js ``` ```bash pnpm add sails-js ``` ```bash yarn add sails-js ``` ```bash bun add sails-js ``` Getting Started [#getting-started] To begin using `sails-js`, choose the method that best fits your development workflow: * **Maximum Control with Dynamic Interaction** If you need fine-grained control over low-level operations or wish to handle side effects yourself, consider using the `.parseIdl` method to work with the raw `Sails` object. This approach is suitable for developers who require customization beyond what the generated client library offers. Refer to the [Overview](/docs/developing/sails-js/overview) section, which explains how to use the `Sails` class and its methods. * **Quick Start with Generated Client Library** If you prefer a faster setup and a straightforward way to interact with your application, the generated client library is an excellent starting point. It abstracts away many complexities, making it ideal for developers who want to focus on application logic. See the [Client Generation](/docs/developing/sails-js/client-generation) section for guidance on using `cargo sails client-js` and integrating the generated `.ts` file into your project. **The Sails-JS code is available on [GitHub](https://github.com/gear-tech/sails).** # Library Overview The `sails-js` library workflow begins by creating a `Sails` object through parsing a provided IDL description. This process maps the interface defined by the IDL to corresponding objects in the `Sails` instance, including: * Constructors * Services * Functions (referred to as Commands in the Sails framework) * Queries * Events The library also offers methods for decoding information from payloads and encoding data to be sent to a program via transactions. The `TransactionBuilder` class facilitates the building and sending of these transactions to the blockchain. For further details, refer to the [Transaction Builder](/docs/developing/sails-js/transaction-builder) section. Parsing an IDL Description [#parsing-an-idl-description] ```javascript import { Sails } from 'vara-network-wiki/content/docs/developing/sails-js/index.mdx'; import { SailsIdlParser } from 'sails-js-parser'; const parser = await SailsIdlParser.new(); const sails = new Sails(parser); const idl = ''; sails.parseIdl(idl); ``` The `sails` object now contains all the constructors, services, functions, and events available in the IDL file. To send messages, create programs, and subscribe to events using `Sails`, you need to connect to the chain using `@gear-js/api` and set the `GearApi` instance using the `setApi` method. For further details, refer to the [Gear-JS](/docs/developing/api) API section. ```javascript import { GearApi } from '@gear-js/api'; const api = await GearApi.create(); sails.setApi(api); ``` The Sails Class [#the-sails-class] Constructors [#constructors] The `sails.ctors` property contains an object with all the constructors available in the IDL file. The keys are the constructor names, and each value is an object with the following properties: ```javascript { args: Array<{ name: string, type: string }>, // Array of arguments with their names and SCALE codec types encodePayload: (...args: any) => HexString, // Function to encode the payload decodePayload: (bytes: HexString) => any, // Function to decode the payload fromCode: ( code: Uint8Array | Buffer, ...args: unknown[] ) => TransactionBuilder, // Function to create a transaction builder to deploy the program using code bytes fromCodeId: ( codeId: string, ...args: unknown[] ) => TransactionBuilder // Function to create a transaction builder to deploy the program using a code ID } ``` To get a constructor object, use `sails.ctors.ConstructorName`. The `fromCode` and `fromCodeId` methods return an instance of the `TransactionBuilder` class, which can be used to build and send the transaction to the chain. Services [#services] The `sails.services` property contains an object with all the services available in the IDL file. The keys are the service names, and each value is an object with the following properties: ```javascript { functions: Record, // Object with all the functions available in the service queries: Record, // Object with all the queries available in the service events: Record, // Object with all the events available in the service } ``` To get a service object, use `sails.services.ServiceName`. Functions [#functions] The `sails.services.ServiceName.functions` property contains an object with all the functions from the IDL file that can be used to send messages to the program. The keys are the function names, and each value can be used either as a function that accepts arguments and returns an instance of the `TransactionBuilder` class, or as an object with the following properties: ```javascript { args: Array<{ name: string, type: string }>, // Array of arguments with their names and SCALE codec types returnType: any, // SCALE codec definition of the return type encodePayload: (...args: any) => Uint8Array, // Function to encode the payload decodePayload: (bytes: Uint8Array) => any, // Function to decode the payload decodeResult: (result: Uint8Array) => any // Function to decode the result } ``` It's necessary to provide a program ID so that the function can be called. You can set the program ID using the `.setProgramId` method of the `Sails` class: ```javascript sails.setProgramId('0x...'); ``` To create a transaction for a function call, you can do the following: ```javascript const transaction = sails.services.ServiceName.functions.FunctionName(arg1, arg2); ``` Queries [#queries] The `sails.services.ServiceName.queries` property contains an object with all the queries from the IDL file that can be used to read the program state. The keys are the query names. The values include the same properties as described in the [Functions](/docs/developing/sails-js/overview#functions) section above. Note that the query function returns the result of the query, not a transaction builder. *The query function accepts three additional arguments beyond those specified in the IDL:* * **`originAddress`**: The address of the account that is calling the function. * **`value`**: *(Optional, default is `0`)* The amount of tokens sent with the function call. * **`atBlock`**: *(Optional)* The block at which the query is executed. **Example:** ```javascript const alice = 'kGkLEU3e3XXkJp2WK4eNpVmSab5xUNL9QtmLPh8QfCL2EgotW'; // functionArg1, functionArg2 are the arguments of the query function from the IDL file const result = await sails.services.ServiceName.queries.QueryName( alice, null, null, functionArg1, functionArg2 ); console.log(result); ``` In this example, `alice` is the origin address, and `functionArg1`, `functionArg2` are the arguments specific to the query function as defined in your IDL file. The `null` values indicate that the optional `value` and `atBlock` parameters are not being specified. Events [#events] The `sails.services.ServiceName.events` property contains an object with all the events available in the IDL file. The keys are the event names, and each value is an object with the following properties: ```javascript { type: any, // SCALE codec definition of the event is: (event: UserMessageSent) => bool, // Function to check if the event is of the specific type decode: (data: Uint8Array) => any, // Function to decode the event data subscribe: (callback: (data: any) => void | Promise) => void // Function to subscribe to the event } ``` To subscribe to an event, use the `subscribe` method of the event object: ```javascript sails.services.ServiceName.events.EventName.subscribe((data) => { console.log(data); }); ``` This will call the provided callback function whenever the event occurs, with `data` containing the decoded event data. Get Function Name and Decode Bytes [#get-function-name-and-decode-bytes] You can extract service and function names from payload bytes and decode messages using the following methods: * Use the `getServiceNamePrefix` function to get the service name from the payload bytes. * Use the `getFnNamePrefix` method to get the function or event name from the payload bytes. * Use the `sails.services.ServiceName.functions.FunctionName.decodePayload` method to decode the payload bytes of the sent message. * Use the `sails.services.ServiceName.functions.FunctionName.decodeResult` method to decode the result bytes of the received message. **Example:** ```javascript import { getServiceNamePrefix, getFnNamePrefix } from 'vara-network-wiki/content/docs/developing/sails-js/index.mdx'; const payloadOfSentMessage = '0x'; const serviceName = getServiceNamePrefix(payloadOfSentMessage); const functionName = getFnNamePrefix(payloadOfSentMessage); console.log( sails.services[serviceName].functions[functionName].decodeResult(payloadOfSentMessage) ); const payloadOfReceivedMessage = '0x'; console.log( sails.services[serviceName].functions[functionName].decodePayload(payloadOfReceivedMessage) ); ``` **Note:** Ensure that you use `sails.services[serviceName]` when accessing services. Encode and Decode Constructors and Events [#encode-and-decode-constructors-and-events] You can use the same approach to encode and decode bytes of constructors or events: ```javascript // Encoding and decoding constructor payloads sails.ctors.ConstructorName.encodePayload(arg1, arg2); sails.ctors.ConstructorName.decodePayload(''); // Decoding event data sails.events.EventName.decode(''); ``` Encode Payload [#encode-payload] Use the `sails.services.ServiceName.functions.FunctionName.encodePayload` method to encode the payload for a specific function. The bytes returned by this method can be used to send a message to the program. **Example:** ```javascript const payload = sails.services.ServiceName.functions.FunctionName.encodePayload(arg1, arg2); ``` In this example, `arg1` and `arg2` are the arguments required by the function as defined in your IDL file. The encoded `payload` can then be sent in a transaction to the program. # React Hooks The [`@gear-js/react-hooks`](/docs/developing/api/tooling/react-hooks) library provides React hook abstractions over `sails-js` and its [generated program library](/docs/developing/sails-js/client-generation). This significantly simplifies the development of front-end applications built with Sails. [TanStack Query](https://tanstack.com/query) is used as the async state manager to handle queries and mutations, making it easier to manage asynchronous operations like fetching data (queries) and performing create/update/delete operations (mutations) against the defined program library. Therefore, most hooks' parameters and return values correspond to the library's conventions. Hooks [#hooks] useSails [#usesails] The `useSails` hook returns a `Sails` instance as described in the [Library Overview](/docs/developing/sails-js/overview#the-sails-class) section, based on the provided `programId` and IDL description of the deployed application. **Example:** ```js import { useSails } from '@gear-js/react-hooks'; const programId = '0x...'; const idl = '...'; const { data } = useSails({ programId, idl, }); console.log(data); ``` useProgram [#useprogram] The `useProgram` hook returns a `Program` instance as described in the [Client Generation](/docs/developing/sails-js/client-generation#methods-of-the-program-class) section using a generated library (`lib.ts`) and the `id` of a deployed application. **Example:** ```js import { useProgram } from '@gear-js/react-hooks'; import { Program } from './lib'; const { data } = useProgram({ library: Program, id: '0x...', }); console.log(data); ``` useSendProgramTransaction [#usesendprogramtransaction] The `useSendProgramTransaction` hook is used to call a `function` (also referred to as a [command](/docs/developing/build/sails/coreconcepts#service) in Sails) of a Sails `service` from the application deployed at `id`. It returns a mutation to sign and send a transaction for this purpose. > **Note:** The returned mutation callback is essentially a wrapper over the [`TransactionBuilder`](/docs/developing/sails-js/transaction-builder) in `sails-js` and its `signAndSend` method, which will be called upon mutation execution. The `useSendProgramTransaction` hook abstracts this process by internally specifying an account and a [`@gear-js/api`](/docs/developing/api) instance, making it a shortcut for this procedure. **Example:** ```jsx import { useProgram, useSendProgramTransaction } from '@gear-js/react-hooks'; import { Program } from './lib'; function SendTransaction() { const { data: program } = useProgram({ library: Program, id: '0x...', }); const { sendTransactionAsync } = useSendProgramTransaction({ program, serviceName: 'service', functionName: 'function', }); const handleClick = async () => { const { response } = await sendTransactionAsync({ args: ['arg', 'anotherArg'], account: { addressOrPair: '0x...' }, // Defaults to the connected account from the extension if not provided value: 1000000n, // Defaults to 0 if not provided gasLimit: 1000000000n, // Automatically calculated if not provided voucherId: '0x...', // If not provided, the transaction will be sent without a voucher awaitFinalization: false // Defaults to false. If not provided, the transaction will be processed at the `inBlock` status }); console.log('response: ', response); }; return ( ); } export { SendTransaction }; ``` useProgramQuery [#useprogramquery] The `useProgramQuery` hook is used to call a `query` of a Sails `service` from the application deployed at `id`. It returns a query containing the program's current state. **Example:** ```jsx import { useProgram, useProgramQuery } from '@gear-js/react-hooks'; import { Program } from './lib'; function State() { const { data: program } = useProgram({ library: Program, id: '0x...', }); const { data } = useProgramQuery({ program, serviceName: 'service', functionName: 'query', args: ['arg', 'anotherArg'], watch: false, // If true, initializes a subscription to the program's state changes in the Gear MessagesDispatched event. Can increase network traffic. }); return
{JSON.stringify(data)}
; } ``` useProgramEvent [#useprogramevent] The `useProgramEvent` hook initializes a subscription to a specified program event. **Example:** The `useProgramEvent` hook below sets up a listener on the `event` function of the specified `service` and executes the `onData` callback with the new data whenever the event is triggered. ```jsx import { useProgram, useProgramEvent } from '@gear-js/react-hooks'; import { Routing } from './pages'; import { Program } from './lib'; function App() { const { data: program } = useProgram({ library: Program, id: '0x...', }); useProgramEvent({ program, serviceName: 'service', functionName: 'event', onData: (data) => console.log(data), }); return (
); } export { App }; ``` Prepare Hooks [#prepare-hooks] When performing long-running asynchronous work in response to a user transaction using [`useSendProgramTransaction`](/docs/developing/sails-js/react-hooks#usesendprogramtransaction)) hook, the user only receives feedback on the transaction status after transaction is complete. Prepare hooks can be used to eagerly obtain values before user interaction or to perform upfront validation before sending a transaction. usePrepareProgramTransaction [#useprepareprogramtransaction] The `usePrepareProgramTransaction` hook returns a mutation that retrieves the intermediate transaction state for a specified `function` and `service` of a deployed application at `id`. This hook is useful for eagerly obtaining values such as `gasLimit`, `extrinsic`, and `transactionFee`, which are essential for providing a smoother user experience. **Example:** The example below shows how to obtain the `transactionFee` for a transaction, which can be used to validate or display the fee to the user before proceeding. ```jsx import { useProgram, usePrepareProgramTransaction } from '@gear-js/react-hooks'; import { Program } from './lib'; function LogTransactionFeeButton() { const { data: program } = useProgram({ library: Program, id: '0x...', }); const { data, prepareTransactionAsync } = usePrepareProgramTransaction({ program, serviceName: 'service', functionName: 'function', }); const handleClick = async () => { const { fee } = await prepareTransactionAsync({ args: ['arg', 'anotherArg'], account: { addressOrPair: '0x...' }, value: 1000000n, gasLimit: 1000000000n, voucherId: '0x...', }); console.log('fee: ', fee); }; return ( ); } export { LogTransactionFeeButton }; ``` **Example:** Using the hook in combination with [useSendProgramTransaction](/docs/developing/sails-js/react-hooks#usesendprogramtransaction): ```jsx import { useProgram, usePrepareProgramTransaction, useSendProgramTransaction } from '@gear-js/react-hooks'; import { Program } from './lib'; function SendPreparedTransaction() { const { data: program } = useProgram({ library: Program, id: '0x...', }); const { prepareTransactionAsync } = usePrepareProgramTransaction({ program, serviceName: 'service', functionName: 'function', }); const { sendTransactionAsync } = useSendProgramTransaction({ program, serviceName: 'service', functionName: 'function', }); const handleClick = async () => { const { transaction, fee } = await prepareTransactionAsync({ args: ['arg', 'anotherArg'], account: { addressOrPair: '0x...' }, value: 1000000n, gasLimit: 1000000000n, voucherId: '0x...', }); console.log('fee: ', fee); const { response } = await sendTransactionAsync({ transaction }); console.log('response: ', response); }; return ( ); } export { SendPreparedTransaction }; ``` # Transaction Builder The `TransactionBuilder` class is used to build and send transactions to the chain. Use the `.programId` property to get the ID of the program that is used in the transaction. Methods to Build and Send the Transaction [#methods-to-build-and-send-the-transaction] withAccount [#withaccount] The `withAccount` method sets the account that is sending the message. It accepts either a `KeyringPair` instance or an address with `signerOptions`. **Example using a `KeyringPair` instance:** ```jsx import { Keyring } from '@polkadot/api'; const keyring = new Keyring({ type: 'sr25519' }); const pair = keyring.addFromUri('//Alice'); // Set the account using the KeyringPair instance transaction.withAccount(pair); ``` **Example using an address and signer options (commonly used on the frontend with a connected wallet):** ```jsx // This case is mostly used on the frontend with a connected wallet. import { web3FromSource, web3Accounts } from '@polkadot/extension-dapp'; // Retrieve all accounts from the wallet extension const allAccounts = await web3Accounts(); const account = allAccounts[0]; // Get the injector for the account's source const injector = await web3FromSource(account.meta.source); // Set the account address and signer in the transaction transaction.withAccount(account.address, { signer: injector.signer }); ``` withValue [#withvalue] The `withValue` method sets the value (amount) to be sent with the message. **Example:** ```jsx // Set the value to 10 VARA transaction.withValue(BigInt(10 * 1e12)); ``` calculateGas [#calculategas] The `calculateGas` method calculates the gas limit of the transaction. Optionally, you can provide two arguments: * The first argument, `allowOtherPanics`, determines whether panics in other programs are allowed to be triggered. It is set to `false` by default. * The second argument, `increaseGas`, is the percentage to increase the gas limit. It is set to `0` by default. **Example:** ```jsx // Calculate gas limit with default options await transaction.calculateGas(); // Calculate gas limit allowing other panics and increasing gas limit by 10% await transaction.calculateGas(true, 10); ``` withGas [#withgas] The `withGas` method manually sets the gas limit of the transaction. This can be used instead of the `calculateGas` method. **Example:** ```jsx // Set the gas limit manually transaction.withGas(100_000_000_000n); ``` withVoucher [#withvoucher] The `withVoucher` method sets the voucher ID to be used for the transaction. **Example:** ```jsx const voucherId = '0x...'; // Replace with actual voucher ID transaction.withVoucher(voucherId); ``` transactionFee [#transactionfee] The `transactionFee` method retrieves the transaction fee. **Example:** ```jsx const fee = await transaction.transactionFee(); console.log('Transaction fee:', fee.toString()); ``` signAndSend [#signandsend] The `signAndSend` method sends the transaction to the chain. This method returns an object containing: * `msgId`: the ID of the sent message. * `txHash`: the transaction hash. * `blockHash`: the hash of the block in which the message is included. * `isFinalized`: a function to check if the transaction is finalized. * `response`: a function that can be used to get the response from the program. The `response` function returns a promise with the response from the program. If an error occurs during message execution, the promise will be rejected with the error message. **Example:** ```jsx const { msgId, blockHash, txHash, response, isFinalized } = await transaction.signAndSend(); console.log('Message ID:', msgId); console.log('Transaction hash:', txHash); console.log('Block hash:', blockHash); // Check if the transaction is finalized const finalized = await isFinalized; console.log('Is finalized:', finalized); // Get the response from the program try { const result = await response(); console.log('Program response:', result); } catch (error) { console.error('Error executing message:', error); } ``` # Program Basics Program Basics [#program-basics] Vara.eth programs are **Gear programs** that run on Ethereum. If you're familiar with [Gear Protocol](/docs/gear) or [Vara Network](/docs/vara-network), you already know how to build for Vara.eth. This page covers the essentials. For detailed program development, see the [Sails Framework](/docs/developing/build/sails) and [Gear documentation](/docs/gear). Vara.eth vs Standard Gear Programs [#varaeth-vs-standard-gear-programs] The runtime, actor model, and WASM execution are identical. The only difference is the deployment target. Project Setup [#project-setup] Create a new program: ```bash cargo sails new my-program cd my-program ``` **Update `Cargo.toml`:** ```toml [dependencies] sails-rs = { version = "1.0.0", features = ["ethexe"] } ``` **The `ethexe` feature** enables Ethereum-specific functionality — event emission to L1, Mirror contract integration, and settlement flows. Sails 1.0.0 re-exports the SCALE codec and scale-info traits through the `#[sails_type]` attribute, so user code defining custom types no longer needs direct dependencies on `parity-scale-codec` or `scale-info`. → [Sails Framework](/docs/developing/build/sails) | [Quick Start](/docs/vara-eth/build/quickstart) Minimal Example [#minimal-example] Here's a complete counter program: ```rust #![no_std] use sails_rs::prelude::*; static mut COUNTER: i64 = 0; #[derive(Default)] pub struct CounterService; #[service] impl CounterService { #[export] pub fn increment(&mut self) -> i64 { unsafe { COUNTER += 1; COUNTER } } #[export] pub fn get(&self) -> i64 { unsafe { COUNTER } } } #[derive(Default)] pub struct CounterProgram; #[program] impl CounterProgram { pub fn init() -> Self { Self } pub fn counter(&self) -> CounterService { CounterService } } ``` If your program uses custom structs or enums in messages or state, annotate them with `#[sails_type]`. The attribute re-exports the SCALE codec and scale-info traits with the correct paths — no manual `Encode` / `Decode` / `TypeInfo` derives or `#[codec(crate=...)]` boilerplate needed. ```rust use sails_rs::prelude::*; #[sails_type] pub struct MyData { pub value: u64, pub owner: ActorId, } ``` Type Restrictions [#type-restrictions] When you plan to generate a Solidity interface (`cargo sails sol`) and use "Read/Write as Proxy" on Etherscan, design your public API with Solidity-compatible types in mind. **Safe baseline for ABI-facing methods:** * Integers: `u8`, `u16`, `u32`, `u64`, `u128`, and signed counterparts * `bool` * `String` * `Vec` (maps to dynamic `bytes`) * Fixed-size byte arrays (`[u8; 32]` etc.) * Structs composed only of supported fields * Arrays/vectors of supported element types **Types that commonly cause ABI friction:** * Rust-specific collections (`HashMap`, `BTreeMap`, complex nested containers) * Deeply nested enums/tuples and heavily generic public signatures * Program-internal types exposed directly in ABI-facing methods `ActorId` is 32 bytes, while Ethereum `address` is 20 bytes. In Gear primitives, `ActorId::to_address_lossy()` converts to `H160` with potential loss of the first 12 bytes. The reverse direction (`H160 -> ActorId`) is represented with zero-padding in the leading 12 bytes. If a method is intended for Solidity wallets/contracts, prefer address-oriented parameters in the ABI-facing surface, or document explicit conversion rules. **Best practice:** 1. Keep your external API minimal and Solidity-friendly. 2. Run `cargo sails sol --idl-path ...` early. 3. Treat generator output as the source of truth: if a type generates awkward Solidity, simplify the Rust signature before deployment. Reserved Words [#reserved-words] When exposing methods through generated Solidity interfaces, avoid names that conflict with Solidity language keywords or EVM special function names. **Avoid using as public method/service names:** * Solidity keywords like `contract`, `library`, `interface`, `function`, `mapping`, `event`, `error` * Special function names like `constructor`, `receive`, `fallback` Even when compilation succeeds, naming your API too close to low-level Ethereum terms can make Etherscan proxy views confusing. Prefer explicit domain names like `initCounter`, `joinMember`, `getProfile` instead of ambiguous names. **Practical naming rules:** * Use `snake_case` in Rust, let generated clients map naming as needed. * Keep constructor/init semantics explicit (`init`, `init_`). * Avoid reusing generic names that may look like transport/runtime internals (`send`, `call`, `execute`, `state`). Program Lifecycle [#program-lifecycle] ``` Upload Code → Validate → Create Instance → Top Up → Initialize → Active → (Exit) (blob) (executors) (Mirror deploy) (wVARA) (first msg) (processing) ``` Upload [#upload] Developer uploads compiled WASM code to Ethereum as an [EIP-4844 blob](/docs/vara-eth/reference/glossary#blob-eip-4844) and calls `requestCodeValidation(codeId)` on the Router. Validate [#validate] Executors fetch the blob, verify the WASM is valid, and include a `CodeCommitment` in the next batch. If valid, the `codeId` is marked as approved. Create Instance [#create-instance] Anyone can deploy a program from a validated `codeId` by calling `createProgram(...)` on the Router. This deploys a Mirror contract on Ethereum. The program's address is **deterministic**: `address = keccak256(codeId, salt)`. Top Up [#top-up] Fund the program's Executable Balance with wVARA. Without balance, the program cannot execute messages. → [Funding Executable Balance](/docs/vara-eth/interact/funding-executable-balance) Initialize [#initialize] The first message to a program triggers its constructor (`init`). Until initialization completes, the program cannot process regular messages. Only the address that created the program can send the first message. Active [#active] The program processes messages from its queue in strict `(blockNumber, txIndex, logIndex)` order. Each processed message produces a `StateTransition` that gets committed to Ethereum via the Router. Exit (Optional) [#exit-optional] A program can terminate itself by calling `msg::exit()`. On exit: * The program specifies an **inheritor address** where remaining balances are transferred * Executable Balance and remaining program value are transferred to the inheritor * The Mirror contract is marked as `exited` and stops accepting new messages The inheritor can claim the transferred value via `Mirror.transferLockedValueToInheritor()`. Program Identity [#program-identity] Every program has two identifiers: | Identifier | Format | Where Used | | -------------- | --------------------------- | ---------------------------------------------------- | | `ActorId` | 256-bit hash | Inside Vara.eth runtime, for inter-program messaging | | Mirror Address | Ethereum address (20 bytes) | On Ethereum L1, for user interaction | Both are derived deterministically from `(codeId, salt)`. Execution Cost Model [#execution-cost-model] Programs don't pay per-message. Instead, each program has an **Executable Balance** in wVARA: 1. Every program gets a small **free compute threshold** per message 2. Beyond the threshold, execution time is metered at a `wvaraPerSecond` rate 3. If the Executable Balance runs out, messages remain queued until the program is topped up 4. The consumed balance is distributed to validators as rewards → [Reverse Gas Model](/docs/vara-eth/economics/reverse-gas-model) # Quick Start Quick Start [#quick-start] This guide takes you from zero to a deployed program on Vara.eth. You'll create a simple counter program, deploy it to Ethereum, and interact with it. Rust toolchain, Sails CLI, and ethexe CLI installed. → [Tools Install](/docs/vara-eth/setup/tools-install) 1. Create a New Project [#1-create-a-new-project] ```bash cargo sails new counter-app cd counter-app ``` This scaffolds a new [Sails](/docs/developing/build/sails) program with the correct project structure. 2. Write Your Program [#2-write-your-program] Open `app/src/lib.rs` and replace the contents with: ```rust title="app/src/lib.rs" #![no_std] use sails_rs::prelude::*; // Program state static mut COUNTER: i64 = 0; // Service definition — your program's API #[derive(Default)] pub struct CounterService; #[service] impl CounterService { /// Increment the counter and return the new value #[export] pub fn increment(&mut self) -> i64 { unsafe { COUNTER += 1; COUNTER } } /// Decrement the counter and return the new value #[export] pub fn decrement(&mut self) -> i64 { unsafe { COUNTER -= 1; COUNTER } } /// Read the current counter value #[export] pub fn get(&self) -> i64 { unsafe { COUNTER } } } // Program entry point #[derive(Default)] pub struct CounterProgram; #[program] impl CounterProgram { /// Constructor — called once when the program is initialized pub fn init() -> Self { Self } /// Expose the CounterService pub fn counter(&self) -> CounterService { CounterService } } ``` In Vara.eth's actor model, each program processes messages sequentially (single-threaded execution per program), so this simple `static mut` counter example does not have concurrent access. For production contracts and richer state, prefer structured state patterns in Sails services. Update `Cargo.toml` to enable ethexe support: ```toml title="Cargo.toml" [package] name = "counter-app" version = "0.1.0" edition = "2024" [dependencies] sails-rs = { version = "1.0.0", features = ["ethexe"] } ``` 3. Build [#3-build] ```bash cargo build --release ``` This produces two artifacts in `target/wasm32-gear/release/`: * `counter_app.opt.wasm` — the compiled WASM binary * `counter_app.idl` — the [Interface Definition Language](/docs/developing/build/sails/idl) file describing your program's API 4. Deploy [#4-deploy] The `gear-tech.io` RPC endpoints below were operated by Gear for the launch campaigns and are no longer served. Substitute any Ethereum RPC provider for the chain you target — the Router addresses are unchanged and still deployed — or run a [full local stack](/docs/vara-eth/setup/local-node) and use your local RPC URL and Router address. Set up your environment: ```bash # Insert your private key (testnet wallet only!) ethexe key insert # Set variables for convenience export RPC="wss://hoodi-reth-rpc.gear-tech.io/ws" export ROUTER="0xE549b0AfEdA978271FF7E712232B9F7f39A0b060" export SENDER="" ``` ```bash # Insert your private key ethexe key insert # Set variables for convenience export RPC="wss://mainnet-reth-rpc.gear-tech.io/ws" export ROUTER="0x9C13FE9242dfe2ba2Cd446480A9308279aA74cb6" export SENDER="" ``` For the latest network addresses, always check [Contract Addresses](/docs/vara-eth/reference/contract-addresses). Upload the WASM code [#upload-the-wasm-code] ```bash ethexe tx upload target/wasm32-gear/release/counter_app.opt.wasm \ --watch \ --ethereum-rpc "$RPC" \ --ethereum-router "$ROUTER" \ --sender "$SENDER" ``` This uploads your WASM as an [EIP-4844 blob](/docs/vara-eth/reference/glossary#blob-eip-4844) and requests code validation. Wait for the `CODE_ID` to appear in the output. ```bash export CODE_ID="0x..." # Copy from output ``` Create a program instance [#create-a-program-instance] ```bash ethexe tx create "$CODE_ID" \ --salt 0x0000000000000000000000000000000000000000000000000000000000000001 \ --ethereum-rpc "$RPC" \ --ethereum-router "$ROUTER" \ --sender "$SENDER" ``` This deploys a Mirror contract on Ethereum representing your program. The output contains your `PROGRAM_ID` (Mirror address). ```bash export PROGRAM_ID="0x..." # Copy from output ``` The `--salt` parameter makes the Mirror address deterministic. Use different salts to deploy multiple instances of the same code. Address is computed as `CREATE2(Router, keccak256(codeId, salt))`. Fund the program [#fund-the-program] Your program needs wVARA to pay for execution. Use the `executableBalanceTopUp` function on the Mirror contract via Etherscan, the SDK, or CLI. → [Funding Executable Balance](/docs/vara-eth/interact/funding-executable-balance) Initialize the program [#initialize-the-program] Send the first message to trigger the constructor: ```bash ethexe tx send-message "$PROGRAM_ID" "0x..." 0 \ --ethereum-rpc "$RPC" \ --ethereum-router "$ROUTER" \ --sender "$SENDER" ``` 5. Interact with Your Program [#5-interact-with-your-program] Once initialized, anyone can send messages to your program. The Mirror contract on Ethereum accepts standard transactions. **Interact via:** * **Etherscan** — If you deployed with an ABI interface, methods appear under "Write as Proxy" * **CLI** — Use `ethexe tx send-message` * **SDK** — Use [sails-js](/docs/developing/sails-js) or `@vara-eth/api` from your frontend Read state [#read-state] ```bash ethexe tx query "$PROGRAM_ID" \ --ethereum-rpc "$RPC" \ --ethereum-router "$ROUTER" \ --sender "$SENDER" ``` What Just Happened? [#what-just-happened] You completed the core lifecycle: 1. Build and upload code 2. Create Mirror instance 3. Fund executable balance 4. Initialize and interact Your program is now live on Ethereum via its Mirror address. → Full lifecycle details: [Program Basics](/docs/vara-eth/build/program-basics)\ → API/design structure: [Sails Workflow](/docs/vara-eth/build/sails-workflow) # Sails Workflow Sails Workflow [#sails-workflow] [Sails](https://github.com/gear-tech/sails) is the developer framework for building Vara.eth programs. It provides Rust macros for defining services and interfaces, generates IDL files for interoperability, and produces client bindings for TypeScript and Solidity. For the full Sails reference, see the [Sails Library documentation](/docs/developing/build/sails). Why Sails? [#why-sails] Without Sails, you'd manually encode/decode messages in SCALE format, write ABI interfaces by hand, and build your own client libraries. Sails automates all of this: **`#[service]`** — Define your program's API with typed methods\ **`#[program]`** — Wire services into a program entry point\ **IDL generation** — Automatic [Interface Definition Language](/docs/developing/build/sails/idl) from your Rust code\ **ABI generation** — Solidity interface for Etherscan and Ethereum tooling\ **Client bindings** — TypeScript ([sails-js](/docs/developing/sails-js)) and Rust clients generated from IDL Services & Routes [#services--routes] A **service** is a logical grouping of related methods. A **program** exposes one or more services via named routes. → [Sails Routing](/docs/developing/build/sails/routing) Mutable vs Read-Only Methods [#mutable-vs-read-only-methods] **Methods taking `&mut self`** — Modify program state, called via transactions (messages)\ **Methods taking `&self`** — Read-only queries, called via RPC without submitting a transaction Example: Multi-Service Program [#example-multi-service-program] ```rust #[derive(Default)] pub struct TokenService; #[service] impl TokenService { #[export] pub fn transfer(&mut self, to: ActorId, amount: u128) -> bool { // Transfer logic — modifies state true } #[export] pub fn balance_of(&self, owner: ActorId) -> u128 { // Read-only — no transaction needed 0 } } #[derive(Default)] pub struct AdminService; #[service] impl AdminService { #[export] pub fn set_config(&mut self, key: String, value: String) { // Admin-only state modification } } #[derive(Default)] pub struct MyProgram; #[program] impl MyProgram { pub fn init() -> Self { Self } // Two services exposed via named routes pub fn token(&self) -> TokenService { TokenService } pub fn admin(&self) -> AdminService { AdminService } } ``` IDL — Interface Definition Language [#idl--interface-definition-language] When you build a Sails program, the compiler generates an `.idl` file alongside the WASM. The IDL is the **source of truth** for your program's interface — it describes every service, method, type, and event. → [IDL Specification](/docs/developing/build/sails/idl) **The IDL is used by:** 1. **[sails-js](/docs/developing/sails-js)** — to generate TypeScript client bindings 2. **Sails CLI** — to generate a Solidity ABI interface 3. Explorer/debug tooling — to display methods and inspect interactions during development Events [#events] Programs can emit [events](/docs/developing/build/sails/events) that are delivered as native Ethereum logs through the Mirror contract. How Events Reach Ethereum [#how-events-reach-ethereum] 1. The program emits an event frame to a reserved address (`ETH_EVENT_ADDR`) 2. Executors include the frame in the `StateTransition` 3. The Mirror contract parses the frame and re-emits it as a native EVM log (`log1..log4`) 4. Ethereum indexers (The Graph, custom indexers) can subscribe to these logs normally This gives you a **1:1 mapping** of program events to Ethereum logs — no custom decoders needed. Type Generation [#type-generation] From the IDL, you can generate strongly typed clients for any platform. TypeScript (sails-js) [#typescript-sails-js] ```bash cargo sails client-js ./my_program.idl ./src/lib.ts ``` → [sails-js Client Generation](/docs/developing/sails-js/client-generation) Solidity ABI [#solidity-abi] ```bash cargo sails sol --idl-path ./my_program.idl ``` When you deploy with `createProgramWithAbiInterface(...)`, the generated ABI attaches to the Mirror proxy. This means: * **Etherscan** shows your methods under "Write as Proxy" / "Read as Proxy" * **MetaMask** can call your program like any Ethereum contract * **Hardhat/Foundry** can interact with your program using standard tooling Rust Client [#rust-client] For backend services or inter-program communication, Sails also generates Rust client types from the IDL. Development Flow [#development-flow] ``` Write Rust → Build → IDL generated → Generate clients → Deploy (#[service]) (cargo) (.idl file) (TS / Solidity) (ethexe CLI) ``` 1. Define services with `#[service]` and wire them in `#[program]` 2. `cargo build --release` produces `.opt.wasm` + `.idl` 3. Generate client bindings from the IDL 4. Deploy using `ethexe` CLI (or project deployment scripts) 5. Interact from frontend ([sails-js](/docs/developing/sails-js)), Solidity, or CLI Key Files [#key-files] | File | Purpose | | ---------------- | --------------------------------------------------- | | `app/src/lib.rs` | Program logic (services, state, events) | | `build.rs` | Build configuration | | `Cargo.toml` | Dependencies with `sails-rs` and `ethexe` feature | | `*.opt.wasm` | Compiled WASM binary (deploy this) | | `*.idl` | Generated interface definition (share with clients) | # Testing & Debugging Testing & Debugging [#testing--debugging] Before deploying on-chain, test your programs locally. This page covers unit testing, integration testing patterns, and common debugging techniques. Unit Tests [#unit-tests] Sails programs are standard Rust code, so you can use `#[cfg(test)]` modules and `cargo test` for basic logic testing. ```bash cargo test ``` Unit tests run natively (not in WASM), so they don't test the full Gear runtime behavior. Use them for business logic validation. Example Unit Test [#example-unit-test] ```rust #[cfg(test)] mod tests { use super::*; use sails_rs::gstd::services::Service; #[test] fn test_increment() { let mut service = CounterService.expose(0); let result = service.increment(); assert_eq!(result, 1); } #[test] fn test_decrement_from_zero() { let mut service = CounterService.expose(0); let result = service.decrement(); assert_eq!(result, -1); } } ``` Integration Tests with gtest [#integration-tests-with-gtest] For integration tests that simulate the Gear runtime, use the [gtest framework](/docs/developing/build/testing-gtest). This allows you to test your program's message handling, state transitions, and inter-program communication without deploying to a real network. ```rust use gtest::{Program, System}; #[test] fn test_counter_program() { let system = System::new(); system.init_logger(); let program = Program::from_file( &system, "target/wasm32-gear/release/counter_app.opt.wasm", ); // Initialize the program let result = program.send_bytes(42, b"init"); assert!(!result.main_failed()); // Send increment message let result = program.send_bytes(42, b"Counter/Increment"); assert!(!result.main_failed()); } ``` → [gtest Framework](/docs/developing/build/testing-gtest) Integration Tests with gclient [#integration-tests-with-gclient] For full end-to-end tests that interact with a local Vara node, use the [gclient testing framework](/docs/developing/build/testing-gclient). This spins up a local node and lets you deploy and interact with your program in a realistic environment. → [gclient Framework](/docs/developing/build/testing-gclient) Local Development Loop [#local-development-loop] ``` Edit code → cargo test → cargo build --release → Deploy to your node → Test via CLI/SDK ``` **Tips for fast iteration:** 1. Test business logic with `cargo test` first — it's instant 2. Build WASM only when you need to deploy: `cargo build --release` 3. Use the same `salt` when redeploying to keep the same Mirror address during development 4. Monitor executor logs for detailed error messages Common Debugging Patterns [#common-debugging-patterns] Check Build Output [#check-build-output] Make sure your build produces both artifacts: ```bash ls target/wasm32-gear/release/*.opt.wasm ls target/wasm32-gear/release/*.idl ``` Verify Code Validation [#verify-code-validation] After uploading, check that your code was validated by looking for the `CodeGotValidated` event on the Router contract via Etherscan. If the event shows `valid: false`, check your WASM module. If no event yet, wait for the next executor batch (\~12 seconds). Debug Message Failures [#debug-message-failures] If your program doesn't respond to messages: 1. **Check Executable Balance** — Most common issue. Verify top-ups/events on explorer or use `@vara-eth/api` executable balance query 2. **Check initialization** — The first message must come from the program creator 3. **Check payload encoding** — Regenerate client bindings from the latest IDL 4. **Check events on Etherscan** — Look for `MessageQueueingRequested` events on the Mirror Common Errors [#common-errors] | Error | Cause | Fix | | ---------------------- | ------------------------- | --------------------------------------------------- | | Code validation failed | Invalid WASM module | Check `cargo build` output for errors | | Message not processed | Zero Executable Balance | Call `executableBalanceTopUp` on Mirror | | Init failed | Wrong initializer address | Only the program creator can send the first message | | Payload decode error | Mismatched types | Regenerate client from latest IDL | | Program not found | Wrong Mirror address | Verify `PROGRAM_ID` from `createProgram` output | Logging [#logging] Within your program, you can use `gstd::debug!` for debug output (visible in executor logs during local testing). Debug output is stripped in production builds. It's only available during local development and testing. → [Errors & Troubleshooting](/docs/vara-eth/reference/errors-troubleshooting) # Create Program Instance Create Program Instance [#create-program-instance] Once your code is validated, you can create one or more program instances from it. Each instance gets its own Mirror contract on Ethereum, its own state, and its own Executable Balance. Creation Methods [#creation-methods] CLI [#cli] **Without ABI:** ```bash ethexe tx create "$CODE_ID" \ --ethereum-rpc "$RPC" \ --ethereum-router "$ROUTER" \ --sender "$SENDER" ``` **With ABI:** ```bash # First deploy ABI interface contract, then: ethexe tx create-with-abi "$CODE_ID" "$ABI_INTERFACE_ADDRESS" \ --salt 0x0000000000000000000000000000000000000000000000000000000000000000 \ --initializer "$SENDER" \ --ethereum-rpc "$RPC" \ --ethereum-router "$ROUTER" \ --sender "$SENDER" ``` TypeScript SDK [#typescript-sdk] **Without ABI:** ```typescript import { EthereumClient } from '@vara-eth/api'; const client = new EthereumClient(publicClient, ROUTER_ADDRESS, signer); await client.waitForInitialization(); const tx = await client.router.createProgram(codeId); await tx.sendAndWaitForReceipt(); ``` **With ABI:** ```typescript const tx = await client.router.createProgramWithAbiInterface( codeId, abiInterfaceAddress, // Deployed ABI interface contract address ); await tx.sendAndWaitForReceipt(); ``` Why Deploy With ABI? [#why-deploy-with-abi] Deploying with ABI attaches a Solidity interface to your Mirror contract: **Without ABI:**\ Only base Mirror methods visible — `sendMessage`, `sendReply`, `executableBalanceTopUp`, `claimValue` **With ABI:**\ Your custom methods appear on Etherscan under "Write as Proxy" / "Read as Proxy" with decoded parameter names and types **Benefits:** * Etherscan shows your custom methods * MetaMask can call methods directly * Hardhat/Foundry can interact as a standard contract * Other Solidity contracts can call your program with typed interfaces Generating ABI from IDL [#generating-abi-from-idl] If your program is built with [Sails](/docs/developing/build/sails), generate a Solidity interface from the IDL: ```bash cargo sails sol --idl-path ./target/wasm32-gear/release/my_program.idl ``` This produces `MyProgram.sol` with: * Solidity interface matching your Sails services * ABI contract definition * Callback definitions for handling replies **Deploy the ABI contract** to Ethereum (via Foundry, Hardhat, or any tool), then use its address when creating your program. → [Solidity Integration](/docs/vara-eth/integrate/solidity-integration) Salt & Deterministic Addressing [#salt--deterministic-addressing] The program's address is computed deterministically: ``` Mirror Address = CREATE2(Router, keccak256(codeId, salt)) ActorId = keccak256(codeId, salt) ``` **This means:** * Same code + same salt = same address (across deployments) * Different salt = different address (multiple instances of same code) * You can predict the address before deploying → [EIP-1167 Minimal Proxy](/docs/vara-eth/reference/glossary#eip-1167-minimal-proxy) What Gets Deployed [#what-gets-deployed] When you call `createProgram`, the Router: 1. Verifies the `codeId` is in `Validated` state 2. Computes the deterministic address from `(codeId, salt)` 3. Deploys a `MirrorProxy` contract at that address (EIP-1167 minimal proxy) 4. Calls `Mirror.initialize(...)` on the new proxy 5. Emits `ProgramCreated(actorId, codeId)` event After Creation [#after-creation] The program exists on Ethereum but is **not yet initialized**. You need to: 1. **Fund it** — Top up the Executable Balance with wVARA 2. **Initialize it** — Send the first message (constructor call) ``` createProgram → Fund (wVARA) → Initialize (first message) → Active ``` **Example initialization command:** ```bash ethexe tx send-message "$PROGRAM_ID" "0x..." 0 \ --ethereum-rpc "$RPC" \ --ethereum-router "$ROUTER" \ --sender "$SENDER" ``` → [Funding Executable Balance](/docs/vara-eth/interact/funding-executable-balance) | [Initialization](/docs/vara-eth/interact/initialization) Multiple Instances [#multiple-instances] You can deploy many instances from the same `CODE_ID` using different salts. Each instance has independent state, balance, and lifecycle. **Use cases:** * Multi-tenant applications (one instance per user/organization) * Testing different configurations of the same program * Deploying sharded versions for horizontal scaling # Upload & Validation Upload & Validation [#upload--validation] The `gear-tech.io` and `vara.network` URLs in the examples below were operated by Gear for the launch campaigns and are no longer served. Swap the Ethereum RPC for any Hoodi or Mainnet provider, and the Vara.eth RPC for [your own node](/docs/vara-eth/setup/local-node) — see [Network Endpoints](/docs/vara-eth/reference/network-endpoints). The code itself is unchanged. Deploying a Vara.eth program starts with uploading your compiled WASM code to Ethereum and having it validated by the executor network. What Gets Uploaded [#what-gets-uploaded] Your compiled `.opt.wasm` file is uploaded to Ethereum as an **EIP-4844 blob** — a special data type designed for large payloads. **Why blobs:** * Significantly cheaper than calldata storage * WASM code is the canonical source for executors * Blob data is pruned after \~18 days, but archive nodes retain it permanently → [EIP-4844 Blob](/docs/vara-eth/reference/glossary#blob-eip-4844) Upload Methods [#upload-methods] CLI [#cli] ```bash ethexe tx upload target/wasm32-gear/release/my_program.opt.wasm \ --watch \ --ethereum-rpc "wss://hoodi-reth-rpc.gear-tech.io/ws" \ --ethereum-router "0xE549b0AfEdA978271FF7E712232B9F7f39A0b060" \ --sender "$SENDER" ``` ```bash ethexe tx upload target/wasm32-gear/release/my_program.opt.wasm \ --watch \ --ethereum-rpc "wss://mainnet-reth-rpc.gear-tech.io/ws" \ --ethereum-router "0x9C13FE9242dfe2ba2Cd446480A9308279aA74cb6" \ --sender "$SENDER" ``` Use `ethexe` CLI as the default and documented upload path. TypeScript upload flow can be used as an optional alternative if your `@vara-eth/api` version explicitly exposes the upload helpers you need. What Happens On-Chain [#what-happens-on-chain] When you upload, the Router contract records the `codeId` as **pending validation**: 1. Router emits `CodeValidationRequested` event 2. WASM blob is included in the transaction via EIP-4844 3. `codeId` (deterministic code hash) is marked as pending `codeId` is a deterministic hash of your WASM binary. Same code always produces the same `codeId`, regardless of who uploads it. Validation Process [#validation-process] After the upload transaction is included in an Ethereum block: 1. Executors observe the `CodeValidationRequested` event 2. Executors fetch the WASM blob from the Ethereum block 3. Executors validate the WASM module: * Valid WebAssembly binary * Required Gear runtime entry points present * Memory allocation within 2 GB limit * Compatible with current Sails/Gear runtime 4. Executors include a `CodeCommitment` in the next batch 5. Router applies the commitment: if valid, code is marked `Validated` Check Validation Status [#check-validation-status] Check on Etherscan by searching for `CodeGotValidated` event on the Router contract: * **Event found with `valid: true`** — Code is validated and ready to use * **Event found with `valid: false`** — Code failed validation, check WASM module * **No event yet** — Wait for the next executor batch (\~12 seconds) Validation Failures [#validation-failures] | Failure | Cause | | -------------------- | ----------------------------------------------- | | Invalid WASM | Binary is not a valid WebAssembly module | | Missing entry points | Required Gear runtime exports are missing | | Memory overflow | Program requests more than 2 GB limit | | Incompatible runtime | WASM compiled against unsupported Sails version | CODE_ID [#code_id] The `CODE_ID` is the unique identifier for your uploaded code: * 256-bit deterministic hash of the WASM binary (`CodeId`) * **Deterministic** — same code always produces the same ID * **Reusable** — once validated, anyone can create program instances from it * **Immutable** — the code behind a `CODE_ID` never changes You can share your `CODE_ID` with other developers. They can deploy their own instances of your program without re-uploading the code. Cost [#cost] Uploading code costs standard Ethereum gas for the transaction, plus the EIP-4844 blob fee. On testnet, use Hoodi ETH (see [Wallet Setup](/docs/vara-eth/setup/wallet-setup)). On mainnet, standard Ethereum gas fees apply. # Verify & Inspect Verify & Inspect [#verify--inspect] After deploying your program, verify it on Ethereum block explorers and inspect its state through multiple tools. Explorer Verification [#explorer-verification] Etherscan [#etherscan] Find your Mirror contract on Etherscan — [Mainnet](https://etherscan.io) or [Hoodi Testnet](https://hoodi.etherscan.io). Search by the Mirror address returned during [program creation](/docs/vara-eth/deploy/create-program-instance). **Without ABI:**\ Base Mirror methods visible — `sendMessage`, `sendReply`, `executableBalanceTopUp`, `claimValue` **With ABI:**\ Under "Write as Proxy" and "Read as Proxy", your program's custom methods appear with decoded parameters Key Events [#key-events] On your Mirror contract's "Events" tab: | Event | Meaning | | --------------------------------- | ------------------------------------------- | | `MessageQueueingRequested` | Message was queued for your program | | `ExecutableBalanceTopUpRequested` | wVARA was deposited for execution | | `StateChanged` | Program state was updated (batch committed) | | `Message` | Program sent an outgoing message | | `Reply` | Program replied to a message | | `ValueClaimed` | Value was claimed from the program | Read Program State [#read-program-state] CLI [#cli] ```bash ethexe tx query "$PROGRAM_ID" \ --ethereum-rpc "$RPC" \ --ethereum-router "$ROUTER" \ --sender "$SENDER" ``` TypeScript SDK (sails-js) [#typescript-sdk-sails-js] ```typescript import { MyProgram } from './generated'; const program = new MyProgram(provider, PROGRAM_ADDRESS); // Read-only queries — free, no gas const counter = await program.counter.get(); const balance = await program.token.balanceOf(userAddress); ``` Etherscan (with ABI) [#etherscan-with-abi] If deployed with ABI, navigate to the Mirror address on Etherscan and use the "Read as Proxy" tab to call read-only methods directly in the browser. State Hash [#state-hash] Every program's Mirror contract stores a `stateHash` — a cryptographic commitment to the program's full state. ```typescript const mirror = new ethers.Contract(mirrorAddress, mirrorAbi, provider); const stateHash = await mirror.stateHash(); ``` The `stateHash` guarantees that the state stored by executors matches what's committed on L1. Any dispute about program state can be resolved by comparing hashes. Checking Program Health [#checking-program-health] A healthy, active program should show: **Valid code** — `CodeGotValidated(codeId, true)` event exists on the Router\ **Initialized** — At least one `StateChanged` event after the init message\ **Funded** — At least one successful `ExecutableBalanceTopUpRequested` event and continued `StateChanged` activity\ **Processing** — Recent `StateChanged` events when messages are sent If something looks wrong, consult [Errors & Troubleshooting](/docs/vara-eth/reference/errors-troubleshooting). # Getting wVARA Getting wVARA [#getting-wvara] Hoodi [#hoodi] The Vara.eth faucet distributed Hoodi wVARA and ETH during the TestNet campaign. That campaign is complete and the faucet is no longer running — the WVARA contract itself is still deployed on Hoodi, see [Contract Addresses](/docs/vara-eth/reference/contract-addresses). To experiment today, run a [full local stack](/docs/vara-eth/setup/local-node) — it deploys its own WVARA contract, so no faucet is needed. Mainnet [#mainnet] Bridge from Vara Network [#bridge-from-vara-network] The primary way to get wVARA: 1. Acquire VARA on the Vara Network (exchanges, staking rewards, etc.) — see [Vara Network Tokenomics](/docs/vara-network/tokenomics) 2. Use the official [bridge](/docs/vara-network/bridge) to wrap VARA -> wVARA on Ethereum 3. wVARA is minted 1:1 against locked VARA For the technical details of the bridge, see the [Bridge Technical Overview](/docs/vara-network/bridge/bridge_technical). Validator Rewards [#validator-rewards] Validators earn wVARA for: * Executing programs and committing batches * Participating in the FROST signing protocol * Maintaining uptime and correct execution See [Validators & Staking](/docs/vara-eth/economics/validators-staking) for details. Token Address [#token-address] | Network | wVARA Address | | ---------------- | ----------------------------------------------------------------------------------------------------------------------- | | Hoodi Testnet | See [Contract Addresses](/docs/vara-eth/reference/contract-addresses) | | Ethereum Mainnet | [`0xB67010F2246814e5c39593ac23A925D9e9d7E5aD`](https://etherscan.io/address/0xB67010F2246814e5c39593ac23A925D9e9d7E5aD) | Add wVARA to MetaMask: Import the token address as a custom ERC-20 token. See [Wallet Setup](/docs/vara-eth/setup/wallet-setup) for instructions. # Reverse Gas Model Reverse Gas Model [#reverse-gas-model] In traditional blockchains, users pay gas for every transaction. Vara.eth flips this: **programs pay for their own execution**, and users only pay standard Ethereum gas for the L1 transaction. How It Works [#how-it-works] Traditional Model (Ethereum) [#traditional-model-ethereum] ``` User sends transaction -> User pays gas -> Contract executes ``` Every interaction costs the user gas. Complex operations cost more. Users must estimate gas, risk failed transactions, and hold ETH for every action. Vara.eth Reverse Model [#varaeth-reverse-model] ``` User sends message -> User pays L1 gas (~60-100k) -> Program pays wVARA for execution ``` Users pay a flat, predictable ETH cost for the Ethereum transaction. The program's Executable Balance covers the actual computation cost, regardless of complexity. This concept is related to Vara Network's [gasless and signless transactions](/docs/vara-network/about/features/gassignless) feature, which uses vouchers to enable gas-free user interactions. Execution Pricing [#execution-pricing] Free Threshold [#free-threshold] Each message gets a free computation allowance. Simple operations that complete within this threshold cost the program nothing beyond the base commitment. Metered Execution [#metered-execution] When execution exceeds the free threshold, the program is charged at the `wvaraPerSecond` rate: ``` Execution cost = max(0, execution_time - free_threshold) * wvaraPerSecond ``` The `wvaraPerSecond` rate is a network parameter set by governance, reflecting the cost of validator computation resources. What Determines Execution Time [#what-determines-execution-time] * **Computational complexity** — Loops, cryptographic operations, data processing * **Memory usage** — Allocating and accessing large state * **Message handling** — Processing incoming data, generating replies * **Inter-program calls** — Sending messages to other programs Cost Example [#cost-example] | Operation | Approximate Cost | | -------------------------- | -------------------------------------------- | | Simple counter increment | \~0.001 wVARA (within free threshold = free) | | Token transfer with checks | \~0.005 wVARA | | Complex DeFi calculation | \~0.05 wVARA | | Heavy ML inference | \~0.5 wVARA | These are illustrative — actual costs depend on network parameters and implementation. Why Reverse Gas? [#why-reverse-gas] Better UX [#better-ux] Users don't need to understand gas, hold wVARA, or estimate execution costs. They sign one Ethereum transaction with predictable gas. This enables: * Onboarding users who don't know about wVARA * Predictable costs for frontend applications * No "out of gas" failures at the user level Developer-Funded Model [#developer-funded-model] Developers fund their programs like running a server — pay for infrastructure so users get a free experience. This aligns with Web2 expectations where users don't pay per API call. Flexible Funding [#flexible-funding] Anyone can top up a program's Executable Balance: * **Developers** fund their own programs * **DAOs** fund community programs via governance * **Users** optionally contribute to programs they use * **Sponsors** fund programs as part of grant programs Spam Prevention [#spam-prevention] Programs that don't maintain their Executable Balance simply stop processing messages. This creates natural economic pressure: only programs with funding (i.e., someone values their existence) consume network resources. Executable Balance Management [#executable-balance-management] Funding [#funding] ```typescript // Fund via ethers.js const mirror = new ethers.Contract(mirrorAddress, abi, signer); await mirror.executableBalanceTopUp(parseUnits("10", 18)); ``` Monitoring [#monitoring] ```typescript // Read state and take executableBalance field const mirror = getMirrorClient(programId, signer, publicClient); const stateHash = await mirror.stateHash(); const state = await api.query.program.readState(stateHash); console.log(`Executable balance: ${state.executableBalance} base units`); ``` What Happens When Balance Runs Out [#what-happens-when-balance-runs-out] 1. Messages continue to arrive at the Mirror (users still pay L1 gas) 2. Messages are queued in the program's message queue 3. Executors detect zero Executable Balance and skip execution 4. No state changes occur — the program is effectively paused 5. Once funded again, queued messages are processed in order Users who send messages to an unfunded program still pay L1 gas, but their messages won't be processed until the program is funded. Design your frontend to check Executable Balance and warn users. Cost Optimization [#cost-optimization] * **Minimize computation in hot paths** — avoid unnecessary work in frequently called methods * **Use lazy evaluation** — don't compute what isn't needed * **Batch operations** in single messages when possible * **Cache computed values** in state rather than recomputing * **Keep payloads small** — less data to decode * **Monitor consumption patterns** and right-size funding with automated alerts Comparison with Other Models [#comparison-with-other-models] | Model | Who Pays | Predictability | UX | | --------------------- | -------- | ----------------------- | ------------------------------ | | **Ethereum L1** | User | Variable gas | Poor — users estimate gas | | **L2 Rollups** | User | Lower but variable | Better — cheaper gas | | **Vara.eth** | Program | Fixed L1 cost for users | Best — users pay only L1 gas | | **Gasless (meta-tx)** | Relayer | Variable | Good — but centralized relayer | # Validators & Staking Validators & Staking [#validators--staking] Validators (also called executors) are the nodes that execute Vara.eth programs off-chain and commit results to Ethereum. The network currently uses a controlled validator set (PoA). Permissionless staking is planned for a later phase. Role of Validators [#role-of-validators] Validators perform three critical functions: 1. **Execute programs** — Download WASM code, process messages, compute state transitions 2. **Sign pre-confirmations** — Participate in FROST threshold signatures for sub-second results 3. **Commit batches** — Submit `StateTransition` data to the Router contract on Ethereum Execution Flow [#execution-flow] ``` L1 block with messages | Validators detect new messages (monitor Router events) | Fetch program WASM + current state | Execute messages sequentially per program | Compute new stateHash | FROST threshold signature for pre-confirmation | Batch commitment to Router (one validator submits, all sign) | Router verifies and updates Mirror state ``` Staking [#staking] How Staking Works [#how-staking-works] Validators participate in the executor set through **Symbiotic** — a liquid restaking protocol on Ethereum. The system uses: * **Middleware Contract** ([IMiddleware.sol](https://github.com/gear-tech/gear/blob/master/ethexe/contracts/src/IMiddleware.sol)) — Manages validator registration, stake vaults, slashing, and rewards distribution * **Stake Vaults** — Operators stake wVARA through Symbiotic vaults with configurable epochs and slashing parameters * **Operator Registry** — Validators register as operators and opt-in to the Vara.eth subnetwork Staking serves two purposes: * **Sybil resistance** — Prevents spam validators * **Economic security** — Slashing for misbehavior ensures honest execution Vara.eth integrates with [Symbiotic](https://docs.symbiotic.fi/) for flexible staking with custom vaults, delegators, and slashing mechanisms. See the [Middleware contract](https://github.com/gear-tech/gear/blob/master/ethexe/contracts/src/Middleware.sol) for implementation details. This approach enables **liquid restaking** — staked wVARA can participate in multiple networks simultaneously, improving capital efficiency compared to isolated staking pools. Staking Parameters [#staking-parameters] | Parameter | Description | | ------------------- | ---------------------------------------------------- | | Minimum stake | Minimum wVARA required to become a validator | | Era duration | Period between validator set rotations | | Unbonding period | Time between requesting unstake and receiving tokens | | Slashing conditions | Incorrect state commitments, downtime, equivocation | Exact values are governance-controlled and may change. Check [Network Endpoints](/docs/vara-eth/reference/network-endpoints) for current parameters. Validator Onboarding Status [#validator-onboarding-status] During the current PoA phase, validator onboarding is not open to the public. The expected permissionless flow (register operator, register vault, run node) will be enabled in later phases once parameters and rollout milestones are announced. Validator Election [#validator-election] Each era, the Middleware selects validators through `makeElectionAt(timestamp, maxValidators)`: * Operators are ranked by their total stake across all registered vaults at the election timestamp * Top N operators (up to `maxValidators`) are selected for the next era * Stake is calculated via `getOperatorStakeAt(operator, timestamp)` which queries Symbiotic vaults * The selected validator set is committed on-chain and used for FROST signature verification Rewards [#rewards] Revenue Sources [#revenue-sources] Validators earn from: * **Execution fees** — wVARA consumed from programs' Executable Balances during message processing * **Era rewards** — Additional wVARA distributed per era from network inflation or fee pool Distribution [#distribution] Execution fees are distributed proportionally based on: * Participation in batch commitments * Pre-confirmation signing participation * Uptime and responsiveness Fee Flow [#fee-flow] ``` Program's Executable Balance | Message executed (wVARA consumed at wvaraPerSecond rate) | Fee pool for the batch | Distributed to validators who participated in the batch ``` Slashing [#slashing] Validators can be slashed (lose part of their stake) for: * **Incorrect execution** — Committing a wrong `stateHash` that doesn't match the program's actual state * **Equivocation** — Signing conflicting state transitions for the same block * **Extended downtime** — Failing to participate in consensus for extended periods The Router contract verifies batch commitments. If a committed state is challenged and proven incorrect, the submitting validator's stake is slashed. On Vara Network, programs can interact with the staking system directly via the [Staking Built-in Actor](/docs/developing/build/builtinactors/bia-staking), enabling programmatic staking operations like bonding, unbonding, nominating, and claiming rewards. FROST Threshold Signatures [#frost-threshold-signatures] Validators use [FROST](https://eprint.iacr.org/2020/852) (Flexible Round-Optimized Schnorr Threshold) signatures for pre-confirmations: * A threshold (e.g., 2/3) of validators must sign to produce a valid pre-confirmation * No single validator can forge a pre-confirmation * The aggregated signature is verifiable on-chain * Enables sub-second response times without waiting for L1 finality Pre-Confirmation Security [#pre-confirmation-security] Pre-confirmations are backed by the economic security of the validator set. To forge a false pre-confirmation, an attacker would need to control the threshold of validators and risk their combined stake being slashed. The [BLS12-381 Built-in Actor](/docs/developing/build/builtinactors/bia-bls) on Vara Network provides native BLS cryptographic operations used for efficient signature aggregation in the bridge's ZK proofs. Validator Economics [#validator-economics] Revenue Estimation [#revenue-estimation] A validator's revenue depends on: * Total wVARA consumed across all programs (network activity) * Number of active validators (more validators = smaller share) * Validator's uptime and participation rate Cost Structure [#cost-structure] * **Hardware** — Server with sufficient CPU, RAM, and storage for WASM execution * **Bandwidth** — P2P networking and Ethereum RPC access * **ETH for gas** — Batch commitment transactions cost ETH * **Opportunity cost** — Staked wVARA is locked during the unbonding period Profitability [#profitability] Validators are profitable when: ``` Execution fee rewards + Era rewards > Hardware costs + Gas costs + Opportunity cost ``` As network activity grows (more programs, more messages), the total fee pool grows, making validation more attractive. Current Status [#current-status] The network currently operates with a controlled PoA validator set. Permissionless validation and open staking are planned for future phases. To prepare for permissionless validation: * Follow [`ethexe` development](https://github.com/gear-tech/gear/tree/master/ethexe) * Set up and test executor nodes * Understand the staking and slashing mechanics * Join the [Vara community](https://discord.gg/vara) for validator coordination # wVARA Overview wVARA Overview [#wvara-overview] wVARA is the utility token that powers the Vara.eth execution economy. It funds program execution and secures validator incentives. What is wVARA? [#what-is-wvara] wVARA (wrapped VARA) is an ERC-20 token on Ethereum that represents VARA from the [Vara Network](/docs/vara-network). It serves three primary functions: 1. **Execution fuel** — Programs pay for computation using wVARA from their Executable Balance 2. **Validator incentives** — Validators earn wVARA for executing programs and committing batches 3. **Staking collateral** — Operators and vaults use wVARA in the validator economy For more on VARA tokenomics, see the [Vara Network Tokenomics](/docs/vara-network/tokenomics) page. wVARA vs ETH [#wvara-vs-eth] Both tokens play a role in Vara.eth, but for different purposes: | | ETH | wVARA | | ------------------ | ------------------ | ---------------------------- | | **Who pays** | Users | Programs | | **What it covers** | L1 transaction gas | Off-chain execution cost | | **When it's used** | Every message send | Every message processed | | **Where it lives** | User's wallet | Program's Executable Balance | When a user sends a message to a Vara.eth program: * The **user** pays ETH for the Ethereum transaction (L1 gas) * The **program** pays wVARA for executing the message (computation cost) This separation is the **Reverse Gas Model** — users don't need wVARA to interact with programs. Token Mechanics [#token-mechanics] ERC-20 on Ethereum [#erc-20-on-ethereum] wVARA is a standard ERC-20 token deployed on Ethereum. You can hold it in any Ethereum wallet, transfer it, trade it on DEXes, and use it in DeFi protocols. **Contract address (Hoodi testnet)**: Check [Contract Addresses](/docs/vara-eth/reference/contract-addresses) for the latest. **Contract address (Ethereum Mainnet)**: [`0xB67010F2246814e5c39593ac23A925D9e9d7E5aD`](https://etherscan.io/address/0xB67010F2246814e5c39593ac23A925D9e9d7E5aD) Supply and Bridging [#supply-and-bridging] wVARA is minted when VARA is bridged from the Vara Network to Ethereum, and burned when bridged back. The total supply of wVARA on Ethereum corresponds to the amount of VARA locked in the [bridge](/docs/vara-network/bridge). For technical details on the bridge mechanism, see the [Bridge Technical Overview](/docs/vara-network/bridge/bridge_technical). Decimals [#decimals] wVARA uses 18 decimals, consistent with most ERC-20 tokens: ``` 1 wVARA = 1_000_000_000_000_000_000 (1e18) base units ``` Where wVARA Flows [#where-wvara-flows] ``` Users / Developers | Fund programs (executableBalanceTopUp) | Executable Balance (Router-managed) | Consumed per message execution | Distributed to Validators ``` Additional flows: * **Top-up flow** — Accounts approve + top up program executable balance via Mirror * **Staking** — Validators and vaults use wVARA in Symbiotic-backed security * **Bridge/liquidity** — wVARA can be bridged and traded as ERC-20 on Ethereum Balance Types [#balance-types] Each program has **two key balances** you should monitor: Executable Balance (wVARA) [#executable-balance-wvara] Managed by the Router. Automatically consumed to pay for message execution. Think of it as a prepaid gas tank — when it runs dry, messages queue up but aren't processed. * Funded via `executableBalanceTopUp(value)` on the Mirror (with prior ERC-20 approve) * Consumed at the `wvaraPerSecond` rate during execution * Cannot be withdrawn by the program * Can be funded by anyone (developer, users, sponsors) * Transferred to inheritor on program exit See [Funding Executable Balance](/docs/vara-eth/interact/funding-executable-balance) for management details. Program Value Balance (Owned Balance, Ether) [#program-value-balance-owned-balance-ether] Programs can also hold **native Ether** in addition to wVARA. This is useful for programs that need to interact with ETH-based contracts or hold ETH as part of their logic. * Funded via `sendMessage{ value: ethAmount }` (payable) * Or via direct Ether transfer to the Mirror contract * Controlled by program logic * Transferred to inheritor on program exit In `@vara-eth/api` state reads, these appear as: * `state.executableBalance` — execution budget (wVARA base units) * `state.balance` — program value balance See the [Mirror contract source](https://github.com/gear-tech/gear/blob/master/ethexe/contracts/src/Mirror.sol) for the full interface including `payable` methods. Getting wVARA [#getting-wvara] Hoodi [#hoodi] The campaign faucet is no longer running. On a [full local stack](/docs/vara-eth/setup/local-node), the WVARA contract is deployed with your Router and you control the supply. Mainnet [#mainnet] wVARA is available via: * Bridging VARA from [Vara Network](/docs/vara-network) via the [bridge](/docs/vara-network/bridge/bridge_guide) * Earning as a validator See [Getting wVARA](/docs/vara-eth/economics/getting-wvara) for detailed instructions. # Backend Integration Backend Integration [#backend-integration] The `gear-tech.io` and `vara.network` URLs in the examples below were operated by Gear for the launch campaigns and are no longer served. Swap the Ethereum RPC for any Hoodi or Mainnet provider, and the Vara.eth RPC for [your own node](/docs/vara-eth/setup/local-node) — see [Network Endpoints](/docs/vara-eth/reference/network-endpoints). The code itself is unchanged. Backend services connect to Vara.eth for automation, indexing, monitoring, and bridging off-chain systems with on-chain programs. Method Coverage by Flow [#method-coverage-by-flow] Backend usually orchestrates the full lifecycle, not only reads/events. | Flow Step | Method(s) | Notes | | -------------------- | ---------------------------------------------------------------- | --------------------------------------------- | | Upload code | `ethexe tx upload ... --watch` | Canonical path for code submission/validation | | Create program | `router.createProgram(...)` | Standard deployment | | Create with ABI | `router.createProgramWithAbiInterface(...)` | Needed for typed Solidity/Etherscan proxy UX | | Top up balance | `wvara.approve(...)` + `mirror.executableBalanceTopUp(...)` | Required before processing user workload | | Send message (L1) | `mirror.sendMessage(...)` | Standard Ethereum-side path | | Send message (fast) | `api.createInjectedTransaction(...)` + `sendAndWaitForPromise()` | Vara.eth-side pre-confirmation path | | Read canonical state | `mirror.stateHash()` + `api.query.program.readState(...)` | Hash from L1, body from Vara.eth API | | Read computed query | `api.call.program.calculateReplyForHandle(...)` | Read-only execution without sending tx | Tx Lifecycle Helpers (@vara-eth/api) [#tx-lifecycle-helpers-vara-ethapi] Useful when building robust backend workers: * `ethereumClient.waitForInitialization()` — wait until Router/WVARA clients are ready * `tx.sendAndWaitForReceipt()` — reliable write completion point * `tx.estimateGas()` — preflight for operational safety * `tx.findEvent(...)` / `tx.getProgramId()` — event-based extraction for pipelines Connection Methods [#connection-methods] WebSocket RPC [#websocket-rpc] Use `@vara-eth/api` for Vara.eth-side operations (recommended): ```typescript import { VaraEthApi, WsVaraEthProvider, EthereumClient } from "@vara-eth/api"; const ethereumClient = new EthereumClient(publicClient, ROUTER_ADDRESS, signerAdapter); await ethereumClient.waitForInitialization(); const api = new VaraEthApi( new WsVaraEthProvider("wss://vara-eth-validator-1.gear-tech.io"), ethereumClient ); // Read program ids const ids = await api.query.program.getIds(); console.log("programs:", ids.length); // Read full state by state hash const state = await api.query.program.readState(stateHash); console.log("state:", state); ``` See [RPC API](/docs/vara-eth/reference/rpc-api) for the full method reference. Ethereum RPC [#ethereum-rpc] Monitor Mirror events via standard Ethereum JSON-RPC: ```typescript import { ethers } from "ethers"; const provider = new ethers.WebSocketProvider( "wss://hoodi-reth-rpc.gear-tech.io/ws" ); const mirror = new ethers.Contract(mirrorAddress, mirrorAbi, provider); mirror.on("StateChanged", (stateHash) => { console.log(`New state hash: ${stateHash}`); }); mirror.on("Message", (id, destination, payload, value) => { console.log(`Program sent message to ${destination}`); }); ``` ```typescript import { ethers } from "ethers"; const provider = new ethers.WebSocketProvider( "wss://mainnet-reth-rpc.gear-tech.io/ws" ); const mirror = new ethers.Contract(mirrorAddress, mirrorAbi, provider); mirror.on("StateChanged", (stateHash) => { console.log(`New state hash: ${stateHash}`); }); mirror.on("Message", (id, destination, payload, value) => { console.log(`Program sent message to ${destination}`); }); ``` sails-js (Node.js) [#sails-js-nodejs] Use the typed SDK for server-side operations: ```typescript import { SailsProgram } from "sails-js"; import { ethers } from "ethers"; const provider = new ethers.JsonRpcProvider( "https://mainnet-reth-rpc.gear-tech.io" ); const wallet = new ethers.Wallet(PRIVATE_KEY, provider); const program = new SailsProgram({ idl: programIdl, address: mirrorAddress, provider, signer: wallet, }); // Read state (free, no tx) const balance = await program.token.balanceOf(userAddress); // Send message (requires ETH for gas) await program.token.transfer(recipient, amount); ``` ```typescript import { SailsProgram } from "sails-js"; import { ethers } from "ethers"; const provider = new ethers.JsonRpcProvider( "https://hoodi-reth-rpc.gear-tech.io" ); const wallet = new ethers.Wallet(PRIVATE_KEY, provider); const program = new SailsProgram({ idl: programIdl, address: mirrorAddress, provider, signer: wallet, }); // Read state (free, no tx) const balance = await program.token.balanceOf(userAddress); // Send message (requires ETH for gas) await program.token.transfer(recipient, amount); ``` Common Backend Patterns [#common-backend-patterns] Event Indexer [#event-indexer] Index program events into a database for fast queries: ```typescript import { ethers } from "ethers"; import { Pool } from "pg"; const db = new Pool({ connectionString: DATABASE_URL }); const provider = new ethers.WebSocketProvider(RPC_URL); const mirror = new ethers.Contract(mirrorAddress, abi, provider); // Index all transfer events mirror.on("Transferred", async (from, to, amount, event) => { await db.query( `INSERT INTO transfers (from_addr, to_addr, amount, block_number, tx_hash) VALUES ($1, $2, $3, $4, $5)`, [ from, to, amount.toString(), event.log.blockNumber, event.log.transactionHash, ] ); }); // Backfill historical events async function backfill(fromBlock: number) { const events = await mirror.queryFilter("Transferred", fromBlock, "latest"); for (const event of events) { // Insert into DB... } } ``` Automation Bot [#automation-bot] Automate actions based on program state or events: ```typescript async function monitorAndAct() { const program = new SailsProgram({ idl, address: mirrorAddress, provider, signer: botWallet, }); setInterval(async () => { const state = await program.myService.getStatus(); if (state.needsRebalance) { console.log("Triggering rebalance..."); await program.myService.rebalance(); } }, 30_000); } ``` Keeper Service [#keeper-service] Execute time-sensitive operations that programs cannot trigger themselves: ```typescript // Programs respond to messages — they can't initiate actions on their own. // A keeper sends periodic messages to trigger scheduled logic. async function keeper() { const program = new SailsProgram({ idl, address: mirrorAddress, provider, signer: keeperWallet, }); const cron = new CronJob("0 0 * * *", async () => { try { await program.settlement.processDailyBatch(); console.log("Daily settlement triggered"); } catch (err) { console.error("Settlement failed:", err); } }); cron.start(); } ``` On [Vara Network](/docs/vara-network), programs can use [delayed messages](/docs/vara-network/about/features/message-automation) for self-scheduling. On Vara.eth, external keepers fill this role. API Gateway [#api-gateway] Expose program state via REST API: ```typescript import express from "express"; import { SailsProgram } from "sails-js"; const app = express(); const program = new SailsProgram({ idl, address: mirrorAddress, provider }); // Read-only endpoints (free, no gas) app.get("/api/balance/:address", async (req, res) => { const balance = await program.token.balanceOf(req.params.address); res.json({ balance: balance.toString() }); }); app.get("/api/state", async (req, res) => { const state = await program.myService.getFullState(); res.json(state); }); // Write endpoints (requires gas) app.post("/api/action", async (req, res) => { const signedProgram = program.connect(serverWallet); const result = await signedProgram.myService.doAction(req.body.params); res.json({ success: true, result }); }); app.listen(3000); ``` The Graph Subgraph [#the-graph-subgraph] For production indexing, use The Graph to create a subgraph: ```yaml title="subgraph.yaml" specVersion: 0.0.5 schema: file: ./schema.graphql dataSources: - kind: ethereum name: MyProgram network: hoodi source: address: "0x..." # Mirror address abi: Mirror startBlock: 1000000 mapping: kind: ethereum/events apiVersion: 0.0.7 language: wasm/assemblyscript entities: - Transfer - StateUpdate abis: - name: Mirror file: ./abis/Mirror.json eventHandlers: - event: StateChanged(bytes32) handler: handleStateChanged - event: Transferred(address,address,uint256) handler: handleTransfer ``` Monitoring & Alerting [#monitoring--alerting] Health Check [#health-check] ```typescript async function healthCheck() { const provider = new ethers.JsonRpcProvider(RPC_URL); const mirror = new ethers.Contract(mirrorAddress, abi, provider); const stateHash = await mirror.stateHash(); // Check recent activity const filter = mirror.filters.StateChanged(); const events = await mirror.queryFilter(filter, -1000); const lastUpdate = events[events.length - 1]; if (lastUpdate) { const block = await provider.getBlock(lastUpdate.blockNumber); const age = Date.now() / 1000 - block.timestamp; if (age > 3600) { alert("Program hasn't updated state in over an hour"); } } } ``` Metrics Collection [#metrics-collection] ```typescript import { Counter, Gauge } from "prom-client"; const messagesReceived = new Counter({ name: "vara_messages_received_total", help: "Total messages received by program", }); const executableBalance = new Gauge({ name: "vara_executable_balance", help: "Current executable balance in wVARA", }); mirror.on("MessageQueueingRequested", () => { messagesReceived.inc(); }); setInterval(async () => { const balance = await getExecutableBalance(programId); executableBalance.set(parseFloat(balance)); }, 60_000); ``` Security Considerations [#security-considerations] * **Private key management**: Use environment variables, HSMs, or secret managers (AWS Secrets Manager, HashiCorp Vault) — never hardcode keys. * **Rate limiting**: Vara.eth RPC endpoints may rate-limit. Implement exponential backoff and connection pooling. * **Idempotency**: Design your backend to handle duplicate events. Mirror events can be re-emitted during chain reorgs. * **Error handling**: Always wrap message sends in try/catch. Failed transactions still cost gas. * **Monitoring gas**: Backend wallets need ETH for gas. Monitor balances and set up alerts for low funds. # Choosing a Pattern Choosing a Pattern [#choosing-a-pattern] Vara.eth integration is easiest to choose by **team role** and ownership. Quick Choice by Role [#quick-choice-by-role] | Team Role | Start Here | Add Next | | --------- | ------------------------------------- | ------------------------------------ | | Frontend | Direct wallet -> Mirror calls | Injected transactions for fast UX | | Backend | Event indexing + monitoring + top-ups | Keepers, alerts, API layer | | Solidity | Contract -> Mirror async calls | Keeper that reacts to `Reply` events | Frontend Teams [#frontend-teams] Use this when users interact directly from wallet-connected UI. ``` User (Browser) -> Wallet -> Mirror -> Vara.eth Program ``` What frontend usually owns: * Connect wallet and send user actions (`sendMessage`) * Read state (`calculateReplyForHandle`, `readState`) * Show pre-confirmed vs finalized status in UI When to add backend: * You need history, analytics, notifications, or retries * You need operational top-ups for executable balance Backend Teams [#backend-teams] Use this when you need production operations around programs. ``` Frontend -> Mirror Backend -> indexer + DB + keeper + monitoring ``` What backend usually owns: * Index Mirror/Router events * Monitor `executableBalance` and top up before depletion * Run automation jobs and expose API endpoints for UI Solidity Teams [#solidity-teams] Use this when existing Solidity contracts must trigger Vara.eth computation. ``` User -> Solidity contract -> Mirror -> Vara.eth Program | async Reply event | Keeper callback ``` Important design rule: * Flow is asynchronous. Do not expect synchronous return values from `sendMessage`. Recommended Rollout [#recommended-rollout] 1. Start with frontend + direct Mirror interaction. 2. Add backend for indexing, monitoring, and automation. 3. Add Solidity integration only when on-chain composability is required. # Frontend Integration Frontend Integration [#frontend-integration] The `gear-tech.io` and `vara.network` URLs in the examples below were operated by Gear for the launch campaigns and are no longer served. Swap the Ethereum RPC for any Hoodi or Mainnet provider, and the Vara.eth RPC for [your own node](/docs/vara-eth/setup/local-node) — see [Network Endpoints](/docs/vara-eth/reference/network-endpoints). The code itself is unchanged. Vara.eth programs are designed to feel like standard Ethereum smart contracts from a frontend perspective. You connect a wallet, call methods, listen to events — all through familiar tools. The key difference is under the hood: your calls go to a Mirror contract on L1, which routes them to off-chain WASM execution with instant pre-confirmed results. SDK Overview [#sdk-overview] Three libraries power frontend integration: | Library | Purpose | Install | | ----------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------- | --------------------------- | | [`@vara-eth/api`](https://github.com/gear-tech/gear-js/blob/main/apis/vara-eth/README.md) | Core Vara.eth client — EthereumClient, VaraEthApi, injected transactions, state queries | `npm install @vara-eth/api` | | [sails-js](/docs/developing/sails-js) | Typed client generated from your program's IDL — handles payload encoding and decoding | `npm install sails-js` | `@vara-eth/api` is the primary SDK. It provides two interaction paths: * **Ethereum-side** — `EthereumClient` with `RouterClient`, `MirrorClient`, and `WrappedVaraClient` for on-chain operations * **Vara.eth-side** — `VaraEthApi` with injected transactions and `calculateReplyForHandle` for direct validator communication (pre-confirmations, instant reads) Use `sails-js` alongside `@vara-eth/api` to encode/decode payloads from your program's [IDL](/docs/developing/build/sails/idl). See the full [RPC API reference](/docs/vara-eth/reference/rpc-api) for detailed SDK documentation. Method Coverage by Flow [#method-coverage-by-flow] This section maps the full product flow to concrete methods, so frontend teams can see what is handled in UI and what is typically handled by ops/backend. | Flow Step | Primary Method(s) | Typical Owner | | -------------------------------- | -------------------------------------------------------------------------- | ------------------------- | | Upload code | `ethexe tx upload ... --watch` | DevOps / release pipeline | | Create program | `router.createProgram(...)` or `router.createProgramWithAbiInterface(...)` | Backend / deploy scripts | | Top up executable balance | `wvara.approve(...)` + `mirror.executableBalanceTopUp(...)` | Backend / keeper | | Send user action (L1 path) | `mirror.sendMessage(...)` (via `sails-js` typed call) | Frontend | | Send fast action (injected path) | `api.createInjectedTransaction(...)` + `sendAndWaitForPromise()` | Frontend (advanced UX) | | Read canonical state | `mirror.stateHash()` + `api.query.program.readState(...)` | Frontend / backend | | Read computed value (no write) | `api.call.program.calculateReplyForHandle(...)` | Frontend / backend | Frontend usually owns interaction and reads. Upload/create/top-up are usually operational flows (backend scripts, CI, or manual CLI) and should not be hardcoded into browser UI. Project Setup [#project-setup] Install Dependencies [#install-dependencies] ```bash npm install @vara-eth/api viem@^2.39.0 kzg-wasm@1.0.0 sails-js ``` Generate Typed Client [#generate-typed-client] From your program's IDL file, generate TypeScript bindings: ```bash cargo sails client-js ./path/to/your_program.idl ./src/generated/lib.ts ``` This creates a typed client class with methods matching your program's services and routes. Every method call is automatically encoded to the correct SCALE format. For more details, see [sails-js client generation](/docs/developing/sails-js/client-generation). Environment Configuration [#environment-configuration] ```typescript title="config.ts" export const config = { // Ethereum RPC (Hoodi testnet) ethRpcHttp: "https://hoodi-reth-rpc.gear-tech.io", ethRpcWs: "wss://hoodi-reth-rpc.gear-tech.io/ws", // Vara.eth RPC (for pre-confirmations and state queries) varaEthWs: "wss://vara-eth-validator-1.gear-tech.io", // Contract addresses routerAddress: "0xE549b0AfEdA978271FF7E712232B9F7f39A0b060", programId: "0x...", // Your program's Mirror address }; ``` ```typescript title="config.ts" export const config = { // Ethereum RPC (Mainnet) ethRpcHttp: "https://mainnet-reth-rpc.gear-tech.io", ethRpcWs: "wss://mainnet-reth-rpc.gear-tech.io/ws", // Vara.eth RPC (for pre-confirmations and state queries) varaEthWs: "wss://validator-1-eth.vara.network", // Contract addresses routerAddress: "0x9C13FE9242dfe2ba2Cd446480A9308279aA74cb6", programId: "0x...", // Your program's Mirror address }; ``` Keep addresses in one source of truth: [Contract Addresses](/docs/vara-eth/reference/contract-addresses). Connecting to a Program [#connecting-to-a-program] With sails-js (Recommended) [#with-sails-js-recommended] ```typescript import { ethers } from "ethers"; import { MyProgram } from "./generated"; // Connect wallet const provider = new ethers.BrowserProvider(window.ethereum); const signer = await provider.getSigner(); // Create typed program instance const program = new MyProgram(signer, config.programId); // Call methods with full type safety const result = await program.myService.myMethod(arg1, arg2); ``` With ethers.js + ABI (Alternative) [#with-ethersjs--abi-alternative] If the program was deployed with `createProgramWithAbiInterface(...)`, you can interact through the standard Ethereum ABI: ```typescript import { ethers } from "ethers"; import abi from "./MyProgramAbi.json"; const provider = new ethers.BrowserProvider(window.ethereum); const signer = await provider.getSigner(); const mirror = new ethers.Contract(config.programId, abi, signer); // Call like a normal Ethereum contract const tx = await mirror.myMethod(arg1, arg2); await tx.wait(); ``` The ABI approach works natively with Etherscan's "Write as Proxy" interface, making debugging and manual testing straightforward. MetaMask Integration [#metamask-integration] Vara.eth uses standard Ethereum transactions, so MetaMask works out of the box. Users sign transactions to the Mirror contract on L1 — no custom wallet adapters or network switching required. Connecting MetaMask [#connecting-metamask] ```typescript async function connectWallet() { if (!window.ethereum) { throw new Error("MetaMask not found"); } const provider = new ethers.BrowserProvider(window.ethereum); const signer = await provider.getSigner(); const address = await signer.getAddress(); return { provider, signer, address }; } ``` Adding Hoodi Testnet [#adding-hoodi-testnet] ```typescript async function addHoodiNetwork() { await window.ethereum.request({ method: "wallet_addEthereumChain", params: [ { chainId: "0x88bb0", // 560048 in hex chainName: "Ethereum Hoodi Testnet", rpcUrls: ["https://hoodi-reth-rpc.gear-tech.io"], nativeCurrency: { name: "ETH", symbol: "ETH", decimals: 18, }, }, ], }); } ``` Type Generation from IDL [#type-generation-from-idl] The [IDL](/docs/developing/build/sails/idl) file is the source of truth for your program's interface. It describes services, methods, parameters, return types, and events. Generating TypeScript Types [#generating-typescript-types] ```bash cargo sails client-js \ ./target/wasm32-gear/release/my_program.idl \ ./src/generated/lib.ts ``` Generating Solidity ABI [#generating-solidity-abi] ```bash cargo sails sol --idl-path ./target/wasm32-gear/release/my_program.idl ``` What Gets Generated [#what-gets-generated] The sails-js generator produces: * **Program class** — main entry point with typed methods for each service * **Type definitions** — TypeScript interfaces matching your Rust structs and enums * **Event types** — typed event listeners for program emissions * **Encoding/decoding** — automatic SCALE serialization, invisible to the developer Handling Pre-Confirmations in UX [#handling-pre-confirmations-in-ux] Pre-confirmations give your dApp sub-second feedback. See [Events & State Reading](/docs/vara-eth/interact/events-state-reading) for the full explanation. Optimistic UI Pattern [#optimistic-ui-pattern] ```typescript async function handleSwap(tokenA: string, tokenB: string, amount: bigint) { // 1. Show pending state in UI setStatus("pending"); // 2. Send an Ethereum-side transaction through Mirror const tx = await program.dex.swap(tokenA, tokenB, amount); // 3. Wait for Ethereum confirmation and then refresh state await tx.wait(); const updated = await program.dex.balanceOfCurrentUser(); setBalance(updated); setStatus("confirmed"); } ``` Injected Pre-Confirmation Pattern (@vara-eth/api) [#injected-pre-confirmation-pattern-vara-ethapi] For sub-second feedback, use Vara.eth-side injected transactions: ```typescript import { VaraEthApi, WsVaraEthProvider, EthereumClient } from "@vara-eth/api"; const ethereumClient = new EthereumClient(publicClient, config.routerAddress, signerAdapter); await ethereumClient.waitForInitialization(); const api = new VaraEthApi(new WsVaraEthProvider(config.varaEthWs), ethereumClient); const injected = await api.createInjectedTransaction({ destination: config.programId, payload: encodedPayload, // encode with sails-js value: 0n, }); const promise = await injected.sendAndWaitForPromise(); await promise.validateSignature(); // cryptographic check setStatus("pre-confirmed"); const replyPayload = promise.payload; // decode replyPayload with sails-js ``` Trust Levels [#trust-levels] | Trust Level | When to Use | Example | | --------------------- | ------------------------ | -------------------------------------- | | **Full trust** | UI updates, display data | Show swap result, game state | | **Caution** | Financial decisions | Display pending balance with indicator | | **Wait for finality** | High-value settlements | Large transfers, withdrawals | Listening to Events [#listening-to-events] Mirror Events (Standard Ethereum) [#mirror-events-standard-ethereum] ```typescript const provider = new ethers.WebSocketProvider(config.ethRpcWs); const mirror = new ethers.Contract(config.programId, mirrorAbi, provider); mirror.on("StateChanged", (stateHash, event) => { console.log("New state:", stateHash); }); mirror.on("Message", (id, destination, payload, value, event) => { console.log("Outgoing message from program"); }); ``` Custom Program Events (via Sails) [#custom-program-events-via-sails] ```typescript // With sails-js typed events program.on("SwapExecuted", (event) => { console.log(`Swapped ${event.amountIn} for ${event.amountOut}`); updateTradeHistory(event); }); ``` Reading State [#reading-state] Via RPC (Free, Instant) [#via-rpc-free-instant] ```typescript // Typed state query via sails-js const balance = await program.token.balanceOf(userAddress); const totalSupply = await program.token.totalSupply(); // These are free — no L1 transaction needed ``` Error Handling [#error-handling] ```typescript try { const tx = await program.myService.myMethod(args); const result = await tx.wait(); } catch (error) { if (error.code === "INSUFFICIENT_FUNDS") { showError("Insufficient ETH for transaction fee"); } else if (error.message.includes("ExecutableBalance")) { showError("Program temporarily unavailable. Please try again later."); } else if (error.message.includes("not initialized")) { showError("Program is not yet active"); } else { showError("Transaction failed: " + error.message); } } ``` | Error | Cause | Solution | | ------------------------- | ------------------------- | -------------------------------------- | | User rejected transaction | MetaMask popup declined | Show retry prompt | | Insufficient ETH | Not enough ETH for L1 gas | Prompt user to fund wallet | | Executable balance zero | Program has no wVARA | Developer must top up program | | Program not initialized | First message not sent | Initialize the program first | | Encoding error | Wrong payload format | Check IDL matches your program version | Complete Example: React dApp [#complete-example-react-dapp] ```typescript import { useState } from "react"; import { ethers } from "ethers"; import { MyProgram } from "./generated"; function App() { const [program, setProgram] = useState(null); const [counter, setCounter] = useState(0); const [status, setStatus] = useState("disconnected"); async function connect() { const provider = new ethers.BrowserProvider(window.ethereum); const signer = await provider.getSigner(); const p = new MyProgram(signer, PROGRAM_ADDRESS); setProgram(p); setStatus("connected"); // Load initial state const value = await p.counter.get(); setCounter(Number(value)); } async function increment() { if (!program) return; setStatus("sending..."); const tx = await program.counter.increment(); // Pre-confirmed result — sub-second setCounter((prev) => prev + 1); setStatus("pre-confirmed"); // Wait for L1 finality await tx.waitForFinality(); setStatus("finalized"); } return (

Counter: {counter}

Status: {status}

); } ``` # Solidity Integration Solidity Integration [#solidity-integration] Existing Ethereum contracts can call Vara.eth programs through the Mirror contract interface. This enables hybrid architectures where Solidity handles on-chain DeFi composability while Vara.eth handles heavy computation. Mirror Interface [#mirror-interface] Every Vara.eth program has a Mirror contract on Ethereum. Your Solidity contracts interact with this Mirror using the `IMirror` interface: ```solidity // SPDX-License-Identifier: MIT pragma solidity ^0.8.26; interface IMirror { // Send a message to the Vara.eth program function sendMessage( bytes calldata payload, bool callReply ) external payable returns (bytes32); // Top up program's executable balance function executableBalanceTopUp(uint128 value) external; // Claim value sent back by the program function claimValue(bytes32 claimedId) external; // Events emitted by the Mirror event MessageQueueingRequested( bytes32 id, address source, bytes payload, uint128 value, bool callReply ); event Reply(bytes payload, uint128 value, bytes32 replyTo, bytes4 replyCode); event StateChanged(bytes32 stateHash); event Message(bytes32 id, address destination, bytes payload, uint128 value); event ValueClaimed(bytes32 claimedId, uint128 value); } ``` See [Contract Addresses](/docs/vara-eth/reference/contract-addresses) for the full Router and Mirror interfaces. Sending Messages from Solidity [#sending-messages-from-solidity] Basic Call [#basic-call] ```solidity contract VaraClient { IMirror public mirror; constructor(address _mirror) { mirror = IMirror(_mirror); } function callProgram(bytes calldata payload) external { // Fire-and-forget: message queued, executed by validators mirror.sendMessage(payload, false); } } ``` With Value Transfer [#with-value-transfer] To top up **Executable Balance** in wVARA, approve the program (Mirror address) first, then call `executableBalanceTopUp`: ```solidity import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; contract VaraDepositor { IMirror public mirror; IERC20 public wvara; constructor(address _mirror, address _wvara) { mirror = IMirror(_mirror); wvara = IERC20(_wvara); } function topUpExecutableBalance(uint128 amount) external { // Approve the program (Mirror address) as spender wvara.approve(address(mirror), amount); mirror.executableBalanceTopUp(amount); } } ``` To send **ETH value** with a message, use `payable` call value: ```solidity function sendWithEth(bytes calldata payload) external payable { mirror.sendMessage{value: msg.value}(payload, false); } ``` Requesting a Reply [#requesting-a-reply] Set `callReply = true` to receive the program's reply as an on-chain event: ```solidity function callWithReply(bytes calldata payload) external { // Reply will be emitted as a Reply event on the Mirror mirror.sendMessage(payload, true); } ``` Replies are emitted as events, not as synchronous return values. The execution happens asynchronously — your contract sends a message, and the reply arrives in a later transaction (the batch commitment). Encoding Payloads [#encoding-payloads] Vara.eth programs use SCALE encoding by default. When a program is deployed with ABI (recommended), the Mirror accepts standard ABI-encoded payloads. With ABI (Recommended) [#with-abi-recommended] ```solidity // Encode a call to: TokenService::Transfer(to, amount) bytes memory payload = abi.encodeWithSignature( "Transfer(address,uint256)", recipient, amount ); mirror.sendMessage(payload, false); ``` Using Generated ABI [#using-generated-abi] Generate a Solidity interface from the program's IDL: ```bash cargo sails sol --idl-path ./target/wasm32-gear/release/my_program.idl ``` This generates a typed Solidity interface: ```solidity import {IMyProgram} from "./IMyProgram.sol"; contract Client { IMyProgram public program; function doSomething() external { // Type-safe call via generated interface program.myService_myMethod(arg1, arg2); } } ``` Listening for Events [#listening-for-events] Reply Events [#reply-events] ```solidity contract ReplyListener { IMirror public mirror; // Called by off-chain service when Reply event is detected function handleReply( bytes calldata payload, uint128 value, bytes32 replyTo, bytes4 replyCode ) external { // replyCode == 0x00000000 means success require(replyCode == bytes4(0), "Program returned error"); // Decode and use payload... } } ``` State Change Events [#state-change-events] ```typescript // Off-chain listener (ethers.js) mirror.on("StateChanged", (stateHash) => { console.log(`Program state updated: ${stateHash}`); }); ``` Custom Program Events [#custom-program-events] Programs can define custom events that are re-emitted as native EVM logs by the Mirror: ```typescript mirror.on("Transferred", (from, to, amount) => { console.log(`Transfer: ${from} -> ${to}: ${amount}`); }); ``` Claiming Value [#claiming-value] When a Vara.eth program sends value to an address, the recipient claims it through the Mirror: ```solidity function claimMyValue(bytes32 claimedId) external { // claimedId comes from the Message event emitted by Mirror mirror.claimValue(claimedId); // Claimed value is transferred to msg.sender } ``` Architecture Patterns [#architecture-patterns] Computation Offloading [#computation-offloading] Keep DeFi composability in Solidity, offload heavy logic to Vara.eth: ``` User -> Solidity Router -> Mirror.sendMessage() -> Vara.eth Program | Heavy computation | Reply event <- Mirror | Off-chain service reads reply | Triggers next Solidity action ``` Hybrid DeFi [#hybrid-defi] ```solidity contract HybridDEX { IMirror public pricingEngine; // Vara.eth handles complex pricing IUniswap public uniswap; // Solidity handles token swaps function executeSwap( address tokenIn, address tokenOut, uint256 amountIn ) external { // 1. Ask Vara.eth program for optimal price/route bytes memory query = abi.encode(tokenIn, tokenOut, amountIn); pricingEngine.sendMessage(query, true); // 2. Reply arrives asynchronously with routing data // 3. Off-chain keeper executes the swap using the route } } ``` Oracle/Aggregator [#oracleaggregator] ```solidity contract VaraOracle { IMirror public oracle; mapping(bytes32 => uint256) public prices; function updatePrice( bytes32 asset, uint256 price, bytes calldata proof ) external { // Verify proof from Vara.eth execution prices[asset] = price; } } ``` Important Considerations [#important-considerations] * **Asynchronous execution**: Messages sent via `sendMessage` are not executed immediately. They enter the program's message queue and are processed by executors in the next batch. * **Gas costs**: Sending a message via Mirror costs standard Ethereum gas (\~60-100k gas). The program's execution cost is paid from its [Executable Balance](/docs/vara-eth/interact/funding-executable-balance), not by the caller. * **No synchronous returns**: Unlike typical Solidity function calls, you cannot get a return value from `sendMessage`. Use `callReply = true` and listen for `Reply` events, or read state after `StateChanged`. * **ABI vs SCALE**: Deploy programs with ABI for the smoothest Solidity integration. Without ABI, you'll need to SCALE-encode payloads manually. * **Value handling**: `sendMessage` carries ETH via `msg.value`. wVARA is used to fund executable balance via `executableBalanceTopUp`, with prior ERC-20 approve to the program (Mirror) address. # AI & ML Full guide coming soon. Two starting points today: * **[Agent Skills](/docs/vara-eth/setup/agent-skills)** — drive the whole build loop (Sails contract, `ethexe` deployment, ABI, frontend) from Claude Code, Codex or Cursor. * **[Robo Miner](https://github.com/gear-foundation/robo-miner)** — a live agent arena on Vara.eth, shipping an Agent Skill Pack that lets AI agents hold a wallet and play against each other on-chain. # DeFi Coming soon. # Enterprise Coming soon. # Gaming Full guide coming soon. In the meantime, [Robo Miner](https://github.com/gear-foundation/robo-miner) is a complete game built on Vara.eth and a good place to read real code: a Phaser client over on-chain programs for the game world, proxying, RES token rewards and redemption, plus a leaderboard and a live agent arena. See also [Deep Dives](/docs/vara-eth/start-here/deep-dives) for the rest of the reference repositories. # Performance Coming soon. # Web2 Integration Coming soon. # CLI Reference CLI Reference [#cli-reference] The `ethexe` CLI is the primary command-line tool for interacting with Vara.eth. It handles code upload, program creation, message sending, and state queries. Installation [#installation] ```bash # Install from the Gear repository cargo install --git https://github.com/gear-tech/gear ethexe-cli # Verify installation ethexe --version ``` Global Options [#global-options] For `ethexe tx` commands, use: | Option | Description | | ----------------------------- | ----------------------------------------------- | | `--ethereum-rpc ` | Ethereum RPC endpoint (alias: `--eth-rpc`) | | `--ethereum-router
` | Router contract address (alias: `--eth-router`) | | `--sender
` | Sender address (must exist in key store) | | `--key-store ` | Path to key store directory | Top-level option for any command: | Option | Description | | -------------- | ---------------------------------------------------------- | | `--cfg ` | Path to `.ethexe.toml` config file (use `none` to disable) | Commands [#commands] ethexe tx upload [#ethexe-tx-upload] Upload WASM code to Ethereum as an [EIP-4844 blob](/docs/vara-eth/reference/glossary#blob-eip-4844). ```bash ethexe tx upload path/to/program.opt.wasm \ --ethereum-rpc "$RPC" \ --ethereum-router "$ROUTER" \ --sender "$SENDER" ``` **Options:** * `--watch` / `-w` — Wait for code validation approval * `--legacy` / `-l` — Use old blob transaction format **Output**: Transaction hash, code ID (Blake2b256 hash), and validation status if `--watch` is used. ethexe tx create [#ethexe-tx-create] Create a new program instance from validated code. ```bash ethexe tx create "$CODE_ID" \ --ethereum-rpc "$RPC" \ --ethereum-router "$ROUTER" \ --sender "$SENDER" ``` **Positional Arguments:** * `` — Code ID (required) **Options:** * `--salt` / `-s` — 32-byte salt in hex (optional, random if omitted) * `--initializer` / `-i` — Initializer address (optional, defaults to sender) **Output**: Mirror contract address and actor ID. ethexe tx create-with-abi [#ethexe-tx-create-with-abi] Create a program with an ABI interface contract for Etherscan integration. ```bash ethexe tx create-with-abi "$CODE_ID" "$ABI_INTERFACE_ADDRESS" \ --salt 0x0000000000000000000000000000000000000000000000000000000000000000 \ --initializer "$SENDER" \ --ethereum-rpc "$RPC" \ --ethereum-router "$ROUTER" \ --sender "$SENDER" ``` **Positional Arguments:** * `` — Code ID * `` — Address of deployed ABI interface contract **Options:** * `--salt` / `-s` — Salt (optional) * `--initializer` / `-i` — Initializer (optional) **Output**: Mirror contract address linked to ABI interface. ethexe tx send-message [#ethexe-tx-send-message] Send a message to a Vara.eth program. ```bash ethexe tx send-message "$MIRROR_ADDRESS" "0x..." 0 \ --ethereum-rpc "$RPC" \ --ethereum-router "$ROUTER" \ --sender "$SENDER" ``` **Positional Arguments:** * `` — Mirror contract address * `` — Hex-encoded message payload * `` — ETH value to send (in wei) **Options:** * `--call-reply` / `-c` — Force mirror to call destination actor on reply * `--watch` / `-w` — Wait for reply (currently not implemented in v1.10.0) ethexe tx executable-balance-top-up [#ethexe-tx-executable-balance-top-up] Top up a program's [Executable Balance](/docs/vara-eth/interact/funding-executable-balance) (wVARA). ```bash ethexe tx executable-balance-top-up "$MIRROR_ADDRESS" 10000000000000000000 \ --approve \ --ethereum-rpc "$RPC" \ --ethereum-router "$ROUTER" \ --sender "$SENDER" ``` **Positional Arguments:** * `` — Mirror contract address * `` — wVARA amount (in base units) **Options:** * `--approve` / `-a` — Automatically approve wVARA spending ethexe tx owned-balance-top-up [#ethexe-tx-owned-balance-top-up] Top up a program's Owned Balance (native ETH). ```bash ethexe tx owned-balance-top-up "$MIRROR_ADDRESS" 1000000000000000000 \ --ethereum-rpc "$RPC" \ --ethereum-router "$ROUTER" \ --sender "$SENDER" ``` **Positional Arguments:** * `` — Mirror contract address * `` — ETH amount (in wei) ethexe tx query [#ethexe-tx-query] Query Mirror contract state including balances, state hash, and metadata. ```bash ethexe tx query "$MIRROR_ADDRESS" \ --ethereum-rpc "$RPC" \ --ethereum-router "$ROUTER" \ --sender "$SENDER" ``` **Positional Arguments:** * `` — Mirror contract address **Output:** * Router address * State hash * Nonce * Exited status * Inheritor address * Initializer address * ETH balance (Owned Balance) In `v1.10.0`, `ethexe tx query` shows mirror state and ETH balance. For executable (wVARA) balance, use `@vara-eth/api` (RPC) or inspect `ExecutableBalanceTopUpRequested`/related state transitions on explorer tooling. Sails CLI [#sails-cli] The `sails-cli` Cargo subcommand (`cargo sails`) covers Rust-side workflows, IDL handling, and TypeScript/Solidity/Rust client generation. Generate TypeScript Client [#generate-typescript-client] ```bash cargo sails client-js program.idl ./src/generated/lib.ts ``` Produces typed TypeScript bindings for all services, methods, and events. See [sails-js client generation](/docs/developing/sails-js/client-generation) for details. Generate Solidity ABI [#generate-solidity-abi] ```bash cargo sails sol --idl-path program.idl ``` Produces Solidity ABI artifacts for use with `createProgramWithAbiInterface` and Etherscan. Generate Rust Client [#generate-rust-client] ```bash cargo sails client-rs program.idl ./src/generated/program_client.rs ``` Produces typed Rust client code for inter-program communication. Version Note [#version-note] This page is aligned with `ethexe` `v1.10.0` command set. # Contract Addresses Contract Addresses [#contract-addresses] These contracts were deployed during the TestNet and MainNet campaigns and remain on-chain, verifiable on Etherscan. Only the Gear-operated RPC and validator endpoints were retired — the addresses below are unchanged and can be used from your own node. Hoodi Testnet [#hoodi-testnet] | Contract | Address | Description | | ---------- | ----------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------- | | **Router** | [`0xE549b0AfEdA978271FF7E712232B9F7f39A0b060`](https://hoodi.etherscan.io/address/0xE549b0AfEdA978271FF7E712232B9F7f39A0b060) | Core settlement contract. Manages code validation, program creation, and batch commitments. | | **wVARA** | [`0xE1ab85A8B4d5d5B6af0bbD0203EB322DF33d0464`](https://hoodi.etherscan.io/address/0xE1ab85A8B4d5d5B6af0bbD0203EB322DF33d0464) | ERC-20 wrapped VARA token. | For Vara Network bridge contract addresses (Verifier, MessageQueue, BridgingPayment, ERC20Manager, etc.), see the [Bridge Developer Hub](/docs/vara-network/bridge/developer_hub). Ethereum Mainnet [#ethereum-mainnet] | Contract | Address | Description | | ---------- | ----------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------- | | **Router** | [`0x9C13FE9242dfe2ba2Cd446480A9308279aA74cb6`](https://etherscan.io/address/0x9C13FE9242dfe2ba2Cd446480A9308279aA74cb6) | Core settlement contract. Manages code validation, program creation, and batch commitments. | | **wVARA** | [`0xB67010F2246814e5c39593ac23A925D9e9d7E5aD`](https://etherscan.io/address/0xB67010F2246814e5c39593ac23A925D9e9d7E5aD) | ERC-20 wrapped VARA token. | # Errors & Troubleshooting Errors & Troubleshooting [#errors--troubleshooting] Build Errors [#build-errors] cargo build fails with missing dependencies [#cargo-build-fails-with-missing-dependencies] ``` error: failed to select a version for `sails-rs` ``` **Fix**: Ensure your `Cargo.toml` points to the correct Gear crate versions. Check [github.com/gear-tech/gear](https://github.com/gear-tech/gear) for the latest compatible versions. WASM build produces no .opt.wasm [#wasm-build-produces-no-optwasm] **Fix**: Build the Sails project normally: ```bash cargo build --release ``` Sails build tooling resolves the target automatically and emits artifacts in `target/wasm32-gear/release/`. Also ensure `wasm-opt` is installed. The `.opt.wasm` file is the optimized version needed for deployment. IDL not generated [#idl-not-generated] **Fix**: Ensure your program uses the `#[program]` macro from [Sails](/docs/developing/build/sails) and that all service types implement the required traits. The [IDL](/docs/developing/build/sails/idl) is generated from your Rust type definitions at build time. Deployment Errors [#deployment-errors] Code validation fails [#code-validation-fails] **Possible causes**: * Invalid WASM format (not a valid WebAssembly module) * Missing Gear-compatible entry points * Memory allocation exceeds limits (>2GB) * Incompatible runtime version **Fix**: Rebuild with the latest Sails version. Verify the `.opt.wasm` file is valid: ```bash wasm-validate program.opt.wasm ``` createProgram reverts [#createprogram-reverts] **Possible causes**: * Code ID not yet validated (status is `Pending`) * Code ID was rejected (invalid) * Insufficient ETH for gas **Fix**: Wait for `CodeGotValidated` event before creating the program. Check code status via: * **Etherscan**: Search for `CodeGotValidated` events on the Router contract * **TypeScript**: Use `@vara-eth/api` to query the Router contract's validation status Mirror address mismatch [#mirror-address-mismatch] **Fix**: Mirror addresses are deterministic: `CREATE2(Router, keccak256(codeId, salt))`. If you get an unexpected address, verify you're using the correct code ID and salt. Message Errors [#message-errors] Message not being executed [#message-not-being-executed] **Possible causes**: 1. **Zero Executable Balance** — Most common. The program has no wVARA to pay for execution. 2. **Program not initialized** — First message must be from the program creator. 3. **Payload encoding error** — SCALE or ABI encoding mismatch. 4. **Validators not processing** — No executor network is reachable. The campaign validators are retired; run [your own node](/docs/vara-eth/setup/local-node). **Diagnosis steps**: ```bash # 1. Check mirror state and ETH balance ethexe tx query "$PROGRAM_ID" \ --ethereum-rpc "$RPC" \ --ethereum-router "$ROUTER" \ --sender "$SENDER" # 2. Verify executable balance top-up and message queue events on Etherscan ``` "Message panicked" in execution [#message-panicked-in-execution] The program's Rust code panicked during execution. **Common causes**: * `unwrap()` on a `None` or `Err` value * Array index out of bounds * Integer overflow * Assertion failure (`assert!`, `panic!`) **Fix**: Add proper error handling. Use `expect()` with descriptive messages for debugging. Test thoroughly with `cargo test` before deployment. Reply not received [#reply-not-received] **Possible causes**: * `callReply` was set to `false` — Reply events are only emitted when `callReply = true` * Execution hasn't been committed yet — Wait for the next batch * Program didn't generate a reply — Check program logic **Fix**: Ensure `callReply = true` when sending, and listen for `Reply` events on the Mirror. State Query Errors [#state-query-errors] "Program not found" [#program-not-found] **Fix**: Verify the actor ID / program ID is correct. Programs have both an `ActorId` (256-bit, used in the runtime) and a Mirror address (20-byte Ethereum address). Use the correct identifier for the context. State query returns empty/zero [#state-query-returns-emptyzero] **Possible causes**: * Program hasn't been initialized yet * Querying with wrong method name or arguments * State hasn't been updated since last batch **Fix**: Verify initialization by checking for `StateChanged` events. Use the exact method name from the IDL. Stale state [#stale-state] Pre-confirmed state is more recent than on-chain state. If you're reading state via Ethereum RPC (`eth_call`), you'll see the last committed batch state. For the latest pre-confirmed state, use the [Vara.eth RPC methods](/docs/vara-eth/reference/rpc-api). Frontend Errors [#frontend-errors] MetaMask chain mismatch [#metamask-chain-mismatch] ``` Chain ID 560048 not recognized ``` **Fix**: Add the Hoodi testnet to MetaMask manually. See [Wallet Setup](/docs/vara-eth/setup/wallet-setup). Transaction underpriced [#transaction-underpriced] **Fix**: Increase gas price. On testnet, gas prices can spike during high activity. sails-js type mismatch [#sails-js-type-mismatch] ``` TypeError: Cannot read property 'encode' of undefined ``` **Fix**: Regenerate TypeScript bindings from the latest IDL: ```bash cargo sails client-js program.idl ./src/generated/lib.ts ``` Ensure the IDL matches the deployed program version. Network Errors [#network-errors] RPC connection refused / timeout [#rpc-connection-refused--timeout] **Fix**: * Check the endpoint URL (no typos) * Try HTTPS vs WebSocket * Check if there's a network outage * Use a fallback endpoint if available Transaction stuck (pending) [#transaction-stuck-pending] **Fix**: The Hoodi testnet can occasionally have slow block times. Wait a few minutes. If persistent, try resubmitting with higher gas. Pre-confirmation unavailable [#pre-confirmation-unavailable] **Fix**: Pre-confirmations require a reachable executor network. The Gear-operated validators from the launch campaigns are retired, so point the Vara.eth provider at [your own node](/docs/vara-eth/setup/local-node) — or fall back to waiting for L1 finality, which needs no executor at all. Common Mistakes [#common-mistakes] | Mistake | Fix | | ----------------------------------------- | ----------------------------------------- | | Forgetting to fund Executable Balance | Top up before sending messages | | Sending init message from wrong address | Only the creator can initialize | | Using SCALE encoding when ABI is expected | Match encoding to program deployment type | | Not waiting for code validation | Check status before `createProgram` | | Hardcoding gas estimates | Use `estimateGas()` or generous limits | | Not handling async replies | Design UI around async message flow | | Using `unwrap()` in production code | Use proper error handling with `Result` | Getting Help [#getting-help] * **GitHub Issues**: [github.com/gear-tech/gear/issues](https://github.com/gear-tech/gear/issues) * **Discord**: Join the [Vara community](https://discord.gg/vara) for developer support * **Gear IDEA**: Use the web UI for visual debugging at [idea.gear-tech.io](https://idea.gear-tech.io) # Glossary Glossary [#glossary] Actor Model [#actor-model] A concurrency model where each program is an isolated "actor" with its own state and message queue. Actors communicate exclusively through asynchronous messages. Vara.eth uses this model — each program processes messages sequentially, but different programs execute in parallel. See [Gear Protocol Actor Model](/docs/gear/actor-model) for a deep dive. ActorId [#actorid] A 256-bit identifier for a program within the Vara.eth runtime. Derived from the program's creation parameters (`keccak256(codeId, salt)`). Used for inter-program messaging. Not the same as the Mirror address (which is a 20-byte Ethereum address). Batch [#batch] A collection of state transitions for multiple programs, committed to Ethereum in a single transaction by validators. Batches are the settlement unit — all state changes within a batch are atomic on L1. Blob (EIP-4844) [#blob-eip-4844] Binary Large Object introduced by Ethereum's Dencun upgrade. Vara.eth uses blobs to upload WASM code to Ethereum efficiently. Blobs are temporary (\~18 days) but their commitment (hash) is permanent. Archive nodes retain full blob data. Code ID [#code-id] `keccak256(wasm_bytes)` — a deterministic hash of the WASM binary. Used to reference validated code. Multiple program instances can share the same Code ID (same logic, different state). Once validated, anyone can create instances from it. EIP-1167 (Minimal Proxy) [#eip-1167-minimal-proxy] An Ethereum standard for lightweight proxy contracts. Each Mirror is an EIP-1167 proxy pointing to the MirrorProxy implementation. This keeps deployment costs low while enabling each program to have its own contract address. Executable Balance [#executable-balance] A program's wVARA balance managed by the Router, used to pay for message execution. Automatically consumed during processing at the `wvaraPerSecond` rate. If zero, messages queue but aren't executed. Can be funded by anyone. See [Funding Executable Balance](/docs/vara-eth/interact/funding-executable-balance). Executor [#executor] See **Validator**. Program Value Balance (Owned Balance) [#program-value-balance-owned-balance] Value held by a program's logic (exposed as `state.balance` in API reads). On the current Mirror flow, message value is carried via payable ETH and then managed by program logic. FROST (Flexible Round-Optimized Schnorr Threshold) [#frost-flexible-round-optimized-schnorr-threshold] A [threshold signature scheme](https://eprint.iacr.org/2020/852) used by validators for pre-confirmations. A threshold (e.g., 2/3) of validators must sign to produce a valid signature, enabling sub-second response times without waiting for L1 finality. Gear [#gear] The runtime and virtual machine that powers Vara.eth. [Gear](/docs/gear) processes WASM programs using the actor model with message passing, memory isolation, and deterministic execution. Also powers [Vara Network](/docs/vara-network). Injected Transaction [#injected-transaction] A Vara.eth-native transaction sent directly to validator nodes, bypassing Ethereum L1. Signed with an Ethereum private key but submitted off-chain. Cheaper (no ETH gas) and faster (sub-second) than Ethereum-side messages. References an Ethereum block for security anchoring. Used for pre-confirmations and fast UX. See [RPC API](/docs/vara-eth/reference/rpc-api#injected-transactions). IDL (Interface Definition Language) [#idl-interface-definition-language] A machine-readable description of a program's interface — its services, methods, types, and events. Generated automatically from Rust code by the [Sails framework](/docs/developing/build/sails). Used to generate client bindings for [TypeScript](/docs/developing/sails-js), Solidity, and Rust. Inheritor [#inheritor] An address specified by a program when it exits. Upon program termination, remaining balances are transferred to the inheritor, which can claim locked value via `Mirror.transferLockedValueToInheritor()`. Liquid Restaking [#liquid-restaking] The ability to stake tokens in multiple protocols simultaneously, improving capital efficiency. Vara.eth uses [Symbiotic](https://docs.symbiotic.fi/) for liquid restaking, where validators stake wVARA through vaults that can participate in multiple networks. Message [#message] The fundamental communication primitive in Vara.eth. Messages carry a payload (encoded data), optional value (payable ETH via Mirror), and are processed asynchronously. Users send messages via Mirror contracts on Ethereum. Middleware [#middleware] An Ethereum smart contract ([IMiddleware.sol](https://github.com/gear-tech/gear/blob/master/ethexe/contracts/src/IMiddleware.sol)) that manages validator operations through Symbiotic. Handles operator registration, stake vault management, election of validators for each era, rewards distribution, and slashing execution. Sits between the Router and the Symbiotic ecosystem. MirrorProxy (Small) [#mirrorproxy-small] A gas-optimized variant of the Mirror proxy for programs with minimal state. Two implementations exist: standard `MirrorProxy` and `MirrorProxySmall`. The `isSmall` flag is set during program creation to select the appropriate implementation. Mirror [#mirror] An Ethereum smart contract (EIP-1167 proxy) that represents a Vara.eth program on L1. Every program has exactly one Mirror. Users interact with the Mirror to send messages, fund execution, and read events. See [Contract Addresses](/docs/vara-eth/reference/contract-addresses) for the interface. MirrorProxy [#mirrorproxy] The singleton implementation contract that all Mirror proxies delegate to. Contains the actual logic for message queuing, event emission, and value management. Pre-confirmation [#pre-confirmation] A sub-second response from the executor network indicating the expected result of a message, before it's committed to Ethereum. Backed by FROST threshold signatures. Not yet final — finality requires L1 settlement. See [Events & State Reading](/docs/vara-eth/interact/events-state-reading). Program [#program] A WASM application running on Vara.eth. Programs have isolated state, process messages sequentially, and can communicate with other programs. Think of them as smart contracts with their own execution environment and up to 2 GB of memory. Reverse Gas Model [#reverse-gas-model] Vara.eth's cost model where programs pay for their own execution (via Executable Balance) rather than users. Users only pay standard Ethereum gas for the L1 transaction. See [Reverse Gas Model](/docs/vara-eth/economics/reverse-gas-model). Router [#router] The core settlement contract on Ethereum. Manages code validation, program creation, batch commitments, and the validator set. Single instance per network. See [Contract Addresses](/docs/vara-eth/reference/contract-addresses). Sails [#sails] A Rust framework for building Vara.eth programs. Provides macros (`#[service]`, `#[program]`), automatic [IDL generation](/docs/developing/build/sails/idl), type-safe message handling, and client code generation. See [Sails Library](/docs/developing/build/sails). SCALE (Simple Concatenated Aggregate Little-Endian) [#scale-simple-concatenated-aggregate-little-endian] A compact binary encoding format used by Substrate-based chains. Vara.eth uses SCALE internally for message encoding. When ABI is enabled, the Mirror translates between ABI and SCALE transparently. Salt [#salt] Random bytes used in `CREATE2` address computation. Combined with Code ID to produce a deterministic Mirror address. Different salts with the same Code ID create different program instances. StateTransition [#statetransition] The result of executing messages for a program — includes the new state hash, outgoing messages, events, and value transfers. Committed to Ethereum as part of a batch. stateHash [#statehash] A cryptographic commitment (hash) to a program's complete state. Stored on-chain in the Mirror contract. Updated with each batch commitment. Enables state verification without storing full state on L1. Validator (Executor) [#validator-executor] A node in the Vara.eth P2P network that executes programs, signs pre-confirmations, and commits batches to Ethereum. Validators stake wVARA and earn execution fees. See [Validators & Staking](/docs/vara-eth/economics/validators-staking). WASM (WebAssembly) [#wasm-webassembly] A portable binary instruction format. Vara.eth programs compile from Rust to WASM, which is then executed by validators in a sandboxed environment with deterministic execution guarantees. See [Gear WASM documentation](/docs/gear/WASM). wVARA (Wrapped VARA) [#wvara-wrapped-vara] An ERC-20 token on Ethereum representing VARA from the [Vara Network](/docs/vara-network). Used to fund program execution (Executable Balance), transfer value within programs, and stake as a validator. See [wVARA Overview](/docs/vara-eth/economics/wvara-overview). wvaraPerSecond [#wvarapersecond] A network parameter that determines the rate at which Executable Balance is consumed during message execution. Set by governance. Higher values mean higher execution costs. # Network Endpoints Network Endpoints [#network-endpoints] Every `gear-tech.io` / `vara.network` endpoint on this page was operated by Gear for the TestNet and MainNet campaigns and is no longer served. They stay documented because the chain IDs, explorers and deployed contracts are unchanged, and because the roles map cleanly onto replacements: * **Ethereum RPC and Beacon RPC** — plain Ethereum endpoints. Substitute any Hoodi or Mainnet provider; everything else keeps working. * **Vara.eth RPC (validator network)** — Gear's own `ethexe` validators. There is no drop-in public replacement; [run your own node](/docs/vara-eth/setup/local-node) to get pre-confirmations and Vara.eth state queries. Hoodi Testnet [#hoodi-testnet] The Vara.eth testnet deployment on Ethereum Hoodi. | Parameter | Value | | ------------------- | ------------------------------------------------ | | **Network name** | Vara.eth (Hoodi) | | **Chain ID** | `560048` | | **RPC (HTTPS)** | `https://hoodi-reth-rpc.gear-tech.io` | | **RPC (WebSocket)** | `wss://hoodi-reth-rpc.gear-tech.io/ws` | | **Base L1** | Ethereum Hoodi testnet | | **Block explorer** | [hoodi.etherscan.io](https://hoodi.etherscan.io) | MetaMask Configuration [#metamask-configuration] ``` Network Name: Hoodi Testnet RPC URL: https://hoodi-reth-rpc.gear-tech.io Chain ID: 560048 Currency: ETH Explorer: https://hoodi.etherscan.io ``` ethers.js Connection [#ethersjs-connection] ```typescript import { ethers } from "ethers"; // HTTPS (for queries and transactions) const provider = new ethers.JsonRpcProvider( "https://hoodi-reth-rpc.gear-tech.io" ); // WebSocket (for subscriptions and real-time events) const wsProvider = new ethers.WebSocketProvider( "wss://hoodi-reth-rpc.gear-tech.io/ws" ); ``` Beacon RPC (Hoodi) [#beacon-rpc-hoodi] ```text https://hoodi-lighthouse-rpc.gear-tech.io ``` Vara.eth RPC (Validator Network) [#varaeth-rpc-validator-network] For pre-confirmations and state queries via the validator P2P network. These campaign validators are retired — use the RPC port of [your own node](/docs/vara-eth/setup/local-node) instead: ```text wss://vara-eth-validator-1.gear-tech.io (retired) wss://vara-eth-validator-2.gear-tech.io (retired) wss://vara-eth-validator-3.gear-tech.io (retired) wss://vara-eth-validator-4.gear-tech.io (retired) ``` Whichever endpoint you use, it plugs into the `@vara-eth/api` providers (`WsVaraEthProvider` / `HttpVaraEthProvider`) for injected transactions and pre-confirmation flows — a local node exposes the same interface on `ws://127.0.0.1:9944`. Ethereum Mainnet [#ethereum-mainnet] | Parameter | Value | | ------------------- | ---------------------------------------- | | **Network name** | Vara.eth (Mainnet) | | **Chain ID** | `1` | | **RPC (HTTPS)** | `https://mainnet-reth-rpc.gear-tech.io` | | **RPC (WebSocket)** | `wss://mainnet-reth-rpc.gear-tech.io/ws` | | **Base L1** | Ethereum Mainnet | | **Block explorer** | [etherscan.io](https://etherscan.io) | ethers.js Connection [#ethersjs-connection-1] ```typescript import { ethers } from "ethers"; // HTTPS (for queries and transactions) const provider = new ethers.JsonRpcProvider( "https://mainnet-reth-rpc.gear-tech.io" ); // WebSocket (for subscriptions and real-time events) const wsProvider = new ethers.WebSocketProvider( "wss://mainnet-reth-rpc.gear-tech.io/ws" ); ``` Beacon RPC (Mainnet) [#beacon-rpc-mainnet] ```text https://mainnet-lighthouse-rpc.gear-tech.io ``` Vara.eth RPC (Validator Network) [#varaeth-rpc-validator-network-1] For pre-confirmations and state queries via the validator P2P network. These campaign validators are retired — use the RPC port of [your own node](/docs/vara-eth/setup/local-node) instead: ```text wss://validator-1-eth.vara.network (retired) wss://validator-2-eth.vara.network (retired) wss://validator-3-eth.vara.network (retired) wss://validator-4-eth.vara.network (retired) ``` Network Health [#network-health] Run these against whichever Ethereum RPC you use — your provider's Hoodi endpoint, or `http://127.0.0.1:8545` on a [full local stack](/docs/vara-eth/setup/local-node). Check RPC Status [#check-rpc-status] ```bash curl -X POST \ -H "Content-Type: application/json" \ -d '{"jsonrpc":"2.0","method":"eth_blockNumber","params":[],"id":1}' ``` Check Chain ID [#check-chain-id] ```bash curl -X POST \ -H "Content-Type: application/json" \ -d '{"jsonrpc":"2.0","method":"eth_chainId","params":[],"id":1}' # Should return "0x88bb0" (560048) ``` # RPC API RPC API [#rpc-api] The `gear-tech.io` and `vara.network` URLs in the examples below were operated by Gear for the launch campaigns and are no longer served. Swap the Ethereum RPC for any Hoodi or Mainnet provider, and the Vara.eth RPC for [your own node](/docs/vara-eth/setup/local-node) — see [Network Endpoints](/docs/vara-eth/reference/network-endpoints). The code itself is unchanged. Vara.eth provides **two interaction paths** for communicating with programs: 1. **Ethereum-side** — Send messages through Mirror contracts on Ethereum L1. Standard Ethereum transactions, settled on-chain. 2. **Vara.eth-side** — Send messages directly to Vara.eth validator nodes, bypassing Ethereum. Faster, cheaper, used for pre-confirmations and state queries. Both paths are unified in the [`@vara-eth/api`](https://github.com/gear-tech/gear-js/tree/main/apis/vara-eth) TypeScript library. SDK: @vara-eth/api [#sdk-vara-ethapi] The primary client library for Vara.eth. Built on [viem](https://viem.sh/), it provides typed interfaces for both Ethereum-side and Vara.eth-side operations. ```bash npm install @vara-eth/api viem@^2.39.0 kzg-wasm@1.0.0 ``` Initialization [#initialization] ```typescript import { VaraEthApi, WsVaraEthProvider, EthereumClient } from "@vara-eth/api"; import { walletClientToSigner } from "@vara-eth/api/signer"; import { createPublicClient, createWalletClient, http } from "viem"; import { privateKeyToAccount } from "viem/accounts"; const publicClient = createPublicClient({ transport: http("https://hoodi-reth-rpc.gear-tech.io"), }); const account = privateKeyToAccount("0x..."); const walletClient = createWalletClient({ account, transport: http("https://hoodi-reth-rpc.gear-tech.io"), }); const signer = walletClientToSigner(walletClient); const ethereumClient = new EthereumClient( publicClient, routerAddress, signer ); await ethereumClient.waitForInitialization(); const api = new VaraEthApi( new WsVaraEthProvider("ws://localhost:9944"), ethereumClient ); const router = ethereumClient.router; // RouterClient const wvara = ethereumClient.wvara; // WrappedVaraClient ``` ```typescript import { VaraEthApi, WsVaraEthProvider, EthereumClient } from "@vara-eth/api"; import { walletClientToSigner } from "@vara-eth/api/signer"; import { createPublicClient, createWalletClient, http } from "viem"; import { privateKeyToAccount } from "viem/accounts"; const publicClient = createPublicClient({ transport: http("https://mainnet-reth-rpc.gear-tech.io"), }); const account = privateKeyToAccount("0x..."); const walletClient = createWalletClient({ account, transport: http("https://mainnet-reth-rpc.gear-tech.io"), }); const signer = walletClientToSigner(walletClient); const ethereumClient = new EthereumClient( publicClient, routerAddress, signer ); await ethereumClient.waitForInitialization(); const api = new VaraEthApi( new WsVaraEthProvider("ws://localhost:9944"), ethereumClient ); const router = ethereumClient.router; // RouterClient const wvara = ethereumClient.wvara; // WrappedVaraClient ``` Key Classes [#key-classes] | Class | Description | | ------------------- | ------------------------------------------------------------------------- | | `VaraEthApi` | Main API for Vara.eth-side operations (queries, injected tx, state reads) | | `EthereumClient` | Ethereum-side wrapper (Router, Mirror, wVARA contracts) | | `RouterClient` | Interface to the Router contract (code validation, program creation) | | `MirrorClient` | Interface to Mirror contracts (send messages, top up balance, state hash) | | `WrappedVaraClient` | ERC-20 wVARA token operations (approve, balanceOf, allowance) | Source contracts: [Router.sol](https://github.com/gear-tech/gear/blob/master/ethexe/contracts/src/Router.sol), [Mirror.sol](https://github.com/gear-tech/gear/blob/master/ethexe/contracts/src/Mirror.sol), [WrappedVara.sol](https://github.com/gear-tech/gear/blob/master/ethexe/contracts/src/WrappedVara.sol) *** Ethereum-Side Operations [#ethereum-side-operations] Standard Ethereum transactions through Mirror contracts on L1. Program Creation [#program-creation] ```typescript const codeId = "0x..."; // From ethexe CLI upload // Create program from validated code const tx = await router.createProgram(codeId); await tx.sendAndWaitForReceipt(); const programId = await tx.getProgramId(); // Or with Solidity ABI for Etherscan compatibility const tx2 = await router.createProgramWithAbiInterface(codeId, abiAddress); await tx2.sendAndWaitForReceipt(); ``` Sending Messages [#sending-messages] ```typescript import { getMirrorClient } from "@vara-eth/api"; const mirror = getMirrorClient(programId, signer, publicClient); // Send message (payload encoded via sails-js) const tx = await mirror.sendMessage(payload, 0n); await tx.send(); // Get message details const message = await tx.getMessage(); console.log("Message ID:", message.id); // Wait for reply const { waitForReply } = await tx.setupReplyListener(); const { payload: replyPayload, replyCode, value } = await waitForReply; ``` Use [sails-js](https://github.com/gear-tech/sails/tree/master/js) to encode payloads and decode replies from your program's IDL. See [Sails-JS documentation](/docs/developing/sails-js). Managing Executable Balance [#managing-executable-balance] ```typescript // Check wVARA balance const balance = await wvara.balanceOf(ethereumClient.accountAddress); // Approve program to spend wVARA const approveTx = await wvara.approve(programId, BigInt(10 * 1e12)); await approveTx.sendAndWaitForReceipt(); // Top up program's Executable Balance const topUpTx = await mirror.executableBalanceTopUp(BigInt(10 * 1e12)); const { status } = await topUpTx.sendAndWaitForReceipt(); ``` Checking Program State (Ethereum-side) [#checking-program-state-ethereum-side] ```typescript // Code validation status const codeState = await router.codeState(codeId); // 'Validated' | 'Rejected' | 'Unknown' // Program's code ID const programCodeId = await router.programCodeId(programId); // State hash from Mirror const stateHash = await mirror.stateHash(); // Program nonce const nonce = await mirror.nonce(); ``` TxManager [#txmanager] All contract write methods return a `TxManager` that handles the transaction lifecycle: ```typescript const tx = await router.createProgram(codeId); // Send and get hash const response = await tx.send(); console.log("Tx hash:", response.hash); // Send and wait for receipt const receipt = await tx.sendAndWaitForReceipt(); // Estimate gas const gasEstimate = await tx.estimateGas(); // Find events in receipt const event = await tx.findEvent("ProgramCreated"); ``` *** Vara.eth-Side Operations [#varaeth-side-operations] Direct communication with Vara.eth validator nodes. This is the **primary path for pre-confirmations** and read-only state queries -- faster and cheaper than going through Ethereum. Connecting to Vara.eth Nodes [#connecting-to-varaeth-nodes] ```typescript import { VaraEthApi, WsVaraEthProvider, HttpVaraEthProvider } from "@vara-eth/api"; // WebSocket — for subscriptions and real-time updates const api = new VaraEthApi( new WsVaraEthProvider("ws://localhost:9944"), ethereumClient ); // HTTP — for one-time queries const api = new VaraEthApi( new HttpVaraEthProvider("http://localhost:9944"), ethereumClient ); // Disconnect when done await api.provider.disconnect(); ``` Injected Transactions [#injected-transactions] Injected transactions are **Vara.eth-native transactions sent directly to validators**, bypassing Ethereum entirely. They are: * Signed with your Ethereum private key but submitted off-chain * Cheaper (no Ethereum gas costs) * Faster (no L1 block time wait) * Reference an Ethereum block for security anchoring This is the primary mechanism for pre-confirmations. ```typescript // Create injected transaction const injected = await api.createInjectedTransaction({ destination: programId, payload: encodedPayload, value: 0n, // Auto-populated if not provided: // recipient: validator address (auto-selected) // referenceBlock: recent Ethereum block hash // salt: random salt for uniqueness }); // Send transaction const result = await injected.send(); // Send and wait for promise (includes reply) const promise = await injected.sendAndWaitForPromise(); // Validate the FROST threshold signature await promise.validateSignature(); ``` Configuring Injected Transactions [#configuring-injected-transactions] ```typescript const injected = await api.createInjectedTransaction({ destination: programId, payload: encodedPayload, value: 1000n, referenceBlock: blockHash, // Specific Ethereum block salt: "0x030405", // Custom salt recipient: validatorAddress, // Specific validator }); // Modify using fluent API injected .setValue(2000n) .setSalt("0x060708"); // Access properties const messageId = injected.messageId; // Update fields await injected.setReferenceBlock(); // Fetch latest block await injected.setRecipient(); // Auto-select validator await injected.setRecipient(address); // Specific validator ``` Querying Program Data [#querying-program-data] ```typescript // List all program IDs const programIds = await api.query.program.getIds(); // Get program's code ID const codeId = await api.query.program.codeId(programId); // Read full program state by state hash const stateHash = await mirror.stateHash(); const state = await api.query.program.readState(stateHash); if ("Active" in state.program) { console.log("Program is active"); console.log("Balance:", state.balance); } ``` Reading State via calculateReplyForHandle [#reading-state-via-calculatereplyforhandle] Perform **read-only queries** on program state without sending transactions or paying gas. This simulates a message and returns what the program would reply: ```typescript // Encode query payload using sails-js const queryPayload = sails.services.Counter.queries.GetValue.encodePayload(); // Calculate reply (read-only, free) const reply = await api.call.program.calculateReplyForHandle( await ethereumClient.getAccountAddress(), // Source address programId, // Program to query queryPayload // Encoded query ); // Decode result using sails-js const value = sails.services.Counter.queries.GetValue.decodeResult( reply.payload ); console.log("Current counter value:", value); ``` This is useful for: * Reading program state without modifying it * Testing message payloads before sending * Querying computed values from programs * Building responsive UIs with instant state reads *** Interaction Flow Summary [#interaction-flow-summary] ``` ┌──────────────────────┐ │ Your dApp │ └──────┬───────┬───────┘ │ │ Ethereum-side│ │Vara.eth-side │ │ ┌──────▼──┐ ┌──▼──────────┐ │ Mirror │ │ VaraEthApi │ │Contract │ │ (direct) │ └──────┬──┘ └──┬───────────┘ │ │ ┌──────▼───────▼──────────┐ │ Vara.eth Validators │ │ (WASM execution) │ └─────────┬───────────────┘ │ ┌─────────▼───────────────┐ │ Ethereum L1 │ │ (batch settlement) │ └─────────────────────────┘ ``` | Path | Use Case | Cost | Latency | | ------------------------------ | ---------------------------------------------------------- | --------------- | ---------------- | | **Ethereum-side** (Mirror) | On-chain settlement, value transfers, Solidity integration | ETH gas + wVARA | \~12s (L1 block) | | **Vara.eth-side** (Injected) | Pre-confirmations, fast UX, state queries | No ETH gas | Sub-second | | **Read-only** (calculateReply) | State queries, testing payloads | Free | Instant | *** JSON-RPC Methods (Low-Level) [#json-rpc-methods-low-level] For direct JSON-RPC access without the SDK: Connection [#connection] ``` # Mainnet Ethereum RPC (L1): https://mainnet-reth-rpc.gear-tech.io # Testnet (Hoodi) Ethereum RPC (L1): https://hoodi-reth-rpc.gear-tech.io # Vara.eth validator nodes (local or public) Vara.eth RPC: http://127.0.0.1:9944 Vara.eth WS: ws://127.0.0.1:9944 ``` Standard Ethereum Methods [#standard-ethereum-methods] Use Ethereum RPC for standard `eth_*` methods (`eth_blockNumber`, `eth_call`, `eth_sendRawTransaction`, `eth_getLogs`, etc.). Vara.eth Extension Methods [#varaeth-extension-methods] | Method | Description | | ---------------------------------------------------------------------------------- | ------------------------------------------------ | | `program_ids` | List known program IDs | | `program_codeId` | Get code ID for a program | | `program_readState` | Read full program state by state hash | | `program_calculateReplyForHandle` | Read-only simulation of a message call | | `block_header` | Get latest block header (or by hash) | | `injected_sendTransaction` | Send an injected transaction to validators | | `injected_sendTransactionAndWatch` / `injected_sendTransactionAndWatchUnsubscribe` | Subscribe to injected tx promise and unsubscribe | Additional node methods (not wrapped by high-level SDK helpers): | Method | Description | | ------------------------------------------------------------------------------------------ | -------------------------------------------------- | | `program_readQueue` / `program_readWaitlist` / `program_readStash` / `program_readMailbox` | Inspect internal program queues/storage fragments | | `program_readFullState` | Read extended full state snapshot | | `program_readPages` / `program_readPageData` | Inspect memory pages/page data | | `block_events` / `block_outcome` | Inspect block-level request events and transitions | | `code_getOriginal` / `code_getInstrumented` | Fetch uploaded/or instrumented code blobs | Error Codes [#error-codes] | Code | Meaning | | -------- | ------------------------------------------------------------------ | | `-32600` | Invalid request | | `-32601` | Method not found | | `-32602` | Invalid params | | `-32603` | Internal error | | `8000` | Node runtime/database/internal error (ethexe RPC service-specific) | # Agent Skills **Vara.eth Skills** is an open-source pack of agentic playbooks specialized for Vara.eth — contract authoring, deployment via `ethexe`, message-passing, frontend wiring, and Solidity integration through generated ABIs. The skills are tool-agnostic: any AI coding agent that can read files — Claude Code, Codex, Cursor, Copilot — can use them. Just clone the repo and point your agent at the skills directory. 👉 **Repository:** [github.com/gear-foundation/vara-eth-skills](https://github.com/gear-foundation/vara-eth-skills) See [Vara Skills](/docs/vara-network/api/tooling/vara-skills) for the Sails-focused pack covering Vara Mainnet. What's Inside [#whats-inside] The repo is organized into four layers: * **Skills** — agent-facing instructions that drive Vara.eth-specific workflows * **Playbooks** — step-by-step workflow documentation (ABI generation, `ethexe` CLI, injected transactions, etc.) * **References** — review checklists, source map into the `ethexe/` subtree of the [Gear repository](https://github.com/gear-tech/gear), troubleshooting notes * **Examples** — runnable, verifiable demos (vault, escrow, digit-recognition frontend) Installation [#installation] Clone the repository and point your agent at it: ```bash git clone https://github.com/gear-foundation/vara-eth-skills ``` Then load the skills into your agent of choice. The skills are plain Markdown files — any agent that reads context files can use them. Skill Catalog [#skill-catalog] | Skill | Purpose | | ------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------- | | `vara-eth-contract-writer` | Author Rust Sails contracts for Vara.eth — `ethexe` feature, ABI-safe types, event patterns, executable balance model | | `vara-eth-app-builder` | Deploy and integrate around existing contracts — upload WASM, create Mirror, fund executable balance, send messages, read state | | `vara-eth-full-app-builder` | Build a complete user-facing dApp — contract + wallet + frontend | | `vara-eth-injected-app-builder` | Apps that use [injected transactions](/docs/vara-eth/reference/rpc-api#injected-transactions) for sub-second writes and RPC for state reads | | `vara-eth-mirror-adapter-frontend-builder` | Browser frontends that call Solidity adapters backed by Vara.eth | | `vara-eth-solidity-integrator` | Solidity contracts that talk to Vara.eth programs through generated ABI interfaces and async callbacks | Requirements [#requirements] * **Rust** with `cargo` and the WASM target — see [Tools Install](/docs/vara-eth/setup/tools-install) * **Node.js / npm** for TypeScript examples and frontend builds * **Basic Ethereum tooling** familiarity for ABI workflows (only for the Solidity integration skill) Verifying Examples [#verifying-examples] Each example in the repo ships with a verification command. From the repo root: ```bash # Rust contract examples cargo build --release cargo test --release # Vault TypeScript example cd examples/vault npm run check # Digit-recognition injected frontend cd examples/digit-recognition-injected-frontend npm run check npm run build # Escrow Solidity adapter cd examples/escrow cargo build --release cargo sails sol --idl-path target/wasm32-gear/release/order_escrow.idl ``` Implementation Authority [#implementation-authority] The primary implementation lives in the [`ethexe/`](https://github.com/gear-tech/gear/tree/master/ethexe) subtree of the main Gear repository. All skills and playbooks cite that source for CLI behavior, runtime mechanics, RPC methods, and deployment lifecycle details. # Local Node Local Node [#local-node] Anyone can run their own Vara.eth node and experiment. There are two modes: 1. **Node-only mode** — connect your local node to an existing Ethereum Router (for example, Hoodi). Requires a working Ethereum RPC endpoint. 2. **Full local stack (recommended)** — run local Ethereum + deploy Router yourself + connect node to it. Fully self-contained. The Ethereum RPC endpoints Gear operated for the launch campaigns are no longer served, so `--ethereum-rpc` below is a placeholder. Node-only mode still works against the deployed Hoodi and Mainnet Routers — point it at any Ethereum RPC provider for that chain. The full local stack depends on nothing external at all. Prerequisites [#prerequisites] Before starting, complete [Tools Install](/docs/vara-eth/setup/tools-install) (at minimum: `ethexe` CLI). Mode 1: Node-Only (No Router Deployment) [#mode-1-node-only-no-router-deployment] Use this when you already have a Router address (for example on Hoodi). The Routers from the campaigns are still deployed — see [Contract Addresses](/docs/vara-eth/reference/contract-addresses). ```bash ethexe run \ --ethereum-rpc "" \ --ethereum-router "0xE549b0AfEdA978271FF7E712232B9F7f39A0b060" \ --rpc-port 9944 ``` `--ethereum-rpc` takes a plain Ethereum node WebSocket URL, so any Hoodi (or Mainnet) provider works. Node-only mode is enough to inspect behavior, test RPC flows, and run integration checks against an existing network Router — provided you have a working Ethereum RPC for that network. Mode 2: Full Local Stack (Deploy Router Yourself) [#mode-2-full-local-stack-deploy-router-yourself] Use this when you want a fully isolated environment. 1) Start local Ethereum [#1-start-local-ethereum] ```bash anvil --port 8545 --block-time 2 ``` 2) Deploy Router/WVARA contracts [#2-deploy-routerwvara-contracts] ```bash git clone https://github.com/gear-tech/gear.git cd gear/ethexe/contracts forge install forge script script/Router.s.sol:RouterScript \ --rpc-url "ws://127.0.0.1:8545" \ --broadcast ``` 3) Get deployed Router proxy address [#3-get-deployed-router-proxy-address] ```bash export ROUTER=$(jq -r '.transactions[] | select(.contractName=="TransparentUpgradeableProxy") | .contractAddress' \ broadcast/Router.s.sol/31337/run-latest.json | head -n1) echo "$ROUTER" ``` 4) Run ethexe node against local Router [#4-run-ethexe-node-against-local-router] ```bash ethexe run \ --ethereum-rpc "ws://127.0.0.1:8545" \ --ethereum-router "$ROUTER" \ --rpc-port 9944 ``` Available Endpoints [#available-endpoints] Once running, these endpoints become available: | Endpoint | Address | Description | | ------------------- | ----------------------- | ----------------------------------------- | | **Ethexe RPC** | `http://127.0.0.1:9944` | Main RPC endpoint for Vara.eth operations | | **Ethereum RPC** | `ws://127.0.0.1:8545` | Ethereum-compatible RPC (Anvil) | | **Router Contract** | `` | Router used by your node | Quick Test [#quick-test] Verify the node is running: ```bash # Check Ethexe RPC curl http://127.0.0.1:9944 -X POST -H "Content-Type: application/json" \ -d '{"jsonrpc":"2.0","method":"eth_blockNumber","params":[],"id":1}' # Check Ethereum RPC cast block-number --rpc-url http://127.0.0.1:8545 ``` Advanced Configuration [#advanced-configuration] Custom RPC Port [#custom-rpc-port] ```bash ethexe run \ --ethereum-rpc "" \ --ethereum-router "0xE549b0AfEdA978271FF7E712232B9F7f39A0b060" \ --rpc-port 8080 ``` Disable P2P Networking [#disable-p2p-networking] ```bash ethexe run \ --ethereum-rpc "" \ --ethereum-router "0xE549b0AfEdA978271FF7E712232B9F7f39A0b060" \ --no-network ``` Enable Debug Logging [#enable-debug-logging] ```bash RUST_LOG=debug ethexe run \ --ethereum-rpc "" \ --ethereum-router "0xE549b0AfEdA978271FF7E712232B9F7f39A0b060" ``` Stop the node and delete the local database to reset: ```bash rm -rf ~/.ethexe/db ``` Troubleshooting [#troubleshooting] **"Connection refused" on port 8545**\ → Start Anvil manually: `anvil --port 8545` (only for full local stack) **"Contract not found"**\ → Re-deploy Router contracts and update `--ethereum-router` **Slow compilation**\ → Use prebuilt `ethexe` binary if you do not need custom source changes Switch Back to Hoodi [#switch-back-to-hoodi] To point back at the deployed Hoodi Router: 1. Stop your local node 2. Run `ethexe tx` commands with your Hoodi RPC and the Router address (or put them into `.ethexe.toml`): ```bash ethexe tx query "$PROGRAM_ID" \ --ethereum-rpc "" \ --ethereum-router "0xE549b0AfEdA978271FF7E712232B9F7f39A0b060" \ --sender "$SENDER" ``` → [Networks](/docs/vara-eth/setup/networks) # Networks Networks [#networks] Vara.eth runs on Ethereum. The testnet is deployed on **Ethereum Hoodi**, and mainnet on **Ethereum Mainnet**. The `gear-tech.io` RPC endpoints below were operated by Gear for the launch campaigns and are no longer served. Chain IDs, explorers and deployed contracts are unchanged — substitute any Ethereum RPC provider for Hoodi or Mainnet, and [run your own node](/docs/vara-eth/setup/local-node) for the Vara.eth side. Testnet (Ethereum Hoodi) [#testnet-ethereum-hoodi] The Vara.eth testnet deployment on Ethereum Hoodi: * Chain ID: `560048` * Ethereum RPC: any Hoodi provider (the retired Gear endpoint was `https://hoodi-reth-rpc.gear-tech.io`) * Explorer: [hoodi.etherscan.io](https://hoodi.etherscan.io) For the full endpoint matrix (HTTPS, WebSocket, health checks, and connection examples), use: → [Network Endpoints](/docs/vara-eth/reference/network-endpoints) For Router and related contract addresses, use: → [Contract Addresses](/docs/vara-eth/reference/contract-addresses) Mainnet (Ethereum Mainnet) [#mainnet-ethereum-mainnet] The Vara.eth mainnet deployment on Ethereum Mainnet: * Chain ID: `1` * Ethereum RPC: any Ethereum Mainnet provider (the retired Gear endpoint was `https://mainnet-reth-rpc.gear-tech.io`) * Explorer: [etherscan.io](https://etherscan.io) For the full endpoint matrix (HTTPS, WebSocket, Beacon, Validator RPCs), use: → [Network Endpoints](/docs/vara-eth/reference/network-endpoints) For Router and wVARA contract addresses, use: → [Contract Addresses](/docs/vara-eth/reference/contract-addresses) # Tools Install Tools Install [#tools-install] Vara.eth development requires a few tools. This guide walks through installing each one. Rust Toolchain [#rust-toolchain] Vara.eth programs are written in Rust and compiled to WASM. Install the Rust toolchain: ```bash curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh ``` Add the WASM compilation target: ```bash rustup target add wasm32v1-none ``` Sails builds with `cargo build --release` and manages the effective WASM target for you. Do not pass a manual `--target` flag in normal Sails workflows. Build artifacts are generated in `target/wasm32-gear/release/`. Verify: ```bash rustc --version cargo --version ``` Rust 1.80+ is recommended. Run `rustup update` if you need to upgrade. Sails CLI [#sails-cli] [Sails](/docs/developing/build/sails) is the developer toolkit for writing Gear programs. The CLI scaffolds projects, builds WASM binaries, and generates interfaces. Install via Cargo: ```bash cargo install sails-cli ``` Verify: ```bash cargo sails --version ``` Key Commands [#key-commands] | Command | Description | | ---------------------------------------- | ----------------------------------- | | `cargo sails new ` | Create a new Sails program project | | `cargo build --release` | Compile your program to WASM | | `cargo sails sol --idl-path ` | Generate Solidity ABI from IDL | | `cargo sails client-js []` | Generate TypeScript client from IDL | | `cargo sails client-rs ` | Generate Rust client from IDL | When you build with `cargo build --release`, Sails automatically generates `.opt.wasm` and `.idl` files in `target/wasm32-gear/release/`. → [Sails Library](/docs/developing/build/sails) ethexe CLI [#ethexe-cli] The `ethexe` CLI is the command-line tool for interacting with Vara.eth — uploading code, creating programs, sending messages, and reading state. Download the latest binary: ```bash # Visit https://get.gear.rs for platform-specific downloads curl -L https://get.gear.rs/ethexe -o ethexe chmod +x ethexe ``` Verify: ```bash ethexe --version ``` Key Commands [#key-commands-1] | Command | Description | | ------------------------------------------------------- | -------------------------------------------- | | `ethexe key insert ` | Store a private key for signing transactions | | `ethexe tx upload ` | Upload WASM code to Ethereum | | `ethexe tx create ` | Create a new program instance (Mirror) | | `ethexe tx send-message ` | Send message to a program | | `ethexe tx query ` | Query mirror state on Ethereum | → [CLI Reference](/docs/vara-eth/reference/cli-reference) Optional: Foundry [#optional-foundry] If you plan to interact with Vara.eth from Solidity or deploy ABI interface contracts: ```bash curl -L https://foundry.paradigm.xyz | bash foundryup ``` This gives you `forge`, `cast`, and `anvil` for Solidity development. Hardhat works too — Vara.eth Mirror contracts are standard Ethereum contracts visible in Etherscan, Tenderly, and any EVM framework. Optional: TypeScript SDK [#optional-typescript-sdk] For frontend development or using the TypeScript SDK: ```bash npm install @vara-eth/api sails-js ``` **[@vara-eth/api](https://github.com/gear-tech/gear-js/blob/main/apis/vara-eth/README.md)** — Core SDK with EthereumClient, VaraEthApi, injected transactions, state queries\ **sails-js** — Payload encoding/decoding from your program's IDL Generate the TypeScript client from your program's IDL using the Sails CLI: ```bash cargo sails client-js ./target/wasm32-gear/release/my_program.idl ./src/generated/lib.ts ``` → [Frontend Integration](/docs/vara-eth/integrate/frontend-integration) Verify Your Setup [#verify-your-setup] Quick checklist: * `rustc --version` → 1.80+ * `rustup target list --installed` → includes `wasm32v1-none` * `cargo sails --version` → installed * `ethexe --version` → installed * MetaMask connected to Hoodi with test tokens → [Wallet Setup](/docs/vara-eth/setup/wallet-setup) | [Networks](/docs/vara-eth/setup/networks) # Wallet Setup Wallet Setup [#wallet-setup] Vara.eth uses standard Ethereum wallets. If you have MetaMask, you're ready to go. The Vara.eth faucet and the Gear-operated RPC endpoints served the launch campaigns and are no longer running. Hoodi itself is unaffected: add the network with any public RPC, and get gas ETH from a public Hoodi faucet. For wVARA, run a [full local stack](/docs/vara-eth/setup/local-node), which deploys its own Router and WVARA contracts and lets you control the supply. Add Hoodi Network [#add-hoodi-network] The fastest way is to visit [Chainlist](https://chainlist.org/chain/560048) and click "Add to MetaMask." **Manual setup:** 1. Open MetaMask → Settings → Networks → Add Network 2. Fill in the current Hoodi values from [Networks](/docs/vara-eth/setup/networks) 3. Save Get Test ETH [#get-test-eth] You need test ETH on Hoodi to pay for L1 transaction fees (sending messages to Mirror contracts, creating programs). Get Hoodi ETH from any public Ethereum Hoodi faucet, such as [hoodifaucet.io](https://www.hoodifaucet.io/). The Vara.eth campaign faucet is no longer running, but it only ever distributed the same public Hoodi ETH. Even though Vara.eth uses the [reverse gas model](/docs/vara-eth/economics/reverse-gas-model) (programs pay for execution), you still need ETH for the base Ethereum transaction fee. Get Test wVARA [#get-test-wvara] wVARA funds your program's [Executable Balance](/docs/vara-eth/interact/funding-executable-balance). Without wVARA, your program cannot execute messages. The Vara.eth campaign faucet distributed Hoodi wVARA and is no longer running. On a [full local stack](/docs/vara-eth/setup/local-node), the WVARA contract is deployed together with your Router and you control the supply. On Ethereum Mainnet, wVARA is obtained by bridging VARA — see [Getting wVARA](/docs/vara-eth/economics/getting-wvara). Once you have wVARA, fund your program via: * `executableBalanceTopUp(value)` on the Mirror contract * `ethexe` CLI * TypeScript SDK → [wVARA Overview](/docs/vara-eth/economics/wvara-overview) Verify Your Setup [#verify-your-setup] Quick checklist: * MetaMask switched to Hoodi network * ETH balance > 0 * wVARA balance > 0 (add token via [Contract Addresses](/docs/vara-eth/reference/contract-addresses)) * Your address visible on [Hoodi Etherscan](https://hoodi.etherscan.io) For CLI Users [#for-cli-users] If you prefer command-line workflows, insert your private key into `ethexe`: ```bash ethexe key insert ``` Only use test wallets with testnet keys. Never use your mainnet private key in development environments. # Deep Dives Deep Dives [#deep-dives] Comprehensive technical documentation for developers, architects, and researchers who need detailed knowledge of Vara.eth internals. Official Specifications [#official-specifications] | Document | Version | Contents | | ------------------------------------------------------------------------------------- | ------- | ------------------------------------------------------------------------------------------- | | [Vara.eth Whitepaper](https://eth.vara.network/whitepaper) | v0.3 | Complete system design: architecture, components, security model, economics, use cases | | [Technical Documentation](https://eth.vara.network/whitepaper/vara.eth-technical.pdf) | v0.2 | Implementation specification: actors, components, system flows, design decisions, economics | System Architecture [#system-architecture] Core Components [#core-components] | Component | Purpose | Interface | | -------------- | ------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------- | | **Router** | Central L1 settlement contract. Validates code, creates programs, commits batches, coordinates with Middleware | [IRouter.sol](https://github.com/gear-tech/gear/blob/master/ethexe/contracts/src/IRouter.sol) | | **Middleware** | Validator management via [Symbiotic](https://docs.symbiotic.fi/) liquid restaking. Handles registration, elections, rewards, slashing | [IMiddleware.sol](https://github.com/gear-tech/gear/blob/master/ethexe/contracts/src/IMiddleware.sol) | | **Mirror** | Per-program L1 proxy (EIP-1167). Queues messages, emits events, stores `stateHash`. Variants: `MirrorProxy`, `MirrorProxySmall` | [IMirror.sol](https://github.com/gear-tech/gear/blob/master/ethexe/contracts/src/IMirror.sol) | | **Executors** | Decentralized validator network. Runs WASM programs, produces FROST pre-confirmations, commits batches | → [Validators & Staking](/docs/vara-eth/economics/validators-staking) | | **Sails** | Rust developer framework. Macros (`#[service]`, `#[program]`), IDL generation, client bindings | → [Sails Workflow](/docs/vara-eth/build/sails-workflow) | → [Contract Addresses](/docs/vara-eth/reference/contract-addresses) | [Program Basics](/docs/vara-eth/build/program-basics) Execution and Settlement [#execution-and-settlement] Complete execution flow — from user transaction to L1 settlement — is described in [Technical Documentation, Section 4: System Flows](https://eth.vara.network/whitepaper/vara.eth-technical.pdf). **Key characteristics:** * Message ordering: deterministic by `(blockNumber, txIndex, logIndex)` * Pre-confirmations: instant via FROST threshold signatures * Settlement: batch commits to Router with validator signature verification **Validator management:** * Era-based rotation (\~1 day intervals) via `Middleware.makeElectionAt()` * Economic security through wVARA staking in Symbiotic vaults * Slashing for incorrect execution → [Validators & Staking](/docs/vara-eth/economics/validators-staking) Security Modes [#security-modes] | Mode | Status | Verification Method | | ------------------------ | ------------------------------------- | --------------------------------------------------------------- | | **PoA (current phase)** | Proven in MainNet & TestNet campaigns | Controlled validator set operated by the protocol team | | **Liquid Restaking PoS** | Planned | Economic security via wVARA stake and slashing | | **ZK-Verified** | Future | Mathematical certainty via on-chain ZK-SNARK proof verification | → [Vara.eth Whitepaper, Section 4: Security](https://eth.vara.network/whitepaper) Technology Foundation [#technology-foundation] Vara.eth is built on the Gear Protocol stack, a production-grade WASM runtime proven on Vara Network. Gear Protocol [#gear-protocol] Actor Model runtime with persistent memory and WASM execution. Powers both Vara Network and Vara.eth. Actor isolation enables native parallelism — programs run concurrently without coordination overhead. → [Gear Protocol](/docs/gear) Actor Model [#actor-model] Each program is an isolated actor with private state and message queue. Inter-program communication is asynchronous message-passing only. No shared state means no contention. → [Actor Model](/docs/gear/actor-model) Persistent Memory [#persistent-memory] Programs maintain state across executions without explicit storage APIs. Write Rust code as if state is always in memory — simpler than Ethereum's storage model. → [Persistent Memory](/docs/gear/persist-memory) WASM Execution [#wasm-execution] WebAssembly provides portable, sandboxed, deterministic execution. Programs compile once, run anywhere — validators execute identical bytecode. Supports up to 2 GB memory per program vs. EVM's gas constraints. → [WASM Execution](/docs/gear/WASM) Cross-Chain Bridge [#cross-chain-bridge] Vara.eth programs can interact with Vara Network through a ZK-based trustless bridge. | Resource | Description | | --------------------------------------------------------------------------- | ----------------------------------------------- | | [Bridge Technical Overview](/docs/vara-network/bridge/bridge_technical) | ZK architecture using Plonky2 and Gnark provers | | [Bridge Built-in Actor](/docs/developing/build/builtinactors/bia-bridge) | On-chain actor API for cross-chain messages | | [BLS12-381 Built-in Actor](/docs/developing/build/builtinactors/bia-bls) | Native BLS signatures for efficient aggregation | | [Vara → Ethereum Guide](/docs/vara-network/bridge/developer-guide-vara-eth) | Cross-chain application patterns | | [Ethereum → Vara Guide](/docs/vara-network/bridge/developer-guide-eth-vara) | Receiving Ethereum events on Vara | Source Code [#source-code] Main Repository [#main-repository] [gear-tech/gear/ethexe](https://github.com/gear-tech/gear/tree/master/ethexe) | Directory | Purpose | | -------------------------------------------------------------------------------- | ------------------------------------------------------------- | | [contracts/](https://github.com/gear-tech/gear/tree/master/ethexe/contracts) | Solidity contracts: Router, Mirror, Middleware, WrappedVara | | [cli/](https://github.com/gear-tech/gear/tree/master/ethexe/cli) | `ethexe` CLI for code upload and program management | | [rpc/](https://github.com/gear-tech/gear/tree/master/ethexe/rpc) | JSON-RPC server (`VaraEthApi`) for Vara.eth-side interactions | | [observer/](https://github.com/gear-tech/gear/tree/master/ethexe/observer) | L1 event observer (monitors Router/Mirror events) | | [processor/](https://github.com/gear-tech/gear/tree/master/ethexe/processor) | Message queue processor (executes WASM programs) | | [consensus/](https://github.com/gear-tech/gear/tree/master/ethexe/consensus) | FROST threshold signature implementation | | [network/](https://github.com/gear-tech/gear/tree/master/ethexe/network) | P2P layer for validator communication | | [blob-loader/](https://github.com/gear-tech/gear/tree/master/ethexe/blob-loader) | EIP-4844 blob fetcher (retrieves WASM code) | | [runtime/](https://github.com/gear-tech/gear/tree/master/ethexe/runtime) | Gear WASM runtime integration | | [service/](https://github.com/gear-tech/gear/tree/master/ethexe/service) | Main validator node service | Related Repositories [#related-repositories] | Repository | Purpose | | ------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | [Sails framework](https://github.com/gear-tech/sails) | Rust macros, IDL generation, client SDKs | | [`@vara-eth/api`](https://github.com/gear-tech/gear-js/tree/main/apis/vara-eth) | TypeScript SDK (Ethereum-side + Vara.eth-side) | | [One of Us](https://github.com/gear-foundation/one-of-us) | Full reference application from the completed launch campaign — demonstrated instant UI on Ethereum, with 931 members joining ([campaign recap on X](https://x.com/VaraNetwork/status/2016895724873777269)) | | [Robo Miner](https://github.com/gear-foundation/robo-miner) | Campaign reference stack for a live agent arena — a Phaser mining game backed by Vara.eth programs (world, proxy, RES rewards, redeem) with a leaderboard and an Agent Skill Pack so AI agents can play ([campaign announcement on X](https://x.com/VaraNetwork/status/2079896261478818255)) | # Pick Your Path Choose the path that matches your goal. Each track is designed to get you productive quickly. Build with an AI Agent (Recommended) [#build-with-an-ai-agent-recommended] **Modern development runs on AI agents — describe what you want, the agent builds it.** Whether you're shipping a production dApp or just exploring an idea, load [Vara.eth Agent Skills](/docs/vara-eth/setup/agent-skills) into Claude Code, Codex, or Cursor and the agent handles the full loop: Sails contract, `ethexe` deployment, ABI generation, frontend wiring, and Solidity integration. The skills cite real APIs and verified patterns on every run, so you don't get hallucinated imports or broken builds. **Available right now**, not on a roadmap. → Start: [Agent Skills](/docs/vara-eth/setup/agent-skills) **Need:** an AI coding agent (Claude Code, Codex, Cursor). No specific language background required for the common cases. Build Programs [#build-programs] **Write application logic in Rust, compile to WASM, deploy to Ethereum.** Programs run off-chain in validator networks but maintain state on L1. You'll use the [Sails framework](/docs/developing/build/sails) and work with the [actor model](/docs/gear/actor-model). → Start: [Quick Start](/docs/vara-eth/build/quickstart) (5 minutes to first deploy)\ → Full workflow: [Program Basics](/docs/vara-eth/build/program-basics) → [Testing](/docs/vara-eth/build/testing-debugging) → [Deploy](/docs/vara-eth/deploy/upload-validation) **Need:** Basic Rust. No Solidity required. Integrate from Solidity [#integrate-from-solidity] **Call Vara.eth programs from your existing Ethereum contracts.** Offload heavy computation (order books, AI inference, complex game logic) while keeping your core contracts on L1. → Start: [Solidity Integration](/docs/vara-eth/integrate/solidity-integration)\ → Patterns: [Choosing a Pattern](/docs/vara-eth/integrate/choosing-a-pattern) **Need:** Solidity, Foundry or Hardhat. Integrate from Frontend [#integrate-from-frontend] **Connect web apps to Vara.eth programs with TypeScript.** Use typed clients generated from your program's IDL. Handle both Ethereum-side (wallet signatures) and Vara.eth-side (instant pre-confirmations) flows. → Start: [Frontend Integration](/docs/vara-eth/integrate/frontend-integration)\ → Library docs: [Sails-JS](/docs/developing/sails-js) (typed clients, React hooks) **Need:** JavaScript/TypeScript, basic web3 (ethers.js or viem). Integrate from Backend [#integrate-from-backend] **Build services that interact with Vara.eth programs.** Run off-chain workers, automate program interactions, or build indexers. Use the same TypeScript SDK as frontends or call JSON-RPC directly. → Start: [Backend Integration](/docs/vara-eth/integrate/backend-integration)\ → API reference: [RPC API](/docs/vara-eth/reference/rpc-api) **Need:** Any language with HTTP client. TypeScript SDK available. Learn the System [#learn-the-system] **Understand how Vara.eth works: validators, settlement, economics.** Read if you're evaluating Vara.eth for your project, contributing to the protocol, or building infrastructure. **Recommended order:** 1. [What is Vara.eth?](/docs/vara-eth/start-here/what-is-vara-eth) — mental model 2. [Why It Matters](/docs/vara-eth/start-here/why-it-matters) — core innovations 3. [Vara.eth Whitepaper](https://eth.vara.network/whitepaper) — complete design 4. [Technical Documentation](https://eth.vara.network/whitepaper/vara.eth-technical.pdf) — implementation spec 5. [Deep Dives](/docs/vara-eth/start-here/deep-dives) — architecture details, source code **Economics and operations:** * [wVARA Overview](/docs/vara-eth/economics/wvara-overview) — token model * [Reverse Gas](/docs/vara-eth/economics/reverse-gas-model) — programs pay, not users * [Validators & Staking](/docs/vara-eth/economics/validators-staking) — how validation works **Foundation:** * [Gear Protocol](/docs/gear) — underlying runtime * [Actor Model](/docs/gear/actor-model) — execution model * [Bridge Technical Overview](/docs/vara-network/bridge/bridge_technical) — cross-chain architecture # What is Vara.eth? What is Vara.eth? [#what-is-varaeth] Vara.eth is a **high-performance application platform** built directly into Ethereum. It lets you build applications that were previously impossible on-chain — real-time order books, on-chain AI inference, complex game logic — with instant feedback and no gas fees for users. Your program runs off-chain in a decentralized network of validators, but its state is anchored to Ethereum L1. Users interact through normal Ethereum contracts. No bridging, no new chain, no fragmented liquidity. Under the hood, Vara.eth uses the [Gear Protocol](/docs/gear) execution engine — the same battle-tested runtime that powers [Vara Network](https://vara.network). Mental Model [#mental-model] When you deploy a Vara.eth program, here's what actually happens: Program [#program] Your Rust code compiled to WASM. It runs off-chain in the Gear runtime but behaves like an isolated Ethereum contract with its own state and message queue ([actor model](/docs/gear/actor-model)). Mirror Contract [#mirror-contract] The on-chain "face" of your program. Users call it like any Solidity contract: `mirror.sendMessage(payload)`. The Mirror represents your program's state on Ethereum L1. Executors [#executors] A decentralized network of validator nodes that execute your WASM program, process messages in parallel, and collectively sign the results. Router Contract [#router-contract] The central L1 contract that verifies validator signatures, finalizes execution batches, and anchors all state transitions to Ethereum. Vara.eth structural overview How It Differs from L2s [#how-it-differs-from-l2s] Vara.eth is **not a rollup**. It doesn't produce its own blocks, doesn't maintain shared state, and doesn't require a centralized sequencer. It's an application layer, not a new chain. | Feature | L2 Rollup | Vara.eth | | ------------------ | ---------------------- | ------------------------------------------- | | Own blockchain | ❌ Yes (separate chain) | ✅ No (anchored to Ethereum L1) | | Bridging required | ❌ Yes | ✅ No (native L1 contracts) | | Finality | Minutes to days | Ethereum-native + instant pre-confirmations | | Parallel execution | Limited | ✅ Native per-program | | Memory limit | Gas-constrained | Up to 2 GB per program | | Who pays gas? | User pays | Program pays (reverse gas) | Two Interaction Paths [#two-interaction-paths] | Path | Mechanism | Latency | Cost | | ----------------- | ----------------------------------------------------------------------------------------------------- | ---------------- | --------------- | | **Ethereum-side** | Transaction to Mirror contract on L1 | \~12s (L1 block) | ETH gas + wVARA | | **Vara.eth-side** | [Injected transaction](/docs/vara-eth/reference/rpc-api#injected-transactions) directly to validators | Sub-second | No ETH gas | The **Ethereum-side** path is used for on-chain settlement, value transfers, and Solidity composability. The **Vara.eth-side** path bypasses Ethereum entirely — messages go straight to validator nodes for instant execution and pre-confirmations. Both paths are unified in the [`@vara-eth/api`](https://github.com/gear-tech/gear-js/blob/main/apis/vara-eth/README.md) TypeScript SDK. Key Terminology [#key-terminology] **Mirror Contract**\ The on-chain Ethereum representation of your program. Users send messages to the Mirror. **Router Contract**\ Central Ethereum contract that coordinates code registration, program creation, and batch settlement. **Gear Program**\ A WASM module that runs inside the [Gear runtime](/docs/gear), representing your application's logic. **Executable Balance**\ wVARA tokens that fund your program's execution costs ([reverse gas model](/docs/vara-eth/economics/reverse-gas-model)). **Pre-confirmation**\ Instant feedback from executors before the transaction is finalized on Ethereum L1. **Injected Transaction**\ A message sent directly to Vara.eth validators, bypassing Ethereum for sub-second execution. For the full list of terms, see the [Glossary](/docs/vara-eth/reference/glossary). Architecture Deep Dive [#architecture-deep-dive] Vara.eth's architecture is described in detail in the [Vara.eth Whitepaper](https://eth.vara.network/whitepaper), [Technical Documentation](https://eth.vara.network/whitepaper/vara.eth-technical.pdf), and the [GitHub Repository](https://github.com/gear-tech/gear/tree/master/ethexe). # Why It Matters Why It Matters [#why-it-matters] Vara.eth introduces three fundamental innovations that change how developers build on Ethereum. Each addresses a critical limitation holding back mass adoption. Reverse Gas: Programs Pay, Not Users [#reverse-gas-programs-pay-not-users] On Ethereum, every user must hold ETH and pay gas for every action — even simple ones. This creates friction and makes Web2-style UX impossible. Vara.eth flips the model: **Users pay** only the base Ethereum transaction fee (in ETH) to submit a message to the Mirror contract. **Programs pay** for their own execution from a pre-funded balance (in wVARA). ``` Traditional: User ──pays ETH gas──▶ Smart Contract execution Vara.eth: User ──pays ETH (L1 tx only)──▶ Mirror ──program pays wVARA──▶ Execution ``` Developers fund their program's balance once, then users interact for free. If the balance runs low, the program pauses until replenished — no funds are lost. This enables gasless experiences: users click, sign with MetaMask, and the program covers compute. No token management, no gas estimation, no multi-step approval flows. → [Reverse Gas Model](/docs/vara-eth/economics/reverse-gas-model) | [Funding Your Program](/docs/vara-eth/interact/funding-executable-balance) Pre-confirmations: Instant Feedback + Ethereum Security [#pre-confirmations-instant-feedback--ethereum-security] Ethereum's \~12 second block time destroys UX for real-time applications. Trading, gaming, interactive apps need instant feedback. Vara.eth provides **pre-confirmations**: 1. User sends message to Mirror contract 2. Executors observe transaction, execute program immediately, sign result 3. SDK fetches signed result via RPC — user sees outcome **in milliseconds** 4. Later, batch settles to Ethereum for canonical finality ``` Timeline: 0ms User submits transaction ~100ms Executors sign result (pre-confirmation) ← user sees this ~12s Ethereum block inclusion ~13min Full finality ``` Pre-confirmations are cryptographically signed by validators using the same execution logic as final settlement. For most apps, the pre-confirmed result is identical to the final one. The SDK provides clear APIs to handle both pre-confirmed and finalized states when needed. → [Events & State Reading](/docs/vara-eth/interact/events-state-reading) Parallel Execution: No Global Bottlenecks [#parallel-execution-no-global-bottlenecks] Ethereum processes transactions sequentially — one at a time, globally. Every contract competes for the same execution slot. This creates a fundamental throughput ceiling. Vara.eth takes a different approach. Each program is an **isolated actor** with its own state and message queue. Programs don't share memory or storage. This means: **Thousands of programs execute simultaneously** without coordination overhead. **Each program is effectively its own rollup** — collectively forming a "swarm of rollups" architecture. **Horizontal scaling comes naturally** — more programs don't slow down existing ones. ``` Ethereum: [Program A] → [Program B] → [Program C] (sequential) Vara.eth: [Program A] ─┐ [Program B] ─┼─ all running in parallel [Program C] ─┘ ``` This architecture is enabled by the [Actor Model](/docs/gear/actor-model) that powers the Gear runtime. Executors run multi-threaded, processing multiple programs simultaneously on each node. What This Enables [#what-this-enables] These innovations unlock applications previously impossible on Ethereum: **DeFi**\ Order books with sub-second matching. Perpetual futures with instant liquidations. Real-time risk models. Each program supports up to 2 GB memory for complex financial simulations. **Gaming**\ Real-time game loops with PvP combat, anti-cheat, and complex state — all settled on Ethereum. Pre-confirmations enable instant gameplay feedback. **AI/ML**\ On-chain inference, autonomous agents, ML model execution. Up to 2 GB memory per program. Verifiable computation backed by validator consensus. **Enterprise**\ Supply chain IoT processing, large-scale DAO voting, Web2 integrations via verified HTTPS/TLS proofs. Build these today: load the [Agent Skills](/docs/vara-eth/setup/agent-skills) into your AI coding agent, or [run your own node](/docs/vara-eth/setup/local-node) and start experimenting. # Common Workflows Common Workflows [#common-workflows] This page describes the most common interaction patterns you'll use when building on Vara.eth. Send, Wait, Read State [#send-wait-read-state] The simplest pattern: send a message, wait for execution, then read the updated state. ```typescript // 1. Send a message const tx = await program.counter.increment(); // 2. Wait for Ethereum confirmation await tx.wait(); // 3. Read updated state const value = await program.counter.get(); ``` Send with Value [#send-with-value] Attach ETH to a message for programs that accept payable calls: ```typescript // Send message with 0.01 ETH value const tx = await program.vault.deposit({ value: ethers.parseEther('0.01') }); ``` The attached value is credited through the Mirror payable flow and handled by program logic during execution. Event-Driven Pattern [#event-driven-pattern] Subscribe to events for real-time updates across multiple users: ```typescript const mirror = new ethers.Contract(mirrorAddress, abi, wsProvider); // React to state changes mirror.on('StateChanged', async (stateHash) => { const newState = await program.myService.getState(); refreshUI(newState); }); // React to custom events program.on('TradeExecuted', (event) => { addToTradeHistory(event); updatePriceChart(event); }); ``` This pattern is essential for multi-user applications where one user's action should update other users' UIs. Pre-Confirmation UX [#pre-confirmation-ux] Build responsive UIs by showing pre-confirmed results immediately, then updating on finality: ```typescript async function handleSwap(tokenA, tokenB, amount) { setStatus('pending'); const injected = await api.createInjectedTransaction({ destination: programId, payload: encodedPayload, // encode via sails-js value: 0n, }); const promise = await injected.sendAndWaitForPromise(); await promise.validateSignature(); setStatus('pre-confirmed'); // decode promise.payload and update UI } ``` → [Events & State Reading](/docs/vara-eth/interact/events-state-reading) Contract-to-Contract [#contract-to-contract] Solidity contracts can interact with Vara.eth programs by sending messages to the Mirror: ```solidity contract MyDeFi { IMirror public pricingEngine; function requestPrice(address token) external { bytes memory payload = abi.encode(token); pricingEngine.sendMessage(payload, true); } } ``` → [Solidity Integration](/docs/vara-eth/integrate/solidity-integration) Batch Operations [#batch-operations] For multiple operations in sequence, send messages in order — they'll be processed sequentially by the program: ```typescript await program.token.approve(spenderAddress, amount1); await program.dex.swap(tokenA, tokenB, amount2); await program.vault.deposit({ value: amount3 }); ``` Each message is a separate Ethereum transaction. For atomic multi-step operations, implement the logic within a single program method that handles the entire workflow. Program-to-Program Communication [#program-to-program-communication] Programs can send messages to other programs via `ActorId`. Inter-program messages are processed within the same batch when possible. → [Message Passing on Vara Network](/docs/developing/build/gstd/create) ```rust // Inside a Vara.eth program use sails_rs::prelude::*; fn call_other_program(&self, target: ActorId, payload: Vec) { msg::send(target, payload, 0).expect("Failed to send"); } ``` Claim/Refund Pattern [#claimrefund-pattern] Programs can hold value and allow users to claim it later: ```typescript // Program sends value to a user address // User claims via Mirror const mirror = new ethers.Contract(mirrorAddress, mirrorAbi, signer); await mirror.claimValue(claimedId); // claimedId is from the Message event emitted by the Mirror ``` # Events & State Reading Events & State Reading [#events--state-reading] Vara.eth programs emit events and expose state queries. Combined with pre-confirmations, this enables responsive dApps that feel like Web2 while running on Ethereum. Event Types [#event-types] Mirror Events (L1) [#mirror-events-l1] Standard Ethereum events emitted by the Mirror contract when a batch is committed: | Event | Description | | ---------------------------------------------------------------------------------------------------- | -------------------------------- | | `StateChanged(bytes32 stateHash)` | Program state was updated | | `MessageQueueingRequested(bytes32 id, address source, bytes payload, uint128 value, bool callReply)` | A message was queued | | `Reply(bytes payload, uint128 value, bytes32 replyTo, bytes4 replyCode)` | Program replied to a message | | `Message(bytes32 id, address destination, bytes payload, uint128 value)` | Program sent an outgoing message | | `ValueClaimed(bytes32 claimedId, uint128 value)` | Value was claimed | Custom Program Events [#custom-program-events] Events defined in your Rust code (via [Sails events](/docs/developing/build/sails/events)) are re-emitted as native Ethereum logs by the Mirror. Standard Ethereum tooling (The Graph, ethers.js, custom indexers) can subscribe to these logs without custom decoders. ```typescript // Listen for custom events via sails-js program.on("SwapExecuted", (event) => { console.log(`Swapped ${event.amountIn} for ${event.amountOut}`); }); // Or via ethers.js const mirror = new ethers.Contract(mirrorAddress, abi, provider); mirror.on("Transferred", (from, to, amount) => { console.log(`Transfer: ${from} → ${to}: ${amount}`); }); ``` Pre-Confirmations [#pre-confirmations] Pre-confirmations provide **sub-second results** before Ethereum L1 finality (\~13 minutes). They are the key to building responsive applications on Vara.eth. How Pre-Confirmations Work [#how-pre-confirmations-work] 1. User sends a message to the Mirror (standard Ethereum tx) 2. Once the tx is included in an L1 block, executors immediately execute the program 3. Executors sign the result using [FROST threshold signatures](https://eprint.iacr.org/2020/852) 4. The signed result is available via the [Vara.eth RPC](/docs/vara-eth/reference/rpc-api) 5. The SDK fetches and verifies the attestation 6. Your app displays the result immediately Using Pre-Confirmations (Injected Transaction) [#using-pre-confirmations-injected-transaction] ```typescript const injected = await api.createInjectedTransaction({ destination: programId, payload: encodedPayload, // encode payload via sails-js value: 0n, }); const promise = await injected.sendAndWaitForPromise(); await promise.validateSignature(); // Decode and apply immediate result from promise.payload ``` Pre-Confirmed vs Finalized [#pre-confirmed-vs-finalized] | Aspect | Pre-Confirmed | Finalized | | ---------- | ---------------------------------- | --------------------------------- | | Latency | Sub-second after L1 block | \~13 minutes (Ethereum finality) | | Security | Threshold validator signatures | Full Ethereum consensus | | Reorg risk | Possible (if Ethereum reorgs) | None | | Use for | UI updates, previews, real-time UX | Settlements, irreversible actions | When to Trust Pre-Confirmations [#when-to-trust-pre-confirmations] * **Trust fully**: UI updates, game state, dashboard displays, notifications * **Trust with caution**: Financial operations where a short reorg could matter * **Wait for finality**: High-value settlements, cross-protocol composability Display a visual indicator (e.g., a "confirming..." label) for pre-confirmed data that hasn't reached finality yet. Reading State [#reading-state] Via calculateReplyForHandle (Recommended) [#via-calculatereplyforhandle-recommended] The [`@vara-eth/api`](https://github.com/gear-tech/gear-js/blob/main/apis/vara-eth/README.md) SDK provides `calculateReplyForHandle` — a **read-only query** that simulates a message and returns what the program would reply. No transaction, no gas, instant: ```typescript import { VaraEthApi, WsVaraEthProvider } from "@vara-eth/api"; const api = new VaraEthApi( new WsVaraEthProvider("ws://vara-eth-node:9944"), ethereumClient ); // Encode query payload using sails-js const queryPayload = sails.services.Counter.queries.GetValue.encodePayload(); // Simulate the message (free, read-only) const reply = await api.call.program.calculateReplyForHandle( await ethereumClient.getAccountAddress(), programId, queryPayload ); // Decode result const value = sails.services.Counter.queries.GetValue.decodeResult( reply.payload ); ``` This is useful for: * Reading program state without modifying it * Testing message payloads before sending * Building responsive UIs with instant state reads Via Vara.eth Program Queries [#via-varaeth-program-queries] ```typescript // List all programs const programIds = await api.query.program.getIds(); // Get code ID const codeId = await api.query.program.codeId(programId); // Read full state by hash const stateHash = await mirror.stateHash(); const state = await api.query.program.readState(stateHash); ``` Via Etherscan (With ABI) [#via-etherscan-with-abi] If an ABI interface was attached at deployment, read methods appear under "Read as Proxy" on Etherscan. Via CLI [#via-cli] ```bash ethexe tx query "$PROGRAM_ID" \ --ethereum-rpc "$RPC" \ --ethereum-router "$ROUTER" \ --sender "$SENDER" ``` This will display: * State hash * Nonce * Balances (ETH and wVARA) * Initializer and inheritor addresses # Funding Executable Balance Funding Executable Balance [#funding-executable-balance] Every Vara.eth program needs wVARA in its **Executable Balance** to pay for computation. This is the core of the [reverse gas model](/docs/vara-eth/economics/reverse-gas-model) — users pay ETH for L1 transactions, but the program itself pays wVARA for execution. How to Fund [#how-to-fund] Mirror Contract (Primary Method) [#mirror-contract-primary-method] The `executableBalanceTopUp` function on the Mirror contract is the primary way to fund a program. Anyone can call it. ```typescript import { ethers, parseUnits } from 'ethers'; // 1. Approve program (Mirror address) to spend wVARA const wvara = new ethers.Contract(wvaraAddress, erc20Abi, signer); await wvara.approve(mirrorAddress, parseUnits('100', 18)); // 2. Top up the program's Executable Balance const mirror = new ethers.Contract(mirrorAddress, mirrorAbi, signer); await mirror.executableBalanceTopUp(parseUnits('100', 18)); ``` You must first approve the program address (Mirror) as spender for your wVARA tokens. CLI [#cli] ```bash ethexe tx executable-balance-top-up "$PROGRAM_ID" 10000000000000000000 \ --approve \ --ethereum-rpc "$RPC" \ --ethereum-router "$ROUTER" \ --sender "$SENDER" ``` **Parameters:** * `$PROGRAM_ID` — Mirror contract address * `10000000000000000000` — Amount in base units (10 wVARA in this example) * `--approve` — Automatically approve wVARA spending before top-up Etherscan [#etherscan] Navigate to the Mirror address on Etherscan. Under "Write as Proxy" (or "Write Contract"), find `executableBalanceTopUp` and enter the amount. How Execution Is Charged [#how-execution-is-charged] 1. Each message gets a small **free compute threshold** — simple operations may cost nothing 2. Beyond the threshold, CPU time is metered at a configurable `wvaraPerSecond` rate 3. The consumed amount is deducted from the Executable Balance 4. Consumed wVARA is distributed to validators as rewards → [Validators & Staking](/docs/vara-eth/economics/validators-staking) Funding Models [#funding-models] Different funding strategies suit different applications: **Developer-Funded (Patron Model)** — Developer pre-funds the program. Best for onboarding, demos, consumer-facing apps, and games where you want zero-friction UX. **Revenue-Funded** — Program logic collects fees (e.g., swap fees in a DEX) and the developer periodically tops up from revenue. Best for DeFi protocols and fee-generating services. **User-Funded** — Users contribute to the Executable Balance directly. Best for per-action pricing, premium features, or pay-per-use services. **Sponsor-Funded** — DAOs, grants, or ecosystem partners fund programs. Best for public goods, community tools, and infrastructure. What Happens When Balance Runs Out [#what-happens-when-balance-runs-out] 1. New messages are **queued but not executed** 2. The program's state remains frozen at the last committed state 3. No events are emitted for queued messages 4. Once balance is replenished, queued messages resume processing in order Users who send messages to an unfunded program still pay L1 gas, but their messages won't execute. Design your frontend to check Executable Balance and warn users before they transact. Monitoring Balance [#monitoring-balance] TypeScript SDK [#typescript-sdk] ```typescript // Read state by current state hash, then use executableBalance field const mirror = getMirrorClient(programId, signer, publicClient); const stateHash = await mirror.stateHash(); const state = await api.query.program.readState(stateHash); console.log(`Executable balance: ${state.executableBalance} base units`); ``` CLI [#cli-1] ```bash ethexe tx query "$PROGRAM_ID" \ --ethereum-rpc "$RPC" \ --ethereum-router "$ROUTER" \ --sender "$SENDER" ``` In `v1.10.0`, this command shows mirror metadata and ETH balance. For executable (wVARA) balance monitoring in SDK, read state through `api.query.program.readState(...)` and use `state.executableBalance`. Set up automated alerts when balance drops below a threshold to avoid service interruption. What Types of Balances Does a Program Have? [#what-types-of-balances-does-a-program-have] | Balance | Purpose | Who Controls | | ----------------------------------------- | ------------------------------------------ | --------------------------------------- | | **Executable Balance** | Pays for program execution (compute costs) | Router (consumed automatically) | | **Program Value Balance (Owned Balance)** | Value held by program logic (payable flow) | Program logic (via Mirror payable flow) | # Initialization Initialization [#initialization] After funding, every Vara.eth program must be initialized before it can process regular messages. Initialization is triggered by the first message sent to the program, which calls the constructor defined in your `#[program]` block. What Is Initialization? [#what-is-initialization] ``` Created (Mirror deployed and topped up) → Init message sent → Constructor runs → Active (processing) ``` A freshly created program has no state. The constructor establishes the starting conditions — owner addresses, default configurations, initial balances, etc. Who Can Initialize [#who-can-initialize] Only the **initializer** — the address that called `createProgram(...)` on the Router — can send the first message to a program. Init vs Regular Messages [#init-vs-regular-messages] | Aspect | Init Message | Regular Message | | ------------- | -------------------- | --------------- | | Sender | Owner only | Anyone | | Frequency | Once per program | Unlimited | | Target | Constructor (`init`) | Service methods | | Required | Yes | No | | Carries value | Optional | Optional | Sending the Init Message [#sending-the-init-message] CLI [#cli] ```bash ethexe tx send-message "$PROGRAM_ID" "0x..." 0 \ --ethereum-rpc "$RPC" \ --ethereum-router "$ROUTER" \ --sender "$SENDER" ``` TypeScript SDK (sails-js) [#typescript-sdk-sails-js] ```typescript import { MyProgram } from './generated'; const program = new MyProgram(signer, PROGRAM_ADDRESS); // The first message triggers initialization const tx = await program.init(constructorArgs); await tx.wait(); ``` Etherscan (with ABI) [#etherscan-with-abi] If deployed with ABI, navigate to the Mirror on Etherscan. Under "Write as Proxy", find the init method and call it directly. Common Issues [#common-issues] **"Only initializer can send first message"** — You're trying to initialize from a different address than the one that created the program. Use the same wallet that called `createProgram`. **Init message not processed** — The program likely has zero Executable Balance. Fund it before or alongside initialization. → [Funding Executable Balance](/docs/vara-eth/interact/funding-executable-balance) **Constructor panic** — Your init logic has a bug. Check the payload encoding matches your constructor signature. Test with `cargo test` before deploying. # Sending Messages Sending Messages [#sending-messages] Messages are the fundamental way to interact with Vara.eth programs. Every user action — whether a token transfer, a game move, or a state query — is a message sent to a program's Mirror contract on Ethereum. Message Structure [#message-structure] | Field | Description | | ----------- | -------------------------------------------------------- | | `payload` | Encoded method call and arguments (SCALE or ABI-encoded) | | `value` | Optional ETH amount (wei) sent to the program | | `callReply` | Whether to request an on-chain reply | Two Sending Paths [#two-sending-paths] Vara.eth offers two distinct paths for sending messages, each with different trade-offs: | Path | How | Cost | Latency | Use Case | | ---------------------------- | ------------------------------------ | --------------- | ---------------- | ------------------------------------ | | **Ethereum-side** | Transaction to Mirror contract on L1 | ETH gas + wVARA | \~12s (L1 block) | On-chain settlement, value transfers | | **Vara.eth-side (Injected)** | Direct to validator nodes | No ETH gas | Sub-second | Pre-confirmations, fast UX | ```mermaid sequenceDiagram participant User participant Mirror as Mirror Contract
(Ethereum L1) participant Executors as Executor Network participant Router as Router Contract
(Ethereum L1) rect rgb(230, 245, 255) Note over User,Router: Path 1: Ethereum-side (Classic L1 Transaction) User->>Mirror: sendMessage(payload, value)
[Pays ETH gas] Mirror->>Mirror: Emit MessageQueueingRequested Executors->>Mirror: Observe event Executors->>Executors: Execute WASM program Executors->>Executors: Sign result (FROST) Executors->>Router: commitBatch(stateTransition) Router->>Mirror: Update stateHash Mirror-->>User: StateChanged event
[~12s total] end rect rgb(255, 243, 224) Note over User,Router: Path 2: Vara.eth-side (Injected Transaction) User->>Executors: send() via RPC
[No ETH gas] Executors->>Executors: Execute WASM immediately Executors->>Executors: FROST signature Executors-->>User: Pre-confirmation
[Sub-second] Note over Executors,Router: Later: batch settlement to L1 Executors->>Router: commitBatch(stateTransition) Router->>Mirror: Update stateHash end ``` **Ethereum-side**: On-chain value transfer, Solidity composability, L1 receipts. **Vara.eth-side**: Instant feedback for users (Web2-like UX) when immediate L1 settlement is not required. Both paths are supported by the [`@vara-eth/api`](https://github.com/gear-tech/gear-js/blob/main/apis/vara-eth/README.md) SDK. → [RPC API](/docs/vara-eth/reference/rpc-api) Ethereum-Side: Mirror Contract [#ethereum-side-mirror-contract] CLI [#cli] ```bash ethexe tx send-message "$PROGRAM_ID" "0x..." 0 \ --ethereum-rpc "$RPC" \ --ethereum-router "$ROUTER" \ --sender "$SENDER" ``` TypeScript SDK [#typescript-sdk] ```typescript import { getMirrorClient } from '@vara-eth/api'; const mirror = getMirrorClient(programId, signer, publicClient); // Send message (payload encoded via sails-js) const tx = await mirror.sendMessage(payload, 0n); await tx.send(); // Wait for reply const { waitForReply } = await tx.setupReplyListener(); const { payload: replyPayload, replyCode, value } = await waitForReply; ``` Etherscan (with ABI) [#etherscan-with-abi] If the program was deployed with ABI, navigate to the Mirror on [Etherscan](https://etherscan.io) (or [Hoodi Etherscan](https://hoodi.etherscan.io) for testnet), go to "Write as Proxy", and call methods directly. From Solidity [#from-solidity] ```solidity IMirror mirror = IMirror(mirrorAddress); mirror.sendMessage(payload, false); ``` → [Solidity Integration](/docs/vara-eth/integrate/solidity-integration) Vara.eth-Side: Injected Transactions [#varaeth-side-injected-transactions] Injected transactions are sent **directly to Vara.eth validator nodes**, bypassing Ethereum entirely. They are signed with your Ethereum private key but submitted off-chain — no ETH gas costs, sub-second execution. This is the primary mechanism for **pre-confirmations**. ```typescript import { VaraEthApi, WsVaraEthProvider } from '@vara-eth/api'; const api = new VaraEthApi(new WsVaraEthProvider('ws://vara-eth-node:9944'), ethereumClient); // Create and send injected transaction const injected = await api.createInjectedTransaction({ destination: programId, payload: encodedPayload, value: 0n, }); // Send and wait for reply with FROST signature const promise = await injected.sendAndWaitForPromise(); await promise.validateSignature(); // Verify validator signatures ``` Message Lifecycle [#message-lifecycle] ``` User sends tx → Mirror queues → Executors detect → WASM executes → Batch commits → State updated ``` 1. User sends a transaction to the Mirror contract on Ethereum L1 2. Mirror emits `MessageQueueingRequested` event with the payload 3. Executors observe the event and queue the message for processing 4. The program's WASM code executes the message handler 5. Results (state transition, replies, events) are included in the next batch 6. The Router commits the batch to Ethereum, updating the Mirror's `stateHash` Handling Replies [#handling-replies] On-Chain Replies [#on-chain-replies] Set `callReply = true` when sending. The program's reply is emitted as a `Reply` event on the Mirror when the batch is committed. ```typescript // Listen for replies mirror.on('Reply', (payload, value, replyTo, replyCode) => { if (replyCode === '0x00000000') { // Success — decode payload } }); ``` Pre-Confirmed Replies (Off-Chain) [#pre-confirmed-replies-off-chain] For instant feedback, use injected transactions via Vara.eth RPC and wait for the signed promise: ```typescript const injected = await api.createInjectedTransaction({ destination: programId, payload: encodedPayload, value: 0n, }); const promise = await injected.sendAndWaitForPromise(); await promise.validateSignature(); // Decode promise.payload ``` → [Events & State Reading](/docs/vara-eth/interact/events-state-reading) Sending with Value [#sending-with-value] Ether Value (via sendMessage) [#ether-value-via-sendmessage] Programs can also receive native Ether. The `sendMessage` function on the Mirror is `payable`: ```typescript // Send message with Ether const mirror = new ethers.Contract(mirrorAddress, mirrorAbi, signer); await mirror.sendMessage(payload, false, { value: ethers.parseEther('0.1'), // Send 0.1 ETH }); ``` Sending a message with `sendMessage` transfers **ETH** (Owned Balance). Funding execution requires **wVARA** top-up to Executable Balance.

→ [Funding Executable Balance](/docs/vara-eth/interact/funding-executable-balance)
Gas Considerations [#gas-considerations] As a user, you only pay **ETH gas** for the L1 transaction to the Mirror. You do not pay for the off-chain execution — that's covered by the program's Executable Balance (the [reverse gas model](/docs/vara-eth/economics/reverse-gas-model)). Typical gas costs on Ethereum L1: | Operation | Gas | | ------------------------ | -------------------- | | `sendMessage` | \~60,000-100,000 gas | | `sendReply` | \~60,000-100,000 gas | | `executableBalanceTopUp` | \~50,000-80,000 gas | # Developing on Vara Introduction [#introduction] As a cutting-edge blockchain programs engine, Vara Network leverages the robust [Gear Protocol](https://gear-tech.io/) for seamless execution of WebAssembly (Wasm) programs. Integrating Gear Protocol empowers Vara to create a developer-friendly environment that excels in implementing and running programs with unparalleled flexibility, accommodating both intricate logic and complex functionalities. This collaboration ensures developers can unleash their creativity while maintaining a strong focus on safety and security. With Vara Network and Gear Protocol working hand in hand, the world of blockchain programs becomes more accessible and dynamic, offering endless possibilities for developers and users alike. Build with an AI Agent [#build-with-an-ai-agent] **Modern development doesn't happen without AI agents anymore.** Whether you're shipping a real product or building your first dApp, you describe what you need and the agent — Claude Code, Codex, Cursor — produces the code. Vara ships a complete, open-source skill pack purpose-built for this loop, ready to use here and now. [Vara Skills](/docs/vara-network/api/tooling/vara-skills) gives the agent the exact APIs, real imports, and verified patterns for every step — spec, Sails contract, IDL, frontend, tests, and deployment. No hallucinated symbols, no broken builds. This is the recommended path for new projects today. And the agent loop reaches beyond building: the [Trading Skill](/docs/vara-network/api/tooling/trading-skill) lets an agent trade VARA and other spot pairs on Gate.io and Coinbase, with local-only keys and dry-run-first safety. Messaging Concept [#messaging-concept] Understanding the message communication model and its benefits is of utmost importance for the safety and success of developers working with Vara Network. This innovative model is built upon the principles of the [Actor Model](/docs/vara-network/about/technology/actor-model), where all participants, including programs and users, are considered "actors." These actors interact seamlessly by exchanging messages. Explore the comprehensive resources available in [this Wiki](/docs/developing/build/introduction) to delve deeper into the development process and leverage the full potential of the message communication model. This valuable reference provides clear insights and guidance on implementing smart contracts with efficiency and precision. Rent-Based system [#rent-based-system] {/* ### Program rent Vara Network introduces an innovative program life cycle management system, prioritizing sustainable and economical resource usage. Within this system, programs on the Vara Network can rent network resources for their memory storage. This rental concept is similar to a time-limited subscription that allows the program to remain active and operational within the network. Upon program upload, each program is automatically assigned a predefined expiration date for free, typically set to last for 5,000,000 blocks, where each block lasts approximately 3 seconds (~173 days). To extend the program's functionality beyond this period, users can increase this expiration parameter for the program. However, to ensure the program's viability throughout the chosen lifetime extension, the user must possess enough VARA tokens in their balances. To maintain the program's functionality after the current rent period expires, the user should proactively pay more VARA tokens for the program. Any user on the network can contribute additional VARA to any program as needed. After the rent period's expiration, the program automatically transitions to a 'paused' state, leading to the removal of its memory pages from the storage. However, important data such as the block number when the program was paused and its memory hash remain accessible on the network. Consequently, the paused program can be resumed with its state restored to the point at which it was paused, provided an archive node is connected to the network. */} Message rent [#message-rent] {/*Additionally,*/} Vara enables rent for messages stored in the Waitlist. In the Gear Protocol **Actor Model**, actors communicate via messages. The **Waitlist** is a specific entity—a database where these messages (not transactions) are stored while awaiting processing or fulfilling specific conditions. A message enters payable storage with locked funds for a maximum duration in `N` blocks. In `M` blocks (where `M 4. Create a master password and click `Continue`. SubWallet cannot help restore the password once it is lost as the wallet is non-custodial. Recover the account using the seed if the master password is forgotten. img alt 5. Keep the seed phrase in a safe place and never disclose it to anyone. After saving it, click `I have kept it somewhere safe` to move on. img alt 6. The wallet has been successfully created! img alt Via Talisman [#via-talisman] Talisman is a popular non-custodial wallet for creating and managing Substrate accounts. Connected to Web3 applications across various networks, Talisman browser extension allows for storing, sending, and receiving assets. It is one of the easiest and most secure ways of creating a Substrate account. 1. Install Talisman extension for the browser [here](https://talisman.xyz/). 2. Open the extension by clicking on the logo in the menu bar. 3. In the opened application, click "New wallet": img alt 4. Enter a strong password: img alt And that's it. The wallet has been created: img alt Don't forget to save your seed phrase by clicking the **Backup Now** button. **Caution:** If you lose this phrase, you may lose access to your funds forever. If you skip this now, you can find the backup option later in **Settings**. Via Polkadot.js Browser Extension [#via-polkadotjs-browser-extension] The Polkadot.js browser extension is the original application created by Parity Technologies for managing Substrate accounts. This method involves installing the Polkadot.js plugin and using it as a "virtual vault" separate from the browser to store private keys and sign transactions. 1. Install the extension via the following links: Install on [Chromium](https://chrome.google.com/webstore/detail/polkadot%7Bjs%7D-extension/mopnmbcafieddcagagdcbnhejhlodfdd?hl=en)-based browser. Install on [Firefox](https://addons.mozilla.org/en-US/firefox/addon/polkadot-js-extension). 2. Open the extension by clicking on the logo in the menu bar. 3. Click the large plus button to create a new account. Alternatively, navigate to the smaller plus icon in the top right and select "Create New Account". img alt 4. Save the `seed phrase` in a safe place using the security advice at the beginning of this article. img alt 5. Specify the name of the account and a strong password for making transactions. Click "Add the account with the generated seed". img alt The password you choose encrypts your account information and is **stored locally** in your browser extension. You will need to re-enter it to: * Process any **outgoing transactions**. * **Cryptographically sign** messages. A new account has now been successfully created using the Polkadot.js browser extension. Via Mobile App [#via-mobile-app] For accessing accounts on a mobile device, the following multi-chain wallets can be used: [img alt](https://subwallet.app/) [img alt](https://novawallet.io/) Via Telegram [#via-telegram] Vara Wallet is available as a Telegram bot, allowing you to manage your Vara account directly within the Telegram app — no browser extension or mobile install required. Varan Wallet — Your Crypto Here Try it now on Telegram: [@varanwalletbot](https://t.me/varanwalletbot) Via Telegram [#via-telegram-1] Vara Wallet is available as a Telegram bot, allowing you to manage your Vara account directly within the Telegram app — no browser extension or mobile install required. Varan Wallet — Your Crypto Here Try it now on Telegram: [@varanwalletbot](https://t.me/varanwalletbot) Via Console [#via-console] If the `Gear` or `Vara` node is installed, generate a new `seed phrase` using the command: ```sh ./gear key generate --network vara ``` Then `import` the generated seed phrase into any convenient wallet. How to Get Your Address in Vara Format [#how-to-get-your-address-in-vara-format] To display your account address in the Vara format, you can use the [Vara Explorer](https://vara.subscan.io/). Simply paste any Substrate-compatible address into the search bar - the resulting account information will be shown in Vara format automatically. If you need to manually convert an address into Vara format, you can use third-party tools such as: * [ss58.org](https://ss58.org/) * [Polkadot Subscan Format Transform Tool](https://polkadot.subscan.io/tools/format_transform) These tools allow you to convert addresses between different SS58 formats used across Substrate-based networks, including Vara. Frequently Asked Questions [#frequently-asked-questions] Is this account portable? [#is-this-account-portable] > Yes, access the account on a different device or wallet of choice as long as the account's seed phrase or JSON file associated with the account is available. Can transactions be done directly in the Polkadot.js browser extension? [#can-transactions-be-done-directly-in-the-polkadotjs-browser-extension] > The Polkadot.js browser extension does not have functionality to make transactions directly. To make transactions, launch [Gear Idea](https://idea.gear-tech.io) on the browser. Before making any transactions, allow Gear Idea to access the account. The browser extension holds the account information. Choose which websites get to access this information. Does Vara support hard wallets? [#does-vara-support-hard-wallets] > Yes! Vara currently supports [Ledger](/docs/vara-network/account/ledger) and [Polkadot Vault](/docs/vara-network/account/polkadot-vault). # Ledger Support Hardware wallets, also known as cold wallets, are physical devices designed to securely store private keys offline, safeguarding your funds from unauthorized access and cyber threats. **Ledger** is one of the world’s leading hardware wallet providers, trusted globally for robust security and ease of use. **Vara Network** supports Ledger hardware wallets through the **Polkadot application**, as Vara does not currently have its own dedicated Ledger app. By connecting Ledger devices with **NovaWallet** or **SubWallet**, you can securely store **VARA tokens**, make transactions, and engage in staking and governance activities. Supported Ledger Wallet Models [#supported-ledger-wallet-models] * Ledger Nano S Plus * Ledger Nano X * Ledger Stax * Ledger Flex **Key Note:** Always verify compatibility and supported devices via the official Ledger website. Requirements [#requirements] To use Ledger with **Vara Network**, ensure you meet the following requirements: * **Initialized Ledger Device** (How to Setup). * **Up-to-date Ledger Live Installed** (Download Ledger Live). Connecting Ledger via the Polkadot App [#connecting-ledger-via-the-polkadot-app] Ledger allows integration with the Vara Network through the official **Polkadot app**. Steps to connect: [#steps-to-connect] 1. Open **Ledger Live** and go to **"My Ledger"**. 2. Connect and unlock your **Ledger device**. 3. Search for and install the **Polkadot application**. The address you create is a **universal Substrate-based address**. This means you can use the exact same account to receive and manage **VARA tokens**, as well as assets from other Substrate networks. How to Manage and Stake VARA Using a Ledger? [#how-to-manage-and-stake-vara-using-a-ledger] To securely manage your account using **Ledger**, you can connect it to mobile or desktop applications based on your preference: | | Mobile Wallet | Desktop Wallet | VARA Management | Staking | | -------------- | -------------------------------------------- | ---------------- | --------------- | ------- | | **NovaWallet** | ✅ *(iOS: Bluetooth only, Android: USB only)* | ❌ No desktop app | ✅ Yes | ✅ Yes | | **SubWallet** | ❌ No Ledger support | ✅ *(USB only)* | ✅ Yes | ✅ Yes | Using NovaWallet: [#using-novawallet] Connect Ledger to NovaWallet Using SubWallet: [#using-subwallet] Connect Ledger to SubWallet via Polkadot App

Once your Ledger device is connected to **NovaWallet** or **SubWallet**, you will gain access to your **Substrate-based account**, allowing you to: * **Manage addresses** (including VARA format) * **View your balances** * **Send and receive tokens** * **Stake tokens** *** Helpful Tips & Recommendations [#helpful-tips--recommendations] * **PIN Security:** Always set and use a strong PIN code for your Ledger device. * **Recovery Phrase:** Keep your **24-word recovery phrase** offline and secure. * **Official Sources:** Download software and updates **only from official Ledger sources** to protect your assets from phishing attempts. *** # Polkadot Vault Polkadot Vault allows you to turn any mobile device based on Android or iOS into a real cold wallet. Your private keys are kept secure offline at all times and transactions are signed in an air-gapped way via QR-codes. A huge advantage is the completely open and transparent source code of [the product](https://github.com/paritytech/parity-signer). Vara Network completely supports working with Polkadot Vault. Installation [#installation] There are several ways to get the Polkadot Vault. Download from the application store for your device:

Download directly from the GitHub: [https://github.com/paritytech/parity-signer/releases](https://github.com/paritytech/parity-signer/releases) Create your keys [#create-your-keys] To protect your accounts, the device with **Polkadot Vault must never go online.** Physically disable or turn off **Wi-Fi, Mobile Data, and Bluetooth**. While switching to **Airplane Mode** suffices in many cases, total isolation is the best practice to prevent online threats. 1. Go offline, open your Polkadot Vault application, accept Privacy Policy, create your PIN code. 2. Click the `New seed` button to generate and store your private keys. Backup your seed phrase on paper or try other reliable methods. Add Vara Network [#add-vara-network] To sign messages/transactions, and transfer funds to and from Vara account you need to add Vara Network into the app. 1. Open your Polkadot Vault and tap the `scan` button 2. Go to [Metadata Portal](https://metadata.novasama.io/#/vara), switch to `Chain Specs` tab 3. Scan the `spec QR code` with your device to add Vara network. After spec scanning you can select Vara Network for your account: add metadata Add network Metadata [#add-network-metadata] To sign and broadcast your transaction, we will also need the metadata of the given network. Polkadot Vault can be easily updated with the latest blockchain metadata while staying offline. add metadata To get the latest blockchain metadata: 1. Open your Polkadot Vault and select tab `scan` 2. Again go to [Metadata Portal](https://metadata.novasama.io/#/vara) 3. In the `Metadata` tab, scan video QR with metadata frames. Wait for `Parsing multipart data` is completed. Scanning QR codes with metadata can take some time. Ensure your **Polkadot Vault** is updated to the latest version of metadata after every network runtime upgrade. **Why?** We generate new QR codes for each upgrade. If the metadata is outdated, you will need to re-scan to maintain compatibility. When all metadata is uploaded, Polkadot Vault will be ready to work with the Vara Network. How to use [#how-to-use] Polkadot Vault allows you to sign transactions using an air-gapped way. You scan a QR code with details about your transaction, then generate a code already signed on your offline device and deliver it as confirmation. Many applications support this feature. SubWallet [#subwallet] img alt 1. For instructions on how to use Polkadot Vault with SubWallet Extension, see [here](https://docs.subwallet.app/main/extension-user-guide/account-management/attach-a-polkadot-vault-previously-parity-signer-account). 2. For instructions on how to use Polkadot Vault with SubWallet Mobile, see [here](https://docs.subwallet.app/main/mobile-app-user-guide/account-management/attach-a-polkadot-vault-previously-parity-signer-account). 3. For visual instructions, see the video [here](https://youtu.be/9qKf_FrwzxE?si=_hQJj9tp2sQ--rpV). Nova Wallet [#nova-wallet] Nova Wallet also supports Polkadot Vault. More details are [here](https://docs.novawallet.io/nova-wallet-wiki/welcome-to-nova-wallet/hardware-wallets). Polkadot.{js} [#polkadotjs] To import your hard wallet account into Polkadot.js: 1. You should go to [Accounts](https://polkadot.js.org/apps/#/accounts) section and click the `from QR` button. add metadata 2. In the Polkadot Vault app, select your account and network. add metadata 3. Show your code to the camera. Done! Now, when making transactions from this account, you will sign them with your Polkadot Vault app using QR codes. Polkadot.{js} extension [#polkadotjs-extension] 1. In your Polkadot.\{js} extension allow QR camera access add metadata 2. Click + to import new account and choose `Attach external QR-signer account` option: add metadata 3. Show your account QR-code to the camera. Done! # SS58 Address Format SS58 is a simple address format designed for Substrate based chains. The basic idea is a base-58 encoded value which can identify a specific account on the Substrate chain. For more details about the format, please check [https://docs.substrate.io/v3/advanced/ss58/](https://docs.substrate.io/v3/advanced/ss58/). Select address format to use with Gear network [#select-address-format-to-use-with-gear-network] Both the Gear Idea backend and programs written on Vara use the Substrate address format. When you participate in activities or events organized by the Gear Foundation, such as workshops or live AMAs, you may be required to share your account's public key. In these cases, you will need to provide an account address in the Substrate address format. If you already have any Substrate-based network account (such as Polkadot account), you can set the address format for your wallets in polkadot.js extension: 1. Open polkadot.js extension in your browser and click the Settings icon on the top right corner. 2. In the opened window click on the dropdown menu "Display address format for" and select "Substrate": img alt SS58 Address converter [#ss58-address-converter] It is often more convenient to work with addresses in the respective network's format. You can either convert an address to a key yourself or use this simple online converter: [https://ss58.org](https://ss58.org). # Calculate Gas Vara nodes charge gas fees for all network operations, including executing a program’s code or processing a message. This gas is paid for by the initiator of these actions. To ensure successful message processing and avoid errors like `Gas limit exceeded`, simulate the execution in advance to calculate the exact amount of gas that will be consumed. Calculate Gas for Messages [#calculate-gas-for-messages] To determine the minimum gas amount required to send an extrinsic, use `api.program.calculateGas.[method]`. Depending on the conditions, calculate gas for initializing a program or processing a message in `handle()` or `reply()`. * `min_limit` - minimum gas limit required for execution * `reserved` - gas amount reserved for other on-chain interactions * `burned` - number of gas units burned during message processing * `may_be_returned` - value that can be returned in some cases * `waited` - notification that the message will be added to the waitlist Init (for upload_program extrinsic) [#init-for-upload_program-extrinsic] ```javascript const code = fs.readFileSync('demo_ping.opt.wasm'); const gas = await api.program.calculateGas.initUpload( '0xd43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d', // source id code, '0x00', // payload 0, // value true, // allow other panics ); console.log(gas.toHuman()); ``` Init (for create_program extrinsic) [#init-for-create_program-extrinsic] ```javascript const codeId = '0x…'; const gas = await api.program.calculateGas.initCreate( '0xd43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d', // source id codeId, '0x00', // payload 0, // value true, // allow other panics ); console.log(gas.toHuman()); ``` Handle [#handle] ```javascript import { getProgramMetadata } from '@gear-js/api'; const metadata = await getProgramMetadata('0x' + fs.readFileSync('demo_new_meta.meta.txt')); const gas = await api.program.calculateGas.handle( '0xd43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d', // source id '0xa178362715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d', // program id { id: { decimal: 64, hex: '0xd43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d', }, }, // payload 0, // value false, // allow other panics metadata, ); console.log(gas.toHuman()); ``` Reply to a Message [#reply-to-a-message] ```javascript import { getProgramMetadata } from '@gear-js/api'; const metadata = await getProgramMetadata('0x' + fs.readFileSync('demo_async.meta.txt')); const gas = await api.program.calculateGas.reply( '0xd43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d', // source id '0x518e6bc03d274aadb3454f566f634bc2b6aef9ae6faeb832c18ae8300fd72635', // message id 'PONG', // payload 0, // value true, // allow other panics metadata, ); console.log(gas.toHuman()); ``` # Events Events are generated for certain operations during execution. The following sections describe events that are part of the default Gear Protocol's runtime. To subscribe to all events: ```javascript const unsub = await api.query.system.events((events) => { console.log(events.toHuman()); }); // Unsubscribe unsub(); ``` Events Types [#events-types] MessageQueued [#messagequeued] **Summary:** When a user successfully sends a message to a program and it gets added to the message queue. ```rust MessageQueued { /// Generated id of the message. id: MessageId, /// Account id of the source of the message. source: T::AccountId, /// Program id, who is the message's destination. destination: ProgramId, /// Entry point for processing of the message. /// On the sending stage, the processing function /// of the program is always known. entry: MessageEntry, } ``` UserMessageSent [#usermessagesent] **Summary:** When someone has sent a message to the user. ```rust UserMessageSent { /// Message sent. message: UserMessage, /// Block number of expiration from `Mailbox`. /// /// Equals `Some(_)` with block number when message /// will be removed from `Mailbox` due to some /// reasons (see #642, #646 and #1010). /// /// Equals `None` if message wasn't inserted to /// `Mailbox` and appears as only `Event`. expiration: Option, } ``` UserMessageRead [#usermessageread] **Summary:** When a message has been marked as "read" and it has been removed from the `Mailbox`. This event only affects messages, which were already prior inserted into the `Mailbox`. ```rust UserMessageRead { /// Id of the message read. id: MessageId, /// The reason for the reading (removal from `Mailbox`). /// /// NOTE: See more docs about reasons at `gear_common::event`. reason: UserMessageReadReason, } ``` MessagesDispatched [#messagesdispatched] **Summary:** The result of when a message is processed within the block. ```rust MessagesDispatched { /// Total amount of messages removed from message queue. total: MessengerCapacityOf, /// Execution statuses of the messages, which were already known /// by `Event::MessageQueued` (sent from user to program). statuses: BTreeMap, /// Ids of programs, which state changed during queue processing. state_changes: BTreeSet, } ``` MessageWaited [#messagewaited] **Summary:** When a message's execution has been delayed and it has been added to the waitlist. ```rust MessageWaited { /// Id of the message waited. id: MessageId, /// Origin message id, which started messaging chain with programs, /// where currently waited message was created. /// /// Used to identify by the user that this message associated /// with him and the concrete initial message. origin: Option>, /// The reason of the waiting (addition to `Waitlist`). /// /// NOTE: See more docs about reasons at `gear_common::event`. reason: MessageWaitedReason, /// Block number of expiration from `Waitlist`. /// /// Equals block number when message will be removed from `Waitlist` /// due to some reasons (see #642, #646 and #1010). expiration: T::BlockNumber, } ``` MessageWoken [#messagewoken] **Summary:** When a message is ready to continue its execution and has been removed from the `Waitlist`. ```rust MessageWoken { /// Id of the message woken. id: MessageId, /// The reason of the waking (removal from `Waitlist`). /// /// NOTE: See more docs about reasons at `gear_common::event`. reason: MessageWokenReason, } ``` CodeChanged [#codechanged] **Summary:** When a program's code has been altered. ```rust CodeChanged { /// Id of the code affected. id: CodeId, /// Change applied on code with current id. /// /// NOTE: See more docs about change kinds at `gear_common::event`. change: CodeChangeKind, } ``` ProgramChanged [#programchanged] **Summary:** Any data related to program changed. ```rust ProgramChanged { /// Id of the program affected. id: ProgramId, /// Change applied on program with current id. /// /// NOTE: See more docs about change kinds at `gear_common::event`. change: ProgramChangeKind, } ``` ProgramResumeSessionStarted [#programresumesessionstarted] **Summary:** Program resume session has been started. ```rust ProgramResumeSessionStarted { /// Id of the session. session_id: SessionId, /// Owner of the session. account_id: T::AccountId, /// Id of the program affected. program_id: ProgramId, /// Block number when the session will be removed if not finished. session_end_block: T::BlockNumber } ``` Check what the event is [#check-what-the-event-is] ```javascript api.query.system.events((events) => { events .filter(({ event }) => api.events.gear.MessageQueued.is(event)) .forEach(({ event: { data } }) => { console.log(data.toHuman()); }); events .filter(({ event }) => api.events.balances.Transfer.is(event)) .forEach(({ event: { data } }) => { console.log(data.toHuman()); }); }); ``` Subscribe to specific events [#subscribe-to-specific-events] Subscribe to messages sent from a program [#subscribe-to-messages-sent-from-a-program] ```javascript const unsub = api.gearEvents.subscribeToGearEvent( 'UserMessageSent', ({ data: { message: { id, source, destination, payload, value, reply }, }, }) => { console.log(` messageId: ${id.toHex()} source: ${source.toHex()} payload: ${payload.toHuman()} `); }, ); // Unsubscribe unsub(); ``` Subscribe to messages intended for a program [#subscribe-to-messages-intended-for-a-program] ```javascript const unsub = api.gearEvents.subscribeToGearEvent( 'MessageQueued', ({ data: { id, source, destination, entry } }) => { console.log({ messageId: id.toHex(), programId: destination.toHex(), userId: source.toHex(), entry: entry.isInit ? entry.asInit : entry.isHandle ? entry.asHandle : entry.asReply, }); }, ); // Unsubscribe unsub(); ``` Subscribe to Transfer events [#subscribe-to-transfer-events] ```javascript const unsub = await api.gearEvents.subscribeToTransferEvents( ({ data: { from, to, amount } }) => { console.log(` Transfer balance: from: ${from.toHex()} to: ${to.toHex()} amount: ${+amount.toString()} `); }, ); // Unsubscribe unsub(); ``` Subscribe to new blocks [#subscribe-to-new-blocks] ```javascript const unsub = await api.gearEvents.subscribeToNewBlocks((header) => { console.log( `New block with number: ${header.number.toNumber()} and hash: ${header.hash.toHex()}`, ); }); // Unsubscribe unsub(); ``` # Cookbook Here is collected a set of useful code snippets in a question-answer format: Get block data [#get-block-data] ```javascript const data = await api.blocks.get(blockNumberOrBlockHash); console.log(data.toHuman()); ``` Get block timestamp [#get-block-timestamp] ```javascript const ts = await api.blocks.getBlockTimestamp(blockNumberOrBlockHash); console.log(ts.toNumber()); ``` Get blockHash by block number [#get-blockhash-by-block-number] ```javascript const hash = await api.blocks.getBlockHash(blockNumber); console.log(hash.toHex()); ``` Get block number by blockhash [#get-block-number-by-blockhash] ```javascript const hash = await api.blocks.getBlockNumber(blockHash); console.log(hash.toNumber()); ``` Get all block's events [#get-all-blocks-events] ```javascript const events = await api.blocks.getEvents(blockHash); events.forEach((event) => { console.log(event.toHuman()); }); ``` Get all block's extrinsics [#get-all-blocks-extrinsics] ```javascript const extrinsics = await api.blocks.getExtrinsics(blockHash); extrinsics.forEach((extrinsic) => { console.log(extrinsic.toHuman()); }); ``` Get transaction fee [#get-transaction-fee] ```javascript const api = await GearApi.create(); api.program.submit({ code, gasLimit }); // same for api.message, api.reply and others const paymentInfo = await api.program.paymentInfo(alice); const transactionFee = paymentInfo.partialFee.toNumber(); consolg.log(transactionFee); ``` Sign data [#sign-data] Create signature ```javascript import { GearKeyring } from '@gear-js/api'; const message = 'your message'; const signature = GearKeyring.sign(keyring, message); ``` Validate signature ```javascript import { signatureIsValid } from '@gear-js/api'; const publicKey = keyring.address; const verified = signatureIsValid(publicKey, signature, message); ``` Convert public keys into ss58 format and back [#convert-public-keys-into-ss58-format-and-back] Use `encodeAddress` and `decodeAddress` functions to convert the public key into ss58 format and back. Convert to raw format ```javascript import { decodeAddress } from '@gear-js/api'; console.log(decodeAddress('5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY')); ``` Convert to ss58 format ```javascript import { encodeAddress } from '@gear-js/api'; console.log( encodeAddress( '0xd43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d', ), ); ``` # Extrinsics Gear Protocol provides a set of extrinsics to interact with the Gear pallets. The extrinsics of other pallets used in the chain are also available and can be found in the [Substrate Extrinsics documentation](https://polkadot.js.org/docs/substrate/extrinsics). `author_submitExtrinsic` method is used to submit an extrinsic to the chain. Gear pallets methods [#gear-pallets-methods] Save program's code in storage [#save-programs-code-in-storage] * summary: The method saves the program's `code` in storage, which can later be used to initialize the program from it. * struct: `uploadCode(code: Bytes)` * gear-js/api method: `api.code.upload` Program init in the same block [#program-init-in-the-same-block] * summary: Creates program initialization request (message), that is scheduled to be run in the same block. * struct: `uploadProgram(code: Bytes, salt: Bytes, init_payload: Bytes, gas_limit: u64, value: u128, keep_alive: bool)` * gear-js/api method: `api.program.upload` Create program from code [#create-program-from-code] * summary: Creates program via `code_id` from storage. * struct: `createProgram(code_id: H256, salt: Bytes, init_payload: Bytes, gas_limit: u64, value: u128, keep_alive: bool)` * gear-js/api method: `api.program.create` Send message [#send-message] * struct: `sendMessage(destination: H256, payload: Bytes, gas_limit: u64, value: u128, keep_alive: bool)` * summary: Sends a message to a program or to another account. * gear-js/api method: `api.message.send` Send reply [#send-reply] * struct: `sendReply(reply_to_id: H256, payload: Bytes, gas_limit: u64, value: u128, keep_alive: bool)` * summary: Sends a reply to a message in the `Mailbox`. * gear-js/api method: `api.message.sendReply` Claim value from message [#claim-value-from-message] * struct: `claim_value(message_id: H256)` * summary: Claims value from a message in the `Mailbox`. * gear-js/api method: `api.mailbox.claimValue.submit` Gas Voucher [#gas-voucher] Issue a new voucher [#issue-a-new-voucher] * struct: `issue(spender: H256, balance: u128, programs: Option>, code_uploading: bool, duration: BlockNumber)` * summary: Issues a new voucher at the spender's expense to be used in a specific program. * gear-js/api method: `api.voucher.issue` Execute prepaid call with given voucher [#execute-prepaid-call-with-given-voucher] * struct: `call(voucher_id: H256, call: PrepaidCall)` * summary: Execute prepaid call with given voucher id. * gear-js/api method: `api.voucher.call` Revoke voucher [#revoke-voucher] * struct: `revoke(spender: H256, voucher_id: H256)` * summary: Revokes an existing voucher. * gear-js/api method: `api.voucher.revoke` Update voucher [#update-voucher] * struct: `update(spender: AccountId, voucher_id: VoucherId, move_ownership: Option, balance_top_up: Option, append_programs: Option>>, code_uploading: Option, prolong_duration: Option)` * summary: Updates an existing voucher. * gear-js/api method: `api.voucher.update` # Getting Started Gear-JS API [#gear-js-api] The Gear-JS API offers a set of utilities, libraries, and tools that enable JavaScript applications to interact with programs running on the Vara network via queries to a Vara node. For applications built using the [Sails](/docs/developing/build/sails) framework, it is generally recommended to use [Sails-JS](/docs/developing/sails-js), which leverages the Gear-JS API for low-level communication with Vara nodes. The sections below describe tools for implementing basic functions in a JS application, such as managing key pairs (accounts), calculating gas required for network operations, uploading programs to the network, sending messages to programs, reading program states, retrieving messages from the user's mailbox, working with metadata, and more. Useful code snippets are provided in the Cookbook section. The basic API is implemented on the Substrate layer and is consistent across all Substrate-based networks. The Gear-JS API code is available on [GitHub](https://github.com/gear-tech/gear-js). A complete API overview can be found on the [Polkadot documentation portal](https://polkadot.js.org/docs/). For Vara Mainnet, use the dedicated `VaraApi` class. It accounts for the network's **runtime version** and extrinsic signatures, and is more convenient and reliable than the generic `GearApi`. Installation [#installation] Install the Gear-JS API using npm: npm pnpm yarn bun ```bash npm install @gear-js/api ``` ```bash pnpm add @gear-js/api ``` ```bash yarn add @gear-js/api ``` ```bash bun add @gear-js/api ``` Getting Started [#getting-started] Start the API connection to the local running RPC node: ```javascript import { GearApi } from '@gear-js/api'; const gearApi = await GearApi.create(); ``` Connect to a different node: ```javascript const gearApi = await GearApi.create({ providerAddress: 'ws[s]://someIP[:somePort]', }); ``` For connection to the local node, use: ```bash ws://127.0.0.1:9944 ``` For connection to Vara Network Mainnet, use: ```bash wss://rpc.vara.network ``` Retrieve node information: ```javascript const chain = await gearApi.chain(); const nodeName = await gearApi.nodeName(); const nodeVersion = await gearApi.nodeVersion(); const genesis = gearApi.genesisHash.toHex(); ``` Example [#example] This simple example demonstrates how to subscribe to new blocks and get chain specifications: ```javascript async function connect() { const gearApi = await GearApi.create({ providerAddress: 'wss://rpc.vara.network', }); const [chain, nodeName, nodeVersion] = await Promise.all([ gearApi.chain(), gearApi.nodeName(), gearApi.nodeVersion(), ]); console.log( `Connected to chain ${chain} using ${nodeName} v${nodeVersion}`, ); const unsub = await gearApi.gearEvents.subscribeToNewBlocks((header) => { console.log( `New block with number: ${header.number.toNumber()} and hash: ${header.hash.toHex()}`, ); }); } connect().catch(console.error); ``` Refer to the [NFT Marketplace example](/docs/vara-network/examples/NFTs/nft-marketplace/nft-application) for a demonstration of creating a React application that connects to an [NFT smart contract](/docs/vara-network/examples/Standards/vnft) running on the blockchain. # JSON-RPC methods Gear Protocol provides a set of JSON-RPC methods to interact with Gear pallets. The JSON-RPC methods of other pallets used in the chain are also available and can be found in the [Substrate JSON-RPC documentation](https://polkadot.js.org/docs/substrate/rpc). Gear pallets [#gear-pallets] Calculate gas for Init message (upload_program extrinsic) [#calculate-gas-for-init-message-upload_program-extrinsic] * summary: The method allows to calculate gas for Init message (upload\_program extrinsic) * method name: `gear_calculateGasForUpload` * structure: `calculateGasForUpload(source: H256, code: Vec, payload: Vec, value: u128, allow_other_panics: bool): GasInfo` * gear-js/api method: `api.program.calculateGas.initUpload` Calculate gas for Init message (create_program extrinsic) [#calculate-gas-for-init-message-create_program-extrinsic] * summary: The method allows to calculate gas for Init message (create\_program extrinsic) * method name: `gear_calculateGasForCreate` * structure: `calculateGasForCreate(source: H256, codeId: H256, payload: Vec, value: u128, allow_other_panics: bool): GasInfo` * gear-js/api method: `api.program.calculateGas.initCreate` Calculate gas for Handle message (send_message extrinsic) [#calculate-gas-for-handle-message-send_message-extrinsic] * summary: The method allows to calculate gas for Handle message (send\_message extrinsic) * method name: `gear_calculateGasForHandle` * structure: `calculateGasForHandle(source: H256, destination: H256, payload: Vec, value: u128, allow_other_panics: bool): GasInfo` * gear-js/api method: `api.program.calculateGas.handle` Calculate gas for Reply message (send_reply extrinsic) [#calculate-gas-for-reply-message-send_reply-extrinsic] * summary: The method allows to calculate gas for Reply message (send\_reply extrinsic) * method name: `gear_calculateGasForReply` * structure: `calculateGasForReply(source: H256, message_id: H256, payload: Vec, value: u128, allow_other_panics: bool): GasInfo` * gear-js/api method: `api.program.calculateGas.reply` Read the metahash of a program metadata [#read-the-metahash-of-a-program-metadata] * summary: The method allows to read the metahash of a program metadata * method name: `gear_readMetahash` * structure: `readMetahash(program_id: H256, at: Option): H256` * gear-js/api method: `api.program.metaHash` Read the state of a program [#read-the-state-of-a-program] * summary: The method allows to read the state of a program * method name: `gear_readState` * structure: `readState(program_id: H256, payload: Vec, at: Option): Bytes` * gear-js/api method: `api.program.readState` Сollect reply details [#сollect-reply-details] * summary: The method allows to run queue with the message to collect reply details: payload, value and reply code. * method name: `gear_calculateReplyForHandle` * structure: `calculateReplyForHandle(origin: H256, destination: H256, payload: Vec, gas_limit: u64, value: u128, at: Option): ReplyInfo` * gear-js/api method: `api.message.calculateReply` # Keyring Keyring enables you to manage your key pair (account) to perform a wide range of operations, including signing, verifying and encrypting/decrypting. The SecretKey is never exposed to the outside. Create keyring [#create-keyring] Creating a new keyring ```javascript import { GearKeyring } from '@gear-js/api'; const { keyring, json } = await GearKeyring.create('keyringName', 'passphrase'); ``` Getting a keyring from JSON ```javascript const jsonKeyring = fs.readFileSync('path/to/keyring.json').toString(); const keyring = GearKeyring.fromJson(jsonKeyring, 'passphrase'); ``` Getting JSON for keyring ```javascript const json = GearKeyring.toJson(keyring, 'passphrase'); ``` Getting a keyring from seed ```javascript const seed = '0x496f9222372eca011351630ad276c7d44768a593cecea73685299e06acef8c0a'; const keyring = await GearKeyring.fromSeed(seed, 'name'); ``` Getting a keyring from mnemonic ```javascript const mnemonic = 'slim potato consider exchange shiver bitter drop carpet helmet unfair cotton eagle'; const keyring = GearKeyring.fromMnemonic(mnemonic, 'name'); ``` Generate mnemonic and seed ```javascript const { mnemonic, seed } = GearKeyring.generateMnemonic(); // Getting a seed from mnemonic const { seed } = GearKeyring.generateSeed(mnemonic); ``` Default Accounts [#default-accounts] In most cases on development chains, Substrate has a number of standard accounts that are pre-funded. Generally when operating on development chains, you will be introduced to characters such as `Alice`, `Bob`, `Charlie`, `Dave`, `Eve` and `Ferdie`. To create keyring from pre-installed accounts use: ```javascript const keyring = await GearKeyring.fromSuri('//Alice'); ``` # Mailbox The mailbox contains messages from the program that are waiting for user action. Read messages from Mailbox [#read-messages-from-mailbox] ```javascript const api = await GearApi.create(); const mailbox = await api.mailbox.read( '5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY', ); console.log(mailbox); ``` Claim value [#claim-value] To claim value from a message in the mailbox, use `api.mailbox.claimValue.submit` method. ```javascript const api = await GearApi.create(); const submitted = await api.mailbox.claimValue.submit(messageId); await api.mailbox.claimValue.signAndSend(/* ... */); ``` Waitlist [#waitlist] To read the program's waitlist, use `api.waitlist.read` method. ```javascript const api = await GearApi.create(); const programId = '0x1234…'; const waitlist = await api.waitlist.read(programId); console.log(waitlist); ``` # Metadata & Type Creation Metadata enables interaction between the client side (JavaScript application) and the program (smart-contract). Metadata is a kind of interface map that can help to identify and order a set of bytes into an understandable structure and indicates the function it is intended for. There are two types of metadata. ProgramMetadata [#programmetadata] `ProgramMetadata` is used to encode/decode messages to/from a program and to read the **entire program's** `State`. In this case, metadata looks like a hex string and generates automatically while the Vara program compiles. To get program metadata, use the `getProgramMetadata` function: ```javascript import { getProgramMetadata } from '@gear-js/api'; const metadata = getProgramMetadata('0x…'); // The function getProgramMetadata() takes the program's metadata in the format of a hex string. // It returns an object of the `ProgramMetadata` class with the property `types` containing all program types. metadata.types.init.input; // can be used to encode input message for init entrypoint of the program metadata.types.init.output; // can be used to decode output message for init entrypoint of the program // the same thing available for all entrypoints of the program metadata.types.state; // contains type for decoding state output ``` StateMetadata [#statemetadata] This is the most convenient way to work with metadata. It enables creating arbitrary custom functions to query only specific parts of the program state instead of reading the entire program’s state. This will be especially useful for large programs with big state. In order to use a custom implementation of reading a program `State`, you should call the `StateMetadata` function to get metadata. The function takes `meta.wasm` as `Buffer` to read the `State`. It returns the object of the `StateMetadata` class that has functions to query the program's `State`. ```js import { getStateMetadata } from '@gear-js/api'; const fileBuffer = fs.readFileSync('path/to/state.meta.wasm'); const metadata = await getStateMetadata(fileBuffer); metadata.functions; // is an object whose keys are names of functions and values are objects of input/output types ``` Metadata class methods [#metadata-class-methods] Both `ProgramMetadata` and `StateMetadata` classes have a few methods that can help to understand what one or another type is as well as get the name of a type (because types are represented as numbers in the registry). Also, there is some method for encoding and decoding data. ```js import { ProgramMetadata } from '@gear-js/api`; const metadata = getProgramMetadata('0x…'); // returns the name of the type with this index metadata.getTypeName(4); // returns the structure of this type metadata.getTypeDef(4); // if you need to get a type structure with an additional field (name, type, kind, len) you have to pass the second argument metadata.getTypeDef(4, true); // returns all custom types that existed in the registry of the program metadata.getAllTypes(); // encode or decode payload with indicated type metadata.createType(4, { value: 'value' }); ``` Type creation [#type-creation] More information about the basic types and methods of work can be found in the main documentation of Polkadot [here](https://polkadot.js.org/docs/developing/api/start/types.basics) If for some reason you need to create a type "manually" to encode/decode any payload: ```javascript import { CreateType } from '@gear-js/api'; // If "TypeName" already registered. // Learn more https://polkadot.js.org/docs/developing/api/start/types.create#choosing-how-to-create const result = CreateType.create('TypeName', somePayload); // Otherwise need to add metadata containing TypeName and all required types const result = CreateType.create('TypeName', somePayload, metadata); ``` The result of this function is data of type `Codec` and it has the following methods: ```javascript result.toHex(); // returns a hex represetation of the value result.toHuman(); // returns human-friendly object representation of the value result.toString(); // returns a string representation of the value result.toU8a(); // encodes the value as a Unit8Array result.toJSON(); // converts the value to JSON ``` # Program Rent To pay program rent, use the following JavaScript code: ```javascript // program.payRent has params: // programId // blockCount - number of blocks for which we want to extend const tx = await api.program.payRent('0x...', 100_000); tx.signAndSend(account, (events) => { events.forEach(({ event }) => console.log(event.toHuman())); }); ``` You can calculate the current rent price using the following code: ```javascript const price = api.program.calculatePayRent(blockCount); ``` If a program was paused and its pages removed from storage, you can restore it using the `api.program.resumeSession` methods: * `init`: Start a new session to resume the program. * `push`: Push a bunch of program pages. * `commit`: Finish the resume session. Here's how you can resume a paused program: ```javascript const program = await api.programStorage.getProgram(programId, oneBlockBeforePauseHash); const initTx = api.program.resumeSession.init({ programId, allocations: program.allocations, codeHash: program.codeHash.toHex(), }); let sessionId; initTx.signAndSend(account, ({ events }) => { events.forEach(({ event: { method, data } }) => { if (method === 'ProgramResumeSessionStarted') { sessionId = data.sessionId.toNumber(); } }); }); const pages = await api.programStorage.getProgramPages(programId, program, oneBlockBeforePauseHash); for (const memPage of Object.entries(page)) { const tx = api.program.resumeSession.push({ sessionId, memoryPages: [memPage] }); tx.signAndSend(account); } const tx = api.program.resumeSession.commit({ sessionId, blockCount: 20_000 }); tx.signAndSend(account); ``` # Read State There are two different ways to query the program state: 1. Query the full state of the program. To read the full state of the program, you need to have only the metadata of this program. Use the `api.programState.read` method to get the state. ```javascript import { GearApi } from '@gear-js/api'; const api = await GearApi.create({ providerAddress: 'wss://rpc.vara.network', }); await api.programState.read({ programId: '0x…' }, programMetadata); ``` It is also possible to read the state of the program at a specific block specified by its hash: ```javascript await api.programState.read( { programId: '0x…', at: '0x…' }, programMetadata, ); ``` 2. If using custom functions to query only specific parts of the program state ([see more](/docs/developing/build/gstd/metadata#generate-metadata)), call the `api.programState.readUsingWasm` method: ```javascript import { getStateMetadata } from '@gear-js/api'; const stateWasm = readFileSync('path/to/state.meta.wasm'); const metadata = await getStateMetadata(stateWasm); const state = await api.programState.readUsingWasm( { programId: '0x…', fn_name: 'name_of_function_to_execute', stateWasm, argument: { input: 'payload' }, }, metadata, ); ``` Cookbook [#cookbook] To read state in JavaScript applications, use the `fetch` browser API to get the buffer from `meta.wasm`: ```javascript const res = await fetch(metaFile); const arrayBuffer = await res.arrayBuffer(); const buffer = await Buffer.from(arrayBuffer); const metadata = await getStateMetadata(buffer); // Get state of the first wallet const firstState = await api.programState.readUsingWasm( { programId: '0x…', fn_name: 'first_wallet', buffer }, metadata, ); // Get wallet state by ID const secondState = await api.programState.readUsingWasm( { programId: '0x…', fn_name: 'wallet_by_id', buffer, argument: { decimal: 1, hex: '0x01' } }, metadata, ); ``` # Send Message Use `api.message.send` method to send messages to the program: ```javascript try { const message = { destination: destination, // programId payload: somePayload, gasLimit: 10000000, value: 1000, // prepaid: true, // account: accountId, // if you send message with issued voucher }; // In that case payload will be encoded using meta.types.handle.input type let extrinsic = api.message.send(message, meta); // So if you want to use another type you can specify it extrinsic = api.message.send(message, meta, meta.types.other.input); } catch (error) { console.error(`${error.name}: ${error.message}`); } try { await extrinsic.signAndSend(keyring, (event) => { console.log(event.toHuman()); }); } catch (error) { console.error(`${error.name}: ${error.message}`); } ``` To ensure successful message processing in real-world conditions, you should always calculate the required gas using: `api.program.calculateGas` For more details and code examples, refer to the [Calculate Gas](/docs/developing/api/calculate-gas) guide. Send reply message [#send-reply-message] When you need to reply to a message received from a program, use `api.message.reply`: ```javascript try { const reply = { replyToId: messageId, payload: somePayload, gasLimit: 10000000, value: 1000, // prepaid: true, // account: accountId, // if you send message with issued voucher }; // In this case payload will be encoded using `meta.types.reply.input` type. const extrinsic = api.message.sendReply(reply, meta); } catch (error) { console.error(`${error.name}: ${error.message}`); } try { await extrinsic(keyring, (events) => { console.log(event.toHuman()); }); } catch (error) { console.error(`${error.name}: ${error.message}`); } ``` # Upload code In certain scenarios, it may be necessary to upload the program code to the blockchain without initializing the program. This means that the resulting code will not be an active program, but rather a code snippet stored on the blockchain that can be used to initialize any number of program instances as needed, depending on your dApp's business logic. To create an `upload_code` extrinsic that uploads the program code to the blockchain without initialization, use the `GearApi.code.upload` method: ```javascript const code = fs.readFileSync('path/to/program.opt.wasm'); const { codeHash } = await api.code.upload(code); api.code.signAndSend(alice, () => { events.forEach(({ event: { method, data } }) => { if (method === 'ExtrinsicFailed') { throw new Error(data.toString()); } else if (method === 'CodeChanged') { console.log(data.toHuman()); } }); }); ``` Create program from uploaded code on chain [#create-program-from-uploaded-code-on-chain] Use `api.program.create` method to create `create_program` extrinsic: ```javascript const codeId = '0x…'; const program = { codeId, gasLimit: 1000000, value: 1000, initPayload: somePayload, }; const { programId, salt, extrinsic } = api.program.create(program, meta); await extrinsic.signAndSend(keyring, (event) => { console.log(event.toHuman()); }); ``` # Upload Program A program compiled to Wasm can be uploaded to the Vara network. During uploading, it is initialized in the network to be able to send and receive messages with other actors in the network (programs and users). Use `api.program.upload` method to create `upload_program` extrinsic ```javascript const code = fs.readFileSync('path/to/program.wasm'); const program = { code, gasLimit: 1000000, value: 1000, initPayload: somePayload, }; try { const { programId, codeId, salt, extrinsic } = api.program.upload( program, meta, ); } catch (error) { console.error(`${error.name}: ${error.message}`); } try { await extrinsic.signAndSend(keyring, (event) => { console.log(event.toHuman()); }); } catch (error) { console.error(`${error.name}: ${error.message}`); } ``` To calculate the gas required for processing an `init` message, use the following method: `api.program.calculateGas.initUpload()` For a deeper dive into the gas mechanics, see the [Calculate Gas](/docs/developing/api/calculate-gas) documentation. # Gas Vouchers Vouchers empower users with gas-free interactions, allowing seamless messaging to specific programs from any actor. An example of using vouchers is shown in the [Battleship](/docs/vara-network/examples/Gaming/Battleship) game. Users without tokens on their balance can make moves by sending messages to a program using a voucher. Issue a Voucher [#issue-a-voucher] Use the `api.voucher.issue` method to issue a new voucher for a user, which can be used to pay for sending messages to programs. ```javascript import { VoucherIssued } from '@gear-js/api'; const programs = ['0x1234...', '0x5678...']; const spenderAddress = '0x...'; const validForOneHour = (60 * 60) / 3; // number of blocks in one hour const { extrinsic } = await api.voucher.issue(spenderAddress, 100 * 10 ** 12, validForOneHour, programs, true); // To enable the voucher for code uploading, set the last argument of the `.issue` method to true. extrinsic.signAndSend(account, ({ events }) => { const voucherIssuedEvent = events.find(({event: { method }}) => method === 'VoucherIssued')?.event as VoucherIssued; if (voucherIssuedEvent) { console.log(voucherIssuedEvent.toJSON()); } }) ``` Check Voucher Existence [#check-voucher-existence] The `api.voucher.exists` method verifies the existence of a voucher for a specific user and program. It returns a boolean value indicating whether the voucher exists. ```javascript const voucherExists = await api.voucher.exists(accountId, programId) ``` Retrieve All Vouchers for an Account [#retrieve-all-vouchers-for-an-account] The `api.voucher.getAllForAccount` method retrieves an object in which the key is the voucher ID and the value is an array of programs for which the voucher can be utilized. ```javascript const allVouchers = await api.voucher.getAllForAccount(accountId); ``` Get voucher details [#get-voucher-details] ```javascript const details = api.voucher.details(spenderAddress, voucherId); console.log(`Voucher details: owner: ${details.owner} programs: ${details.programs} expiry: ${details.expiry}`); ``` Send a Message Using the Issued Voucher [#send-a-message-using-the-issued-voucher] To send a message with a voucher, you can use the `api.voucher.call` method. ```javascript const messageTx = api.message.send({ destination: destination, payload: somePayload, gasLimit: 10000000, value: 1000 }, meta); const voucherTx = api.voucher.call(voucherId, { SendMessage: messageTx }); await voucherTx.signAndSend(account, (events) => { console.log(events.toHuman()); }); ``` Send a Reply with an Issued Voucher [#send-a-reply-with-an-issued-voucher] Sending a reply with an issued voucher works in the same way as sending a message with a voucher. ```javascript const messageTx = api.message.sendReply(...); const voucherTx = api.voucher.call(voucherId, { SendReply: messageTx }); await voucherTx.signAndSend(account, (events) => { console.log(events.toHuman()); }); ``` Upload Code with an Issued Voucher [#upload-code-with-an-issued-voucher] ```javascript const { extrinsic } = await api.code.upload(code); const tx = api.voucher.call(voucherId, { UploadCode: extrinsic }) await tx.signAndSend(account, (events) => { console.log(events.toHuman()); }); ``` Update a Voucher [#update-a-voucher] The `api.voucher.update` method can be used to update a voucher. All parameters in the third argument are optional; however, at least one parameter must be specified. ```javascript const tx = await api.voucher.update( spenderAddress, voucherId, { moveOwnership: newOwnerAddress, // The new voucher owner balanceTopUp: 1_000 * 10 ** 12, // Top up voucher balance appendPrograms: ['0x9123...', '0x4567...'], // Append programs for which the voucher can be used prolongValidity: 1_000_000 // Prolong the voucher validity for 1_000_000 blocks } ) ``` Revoke a Voucher [#revoke-a-voucher] The `api.voucher.revoke` method is used to revoke an issued voucher. A voucher can only be revoked after its validity period has expired. Unused funds are returned to a voucher issuer. ```javascript const tx = api.voucher.revoke(spenderAddress, voucherId); tx.signAndSend(...); ``` Decline a Voucher [#decline-a-voucher] The `api.voucher.decline` method can be used to decline an existing voucher that has not yet expired. This action will mark the voucher as expired so you will be able to Revoke it. ```javascript const tx = api.voucher.decline(voucherId); tx.signAndSend(...); ``` Get Voucher Constants [#get-voucher-constants] ```javascript // Minimum and maximum duration of the voucher in blocks const minBlocks = await api.voucher.minDuration(); const maxBlocks = await api.voucher.maxDuration(); // Maximum amount of programs that can be added to the voucher const maxProgramsAmount = await api.voucher.maxProgramsAmount(); ``` # Bridge Guideline [**EitherWay**](https://bridge.vara.network/) – the officially supported bridge interface designed for asset transfers between Vara and Ethereum. It currently supports transfers for the following assets: * **VARA (Vara Native) ↔ wVARA** (on Ethereum) * **ETH (Ethereum Native) ↔ wETH** (on Vara) * **USDT (on Ethereum) ↔ wUSDT** (on Vara) * **USDC (on Ethereum) ↔ wUSDC** (on Vara) * **BTC (on Ethereum) ↔ wBTC** (on Vara) *** Quick Guide: Bridging Assets via EitherWay [#quick-guide-bridging-assets-via-eitherway] Wallet Requirements [#wallet-requirements] **From Vara → Ethereum** Connect your Vara wallet by selecting one of [the supported wallets](https://vara.network/ecosystem?tags=6382654c-162d-4ed7-9215-b9504b60e72c). **From Ethereum → Vara** Connect your Ethereum-compatible wallet (e.g., [MetaMask](https://metamask.io/), [Trust Wallet](https://trustwallet.com/) etc) *** Step 1: Select Asset & Amount [#step-1-select-asset--amount] * Pick the asset you want to transfer (**VARA**, **USDC**, **USDT** or **ETH**). * Specify the **exact amount** you want to bridge. *** Step 2: Enter Destination Address [#step-2-enter-destination-address] * Provide the **receiving wallet address** on the **target network**. *** Step 3: Confirm and Initiate Swap [#step-3-confirm-and-initiate-swap] * If your wallet is not already connected, click **Connect Wallet** * Click **Transfer** to start the process * Review the **transaction details** * Approve and **sign the transaction** *** Step 4: Track Your Transaction [#step-4-track-your-transaction] The **Transactions** tab lets you check the status of current and past transfers without leaving the interface. Transfers usually complete within **\~30 minutes**. *** Step 5: Claim Native VARA (if bridging from Ethereum) [#step-5-claim-native-vara-if-bridging-from-ethereum] If you bridged **wVARA** from Ethereum back to Vara, the tokens will first arrive in your account as VFT tokens managed by the bridge. To convert them into native VARA, you’ll need to **unlock** them manually: * Go to the “My Tokens” tab * Make sure your **Substrate wallet is connected** * Click **Unlock** next to your VFT token balance The gas token required always depends on the **source network**: * **Vara → Ethereum**: You only need **VARA** tokens. *(No ETH is required for the initial transfer.)* * **Ethereum → Vara**: You need **ETH**, **regardless** of the asset you are bridging (VARA, ETH, or stablecoins). *Tip: Always keep a small amount of the native gas token in your source wallet to avoid transaction failures.* # Technical Overview The bridge architecture has undergone a professional security audit. All critical findings and edge cases have been fully addressed in the final implementation. The audit confirms the safety of core components: * **Message Validation** & **Token Logic** * **Relayer Incentives** * **Real-world condition stability** [📄 View Full Audit Report (PDF)](https://github.com/gear-tech/gear-bridges/blob/main/audits/ethernal.pdf) To explore the bridge implementation details, visit the official repository: [🔗 Bridge Implementation Repository](https://github.com/gear-tech/gear-bridges) The Tech Behind the Bridge [#the-tech-behind-the-bridge] The Vara ↔ Ethereum Bridge is the product of deep technical architecture and real-world experience in cross-chain interoperability and cryptography. It brings together a set of purpose-built technologies that prioritize security, scalability, and trustless execution — all while remaining compatible with existing Ethereum infrastructure. At the foundation of the bridge lies a modern cryptographic stack, including `Plonky2` for high-speed recursive zero-knowledge proofs and `GNARK` for zk-proof verification inside Ethereum contracts. These tools allow the bridge to verify validator consensus and message authenticity without revealing sensitive data — a critical feature for decentralized networks. To ensure minimal gas costs and maximum verifiability, the system relies on `Merkle Trees` for compact message commitments, and integrates `Ethereum’s Beacon Chain` API and `Sync Committee` to independently validate Ethereum block finality on the Vara side. On Vara, the bridge logic is powered by the **Gear runtime** and a set of specialized programs and pallets that coordinate message queues, validator sets, and light client behavior. This combined stack allows the bridge to operate without trusted intermediaries — every proof, message, and block is cryptographically verifiable on-chain, laying the groundwork for a secure and extensible messaging system between Ethereum and Vara. Conceptual Flow: How the Bridge Works [#conceptual-flow-how-the-bridge-works] From a user’s point of view, bridging tokens or data between Vara and Ethereum may feel like a simple operation — click, confirm, done. But under the hood, the system follows a strict flow that ensures only finalized, verified information is ever transmitted across chains. From Vara to Ethereum [#from-vara-to-ethereum] The process begins with finalized blocks. When a user performs an action that generates a message to Ethereum — such as locking VARA tokens. These messages are collected by the `gear_eth_bridge` pallet and stored in an outbound queue. Every time a block is produced on Vara, a new Merkle root of the message queue is calculated. Relayers — permissionless, decentralized actors — then take over. They generate a zero-knowledge proof that this Merkle root was signed by the active validator set on Vara. This proof, along with metadata such as the block number and validator snapshot, is submitted to a `Proof Verification Contract` on Ethereum. This contract uses `gnark` to check the validity of the proof. If the zk-proof is valid, Ethereum records the Merkle root and considers it an approved source of truth. After that, any individual message from Vara can be submitted to Ethereum through the `Bridge Message Contract`, along with a Merkle proof of inclusion. The contract checks that the message: * Belongs to an approved Merkle root. * Has not already been processed. If both conditions are met, the message is marked as used, and the corresponding action (e.g., minting tokens) is triggered. From Ethereum to Vara [#from-ethereum-to-vara] In the reverse direction, the system does not use zk-proofs. Instead, it relies on Ethereum’s native finality mechanism and event logs. Relayers continuously monitor Ethereum’s **Beacon Chain and Sync Committee** — a rotating group of Ethereum validators that sign finalized block headers. Once a block is finalized, a relayer submits the block header and associated sync-committee signatures to Vara. On the Vara side, a `checkpoint light client` program verifies that the submitted block was indeed finalized, using the aggregated BLS signatures from the `sync committee`. Once Vara accepts the block as finalized, any relevant event emitted on Ethereum (e.g., a Burn event from the wVARA Manager contract) can be proven via a Merkle proof. These proofs are then consumed on the Vara side, such as the VFT Manager program, which performs the appropriate on-chain logic, like minting VARA tokens. In simple terms, the **VFT Manager** acts as an intermediary between the bridge and the token contract: * **Action:** It receives commands from the **Vara-Ethereum Bridge**. * **Execution:** It triggers the corresponding actions in the token contract, such as **minting** or **unlocking** assets. Example Transaction Flow [#example-transaction-flow] Let’s walk through an end-to-end example: 1. A user wants to move VARA to Ethereum. 2. They lock 100 VARA via the VFT Manager on Vara side. 3. This action generates a message, stored in the outbound queue. 4. After finality, the `gear_eth_bridge` takes a new Merkle root. 5. A relayer generates a zk-proof and submits it to the Ethereum `Proof Verification Contract`. 6. The root is accepted, and the message can now be submitted — typically by a relayer — along with a Merkle proof. 7. The `wVARA Manager ERC-20 contract` mints 100 wVARA to the user's address. The reverse flow (burn wVARA on Ethereum → unlock VARA on Vara) follows the same structure in reverse, with checkpoints, events, and proofs flowing through Ethereum light clients and message verifiers on Vara. 1. The user sends wVARA to the wVARA Manager contract on Ethereum and triggers a Burn event. 2. The burn is recorded in a block finalized by Ethereum’s Beacon Chain. 3. A relayer submits the finalized header and signatures to Vara’s light client. 4. Vara accepts the block and processes the event via the VFT Manager. 5. Native VARA are minted or unlocked to the user’s account on Vara. This dual-model architecture — zk-proofs in one direction, consensus/event-based validation in the other — ensures both efficiency and robustness. Transport Layer [#transport-layer] The transport layer is the core of the bridge — responsible for ensuring that messages are collected, verified, and delivered correctly across both networks. These components make sure each side of the system speaks the same language — securely and consistently. Vara Side Components [#vara-side-components] The main actor here is the `gear_eth_bridge pallet`. It serves as the orchestrator for outbound and inbound messages: * Maintains the current validator set * Tracks finalized blocks and outbound message queue * Computes the Merkle root for finalized message batches * Stores metadata necessary for ZK-proof generation Other important modules are programs: `Checkpoint Light Client` — A minimal Ethereum Beacon Chain client that stores finalized block headers and Sync Committee sets. It: * Accepts newly finalized headers and stores them after verification. * Enables trustless validation of Ethereum-side data. `Ethereum Event Client` — This program processes Ethereum-side events by: * Cross-checking data with the Checkpoint Light Client * Verifying Merkle Patricia proofs that confirm a transaction is part of a block * Decoding transaction receipts and events `Historical Proxy` — A routing layer between users and multiple versions of the Ethereum Event Client. It chooses the correct version of the event client to handle a given request based on Ethereum’s slot number. Ethereum evolves over time, and its data structures or client versions may change. The **Historical Proxy** ensures seamless compatibility by **automatically selecting the correct client version** based on the specific block’s slot, allowing the bridge to process historical data without errors. Ethereum Side Components [#ethereum-side-components] Ethereum hosts a few key contracts that handle the bridge logic. Each of these components plays a focused role in message handling and state verification: `Proof Verification Contract` — Verifies zk-proofs submitted by relayers using the `gnark` proof system. `Relayer Contract` — Accepts proofs and Merkle roots from relayers and emits events when they’re accepted. `MessageQueue Contract` — Verifies messages included in a Merkle root and ensures they haven't been used before. `ProxyUpdater Contract` — Processes system-level updates, such as governance-approved changes to bridge components. `Event Submission Interface` — A helper contract for relayers to submit finalized Ethereum blocks and event data, used when sending messages from Ethereum to Vara. Relayers [#relayers] Relayers play a crucial role in the bridge’s cross-chain communication model. These are independent, permissionless actors that deliver messages, block headers, and proofs between Ethereum and Vara. Relayers do not validate or interpret the data they carry. Instead, they act as messengers that: * Submit zk-proofs and Merkle roots from Vara to Ethereum * Submit finalized Ethereum headers and transaction data to Vara Because the bridge is **fully permissionless**, anyone can run a relayer — no special access is required. This ensures decentralization and availability across different network conditions. Relayers may also be economically incentivized. Each successful message delivery or header submission can result in a reward, ensuring that relayers are motivated to stay online and maintain service availability. They are **replaceable, stateless, and independent by design** — if one stops, another can continue without any coordination. Many can run simultaneously. To ensure network liveness, the system supports optional bridging payment contracts on both sides: * **Automatic:** Users pay a small fee during message initiation, which relayers claim as an incentive. * **Manual:** Users can relay messages themselves without an automatic fee, though in practice, professional relayers require incentives to operate. This dual approach provides flexibility while maintaining **relayer incentives** for a smooth user experience. Because all bridging data is stored **on-chain** (using Merkle Trees), the system is inherently resilient: * **Persistence:** No data is lost during downtime. * **Recovery:** Any new or returning relayer can simply retrieve the proofs and resume transmission. * **Security:** This design ensures **liveness** and eliminates central points of failure. Smart Contract Layer: Token Logic & Extensibility [#smart-contract-layer-token-logic--extensibility] On top of the transport and validation infrastructure lies the token logic layer — responsible for handling tokens and business logic on both chains. On both sides of the bridge, the architecture is built around two elements: * Asset managers (e.g., ERC20Manager, VFT Manager) * Asset contracts (e.g., ERC-20 tokens or FT programs) **Managers** are responsible for: * Coordinating token logic — minting, burning, locking, or unlocking * Validating and processing verified bridge messages These managers coordinate with multiple components in the system — such as `gear_eth_bridge` on Vara, or `MessageQueue` on Ethereum — to ensure that only verified and authorized operations take place. The asset contracts are independent and modular. Developer Extensibility [#developer-extensibility] The bridge was built to be extensible and permissionless. Developers can: * Build their own token managers or message handlers * Extend the bridge with custom asset (NFTs, game assets, or DeFi primitives) Integration is subject to DAO governance — allowing the community to review, approve, and add new logic to the bridge registry This ensures the bridge is not limited to any specific asset type and can adapt to the needs of the evolving ecosystem. Governance Layer [#governance-layer] The governance system ensures that the bridge remains secure, upgradeable, and adaptable to ecosystem needs — all without requiring disruptive hard forks. At its core, the bridge uses a customized version of **Substrate’s OpenGov pallet**, adapted specifically for cross-chain infrastructure. This system allows the community to submit and vote on proposals that can directly influence the bridge’s behavior through on-chain execution. Through proposals the bridge allows: * **Expanding bridge functionality** - Add new tokens, assets, or modules without interrupting operations. * **Smart contract upgrades** - Seamlessly upgrade Ethereum contracts using a proxy pattern, and update logic on Vara via trusted admin messages. * **Tuning economic parameters** - Adjust fees, relayer incentives, and operational limits through proposals. * **Emergency control** - Pause or restrict bridge activity in response to critical threats, based on transparent community consensus. The bridge’s **DAO logic** is natively integrated with its transport layer to ensure secure upgrades: * **Mechanism:** Administrative messages sent through governance are automatically recognized by both smart contracts and pallets. * **Benefit:** This allows for **controlled updates** and parameter tuning without bypassing the on-chain security protocols. Governance makes the bridge resilient and **future-proof**: * **Community-Driven:** Any member can propose improvements or architectural updates. * **Seamless Execution:** Once a proposal is approved via the DAO, changes are implemented automatically with **no downtime** or manual intervention. This ensures the bridge evolves alongside the needs of the ecosystem while maintaining total transparency. *** Further Reading [#further-reading] * [Plonky2 – Recursive SNARKs](https://github.com/0xPolygonZero/plonky2) * [Gnark – zkSNARKs on Go](https://github.com/ConsenSys/gnark) * [Ethereum Beacon Chain – Consensus Specification](https://github.com/ethereum/consensus-specs/tree/dev/specs/bellatrix) * [Merkle Trees Explained](https://ethereum.org/en/developers/docs/data-structures-and-encoding/patricia-merkle-trie/) * [Substrate OpenGov – Governance Pallet](https://docs.polkadot.com/polkadot-protocol/onchain-governance/overview/) # Ethereum → Vara App Guide Introduction [#introduction] This article describes the flow for building a cross-chain application from **Ethereum to Vara**. The guide demonstrates how to design an event-based “ping-pong” dApp, where the message originates on Ethereum and is securely delivered and processed on Vara. Unlike the reverse direction (Vara → Ethereum), where ZK-proofs are used to guarantee trustless delivery, the **Ethereum → Vara** route is based on a **Light Client** mechanism. Here, there are no ZK-proofs on the Ethereum side. Instead, trust is achieved through on-chain light client validation of Ethereum’s consensus — specifically, through the **beacon chain** and its finalized checkpoints. In this design, **event logs** on Ethereum serve as the source of truth. Applications emit events as part of contract execution. These events are included in Ethereum's canonical chain history (the beacon chain), and can be cryptographically proven by anyone — but only after finality is reached on Ethereum. A permissionless relayer observes the beacon chain, builds receipt proofs for the relevant events, and submits them to Vara. On Vara, a light client validates that the proof corresponds to an actually finalized event on Ethereum, and only then is the custom Vara program triggered. This mechanism is fundamentally **event-based** and asynchronous. Application developers are not required to build the bridge or proof logic themselves — only to emit and handle events, while the trustless bridge and relayers manage the cross-chain transport. For simplification, developers can now use the ready-made [**GearJS Bridge library**](https://github.com/gear-tech/gear-bridges/tree/main/js/bridge-js), which performs most of the heavy lifting: proof generation, slot synchronization, and submission to Vara. This allows application developers to focus purely on emitting events on Ethereum and writing the receiving logic on Vara. *** High-Level Architecture [#high-level-architecture] [Complete source code on GitHub](https://github.com/gear-foundation/cross-ping/tree/master/packages) The Ethereum → Vara cross-chain application consists of three core components: 1. **Ethereum Smart Contract (Ping Emitter)**\ Minimal contract `EthPinger` with `ping()` that emits `PingFromEthereum(address indexed from)`. Location: [`eth-vara-pinger-eth/`](https://github.com/gear-foundation/cross-ping/tree/master/packages/eth-vara-pinger-eth) 2. **Relayer Service (Off-chain)** * Connects to Ethereum and Vara. * Subscribes to `PingFromEthereum` (Ethereum). * For each event: calls `relayEthToVara({ transactionHash, ... })`. * Optionally sets `wait: true` to block until the required checkpoint is finalized. Location: [`eth-vara-relayer-node/`](https://github.com/gear-foundation/cross-ping/tree/master/packages/eth-vara-relayer-node) 3. **Vara Program (Receiver)** * **Checkpoint Light Client** — tracks finalized Ethereum checkpoints. * **Historical Proxy** — validates proofs and forwards messages. * **Ping Receiver** — your program exposing `SubmitReceipt(slot, transaction_index, receipt_rlp)`. Location: [`eth-vara-receiver-vara/`](https://github.com/gear-foundation/cross-ping/tree/master/packages/eth-vara-receiver-vara) *** Implementation Details [#implementation-details] Ethereum Side [#ethereum-side] A minimal contract that emits the event: ```solidity // SPDX-License-Identifier: MIT pragma solidity ^0.8.24; contract EthPinger { event PingFromEthereum(address indexed from); function ping() external { emit PingFromEthereum(msg.sender); } } ``` Relayer Side (using the library) [#relayer-side-using-the-library] The relayer’s job can be summarized in **four steps**: connect to Ethereum, connect to Vara, listen for events, and relay them using the library. Create Ethereum clients (HTTP + WS) [#create-ethereum-clients-http--ws] ```ts // via viem export const ethereumPublicClient = createPublicClient({ ... }); export const ethereumWalletClient = createWalletClient({ ... }); // via ethers (for WS subscriptions) const ethWs = new ethers.WebSocketProvider(ETHEREUM_WS_RPC_URL); listenPingFromEthereum(ethWs, (from, event) => { const txHash = event.transactionHash; handleEvent(txHash); }); ``` Connect to Vara and initialize Sails + bridge events [#connect-to-vara-and-initialize-sails--bridge-events] ```ts varaProvider = await GearApi.create({ providerAddress: VARA_RPC_URL }); sails = new Sails(parser); sails.parseIdl(CROSS_PING_IDL); sails.setApi(varaProvider); sails.setProgramId(CROSS_PING_PROGRAM_ID); listenPingSent((e) => { console.log("Ping confirmed on Vara:", e); }); ``` Subscribe to Ethereum event [#subscribe-to-ethereum-event] ```ts const contract = new ethers.Contract(ETH_CONTRACT_ADDRESS, ETH_PINGER_ABI, provider); contract.on("PingFromEthereum", (_, payload) => { const txHash = payload.log.transactionHash; handleEvent(txHash); }); ``` Relay to Vara via GearJS Bridge [#relay-to-vara-via-gearjs-bridge] ```ts import { relayEthToVara } from "@gear-js/bridge"; async function handleEvent(txHash: `0x${string}`) { const { ok, error } = await relayEthToVara({ transactionHash: txHash, beaconRpcUrl: BEACON_API_URL, ethereumPublicClient, gearApi: varaProvider!, checkpointClientId: CHECKPOINT_LIGHT_CLIENT, historicalProxyId: HISTORICAL_PROXY_ID, clientId: PING_RECEIVER_PROGRAM_ID, clientServiceName: "PingReceiver", clientMethodName: "SubmitReceipt", signer, wait: true, }); if (error) console.error("Proxy error:", error); else console.log("Relayed OK:", ok); } ``` That’s all: **listen to the Ethereum event → forward tx hash → the library handles proofs & finality**.\ Full code is available in the [relayer package](https://github.com/gear-foundation/cross-ping/tree/master/packages/eth-vara-relayer-node). Vara Side [#vara-side] Your program exposes a service method that the Historical Proxy will call **after** validation.\ **The method must be exported** so it’s invokable externally. A minimal implementation that emits an internal event on receipt: ```rust #![no_std] use sails_rs::prelude::*; #[sails_rs::event] #[sails_rs::sails_type] pub enum Event { ReceiptSubmitted(u64, u32), } pub struct PingReceiverService; #[service(events = Event)] impl PingReceiverService { #[export] pub fn submit_receipt( &mut self, slot: u64, transaction_index: u32, _receipt_rlp: Vec, ) -> Result<(), String> { self.emit_event(Event::ReceiptSubmitted(slot, transaction_index)) .map_err(|_| "Failed to emit event".to_string())?; Ok(()) } } ``` Program location: * [`eth-vara-receiver-vara/`](https://github.com/gear-foundation/cross-ping/tree/master/packages/eth-vara-receiver-vara) **Names must match** what you pass from the relayer: * `clientServiceName = "PingReceiver"` * `clientMethodName = "SubmitReceipt"` Verification [#verification] Slot synchronization with the Light Client takes around 15–20 minutes (after the Ethereum event is finalized). Once the slot is finalized, a message from HistoricalProxy is delivered to the application program on Vara. # Vara → Ethereum App Guide Cross-chain communication can seem complex, but at its core, it’s about securely moving messages and value between independent blockchains. To make this process clear and approachable, a simple Ping-Pong application is used as the most illustrative example, showing step by step how secure cross-chain messaging works on top of the **Vara ↔ Ethereum Bridge**. The **ping-pong pattern** is a classic distributed systems example used to demonstrate reliable message delivery: * **Ping:** A simple message sent from the source chain. * **Pong:** A response received back from the target chain. This pattern confirms that the **Vara-Ethereum Bridge** transport layer is operating correctly in both directions. This guide focuses on the flow from Vara to Ethereum using the bridge’s built-in ZK-based mechanism. The protocol handles the heavy lifting — cryptographic proofs, relaying, and validation — allowing developers to concentrate on the application logic. Future articles will explore the reverse direction (Ethereum → Vara) and additional cross-chain patterns. High-Level Architecture [#high-level-architecture] At a high level, the cross-chain ping-pong app consists of three main parts, each focused on its own responsibility while relying on the bridge’s secure protocol layer: 1. **On Vara:** a Gear Program in Rust acts as the Ping sender. It packages a simple payload and sends it using the built-in bridge API, specifying the destination contract on Ethereum. [See Vara Program Code](https://github.com/gear-foundation/cross-ping/tree/master/packages/vara-eth-pinger-vara) 2. **In the middle:** a permissionless relayer listens for events and — once the Merkle root for the message is submitted to Ethereum — relays the message for final delivery. [See Relayer Code](https://github.com/gear-foundation/cross-ping/tree/master/packages/vara-eth-relayer-node) 3. **On Ethereum:** a specialized MessageQueue contract verifies the root and proof. If valid, it forwards the message to your destination contract.\ [See Ping Receiver Code](https://github.com/gear-foundation/cross-ping/tree/master/packages/vara-eth-receiver-eth) What You Don’t Implement [#what-you-dont-implement] As a developer, you do **not need to implement** the transport layers yourself. The bridge infrastructure handles these complex operations automatically: * **Calculations:** Merkle root and proof generation. * **Security:** ZK-inclusion proofs. * **Sync:** Cross-chain state synchronization. This allows you to focus purely on your application logic while the protocol manages the underlying **Vara-Ethereum** communication. Your responsibility is **only your app’s custom logic**: how to format the message and how to process it on arrival. If you want a deep dive into the protocol’s internals — including proof generation, validator set management, and message processing — please see the official technical overview: [**Vara ↔ Ethereum Bridge Technical Overview**](/docs/bridge/bridge_technical) The bridge’s protocol ensures that Ethereum can trust which messages were sent from Vara, so you never need to worry about the details of proof construction or on-chain validation.\ Everything you need for secure message delivery is already built in. *** Vara → Ethereum Cross-Chain App’s Implementation Details [#vara--ethereum-cross-chain-apps-implementation-details] Let’s break down the flow and key implementation details of our `CrossPing` app on Vara, which is responsible for initiating the cross-chain ping. [See Vara Program Source](https://github.com/gear-foundation/cross-ping/tree/master/packages/vara-eth-pinger-vara) Application Flow (Vara) [#application-flow-vara] 1. **Initialize the destination:** The program is initialized with the address (H160) of the Ethereum contract that will receive the ping. 2. **Send a ping:** When triggered, it prepares a payload (just the sender’s `ActorId`) and constructs a bridge request with it. 3. **Send the message via the built-in bridge:** It uses a built-in actor (Gear-Ethereum Bridge) to submit the message using `send_bytes_with_gas_for_reply`, which handles gas and bridge fee logic. 4. **Wait for bridge response:** The bridge responds with a nonce and a message hash, confirming message inclusion. 5. **Emit event for relayer:** The program emits a `PingSent` event with all details. This is what the relayer listens for. *** Key Components and Interfaces [#key-components-and-interfaces] * **Built-in Bridge Actor ID:** ```rust const BRIDGE_ACTOR_ID: [u8; 32] = [ /* ... see full source ... */ ]; ``` * **Destination Contract Address:**\ Set at initialization via `new(destination: H160)`. * **Bridge Message Format:** ```rust BridgeRequest::SendEthMessage { destination, payload } ``` * **Gas & Fee Constants:** ```rust const GAS_TO_SEND_REQUEST: u64 = 200_000_000_000; const FEE_BRIDGE: u128 = 0; const GAS_FOR_REPLY_DEPOSIT: u64 = 200_000_000_000; ``` * **Event Emission:**\ Emits: ```rust Event::PingSent(PingSent { nonce: Some(nonce.as_u64()), message_hash: hash, }) ``` *** Example: Sending a Ping from Vara [#example-sending-a-ping-from-vara] ```rust pub async fn send_ping(&mut self) -> Result<(), Error> { let state = unsafe { STATE.as_ref().expect("State not initialized") }; let destination = state.destination.ok_or(Error::DestinationNotInitialized)?; let sender = exec::program_id(); let payload = sender.as_ref().to_vec(); let bridge_actor_id = ActorId::from(BRIDGE_ACTOR_ID); let request = BridgeRequest::SendEthMessage { destination, payload }.encode(); let reply_bytes = msg::send_bytes_with_gas_for_reply( bridge_actor_id, request, GAS_TO_SEND_REQUEST, FEE_BRIDGE, GAS_FOR_REPLY_DEPOSIT, ) .map_err(|_| Error::BridgeSendFailed)? .await .map_err(|_| Error::BridgeReplyFailed)?; let reply = BridgeResponse::decode(&mut &reply_bytes[..]) .map_err(|_| Error::InvalidBridgeResponse)?; if let BridgeResponse::EthMessageQueued { nonce, hash } = reply { self.emit_event(Event::PingSent(PingSent { nonce: Some(nonce.as_u64()), message_hash: hash, }))?; } Ok(()) } ``` *** Relayer [#relayer] The relayer is a Node.js service that connects both to Vara and Ethereum, listens for events, and relays messages once the Merkle root is confirmed on Ethereum. [See Relayer Code](https://github.com/gear-foundation/cross-ping/tree/master/packages/vara-eth-relayer-node) How the Relayer Works [#how-the-relayer-works] 1. **Connects to Vara and Ethereum:** ```ts const gearApi = await connectVara(); ``` 2. **Listens for PingSent events:** ```ts await listenPingSent(async ({ nonce, blockNumber, messageHash }) => { console.log(`[Relay] PingSent: nonce=${nonce}, block=${blockNumber}, hash=${messageHash}`); // ... }); ``` 3. **Relays the message with built-in wait mechanism:** In the latest version (`@gear-js/bridge@0.1.x`), the `relayVaraToEth` function accepts a `wait: true` flag that internally waits for the Merkle root to appear on Ethereum before relaying. ```ts const result = await relayVaraToEth({ nonce, blockNumber, ethereumPublicClient, ethereumWalletClient, ethereumAccount: account, gearApi, messageQueueAddress: MESSAGE_QUEUE_PROXY_ADDRESS, wait: true, statusCb: (status, details) => { console.log(`[Relay] [Status]`, status, details); } }); ``` *** Ethereum Side: Ping Receiver Contract [#ethereum-side-ping-receiver-contract] A minimal contract that accepts messages via the bridge and emits `PongEmitted`. ```solidity // SPDX-License-Identifier: MIT pragma solidity ^0.8.24; interface IMessageHandler { function handleMessage(bytes32 source, bytes calldata payload) external; } contract PingReceiver is IMessageHandler { event PongEmitted(bytes32 sender); function handleMessage(bytes32 sender, bytes calldata) external override { emit PongEmitted(sender); } } ``` Summary [#summary] * Your contract must implement the `IMessageHandler` interface. * The `handleMessage(sender, payload)` function is called by the MessageQueue. * You can do anything you want with the payload inside this method. *** End-to-End Verification [#end-to-end-verification] After sending a ping: 1. **Check MessageQueue contract** on Ethereum for message processing events. 2. **Check your PingReceiver contract** for the `PongEmitted` event. 3. The relayer logs the full tx hash after successful delivery: ```text [Relay] ✅ Success: txHash=0xabc123... ``` If the logs and events appear correctly on Etherscan (or your block explorer), everything worked as expected. # Developer Hub import { ethMainnet, varaMainnet } from '@/addresses/bridge-addresses.js'; * **Bridge:** [https://bridge.vara.network/](https://bridge.vara.network/) **Use this section to:** * Explore and verify bridge-related contracts * Begin building integrations with the bridge (e.g. relayers, token managers, custom assets) *** Vara Bridge Deployments [#vara-bridge-deployments] Vara Mainnet is the only public network. The bridge testnet deployment has been decommissioned along with the Vara testnet - build and integrate directly against the production addresses below.
Ethereum Mainnet [#ethereum-mainnet] Core Components [#core-components] | Component | Address | | -------------------- | ------------------------------------------------------------------------------------------------------------------------ | | **Verifier** | {ethMainnet.verifier} | | **MessageQueue** | {ethMainnet.messageQueue} | | **Bridging Payment** | {ethMainnet.bridgingPayment} | Token Contracts [#token-contracts] | Token | Address | | --------- | ---------------------------------------------------------------------------------------------------- | | **USDC** | {ethMainnet.usdc} | | **USDT** | {ethMainnet.usdt} | | **WETH** | {ethMainnet.WETH} | | **WVARA** | {ethMainnet.wvara} | | **WBTC** | {ethMainnet.wbtc} | ERC20 Manager [#erc20-manager] | Component | Address | | ---------------- | ------------------------------------------------------------------------------------------------------------------ | | **ERC20Manager** | {ethMainnet.erc20Manager} | Bridge Governance [#bridge-governance] | Component | Address | | -------------------- | -------------------------------------------------------------------------------------------------------------------------- | | **GovernanceAdmin** | {ethMainnet.governanceAdmin} | | **GovernancePauser** | {ethMainnet.governancePauser} | *** Vara Mainnet [#vara-mainnet] Core Components [#core-components-1] | Component | Address | | --------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | **checkpoint-light-client** | {varaMainnet.checkpointLightClient} | | **historical-proxy** | {varaMainnet.historicalProxy} | | **vft-manager** | {varaMainnet.vftManager} | | **Bridging Payment** | {varaMainnet.bridgingPayment} | Token Contracts [#token-contracts-1] | Token | Address | | ------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **WUSDC** | {varaMainnet.wusdc} | | **WUSDT** | {varaMainnet.wusdt} | | **WETH** | {varaMainnet.weth} | | **WBTC** | {varaMainnet.wbtc} | | **Tokenized VARA** | {varaMainnet.tokenizedVara} |
# Vara ↔ Ethereum Bridge Overview * **Bridge:** [https://bridge.vara.network/](https://bridge.vara.network/) **Vara Bridge** is a trustless, ZK-based cross-chain bridge designed to securely transfer tokens and data between Ethereum and the Vara Network. By leveraging best practices in cryptographic security (such as Plonky2, Merkle Trees, and Gnark), the bridge validates transactions without revealing sensitive information. As a result, the Vara Bridge minimizes risks of manipulation or fraud while delivering efficient cross-chain transfers. In short, the bridge lets you effortlessly transfer `tokens`, `NFTs`, and `data`, enhancing their liquidity, usability, and accessibility across blockchain ecosystems. What Does the Vara Bridge Mean for the Vara Ecosystem and Its Projects? [#what-does-the-vara-bridge-mean-for-the-vara-ecosystem-and-its-projects] The bridge unlocks significant opportunities for the entire Vara ecosystem, benefiting both the ecosystem itself and individual projects built on Vara: **Greater Liquidity** Easily transfer your assets between Ethereum and Vara, expanding trading opportunities and enhancing asset liquidity across multiple platforms. * Direct access to popular Ethereum DEXs (Balancer, Uniswap, HyperLiquid, etc.). * Potential listings on centralized exchanges supporting Ethereum-based assets. * Broader exposure to Ethereum’s comprehensive DeFi infrastructure. **Enhanced Utility** Bridging ensures your digital assets are no longer locked to one blockchain. Tokens and NFTs can seamlessly move and be utilized across various decentralized applications, significantly increasing their functionality and value. **Developer Empowerment** Developers can build powerful, cost-effective dApps by combining Ethereum’s extensive ecosystem with Vara’s efficient execution environment. A game developer can mint NFTs on **Ethereum** to leverage its massive liquidity, then bridge them to the **Vara Network** to enable: * **Speed:** Near-instant in-game transactions. * **Cost:** Significantly lower gas fees for player interactions. *In short, the Vara-Ethereum Bridge maximizes the versatility and value of digital assets, creating new possibilities across blockchains.* Special Case: Gear.exe [#special-case-gearexe] The Vara-Ethereum Bridge plays a critical role for **[Gear.exe](https://gear-tech.io/gear-exe)** — an advanced coprocessor technology significantly enhancing Ethereum’s computational capabilities. Gear.exe uses the native VARA token deployed on Ethereum for execution fees and network security, making the seamless bridging of VARA tokens essential. Want to master the technology behind the Vara Ethereum layer? Explore all architectural details and features in the [Gear.exe Whitepaper](https://eth.vara.network/whitepaper). Is it Secure? [#is-it-secure] Yes. The Vara-Ethereum Bridge employs advanced Zero-Knowledge Proof (ZK-proof) technology, ensuring your transactions and data transfers remain confidential and secure. Internally, the bridge utilizes cryptographic proofs, validator consensus, and robust governance mechanisms to guarantee safety and prevent duplication or unauthorized access. The bridge is designed to be secure by default — providing a safe and straightforward experience for all users, regardless of technical knowledge. Curious about the underlying mechanics and security? Explore these resources: * **Technical Details:** Read our [comprehensive architecture overview](/docs/vara-network/bridge/bridge_technical). * **Security & Code:** Review the bridge security mechanisms in the [GitHub repository](https://github.com/gear-tech/gear-bridges). What’s next? [#whats-next] Ready to bridge your assets? Follow our step-by-step guide on [How to Use the Vara-Ethereum Bridge](/docs/vara-network/bridge/bridge_guide). # Manual Relay Guide import { ethMainnet, varaMainnet } from '@/addresses/bridge-addresses.js'; This guide describes how to manually push a message across the bridge between Vara network and Ethereum network if the **Relayer is unavailable**. All steps are illustrated for `USDT` token as an example, but the same process applies to any supported token or arbitrary cross-chain message. If the Relayer is unavailable, any bridging message remains safely **stored on-chain**. No message is lost—manual relaying can be used at any time to finalize the process and complete the delivery. **Crucial:** Always verify all contract addresses before initiating a bridge transaction. The addresses used in this guide are the current Ethereum Mainnet and Vara Mainnet deployments. You can find the most up-to-date official addresses in the [Vara Bridge Developer Hub](https://wiki.vara.network/docs/bridge/developer_hub). Bridging Ethereum → Vara [#bridging-ethereum--vara] 1. Approve Token Transfer [#1-approve-token-transfer] Approve USDT for Bridging * **Open** the USDT contract on Etherscan. * Go to **Contract → Write Contract** tab. * Click **Connect to Web3** and select a wallet with sender’s account. * Use the `approve` method: * **spender:** {ethMainnet.erc20Manager} (Erc20Manager address on Ethereum Mainnet) * **value:** Amount to transfer (USDT uses 6 decimals, so `1 USDT = 1_000_000`, `5 USDT = 5_000_000`) 2. Initiate Bridging [#2-initiate-bridging] Request Bridging * **Open** Erc20Manager contract on Etherscan. * Go to **Contract → Write as Proxy**. * Connect wallet if needed. * Use the `requestBridging` method: * **token:** {ethMainnet.usdt} (USDT address) * **amount:** Same as above or less (≤ approved amount) * **to:** Recipient address on Vara (hex string, e.g., convert from SS58 on ss58.org) * **Send** the transaction. At this point, Erc20Manager emits a `BridgingRequested` event. 3. Relay Event Data to Vara [#3-relay-event-data-to-vara] The final step is to relay proof of the bridging event from Ethereum to the Vara network. This means providing evidence that the required Ethereum block was finalized and that a transaction containing the relevant event (`BridgingRequested`) was actually included and executed on-chain. Event hash USDT for Bridging To do this: * **Find the transaction hash** that emitted the `BridgingRequested` event. * This is visible in the event list after submitting the bridging transaction. * This hash serves as the unique reference to the on-chain proof and will be used by the relayer to gather and submit all necessary data from Ethereum to Vara. Next, run the Gear Bridge Relayer Docker container, providing it with the transaction hash and other required parameters. ```sh docker pull ghcr.io/gear-tech/bridge-relayer:main.1037c949fb897aa855ec117830747e6fb16749c4 ``` > Latest Docker images for the Gear Bridge Relayer are published at: [https://github.com/gear-tech/gear-bridges/pkgs/container/bridge-relayer](https://github.com/gear-tech/gear-bridges/pkgs/container/bridge-relayer) ```sh docker run --rm -it --platform linux/amd64 \ ghcr.io/gear-tech/bridge-relayer:main.1037c949fb897aa855ec117830747e6fb16749c4 \ gear-eth-manual \ --tx-hash \ --checkpoint-light-client 0xf0a411ed5b8c28194d60781306de2ef131ec7df28658f7a857fc95fd86db7e8b \ --historical-proxy 0x0d94bc213584835a98a49227d5f40bcecc18019f252835138ea364cac9690fb1 \ --receiver-program 0x440e8faddaef7e9cc7230564fd062cd807d4a957d2dfe61da8638ee633d72f7f \ --ethereum-endpoint "" \ --ethereum-beacon-rpc \ --gear-domain wss://rpc.vara.network \ --gear-port 443 \ --gear-suri "" ``` Replace `` with your transaction hash.\ Replace `` with the **mnemonic phrase for the Vara account** that pays for network fees.\ Replace `` and `` with your own Ethereum Mainnet execution and beacon node endpoints. After submitting the proof, the rewarding process requires waiting for the **Light Client** on Vara to synchronize. * **Timing:** This usually takes **15–20 minutes** after the bridging transaction is mined on Ethereum. * **Process:** The proof can only be submitted and verified on-chain once this synchronization is complete. * **Result:** Transferred tokens will appear on Vara immediately after the cross-chain proof is accepted. *Please do not resubmit the transaction during this synchronization period.* To confirm the result, check the state of the **wrapped USDT token program** on Vara using the `balanceOf` function.\ This can be done on IDEA: * Click "Read state" * Select `balanceOf` * Enter the recipient address The updated balance will reflect the received tokens after successful bridging. *** Bridging Vara → Ethereum [#bridging-vara--ethereum] The bridging procedure from Vara to Ethereum is **very similar** to the reverse direction described above. The user interacts with the **VftManager program** on Vara. The bridging flow consists of the following main steps: 0. (Optional) Tokenize VARA to VFT [#0-optional-tokenize-vara-to-vft] If your tokens are not yet tokenized as VFT (Vara Fungible Tokens), you need to convert your VARA using the `VftNativeExchange::Mint` message.\ This will mint the desired amount of VFT in Tokenized VARA program. 1. Approve VftManager to Spend Tokens [#1-approve-vftmanager-to-spend-tokens] Approve VARA * Open the VFT token contract, e.g., Tokenized VARA program. * Use the `approve` function: * **spender:** {varaMainnet.vftManager} (VftManager address) * **value:** Amount to transfer (VARA uses 12 decimals, so `1 VARA = 1_000_000_000_000`) 2. Request Bridging via VftManager [#2-request-bridging-via-vftmanager] Request VARA Bridging * Open the VftManager program * Call the `RequestBridging` function: * **vara\_token\_id:** Address of the token contract (e.g., {varaMainnet.tokenizedVara}) * **amount:** Amount to bridge (must be ≤ approved value) * **receiver:** Ethereum recipient address (hex) After successful execution of both the **approve** and **RequestBridging** steps,\ the VftManager program emits a `BridgingRequested` event containing a `nonce` and `block` number. **How to find the event:** * Go to the VftManager program * Switch to the **Events** tab to locate the relevant `BridgingRequested` event and copy the `nonce` and `block` values. Event VARA 3. Relay Proof to Ethereum [#3-relay-proof-to-ethereum] Run the relayer Docker container with the `nonce` and `block` values from your `BridgingRequested` event. ```shell docker run --rm -it --platform linux/amd64 \ ghcr.io/gear-tech/bridge-relayer:main.1037c949fb897aa855ec117830747e6fb16749c4 \ gear-eth-manual \ --message-nonce "" \ --message-block \ --from-eth-block \ --gear-domain wss://archive-rpc.vara.network \ --gear-port 443 \ --ethereum-endpoint "" \ --mq-address "0x8E01Fbf136cA97627ca241dB9EFf1DFE3f2195F6" \ --eth-fee-payer "" ``` how to get Ethereum block * To get `` go to Erc20Manager contract events, find your relevant bridging transaction, and note the Ethereum block number in which the event was emitted. Use this block number as the value for `--from-eth-block`. * ``: This is the MQ (Message Queue) address used by the bridge. You can find it in the documentation or in the contract’s Read/Write panel on Etherscan. * ``: Your Ethereum fee-payer private key. The private key is used **only locally** for signing. The relayer does not transmit your sensitive data, but you should always handle private keys with extreme caution. Finalization and minting on Ethereum are **not instant**. After relaying, the message will be processed, and wrapped tokens (e.g., **WVARA**) will appear in the recipient's balance once the network confirms the transaction. **To verify the result:** 1. Open the WVARA Contract (or your specific token) on **Etherscan**. 2. Navigate to the **Read Contract** tab. 3. Call the `balanceOf` function using the recipient's address. See the result of VARA Bridging # Program Examples This section contains a set of examples that can be used for familiarization with writing programs on Vara or as the basis for dApps: [https://github.com/gear-foundation](https://github.com/gear-foundation). Programs can be written from scratch or built from provided examples. Stable Environment [#stable-environment] All program examples and JS applications have been tested in a stable environment with specific development tool versions necessary for implementing, building, and running programs and JS applications. [//]: # "A local development environment can be configured according to the information provided below or by using a pre-configured Docker image as described in the [Using Docker](#using-docker) section." The **latest releases** of Gear librarires are avaible in the **[GitHub](https://github.com/gear-tech/gear/releases)**. Stay up to date with the latest releases and updates from the development team by subscribing to our Telegram channel: [@GearVaraDev](https://t.me/GearVaraDev).
Compiler Tools Version How to install / access
Linux users should generally install `GCC` and `Clang` , according to their distribution's documentation. Latest For example, on Ubuntu use: ```bash sudo apt install -y binaryen build-essential clang cmake curl git jq libssl-dev pkg-config ``` On macOS, install a compiler toolset by running: ```bash xcode-select --install ```
Rust stable Install [Rustup](https://rustup.rs/) to get Rust compiler ready: ```bash curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh ``` Add Wasm target to the toolchain: ```bash rustup target add wasm32-unknown-unknown ```
[Gear Sails](/docs/developing/build/sails) library latest Install the latest version of Sails library: ```bash cargo install sails-cli ```
[Gear Rust](/docs/developing/build/gstd) libraries: `gstd` , `gtest` , `gclient` , `gear-wasm-builder` latest The latest version is tagged in the `Cargo.toml` file autmatically when creating a new program (for example named 'vara-app'): ```bash cargo sails new vara-app ```
Vara Network [Mainnet](https://idea.gear-tech.io/programs?node=wss%3A%2F%2Frpc.vara.network) latest Ensure connection to Vara Network Mainnet - it is selected by default in [https://idea.gear-tech.io](https://idea.gear-tech.io). New wallets can claim a one-time grant of 100 VARA to cover deployment and first transactions.
| **Gear JS Tools** | **Version** | **How to install / access** | | ------------------- | ----------- | ----------------------------------------------------------------------------------- | | Gear JS API | latest | Ensure this version is specified in the package.json file of the program repository | | Sails JS | latest | Ensure this version is specified in the package.json file of the program repository | | Gear JS React Hooks | latest | Ensure this version is specified in the package.json file of the program repository | | Vara UI Kit | latest | Ensure this version is specified in the package.json file of the program repository | Windows users often encounter issues with Rust dependencies. We **strongly recommend** using Linux or macOS for compiling Vara nodes and programs. **Tip:** If you are on Windows, using **WSL2** (Windows Subsystem for Linux) is the best way to ensure a stable development environment. First Steps [#first-steps] * Create a new project (named, for example - `vara-app`) by running: ```bash cargo sails new vara-app ``` Your `vara-app` directory tree should look like this ``` vara-app │ ├── app │ └── src │ └── lib.rs │ ├── client │ └── ... │ ├── src │ └── lib.rs │ ├── tests │ └── gtest.rs │ ├── build.rs │ ├── Cargo.toml │ └── README.md ``` `Cargo.toml` file is a project manifest in Rust. The essential libraries required for compiling a project are automatically included there. * Write your program's code * Compile the Wasm program in the app folder: ```bash cargo build --release ``` Refer to [Getting Started](/docs/vara-network/quick-start) for additional details. [//]: # "## Using Docker" [//]: # [//]: # "A pre-configured Docker image can be used to build and test programs. The image contains all necessary tools and dependencies for building and running programs and JS applications." [//]: # [//]: # "The source code of the image is available on [GitHub](https://github.com/gear-foundation/gear-env)." [//]: # [//]: # "To use the image, install Docker on the machine. Installation instructions for different operating systems are available on the [Docker website](https://docs.docker.com/get-docker/)." [//]: # [//]: # "After installing Docker, pull the image from Docker Hub:" [//]: # [//]: # "```bash" [//]: # "docker pull ghcr.io/gear-foundation/gear-env:stable" [//]: # "```" [//]: # [//]: # "Run the image with the following command:" [//]: # [//]: # "```bash" [//]: # "docker run --rm --name gear-env -itd ghcr.io/gear-foundation/gear-env:stable bash" [//]: # "```" [//]: # [//]: # "The command will run the image in the background and provide access to the container's shell. Access the container's shell with:" [//]: # [//]: # "```bash" [//]: # "docker exec -it gear-env bash" [//]: # "```" [//]: # [//]: # "Copy a program to be built to the container (use the `gear-app` created above):" [//]: # [//]: # "```bash" [//]: # "docker cp ./gear-app gear-env:/root" [//]: # "```" [//]: # [//]: # "Build the program:" [//]: # [//]: # "```bash" [//]: # "docker exec -itw /root/gear-app gear-env cargo build --release" [//]: # "```" [//]: # [//]: # "The compiled program will be available in the `target/wasm32-unknown-unknown/release` folder inside the container. Copy it to the local machine:" [//]: # [//]: # "```bash" [//]: # "docker cp gear-env:/root/gear-app/target/wasm32-unknown-unknown/release/. ./" [//]: # "```" [//]: # [//]: # "Stop the Docker container after use:" [//]: # [//]: # "```shell" [//]: # "docker stop gear-env" [//]: # "```" # PING-PONG Explore the simplicity of coding with Vara through a minimal program that demonstrates a [Ping-Pong](https://github.com/gear-foundation/dapps/tree/master/contracts/ping-pong) service. The project code is built using the [Sails](/docs/developing/build/sails) framework, which simplifies program development on Gear Protocol. Implementation details [#implementation-details] The Ping-Pong program is found in the `app/src/lib.rs` file and responds with a *"Pong!"* message every time it receives a `ping` request. Additionally, it keeps a count of the pings received, allowing users to track their interactions with the service. The code below defines a basic `PingPongService` using the `sails` framework: ```rust title="app/src/lib.rs" #[sails_rs::service] impl PingPongService { fn init() -> Self { unsafe { PING_COUNTER = Some(U256::zero()); } Self(()) } // Service's method (command) pub fn ping(&mut self) -> String { let ping_counter = self.get_mut(); *ping_counter += U256::one(); "Pong!".to_string() } // Service's query pub fn get_ping_count(&self) -> U256 { *self.get() } } ``` This code outlines the `PingPongService` with the `sails_rs::service` macro and includes the following core functions: * `init()`: This initializes the `PingPongService` by setting a global `PING_COUNTER` to zero, preparing it for tracking future pings. * `ping(&mut self) -> String`: This command method increments the ping counter on each call and returns the response *"Pong!"*. * `get_ping_count(&self) -> U256`: This query method provides the current count of pings without altering the state, allowing external users to view the interaction count. Source code [#source-code] The source code of this example of Ping-Pong program and the example of an implementation of its testing is available on [gear-foundation/dapp/contracts/ping-pong](https://github.com/gear-foundation/dapps/tree/master/contracts/ping-pong). See also an example of the smart contract testing implementation based on `gtest`: [gear-foundation/dapps/contracts/ping-pong/tests](https://github.com/gear-foundation/dapps/tree/master/contracts/ping-pong/tests). For more details about testing programs written on Gear, refer to the [Program Testing](/docs/developing/build/testing) article. # Smart Contracts Smart contracts are transactional computer programs that can execute the terms of an agreement automatically with no intervention. They were first proposed by Nick Szabo in the early ‘90s; however, because of technological constraints, they couldn’t function properly. It wasn’t until improvements were made to fundamental blockchain technology that the application of smart contracts became possible. In terms of blockchains, smart contracts store and execute digital agreements on a blockchain when predetermined criteria are met. Upon making a specific input, the system automatically executes a predetermined and specific output. Smart contracts can be written in multiple different languages, with Solidity being one of the most popular. But thanks to recent advances in technology, smart contracts can now be written in more traditional programming languages, compiled and deployed on the WebAssembly virtual machine. Not only does this make deploying smart contracts much easier, but it also provides efficiency and speed benefits. You can find out more about this [here](https://medium.com/@gear_techs/why-gear-uses-webassembly-35b705341241). How do smart contracts work? [#how-do-smart-contracts-work] Smart contracts have three major functions. * They store rules * They verify rules * They self-execute rules Smart contracts do this by following simple “if, when & then” statements that are written in programmable code. Once a specific input criteria is met, a predetermined output is executed. Once this occurs, a network of computers that helped facilitate the transaction, records the transaction on the blockchain where it gets encrypted and becomes immutable. Why are smart contracts significant? [#why-are-smart-contracts-significant] Smart contracts, * Significantly improve slow, expensive, centralized and insecure transactional processes * Enable transactional processes to become more efficient, transparent and autonomous * Remove the need for third parties and intermediaries to ease human error and significant time and financial costs This presents opportunities to automate and streamline industries that are long-winded and in need of more efficient transactional processes. What are some real-world applications of smart contracts? [#what-are-some-real-world-applications-of-smart-contracts] Smart contracts best suit industries where an automatic executing process would be useful. For instance, implementing smart contracts would be great in industries which require an extreme amount of intermediation. This would help automate routine and repetitive processes which individuals currently pay sizable fees for. Processes where the trading of assets is extremely long-winded, like real estate for example, would witness exponential efficiency improvements and other benefits like enhanced security and reduced costs. To date, smart contracts have been used: * In the financial industry to dis-intermediate and decentralize financial services. * To change how modern games are played. * In the legal industry to automatically execute legally binding contracts. * To facilitate complex computational tasks like those involved in machine learning and artificial intelligence. Will smart contracts enable complete decentralization and automation? [#will-smart-contracts-enable-complete-decentralization-and-automation] Smart contracts present more opportunities than simply improving singular transactional processes alone. They also facilitate the creation of completely decentralized applications that run on the blockchain. These decentralized applications, also called dApps, combine easy-to-use interfaces that emulate conventional web applications with the new added possibilities of programmable smart contracts and blockchain technology. dApps bring major enhancements to conventional web applications. A key highlight is the strong emphasis on privacy and data security, achieved through cryptographic encryption that safeguards and makes data immutable. This ensures tamper-proof records. dApps streamline user interactions with decentralized services, enhancing the overall experience. With hosting on a network maintained by many operators, most dApps boast zero downtime. Eliminating third parties reduces costs and boosts transaction speeds. Smart contracts also enable more than just applications to run autonomously without centralized control. They also allow entire organizations to function without centralized involvement. These organizations are often referred to as decentralized autonomous organizations (DAOs) and you can think of them as businesses that are run on the blockchain and collectively owned by their community. DAOs represent a revolutionary new form of corporate governance that enables global collaboration between individuals who do not personally know one another. This means that users no longer need to “trust” each other before they collaborate. Instead, users only need to trust the DAO’s smart contracts, which are completely transparent and verifiable by anyone. DAOs open up new and exciting opportunities for global collaboration and they have the potential to change how institutions are governed worldwide. # dApps Decentralized Applications, or dApps, are applications that offer similar functionality to traditional applications but the main difference is that they are run on decentralized peer-to-peer networks, such as blockchains. Decentralized applications offer the services that make up the Web3 revolution. Because dApps are decentralized, there isn’t a single entity that has control over the application. Instead these applications are open-source and their data is publicly available. DApps use cryptographic tokens to help keep the network secure and they are totally permissionless, which means that anyone, anywhere can interact with them. What makes dApps so innovative apart from their inclusive nature and exceptionally low barrier to entry is that they are censorship-resistant. This means that because there is no single point of failure, it’s very difficult to restrict access to a decentralized application. This lack of a single point of failure also means that dApps are very difficult to attack and that there is often zero downtime. In most cases, to interact with a dApp, users must possess that application's native cryptocurrency. # DAO DAOs are an effective and safe way to work with like-minded people around the globe. Essentially, they’re safe ways to collaborate with strangers and commit funds to a specific cause. You can think of DAOs as magic internet organizations that’re collectively owned and managed by its members. The backbone of a DAO is the smart contract. The contract defines the rules of the organisation and holds the group's treasury. Once the contract is live, no one can change the rules except by a vote. If anyone tries to do something that's not covered by the rules and logic in the code, it will fail. DAOs operate without a central authority, their ultimate strength. Decisions are collectively made through votes, and once approved, execution is automated. This eliminates human intervention, significantly reducing errors and preventing fraudulent or malicious manipulation. DAOs differ from traditional organisations in many ways, but the most noticeable is that they’re usually flat and fully democratised instead of hierarchical. This is different from traditional organisations because they usually have a sole decision maker. DAOs are also fully transparent and public instead of private with certain information limited to executives and managers. The concept of DAOs are extremely exciting as they promise to revolutionize organizational structure as we know it. DAOs solve almost everything that’s wrong with how modern day organisations are run. A perfectly structured DAO gives every member the opportunity to shape the organisation, which can help shape an entire industry. # DeFi Decentralized finance is a collection of financial services that are an alternative to the current financial system. While the traditional financial sector is opaque, tightly controlled and outdated, DeFi empowers individuals by providing them with more personal control over their finances. DeFi applications enable individuals to borrow, save, invest and trade without having to rely on third parties like banks, exchanges or lending institutions. It’s able to do this because it’s built on blockchain technology, making use of cryptocurrencies and smart contracts which can execute transactions automatically and instantaneously. DeFi solves many problems within the traditional financial system. For a start, it makes financial services more accessible and inclusive. This is significant because it enables over 1.7 billion people, who were previously excluded, to have access to a safe and secure financial industry. On top of that, DeFi is much faster and more efficient than traditional financial services because applications are powered by smart contracts. This removes the requirement for intermediaries, which removes a lot of the incurred costs associated with financial services. DeFi applications can be accessed 24/7, anywhere in the world, meaning that individuals are not limited to operating on and at specific dates and times or in specific time zones. # NFTs What are NFTs? [#what-are-nfts] A Non-Fungible Token (NFT) is a unique type of cryptographic token that enables the tokenization of unique units of data to be stored on a blockchain. NFTs can essentially represent anything that is unique, with their most common use case today being tokens that represent digital artwork. However, contrary to many beliefs, NFT’s use cases do not stop at verifying true authenticity and ownership of artwork alone. NFTs can represent anything that is not fungible. This means that anything with unique properties now has a way to be cryptographically represented in order to be stored on a blockchain to benefit from its unique features. NFTs can be used to represent anything from the deeds to a house to a receipt for a product or service. In the future, all data units will likely be represented as NFTs. This is because they offer secure storage for valuable data and easy accessibility, transforming traditionally illiquid assets into highly liquid ones. # Web3 Web 3.0 is a new iteration of the internet that’s powered by new technologies like artificial intelligence, machine learning and most importantly, blockchain technology. Web 3.0 aims to create a more open, connected and intelligent internet that is permissionless and open to everyone without monetizing personal data. With Web3, anyone who is on the network has permission to use all Web3 services. There isn’t a central authority who can block or deny access. Because Web3 is entirely decentralized, there isn’t a single entity that owns or controls the network. Rather, the community are the ones who control the network. And because the infrastructure that powers Web3 is turing-complete, pretty much anything imaginable can get programmed as a decentralized application. On top of that, most interactions and transactions that occur on Web3 will benefit in terms of security, speed and cost thanks to the unique nature of blockchain technology. The value proposition is simple. The main advantage of Web3 is that it addresses the biggest problem that’s resulted from Web2 - the collection and monetization of personal data. Web3 empowers the user and it creates a more efficient, secure and transparent Internet. # Governance Introduction [#introduction] Vara empowers its community to decide the evolution of the network through its on-chain governance mechanism. In the interests of decentralization, longevity and flexibility, Vara leverages the widely adopted OpenGov framework as its native governance model. This allows any VARA token holder to participate in Vara’s decentralized governance to help determine any future updates or modifications to the network. The goal is to ensure that the Vara Community is represented, led and engaged in decision-making processes to collaboratively decide the direction of the network. To accomplish this, Vara establishes a clear mandate and values to ensure orderly discussion and execution of decisions. At its most fundamental level, Vara’s governance model ensures that community members controlling at least 50% of all funds staked to the network can direct its future — assuming they have enough *conviction* in their position. Glossary Terms [#glossary-terms] * **Conviction** - The amount of “weight” a vote carries. This is calculated using the number of tokens locked into a vote, plus the user-defined period of time tokens should be locked for. The longer this lock-in period, the greater conviction a vote will carry. * **Proposal** - an action or item proposed by a token holder, which is open for voting and discussion by all token holders. This is tied to a specific change to the Vara network, including values for key parameters, code upgrades, or changes to the governance system itself. * **Referendum** (plural: referenda) - a proposal that has met the proposal criteria and entered from the Lead-In period to the Deciding Period. It remains open for token-holder voting for the defined time, before being checked against referendum criteria and entering the Enactment period if successful. * **Origin** - a set of permissions that define the source for an operation, which is used to determine the Track that a referendum is posted under. * **Track** - an Origin-specific pipeline that outlines the life cycle of proposals. Vara currently has 15 Tracks: Root, Whitelisted Caller, Staking Admin, Treasurer, Fellowship Admin, General Admin, Referendum Canceller, Referendum Killer, Small Tipper, Big Tipper, Small Spender, Medium Spender, Big Spender, Bridge Admin, and Bridge Pauser. * **Fellowship** - a group of community members who have special voting rights within Vara and can add certain proposals to the Whitelisted Caller Track. * **Approval** - the minimum "Aye" votes as a percentage of overall Conviction-weighted votes needed for an approval. * **Support** - the minimum number of "Aye" votes, not taking into consideration Conviction-weighted votes, needed as a percent of the total supply needed for an approval. * **Lead-in Period** - the initial proposal voting and discussion period. At this stage, proposals are in an undecided state until they pass some criteria for the given Track, including the Track having capacity, a minimum Prepare Period having passed, and a decision deposit having been paid. * **Decision Deposit** - the minimum deposit amount required for a referendum to progress to the Decision Period after the end of the Lead-in Period. Its goal is to mitigate spam since each Track has a predefined capacity for concurrent referenda. * **Deciding Period** - token holders continue to vote on the referendum. If a referendum does not pass by the end of the period, it will be rejected, and the Decision Deposit will be refunded. * **Enactment Period** - a specified time, which is defined at the time the proposal was created, that an approved referendum waits before it can be dispatched. There is a minimum amount of time for each Track. How to Participate in Governance [#how-to-participate-in-governance] Vara’s governance is democratic, factoring token holders’ votes and giving them the ability to delegate voting power to community members in several ways. Proposals are submitted by the community, after which they will be discussed and voted upon by the community. Referenda can be proposed, discussed, and voted towards, using Vara’s governance forum: [https://vara.subsquare.io/referenda](https://vara.subsquare.io/referenda) Referenda are simple and inclusive. Stake-based voting schemes use votes from different organizations to make decisions in the shared ecosystem of a distributed ledger (i.e on-chain governance). Each referendum has a specific proposal that takes the form of a privileged function call, which can switch out the entire code of the runtime in the runtime, which would typically require a "hard fork" in legacy blockchain networks. Referenda can be initiated by following one of several ways. * Publicly submitted proposals * Proposals submitted by the fellowship * Proposals submitted as part of the enactment of a prior referendum. All referenda have an enactment delay associated with them. This delay is the period between the referenda ending and — assuming the proposal was approved — being enacted. Vara’s network members can start a referendum at any time. Referenda can be started as often as necessary and can last indefinitely. Several features, known as Origins and Tracks, help streamline the referenda flow. An Origin can be thought of as a rich descriptor for a given privilege level. The proposer of the referenda now selects an appropriate Origin for their request based on the proposal's requirements. Each Origin is associated with a single referendum class, and each type is related to a Track. The Track outlines the lifecycle of the proposal. It is independent of other proposal tracks, which allows the network to tailor the dynamics of referenda based on their implied privilege level. In the example, a runtime upgrade (set\_code call) has different implications for the ecosystem than a small treasury tip (a Small Tipper spend). Therefore different origins need to be established with separate predetermined turnouts, approvals, deposits, and minimum enactment periods. Proposing a Referendum [#proposing-a-referendum] Vara aims to keep barriers to entry low and allows anyone to start a referendum at any time. Anyone may also vote on these referenda, and there are no limits to the number of referenda that are open and able to be voted on at any one time. In Gov2, the proposer is given the option to designate the specific Origin for their proposal's execution. Each Origin is linked to a distinct type of referendum class, with some classes having a single corresponding origin, while others consist of multiple Origins. Each class has its own Track, which serves as a pipeline for the proposal to progress through and is entirely separate from the tracks of other classes. | ID | Origin Track | Description | Examples | | :-: | :------------------: | :-------------------------------------------------------------------------: | :--------------------------------------------------------: | | 0 | Root | Highest privilege | Forced runtime changes, changing the ideal validator count | | 1 | Whitelisted Caller | Root-level operations whitelisted by the Fellowship for a faster turnaround | Runtime upgrades, adding validator slots, token burns | | 10 | Staking Admin | Staking-related interventions | Cancelling pending slashes | | 11 | Treasurer | Treasury management; spends of up to 50,000,000 VARA | Large treasury spends, rejecting treasury proposals | | 12 | Fellowship Admin | Managing the Fellowship | Inducting, promoting or demoting Fellowship members | | 13 | General Admin | For general on-chain decisions | Managing identity registrars | | 20 | Referendum Canceller | For cancellation of a referendum. Decision Deposit is refunded | Wrongly constructed referendum | | 21 | Referendum Killer | For killing of bad/malicious referendum. Decision Deposit is slashed | Malicious referendum | | 30 | Small Tipper | Treasury tips of up to 1,000 VARA | Small community rewards | | 31 | Big Tipper | Treasury tips of up to 5,000 VARA | Larger community rewards | | 32 | Small Spender | Treasury spends of up to 50,000 VARA | Funding small community initiatives | | 33 | Medium Spender | Treasury spends of up to 500,000 VARA | Funding medium-sized community initiatives | | 34 | Big Spender | Treasury spends of up to 5,000,000 VARA | Funding large ecosystem programs | | 40 | Bridge Admin | Administering the Vara ↔ Ethereum bridge | Bridge governance operations | | 41 | Bridge Pauser | Emergency-pausing the Vara ↔ Ethereum bridge | Halting the bridge in an emergency | The existence of distinct tracks enables proposers to customize the characteristics of their referenda according to their associated privilege levels. Referenda executed from more influential Origins will be subject to stricter safety measures, higher voting thresholds, and longer periods of consideration. The Root Origin is subject to the most stringent safeguards and highest thresholds. Origins that confer relatively limited power, such as the Small Tipper Origin, which only has the ability to spend up to 1,000 VARA from the treasury, will have correspondingly shorter consideration periods and lower approval thresholds. Upon creation, a referendum becomes immediately votable by any member of the community. However, it does not enter a state in which it can be concluded, have its votes tallied, approved, and subsequently enacted. Before transitioning into the Deciding period, referenda must satisfy certain requirements. Until these conditions are met, they remain unresolved. There are three primary conditions that must be fulfilled. Firstly, all referenda have a lead-in period, which is a predetermined duration that must elapse following the proposal before the deciding period can commence. This period serves as an initial notice period, during which votes can be cast, thus mitigating the possibility of "decision sniping." Such sniping could occur if an attacker with a significant proportion of voting power attempted to pass a proposal soon after its submission, thereby not allowing the broader voting population sufficient time to consider and vote. The second requirement is the availability of space for the referendum within its track, as each track has a maximum limit on the number of referenda that can be in the Deciding period simultaneously. The number of proposals allowed on a track is dependent on the privileges of the associated Origin, with more influential Origins resulting in a lower limit. For instance, the Root-level Origin has a limit of one, indicating that only a single high-risk proposal can be in the Deciding state at any given time. On the other hand, the relatively weak tipper tracks (Small Tipper and Big Tipper) have less stringent limits since overpopulation of referenda is less of a threat, and it generally makes more sense to have more tips being decided over at any given time. When there is available space, the eligible referendum with the most votes in favor of approval from its class is elevated to the Deciding state. The final requirement is the payment of a Decision Deposit, which serves to discourage frivolous proposals and prevent spamming or bloating of the system. Although the cost of creating a referendum is low and only covers the on-chain storage required to track it, having a referendum in the Deciding state poses greater risks and consumes limited system resources. To mitigate these risks and reserve space for more serious proposals, a larger deposit must be paid. By requiring a deposit, Vara aims to ensure that proposers carefully consider the potential impact of their proposals and contribute to a healthy decision-making environment. Track Parameters [#track-parameters] Each Track defines its own capacity (the maximum number of referenda that can be in the Deciding Period at once), Decision Deposit, and set of periods. The values below are read directly from the Vara runtime (`api.consts.referenda.tracks`); the current values for each Track can always be inspected on [Subsquare](https://vara.subsquare.io/referenda). | ID | Track | Capacity | Decision Deposit (VARA) | Prepare Period | Decision Period | Confirm Period | Min Enactment Period | | :-: | :------------------: | :------: | :---------------------: | :------------: | :-------------: | :------------: | :------------------: | | 0 | Root | 1 | 15,000,000 | 2 hours | 14 days | 1 day | 1 day | | 1 | Whitelisted Caller | 100 | 1,500,000 | 30 minutes | 14 days | 10 minutes | 10 minutes | | 10 | Staking Admin | 10 | 750,000 | 2 hours | 14 days | 3 hours | 10 minutes | | 11 | Treasurer | 10 | 150,000 | 2 hours | 14 days | 3 hours | 1 day | | 12 | Fellowship Admin | 10 | 750,000 | 2 hours | 14 days | 3 hours | 10 minutes | | 13 | General Admin | 10 | 750,000 | 2 hours | 14 days | 3 hours | 10 minutes | | 20 | Referendum Canceller | 1,000 | 1,500,000 | 2 hours | 7 days | 3 hours | 10 minutes | | 21 | Referendum Killer | 1,000 | 7,500,000 | 2 hours | 14 days | 3 hours | 10 minutes | | 30 | Small Tipper | 200 | 150 | 1 minute | 7 days | 10 minutes | 1 minute | | 31 | Big Tipper | 100 | 1,500 | 10 minutes | 7 days | 1 hour | 10 minutes | | 32 | Small Spender | 50 | 15,000 | 4 hours | 14 days | 12 hours | 1 day | | 33 | Medium Spender | 50 | 30,000 | 4 hours | 14 days | 1 day | 1 day | | 34 | Big Spender | 50 | 60,000 | 4 hours | 14 days | 2 days | 1 day | | 40 | Bridge Admin | 3 | 15,000,000 | 4 hours | 14 days | 6 hours | 1 hour | | 41 | Bridge Pauser | 5 | 3,750,000 | 3 minutes | 12 hours | 1 block | 1 block | In addition, submitting any referendum requires a flat Submission Deposit of 1,500 VARA, and a referendum that never satisfies the conditions to enter the Deciding Period times out after 14 days. Voting and Voluntary Locking [#voting-and-voluntary-locking] Voluntary Locking allows token holders to increase their voting power by declaring how long they are willing to lock up their tokens. The number of votes for each token holder will be determined by the following formula: *votes* = *tokens* \* *conviction\_multiplier* The conviction multiplier increases the vote by one every time the number of lock periods doubles. | Lock Periods | Vote Multiplier | Length in Days | | :----------: | :-------------: | :------------: | | 0 | 0.1 | 0 | | 1 | 1 | 7 | | 2 | 2 | 14 | | 4 | 3 | 28 | | 8 | 4 | 56 | | 16 | 5 | 112 | | 32 | 6 | 224 | The maximum number of "doublings" of the lock period is set to 6 (and thus 32 lock periods in total), and one lock period equals 7 days. Only doublings are allowed. You cannot lock for 24 periods and increase your conviction by 5.5. While a token is locked, you can still use it to vote and stake your tokens. You are only prevented from transferring these tokens to another account. Votes are always "counted" at the same time, which is at the end of the voting period. This is not impacted by the locking period of the tokens. Token holders can also delegate their voting power to another voter in the system. When votes are delegated in this way, the delegate votes with their own voting power, plus any delegated to them. This works with conviction voting, allowing VARA holders to lock up their tokens to increase your delegate's voting power on your behalf. Of course, the tokens in question never leave the delegator’s control, and they are free to switch delegates or regain direct control at any time. Current updates improve on this with a unique feature called Multirole Delegation. This allows you to specify a different delegate for every class of referendum in the system. The Referendum Lifecycle [#the-referendum-lifecycle] Referenda are discrete events in the Vara Network with fixed voting periods. When the voting period ends, these votes are counted, tallied, and then chartered. These actions are separated from other init calls by using an identifier for each and can be executed if the vote is approved. With this binary nature comes simple voting options such as: `aye` (yes), `nay` (no), or abstaining entirely. When a proposal has been submitted, it will have a predetermined **Lead-In Period**, depending on its Track. At this stage, other token holders may vote towards it and lock up additional funds to demonstrate their conviction. Once this period has passed and the required criteria are met, the proposal progresses into the Deciding Period. add metadata *The Proposal/Referendum Process within The Vara Network* Once a proposal reaches the **Deciding Period**, it has the potential to be approved. However, this eligibility only lasts for the duration of the Track's Decision Period — 14 days for most Vara Tracks (see [Track Parameters](#track-parameters)) — after which it will be rejected by default. To pass, a referendum must meet two criteria related to *approval* and *support* and must continue to meet these criteria for a specific Confirmation Period. Different referendum classes have varying Confirmation Period lengths, with more influential classes requiring a longer Confirmation Period. This serves as an additional safeguard against whale voters attempting to manipulate the voting process by placing a large enough vote to meet approval criteria unexpectedly. *Approval* is defined as the share of approval vote-weight (i.e. after adjustment for conviction) against the total number of vote-weight (for both approval and rejection). Support is gauged by the overall count of approval votes (without adjusting for conviction) in relation to the maximum potential votes allowed in the system. Various referendum classes impose distinct criteria for these metrics, and these criteria may diminish periodically based on a predetermined timetable. Consequently, as the voting unfolds during the Deciding Period, the thresholds for both support and approval necessary for a proposal to succeed can gradually decrease. However, the thresholds will always start and end at similar levels, with at least 50% approval required. If a proposal fails to get approved within its Track's Decision Period, it is automatically rejected, and the Decision Deposit can be refunded. However, if the proposal successfully meets the passing criteria and continues to do so for the Confirmation Period, it is approved and enters the **Enactment Period**, where it is scheduled to execute from its proposed origin after an Enactment Delay. The Enactment Delay, which is specified during proposal, has a minimum value based on the track. More powerful tracks require a longer Enactment Delay to give the network enough time to prepare for any potential changes introduced by the proposal. In addition, there is a unique operation called **Cancelation** for intervening with a proposal already being voted on. The process will immediately reject an ongoing referendum regardless of its status. There is also a provision to ensure the deposit of the proposer is slashed if the proposal is malicious or spam — this is handled by the separate Referendum Killer Track. Cancellation is a governance operation that must be voted upon by the network to be executed. The cancellation comes with its own Origin and Track (Referendum Canceller), which has a low lead-time and Approval/Support curves with slightly sharper reductions in their thresholds for passing, given that it is invoked with a sense of urgency. Vara Fellowship [#vara-fellowship] Root-Origin proposals are necessary for system upgrades, fixes, and rescues, but they also have the potential to cause significant damage to the network and ecosystem. In Gov2, the approval and support required for early-passing are extremely high to ensure the safety of the system, and the lead-in and enactment periods are lengthy to provide adequate notice to all parties and prevent bad proposals from being approved. Although this process may be slow, it ensures maximum safety for everyone in the Vara network. However, there are times when it is crucial to implement a fix, upgrade, or rescue logic quickly. While there may be widespread agreement on the need for such action, the voting process's safeguards can make executing these changes challenging or impractical due to time constraints alone. In such cases, it can be helpful to oraclize expert opinions to make informed decisions and establish a clear, well-considered action plan on a tight timeline. Gov2 has a governing body called the "Fellowship" to oversee this process. The Fellowship is a self-governing body with the primary goal of protecting the interests of the Vara community and network participants. A rank is associated with each member so that an organization can determine how well-informed it expects its opinions to be about technical matters and whether those opinions are grounded in sound reasoning and in line with Vara's interests. On Vara, ranks range from 0 (Candidates) to 9 (Grand Masters). The Fellowship is designed to have a broad membership with low barriers to entry. Candidate members are inducted by the existing membership, or through a public referendum on the Fellowship Admin Track. Members of the Fellowship can vote on any given proposal, and the Fellowship considers their collective opinion weighted by their rank. Ranking System [#ranking-system] To prevent a small group of participants from gaining effective control over the network, we adhere to three principles: firstly, the Fellowship must never have hard power over the network: it cannot change parameters, conduct rescues, or move assets. Concerning governance over the network, the only thing in its power is to reduce the effective timeline on which a referendum takes place. Secondly, the rank system and weights must be designed so that we would not expect small groups of individuals to be able to capture and control overall decision-making capacity. While the Fellowship weights those with higher rank more in the aggregate opinion, the weight should not be so high as to make a small number of higher members’ ideas impossible compared to a coherent thought from lower-ranked membership. Thirdly, the Fellowship should be designed to grow and develop its membership and aggregate levels of expertise and ensure that its overall decision-making capacity strengthens over time. In light of this, the Fellowship will have a constitution that describes in specific terms the requirements and expectations for individuals to attain and retain any given rank. Higher ranks can vote and promote lower ranks based on this constitution. To support these conditions, the Fellowship will have a constitution that outlines the requirements and expectations for individuals to attain and retain any given rank. Higher ranks are able to vote and promote lower ranks based on this constitution. Demotion happens automatically after a period in which a member cannot defend their position to their peers. Suspension can happen only through a general referendum, ensuring that controversy or unpopularity within the Fellowship does not (necessarily) result in expulsion. To guard against the chance of the Fellowship becoming a cabal, entrance into the top-tier ranks also requires a full referendum. It cannot be bestowed merely by one's Fellowship peer. Whitelisting [#whitelisting] The Whitelist pallet has a singular purpose: to allow one Origin to escalate the privilege level of another Origin for a particular operation. The Fellowship (through its members of rank III — Fellows — and above) authorizes an origin known as Whitelisted Caller to be executed with Root-level privileges and will only work with specific specified commands that the Fellowship has assigned. The Whitelist pallet verifies two things: 1. The origin is the Whitelisted Caller, i.e. that the referendum passed on this track 2. The Fellowship has indeed whitelisted the proposal. Only if both conditions are satisfied the operation executes with Root-level privileges. This system enables the ability to have a new parallel Track (Whitelisted Caller Origin), whose parameters allow for a shorter voting turnaround. In addition, an actively harmful proposal may be killed through the Referendum Killer Track. The referendum is immediately terminated regardless of its status and, unlike a cancellation, its Decision Deposit is slashed. This is useful for stopping malicious or spam proposals that have already entered the pipeline. # Overview Indexers, in a broad context, play a fundamental role in organising and optimising data retrieval within various systems. These tools act as navigational aids, allowing efficient access to specific information by creating structured indexes. In the realm of databases and information management, indexers enhance query performance by creating a roadmap to swiftly locate data entries. In the context of blockchain and dApps, indexers go beyond traditional databases, facilitating streamlined access to on-chain data. This includes transaction histories, smart contract states, and event logs. In the dynamic and decentralised world of blockchain, indexers contribute to the efficiency of data queries, supporting real-time updates and ensuring the seamless functionality of diverse applications and platforms. There are several indexer solutions available, each offering different levels of decentralisation, ease of development, and performance for you to consider. These solutions serve as intermediaries to assist in indexing the Vara network. # SubQuery This article has been kindly provided by the [SubQuery](https://subquery.network) team. Intro [#intro] SubQuery is a leading blockchain data indexer that provides developers with fast, flexible, universal, open source and decentralised APIs for web3 projects. SubQuery SDK allows developers to get rich indexed data and build intuitive and immersive decentralised applications in a faster and more efficient way. SubQuery supports 100+ ecosystems including Vara, Polkadot, Cosmos, Ethereum, Polygon, Algorand, NEAR, and Avalanche. Another Subquery's competitive edge lies in the capacity to consolidate data, not just within a single blockchain but across multiple blockchains within a unified project. This facilitates the development of comprehensive dashboard analytics, cross-chain block scanners, or other multi-chain indexing initiatives. Other advantages include superior performance with multiple RPC endpoint configurations, multi-worker capabilities and a configurable caching architecture. To find out more, visit our [documentation](https://academy.subquery.network/). **Useful resources**: * SubQuery Docs: [SubQuery Academy (Documentation)](https://academy.subquery.network/) * Intro Quick Start Guide: [1. Create a New Project](https://academy.subquery.network/quickstart/quickstart.html) * [Vara Starter Project](https://github.com/subquery/subql-starter/tree/main/Vara/vara-starter) * For technical questions and support reach out to us [start@subquery.network](mailto:start@subquery.network) Running and Hosting your Vara SubQuery APIs [#running-and-hosting-your-vara-subquery-apis] SubQuery is open-source, meaning you have the freedom to run it in the following three ways: * Locally on your own computer (or a cloud provider of your choosing), [view the instructions on how to run SubQuery Locally](https://academy.subquery.network/run_publish/run.html). * You can publish it to SubQuery's enterprise-level [Managed Service](https://managedservice.subquery.network/), where we'll host your SubQuery project in production ready services for mission critical data with zero-downtime blue/green deployments. There even is a generous free tier. [Find out how](https://academy.subquery.network/run_publish/publish.html). * You can publish it to the decentralised [SubQuery Network](https://subquery.network/network), the most open, performant, reliable, and scalable data service for dApp developers. The SubQuery Network indexes and services data to the global community in an incentivised and verifiable way and supports Vara from launch. # Staking Overview Skip the basics and start [Validating](/docs/vara-network/staking/validate) or [Nominating](/docs/vara-network/staking/nominate) your VARA tokens right now. Vara Network uses an inclusive and reliable Nominated Proof-of-Stake (NPoS) mechanism to select validators for its consensus protocol. It chooses validators based on their own stakes and the funds nominated by other token holders. This approach helps make the process more democratic and decentralized. NPoS ensures decentralization by preventing a situation where only the richest entities can be validators. Let's explore the benefits of NPoS in more detail: [#lets-explore-the-benefits-of-npos-in-more-detail] * **More People Involved:** NPoS encourages active participation and governance by attracting more users to join the network. As a nominator, you can show your trust in different validators, which results in a diverse and strong group of validators. * **Stronger Network Security:** With NPoS, stakes are spread across multiple validators, boosting network security. It's harder for hackers to harm a system with diversified support. * **Optimal Stake Allocation:** NPoS improves stakes distribution among validators, ensuring better security. The concept of "eras" motivates both nominators and validators to behave well and contribute to a healthy and secure blockchain ecosystem. In the Vara network, validators can publicly announce their candidacy for validation, and nominators can endorse up to 16 candidates. The validators with the most VARA backing are elected and activated in the next era. Nominators must stake a minimum of 50 VARA to register as a nominator. Both nominators and validators can be slashed in NPoS. Vara Staking Constants [#vara-staking-constants] | Constant | Value | Description | | -------------------------------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------- | | Bonding duration | 7 days | The duration it takes for staked assets to become liquid again once they have been unstaked. | | Round Duration | 1 Era | Defined periods during which validator sets are chosen, rewards are distributed, and other important network functions are performed. | | Minimum Delegation per Candidate | 50 VARA | The lowest amount of VARA a delegator can stake to a validator. | | Max Active Validators | 1000 | The maximum number of active validators in each era. | | Slash defer duration | 7 days | The period during which a slashing event remains unapplied. | Vara Staking Concepts [#vara-staking-concepts] Validation and Nomination role [#validation-and-nomination-role] Validators [#validators] Validators are crucial for maintaining the integrity of the NPoS model. They have important roles within the network, such as creating new blocks and confirming transactions. In each era, the algorithm selects the required number of validators needed to ensure consensus. Once elected to the validator set, validators embark on their main task - creating genuine and accurate blocks. To show commitment to the network's rules, Validators must stake VARA, the native token of the Vara Network. Validators who break the rules will lose their stake in the network. Any node capable of handling these responsibilities can publicly declare its candidacy for validation. Anyone can take on the validator role by running a node and staking VARA. Visit the [Vara Validators Dashboard](https://validators.vara.network/) to view current validators and their performance. Nominators [#nominators] Nominators play a pivotal role in the NPoS model by supporting specific validators with their stake. Their job is to help validators become active and produce blocks. Nominators publicly announce a list of validators they trust and stake a certain amount of VARA to show their support. If these validators are elected, the nominator shares rewards and penalties based on their staked VARA. By carefully selecting and supporting validators who follow good security practices, nominators can earn a portion of the VARA rewards. In simpler terms, nominators help decide which validators are trusted to secure the network. Unlike other Proof-of-Stake networks, there is no limit on the number of nominators, encouraging more people to participate and reducing entry barriers. For their trust and the risk of staking tokens, nominators generally receive a part of the staking rewards earned by the validators they support. The token encourages them to select reliable and high-performing validators, indirectly contributing to the overall stability and security of the network. NPoS Election Algorithm [#npos-election-algorithm] In each era, which lasts 12 hours, a maximum of 1000 active validators are selected. The responsibility of choosing these validators and distributing rewards among them lies with the NPoS Election Algorithms or Phragmén. The algorithm aims to select the necessary number of validators to ensure decentralization of power and optimizes the following factors: * Maximizing the total stake amount. * Maximizing the stake behind validators with the minimum stake. * Minimizing the stake variance within the set. The algorithm considers several factors to determine the validators for the next era: * **Stake Amount**: Validators with a high stake, including tokens they own and those delegated to them, will likely be chosen for the active set. The stake amount shows how much validators support the network economically and their commitment to taking on risks. * **Era Points**: Validators earn era points based on their performance and contributions in previous eras. The more points a validator has, the greater their chances of being selected for the active set. High-era points show the validator has a reliable track record and has been actively participating in the network. * **Security Considerations**: The consensus algorithm aims to maintain a decentralized and secure network. To achieve this, the active set selection process considers factors such as geographic distribution, avoiding concentration of stake or influence, and minimizing the chance of validators working together in a harmful way for the network. * **Validator Performance**: Validators are expected to fulfill their duties diligently, including block production, participation in consensus activities, and follow the network's rules and protocols. Validators with a history of reliable performance and minimal downtime are favored in the selection process. The NPoS algorithm balances security and decentralization: * **Higher Stake:** Validators with a larger stake have a greater chance of being selected for the active set (more secure). * **Smaller Stake:** The system also allows block production by validators with a smaller stake to prevent centralization and maintain fairness. This dynamic approach ensures the network remains both secure and decentralized. Eras and Sessions [#eras-and-sessions] Let's break down eras and sessions into easily digestible points: * An era lasts for 12 hours. * Validators are split into two groups: active and inactive. * The active validator group produces blocks and performs actions during an era. * The composition of the active group can change between eras. Each era is further divided into smaller units called epochs or sessions. An epoch/session lasts for 2 hours. During each epoch/session, validators are assigned as block producers for specific time frames or slots. Having sessions adds a layer of security because it decreases the chance of having multiple validators assigned to a slot colluding to harm the network. Era Points [#era-points] Era points are crucial for electing validators and distributing rewards in Vara Network. They determine which validators become active and influence their chances of selection. Validators with higher era points have a better chance of being chosen because of their past performance and contributions. Validators earn era points by performing various tasks such as producing blocks, participating in consensus activities and handling network-related responsibilities. The more diligently and reliably a validator carries out these duties, the more era points it gains. On the other hand, if a validator fails to fulfill its responsibilities or engages in malicious behavior, it may receive penalties leading to a deduction of era points. Era points validator incentives to act in the network's best interest, maintain a high uptime and contribute to its overall security and reliability. Reward Distribution [#reward-distribution] In the Vara Network, all validators in the active set are paid **nearly equally**, regardless of their total stake. **Note:** Minor differences in rewards occur based on **Era Points**, which are earned through performance metrics like block production and network activity. Validators receive rewards in every era based on the inflation-based economic model. These rewards are allocated among all validators, and the amount each validator receives depends on their accumulated era points. The distribution of rewards is not affected by the number of tokens staked behind a validator. The NPoS algorithm aims to achieve a fair and equal distribution of rewards among all validators, with some possible but not substantial variation. Validators receive a portion of the reward as compensation for their work, while the remaining portion is distributed among their backers proportionally to their respective stakes. This ensures validators and their nominators receive a fair share of the overall reward pool. Claim Rewards [#claim-rewards] The distribution of staking rewards to nominators is not automated and requires manual triggering by an authorized entity. Typically, validators are responsible for initiating the payout process. However, in a permissionless manner, anyone can trigger the reward distribution for all nominators who have supported a specific validator in the active set of that era. Here are essential points to understand about staking rewards: * Staking rewards are available for a total of 84 eras. * Validators and their nominators can start the payout for all unclaimed eras. * When the payout is triggered, the network will distribute rewards to all nominators who supported the validator during those eras. Rewards must be claimed within **84 eras** (\~42 days). If not requested in time, or if a validator unbinds their entire stake, all pending rewards will be **permanently forfeited**. **Calculation:** `84 eras × 12h = 1008 hours = 42 days` Slashing [#slashing] The slashing mechanism on Vara serves to maintain network security and deter bad behavior by penalizing validators who misbehave. It plays a crucial role in the Nominated Proof-of-Stake (NPoS) consensus algorithm in Vara Network. Validators can face slashing if they go against the network's rules or engage in malicious actions like double signing, equivocation or other misconduct. Slashing involves reducing or taking away the tokens that a validator has staked as a penalty for their misbehavior. Here are actions violations subject to punishment: * Double Signing * Late or Missing Block Production * Downtime * Collusion Be careful: if a validator is slashed, **all associated nominators** also lose a portion of their stake. It is crucial to choose trustworthy validators. *Note: Slashed funds are not burned; they are redirected to the Treasury.* When a validator is slashed on Vara, it goes into a state called "unapplied slash" for 7 days. During this time, there is a chance to submit a governance proposal to undo the slashing decision. This proposal allows for a review of the slashing and potential mitigation of its effects. # Become Nominator Nominators are passive participants in any `Proof of Stake (PoS)` consensus mechanism. They indirectly contribute to maintaining the health and security of the network by delegating their tokens to the most reliable validators who actively confirm blocks. Nominators receive rewards for their delegation, which depend on the network's economy and the percentage that the validator takes for themselves. Being a nominator does not require running your own node or worrying about online uptime. Start Nominating [#start-nominating] The minimum amount of VARA required to start nominating validators directly and earning rewards is **50 VARA**. If your balance is less than this minimum, consider joining [Nomination Pools](/docs/vara-network/staking/nominate#join-nomination-pools) instead. Via Vara Staking Dashboard [#via-vara-staking-dashboard] Vara offers a user-friendly staking **[application](https://staking.vara.network/)**, inspired by the Polkadot's staking app, that simplifies the process of staking and nominating validators. 1. To begin your VARA token staking journey, visit Vara staking at **[staking.vara.network](https://staking.vara.network/)**. 2. Connect your account and navigate to the "Nominate" section. 3. Click the `Start Nominating` button. setup dashboard 4. Payout Options Next, you'll be presented with payout options: * **Compound**: Rewards are automatically re-staked and bonded. * **To Your Account**: Rewards are deposited into your account. Select the option that suits your preferences and click "Continue" to proceed. setup dashboard 5. Nominating Validators Now, it's time to choose your nominations. The staking dashboard provides two methods: * **Automatic Optimal Selection**: This feature selects validators for you based on their performance and rewards. It's an excellent option if you're unsure about which validators to nominate. * **Manual Selection**: If you want more control over your nominations, opt for manual selection. Carefully choose the validators you wish to nominate. Nominating multiple validators can help diversify your risk. setup dashboard 6. Define your Bond - specify amount of VARA to be staked, confirm and sign the transaction. setup dashboard Congratulations! You are now an active nominator. You will now begin earning staking rewards starting from the **next era**. Via SubWallet [#via-subwallet] SubWallet enables VARA staking in a few clicks. See how it's done with the example of a mobile app: 1. Navigate to the `Staking` section in the SubWallet application: subwallet staking 2. If you don't see Vara Network there, just use the search function—type 'Vara': subwallet staking 3. Specify the amount of VARA to stake and choose up to 16 validators to **Nominate**. Alternatively you can opt for **pool** staking, in this case make sure you have filtered out only **Active pools**: subwallet staking 4. Review the specified data and click the `Approve` button: subwallet staking 5. That's it! You've successfully staked your VARA. The next time you enter the 'Staking' section, your staking details will be there. subwallet staking Via Polkadot.{js} app [#via-polkadotjs-app] Go to the Polkadot.\{js} app, navigate to the Network → Staking → [Accounts](https://polkadot.js.org/apps/?rpc=wss%3A%2F%2Frpc.vara.network#/staking/actions) section, and click `+ Nominator`: 1. Select the stash and controller accounts, along with the amount you wish to bond. You can see in the screenshot what Stash and Controller accounts are. If you have not created your controller account, you can use the same one for both. setup nominator 2. Choose a validator (or validators) you'd like to nominate. You can select up to 16 validators, and the algorithm will determine how to distribute your funds effectively for each Era. It's advisable to choose only reliable validators with a good reputation to ensure they won't be slashed. setup nominator 3. Click `Bond & Nominate` and sign the transaction. If everything is correct, your bonded VARA will appear in the Nominators list. setup nominator Your nominations will take effect in the next era. Nomination pools [#nomination-pools] Vara Staking Pools empower users to actively participate in the network's development and receive rewards, regardless of their token holdings. With Staking Pools, even if a user has only 1 VARA token, they can start earning rewards by staking natively on Vara. Additionally, anyone who has at least 50 VARA tokens can create their own pool. These pools enable users to join forces by pooling their VARA tokens together on-chain. Through this collaborative effort, participants can nominate validators and collectively stake their tokens, significantly enhancing the network's scalability and security. Create Nomination Pool [#create-nomination-pool] Via Vara Staking Dashboard [#via-vara-staking-dashboard-1] 1. To create your own pool, go to the Pools tab and click the Create Pool button. create pool vara 2. Set a custom name for your pool and click Continue. create pool vara 3. Select the validators to nominate with the pool’s funds. You can choose automatically from the suggested options or manually. create pool vara 4. Assign addresses for the Root, Nominator, and Bouncer roles. By default, these are all set to your currently connected account. create pool vara 5. Specify the amount to bond initially to the pool (minimum 50 VARA). create pool vara 6. Review the entered parameters, and if everything is correct, click Create Pool and sign the transaction. create pool vara 7. Done! Your pool has been created. You can now invite other nominators to nominate through your pool. create pool vara Join Nomination Pools [#join-nomination-pools] Via Vara Staking Dashboard [#via-vara-staking-dashboard-2] 1. Navigate to the 'Pools' section in the [Vara Staking Dashboard](https://staking.vara.network/#/pools) and click the `Join` button: stake pool vara 2. Select any pool you like: stake pool vara Select only pools with **active nominations**. Staking in a pool with a `Not Nominating` status **does not yield rewards**. 3. Specify the amount of VARA to stake and click `Submit`: stake pool vara Always keep a small amount of VARA (e.g., 1-2 VARA) liquid to cover gas fees. If you stake your entire balance, you won't be able to pay for the transactions required to **claim rewards** or **unstake** your tokens. 4. Congratulations! You have successfully staked VARA in the pool. You will begin earning staking rewards from the next era. stake pool vara Staking rewards are **not claimed automatically**. You must periodically check your available balance and trigger the claim transaction manually. Via Polkadot.js app using SubWallet browser as an example [#via-polkadotjs-app-using-subwallet-browser-as-an-example] 1. If you have some VARA tokens in your account in the SubWallet mobile application, click **[this link](https://mobile.subwallet.app/browser?url=https%3A%2F%2Fslink.subwallet.app%2Fpolkadot-vara)** to open the Polkadot.js application directly in the wallet's browser. stake pool polkajs 2. Select the `Network` drop-down, and then click `Staking`: stake pool polkajs 3. Choose `Pools`, navigate to `All pools`, select any desired pool, and click `Join`: stake pool polkajs 4. Specify your account and the amount of VARA you wish to stake: stake pool polkajs 5. Review the transaction details and click `Sign and Submit`: stake pool polkajs 6. Approve the transaction by signing in to the Wallet application: stake pool polkajs 7. Your nominations will take effect in the next era. You can check your stake at `Pools - Own pools` as well as `Accounts - Pooled`: stake pool polkajs stake pool polkajs These instructions also apply to laptop and desktop devices. You can access the interface directly via [Polkadot.js Apps](https://polkadot.js.org/apps/?rpc=wss://rpc.vara.network#/staking/pools). Rewards and Withdrawals [#rewards-and-withdrawals] [Payouts](https://staking.vara.network/#/payouts) represent staking rewards on Vara and depend on the accumulation of 'Era Points' by your nominated validators over time. These rewards are determined at the end of each Era, which spans 12-hour periods. Direct Nomination Rewards [#direct-nomination-rewards] To claim staking rewards, a Payout needs to be requested (Claimed). Any nominator supporting the relevant validator can initiate this request. **One request** triggers the reward payout for **all nominators**. Consequently, for most nominators, payouts are received **automatically**. As per the user's staking options, rewards will either automatically bond with the current bond or can be sent to the stash, controller, or an external account. The [Nominate](https://staking.vara.network/#/nominate) section of the Vara Staking Portal provides an overview of Bonded Funds and Nominator Status. Additionally, it offers a `Claim` option for available Pending Payouts. nomnate overview Depending on the interface used for staking, the available options may vary. Pool Nomination Rewards [#pool-nomination-rewards] Unlike direct nominators, [Pool](https://staking.vara.network/#/pools) members are required to **manually claim** their rewards. Users have two choices for claiming rewards: * Reinvest them into the pool (`Compound`), increasing the pool share and accumulating further rewards. * `Withdraw` them from the pool to the user's account as free (transferable) VARA. You must always keep a small balance of free VARA tokens to cover transaction fees (claiming, withdrawing, etc.). If your balance is insufficient, **you will not be able to perform any transactions** until you replenish it. nomnate overview Unstaking [#unstaking] When users require their tokens to be transferable, they must unstake them first. * For direct nominators, click the `Unstake` button in the [Nominate](https://staking.vara.network/#/nominate) section. * As a [Pool](https://staking.vara.network/#/pools) member, click `Manage` and then select the `Leave pool` option. Your funds will be unbonded, and your account will exit the nomination pool. Once unbonded, users' funds will be unlocked **after 7 days**. Users must click the `padlock` icon to withdraw their funds to their wallet. # Become Validator Validators are essential contributors to a successful blockchain network. When chosen to be part of a `validator set`, they help produce blocks and receive rewards for their contributions to the platform. Holding such responsibility requires careful commitment from each validator because of personal and nominator stakes that may be at risk; however, working as a network validator is also highly rewarding! Check out the [Vara Validators Dashboard](https://validators.vara.network/) that provides statistical information about the existing validators, including their status, installed node version, hardware details, staking info, slashing history, and more. Hardware Requirements [#hardware-requirements] The recommended way for beginners to set up a validator is using a **Linux-based cloud server (VPS)**. Choose a provider that meets the hardware requirements below to ensure optimal performance and security. * `OS:` Ubuntu 22.04+ (or any distribution with GLIBC 2.35+) * `CPU:` 2vCPUs @ 3.4GHz (Intel Ice Lake or equivalent) * `Memory:` 8GB RAM * `Storage:` minimum 80GB SSD storage. Should be increased as the blockchain grows. Initial Set Up [#initial-set-up] Install Vara Node [#install-vara-node] Download the latest stable release of the `gear` node from the builds repo and unpack (choose the binary according to your host operating system): Terminal: ```bash curl https://get.gear.rs/gear-v1.9.2-x86_64-unknown-linux-gnu.tar.xz | tar xJ ``` or **Linux x86-64**: [gear-v1.9.2-x86\_64-unknown-linux-gnu.tar.xz](https://get.gear.rs/gear-v1.9.2-x86_64-unknown-linux-gnu.tar.xz) Run the node: ``` ❯ ./gear --version gear 1.9.2-5ef221109b8 ``` Terminal: ```bash curl https://get.gear.rs/gear-v1.9.2-aarch64-apple-darwin.tar.xz | tar xJ ``` or **macOS ARM**: [gear-v1.9.2-aarch64-apple-darwin.tar.xz](https://get.gear.rs/gear-v1.9.2-aarch64-apple-darwin.tar.xz) Run the node: ``` ❯ ./gear --version gear 1.9.2-5ef221109b8 ``` Terminal: ```bash curl https://get.gear.rs/gear-v1.9.2-x86_64-apple-darwin.tar.xz | tar xJ ``` or **macOS Intel x64**: [gear-v1.9.2-x86\_64-apple-darwin.tar.xz](https://get.gear.rs/gear-v1.9.2-x86_64-apple-darwin.tar.xz) Run the node: ``` ❯ ./gear --version gear 1.9.2-5ef221109b8 ``` Generate a node key [#generate-a-node-key] Generate a unique libp2p secret key at the default path, which determines the Peer ID of the node in the p2p network. ```bash gear key generate-node-key --default-base-path --chain vara ``` Running the command won't overwrite existing keys. If no existing key is found, it will print the path to the generated secret key along with the Peer ID. Example: ``` Generating key in "$HOME/.local/share/gear/chains/vara_network/network/secret_ed25519" 12D3KooWFmqMyz4fNHcm9X38rvZsSQ8RMt4K4vsWU7EJEiGZSkFz ``` You only need to back up the secret key if you want to preserve the Peer ID during migration. Never reuse the same key across nodes, as each node requires a unique Peer ID. Run Vara Node as a Service [#run-vara-node-as-a-service] Copy the `gear` executable to the `/usr/bin` directory: ```bash sudo cp gear /usr/bin ``` To run the Vara node as one of the Linux services, you need to configure the systemd file: ```bash cd /etc/systemd/system sudo nano vara-node.service ``` Configure and save: ```bash [Unit] Description=Vara Node After=network.target [Service] Type=simple User=root Group=root ExecStart=/usr/bin/gear \ --chain vara \ --rpc-cors all \ --name "VALIDATOR_NAME" \ --telemetry-url "wss://telemetry.rs/submit 1" \ --validator Restart=always RestartSec=3 LimitNOFILE=10000 [Install] WantedBy=multi-user.target ``` The `ExecStart` directive points to the `gear` binary location (default: `/usr/bin`). **Security Note:** This example is configured to run the service as the **root** user. For production environments, it is recommended to use a dedicated non-privileged user. Save & Exit. That’s it. Now we can start the service. ```sh sudo systemctl start vara-node.service ``` Syncing the Blockchain [#syncing-the-blockchain] If you follow all the instructions, the node will require time to synchronize with the blockchain. To check the service status in real time use: ```sh sudo journalctl --follow -u vara-node.service ``` You can also see your running node in telemetry portal: [https://telemetry.rs](https://telemetry.rs) Create Stash and Controller Accounts [#create-stash-and-controller-accounts] Create two accounts: Stash and Controller, for security reasons. Ensure each of them has enough funds to pay the transaction fee. Store most of the funds on the Stash accounts, which are the optimal location for saving staking funds safely and securely. Use the prompt to generate a new seed phrase: ```sh ./gear key generate --network vara ``` **Save both seed phrases in a secure, offline place.** If you already have a seed phrase and want to use it, skip this step. You can manage your accounts via [Polkadot.\{js} Apps](https://polkadot.js.org/apps/#/accounts) or the [Polkadot Extension](https://polkadot.js.org/extension/). Get Session Keys [#get-session-keys] You need to tell the chain your Session keys. If you are on a remote server, it is easier to run this command on the same machine (while the node is running with the default HTTP RPC port configured): ```sh curl -H "Content-Type: application/json" -d '{"id":1, "jsonrpc":"2.0", "method": "author_rotateKeys", "params":[]}' http://localhost:9944 ``` Output: ``` {“jsonrpc”:”2.0",”result”:”0x5e977ddcc0c69a6aed067052d5bd8f6bd365fae03562fd447d434e9814ac415d7c9ffe722364922bda314e44654f5c0cdc00d152470d5433f12cb73d078061863ac769d5f17b5460f042d221edf0099d2ce4c23edbe96ac943452cc4d3ad6d72”,”id”:1} ``` The output will have a hex-encoded `result` field. Copy and save it! Setup Validator [#setup-validator] Once your node is live, synchronized, and appears in telemetry, and session keys are prepared, it's time to set up the validator. Go to Polkadot.\{js} app and navigate to Network → Staking → [Account actions](https://polkadot.js.org/apps/?rpc=wss%3A%2F%2Frpc.vara.network#/staking/actions) section and click `+Validator`: Choose your stash and controller accounts and specify your stake amount. It's recommended to utilize distinct accounts both stash and controller. The minimum self-stake required to become an active validator is currently **100,000 VARA**. Please note that this threshold is dynamic and may **change over time** based on network governance. setup validator Set the `session key` and reward commission. setup validator Sign the transaction. Ensure you're added to the stash account and wait for the next Era to start. Once it begins, the network will add your validator. setup validator Update Validator [#update-validator] Regularly updating your validator is critical for keeping up with current maintenance requirements. The fastest and simplest way to update the validator: 1. Download the latest binary version 2. Swap binaries 3. Restart your service ```sh sudo systemctl start vara-node.service ``` 👏👏👏 Congratulations! You’ve successfully set up your validator node. 👏👏👏 If you encounter challenges or need help — reach out in the [Validators Chat](https://discord.com/channels/891063355526217738/1014177678473035777)! Using Snapshots to Speed Up Node Sync [#using-snapshots-to-speed-up-node-sync] When initializing your Vara validator node, syncing from the genesis block can be slow — especially on machines with limited I/O or bandwidth. To accelerate the setup, Vara provides regularly updated **[snapshots](https://snapshots.vara.network)** of the blockchain state. Snapshots allow your node to start from a recent block instead of syncing the entire history, reducing setup time from hours to minutes. 📦 Steps to Use a Snapshot [#-steps-to-use-a-snapshot] 1. **Stop the node** (if already running). 2. **Download and extract the latest snapshot**: ```bash curl -o - -L https://snapshots.vara-network.io/vara-mainnet-latest.tar.lz4 | lz4 -c -d - | tar -x -C /home/ubuntu/gear-data/chains/vara_network/db ``` 3. **Restart your node**: ```bash gear --chain vara --base-path /home/ubuntu/gear-data/ --database rocksdb --pruning 256 ``` 📌 Note: Always use snapshots from the official Vara source to ensure trust and integrity: [https://snapshots.vara.network](https://snapshots.vara.network). # Validator FAQs Here is a list of frequently asked questions about running a Vara validator node. What is Vara Network? [#what-is-vara-network] [Vara Network](https://vara.network/) is a fast and scalable Layer-1 decentralized network powered by [Gear Protocol](https://gear-tech.io/). Links: * Official site: [https://vara.network/](https://vara.network) * Block Explorer: [https://vara.subscan.io/](https://vara.subscan.io) * Vara Wiki: [https://wiki.vara.network/](https://wiki.vara.network) * Node Telemetry: [https://telemetry.rs/](https://telemetry.rs) * Vara Snapshots: [https://snapshots.vara.network/](https://snapshots.vara.network/) * Tokenomics: [https://wiki.vara.network/docs/vara-network/tokenomics/](/docs/vara-network/tokenomics) * Whitepaper: [https://whitepaper.gear.foundation/](https://whitepaper.gear.foundation) * Gear Foundation: [https://gear.foundation/](https://gear.foundation) What are the public RPC endpoints? [#what-are-the-public-rpc-endpoints] * RPC Node: * `wss://rpc.vara.network` * Archive RPC Node: * `wss://archive-rpc.vara.network` What bootnode should I use to connect to the network? [#what-bootnode-should-i-use-to-connect-to-the-network] Here are addresses of official Vara bootnodes: * `/dns4/vara-connect-1.vara-network.io/tcp/30333/ws/p2p/12D3KooWLDpZ5sWtSmZtiHXBstoQVwMAZ5yRWpDUBdjQHV7vBLHy` * `/dns4/vara-connect-2.vara-network.io/tcp/30333/ws/p2p/12D3KooWCJas1xGRDUC8nnSvfSiC1RcTXWaAef6oom1vGvqUMZV7` These nodes are already included in the [Vara chain spec](https://raw.githubusercontent.com/gear-tech/gear/master/node/res/vara.json). This configuration is applied **automatically** when you start your node with the `--chain vara` flag. What is the block time of the Vara Network? [#what-is-the-block-time-of-the-vara-network] It is operating at a rate of one block every 3 seconds. How long does an era last? [#how-long-does-an-era-last] 12 hours. An era is a period during which a specific set of active validators exists. Each era has six epochs (or sessions). During the last epoch, the active set of the next era is elected. And after the end of each era, the rewards are calculated and are ready to be distributed to the validators and nominators. How long does an epoch / a session last? [#how-long-does-an-epoch--a-session-last] 2 hours. What is the total issuance of $VARA? [#what-is-the-total-issuance-of-vara] 10 Billion. What is the precision of the $VARA token? [#what-is-the-precision-of-the-vara-token] 12 decimals. What is a validator in Vara Network? [#what-is-a-validator-in-vara-network] Validators are essential contributors to a successful blockchain network. When chosen to be part of a validator set, they help produce blocks and receive rewards for their input. A validator node is required to be responsive, perform its expected duties in a timely manner, and avoid any slashable behavior. How to run a validator node? [#how-to-run-a-validator-node] Please follow this guide to get started: [Run Vara Validator](/docs/vara-network/staking/validate/). What are the hardware requirements to run a validator node? [#what-are-the-hardware-requirements-to-run-a-validator-node] * `OS:` Ubuntu 22.04+ (or any distribution with GLIBC 2.35+) * `CPU:` 2vCPUs @ 3.4GHz (Intel Ice Lake or equivalent) * `Memory:` 8GB RAM * `Storage:` minimum 80GB SSD storage. Should be increased as the blockchain grows. You can find related information on the Vara wiki page: [Run Vara Validator > Hardware requirements](/docs/vara-network/staking/validate/#hardware-requirements) Where do I download the node binary? [#where-do-i-download-the-node-binary] Navigate to [https://get.gear.rs/](https://get.gear.rs/) and download the most recent stable release. You can also get it from github release page: [https://github.com/gear-tech/gear/releases/](https://github.com/gear-tech/gear/releases/) How fast should I upgrade node binary? [#how-fast-should-i-upgrade-node-binary] All nodes must upgrade to the latest version within 12 hours of its release if it is labeled “critical” or “high” priority and 24 hours if it is labeled “medium” or “low” priority. Is there official docker image? [#is-there-official-docker-image] Yes, you can pull the latest image from GitHub Container Registry ``` docker pull ghcr.io/gear-tech/node:latest ``` or find more tags here: [https://github.com/gear-tech/gear/pkgs/container/node](https://github.com/gear-tech/gear/pkgs/container/node) How do I check if my node is validator or not? [#how-do-i-check-if-my-node-is-validator-or-not] Navigate to [https://polkadot.js.org/apps/?rpc=wss://rpc.vara.network#/staking](https://polkadot.js.org/apps/?rpc=wss://rpc.vara.network#/staking), and you will find a list of the active validators What is a snapshot and how do I use it? [#what-is-a-snapshot-and-how-do-i-use-it] A **snapshot** is a compressed archive of the Vara blockchain database at a specific block. Instead of syncing from genesis, you can restore the state from a snapshot and dramatically reduce setup time. Why use a snapshot? * Fast validator or archive node bootstrapping * Quick disaster recovery (e.g., after disk failure) * Faster scaling of RPC or observer nodes For instructions on how to use it, check this [article](/docs/vara-network/staking/validate/#-steps-to-use-a-snapshot). What is slashing? [#what-is-slashing] Slashing is a mechanism to punish validators who act maliciously or negligently. If a validator tries to corrupt the network or is offline too often, a part of their staked fund (and those of their nominators) can be slashed. What can lead to a validator's stake being slashed? [#what-can-lead-to-a-validators-stake-being-slashed] Your validator node can be slashed under the following scenarios: 1. **Equivocation**: This occurs when a validator signs two or more blocks at the same height on the same chain. It's a serious offense because it can lead to the creation of two different versions of the blockchain, a situation known as a fork. 2. **Unresponsiveness**: Validators are expected to be online and participate in the network consensus. If a validator is offline for an extended period and misses a certain number of votes, a portion of their stake can be slashed. This is generally considered a minor offense, and the slashing is less severe than for equivocation or producing invalid blocks. 3. **Invalid Block**: Validators are expected to validate blocks correctly. If a validator produces a block that the network deems invalid, it can result in slashing. Note that it's unlikely to produce an invalid block unless you are running a modified node binary, therefore it's recommended to always download the binary from the [official release page](https://github.com/gear-tech/gear/releases). How is the liveness slashing penalty calculated? [#how-is-the-liveness-slashing-penalty-calculated] If a single validator is offline there’s no obvious reason to suspect malicious intent. The validator is not slashed, which means they also keep all their nominations. They are chilled. In practice, the protocol considers it problematic when 10% of the validator set is offline—that’s when slashing conditions kick in. liveness slashing penalty The penalty starts relatively small, 0.021% slashed if 10% are offline. But penalties increase linearly from there until the maximum penalty of 7% is hit when 44% of the network is offline. How is the equivocation / double-sign slashing penalty calculated? [#how-is-the-equivocation--double-sign-slashing-penalty-calculated] Validators may run their nodes on multiple machines to make sure they can still perform validation work in case one of their nodes goes down, but validator operators should be extremely careful in setting these up. If they do not have good coordination to manage signing machines, equivocation is possible, and equivocation offenses are slashed at much higher rates than equivalent offline offenses. equivocation slashing penalty GRANDPA equivocation has the harshest slashing penalty of all offenses, and it also exemplifies slashing based on the threat to the network—if 33% or more of the validator set commits a GRANDPA equivocation, the offenders lose 100% of their stake. How to stop validating? [#how-to-stop-validating] If you wish to remain a validator or nominator (e.g. you're only stopping for planned downtime or server maintenance), submitting the `chill` extrinsic in the `staking` pallet should suffice. It is only if you wish to unbond funds or reap an account that you should continue with the following. To ensure a smooth stop to validation, make sure you should do the following actions: * Chill your validator * Purge validator session keys * Unbond your tokens These can all be done with [PolkadotJS Apps](https://polkadot.js.org/apps/#/explorer) interface or with extrinsics. How long does it take to unbond tokens? [#how-long-does-it-take-to-unbond-tokens] The unbonding duration is 14 era, i.e. 7 days. What is the max number of nominators I can have? [#what-is-the-max-number-of-nominators-i-can-have] For each validator only the 256 biggest nominators can claim their reward. If you have more than 256 nominators, it is called oversubscription. Nominators should generally avoid nominating oversubscribed validators. Can I run multiple validators? [#can-i-run-multiple-validators] Yes, you can run multiple validators in Vara Network. Each validator will need its own separate stash and controller keys, as well as enough staked fund (either self-staked or nominated by others) to be in the active validator set. Running multiple validators can increase your chances of earning rewards, but it also increases your responsibilities and potential risk of slashing. Remember, each validator node requires maintenance, a stable internet connection, and a secure setup to prevent downtime and malicious attacks. It's important to note that running multiple validators doesn't mean running multiple nodes on the same server or under the same network conditions. This could lead to simultaneous failures, which could result in multiple nodes being slashed at the same time. Ideally, each validator node should be run on separate servers and potentially in different geographic locations for redundancy and resilience. How to achieve high availability for my validator node? [#how-to-achieve-high-availability-for-my-validator-node] To maintain high availability for your validator node, it may seem tempting to use redundant validator setups. However, incorrect setups can be risky because validator session keys should be confined to one node. Duplication across nodes may cause equivocation slashes, leading to the loss of all staked funds. Fortunately, a validator doesn't require constant uptime, as there's some tolerance within eras for offline periods and upgrades, and the slashing penalties due to downtime are much less severe than that caused by equivocation. Therefore, only attempt a high availability set-up when you know what you're doing. How do I monitor my node? [#how-do-i-monitor-my-node] * [Telemetry](https://telemetry.rs) This tracks your node details including the version you are running, block height, CPU & memory usage, block propagation time, etc. To see your node on the [telemetry dashboard](https://telemetry.rs), add the following parameter to your startup command: `--telemetry-url "wss://telemetry.rs/submit 1"` * [Prometheus](https://prometheus.io)-based monitoring stack, including Grafana for dashboards and log aggregation. It includes alerting, querying, visualization, and monitoring features and works for both cloud and on-premise systems. To enable monitoring, specify the prometheus port (e.g., `--prometheus-port 9615`). Once started, node metrics will be available locally at: `http://127.0.0.1:9615/metrics` What is the expected uptime from validators? [#what-is-the-expected-uptime-from-validators] Ideally, a validator node should strive for 100% uptime to avoid any penalties and to perform its duties on the network effectively. In practice 99.5% uptime is considered safe, i.e. you should keep the daily average downtime within 15 minutes. What is the minimum bond requirement for a validator? [#what-is-the-minimum-bond-requirement-for-a-validator] The minimum self-stake required is 100k VARA. Note that the actual amount of staked token (self-stake plus nominations) required to become a validator may be significantly higher due to competition from other validators. What is the least staking amount required for a validator to be elected? [#what-is-the-least-staking-amount-required-for-a-validator-to-be-elected] The total amount of token that needs to be staked for a validator to be elected (self-stake plus nominations) is determined by the amount staked on other validators. If there are more validators who have a higher total stake, those validators will be chosen instead. You can get the details by running the [subquery/tutorials-validator-threshold](https://github.com/subquery/tutorials-validator-threshold) indexer on Vara Network. How often are staking rewards distributed? [#how-often-are-staking-rewards-distributed] Staking rewards are distributed at the end of each era, i.e., every 12 hours. How are staking rewards calculated? [#how-are-staking-rewards-calculated] Rewards for validators and nominators are computed per era (an era is a period of 12 hours) based on era points. An era point is a reward point earned by a validator for performing certain actions such as producing a block. The total reward pool for an era is split between validators (and their nominators) proportionally to the number of era points they have earned. The more era points a validator earns, the larger the portion of the reward pool they (and their nominators) receive. The validator's own reward is then calculated based on their commission, which is a percentage of the total reward that they take before distributing the rest to their nominators. Nominators' rewards are distributed based on the amount of fund they have staked. How should I decide the commission rate? [#how-should-i-decide-the-commission-rate] The commission is the percentage of rewards you keep before they are given out to nominators. With 0% commission, nominators get all the rewards; with 100% commission, they get NO rewards. In general, in order to stay attractive to nominators you should keep the commission low. However, you also need to ensure that the commission rate you set allows you to sustainably operate your validator node. It's a balance of competitive positioning, sustainability, and profitability. How many validators will there be in each era? [#how-many-validators-will-there-be-in-each-era] The ideal number of active validators is defined by the `staking.validatorsCount` storage item, and is subject to change as the network matures. You can query the storage item by visiting [PolkadotJS Apps](https://polkadot.js.org/apps/?rpc=wss://rpc.vara.network) > Developer > Chain state > staking > validatorCount querying chainstate In each era, if there are more validators than the ideal threshold, the inactive validators will stay in the waiting list until they are elected. What files should I backup before migrating the validator node to another machine? [#what-files-should-i-backup-before-migrating-the-validator-node-to-another-machine] It is recommended to have backups of the following files and directories in case your current node becomes inaccessible due to extreme conditions: 1. **Validator Session Keys**: If you are running a validator node, please backup the secret seeds of your session keys for minimal downtime migration. These files are located in the `~/.local/share/gear/chains/vara_network/keystore` directory. You must submit an extrinsic to replace the keys if they are leaked or lost. 2. **Network Key**(optional): The network key uniquely identifies your node in the peer-to-peer network. It is located at `~/.local/share/gear/chains/vara_network/network/secret_ed25519`. If this file is lost, a new peer identity will be randomly generated when the node starts. 3. **Blockchain Database**(optional): The blockchain database resides in the `~/.local/share/gear/chains/vara_network/db` directory. It can be deleted and synchronized from scratch at any time, but keep in mind that this process may take some time. **Stop the old node before launching the new one.** Running multiple nodes with the same session keys will lead to **equivocation slashes** (direct loss of your staked tokens). # Vara Token Economy This article covers Vara Network's crypto-economics, highlighting key components, features, and the core utility of VARA. It also discusses measures taken to balance incentives and ensure ecosystem stability. A visualization of network tokenomics is available on the [Vara website](https://vara.network/network/tokenomics). VARA Utility & Token Distribution [#vara-utility--token-distribution] **Total Token Supply: 10 Billions VARA** token distribution The vesting periods for the initial mint of VARA tokens are as follows: * **Founders/Team/Advisors** - Fully locked for 12 months, then 36 months linear vesting (no lump-sum unlock). * **Investors** - Fully locked for 12 months, then 12 months linear vesting (no lump-sum unlock). VARA tokens maintain core utility features required for the network to operate: **transaction fees**, **staking** and network security, and **governance**. All transfers of the gas token or interactions with smart contracts on the network require VARA, including developers deploying smart contracts. Developers can also pre-fund their smart contracts with gas for future execution using Gas Pools. This enables one of Vara Network’s exciting features — **self-executing smart contracts**. VARA tokens also play a crucial role in the consensus model of the Vara Network, where token holders help contribute to network security by staking their VARA tokens in exchange for a portion of the block reward. Incentives on the Vara Network have been carefully aligned to encourage the continued engagement of participants and support decentralization. Typically, most protocols award the entire network rewards (block rewards, tx fees, etc.) to the nodes who were selected to produce the latest block, with some protocols diverting a portion of these funds to a protocol treasury. This model is straightforward, reliable, and battle-tested. However, there is potential room to unlock additional token utility for growing decentralized ecosystems and create value for all stakeholders; Contributors to the Gear protocol are exploring the mechanics of how to distribute a portion of block rewards directly toward dApp developers in order to incentivize the creation of world-class dApps on the network. Subject to a governance decision, an automatic or semi-automatic rewards distribution program could be employed, where, for example, a part of block rewards and/or the tx fees go to the dApp developers, or optionally to stakers who support those dApps. With this model, a portion of VARA block rewards and/or the transaction fees in each block or epoch would go directly to dApp developers. The size of this reward could be based on several possible on-chain and off-chain metrics, like transaction volume, user activity, total gas spend, and total value locked. These rewards could also be allocated to dApps to, for example, fund their own gas pool used to subsidize transaction fees for end-users. Rewarding developers is an important principle that may ultimately help build a stronger ecosystem for all participants — however, it is crucial that any new reward mechanism be thoroughly tested. Therefore, there are different implementations of automated developer reward systems (as well as mechanics of developer contribution validation ) that might be considered by contributors to the Vara Network for maximum security and efficiency. Final mechanics are subject to governance voting before being rolled out in several phases. According to initial research, by including developers and dApps within the network’s core incentive and rewards mechanisms, Vara Network could: * Enable transparent rewards for developer's efforts in building and deploying dApps. * Provide additional resources for developers and an opportunity for projects to bootstrap their projects’ own continued development by increasing user activity. * Support the flexibility for dApps to use the rewards however they see fit to benefit their users and ecosystem (For example, by using them for developer incentives, community airdrops, or staking these funds to help operate a dedicated node, etc.) This allows for new business models, as well as adding extra points of engagement for dApps and their target audience. * Create a capture and distribution flywheel for VARA token consumption, and could also stimulate base protocol token accumulation within the hands of stakeholders who are inherently interested in base network development. Community members participating in the Vara Network will have the opportunity to vote on the exact implementation for developer incentives. VARA Inflation and Anti-inflation Mechanics [#vara-inflation-and-anti-inflation-mechanics] Excessive inflation is one of the most criticized features of Layer 1/Layer 2 projects due to the impact it can have on financial sustainability and the community. Overly high inflation for a protocol’s token may cause: * A concentration of token ownership, i.e. the accumulation of large shares of supply by team members and early community backers by staking their locked allocations * Excessive token liquidation pressure * Rising ecosystem rates, forcing dApps to over-inflate incentive programs These factors become even more critical when market conditions are subject to increased turbulence, thus less liquidity and demand. Vara is designed such that protocol rewards would not affect network decentralization or concentration through periods of changes in liquidity. At the same time, the protocol must sufficiently reward stakers for contributing to network security. To achieve this, several mechanics were implemented to offset inflationary pressure and protect the development of the protocol and VARA token: 1. The maximum inflation rate for year 1 is 6.00%. VARA has been designed with a decreasing inflation rate in mind. Below is a sample inflation rate schedule, however, the exact rates will be determined by governance voting: | Year since genesis | Inflation rate | | ------------------ | --------------------------------------------- | | Year 1 | 6.00% | | Year 2 | 5.50% | | Year 3 | 5.00% | | Year 4 | 4.75% | | Year 5 - onwards | gradual decrease to the minimum level of 3-4% | 2. **Limit the amount of stakeable tokens to decrease the annual inflation rate.** Team and investor locked tokens are not stakeable until they are vested (this helps by reducing the potential inflation by more than 50% in the first 12 months and, more importantly, protecting against any increase in the centralization of token ownership) 3. An Inflation Offsetting Pool to **reduce TTS Inflation to 0% for the first 24+ months**. 10% of Total Token Supply (TTS) is allocated to a separate pool **(the Inflation Offsetting Pool)** to offset the increase of TTS caused by token emission from block rewards. Tokens from this pool are set to be automatically sent to a zero address (or “burned”) proportionally to the number of minted inflationary tokens. This will remain in effect unless otherwise allocated by the community for other resource building initiatives (e.g. for **developer grants/rewards pool**), as voted upon and changed by the community. About ⅓ of the Foundation’s staking rewards will replenish the Inflation Offsetting Pool, with the remaining 2/3 going to fund Foundation operations and further protocol initiatives and development. Staking rewards received by Developer grants and Education/Bootcamp programs/PR/Events pools are going to fund the respective pools activities. 4. **Provide in-market staking** incentives over time as the market continues to mature. The Vara Network maintains the Ideal Staking Rate by incentivizing or disincentivizing staking. Any downside divergence from the ideal staking rate will result in a higher ROI for stakers, providing a greater incentive to stake until the ideal stake rate has been achieved. Inversely, a staking rate higher than the ideal staking rate would yield a diluted return for stakers, encouraging token holders to unstake. **The Ideal Staking Rate on Vara is 85%**. Based on preliminary research provided by community enthusiasts and observations of the other L1/L2 protocols, we expect the Ideal Staking Rate to decrease 75% within 12 months since the PoS launch (subject to governance voting) before settling around 50-67%. This would allow the protocol to align the staking rate and inflation levels with the maturing circulating supply. These measures have the capacity to allow for maintenance of the same total token supply within the next 24+ months and, with the entire ecosystem in a more mature development stage, maintain an inflation rate in the low single-digits thereafter. token distribution To encourage system flexibility, the community could choose to initiate a governance proposal to decide whether: * The Inflation Offsetting Pool allocation should be kept in full to continue offsetting TTS inflation, * or 25-100% of this allocation should be moved to the developer grants pool In addition, the DAO will also decide on switching to the treasury fueling model where up to 10% of the total block rewards go to the protocol treasury in order to fuel further protocol development. Based on early research, the ideal time to implement these changes would be roughly 12-24 months from genesis. The implementation will be determined by the community via governance vote. Block Reward Curve, Staking Rate, and ROI Models [#block-reward-curve-staking-rate-and-roi-models] Vara was designed with a decreasing annual inflation model in mind, instead of a flat predetermined inflation rate. This may be implemented by governance vote and aims to prevent an excess of tokens being minted that would be out of line with ecosystem growth, mitigating inflation and preventing token holders from becoming unnecessarily diluted. Staking ROI is driven by both the staking rate (the percentage of stakeable tokens staked) and the block rewards curve. The block rewards curve effectively represents the total protocol inflation with hardcoded Minimum Inflation (constant = 1%) and Maximum inflation. The Maximum Inflation level decreases (see above) as the Network and the Ecosystem mature. Vara uses [Polkadot’s standard inflation model](https://wiki.polkadot.network/docs/learn-inflation) to calculate the distribution of block rewards, with several key modifications: 1. **Staking rate:** The Staking Rate is calculated using these metrics: **Formula:** `Staking Rate = staked tokens / stakeable tokens` **Stakeable tokens:** `Total Supply (TTS) - Locked Team - Locked Investors - Inflation Pool` At Genesis the stakeable tokens amount to 48.5% of TTS. This initially allows the protocol to have 51.5% less inflation while providing competitive market rates for staking ROI. While tokens are getting unlocked and/or expended, the number of stakeable tokens trends towards the TTS. The proven and battle-tested block reward curve and ROI model implemented by Polkadot also supports the Vara network to incentivize token holders to maintain the Ideal Staking Rate. Within the first 24 months, the Ideal Staking Rate will be decreased from 85% to 50-67% level (subject to governance voting as mentioned above), in line with token vesting unlocks and depending on the Foundation’s spending schedules/overall ecosystem development. 2. **Variable inflation rate** Effective inflation of total token supply is a variable, not a constant, in the developed model. To calculate the total amount of tokens issued as block rewards, each block uses the block rewards curve instead of a fixed percentage. This is a divergence from Polkadot’s model, where a fixed inflation rate is minted before being divided between stakers and the Foundation treasury. Instead, the network mints only the number of tokens actually required to reward participation by validators in the network. In this model, Polkadot’s inflation model is used to determine the actual number of tokens minted, by aligning this with the Staking ratio. Another difference from the Polkadot model is the staking ROI cap: in case the staking ratio is significantly lower than the Ideal Staking Rate and ROI increases above a certain level, the surplus ROI surplus will be distributed to the treasury. This enables Vara to issue only the amount of new tokens required to reward the percentage of stakeable tokens that were staked, so that the absolute inflation level is aligned with the participation of token holders staking to the network. 3. **Block rewards curve** * To form the Block rewards curve, the following rates are used: * The minimum inflation rate: constant 1% each year in case Staking rate = 0% * The maximum Inflation rate, which is constant for 12 months. * The max inflation rate will then be decreased as the network and the ecosystem mature (subject to governance vote as described above). * From Minimum to Maximum inflation rates (i.e from 0% tokens staked to Ideal Staking Rate), the Block rewards curve grows gradually according to a linear equation (as in the standard Polkadot model). * In case the % of tokens staked > Ideal Staking Rate, the curve decreases to the Minimum inflation level according to a hyperbolic equation (as in the standard Polkadot model). The staking ROI curve is driven by the block rewards curve and % of tokens staked: Block rewards curve / % of stakeable tokens staked. It decreases hyperbolically until the Ideal Staking Rate is reached and then falls hyperbolically in case % of the tokens staked increases beyond the Ideal Staking Rate. A maximum ROI of 30% has been implemented - if the staking rate falls below the Ideal Staking Rate, stakers receive a higher ROI than they would at the Ideal Staking Rate. Any staking rewards surplus on top of the 30% ROI cap goes to the Vara treasury. # Backup and Restore Data Structure [#data-structure] Vara node stores its data in a dedicated directory. * Linux: `$HOME/.local/share/gear` * macOS: `$HOME/Library/Application Support/gear` * Windows: `%USERPROFILE%\AppData\Local\gear.exe` For example, if the node runs as the root user on Linux (`$HOME` = `/root`), the absolute path of the node's data directory will be: /root/.local/share/gear Let's explore the data that the node stores in this directory. ``` └── gear └── chains ├── dev │   └── ... ├── vara_network │ ├── db │ │   └── full │ ├── keystore │ └── network └── vara_network ├── db │   └── full ├── keystore └── network ``` Chains [#chains] The node can connect to different chains. The chain can be selected using the `--chain` argument. When connected to Vara Mainnet (`--chain=vara`), the chain subdirectory name is `vara_network`, resulting in the `gear/chains/vara_network` path. If the node starts with the `--dev` argument, the virtual network in development mode will run with the data stored in the `gear/chains/dev` directory. Database [#database] The database keeps the blockchain state in the local node storage. It synchronizes with other nodes over a peer-to-peer protocol. The DB format can be selected using the `--database` argument. Possible options are: * `rocksdb` (default): uses RocksDB as the database engine, data is stored in the `/db/full` subdirectory. * `paritydb`: uses ParityDB as the database engine, data is stored in the `/paritydb/full` subdirectory. * `paritydb-experimental`: deprecated experimental mode of the ParityDB engine (will be removed in future releases), data is stored in the `/paritydb/full` subdirectory. Note that the database contents depend on the pruning mode of the node. By default, the node keeps only the last 256 blocks. To keep all the blocks, use the `--pruning=archive` argument when running the node. The database can be deleted and synchronized from scratch at any time. Use the `gear purge-chain` command to completely delete the DB. Network Key [#network-key] The network private key is used to calculate the unique peer identifier (starts with `12D3KooW`). This key is stored in the `/network/secret_ed25519` file. The key file is a binary file containing 32 bytes of the Ed25519 (by default) private key. Use the `hexdump` command to read the key: ```shell hexdump -e '1/1 "%02x"' /root/.local/share/gear/chains/vara_network/network/secret_ed25519 # 42bb2fdd46edfa4f41a5f0f9c1a5a1d407a39bafbce6f07456a2c8d9963c8f5c ``` Override this key by running the node with the `--node-key` argument: ```shell gear --node-key=42bb2fdd46edfa4f41a5f0f9c1a5a1d407a39bafbce6f07456a2c8d9963c8f5c # Discovered new external address for our node: /ip4/127.0.0.1/tcp/30333/ws/p2p/12D3KooWMRApe2S5QMdhHwmcDapDxZ7xf2Xa3z2HfCCYoHTmjiXV ``` If there is no `--node-key` argument, the node uses the key from the `secret_ed25519` file. If it does not exist, it is created with a newly generated secret key. The network key file cannot be recovered if lost. Therefore, keep it (or the private key itself) to have the possibility of running the node with the same peer ID. Moving the Node [#moving-the-node] To move the node to a new server, backup and restore the following (provided paths are for a Vara Mainnet node): * The network private key of the node: * Linux: `$HOME/.local/share/gear/chains/vara_network/network/secret_ed25519` * macOS: `$HOME/Library/Application Support/gear/chains/vara_network/network/secret_ed25519` * Windows: `%USERPROFILE%\AppData\Local\gear.exe\chains\vara_network\network\secret_ed25519` * (optional) The database: * Linux: `$HOME/.local/share/gear/chains/vara_network/db/full` * macOS: `$HOME/Library/Application Support/gear/chains/vara_network/db/full` * Windows: `%USERPROFILE%\AppData\Local\gear.exe\chains\vara_network\db\full` * (optional) The service configuration if the node is configured as a service: * Linux: `/etc/systemd/system/gear-node.service` If the database is not backed up, it can be synchronized from scratch, but this process may take some time. **Stop the node** before backing up the database to prevent data corruption. Use the following command to stop the service: ```shell sudo systemctl stop gear-node ``` # Dev Net Mode Dev net is helpful for the development and debugging of programs. It allows direct upload of programs to a local node, sending messages to programs, and validating program logic. To run the Vara node in dev net mode: 1. Compile or download the nightly build for the operating system as described in [Setting Up](/docs/vara-network/node). 2. Run the node in dev mode (assuming the executable is in the `/usr/bin` directory): ```bash gear --dev ``` 3. Follow [https://idea.gear-tech.io/](https://idea.gear-tech.io/) and connect to a local dev node. Click network selection via the left top button, choose Development -> Local node, and click the Switch button. Use the Idea portal for sending messages, reading the program's state, etc. 4. To purge any existing dev chain state, use: ```bash gear purge-chain --dev ``` 5. To start a dev chain with detailed logging, use: ```bash RUST_LOG=debug RUST_BACKTRACE=1 gear -lruntime=debug --dev ``` # Vara Node Introduction [#introduction] There are two ways to get started with the Vara node. First, download a pre-built binary file and run it. Second, compile the binary file and configure it manually. Using a ready-made build is a quick and convenient way to get started, as it skips the installation of Rust and all its dependencies and the node compiling process. Compiling the node from scratch may take about twenty minutes or more, depending on hardware. System Requirements [#system-requirements] • CPU: 2 vCPUs @ \~3.4 GHz (Intel Ice Lake or equivalent) • Memory: 8 GB RAM • Storage: 80 GB SSD Install with Pre-built Binary [#install-with-pre-built-binary] Depending on the operating system, download the latest release build of the node from [https://get.gear.rs](https://get.gear.rs): Terminal: ```bash curl https://get.gear.rs/gear-v1.2.1-x86_64-unknown-linux-gnu.tar.xz | tar xJ ``` or **Linux x86-64**: [gear-v1.2.1-x86\_64-unknown-linux-gnu.tar.xz](https://get.gear.rs/gear-v1.2.1-x86_64-unknown-linux-gnu.tar.xz) Run the node: ```bash ./gear --version gear 1.2.1-a218853 ``` Terminal: ```bash curl https://get.gear.rs/gear-v1.2.1-aarch64-apple-darwin.tar.xz | tar xJ ``` or **macOS ARM**: [gear-v1.2.1-aarch64-apple-darwin.tar.xz](https://get.gear.rs/gear-v1.2.1-aarch64-apple-darwin.tar.xz) Run the node: ```bash ./gear --version gear 1.2.1-a218853 ``` Terminal: ```bash curl https://get.gear.rs/gear-v1.2.1-x86_64-apple-darwin.tar.xz | tar xJ ``` or **macOS x86-64**: [gear-v1.2.1-x86\_64-apple-darwin.tar.xz](https://get.gear.rs/gear-v1.2.1-x86_64-apple-darwin.tar.xz) Run the node: ```bash ./gear --version gear 1.2.1-a218853 ``` Terminal: ```bash curl -O https://get.gear.rs/gear-v1.2.1-x86_64-pc-windows-msvc.zip ``` or **Windows x86-64**: [gear-v1.2.1-x86\_64-pc-windows-msvc.zip](https://get.gear.rs/gear-v1.2.1-x86_64-pc-windows-msvc.zip) Unzip the downloaded package, then run the node: ```bash gear.exe --version gear.exe 1.2.1-a218853 ``` If you require cutting-edge functionality before it hits the stable release, you can use our [nightly builds](https://get.gear.rs/#gear-nightly). **Note:** Nightly versions may be less stable than official releases. Compile Node Manually [#compile-node-manually] Compiling the build will take some time and requires the installation of some dependencies. * Windows users often face issues with Rust dependencies and build tools. * We **strongly recommend** using Linux or macOS for compiling Vara nodes and programs. **Tip:** If you are on Windows, using **WSL2** (Windows Subsystem for Linux) is the best way to ensure a stable development environment. Prerequisites [#prerequisites] Linux users should generally install `GCC` and `Clang`, according to their distribution’s documentation. Additionally, the `binaryen` toolset, which contains the required `wasm-opt` tool, should be installed. For example, on Ubuntu use: ```bash sudo apt install -y clang build-essential binaryen cmake protobuf-compiler ``` On macOS, get a compiler toolset and `binaryen` by running: ```bash xcode-select --install brew install binaryen ``` Install Rust [#install-rust] If Rust is already installed, skip this step. ```bash curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh ``` Reboot the terminal after installation. Install Wasm Toolchains [#install-wasm-toolchains] ```bash rustup toolchain add nightly rustup target add wasm32-unknown-unknown --toolchain nightly ``` Clone Gear Repository [#clone-gear-repository] ```bash git clone https://github.com/gear-tech/gear.git cd gear # Checkout to the latest release tag git checkout v1.2.1 -b release-1.2.1 ``` To access the latest or experimental Gear Protocol functions, compile the node from the `master` branch. If you have switched to another branch, you can return to `master` using: ```bash git checkout master ``` Compile [#compile] ```bash cargo build -p gear-cli --release ``` or ```bash make node-release ``` The compiled binary is located at: `target/release/gear` To navigate to the output folder, run: ```bash cd target/release ``` Run Vara Node [#run-vara-node] Regardless of how you obtained the binary, navigate to its directory and run the node to connect to **Vara Mainnet**: ```bash ./gear --chain=vara ``` To run the Vara node in dev mode: ```bash ./gear --dev ``` Command Flags and Options [#command-flags-and-options] ```bash gear [subcommand] [options] ``` * `--chain=vara` Connect the node to Vara Mainnet. * `--dev` Run a local node in development mode for testing purposes. This node will not be connected to any external network and will store the state in temporary storage. * `purge-chain` Remove storage of the selected chain type. Specify the chain connection type `--chain=vara` or `--dev`. * `help`, `--help` Print the available subcommands/options or the help of the given subcommand. Special Environment Variables [#special-environment-variables] To run the Vara node with logs from contracts: ```bash RUST_LOG="gwasm=debug" gear [subcommand] [options] ``` # Local Chain Setup If you're experimenting with runtime logic, benchmarking smart contracts, or just exploring how blockchain consensus actually works under the hood, running a local Vara chain is one of the best ways to do it. While Vara offers a ready-to-use public mainnet, local environments give you more control. You can modify chain parameters, test custom runtimes, or simulate multi-validator behavior without relying on any external infrastructure. In this guide, you'll learn how to set up and operate a full Vara blockchain network locally, from compiling the runtime to launching validators and connecting through Polkadot.js. Overview [#overview] This guide walks you through building and running a local Vara blockchain network for development and testing purposes. While Vara Mainnet is open to everyone, local development offers complete control over chain parameters, consensus mechanisms, and runtime modifications. When to Use This Approach [#when-to-use-this-approach] * **Custom runtime modifications** - Test changes to block time, consensus parameters, or governance * **Private testing environments** - Develop and test without external network dependencies * **Performance testing** - Benchmark your programs under controlled conditions * **Educational purposes** - Learn blockchain internals without risking real assets * **CI/CD integration** - Automated testing in build pipelines Key Benefits [#key-benefits] * Full control over all network parameters * Instant block production for rapid development * No external dependencies or network connectivity required * Cost-free testing environment * Customizable validator count and network topology Prerequisites [#prerequisites] System Requirements [#system-requirements] * **Operating System**: Linux, macOS, or Windows with WSL2 * **RAM**: Minimum 4GB (8GB+ recommended for multi-validator setups) * **Storage**: 10GB+ free space * **CPU**: 2+ cores (4+ recommended for validator networks) Required Software [#required-software] **Container Runtime** (choose one): * Podman (recommended) * Docker > **Note**: Throughout this guide, we use `ghcr.io/gear-tech/node:v1.9.2` as an example. Always check the [latest releases](https://github.com/gear-tech/gear/releases) for the most recent version. **Development Tools**: * Git * Rust toolchain with WASM target * `jq` (JSON processor) * `xxd` (hex dump utility) **Optional but Recommended**: * Polkadot.js extension for wallet management * Terminal multiplexer (tmux/screen) for managing multiple nodes Verification Steps [#verification-steps] ```bash # Verify container runtime podman --version || docker --version # Verify Rust installation rustc --version rustup target list --installed | grep wasm # Verify required utilities jq --version xxd -v ``` Quick Start for Experienced Users [#quick-start-for-experienced-users] If you're already familiar with Substrate-based chains and have all prerequisites installed: ```bash # 1. Clone and build git clone https://github.com/gear-tech/gear.git cd gear cargo build --release -p vara-runtime --features dev # 2. Generate chain spec podman run --rm -v $(pwd):/data ghcr.io/gear-tech/node:v1.9.2 \ gear build-spec --disable-default-bootnode --chain local > customSpec.json # 3. Update runtime (example: 5-minute epochs) sed -i 's/EPOCH_DURATION_IN_BLOCKS: BlockNumber = 2 \* HOURS/EPOCH_DURATION_IN_BLOCKS: BlockNumber = 5 * MINUTES/' \ runtime/vara/src/constants.rs # 4. Generate validator setup COUNT=3 bash generate-for-chain-spec.sh bash generate-node-keys.sh # 5. Start network bash start-local-node.sh # 6. Connect via Polkadot.js # Open https://polkadot.js.org and connect to ws://localhost:41000 ``` > **Note**: Replace `podman` with `docker` if using Docker. See detailed sections below for explanations and troubleshooting. Architecture Overview [#architecture-overview] ``` ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ Gear Node 1 │◄───────►│ Gear Node 2 │◄───────►│ Gear Node N │ └─────────────┘ └─────────────┘ └─────────────┘ ▲ ▲ ▲ │ │ │ │ WebSocket │ │ └───────────────────────┴───────────────────────┘ │ ┌─────────▼─────────┐ │ Polkadot.js UI │ └───────────────────┘ ``` Key Components [#key-components] * **Runtime WASM**: Contains your custom blockchain logic * **Chain Specification**: Defines network parameters and initial state * **Validator Nodes**: Produce blocks and maintain consensus * **Session Keys**: Cryptographic keys for validator operations * **Consensus Protocol**: BABE (block production) + GRANDPA (finality) Detailed Setup Guide [#detailed-setup-guide] Step 1: Install Dependencies [#step-1-install-dependencies] Ubuntu/Debian [#ubuntudebian] ```bash sudo apt update sudo apt install -y git clang curl libssl-dev llvm libudev-dev \ cmake protobuf-compiler jq xxd ``` macOS [#macos] ```bash # Install Homebrew if necessary /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install.sh)" brew update brew install openssl jq ``` Windows (WSL2) [#windows-wsl2] Follow Ubuntu instructions within the WSL2 environment. **Validation**: Run `git --version && jq --version && xxd -v` to confirm installation. Step 2: Install Rust Toolchain [#step-2-install-rust-toolchain] ```bash # Install Rust if not present curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh source ~/.cargo/env # Add WASM target rustup target add wasm32-unknown-unknown # Verify installation rustc --version && rustup target list --installed | grep wasm ``` Step 3: Clone and Build Gear [#step-3-clone-and-build-gear] ```bash git clone https://github.com/gear-tech/gear.git cd gear # Optional: Apply custom patches # Example: Change epoch duration to 5 minutes sed -i 's/EPOCH_DURATION_IN_BLOCKS: BlockNumber = 2 \* HOURS/EPOCH_DURATION_IN_BLOCKS: BlockNumber = 5 * MINUTES/' \ runtime/vara/src/constants.rs # Verify the change grep -n "EPOCH_DURATION_IN_BLOCKS" runtime/vara/src/constants.rs # Build runtime (this will take 10-30 minutes) cargo build --release -p vara-runtime --features dev # Verify build output ls -la target/release/wbuild/vara-runtime/vara_runtime.compact.compressed.wasm ``` **Expected Output**: You should see the WASM file containing the runtime of the network. *** Step 4: Generate Chain Specification [#step-4-generate-chain-specification] ```bash # Generate base specification podman run --rm -v $(pwd):/data ghcr.io/gear-tech/node:v1.9.2 \ gear build-spec --disable-default-bootnode --chain local > customSpec.json # Verify file creation ls -la customSpec.json jq '.name' customSpec.json # Should show "Vara Local Testnet" ``` Step 5: Update Runtime in Chain Spec [#step-5-update-runtime-in-chain-spec] ```bash # Convert WASM to hex xxd -p target/release/wbuild/vara-runtime/vara_runtime.compact.compressed.wasm | tr -d '\n' > vara_runtime.hex # Update chain spec with new runtime jq --rawfile data vara_runtime.hex \ '.genesis.runtimeGenesis.code="0x" + $data' customSpec.json > customSpec.tmp && \ mv customSpec.tmp customSpec.json # Verify update jq '.genesis.runtimeGenesis.code' customSpec.json | head -c 100 # Should start with "0x" ``` Step 6: Generate Validator Configuration [#step-6-generate-validator-configuration] Create `generate-for-chain-spec.sh` with the following content: ```bash #!/bin/sh COUNT_=${COUNT:-2} # Initialize arrays balances_array="[]" session_keys_array="[]" stakers_array="[]" invulnerables_array="[]" for NODE in `seq 0 $(( $COUNT_ - 1 ))`; do echo "Generating configuration for validator $NODE" SURI="bottom drive obey lake curtain smoke basket hold race lonely fit walk//validator-$NODE" GRANDPA=`podman run --platform linux/amd64 --rm ghcr.io/gear-tech/node:v1.9.2 \ gear key inspect --output-type json --scheme ed25519 "$SURI" | jq --raw-output .ss58Address` STASH=`podman run --platform linux/amd64 --rm ghcr.io/gear-tech/node:v1.9.2 \ gear key inspect --output-type json "$SURI//stash" | jq --raw-output .ss58Address` BABE=`podman run --platform linux/amd64 --rm ghcr.io/gear-tech/node:v1.9.2 \ gear key inspect --output-type json "$SURI" | jq --raw-output .ss58Address` # Add to balances array balances_array=$(echo "$balances_array" | jq ". += [[\"$STASH\", 100000000000000], [\"$BABE\", 100000000000000000000]]") # Add to session keys array session_keys_array=$(echo "$session_keys_array" | jq ". += [[\"$STASH\", \"$STASH\", {\"authority_discovery\": \"$BABE\", \"babe\": \"$BABE\", \"grandpa\": \"$GRANDPA\", \"im_online\": \"$BABE\"}]]") # Add to stakers array stakers_array=$(echo "$stakers_array" | jq ". += [[\"$STASH\", \"$BABE\", 100000000000000, \"Validator\"]]") # Add to invulnerables array invulnerables_array=$(echo "$invulnerables_array" | jq ". += [\"$STASH\"]") done echo "Generated $COUNT_ validators" echo "Updating chain specification..." # Update customSpec.json with all arrays at once jq -M ".genesis.runtimeGenesis.patch.staking.validatorCount = $COUNT_ | \ .genesis.runtimeGenesis.patch.balances.balances += $balances_array | \ .genesis.runtimeGenesis.patch.session.keys = $session_keys_array | \ .genesis.runtimeGenesis.patch.staking.stakers = $stakers_array | \ .genesis.runtimeGenesis.patch.staking.invulnerables = $invulnerables_array" \ customSpec.json > customSpec.tmp && mv customSpec.tmp customSpec.json echo "Chain specification updated successfully" ``` Run the script: ```bash chmod +x generate-for-chain-spec.sh COUNT=3 ./generate-for-chain-spec.sh # Creates 3 validators ``` Step 7: Generate Node Keys [#step-7-generate-node-keys] Create `generate-node-keys.sh`: ```bash #!/bin/sh COUNT_=${COUNT:-2} for NODE in `seq 0 $(( $COUNT_ - 1 ))`; do echo "Generating keys for node-$NODE" mkdir -p node-$NODE cp ./customSpec.raw ./node-$NODE/ SURI="bottom drive obey lake curtain smoke basket hold race lonely fit walk//validator-$NODE" # Insert session keys podman run --platform linux/amd64 --rm -v ./node-$NODE:/base-path \ ghcr.io/gear-tech/node:v1.9.2 gear key insert \ --base-path /base-path --chain /base-path/customSpec.raw \ --scheme Sr25519 --suri "$SURI" --key-type audi podman run --platform linux/amd64 --rm -v ./node-$NODE:/base-path \ ghcr.io/gear-tech/node:v1.9.2 gear key insert \ --base-path /base-path --chain /base-path/customSpec.raw \ --scheme Sr25519 --suri "$SURI" --key-type babe podman run --platform linux/amd64 --rm -v ./node-$NODE:/base-path \ ghcr.io/gear-tech/node:v1.9.2 gear key insert \ --base-path /base-path --chain /base-path/customSpec.raw \ --scheme Sr25519 --suri "$SURI" --key-type imon podman run --platform linux/amd64 --rm -v ./node-$NODE:/base-path \ ghcr.io/gear-tech/node:v1.9.2 gear key insert \ --base-path /base-path --chain /base-path/customSpec.raw \ --scheme Ed25519 --suri "$SURI" --key-type gran podman run --platform linux/amd64 --rm -v ./node-$NODE:/base-path \ ghcr.io/gear-tech/node:v1.9.2 gear key generate-node-key \ --base-path /base-path --chain /base-path/customSpec.raw done echo "Node keys generated successfully" ``` Run the script: ```bash chmod +x generate-node-keys.sh ./generate-node-keys.sh ``` Step 8: Convert Chain Spec to Raw Format [#step-8-convert-chain-spec-to-raw-format] ```bash podman run --platform linux/amd64 --rm -v $(pwd):/data \ ghcr.io/gear-tech/node:v1.9.2 gear build-spec \ --disable-default-bootnode --chain=/data/customSpec.json --raw > customSpec.raw # Verify raw spec ls -la customSpec.raw jq '.name' customSpec.raw # Should show "Vara Local Testnet" ``` Step 9: Start Validator Network [#step-9-start-validator-network] Create `start-local-node.sh`: ```bash #!/bin/sh # Configuration IP='127.0.0.1' # Use your machine's IP address BASE_PORT_RPC=41000 BASE_PORT_P2P=42000 echo "Starting local Gear validator network..." # Start first node (bootnode) NODE=0 PORT_P2P=$(( $BASE_PORT_P2P + $NODE )) PORT_RPC=$(( $BASE_PORT_RPC + $NODE )) echo "Starting bootnode (validator-0) on RPC port $PORT_RPC, P2P port $PORT_P2P" podman run --replace --platform linux/amd64 --detach --name validator-$NODE \ -v $(pwd)/node-$NODE:/base-path ghcr.io/gear-tech/node:v1.9.2 \ gear --base-path /base-path --chain /base-path/customSpec.raw \ --validator --rpc-cors all --rpc-external --rpc-methods=unsafe \ --name node-$NODE --rpc-port $PORT_RPC --port $PORT_P2P \ --no-telemetry --no-prometheus --allow-private-ip --in-peers 256 \ --no-mdns --no-hardware-benchmarks --state-pruning archive \ --blocks-pruning archive --public-addr "/ip4/$IP/tcp/$PORT_P2P" # Wait for bootnode to start sleep 5 # Get bootnode ID BOOTNODE_ID=`podman run --platform linux/amd64 -v $(pwd)/node-$NODE:/base-path \ ghcr.io/gear-tech/node:v1.9.2 gear key inspect-node-key \ --file /base-path/chains/vara_local_testnet/network/secret_ed25519` echo "Bootnode ID: $BOOTNODE_ID" # Start remaining nodes COUNT_=${COUNT:-2} for NODE in `seq 1 $(( $COUNT_ - 1 ))`; do PORT_P2P=$(( $BASE_PORT_P2P + $NODE )) PORT_RPC=$(( $BASE_PORT_RPC + $NODE )) echo "Starting validator-$NODE on RPC port $PORT_RPC, P2P port $PORT_P2P" podman run --replace --platform linux/amd64 --detach --name validator-$NODE \ -v $(pwd)/node-$NODE:/base-path ghcr.io/gear-tech/node:v1.9.2 \ gear --base-path /base-path --chain /base-path/customSpec.raw \ --validator --rpc-cors all --rpc-external --rpc-methods=unsafe \ --name validator-$NODE --rpc-port $PORT_RPC --port $PORT_P2P \ --no-telemetry --no-prometheus --allow-private-ip --in-peers 256 \ --no-mdns --no-hardware-benchmarks \ --bootnodes /ip4/$IP/tcp/$BASE_PORT_P2P/p2p/$BOOTNODE_ID \ --state-pruning archive --blocks-pruning archive done echo "All validators started successfully!" echo "RPC endpoints available at: ws://localhost:41000, ws://localhost:41001, etc." ``` Run the script: ```bash chmod +x start-local-node.sh ./start-local-node.sh ``` Validation and Testing [#validation-and-testing] Check Node Status [#check-node-status] ```bash # List running containers podman ps # Check logs for first validator podman logs validator-0 | tail -20 # Look for these key messages: # - "Idle (1 peers)" - indicates network connectivity # - "Imported" - shows block production # - "Starting consensus session" - validator is active ``` Test RPC Connection [#test-rpc-connection] ```bash # Test RPC endpoint (requires curl and jq) curl -H "Content-Type: application/json" \ -d '{"id":1,"jsonrpc":"2.0","method":"system_health","params":[]}' \ http://localhost:41000 # Expected response should show: # {"jsonrpc":"2.0","result":{"isSyncing":false,"peers":2,"shouldHavePeers":true},"id":1} ``` Connect via Polkadot.js [#connect-via-polkadotjs] 1. Open [https://polkadot.js.org/apps](https://polkadot.js.org/apps) 2. Click on the network selector (top left) 3. Choose "Development" → "Local Node" 4. If using custom ports, select "Custom" and enter `ws://localhost:41000` 5. You should see the chain name and block number Monitoring and Health Checks [#monitoring-and-health-checks] Key Metrics to Monitor [#key-metrics-to-monitor] * **Block Production**: Blocks should be produced regularly * **Peer Count**: Each node should connect to other validators * **Finalization**: GRANDPA finality should be working * **Memory Usage**: Monitor for memory leaks * **Disk Usage**: Archive nodes store full history Useful Monitoring Commands [#useful-monitoring-commands] ```bash # Check all validators are running podman ps | grep validator # Monitor logs in real-time podman logs -f validator-0 # Check network connectivity for port in 41000 41001 41002; do echo "Testing port $port" curl -s -H "Content-Type: application/json" \ -d '{"id":1,"jsonrpc":"2.0","method":"system_health","params":[]}' \ http://localhost:$port | jq '.result.peers' done # Check block height across all nodes for port in 41000 41001 41002; do echo "Node on port $port:" curl -s -H "Content-Type: application/json" \ -d '{"id":1,"jsonrpc":"2.0","method":"chain_getHeader","params":[]}' \ http://localhost:$port | jq '.result.number' done ``` Setting Up Prometheus Monitoring (Optional) [#setting-up-prometheus-monitoring-optional] For production-like monitoring, you can enable Prometheus metrics by removing `--no-prometheus` flag and configuring Prometheus to scrape the metrics endpoint. Troubleshooting [#troubleshooting] Common Issues [#common-issues] 1. Port Already in Use [#1-port-already-in-use] **Error**: `Address already in use` **Solution**: ```bash # Find process using port sudo lsof -i :41000 # Kill process or change port in start script ``` 2. Container Startup Failures [#2-container-startup-failures] **Error**: `Error: unable to start container` **Solution**: ```bash # Check container logs podman logs validator-0 # Check disk space df -h # Check memory usage free -h ``` 3. Nodes Not Connecting [#3-nodes-not-connecting] **Error**: `0 peers` or `Syncing 0 peers` **Solution**: ```bash # Verify bootnode ID is correct podman run --platform linux/amd64 -v $(pwd)/node-0:/base-path \ ghcr.io/gear-tech/node:v1.9.2 gear key inspect-node-key \ --file /base-path/chains/vara_local_testnet/network/secret_ed25519 # Check network connectivity between containers # Ensure all nodes use the same IP address in startup script ``` 4. Chain Spec Issues [#4-chain-spec-issues] **Error**: `Chain specification is invalid` **Solution**: ```bash # Verify JSON syntax jq . customSpec.json > /dev/null && echo "Valid JSON" || echo "Invalid JSON" # Check required fields are present jq '.genesis.runtimeGenesis | keys' customSpec.json ``` 5. Runtime WASM Issues [#5-runtime-wasm-issues] **Error**: `Runtime construction failed` **Solution**: ```bash # Verify WASM file exists and is valid ls -la target/release/wbuild/vara-runtime/vara_runtime.compact.compressed.wasm file target/release/wbuild/vara-runtime/vara_runtime.compact.compressed.wasm # Rebuild runtime if necessary cargo build --release -p vara-runtime --features dev ``` Getting Help [#getting-help] * **Check logs first**: `podman logs validator-0 | tail -50` * **Verify file permissions**: `ls -la node-0/` * **Check system resources**: `htop` or `top` * **Gear Discord**: Join the [official Gear Discord](https://discord.gg/gear) for community support * **GitHub Issues**: Report bugs at [https://github.com/gear-tech/gear/issues](https://github.com/gear-tech/gear/issues) Cleanup and Teardown [#cleanup-and-teardown] Stop Network Gracefully [#stop-network-gracefully] Create `stop-local-node.sh`: ```bash #!/bin/sh echo "Stopping local Gear validator network..." # Stop and remove all validator containers for NODE in $(podman ps --format "{{.Names}}" | grep validator); do echo "Stopping $NODE" podman stop $NODE podman rm $NODE done echo "All validators stopped and removed" ``` Run: ```bash chmod +x stop-local-node.sh ./stop-local-node.sh ``` Complete Cleanup [#complete-cleanup] ```bash # Remove all generated files rm -rf node-*/ customSpec.* vara_runtime.hex # Optional: Remove built runtime rm -rf target/release/wbuild/ # Optional: Remove cloned repository cd .. rm -rf gear/ # Remove container images (if no longer needed) podman rmi ghcr.io/gear-tech/node:v1.9.2 ``` Data Backup (Optional) [#data-backup-optional] Before cleanup, you might want to back up: ```bash # Chain state cp -r node-0/chains/ ~/gear-backup/ # Keys cp -r node-0/keystore/ ~/gear-backup/ # Logs podman logs validator-0 > validator-0.log ``` Security Considerations [#security-considerations] ⚠️ Important Security Warnings [#️-important-security-warnings] * **Unsafe RPC Methods**: The `--rpc-methods=unsafe` flag enables dangerous RPC calls. Never use this in production. * **CORS Settings**: `--rpc-cors all` allows cross-origin requests from any domain. Restrict this in production. * **Private Keys**: The mnemonic `bottom drive obey lake curtain smoke basket hold race lonely fit walk` is publicly known. Never use this for mainnet or any valuable assets. * **Network Isolation**: Run local networks in isolated environments to prevent accidental connections to mainnet. * **Port Exposure**: Be careful about exposing RPC ports to public networks. Best Practices [#best-practices] ```bash # Use specific CORS origins instead of "all" --rpc-cors "https://polkadot.js.org" # Restrict RPC methods in production --rpc-methods safe # Use proper firewall rules # Allow only specific IP ranges # Block external access to validator ports # Regular security updates podman pull ghcr.io/gear-tech/node:v1.9.2 # Get latest security patches ``` Performance and Resources [#performance-and-resources] Resource Requirements by Network Size [#resource-requirements-by-network-size] | Validators | CPU Cores | RAM (GB) | Storage (GB/day) | Network | | ---------- | --------- | -------- | ---------------- | ------- | | 1 | 2 | 4 | 1-2 | Local | | 3 | 4 | 8 | 3-5 | Local | | 6 | 8 | 16 | 6-10 | Local | | 12 | 16 | 32 | 12-20 | LAN | Performance Optimization Tips [#performance-optimization-tips] * Use SSD storage for better I/O performance * Increase container memory limits for large networks * Use state pruning for non-archive nodes (`--state-pruning 256`) * Limit peer connections if network becomes unstable (`--in-peers 50`) * Monitor system resources and adjust validator count accordingly Benchmarking Your Setup [#benchmarking-your-setup] ```bash # Measure block production time curl -s -H "Content-Type: application/json" \ -d '{"id":1,"jsonrpc":"2.0","method":"chain_getBlock","params":[]}' \ http://localhost:41000 | jq '.result.block.header.timestamp' # Monitor transaction throughput # Use Polkadot.js to submit multiple transactions and measure processing time # Test finality speed # GRANDPA should finalize blocks within 2-3 seconds of production ``` Common Use Cases [#common-use-cases] 1. Smart Contract Development [#1-smart-contract-development] * Deploy test contracts locally * Test contract interactions * Debug contract execution * Measure gas consumption 2. Runtime Development [#2-runtime-development] * Test runtime upgrades * Experiment with governance parameters * Debug consensus issues * Validate economic models 3. Integration Testing [#3-integration-testing] * Test dApp frontend connectivity * Simulate network conditions * Test wallet integrations * Validate transaction flows 4. Educational Purposes [#4-educational-purposes] * Learn blockchain consensus * Understand validator mechanics * Experiment with cryptography * Study network protocols 5. CI/CD Pipelines [#5-cicd-pipelines] * Automated contract testing * Regression testing * Performance benchmarking * Integration testing Advanced Configuration [#advanced-configuration] Custom Runtime Modifications [#custom-runtime-modifications] Beyond epoch duration, you can modify: * **Block time**: Change `MILLISECS_PER_BLOCK` in constants * **Governance parameters**: Voting periods, thresholds * **Staking rewards**: Inflation rate, validator set size * **Transaction fees**: Weight calculations, fee multipliers * **Account balances**: Initial token distribution Network Topology Options [#network-topology-options] * **Single node**: Simplest setup for development * **Multi-validator**: Test consensus and finality * **Bootnode topology**: Star network with central bootnode * **Mesh network**: All nodes connect to each other * **Hybrid setups**: Mix of validator and full nodes Integration with External Tools [#integration-with-external-tools] * **Prometheus/Grafana**: Advanced monitoring * **Kubernetes**: Container orchestration * **Docker Compose**: Multi-container management * **Terraform**: Infrastructure as code * **Ansible**: Configuration management * **Gear IDEA**: You can point [Gear IDEA](https://idea.gear-tech.io/) to your custom network and get a user-friendly UX for interacting with the network. Conclusion [#conclusion] Running a local Vara chain is one of the most effective ways to experiment safely and understand how different components interact. Once everything is configured, it becomes a flexible tool for contract testing, runtime upgrades, and integration checks. Remember to: [#remember-to] * ✅ Always validate each step before proceeding * ✅ Monitor your network's health regularly * ✅ Follow security best practices * ✅ Clean up resources when finished * ✅ Keep software updated for security patches For more details and community help: [#for-more-details-and-community-help] * Visit the [Gear Documentation](https://docs.gear.foundation/) for technical guidance * Explore the [GitHub repository](https://github.com/gear-tech/gear) to track updates or report issues * Connect with developers in the [Gear Discord community](https://discord.gg/gear) Happy building! 🚀 # Multi-Node Mode For more advanced programs debugging and testing closer to the real network environment, you can build your local testnet that consists of several interconnected nodes. This mode allows to see the multi-node consensus algorithm in action. Run a local testnet with two validator nodes, Alice and Bob, that have been [configured](https://github.com/gear-tech/gear/blob/master/node/src/chain_spec.rs) as the initial authorities of the `local` testnet chain and endowed with testnet units. Note: this will require two terminal sessions (one for each node). 1. Start Alice's node first. The command below uses the default TCP port (30333) and specifies `/tmp/alice` as the chain database location. Alice's node ID will be `12D3KooWEyoppNCUx8Yx66oV9fJnriXwCcXwDDUA2kj6vnc6iDEp` (legacy representation: `QmRpheLN4JWdAnY7HGJfWFNbfkQCb6tFf4vvA6hgjMZKrR`); this is determined by the node-key. ```bash gear \ --base-path /tmp/alice \ --chain=local \ --alice \ --node-key 0000000000000000000000000000000000000000000000000000000000000001 \ --telemetry-url "wss://telemetry.rs/submit 0" ``` The Alice node will be run in idle mode at block #0 waiting for the second validator node. ``` Gear Node ✌️ version 1.1.1-d02d306f97c ❤️ by Gear Technologies, 2021-2024 📋 Chain specification: Vara Local Testnet 🏷 Node name: Alice 👤 Role: AUTHORITY 💾 Database: RocksDb at /tmp/alice/chains/gear_local_testnet/db/full ⛓ Native runtime: vara-1020 (vara-1.tx1.au1) 👶 Creating empty BABE epoch changes on what appears to be first startup. Using default protocol ID "sup" because none is configured in the chain specs 🏷 Local node identity is: 12D3KooWEyoppNCUx8Yx66oV9fJnriXwCcXwDDUA2kj6vnc6iDEp 💻 Operating system: macos 💻 CPU architecture: aarch64 📦 Highest known block at #0 〽️ Prometheus exporter started at 127.0.0.1:9615 Running JSON-RPC HTTP server: addr=127.0.0.1:9933, allowed origins=Some(["http://localhost:*", "http://127.0.0.1:*", "https://localhost:*", "https://127.0.0.1:*", "https://polkadot.js.org"]) Running JSON-RPC WS server: addr=127.0.0.1:9944, allowed origins=Some(["http://localhost:*", "http://127.0.0.1:*", "https://localhost:*", "https://127.0.0.1:*", "https://polkadot.js.org"]) 🏁 CPU score: 1020MB/s 🏁 Memory score: 37732MB/s 🏁 Disk score (seq. writes): 1496MB/s 🏁 Disk score (rand. writes): 421MB/s 👶 Starting BABE Authorship worker 💤 Idle (0 peers), best: #0 (0x22c7…6847), finalized #0 (0x22c7…6847), ⬇ 0 ⬆ 0 💤 Idle (0 peers), best: #0 (0x22c7…6847), finalized #0 (0x22c7…6847), ⬇ 0 ⬆ 0 💤 Idle (0 peers), best: #0 (0x22c7…6847), finalized #0 (0x22c7…6847), ⬇ 0 ⬆ 0 💤 Idle (0 peers), best: #0 (0x22c7…6847), finalized #0 (0x22c7…6847), ⬇ 0 ⬆ 0 ``` 2. In another terminal, use the following command to start Bob's node on a different TCP port (`30334`) and with a chain database location of `/tmp/bob`. The `--bootnodes` option will connect this node to the Alice's one on TCP port `30333`: ```bash gear \ --base-path /tmp/bob \ --bootnodes /ip4/127.0.0.1/tcp/30333/p2p/12D3KooWEyoppNCUx8Yx66oV9fJnriXwCcXwDDUA2kj6vnc6iDEp \ --chain=local \ --bob \ --port 30334 \ --ws-port 9945 \ --telemetry-url "wss://telemetry.rs/submit 0" ``` After running the second validator the network starts to produce new blocks. ``` Gear Node ✌️ version 1.1.1-33ee05d5aab ❤️ by Gear Technologies, 2021-2024 📋 Chain specification: Vara Local Testnet 🏷 Node name: Bob 👤 Role: AUTHORITY 💾 Database: RocksDb at /tmp/bob/chains/gear_local_testnet/db/full ⛓ Native runtime: vara-1110 (vara-1.tx1.au1) 🔨 Initializing Genesis block/state (state: 0xf470…d2dc, header-hash: 0x22c7…6847) 👴 Loading GRANDPA authority set from genesis on what appears to be first startup. 👶 Creating empty BABE epoch changes on what appears to be first startup. Using default protocol ID "sup" because none is configured in the chain specs 🏷 Local node identity is: 12D3KooWHpsf9Gp59ct6t6d1MmKHxbmZRvSWcUej7cUNmWNBdvZE 💻 Operating system: macos 💻 CPU architecture: aarch64 📦 Highest known block at #0 Running JSON-RPC HTTP server: addr=127.0.0.1:61429, allowed origins=Some(["http://localhost:*", "http://127.0.0.1:*", "https://localhost:*", "https://127.0.0.1:*", "https://polkadot.js.org"]) Running JSON-RPC WS server: addr=127.0.0.1:9945, allowed origins=Some(["http://localhost:*", "http://127.0.0.1:*", "https://localhost:*", "https://127.0.0.1:*", "https://polkadot.js.org"]) 🏁 CPU score: 875MB/s 🏁 Memory score: 38353MB/s 🏁 Disk score (seq. writes): 1497MB/s 🏁 Disk score (rand. writes): 421MB/s 👶 Starting BABE Authorship worker discovered: 12D3KooWEyoppNCUx8Yx66oV9fJnriXwCcXwDDUA2kj6vnc6iDEp /ip4/192.168.1.4/tcp/30333 discovered: 12D3KooWEyoppNCUx8Yx66oV9fJnriXwCcXwDDUA2kj6vnc6iDEp /ip6/::1/tcp/30333 discovered: 12D3KooWEyoppNCUx8Yx66oV9fJnriXwCcXwDDUA2kj6vnc6iDEp /ip4/127.0.0.1/tcp/30333 🙌 Starting consensus session on top of parent 0x22c70bb6baf611e5c9a6b1886b307dfa25cf1972b3400e70eb15d624c8b96847 🎁 Prepared block for proposing at 1 (0 ms) [hash: 0x1f55c4a9daf8c35d4388313ad67deec4ff5e472659e2fec310bd3032bcbdfe71; parent_hash: 0x22c7…6847; extrinsics (2): [0x8bbd…2a85, 0x7d5c…0b86]] 🔖 Pre-sealed block for proposal at 1. Hash now 0xe46f6031bb73710e239665d0da4212fbdcca77d57f9504d357c078f3b315389e, previously 0x1f55c4a9daf8c35d4388313ad67deec4ff5e472659e2fec310bd3032bcbdfe71. 👶 New epoch 0 launching at block 0xe46f…389e (block slot 1667197557 >= start slot 1667197557). 👶 Next epoch starts at slot 1667198157 ✨ Imported #1 (0xe46f…389e) 🙌 Starting consensus session on top of parent 0xe46f6031bb73710e239665d0da4212fbdcca77d57f9504d357c078f3b315389e 🎁 Prepared block for proposing at 2 (0 ms) [hash: 0x66281c25f34157c713876cda6f39324a0da6a9a50c0c32310683bca875c1ab4c; parent_hash: 0xe46f…389e; extrinsics (2): [0xcb81…f7c3, 0x7d5c…0b86]] 🔖 Pre-sealed block for proposal at 2. Hash now 0x2ab179fe98969cab0970df085b86e03dbee33daed527f6595bcdbeffebc64175, previously 0x66281c25f34157c713876cda6f39324a0da6a9a50c0c32310683bca875c1ab4c. ✨ Imported #2 (0x2ab1…4175) 🙌 Starting consensus session on top of parent 0x2ab179fe98969cab0970df085b86e03dbee33daed527f6595bcdbeffebc64175 🎁 Prepared block for proposing at 3 (0 ms) [hash: 0xb5b9be01adb191671e0421f4673f8bee0751481aaac7ea453d806161e3b46dd7; parent_hash: 0x2ab1…4175; extrinsics (2): [0x7771…de48, 0x7d5c…0b86]] 🔖 Pre-sealed block for proposal at 3. Hash now 0xf2ed128d41d96da623281745555a0fe00a033630aacf57ac02acc31ced267db0, previously 0xb5b9be01adb191671e0421f4673f8bee0751481aaac7ea453d806161e3b46dd7. ✨ Imported #3 (0xf2ed…7db0) ``` 3. The network is live! If you want to stop it, just press Ctrl + C in both running sessions. # Node as a Service Prerequisites [#prerequisites] Download or compile the `gear` executable file for the operating system. [See more](/docs/vara-network/node#install-with-pre-built-binary) Configuration [#configuration] Copy the `gear` executable to the `/usr/bin` directory: ```bash sudo cp gear /usr/bin ``` To run the Vara node as a Linux service, configure the systemd file: ```bash cd /etc/systemd/system sudo nano gear-node.service ``` Configure and save: ```toml [Unit] Description=Vara Node After=network.target [Service] Type=simple User=root WorkingDirectory=/root/ ExecStart=/usr/bin/gear --name "NODE_NAME" --telemetry-url "wss://telemetry.rs/submit 0" Restart=always RestartSec=3 LimitNOFILE=10000 [Install] WantedBy=multi-user.target ``` The `ExecStart` directive points to the `gear` binary location (default: `/usr/bin`). **Tip:** You can append optional launch parameters (like `--name` or `--validator`) directly to the execution command if needed. Starting the Node [#starting-the-node] Run the following command to start the service: ```sh sudo systemctl start gear-node ``` Enable the service to start on boot: ```sh sudo systemctl enable gear-node ``` Check the status of the gear-node service: ```sh sudo systemctl status gear-node ``` Checking Logs [#checking-logs] To view the service logs, run: ```sh journalctl -u gear-node ``` Use navigation keys to browse the logs and the q key to exit. View the last 50 lines of logs by adding the `-n 50` parameter: ```sh journalctl -u gear-node -n 50 ``` See the last lines of logs in continuous mode (press Ctrl+C to exit): ```sh journalctl -u gear-node -fn 50 ``` Updating the Node to a New Version [#updating-the-node-to-a-new-version] After running the node for a while, updating to the latest version may be necessary. Replace the node executable (`gear`) with the latest version and restart the service. For example, if the Linux executable is located at `/usr/bin/gear` (as configured above), run: ```sh curl https://get.gear.rs/gear-v1.1.1-x86_64-unknown-linux-gnu.tar.xz | sudo tar -xJC /usr/bin sudo systemctl restart gear-node ``` Removing the Node [#removing-the-node] If running the node is no longer needed, completely purge it from the disk. Once the node is deleted, it **cannot be fully restored**. Before proceeding, ensure you have followed the [Backup and Restore](/docs/vara-network/node/backup-restore) instructions to save your important data. Remove the node's storage, the service configuration, and the executable: ```sh sudo systemctl stop gear-node sudo systemctl disable gear-node sudo rm -rf /root/.local/share/gear sudo rm /etc/systemd/system/gear-node.service sudo rm /usr/bin/gear ``` # Node FAQ Can I run the Vara node now? [#can-i-run-the-vara-node-now] Yes, follow the instructions from this article on how to set up and run Vara node under MacOS, Linux and Windows: [Setting Up](/docs/vara-network/node) What are hardware requirements for Vara node? [#what-are-hardware-requirements-for-vara-node] There are no special hardware requirements except SSD for running a Vara node in dev mode. For nodes in the production network, see the requirements below. Please refer to the [System Requirements](/docs/vara-network/node#system-requirements) to see the actual hardware requirements. I have an error when trying to run the node [#i-have-an-error-when-trying-to-run-the-node] Please refer to the [Troubleshooting](/docs/vara-network/node/troubleshooting) section to find typical errors and solutions. Are there rewards for running nodes? [#are-there-rewards-for-running-nodes] Validator nodes in [Vara](https://vara.network/), are incentivized according to the Nominated Proof-of-Stake (NPoS) mechanism. Refer to [this article](/docs/vara-network/staking) for more details. Additionally, anyone can run a local node for development or/and run local services. Could we run validator now? [#could-we-run-validator-now] Yes, anyone can setup a validator in the Vara network. Follow the instructions from the [Validators guide](/docs/vara-network/staking/validate). How do I make the node work in the background? [#how-do-i-make-the-node-work-in-the-background] The solution is to configure the [Vara node as a service](/docs/vara-network/node/node-as-service). My host provider claims the node abuses their network. [#my-host-provider-claims-the-node-abuses-their-network] This should be resolved by adding `--no-private-ipv4` argument when running the node. If for some reason, that argument doesn't solve the issue for you, then you can deny egress traffic to: ```bash 10.0.0.0/8 172.16.0.0/12 192.168.0.0/16 100.64.0.0/10 169.254.0.0/16 ``` For example you can use this manual: [https://community.hetzner.com/tutorials/block-outgoing-traffic-to-private-networks](https://community.hetzner.com/tutorials/block-outgoing-traffic-to-private-networks) I've configured the node as a service. How can I update it? [#ive-configured-the-node-as-a-service-how-can-i-update-it] You just need to replace the node executable (`gear`) with the latest version and restart the execution. For example, if your Linux executable is located at `/usr/bin` you are to run: ```shell curl https://get.gear.rs/gear-v1.1.1-x86_64-unknown-linux-gnu.tar.xz | sudo tar -xJC /usr/bin sudo systemctl restart gear-node ``` My node stopped to increment the block height after some block number. [#my-node-stopped-to-increment-the-block-height-after-some-block-number] [Update](/docs/vara-network/node/node-as-service#updating-the-node-to-a-new-version) the node binary to the latest version. How do I change the port number if the default one is already used by another software? [#how-do-i-change-the-port-number-if-the-default-one-is-already-used-by-another-software] Use one of the supported flags when running the node: ``` --port Specify p2p protocol TCP port --prometheus-port Specify Prometheus exporter TCP Port --rpc-port Specify JSON-RPC server TCP port ``` Default ports are: * P2P: `30333` * Prometheus: `9615` * WebSocket/HTTP JSON-RPC: `9944` How to see Vara node service logs? [#how-to-see-vara-node-service-logs] See the [Checking logs](/docs/vara-network/node/node-as-service#checking-logs) section for details. What is the node syncing time? [#what-is-the-node-syncing-time] The full node syncing time may be calculated using the info from the log: yarn add katex $$ syncing\char`_time \text{ [secs]} = \frac{target\char`_block - finalized\char`_block} {bps} $$ For example, let's calculate the syncing time from the following log record: ``` ... ⚙️ Syncing 143.1 bps, target=#3313788 ... finalized #3223552 ... ``` $$ syncing\char`_time = \frac {3313788 - 3223552} {143.1} \approx 630 \text{ secs } (10.5 \text{ mins}) $$ Is the node visible in telemetry during syncing? [#is-the-node-visible-in-telemetry-during-syncing] Only Validator nodes are visible on the telemetry portal - [https://telemetry.rs](https://telemetry.rs). Is there any command to check for new updates for the node? [#is-there-any-command-to-check-for-new-updates-for-the-node] There is no such command. # Node Monitoring The health of each Vara node and the entire network needs to be monitored to ensure truly decentralized and robust operations. This includes the various block production metrics as well as node uptime which is vital in PoS networks. As any other Substrate based nodes, Vara node can be connected to an arbitrary telemetry backend using the `--telemetry-url`. To start sending telemetry messages to the arbitrary telemetry server instance, one needs to specify an additional key during node run that will enable sending telemetry to specified http address. If you want to participate and share your telemetry, run your node with the flag (we assume the executable is in `/usr/bin` directory): ```sh gear --telemetry-url "wss://telemetry.rs/submit 0" ``` Also, you can provide your node name using the `--name` flag: ```sh gear --name "NODE NAME" ``` For example, to start a node with telemetry, run the command: ```sh gear --telemetry-url "wss://telemetry.rs/submit 0" --name "My_Gear_node_name" ``` You can send more information (e.g., the validator address) by increasing the verbosity level: ```sh gear --telemetry-url "wss://telemetry.rs/submit 1" --name "My_validator_name" ``` To check telemetry for currently running nodes, visit the web address: [https://telemetry.rs](https://telemetry.rs). You can find some additional information about monitoring in the [Substrate docs](https://docs.substrate.io/maintain/monitor/). # Troubleshooting Typical errors and solutions are described here. Unavailable LOCK file [#unavailable-lock-file] * **Error:** `IO error: While lock file /root/.local/share/gear/chains/vara_network/db/full/LOCK: Resource temporarily unavailable` * **Solution:** You seem to be running several Vara node instances. Note that only one node instance is allowed to run. You likely have configured the node as a service and then ran the second instance from the command line. You should either stop the service or don't run the Vara node from the command line. You can see the current node processes by running the command: ```shell ps aux | grep gear ``` If you want to break all node processes you may run: ```shell pkill -sigint gear ``` Note that the SystemD service can't be stopped by the command above. Run instead: ```shell sudo systemctl stop gear-node ``` Unexpected argument when starting the node service [#unexpected-argument-when-starting-the-node-service] * **Error:** `Found argument '\' which wasn't expected, or isn't valid in this context` * **Solution:** The `gear-node.service` configuration file seems to be misconfigured. Some versions of SystemD do not accept the backslash character (`\`) as a line break. Therefore, it is better to write each of the config entry on one line. Refer to /node/node-as-service for properly configuring the node as a service. Don't forget to restart the node after fixing the service configuration: ```shell sudo systemctl daemon-reload sudo systemctl restart gear-node ``` Corrupted data base [#corrupted-data-base] * **Error:** `Database version cannot be read from existing db_version file` * **Alternative error:** `Invalid argument: Column families not opened: ..., col2, col1, col0` * **Solution:** The root of this problem is the lack of the disk free space. You may check the free space using the following command: ```shell df -h ``` Also, you may check how many space is used by the blockchain DB: ```shell du -h $HOME/.local/share/gear/chains/vara_network/db/full ``` Please refer to the [System Requirements](/docs/vara-network/node#system-requirements) to see the minimum disk space required. You need to free more space then purge the chain: ```shell sudo systemctl stop gear-node # Provide more free space on the disk gear purge-chain sudo systemctl start gear-node ``` Node executable file obsolescence [#node-executable-file-obsolescence] * **Error:** `Verification failed for block received from peer ` * **Alternative error:** `runtime requires function imports which are not present on the host` * **Solution:** [Update](/docs/vara-network/node/node-as-service#updating-the-node-to-a-new-version) the node binary to the latest version. Masked service [#masked-service] * **Error:** `Failed to start gear-node.service: Unit gear-node.service is masked.` * **Solution:** Please check: [https://askubuntu.com/questions/1017311/what-is-a-masked-service](https://askubuntu.com/questions/1017311/what-is-a-masked-service) # BLS12-381 `0x6b6e292c382945e80bf51af2ba7fe9f458dcff81ae6075c46f9095e1bbecdc37` [BLS12-381](https://github.com/gear-tech/gear/blob/master/pallets/gear-builtin/src/bls12_381.rs) is the first Built-in Actor integrated into the Vara runtime. It provides an interface that enables validators to perform native runtime computations of BLS cryptography methods. BLS cryptography facilitates efficient signature aggregation and verification at scale using Elliptic Curve cryptography. However, these operations are computationally intensive, and the Wasm VM used in Vara is not capable (without significant increase of minimal hardware requirements) of processing them quickly enough to fit within the single block time of Vara’s network (which is 3 seconds). The BLS12-381 Built-in Actor addresses this issue. A program on the blockchain can send a message to this actor's address with the necessary arguments for a BLS method call. The validator then executes this in native mode off-chain, and subsequently returns the result to the originating program that initiated the request. While technically possible, executing BLS methods on-chain is **extremely resource-intensive**: * **Block Space:** It would occupy **30+ blocks** on the Vara network. * **Complexity:** Requires manual optimization by dividing the algorithm into equivalent parts. * **Costs:** Results in significantly **higher gas consumption**. # Bridge `0xf2816ced0b15749595392d3a18b5a2363d6fefe5b3b6153739f218151b7acdbf` **The Bridge Built-in Actor** enables Gear programs to communicate with Ethereum by sending outbound messages via a standardized interface. It accepts messages of type `Request`, processes them internally, and queues them for relay to the Ethereum network. This actor provides programs with the capability to initiate cross-chain interactions securely and efficiently without requiring additional deployment logic. It is accessible at a fixed address within the Vara runtime. Interface [#interface] The actor exposes a unified request interface via messages. ```rust pub enum Request { SendEthMessage { destination: H160, // destination on Ethereum payload: Vec, } } ``` The expected response type on a successful message queuing is: ```rust pub enum Response { EthMessageQueued { nonce: U256, // Message nonce. hash: H256, // Message hash. } ``` Usage ```rust use gbuiltin_eth_bridge::{Request, Response}; const BRIDGE_ACTOR_ID: [u8; 32] = [ 0xf2, 0x81, 0x6c, 0xed, 0x0b, 0x15, 0x74, 0x95, 0x95, 0x39, 0x2d, 0x3a, 0x18, 0xb5, 0xa2, 0x36, 0x3d, 0x6f, 0xef, 0xe5, 0xb3, 0xb6, 0x15, 0x37, 0x39, 0xf2, 0x18, 0x15, 0x1b, 0x7a, 0xcd, 0xbf ]; let request = Request::SendEthMessage { destination, payload, }.encode(); let reply_bytes = msg::send_bytes_with_gas_for_reply( bridge_actor_id, request, GAS_TO_SEND_REQUEST, FEE_BRIDGE, GAS_FOR_REPLY_DEPOSIT, ) .await?; let reply = Response::decode(&mut &reply_bytes[..])?; match reply { Response::EthMessageQueued { nonce, hash } => { // Handle the response... } } ``` # Proxy `0x8263cd9fc648e101f1cd8585dc0b193445c3750a63bf64a39cdf58de14826299` The [Proxy Built-in Actor](https://github.com/gear-tech/gear/blob/master/pallets/gear-builtin/src/proxy.rs) serves as an intermediary that allows Gear programs to interact with the [Proxy pallet](https://paritytech.github.io/polkadot-sdk/master/pallet_proxy/index.html). It translates proxy-related requests into dispatchable calls, enabling programs to manage proxy relationships dynamically. This actor allows programs to define and manage delegation of account actions securely and efficiently. Proxy relationships can be created or revoked with a specified type, providing granular control over delegated actions. Currently, all proxies managed through this actor do not require an announcement or delay for delegate actions, simplifying the delegation process. The `ProxyType` enum defines the types of actions a proxy can perform, mirroring the proxy pallet's `ProxyType`. This provides flexibility for use cases such as governance participation, staking, and identity verification. ```rust pub enum ProxyType { Any, NonTransfer, Governance, Staking, IdentityJudgement, CancelProxy, } ``` Supported Proxy Operations [#supported-proxy-operations] The `Request` enum specifies the operations that the Proxy Built-in Actor can handle. Each variant represents a different proxy action a program can request. Below, each operation and its parameters are detailed. AddProxy [#addproxy] Adds a delegate as a proxy for the sender's account. The `proxy_type` defines the types of actions the delegate is authorized to perform. ```rust AddProxy { delegate: ActorId, proxy_type: ProxyType, } ``` RemoveProxy [#removeproxy] Removes a delegate from the sender's list of proxies for a specific `proxy_type`. ```rust RemoveProxy { delegate: ActorId, proxy_type: ProxyType, } ``` # Staking `0x77f65ef190e11bfecb8fc8970fd3749e94bed66a23ec2f7a3623e785d0816761` The [Staking Built-in Actor](https://github.com/gear-tech/gear/blob/master/gbuiltins/staking/src/lib.rs) serves as an intermediary that allows Gear programs to interact with the [Staking pallet](https://paritytech.github.io/polkadot-sdk/master/pallet_staking/index.html) by translating staking-related requests into dispatchable calls. The actor receives messages in the form of a `Request` and executes staking actions accordingly. This actor provides programs with the capability to perform staking operations like bonding, unbonding, nominating, and managing staking rewards. The `RewardAccount` enum is used to specify where staking rewards should be accumulated when bonding or managing payouts. It is a mirror of the staking pallet's `RewardDestination` type and offers flexibility for staking reward management. ```rust pub enum RewardAccount { /// Pay rewards to the sender's account, increasing the amount at stake. Staked, /// Pay rewards to the sender's account (typically derived from `program_id`) /// without increasing the staked amount. Program, /// Pay rewards to a specified custom account. Custom(ActorId), /// Choose not to receive any rewards. None, } ``` Supported Staking Operations [#supported-staking-operations] The `Request` enum defines the various staking operations that the Staking Built-in Actor can handle. Each variant represents a different staking action that a program can request. Below, each operation and its required parameters are detailed. Bond [#bond] Bonds up to the specified `value` from the sender to self as the controller, with the reward destination specified by `payee`. ```rust Bond { value: u128, payee: RewardAccount } ``` BondExtra [#bondextra] Adds up to the given `value` to the sender's already bonded amount. ```rust BondExtra { value: u128 } ``` Unbond [#unbond] Unbonds up to the specified `value` to allow withdrawal after the unbonding period has elapsed. ```rust Unbond { value: u128 } ``` WithdrawUnbonded [#withdrawunbonded] Withdraws unbonded chunks for which the unbonding period has elapsed, based on the `specified num_slashing_spans`. ```rust WithdrawUnbonded { num_slashing_spans: u32 } ``` Nominate [#nominate] Adds the sender as a nominator of the specified `targets`, or updates the existing targets. ```rust Nominate { targets: Vec } ``` Chill [#chill] Declares the sender's intention to temporarily stop nominating while still keeping funds bonded. No additional parameters. PayoutStakers [#payoutstakers] Requests a payout for stakers for the given `validator_stash` during the specified `era`. ```rust PayoutStakers { validator_stash: ActorId, era: u32 } ``` Rebond [#rebond] Re-bonds a portion of the sender's stash scheduled to be unlocked, up to the given `value`. ```rust Rebond { value: u128 } ``` SetPayee [#setpayee] Sets the reward destination to the specified `payee`. ```rust SetPayee { payee: RewardAccount } ``` Example: Staking Broker [#example-staking-broker] To help illustrate how to use the Staking Built-in Actor, we provide a basic implementation called the **Staking Broker**. This program acts as a simple example to demonstrate the concepts of bonding, unbonding, and nominating validators on behalf of users. To explore the code and learn more about the implementation details, you can visit the Staking Broker example on [GitHub](https://github.com/gear-tech/gear/tree/master/examples/staking-broker). The Staking Broker is intended for **demonstration purposes only**. While it demonstrates bonding and nominating, it **does not handle critical production aspects** like the unbonding period or complex state management. A real-world liquid staking implementation would require a significantly more intricate architecture. # Built-in Actors Overview [#overview] The Built-in Actors framework is an extensible system that provides a registry of built-in actors available in the Vara node runtime. Built-in Actors are programs (but not a Wasm programs) implemented as a runtime [pallet](https://github.com/gear-tech/gear/tree/master/pallets/gear-builtin) capable of handling messages from other actors (programs) and process them in applications built on top of blockchain logic (such as staking, governance, computationally intensive calculations and more). This framework allows for the integration of custom logic, including access to the node's operating system and native execution. Built-in Actors may be added, removed, updated (including address change) by the Gear Protocol's development team and appear on Vara during runtime upgrades. If a new runtime interface added with a Built-in Actor, upgrading the node client is essential to import blocks and maintain onchain functionality. **Built-in Actors** are part of the runtime and can only be added by the developers of the Gear Protocol. * **Restrictions:** They cannot be created by other programs or initiated by users. * **Access:** Despite their special status, they are accessible to everyone through the standard messaging system, just like any other program. Usage [#usage] Built-in actors each have a unique `BuiltinId`. These IDs can be used by other programs to send specifically formatted messages to perform actions related to the blockchain logic implemented in the `Runtime`. `BuiltinId` is like an index of BIA, and with special RPC call it can be converted into the `ProgramId` to get trusted and valid program id from on-chain. A set of unique `BuiltinActor`s is defined in the `pallet_gear_builtin_actor::Config` trait as an associated type — a tuple consisting of custom types, each of which must implement the `pallet_gear_builtin::BuiltinActor` trait (similar to how migrations or signed extras are defined in the Runtime). These can be pallets (if access to on-chain storage is required) or any other custom types. When the next message is dequeued, a runtime checks the destination. If it is BIA, the runtime calls the respective BIA with the messages. Otherwise, it searches the storage for the Wasm program by address and executes it. The functionality of routing to a specific built-in actor instead of stored programs is provided by the `gear_builtin` pallet. To achieve this, the actor is automatically registered with the `pallet_gear::Config::::BuiltinRouter` associated type that implements the `pallet_gear::builtin::BuiltinRouter` trait and provides the `lookup()` function. This function is used to derive the respective `BuiltinId` based on the message's destination address (`ProgramId`). # Asynchronous Programming Asynchronous interaction between Vara programs is similar to the usual asynchronous request in using `await` and implemented by sending a message. Program entry points [#program-entry-points] If a program's logic implies asynchronous messaging, its main executable functions must differ from those used in synchronous communications. async init() [#async-init] In case of an asynchronous call in the program initialization, the `async init()` must be used instead of `init()`. Also, it should be preceded by the [`gstd::async_init`](https://docs.rs/gstd/latest/gstd/attr.async_init.html) macro: ```rust #[gstd::async_init] async fn init() { gstd::debug!("Hello world!"); } ``` async main() [#async-main] The same for asynchronous messages, the `async main()` must be used instead of `handle()` and `handle_reply()`. Also, it should be preceded by the [`gstd::async_main`](https://docs.rs/gstd/latest/gstd/attr.async_main.html) macro: ```rust #[gstd::async_main] async fn main() { gstd::debug!("Hello world!"); } ``` While `async init` and `async main` can be used together, there are strict limitations: * You **cannot** define `init` or `handle_reply` functions if these macros are used. * Attempting to mix standard entry points with async macros will cause a compilation error. Cross-program message [#cross-program-message] To send a message to a Vara program, use the [`msg::send_for_reply()`](https://docs.rs/gstd/latest/gstd/msg/fn.send_for_reply.html) function. In this function: * `program` - the address of the program to send the message for; * `payload` - the message to the program; * `value` - the funds attached to the message (zero if no value is attached); * `reply_deposit` - used to provide gas for future reply handling (skipped if zero). To get an encoded reply from another actor use the [`msg::send_for_reply_as()`](https://docs.rs/gstd/latest/gstd/msg/fn.send_for_reply_as.html) function. ```rust pub fn send_for_reply_as( program: ActorId, payload: E, value: u128, reply_deposit: u64 ) -> Result> ``` Usage example: ```rust let reply: SomeEvent = msg::send_for_reply_as( receiver_id, SomeAction { command: 42, }, 0, 0, ) .expect("Unable to send message") .await .expect("Error in receiving reply"); ``` # Data Encoding/Decoding To optimize how data is sent and received over the network, Gear uses the [`parity-scale-codec`](https://docs.rs/parity-scale-codec) - a Rust implementation of the SCALE Codec. This codec is used by the Substrate nodes' internal runtime. SCALE is a lightweight format that enables the serialization and deserialization of data. Encoding (and decoding) data using SCALE makes it highly suitable for resource-constrained execution environments like blockchain runtimes and low-power/low-memory devices. To use the SCALE codec in a program, add it to `Cargo.toml`: ```toml [dependencies] // ... codec = { package = "parity-scale-codec", version = "3.6", default-features = false } ``` ```rust use codec::{Decode, Encode}; #[derive(Encode, Decode)] enum MyType { MyStruct { field: ... }, ... } ``` The `Encode` and `Decode` traits are required when using standard `gstd` methods: [`msg::load`](https://docs.rs/gstd/latest/gstd/msg/fn.load.html), [`msg::send`](https://docs.rs/gstd/latest/gstd/msg/fn.send.html), [`msg::reply`](https://docs.rs/gstd/latest/gstd/msg/fn.reply.html), and [`msg::send_for_reply`](https://docs.rs/gstd/latest/gstd/msg/fn.send_for_reply.html). **Note on Byte Methods:** When using byte-level operations like [`msg::load_bytes`](https://docs.rs/gstd/latest/gstd/msg/fn.load_bytes.html), [`msg::send_bytes`](https://docs.rs/gstd/latest/gstd/msg/fn.send_bytes.html) or [`msg::reply_bytes`](https://docs.rs/gstd/latest/gstd/msg/fn.reply_bytes.html), encoding/decoding is not required as you are working directly with raw data. Learn more about the SCALE Codec [here](https://github.com/paritytech/parity-scale-codec). scale-info [#scale-info] [`scale-info`](https://docs.rs/scale-info/) is a library to describe Rust types, intended for providing information about the structure of encodable SCALE types. The definitions provide third-party tools (e.g., a UI client) with information about how to decode types agnostic of language. The interface that uses `scale-info` for Gear programs is called **metadata**. It defines incoming and outgoing types for all necessary entry points and allows programs and the client part to understand each other. For a deeper dive into program configuration, see the [Metadata Overview](/docs/developing/build/gstd/metadata) guide. To use `scale-info` in a project: ```toml [dependencies] // ... scale-info = { version = "2.9", default-features = false, features = ["derive"] } ``` Learn more about `scale-info` [here](https://github.com/paritytech/scale-info). # Create Program The business logic of any decentralized application may necessitate enabling the program to create, initialize, and launch one or multiple other programs within the network. This capability becomes crucial when external parties, such as users, require access to their unique instance of a standard program. For example, consider a program that implements loan functionality. In this case, the program developer can create a loan factory program that will create instances of loan programs on demand and operate them. Firstly, to create a program, submit the program code to the network using an extrinsic [`gear.uploadCode`](https://docs.gear.rs/pallet_gear/pallet/struct.Pallet.html#method.upload_code) and obtain its code hash. Submitting the program code does not initialize the program. To submit your code to the network, you can use: * **Web UI:** The [Gear IDEA](https://idea.gear-tech.io/) application. * **Library:** The [@gear-js/api](https://github.com/gear-tech/gear-js/tree/main/apis/gear#readme) library for automated interactions. * **CLI:** The [Gear Program CLI (gcli)](https://github.com/gear-tech/gear/tree/master/gcli) for command-line fans. After the code has been submitted, it can be used to create a new program: ```rust use gstd::{prog::ProgramGenerator, CodeHash, msg}; #[no_mangle] extern "C" fn handle() { let submitted_code: CodeHash = hex_literal::hex!("abf3746e72a6e8740bd9e12b879fbdd59e052cb390f116454e9116c22021ae4a") .into(); // ProgramGenerator returns ProgramId let program_id = ProgramGenerator::create_program_with_gas(submitted_code, b"payload", 10_000_000_000, 0) .expect("Unable to create program"); msg::send(program_id, b"hello", 0).expect("Unable to send message"); } ``` More information about the `gstd::prog` module can be found at [https://docs.rs/gstd/latest/gstd/prog/index.html](https://docs.rs/gstd/latest/gstd/prog/index.html). # Delayed Messages Smart contracts on other blockchains typically rely on external, centralized resources to continue functioning. This means that the code of these contracts will not run and make changes to the blockchain's state until it is triggered by an off-chain transaction. The external transaction serves as a "poke" to activate the smart contract and initiate its logic. For instance, someone can start an auction by sending a message to the auction contract. When the auction time has passed, the contract will need to process the result of the auction. However, this will not happen until someone sends the appropriate message to the contract to trigger this action. Gear Protocol solves this issue by introducing delayed messaging functionality. Programs in Vara Network can execute themselves an **unlimited** number of blocks, as long as enough gas for execution is kept available. The [gas reservation](/docs/developing/build/gstd/gas-reservation) option ensures this. As a result, the need for including centralized components in dApps is eliminated, allowing them to function **totally on-chain**. The [`msg::send_delayed`](https://docs.rs/gstd/latest/gstd/msg/fn.send_delayed.html) function allows sending a message after a specified delay. The function takes the following parameters: * `program` - the program (or user) to which the message will be sent * `payload` - the payload of the message * `value` - the amount of tokens to be sent with the message * `delay` - the delay in blocks after which the message will be sent The delayed message will be executed after the specified `delay` measured in blocks. For example, on a network with a block production time of 3 seconds, a delay of 20 blocks is equal to 1 minute. Gear Protocol enables the message processing to be paid with reserved gas by using the [`msg::send_delayed_from_reservation`](https://docs.rs/gstd/latest/gstd/msg/fn.send_delayed_from_reservation.html) function, which takes a reservation ID as the first parameter. Considering the auction example, the auction can be started by sending a message to the auction program. After completing all the necessary logic, the auction program will send a delayed message to itself, which will settle the auction after the indicated time. # Gas Reservation Gas reservation is a powerful feature of the Gear Protocol that enables a new approach to programming and modern [use cases](/docs/about/features/message-automation). Briefly, a program can send a message using gas that was reserved beforehand instead of using gas from the currently processing message. One of the key advantages of this feature is the ability to send [messages delayed](/docs/developing/build/gstd/delayed-messages) in time automatically to any actor in the network, whether it is a user, another program, or even **itself**. In fact, a program can execute itself **an unlimited** number of blocks, provided that enough gas for execution is kept available. A program developer can provide a special function in the program's code that takes a defined amount of gas from the amount available to the program and reserves it. A reservation gets a unique identifier that can be used by the program to retrieve this reserved gas and use it later. To reserve the amount of gas for future use, use the [`ReservationId::reserve`](https://docs.rs/gstd/latest/gstd/trait.ReservationIdExt.html#tymethod.reserve) function: ```rust let reservation_id = ReservationId::reserve(RESERVATION_AMOUNT, TIME) .expect("Reservation across executions"); ``` The block count within which the reserve must be used also has to be specified. Gas reservation is not free; reserving gas for one block costs some gas. The `reserve` function returns a [`ReservationId`](https://docs.rs/gstd/latest/gstd/struct.ReservationId.html), which can be used for sending a message with that gas. To send a message using the reserved gas: ```rust msg::send_from_reservation(reservation_id, program, payload, value) .expect("Failed to send message from reservation"); ``` If gas is not needed within the time specified during the reservation, it can be unreserved and the gas will be returned to the user who made the reservation. ```rust id.unreserve().expect("Unreservation across executions"); ``` Programs can have different executions, change state, and evolve in various ways, but when necessary, a program can send a message using the reserved gas instead of its own gas. For example, consider a game that works entirely on-chain. The players are programs that compete with each other by implementing various playing strategies. Typically, in these types of games, there is a master program that starts the game and controls the move order between the players. To start the game, someone sends a message to the program. The gas attached to this message is spent on the players' programs, which in turn spend gas on their execution. Since the game can last many rounds, the attached gas may not be enough to complete the game. Instead of sending a message asking the program to continue the game, gas reservation can be used to make the play fully automatic. Using gas reservation, the program will be able to run the game without interruption. # Gear Library (Low-Level) The Gear Protocol’s library `gstd` provides all the necessary and sufficient functions and methods for developing programs. Importing familiar types via prelude [#importing-familiar-types-via-prelude] The `gstd` default prelude lists things that Rust automatically imports into every program. It re-imports default `std` modules and traits. `std` can be safely replaced with `gstd` in the Vara programs on Rust. See more details [here](https://docs.rs/gstd/latest/gstd/prelude/index.html). Message handling [#message-handling] The Gear Protocol allows users and programs to interact with other users and programs via messages. Messages can contain a `payload` that can be processed during message execution. Interaction with messages is possible thanks to the module `msg`: ```rust use gstd::msg; ``` Message processing is possible only inside the defined functions `init()`, `handle()`, `hadle_reply()`, and `state()`. They also define the context for processing such messages. * Get a payload of the message currently being processed and decode it: ```rust #![no_std] use gstd::{msg, prelude::*}; #[no_mangle] extern "C" fn handle() { let payload_string: String = msg::load().expect("Unable to decode `String`"); } ``` * Reply to the message with payload: ```rust #![no_std] use gstd::msg; #[no_mangle] extern "C" fn handle() { msg::reply("PONG", 0).expect("Unable to reply"); } ``` * Send message to user: ```rust #![no_std] use gstd::{msg, prelude::*}; #[no_mangle] extern "C" fn handle() { // ... let id = msg::source(); let message_string = "Hello there".to_string(); msg::send(id, message_string, 0).expect("Unable to send message"); } ``` More cases of using the `msg` module can be found in the [documentation](https://docs.rs/gstd/latest/gstd/msg/index.html). Execution info [#execution-info] A program can get some useful information about the current execution context by using the `exec` module: ```rust use gstd::exec; ``` * Send a reply after the block timestamp reaches the indicated date: ```rust #![no_std] use gstd::{exec, msg}; #[no_mangle] extern "C" fn handle() { // Timestamp is in milliseconds since the Unix epoch if exec::block_timestamp() >= 1672531200000 { msg::reply(b"Current block has been generated after January 01, 2023", 0) .expect("Unable to reply"); } } ``` * Get self value balance of a program: ```rust #![no_std] use gstd::exec; #[no_mangle] extern "C" fn handle() { // Get self value balance in program let my_balance = exec::value_available(); } ``` More info about program syscalls can be found [here](https://docs.rs/gstd/latest/gstd/exec/index.html). Logging inside the programs [#logging-inside-the-programs] Macro `gstd::debug` provides an ability to debug program during program execution: ```rust #![no_std] use gstd::{debug, msg, prelude::*}; #[no_mangle] extern "C" fn handle() { let payload_string: String = msg::load().expect("Unable to decode `String`"); debug!("Received message: {payload_string:?}"); } ``` The `debug!` macro is only available when the **debug** feature is enabled in your `Cargo.toml`. Add it to your `gstd` dependencies: ```toml [dependencies] gstd = { git = "https://github.com/gear-tech/gear.git", tag = "v1.1.1", features = ["debug"] } ``` # Mailbox When the program sends a message to the user (e.g., using the [`msg::send`](https://docs.rs/gstd/latest/gstd/msg/fn.send.html) function), this message is placed in the user's mailbox. Actually, the mailbox is a dedicated storage that keeps a message received from a program. The user can detect a message received by subscribing to the [`UserMessageSent`](https://docs.gear.rs/pallet_gear/pallet/enum.Event.html#variant.UserMessageSent) event. It's important to note that the reply (sent using the [`msg::reply`](https://docs.rs/gstd/latest/gstd/msg/fn.reply.html) function) doesn't go to the mailbox, it just generates an event. Messages in the Mainlbox are shown in the Gear Idea application: [https://idea.gear-tech.io/mailbox](https://idea.gear-tech.io/mailbox) Storing messages in the mailbox is **not free**. Each message is charged based on its duration in the mailbox. **Important:** Messages can only stay in the mailbox for a limited time. Ensure they are processed before they expire or consume all allocated gas. Let's explore possible user reactions to the mailbox's message. User sends a reply to the message [#user-sends-a-reply-to-the-message] WhenTthe program sends a message to the user and waits for their reply, user can respond using a [`send_reply`](https://docs.gear.rs/pallet_gear/pallet/struct.Pallet.html#method.send_reply) extrinsic. The value linked to the message gets transferred to the user's account, removing the message from the mailbox. Simultaneously, the new reply message gets added to the message queue. User claims value from a message in the mailbox [#user-claims-value-from-a-message-in-the-mailbox] If a message in the mailbox has an associated value, the user can claim it using a [`claim_value`](https://docs.gear.rs/pallet_gear/pallet/struct.Pallet.html#method.claim_value) extrinsic. Value is transferred to the user's account and the message is removed from the mailbox. User ignores a message in the mailbox [#user-ignores-a-message-in-the-mailbox] A message is charged for every block in the mailbox within its gas limit. If the message hasn't an explicit gas limit, gas is borrowed from the origin's limit (e.g. an actor that has initiated the execution). When the message's gas runs out, the message is removed from the mailbox, and the associated value transferred back to the message sender. # Metadata Metadata is a kind of interface map that helps to transform a set of bytes into an understandable structure and indicates the function this structure is intended for. Metadata determines how all incoming and outgoing data will be encoded/decoded. Metadata allows dApp’s parts - the program and the client side (JavaScript), to understand each other and exchange data. To describe metadata interface use `gmeta` crate: ```rust use gmeta::{InOut, Metadata, Out}; pub struct ProgramMetadata; // Be sure to describe all the types. // But if any of the endpoints is missing in your program, you can use (); // as indicated in the case of `type Signal`. impl Metadata for ProgramMetadata { type Init = InOut; type Handle = InOut; type Others = InOut>; type Reply = String; type Signal = (); type State = Out>; } ``` Metadata enables you to determine the expected data at the input/output for the program at each endpoint. Where: * `Init` - describes incoming/outgoing types for `init()` function. * `Handle` - describes incoming/outgoing types for `handle()` function. * `Others` - describes incoming/outgoing types for `main()` function in case of asynchronous interaction. * `Reply` - describes an incoming type of message performed using the `handle_reply` function. * `Signal` - describes only the outgoing type from the program while processing the system signal. * `State` - describes the types for the queried State Generate metadata [#generate-metadata] To generate metadata, the following `build.rs` file in the root of your project folder is required: ```rust // build.rs // Where ProgramMetadata is your metadata structure use meta_io::ProgramMetadata; fn main() { gear_wasm_builder::build_with_metadata::(); } ``` As a result of the program compilation, a `*.meta.txt` file will be generated. This metadata file can be used in the UI applications that will interact with this program. The file’s content looks like a hex string: ``` 0002000000010000000001000000000000000000010100000009021c00082c74656d706c6174655f696f2050696e67506f6e670001081050696e6700000010506f6e670001000004000002080008000004080c18000c10106773746418636f6d6d6f6e287072696d6974697665731c4163746f724964000004001001205b75383b2033325d000010000003200000001400140000050300180000050700 ``` # Reply deposit When a program or user sends a reply to a message, it should provide gas for the reply handling. The user replies using [`gear.sendReply`](https://docs.gear.rs/pallet_gear/pallet/struct.Pallet.html#method.send_reply) extrinsic. The program uses the [`msg::reply`](https://docs.rs/gstd/latest/gstd/msg/fn.reply.html) or [`msg::reply_with_gas`](https://docs.rs/gstd/latest/gstd/msg/fn.reply_with_gas.html) function. Sometimes, it is more convenient to provide gas for the reply handling in advance. For example, if the program sends a message to another program and waits for a reply, it can provide gas for the reply handling in advance. In this case, the program doesn't need to provide gas for the reply handling when it sends a reply. To provide gas for the reply handling in advance, the program should use the [`exec::reply_deposit`](https://docs.rs/gstd/latest/gstd/exec/fn.reply_deposit.html) function: ```rust let message_id = msg::send(msg::source(), b"Outgoing message", 0).expect("Failed to send message"); exec::reply_deposit(message_id, 1_000_000_000).expect("Error during reply deposit"); #[no_mangle] extern "C" fn handle_reply() { // The reply handling will be paid with the deposited gas } ``` The program can deposit gas when using the [`msg::send_for_reply`](https://docs.rs/gstd/latest/gstd/msg/fn.send_for_reply.html) function by setting the `reply_deposit` parameter. The `reply_deposit` parameter is the amount of gas that will be reserved for the reply handling. The `reply_deposit` parameter is optional. If the `reply_deposit` parameter is zero, the program should provide gas for the reply handling when it sends a reply. ```rust let message_id = msg::send_for_reply( msg::source(), b"Outgoing message", 0, 1_000_000_000, ).expect("Failed to send message"); #[no_mangle] extern "C" fn handle_reply() { // The reply handling will be paid with the deposited gas } ``` # State Functions Persistent data of the Vara program is stored in the same way as in a classic program and does not require initialization of the external storage. ```rust // Describe state structure #[derive(TypeInfo, Decode, Encode, Clone)] pub struct Wallet { pub id: ActorId, pub person: String, } // Declare and initialize the state static mut WALLETS: Vec = Vec::new(); ``` If you're programming in Rust or other object-oriented languages, you should be familiar with most types. However, the `ActorId` type is something new when developing programs via the Gear Protocol. [`ActorId`](https://docs.rs/gstd/latest/gstd/struct.ActorId.html) is a fundamental type in Gear Protocol. It represents a **32-byte array** and is used to define any unique identifier within the network (e.g., users, programs, or contracts). State functions [#state-functions] To display the program state information, the `state()` function is used. It allows you to instantly read the program status (for example, program balance). Reading state is a free function and does not require gas costs. Example of the returning all wallets defined above: ```rust #[no_mangle] extern "C" fn state() { msg::reply(unsafe { WALLETS.clone() }, 0).expect("Failed to share state"); } ``` Additionally, you can handle incoming payload and return only the necessary part of the state. For example, you can return only the selected wallet: ```rust #[no_mangle] extern "C" fn state() { let index: usize = msg::load().expect("Unable to decode `usize`"); let wallets = unsafe { WALLETS.clone() }; if index < wallets.len() => { msg::reply(wallets[index], 0).expect("Failed to share state"); } else { panic!("Wallet index out of bounds"); } } ``` Custom program to read the state [#custom-program-to-read-the-state] Additionally, you can create your own program to read the state. This wrapper will allow you to implement custom functions for the client side, not depending on the main program. This has a number of advantages, for example, you will always be able to read the state even if the program changes (as long as the incoming or outgoing types have not changed). Or you are creating a service based on an already existing program and you need some of your own functions to get your own chanks of data from the state. To do this, create an independent program and describe the necessary functions inside the `metawasm` trait. For example: ```rust // ... use gmeta::metawasm; #[metawasm] pub mod metafns { pub type State = Vec; pub fn all_wallets(state: State) -> Vec { state } pub fn first_wallet(state: State) -> Option { state.first().cloned() } pub fn last_wallet(state: State) -> Option { state.last().cloned() } } ``` Or more complex example: ```rust // ... use gmeta::metawasm; #[metawasm] pub mod metafns { pub type State = Vec; pub fn wallet_by_id(state: State, id: Id) -> Option { state.into_iter().find(|w| w.id == id) } pub fn wallet_by_person(state: State, person: String) -> Option { state.into_iter().find(|w| w.person == person) } } ``` To build `*.meta.wasm`, the following `build.rs` file in the root of your project is required: ```rust fn main() { gear_wasm_builder::build_metawasm(); } ``` [Learn more](/docs/developing/build/gstd/metadata) how metadata works. # System signals The Vara network ensures system and program's state consistency via introducing special handling mechanisms for potential issues and corner cases. Actors have three common entry points - `init`, `handle`, `handle_reply`. Another special system entry point introduced by the Gear Protocol is `handle_signal`. It allows the system to communicate with programs if it is necessary to notify (signal) that some event related to the program's messages has happened. Only the system (Vara node runtime) can send signal messages to a program. First of all, it can be useful to free up resources occupied by the program. A custom async logic in Vara implies storing `Futures` in a program's memory. The execution context of `Futures` can occupy some significant amount of memory in the case of many futures. When a program sends a message and waits for a reply to be woken, the reply can not be received. So there might be the case that if the initial message in the waitlist runs out of gas or the gas amount is not enough to properly finish the execution, the program’s state will be rolled back and `Future` will never be freed. In this case, `Futures` remain in memory pages forever. Other messages are not aware of `Futures` associated with other messages. Over time, `Futures` accumulate in the program's memory so eventually a large amount of Futures limits the max amount of space the program can use. In case a message has been removed from the waitlist due to gas constraints, the system sends a system message (signal) that is baked by an amount of [reserved gas](/docs/developing/build/gstd/gas-reservation), which informs the program that it’s message was removed from the waitlist. Based on this info, a program can clean up its used system resources (`Futures`). The `gstd` library provides a separate [`exec::system_reserve_gas`](https://docs.rs/gstd/latest/gstd/exec/fn.system_reserve_gas.html) function for reserving gas specifically for system signal messages. It cannot be used for sending other regular cross-actor messages: ```rust exec::system_reserve_gas(1_000_000_000).expect("Error during system gas reservation"); ``` Even if this function hasn't been called, the system will reserve gas for system messages automatically with the [default amount](https://docs.rs/gstd/latest/gstd/struct.Config.html#method.system_reserve) of `1_000_000_000`. If a signal message appears, it uses gas specifically reserved for such kinds of messages. If no gas has been reserved for system messages, they are just skipped and the program will not receive them. If gas has been reserved but no system messages occur during the current execution, then this gas returns back from where it was taken. The same relates to gas reserved for non-system messages - gas returns back after a defined number of blocks or by the program’s command. `handle_signal` has a default implementation if the program has `async init` or/and `async main` functions (see [Asynchronous Programming](/docs/developing/build/gstd/async-programing) for more details about async entry points). To define a custom signal handler, use the [`gstd::async_init`](https://docs.rs/gstd/latest/gstd/attr.async_init.html) or [`gstd::async_main`](https://docs.rs/gstd/latest/gstd/attr.async_main.html) macro with the specified `handle_signal` argument. For example: ```rust #[gstd::async_main(handle_signal = my_handle_signal)] async fn main() { // ... } fn my_handle_signal() { // ... } ``` Note that the custom signal handler derives its default behavior. This ensures that standard system signals are still processed correctly even when custom logic is implemented. Some useful functions that can be used in `handle_signal`: * [`msg::signal_from`](https://docs.rs/gstd/latest/gstd/msg/fn.signal_from.html) - returns an identifier of the message which caused the signal; * \[`msg::signal_code`]\([https://docs.rs/gstd/latest/gstd/msg/fn.signal\_code.html](https://docs.rs/gstd/latest/gstd/msg/fn.signal_code.html) - returns the reason code of the signal (see [`SignalCode`](https://docs.rs/gstd/latest/gstd/errors/enum.SignalCode.html) enum for more details). It can be useful for a developer when writing communication between programs. Developer can define `my_handle_signal` function and implement some logic there. For example, `Program A` sent a message to `Program B`. `Program A` is waiting for a reply from `Program B` but `Program B` runs out of gas. The current execution will be interrupted, but the system will send a signal to `Program A` and indicates the message identifier during which the execution was interrupted. So, `Program A` sends a message and saves the message identifier: ```rust exec::system_reserve_gas(2_000_000_000) .expect("Error during system gas reservation"); let result = msg::send_for_reply(address, payload, value, reply_deposit); let (msg_id, msg_future) = if let Ok(msg_future) = result { (msg_future.waiting_reply_to, msg_future) } else { // handle the error here }; // save the `msg_id` in program state unsafe { STATE.msg_id == msg::id() }; let reply = msg_future.await; ``` The execution fails in `Program B`, and `Program A` receives a signal: ```rust #[no_mangle] extern "C" fn my_handle_signal() { if unsafe { STATE.msg_id == msg::signal_from() } { // write logic here } } ``` However, it is important to understand that the execution of `my_handle_signal` should not be long and should not consume a lot of gas. It can be used for tracking failures during the transaction. The program can use the information about failures the next time it is executed. For programs written using the Gear Protocol's `gstd` library, such signals can be sent to programs automatically under the hood when applicable. If a developer implements a program using `gcore` or Gear's syscalls, then such signals should be considered in the program's code explicitly. # Programs and Services The Sails framework is based on the concept of **services** and **programs**. A program serves as a container for one or more services. This is reflected in the code as follows: * **Services**: A service is a Rust `struct` whose `impl` block is marked with the `#[service]` attribute. It is responsible for handling the business logic of the application. It exposes public methods as remote calls, with commands that modify state and queries that return data without making state changes. * **Programs**: A program is Rust `struct` whose `impl` block is marked with the `#[program]` attribute. It hosts one or more services and manages the application lifecycle. It defines how the program and its services are initialized and interact with external consumers through constructors and public methods. *Only one program is allowed per application.* Additionally, Sails provides a mechanism to emit **events** from your service while processing commands, as well as the option for customized **message routing**, which involves defining rules for directing incoming request messages to specific service methods based on service and method names. It also offers advanced features like the ability to extend (or **mix in**) existing services. ```mermaid %%{ init: { 'width': '100%' } }%% graph LR subgraph "Program" A[Service 1]:::varaStyle; B[Service 2]:::varaStyle; C["..."]:::inv; D[Service n]:::varaStyle; style Program color:#00ffa0, fill:none; classDef varaStyle stroke:#00ffc3; classDef inv color:#FFFFFF,stroke-width:0px,fill:none; end ``` Service [#service] The service's main responsibility is to implement specific aspects of the application's business logic. The set of its **public** methods defined in the `impl` block essentially represents a collection of remote calls that the service exposes to external consumers. In Sails, there are two types of calls available: * **Commands**: A method working over `&mut self` is treated as a command. It is a call that is *changing some state* of the program. * **Query**: A method working over a `&self` is treated as a query *keeping everything unchanged and only returning some data*. Both types of methods can accept some parameters passed by a client and can be synchronous or asynchronous. All the other service's methods and associated functions are treated as implementation details and ignored. The code generated behind the service by the `#[service]` attribute decodes an incoming request message and dispatches it to the appropriate method based on the method's name. On the method's completion, its result is encoded and returned as a response to a caller. ```rust use sails_rs::prelude::*; struct MyService; #[service] impl MyService { // Command — has &mut self, mutates state. #[export] pub fn do_something(&mut self, p1: u32, p2: String) -> Vec { // ... Vec::new() } // Query — has &self, returns data only. #[export] pub fn some_value(&self, p1: Option) -> String { // ... String::new() } } ``` The `#[export]` attribute is what makes a method part of the service's public ABI. Methods without `#[export]` are private helpers and are not exposed in the IDL or routed to from incoming messages. Program constructors that return `Result` must use `#[export(unwrap_result)]` so the macro can unwrap the success arm into the program state. Program [#program] The program's main responsibility is to host one or more services and expose them to external consumers. The structure of a program is somewhat simpler compared to that of services. As with services, there are two possible variants for its **public** methods: * **Application Constructors**: Methods returning `Self` are treated as application constructors. These functions can accept some parameters passed by a client and can be synchronous or asynchronous. One of them will be called once at the very beginning of the application lifetime, i.e. when the application is loaded onto the network. The returned program instance will live until the application stays on the network. * **Exposed Service Constructors**: Methods working over `&self` and having no other parameters are treated as *exposed service constructors* and are called each time when an incoming request message needs be dispatched to a selected service. All the other methods and associated functions are treated as implementation details and ignored. The code generated behind the program by the `#[program]` attribute receives an incoming request message from the network, decodes it and dispatches it to a matching service for actual processing. After that, the result is encoded and returned as a response to a caller. Only one program is allowed per application. ```rust pub struct MyProgram; #[program] impl MyProgram { /// Application constructor — called once at program upload. pub fn default() -> Self { Self } /// Fallible application constructor — `unwrap_result` makes the macro /// treat the inner `Self` as the success path and propagate `Err` /// as the failure path. #[export(unwrap_result)] pub fn new(p1: u32) -> Result { if p1 == 0 { return Err("p1 must be non-zero".to_string()); } Ok(Self) } // Exposed service constructor — called every time a message // is routed to this service. pub fn ping_svc(&self) -> MyPing { MyPing::default() } } ``` Add `#[program(payable)]` if the program needs to accept value-bearing messages. # Events Sails offers a mechanism to emit events from a service while processing commands. These events notify off-chain subscribers about state changes. Events are declared per service via the `events` argument to `#[service]`. The event payload is a Rust `enum` whose variants describe each event shape. Annotate the enum with `#[event]` (registers it as an event type) and `#[sails_type]` (wires the SCALE codec and scale-info traits with the correct re-export paths). When a service declares `events = SomeEnum`, `#[service]` generates an `emit_event` method on the service exposure that the command body calls to publish events. ```rust use sails_rs::{cell::RefCell, prelude::*}; pub struct CounterData { counter: u32, } impl CounterData { pub const fn new(counter: u32) -> Self { Self { counter } } } // `#[event]` registers the enum as an event payload type. // `#[sails_type]` wires up SCALE codec + type-info traits with the // correct re-export paths (no manual `#[codec(crate=...)]` needed). #[event] #[sails_type] #[derive(Clone, Debug, PartialEq)] pub enum MyCounterEvent { Incremented(u32), } pub struct MyCounter { data: RefCell, } #[service(events = MyCounterEvent)] impl MyCounter { pub fn new(initial: u32) -> Self { Self { data: RefCell::new(CounterData::new(initial)) } } #[export] pub fn increment(&mut self) -> u32 { let value = { let mut data = self.data.get_mut(); data.counter += 1; data.counter }; // `emit_event` is generated by `#[service(events = ...)]`. // The event is only published when the command completes // successfully (it rides on the same message-transmission // mechanism as any other Gear reply). self.emit_event(MyCounterEvent::Incremented(value)).unwrap(); value } } ``` Internally, events ride on the same Gear message-transmission machinery as any other reply. As a result, an event is only published when the command that emitted it completes successfully — a panic, trap, or error return cancels every event queued during the call. # IDL Specification Applications developed with the Sails framework include an auto-generated IDL file that contains detailed information about the application, including: * **Types**: Custom types used within the program. * **Constructors**: The program's constructor. * **Services**: Commands and queries for all the services exposed by the program. * **Events**: Events utilized within the program. IDL versions [#idl-versions] Sails 1.0.0 emits IDL v2. The grammar is a declarative PEG (Pest) and supports global directives (`!@sails: `, `!@include: `), local annotations (`@query`, `@indexed`, `@partial`, `@entry_id: N`), inline service composition via `extends { ... }`, and pinned service identifiers via the `@` suffix. For the full grammar, see the [IDL v2 specification](https://github.com/gear-tech/sails/blob/master/docs/idl-v2-spec.md). IDL v1 (used by Sails 0.x) is preserved for compatibility with already deployed programs and is still parsed by the legacy `sails-idl-parser` crate. Examples [#examples] Types [#types] This example demonstrates how the custom Rust type `IoTrafficLightState` is represented in the corresponding IDL file. ```rust #[sails_type] #[derive(Clone)] pub struct IoTrafficLightState { pub current_light: String, pub all_users: Vec<(ActorId, String)>, } ``` ```rust struct IoTrafficLightState { current_light: String, all_users: [(actor, String)], } ``` Constructors, Services and Events [#constructors-services-and-events] The example below illustrates how a Sails program is represented in the IDL v2 file. Note that read-only methods (`&self`) are annotated with `@query` in the IDL output, and event payloads live inside the service's `events { ... }` block. ```rust use sails_rs::prelude::*; #[event] #[sails_type] pub enum TokenEvent { Minted { to: ActorId, value: U256 }, Transferred { from: ActorId, to: ActorId, value: U256 }, } pub struct Token { /* ... */ } #[service(events = TokenEvent)] impl Token { #[export] pub fn mint(&mut self, to: ActorId, value: U256) { // ... self.emit_event(TokenEvent::Minted { to, value }).unwrap(); } #[export] pub fn name(&self) -> String { // ... String::new() } #[export] pub fn balance_of(&self, account: ActorId) -> U256 { // ... U256::zero() } } pub struct MyProgram; #[program] impl MyProgram { pub fn new(name: String) -> Self { // ... Self } pub fn token(&self) -> Token { // ... Token { /* ... */ } } } ``` ```rust !@sails: 1.0.0 service Token { events { Minted { to: actor, value: u256 }, Transferred { from: actor, to: actor, value: u256 }, } functions { Mint(to: actor, value: u256); @query Name() -> String; @query BalanceOf(account: actor) -> u256; } } program MyProgram { constructors { New(name: String); } services { Token: Token; } } ``` # Sails Library What is Sails? [#what-is-sails] **[Sails](https://github.com/gear-tech/sails)** is a library designed to elevate the experience of developing applications with Gear Protocol, placing a strong emphasis on simplicity and clarity. It removes the need to write low-level boilerplate code with `gstd`, allowing developers to focus on business challenges. Additionally, Sails automatically generates a corresponding Interface Definition Language (IDL) file for the application, which outlines data types, services, constructors, and events in a language-independent format. This IDL file can then be used to automatically generate client code in Rust and TypeScript, facilitating seamless interaction with the application. Development Workflow [#development-workflow] The typical development workflow with the Sails framework consists of the following steps: * **Implement**: The program logic is implemented in Rust using the Sails framework. * **Build**: The Rust program is compiled into an optimized WebAssembly (WASM) blob. The `sails_idl_gen` crate is then used to automatically generate a corresponding IDL file for interfacing with the program. Typically, all of these steps are executed automatically with a proper cargo configuration for Sails. * **Deploy**: The optimized WASM blob and corresponding IDL file are uploaded to Vara Network via the [Gear IDEA portal](https://idea.gear-tech.io/). * **Interact**: Interaction with the deployed dApp is carried out either directly through the Gear IDEA portal or by using a Rust/TypeScript client generated from the IDL file. ```mermaid graph LR A[Implement]:::varaStyle; B[Build]:::varaStyle; C[WASM blob]:::plain; D[IDL file]:::plain; E[Generate Client]:::varaStyle; F[Deploy to Vara Network]:::Vara; classDef Vara stroke:#00ffa0,stroke-width:3px; classDef varaStyle stroke:#00ffc3; classDef plain stroke:#FFFFFF; A-->B; B-->C; B-->D; C-->F; D-->F; D-->E; ``` This guide targets **Sails 1.0.0**. Install the CLI: ```bash cargo install sails-cli ``` Or install a prebuilt binary: ```bash cargo binstall sails-cli ``` Scaffold a new project: ```bash cargo sails new my-ping ``` # Migration [`Sails`](/docs/developing/build/sails) is a library for writing applications utilizing the Gear Protocol with simplicity and clarity. This powerful tool represents the next step in the evolution of the Gear Protocol. Initially, Gear applications typically employ the less convenient [`gstd`](/docs/developing/build/gstd) library for managing low-level operations and essential functionality. The `Sails` library builds programs on top of `gstd`, handling most low-level interactions under the hood. As projects grow, adopting the `Sails` framework allows developers to shift towards a more modular and service-oriented architecture. Existing dApps written with `gstd` can be effortlessly migrated to `Sails`. This guide focuses on the key steps involved in transitioning existing gstd-based code to the `Sails` framework, outlining the necessary code transformations and architectural adjustments while leveraging the underlying `gstd` infrastructure. Setting Up Your Sails Project [#setting-up-your-sails-project] To get started with the migration process, you can use a template to create a new project with Sails. * First, install the sails-cli tool using the following command: ```bash cargo install sails-cli ``` * After installation, you can generate a new project named "vara-app" by running: ```bash cargo sails new vara-app ``` Migrating Action Handling [#migrating-action-handling] * In **gstd**-based projects, actions are typically defined using enums, which are processed within a `handle` function. This function decodes incoming messages and matches them to the corresponding action: ```rust pub enum Action { DoA, DoB, } impl Metadata for ProgramMetadata { type Handle = In; } #[no_mangle] extern fn handle() { let action: Action = msg::load().expect("Failed to decode `Action` message."); match action { Action::DoA => { /* Implementation for action A */ }, Action::DoB => { /* Implementation for action B */ }, } } ``` * In **`Sails`**, this approach is restructured to leverage the framework’s service-oriented architecture. While the actions are still processed through a single entry point (`handle` function), logically they are dispatched to service methods based on string identifiers rather than enum types. Each action is linked to a specific service method, which improves modularity. ```rust #[service] impl Service { #[export] pub fn do_a(&mut self) { /* Implementation for action A */ } #[export] pub fn do_b(&mut self) { /* Implementation for action B */ } } ``` Unlike the `gstd` approach of matching enum variants, the **Sails** framework uses string-based service identifiers (e.g., `do_a` or `do_b`). This mapping to service methods allows for more flexible and dynamic action routing within your programs. Migrating Reply Messages [#migrating-reply-messages] * In the case of low-level use of **`gstd`**, sending a reply message is done manually after the action has been processed. The reply should be sent using the `msg::reply` function: ```rust #[no_mangle] extern fn handle() { let action: Action = msg::load().expect("Failed to decode `Action` message."); match action { Action::DoA => { // Implementation for action A msg::reply(reply, 0).expect("Failed to encode `reply`"); }, } } ``` * When using **`Sails`**, replies are returned directly from the service function, and the framework manages the encoding and sending of the message: ```rust #[service] impl Service { #[export] pub fn do_a(&mut self) -> TypeOfReply { // Implementation for action A reply } } ``` (The `#[export]` attribute marks the method as part of the service's public ABI — without it the method is treated as a private helper.) This reduces manual work, as the framework abstracts the reply handling, resulting in more concise code. Migrating State Queries [#migrating-state-queries] * State queries are often implemented through enums, which handle different state requests and responses manually within a `state` function: ```rust pub enum StateQuery { State1, State2, } pub enum StateReply { State1(...), State2(...), } impl Metadata for ProgramMetadata { type State = InOut; } #[no_mangle] extern fn state() { let query: StateQuery = msg::load().expect("Unable to load the state query"); match query { StateQuery::State1 => { msg::reply(StateReply::State1(state), 0).expect("Unable to share the state"); }, StateQuery::State2 => { msg::reply(StateReply::State2(state), 0).expect("Unable to share the state"); }, } } ``` * When migrating to **`Sails`**, state-related functions are methods that take `&self` as a parameter, since they don’t modify the state but only query it: ```rust #[service] impl Service { #[export] pub fn state_1(&self) -> TypeOfReply { // Implementation for State1 state } #[export] pub fn state_2(&self) -> TypeOfReply { // Implementation for State2 state } } ``` Conclusion [#conclusion] Migrating a project from the `gstd` library to the `Sails` framework is a straightforward process that primarily involves adapting the code to a more modular and service-oriented structure. Since `Sails` operates on top of `gstd`, the core functionality remains the same, but the framework provides a more organized and maintainable approach to handling actions, replies, and state queries. By following the steps outlined in this guide, you can easily refactor your project to take advantage of the abstractions and efficiency offered by `Sails`. This migration not only simplifies code management but also enhances the long-term scalability. With just a few adjustments to your existing codebase, such as reorganizing action handling into service methods and streamlining state queries, your project can seamlessly transition to `Sails` without sacrificing any of the functionality provided by `gstd`. # Service Extending (Mixins) A standout feature of Sails is its capability to extend (or mix in) existing services. This is facilitated through the use of the `extends` argument in the `#[service]` attribute. Consider you have `Service A` and `Service B`, possibly sourced from external crates, and you aim to integrate their functionalities into a new `Service C`. This integration would result in methods and events from `Services A` and `B` being seamlessly incorporated into `Service C`, as if they were originally part of it. In such a case, the methods available in `Service C` represent a combination of those from `Services A` and `B`. Should a method name conflict arise, where both `Services A` and `B` contain a method with the same name, the method from the service specified first in the extends argument takes precedence. This strategy not only facilitates the blending of functionalities but also permits the overriding of specific methods from the original services by defining a method with the same name in the new service. With event names, conflicts are not allowed. Unfortunately, the IDL generation process is the earliest when this can be reported as an error. For example: ```rust use sails_rs::prelude::*; pub struct MyServiceA; #[service] impl MyServiceA { #[export] pub fn do_a(&mut self) { // ... } } pub struct MyServiceB; #[service] impl MyServiceB { #[export] pub fn do_b(&mut self) { // ... } } pub struct MyServiceC; #[service(extends = [MyServiceA, MyServiceB])] impl MyServiceC { // New method on the merged service. #[export] pub fn do_c(&mut self) { // ... } // Overrides `do_a` from MyServiceA. Use `overrides = MyServiceA` // when the same method name appears in more than one extended // service and you need to disambiguate. #[export(overrides = MyServiceA)] pub fn do_a(&mut self) { // ... } // `do_b` from MyServiceB is exposed transparently via `extends`. } ``` When two extended services define a method with the same name, mark the overriding method with `#[export(overrides = SomeService)]` so the macro knows which inherited method is being replaced. The same attribute is used to override a method that exists in only one parent service when ambiguity is not at stake — it documents intent and keeps the IDL stable under future refactors. # Message Routing Message **routing** controls how an incoming request is dispatched to a specific service method. Routing rules are altered by the `route` argument of the `#[export]` attribute, applied to public methods on structs labelled with `#[program]` or `#[service]`. By default, every service exposed by a program is exposed under the service-constructor name converted to *PascalCase*; every service method is exposed under its method name converted to *PascalCase*. For example: ```rust #[program] impl MyProgram { // The `MyPing` service is exposed under the default route `PingSvc` // (the service-constructor name in PascalCase). pub fn ping_svc(&self) -> MyPing { MyPing::default() } } ``` The default name can be overridden via `#[export(route = "...")]`. The supplied string is normalised to PascalCase before it is used as the route name: ```rust #[program] impl MyProgram { // The `MyPing` service is exposed as `Ping`. // The given route is converted into PascalCase. #[export(route = "ping")] pub fn ping_svc(&self) -> MyPing { MyPing::default() } } ``` The same `route` argument works on service methods: ```rust #[service] impl MyPing { // `do_ping` is exposed as `Ping`. #[export(route = "ping")] pub fn do_ping(&mut self) { // ... } // `ping_count` is exposed using its default name `PingCount`. #[export] pub fn ping_count(&self) -> u64 { 0 } } ``` # Vara Smart Contract Standards Smart contract standards define a unified set of rules and interfaces that ensure consistency and interoperability across the Vara Network ecosystem. By following these standards, developers create tokens and applications that work seamlessly with wallets, exchanges, dApps, and other platforms without requiring custom integrations. Why Standards Matter [#why-standards-matter] Standards provide several key benefits: * **Interoperability** - Tokens built on standards are automatically compatible with any application that supports the standard * **User Experience** - Wallets can display tokens natively without additional integration work * **Development Speed** - Developers can build on proven, tested implementations rather than starting from scratch * **Ecosystem Growth** - Shared standards enable composability between different applications and services Who Can Use Vara Standards [#who-can-use-vara-standards] Vara standards are designed for developers building decentralized applications on the Vara Network. Whether you're creating: * [**Fungible tokens**](/docs/developing/build/standards/vft) for payments or rewards * [**NFT collections**](/docs/developing/build/standards/vnft) for digital art or gaming assets * [**Multi-token**](/docs/developing/build/standards/vmt) systems for complex applications These standards provide the foundation you need. How to Use Standards [#how-to-use-standards] Each Vara standard is implemented using the [Sails](/docs/developing/build/sails) framework and available as a base service on [GitHub](https://github.com/gear-foundation/standards). Rather than complete applications, these services serve as foundational building blocks that you can extend and customize with your own business logic, access controls, and additional features. Explore the available standards below to find the right foundation for your project. # Fungible Token The **Vara Fungible Token (VFT)** standard is the equivalent of Ethereum's **ERC-20** on the Vara Network. The Vara Fungible Token Standard provides a unified API for programs to implement token functionalities. It encompasses critical operations like token transfer and approvals for third-party spending on the blockchain. Below is a detailed look at the state of the program, its interface, and key methods to facilitate these operations. The project code is built using the [Sails](/docs/developing/build/sails) framework, which simplifies program development on Gear Protocol. Functions [#functions] ``` Approve(spender, value) Transfer(to, value) TransferFrom(from, to, value) Allowance(owner, spender) BalanceOf(owner) Decimals() Name() Symbol() TotalSupply() ``` Events [#events] ``` Approval(owner, spender, value); Transfer(from, to, value); ``` Key methods [#key-methods] Approve [#approve] ```rust pub fn approve(&mut self, spender: ActorId, value: U256) -> bool ``` This function allows a designated spender (`spender`) to withdraw up to an `value` of tokens from your account, multiple times up to the amount limit. Resets allowance to `value` with a subsequent call. Returns a boolean value indicating whether the operation succeeded. Upon successful execution, triggers the event: ```rust Approval { owner: ActorId, spender: ActorId, value: U256, } ``` Transfer [#transfer] ```rust pub fn transfer(&mut self, to: ActorId, value: U256) -> bool ``` Transfers the specified `value` of tokens to the account `to`. Returns a boolean value indicating whether the operation Upon successful execution, triggers the event: ```rust Transfer { from: ActorId, to: ActorId, value: U256, } ``` TransferFrom [#transferfrom] ```rust pub fn transfer_from(&mut self, from: ActorId, to: ActorId, value: U256) -> bool ``` Transfers a specified `value` of tokens `from` one account `to` another, using the allowance mechanism. Value is then deducted from the caller’s allowance. Returns a boolean value indicating whether the operation succeeded. Upon successful execution, triggers the event: ```rust Transfer { from: ActorId, to: ActorId, value: U256, } ``` Query methods [#query-methods] name [#name] Returns the name of the token. ```rust pub fn name(&self) -> String ``` symbol [#symbol] Returns the symbol of the token. ```rust pub fn symbol(&self) -> String ``` decimals [#decimals] Returns the decimals of the token. ```rust pub fn decimals(&self) -> u8 ``` total_supply [#total_supply] Returns the total supply of the token. ```rust pub fn total_supply(&self) -> U256 ``` balance_of [#balance_of] Returns the token balance of the `owner` address. ```rust pub fn balance_of(&self, account: ActorId) -> U256 ``` allowance [#allowance] Returns the number of tokens the `spender` account is authorized to spend on behalf of the `owner`. ```rust pub fn allowance(&self, owner: ActorId, spender: ActorId) -> U256 ``` Conclusion [#conclusion] The provided service is a foundational base for creating your own token, rather than a complete application. It can be extended and inherited to add additional functionality, allowing developers to build upon this framework. The code for this service is available on [GitHub](https://github.com/gear-foundation/standards/tree/master/vft-service). By adhering to this standard, programs ensure interoperability and a consistent user experience across platforms and applications within the blockchain ecosystem. dApp developers can also ensure that their in-app tokens, built on this standard, will be natively displayed in user wallets without additional integration efforts, as long as wallet applications support this standard. # Multi-Token The **Vara Multi-Token (VMT)** standard is the equivalent of Ethereum's **ERC-1155** on the Vara Network. The VMT Standard establishes a unified API for multi-token functionality in applications. It details the core VMT service, which includes operations such as batch token transfers and balance queries, along with the contract's state, interface, and key methods. The source code for the core service can be found on [GitHub](https://github.com/gear-foundation/standards/tree/master/vmt-service). The project code is built using the [Sails](/docs/developing/build/sails) framework, which simplifies program development on Gear Protocol. Core VMT Service [#core-vmt-service] Functions [#functions] * `Approve(to)` * `TransferFrom(from, to, id, amount)` * `BatchTransferFrom(from, to, ids, amounts)` * `BalanceOf(account, id)` * `BalanceOfBatch(accounts, ids)` * `IsApproved(account, operator)` * `Name()` * `Symbol()` * `Decimals()` * `TotalSupply()` Events [#events] * `Approval(from, to)` * `Transfer(from, to, ids, amounts)` Key Methods [#key-methods] Approve [#approve] ```rust pub fn approve(&mut self, to: ActorId) -> bool ``` Allows one account to authorize another to manage its tokens. Upon success, it triggers the `Approval` event. TransferFrom [#transferfrom] ```rust pub fn transfer_from(&mut self, from: ActorId, to: ActorId, id: TokenId, amount: U256) ``` Transfers a specified amount of a token from one account to another. The caller must be authorized to manage the tokens. It triggers the `Transfer` event upon success. BatchTransferFrom [#batchtransferfrom] ```rust pub fn batch_transfer_from( &mut self, from: ActorId, to: ActorId, ids: Vec, amounts: Vec, ) ``` Transfers multiple token types from one account to another, triggering a `Transfer` event for each. Query Methods [#query-methods] BalanceOf [#balanceof] Returns the balance of a specific token for a given account. ```rust pub fn balance_of(&self, account: ActorId, id: TokenId) -> U256 ``` BalanceOfBatch [#balanceofbatch] Returns the balances for specified accounts and token IDs. ```rust pub fn balance_of_batch(&self, accounts: Vec, id: Vec) -> Vec ``` IsApproved [#isapproved] Checks if an operator is approved to manage an account's tokens. ```rust pub fn is_approved(&self, account: ActorId, operator: ActorId) -> bool ``` Conclusion [#conclusion] The core VMT service forms the foundation for multi-token contracts and can be extended for more advanced functionalities like minting and burning. The complete code is available on [GitHub](https://github.com/gear-foundation/standards/tree/master/vmt-service). # Non-Fungible Token The **Vara Non-Fungible Token (VNFT)** standard is the equivalent of Ethereum's **ERC-721** on the Vara Network. The Vara Non-Fungible Token (VNFT) Standard provides a unified API for programs to implement non-fungible token (NFT) functionalities. It encompasses critical operations like token transfer and approvals for third-party spending on the blockchain. Below is a detailed look at the state of the program, its interface, and key methods to facilitate these operations. The project code is built using the [Sails](/docs/developing/build/sails) framework, which simplifies program development on Gear Protocol. Functions [#functions] ``` Approve(approved, token_id) Transfer(to, token_id) TransferFrom(from, to, token_id) BalanceOf(owner) OwnerOf(token_id) GetApproved(token_id) Name() Symbol() ``` Events [#events] ``` Approval(owner, approved, token_id); Transfer(from, to, token_id); ``` Key methods [#key-methods] Approve [#approve] ```rust pub fn approve(&mut self, approved: ActorId, token_id: TokenId) ``` This function allows a designated address (`approved`) to transfer a specific token (`token_id`) on behalf of the token owner. It authorizes the `approved` address to handle the given token once. Upon successful execution, it triggers the `Approval` event. Upon successful execution, triggers the event: ```rust Approval { owner: ActorId, approved: ActorId, token_id: TokenId, } ``` Transfer [#transfer] ```rust pub fn transfer(&mut self, to: ActorId, token_id: TokenId) ``` Transfers the specified `token_id` to the designated account `to`. This method checks the caller’s ownership before executing the transfer. Upon successful execution, triggers the event: ```rust Transfer { from: ActorId, to: ActorId, token_id: TokenId, } ``` TransferFrom [#transferfrom] ```rust pub fn transfer_from(&mut self, from: ActorId, to: ActorId, token_id: TokenId) ``` Transfers a specified `token_id` from one account (`from`) to another (`to`), using the approval mechanism. This function ensures that the caller is authorized to make the transfer either as the owner or as an approved address. Upon successful execution, triggers the event: ```rust Transfer { from: ActorId, to: ActorId, token_id: TokenId, } ``` Query methods [#query-methods] name [#name] Returns the name of the token collection. ```rust pub fn name(&self) -> &'static str ``` symbol [#symbol] Returns the symbol of the token collection. ```rust pub fn symbol(&self) -> &'static str ``` balance_of [#balance_of] Returns the number of tokens owned by a given `owner` address. ```rust pub fn balance_of(&self, owner: ActorId) -> U256 ``` owner_of [#owner_of] Returns the owner of the specified `token_id`. ```rust pub fn owner_of(&self, token_id: TokenId) -> ActorId ``` get_approved [#get_approved] Returns the address approved for a specific `token_id`. ```rust pub fn get_approved(&self, token_id: TokenId) -> ActorId ``` Conclusion [#conclusion] The provided service is a foundational base for creating your own token, rather than a complete application. It can be extended and inherited to add additional functionality, allowing developers to build upon this framework. The code for this service is available on [GitHub](https://github.com/gear-foundation/standards/tree/master/vnft-service). By adhering to this standard, programs ensure interoperability and a consistent user experience across platforms and applications within the blockchain ecosystem. dApp developers can also ensure that their in-app tokens, built on this standard, will be natively displayed in user wallets without additional integration efforts, as long as wallet applications support this standard. # Built-in Actors Overview [#overview] [Built-In Actors](/docs/developing/build/builtinactors) are specialized entities within the Gear runtime that execute specific business logic when programs communicate with them via messages. This feature addresses a critical limitation: while users can directly interact with pallets through extrinsics like `bond`, `nominate`, `vote`, etc., programs within the Gear Protocol can only send messages, not extrinsics. Purpose and Operation of BIAs [#purpose-and-operation-of-bias] Generally, BIAs are essential for enabling programs on the blockchain (i.e., smart contracts) to access and utilize the functionalities of pallets. Managed by the protocol, BIAs share the same interface similar to other programs, primarily using messaging. However, unlike programs loaded onto the network, Built-In Actors are introduced and updated through runtime upgrades, maintaining synchronization with the network's evolution. Applications of Built-In Actors [#applications-of-built-in-actors] Accessing Pallet Functions [#accessing-pallet-functions] BIAs allow programs to interact with existing Substrate pallets and others (like staking or proxy pallets). For example, a program can send a BIA a message to `bond` a certain amount or `nominate` a validator, and the BIA will execute these on behalf of the program. **Examples:** * DAO members might pool funds in a program that uses a BIA to stake these funds. The staking rewards are then distributed weekly among the participants. * In a play-to-earn game, a user could set up a program to automatically stake earned assets or transfer rewards to an external address or within the game's balance. * For users with zero balance, a program could use a special BIA to issue vouchers from the program’s funds without needing external services. This bypasses the limitation where such actions could only be funded by the user’s own resources. * A special BIA could grant programs access to a proxy pallet, allowing various programs to perform limited authorized operations on behalf of the proxied program. This provides dApp developers with a powerful mechanism for automating scenarios and implementing custom logic in programs. For instance, BIAs provides acceess to the `staking` pallet for programs, facilitating the creation of automated staking. Intensive Computations [#intensive-computations] BIAs offload intensive computational tasks from the programs, efficiently handling them at the node level to minimize gas costs and execution time. This is crucial for operations that exceed the gas limit of a single block or require extensive computational resources, such as cryptographic verifications or neural network tasks. Some examples where BIAs can be theoretically used: * For instance, cryptographic computations like BLS signature verification. BIAs designed for such computations perform them efficiently and cost-effectively at the node level. * Simple operations like SHA-256 hashing are executed much faster through a BIA than in an on-chain program. * BIAs can also manage neural network operations, image generation, and pattern recognition from program inputs. BIA as Cross-Chain Bridge [#bia-as-cross-chain-bridge] A BIA pallet can implement a cross-chain bridge logic that receives messages from programs, queues them, and sends them to another network based on specific logic. After execution on the other side, the program receives a response indicating the success or failure of the operation. This eliminates the need for custom intermediary nodes or teleport programs. The BIA can also be used to accelerate primitives for verifying ZK proofs through ECC (Elliptic Curve Cryptography), performing these calculations efficiently at the node level because they are too intensive for on-chain execution within gas limits. Conclusion [#conclusion] BIAs elevate the capabilities of programs (smart contracts) to the user level. Features typically implemented through pallets and accessible only through extrinsics are now available to programs. This includes using balances for governance voting, issuing vouchers, allowing other actors to perform operations on behalf of the program (proxying), conducting complex computations, and much more. This expansion significantly enhances the functionality and potential applications of blockchain programs. # Gasless and Signless Gear Protocol's introduction of gasless transactions, along with signless user interactions, presents a groundbreaking concept in the Web3 sphere. This innovation is revolutionizing communication within Vara network and boosting the adoption of decentralized applications. By making their usability comparable to Web2 services and applications, Gear Protocol significantly enhances user experience in the decentralized space. This innovative feature empowers actors to issue [vouchers](/docs/developing/api/vouchers) that enable specific users to send messages to designated programs without incurring gas fees. This paradigm-shifting approach fosters a sponsorship-like environment for users, transforming the way interactions take place in the Web3 ecosystem. An example of using vouchers is shown in the [Battleship](/docs/vara-network/examples/Gaming/Battleship) game. Users without tokens in their balance can interact with a program by sending messages using a voucher, freeing them from the need to sign every transaction when interacting with the blockchain. Understanding Gasless Transactions [#understanding-gasless-transactions] At the core of the Gasless Transactions feature lies the concept of vouchers. These vouchers are crafted to grant users the ability to send messages to specific programs within the network, all without the burden of gas fees. Each voucher comes with a predetermined allocation of gas, empowering users to initiate interactions without worrying about transaction costs. How Gasless Transactions Work [#how-gasless-transactions-work] 1. **Voucher Issuance**: Actors within the Vara Network can issue vouchers, creating a sponsorship mechanism for users. These vouchers are tied to specific programs and include a designated gas allocation. 2. **Message Sending**: Holders of valid vouchers can use them to send messages to the designated program. These messages can include instructions or data, enriching interactions within the network. 3. **Gas-Free Interactions**: Users who has valid vouchers can enjoy gas-free interactions when sending messages. The allocated gas from the voucher covers the associated transaction costs. Learn how to create and use vouchers in [this article](/docs/developing/api/vouchers). Signless dApp Interactions [#signless-dapp-interactions] The signless transaction introduces another feature: anyone can interact with the dApp without needing to sign in. The dApp transaction operates in a manner similar to the steps outlined above, with one difference — a voucher is issued not directly to a user, but to a temporarily created account (sub-account) to which the user grants temporary rights to sign transactions on their behalf in this application. Vara's signless transactions further enhance the user experience. These transactions allow users to interact with dApps for a certain period without the need to individually sign each transaction. By eliminating the repetitive signing process, this approach streamlines interactions and significantly improves the overall UX efficiency. Benefits of Gasless and Signless Transactions [#benefits-of-gasless-and-signless-transactions] * Improved User Experience: The signless and gasless features simplify the interaction process, making it more accessible and user-friendly, akin to Web2 experiences. This attracts new users who might be daunted by the complexities of token management. * Enhanced User Privacy: Users are not required to disclose their token balances when interacting with dApps, thus protecting their financial privacy. Individuals can contribute health data to research studies without revealing their identities. Additionally, signless dApps could facilitate anonymous voting systems, enhancing privacy and security for voters in elections. # Vara's Key Features for dApp Development Vara Network offers a suite of advanced functionalities tailored to simplify and enhance the development of decentralized applications (dApps). These features focus on automating operations, minimizing user friction, and broadening the scope of what dApps can achieve. Automation via Delayed Messaging [#automation-via-delayed-messaging] Vara supports the automation of internal processes via delayed messages. This allows programs to schedule future actions autonomously, such as state updates or periodic tasks, without needing external inputs. This feature is crucial for developers aiming to build self-sustaining applications that require minimal maintenance and can react to changes in state or external conditions on a pre-defined schedule. Gasless Transactions [#gasless-transactions] To tackle the issue of user participation costs, Vara enables transactions without gas fees through the issuance of vouchers. This feature allows users with zero balance to interact with the blockchain, potentially widening the user base for dApps and removing the initial financial barrier to entry. Developers can leverage this to attract a more diverse group of users who might not want to invest in cryptocurrency initially. Signless Transactions [#signless-transactions] Vara enhances user experience by enabling signless transactions. By utilizing temporary sub-accounts with limited signing permissions, this feature reduces the need for user intervention in every transaction, streamlining processes that involve frequent interactions with the blockchain. This is particularly useful for applications that require a seamless user experience, such as gaming or social media platforms on the blockchain. Built-In Actors (BIAs) [#built-in-actors-bias] The inclusion of Built-In Actors (BIAs) allows dApp developers to tap into blockchain functionalities that are typically out of reach for programs. BIAs act as intermediaries that can execute specific operations like staking, proxy management, or cross-chain communications on behalf of the program. This extends the capabilities of dApps beyond simple transactions and interactions, enabling more complex and powerful applications. # Messaging automation Truly decentralized [#truly-decentralized] One of the well-known drawbacks of other platforms' smart contracts is that they cannot trigger their own functions. Instead, to run certain functions they require an external component or service to trigger on-chain transactions. While classic smart contract logic may rely on users to initiate transactions and awaken the contract, many cases require a trigger when certain conditions are met, such as reaching a specific point in time or the occurrence of a particular event. In the past, this has either limited the capabilities of smart contracts or required developers to introduce a centralized service to trigger smart contracts. Now thanks to Gear Protocol's support for asynchronous messaging, program developers can implement arbitrary logic with [delayed messages](/docs/developing/build/gstd/delayed-messages) that can wake the program after a specified period of time or in response to certain events. This enables a much more extensive range of use cases for programs, unlocks new decentralized functionality, and unlocks more value for users in the blockchain ecosystem. Continuing messaging automation [#continuing-messaging-automation] The execution of any messages in Vara, including the [system messages](/docs/developing/build/gstd/system-signals), consumes "gas". The Gear Protocol introduces the concept of [gas reservation](/docs/developing/build/gstd/gas-reservation), which allows for the creation of gas pools that can be used by programs for further execution. Each pool is unique to the program that creates it, and the gas from the pool can be consumed by the program if its "gas\_available" is not sufficient. One of the key benefits of the gas reservation is the ability to send **delayed messages** that can be triggered automatically at a specific time in the future. These messages, like any other message in Vara, can invoke another program in the network or appear in the user's mailbox. Perhaps most interestingly, gas reservation allows a program to send a message to itself at a later time, allowing it to continue execution after a defined period. This effectively enables a program to execute itself an **unlimited** number of times (provided that enough gas is available for the execution). This opens up a wide range of possibilities for the implementation of functional logic related to **continuing messaging automation** in programs. Delayed messages are similar to cron jobs, which cannot be implemented in programs on other blockchain platforms without the use of external resources. The remarkable advantage of this solution is that it eliminates the need for centralized components in dApps, ensuring they function **completely on-chain** and are fully decentralized and autonomous. Applications that use delayed messages in their business logic and their source code are shown in the [Program Examples](/docs/vara-network/examples) section. These include applications such as: [Game Battle](/docs/vara-network/examples/Gaming/battle), [VaraTube](/docs/vara-network/examples/Infra/varatube), [Dynamic NFT](/docs/vara-network/examples/NFTs/dynamic-nft). Use cases [#use-cases] For instance, let's consider some use case examples that become achievable: NFTs [#nfts] Non-Fungible Tokens (NFTs) are unique digital assets that can be owned and traded on blockchain networks. One of the key features of NFTs is that they can be dynamic, meaning that their properties can be changed based on certain conditions. Dynamic NFTs can be updated immediately by their owner or gradually using delayed messages. This can be useful in a variety of situations, such as updating an NFT based on changes in its price, as is done in the Curse NFT using Oracles, or in gaming applications where the properties of an NFT might change over time. There are scenarios in which NFTs are updated: * The user can send a message to update the NFT immediately * The NFT program can send a message to itself at regular intervals to update the token's properties * The NFT program can send a delayed message to another actor (such as a program or account) and change the NFT's properties based on the result of processing that message. Overall, the ability to update NFTs dynamically opens up a wide range of possibilities for their use in a variety of applications. Gaming [#gaming] Everyone knows that the most successful games are those that are exciting, that you play with pleasure. The success of such games depends on the right game mechanics established by the developers. The Gear Protocol offers a tool for developing such games and running them on [Vara](https://vara.network/). Features such as [delayed messages](/docs/developing/build/gstd/delayed-messages), [gasless and signless transactions](/docs/developing/api/vouchers), and [gas reservation](/docs/developing/build/gstd/gas-reservation) become essential tools for developers aiming to create successful games on the Vara Network. Here are a few of the many examples where such functionality would be useful: Tamagotchi is a classic digital pet game where players must care for a virtual creature by providing it food, attention, and other forms of care. As a dynamic NFT, a Tamagotchi can change its appearance based on its properties (such as hunger, fatigue, or happiness) and can notify the user when it needs to be fed or played with. The user can feed the NFT with gas, which is used to send delayed messages that are required to update the Tamagotchi's state. "Game strategies battle" is a game in which several programs compete with each other using different algorithms or strategies. The game can be based on a variety of classic games, such as checkers, tic-tac-toe, races, or Monopoly. Each participant creates a program with their own game strategy and uploads it to the blockchain. The programs then play against each other until someone wins or the gas runs out. If the gas runs out, one of the participants can continue the game by sending a message with more gas. This allows the game to continue indefinitely, with the most effective strategy ultimately emerging as the winner. DeFi [#defi] Decentralized finance (DeFi) applications can improve the user experience by implementing delayed messages. For example, when users deposit tokens into a liquidity pool on an automated market maker (AMM) or participate in staking to earn rewards, they often have to manually claim their earnings (known as "harvesting yield"). With the Vara, users can enjoy a set-and-forget DeFi experience where their earnings are automatically deposited into their accounts without any manual intervention. Rewards are regularly harvested, swapped for the original vault asset, and deposited again for compound farming, allowing users to earn even more without having to take any additional action. In general, the use of delayed messages in DeFi can greatly improve the user experience by making it easier for users to earn rewards and take advantage of the benefits of DeFi without constantly having to monitor and manage their assets. This can help drive broader adoption of DeFi and unlock new opportunities for growth in the industry. # Rent-Based System Messaging Concept [#messaging-concept] Understanding the message communication model and its benefits is of utmost importance for the safety and success of developers working with Vara Network. This innovative model is built upon the principles of the [Actor Model](/docs/vara-network/about/technology/actor-model), where all participants, including programs and users, are considered "actors." These actors interact seamlessly by exchanging messages. Rent-Based system [#rent-based-system] {/* ### Program rent Vara Network introduces an innovative program life cycle management system, prioritizing sustainable and economical resource usage. Within this system, programs on the Vara Network can rent network resources for their memory storage. This rental concept is similar to a time-limited subscription that allows the program to remain active and operational within the network. Upon program upload, each program is automatically assigned a predefined expiration date for free, typically set to last for 5,000,000 blocks, where each block lasts approximately 3 seconds (~173 days). To extend the program's functionality beyond this period, users can increase this expiration parameter for the program. However, to ensure the program's viability throughout the chosen lifetime extension, the user must possess enough VARA tokens in their balances. To maintain the program's functionality after the current rent period expires, the user should proactively pay more VARA tokens for the program. Any user on the network can contribute additional VARA to any program as needed. After the rent period's expiration, the program automatically transitions to a 'paused' state, leading to the removal of its memory pages from the storage. However, important data such as the block number when the program was paused and its memory hash remain accessible on the network. Consequently, the paused program can be resumed with its state restored to the point at which it was paused, provided an archive node is connected to the network. */} Message rent [#message-rent] {/*Additionally,*/} Vara enables rent for messages stored in the Waitlist. In the Gear Protocol **Actor Model**, actors communicate via messages. The **Waitlist** is a specific entity—a database where these messages (not transactions) are stored while awaiting processing or fulfilling specific conditions. A message enters payable storage with locked funds for a maximum duration in `N` blocks. In `M` blocks (where `M AI Code Generator is available for public use and experimentation. Users can access the tool directly via the following link: 👉 [AI Code Generator](https://ai-codegen.vara.network/) Use Cases [#use-cases] * **Rapid Experimentation**: AI Code Generator is ideal for quick ideation and proof-of-concept development. * **Educational Tool**: For newcomers to smart contract development and the Gear Protocol, AI Code Generator offers an accessible and low-friction way to learn. * **Prototyping Full-Stack dApps**: The tool supports not only smart contract generation, but also creates companion frontend UIs and backend logic. * **Pre-deployment Contract Review**: Integrated auditing support helps highlight common issues and suggests improvements. # Vara React Application Template Accelerate Decentralized App Development [#accelerate-decentralized-app-development] For those looking to swiftly launch decentralized applications (dApps) on the Vara network, the Vara React Application Template (aka `create-vara-app`) offers a pre-configured solution designed to streamline the development process. With its well-thought-out infrastructure and convenient features, this template allows for quick creation and deployment of dApps on the Vara network. The Vara React Application Template is available in two themed variations: * **light theme** with Vara logo, [GitHub](https://github.com/gear-foundation/dapps/tree/master/frontend/templates/create-vara-app) * **dark theme** with Gear logo, [GitHub](https://github.com/gear-foundation/dapps/tree/master/frontend/templates/create-gear-app) | Home Page | Wallet Connection | | --------------------------------------------------------------------- | ----------------------------------------------------------------------------- | | Vara Template Home Page | Vara Template Wallet Connection | | Gear Template Home Page | Gear Template Wallet Connection | This template is packed with benefits and features that make it an ideal choice for developers seeking efficiency and simplicity in their dApp development workflow. Features [#features] Key features of the Vara React Application Template include: 1. **Vara API Connections**: Integrating with the Vara blockchain is made seamless with pre-configured API connections, eliminating the need for manual setup. This enables easy interaction with the blockchain and access to essential functionalities. 2. **Login Interface**: The template includes a pre-built login interface, simplifying the implementation of user authentication and secure management of user access. This allows developers to focus on building core features without worrying about the authentication process. 3. **Vara React-Hooks Support**: Designed to leverage Gear's react-hooks library, the template provides a smooth and intuitive development experience. It includes all necessary environments and configurations for seamless integration of react-hooks into the dApp. 4. **Branded UI-Kit**: The template offers a branded UI-kit to ensure a visually appealing and consistent user interface. Aligned with Gear's branding guidelines, the UI-kit helps create an attractive and professional-looking dApp, allowing developers to concentrate on unique aspects while maintaining a polished design. The Vara React Application Template is an invaluable resource for developers in the blockchain space, whether experienced or just starting out. It significantly speeds up the dApp development process, simplifies integration with the Vara network, and provides essential features like a robust login interface and branded UI-kit. Installation [#installation] * Create new project based on **light** themed template (Vara): npm pnpm yarn bun ```bash npx degit gear-foundation/dapps/frontend/templates/create-vara-app dApp cd dApp ``` ```bash pnpm dlx degit gear-foundation/dapps/frontend/templates/create-vara-app dApp cd dApp ``` ```bash yarn dlx degit gear-foundation/dapps/frontend/templates/create-vara-app dApp cd dApp ``` ```bash bun x degit gear-foundation/dapps/frontend/templates/create-vara-app dApp cd dApp ``` * Create new project based on **dark** themed template (Gear): npm pnpm yarn bun ```bash npx degit gear-foundation/dapps/frontend/templates/create-gear-app dApp cd dApp ``` ```bash pnpm dlx degit gear-foundation/dapps/frontend/templates/create-gear-app dApp cd dApp ``` ```bash yarn dlx degit gear-foundation/dapps/frontend/templates/create-gear-app dApp cd dApp ``` ```bash bun x degit gear-foundation/dapps/frontend/templates/create-gear-app dApp cd dApp ``` # Gasless and Signless Transactions The **EZ-Transactions package** (aka Easy-transactions) simplifies blockchain interactions by enabling gasless and signless transactions. Anyone can use it to integrate into their dApp projects. With gasless transactions, the dApp developer covers gas fees for their users (web2-like approach), while signless transactions allow automatic signing via temporary sub-accounts. This combination creates a seamless user experience, as they no longer need to pay gas fees or manually sign each transaction. Developers can easily integrate these features into their dApps using provided hooks and providers, such as `GaslessTransactionsProvider` and `SignlessTransactionsProvider`. The package also includes UI components for enabling or disabling these features. For more details, visit the [GitHub page](https://github.com/gear-foundation/dapps/tree/master/frontend/packages/ez-transactions). Key Features [#key-features] Gasless Transactions [#gasless-transactions] * Users no longer need to worry about paying gas fees. The [backend](https://github.com/gear-foundation/dapps/tree/master/backend/gasless) handles transaction fees, creating a more streamlined experience. * This is particularly useful for applications targeting non-crypto-savvy users who might find gas fees confusing. Signless Transactions [#signless-transactions] * Signless transactions are automatically signed by a temporary sub-account on the user's behalf. * This removes the need for users to manually sign every transaction, speeding up processes and making applications more user-friendly. Package Composition [#package-composition] The package includes several key elements that developers can leverage to integrate these features into their applications: Providers [#providers] * `GaslessTransactionsProvider`: Wraps components to enable gasless transaction functionality. This ensures that the backend manages all transaction costs. * `SignlessTransactionsProvider`: Enables automatic signing of transactions, reducing user friction. Hooks [#hooks] * `useGaslessTransaction`: Allows developers to easily implement gasless functionality in specific parts of their dApp. * `useSignlessTransaction`: Integrates signless functionality for seamless transaction processing. UI Components [#ui-components] The package also provides UI elements that can toggle these features on or off, giving flexibility to both developers and users. These components can be customized to suit the needs of the application. Use Cases [#use-cases] * **User-friendly dApps**: Great for developers wanting to improve the user experience, especially for users unfamiliar with blockchain or unwilling to deal with gas fees or manual signing. * **DeFi Projects**: Reduces friction for frequent transactions, such as trading or lending operations, where user experience is critical. * **Gaming dApps**: Gamers can focus on playing without worrying about blockchain complexities. Conclusion [#conclusion] The **EZ-Transactions package** empowers developers to create smoother and more accessible blockchain applications. By integrating gasless and signless transaction capabilities, developers can remove major pain points for their users and improve overall engagement. For detailed documentation about how to use, check the [GitHub page](https://github.com/gear-foundation/dapps/tree/master/frontend/packages/ez-transactions). # Vara Meta CLI This CLI tool is **deprecated** and will be removed in a future release. **Recommended alternative:** Use `getProgramMetadata` from the **@gear-js/api** library as described in the [Metadata Type Creation](/docs/developing/api/metadata-type-creation) guide. Vara Meta CLI [#vara-meta-cli] CLI tool to encode/decode payloads and work with .meta.wasm files. Installation [#installation] npm pnpm yarn bun ```bash npm install -g @gear-js/gear-meta ``` ```bash pnpm add -g @gear-js/gear-meta ``` ```bash yarn global add @gear-js/gear-meta ``` ```bash bun add --global @gear-js/gear-meta ``` Usage [#usage] Full list of commands [#full-list-of-commands] ```sh gear-meta --help ``` Available commands [#available-commands] **decode** - *Decode payload from hex* **encode** - *Encode payload to hex* **meta** - *Display metadata from .meta.wasm* **type** - *Display type structure for particular type from .meta.wasm* You can simply run these commands and you will be prompted to enter the necessary data. Or you can specify data through options: **-t, --type** - *Type to encode or decode the payload. If it is not specified you can select it later* **-m, --meta** - *Path to .meta.wasm file with program's metadata* **-o --output** - *Output JSON file. If it doesn't exist it will be created* **-j --payloadFromJson** - *If need to take the payload from json* All of these options are available for `decode` and `encode` commands `-o --output` option is available for `meta` command `-m, --meta` option is available for `type` command Examples [#examples] ```sh gear-meta encode '{"amount": 8, "currency": "GRT"}' -t init_input -m ./path/to/demo_meta.meta.wasm # Output: # Result: # 0x080c475254 ``` ```sh gear-meta decode '0x080c475254' -t init_input -m ./path/to/demo_meta.meta.wasm # Output: # Result: # { amount: '8', currency: 'GRT' } ``` ```sh gear-meta type handle_input -m ./path/to/demo_meta.meta.wasm # Output: # TypeName: MessageIn # { id: { decimal: 'u64', hex: 'Bytes' } } ``` ```sh gear-meta meta ./path/to/demo_meta.meta.wasm # Output: # Result: # { # types: '0x50000824646...0000023800', # init_input: 'MessageInitIn', # init_output: 'MessageInitOut', # async_init_input: 'MessageInitAsyncIn', # async_init_output: 'MessageInitAsyncOut', # handle_input: 'MessageIn', # handle_output: 'MessageOut', # async_handle_input: 'MessageHandleAsyncIn', # async_handle_output: 'MessageHandleAsyncOut', # title: 'Example program with metadata', # meta_state_input: 'Option', # meta_state_output: 'Vec', # meta_state: undefined # } ``` # Gear-JS React Hooks Description [#description] A React library that provides hooks used across Gear applications. This package provides ready-to-use hooks for interacting with the [@gear-js/api](https://github.com/gear-tech/gear-js/tree/main/apis/gear), managing accounts and wallets, formatting balances, querying on-chain state, handling program transactions, and working with vouchers and staking. Designed to simplify dApp development, these hooks abstract away low-level details and provide a consistent, declarative interface for common blockchain operations, including real-time subscriptions, state management, and user notifications. Installation [#installation] npm pnpm yarn bun ```bash npm install @gear-js/react-hooks ``` ```bash pnpm add @gear-js/react-hooks ``` ```bash yarn add @gear-js/react-hooks ``` ```bash bun add @gear-js/react-hooks ``` Peer Dependencies [#peer-dependencies] This package requires several peer dependencies to be installed in your project: * @gear-js/api * @polkadot/api * @tanstack/react-query * sails-js > **Note:** > We recommend not installing peer dependencies explicitly if your package manager can resolve them automatically. However, if you have to fix version conflicts, or if you want to use functionality from these libraries directly in your project, you should install them explicitly. > Please refer to the `peerDependencies` section in the `package.json` for the required versions. Getting started [#getting-started] To use the hooks, wrap your application with the required providers. Each provider is described below. QueryClientProvider (TanStack Query) [#queryclientprovider-tanstack-query] Wrap your app with [`QueryClientProvider`](https://tanstack.com/query/latest/docs/framework/react/reference/QueryClientProvider) from TanStack Query to enable query and mutation management. For more details on setting up TanStack Query, see the [TanStack Query documentation](https://tanstack.com/query/latest/docs/framework/react/quick-start). Props [#props] * `client` (**required**): An instance of `QueryClient`. Example: [#example] ```jsx import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; const queryClient = new QueryClient(); {/* ... */}; ``` ApiProvider [#apiprovider] Provides Gear API context to the app. Props [#props-1] * `initialArgs` (**required**): `ProviderArgs` — Arguments for the API provider. Determines how the API connection is established. * `WsProviderArgs`: `{ endpoint: string | string[]; autoConnectMs?: number | false; headers?: Record; timeout?: number; }` — Connects via JSON-RPC. `endpoint` is the node address or addresses. `autoConnectMs` sets the auto-reconnect interval. `headers` and `timeout` are optional connection options. * `ScProviderArgs`: `{ spec: string; sharedSandbox?: ScProvider; }` — Connects via Light Client. `spec` is the specification string. `sharedSandbox` allows sharing a sandbox instance. Example: [#example-1] ```jsx import { ApiProvider } from '@gear-js/react-hooks'; {/* ... */}; ``` AccountProvider [#accountprovider] Provides account and wallet context to the app. Props [#props-2] * `appName` (**required**): `string` — The name of your application. The value provided here will be displayed in the wallet app or extension. Example: [#example-2] ```jsx import { AccountProvider } from '@gear-js/react-hooks'; {/* ... */}; ``` AlertProvider [#alertprovider] Provides alert context for notifications and messages. Props [#props-3] * `template` (**required**): `ComponentType` — The component to use as the alert template. Defines how alerts are rendered in your app. * `containerClassName` (optional): `string` — Custom class name for the alert container. Use this to style or position the alert area. Example: [#example-3] ```jsx import { AlertProvider } from '@gear-js/react-hooks'; import { AlertTemplate } from './alert-template'; {/* ... */}; ``` Combine all providers at the root of your app: [#combine-all-providers-at-the-root-of-your-app] ```jsx ``` Usage Example [#usage-example] As simple as it is, here's a quick example: ```jsx import { ProgramMetadata } from '@gear-js/api'; import { useReadFullState } from '@gear-js/react-hooks'; function State() { const programId = '0x...'; const metadataHex = '0x...'; const payload = null; const { state } = useReadFullState(programId, ProgramMetadata.from(METADATA_HEX), payload); return
{JSON.stringify(state)}
; } export { State }; ``` Context Hooks [#context-hooks] useApi [#useapi] Provides access to the Gear API instance, allowing you to interact with the blockchain, check if the API is ready, and switch networks programmatically. Parameters [#parameters] None. Returns [#returns] * `api` (`GearApi | undefined`): The current Gear API instance, or `undefined` if not yet connected. * `isApiReady` (`boolean`): Indicates whether the API is fully initialized and ready to use. * `switchNetwork` (`(args: ProviderArgs) => Promise`): Function to switch the network provider. Usage Example [#usage-example-1] ```jsx import { useApi } from '@gear-js/react-hooks'; function ApiStatus() { const { api, isApiReady, switchNetwork } = useApi(); if (!isApiReady) return
Connecting to network...
; return (
API connected: {String(!!api)}
Spec Version: {api?.specVersion}
); } ``` useAccount [#useaccount] Provides access to the current wallet and account context, allowing you to manage user wallet connection, retrieve available wallets, and handle login/logout actions in your application. This hook interacts with browser wallet extensions. Parameters [#parameters-1] None. Returns [#returns-1] * `wallets` (`Wallets | undefined`): An object containing all detected wallets and their accounts, or `undefined` if not yet loaded. * `account` (`Account | undefined`): The currently selected account, or `undefined` if not logged in. * `isAnyWallet` (`boolean`): Indicates whether any wallet extension is available. * `isAccountReady` (`boolean`): Indicates whether wallet and account information has been loaded. * `login` (`(account: Account) => void`): Function to log in with a specific account. * `logout` (`() => void`): Function to log out the current account. Usage Example [#usage-example-2] ```jsx import { useAccount } from '@gear-js/react-hooks'; function AccountInfo() { const { wallets, account, isAnyWallet, isAccountReady, login, logout } = useAccount(); if (!isAccountReady) return
Loading wallets...
; if (!isAnyWallet) return
No wallet extension found
; return (
Current account: {account?.address || 'None'}
{account && }
); } ``` useAlert [#usealert] Provides access to alert and notification methods, allowing you to show, update, and remove alerts or notifications in your application. Parameters [#parameters-2] None. Returns [#returns-2] * `info` (`(content: ReactNode, options?: TemplateAlertOptions) => string`): Show an info alert. * `error` (`(content: ReactNode, options?: TemplateAlertOptions) => string`): Show an error alert. * `success` (`(content: ReactNode, options?: TemplateAlertOptions) => string`): Show a success alert. * `loading` (`(content: ReactNode, options?: TemplateAlertOptions) => string`): Show a loading alert. * `update` (`(alertId: string, content: ReactNode, options?: Partial) => void`): Update an existing alert by its ID. * `remove` (`(alertId: string) => void`): Remove an alert by its ID. Usage Example [#usage-example-3] ```jsx import { useAccount, useAlert } from '@gear-js/react-hooks'; function AlertButtons() { const alert = useAlert(); const handleSuccessClick = () => { alert.success('Success alert!'); }; const handleErrorClick = () => { alert.error('Error alert!'); }; return (
); } ``` Balance Hooks [#balance-hooks] useBalanceFormat [#usebalanceformat] Provides utilities for formatting and converting balances and gas values according to the current chain's decimals and token unit. Use this hook when you need to display user-friendly balances, convert between raw and formatted values, or transform gas calculation values. This hook is based on `api.registry.chainDecimals`, `api.registry.chainTokens`, and `api.valuePerGas`. > **Note:** > This hook uses the BigNumber.js library under the hood. If you are not sure, all values returned as BigNumber instances should be converted or used in calculations only with BigNumber methods — using them as regular JavaScript numbers is not safe and may lead to precision errors. Parameters [#parameters-3] None. Returns [#returns-3] * `balanceMultiplier` (`BigNumber`): Multiplier for converting between raw and formatted balances. * `decimals` (`number`): Number of decimals for the current chain. * `getChainBalanceValue` (`(value: string | number) => BigNumber`): Converts a formatted value to the raw chain balance. * `getFormattedBalanceValue` (`(value: string | number) => BigNumber`): Converts a raw chain balance to a formatted value. * `getFormattedBalance` (`(balance: BN | string | number) => { value: string, unit: string }`): Formats a balance for display, returning the value and unit. * `getChainGasValue` (`(value: string | number) => BigNumber`): Converts a formatted gas value to the raw chain gas value. * `getFormattedGasValue` (`(value: string | number) => BigNumber`): Converts a raw chain gas value to a formatted value. Usage Example [#usage-example-4] ```jsx import { useApi, useBalanceFormat } from '@gear-js/react-hooks'; const RAW_BALANCE = 1_000_000_000_000n; function BalanceDisplay({ balance }) { const { isApiReady } = useApi(); const { getFormattedBalance } = useBalanceFormat(); const formattedBalance = isApiReady ? getFormattedBalance(RAW_BALANCE) : undefined; if (!formattedBalance) return Loading...; return ( {formattedBalance.value} {formattedBalance.unit} ); } ``` useBalance [#usebalance] Retrieves and subscribes to the total balance of a given account address, allowing you to display or react to the overall balance in your application. This hook is based on `api.balance.findOut` and `api.gearEvents.subscribeToBalanceChanges`. > **Note:**\ > This hook returns the account's total balance as a single value, not its individual components (such as free, reserved, or misc frozen balances). On Substrate-based chains, the balance type is a composite structure; to access specific fields like free balance, use the [useDeriveBalancesAll](/docs/developing/api/tooling/react-hooks#usederivebalancesall). Parameters [#parameters-4] * `address` (`string | undefined`): The account address to fetch and subscribe to the balance for. Returns [#returns-4] * `balance` (`Balance | undefined`): The current total balance of the account, or `undefined` if not yet loaded. * `isBalanceReady` (`boolean`): Indicates whether the balance has been successfully loaded. Usage Example [#usage-example-5] ```jsx import { useBalance } from '@gear-js/react-hooks'; type Props = { address: string | undefined; } function Balance({ address }: Props) { const { balance, isBalanceReady } = useBalance(address); if (!isBalanceReady) return
Loading...
; return
Total Balance: {balance?.toString()}
; } ``` Derive Hooks [#derive-hooks] useDeriveBalancesAll [#usederivebalancesall] Retrieves and subscribes to all balance components (transferable, reserved, misc frozen, etc.) for a given account address, providing a full breakdown of the account's balance as returned by Substrate's derive API. Use this hook when you need to display or react to specific balance fields, such as transferable or reserved balance, rather than the total. This hook is based on `api.derive.balances.all`. Parameters [#parameters-5] * `address` (`string | undefined`): The account address to fetch and subscribe to the balance breakdown for. * `watch` (`boolean`, optional): If true, subscribes to balance changes and updates automatically. Default is false. * `query` (`QueryParameters`, optional): Additional query options for TanStack Query. Returns [#returns-5] * TanStack Query `UseQueryResult` with `DeriveBalancesAll` data. Usage Example [#usage-example-6] ```jsx import { useDeriveBalancesAll } from '@gear-js/react-hooks'; function BalanceBreakdown({ address }) { const { data, isLoading } = useDeriveBalancesAll({ address }); if (isLoading) return
Loading...
; if (!data) return
No data
; return (
Transferable: {data.transferable.toString()}
Reserved: {data.reservedBalance.toString()}
Frozen Misc: {data.frozenMisc.toString()}
Frozen Fee: {data.frozenFee.toString()}
); } ``` useDeriveStakingAccount [#usederivestakingaccount] Retrieves and subscribes to staking information for a given account address, including staking ledger, nominations, rewards, and more, as provided by Substrate's derive API. Use this hook when you need to display or react to staking-related data for an account. This hook is based on `api.derive.staking.account`. Parameters [#parameters-6] * `address` (`string | undefined`): The account address to fetch and subscribe to staking information for. * `watch` (`boolean`, optional): If true, subscribes to staking changes and updates automatically. Default is false. * `query` (`QueryParameters`, optional): Additional query options for TanStack Query. Returns [#returns-6] * TanStack Query `UseQueryResult` with `DeriveStakingAccount` data. Usage Example [#usage-example-7] ```jsx import { useDeriveStakingAccount } from '@gear-js/react-hooks'; function StakingInfo({ address }) { const { data, isLoading } = useDeriveStakingAccount({ address }); if (isLoading) return
Loading...
; if (!data) return
No staking data
; return (
Stash: {data.stashId.toHex()}
Controller: {data.controllerId.toHex()}
Ledger: {JSON.stringify(data.stakingLedger)}
Nominations: {JSON.stringify(data.nominations)}
); } ``` Voucher Hooks [#voucher-hooks] useVoucher [#usevoucher] Retrieves details for a specific voucher associated with a given account address. Use this hook when you need to display or react to voucher information for a user. This hook is based on `api.voucher.getDetails`. Parameters [#parameters-7] * `voucherId` (`HexString | undefined`): The voucher ID to fetch details for. * `accountAddress` (`string | undefined`): The account address to fetch the voucher for. Returns [#returns-7] * `voucher` (`IVoucherDetails | undefined`): The voucher details, or `undefined` if not yet loaded. * `isVoucherReady` (`boolean`): Indicates whether the voucher details have been successfully loaded. Usage Example [#usage-example-8] ```jsx import { useVoucher } from '@gear-js/react-hooks'; function VoucherInfo({ voucherId, accountAddress }) { const { voucher, isVoucherReady } = useVoucher(voucherId, accountAddress); if (!isVoucherReady) return
Loading...
; if (!voucher) return
No voucher found
; return (
Owner: {voucher.owner}
Expiry: {voucher.expiry}
Code Uploading: {voucher.codeUploading}
Programs: {JSON.stringify(voucher.programs)}
); } ``` useVouchers [#usevouchers] Retrieves all voucher details for a given account address, optionally filtered by a specific program ID. Use this hook when you need to display or react to all vouchers associated with a user. This hook is based on `api.voucher.getAllForAccount`. Parameters [#parameters-8] * `accountAddress` (`string | undefined`): The account address to fetch vouchers for. * `programId` (`HexString`, optional): The program ID to filter vouchers by. If omitted, returns all vouchers for the account. Returns [#returns-8] * `vouchers` (`Record | undefined`): An object mapping voucher IDs to their details, or `undefined` if not yet loaded. * `isEachVoucherReady` (`boolean`): Indicates whether the vouchers have been successfully loaded. Usage Example [#usage-example-9] ```jsx import { useVouchers } from '@gear-js/react-hooks'; function VouchersList({ accountAddress, programId }) { const { vouchers, isEachVoucherReady } = useVouchers(accountAddress, programId); if (!isEachVoucherReady) return
Loading...
; if (!vouchers || Object.keys(vouchers).length === 0) return
No vouchers found
; return (
    {Object.values(vouchers).map((voucher, index) => (
  • Owner: {voucher.owner}
    Expiry: {voucher.expiry}
    Code Uploading: {voucher.codeUploading}
    Programs: {JSON.stringify(voucher.programs)}
  • ))}
); } ``` Sails Hooks [#sails-hooks] React hooks abstraction over [sails-js](https://github.com/gear-tech/sails/tree/master/js) and its generated program library. [TanStack Query](https://tanstack.com/query) is used as an async state manager to handle queries and mutations. Therefore, most hooks' parameters and return data correspond to the library's conventions. useSails [#usesails] Returns a Sails instance for interacting with a program using its interface for a given program ID and IDL. Parameters [#parameters-9] * `programId` (`string`): The program ID to connect to. * `idl` (`string`): The program's interface definition language (IDL) as a string. * `query` (`QueryParameters`, optional): Additional query options for TanStack Query. Returns [#returns-9] * TanStack Query `UseQueryResult` with `Sails` instance data. Usage Example [#usage-example-10] ```jsx import { useSails } from '@gear-js/react-hooks'; const PROGRAM_ID = '0x...'; const IDL = '...'; function SailsInfo() { const { data: sails } = useSails({ programId: PROGRAM_ID, idl: IDL, }); if (!sails) return
Loading...
; return
Sails instance ready: {String(!!sails)}
; } ``` useProgram [#useprogram] Returns a generated program library instance for interacting with a specific program. Use this hook to access the program's methods and services as defined in your generated library. Parameters [#parameters-10] * `library` (`Program`): The generated program library (e.g., `Program` from your codegen output). * `id` (`string`): The program ID to connect to. * `query` (`QueryParameters`, optional): Additional query options for TanStack Query. Returns [#returns-10] * TanStack Query `UseQueryResult` with `Program` instance data. Usage Example [#usage-example-11] ```jsx import { useProgram } from '@gear-js/react-hooks'; import { Program } from './lib'; function ProgramInfo() { const { data: program } = useProgram({ library: Program, id: '0x...', }); if (!program) return
Loading...
; return
Program instance ready: {String(!!program)}
; } export { ProgramInfo }; ``` useSendProgramTransaction [#usesendprogramtransaction] Returns a mutation to sign and send a transaction to a program with minimal effort. Use this hook to interact with a program's service and function, to handle signing, sending, and response management. Parameters [#parameters-11] * `program` (`Program`): The program instance returned by `useProgram`. * `serviceName` (`string`): The name of the service to call. * `functionName` (`string`): The name of the function to call within the service. Returns [#returns-11] * `sendTransactionAsync` (`function`): A function to sign and send the transaction. Accepts an object with: * `args` (`TFunctionArgs[]`): Arguments for the function call. * `account` (`AccountParameters`, optional): Account options to sign the transaction. Defaults to the connected account. * `value` (`bigint`, optional): Value to send with the transaction. Defaults to 0. * `gasLimit` (`bigint | CalculateGasParameters`, optional): Gas limit for the transaction. If not provided, calculated automatically. * `voucherId` (`HexString`, optional): Voucher ID to use for the transaction. If not provided, the transaction will be sent without a voucher. * `awaitFinalization` (`boolean`, optional): Flag indicating whether to wait for the transaction to be finalized. By default, if not specified, the transaction will be processed at the `inBlock` status. Usage Example [#usage-example-12] ```jsx import { useProgram, useSendProgramTransaction } from '@gear-js/react-hooks'; import { Program } from './lib'; function SendTransaction() { const { data: program } = useProgram({ library: Program, id: '0x...', }); const { sendTransactionAsync } = useSendProgramTransaction({ program, serviceName: 'service', functionName: 'function', }); const handleClick = async () => { const { response } = await sendTransactionAsync({ args: ['arg', 'anotherArg'], }); console.log('response: ', response); }; return ( ); } export { SendTransaction }; ``` Or in combination with [usePrepareProgramTransaction](/docs/developing/api/tooling/react-hooks#useprepareprogramtransaction): ```jsx import { useProgram, usePrepareProgramTransaction, useSendProgramTransaction } from '@gear-js/react-hooks'; import { Program } from './lib'; function SendPreparedTransaction() { const { data: program } = useProgram({ library: Program, id: '0x...', }); const { prepareTransactionAsync } = usePrepareProgramTransaction({ program, serviceName: 'service', functionName: 'function', }); const { sendTransactionAsync } = useSendProgramTransaction({ program, serviceName: 'service', functionName: 'function', }); const handleClick = async () => { const { transaction, fee } = await prepareTransactionAsync({ args: ['arg', 'anotherArg'], }); console.log('fee: ', fee); const { response } = await sendTransactionAsync({ transaction }); console.log('response: ', response); }; return ( ); } export { SendPreparedTransaction }; ``` usePrepareProgramTransaction [#useprepareprogramtransaction] Returns a mutation that retrieves the intermediate transaction state. This can be useful for eagerly obtaining values such as `gasLimit`, `extrinsic`, and `transactionFee`, which are essential for providing a better UX. For example, it can be used to perform validation checks before sending the transaction. Parameters [#parameters-12] * `program` (`Program`): The program instance returned by `useProgram`. * `serviceName` (`string`): The name of the service to call. * `functionName` (`string`): The name of the function to call within the service. Returns [#returns-12] * `prepareTransactionAsync` (`function`): A function to prepare the transaction. Accepts an object with: * `args` (`TFunctionArgs[]`): Arguments for the function call. * `account` (`AccountParameters`, optional): Account options to sign the transaction. Defaults to the connected account. * `value` (`bigint`, optional): Value to send with the transaction. Defaults to 0. * `gasLimit` (`bigint | CalculateGasParameters`, optional): Gas limit for the transaction. If not provided, calculated automatically. * `voucherId` (`HexString`, optional): Voucher ID to use for the transaction. Usage Example [#usage-example-13] ```jsx import { useProgram, usePrepareProgramTransaction } from '@gear-js/react-hooks'; import { Program } from './lib'; function LogTransactionFeeButton() { const { data: program } = useProgram({ library: Program, id: '0x...', }); const { prepareTransactionAsync } = usePrepareProgramTransaction({ program, serviceName: 'service', functionName: 'function', }); const handleClick = async () => { const { fee } = await prepareTransactionAsync({ args: ['arg', 'anotherArg'], }); console.log('fee: ', fee); }; return ( ); } export { LogTransactionFeeButton }; ``` useProgramQuery [#useprogramquery] Returns a query with the program's state for a given service and function. Use this hook to read state from a program, optionally subscribing to state changes. Parameters [#parameters-13] * `program` (`Program`): The program instance returned by `useProgram`. * `serviceName` (`string`): The name of the service to query. * `functionName` (`string`): The name of the function to query. * `args` (`TFunctionArgs[]`): Arguments for the function call. * `watch` (`boolean`, optional): If true, subscribes to state changes. Default is false. * `query` (`QueryParameters`, optional): Additional query options for TanStack Query. Returns [#returns-13] * TanStack Query `UseQueryResult` with the function's return data. Usage Example [#usage-example-14] ```jsx import { useProgram, useProgramQuery } from '@gear-js/react-hooks'; import { Program } from './lib'; function State() { const { data: program } = useProgram({ library: Program, id: '0x...', }); const { data } = useProgramQuery({ program, serviceName: 'service', functionName: 'function', args: ['arg', 'anotherArg'], }); return
{JSON.stringify(data)}
; } ``` useProgramEvent [#useprogramevent] Initializes a subscription to a particular program event. Use this hook to listen for events emitted by a program's service and function. Parameters [#parameters-14] * `program` (`Program`): The program instance returned by `useProgram`. * `serviceName` (`string`): The name of the service to subscribe to. * `functionName` (`string`): The name of the function to subscribe to. * `onData` (`function`): Callback function to handle event data. Returns [#returns-14] None. Usage Example [#usage-example-15] ```jsx import { useProgram, useProgramEvent } from '@gear-js/react-hooks'; import { Routing } from './pages'; import { Program } from './lib'; function App() { const { data: program } = useProgram({ library: Program, id: '0x...', }); useProgramEvent({ program, serviceName: 'service', functionName: 'function', onData: (data) => console.log(data), }); return (
); } export { App }; ``` # Trading Skill **Vara Trading Skill** is an open-source pack of agentic playbooks plus a local CLI (`vara-agent`) that turns any AI coding agent — Claude Code, Codex, Cursor — into a hands-on spot trader on centralized exchanges (CEX). You describe a trade in plain language — *"check the VARA/USDT order book on Gate.io"* or *"place a limit buy for 2 USDT at 0.000605"* — and the agent runs the matching command locally on your machine. VARA is the default example, but every command works with any active spot pair the exchange supports. 👉 **Repository:** [github.com/gear-foundation/vara-trading-skill](https://github.com/gear-foundation/vara-trading-skill) See [Vara Skills](/docs/vara-network/api/tooling/vara-skills) — the Sails-focused pack that turns agents into Vara dApp builders. This skill places real orders against real exchanges. Start with dry-run mode or small amounts, and only trade funds you can afford to lose. It never gives investment advice — market data is evidence, not a signal to trade. What You Get [#what-you-get] * **Agent-driven trading** — describe a trade in natural language; the agent runs the matching command * **Two live exchanges** — Gate.io and Coinbase, via [CCXT](https://github.com/ccxt/ccxt) * **Dry-run first** — orders simulate by default; live execution needs explicit, per-trade confirmation * **Guided onboarding** — a wizard walks through exchange choice, keys, risk limits, validation, and a dry-run * **Keys stay local** — credentials live in `~/.vara-trading-agent/.env`; the agent never asks you to paste them into chat * **No backend, no daemon** — everything runs on your machine; there is no autonomous 24/7 loop Supported Exchanges [#supported-exchanges] | Exchange | Trading | Read-only | Withdrawals | | ------------ | ------- | --------- | ----------- | | **Gate.io** | ✅ | ✅ | ✅ (opt-in) | | **Coinbase** | ✅ | ✅ | — | On Coinbase use `VARA/USDC` or `VARA/USD` (no `VARA/USDT`); run `check-market` before adding any pair to your risk limits. Crypto.com, Exolix, Banxa, and DEX/on-chain flows are planned future integrations. Installation [#installation] ```bash npm install -g vara-trading-skill npx skills add https://github.com/gear-foundation/vara-trading-skill/tree/master/skill-pack -g --all -y ``` The first command installs the `vara-agent` CLI; the second installs the skill pack so your agent can discover it. Restart your agent session if it doesn't pick up the skill right away. Start With an Agent [#start-with-an-agent] Once installed, just ask: ```text Use vara-trading-skill and set up local trading. ``` The agent runs `vara-agent init-config` and `vara-agent onboarding interactive`, then guides you through setup. `init-config` creates `~/.vara-trading-agent/.env` — but the agent will **never** ask you to paste API keys into chat. When credentials are needed, you edit that file locally. The Onboarding Wizard [#the-onboarding-wizard] Live trading unlocks only after the wizard is complete. It flows through: a risk warning → choosing an exchange → exchange-specific key setup → saving credentials locally → setting risk limits (max trade size, allowed assets, allowed pairs, max slippage) → validation against your account → a dry-run → final confirmation. Allowed pairs must be listed explicitly — the agent refuses any live order for a pair outside that list. API Key Safety [#api-key-safety] Create a **dedicated** API key per exchange and grant only **read + trade** permissions — never withdrawal or transfer. Keys go into `~/.vara-trading-agent/.env`, which is owner-only and never shared in chat. A secret is often shown once by the exchange, so copy it carefully. Enable IP whitelisting where available, and consider a separate subaccount funded with only what you're willing to trade. Command Reference [#command-reference] The skill maps your request to a `vara-agent` command. Market and account commands are read-only — they never place orders: | Command | Purpose | | ------------------------ | ---------------------------------------------------------- | | `routes` | Discover routes for an asset/quote pair | | `check-market` | Verify a market; inspect limits and precision | | `ticker` / `orderbook` | Current price data / order book depth | | `balance` | Non-zero account balances | | `open-orders` / `orders` | Active orders / order history | | `watch --orders` | Read-only polling for fills and status changes (no daemon) | Trading commands place or cancel real orders: | Command | Purpose | | -------------------------- | -------------------------------------------- | | `buy` / `sell` | Market spot orders | | `limit-buy` / `limit-sell` | Limit spot orders | | `cancel-order` | Cancel an order by id (requires `--confirm`) | Every order command defaults to `--mode dry-run` (simulated). Live execution requires `--mode live`, completed onboarding, and an allowlisted pair. Safety Model [#safety-model] * Credentials stay local in `~/.vara-trading-agent/.env`; the agent never asks for or displays them * Trading keys carry **read + trade** permissions only * Dry-run is the default; live orders need explicit per-trade instruction — the agent never infers a trade from discussion * Live orders are limited to the assets and pairs set during onboarding * Canceling requires the exact order id plus `--confirm` * No autonomous monitoring, conditional orders, or arbitrage — the skill does only what you ask, when you ask Withdrawals [#withdrawals] API withdrawals are an explicit, high-risk capability, **off by default** and supported on Gate.io only. Enabling them requires dedicated withdrawal keys (separate from trading keys), asset/network/address allowlists, per-asset limits, and explicit confirmation flags on every withdrawal. Once accepted by the exchange, a withdrawal can't be undone. Use address allowlists and a small test withdrawal first. Release Scope [#release-scope] The current release covers Gate.io and Coinbase trading, local onboarding, read-only market and account checks, order placement and management, order watching, and explicitly confirmed withdrawals on supported providers. Crypto.com, Exolix swaps, Banxa, DEX trading, autonomous monitoring, conditional orders, and arbitrage remain on the roadmap. # Vara Skills **Vara Skills** is an open-source pack of agentic playbooks that gives AI coding agents — Claude Code, Codex, Cursor, and any tool that supports the universal `skills` format — exact, documentation-backed workflows for every step of Gear/Vara Sails development. Each skill is a self-contained Markdown file the agent reads at runtime. 👉 **Repository:** [github.com/gear-foundation/vara-skills](https://github.com/gear-foundation/vara-skills) See [Agent Skills](/docs/vara-eth/setup/agent-skills) for the Vara.eth-focused pack covering `ethexe`, Mirror contracts, and Solidity integration. What You Get [#what-you-get] * **Documentation-backed workflows** — every skill cites the exact Sails / Gear APIs, real imports, and verified patterns * **Auto-routing** — describe what you want and the `ship-sails-app` router picks the right sequence of skills * **Tool-agnostic** — the same skill files work across Claude Code, Codex, and any agent that reads a `SKILL.md` * **Zero config** — install once, the agent discovers the skills automatically * **Open source** — MIT-licensed, community contributions welcome Installation [#installation] Claude Code [#claude-code] Install from the marketplace: ```bash /plugin marketplace add https://github.com/gear-foundation/vara-skills /plugin install vara-skills@vara-skills ``` For local development (after cloning the repo): ```bash /plugin marketplace add . /plugin install vara-skills@vara-skills ``` Use `/reload-plugins` after editing skill files locally. Codex [#codex] Clone the repo and run the install script: ```bash git clone https://github.com/gear-foundation/vara-skills.git cd vara-skills bash scripts/install-codex-skills.sh ``` Any Agent (Universal) [#any-agent-universal] ```bash npx skills add gear-foundation/vara-skills ``` Skill Catalog [#skill-catalog] The pack is organized by phase. The router skill (`ship-sails-app`) chooses the right path for your task. Planning [#planning] | Skill | Purpose | | --------------------------- | -------------------------------------------- | | `idea-to-spec` | Turn an idea into a structured specification | | `gear-architecture-planner` | High-level Gear program architecture | | `sails-architecture` | Service and state design in Sails | | `task-decomposer` | Break a spec into actionable tasks | Build & Implementation [#build--implementation] | Skill | Purpose | | ------------------------- | --------------------------------------- | | `sails-dev-env` | Set up the Rust/Sails toolchain | | `sails-new-app` | Scaffold a new Sails program | | `sails-feature-workflow` | Add features to an existing program | | `sails-rust-implementer` | Write Sails services in Rust | | `sails-idl-client` | Generate typed clients from the IDL | | `sails-frontend` | Build a frontend wired to the program | | `sails-indexer` | Set up an indexer for on-chain events | | `awesome-sails-vft` | Work with VFT (fungible token) standard | | `sails-program-evolution` | Upgrade and evolve deployed programs | | `vara-wallet` | Wallet integration patterns | Verification [#verification] | Skill | Purpose | | ------------------------ | ----------------------------------------- | | `sails-gtest` | Write unit/integration tests with `gtest` | | `gtest-tdd-loop` | Test-driven development workflow | | `sails-local-smoke` | Smoke-test against a local Gear node | | `gear-message-execution` | Trace and debug message flow | Deep Capability & Maintenance [#deep-capability--maintenance] | Skill | Purpose | | --------------------- | --------------------------------- | | `gear-gstd-api-map` | Map of low-level `gstd` APIs | | `vara-skills-upgrade` | Keep your local skills up to date | Reference dApps [#reference-dapps] Real dApps built end-to-end with Vara Skills live in a companion repository: 👉 [github.com/gear-foundation/Agentic-development-dapps](https://github.com/gear-foundation/Agentic-development-dapps) # Gear-JS Wallet Connect Description [#description] A React library to connect supported Substrate-based wallets in a standardized and consistent way across decentralized applications. Installation [#installation] npm pnpm yarn bun ```bash npm install @gear-js/wallet-connect @gear-js/react-hooks @gear-js/ui @gear-js/vara-ui ``` ```bash pnpm add @gear-js/wallet-connect @gear-js/react-hooks @gear-js/ui @gear-js/vara-ui ``` ```bash yarn add @gear-js/wallet-connect @gear-js/react-hooks @gear-js/ui @gear-js/vara-ui ``` ```bash bun add @gear-js/wallet-connect @gear-js/react-hooks @gear-js/ui @gear-js/vara-ui ``` Getting started [#getting-started] Configure API [#configure-api] Before using `@gear-js/wallet-connect`, make sure to configure [`@gear-js/react-hooks`](https://github.com/gear-tech/gear-js/tree/main/utils/gear-hooks#readme) in your project according to its documentation. This setup is required for API connection and account management. Configure UI [#configure-ui] Depending on your chosen theme, you must also install and configure the corresponding UI library styles: * For the **`vara`** theme (default), follow the [`@gear-js/vara-ui`](https://github.com/gear-tech/gear-js/tree/main/utils/vara-ui#readme) documentation to set up global styles. * For the **`gear`** theme, follow the [`@gear-js/ui`](https://github.com/gear-tech/gear-js/tree/main/utils/gear-ui#readme) documentation to set up global styles (typically via your `index.scss`). Components [#components] Wallet [#wallet] A React component that displays the current account or wallet connection button, and (optionally) the account’s total balance. It uses [`useAccount`](https://github.com/gear-tech/gear-js/tree/main/utils/gear-hooks#useaccount) from `@gear-js/react-hooks` to manage account state and modal visibility for wallet actions. This is a generic, **ready-to-use component** for wallet management. It handles: * **Connection:** Managing the wallet connection flow. * **Account Display:** Showing the currently active account. * **Modals:** Built-in modal handling for selection and management. For most dApps, this component is the quickest way to integrate wallet functionality. Props [#props] * `theme` (`'gear' | 'vara'`, optional): UI theme for the wallet controls. Defaults to `'vara'`. * `displayBalance` (`boolean`, optional): Whether to show the account’s total balance. Defaults to `true`. Usage Example [#usage-example] ```jsx import { Wallet } from '@gear-js/wallet-connect'; import Logo from './logo.svg?react'; function Header() { return (
); } export { Header }; ``` WalletModal [#walletmodal] A React modal component for managing wallet connections and account selection. It provides a user interface for connecting to supported wallets, switching between accounts, copying addresses, and logging out. This component is used internally by the [Wallet](/docs/developing/api/tooling/wallet-js#wallet) component. Use this component if you need to open the wallet modal **programmatically** or create a **custom** button for account management. Unlike the generic `Wallet` component, this one gives you full control over the modal's behavior and appearance. Props [#props-1] * `theme` (`'gear' | 'vara'`, optional): UI theme for the modal. Defaults to `'vara'`. * `close` (`() => void`): Function to close the modal. Usage Example [#usage-example-1] ```jsx import { WalletModal } from '@gear-js/wallet-connect'; function CustomWalletButton() { const [isModalOpen, setIsModalOpen] = useState(false); const openModal = () => setIsModalOpen(true); const closeModal = () => setIsModalOpen(false); return ( <> {isModalOpen && } ); } ``` # X402 Protocol Vara Network supports the [X402 payment protocol](https://www.x402.org/), enabling developers to build monetizable APIs and services with seamless micropayments. Overview [#overview] X402 is an open standard for HTTP-based payments developed by Coinbase. It uses the HTTP 402 "Payment Required" status code to enable pay-per-request API access, content paywalls, and agentic commerce. When a client requests a paid resource, the server responds with a 402 status and payment details. The client then includes payment information via the `X-PAYMENT` header in subsequent requests. Key Features [#key-features] * **Native token support** — accepts both VARA and VFT (Vara Fungible Tokens) * **Framework integrations** — ready-to-use middleware for Express.js, Next.js, and Axios interceptors * **Low-cost micropayments** — Vara's sub-second block times and minimal fees make per-request payments practical * **Protocol compliance** — full compatibility with the X402 specification How It Works [#how-it-works] 1. Client requests a resource from a server 2. Server responds with `402 Payment Required` and payment instructions 3. Client prepares and submits a payment payload 4. Server verifies payment via the x402 facilitator's `/verify` and `/settle` endpoints 5. Upon successful verification, server provides the requested resource The facilitator handles payment verification and on-chain settlement, so developers don't need direct blockchain infrastructure. Use Cases [#use-cases] * **API monetization** — charge per request for AI/ML inference, data feeds, oracles, or computational services * **Content paywalls** — direct payments to content creators without intermediaries * **Agentic commerce** — enable AI agents to pay for API calls, data queries, and services autonomously Resources [#resources] | Resource | Link | | ----------------------------- | ---------------------------------------------------------------- | | x402-vara Package | [GitHub](https://github.com/gear-foundation/x402-vara) | | Next.js Demo Example | [GitHub](https://github.com/gear-foundation/x402-vara-next-demo) | | X402 Protocol Specification | [x402.org](https://www.x402.org/) | | More details and Announcement | [X Post](https://x.com/VaraNetwork/status/2009626274612793497) | # DEX Introduction [#introduction] A decentralized exchange (DEX) is a peer-to-peer marketplace where transactions occur directly between cryptocurrency traders. Unlike centralized exchanges like Binance, DEXs exclusively trade cryptocurrency tokens for other cryptocurrency tokens, without allowing exchanges between fiat and cryptocurrencies. Decentralized exchanges are essentially a set of smart contracts. They establish the prices of various cryptocurrencies algorithmically and use "liquidity pools," in which investors lock funds in exchange for interest-like rewards, to facilitate trades. While transactions on a centralized exchange are recorded in that exchange's internal database, DEX transactions are settled directly on the blockchain. DEXs are usually built on open-source code, allowing anyone interested to see exactly how they work. This also means that developers can adapt existing code to create new competing projects, as seen with Uniswap's code being adapted by various DEXs like Sushiswap and Pancakeswap. The source code, developed using the [Sails](/docs/developing/build/sails) framework, is available on [GitHub](https://github.com/gear-foundation/dapps/tree/master/contracts/tic-tac-toe). The exchange uses [Vara fungible tokens (VFT)](/docs/vara-network/examples/Standards/vft). This article describes the program interface, data structure, basic functions and explains their purpose. It can be used as is or modified to suit your own scenarios. Math [#math] DEXs use an automated market maker (AMM) model to manage trades. The pricing and liquidity mechanism is governed by the following formula: $$ reserve_0 \cdot reserve_1 = K $$ where: * **reserve\_0**: The reserve of token A in the liquidity pool. * **reserve\_1**: The reserve of token B in the liquidity pool. * **K**: A constant that must remain unchanged during swaps. Swap Mechanics [#swap-mechanics] When a user swaps token A for token B, the following process occurs: 1. **Input Token Addition**: The user deposits a certain amount of token A ( in\_amount ) into the pool, increasing ( reserve\_0 ). 2. **Output Token Calculation**: The output amount of token B ( out\_amount ) is calculated using the formula: $$ out\_amount = \frac{in\_amount \cdot reserve_1}{reserve_0 + in\_amount} $$ 3. **Reserve Updates**: The reserves are updated after the swap: * `reserve_0` increases by `in_amount`. * `reserve_1` decreases by `out_amount`. 4. **Constant Product**: The product `reserve_0 * reserve_1` remains unchanged during the swap, ensuring the AMM's balance and proper token pricing. Implementation details [#implementation-details] The core state of the DEX is encapsulated in the `Storage` structure. This data structure is central to the DEX's operations and tracks all essential information about reserves, liquidity, and tokens. ```rust title="dex/app/src/lib.rs" pub struct Storage { pub admin: ActorId, pub reserve_a: U256, pub reserve_b: U256, pub total_liquidity: U256, pub liquidity_providers: HashMap, pub token_a: ActorId, pub token_b: ActorId, pub k_last: U256, pub dns_info: Option<(ActorId, String)>, pub liquidity_action_gas: u64, pub swap_status: SwapStatus, } ``` * **admin**: The administrator of the DEX. * **reserve\_a** and **reserve\_b**: Current reserves of token A and token B. * **total\_liquidity**: Total liquidity in the pool. * **liquidity\_providers**: A mapping of liquidity providers and their respective contributions. * **token\_a** and **token\_b**: The identifiers (ActorId) for token A and token B. * **k\_last**: The product of reserves ( reserve\_a \cdot reserve\_b ), updated after liquidity changes. * **dns\_info**: Optional information for domain name system ([dDNS](/docs/vara-network/examples/Infra/dein)) integration. * **liquidity\_action\_gas**: Gas required for liquidity-related actions. * **swap\_status**: Status of the current swap operation. Events [#events] Events are emitted to notify external observers about significant actions on the DEX. The following events are defined: ```rust title="dex/app/src/lib.rs" pub enum Event { AddedLiquidity { sender: ActorId, amount_a: U256, amount_b: U256, liquidity: U256, }, RemovedLiquidity { sender: ActorId, amount_a: U256, amount_b: U256, to: ActorId, }, Swap { kind: SwapKind, sender: ActorId, in_amount: U256, out_amount: U256, }, Sync { reserve_a: U256, reserve_b: U256, }, Killed { inheritor: ActorId, }, } ``` Initialization [#initialization] The initialization function sets up the DEX, defining its key parameters such as the tokens it will handle and optional DNS registration. This function ensures that the DEX is in a consistent state before operations begin. ```rust title="dex/app/src/lib.rs" async fn init( token_a: ActorId, token_b: ActorId, liquidity_action_gas: u64, dns_id_and_name: Option<(ActorId, String)>, ) -> Self { unsafe { STORAGE = Some(Storage { admin: msg::source(), reserve_a: U256::zero(), reserve_b: U256::zero(), total_liquidity: U256::zero(), liquidity_providers: HashMap::new(), token_a, token_b, k_last: U256::zero(), dns_info: dns_id_and_name.clone(), liquidity_action_gas, swap_status: SwapStatus::Ready, }); } if let Some((id, name)) = dns_id_and_name { let request = [ "Dns".encode(), "AddNewProgram".to_string().encode(), (name, exec::program_id()).encode(), ] .concat(); msg::send_bytes_with_gas_for_reply(id, request, 5_000_000_000, 0, 0) .expect("Error in sending message") .await .expect("Error in `AddNewProgram`"); } Self(()) } ``` Adding Liquidity [#adding-liquidity] Adding liquidity allows users to contribute tokens to the pool, increasing its reserves and enabling more robust trading. This process involves updating the reserves and minting liquidity tokens proportional to the user's contribution. ```rust title="dex/app/src/lib.rs" pub async fn add_liquidity(&mut self, amount_a: U256, amount_b: U256) -> bool { let storage = self.get_mut(); if storage.swap_status != SwapStatus::Ready { panic!("Swap status is incorrect"); } if exec::gas_available() < storage.liquidity_action_gas { panic!("Not enough gas; requires a least: {:?}", storage.liquidity_action_gas); } let sender = msg::source(); let program_id = exec::program_id(); if amount_a.is_zero() || amount_b.is_zero() { panic!("Amounts must be greater than zero"); } let first_time = storage.reserve_a.is_zero() && storage.reserve_b.is_zero(); let liquidity = if first_time { // Initial liquidity let liquidity = (amount_a * amount_b).integer_sqrt(); if liquidity < MINIMUM_LIQUIDITY.into() { panic!("Liquidity is low"); } liquidity } else { // Ensure tokens are added in correct proportions let expected_b = (amount_a * storage.reserve_b) / storage.reserve_a; let expected_a = (amount_b * storage.reserve_a) / storage.reserve_b; if amount_b != expected_b && amount_a != expected_a { panic!("Tokens must be provided in correct proportions"); } let liquidity = U256::min( (amount_a * storage.total_liquidity) / storage.reserve_a, (amount_b * storage.total_liquidity) / storage.reserve_b, ); if liquidity.is_zero() { panic!("Insufficient liquidity minted"); } liquidity }; storage.swap_status = SwapStatus::Paused; // Transfer tokens to contract let request_a = vft_io::TransferFrom::encode_call(sender, program_id, amount_a); msg::send_bytes_with_gas_for_reply(storage.token_a, request_a, 5_000_000_000, 0, 5_000_000_000) .expect("Error in async message to vft contract") .up_to(Some(5)) .expect("Reply timeout") .handle_reply(|| { let reply_bytes = msg::load_bytes().expect("Unable to load bytes"); let result = vft_io::TransferFrom::decode_reply(reply_bytes); if result.is_err() { let storage = unsafe { STORAGE.as_mut().expect("Dex is not initialized") }; storage.swap_status = SwapStatus::Ready; } }) .expect("Reply hook error") .await .expect("Error getting answer from the vft contract"); let request_b = vft_io::TransferFrom::encode_call(sender, program_id, amount_b); if let Err(_e) = msg::send_bytes_with_gas_for_reply(storage.token_b, request_b, 5_000_000_000, 0, 0) .expect("Error in async message to vft contract") .await { let request = vft_io::Transfer::encode_call(sender, amount_a); msg::send_bytes_with_gas_for_reply(storage.token_a, request, 5_000_000_000, 0, 0) .expect("Error in async message to vft contract") .await .expect("Error getting answer from the vft contract"); storage.swap_status = SwapStatus::Ready; false } else { if first_time { let liquidity_to_mint = liquidity - MINIMUM_LIQUIDITY; storage.reserve_a = amount_a; storage.reserve_b = amount_b; storage.total_liquidity = liquidity_to_mint; storage.liquidity_providers.insert(sender, liquidity_to_mint); } else { storage.reserve_a += amount_a; storage.reserve_b += amount_b; storage.total_liquidity += liquidity; let user_liquidity = storage .liquidity_providers .entry(sender) .or_insert(U256::zero()); *user_liquidity += liquidity; } storage.k_last = storage.reserve_a * storage.reserve_b; storage.swap_status = SwapStatus::Ready; self.emit_event(Event::AddedLiquidity { sender, amount_a, amount_b, liquidity: storage.total_liquidity, }) .expect("Notification Error"); true } } ``` * Proportional Contribution: Users must provide both tokens in the same ratio as the current reserves to maintain balance. * Initial Liquidity: When the pool is empty, the first user to provide liquidity sets the initial reserves and creates the baseline price. Removing Liquidity [#removing-liquidity] Removing liquidity allows users to withdraw their contributed tokens along with a proportional share of the trading fees collected in the pool. ```rust title="dex/app/src/lib.rs" pub async fn remove_liquidity(&mut self, amount: U256) { let storage = self.get_mut(); if storage.swap_status != SwapStatus::Ready { panic!("Swap status is incorrect"); } if exec::gas_available() < storage.liquidity_action_gas { panic!("Not enough gas; requires a least: {:?}", storage.liquidity_action_gas); } let sender = msg::source(); let user_liquidity = storage .liquidity_providers .get_mut(&sender) .expect("No liquidity"); if *user_liquidity < amount { panic!("Insufficient liquidity"); } let amount_a = (amount * storage.reserve_a) / storage.total_liquidity; let amount_b = (amount * storage.reserve_b) / storage.total_liquidity; if storage.reserve_a < amount_a || storage.reserve_b < amount_b { panic!("Insufficient contract balance for token transfer"); } storage.swap_status = SwapStatus::Paused; // Transfer tokens back to the user let request_a = vft_io::Transfer::encode_call(sender, amount_a); msg::send_bytes_with_gas_for_reply(storage.token_a, request_a, 5_000_000_000, 0, 0) .expect("Error in async message to vft contract") .await .expect("Error getting answer from the vft contract"); let request_b = vft_io::Transfer::encode_call(sender, amount_b); msg::send_bytes_with_gas_for_reply(storage.token_b, request_b, 5_000_000_000, 0, 0) .expect("Error in async message to vft contract") .await .expect("Error getting answer from the vft contract"); storage.reserve_a -= amount_a; storage.reserve_b -= amount_b; storage.total_liquidity -= amount; *user_liquidity -= amount; storage.k_last = storage.reserve_a * storage.reserve_b; storage.swap_status = SwapStatus::Ready; self.emit_event(Event::RemovedLiquidity { sender, amount_a, amount_b, to: sender, }) .expect("Notification Error"); } ``` * Proportional Withdrawal: The amount of tokens a user receives is proportional to their liquidity share. * Updating Reserves: The reserves are reduced to reflect the tokens removed from the pool. Token Swapping [#token-swapping] Token swapping is the core feature of a DEX, enabling users to exchange one token for another while maintaining the constant product formula. ```rust title="dex/app/src/lib.rs" pub async fn swap(&mut self, in_amount: U256, out_is_a: bool) { let storage = self.get_mut(); let sender = msg::source(); let program_id = exec::program_id(); if storage.swap_status != SwapStatus::Ready { panic!("Swap status is incorrect"); } let (in_token, out_token, in_reserve, out_reserve) = if out_is_a { ( storage.token_b, storage.token_a, &mut storage.reserve_b, &mut storage.reserve_a, ) } else { ( storage.token_a, storage.token_b, &mut storage.reserve_a, &mut storage.reserve_b, ) }; // Ensure the input amount is greater than zero if in_amount == U256::zero() { panic!("Input amount must be greater than zero"); } // Ensure reserves are sufficient for the swap if *in_reserve == U256::zero() || *out_reserve == U256::zero() { panic!("Insufficient reserves for swap"); } let out_amount = (in_amount * *out_reserve) / (*in_reserve + in_amount); // Ensure the reserves are sufficient to cover the output if out_amount > *out_reserve { panic!("Insufficient output reserves"); } storage.swap_status = SwapStatus::Paused; // Transfer the input tokens to the contract let request_in = vft_io::TransferFrom::encode_call(sender, program_id, in_amount); msg::send_bytes_with_gas_for_reply(in_token, request_in, 5_000_000_000, 0, 5_000_000_000) .expect("Error in async message to vft contract") .up_to(Some(5)) .expect("Reply timeout") .handle_reply(|| { let reply_bytes = msg::load_bytes().expect("Unable to load bytes"); let result = vft_io::TransferFrom::decode_reply(reply_bytes); if result.is_err() { let storage = unsafe { STORAGE.as_mut().expect("Dex is not initialized") }; storage.swap_status = SwapStatus::Ready; } }) .expect("Reply hook error") .await .expect("Error getting answer from the vft contract"); // Transfer the output tokens to the user let request_out = vft_io::TransferFrom::encode_call(program_id, sender, out_amount); msg::send_bytes_with_gas_for_reply(out_token, request_out, 5_000_000_000, 0, 5_000_000_000) .expect("Error in async message to vft contract") .up_to(Some(5)) .expect("Reply timeout") .handle_reply(move || handle_reply_hook_for_output_tokens(out_token, sender, in_amount, out_amount, out_is_a)) .expect("Reply hook error") .await .expect("Error getting answer from the vft contract"); *in_reserve += in_amount; *out_reserve -= out_amount; storage.swap_status = SwapStatus::Ready; self.emit_event(Event::Swap { kind: if out_is_a { SwapKind::AForB } else { SwapKind::BForA }, sender, in_amount, out_amount, }) .expect("Notification Error"); } ``` * Dynamic Pricing: The price of tokens adjusts dynamically based on the trade size and pool reserves. * Constant Product Formula: Ensures that the product of reserves remains constant after every swap. * Reserve Updates: Updates the reserves to reflect the new state of the pool after the trade. Continue Token Swapping [#continue-token-swapping] The `continue_swap` function is designed to handle scenarios where a token swap operation encounters an intermediate error or requires additional steps to complete. This function ensures the swap process is resumed and finalized correctly. ```rust title="dex/app/src/lib.rs" pub async fn continue_swap(&mut self) { let storage = self.get_mut(); let (to, in_amount, out_amount, out_is_a) = match storage.swap_status { SwapStatus::TokenTransferError { out_token, to, in_amount, out_amount, out_is_a } => { // Transfer the output tokens to the user let request_out = vft_io::TransferFrom::encode_call(exec::program_id(), to, out_amount); msg::send_bytes_with_gas_for_reply(out_token, request_out, 5_000_000_000, 0, 0) .expect("Error in async message to vft contract") .await .expect("Error getting answer from the vft contract"); (to, in_amount, out_amount, out_is_a) } SwapStatus::TokenTransferOk { to, in_amount, out_amount, out_is_a } => (to, in_amount, out_amount, out_is_a), _ => panic!("Swap status is incorrect") }; if out_is_a { storage.reserve_b += in_amount; storage.reserve_a -= out_amount; } else { storage.reserve_a += in_amount; storage.reserve_b -= out_amount; }; storage.swap_status = SwapStatus::Ready; self.emit_event(Event::Swap { kind: if out_is_a { SwapKind::AForB } else { SwapKind::BForA }, sender: to, in_amount, out_amount, }) .expect("Notification Error"); } ``` Key Use Cases: * Error Recovery: Ensures the system can gracefully recover from temporary token transfer issues without disrupting the DEX's operation. * Swap Finalization: Provides a mechanism to finalize partially completed swaps, ensuring the reserves and state remain consistent. This function is critical for maintaining reliability in token swaps, especially in environments where asynchronous operations or network delays may cause transient errors. Synchronizing Reserves [#synchronizing-reserves] This function ensures that the reserves stored in the contract match the actual token balances held in the DEX. It is typically called to rectify discrepancies caused by external factors. ```rust title="dex/app/src/lib.rs" pub async fn sync(&mut self) { let storage = self.get_mut(); // Fetch the current balance of token A in the contract let request = vft_io::BalanceOf::encode_call(exec::program_id()); let bytes_reply_balances = msg::send_bytes_for_reply(storage.token_a, request.clone(), 0, 0) .expect("Error in async message to vft contract") .await .expect("Error getting answer from the vft contract"); let balances_a: U256 = vft_io::BalanceOf::decode_reply(bytes_reply_balances).unwrap(); // Fetch the current balance of token B in the contract let bytes_reply_balances = msg::send_bytes_for_reply(storage.token_b, request, 0, 0) .expect("Error in async message to vft contract") .await .expect("Error getting answer from the vft contract"); let balances_b: U256 = vft_io::BalanceOf::decode_reply(bytes_reply_balances).unwrap(); storage.reserve_a = balances_a; storage.reserve_b = balances_b; self.emit_event(Event::Sync { reserve_a: storage.reserve_a, reserve_b: storage.reserve_b, }) .expect("Notification Error"); } ``` Query [#query] ```rust title="dex/app/src/lib.rs" pub fn admin(&self) -> ActorId { self.get().admin } pub fn reserve_a(&self) -> U256 { self.get().reserve_a } pub fn reserve_b(&self) -> U256 { self.get().reserve_b } pub fn total_liquidity(&self) -> U256 { self.get().total_liquidity } pub fn liquidity_providers(&self) -> Vec<(ActorId, U256)> { self.get().liquidity_providers.clone().into_iter().collect() } pub fn token_a(&self) -> ActorId { self.get().token_a } pub fn token_b(&self) -> ActorId { self.get().token_b } pub fn dns_info(&self) -> Option<(ActorId, String)> { self.get().dns_info.clone() } pub fn swap_status(&self) -> SwapStatus { self.get().swap_status } pub fn liquidity_action_gas(&self) -> u64 { self.get().liquidity_action_gas } ``` * `admin`: Returns the address of the DEX administrator. * `reserve_a`: Retrieves the current reserve of token A in the liquidity pool. * `reserve_b`: Retrieves the current reserve of token B in the liquidity pool. * `total_liquidity`: Provides the total liquidity available in the pool. * `liquidity_providers`: Returns a list of liquidity providers and their respective contributions. * `token_a`: Retrieves the identifier of token A used in the DEX. * `token_b`: Retrieves the identifier of token B used in the DEX. * `dns_info`: Returns optional DNS information for the DEX, if available. * `swap_status`: Provides the current status of the swap operation. * `liquidity_action_gas`: Retrieves the gas required for liquidity-related actions. Source Code [#source-code] The source code of this DEX factory program example and its testing implementation is available on [gear-foundation/dapps/dex](https://github.com/gear-foundation/dapps/tree/master/contracts/dex). See also an example of the program testing implementation based on `gtest`: [gear-foundation/dapps/dex/tests](https://github.com/gear-foundation/dapps/tree/master/contracts/dex/tests). For more details about testing programs written on Vara, refer to the [Program Testing](/docs/developing/build/testing) article. # Multisig Wallet Introduction [#introduction] Multisignature wallets are cryptocurrency wallets that require multiple private keys to sign and authorize a transaction. To illustrate, think of a bank vault that demands multiple keys to unlock; this analogy captures how multisignature cryptocurrency wallets operate. Advocates of multisignature wallets argue that they offer the most secure and fail-proof method for storing cryptocurrency. Even if a thief were to obtain one of the wallet keys, they would still be unable to access the account without the keys associated with the other wallets in the setup. This article provides an explanation of the programming interface, data structure, basic functions, and their respective purposes. It can be used as-is or customized to fit specific needs. Anyone can create an application and deploy it on the Gear Network. The source code, developed using the [Sails](/docs/developing/build/sails) framework, is available on [GitHub](https://github.com/gear-foundation/dapps/tree/master/contracts/multisig-wallet). Logic [#logic] The wallet belongs to one or more owners. For any significant action to occur, the necessary number of owners must confirm it. The contract deployer can determine the permitted number of owners who can initiate a transaction from the wallet and specify the minimum required owners for the transaction. For instance, they can opt for a 2-of-3 multisig, where two out of three assigned private keys are necessary. The flexibility extends to configurations like 3-of-5, 5-of-7, and so forth. To send a transaction through the multisig wallet, one of the owners should send a transaction to the wallet with a `SubmitTransaction` action in the payload, and other owners should approve this transaction by the `ConfirmTransaction` action until the required amount is reached. The transaction description allows the owner to include helpful information. > The wallet offers flexibility, enabling users to oversee the list of owners and determine the necessary confirmations. Security is a priority in the contract, restricting actions like adding, removing, or replacing owners and altering confirmation counts to only be executed with the requisite confirmations from other owners. The transaction approval logic is complex, for example: 1. When the owner submits a transaction requiring only one confirmation for execution, the contract adds it to the storage, obtains confirmation from the submitting owner, and then automatically executes the transaction. 2. For transactions needing two or more confirmations, the contract follows a similar process. Initially, it adds the transaction to the storage and secures confirmation from the submitting owner. To execute the transaction, the wallet still requires one or more confirmations. Subsequently, another owner sends a `ConfirmTransaction` action to the contract. If all is well, the transaction is executed automatically. > Transactions usually happen automatically when all confirmations are done. However, if a transaction was confirmed `n` times, and the contract needs `n + 1` or more confirmations, and then the owners change it to `n` or less, they can either wait for the next confirmation or just call `ExecuteTransaction` with the transaction ID to execute it. Interface [#interface] Program description [#program-description] The program contains the following information ```rust title="multisig-wallet/app/src/utils.rs" pub struct MultisigWallet { pub transactions: HashMap, pub confirmations: HashMap>, pub owners: HashSet, pub required: u32, pub transaction_count: U256, } ``` * `transactions` - a mapping of `TransactionId` to `Transaction` that stores all transactions in the wallet * `confirmations` - a mapping of `TransactionId` to a set of `ActorIds`, representing confirmations by each owner for specific transactions * `owners` - a set of `ActorIds` representing the authorized owners of the wallet * `required` - the number of confirmations required to execute a transaction * `transaction_count` - a counter tracking the number of transactions Where `Transaction` is defined as follows: ```rust title="multisig-wallet/app/src/utils.rs" pub struct Transaction { pub destination: ActorId, pub payload: Vec, pub value: u128, pub description: Option, pub executed: bool, } ``` * `destination` - the account where the transaction funds or actions are directed * `payload` - data related to the transaction, potentially containing specific instructions or actions * `value` - the amount of funds or value to transfer * `description` - an optional description providing context for the transaction * `executed` - a boolean indicating if the transaction has been executed Initialization [#initialization] During program initialization, the following variables are set: the list of wallet owners and the minimum number of confirmations required to execute a transaction. ```rust title="multisig-wallet/app/src/lib.rs" fn init(owners: Vec, required: u32) -> Self { let owners_count = owners.len(); validate_requirement(owners_count, required); let mut wallet = MultisigWallet::default(); for owner in &owners { if wallet.owners.contains(owner) { panic!("The same owner contained twice") } else { wallet.owners.insert(*owner); } } wallet.required = required; unsafe { STORAGE = Some(wallet) }; Self(()) } ``` Actions [#actions] ```rust title="multisig-wallet/app/src/lib.rs" pub fn add_owner(&mut self, owner: ActorId); pub fn remove_owner(&mut self, owner: ActorId); pub fn replace_owner(&mut self, old_owner: ActorId, new_owner: ActorId); pub fn change_required_confirmations_count(&mut self, count: u32); pub fn submit_transaction( &mut self, destination: ActorId, data: Vec, value: u128, description: Option, ); pub fn confirm_transaction(&mut self, transaction_id: U256); pub fn revoke_confirmation(&mut self, transaction_id: U256); pub fn execute_transaction(&mut self, transaction_id: U256); ``` * `add_owner` is an action to add a new owner. The action has to be used through `submit_transaction`. * `remove_owner` is an action to remove an owner. The action has to be used through `submit_transaction`. * `replace_owner` is an action to replace an owner with a new owner. The action has to be used through `submit_transaction`. * `change_required_confirmations_count` is an action to change the number of required confirmations. The action has to be used through `submit_transaction`. * `submit_transaction` is an action that allows an owner to submit and automatically confirm a transaction. * `confirm_transaction` is an action that allows an owner to confirm a transaction. If this is the last confirmation, the transaction is automatically executed. * `revoke_confirmation` is an action that allows an owner to revoke a confirmation for a transaction. * `execute_transaction` is an action that allows anyone to execute a confirmed transaction. Events [#events] ```rust title="multisig-wallet/app/src/lib.rs" pub enum Event { Confirmation { sender: ActorId, transaction_id: U256, }, Revocation { sender: ActorId, transaction_id: U256, }, Submission { transaction_id: U256, }, Execution { transaction_id: U256, }, OwnerAddition { owner: ActorId, }, OwnerRemoval { owner: ActorId, }, OwnerReplace { old_owner: ActorId, new_owner: ActorId, }, RequirementChange(U256), } ``` * `Confirmation` is an event that occurs when the `ConfirmTransaction` action is successfully used. * `Revocation` is an event that occurs when the `RevokeConfirmation` action is successfully used. * `Submission` is an event that occurs when the `SubmitTransaction` action is successfully used. * `Execution` is an event that occurs when the `ExecuteTransaction` action is successfully used. * `OwnerAddition` is an event that occurs when the wallet uses the `AddOwner` action successfully. * `OwnerRemoval` is an event that occurs when the wallet uses the `RemoveOwner` action successfully. * `OwnerReplace` is an event that occurs when the wallet uses the `ReplaceOwner` action successfully. * `RequirementChange` is an event that occurs when the wallet uses the `ChangeRequiredConfirmationsCount` action successfully. Query [#query] ```rust title="multisig-wallet/app/src/lib.rs" pub fn get_state(&self) -> State { self.get().clone().into() } ``` Where `State` is defined as follows: ```rust title="multisig-wallet/app/src/utils.rs" pub struct State { pub transactions: Vec<(TransactionId, Transaction)>, pub confirmations: Vec<(TransactionId, Vec)>, pub owners: Vec, pub required: u32, pub transaction_count: U256, } ``` Source Code [#source-code] The source code of this example of a Multisig Wallet program and an implementation of its testing is available on [GitHub](https://github.com/gear-foundation/dapps/tree/master/contracts/multisig-wallet). See also an example of the program testing implementation based on `gtest`: [multisig-wallet/tests](https://github.com/gear-foundation/dapps/tree/master/contracts/multisig-wallet/tests). For more details about testing program written on Vara, refer to the [Program Testing](/docs/developing/build/testing) article. # Staking Introduction [#introduction] Staking is an analogue of a bank deposit, providing passive earnings through the simple storage of cryptocurrency tokens. The percentage of income may vary depending on the term of the deposit. Anyone can create a Staking program and run it on the Gear Network. The source code, developed using the [Sails](/docs/developing/build/sails) framework, is available on [GitHub](https://github.com/gear-foundation/dapps/tree/master/contracts/staking). The staking program interacts with the [Vara Fungible Token (VFT)](/docs/vara-network/examples/Standards/vft) standard to handle token transfers seamlessly, enabling staking deposits, reward distributions, and withdrawals through secure and efficient transfer operations. These transfers are executed via asynchronous calls to the VFT contract, ensuring accurate state synchronization and error handling during token movements. This article explains the programming interface, data structure, basic functions, and their purposes. The program can be used as-is or modified to suit specific scenarios. Mathematics [#mathematics] Tokens can be deposited into the staking program and later claimed, along with rewards. Staking involves depositing fungible tokens into a program to earn rewards. These rewards, minted at regular intervals (e.g., per minute), are distributed equitably among all stakers. How Staking Works [#how-staking-works] Consider Alice staking 100 tokens and Bob staking 50 tokens. If reward tokens are minted every minute, and after one week Alice decides to unstake her tokens, the total tokens in the staking program remain 150. The duration of Alice's staking period is 7 days, and the reward tokens accumulated can be calculated based on this timeframe: $$ R ⋅ \frac {100} {150} ⋅ 7 ⋅ 24 ⋅ 60 $$ A week later, Bob chooses to unstake his 50 tokens. In the initial week, he staked 50 tokens out of 150. In the second week, he staked 50 tokens out of 50. Here’s how to determine his reward: $$ R ⋅ (\frac {50} {150} + \frac {50} {50}) ⋅ 7 ⋅ 24 ⋅ 60 $$ The formula can be generalized as: $$ r(a, b) = R\sum_{t=a}^b \frac {l(t)} {L(t)} $$ where: * $r(a, b)$ - reward for the user for the time interval $a \le t \le b$; * $R$ - rewards minted per minute; * $L(t)$ - total staked amount of tokens at time $t$; * $l(t)$ - tokens staked by the user at time $t$. To apply the formula, $l(t)$ for each user and time interval, and $L(t)$ for each time interval must be stored. Calculating a reward involves executing a loop for each time interval, consuming significant gas and storage. A more efficient approach is feasible: If $l(t)$ for a user is constant $k$ for $a \le t \le b$, then: $$ r(a, b) = R\sum_{t=a}^b \frac {l(t)} {L(t)} = Rk\sum_{t=a}^b \frac {1} {L(t)} $$ This can be further simplified: $$ \sum_{t=a}^b \frac {1} {L(t)} = \frac {1} {L(a)} + \frac {1} {L(a + 1)} + ... + \frac {1} {L(b)} = $$ $$ \frac {1} {L(0)} + \frac {1} {L(1)} + ... + \frac {1} {L(b)} - (\frac {1} {L(0)} + \frac {1} {L(1)} + ... + \frac {1} {L(a - 1)}) = $$ $$ \sum_{t=0}^b \frac {1} {L(t)} - \sum_{t=0}^{a-1} \frac {1} {L(t)} $$ So, the equation to calculate the reward that a user will receive from t=a to t=b under the condition that the number of tokens staked is constant: $$ Rk\sum_{t=a}^b \frac {1} {L(t)} = Rk(\sum_{t=0}^b \frac {1} {L(t)} - \sum_{t=0}^{a-1} \frac {1} {L(t)}) $$ Based on that equation, the implementation in the program can be written as: ```rust (staker.balance * self.tokens_per_stake) / DECIMALS_FACTOR + staker.reward_allowed - staker.reward_debt - staker.distributed ``` Program Description [#program-description] The staking program is initialized by the admin using the `StakingProgram::new` method, which sets up the reward token address, the distribution time, and the total reward to be distributed. The admin can view the list of stakers and their staking details using the `stakers()` method and can update the reward token address, distribution time, or total reward through the `update_staking` function. Users can participate in the program by staking tokens using the `stake` function, which transfers tokens from the user's account to the contract and updates their staking balance. Accumulated rewards can be claimed on demand via the `get_reward` function. Users can also partially or fully withdraw their staked tokens using the `withdraw` function, which adjusts their balance and returns the specified tokens to their account. Structs [#structs] The program has the following structs: ```rust title="staking/app/src/lib.rs" struct Staking { owner: ActorId, reward_token_address: ActorId, tokens_per_stake: u128, total_staked: u128, distribution_time: u64, produced_time: u64, reward_total: u128, all_produced: u128, reward_produced: u128, stakers: HashMap, } ``` where: * `owner` - the owner of the staking program * `reward_token_address` - address of the reward token program * `tokens_per_stake` - the calculated value of tokens per stake * `total_staked` - total amount of deposits * `distribution_time` - time of distribution of reward * `reward_total` - the reward to be distributed within distribution time * `produced_time` - time of `reward_total` update * `all_produced` - the reward received before the update `reward_total` * `reward_produced` - the reward produced so far * `stakers` - map of the stakers ```rust title="staking/app/src/lib.rs" pub struct Staker { pub balance: u128, pub reward_allowed: u128, pub reward_debt: u128, pub distributed: u128, } ``` where: * `balance` - staked amount * `reward_allowed` - the reward that could have been received from the withdrawn amount * `reward_debt` - the reward that the depositor would have received if he had initially paid this amount * `distributed` - total remuneration paid Event [#event] ```rust title="staking/app/src/lib.rs" pub enum Event { StakeAccepted(u128), Updated, Reward(u128), Withdrawn(u128), } ``` Functions [#functions] Thе `produced` function determines the total rewards generated since the last update. It ensures the reward does not exceed the total defined by the `distribution_time`. ```rust title="staking/app/src/lib.rs" fn produced(&mut self) -> u128 { let mut elapsed_time = exec::block_timestamp() - self.produced_time; if elapsed_time > self.distribution_time { elapsed_time = self.distribution_time; } self.all_produced + self.reward_total.saturating_mul(elapsed_time as u128) / self.distribution_time as u128 } ``` Thе `update_reward` function updates the state to reflect the latest reward distribution and recalculates the `tokens_per_stake` based on new rewards and the total staked amount. ```rust title="staking/app/src/lib.rs" fn update_reward(&mut self) { let reward_produced_at_now = self.produced(); if reward_produced_at_now > self.reward_produced { let produced_new = reward_produced_at_now - self.reward_produced; if self.total_staked > 0 { self.tokens_per_stake = self .tokens_per_stake .saturating_add((produced_new * DECIMALS_FACTOR) / self.total_staked); } self.reward_produced = self.reward_produced.saturating_add(produced_new); } } ``` Thе `get_max_reward` function calculates the theoretical maximum reward for a given staked amount based on `tokens_per_stake`. ```rust title="staking/app/src/lib.rs" fn get_max_reward(&self, amount: u128) -> u128 { (amount * self.tokens_per_stake) / DECIMALS_FACTOR } ``` Thе `calc_reward` function the reward that a specific staker is eligible to claim. It factors in: * Current staked balance. * Rewards already allowed, debt, and previously distributed amounts. ```rust title="staking/app/src/lib.rs" fn calc_reward(&self, id: &ActorId) -> u128 { match self.stakers.get(id) { Some(staker) => { self.get_max_reward(staker.balance) + staker.reward_allowed - staker.reward_debt - staker.distributed } None => panic!("Staker not found"), } } ``` Thе `update_staking` function allows the admin to modify key parameters like the reward token, distribution time, and total reward. Resets reward tracking fields and recalculates staking-related variables. It ensures the program remains adaptable to changing requirements. ```rust title="staking/app/src/lib.rs" pub fn update_staking( &mut self, reward_token_address: ActorId, distribution_time: u64, reward_total: u128, ) { if reward_total == 0 { panic!("Reward is zero"); } if distribution_time == 0 { panic!("Distribution time is zero"); } let storage = self.get_mut(); if msg::source() != storage.owner { panic!("Not owner"); } storage.reward_token_address = reward_token_address; storage.distribution_time = distribution_time; storage.update_reward(); storage.all_produced = storage.reward_produced; storage.produced_time = exec::block_timestamp(); storage.reward_total = reward_total; self.emit_event(Event::Updated).expect("Notification Error"); } ``` The `stake` function enables users to deposit tokens into the staking program. Its operations include: * Transferring the specified amount of tokens from the user's account to the contract. * Updating the user's staking balance and associated reward debt in the program's state. ```rust title="staking/app/src/lib.rs" pub async fn stake(&mut self, amount: u128) { if amount == 0 { panic!("Amount is zero"); } let storage = self.get_mut(); let msg_src = msg::source(); let request = vft_io::TransferFrom::encode_call(msg_src, exec::program_id(), amount.into()); msg::send_bytes_with_gas_for_reply( storage.reward_token_address, request, 5_000_000_000, 0, 0, ) .expect("Error in sending a message") .await .expect("Error in transfer Fungible Token"); storage.update_reward(); let amount_per_token = storage.get_max_reward(amount); storage .stakers .entry(msg_src) .and_modify(|stake| { stake.reward_debt = stake.reward_debt.saturating_add(amount_per_token); stake.balance = stake.balance.saturating_add(amount); }) .or_insert(Staker { reward_debt: amount_per_token, balance: amount, ..Default::default() }); storage.total_staked = storage.total_staked.saturating_add(amount); self.emit_event(Event::StakeAccepted(amount)) .expect("Notification Error"); } ``` The `get_reward` function enables users to claim their accumulated rewards. Its operations include: * Calculating the eligible reward amount using the `calc_reward` method. * Transferring the calculated reward to the user's account. * Updating the program's state to reflect the distribution of the reward. ```rust title="staking/app/src/lib.rs" pub async fn get_reward(&mut self) { let storage = self.get_mut(); storage.update_reward(); let msg_src = msg::source(); let reward = storage.calc_reward(&msg_src); if reward == 0 { panic!("Zero reward") } let request = vft_io::Transfer::encode_call(msg_src, reward.into()); msg::send_bytes_with_gas_for_reply( storage.reward_token_address, request, 5_000_000_000, 0, 0, ) .expect("Error in sending a message") .await .expect("Error in transfer Fungible Token"); storage .stakers .entry(msg::source()) .and_modify(|stake| stake.distributed = stake.distributed.saturating_add(reward)); self.emit_event(Event::Reward(reward)) .expect("Notification Error"); } ``` The `withdraw` function enables users to withdraw their staked tokens, either partially or in full. Its operations include: * Validating the requested withdrawal amount to ensure it does not exceed the user’s available balance. * Transferring the specified amount of tokens back to the user’s account. * Updating the user’s staking balance and associated reward debt in the program's state. ```rust title="staking/app/src/lib.rs" pub async fn withdraw(&mut self, amount: u128) { if amount == 0 { panic!("Amount is zero"); } let storage = self.get_mut(); storage.update_reward(); let amount_per_token = storage.get_max_reward(amount); let msg_src = msg::source(); let staker = storage.stakers.get_mut(&msg_src).expect("Staker not found"); if staker.balance < amount { panic!("Insufficent balance"); } let request = vft_io::Transfer::encode_call(msg_src, amount.into()); msg::send_bytes_with_gas_for_reply( storage.reward_token_address, request, 5_000_000_000, 0, 0, ) .expect("Error in sending a message") .await .expect("Error in transfer Fungible Token"); staker.reward_allowed = staker.reward_allowed.saturating_add(amount_per_token); staker.balance = staker.balance.saturating_sub(amount); storage.total_staked = storage.total_staked.saturating_sub(amount); self.emit_event(Event::Withdrawn(amount)) .expect("Notification Error"); } ``` Query The following functions are provided to read the current state of the staking program. These queries allow both administrators and users to retrieve information about the program's configuration, status, and individual staking details without modifying its state: ```rust title="tic-tac-toe/app/services/game/mod.rs" pub fn owner(&self) -> ActorId { self.get().owner } pub fn reward_token_address(&self) -> ActorId { self.get().reward_token_address } pub fn tokens_per_stake(&self) -> u128 { self.get().tokens_per_stake } pub fn total_staked(&self) -> u128 { self.get().total_staked } pub fn distribution_time(&self) -> u64 { self.get().distribution_time } pub fn produced_time(&self) -> u64 { self.get().produced_time } pub fn reward_total(&self) -> u128 { self.get().reward_total } pub fn all_produced(&self) -> u128 { self.get().all_produced } pub fn reward_produced(&self) -> u128 { self.get().reward_produced } pub fn stakers(&self) -> Vec<(ActorId, Staker)> { self.get().stakers.clone().into_iter().collect() } pub fn calc_reward(&self, id: ActorId) -> u128 { self.get().calc_reward(&id) } ``` * `owner`: Returns the `ActorId` of the staking program's owner. * `reward_token_address`: Retrieves the `ActorId` of the reward token associated with the staking program. * `tokens_per_stake`: Returns the number of reward tokens allocated per unit of stake, adjusted dynamically. * `total_staked`: Provides the total amount of tokens currently staked in the program. * `distribution_time`: Returns the time period (in milliseconds) over which rewards are distributed. * `produced_time`: Retrieves the timestamp of the last reward calculation or update. * `reward_total`: Returns the total reward allocated for the entire distribution period. * `all_produced`: Provides the cumulative rewards generated before the most recent reward update. * `reward_produced`: Returns the total rewards produced up to the current moment. * `stakers`: Retrieves a list of all stakers, including their `ActorId` and corresponding staking details. * `calc_reward`: Calculates and returns the reward amount available for a specific staker identified by their `ActorId`. Conclusion [#conclusion] The source code of this example of a staking program is available on GitHub: [gear-foundation/dapp/contracts/staking](https://github.com/gear-foundation/dapps/tree/master/contracts/staking). See also an example of the smart contract testing implementation based on `gtest`: [gear-foundation/dapps/contracts/staking/tests](https://github.com/gear-foundation/dapps/tree/master/contracts/staking/tests). For more details about testing programs written on Vara, refer to this article: [Program testing](/docs/developing/build/testing). # Web3 Warriors The Battle game is a strategic, turn-based PvP (player-versus-player) game where players compete in combat rounds, using moves like attacks, dodges, and special abilities to outmaneuver and defeat their opponents. Each player’s stats, including attack, defense, and dodge skills, affect their chance of success in the match. The game offers a dynamic experience where players build up their characters (warriors) and participate in battles to achieve victory. This project introduces unique features, such as [gas reservations](/docs/developing/build/gstd/gas-reservation) and [delayed messaging](/docs/developing/build/gstd/delayed-messages), to make the gameplay more automated and engaging. Gas reservation ensures that each player reserves the necessary resources to cover the gas costs of their moves in advance, maintaining the fluidity of the game even when players might not make a move on time. Delayed messages then trigger automated actions if a player doesn't make their move within the time limit, ensuring that the match progresses without interruptions. This setup keeps battles smooth and fair, allowing each round to continue seamlessly and creating a more immersive gaming experience. Messaging automation can also be read about in this [article](/docs/vara-network/about/features/message-automation). Battle The source code, developed using the [Sails](/docs/developing/build/sails) framework, is available on [GitHub](https://github.com/gear-foundation/dapps/tree/master/contracts/battle). The [frontend application](https://github.com/gear-foundation/dapps/tree/master/frontend/apps/web3-warriors-battle) facilitates gameplay and interacts with the smart program. This article describes the program interface, data structure, basic functions and explains their purpose. It can be used as is or modified to suit your own scenarios. Warrior [#warrior] Warriors are the main characters in the Battle, representing programs uploaded to the Vara network. To start or join a game, a player must specify their Warrior during registration or when creating a battle. There are two ways to select a player’s Warrior: 1. Warriors can be generated directly from the Battle UI with randomly selected appearance options, which are then used by the Battle program. 2. Alternatively, players can specify their own Warrior program address within the Battle game. This option allows players to develop and upload unique Warriors for participation in battles, ensuring each Warrior reflects the creativity of its creator. Those interested in crafting their own Warriors can use the example program as a foundation, and build it following the instructions provided in the [README](https://github.com/gear-foundation/dapps/tree/master/contracts/battle/warrior). To customize the Warrior’s appearance, simply modify parameters in the [`lib.rs` file](https://github.com/gear-foundation/dapps/blob/master/contracts/battle/warrior/app/src/lib.rs). For example: ```rust appearance: Appearance { head_index: 1, hat_index: 2, body_index: 3, accessory_index: 4, body_color: "#008000".to_string(), back_color: "#0000FF".to_string(), }, ``` Battle implementation details [#battle-implementation-details] Program description [#program-description] The program contains the following information ```rust title="battle/app/services/game/mod.rs" struct Storage { battles: HashMap, players_to_battle_id: HashMap, admins: HashSet, config: Config, } ``` * `battles` - a map of all active battles, associating each `ActorId` with a `Battle` structure * `players_to_battle_id` - associates players with specific battles to quickly identify the battle each player is participating in * `admins` - a set containing the identifiers of administrators authorized to manage the game * `config` - the game’s configuration, which sets primary parameters like initial health, maximum participants, etc Where `Battle` is defined as follows: ```rust title="battle/app/services/game/utils.rs" pub struct Battle { pub admin: ActorId, pub battle_name: String, pub time_creation: u64, pub bid: u128, pub participants: HashMap, pub defeated_participants: HashMap, pub state: State, pub pairs: HashMap, pub players_to_pairs: HashMap, pub reservation: HashMap, pub waiting_player: Option<(ActorId, PairId)>, pub pair_id: u16, } ``` * `admin` - the identifier of the administrator who created the battle * `battle_name` - the name of the battle * `time_creation` - a timestamp indicating when the battle was created * `bid` - the bet amount for the battle * `participants` - participants in the current battle associated with their ActorId * `defeated_participants` - participants who have been defeated in the battle * `state` - the current state of the battle (Registration, Started, GameIsOver) * `pairs` - pairs of participants who will fight against each other * `players_to_pairs` - associates players with the pairs in which they are participating * `reservation` - gas reservation data relating to players * `waiting_player` - a player waiting for the next fight to start * `pair_id` - the identifier of the current pair Initialization [#initialization] To initialize the game program, the game configuration must be provided. ```rust title="battle/app/services/game/mod.rs" pub fn init(config: Config) -> Self { unsafe { STORAGE = Some(Storage { admins: HashSet::from([msg::source()]), config, ..Default::default() }); } Self(()) } ``` ```rust title="battle/app/services/game/utils.rs" pub struct Config { pub health: u16, pub max_participants: u8, pub attack_range: (u16, u16), pub defence_range: (u16, u16), pub dodge_range: (u16, u16), pub available_points: u16, pub time_for_move_in_blocks: u32, pub block_duration_ms: u32, pub gas_for_create_warrior: u64, pub gas_to_cancel_the_battle: u64, pub time_to_cancel_the_battle: u32, pub reservation_amount: u64, pub reservation_time: u32, } ``` * `health` - the initial health of a player * `max_participants` - the maximum number of participants * `attack_range` - the range of possible attack values * `defence_range` - the range of possible defense values * `dodge_range` - the range of possible dodge values * `available_points` - available points for attribute customization * `time_for_move_in_blocks` - time allocated for making a move in blocks * `block_duration_ms` - the duration of a block in milliseconds * `gas_for_create_warrior` - gas cost to create a warrior * `gas_to_cancel_the_battle` - gas cost to cancel a battle * `time_to_cancel_the_battle` - time limit to cancel a battle * `reservation_amount` - the amount of gas reserved for the battle * `reservation_time` - the time allocated for reservation Action [#action] ```rust title="battle/app/services/game/mod.rs" // creates a new battle with specified parameters, such as battle name, player name, and player attributes (attack, defense, dodge) pub async fn create_new_battle( &mut self, battle_name: String, user_name: String, warrior_id: Option, appearance: Option, attack: u16, defence: u16, dodge: u16, ); // registers a new participant in an existing battle. pub async fn register( &mut self, game_id: ActorId, warrior_id: Option, appearance: Option, user_name: String, attack: u16, defence: u16, dodge: u16, ); // cancels a player’s registration pub fn cancel_register(&mut self); // removes a player from the battle pub fn delete_player(&mut self, player_id: ActorId); // cancels the current tournament pub fn cancel_tournament(&mut self); // cancels the current tournament with a delayed message pub fn delayed_cancel_tournament(&mut self, game_id: ActorId, time_creation: u64); // starts the battle pub fn start_battle(&mut self); // makes a move in the battle pub fn make_move(&mut self, warrior_move: Move); // automatically makes a move for the specified player with a delayed message pub fn automatic_move(&mut self, player_id: ActorId, number_of_victories: u8, round: u8); // starts the next round in the battle pub fn start_next_fight(&mut self); // terminates a player’s participation in the battle pub fn exit_game(&mut self); // adds a new administrator pub fn add_admin(&mut self, new_admin: ActorId); // modifies the game configuration pub fn change_config(&mut self, config: Config); ``` Event [#event] ```rust title="battle/app/services/game/mod.rs" pub enum Event { NewBattleCreated { battle_id: ActorId, bid: u128, }, PlayerRegistered { admin_id: ActorId, user_name: String, bid: u128, }, RegisterCanceled { player_id: ActorId, }, BattleCanceled { game_id: ActorId, }, BattleStarted, MoveMade, BattleFinished { winner: ActorId, }, PairChecked { game_id: ActorId, pair_id: u8, round: u8, }, FirstRoundChecked { game_id: ActorId, wave: u8, }, NextBattleStarted, EnemyWaiting, WarriorGenerated { address: ActorId, }, AdminAdded { new_admin: ActorId, }, ConfigChanged { config: Config, }, GameLeft, RoundAction { round: u8, player_1: (ActorId, Move, u16), player_2: (ActorId, Move, u16), }, AutomaticMoveMade, } ``` Logic [#logic] Create Battle [#create-battle] The `create` function initiates a new battle, assigning a user as the battle's administrator and setting up its properties: * **Timestamp and Config Validation**: Captures the battle’s start time and checks player attributes (attack, defence, dodge) to ensure they align with game rules. * **Player’s Appearance**: Determines player appearance, either from an existing warrior or provided details. Throws an error if neither is provided. * **Check for Existing Battles**: Ensures the player doesn’t already have an active battle to prevent duplicate battles. * **Battle and Player Initialization**: Sets up battle details, adds the player as the first participant, and configures their stats. * **Bid Setup**: Sets the entry bid * **Scheduling Cancellation**: Sets a delayed message to cancel the tournament if inactive. * **Event Creation**: Returns a `NewBattleCreated` event, signaling the battle setup completion. ```rust title="battle/app/services/game/funcs.rs" async fn create( storage: &mut Storage, warrior_id: Option, appearance: Option, user_name: String, battle_name: String, attack: u16, defence: u16, dodge: u16, msg_src: ActorId, msg_value: u128, ) -> Result { let time_creation = exec::block_timestamp(); check_player_settings(attack, defence, dodge, &storage.config)?; let appearance = if let Some(id) = warrior_id { check_owner(id, msg_src).await?; get_appearance(id).await? } else if let Some(app) = appearance { app } else { return Err(BattleError::IdAndAppearanceIsNone); }; if storage.battles.contains_key(&msg_src) { return Err(BattleError::AlreadyHaveBattle); } let mut battle = Battle::default(); let player = Player { warrior_id, appearance, owner: msg_src, user_name: user_name.clone(), player_settings: PlayerSettings { health: storage.config.health, attack, defence: defence * 10, dodge: dodge * 4, }, number_of_victories: 0, ultimate_reload: 0, reflect_reload: 0, }; battle.participants.insert(msg_src, player); battle.bid = msg_value; battle.admin = msg_src; battle.time_creation = time_creation; battle.battle_name = battle_name; storage.battles.insert(msg_src, battle); storage.players_to_battle_id.insert(msg_src, msg_src); send_delayed_message_for_cancel_tournament( msg_src, time_creation, storage.config.gas_to_cancel_the_battle, storage.config.time_to_cancel_the_battle, ); Ok(Event::NewBattleCreated { battle_id: msg_src, bid: msg_value, }) } ``` Register [#register] The register function registers a player in an existing battle: * **Validation of Player Settings**: Validates attack, defence, and dodge values against the configuration. * **Appearance Setup**: Obtains appearance from either a selected warrior or direct input. * **Pre-checks**: Ensures the player isn't already registered for the battle and verifies that the specified battle exists. * **State and Capacity Checks**: Ensures the battle is open for registration and hasn’t exceeded participant limits. * **Bid Verification**: Confirms the player’s bid matches the battle requirement. * **Reservation and Participant Addition**: Adds the player to the battle, creating a reservation of gas and assigning initial settings. * **Event Creation**: Emits a `PlayerRegistered` event, indicating successful registration. ```rust title="battle/app/services/game/funcs.rs" async fn register( storage: &mut Storage, admin_id: ActorId, warrior_id: Option, appearance: Option, user_name: String, attack: u16, defence: u16, dodge: u16, msg_src: ActorId, msg_value: u128, ) -> Result { check_player_settings(attack, defence, dodge, &storage.config)?; let appearance = if let Some(id) = warrior_id { check_owner(id, msg_src).await?; get_appearance(id).await? } else if let Some(app) = appearance { app } else { return Err(BattleError::IdAndAppearanceIsNone); }; if storage.players_to_battle_id.contains_key(&msg_src) { return Err(BattleError::SeveralRegistrations); } let battle = storage .battles .get_mut(&admin_id) .ok_or(BattleError::NoSuchGame)?; if battle.state != State::Registration { return Err(BattleError::WrongState); } if battle.participants.len() >= storage.config.max_participants.into() { return Err(BattleError::BattleFull); } if battle.bid != msg_value { return Err(BattleError::WrongBid); } let reservation_id = ReservationId::reserve( storage.config.reservation_amount, storage.config.reservation_time, ) .expect("Reservation across executions"); battle.reservation.insert(msg_src, reservation_id); battle.participants.insert( msg_src, Player { warrior_id, appearance, owner: msg_src, user_name: user_name.clone(), player_settings: PlayerSettings { health: storage.config.health, attack, defence: defence * 10, dodge: dodge * 4, }, number_of_victories: 0, ultimate_reload: 0, reflect_reload: 0, }, ); storage.players_to_battle_id.insert(msg_src, admin_id); Ok(Event::PlayerRegistered { admin_id, user_name, bid: msg_value, }) } ``` Start Battle [#start-battle] The `start_battle` function initiates the battle: * **Battle Existence and Reservation**: Checks for the battle and reserves the necessary resources. * **State Verification**: Ensures the battle is in the Registration state. * **Player Count Validation**: Verifies the battle has enough players to start. * **Pair Formation**: Organizes participants into pairs for combat. * **State Transition**: Changes the battle state to `Started` and sets a timer for each move. * **Event Creation**: Emits a `BattleStarted` event to signal the beginning of combat. ```rust title="battle/app/services/game/funcs.rs" pub fn start_battle(storage: &mut Storage) -> Result { let msg_src = msg::source(); let battle = storage .battles .get_mut(&msg_src) .ok_or(BattleError::NoSuchGame)?; let reservation_id = ReservationId::reserve( storage.config.reservation_amount, storage.config.reservation_time, ) .expect("Reservation across executions"); battle.reservation.insert(msg_src, reservation_id); match battle.state { State::Registration => { battle.check_min_player_amount()?; battle.split_into_pairs()?; battle.send_delayed_message_make_move_from_reservation( storage.config.time_for_move_in_blocks, ); battle.state = State::Started; } _ => return Err(BattleError::WrongState), } Ok(Event::BattleStarted) } ``` The `Start Battle` function utilizes **Delayed Messages** and **Gas Reservation** to ensure game continuity: * **Mechanism:** After pair formation, `send_delayed_message_make_move_from_reservation` is triggered. * **Purpose:** It uses the participant's pre-allocated gas to force an automatic move if they fail to act within the time limit. * **Benefit:** This prevents the game from getting stuck, ensuring a smooth experience for all players. ```rust title="battle/app/services/game/utils.rs" pub fn send_delayed_message_make_move_from_reservation(&mut self, time_for_move: u32) { let mut new_map_reservation = HashMap::new(); self.reservation .iter() .for_each(|(actor_id, reservation_id)| { if let Some(waiting_player) = self.waiting_player { if waiting_player.0 == *actor_id { new_map_reservation.insert(waiting_player.0, *reservation_id); return; } } let number_of_victories = self .participants .get(actor_id) .expect("The player must exist") .number_of_victories; let round: u8 = 1; let request = [ "Battle".encode(), "AutomaticMove".to_string().encode(), (*actor_id, number_of_victories, round).encode(), ] .concat(); msg::send_bytes_delayed_from_reservation( *reservation_id, exec::program_id(), request, 0, time_for_move, ) .expect("Error in sending message"); }); self.reservation = new_map_reservation; } ``` This mechanism prevents the game from stalling due to player inactivity. If a player doesn’t make a move within the set time, the delayed message triggers an automatic move on their behalf, maintaining the flow of the battle and keeping it fair for all participants. It also uses the gas each player reserved during registration, which helps avoid unnecessary gas consumption from another participants. Make Move [#make-move] The `make_move` function handles each player's turn: * **Player and Battle Validation**: Ensures the player is in an active battle and retrieves their current pair. * **Time Limit Check**: Verifies that the player’s move is within the allowed time. * *Special Move Validation*: Checks whether special moves (`Ultimate` or `Reflect`) are available, based on their cooldowns. * **Move Resolution**: * If the opponent has already made a move, the function computes the round outcome. * If not, it stores the player’s move for later resolution. * **Battle Outcome**: * Updates health values based on the result, determines if a winner is declared, and moves defeated players to `defeated_participants`. * Resets health and reloads if there is a draw. * `check_end_game` and `check_draw_end_game` functions are called to determine if the game should end based on participants' statuses or a draw condition. * **Event Creation**: Emits a `RoundAction` or `MoveMade` event, depending on the state. ```rust title="battle/app/services/game/funcs.rs" pub fn make_move(storage: &mut Storage, warrior_move: Move) -> Result { let player = msg::source(); let game_id = storage .players_to_battle_id .get(&player) .ok_or(BattleError::NoSuchGame)?; let battle = storage .battles .get_mut(game_id) .ok_or(BattleError::NoSuchGame)?; battle.check_state(State::Started)?; let pair_id = battle .players_to_pairs .get(&player) .ok_or(BattleError::NoSuchPair)?; let pair = battle .pairs .get_mut(pair_id) .ok_or(BattleError::NoSuchPair)?; let timestamp = exec::block_timestamp(); let time_for_move_ms = storage.config.block_duration_ms * storage.config.time_for_move_in_blocks; if timestamp.saturating_sub(pair.round_start_time) >= time_for_move_ms as u64 { return Err(BattleError::TimeExpired); } match warrior_move { Move::Ultimate => check_reload_ultimate( battle .participants .get(&player) .expect("The player must exist"), )?, Move::Reflect => check_reload_reflect( battle .participants .get(&player) .expect("The player must exist"), )?, Move::Attack => (), } if let Some(opponent_info) = pair.action { if opponent_info.0 == player { return Err(BattleError::MoveHasAlreadyBeenMade); } let player_1_ptr = battle .participants .get_mut(&opponent_info.0) .expect("The player must exist") as *mut _; let player_2_ptr = battle .participants .get_mut(&player) .expect("The player must exist") as *mut _; let (round_result, player_1, player_2) = unsafe { let player_1 = &mut *player_1_ptr; let player_2 = &mut *player_2_ptr; ( pair.recap_round((player_1, opponent_info.1), (player_2, warrior_move)), player_1, player_2, ) }; pair.action = None; let current_round = pair.round; let (player_1_health, player_2_health) = if let Some(battle_result) = round_result { match battle_result { BattleResult::PlayerWin(winner) => { let loser = pair.get_opponent(&winner); let player_loser = battle .participants .remove(&loser) .expect("The player must exist"); battle.defeated_participants.insert(loser, player_loser); let player_winner = battle .participants .get_mut(&winner) .expect("The player must exist"); let healths = if player_1.owner == winner { (player_winner.player_settings.health, 0) } else { (0, player_winner.player_settings.health) }; player_winner.player_settings.health = storage.config.health; player_winner.reflect_reload = 0; player_winner.ultimate_reload = 0; player_winner.number_of_victories += 1; battle.pairs.remove(pair_id); battle.players_to_pairs.remove(&winner); battle.players_to_pairs.remove(&loser); battle.check_end_game(); healths } BattleResult::Draw(id_1, id_2) => { let player_1 = battle .participants .get_mut(&id_1) .expect("The player must exist"); player_1.player_settings.health = storage.config.health; player_1.reflect_reload = 0; player_1.ultimate_reload = 0; let player_2 = battle .participants .get_mut(&id_2) .expect("The player must exist"); player_2.player_settings.health = storage.config.health; player_2.reflect_reload = 0; player_2.ultimate_reload = 0; battle.pairs.remove(pair_id); battle.players_to_pairs.remove(&id_1); battle.players_to_pairs.remove(&id_2); battle.check_draw_end_game(); (0, 0) } } } else { pair.round += 1; pair.round_start_time = exec::block_timestamp(); ( player_1.player_settings.health, player_2.player_settings.health, ) }; Ok(Event::RoundAction { round: current_round, player_1: (opponent_info.0, opponent_info.1, player_1_health), player_2: (player, warrior_move, player_2_health), }) } else { pair.action = Some((player, warrior_move)); Ok(Event::MoveMade) } } ``` Start Next Fight [#start-next-fight] The `start_next_fight` function initiates the next encounter between available players: * **Battle Validation**: Confirms the battle is active and the player isn’t already in a match. * **Reservation**: Reserves necessary resources for the player. * **Opponent Check**: Checks if there is an opponent waiting. * **Match Setup**: * If an opponent is available, starts the next fight, setting timers for both players. * If no opponent is available, marks the player as waiting and sets up a new pair. * **Event Creation**: Emits either `NextBattleStarted` if the fight begins or `EnemyWaiting` if the player is queued. ```rust title="battle/app/services/game/funcs.rs" pub fn start_next_fight(storage: &mut Storage) -> Result { let player_id = msg::source(); let game_id = storage .players_to_battle_id .get(&player_id) .ok_or(BattleError::NoSuchGame)?; let battle = storage .battles .get_mut(game_id) .ok_or(BattleError::NoSuchGame)?; battle.check_state(State::Started)?; if battle.players_to_pairs.contains_key(&player_id) { return Err(BattleError::AlreadyHaveBattle); } let reservation_id = ReservationId::reserve( storage.config.reservation_amount, storage.config.reservation_time, ) .expect("Reservation across executions"); battle.reservation.insert(player_id, reservation_id); let player = battle .participants .get(&player_id) .ok_or(BattleError::NoSuchPlayer)?; if let Some((opponent, pair_id)) = battle.waiting_player { let pair = battle .pairs .get_mut(&pair_id) .expect("The pair must be created"); pair.player_2 = player.owner; pair.round_start_time = exec::block_timestamp(); battle.players_to_pairs.insert(player.owner, pair_id); battle.waiting_player = None; send_delayed_message_make_move_from_reservation( reservation_id, storage.config.time_for_move_in_blocks, player_id, player.number_of_victories, ); let reservation_id = battle .reservation .get(&opponent) .expect("Reservation must be exist"); let opponent_player = battle .participants .get(&opponent) .expect("Player must be exist"); send_delayed_message_make_move_from_reservation( *reservation_id, storage.config.time_for_move_in_blocks, opponent_player.owner, opponent_player.number_of_victories, ); Ok(Event::NextBattleStarted) } else { let pair = Pair { player_1: player.owner, round: 1, ..Default::default() }; battle.pairs.insert(battle.pair_id, pair); battle.players_to_pairs.insert(player.owner, battle.pair_id); battle.waiting_player = Some((player.owner, battle.pair_id)); battle.pair_id += 1; Ok(Event::EnemyWaiting) } } ``` Query [#query] ```rust title="battle/app/services/game/mod.rs" pub fn get_battle(&self, game_id: ActorId) -> Option { let storage = self.get(); storage .battles .get(&game_id) .cloned() .map(|battle| battle.into()) } pub fn get_my_battle(&self) -> Option { let storage = self.get(); if let Some(game_id) = storage.players_to_battle_id.get(&msg::source()) { storage .battles .get(game_id) .cloned() .map(|battle| battle.into()) } else { None } } pub fn admins(&self) -> Vec { let storage = self.get(); storage.admins.clone().into_iter().collect() } pub fn config(&self) -> &'static Config { let storage = self.get(); &storage.config } ``` * `get_battle(&self, game_id: ActorId)`: returns the state of a specific battle * `get_my_battle(&self)`: returns the battle in which the current player is participating * `admins(&self)`: returns the list of administrators * `config(&self)`: returns the game configuration Source code [#source-code] The source code of this example of Battle Game program and the example of an implementation of its testing is available on [gear-foundation/dapp/contracts/battle](https://github.com/gear-foundation/dapps/tree/master/contracts/battle). See also an example of the smart contract testing implementation based on `gtest`: [gear-foundation/dapps/contracts/battle/tests](https://github.com/gear-foundation/dapps/tree/master/contracts/battle/tests). For more details about testing programs written on Gear, refer to the [Program Testing](/docs/developing/build/testing) article. # Galactic Express galactic-express Galactic Express is a game in which players guide a rocket into space, testing its endurance as it collides with random obstacles. An unpredictable and exciting journey, it challenges players to test their luck and adapt to ever-changing obstacles. Taking their luck into their own hands, players guide a rocket into outer space, facing challenges such as dynamic weather conditions and varied flight circumstances, making each gameplay experience an exciting adventure. Players' strategic decisions about fuel allocation and target gains significantly affect the rocket's path, making each playthrough of the game unique. The article explains the programming interface, data structure, basic functions and explains their purpose. It can be used as is or modified to suit your own scenarios. Anyone can easily create their own application and run it on the Vara Network. The source code, developed using the [Sails](/docs/developing/build/sails) framework, is available on [GitHub](https://github.com/gear-foundation/dapps/tree/master/contracts/galactic-express). How to run the app locally [#how-to-run-the-app-locally] 1. Build a program > Additional details regarding this matter can be located within the [README](https://github.com/gear-foundation/dapps/tree/master/contracts/galactic-express/README.md) directory of the program. 2. Upload the program to the [Vara Network Mainnet](https://idea.gear-tech.io/programs?node=wss%3A%2F%2Frpc.vara.network) > Further details regarding the process of program uploading can be located within the [Getting Started](/docs/vara-network/quick-start#deploy-your-program-to-vara-mainnet) section. 3. Build and run user interface > More information about this can be found in the [README](https://github.com/gear-foundation/dapps/blob/master/frontend/apps/galactic-express/README.md) directory of the frontend. Implementation details [#implementation-details] Program description [#program-description] The program contains the following information ```rust title="galactic-express/app/src/services/galactic_express/mod.rs" pub struct Storage { games: HashMap, player_to_game_id: HashMap, dns_info: Option<(ActorId, String)>, admin: ActorId, } ``` * `games` - list of all games according to the address of the game's creator * `player_to_game_id` - list of players and the games in which they participate * `dns_info` - optional field that stores the [dDNS](/docs/vara-network/examples/Infra/dein) address and the program name * `admin` - game admin Where the `Game` is as follows: ```rust title="galactic-express/app/src/services/galactic_express/mod.rs" pub struct Game { admin: ActorId, admin_name: String, bid: u128, altitude: u16, weather: Weather, reward: u128, stage: Stage, } ``` * `admin` - game administrator's address * `admin_name` - administrator name * `bid` - gaming bet * `altitude` - flight altitude of the session * `weather` - session weather conditions * `reward` - session award * `stage` - current state of play There are two possible states: one during the registration stage and the other when the final results are already available ```rust title="galactic-express/app/src/services/galactic_express/utils.rs" pub enum Stage { Registration(HashMap), Results(Results), } ``` The `Participant` structure stores information about its address, name, fuel and payload amount ```rust title="galactic-express/app/src/services/galactic_express/utils.rs" pub struct Participant { pub id: ActorId, pub name: String, pub fuel_amount: u8, pub payload_amount: u8, } ``` The `Results` record all possible events during the players' turns, the number of scores they have earned and information about the participants ```rust title="galactic-express/app/src/services/galactic_express/utils.rs" pub struct Results { pub turns: Vec>, pub rankings: Vec<(ActorId, u128)>, pub participants: Vec<(ActorId, Participant)>, } ``` In flight, a rocket can either use up some amount of fuel or be destroyed by various space conditions, which are described in `HaltReason` ```rust title="galactic-express/app/src/services/galactic_express/utils.rs" pub enum Turn { Alive { fuel_left: u8, payload_amount: u8 }, Destroyed(HaltReason), } // ... pub enum HaltReason { PayloadOverload, FuelOverload, SeparationFailure, AsteroidCollision, FuelShortage, EngineFailure, } ``` Initialization [#initialization] To initialize the game program, a DNS address and name can be specified. ```rust title="galactic-express/app/src/services/galactic_express/mod.rs" pub async fn init(dns_id_and_name: Option<(ActorId, String)>) -> Self { unsafe { STORAGE = Some(Storage { dns_info: dns_id_and_name.clone(), admin: msg::source(), ..Default::default() }); } if let Some((id, name)) = dns_id_and_name { let request = [ "Dns".encode(), "AddNewProgram".to_string().encode(), (name, exec::program_id()).encode(), ] .concat(); msg::send_bytes_with_gas_for_reply(id, request, 5_000_000_000, 0, 0) .expect("Error in sending message") .await .expect("Error in `AddNewProgram`"); } Self(()) } ``` Action [#action] ```rust title="galactic-express/app/src/services/galactic_express/mod.rs" pub fn create_new_session(&mut self, name: String); pub fn cancel_game(&mut self); pub fn leave_game(&mut self); pub fn register(&mut self, creator: ActorId, participant: Participant); pub fn cancel_register(&mut self); pub fn delete_player(&mut self, player_id: ActorId); pub fn start_game(&mut self, fuel_amount: u8, payload_amount: u8); pub fn change_admin(&mut self, new_admin: ActorId); pub async fn kill(&mut self, inheritor: ActorId); ``` Event [#event] ```rust title="galactic-express/app/src/services/galactic_express/mod.rs" pub enum Event { GameFinished(Results), NewSessionCreated { altitude: u16, weather: Weather, reward: u128, bid: u128, }, Registered(ActorId, Participant), RegistrationCanceled, PlayerDeleted { player_id: ActorId, }, GameCanceled, GameLeft, AdminChanged { new_admin: ActorId, }, Killed { inheritor: ActorId, }, } ``` Logic [#logic] A new game session must be set up using `create_new_session` to start the game. Upon the inception of a new session, random values, encompassing aspects like weather conditions, altitude settings, fuel prices, and potential rewards, are dynamically generated. ```rust title="galactic-express/app/src/services/galactic_express/funcs.rs" pub fn register( storage: &mut Storage, creator: ActorId, participant: Participant, ) -> Result { let msg_source = msg::source(); let msg_value = msg::value(); let reply = register_for_session(storage, creator, participant, msg_source, msg_value); if reply.is_err() { send_value(msg_source, msg_value); } reply } fn register_for_session( storage: &mut Storage, creator: ActorId, participant: Participant, msg_source: ActorId, msg_value: u128, ) -> Result { if storage.player_to_game_id.contains_key(&msg_source) { return Err(GameError::SeveralRegistrations); } if let Some(game) = storage.games.get_mut(&creator) { if msg_value != game.bid { return Err(GameError::WrongBid); } if let Stage::Results(_) = game.stage { return Err(GameError::SessionEnded); } let participants = game.stage.mut_participants()?; if participants.contains_key(&msg_source) { return Err(GameError::AlreadyRegistered); } if participants.len() >= MAX_PARTICIPANTS - 1 { return Err(GameError::SessionFull); } participant.check()?; participants.insert(msg_source, participant.clone()); storage.player_to_game_id.insert(msg_source, creator); Ok(Event::Registered(msg_source, participant)) } else { Err(GameError::NoSuchGame) } } ``` This function `register_for_session` checks the game stage, the number of registered players and the participant's input data. Input values of fuel and payload cannot be exceeded by predetermined values ```rust title="galactic-express/app/src/services/galactic_express/utils.rs" // maximum fuel value that can be entered by the user pub const MAX_FUEL: u8 = 100; // maximum payload value that can be entered by the user pub const MAX_PAYLOAD: u8 = 100; // ... impl Participant { pub fn check(&self) -> Result<(), GameError> { if self.fuel_amount > MAX_FUEL || self.payload_amount > MAX_PAYLOAD { Err(GameError::FuelOrPayloadOverload) } else { Ok(()) } } } ``` After players have successfully registered, the admin can initiate the game using the `start_game` function. This function involves several checks on the admin, the number of participants, and their data. ```rust title="galactic-express/app/src/services/galactic_express/funcs.rs" pub fn start_game( storage: &mut Storage, fuel_amount: u8, payload_amount: u8, ) -> Result { let msg_source = msg::source(); let game = storage .games .get_mut(&msg_source) .ok_or(GameError::NoSuchGame)?; if fuel_amount > MAX_FUEL || payload_amount > MAX_PAYLOAD { return Err(GameError::FuelOrPayloadOverload); } let participant = Participant { id: msg_source, name: game.admin_name.clone(), fuel_amount, payload_amount, }; let participants = game.stage.mut_participants()?; if participants.is_empty() { return Err(GameError::NotEnoughParticipants); } participants.insert(msg_source, participant); let mut random = Random::new()?; let mut turns = HashMap::new(); // ... ``` Turns are automatically and randomly generated for each participant, which include three tests of luck. These moves include both events beyond the control of the participant and events that could occur if the participant decides to take a risk by specifying more fuel and payload. More details about the math of the game can be found in the [README](https://github.com/gear-foundation/dapps/tree/master/contracts/galactic-express#math) ```rust title="galactic-express/app/src/services/galactic_express/funcs.rs" fn turn( turn: usize, remaining_fuel: u8, random: &mut Random, weather: Weather, payload: u8, ) -> Result { let new_remaining_fuel = match remaining_fuel.checked_sub((payload + 2 * weather as u8) / TURNS as u8) { Some(actual_fuel) => actual_fuel, None => return Err(HaltReason::FuelShortage), }; match turn { 0 => { // values in "chance" are transmitted as percentages if random.chance(3) { return Err(HaltReason::EngineFailure); } // this trap for someone who specified a lot of fuel if remaining_fuel >= PENALTY_LEVEL - 2 * weather as u8 && random.chance(10) { return Err(HaltReason::FuelOverload); } } 1 => { // this trap for someone who specified a lot of payload if payload >= PENALTY_LEVEL - 2 * weather as u8 && random.chance(10) { return Err(HaltReason::PayloadOverload); } if random.chance(5 + weather as u8) { return Err(HaltReason::SeparationFailure); } } 2 => { if random.chance(10 + weather as u8) { return Err(HaltReason::AsteroidCollision); } } _ => unreachable!(), } Ok(new_remaining_fuel) } ``` If a participant crashes for any reason, the player receives zero points and, accordingly, loses the game. If the player successfully completes all three parts of the game, the points are counted to determine the winner. ```rust title="galactic-express/app/src/services/galactic_express/funcs.rs" let mut scores: Vec<(ActorId, u128)> = turns .iter() .map(|(actor, turns)| { let last_turn = turns.last().expect("there must be at least 1 turn"); ( *actor, match last_turn { Turn::Alive { fuel_left, payload_amount, } => (*payload_amount as u128 + *fuel_left as u128) * game.altitude as u128, Turn::Destroyed(_) => 0, }, ) }) .collect(); ``` Query [#query] ```rust title="galactic-express/app/src/services/galactic_express/mod.rs" pub fn get_game(&self, player_id: ActorId) -> Option { let storage = self.get(); storage .player_to_game_id .get(&player_id) .and_then(|creator_id| storage.games.get(creator_id)) .map(|game| { let stage = match &game.stage { Stage::Registration(participants_data) => { StageState::Registration(participants_data.clone().into_iter().collect()) } Stage::Results(results) => StageState::Results(results.clone()), }; GameState { admin: game.admin, admin_name: game.admin_name.clone(), altitude: game.altitude, weather: game.weather, reward: game.reward, stage, bid: game.bid, } }) } pub fn all(&self) -> State { self.get().into() } pub fn admin(&self) -> &'static ActorId { &self.get().admin } pub fn dns_info(&self) -> &'static Option<(ActorId, String)> { &self.get().dns_info } ``` * `get_game(&self, player_id: ActorId)`: Retrieves the game state for a specific player based on their `player_id` * `all(&self)`: Provides the overall state of the storage * `admin(&self)`: Returns the admin’s `ActorId` * `dns_info(&self)`: Returns the optional dDNS information, including the dDNS address and program name, if available. Source code [#source-code] The source code of this example of Galactic-Express Game program and the example of an implementation of its testing is available on [gear-foundation/dapp/contracts/galactic-express](https://github.com/gear-foundation/dapps/tree/master/contracts/galactic-express). See also an example of the program testing implementation based on `gtest`: [gear-foundation/dapps/galactic-express/tests](https://github.com/gear-foundation/dapps/tree/master/contracts/galactic-express/app/tests). For more details about testing programs written on Vara, refer to the [Program Testing](/docs/developing/build/testing) article. # Mental Poker This article is aimed at: * **Blockchain Developers:** Interested in secure dApp architectures. * **Cryptographers:** Focused on practical application of cryptographic primitives. * **Poker Enthusiasts:** Eager to understand decentralized online poker mechanics. Mental poker is a unique cryptographic concept first introduced in the landmark 1979 paper ["Mental Poker"](https://people.csail.mit.edu/rivest/pubs/SRA81.pdf) by Ron Rivest, Adi Shamir, and Leonard Adleman (the creators of [RSA](https://en.wikipedia.org/wiki/RSA_cryptosystem)). It enables players to play poker remotely without trusting each other or relying on intermediaries such as dealers or arbiters. Imagine playing poker online with a friend, but neither party trusts the platform or each other. Mental poker solves this trust issue by leveraging mathematics and cryptography to ensure cards are dealt fairly, remain hidden from other players, and enforce game rules purely through code. In an era where decentralization and secure remote interactions have become essential, mental poker addresses the critical need for trustless, secure, and fair online gaming. The original RSA paper proposed a mental poker protocol based on commutative encryption — a cryptographic method where encryption and decryption can occur in any order. This ensures that the deck of cards is shuffled and dealt fairly without revealing card identities prematurely. To illustrate this intuitively, imagine a box locked by multiple padlocks, each belonging to a different player. The box can only open once all padlocks are removed, but the order in which they're unlocked doesn't matter. While the original RSA-based approach proved the concept, it was computationally expensive and lacked efficient, practical methods to **verify the honesty of a shuffle without revealing the permutation** — a challenge that remained open in 1979. Modern elliptic curves offer faster arithmetic with smaller keys, and **zero-knowledge** techniques provide succinct proofs of correct shuffles/deals **without** leaking card order. Together with blockchains, this makes mental poker practical at scale. Today, thanks to advancements in zero-knowledge proofs (ZKPs), elliptic curve cryptography, and blockchain technology, the vision outlined in the original Mental Poker paper is more achievable and practical than ever before. In this article, modern cryptographic tools combined with innovative blockchain architectures, such as Vara Network, will be explored. Importantly, these techniques are applicable not only to poker but can also be generalized to create secure, decentralized versions of many other games and trustless multi-party interactions. How Does the Original Protocol Work? [#how-does-the-original-protocol-work] img alt A deck of 52 cards is represented by unique identifiers that are encrypted and shuffled by multiple players. **Example scenario**: `Alice` and `Bob` playing poker remotely: **Alice encrypts** each card with her secret key $A$: $$ A(C_1), A(C_2), \dots, A(C_{52}) $$ Here, $begin:math:text$ C\_i $end:math:text$ denotes the **unique identifier of the *i*-th card** in the deck — e.g., an integer from 1 to 52 or any internal representation that uniquely maps to a specific rank and suit. * Then Alice randomly shuffles the deck. **Bob receives the encrypted deck**, encrypts it again with his key $B$: $$ B(A(C_1)), B(A(C_2)), \dots, B(A(C_{52})) $$ * Bob then randomly shuffles this doubly-encrypted deck. **Card dealing**: **Alice selects a card for Bob**, removes her encryption layer: $$ B(A(C_i)) \rightarrow B(C_i) $$ sends it to Bob. Bob removes his encryption layer: $$ B(C_i) \rightarrow C_i $$ **Bob selects a card for Alice**, removes his encryption layer: $$ A(B(C_j)) \rightarrow A(C_j) $$ sends it to Alice. Alice removes her encryption layer: $$ A(C_j) \rightarrow C_j $$ **Verification at the end**: Players reveal keys $A$ and $B$ publicly to verify fairness of the game. Multiple encryption and shuffling layers ensure no player knows card positions until fully decrypted. Pseudocode for Encryption and Card Dealing [#pseudocode-for-encryption-and-card-dealing] ```python # Deck of 52 cards deck = [C1, C2, ..., C52] alice_key = generate_secret_key() # Alice's secret key bob_key = generate_secret_key() # Bob's secret key # Alice encrypts and shuffles the deck alice_encrypted_deck = [encrypt(card, alice_key) for card in deck] shuffle(alice_encrypted_deck) # Shuffling (verified via ZK later) # Bob encrypts and shuffles again bob_encrypted_deck = [encrypt(card, bob_key) for card in alice_encrypted_deck] shuffle(bob_encrypted_deck) # Shuffling (verified via ZK later) # Dealing a card to Alice alice_card = select_card(bob_encrypted_deck) # Bob selects for Alice alice_card = decrypt(alice_card, bob_key) # Bob removes his layer alice_card = decrypt(alice_card, alice_key) # Alice removes her layer print(f"Alice receives card: {alice_card}") # Remove the dealt card from the deck bob_encrypted_deck = remove_card(bob_encrypted_deck, alice_card) # Dealing a card to Bob bob_card = select_card(bob_encrypted_deck) # Alice selects for Bob bob_card = decrypt(bob_card, alice_key) # Alice removes her layer bob_card = decrypt(bob_card, bob_key) # Bob removes his layer print(f"Bob receives card: {bob_card}") # Fairness verification (via ZK-circuits, explained later) verify_shuffle(alice_encrypted_deck, bob_encrypted_deck) ``` The pseudocode above is simplified for clarity. In a production environment, real implementations must use specialized **cryptographic libraries** and **ZK-proofs**, which will be covered in detail in the following sections. Modern Advances: ZK-Circuits and Smart Contracts [#modern-advances-zk-circuits-and-smart-contracts] The original SRA protocol provides a foundational concept for mental poker, but modern implementations significantly expand on these ideas by using advanced cryptographic methods such as ZKP, elliptic curve cryptography, and blockchain-based smart contracts. **While the original RSA-based approach proved the concept, it was computationally expensive and lacked efficient methods for verifiable shuffles without revealing card order.** Advances in elliptic curve cryptography and zero-knowledge proofs solve these limitations. ZKP’s Role [#zkps-role] In mental poker, Zero-Knowledge Proofs enable players to demonstrate the correctness and fairness of actions — such as shuffling and dealing cards — without revealing sensitive information like card identities or their order. For example, a **Zero-Knowledge Shuffle Proof** mathematically guarantees the fairness of shuffling without exposing the shuffled order. Why zk-SNARKs? [#why-zk-snarks] Mental poker demands cryptographic guarantees balanced with practical efficiency, making zk-SNARKs (Succinct Non-Interactive Arguments of Knowledge) particularly suitable due to: * **Compact Proofs**: zk-SNARK proofs are small, reducing on-chain verification costs. * **Rapid Verification**: Proofs verify quickly (milliseconds), essential for responsive gameplay. * **Off-chain Proof Generation**: Players perform complex proof generation off-chain, minimizing blockchain computational overhead. * **Privacy with Integrity**: zk-SNARKs provide robust privacy without sacrificing cryptographic soundness, crucial in competitive games. Compared to other systems, **zk-SNARKs** offer superior performance in blockchain environments: * **Compact Proofs:** Groth16 (\~200 bytes) and PLONK (400–600 bytes) are significantly smaller than systems like **Bulletproofs** (1–2 KB+). * **Rapid Verification:** Verification typically takes only **1–10 ms**, even in constrained runtimes like Ethereum (EVM) or WebAssembly (Wasm). This efficiency makes them ideal for on-chain verification where gas and block space are limited. Cryptographic Commitments with Elliptic Curves [#cryptographic-commitments-with-elliptic-curves] A core mechanism of mental poker is cryptographic commitments, analogous to locking a card in a secure box. Modern protocols often use **Pedersen Commitments**: $$ C = m \cdot G + r \cdot H $$ where: * **m**: hidden card value (e.g., ace of spades), * **r**: random value ensuring secrecy, * **G, H**: publicly known elliptic curve points. Pedersen Commitments offer: * **Perfect hiding**: No leakage of card identity. * **Computational binding**: Ensures cards cannot be altered without detection. Elliptic curves provide computationally efficient arithmetic required by these commitments. Elliptic Curves: Selecting Optimal Curves [#elliptic-curves-selecting-optimal-curves] Elliptic curve selection critically influences ZKP efficiency. Two prominent curves stand out: * **Baby Jubjub (BN-254 Curve)**: * Compatible with traditional zk-SNARK systems common in Ethereum-like blockchain environments. * Optimized for frequent operations such as commitments, encrypting, and shuffling cards. * Well-established tooling and library support. * **Bandersnatch (BLS12-381 Curve)**: * Optimized for advanced zk-SNARK proving systems (Groth16, PLONK), providing faster arithmetic and smaller proofs. * Ideal for high-frequency actions (multiple rounds of shuffling, dealing, frequent proof verification). * Reduced gas costs due to smaller proofs and faster verification, making it optimal for scalable and interactive gameplay. Key Performance Factors [#key-performance-factors] Practical online poker requires cryptographic efficiency and minimal latency. For zk-SNARK-based mental poker implementations, the most crucial performance metrics are: * **Off-chain Proof Generation Speed**: Proofs for shuffling and dealing must be rapidly computed by players, typically within seconds or milliseconds, to sustain seamless gameplay. * **On-chain Verification Latency and Cost**: zk-SNARK’s small proof size ensures minimal verification latency and lower gas fees, crucial for frequent blockchain interactions. * **Minimal Proof Size**: Smaller proofs reduce on-chain data storage requirements and network overhead, directly contributing to lower transaction fees and faster processing. In sum, zk-SNARK proofs utilizing elliptic curve cryptography deliver the ideal combination of privacy, fairness, and performance, establishing them as the optimal solution for decentralized mental poker. Mental Poker in the Vara Context [#mental-poker-in-the-vara-context] Vara is a WASM-based blockchain platform that applies the actor model at its core. The combination of actor-based logic, message-driven design, and a secure WASM runtime enables modular, scalable games — each poker table, lobby, or supporting service is an independent actor. Global state or blocking limitations are avoided entirely: each actor maintains its own state, and all cross-component coordination is handled through explicit message passing and events. This allows thousands of games to run in parallel with no data conflicts, and naturally simplifies integration with front-ends and analytics. **Elliptic Curve Cryptography:**\ All cryptographic operations — commitments, encryption, public keys (ElGamal keys for poker), and all arithmetic on encrypted cards — are performed over the Bandersnatch curve (on BLS12-381). This curve is natively supported by the underlying cryptographic libraries (`ark-ed-on-bls12-381-bandersnatch`) and by Vara’s built-in primitives for pairing-based cryptography, including efficient serialization and deserialization. **Pairing-Friendly Architecture:**\ All verification logic relies on pairing-friendly cryptography using [BLS12-381](/docs/developing/build/builtinactors/bia-bls) and Bandersnatch, providing compatibility with modern proof systems such as Groth16 and PLONK. **Built-in Contracts for Verification:**\ On-chain zk-SNARK proof verification (Groth16/PLONK) is always performed by a dedicated zk-verification contract, never inside the game contract itself. Vara exposes **[built-in actors](/docs/developing/build/builtinactors/)** that provide highly optimized cryptographic operations — multi-scalar multiplication, pairing checks, and related primitives. This approach accelerates verification, minimizes gas usage, and isolates security-sensitive code. **Zero-Knowledge Proofs:**\ ZK proofs are generated off-chain by players (typically on client device). Only the compact proof and required public inputs are sent on-chain, avoiding network congestion and keeping user experience responsive. **Gas Reservation:**\ Gas reservation is explicitly used in all important phases (lobby creation, game start, zk verification, and so on), guaranteeing that no key operation is left incomplete or interrupted. The WASM runtime enforces atomic execution of each transition. Implementation Details [#implementation-details] The mental poker DApp consists of two main components: * **On-chain smart contracts** — handle game state, commitments, shuffles, and phase transitions. * **Client-side application** — performs all sensitive cryptographic operations off-chain. **Contracts and circuits source code:** [zk-mental-poker contracts and circuits](https://github.com/gear-foundation/dapps/tree/master/contracts/zk-mental-poker)\ **UI:** [zk-poker frontend](https://github.com/gear-foundation/dapps/tree/master/frontend/apps/zk-poker) Programs Overview [#programs-overview] * **Mental Poker Contract** — Manages the state of a single game instance (poker table/lobby): commitments, shuffling, and phase transitions. * **Poker Factory** — Creates new poker tables (game instances/lobbies) and manages them. * **ZK Verification Contract** — On-chain verification of zk-SNARK proofs (shuffle/deal). * **PTS (Points/Token Service)** — Optional player balances/rewards tracking. Client-Side Responsibilities [#client-side-responsibilities] In addition to handling the standard poker game logic (moves, turns, etc.), the client is responsible for all sensitive cryptographic operations: * **ElGamal Key Generation:**\ Each player generates their own ElGamal keypair locally (in the browser). These keys are used for encrypting cards and sharing public keys between players. * **Card Encryption/Decryption:**\ All card encryption and decryption — including every intermediate encryption layer during shuffling — is performed locally on the client. This ensures that card privacy always remains under player control. * **ZK-Proof Generation:**\ All zk-SNARK proofs (for shuffling, dealing, etc.) are generated by the player on their device, using WASM cryptographic libraries. * **On-Chain Data Submission:**\ Only public data is sent to the blockchain: encrypted cards, public keys, zk-proofs, and required public inputs for proof verification. Client-Side Responsibilities (in game order) [#client-side-responsibilities-in-game-order] 1. **Elliptic Curve ElGamal Key Generation**\ Each player generates their EC-ElGamal keypair locally. * **Private key**: 256-bit scalar, stored locally and never shared. * **Public key**: three 32-byte coordinates (`x`, `y`, `z`), serialized in **little-endian** for on-chain submission. 2. **Public Key Exchange**\ Players exchange public keys. The backend collects all keys and computes an **aggregate public key** used to encrypt the initial deck. 3. **Card Encryption and Shuffling** * Each card is represented as a unique elliptic curve point. * The deck is encrypted with the aggregate public key. * The encrypted deck is shuffled. * A zk-SNARK **shuffle proof** is generated to prove correctness without revealing the permutation. 4. **zk-SNARK Proof Generation**\ All zk-SNARK proofs (e.g., shuffle proofs, decryption proofs) are generated off-chain using WASM-based cryptography (e.g., Groth16/PLONK). 5. **Partial Decryption (repeated)** * In multiple rounds, each player removes their encryption layer from **other players’** cards and produces a **zk decryption proof** for each step. * **Performed twice:** * At the beginning of the round for **player cards**. * After each community stage (**flop**, **turn**, **river**) for **table cards**. 6. **Final Decryption**\ When only the card owner’s layer remains, they decrypt their own cards locally. Decrypted values are **never** sent on-chain; only proofs and public inputs are submitted. Detailed Workflow and Code Examples [#detailed-workflow-and-code-examples] The following sequence describes how the zk-mental-poker DApp interacts with the backend and contracts, including real data formats and serialization logic. Data Flow (high-level) [#data-flow-high-level] img alt 1. ZK Key Generation [#1-zk-key-generation] Before joining a game, a zk-proof key pair is generated locally: ```typescript let { sk, pk } = keyGen(numBits); // Public key format: { X: bigint; Y: bigint; Z: bigint } ``` The private key (`sk`) must be stored locally (`localStorage` or `sessionStorage`) and never shared.\ When registering in a game, the **public key** is sent to the smart contract in Little Endian format: ```rust struct PublicKey { x: [u8; 32], y: [u8; 32], z: [u8; 32], } ``` *** 2. Player Registration and Aggregate Key [#2-player-registration-and-aggregate-key] The backend listens to registration events and collects all public keys.\ Once all are gathered, it computes the **aggregate public key** to encrypt the deck. If the contract status is `WaitingShuffleVerification`, the client requests a shuffle task: ```http GET /get-task?lobby=0xLobbyAddress&player=0xPlayerAddress ``` **Example Response:** ```json { "aggPubkey": { "X": "...", "Y": "...", "Z": "..." }, "deck": [[ "...", "...", "..." ], ...] } ``` 3. Deck Encryption and Shuffle Proof [#3-deck-encryption-and-shuffle-proof] The client: 1. Encrypts the deck using the aggregate key. 2. Shuffles it. 3. Generates a zk-proof of correct shuffle & encryption. 4. Sends the proof + public signals to the backend. The backend collects all proofs and submits them to the contract.\ If verification passes, encrypted cards are distributed to players. 4. Partial Decryption of Player Cards [#4-partial-decryption-of-player-cards] Players must participate in partial decryption so that each card is eventually under only one encryption layer. Example for 3 players: | Player | Must decrypt cards of | | -------- | --------------------- | | Player 1 | Player 2 & Player 3 | | Player 2 | Player 1 & Player 3 | | Player 3 | Player 1 & Player 2 | **This process repeats**: * For **player cards** at the beginning of each round. * For **community cards** after each stage (**flop**, **turn**, **river**). When the status is `WaitingPartialDecryptionsForPlayersCards`, the client requests tasks: ```http GET /get-task?lobby=0xLobbyAddress&player=0xPlayerAddress ``` **Example Response:** ```json [ { "cardOwner": "0xPlayer2", "cardIndex": 0, "card": "CipherCard" }, { "cardOwner": "0xPlayer2", "cardIndex": 1, "card": "CipherCard" }, { "cardOwner": "0xPlayer3", "cardIndex": 0, "card": "CipherCard" }, { "cardOwner": "0xPlayer3", "cardIndex": 1, "card": "CipherCard" } ] ``` For each card, the player computes: ```typescript const dec_i = sk_i * c0; const { proof, publicSignals } = await groth16.fullProve( { c0, sk: sk_i, expected: dec_i }, decryptWasmFile, decryptZkeyFile ); ``` 5. Reading and Decrypting Cards [#5-reading-and-decrypting-cards] Once cards are under a single encryption layer, a player reads them from the contract: ```rust @query PlayerCards(player_id: actor) -> Option<[EncryptedCard; 2]>; ``` The client decrypts them locally using their private key, converting coordinates from bytes (Little Endian) to `BigInt`. *** 6. Table Card Decryption [#6-table-card-decryption] When the stage is: * `WaitingTableCardsAfterPreFlop` → 3 cards * `WaitingTableCardsAfterFlop` → 1 card * `WaitingTableCardsAfterTurn` → 1 card Cards are decrypted in the same way as player cards: ```typescript const c0 = card.c0; const skC0 = scalarMul(F, a, d, c0, sk); const dec: ECPoint = { X: F.neg(skC0.X), Y: skC0.Y, Z: skC0.Z }; const { proof, publicSignals } = await groth16.fullProve( { c0: [c0.X.toString(), c0.Y.toString(), c0.Z.toString()], sk: sk.toString(), expected: [dec.X.toString(), dec.Y.toString(), dec.Z.toString()] }, decryptWasmFile, decryptZkeyFile ); ``` *** 7. Proof Serialization for On-Chain Submission [#7-proof-serialization-for-on-chain-submission] Proofs must be encoded in the correct format before submission: ```typescript function bigintToBytes48(x: string): Uint8Array { const hex = BigInt(x).toString(16).padStart(96, "0"); return Uint8Array.from(Buffer.from(hex, "hex")); } function serializeG1Uncompressed([x, y]: string[]): Uint8Array { return new Uint8Array([...bigintToBytes48(x), ...bigintToBytes48(y)]); } function serializeG2Uncompressed([[x0, x1], [y0, y1]]: string[][]): Uint8Array { return new Uint8Array([ ...bigintToBytes48(x1), ...bigintToBytes48(x0), ...bigintToBytes48(y1), ...bigintToBytes48(y0) ]); } function encodeProof(proof: { pi_a: string[], pi_b: string[][], pi_c: string[] }) { return { a: serializeG1Uncompressed(proof.pi_a), b: serializeG2Uncompressed(proof.pi_b), c: serializeG1Uncompressed(proof.pi_c) }; } ``` Conclusion [#conclusion] Mental poker combines decades-old cryptographic theory with cutting-edge blockchain and ZK technology to achieve trustless, fair, and efficient online gameplay. By leveraging elliptic curve cryptography, zk-SNARKs, and Vara’s actor-model WASM runtime (with Built-in Actors for heavy crypto), the system ensures **scalability**, **privacy**, and **verifiability**. * **Source code:** [zk-mental-poker contracts and circuits](https://github.com/gear-foundation/dapps/tree/master/contracts/zk-mental-poker) * **UI:** [zk-poker frontend](https://github.com/gear-foundation/dapps/tree/master/frontend/apps/zk-poker) * **Built-in Actors (BLS):** [BLS12-381 Built-in Actor](/docs/developing/build/builtinactors/bia-bls) # Monopoly Syndote is the name of a Monopoly-like decentralized game that works **completely on-chain**. It has no backend or any centralized components, the user interface interacts directly with the program uploaded in the Gear Network. This is a "game strategies battle" where players compete with each other by implementing various playing strategies uploaded as programs into the network. Syndote consists of Master program and Player programs. Master program is the main program that starts and controls the game. Player programs implement the game strategies that can be unique for each participant of the game. After the game starts, all moves in the game take place automatically, but the UI provides an ability to jump to each one individually to analyze the player's strategy. To complete the game and determine the winner, many moves are required, which are processed over several blocks. In order for the Master program to have enough gas to perform all the moves, the [gas reservation](/docs/developing/build/gstd/gas-reservation) technology is used. img alt To launch the game locally, you need to: * ⚒️ Build Master and Player programs * 🏗️ Upload the programs to the network * 🖥️ Build and run user interface ⚒️ Build Master and Player programs [#️-build-master-and-player-programs] * Get the source code of Master and Player programs from [GitHub](https://github.com/gear-foundation/dapps/tree/master/contracts/syndote). * Modify Player's program as you wish to achieve optimal game strategy. * Build programs as described in [program/README.md](https://github.com/gear-foundation/dapps/blob/master/contracts/syndote/README.md). 🏗️ Upload programs on chain [#️-upload-programs-on-chain] There are two ways to upload the game onto the chain: 1. Using [Gear IDEA portal](https://idea.gear-tech.io/); 2. Using [`gear-js/cli`](https://github.com/gear-tech/gear-js/tree/main/tools/cli). 1. Using Gear IDEA portal: [#1-using-gear-idea-portal] * In the network selector, select the network where you’ll run the game: * Vara Network Mainnet - wss\://rpc.vara.network; * Local node - ws\://localhost:9944 * Upload Master and Player programs: * Click the `Upload Program` button and specify `x.opt.wasm` file of the program, also specify the `*.idl` file for Master and Palyer programs accordingly. * Save the addresses of the uploaded programs. * Make gas reservations to ensure continuous game execution. * Find your uploaded Master program and click the `Send message` button: img alt * Currently, the single gas reservation amount can be up to 245 000 000 000 since it is not yet possible to make a reservation more than the block gas limit (250 000 000 000). It is recommended to make at least 5-10 reservations to make sure the Master program has enough gas to run and complete the game. 2. Using gear-js/cli [#2-using-gear-jscli] It allows sending transactions to the Gear node based on `yaml` file: * Go to folder with [scripts](https://github.com/gear-foundation/dapps/tree/master/frontend/apps/syndote): ```bash cd ./syndote-upload-game/ ``` * Upload previously built files into `/programs` folder (replace if necessary): `sydnote_player.idl`, `syndote.idl`, `sydnote_player.opt.wasm`, `syndote.opt.wasm` * Install `gear-js/cli`: npm pnpm yarn bun ```bash npm install -g @gear-js/cli ``` ```bash pnpm add -g @gear-js/cli ``` ```bash yarn global add @gear-js/cli ``` ```bash bun add --global @gear-js/cli ``` * Upload the master program. The `upload-game.yaml` file contains a transaction that will deploy the program onto the network, check [README](https://github.com/gear-tech/gear-js/tree/main/tools/cli) for more details. * Customize the following parameters: * Specify the accounts using on of these methods - mnemonic phrase, seed or Using well-known account such as `Alice` and `Bob` ```bash accounts: alice: 'bottom drive obey lake curtain smoke basket hold race lonely fit walk' bob: //Bob my_account: '0x…seed' transactions: account: alice ``` * You can also specify which node you want to deploy the program on by defining the variable `wsAddress` in the file with transactions. If this variable is not defined, the program will be deployed on the local node which should be running beforehand: ```bash wsAddress: wss://rpc.vara.network ``` * To deploy the program, run the command: ```bash gear-js workflow upload-game.yaml ``` * If everything goes well, you will see the program address in the terminal: img alt * To run the application, it is necessary to save this address and later specify it as a variable in the `.env` file. * Make gas reservations to ensure continuous game execution. Open `reserve-gas.yaml` file and customize account and node address as described above. Run the command with the master program address specified: ```bash gear-js workflow reserve-gas.yaml -a program_id='0x60663aaee2971eb60874b63c92e738cc375f3764a1d3b38d65cbc63d8ee8f70c' ``` * If everything goes well, you will see a message confirming a successful reservation. Please send this message 5-10 times to ensure uninterrupted gameplay. img alt * Upload players programs to the network. For testing purposes, you can upload 4 identical player programs. After initialization in the network, their addresses will be unique and you will then need to register them in the game. Open `players.yaml` file and customize account and node address as described above. Run the command to add players in the game: ```bash gear-js workflow players.yaml ``` * You will see the players addresses: img alt * Additionally, you can write your own player program, build it, and place the `.opt.wasm` and `.idl` files into the `upload-game/programs` folder. To deploy your player, specify the necessary files in the `players.yaml` file instead of any existing player. img alt 🖥️ Build and run user interface [#️-build-and-run-user-interface] 1. Download [this repository](https://github.com/gear-foundation/dapps/tree/master/frontend/apps/syndote) locally and go to the `frontend` folder. 2. Install packages: npm pnpm yarn bun ```bash npm install ``` ```bash pnpm install ``` ```bash yarn install ``` ```bash bun install ``` 3. Declare environment variables, go to `/frontend/` foler, create a new `.env` file, and check `.env.example` file to get the necessary variables: * `VITE_NODE_ADDRESS` - specify the address of the node where you deployed the programs. * `VITE_DNS_API_URL` - specify the address of the dDNS API ([https://dns-explorer.gear.foundation/api](https://dns-explorer.gear.foundation/api)). * `VITE_DNS_NAME` - specify the dDNS name of the deployed program (add the dDNS name for your program in the [IDEA dDNS section](https://idea.gear-tech.io/dns?node=wss%3A%2F%2Frpc.vara.network)). 4. Put the latest version of the `syndote.idl` file in the `src\app\utils\sails` folder locally, replacing the existing file if necessary. Then, [generate the client library](https://github.com/gear-tech/sails/blob/master/js/cli/README.md#generate-typescript-client-library-using-the-idl-file) and place it in `src\app\utils\sails\syndote.ts`. To ensure all features work correctly, your node and runtime version must match the current **`@gear-js/api`** version. **If you encounter issues:** * **Network:** Try switching networks or run a **local node** (update the address in your `.env`). * **Programs:** Verify that the `.wasm` files are correctly uploaded and running on the selected network. * **Dependencies:** If `yarn install` fails, try running `npm i --force`. 5. Run the app: npm pnpm yarn bun ```bash npm run start ``` ```bash pnpm run start ``` ```bash yarn run start ``` ```bash bun run start ``` Run the game [#run-the-game] 1. Find the players addresses you have previously uploaded. 2. Register players in the application's interface one after another. Each player registration is a transaction that needs to be signed. It is possible to register up to 4 players. 3. Once everything is done, start the game via the `Play` button. You will see the game result almost instantly. Click `<` and `>` buttons to navigate through the game and check each separate move. img alt 4. If the game is not over, make more reservations and send a message `Play` again. After the game is over, it's state becomes Finished and the admin can restart the game by starting a new player registration. Programs [#programs] Master program [#master-program] The [Master program](https://github.com/gear-foundation/dapps/tree/master/contracts/syndote) initiates with monopoly card details (cell cost, special cells like jail and lottery) and is preloaded with sufficient gas for automated play. Prior to each player's move, the master program verifies the gas amount, triggering a message to the game admin for additional gas reservation if needed. **Players registration**: Players deploy their strategic programs and send a message `Register` to `Master` program. Master program: * Adds the players the list of participants and initializes it with the following structure: ```rust struct PlayerInfo { position: 0, balance: 15000, debt: 0, in_jail: false, round: 0, cells: BTreeSet::new(), penalty: 0, lost: false, } ``` * If the number of participants is 4, `Master` program goes to the state of waiting for the start of the game. When the required number of participants is reached, the admin sends a message `Play` to `Master` program. This message starts an automated game that will scroll through the algorithms of the participants in turn: * `Master`program starts the round. Each round contains 4 steps (Each player makes one step). Before each step `Master` program checks the states of the participants (whether they are bankrupt or may be there is already a winner); * Each step contains the interaction with the strategic program in turn: * If the number of steps in the current round is 4, `Master` program starts the following round. img alt **Strategic messages:** [Strategic programs](https://github.com/gear-foundation/dapps/tree/master/contracts/syndote/player) send strategic messages depending on the position on the monopoly field. A player may be in one of the following positions: * A cell with his own property. A player has a right: * To add a gear on the cell. The gear can be `Bronze`, `Silver` or `Gold`; * To upgrade a gear (from `Bronze` to `Silver` and from `Silver` to `Gold`); * Do nothing. During actions `AddGear` and `Upgrade` a player can sell his other properties to the bank if he does not have enough balance. * A cell with other properties. A player has to pay rent. If he has not enough balance, he can sell his properties to the bank. Otherwise he is declared bankrupt. * Sell with properties which no one has bought yet. A player has a right: * Buy cell (with option to sell properties to the bank if not enough balance); * Do nothing. * Jail. A player can throw the rolls. If it is double he gets out of the jail and his position is moved for the roll sum. If it is not a double a player can pay a fine or wait for the next round to throw the rolls again. * Empty cells. In the `Master` program there are cells that are not filled with anything. The developers themselves can expand the functionality of the game and add different game mechanics. Player's program [#players-program] `Player` is a program in which the monopoly game strategy is implemented. It must meet the following requirements: * It has to accept the following message from `Master` program: ```rust title="syndote/app/src/services/game/utils.rs" pub struct YourTurn { pub players: Vec<(ActorId, PlayerInfo)>, pub properties: Vec>, } ``` That struct contains the information about the state of all players and about the monopoly fields and built gears. * Based on the information received, the strategic program has to make a move (`BuyCell`, `AddGear`, `Upgrade`, `PayRent`, `ThrowRolls` etc). The move must be the asynchronous message to the monopoly program. * The move has to be made correctly. For example, if a player is in an already occupied field and tries to buy it, then `Master`program will fine him. Each wrong step is penalized with one point. If a player scores 5 penalty points, it is out of the game. * After the step a player has to send a reply to the monopoly program. This way the monopoly program knows about the completion of the step. The reply can be a simple empty message. * If a player's program doesn’t reply, `Master` program will remove that player from the game. * `Master` program gives a limited amount of gas per turn. That amount is quite enough for the move, but if the player's program contains an incorrect logic (for example, an infinite loop), it will be removed from the game. # Racing Cars The Racing Cars game revolves around a competition of program algorithms. In essence, participants upload their personalized program strategies, all managed by a central Master program. These strategies are open to optimization and can be re-uploaded. Racing Cars In a well-known Ethereum-based [0xMonaco](https://mirror.xyz/matchboxdao.eth/-xVvvo36y-LHvrW99heb4VJ-xAOWQ-BQAx6MCwHLWnk) game, participants upload their personalized program strategies, all managed by a central Master contract. Central components were necessary to enable multi-block gameplay. However, in the case of Vara, the game operates **fully on-chain**, thanks to the asynchronous messaging paradigm. Various actors (programs) communicate with each other, and if a game round can't be accommodated within a single block, it carries over into subsequent ones. For this version of the game, improvements were made to increase its appeal. The game features a competition where a player races against two pre-loaded programs on the blockchain. Participants have three cars: the player's car is green, while the competitors' cars are white. Both the player and the program algorithms can choose to either accelerate, moving the car forward, or shoot at the nearest car to slow it down. The objective is to cross the finish line first to win the race. The game example uses the [**EZ-Transactions package**](/docs/developing/api/tooling/gasless-txs) that simplifies blockchain interactions by enabling gasless and signless transactions. Anyone can use it to integrate into their dApp projects. For more details, visit the [GitHub page](https://github.com/gear-foundation/dapps/tree/vt-update-ez-transactions/frontend/packages/ez-transactions). The source code for the game program and algorithm examples are available on [GitHub](https://github.com/gear-foundation/dapps/tree/master/contracts/car-races). The [frontend application](https://github.com/gear-foundation/dapps/tree/master/frontend/apps/racing-car-game) facilitates gameplay and interacts with the programs. This article describes the program interface, data structure, basic functions and explains their purpose. It can be used as is or modified to suit your own scenarios. How to run [#how-to-run] 1. Build a program > Additional details regarding this matter can be located within the [README](https://github.com/gear-foundation/dapps/tree/master/contracts/car-races/README.md) directory of the program. 2. Upload the program to the [Vara Network Mainnet](https://idea.gear-tech.io/programs?node=wss%3A%2F%2Frpc.vara.network) > Initiate the process by uploading the bot program, followed by the subsequent upload of the main program. Further details regarding the process of program uploading can be located within the [Getting Started](/docs/vara-network/quick-start#deploy-your-program-to-vara-mainnet) section. 3. Build and run user interface > More information about this can be found in the [README](https://github.com/gear-foundation/dapps/blob/master/frontend/apps/racing-car-game/README.md) directory of the frontend. Implementation details [#implementation-details] To implement this game, the [CarRacesService](https://github.com/gear-foundation/dapps/tree/master/contracts/car-races/app/src/services/mod.rs) was developed, which contains all the core game functionality. Program description [#program-description] The program contains the following information ```rust title="car-races/app/src/services/mod.rs" pub struct ContractData { admins: Vec, strategy_ids: Vec, games: HashMap, messages_allowed: bool, dns_info: Option<(ActorId, String)>, } ``` * `admins` - game admins * `strategy_ids` - program strategy ids * `games` - game information for each player * `messages_allowed` - access to playability * `dns_info` - optional field that stores the [dDNS](/docs/vara-network/examples/Infra/dein) address and the program name. Where `Game` is defined as follows: ```rust title="car-races/app/src/services/game.rs" pub struct Game { pub cars: BTreeMap, pub car_ids: Vec, pub current_turn: u8, pub state: GameState, pub result: Option, pub current_round: u32, pub last_time_step: u64, } ``` ```rust title="car-races/app/src/services/game.rs" pub struct Car { pub position: u32, pub speed: u32, pub car_actions: Vec, pub round_result: Option, } ``` Initialization [#initialization] To initialize the game program, the game configuration and the optional DNS address and name must be provided. ```rust title="car-races/src/app/services/mod.rs" pub async fn init( config: InitConfig, dns_id_and_name: Option<(ActorId, String)>, ) { unsafe { DATA = Some(ContractData { admins: vec![exec_context.actor_id()], games: HashMap::with_capacity(20_000), dns_info: dns_id_and_name.clone(), ..Default::default() }); CONFIG = Some(config.config); } if let Some((id, name)) = dns_id_and_name { let request = [ "Dns".encode(), "AddNewProgram".to_string().encode(), (name, exec::program_id()).encode(), ] .concat(); msg::send_bytes_with_gas_for_reply(id, request, 5_000_000_000, 0, 0) .expect("Error in sending message") .await .expect("Error in `AddNewProgram`"); } } ``` Service functions [#service-functions] ``` !@sails: 1.0.0 service CarRacesService { events { RoundInfo(RoundInfo), GameFinished { player: actor }, Killed { inheritor: actor }, } functions { // Admin actions AddAdmin(admin: actor); RemoveAdmin(admin: actor); UpdateConfig(config: ServicesConfig); AllowMessages(messages_allowed: bool); Kill(inheritor: actor); AddStrategyIds(car_ids: [actor]); RemoveGameInstance(account: actor); RemoveInstances(player_ids: Option<[actor]>); // Player (game-related) actions StartGame(session_for_account: Option); PlayerMove(strategy_move: StrategyAction, session_for_account: Option); // Queries (read-only operations) @query Admins() -> [actor]; @query AllGames() -> [(actor, Game)]; @query ConfigState() -> ServicesConfig; @query DnsInfo() -> Option<(actor, String)>; @query Game(account_id: actor) -> Option; @query MessagesAllowed() -> bool; @query StrategyIds() -> [actor]; } } ``` Logic [#logic] Before starting the game, the admin must first send a message to enable or disable message processing for the game: ```rust title="car-races/app/src/services/mod.rs" pub fn allow_messages(&mut self, messages_allowed: bool) { let msg_src = msg::source(); assert!(self.data().admins.contains(&msg_src), "Not admin"); self.data_mut().messages_allowed = messages_allowed; } ``` After that, the admin needs to add two strategy contracts that will play against the user: ```rust title="car-races/app/src/services/mod.rs" pub fn add_strategy_ids(&mut self, car_ids: Vec) { let msg_src = msg::source(); assert!(self.data().messages_allowed, "Message processing suspended"); assert!(self.data().admins.contains(&msg_src), "Not admin"); assert_eq!(car_ids.len(), 2, "Must be two strategies"); self.data_mut().strategy_ids = car_ids; } ``` This function, `start_game`, initializes a new game session for a player, supporting signless/gasless [sessions](https://github.com/gear-foundation/signless-gasless-session-service), which allows players to participate in games without requiring them to sign each action or pay gas fees. You can read more about signless/gasless sessions [here](/docs/vara-network/about/features/gassignless). ```rust title="car-races/app/src/services/mod.rs" pub fn start_game(&mut self, session_for_account: Option) { // Ensure that message processing is allowed before starting the game. assert!(self.data().messages_allowed, "Message processing suspended"); let msg_src = msg::source(); let sessions = SessionStorage::get_session_map(); // Determine the player, either from the session or the message source. let player = get_player( sessions, &msg_src, &session_for_account, ActionsForSession::StartGame, ); let last_time_step = exec::block_timestamp(); let strategy_ids = self.data().strategy_ids.clone(); // Check if the player already has a game; if finished, reset it, otherwise create a new one. let game = if let Some(game) = self.data_mut().games.get_mut(&player) { assert!(game.state == GameState::Finished, "Game already started"); game.current_round = 0; game.result = None; game.last_time_step = last_time_step; game } else { self.data_mut().games.entry(player).or_insert_with(|| Game { last_time_step, ..Default::default() }) }; // Initialize the cars with the player and two strategy contracts. game.car_ids = vec![player, strategy_ids[0], strategy_ids[1]]; let initial_state = Car { position: 0, speed: config().initial_speed, car_actions: Vec::new(), round_result: None, }; game.cars.insert(player, initial_state.clone()); game.cars.insert(strategy_ids[0], initial_state.clone()); game.cars.insert(strategy_ids[1], initial_state); // Set the game state to allow player actions. game.state = GameState::PlayerAction; } ``` The function `player_move` handles the player's move in the game and ensures proper state transitions and actions based on the player's input. It also supports signless/gasless sessions to enhance player interaction without requiring manual signing or gas fees. ```rust title="car-races/app/src/services/mod.rs" pub async fn player_move( &mut self, strategy_move: StrategyAction, session_for_account: Option, ) { // Ensure that message processing is allowed before processing the move. assert!(self.data().messages_allowed, "Message processing suspended"); let msg_src = msg::source(); let sessions = SessionStorage::get_session_map(); // Determine the player based on the message source or session. let player = get_player( sessions, &msg_src, &session_for_account, ActionsForSession::Move, ); // Retrieve the current game for the player. let game = self.get_game(&player); // Handle the player's move within an asynchronous event handler. event_or_panic_async!(self, || async move { // Validate the current game state. game.verify_game_state()?; // Apply the player's strategy move. game.apply_strategy_move(strategy_move); // Transition the game state to the race phase and update the timestamp. game.state = GameState::Race; game.last_time_step = exec::block_timestamp(); let num_of_cars = game.car_ids.len() as u8; // Update the turn to the next car. game.current_turn = (game.current_turn + 1) % num_of_cars; let mut round_info: Option = None; let mut game_finished = false; // Continue processing car turns until the player can act or the game finishes. while !game.is_player_action_or_finished() { game.process_car_turn().await?; // After all cars have acted, the game returns to the player. if game.current_turn == 0 { game.state = GameState::PlayerAction; game.current_round = game.current_round.saturating_add(1); // Update the positions of the cars after the round. game.update_positions(); // Create round info to track the current state of the game. round_info = Some(create_round_info(game)); // If the game is finished, a delayed message is sent to remove the game instance. if game.state == GameState::Finished { send_msg_to_remove_game_instance(player); game_finished = true; } } } // Return the round info as an event or handle an unexpected state. match round_info { Some(info) => { self.emit_event(Event::RoundInfo(info)) .expect("Notification Error"); if game_finished { self.emit_event(Event::GameFinished { player: msg_src }) .expect("Notification Error"); } } None => { panic(Error::UnexpectedState); } } }) } ``` Source code [#source-code] The source code of this example of Racing Cars Game program and the example of an implementation of its testing is available on [gear-foundation/dapp/contracts/car-races](https://github.com/gear-foundation/dapps/tree/master/contracts/car-races). See also an example of the program testing implementation based on `gtest`: [gear-foundation/dapps/car-races/tests](https://github.com/gear-foundation/dapps/tree/master/contracts/car-races/app/tests). For more details about testing programs written on Vara, refer to the [Program Testing](/docs/developing/build/testing) article. # Tic-Tac-Toe A classic and simple game in which the user competes against a program operating on the blockchain network. Tic-Tac-Toe Usually, the state of a program advances as the application is utilized. A distinctive feature of this game's program implementation is its capability to clean up its storage. In other words, as soon as the game session is completed and the results are recorded in the program, all unnecessary data structures are purged automatically through a special **delayed message**. [Delayed messages](/docs/developing/build/gstd/delayed-messages) represent one of the various unique features of the Gear Protocol. The game example uses the [**EZ-Transactions package**](/docs/developing/api/tooling/gasless-txs) that simplifies blockchain interactions by enabling gasless and signless transactions. Anyone can use it to integrate into their dApp projects. For more details, visit the [GitHub page](https://github.com/gear-foundation/dapps/tree/master/frontend/packages/ez-transactions). The source code, developed using the [Sails](/docs/developing/build/sails) framework, is available on [GitHub](https://github.com/gear-foundation/dapps/tree/master/contracts/tic-tac-toe). The [frontend application](https://github.com/gear-foundation/dapps/tree/master/frontend/apps/tic-tac-toe) facilitates gameplay and interacts with the smart program. This article describes the program interface, data structure, basic functions and explains their purpose. It can be used as is or modified to suit your own scenarios. How to run [#how-to-run] 1. Build a program > Additional details regarding this matter can be located within the [README](https://github.com/gear-foundation/dapps/tree/master/contracts/tic-tac-toe/README.md) directory of the program. 2. Upload the program to the [Vara Network Mainnet](https://idea.gear-tech.io/programs?node=wss%3A%2F%2Frpc.vara.network) > Initiate the process by uploading the bot program, followed by the subsequent upload of the main program. Further details regarding the process of program uploading can be located within the [Getting Started](/docs/vara-network/quick-start#deploy-your-program-to-vara-mainnet) section. 3. Build and run user interface > More information about this can be found in the [README](https://github.com/gear-foundation/dapps/blob/master/frontend/apps/tic-tac-toe/README.md) directory of the frontend. Implementation details [#implementation-details] Program description [#program-description] The program contains the following information ```rust title="tic-tac-toe/app/services/game/mod.rs" pub struct Storage { admins: Vec, current_games: HashMap, config: Config, messages_allowed: bool, dns_info: Option<(ActorId, String)>, } ``` * `admins` - game admins * `current_games` - game information for each player * `config` - game configuration * `messages_allowed` - access to playability * `dns_info` - optional field that stores the [dDNS](/docs/vara-network/examples/Infra/dein) address and the program name. Where `GameInstance` is defined as follows: ```rust title="tic-tac-toe/app/services/game/utils.rs" pub struct GameInstance { pub board: Vec>, pub player_mark: Mark, pub bot_mark: Mark, pub last_time: u64, pub game_over: bool, pub game_result: Option, } ``` ```rust title="tic-tac-toe/app/services/game/utils.rs" pub enum Mark { X, O, } ``` Initialization [#initialization] To initialize the game program, the game configuration and the optional DNS address and name must be provided. ```rust title="tic-tac-toe/app/services/game/mod.rs" pub async fn init(config: Config, dns_id_and_name: Option<(ActorId, String)>) -> Self { unsafe { STORAGE = Some(Storage { admins: vec![msg::source()], current_games: HashMap::with_capacity(10_000), config, messages_allowed: true, dns_info: dns_id_and_name.clone(), }); } if let Some((id, name)) = dns_id_and_name { let request = [ "Dns".encode(), "AddNewProgram".to_string().encode(), (name, exec::program_id()).encode(), ] .concat(); msg::send_bytes_with_gas_for_reply(id, request, 5_000_000_000, 0, 0) .expect("Error in sending message") .await .expect("Error in `AddNewProgram`"); } Self(()) } ``` ```rust title="tic-tac-toe/app/services/game/utils.rs" pub struct Config { pub s_per_block: u64, pub gas_to_remove_game: u64, pub gas_to_delete_session: u64, pub time_interval: u32, pub turn_deadline_ms: u64, pub minimum_session_duration_ms: u64, } ``` * `s_per_block` - time per block in seconds * `gas_to_remove_game` - gas to delete a game using delayed messages * `gas_to_delete_session` - gas to delete a game session using delayed messages * `time_interval` - time after which the game should be deleted using delayed messages * `turn_deadline_ms` - turnaround time * `minimum_session_duration_ms` - minimum session duration Action [#action] ```rust title="tic-tac-toe/app/services/game/mod.rs" // user actions pub fn start_game(&mut self, session_for_account: Option); pub fn turn(&mut self, step: u8, session_for_account: Option); pub fn skip(&mut self, session_for_account: Option); // action for the program itself (for delayed messages) pub fn remove_game_instance(&mut self, account: ActorId); // admin actions pub fn remove_game_instances(&mut self, accounts: Option>); pub fn add_admin(&mut self, admin: ActorId); pub fn remove_admin(&mut self, admin: ActorId); pub fn update_config( &mut self, s_per_block: Option, gas_to_remove_game: Option, time_interval: Option, turn_deadline_ms: Option, gas_to_delete_session: Option, ); pub fn allow_messages(&mut self, messages_allowed: bool); pub async fn kill(&mut self, inheritor: ActorId); ``` Event [#event] ```rust title="tic-tac-toe/app/services/game/mod.rs" pub enum Event { GameFinished { game: GameInstance, player_address: ActorId, }, GameStarted { game: GameInstance, }, MoveMade { game: GameInstance, }, GameInstanceRemoved, ConfigUpdated, AdminRemoved, AdminAdded, StatusMessagesUpdated, Killed { inheritor: ActorId, }, } ``` Logic [#logic] Start Game [#start-game] At the start of the game, the program checks its status and verifies whether the player has an unfinished game. The first move is determined randomly; if the bot is assigned the first move, it automatically plays in the center of the board. ```rust title="tic-tac-toe/app/services/game/funcs.rs" pub fn start_game( storage: &mut Storage, sessions: &HashMap, msg_source: ActorId, session_for_account: Option, ) -> Result { check_allow_messages(storage, msg_source)?; let player = get_player( sessions, &msg_source, &session_for_account, ActionsForSession::StartGame, ); if let Some(current_game) = storage.current_games.get(&player) { if !current_game.game_over { return Err(GameError::GameIsAlreadyStarted); } } let turn = random_turn(player); let (player_mark, bot_mark) = if turn == 0 { (Mark::O, Mark::X) } else { (Mark::X, Mark::O) }; let mut game_instance = GameInstance { board: vec![None; 9], player_mark, bot_mark, last_time: exec::block_timestamp(), game_result: None, game_over: false, }; if bot_mark == Mark::X { game_instance.board[4] = Some(Mark::X); } storage.current_games.insert(player, game_instance.clone()); Ok(Event::GameStarted { game: game_instance, }) } //.. fn check_allow_messages(storage: &Storage, msg_source: ActorId) -> Result<(), GameError> { if !storage.messages_allowed && !storage.admins.contains(&msg_source) { return Err(GameError::NotAllowedToSendMessages); } Ok(()) } ``` Turn [#turn] After successfully starting a new game, players can take their turn where a series of the following checks are performed: ```rust title="tic-tac-toe/app/services/game/funcs.rs" pub fn turn( storage: &mut Storage, sessions: &HashMap, msg_source: ActorId, step: u8, session_for_account: Option, ) -> Result { check_allow_messages(storage, msg_source)?; let player = get_player( sessions, &msg_source, &session_for_account, ActionsForSession::StartGame, ); let game_instance = storage .current_games .get_mut(&player) .ok_or(GameError::GameIsNotStarted)?; if game_instance.board[step as usize].is_some() { return Err(GameError::CellIsAlreadyOccupied); } if game_instance.game_over { return Err(GameError::GameIsAlreadyOver); } let block_timestamp = exec::block_timestamp(); if game_instance.last_time + storage.config.turn_deadline_ms < block_timestamp { return Err(GameError::MissedYourTurn); } //.. ``` After successful game status checks, the player's move is saved and the time of the last move is updated ```rust title="tic-tac-toe/app/services/game/funcs.rs" //.. game_instance.board[step as usize] = Some(game_instance.player_mark); game_instance.last_time = block_timestamp; if let Some(mark) = get_result(&game_instance.board.clone()) { if mark == game_instance.player_mark { game_over(game_instance, &player, &storage.config, GameResult::Player); } else { game_over(game_instance, &player, &storage.config, GameResult::Bot); } return Ok(Event::GameFinished { game: game_instance.clone(), player_address: player, }); } // .. ``` If the game is over, a **delayed message** will be sent to delete the game from the program ```rust title="tic-tac-toe/app/services/game/funcs.rs" fn game_over( game_instance: &mut GameInstance, player: &ActorId, config: &Config, result: GameResult, ) { game_instance.game_over = true; game_instance.game_result = Some(result); send_delayed_message_to_remove_game(*player, config.gas_to_remove_game, config.time_interval); } fn send_delayed_message_to_remove_game( account: ActorId, gas_to_remove_game: u64, time_interval: u32, ) { let request = [ "TicTacToe".encode(), "RemoveGameInstance".to_string().encode(), (account).encode(), ] .concat(); msg::send_bytes_with_gas_delayed( exec::program_id(), request, gas_to_remove_game, 0, time_interval, ) .expect("Error in sending message"); } ``` But if the game is not over, the turn passes to the bot and the same actions are performed ```rust title="tic-tac-toe/app/services/game/funcs.rs" let bot_step = make_move(game_instance); if let Some(step_num) = bot_step { game_instance.board[step_num] = Some(game_instance.bot_mark); } if let Some(mark) = get_result(&game_instance.board.clone()) { if mark == game_instance.player_mark { game_over( game_instance, &msg_source, &storage.config, GameResult::Player, ); } else { game_over(game_instance, &msg_source, &storage.config, GameResult::Bot); } return Ok(Event::GameFinished { game: game_instance.clone(), player_address: player, }); } else if !game_instance.board.contains(&None) || bot_step.is_none() { game_over( game_instance, &msg_source, &storage.config, GameResult::Draw, ); return Ok(Event::GameFinished { game: game_instance.clone(), player_address: player, }); } Ok(Event::MoveMade { game: game_instance.clone(), }) } ``` Skip [#skip] If the player misses their turn, a *Skip* command must be sent to continue the game and allow the bot to make its move. ```rust title="tic-tac-toe/app/services/game/funcs.rs" pub fn skip( storage: &mut Storage, sessions: &HashMap, msg_source: ActorId, session_for_account: Option, ) -> Result { check_allow_messages(storage, msg_source)?; let player = get_player( sessions, &msg_source, &session_for_account, ActionsForSession::StartGame, ); let game_instance = storage .current_games .get_mut(&player) .ok_or(GameError::GameIsNotStarted)?; if game_instance.game_over { return Err(GameError::GameIsAlreadyOver); } let block_timestamp = exec::block_timestamp(); if game_instance.last_time + storage.config.turn_deadline_ms >= block_timestamp { return Err(GameError::NotMissedTurnMakeMove); } let bot_step = make_move(game_instance); game_instance.last_time = block_timestamp; match bot_step { Some(step_num) => { game_instance.board[step_num] = Some(game_instance.bot_mark); let win = get_result(&game_instance.board.clone()); if let Some(mark) = win { if mark == game_instance.player_mark { game_over(game_instance, &player, &storage.config, GameResult::Player); } else { game_over(game_instance, &player, &storage.config, GameResult::Bot); } return Ok(Event::GameFinished { game: game_instance.clone(), player_address: player, }); } else if !game_instance.board.contains(&None) { game_over(game_instance, &player, &storage.config, GameResult::Draw); return Ok(Event::GameFinished { game: game_instance.clone(), player_address: player, }); } } None => { game_over(game_instance, &player, &storage.config, GameResult::Draw); return Ok(Event::GameFinished { game: game_instance.clone(), player_address: player, }); } } Ok(Event::MoveMade { game: game_instance.clone(), }) } ``` Query [#query] ```rust title="tic-tac-toe/app/services/game/mod.rs" pub fn admins(&self) -> &'static Vec { &self.get().admins } pub fn game(&self, player_id: ActorId) -> Option { self.get().current_games.get(&player_id).cloned() } pub fn all_games(&self) -> Vec<(ActorId, GameInstance)> { self.get().current_games.clone().into_iter().collect() } pub fn config(&self) -> &'static Config { &self.get().config } pub fn messages_allowed(&self) -> &'static bool { &self.get().messages_allowed } pub fn dns_info(&self) -> Option<(ActorId, String)> { self.get().dns_info.clone() } ``` * `admins(&self)`: Returns the list of game administrators. * `game(&self, player_id: ActorId)`: Returns the current game instance for a specific player, if it exists. * `all_games(&self)`: Returns a list of all ongoing game instances along with their associated player IDs. * `config(&self)`: Returns the game configuration settings. * `messages_allowed(&self)`: Indicates whether messages are allowed in the current game state. * `dns_info(&self)`: Returns the optional dDNS information, including the dDNS address and program name, if available. Source code [#source-code] The source code of this example of Tic-Tac-Toe Game program and the example of an implementation of its testing is available on [gear-foundation/dapp/contracts/tic-tac-toe](https://github.com/gear-foundation/dapps/tree/master/contracts/tic-tac-toe). See also an example of the smart contract testing implementation based on `gtest`: [gear-foundation/dapps/contracts/tic-tac-toe/tests](https://github.com/gear-foundation/dapps/tree/master/contracts/tic-tac-toe/tests). For more details about testing programs written on Gear, refer to the [Program Testing](/docs/developing/build/testing) article. # Vara-Man Game varaman Vara-man is a classic arcade game, the main character is Vara mouse, who must collect all the coins in the allotted time. In the labyrinth, the main character will encounter cat enemies that must be avoided in order to survive and collect all the coins. The game example uses the [**EZ-Transactions package**](/docs/developing/api/tooling/gasless-txs) that simplifies blockchain interactions by enabling gasless and signless transactions. Anyone can use it to integrate into their dApp projects. For more details, visit the [GitHub page](https://github.com/gear-foundation/dapps/tree/master/frontend/packages/ez-transactions). It implies [vouchers](/docs/developing/api/vouchers) that allow users to play with a zero tokens on their balance (**gasless transactions**). This requires activation of the backend to issue vouchers (instructions for voucher integration will be detailed in the article). Another feature of this example is an option that allos anyone to interact with the dApp without needing to sign in (**signless transactions**). The dApp transaction operates as usual with one difference — a voucher is issued not directly to a user, but to a temporarily created account (sub-account) to which the user grants temporary rights to sign transactions on their behalf in this application. These features allow users to interact with the game for a certain period without the need to individually sign each transaction. By eliminating the repetitive signing process, this approach streamlines interactions and significantly improves the overall UX efficiency. The source code, developed using the [Sails](/docs/developing/build/sails) framework, is available on [GitHub](https://github.com/gear-foundation/dapps/tree/master/contracts/vara-man). This article describes the program interface, data structure, basic functions and explains their purpose. It can be used as is or modified to suit your own scenarios. The game has several reward modes, one of which requires connecting the fungible token program that needs to be uploaded separately. How to run [#how-to-run] 1. Build a program > Additional details regarding this matter can be located within the [README](https://github.com/gear-foundation/dapps/tree/master/contracts/vara-man/README.md) directory of the program. 2. Upload the program to the [Vara Network Mainnet](https://idea.gear-tech.io/programs?node=wss%3A%2F%2Frpc.vara.network) > Further details regarding the process of program uploading can be located within the [Getting Started](/docs/vara-network/quick-start#deploy-your-program-to-vara-mainnet) section. 3. Build and run user interface > More information about this can be found in the [README](https://github.com/gear-foundation/dapps/blob/master/frontend/apps/vara-man/README.md) directory of the frontend. 4. **Optional**. > In case of a reward mode in the form of fungible tokens , build the program as described in the [README](https://github.com/gear-foundation/standards/tree/master/extended-vft/README.md) and upload the program to the [Vara Network Mainnet](https://idea.gear-tech.io/programs?node=wss%3A%2F%2Frpc.vara.network). After that it will be necessary to send a message `ChangeStatus` to the program to put it in status `StartedWithFungibleToken { ft_address }`, where the address of the loaded program should be specified. 5. **Optional**. Build and run the backend to release vouchers. Implementation details [#implementation-details] The VaraMan program contains the following information: ```rust title="vara-man/app/src/services/game/mod.rs" pub struct GameStorage { tournaments: HashMap, players_to_game_id: HashMap, status: Status, config: Config, admins: Vec, dns_info: Option<(ActorId, String)>, } ``` * `tournaments` - this field contains the addresses of the players and information about their games * `players_to_game_id` – this field maps each player's address to the address of the game they are currently playing in. * `status` - program status * `config` - program configuration * `admins` - admins addresses * `dns_info` - optional field that stores the [dDNS](/docs/vara-network/examples/Infra/dein) address and the program name Where the structure of the `Tournament` is defined as follows ```rust title="vara-man/app/src/services/game/mod.rs" pub struct Tournament { tournament_name: String, admin: ActorId, level: Level, participants: HashMap, bid: u128, stage: Stage, duration_ms: u32, } ``` * `tournament_name` - the name of the tournament * `admin` - the address of the tournament administrator * `level` - level of difficulty (Easy/Medium/Hard) * `participants` - a list of players and their corresponding information * `bid` - the entry fee or wager required to participate in the tournament * `stage` - the current stage of the tournament (Registration, Started, or Finished) * `duration_ms` - the duration of the tournament in milliseconds ```rust title="vara-man/app/src/services/game/utils.rs" pub struct Player { pub name: String, pub time: u128, pub points: u128, } ``` * `name` - the player's name * `time` - the total time the player has spent in the game * `points` - the player's score or points accumulated in the tournament ```rust title="vara-man/app/src/services/game/utils.rs" pub enum Stage { #[default] Registration, Started(u64), Finished(Vec), } ``` * `Registration` - the tournament is in the registration phase * `Started(u64)` - the tournament has begun, with a start timestamp * `Finished(Vec)` - the tournament is completed, and a list of winners is available The program has several modes: ```rust title="vara-man/app/src/services/game/utils.rs" pub enum Status { #[default] Paused, StartedUnrewarded, StartedWithFungibleToken { ft_address: ActorId, }, StartedWithNativeToken, } ``` * `Paused` - this status means that the game is suspended * `StartedUnrewarded` - the mode in which the reward is not given * `StartedWithFungibleToken` - the reward is given in the form of fungible tokens (the fungible token program must be uploaded separately) * `StartedWithNativeToken` - the reward is given in the form of native tokens Initialization [#initialization] To initialize the game program, the game configuration and the optional DNS address and name must be provided. ```rust title="vara-man/app/services/game/mod.rs" pub async fn init(config: Config, dns_id_and_name: Option<(ActorId, String)>) -> Self { unsafe { STORAGE = Some(GameStorage { config, admins: vec![msg::source()], dns_info: dns_id_and_name.clone(), ..Default::default() }); } if let Some((id, name)) = dns_id_and_name { let request = [ "Dns".encode(), "AddNewProgram".to_string().encode(), (name, exec::program_id()).encode(), ] .concat(); msg::send_bytes_with_gas_for_reply(id, request, 5_000_000_000, 0, 0) .expect("Error in sending message") .await .expect("Error in `AddNewProgram`"); } Self(()) } ``` ```rust title="vara-man/app/services/game/utils.rs" pub struct Config { pub one_point_in_value: u128, pub max_number_gold_coins: u16, pub max_number_silver_coins: u16, pub points_per_gold_coin_easy: u128, pub points_per_silver_coin_easy: u128, pub points_per_gold_coin_medium: u128, pub points_per_silver_coin_medium: u128, pub points_per_gold_coin_hard: u128, pub points_per_silver_coin_hard: u128, pub gas_for_finish_tournament: u64, pub gas_for_mint_fungible_token: u64, pub time_for_single_round: u32, pub gas_to_delete_session: u64, pub minimum_session_duration_ms: u64, pub s_per_block: u64, } ``` * `one_coin_in_value` - the price of one coin in the value system * `max_number_gold_coins` - the maximum number of gold coins that can be earned in a game * `max_number_silver_coins` - the maximum number of silver coins that can be earned in a game * `points_per_gold_coin_easy` - gold coin price at easy level * `points_per_silver_coin_easy` - silver coin price at easy level * `points_per_gold_coin_medium` - gold coin price at medium level * `points_per_silver_coin_medium` - silver coin price at medium level * `points_per_gold_coin_hard` - gold coin price at hard level * `points_per_silver_coin_hard` - silver coin price at hard level * `gas_for_finish_tournament` - the gas required to finish the tournament * `gas_for_mint_fungible_token` - the gas required to mint fungible tokens as rewards * `gas_to_delete_session` - the gas required to delete a session * `minimum_session_duration_ms` - the minimum duration for a session (in milliseconds) * `s_per_block` - the number of seconds per block for the game timer system Action [#action] ```rust title="vara-man/app/services/game/mod.rs" // user actions pub fn create_new_tournament( &mut self, tournament_name: String, name: String, level: Level, duration_ms: u32, session_for_account: Option, ); pub fn register_for_tournament( &mut self, admin_id: ActorId, name: String, session_for_account: Option, ); pub fn cancel_register(&mut self, session_for_account: Option); pub fn cancel_tournament(&mut self, session_for_account: Option); pub fn delete_player(&mut self, player_id: ActorId, session_for_account: Option); pub async fn finish_single_game( &mut self, gold_coins: u16, silver_coins: u16, level: Level, session_for_account: Option, ); pub fn start_tournament(&mut self, session_for_account: Option); pub fn record_tournament_result( &mut self, time: u128, gold_coins: u16, silver_coins: u16, session_for_account: Option, ); pub fn leave_game(&mut self, session_for_account: Option); // action for the program itself (for delayed messages) pub fn finish_tournament(&mut self, admin_id: ActorId, time_start: u64); // admin actions pub fn change_status(&mut self, status: Status); pub fn change_config(&mut self, config: Config); pub fn add_admin(&mut self, new_admin_id: ActorId); pub async fn kill(&mut self, inheritor: ActorId); ``` Event [#event] ```rust title="vara-man/app/services/game/mod.rs" pub enum Event { GameFinished { winners: Vec, participants: Vec<(ActorId, Player)>, prize: u128, }, SingleGameFinished { gold_coins: u16, silver_coins: u16, points: u128, maximum_possible_points: u128, maximum_number_gold_coins: u16, maximum_number_silver_coins: u16, prize: u128, player_address: ActorId, }, NewTournamentCreated { tournament_name: String, name: String, level: Level, bid: u128, }, PlayerRegistered { admin_id: ActorId, name: String, bid: u128, }, RegisterCanceled, TournamentCanceled { admin_id: ActorId, }, PlayerDeleted { player_id: ActorId, }, ResultTournamentRecorded { gold_coins: u16, silver_coins: u16, time: u128, points: u128, maximum_possible_points: u128, maximum_number_gold_coins: u16, maximum_number_silver_coins: u16, player_address: ActorId, }, GameStarted, AdminAdded(ActorId), StatusChanged(Status), ConfigChanged(Config), LeftGame, Killed { inheritor: ActorId, }, } ``` Logic [#logic] Create a Tournament: [#create-a-tournament] A new tournament is created by specifying the tournament name, difficulty level, and duration. The player who creates it becomes the admin, and participants can later join. ```rust title="vara-man/app/services/game/funcs.rs" pub fn create_new_tournament( storage: &mut GameStorage, sessions: &HashMap, tournament_name: String, name: String, level: Level, duration_ms: u32, session_for_account: Option, ) -> Result { let msg_src = msg::source(); let msg_value = msg::value(); let player = get_player( sessions, &msg_src, &session_for_account, ActionsForSession::CreateNewTournament, ); if storage.status == Status::Paused { msg::send_with_gas(msg_src, "", 0, msg_value).expect("Error in sending the value"); return Err(GameError::GameIsPaused); } if storage.tournaments.contains_key(&player) { msg::send_with_gas(msg_src, "", 0, msg_value).expect("Error in sending the value"); return Err(GameError::AlreadyHaveTournament); } let mut participants = HashMap::new(); participants.insert( player, Player { name: name.clone(), time: 0, points: 0, }, ); let game = Tournament { tournament_name: tournament_name.clone(), admin: player, level, participants, bid: msg_value, stage: Stage::Registration, duration_ms, }; storage.tournaments.insert(player, game); storage.players_to_game_id.insert(player, player); Ok(Event::NewTournamentCreated { tournament_name, name, level, bid: msg_value, }) } ``` Register for the Tournament: [#register-for-the-tournament] Players can register for the tournament by providing their name and placing a bid. This bid must match the one set for the tournament, and players will be added to the participants list. ```rust title="vara-man/app/services/game/funcs.rs" pub fn register_for_tournament( storage: &mut GameStorage, sessions: &HashMap, admin_id: ActorId, name: String, session_for_account: Option, ) -> Result { let msg_src = msg::source(); let msg_value = msg::value(); let player = get_player( sessions, &msg_src, &session_for_account, ActionsForSession::RegisterForTournament, ); let reply = register(storage, player, msg_value, admin_id, name); if reply.is_err() { msg::send_with_gas(msg_src, "", 0, msg_value).expect("Error in sending the value"); } reply } fn register( storage: &mut GameStorage, player: ActorId, msg_value: u128, admin_id: ActorId, name: String, ) -> Result { if storage.status == Status::Paused { return Err(GameError::GameIsPaused); } if storage.players_to_game_id.contains_key(&player) { return Err(GameError::SeveralRegistrations); } let game = storage .tournaments .get_mut(&admin_id) .ok_or(GameError::NoSuchGame)?; if game.stage != Stage::Registration { return Err(GameError::WrongStage); } if game.participants.len() >= MAX_PARTICIPANTS.into() { return Err(GameError::SessionFull); } if game.bid != msg_value { return Err(GameError::WrongBid); } game.participants.insert( player, Player { name: name.clone(), time: 0, points: 0, }, ); storage.players_to_game_id.insert(player, admin_id); Ok(Event::PlayerRegistered { admin_id, name, bid: msg_value, }) } ``` Start the Tournament: [#start-the-tournament] Once the tournament has enough participants, the admin can start the tournament. A delayed message is sent, which ensures the tournament will be completed after a set duration. ```rust title="vara-man/app/services/game/funcs.rs" pub fn start_tournament( storage: &mut GameStorage, sessions: &HashMap, session_for_account: Option, ) -> Result { let msg_src = msg::source(); let player = get_player( sessions, &msg_src, &session_for_account, ActionsForSession::StartTournament, ); if storage.status == Status::Paused { return Err(GameError::GameIsPaused); } let game = storage .tournaments .get_mut(&player) .ok_or(GameError::NoSuchGame)?; if game.stage != Stage::Registration { return Err(GameError::WrongStage); } let time_start = exec::block_timestamp(); game.stage = Stage::Started(time_start); let request = [ "VaraMan".encode(), "FinishTournament".to_string().encode(), (player, time_start).encode(), ] .concat(); msg::send_bytes_with_gas_delayed( exec::program_id(), request, storage.config.gas_for_finish_tournament, 0, game.duration_ms / 3_000 + 1, ) .expect("Error in sending message"); Ok(Event::GameStarted) } ``` Record Results During the Game: [#record-results-during-the-game] As the tournament progresses, players can submit their game results, including the time played, the number of gold and silver coins collected. ```rust title="vara-man/app/services/game/funcs.rs" pub fn record_tournament_result( storage: &mut GameStorage, sessions: &HashMap, time: u128, gold_coins: u16, silver_coins: u16, session_for_account: Option, ) -> Result { if gold_coins > storage.config.max_number_gold_coins || silver_coins > storage.config.max_number_silver_coins { return Err(GameError::ExceededLimit); } let msg_src = msg::source(); let player = get_player( sessions, &msg_src, &session_for_account, ActionsForSession::RecordTournamentResult, ); let admin_id = storage .players_to_game_id .get(&player) .ok_or(GameError::NoSuchPlayer)?; let game = storage .tournaments .get_mut(admin_id) .ok_or(GameError::NoSuchGame)?; if !matches!(game.stage, Stage::Started(_)) { return Err(GameError::WrongStage); } let player = game .participants .get_mut(&player) .ok_or(GameError::NoSuchPlayer)?; let (points_for_gold, points_for_silver) = storage .config .get_points_per_gold_coin_for_level(game.level); let points = points_for_gold * gold_coins as u128 + points_for_silver * silver_coins as u128; let maximum_possible_points = points_for_gold * storage.config.max_number_gold_coins as u128 + points_for_silver * storage.config.max_number_silver_coins as u128; player.time += time; player.points += points; Ok(Event::ResultTournamentRecorded { gold_coins, silver_coins, time: player.time, points: player.points, maximum_possible_points, maximum_number_gold_coins: storage.config.max_number_gold_coins, maximum_number_silver_coins: storage.config.max_number_silver_coins, player_address: msg_src, }) } ``` Finish the Tournament: [#finish-the-tournament] After the tournament duration ends, the program automatically finishes the game. The tournament results are calculated by determining which player(s) earned the highest points in the shortest time. The prize is distributed equally among the winners, and the game stage is marked as finished, concluding the tournament. ```rust title="vara-man/app/services/game/funcs.rs" pub fn finish_tournament( storage: &mut GameStorage, admin_id: ActorId, time_start: u64, ) -> Result { if msg::source() != exec::program_id() { return Err(GameError::AccessDenied); } let game = storage .tournaments .get_mut(&admin_id) .ok_or(GameError::NoSuchGame)?; if game.stage != Stage::Started(time_start) { return Err(GameError::WrongStage); } let mut winners = Vec::new(); let mut max_points = 0; let mut min_time = u128::MAX; for (actor_id, player) in game.participants.iter() { if player.points > max_points { max_points = player.points; min_time = player.time; winners.clear(); winners.push(*actor_id); } else if player.points == max_points { if player.time < min_time { min_time = player.time; winners.clear(); winners.push(*actor_id); } else if player.time == min_time { winners.push(*actor_id); } } } let prize = game.bid * game.participants.len() as u128 / winners.len() as u128; if prize != 0 { winners.iter().for_each(|id| { msg::send_with_gas(*id, "", 0, prize).expect("Error in sending value"); }); } game.stage = Stage::Finished(winners.clone()); let participants: Vec<(ActorId, Player)> = game.participants.clone().into_iter().collect(); Ok(Event::GameFinished { winners, participants, prize, }) } ``` Single-Player Mode Completion: [#single-player-mode-completion] In addition to the multiplayer tournament mode, a single-player mode is also available. When a player finishes a single-player game, their results, including the number of gold and silver coins collected, are recorded. Depending on the game's status, the player either receives native tokens or fungible tokens as a reward. If the game status is `StartedWithFungibleToken`, the function sends a request to a fungible token program (FT) to mint the corresponding amount of tokens and assign them to the player. In the other case, if the game status is `StartedWithNativeToken`, the player is rewarded directly with the game's native tokens. ```rust title="vara-man/app/services/game/funcs.rs" pub async fn finish_single_game( storage: &mut GameStorage, sessions: &HashMap, gold_coins: u16, silver_coins: u16, level: Level, session_for_account: Option, ) -> Result { if gold_coins > storage.config.max_number_gold_coins || silver_coins > storage.config.max_number_silver_coins { return Err(GameError::ExceededLimit); } let msg_src = msg::source(); let _player = get_player( sessions, &msg_src, &session_for_account, ActionsForSession::FinishSingleGame, ); let (points_for_gold, points_for_silver) = storage.config.get_points_per_gold_coin_for_level(level); let points = points_for_gold * gold_coins as u128 + points_for_silver * silver_coins as u128; let maximum_possible_points = points_for_gold * storage.config.max_number_gold_coins as u128 + points_for_silver * storage.config.max_number_silver_coins as u128; let prize = storage.config.one_point_in_value * points; if storage.status == Status::StartedWithNativeToken { msg::send_with_gas(msg_src, "", 0, prize).expect("Error in sending value"); } else if let Status::StartedWithFungibleToken { ft_address } = storage.status { let value: U256 = prize.into(); let request = vft_io::Mint::encode_call(msg_src, value); msg::send_bytes_with_gas_for_reply( ft_address, request, storage.config.gas_for_mint_fungible_token, 0, 0, ) .expect("Error in sending a message") .await .expect("Error in mint Fungible Token"); } Ok(Event::SingleGameFinished { gold_coins, silver_coins, prize, points, maximum_possible_points, maximum_number_gold_coins: storage.config.max_number_gold_coins, maximum_number_silver_coins: storage.config.max_number_silver_coins, player_address: msg_src, }) } ``` Query [#query] ```rust title="vara-man/app/services/game/mod.rs" pub fn config(&self) -> &'static Config { &self.get().config } pub fn admins(&self) -> &'static Vec { &self.get().admins } pub fn status(&self) -> &'static Status { &self.get().status } pub fn get_tournament(&self, player_id: ActorId) -> Option<(TournamentState, Option)> { let storage = self.get(); if let Some(admin_id) = storage.players_to_game_id.get(&player_id) { if let Some(tournament) = storage.tournaments.get(admin_id) { let tournament_state = TournamentState { tournament_name: tournament.tournament_name.clone(), admin: tournament.admin, level: tournament.level, participants: tournament.participants.clone().into_iter().collect(), bid: tournament.bid, stage: tournament.stage.clone(), duration_ms: tournament.duration_ms, }; let time = match tournament.stage { Stage::Started(start_time) => Some(exec::block_timestamp() - start_time), _ => None, }; Some((tournament_state, time)) } else { None } } else { None } } pub fn all(&self) -> VaraManState { (*self.get()).clone().into() } ``` * `config` - Returns the game configuration settings * `admins` - Returns the list of administrators responsible for managing the game * `status` - Returns the current status of the program * `get_tournament` - Retrieves the current tournament information for a player, including details like the tournament name, admin, participants, bid, and the stage. It also calculates the time elapsed since the tournament started, if applicable. * `all` - Returns the entire state of the VaraMan game, including all the current tournaments, players, and settings. Source code [#source-code] The source code of this example of VaraMan Game program and the example of an implementation of its testing is available on [gear-foundation/dapp/contracts/vara-man](https://github.com/gear-foundation/dapps/tree/master/contracts/vara-man). See also an example of the smart contract testing implementation based on `gtest`: [gear-foundation/dapps/vara-man/tests](https://github.com/gear-foundation/dapps/tree/master/contracts/vara-man/app/tests). For more details about testing programs written on Gear, refer to the [Program Testing](/docs/developing/build/testing) article. # Decentralized DNS Decentralized Internet (DNS) demonstrates an on-chain server-less approach to websites and web applications hosting. Unlike server-based DNS built on centralized components and services, decentralized solutions running on the blockchain are characterized by boosted data security, enhanced data reconciliation, minimized system weak points, optimized resource allocation, and demonstrated great fault tolerance. It brings all the benefits of decentralization, such as censorship resistance, security resilience, and high transparency. Briefly, the solution consists of a DNS program that is uploaded on-chain. It lists programs (smart contracts) that are also uploaded on-chain and registered in the DNS program as DNS records. A DNS record consists of a pair - the name of the program and its address on the network. Frontend applications and backend services can refer to the DNS contract to find the current address of programs. This is particularly useful for programs that are frequently updated on the blockchain, leading to changes in their addresses. Consequently, developers often have to redeploy applications that interact with on-chain programs. With this new approach, anyone can create a DNS record and configure their application to access the DNS contract, using the name of the program to find its current address and then interact with it. In case of a program update on the network, the owner of the DNS record can simply update its address in the DNS without needing to redeploy frontend and backend services. This solution can be extended by hosting programs that have their user interface residing on the IPFS. The DNS program in this case can store program IDs and meta info of their interfaces (name and program identifier). The source code of the program is available on [GitHub](https://github.com/gear-foundation/dns). DNS Smart Contract Interface Overview [#dns-smart-contract-interface-overview] The DNS smart contract facilitates efficient management of DNS programs and their administrators, providing essential functions for adding, modifying, and querying program information, as well as handling related events. A DNS contract has the following interface: ```rust !@sails: 1.0.0 service Dns { events { // Emitted when a new program is added NewProgramAdded { name: String, contract_info: ContractInfo }, // Emitted when a program ID is changed ProgramIdChanged { name: String, contract_info: ContractInfo }, // Emitted when a program is deleted ProgramDeleted { name: String }, // Emitted when an admin is added to a program AdminAdded { name: String, contract_info: ContractInfo }, // Emitted when an admin is removed from a program AdminRemoved { name: String, contract_info: ContractInfo }, } functions { // Adds a new admin to the specified program AddAdminToProgram(name: String, new_admin: actor); // Registers a new program with the given name and ID AddNewProgram(name: String, program_id: actor); // Changes the program ID for the specified program ChangeProgramId(name: String, new_program_id: actor); // Deletes the smart contract instance DeleteMe(); // Deletes the specified program DeleteProgram(name: String); // Removes an admin from the specified program RemoveAdminFromProgram(name: String, admin_to_remove: actor); // Retrieves all registered programs and their info @query AllContracts() -> [(String, ContractInfo)]; // Retrieves all actor IDs @query GetAllAddresses() -> [actor]; // Retrieves all program names @query GetAllNames() -> [String]; // Retrieves contract info for the specified program name @query GetContractInfoByName(name: String) -> Option; // Retrieves the program name for the specified program ID @query GetNameByProgramId(program_id: actor) -> Option; } types { struct ContractInfo { admins: [actor], // List of administrator IDs for the program program_id: actor, // Unique identifier for the program registration_time: String, // Timestamp when the program was registered } } } ``` Connect a dApp to the Decentralized DNS [#connect-a-dapp-to-the-decentralized-dns] Options for interacting with the DNS contract: 1. Direct interaction with the DNS contract 2. Deploying a custom indexer that will process the events of the DNS contract Let's consider the first option in detail: 1. Deploy the program on the [network](https://idea.gear-tech.io) and obtain its ActorId. 2. Use the `AddNewProgram` method of the DNS contract to register the program with its ActorId. 3. In your dApp use [sails-js](https://github.com/gear-tech/sails/blob/master/js/README.md) to interact with the DNS program: 1. Install [sails-js](https://github.com/gear-tech/sails/blob/master/js/README.md#installation) 2. [Generate](https://github.com/gear-tech/sails/blob/master/js/README.md#generate-library-from-idl) typescript code from the IDL file 3. [Create program instance](https://github.com/gear-tech/sails/blob/master/js/README.md#create-an-instance) using DNS program ID 4. Use `GetContractInfoByName` query to get your programs ActorId Get DNS records [#get-dns-records] Using [https://idea.gear-tech.io](https://idea.gear-tech.io), read the state of the DNS program to get records - all or filtered by name, ID, and pattern. # Proxy-Based Program Update Approach About [#about] A program uploaded to the blockchain from specific source code represents a unique instance with a clearly defined on-chain address. A program uploaded from the same source code will become a separate instance with its own unique address, even if the logic remains identical. While the program’s state can change according to the defined business logic, there is no technical way to directly update the logic of an already uploaded program while preserving its unique address in the network. Programs often require updates to add new functionality or fix errors, making the ability to update a program’s business logic essential. Updating a program involves modifying the on-chain program in a way that avoids mandatory updates to other components or dependencies, such as the frontend, backend, external links, and more. **Upgrade Proxy programs** are a well-known design pattern in blockchain development that address upgrade challenges. This approach enables the delegation of function calls to a separate logic program. The pattern separates business logic from the program’s state and address, allowing upgrades or modifications to the logic without affecting the existing state or deployed address. This [example](https://github.com/gear-foundation/dapps/tree/master/contracts/upgrade-proxy) demonstrates a practical implementation of an upgrade proxy program designed to forward user interactions to a logic program. The upgrade proxy handles two primary responsibilities: 1. Forwarding state-modifying messages while preserving the original sender's address. 2. Handling state queries to retrieve data from the logic program. This design also supports logic program upgrades, ensuring flexibility and compatibility while maintaining administrative control. The project code is built using the [Sails](/docs/developing/build/sails) framework, which simplifies program development on Gear Protocol. Program logic [#program-logic] Message Forwarding [#message-forwarding] 1. **State-Modifying Messages** The `execute_msg` function forwards a state-modifying message to the logic program. It includes the original sender's address (`msg::source()`) in the payload for transparency and accountability. ```rust title="proxy-example/proxy/app/src/lib.rs" pub async fn execute_msg(&mut self, bytes: Vec) -> Vec { let original_sender = Some(msg::source()); let sender_encoded = original_sender.encode(); let mut new_payload = bytes.clone(); new_payload.extend(sender_encoded); msg::send_bytes_for_reply( self.get().logic_address, new_payload, msg::value(), REPLY_DEPOSIT, ) .expect("Error during message sending") .await .expect("Error during getting the reply") } ``` 2. **State Queries** The `read_state` function handles read-only queries, forwarding requests to the logic program without modifying its state. ```rust title="proxy-example/proxy/app/src/lib.rs" pub async fn read_state(&self, bytes: Vec) -> Vec { msg::send_bytes_for_reply(self.get().logic_address, bytes, 0, 0) .expect("Error during message sending") .await .expect("Error during getting the reply") } ``` Program's Logic Upgrade [#programs-logic-upgrade] The upgrade proxy includes an `update_logic` function that allows the admin to change the logic program address. ```rust title="proxy-example/proxy/app/src/lib.rs" pub fn update_logic(&mut self, new_logic_address: ActorId, msg_source: Option) { self.check_if_proxy(); let msg_source = self.get_msg_source(msg_source); self.only_admin(msg_source); self.get_mut().logic_address = new_logic_address; } ``` Creating a Proxy-Compatible Program [#creating-a-proxy-compatible-program] Making a program compatible with a proxy requires implementing specific features that enable the proxy to forward messages effectively while preserving the original sender's address. The following outlines the key adjustments and features incorporated into the [`Counter` program](https://github.com/gear-foundation/dapps/tree/master/contracts/proxy-example/counter) to support a proxy. Key Features of a Proxy-Compatible Program [#key-features-of-a-proxy-compatible-program] 1. Upgrade Proxy Address Registration: The program must allow an admin to set the address of the proxy, which is responsible for forwarding messages. 2. Forwarded Sender (`msg_source()`): When the proxy forwards a message, it includes the original sender's address (`msg_source()`). The program verifies this sender and distinguishes between direct and proxied calls. ```rust title="proxy-example/counter/app/src/lib.rs" fn get_msg_source(&self, msg_source: Option) -> ActorId { if self.get().proxy_address.is_some() { msg_source.expect("msg_source must be set through proxy") } else { msg::source() } } ``` 3. Forwarding Logic in Proxy-Compatible Methods: To ensure compatibility with a proxy, key methods in the program include an additional parameter of type `Option`. This parameter (`msg_source`) specifies the actual sender of the message: * If the program is called via a proxy: The proxy forwards the `ActorId` of the original sender, and `msg_source` will be `Some(ActorId)`. * If the program is called directly: `msg_source` will be `None`, and the program defaults to using `msg::source()` to determine the sender. ```rust title="proxy-example/counter/app/src/lib.rs" pub fn contribute(&mut self, msg_source: Option) -> u128 { self.check_if_proxy(); let msg_source = self.get_msg_source(msg_source); let amount = msg::value(); assert!(amount > 0, "Contribution must be greater than zero"); let state = self.get_mut(); let contribution = state.contributions.entry(msg_source).or_insert(0); *contribution += amount; state.value += amount; state.value } ``` State Migration [#state-migration] State migration allows the transfer of critical program data from an old program to a new one, enabling smooth upgrades while preserving important state variables. In this example, the migration involves exporting specific fields (`value`, `limit`, and `contributions`) and importing them into a new instance of the program. 1. Exporting State The `export_migration_state` function serializes only the necessary fields from the program's state and encodes them for transfer. ```rust title="proxy-example/counter/app/src/lib.rs" /// Exports the essential state for migration. /// Only includes `value`, `limit`, and `contributions` fields. pub fn export_migration_state(&self) -> Vec { let state = self.get(); let export_data = (state.value, state.limit, state.contributions.clone()); export_data.encode() } ``` 2. Importing State The `import_migration_state` function allows a new program to decode and load the state exported from the old program. ```rust title="proxy-example/counter/app/src/lib.rs" /// Imports the state from the previous program. /// Decodes and applies `value`, `limit`, and `contributions` fields. pub fn import_migration_state(&mut self, encoded_state: Vec) { let (value, limit, contributions) = <(u128, u128, BTreeMap)>::decode(&mut encoded_state.as_ref()) .expect("Failed to decode migration state"); let state = self.get_mut(); state.value = value; state.limit = limit; state.contributions = contributions; } ``` Kill Function [#kill-function] The kill function is used to stop the execution of the current program and transfer its remaining balance to a specified inheritor (e.g., a newly deployed program). This function is typically invoked by the admin as part of an upgrade process. ```rust title="proxy-example/counter/app/src/lib.rs" /// Stops the execution of the current program and transfers its remaining balance /// to the specified inheritor (e.g., a new program). pub async fn kill(&mut self, inheritor: ActorId, msg_source: Option) { self.check_if_proxy(); let msg_source = self.get_msg_source(msg_source); self.only_admin(msg_source); exec::exit(inheritor); } ``` Considerations for State Migration [#considerations-for-state-migration] While state migration is essential for upgrading programs, developers must address the following challenge: 1. **Storage Size Limitations**: * Programs with large or complex states may exceed the gas limit for a single transaction, making it impossible to migrate all data in one go: To address this, developers can implement partial migration, which enables the state to be migrated in smaller, manageable chunks. Source code [#source-code] The source code of the proxy program including its testing is available on [GitHub](https://github.com/gear-foundation/dapps/tree/master/contracts/upgrade-proxy). For more details about testing programs written on Vara, refer to the [Program Testing](/docs/developing/build/testing) article. # Auto-subscription (VaraTube) img alt About [#about] Web services in Web2 often operate on a subscription model, with a key feature being subscription auto-renewal. This means that once the subscription period ends, the payment is automatically deducted, and the subscription renews seamlessly, requiring no action from the user. This dApp example represents a video-hosting service **[VaraTube](https://github.com/gear-foundation/dapps/tree/master/contracts/varatube)** that enables watching videos with a valid subscription that can be renewed automatically. Decentralization aspect [#decentralization-aspect] In most blockchain networks, each transaction is invoked by a user or another program and must be completed in a single block (transactions are atomic). In general, a smart contract is able to invoke several functions, but only within a single block. In case of an error, the result of execution of all related functions is rolled back (the State does not change). According to the Actor Model in Gear Protocol, actors exchange messages. One of the Gear-specific entities is the Waitlist - a database where messages (not transactions) reside. This makes it possible to implement logic when several logically related messages can be spaced apart in time. As a result, extrinsics that are caused by these messages can be executed in different blocks. It is possible for messages to stay in the Waitlist for a long time. This is achieved through [Gas reservation](/docs/developing/build/gstd/gas-reservation) for deferred messages. A program can send a [delayed message](/docs/developing/build/gstd/delayed-messages) to itself over as many blocks as it defines, provided there is enough reserved gas to be in the Waitlist. State consistency between programs is achieved using techniques described [here](/docs/developing/build/distributed_transactions). Program logic [#program-logic] img alt When a user purchases a subscription, access to the service is open, and after the subscription expires, access is closed. The interface provides an option whether to enable or disable subscription auto-renewal. And if it is enabled, the subscription will be renewed automatically by getting funds from the user's. There is also an option to cancel the active subscription. The VaraTube consists of two programs: * [Vara Fungible Token (VFT)](/docs/vara-network/examples/Standards/vft) contract determines user and service balances required to purchase a subscription and approves Subscription program to get funds from user's balance. * [VaraTube Subscription](https://github.com/gear-foundation/dapps/tree/master/contracts/varatube) program manages service's subscription - its availability, expiration, auto renewal. How to run [#how-to-run] ⚒️ Build program [#️-build-program] * Get the source code of [VaraTube program](https://github.com/gear-foundation/dapps/tree/master/contracts/varatube) and [Fungible Token contract](https://github.com/gear-foundation/standards/tree/master/extended-vft) * Build programs as described in [README.md](https://github.com/gear-foundation/standards/tree/master/extended-vft#readme). 🏗️ Upload program [#️-upload-program] 1. Deploy a program using [idea.gear-tech.io](https://idea.gear-tech.io/) 2. In the network selector choose `Vara Network Mainnet` or `Development` if you have running a local node 3. Upload program `varatube.opt.wasm` from `/target/wasm32-unknown-unknown/release/` 4. Upload idl file `varatube.idl` from `varatube/wasm` 5. Specify `init payload` and calculate gas! 6. Repeat steps #3-5 for uploading Fungible Token contract `extended_vft.opt.wasm`... 🖥️ Run UI [#️-run-ui] 1. Download React application repository and install packages as described in [frontend/README.md](https://github.com/gear-foundation/dapps/blob/master/frontend/apps/varatube/README.md) npm pnpm yarn bun ```bash npm install ``` ```bash pnpm install ``` ```bash yarn install ``` ```bash bun install ``` 2. Create `.env` file, `.env.example` will let you know what variables are expected. 3. Configure `.env` file. Specify network address and program ID like in the example below: ```sh REACT_APP_NODE_ADDRESS=wss://rpc-node.gear-tech.io:443/ REACT_APP_IPFS_GATEWAY_ADDRESS=https://ipfs.io/ipfs/ REACT_APP_CONTRACT_ADDRESS=0x95e4b710736a53f4917194cc1a5122df7cf4b6e9d11652470a53a8cdc1ffe296 REACT_APP_FT_CONTRACT_ADDRESS=0xa2677f49725647da5cff15e8a42b2ead9102c387d646ff856f586b81e4b598a0 ``` 3. Run app npm pnpm yarn bun ```bash npm start ``` ```bash pnpm start ``` ```bash yarn start ``` ```bash bun start ``` Source code [#source-code] The source code of the VaraTube program including its testing as well as JS frontend web application example is available on [GitHub](https://github.com/gear-foundation/dapps/tree/master/contracts/varatube). They can be used as is or modified to suit your own scenarios. For more details about testing programs written on Vara, refer to the [Program Testing](/docs/developing/build/testing) article. # P2P streaming streaming P2P streaming is an example of a decentralized application, akin to the widely popular streaming applications in the Web2 realm. Users of this application can connect and watch the live stream of one or more users. A Web2 equivalent could be the Twitch platform. In this example of the application, a user creates a schedule for an upcoming stream broadcast at a designated time in advance. Other users can view a list of scheduled streams and subscribe to one or more. At the appointed time, the streamer commences the broadcast, and other users join it. The application comprises three primary components: * On-chain program: This component is responsible for storing the stream schedule and managing user subscriptions\ The source code, developed using the [Sails](/docs/developing/build/sails) framework, is available on [GitHub](https://github.com/gear-foundation/dapps/tree/master/contracts/w3bstreaming). * Frontend: This component serves as the application's user interface ([GitHub](https://github.com/gear-foundation/dapps/tree/master/frontend/apps/w3bstreaming)) * Signaling server: This component is responsible for establishing peer-to-peer (P2P) connections between the streamer and viewers ([GitHub](https://github.com/gear-foundation/dapps/tree/master/backend/w3bstreaming)). This article details the program interface, data structure, fundamental functions, and their intended purposes. It can be used as is or customized to suit individual scenarios. How to run [#how-to-run] 1. ⚒️ **Build a program**: For detailed instructions on this step, please refer to the [README](https://github.com/gear-foundation/dapps/blob/master/contracts/w3bstreaming/README.md) directory within the program's codebase. 2. 🏗️ **Upload the program** to the [Vara Network Mainnet](https://idea.gear-tech.io/programs?node=wss%3A%2F%2Frpc.vara.network): For further guidance on program uploading, please visit the [Getting Started](/docs/vara-network/quick-start#deploy-your-program-to-vara-mainnet) section. 3. 🔀 **Build and run the backend service**: For more information on this step, please consult the [README](https://github.com/gear-foundation/dapps/blob/master/backend/w3bstreaming/README.md) file within the codebase of the backend service. 4. 🖥️ **Build and run user interface**: For more information on this step, please consult the [README](https://github.com/gear-foundation/dapps/blob/master/frontend/apps/w3bstreaming/README.md) directory within the frontend codebase. Implementation details [#implementation-details] The P2P streaming program contains the following information: ```rust title="w3bstreaming/app/src/lib.rs" pub struct Program { pub streams: HashMap, pub users: HashMap, pub admins: Vec, pub dns_info: Option<(ActorId, String)>, } ``` * `streams` - contains a pair of values such as identifier and stream information * `users` - contains a pair of values such as user address and profile information * `admins` - admins addresses * `dns_info` - optional field that stores the [dDNS](/docs/vara-network/examples/Infra/dein) address and the program name Сonsider in detail the information about the `Stream` ```rust title="w3bstreaming/app/src/utils.rs" pub struct Stream { pub broadcaster: ActorId, pub start_time: u64, pub end_time: u64, pub title: String, pub img_link: String, pub description: Option, } ``` * `broadcaster` - the address of the creator of the stream * `start_time` - stream start time * `end_time` - stream end time * `title` - streaming title * `img_link` - link to the stream banner image * `description` - streaming description `Profile` information is as follows: ```rust title="w3bstreaming/app/src/utils.rs" pub struct Profile { pub name: Option, pub surname: Option, pub img_link: Option, pub time_zone: Option, pub stream_ids: Vec, pub subscribers: Vec, pub subscriptions: Vec, } ``` * `name` - user name * `surname` - user surname * `img_link` - user logo link * `time_zone` - time zone * `stream_ids` - list of all stream identifiers of the user * `subscribers` - list of all subscribers of a user * `subscriptions` - list of all subscribed users Action [#action] The streaming program contains the following functions: ```rust title="w3bstreaming/app/src/lib.rs" pub fn new_stream( &mut self, title: String, description: Option, start_time: u64, end_time: u64, img_link: String, ); pub fn delete_stream(&mut self, stream_id: String); pub fn edit_stream( &mut self, stream_id: String, start_time: Option, end_time: Option, title: Option, img_link: Option, description: Option, ); pub fn subscribe(&mut self, account_id: ActorId); pub fn edit_profile( &mut self, name: Option, surname: Option, img_link: Option, time_zone: Option, ); pub fn add_admin(&mut self, new_admin_id: ActorId); pub async fn kill(&mut self, inheritor: ActorId); ``` Event [#event] ```rust title="w3bstreaming/app/src/lib.rs" pub enum Event { StreamIsScheduled { id: String }, StreamDeleted { id: String }, StreamEdited, Subscribed, ProfileEdited, AdminAdded { new_admin_id: ActorId }, Killed { inheritor: ActorId }, } ``` Logic [#logic] Before starting the stream, it is necessary to register a profile, this can be done using the `edit_profile` function (this function also allows to edit the profile) ```rust title="w3bstreaming/app/src/lib.rs" pub fn edit_profile( &mut self, name: Option, surname: Option, img_link: Option, time_zone: Option, ) { let storage = self.get_mut(); storage .users .entry(msg::source()) .and_modify(|profile| { profile.name.clone_from(&name); profile.surname.clone_from(&surname); profile.img_link.clone_from(&img_link); profile.time_zone.clone_from(&img_link); }) .or_insert_with(|| Profile { name, surname, img_link, time_zone, stream_ids: Vec::new(), subscribers: Vec::new(), subscriptions: Vec::new(), }); self.emit_event(Event::ProfileEdited) .expect("Notification Error"); } ``` After successfully registering a profile, a stream can be scheduled. In order to create a stream it is necessary to enter the following information: title name, stream description, start time, end time and stream picture link ```rust title="w3bstreaming/app/src/lib.rs" pub fn new_stream( &mut self, title: String, description: Option, start_time: u64, end_time: u64, img_link: String, ) { let stream_id = exec::block_timestamp().to_string() + &title; let msg_src = msg::source(); let storage = self.get_mut(); if let Some(profile) = storage.users.get_mut(&msg_src) { profile.stream_ids.push(stream_id.clone()); } else { panic!("Account is no registered"); } storage.streams.insert( stream_id.clone(), Stream { broadcaster: msg_src, img_link, start_time, end_time, title, description, }, ); self.emit_event(Event::StreamIsScheduled { id: stream_id }) .expect("Notification Error"); } ``` The unique stream identifier is composed of the time stamp of the current block and the title name. After successful stream creation the program sends a reply where the identifier is specified. This program also allows deleting information about a scheduled stream or editing it using the stream id. In case of stream deletion, a number of checks are performed to ensure that only the stream creator can delete the stream and the stream with the given identifier exists. ```rust title="w3bstreaming/app/src/lib.rs" pub fn delete_stream(&mut self, stream_id: String) { let storage = self.get_mut(); let msg_src = msg::source(); let profile = storage .users .get_mut(&msg_src) .expect("Account is no registered"); let index = profile .stream_ids .iter() .position(|x| *x == stream_id) .expect("Id is not exist"); profile.stream_ids.remove(index); let stream = storage.streams.get(&stream_id).expect("Id is not exist"); if stream.broadcaster == msg_src { storage.streams.remove(&stream_id); } else { panic!("You are not broadcaster"); } self.emit_event(Event::StreamDeleted { id: stream_id }) .expect("Notification Error"); } ``` Stream data fields can be modified selectively and only the creator of the stream can do that. ```rust title="w3bstreaming/app/src/lib.rs" pub fn edit_stream( &mut self, stream_id: String, start_time: Option, end_time: Option, title: Option, img_link: Option, description: Option, ) { let storage = self.get_mut(); let msg_src = msg::source(); if let Some(stream) = storage.streams.get_mut(&stream_id) { if stream.broadcaster == msg_src { if let Some(start_time) = start_time { stream.start_time = start_time; } if let Some(end_time) = end_time { stream.end_time = end_time; } if let Some(title) = title { stream.title = title; } if let Some(img_link) = img_link { stream.img_link = img_link; } stream.description = description; } else { panic!("You are not broadcaster"); } } else { panic!("Id is not exist"); } self.emit_event(Event::StreamEdited) .expect("Notification Error"); } ``` To subscribe to another account it is necessary to send a message `subscribe` to the program. Subscription may fail if registration has not been completed or the specified account does not exist in the registered accounts ```rust title="w3bstreaming/app/src/lib.rs" pub fn subscribe(&mut self, account_id: ActorId) { let storage = self.get_mut(); if !storage.users.contains_key(&account_id) { panic!("The user is not found"); } let msg_src = msg::source(); if !storage.users.contains_key(&msg_src) { panic!("You are not registered"); } storage .users .entry(account_id) .and_modify(|profile| profile.subscribers.push(msg_src)); storage.users.entry(msg_src).and_modify(|profile| { profile.subscriptions.push(Subscription { account_id, sub_date: exec::block_timestamp(), }) }); self.emit_event(Event::Subscribed) .expect("Notification Error"); } ``` Query [#query] The `get_state` function returns all program information: ```rust title="w3bstreaming/app/src/lib.rs" pub fn get_state(&self) -> ProgramState { self.get().clone().into() } ``` Source code [#source-code] The source code of this example of P2P streaming program and the example of an implementation of its testing is available on [gear-foundation/dapp/contracts/w3bstreaming](https://github.com/gear-foundation/dapps/tree/master/contracts/w3bstreaming). See also an example of the program testing implementation based on `gtest`: [gear-foundation/dapps/w3bstreaming/tests](https://github.com/gear-foundation/dapps/tree/master/contracts/w3bstreaming/tests). For more details about testing programs written on Vara, refer to the [Program Testing](/docs/developing/build/testing) article. # ZKML Utility Introduction [#introduction] Imagine you have a secret way to prove something is true without revealing any extra details. That's precisely what a zero-knowledge (ZK) proof does in the digital world. It's like a special handshake between two parties: one party (the prover) convinces the other party (the verifier) that something is true without exposing anything else. In the world of Web3, zero-knowledge cryptography is a game-changer. It empowers developers to create scalable and private applications. This technology allows them to conceal complex calculations while ensuring their accuracy and to create proofs of computational integrity that are much easier to verify than to compute. Machine learning (ML) is a branch of artificial intelligence that enables computers to learn and improve from data, producing more accurate results when learning through iterative processes. In the context of ZKML, the prover can demonstrate the correctness of ML models outcome without revealing any additional information. This means that the computations are kept secret from the verifier. It's important to note that this mainly involves creating zero-knowledge proofs for the inference step of an ML model, not the training phase. For instance, this technology could be used in the medical industry to apply an ML model to sensitive data, allowing users to learn the model's result without disclosing their input to third parties. You can find comprehensive information about ZK and ML on the internet. This article focuses on an example of implementing a zero-knowledge proof application designed for [Vara Network](https://vara.network). ZKML and Vara: A Perfect Match [#zkml-and-vara-a-perfect-match] One of the main challenges in bringing ML to the blockchain world is the high computational cost. Many ML projects struggle to run directly on blockchains due to limited computational resources. When you combine the complex cryptographic operations and interactions of ZK with the extensive calculations of ML, it becomes clear that most blockchains face limitations in terms of transaction costs, which is a major hurdle for on-chain ML adoption. ZK proofs offer a solution to the computational challenges of ML. They allow for off-chain execution of complex computations while enabling the computation to be proven correct on-chain. ZK's efficiency in validation surpasses recomputation, but this advantage is typically limited to processing native transaction data on the blockchain. When you combine this technology with the high-speed, low-cost transactions of Vara, ZKML becomes a powerful tool for on-chain applications. It enables the utilization of AI while maintaining decentralization and efficiency. The Solution: ZKML Implementation [#the-solution-zkml-implementation] This article presents a proof-of-concept implementation of a zero-knowledge proof application designed for Vara. You can access the source code on [GitHub](https://github.com/gear-foundation/external-zkml-actor). This example is a **technology demonstration** and is not production-ready. For real-world applications, you **must** implement additional optimizations and security audits to address potential performance issues and vulnerabilities. How It Works [#how-it-works] This implementation offers the capability to shift resource-intensive computations away from the blockchain while ensuring their integrity. It demonstrates the evaluation of the [MNIST](https://en.wikipedia.org/wiki/MNIST_database) machine learning model as an illustrative example. This is accomplished by generating zero-knowledge proofs of computations and subsequently verifying them on-chain. The associated program (smart contract) allows only one verification session per client. If a client wishes to discard the current verification and submit new inputs, they have the option to send a transaction to purge in-progress verification. Workflow [#workflow] ZKML The process of employing zero-knowledge proofs for computations involves four key participants: `Initializer`, `User`, `zk-smart-contract`, and `Prover`. 1. `Initializer`: This participant is responsible for deploying the `zk-smart-contract` onto the blockchain and initializing its parameters. During initialization, the structure of the machine learning model is defined, setting the stage for subsequent interactions. 2. `User`: Users of the system (can be a person or another program) interact with the `zk-smart-contract` by submitting their input data, which is intended for processing by the machine learning model. This step demonstrates how external parties can seamlessly engage with the blockchain without directly executing computationally intensive operations. 3. `Prover`: The Prover is deployed on a server and continuously polling `zk-smart-contract` to discover new inputs provided by `User`s, computes outputs, and proves computations. The Prover retrieves input from the `zk-smart-contract`, evaluates the machine learning model, and generates a zero-knowledge proof that attests to the validity of the computation. The resulting output of the machine learning model, along with the zero-knowledge proof, is then submitted back to the `zk-smart-contract`. 4. `User Verification`: Users initiate a verification process by interacting with the `zk-smart-contract`. The `zk-smart-contract` orchestrates the verification of the zero-knowledge proof, independently confirming the accuracy of the computation without exposing sensitive data. 5. `Verification Outcome`: Once the verification process is completed within the `zk-smart-contract`, the user is informed of the verification outcome. Conclusion [#conclusion] The Web3 world has a strong demand for the productivity and decision support of AI and ML. ZKML can play a crucial role in automating **DeFi** trading strategies, enhancing **identity management** while ensuring privacy and security, enabling full functionality for Web3 **games**, and assisting fields like **healthcare** and **legal consulting** that require privacy and extensive case accumulation. In the future, ZKML has the potential to bridge the gap between Web3 and AI, providing robust support for building secure, privacy-preserving, and efficient blockchain applications. # Concert Introduction [#introduction] The idea of this program is to demonstrate a real-life scenario where a fungible token (VFT) transforms into a non-fungible one (VNFT), representing the [Vara Miltiple Token Standard (VMT)](/docs/vara-network/examples/Standards/vmt) utilization. For example, when buying a concert ticket without a specific seat, all tickets are the same and interchangeable (fungible). However, once the concert is over, the electronic ticket can turn into a unique non-fungible token, belonging solely to you and serving as a reminder of the event. In this example, a single deployed program can hold one concert at a time. Firstly, all the tickets for the concert come as fungible-tokens. In order to buy tickets, one should provide the metadata (e.g. seat/row number) that will later be included in NFTs. When the concert ends, all the fungible tokens of all users (ticket holders) will turn into NFTs. The idea is simple - all the internal token interactions are handled using the VMT program, which address must be provided upon initializing a concert program. The article explains the programming interface, data structure, basic functions and explains their purpose. It can be used as is or modified to suit any custom scenarios. Anyone can easily create their own application and run it on the Vara Network. The source code is available on [GitHub](https://github.com/gear-foundation/dapps/tree/master/contracts/concert). Implementation details [#implementation-details] Events [#events] ```rust title="concert/app/src/lib.rs" pub enum Event { Creation { creator: ActorId, concert_id: U256, number_of_tickets: U256, date: u128, }, Hold { concert_id: U256, }, Purchase { concert_id: U256, amount: U256, }, } ``` Functions [#functions] Create a concert [#create-a-concert] ```rust title="concert/src/lib.rs" pub fn create( &mut self, creator: ActorId, name: String, description: String, number_of_tickets: U256, date: u128, token_id: U256, ) ``` * `creator` - is the creator of the concert * `name` - is the name of an upcoming concert * `description` - is a description of the concert * `number_of_tickets` - is the amount of FT minted later * `date` - is the date of the concert holding * `token_id` - id token for multitoken transfer Buy tickets [#buy-tickets] ```rust title="concert/src/lib.rs" pub async fn buy_tickets(&mut self, amount: U256, mtd: Vec>) ``` * `amount` - is the number of tickets one is trying to purchase. * `mtd` - is the tickets metadata (e.g. seat/row). This argument length should equal the `amount` value. Hold a concert [#hold-a-concert] ```rust title="concert/src/lib.rs" pub async fn hold_concert(&mut self) ``` > Hold a concert, turning of the FT (aka tickets) into NFTs; the hold a concert functionality is only available to the creator Init Config [#init-config] To successfully initialize a concert program, one should provide an ActorID of the [Vara Miltiple Token Standard (VMT)](/docs/vara-network/examples/Standards/vmt) program to hold all the token manipulations. ```rust title="concert/src/lib.rs" pub fn init(owner_id: ActorId, vmt_contract: ActorId) -> Self { let storage = Storage { owner_id, contract_id: vmt_contract, ..Default::default() }; unsafe { STORAGE = Some(storage) }; Self(()) } ``` Contract Interface [#contract-interface] The concert program includes the following interface: ```rust !@sails: 1.0.0 service Concert { events { Creation { creator: actor, concert_id: u256, number_of_tickets: u256, date: u128 }, Hold { concert_id: u256 }, Purchase { concert_id: u256, amount: u256 }, } functions { BuyTickets(amount: u256, mtd: [Option]); Create(creator: actor, name: String, description: String, number_of_tickets: u256, date: u128, token_id: u256); HoldConcert(); @query GetStorage() -> State; } types { struct TokenMetadata { title: Option, description: Option, media: Option, reference: Option, } struct State { owner_id: actor, contract_id: actor, name: String, description: String, ticket_ft_id: u256, creator: actor, number_of_tickets: u256, tickets_left: u256, date: u128, buyers: [actor], id_counter: u256, concert_id: u256, running: bool, metadata: [(actor, [(u256, Option)])], token_id: u256, } } } program ConcertProgram { constructors { New(owner_id: actor, vmt_contract: actor); } services { Concert: Concert; } } ``` Conclusion [#conclusion] A source code of the program example is available on GitHub: [`concert/src`](https://github.com/gear-foundation/dapps/tree/master/contracts/concert/src). See also an example of the program testing implementation based on [gtest](https://github.com/gear-foundation/dapps/tree/master/contracts/concert/tests). For more details about testing programs on Vara, refer to this article: [Program Testing](/docs/developing/build/testing). # Dynamic NFT Introduction [#introduction] This is an extension of standard [Vara Non-Fungible token](/docs/vara-network/examples/Standards/vnft). It proposes an additional dynamic part that can change or evolve over time. The source code of the Gear NFT smart contract example is available on [GitHub](https://github.com/gear-foundation/dapps/tree/master/contracts/dynamic-nft). Motivation [#motivation] Unlike traditional NFTs that represent a static digital asset, dynamic NFTs can have various attributes, properties, or behaviors that can be modified based on certain conditions or user interactions. These changes can be triggered by external factors such as market demand, user preferences, or even real-world events. For example, a dynamic NFT representing a digital artwork may change its appearance or color scheme based on the time of day or weather conditions. This example demonstrates Gear Protocol's unique features enabling the new user experience for totally on-chain, truly decentralized applications that do not require centralized components. [Delayed messages](/docs/developing/build/gstd/delayed-messages) allows the contract to wake itself after a specified period of time. The project code is built using the [Sails](/docs/developing/build/sails) framework, which simplifies program development on Gear Protocol. This is an extended version of the standard [vNFT](/docs/vara-network/examples/Standards/vnft). We will focus **solely on the additional features** and fields that differentiate this version from the basic implementation. Token Metadata changes [#token-metadata-changes] The metadata of a token is defined by the TokenMetadata structure: ```rust title="dynamic-nft/app/src/services/dynamic_nft/mod.rs" pub struct TokenMetadata { pub name: String, pub description: String, pub current_media_index: u64, pub media: Vec, pub reference: String, } ``` * `name`: A descriptive name for the token * `description`: A detailed explanation or context for the token * `current_media_index`: Indicates the currently active media in the media list * `media`: A collection of URLs pointing to associated media, ideally stored in decentralized, content-addressed storage like IPFS * `reference`: A URL to an off-chain JSON file containing additional information about the token As the metadata updates, the `current_media_index` will increment, cycling through the list of media URLs in the media field. This dynamic behavior enables the token to evolve visually or contextually over time, enhancing its interactivity and adaptability. Additional Functions [#additional-functions] ``` StartMetadataUpdate(updates_count, update_period_in_blocks, token_id) UpdateMetadata(token_id, owner, update_period, updates_count) ``` Additional Events [#additional-events] ``` MetadataStartedUpdaing(updates_count, update_period_in_blocks, token_id); MetadataUpdated(token_id, current_media_index); ``` Additional Methods [#additional-methods] Start Metadata Update [#start-metadata-update] This function starts a scheduled process to update the metadata of a specific token periodically. It validates the request, checks the token's ownership, and then initializes the metadata update sequence. If multiple updates are required, it schedules delayed messages to execute subsequent updates automatically. ```rust title="dynamic-nft/app/src/services/dynamic_nft/mod.rs" pub fn start_metadata_update( &mut self, updates_count: u32, update_period_in_blocks: u32, token_id: TokenId, ) { let msg_src = msg::source(); if updates_count == 0 { panic!("Updates count cannot be zero") } if update_period_in_blocks == 0 { panic!("Updates period cannot be zero") } services::utils::panicking(|| { funcs::start_metadata_update( self.get().gas_for_one_time_updating, Storage::owner_by_id(), &mut self.get_mut().token_metadata_by_id, token_id, msg_src, updates_count, update_period_in_blocks, ) }); self.emit_event(Event::MetadataStartedUpdaing { updates_count, update_period_in_blocks, token_id, }) .expect("Notification Error"); } ``` ```rust title="dynamic-nft/app/src/services/dynamic_nft/funcs.rs" pub fn start_metadata_update( gas_for_one_time_updating: u64, owner_by_id: &mut HashMap, token_metadata_by_id: &mut HashMap, token_id: TokenId, msg_src: ActorId, updates_count: u32, update_period: u32, ) -> Result<()> { let owner = owner_by_id.get(&token_id).ok_or(Error::TokenDoesNotExist)?; if *owner != msg_src { return Err(Error::DeniedAccess); } let metadata = token_metadata_by_id .get_mut(&token_id) .ok_or(Error::TokenDoesNotExist)?; metadata.current_media_index = metadata.current_media_index.saturating_add(1) % metadata.media.len() as u64; if updates_count.saturating_sub(1) != 0 { let request = [ "DynamicNft".encode(), "UpdateMetadata".to_string().encode(), (token_id, msg_src, update_period, updates_count - 1).encode(), ] .concat(); msg::send_bytes_with_gas_delayed( exec::program_id(), request, gas_for_one_time_updating.saturating_mul(updates_count.into()), 0, update_period, ) .expect("Error in sending message"); } Ok(()) } ``` **Key Steps**: * Validation: Ensures updates\_count and update\_period\_in\_blocks are greater than zero * Ownership Check: Confirms that the caller is the token's owner * Metadata Update: Updates the `current_media_index` of the token's metadata * Scheduling Updates: If more updates are needed, schedules delayed messages to handle the remaining updates Update Metadata [#update-metadata] This function handles the actual metadata update for a token. It cycles through available metadata options and triggers further updates if required. Ownership verification ensures that only authorized requests proceed. ```rust title="dynamic-nft/app/src/services/dynamic_nft/mod.rs" pub fn update_metadata( &mut self, token_id: TokenId, owner: ActorId, update_period: u32, updates_count: u32, ) { if msg::source() != exec::program_id() { panic!("This message can only be sent by the programme") } let current_media_index = services::utils::panicking(|| { funcs::update_metadata( Storage::owner_by_id(), &mut self.get_mut().token_metadata_by_id, token_id, owner, update_period, updates_count, ) }); self.emit_event(Event::MetadataUpdated { token_id, current_media_index }) .expect("Notification Error"); } ``` ```rust title="dynamic-nft/app/src/services/dynamic_nft/funcs.rs" pub fn update_metadata( owner_by_id: &mut HashMap, token_metadata_by_id: &mut HashMap, token_id: TokenId, owner: ActorId, update_period: u32, updates_count: u32, ) -> Result { let current_owner = owner_by_id.get(&token_id).ok_or(Error::TokenDoesNotExist)?; if owner != *current_owner { return Err(Error::DeniedAccess); } let metadata = token_metadata_by_id .get_mut(&token_id) .ok_or(Error::TokenDoesNotExist)?; metadata.current_media_index = metadata.current_media_index.saturating_add(1) % metadata.media.len() as u64; if updates_count.saturating_sub(1) != 0 { let request = [ "DynamicNft".encode(), "UpdateMetadata".to_string().encode(), (token_id, owner, update_period, updates_count - 1).encode(), ] .concat(); msg::send_bytes_with_gas_delayed( exec::program_id(), request, exec::gas_available().saturating_sub(1_000_000_000), 0, update_period, ) .expect("Error in sending message"); } Ok(metadata.current_media_index ) } ``` **Key Steps**: * Validation: Confirms that the message source is the contract itself (enforcing controlled execution) * Ownership Check: Verifies that the provided owner matches the token's registered owner * Metadata Update: Cycles to the next metadata option by incrementing `current_media_index` * Scheduling Updates: If additional updates are pending, schedules the next update via a delayed message Source code [#source-code] The source code of this example program and the example of an implementation of its testing is available on [gear-foundation/dapp/contracts/dynamic-nft](https://github.com/gear-foundation/dapps/tree/master/contracts/dynamic-nft). See also an example of the smart contract testing implementation based on `gtest`: [gear-foundation/contracts/dynamic-nft/tests](https://github.com/gear-foundation/dapps/tree/master/contracts/dynamic-nft/tests). For more details about testing programs written on Gear, refer to the [Program Testing](/docs/developing/build/testing) article. # Vara dApp Patterns This section documents the reusable patterns collected in the [`gear-foundation/vara-dapp-patterns`](https://github.com/gear-foundation/vara-dapp-patterns/tree/master) repository. The repository is organized around three practical layers of a Vara application: | Area | Focus | Includes | | ----------------------------------------------------------- | ----------------------------------------------- | --------------------------------------------------------------------------------------------------------------- | | [Contracts](/docs/vara-network/examples/Patterns/contracts) | On-chain design and execution flow | factories, delayed self-messages, service wrappers, session-based delegation | | [Frontend](/docs/vara-network/examples/Patterns/frontend) | React integration and transaction UX | transaction preparation, execution hooks, verified sending, provider composition, signless and gasless UI flows | | [Backend](/docs/vara-network/examples/Patterns/backend) | Trusted off-chain services around Vara programs | REST gateways, voucher servers, token-gated authentication | All examples in this section are based on the public repository: [gear-foundation/vara-dapp-patterns](https://github.com/gear-foundation/vara-dapp-patterns/tree/master). How to use these patterns [#how-to-use-these-patterns] Use the contract patterns when the main question is how the program should store state, compose services, or schedule internal work. Use the frontend patterns when you need predictable transaction orchestration in React, especially if your app prepares extrinsics in one place and executes them in another. Use the backend patterns when your architecture includes trusted signers, voucher lifecycle management, REST APIs, or access-control services driven by on-chain state. Recommended reading order [#recommended-reading-order] 1. Start with [Contracts](/docs/vara-network/examples/Patterns/contracts) if you are shaping program behavior. 2. Continue with [Frontend](/docs/vara-network/examples/Patterns/frontend) if you need wallet, provider, or transaction UX patterns. 3. Finish with [Backend](/docs/vara-network/examples/Patterns/backend) when your system includes gateways, relayers, or token-gated access. # RMRK NFT standard Introduction [#introduction] RMRK is an NFT protocol dedicated to establishing a standard NFT cross-chain infrastructure. The [RMRK NFT 2.0 Concepts](https://docs.rmrk.app/concepts) are divided into RMRK legos and RMRK concepts. Legos are primitives that make up complex use cases and enable certain concepts. RMRK legos that are already implemented on Vara: * Nested NFTs: The ability for any NFT to contain other RMRK NFT. * Multi-resource NFTs: The ability for an NFT to vary its output depending on the context it is being loaded in. This article explains the programming interface, data structure, basic functions and explains their purpose. The source code is available on [GitHub](https://github.com/gear-foundation/dapps/tree/master/contracts/rmrk). Logic [#logic] Nesting logic [#nesting-logic] The concept of nested NFTs refers to NFTs being able to own other NFTs. So, the NFT owner can be not only an account or a smart contract, but also another NFT. In the usual NFT standard, NFT owners were stored as mapping from the NFT ids to addresses: ```rust BTreeMap ``` In the RMRK NFT standard, token owners are stored as follows in the following manner: ```rust BTreeMap pub struct RMRKOwner { pub token_id: Option, pub owner_id: ActorId, } ``` If the owner of NFT is another NFT then the field `token_id` is `Some(parent_token_id)` and the `owner_id` is the address of the parent RMRK contract, otherwise `token_id` is `None` and `owner_id` is the address of an account or another smart contract. RMRK NFT tokens have the concept of `owner` and `rootowner`. `Rootowner` will always be an account or program where owner can also be an NFT ID in cases where an NFT owns another NFT. For example, if Alice owns `NFT A` which owns `NFT B` then the `owner` of `NFT B` is `NFT A` and the `rootowner` of `NFT B` is Alice. RMRK standard has 2 options of minting tokens. The first one is similar to the `mint` function at usual NFT standard: ```rust /// Mints token to the user or program. /// /// # Requirements: /// * The `token_id` must not exist. /// * The `to` address should be a non-zero address. /// /// # Arguments: /// * `to`: is the address who will own the token. /// * `token_id`: is the tokenId of the new RMRK token. /// /// On success reply [`RMRKEvent::MintToRootOwner`]. MintToRootOwner { to: ActorId, token_id: TokenId, }, ``` The second `mint` function allows to create an NFT that will belong to another NFT (or be a child of another NFT): ```rust /// Mints token that will belong to another token in another RMRK contract. /// /// # Requirements: /// * The `parent_id` must be a deployed RMRK contract. /// * The token with id `parent_token_id` must exist in the `parent_id` contract. /// * The `token_id` must not exist. /// /// # Arguments: /// * `parent_id`: is the address of the RMRK parent contract. /// * `parent_token_id`: is the parent RMRK token. /// * `token_id`: is the tokenId of the new RMRK token. /// /// On success replies [`RMRKEvent::MintToNft`]. MintToNft { parent_id: ActorId, parent_token_id: TokenId, token_id: TokenId, }, ``` When creating a token that will belong to another NFT, the contract sends a message `AddChild` to parent the RMRK contract: ```rust /// That message is designed to be send from another RMRK contracts /// when minting an NFT(child_token_id) to another NFT(parent_token_id). /// It adds a child to the NFT with tokenId `parent_token_id` /// The status of added child is `Pending`. /// /// # Requirements: /// * Token with TokenId `parent_token_id` must exist. /// * There cannot be two identical children. /// /// # Arguments: /// * `parent_token_id`: is the tokenId of the parent NFT. /// * `child_token_id`: is the tokenId of the child instance. /// /// On success replies [`RMRKEvent::PendingChild`]. AddChild { parent_token_id: TokenId, child_token_id: TokenId, }, ``` The root owner or the approved account can accept the child NFT by sending the following message: ```rust /// Accepts an RMRK child being in the `Pending` status. /// Removes RMRK child from `pending_children` and adds it to `accepted_children`. /// /// # Requirements: /// * The `msg::source()` must be an RMRK owner of NFT with tokenId `parent_token_id` or an approved account. /// * The indicated NFT with tokenId `child_token_id` must exist in the pending array of `parent_token_id`. /// /// # Arguments: /// * `parent_token_id`: is the tokenId of the parent NFT /// * `child_token_id`: is the tokenId of the child instance /// /// On success replies [`RMRKEvent::AcceptedChild`]. AcceptChild { parent_token_id: TokenId, child_contract_id: ActorId, child_token_id: TokenId, }, ``` or reject the child NFT with the message: ```rust /// Rejects an RMRK child being in the `Pending` status. /// It sends a message to the child NFT contract to burn the NFT token from it. /// /// # Requirements: /// * The `msg::source()` must be an RMRK owner or an approved account. /// * The indicated NFT with tokenId `child_token_id` must exist in the pending array of `parent_token_id`. /// /// Arguments: /// * `parent_token_id`: is the tokenId of the parent NFT. /// * `child_contract_id`: is the address of the child RMRK contract. /// * `child_token_id`: is the tokenId of the child instance. /// /// On success replies [`RMRKEvent::RejectedChild`]. RejectChild { parent_token_id: TokenId, child_contract_id: ActorId, child_token_id: TokenId, }, ``` The root owner can also remove the already accepted child from his NFT accepted children: ```rust /// Removes an RMRK child being in the `Accepted` status. /// It sends a message to the child NFT contract to burn the NFT token from it. /// /// # Requirements: /// * The `msg::source()` must be an RMRK owner or an approved account. /// /// # Arguments: /// * `parent_token_id`: is the tokenId of the parent NFT. /// * `child_contract_id`: is the address of the child RMRK contract. /// * `child_token_id`: is the tokenId of the child instance. /// /// On success replies [`RMRKEvent::RemovedChild`]. RemoveChild { parent_token_id: TokenId, child_contract_id: ActorId, child_token_id: TokenId, }, ``` If the root owner rejects or removes the child NFT, the child NFT must be burnt from the child NFT contract. The parent NFT contract sends the corresponding message to the child NFT contract: ```rust /// Burns RMRK tokens. It must be called from the RMRK parent contract when the root owner removes or rejects child NFTs. /// The input argument is an `BTreeSet` since a parent contract can have multiple children that must be burnt. /// It also recursively sends messages [`RMRKAction::BurnFromParent`] to children of burnt tokens if any. /// /// # Requirements: /// * The `msg::source()` must be a RMRK parent contract. /// * All tokens in `BTreeSet` must exist. /// /// # Arguments: /// * `token_ids`: is the tokenIds of the burnt tokens. /// /// On success replies [`RMRKEvent::TokensBurnt`]. BurnFromParent { child_token_ids: BTreeSet, root_owner: ActorId, }, ``` The token being burned may also have children in other contracts. When burned, it recursively burns all the children's NFTs. The root owner can also burn the NFT with following message: ```rust /// Burns RMRK token. /// It recursively burns all the children's NFTs. /// It checks whether the token is a child of another token. /// If so, it sends a message to the parent NFT to remove the child. /// /// # Requirements: /// * The `msg::source()` must be the root owner of the token. /// /// # Arguments: /// * `token_id`: is the tokenId of the burnt token. /// /// On success replies [`RMRKEvent::Transfer`]. Burn(TokenId), ``` In addition to burning tokens recursively, it also checks whether the burnt NFT belongs to another NFT. In that case, it is necessary to remove the token from the children list in the parent's contract with the following message: ```rust /// Burns a child of NFT. /// That function must be called from the child RMRK contract during `transfer`, `transfer_to_nft` and `burn` functions. /// /// # Requirements: /// * The `msg::source()` must be a child RMRK contract. /// * The indicated child must exist on the children list of `parent_token_id`. /// /// # Arguments: /// * `parent_token_id`: is the tokenId of the parent NFT. /// * `child_token_id`: is the tokenId of the child instance. /// /// On success replies [`RMRKEvent::ChildBurnt`]. BurnChild { parent_token_id: TokenId, child_token_id: TokenId, }, ``` When NFT is transferred, the destination can be either an account or another NFT. To send an NFT to another account it is necessary to send a message: ```rust /// Transfers NFT to another account. /// If the previous owner is another RMRK contract, it sends the message [`RMRKAction::BurnChild`] to the parent contract. /// /// # Requirements: /// * The `token_id` must exist. /// * The `msg::source()` must be approved by the owner of the token. /// * The `to` address should be a non-zero address. /// /// # Arguments: /// * `to`: is the receiving address. /// * `token_id`: is the tokenId of the transferred token. /// /// On success replies [`RMRKEvent::ChildBurnt`]. Transfer { to: ActorId, token_id: TokenId, }, ``` If the previous owner is another NFT it sends a message `BurnChild` to the parent contract. In case of transferring a token to another NFT, the following message is sent: ```rust /// Transfers NFT to another NFT. /// /// # Requirements: /// * The `token_id` must exist. /// * The `msg::source()` must be approved by the root owner of the token. /// * The `to` address should be a non-zero address /// /// # Arguments: /// * `to`: is the address of the new parent RMRK contract. /// * `destination_id: is the tokenId of the parent RMRK token. /// * `token_id`: is the tokenId of the transferred token. /// /// On success replies [`RMRKEvent::TransferToNft`]. TransferToNft { to: ActorId, token_id: TokenId, destination_id: TokenId, }, ``` In this case, 5 scenarios are possible: 1. Root owner transfers child token from NFT to another his NFT within one contract. In that case child RMRK contract sends message `TransferChild` to parent RMRK contract with indicated previous `TokenId` and new `TokenId`: ```rust /// That message is designed to be sent from another RMRK contracts /// when the root owner transfers his child to another parent token within one contract. /// If root owner transfers child token from NFT to another his NFT /// it adds a child to the NFT with a status that child had before. /// If root owner transfers child token from NFT to another NFT that he does not own /// it adds a child to the NFT with a status `Pending`. /// /// # Requirements: /// * The `msg::source()` must be a child RMRK contract. /// * The `to` must be an existing RMRK token /// * The `root_owner` of `to` and `from` must be the same. /// /// # Arguments: /// * `from`: RMRK token from which the child token will be transferred. /// * `to`: RMRK token to which the child token will be transferred. /// * `child_token_id`: is the tokenId of the child in the RMRK child contract. /// /// On success replies [`RMRKEvent::ChildTransferred`]. TransferChild { from: TokenId, to: TokenId, child_token_id: TokenId, }, ``` 2. Root owner transfers child token from RMRK parent token in one contract to another his RMRK token in another contract: * Child RMRK contract sends message `BurnChild` to previous parent RMRK contract; * Child RMRK contract sends message `AddAcceptedChild` to new parent RMRK contract; ```rust /// That function is designed to be called from another RMRK contracts /// when the root owner transfers his child NFT to another his NFT in another contract. /// It adds a child to the RMRK token with tokenId `parent_token_id` with status `Accepted`. /// /// # Requirements: /// * The `msg::source()` must be a child RMRK contract. /// * The `parent_token_id` must be an existing RMRK token that must have `child_token_id` in its `accepted_children`. /// /// # Arguments: /// * `parent_token_id`: RMRK token to which the child token will be transferred. /// * `child_token_id`: is the tokenId of the child of the RMRK child contract. /// /// On success replies [`RMRKEvent::AcceptedChild`]. AddAcceptedChild { parent_token_id: TokenId, child_token_id: TokenId, }, ``` 3. Root owner transfers child token to RMRK token that belongs to another root account: * Child RMRK contract sends message `BurnChild` to previous parent RMRK contract; * Child RMRK contract sends message `AddChild` to new parent RMRK contract; 4. Transferred RMRK token belongs directly to root owner and he transfers RMRK token to his another RMRK token: child RMRK contract sends message `AddAcceptedChild` to new parent RMRK contract. 5. Transferred RMRK token belongs directly to root owner and he transfers RMRK token to another RMRK token that he doesn’t own: child RMRK contract sends message `AddChild` to new parent RMRK contract. The `approve` function in RMRK NFT standard is similar to the usual nft standard function, except that `msg::source` must the `root_owner`: ```rust /// Approves an account to transfer NFT. /// /// # Requirements: /// * The `token_id` must exist. /// * The `msg::source()` must be approved by the root owner of the token. /// * The `to` address must be a non-zero address /// /// # Arguments: /// * `to`: is the address of the approved account. /// * `token_id`: is the tokenId of the token. /// /// On success replies [`RMRKEvent::Approval`]. Approve { to: ActorId, token_id: TokenId, }, ``` Multiresource logic [#multiresource-logic] The Multi Resource NFT standard is a standalone part of RMRK concepts. The idea is that an NFT can have multiple resources. There are four key use cases for NFT multiresource: * *Cross-metaverse compatibility*: for example, NFT with several resources can be used in different games. * *Multimedia output*: NFT can be stored in different digital formats (image, video, audio, eBooks or text file). * *Media Redundancy*: many NFTs are minted with metadata centralized on a server somewhere or, in some cases, a hardcoded IPFS gateway which can also go down, instead of just an IPFS hash. By adding the same metadata file as different resources, the resilience of the metadata and its referenced media increases exponentially as the chances of all the protocols going down at once become ever less likely. * *NFT Evolution*: many NFTs, particularly game related ones, require evolution. RMRK contract can create a contract to store its resources. Resource storage contract: [#resource-storage-contract] The storage state: ```rust #[derive(Debug, Default)] pub struct ResourceStorage { pub name: String, // the admin is the rmrk contract that initializes the storage contract pub admin: ActorId, pub resources: BTreeMap, pub all_resources: BTreeSet, } ``` To add resource to the token the RMRK contract must send the following message: ```rust /// Adds resource entry on resource storage contract. /// /// # Requirements: /// * The `msg::source()` must be the contract admin (RMRK contract). /// * `id` can not be equal to zero. /// * Resource with indicated `id` must not exist. /// /// # Arguments: /// * `id`: is a resource identifier. /// * `src`: a string pointing to the media associated with the resource. /// * `thumb`: a string pointing to thumbnail media associated with the resource. /// * `metadata_uri`: a string pointing to a metadata file associated with the resource. /// /// On success replies [`ResourceEvent::ResourceEntryAdded`]. AddResourceEntry { id: u8, src: String, thumb: String, metadata_uri: String, }, ``` To get information about whether such a resource exists in the storage or not, send the message: ```rust /// Used to check from the RMRK contract whether the resource with indicated id exists or not. /// /// # Arguments: /// * `id`: is a resource identifier. /// /// On success replies [`ResourceEvent::Resource`]. GetResource { id: u8, }, ``` MultiResource in RMRK contract: [#multiresource-in-rmrk-contract] The RMRK contract admin can add resource to the storage contract through the RMRK contract: ```rust /// Adds resource entry on resource storage contract. /// It sends a message to the resource storage contract with information about a new resource. /// /// # Requirements: /// * The `msg::source()` must be the contract admin. /// /// Arguments: /// * `id`: is a resource identifier /// * `src`: a string pointing to the media associated with the resource. /// * `thumb`: a string pointing to thumbnail media associated with the resource. /// * `metadata_uri`: a string pointing to a metadata file associated with the resource. /// /// On success reply `[RMRKEvent::ResourceEntryAdded]`. AddResourceEntry { id: u8, src: String, thumb: String, metadata_uri: String, }, ``` Anyone can add resource to the existing token in the form of a propose-commit pattern. When adding a resource to a token, it is first placed in the "Pending" array, and must be migrated to the "Active" array by the token owner: ```rust /// Adds resource to an existing token. /// Checks that the resource with indicated id exists in the resource storage contract. /// Proposed resource is placed in the "Pending" array. /// A pending resource can be also proposed to overwrite an existing resource. /// /// # Requirements /// Token with indicated `token_id` must exist. /// The proposed resource must not already exist for the token. /// The resource that is proposed to be overwritten must exist for the token. /// The length of resources in pending status must be less or equal to `MAX_RESOURCE_LEN`. /// /// # Arguments: /// * `token_id`: an id of the token. /// * `resource_id`: a proposed resource. /// * `overwrite_id`: a resource to be overwritten. /// /// On success reply `[RMRKEvent::ResourceAdded]`. AddResource { token_id: TokenId, resource_id: u8, overwrite_id: u8, }, ``` To accept resource the root owner must send the message: ```rust /// Accepts resource from pending list. /// Moves the resource from the pending array to the accepted array. /// /// # Requirements /// Only root owner or approved account can accept a resource. /// `resource_id` must exist for the token in the pending array. /// /// # Arguments: /// * `token_id`: an id of the token. /// * `resource_id`: a resource to be accepted. /// /// On success reply `[RMRKEvent::ResourceAccepted]`. AcceptResource { token_id: TokenId, resource_id: u8, }, ``` or reject resource with the message: ```rust /// Rejects a resource, dropping it from the pending array. /// /// # Requirements /// Only root owner or approved account can reject a resource. /// `resource_id` must exist for the token in the pending array. /// /// # Arguments: /// * `token_id`: an id of the token. /// * `resource_id`: a resource to be rejected. /// /// On success reply `[RMRKEvent::ResourceRejected]`. RejectResource { token_id: TokenId, resource_id: u8, }, ``` An NFT multiple resources are ordered by priority that is set by the root owner: ```rust /// Sets the priority of the active resources array /// Priorities have a 1:1 relationship with their corresponding index in /// the active resources array. E.G, a priority array of [1, 3, 2] indicates /// that the the active resource at index 1 of the active resource array /// has a priority of 1, index 2 has a priority of 3, and index 3 has a priority /// of 2. There is no validation on priority value input; out of order indexes /// must be handled by the frontend. /// /// # Requirements /// Only root owner or approved account can set priority /// The length of the priorities array must be equal to the present length of the active resources array /// /// # Arguments: /// * `token_id`: an id of the token. /// * `priorities`: An array of priorities to set. /// /// On success reply `[RMRKEvent::PrioritySet]`. SetPriority { token_id: TokenId, priorities: Vec, }, ``` Bases and Equippable NFTs [#bases-and-equippable-nfts] That functionality allows NFTs to equip owned NFTs in order to gain extra utility or change their appearance. It is also known as composable NFTs. Resources are divided into three types: * Basic: ```rust #[derive(Debug, Default, Clone, Encode, Decode, TypeInfo)] pub struct BasicResource { /// URI like ipfs hash pub src: String, /// If the resource has the thumb property, this will be a URI to a thumbnail of the given /// resource. pub thumb: Option, /// Reference to IPFS location of metadata pub metadata_uri: String, } ``` * Composable: ```rust #[derive(Debug, Default, Clone, Encode, Decode, TypeInfo)] pub struct ComposedResource { /// URI like ipfs hash pub src: String, /// If the resource has the thumb property, this will be a URI to a thumbnail of the given /// resource. pub thumb: String, /// Reference to IPFS location of metadata pub metadata_uri: String, // The address of base contract pub base: BaseId, // If a resource is composed, it will have an array of parts that compose it pub parts: Parts, } ``` * Slot: ```rust #[derive(Debug, Default, Clone, Encode, Decode, TypeInfo)] pub struct SlotResource { /// URI like ipfs hash pub src: String, /// If the resource has the thumb property, this will be a URI to a thumbnail of the given /// resource. pub thumb: String, /// Reference to IPFS location of metadata pub metadata_uri: String, // The address of base contract pub base: BaseId, /// If the resource has the slot property, it was designed to fit into a specific Base's slot. pub slot: SlotId, } ``` Base contract is a catalogue of parts from which an NFT can be composed. If resource is `Composable` or `Slot` then it refers to `base` contract that contains the information about parts. The base contract state: ```rust #[derive(Debug, Default, Encode, Decode, TypeInfo)] pub struct Base { /// Original creator of the Base. /// Issuer can add, modify or remove parts from Base. pub issuer: ActorId, /// Specifies how an NFT should be rendered(svg, audio, mixed, video, png). pub base_type: String, /// Provided ny user during Base creation. pub symbol: String, /// Mapping from `PartId` to `Part`. pub parts: BTreeMap, } ``` Parts can be `Fixed` or `Slot`: ```rust #[derive(Debug, Clone, Default, Encode, Decode, TypeInfo)] pub struct FixedPart { /// A unique identifier. pub id: PartId, /// An optional zIndex of base part layer. /// specifies the stack order of an element. /// An element with greater stack order is always in front of an element with a lower stack order. pub z: Option, /// An IPFS Uri pointing to main media file of this part. pub src: String, } #[derive(Debug, Clone, Encode, Decode, TypeInfo)] pub struct SlotPart { /// A unique identifier. pub id: PartId, /// Array of whitelisted collections that can be equipped in the given slot. Used with slot parts only. pub equippable: EquippableList, /// An optional zIndex of base part layer. /// specifies the stack order of an element. /// An element with greater stack order is always in front of an element with a lower stack order. pub z: Option, /// An optional IPFS Uri pointing to main media file of this part. pub src: Option, } ``` The messages that the Base contract handles: ```rust #[derive(Debug, Decode, Encode, TypeInfo)] pub enum BaseAction { /// Adds parts to base contract. /// /// # Requirements: /// * The `msg::source()` must be the contract issuer. /// * `PartId` must be unique. /// /// # Arguments: /// * `BTreeMap`: a mapping from `PartId` to fixed or slot `Part`. /// /// On success replies `[BaseEvent::PartsAdded]`. AddParts(BTreeMap), /// Adds equippable to slot part. /// /// # Requirements: /// * The `msg::source()` must be the contract issuer. /// * The indicated collection contract must be RMRK contract. /// * The token from indicated collections must have composable resource that refers to that base. /// /// # Arguments: /// * `collection_id`: an address of RMRK contract. /// * `token_id`: the id of the token in RMRK contract. /// /// On success replies `[BaseEvent::EquippableAdded]`. AddEquippable { collection_id: CollectionId, token_id: TokenId, }, /// Removes parts from the base. /// /// # Requirements: /// * The `msg::source()` must be the contract issuer. /// * The parts with indicated PartIds must exist. /// /// # Arguments: /// * `Vec`: Part IDs to be removed. /// /// On success replies `[BaseEvent::PartsRemoved]`. RemoveParts(Vec), /// Removes equippable from the slot part. /// /// # Requirements: /// * The `msg::source()` must be the contract issuer. /// * Indicated equippable must exist. /// /// # Arguments: /// * `collection_id`: an address of RMRK contract. /// * `token_id`: the id of the token in RMRK contract. /// /// On success replies `[BaseEvent::EquippableRemoved]`. RemoveEquippable { collection_id: CollectionId, token_id: TokenId, }, /// Checks whether the part exists in the Base. /// /// # Arguments: /// * `PartId`: the Part Id. /// /// On success replies `[BaseEvent::Part]`. CheckPart(PartId), } ``` The RMRK contract logic is extended with the following messages: ```rust /// Equip a child NFT's resource to a parent's slot. /// It sends message to the child contract /// to check whether the child token has the indicated slot resource. /// /// # Requirements: /// * The `msg::source()` must be the root owner. /// * The child token must have the slot resource with indicated `base_id` and `slot_id`. /// * The parent token must have composed resource with indicated `base_id`. /// * The parent must not have the equippable with that child token. ///å /// Arguments: /// * `token_id`: the ID of the parent token (equippable token). /// * `resource_id`: the resource id of the equippable token. /// * `child_contract_id`: the child contract address. /// * `child_token_id`: the id of the child token that will be equipped. /// * `child_resource_id`: the resource id of the child token. /// * `base_id`: the address of the base contract. /// * `slot_id`: the id of the slot part in the base contract. /// /// On success reply `[RMRKEvent::TokenEquipped]`. Equip { token_id: TokenId, resource_id: ResourceId, child_contract_id: ActorId, child_token_id: TokenId, child_resource_id: ResourceId, base_id: BaseId, slot_id: SlotId, }, /// Unequip a child NFT's resource from a parent's slot. /// /// # Requirements: /// * The `msg::source()` must be the root owner. /// * The parent must have the equippable with that child token. /// /// Arguments: /// * `token_id`: the ID of the parent token (equippable token). /// * `child_contract_id`: the child contract address. /// * `child_token_id`: the id of the child token that will be equipped. /// /// On success reply `[RMRKEvent::TokenUnequipped]`. Unequip { token_id: TokenId, child_contract_id: ActorId, child_token_id: TokenId, }, ``` Source code [#source-code] The source code of RMRK implementation and the example of an implementation of its testing is available on [GitHub](https://github.com/gear-foundation/dapps/tree/master/contracts/rmrk). For more details about testing programs on Vara, refer to the [Program Testing](/docs/developing/build/testing) article. # VMT (ERC-1155) The **Vara Multi-Token (VMT)** standard is the equivalent of Ethereum's **ERC-1155** on the Vara Network. The VMT Standard outlines a unified API for implementing multi-token functionality in programs. The initial section provides an in-depth exploration of the core VMT service, covering operations like batch token transfers and token balance queries, detailing the contract state, interface, and key methods. The source code of the standard-service is avaiable on the [GitHub](https://github.com/gear-foundation/standards/tree/master/vmt-service). The following section expands on extending this core service to develop a fully functional multi-token application. This part illustrates how to incorporate minting and burning capabilities and advanced management features, building upon the core VMT service to create a robust and flexible token system. The source code of the extended version is avaiable on the [GitHub](https://github.com/gear-foundation/standards/tree/master/extended-vmt). The project code is built using the [Sails](/docs/developing/build/sails) framework, which simplifies program development on Gear Protocol. Public CodeIds [#public-codeids] You can instantiate the extended VFT using the public CodeId below. The instantiating account becomes the initial admin/minter/burner. * **CodeId**: `0x3c902523c31f930a4169a5149ff439ec2574a6a6cebe3d6c06742bb254073566` * Mainnet: [link](https://idea.gear-tech.io/code/0x3c902523c31f930a4169a5149ff439ec2574a6a6cebe3d6c06742bb254073566?node=wss%3A%2F%2Frpc.vara.network) > Open Gear IDEA → **Codes**, select the network, paste the CodeId, and click **Create Program**. Core VMT Service [#core-vmt-service] Functions [#functions] The VMT service includes the following functions: * `Approve(to)` * `TransferFrom(from, to, id, amount)` * `BatchTransferFrom(from, to, ids, amounts)` * `BalanceOf(account, id)` * `BalanceOfBatch(accounts, ids)` * `IsApproved(account, operator)` * `Name()` * `Symbol()` * `Decimals()` * `TotalSupply()` Events [#events] The core service also defines the following events: * `Approval(from, to)` * `Transfer(from, to, ids, amounts)` Key Methods [#key-methods] Approve [#approve] ```rust pub fn approve(&mut self, to: ActorId) -> bool ``` Allows an account to approve another actor (to) to manage all of its tokens. This function sets the approval status of the operator and returns a boolean indicating success. When approved, the `Approval` event is triggered. TransferFrom [#transferfrom] ```rust pub fn transfer_from(&mut self, from: ActorId, to: ActorId, id: TokenId, amount: U256) ``` Transfers `amount` of token with ID `id` from the `from` account to the `to` account. The caller must be approved to manage the `from` account's tokens. The `Transfer` event is triggered upon successful execution. BatchTransferFrom [#batchtransferfrom] ```rust pub fn batch_transfer_from( &mut self, from: ActorId, to: ActorId, ids: Vec, amounts: Vec, ) ``` Transfers multiple token types from one account to another. The `ids` array contains token IDs, and the `amounts` array specifies how many of each token type to transfer. This method triggers the `Transfer` event for each token ID and amount. Query methods [#query-methods] BalanceOf [#balanceof] Returns the balance of tokens for a given account and token ID. ```rust pub fn balance_of(&self, account: ActorId, id: TokenId) -> U256 ``` BalanceOfBatch [#balanceofbatch] Returns the balances of tokens for the specified accounts and token IDs. ```rust pub fn balance_of_batch(&self, accounts: Vec, id: Vec) -> Vec ``` IsApproved [#isapproved] Checks if a specific operator is approved to manage the tokens of the `account`. ```rust pub fn is_approved(&self, account: ActorId, operator: ActorId) -> bool ``` Name [#name] Returns the name of the multi-token collection. ```rust pub fn name(&self) -> &'static str ``` Symbol [#symbol] Returns the symbol of the token collection. ```rust pub fn symbol(&self) -> &'static str ``` Decimals [#decimals] Returns the number of decimal places for the tokens. ```rust pub fn decimals(&self) -> u8 ``` TotalSupply [#totalsupply] Returns the total supply of each token ID. ```rust pub fn total_supply(&self) -> Vec<(TokenId, U256)> ``` The core VMT service serves as a **foundational framework**. It is designed to be extended with more complex functionality such as **minting, burning, and role management**. **Source Code:** [GitHub Repository](https://github.com/gear-foundation/standards/tree/master/vmt-service) Extended VMT Implementation [#extended-vmt-implementation] Additional Features [#additional-features] The extended implementation adds new functionality to the core VMT service, including: Functions [#functions-1] * `Mint(to, id, amount, token_metadata)` * `MintBatch(to, ids, amounts, token_metadata)` * `Burn(from, id, amount)` * `BurnBatch(from, ids, amounts)` * `GrantAdminRole(to)` * `GrantMinterRole(to)` * `GrantBurnerRole(to)` * `RevokeAdminRole(from)` * `RevokeMinterRole(from)` * `RevokeBurnerRole(from)` * `Admins()` * `Minters()` * `Burners()` Events [#events-1] * `Minted(to, ids, amounts)` * `Burned(from, ids, amounts)` Implementation Details [#implementation-details] The VMT core service is imported and extended: ```rust use vmt_service::Service as VmtService; pub struct ExtendedVmtService { vmt: VmtService, } ``` The `#[service(extends = VmtService, events = Event)]` attribute ensures inheritance from `VmtService`. Key Methods [#key-methods-1] Mint [#mint] ```rust pub fn mint( &mut self, to: ActorId, id: TokenId, amount: U256, token_metadata: Option, ) ``` Mints a new token with a specified ID and amount, assigning it to the `to` account. The optional `token_metadata` field allows storing additional information about the token (like title, description, media and reference). This function triggers the `Minted` event. MintBatch [#mintbatch] ```rust pub fn mint_batch( &mut self, to: ActorId, ids: Vec, amounts: Vec, token_metadata: Vec>, ) ``` Mints multiple types of tokens and assigns them to the `to` account. The function accepts arrays of token IDs and amounts, along with corresponding metadata. It triggers the `Minted` event for all tokens in the batch. Burn [#burn] ```rust pub fn burn(&mut self, from: ActorId, id: TokenId, amount: U256) ``` Burns a specified amount of tokens with a specific ID from the `from` account. This reduces the total supply of the burned tokens and triggers the `Burned` event. BurnBatch [#burnbatch] ```rust pub fn burn_batch(&mut self, from: ActorId, ids: Vec, amounts: Vec) ``` Burns multiple types of tokens from the `from` account. This method reduces the total supply for each token in the batch and triggers the `Burned` event. Role Management [#role-management] * `grant_admin_role(&mut self, to: ActorId)` * `grant_minter_role(&mut self, to: ActorId)` * `grant_burner_role(&mut self, to: ActorId)` * `revoke_admin_role(&mut self, from: ActorId)` * `revoke_minter_role(&mut self, from: ActorId)` * `revoke_burner_role(&mut self, from: ActorId)` These methods manage the assignment and revocation of administrative, minting, and burning roles, ensuring that only authorized actors can perform sensitive operations. Query methods [#query-methods-1] minters [#minters] Returns a list of all actors who have the minter role. ```rust pub fn minters(&self) -> Vec ``` burners [#burners] Returns a list of all actors who have the burner role. ```rust pub fn burners(&self) -> Vec ``` admins [#admins] Returns a list of all actors who have the admin role. ```rust pub fn admins(&self) -> Vec ``` Contract Interface [#contract-interface] The extended VMT service includes the following interface: ```rust !@sails: 1.0.0 service Vmt { events { Minted { to: actor, ids: [u256], amounts: [u256] }, Burned { from: actor, ids: [u256], amounts: [u256] }, Approval { from: actor, to: actor }, Transfer { from: actor, to: actor, ids: [u256], amounts: [u256] }, } functions { Burn(from: actor, id: u256, amount: u256); BurnBatch(from: actor, ids: [u256], amounts: [u256]); GrantAdminRole(to: actor); GrantBurnerRole(to: actor); GrantMinterRole(to: actor); Mint(to: actor, id: u256, amount: u256, token_metadata: Option); MintBatch(to: actor, ids: [u256], amounts: [u256], token_metadata: [Option]); RevokeAdminRole(from: actor); RevokeBurnerRole(from: actor); RevokeMinterRole(from: actor); Approve(to: actor) -> bool; BatchTransferFrom(from: actor, to: actor, ids: [u256], amounts: [u256]); TransferFrom(from: actor, to: actor, id: u256, amount: u256); @query Admins() -> [actor]; @query Burners() -> [actor]; @query Minters() -> [actor]; @query BalanceOf(account: actor, id: u256) -> u256; @query BalanceOfBatch(accounts: [actor], ids: [u256]) -> [u256]; @query Decimals() -> u8; @query IsApproved(account: actor, operator: actor) -> bool; @query Name() -> String; @query Symbol() -> String; @query TotalSupply() -> [(u256, u256)]; } types { struct TokenMetadata { title: Option, description: Option, media: Option, reference: Option, } } } program VmtProgram { constructors { New(name: String, symbol: String, decimals: u8); } services { Vmt: Vmt; } } ``` The **Extended VMT** service demonstrates how to build upon the core logic to include advanced features: * **Minting & Burning:** Logic for creating and destroying tokens. * **URI Management:** Handling metadata for multi-tokens. **Source Code:** [GitHub Repository](https://github.com/gear-foundation/standards/tree/master/extended-vmt) Conclusion [#conclusion] The core VMT service establishes a solid foundation for implementing multi-token systems within the Vara ecosystem, encompassing essential functionalities that align with recognized token standards. This service acts as a fundamental core, while the Extended VMT implementation demonstrates how it can be expanded to include advanced features such as minting, burning, and role management. Together, these implementations offer a comprehensive framework for developers, enabling the creation of versatile and feature-rich token systems. By leveraging both the core service and its extended capabilities, developers are equipped to design flexible, secure, and scalable token solutions that meet specific needs and enhance overall system functionality. # VNFT (ERC-721) The **Vara Non-Fungible Token (VNFT)** standard is the equivalent of Ethereum's **ERC-721** on the Vara Network. The Vara Non-Fungible Token Standard outlines a unified API for implementing non-fungible token functionalities in programs. The initial section of this document provides a comprehensive examination of the core VNFT service, which serves as a foundational framework. It covers essential operations such as token transfers and approvals for third-party spending, detailing the contract state, interface, and key methods involved. The source code of the standard-service is avaiable on the [GitHub](https://github.com/gear-foundation/standards/tree/master/vnft-service). The subsequent section expands on how to leverage and extend this core service to develop a fully functional token application. It illustrates the process of adding advanced features like minting and burning, demonstrating how to build upon the core VNFT service to create a comprehensive and customizable token system. This extension highlights the flexibility and potential of the core standard, providing a pathway to develop more sophisticated and tailored token solutions. The source code of the extended version is avaiable on the [GitHub](https://github.com/gear-foundation/standards/tree/master/extended-vnft). The project code is built using the [Sails](/docs/developing/build/sails) framework, which simplifies program development on Gear Protocol. Public CodeIds [#public-codeids] You can instantiate the extended VFT using the public CodeId below. The instantiating account becomes the initial admin/minter/burner. * **CodeId**: `0xbba6636d3bec4f203d4ae9b58d9bc9995c7aa20344028001f22dceb43911afad` * Mainnet: [link](https://idea.gear-tech.io/code/0xbba6636d3bec4f203d4ae9b58d9bc9995c7aa20344028001f22dceb43911afad?node=wss%3A%2F%2Frpc.vara.network) > Open Gear IDEA → **Codes**, select the network, paste the CodeId, and click **Create Program**. Functions [#functions] ``` Approve(to, token_id) Transfer(to, token_id) TransferFrom(from, to, token_id) OwnerOf(token_id) BalanceOf(owner) GetApproved(token_id) Name() Symbol() ``` Events [#events] ``` Approval(owner, approved, token_id); Transfer(from, to, token_id); ``` Key Methods [#key-methods] Approve [#approve] ```rust pub fn approve(&mut self, approved: ActorId, token_id: TokenId) ``` This function authorizes a designated account (`approved`) to transfer the specified NFT (`token_id`) on behalf of the owner. Only one account can be approved at a time for a specific NFT. If the function is called again with a different approved address, it overrides the previous approval. If the function is called with the zero address as approved, it cancels the existing approval. This action triggers the `Approval` event: ```rust Approval { owner: ActorId, approved: ActorId, token_id: TokenId, } ``` Transfer [#transfer] ```rust pub fn transfer(&mut self, to: ActorId, token_id: TokenId) ``` This function transfers ownership of a specific NFT (`token_id`) to another account (`to`). The caller must be the current owner of the NFT. Upon successful execution, it generates the `Transfer` event: ```rust Transfer { from: ActorId, to: ActorId, token_id: TokenId, } ``` TransferFrom [#transferfrom] ```rust pub fn transfer_from(&mut self, from: ActorId, to: ActorId, token_id: TokenId) ``` This function facilitates the transfer of an NFT from one account (`from`) to another (`to`), using the approval mechanism. It allows a third party to transfer an NFT on behalf of its owner, provided the caller has been previously approved. The function triggers the `Transfer` event upon success: ```rust Transfer { from: ActorId, to: ActorId, token_id: TokenId, } ``` Query Methods [#query-methods] owner_of [#owner_of] Returns the owner of a specified NFT (`token_id`). ```rust pub fn owner_of(&self, token_id: TokenId) -> ActorId ``` balance_of [#balance_of] Returns the number of NFTs owned by a specific account (`owner`). ```rust pub fn balance_of(&self, owner: ActorId) -> U256 ``` get_approved [#get_approved] Returns the account approved to transfer a specific NFT (`token_id`), or the zero address if no approval exists. ```rust pub fn get_approved(&self, token_id: TokenId) -> ActorId ``` name [#name] Returns the name of the NFT collection. ```rust pub fn name(&self) -> String ``` symbol [#symbol] Returns the symbol of the NFT collection. ```rust pub fn symbol(&self) -> String ``` This service is a **foundational base** for creating your own token. It is designed to be extended and inherited to add custom functionality. * **Source Code:** Available on [GitHub](https://github.com/gear-foundation/standards/tree/master/vft-service). * **Extended Version:** Look for the extended example to see how to transform this core logic into a fully functional application. Extended VNFT Implementation [#extended-vnft-implementation] Additional Features [#additional-features] The extended implementation introduces new functions and events that enhance the basic VNFT service. The additional features include: Functions [#functions-1] * `Mint(to, token_metadata)` * `Burn(from, token_id)` * `GrantAdminRole(to)` * `GrantBurnerRole(to)` * `GrantMinterRole(to)` * `RevokeAdminRole(from)` * `RevokeBurnerRole(from)` * `RevokeMinterRole(from)` * `Admins()` * `Burners()` * `Minters()` Events [#events-1] * `Minted(to, token_metadata)` * `Burned(from, token_id)` Implementation Details [#implementation-details] To incorporate the extended functionality, the VNFT service is first added as a dependency in the `Cargo.toml` file: ```toml vnft-service = { git = "https://github.com/gear-foundation/standards" } ``` A new service is created that extends the existing VNFT service: ```rust use vnft_service::Service as VnftService; pub struct ExtendedService { vnft: VnftService, } ``` The `#[service(extends = VnftService, events = Event)]` attribute is used to ensure that the `ExtendedService` inherits methods and properties from `VnftService`, making all core functionalities available within the extended service. Key Methods [#key-methods-1] Mint [#mint] ```rust pub fn mint(&mut self, to: ActorId, token_metadata: TokenMetadata) ``` This function mints new tokens and assigns them to a specified actor. The function checks if the caller has the minter role and triggers a `Minted` event if the minting is successful. Burn [#burn] ```rust pub fn burn(&mut self, from: ActorId, token_id: TokenId) ``` This function burns tokens from a specified actor. It checks if the caller has the burner role and triggers a `Burned` event if the burning is successful. Role Management [#role-management] * `grant_admin_role(&mut self, to: ActorId)` * `grant_minter_role(&mut self, to: ActorId)` * `grant_burner_role(&mut self, to: ActorId)` * `revoke_admin_role(&mut self, from: ActorId)` * `revoke_minter_role(&mut self, from: ActorId)` * `revoke_burner_role(&mut self, from: ActorId)` These methods manage the assignment and revocation of administrative, minting, and burning roles, ensuring that only authorized actors can perform sensitive operations. Query methods [#query-methods-1] minters [#minters] Returns a list of all actors who have the minter role. ```rust pub fn minters(&self) -> Vec ``` burners [#burners] Returns a list of all actors who have the burner role. ```rust pub fn burners(&self) -> Vec ``` admins [#admins] Returns a list of all actors who have the admin role. ```rust pub fn admins(&self) -> Vec ``` tokens for owner [#tokens-for-owner] Returns a list of all tokens with metadata that the owner holds. ```rust pub fn tokens_for_owner(&self, owner: ActorId) -> Vec<(TokenId, TokenMetadata)> ``` Contract Interface [#contract-interface] The extended service incorporates the following interface: ```rust !@sails: 1.0.0 service Vnft { events { Minted { to: actor, token_metadata: TokenMetadata }, Burned { from: actor, token_id: u256 }, Transfer { from: actor, to: actor, token_id: u256 }, Approval { owner: actor, approved: actor, token_id: u256 }, } functions { Burn(from: actor, token_id: u256); GrantAdminRole(to: actor); GrantBurnerRole(to: actor); GrantMinterRole(to: actor); Mint(to: actor, token_metadata: TokenMetadata); RevokeAdminRole(from: actor); RevokeBurnerRole(from: actor); RevokeMinterRole(from: actor); Approve(approved: actor, token_id: u256); Transfer(to: actor, token_id: u256); TransferFrom(from: actor, to: actor, token_id: u256); @query Admins() -> [actor]; @query Burners() -> [actor]; @query Minters() -> [actor]; @query TokenId() -> u256; @query TokenMetadataById(token_id: u256) -> Option; @query TokensForOwner(owner: actor) -> [(u256, TokenMetadata)]; @query BalanceOf(owner: actor) -> u256; @query GetApproved(token_id: u256) -> actor; @query Name() -> String; @query OwnerOf(token_id: u256) -> actor; @query Symbol() -> String; } types { struct TokenMetadata { name: String, description: String, media: String, reference: String, } } } program VnftProgram { constructors { New(name: String, symbol: String); } services { Vnft: Vnft; } } ``` The **Extended VNFT** service illustrates how the core logic can be expanded with advanced features such as **minting, burning, and role management**. This implementation provides a versatile framework for building robust, custom token systems within the Vara ecosystem. **Source Code:** [GitHub Repository](https://github.com/gear-foundation/standards/tree/master/extended-vnft) Conclusion [#conclusion] The core [VNFT service](https://github.com/gear-foundation/standards/tree/master/vnft-service) establishes a robust foundation for implementing non-fungible tokens within the Vara ecosystem, encompassing essential functionalities that adhere to recognized token standards. This service functions as a fundamental core, with the [Extended VNFT](https://github.com/gear-foundation/standards/tree/master/extended-vnft) illustrating how it can be expanded to incorporate advanced capabilities, including minting, burning, and role management. Together, these implementations provide a comprehensive framework for developers, facilitating the creation of tailored and sophisticated token systems. By utilizing both the core service and its extended functionalities, developers are well-positioned to design flexible and secure token solutions that address specific requirements and enhance overall system capabilities. # DAO Introduction [#introduction] What is DAO [#what-is-dao] A decentralized autonomous organization, or DAO for short, is a novel approach to managing organizations or institutions that enables individuals to collaborate for a specific cause in transparent, fair, and honest ways. DAOs can be likened to online communities of like-minded individuals, collectively owned and managed by their members in equitable ways. Decisions are made through proposals and votes, ensuring that every member within a decentralized autonomous organization has a voice. This is significant because it prevents any central entity from manipulating matters for personal gain or based on personal beliefs. DAOs provide secure alternatives for pooling funds for a particular cause. It's not limited to ordinary financial management by members. For example, a group could establish a DAO to oversee a charity, accepting donations and distributing aid in an accountable manner. However, the most prominent use cases for DAOs at the moment involve decentralized investment funds. In such scenarios, a group of investors establishes a venture fund that combines capital and transparently votes on its allocation. DAO application example [#dao-application-example] Anyone can easily create their own DAO application and run it on the Vara Network. To facilitate this, use an example of the DAO program (smart contract), which is available on [GitHub](https://github.com/gear-foundation/dapps/tree/a357635b61e27c52f46908885fecb048dc8424e5/contracts/dao-light). This article explains the programming interface, data structure, basic functions, and their purposes. You can use it as-is or modify it to suit your own scenarios. {/* In addition, Gear Foundation provides an example implementation of the DAO user interface to demonstrate its interaction with the program in the Vara Network. You can watch a video on how to get the DAO application up and running and its capabilities here: **https://youtu.be/6lxr7eojADw**. The source code for the DAO application is available on [GitHub](https://github.com/gear-foundation/dapps-dao-app). */} Logic [#logic] The program has the following structs: ```rust title="dao-light/src/lib.rs" struct Dao { approved_token_program_id: ActorId, period_duration: u64, voting_period_length: u64, grace_period_length: u64, total_shares: u128, members: HashMap, proposal_id: u128, locked_funds: u128, proposals: HashMap, } ``` where: `approved_token_program_id` - the reference to the token contract ([VFT](/docs/vara-network/examples/Standards/vft)) that users use as pledge to get the share in the DAO. `period_duration` - the smallest unit time interval for the DAO, in ms. `voting_period_length` - voting time interval. Number of intervals for voting time = period duration \* voting\_period\_length. `grace_period_length` - after the voting period the DAO members are given a period of time in which they can leave the DAO(ragequit) without being diluted and ultimately affected by the proposal’s acceptance into the DAO. `total_shares` - total shares across all members. Initially it is zero. `members` - members of the DAO. `proposal_id` - the index of the last proposal. `locked_funds` - tokens that are locked when a funding proposal is submitted. `proposals` - all proposals (the proposal queue). Parameters `approved_token_program_id`, `voting_period_length`, `period_duration`, `grace_period_length` are set when initializing a program. The program is initialized with the following struct: ```rust title="dao-light/io/src/lib.rs" pub struct InitDao { pub approved_token_program_id: ActorId, pub voting_period_length: u64, pub period_duration: u64, pub grace_period_length: u64, } ``` The proposal struct: ```rust title="dao-light/io/src/lib.rs" pub struct Proposal { pub proposer: ActorId, pub applicant: ActorId, pub yes_votes: u128, pub no_votes: u128, pub quorum: u128, pub amount: u128, pub processed: bool, pub did_pass: bool, pub details: String, pub starting_period: u64, pub ended_at: u64, pub votes_by_member: Vec<(ActorId, Vote)>, } ``` * `proposer` - the member who submitted the proposal * `applicant` - the applicant who wishes to become a member * `yes_votes` - the total number of YES votes for that proposal * `no_votes` - the total number of NO votes for that proposal * `quorum` - a certain threshold of YES votes in order for the proposal to pass * `amount` - number of fungible tokens for donation * `processed` - true if the proposal has already been processed * `did_pass` - true if the proposal has passed * `details` - proposal details * `starting_period` - the start of the voting period * `ended_at` - the end of the voting period * `votes_by_member` - the votes on that proposal by each member The member struct: ```rust title="dao-light/io/src/lib.rs" pub struct Member { pub shares: u128, pub highest_index_yes_vote: Option, } ``` * `shares` - the shares of that member * `highest_index_yes_vote` - - the index of the highest proposal on which the members voted YES (that value is checked when user is going to leave the DAO) The actions that the program receives outside are defined in enum `DaoActions`. The program's replies are defined in the enum `DaoEvents`. DAO functions [#dao-functions] * Joining a DAO * To join the DAO and become a DAO member, a user needs to send the following message to the DAO program: ```rust title="dao-light/io/src/lib.rs" /// Deposits tokens to DAO /// The account gets a share in DAO that is calculated as: (amount * self.total_shares / self.balance) /// /// On success replies with [`DaoEvent::Deposit`] Deposit { /// the number of fungible tokens that user wants to deposit to DAO amount: u128, }, ``` * The funding proposal. * The 'applicant' is an actor that will be funded: ```rust title="dao-light/io/src/lib.rs" /// The proposal of funding. /// /// Requirements: /// /// * The proposal can be submitted only by the existing members; /// * The receiver ID can't be the zero; /// * The DAO must have enough funds to finance the proposal /// /// On success replies with [`DaoEvent::SubmitFundingProposal`] SubmitFundingProposal { /// an actor that will be funded applicant: ActorId, /// the number of fungible tokens that will be sent to the receiver amount: u128, /// a certain threshold of YES votes in order for the proposal to pass quorum: u128, /// the proposal description details: String, }, ``` * The member or the delegate address of the member submits their vote (YES or NO) on the proposal. ```rust title="dao-light/io/src/lib.rs" /// The member submits a vote (YES or NO) on the proposal. /// /// Requirements: /// * The proposal can be submitted only by the existing members; /// * The member can vote on the proposal only once; /// * Proposal must exist, the voting period must have started and not expired; /// /// On success replies with [`DaoEvent::SubmitVote`] SubmitVote { /// the proposal ID proposal_id: u128, /// the member a member vote (YES or NO) vote: Vote, }, ``` * Members have the option to withdraw their capital during a grace period. This feature is useful when members disagree with the outcome of a proposal, especially if the acceptance of that proposal could impact their shares. A member can initiate a 'ragequit' only if they have voted 'NO' on the proposal. ```rust title="dao-light/io/src/lib.rs" /// Withdraws the capital of the member /// /// Requirements: /// * `msg::source()` must be DAO member; /// * The member must have sufficient amount of shares; /// * The latest proposal the member voted YES must be processed; /// /// On success replies with [`DaoEvent::RageQuit`] RageQuit { /// The amount of shares the member would like to withdraw amount: u128, }, ``` * The proposal processing occurs after the proposal completes its grace period. If the proposal is accepted, the tribute tokens are deposited into the program, and new shares are minted and issued to the applicant. In the event of rejection, the tribute tokens are returned to the applicant. ```rust title="dao-light/io/src/lib.rs" /// The proposal processing after the proposal completes during the grace period. /// If the proposal is accepted, the indicated amount of tokens are sent to the receiver. /// /// Requirements: /// * The previous proposal must be processed; /// * The proposal must exist and be ready for processing; /// * The proposal must not already be processed. /// /// On success replies with [`DaoEvent::ProcessProposal`] ProcessProposal { /// the actor who was funded applicant: ActorId, /// the proposal ID proposal_id: u128, /// true if funding proposal has passed did_pass: bool, }, ``` [//]: # "## Consistency of program states" [//]: # [//]: # "The `DAO` program interacts with the `fungible token` program. Every transaction that alters the states of the DAO and the fungible token is recorded in the state until it is finalized. Users can complete a pending transaction by sending a `Continue` message along with the transaction ID. The idempotency feature of the fungible token program allows transactions to be restarted without duplicating changes, ensuring the state consistency of these two programs." [//]: # [//]: # [//]: # "## User interface" [//]: # [//]: # "A [Ready-to-Use application](https://dao.gear-tech.io/) example provides a user interface that interacts with [DAO](https://github.com/gear-foundation/dapps-dao-light) and [gFT](https://github.com/gear-foundation/dapps-fungible-token) programs." [//]: # [//]: # "Gear Fundible Token enables creation of utility token DAO, check [this article](../Standards/gft-20) for details." [//]: # [//]: # "This video demonstrates the entire configuration and user interaction workflow: **https://youtu.be/6lxr7eojADw**" [//]: # [//]: # "![img alt](./img/dao-1.jpg)" [//]: # [//]: # "A DAO application source code is available on [GitHub](https://github.com/gear-foundation/dapps-dao-app)." [//]: # [//]: # "### Configure basic dApp in .env:" [//]: # [//]: # "```sh" [//]: # "REACT_APP_NETWORK" [//]: # "REACT_APP_CONTRACT_ERC" [//]: # "REACT_APP_CONTRACT_DAO" [//]: # "```" [//]: # [//]: # "- `REACT_APP_NETWORK` is Gear network address (wss://rpc-node.gear-tech.io:443)" [//]: # "- `REACT_APP_CONTRACT_ERC` is Fundible Token contract address" [//]: # "- `REACT_APP_CONTRACT_DAO` is DAO program address" [//]: # [//]: # "An example is available: [here](https://github.com/gear-foundation/dapps-dao-app/blob/master/.env.example)" [//]: # [//]: # "### How to run" [//]: # [//]: # "Install required dependencies:" [//]: # "```sh" [//]: # "yarn" [//]: # "```" [//]: # [//]: # "Run:" [//]: # "```sh" [//]: # "yarn run start" [//]: # "```" Program metadata and state [#program-metadata-and-state] Metadata interface description: ```rust title="dao-light/io/src/lib.rs" pub struct DaoLightMetadata; impl Metadata for DaoLightMetadata { type Init = In; type Handle = InOut; type Others = (); type Reply = (); type Signal = (); type State = Out; } ``` To display the full program state information, the `state()` function is used: ```rust title="dao-light/src/lib.rs" #[no_mangle] extern fn state() { let dao = unsafe { DAO.take().expect("Unexpected error in taking state") }; msg::reply::(dao.into(), 0) .expect("Failed to encode or reply with `DaoState` from `state()`"); } ``` To display only necessary certain values from the state, you need to write a separate crate. In this crate, specify functions that will return the desired values from the `DaoState` state. For example - [gear-foundation/dapps/dao/state](https://github.com/gear-foundation/dapps/tree/a357635b61e27c52f46908885fecb048dc8424e5/contracts/dao/state): ```rust title="dao-light/state/src/lib.rs" #[gmeta::metawasm] pub mod metafns { pub type State = DaoState; pub fn user_status(state: State, account: ActorId) -> Role { //.. pub fn all_proposals(state: State) -> Vec { //.. pub fn is_member(state: State, account: ActorId) -> bool { //.. pub fn proposal_id(state: State) -> u128 { //.. pub fn proposal_info(state: State, proposal_id: u128) -> Proposal { //.. pub fn member_info(state: State, account: ActorId) -> Member { //.. pub fn member_power(state: State, account: ActorId) -> u128 { //.. } ``` Source code [#source-code] The source code for this DAO program example and its testing implementation are available on [GitHub](https://github.com/gear-foundation/dapps/tree/a357635b61e27c52f46908885fecb048dc8424e5/contracts/dao-light). The extended version of DAO that includes admin, membership proposals and delegated voting can be found at [GitHub](https://github.com/gear-foundation/dapps/tree/a357635b61e27c52f46908885fecb048dc8424e5/contracts/dao). [//]: # "The application source code is available in: [https://github.com/gear-foundation/dapps-dao-app](https://github.com/gear-foundation/dapps-dao-app)." For more details about testing programs written on Vara, refer to the [Program Testing](/docs/developing/build/testing) article. # Crowdsale Program Mechanics Introduction [#introduction] A public offering to help build brand-new cryptocurrency or other digital assets through crowd contributions is known as a cryptocurrency crowdsale. A crowdsale can be used by new projects to mobilize resources for development and other purposes. It is a time-limited campaign during which crypto owners can exchange their cryptocurrencies for newly proposed tokens, as defined in the campaign. These new tokens are promoted as future functional units once the crowdsale's goal is met and the project launches. The example of a crowdsale program implementation described in this article is just one of many decentralized applications that can be implemented and launched on Gear. This article explains the programming interface, data structure, basic functions, and their purposes. It can be used as-is or modified to suit specific scenarios. Anyone can easily create a crowdsale application and run it on the Vara network. The initial resources used to acquire tokens are determined by the Vara fungible tokens contract - [VFT](/docs/vara-network/examples/Standards/vft). The program's source code is available on [GitHub](https://github.com/gear-foundation/dapps/tree/a357635b61e27c52f46908885fecb048dc8424e5/contracts/crowdsale). Interface [#interface] Source Files [#source-files] 1. `messages.rs` - contains functions for the fungible token contract. The crowdsale program interacts with the fungible token contract through the `transfer_tokens` function: ```rust title="crowdsale/src/messages.rs" pub async fn transfer_tokens( transaction_id: u64, token_address: &ActorId, from: &ActorId, to: &ActorId, amount_tokens: u128, ) -> Result<(), ()> ``` * `transaction_id` - identifier of the associated transaction * `from` - the sender's account * `to` - recipient account * `amount_tokens` - number of tokens This function sends a message (the action is defined in the enum FTokenAction) and gets a reply (the reply is defined in the enum FTokenEvent): ```rust title="crowdsale/src/messages.rs" let reply = msg::send_for_reply_as::( *token_address, FTokenAction::Message { transaction_id, payload: LogicAction::Transfer { sender: *from, recipient: *to, amount: amount_tokens, }, }, 0, 0, ) .expect("Error in sending a message `FTokenAction::Message`") .await; match reply { Ok(FTokenEvent::Ok) => Ok(()), _ => Err(()), } ``` 2. `asserts.rs` - contains assert functions: `owner_message` and `not_zero_address`. * `owner_message` checks if `msg::source()` is equal to `owner`. Otherwise, it panics: ```rust title="crowdsale/src/asserts.rs" pub fn owner_message(owner: &ActorId, message: &str) { if msg::source() != *owner { panic!("{}: Not owner message", message) } } ``` * `not_zero_address` checks if `address` is not equal to `ZERO_ID`. Otherwise, it panics: ```rust title="crowdsale/src/asserts.rs" pub fn not_zero_address(address: &ActorId, message: &str) { if address == &ZERO_ID { panic!("{}: Zero address", message) } } ``` 3. `lib.rs` - defines the program logic. Structs [#structs] The program has the following structs: ```rust title="crowdsale/src/lib.rs" struct IcoContract { ico_state: IcoState, start_price: u128, price_increase_step: u128, time_increase_step: u128, tokens_sold: u128, tokens_goal: u128, owner: ActorId, token_address: ActorId, token_holders: HashMap, transaction_id: u64, transactions: HashMap, } ``` where: * `ico_state` is `IcoState` struct which consists of: ```rust title="crowdsale/io/src/lib.rs" pub struct IcoState { pub ico_started: bool, // true if ICO was started pub start_time: u64, // time when ICO was started, otherwise is zero pub duration: u64, // duration of the ICO, otherwise is zero pub ico_ended: bool, // true if ICO was ended } ``` * `start_price` - initial price of tokens * `price_increase_step` - how much the price increases * `time_increase_step` - the period of time after which the price increases * `tokens_sold` - how many tokens were sold * `tokens_goal` - how many tokens are going to be sold * `owner` - program owner * `token_address` - fungible token address * `token_holders` - the list of buyers and the number of tokens they bought Functions [#functions] * Starts the ICO. Only the owner can call it: ```rust title="crowdsale/src/lib.rs" async fn start_ico(&mut self, config: IcoAction) ``` replies with: ```rust title="crowdsale/src/lib.rs" IcoEvent::SaleStarted { transaction_id: current_transaction_id, duration, start_price, tokens_goal, price_increase_step, time_increase_step, } ``` * Purchase of tokens. Anyone with enough balance can call and buy tokens: ```rust title="crowdsale/src/lib.rs" pub fn buy_tokens(&mut self, tokens_cnt: u128) ``` replies with: ```rust title="crowdsale/src/lib.rs" IcoEvent::Bought { buyer: msg::source(), amount: tokens_cnt, change, } ``` * Ends the ICO. Only the owner can call it: ```rust title="crowdsale/src/lib.rs" async fn end_sale(&mut self) ``` replies with: ```rust title="crowdsale/src/lib.rs" IcoEvent::SaleEnded(current_transaction_id) ``` Program Metadata and State [#program-metadata-and-state] Metadata interface description: ```rust title="crowdsale/io/src/lib.rs" pub struct CrowdsaleMetadata; impl Metadata for CrowdsaleMetadata { type Init = In; type Handle = InOut; type Others = (); type Reply = (); type Signal = (); type State = Out; } ``` To display the full program state information, the `state()` function is used: ```rust title="crowdsale/src/lib.rs" #[no_mangle] extern fn state() { let staking = unsafe { ICO_CONTRACT .take() .expect("Unexpected error in taking state") }; msg::reply::(staking.into(), 0) .expect("Failed to encode or reply with `State` from `state()`"); } ``` To display only specific values from the state, write a separate crate. In this crate, specify functions that will return the desired values from the `State` state. For example, see [gear-foundation/dapps/crowdsale/state](https://github.com/gear-foundation/dapps/tree/master/contracts/crowdsale/state): ```rust title="crowdsale/state/src/lib.rs" #[gmeta::metawasm] pub mod metafns { pub type State = crowdsale_io::State; pub fn current_price(state: State) -> u128 { state.get_current_price() } pub fn tokens_left(state: State) -> u128 { state.get_balance() } pub fn balance_of(state: State, address: ActorId) -> u128 { state.balance_of(&address) } } ``` Conclusion [#conclusion] The source code of this example of an ICO program and the example of an implementation of its testing is available on [GitHub](https://github.com/gear-foundation/dapps/tree/a357635b61e27c52f46908885fecb048dc8424e5/contracts/crowdsale). For more details about testing programs written on Vara, refer to the [Program Testing](/docs/developing/build/testing) article. # Dutch Auction Introduction [#introduction] A Dutch auction is one of several types of auctions for buying or selling goods. Most commonly, it refers to an auction in which the auctioneer begins with a high asking price in the case of selling and gradually lowers it until some participant accepts the price or it reaches a predetermined reserve price. A Dutch auction has also been referred to as a clock auction or open-outcry descending-price auction. This type of auction demonstrates the advantage of speed since a sale never requires more than one bid. The auction uses [Vara non-fungible tokens (VFT)](/docs/vara-network/examples/Standards/vnft) as tradable goods. This article explains the programming interface, data structure, basic functions, and their respective purposes. The program can be used as-is or modified to suit specific scenarios. Anyone can easily create an application and run it on the Gear Network. The source code is available on [GitHub](https://github.com/gear-foundation/dapps/tree/a357635b61e27c52f46908885fecb048dc8424e5/contracts/dutch-auction). Program Description [#program-description] Actions [#actions] ```rust title="dutch-auction/io/src/auction.rs" pub enum Action { /// Creates auction Create(CreateConfig), /// Buy current NFT Buy, /// Stop Auction ForceStop, /// Reward gas to NFT seller Reward, } ``` * `Buy` is an action to buy a GNFT token at the current price. * `Create(CreateConfig)` is an action to create a new auction if the previous one is over or if it's the first auction in this program. * `ForceStop` is an action to forcefully stop an auction if the program owner prefers to finish it ahead of time. > Note how Dutch Auction is composed; it allows users to reuse its functionality repeatedly. Structures in Actions: [#structures-in-actions] ```rust title="dutch-auction/io/src/auction.rs" pub struct CreateConfig { pub nft_contract_actor_id: ActorId, pub token_id: U256, pub starting_price: u128, pub discount_rate: u128, pub duration: Duration, } ``` **To create a new auction, these fields are required:** * `nft_contract_actor_id` is the program (smart contract) address where the auctioneer's NFT has been minted. * `token_id` is the ID of the NFT within its contract. * `starting_price` is the initial price at which the auction begins and subsequently decreases. * `discount_rate` is the rate at which the price decreases per millisecond over time. * `duration` sets the duration of the auction. ```rust title="dutch-auction/io/src/auction.rs" pub struct Duration { pub hours: u64, pub minutes: u64, pub seconds: u64, } ``` * `hours` is the number of hours in the period. * `minutes` is the number of minutes in the period. * `seconds` is the number of seconds in the period. Events [#events] ```rust title="dutch-auction/io/src/auction.rs" pub enum Event { AuctionStarted { token_owner: ActorId, price: u128, token_id: U256, }, Bought { price: u128, }, AuctionStopped { token_owner: ActorId, token_id: U256, }, Rewarded { price: u128, }, } ``` * `AuctionStarted` is an event that occurs when the `Create(CreateConfig)` action is successfully executed. * `AuctionStopped` is an event that occurs when the program owner forcibly ends the auction. Consistency of Program States [#consistency-of-program-states] The `Dutch auction` program interacts with the `non-fungible token` contract. Each transaction that modifies the states of the Dutch Auction and the non-fungible token is stored in the state until it is completed. A user can finalize a pending transaction by sending a message that matches the previous one while specifying the transaction ID. The idempotency of the non-fungible token contract allows for restarting a transaction without causing duplicate changes, ensuring the state consistency of these two programs. Program Metadata and State [#program-metadata-and-state] Metadata interface description: ```rust title="dutch-auction/io/src/io.rs" pub struct AuctionMetadata; impl Metadata for AuctionMetadata { type Init = (); type Handle = InOut>; type Others = (); type Reply = (); type Signal = (); type State = Out; } ``` To display the full program state information, the `state()` function is used: ```rust title="dutch-auction/src/lib.rs" #[no_mangle] extern fn state() { let contract = unsafe { AUCTION.take().expect("Unexpected error in taking state") }; msg::reply::(contract.into(), 0) .expect("Failed to encode or reply with `AuctionInfo` from `state()`"); } ``` To display only specific values from the state, write a separate crate. In this crate, specify functions that will return the desired values from the `AuctionInfo` state. For example, see [dutch-auction/state](https://github.com/gear-foundation/dapps/tree/a357635b61e27c52f46908885fecb048dc8424e5/contracts/dutch-auction/state): ```rust title="dutch-auction/state/src/lib.rs" #[gmeta::metawasm] pub mod metafns { pub type State = AuctionInfo; pub fn info(mut state: State) -> AuctionInfo { if matches!(state.status, Status::IsRunning) && exec::block_timestamp() >= state.expires_at { state.status = Status::Expired; } state } } ``` Source Code [#source-code] The source code for this example of a Dutch Auction program and an implementation of its testing is available on [GitHub](https://github.com/gear-foundation/dapps/tree/a357635b61e27c52f46908885fecb048dc8424e5/contracts/dutch-auction). See also an example of the program testing implementation based on `gtest`: [gear-foundation/dapps/dutch-auction/tests](https://github.com/gear-foundation/dapps/tree/a357635b61e27c52f46908885fecb048dc8424e5/contracts/dutch-auction/tests). For more details about testing programs written on Vara, refer to the [Program Testing](/docs/developing/build/testing) article. # Escrow escrow An escrow is a special wallet to which certain assets (e.g., money or stocks) are deposited and stored until specific conditions are met. In terms of programs, an escrow is a wallet stored on a blockchain that, like a traditional escrow, can receive assets (e.g., cryptocurrency or fungible tokens, such as [Vara fungible tokens - VFT](/docs/vara-network/examples/Standards/vft) in this example) from one user and, when certain conditions are met, send them to another. * Program source code is avalible on [Github](https://github.com/gear-foundation/dapps/tree/a357635b61e27c52f46908885fecb048dc8424e5/contracts/escrow) * dApp UI [Github](https://github.com/gear-foundation/dapps/tree/3f59be8c871dd1eceea5f5768a7dc275c3a29f0a/frontend/apps/escrow) How to run [#how-to-run] This video demonstrates how to configure and run Escrow application on your own and explains the user interaction workflow: **[https://youtu.be/CD8j4epEY4E](https://youtu.be/CD8j4epEY4E)** ⚒️ Build program [#️-build-program] * Get the source code of the [Escrow program](https://github.com/gear-foundation/dapps/tree/a357635b61e27c52f46908885fecb048dc8424e5/contracts/escrow) * Build program as described in [program/README.md](https://github.com/gear-foundation/dapps/tree/a357635b61e27c52f46908885fecb048dc8424e5/contracts/escrow/README.md). 🏗️ Upload Сode [#️-upload-сode] This application requires code to create an escrow wallet 1. You can uploade code using [idea.gear-tech.io](https://idea.gear-tech.io/). 2. In the network selector choose `Vara Network Mainnet` or `Development` (in this case, you should have a local node running) 3. Upload code `escrow.opt.wasm` from `/target/wasm32-unknown-unknown/release/` 🖥️ Run UI [#️-run-ui] 1. Install packages as described in [frontend/README.md](https://github.com/gear-foundation/dapps/tree/3f59be8c871dd1eceea5f5768a7dc275c3a29f0a/frontend/apps/escrow/README.md) 2. Configure .evn file. Specify network address and code ID like in the example below: ```sh REACT_APP_NODE_ADDRESS=wss://rpc.vara.network REACT_APP_CODE_ADDRESS=0xa31fa55b98adf40b0ad18ccda10a3deb8bbb59655fa10971c8046c7f6f959bb9 ``` 3. Run app ```sh yarn start ``` Introduction [#introduction] This article explains at a superficial level the purpose and logic of this program. The source code of the program example is available on [GitHub](https://github.com/gear-foundation/dapps/tree/master/contracts/escrow). Logic [#logic] * Any user can create an escrow wallet as a buyer or seller. * A buyer can make a deposit or confirm a deal and close the wallet. * A seller can refund tokens from a paid wallet to a buyer. * Both the buyer and the seller can cancel a deal and close an unpaid wallet. An escrow wallet contains information about a `buyer`, a `seller`, the wallet `state`, and the `amount` of tokens that this wallet can store: ```rust title="escrow/io/src/lib.rs" pub struct Wallet { /// A buyer. pub buyer: ActorId, /// A seller. pub seller: ActorId, /// A wallet state. pub state: WalletState, /// An amount of tokens that a wallet can have. **Not** a current amount on a wallet balance! pub amount: u128, } ``` `WalletState` is an enum that stores a current state of a wallet: ```rust title="escrow/io/src/lib.rs" pub enum WalletState { AwaitingDeposit, AwaitingConfirmation, Closed, } ``` Interface [#interface] Initialization [#initialization] ```rust title="escrow/io/src/lib.rs" /// Initializes an escrow program. pub struct InitEscrow { /// Address of a fungible token program. pub ft_program_id: ActorId, } ``` Actions [#actions] ```rust title="escrow/io/src/lib.rs" /// An enum to send the program info about what it should do. /// /// After a successful processing of this enum, the program replies with [`EscrowEvent`]. #[derive(Clone, Decode, Encode, TypeInfo)] #[codec(crate = gstd::codec)] #[scale_info(crate = gstd::scale_info)] pub enum EscrowAction { /// Creates one escrow wallet and replies with its ID. /// /// # Requirements /// * [`msg::source()`](gstd::msg::source) must be `buyer` or `seller` for this wallet. /// * `buyer` or `seller` mustn't have the zero address. /// /// On success, returns [`EscrowEvent::Created`]. Create { /// A buyer. buyer: ActorId, /// A seller. seller: ActorId, /// An amount of tokens. amount: u128, }, /// Makes a deposit from a buyer to an escrow wallet /// and changes wallet's [`WalletState`] to [`AwaitingConfirmation`](WalletState::AwaitingConfirmation). /// /// Transfers tokens to an escrow wallet until a deal is confirmed (by [`EscrowAction::Confirm`]) or cancelled ([`EscrowAction::Cancel`]). /// /// # Requirements /// * [`msg::source()`](gstd::msg::source) must be a buyer for this wallet. /// * Wallet mustn't be paid or closed (that is, wallet's [`WalletState`] must be [`AwaitingDeposit`](WalletState::AwaitingDeposit)). /// /// On success, returns [`EscrowEvent::Deposited`]. Deposit( /// A wallet ID. WalletId, ), /// Confirms a deal by transferring tokens from an escrow wallet /// to a seller and changing wallet's [`WalletState`] to [`Closed`](WalletState::Closed). /// /// Transfers tokens from an escrow wallet to a seller for this wallet. /// /// # Requirements /// * [`msg::source()`](gstd::msg::source) must be a buyer for this wallet. /// * Wallet must be paid and unclosed (that is, wallet's [`WalletState`] must be [`AwaitingDeposit`](WalletState::AwaitingConfirmation)). /// /// On success, returns [`EscrowEvent::Confirmed`]. Confirm( /// A wallet ID. WalletId, ), /// Refunds tokens from an escrow wallet to a buyer /// and changes wallet's [`WalletState`] back to [`AwaitingDeposit`](WalletState::AwaitingDeposit) /// (that is, a wallet can be reused). /// /// Refunds tokens from an escrow wallet to a buyer for this wallet. /// /// # Requirements /// * [`msg::source()`](gstd::msg::source) must be a seller for this wallet. /// * Wallet must be paid and unclosed (that is, wallet's [`WalletState`] must be [`AwaitingDeposit`](WalletState::AwaitingConfirmation)). /// /// On success, returns [`EscrowEvent::Refunded`]. Refund( /// A wallet ID. WalletId, ), /// Cancels a deal and closes an escrow wallet by changing its [`WalletState`] to [`Closed`](WalletState::Closed). /// /// # Requirements /// * [`msg::source()`](gstd::msg::source) must be a buyer or seller for this wallet. /// * Wallet mustn't be paid or closed (that is, wallet's [`WalletState`] must be [`AwaitingDeposit`](WalletState::AwaitingDeposit)). /// /// On success, returns [`EscrowEvent::Cancelled`]. Cancel( /// A wallet ID. WalletId, ), /// Continues the transaction if it fails due to lack of gas /// or due to an error in the token contract. /// /// # Requirements: /// * `transaction_id` should exists in `transactions` table; /// /// When transaction already processed replies with [`EscrowEvent::TransactionProcessed`]. Continue( /// Identifier of suspended transaction. u64, ), } ``` Consistency of program states [#consistency-of-program-states] The `Escrow program` interacts with the `fungible token contract`. Each transaction that modifies the states of the Escrow and the fungible token is stored in the state until it is completed. A user can finalize a pending transaction by sending a Continue message, indicating the transaction ID. The idempotency of the fungible token contract allows for the restarting of a transaction without causing duplicate changes, ensuring the state consistency of these two programs. Program metadata and state [#program-metadata-and-state] Metadata interface description: ```rust title="escrow/io/src/lib.rs" pub struct EscrowMetadata; impl Metadata for EscrowMetadata { type Init = In; type Handle = InOut; type Others = (); type Reply = (); type Signal = (); type State = Out; } ``` To display the full program state information, the `state()` function is used: ```rust title="escrow/src/lib.rs" #[no_mangle] extern fn state() { let escrow = unsafe { ESCROW.take().expect("Uninitialized Escrow state") }; msg::reply::(escrow.into(), 0).expect("Failed to share state"); } ``` To display only necessary certain values from the state, you need to write a separate crate. In this crate, specify functions that will return the desired values from the `Escrow` state. For example - [gear-foundation/dapps-escrow/state](https://github.com/gear-foundation/dapps/tree/master/contracts/escrow/state): ```rust title="escrow/state/src/lib.rs" #[gmeta::metawasm] pub mod metafns { pub type State = EscrowState; pub fn info(state: State, wallet_id: U256) -> Wallet { let (_, wallet) = *state .wallets .iter() .find(|(id, _)| id == &wallet_id) .unwrap_or_else(|| panic!("Wallet with the {wallet_id} ID doesn't exist")); wallet } pub fn created_wallets(state: State) -> Vec<(WalletId, Wallet)> { state .wallets .iter() .map(|(wallet_id, wallet)| (*wallet_id, *wallet)) .collect() } } ``` # Game of Chance img alt Game of chance is a simple game program with the lottery logic. * Program source code available on [Github](https://github.com/gear-foundation/dapps/tree/a357635b61e27c52f46908885fecb048dc8424e5/contracts/game-of-chance) * dApp UI [Github](https://github.com/gear-foundation/dapps/tree/3f59be8c871dd1eceea5f5768a7dc275c3a29f0a/frontend/apps/game-of-chance) How to run [#how-to-run] ⚒️ Build program [#️-build-program] * Get the source code of [GOC contract](https://github.com/gear-foundation/dapps/tree/a357635b61e27c52f46908885fecb048dc8424e5/contracts/game-of-chance) * Build programs as described in [program/README.md](https://github.com/gear-foundation/dapps/tree/a357635b61e27c52f46908885fecb048dc8424e5/contracts/game-of-chance/README.md). 🏗️ Upload program [#️-upload-program] 1. You can deploy a program using [idea.gear-tech.io](https://idea.gear-tech.io/). 2. In the network selector choose `Vara Network Mainnet` or `Development` (in this case, you should have a local node running) 3. Upload program `game_of_chance.opt.wasm` from `/target/wasm32-unknown-unknown/release/` 4. Upload metadata file `meta.txt` 5. Specify `init payload` and calculate gas! 🖥️ Run UI [#️-run-ui] 1. Install packages as described in [frontend/README.md](https://github.com/gear-foundation/dapps/tree/3f59be8c871dd1eceea5f5768a7dc275c3a29f0a/frontend/apps/game-of-chance/README.md) 2. Configure `.env` file. Specify network address and program ID like in the example below: ```sh REACT_APP_NODE_ADDRESS=wss://rpc.vara.network REACT_APP_CONTRACT_ADDRESS=0x45f48855184656f4fa7779e5ec0e3e54be8069a61cf62815e114d04d1b4916b4 ``` 3. Run app ```sh yarn start ``` Program logic [#program-logic] For a detailed technical description and implementation details, refer to: * [dApps Documentation Portal](https://dapps.gear.rs/game_of_chance) * [Source Code Section](/docs/vara-network/examples/archive/game-of-chance#source-code) During initialization, the game administrator is assigned. The administrator has the right to start a new game round and select a winner after the end of each one. Other actors can participate in a round if they have enough fungible tokens or the native asset they use to contribute to the prize funds. After the players' entry stage ends, the administrator should execute the action to select a winner. This program randomly selects the winner and then sends the prize funds to them. Interface [#interface] Initialization [#initialization] ```rust title="game-of-chance/io/src/lib.rs" /// Initializes the Game of chance program. /// /// # Requirements /// - `admin` mustn't be [`ActorId::zero()`]. #[derive( Debug, Default, Encode, Decode, PartialEq, Eq, PartialOrd, Ord, Clone, Copy, TypeInfo, Hash, )] #[codec(crate = gstd::codec)] #[scale_info(crate = gstd::scale_info)] pub struct InitGOC { /// [`ActorId`] of the game administrator that'll have the rights to /// [`Action::Start`] a game round and [`Action::PickWinner`]. pub admin: ActorId, } ``` Actions [#actions] ```rust title="game-of-chance/io/src/lib.rs" /// Sends a program info about what it should do. #[derive(Debug, Encode, Decode, PartialEq, Eq, PartialOrd, Ord, Clone, Copy, TypeInfo, Hash)] #[codec(crate = gstd::codec)] #[scale_info(crate = gstd::scale_info)] pub enum Action { /// Starts a game round and allows to participate in it. /// /// # Requirements /// - [`msg::source()`](gstd::msg::source) must be the game administrator. /// - The current game round must be over. /// - `ft_actor_id` mustn't be [`ActorId::zero()`]. /// /// On success, replies with [`Event::Started`]. Start { /// The duration (in milliseconds) of the players entry stage. /// /// After that, no one will be able to enter a game round and a winner /// should be picked. duration: u64, /// The price of a participation in a game round. participation_cost: u128, /// A currency (or FT contract [`ActorId`]) of a game round. /// /// Determines fungible tokens in which a prize fund and a participation /// cost will be collected. [`None`] means that the native value will be /// used instead of fungible tokens. fungible_token: Option, }, /// Randomly picks a winner from current game round participants (players) /// and sends a prize fund to it. /// /// The randomness of a winner pick depends on /// [`exec::block_timestamp()`](gstd::exec::block_timestamp). /// Not the best source of entropy, but, in theory, it's impossible to /// exactly predict a winner if the time of an execution of this action is /// unknown. /// /// If no one participated in the round, then a winner will be /// [`ActorId::zero()`]. /// /// # Requirements /// - [`msg::source()`](gstd::msg::source) must be the game administrator. /// - The players entry stage must be over. /// - A winner mustn't already be picked. /// /// On success, replies with [`Event::Winner`]. PickWinner, /// Pays a participation cost and adds [`msg::source()`] to the current game /// round participants (players). /// /// A participation cost and its currency can be queried from the contract /// state. /// /// # Requirements /// - The players entry stage mustn't be over. /// - [`msg::source()`] mustn't already participate. /// - [`msg::source()`] must have enough currency to pay a participation /// cost. /// - If the current game round currency is the native value /// (`fungible_token` is [`None`]), [`msg::source()`] must send this action /// with the amount of the value exactly equal to a participation cost. /// /// On success, replies with [`Event::PlayerAdded`]. /// /// [`msg::source()`]: gstd::msg::source Enter, } ``` Program metadata and state [#program-metadata-and-state] Metadata interface description: ```rust title="game-of-chance/io/src/lib.rs" pub struct ContractMetadata; impl Metadata for ContractMetadata { type Init = InOut>; type Handle = InOut>; type Reply = (); type Others = (); type Signal = (); type State = Out; } ``` To display the full program state information, the `state()` function is used: ```rust title="game-of-chance/src/lib.rs" #[no_mangle] extern fn state() { let contract = unsafe { CONTRACT.take().expect("Unexpected error in taking state") }; msg::reply::(contract.into(), 0) .expect("Failed to encode or reply with `IoNFT` from `state()`"); } ``` To display only necessary certain values from the state, you need to write a separate crate. In this crate, specify functions that will return the desired values from the `State` struct. Source code [#source-code] The source code of the Game of Chance program and an implementation of its testing are available on [GitHub](https://github.com/gear-foundation/dapps/tree/3f59be8c871dd1eceea5f5768a7dc275c3a29f0a/frontend/apps/game-of-chance). They can be used as is or modified to suit your own scenarios. For more details about testing programs written on Vara, refer to the [Program Testing](/docs/developing/build/testing) article. # VRNFT (ERC-4907) The **Vara Rentable Non-fungible Token (RNFT)** standard is the equivalent of Ethereum's **ERC-4907** on the Vara Network. Introduction [#introduction] This is an extension of the standard [Non-Fungible Token (NFT)](https://github.com/gear-foundation/dapps/tree/a357635b61e27c52f46908885fecb048dc8424e5/contracts/non-fungible-token). It introduces an additional role (user) that can be granted to addresses, along with a predefined expiration time for this role. The user role grants permission to "use" the NFT, but it does not allow the ability to transfer it or set other users. The source code of the smart contract example is available on [GitHub](https://github.com/gear-foundation/dapps/tree/a357635b61e27c52f46908885fecb048dc8424e5/contracts/rentable-nft). Motivation [#motivation] Some NFTs serve specific utilities. For instance, virtual land can be utilized to create scenes, and NFTs representing in-game assets can be used within the game. In some cases, the NFT's owner and user may not always be the same person. There might be an NFT owner who rents it out to a "user." The actions that a "user" can perform with an NFT differ from those of the "owner" (for example, "users" typically shouldn't have the ability to sell ownership of the NFT). In such situations, it is logical to establish distinct roles that determine whether an address represents an "owner" or a "user," and to manage permissions accordingly. Some projects already implement this design concept under various names, such as "operator" or "controller." However, as it becomes more widespread, there is a need for a standardized approach to encourage collaboration among all applications. Moreover, applications of this model, such as renting, often require that user addresses have only temporary access to use the NFT. Typically, this means the owner needs to execute two on-chain transactions: one to designate a new address as the user role at the beginning of the duration and another to reclaim the user role at the end. This process is inefficient in terms of both labor and gas costs. Therefore, an "expires" function is introduced to facilitate the automatic termination of a usage term without the need for a second transaction. Details [#details] The default implementation of the NFT contract is provided in the gear library: [gear-lib/non\_fungible\_token](https://github.com/gear-foundation/dapps/tree/a357635b61e27c52f46908885fecb048dc8424e5/contracts/gear-lib-old/src/non_fungible_token). To use the default implementation it is necessary to include the packages into the *Cargo.toml* file: ```toml gear-lib = { git = "https://github.com/gear-foundation/dapps", tag = "0.3.3" } ``` Rentable NFT contains regular NFT and additional field `users_info`: ```rust title="rentable-nft/src/lib.rs" pub struct Contract { #[NFTStateField] pub token: NFTState, pub token_id: TokenId, pub owner: ActorId, pub transactions: HashMap, pub collection: Collection, pub config: Config, pub users_info: HashMap, } ``` In all other cases, everything also corresponds to the usual [non-fungible-token](https://github.com/gear-foundation/dapps/tree/a357635b61e27c52f46908885fecb048dc8424e5/contracts/non-fungible-token) contract, except additional specific actions: ```rust title="rentable-nft/io/src/lib.rs" pub enum NFTAction { // ... like a usual NFT contract SetUser { token_id: TokenId, address: ActorId, expires: u64, // unix timestamp transaction_id: u64, }, UserOf { token_id: TokenId, }, UserExpires { token_id: TokenId, }, } ``` And features specific events: ```rust title="rentable-nft/io/src/lib.rs" pub enum NFTEvent { // ... like a usual NFT contract UpdateUser { token_id: TokenId, address: ActorId, expires: u64, }, UserOf { address: ActorId, }, UserExpires { expires: u64, }, } ``` Conclusion [#conclusion] Gear provides a reusable [library](https://github.com/gear-foundation/dapps/tree/a357635b61e27c52f46908885fecb048dc8424e5/contracts/gear-lib) with core functionality for the gNFT-4907 protocol. By using object composition, the library can be utilized within a custom NFT contract implementation in order to minimize duplication of community available code. A source code of the Vara NFT smart contract example based on `gear-lib` is available on GitHub: [gear-foundation/dapps-non-fungible-token](https://github.com/gear-foundation/dapps/tree/a357635b61e27c52f46908885fecb048dc8424e5/contracts/rentable-nft). See also an example of the smart contract testing implementation based on `gtest`: [gear-foundation/dapps/non-fungible-token/tests](https://github.com/gear-foundation/dapps/tree/a357635b61e27c52f46908885fecb048dc8424e5/contracts/rentable-nft/tests). For more details about testing programs on Vara, refer to this article: [Program Testing](/docs/developing/build/testing). # Archive of Program Examples This folder contains examples of programs and use cases that may include outdated or no longer maintained code. These examples were created during the early stages of Gear Protocol's development. However, the use cases themselves remain relevant and can be implemented and deployed on the Vara network. The source code for these examples is still available on GitHub and can serve as a valuable resource for understanding how to develop using the low-level [`gstd`](/docs/developing/build/gstd/) library. Anyone can try compiling and deploying them to Vara Mainnet to see how they function. # NFT pixelboard Introduction [#introduction] This guide provides a **high-level overview** of the smart contract's purpose and logic. For deep technical details and implementation, please refer to: * [Detailed Technical Documentation](https://dapps.gear.rs/nft_pixelboard_io) * [Contract Source Code](/docs/vara-network/examples/archive/nft-pixelboard#source-code) The inspiration for this smart contract comes from **The Million Dollar Homepage** ([referencing its Wikipedia article](https://en.wikipedia.org/wiki/The_Million_Dollar_Homepage)). In brief, it's a website featuring a canvas of 1000 by 1000 pixels, where each pixel costs \$1. Anyone can purchase a block of pixels, like 10 by 10, or multiple blocks, paint the acquired pixels, and associate them with a web link. NFT Pixelboard follows the same idea, but with a twist: the bought pixel blocks are NFTs. They can be colored anytime, not just at the purchase and are resellable. Logic [#logic] `NFTPixelboardAction::Mint` is utilized for minting NFTs on a pixelboard. Users can sell their NFTs or paint them using `NFTPixelboardAction::ChangeSaleState` and `NFTPixelboardAction::Paint`, respectively. The `painting()` metafunction is used to get the entire painting of a pixelboard. Users can retrieve details about NFTs and pixels on a pixelboard through the `token_info()` and `pixel_info()` metafunctions, respectively. Interface [#interface] Initialization [#initialization] ```rust title="nft-pixelboard/io/src/lib.rs" /// Initializes the NFT pixelboard program. /// /// # Requirements /// * `owner` address mustn't be [`ActorId::zero()`]. /// * `block_side_length` must be more than 0. /// * `pixel_price` mustn't be more than [`MAX_PIXEL_PRICE`]. /// * A [width](`Resolution#structfield.width`) & /// [height](`Resolution#structfield.height`) (`resolution`) of a canvas must be /// more than 0. /// * Each side of `resolution` must be a multiple of `block_side_length`. /// * `painting` length must equal a pixel count in a canvas (which can be /// calculated by multiplying a [width](`Resolution#structfield.width`) & /// [height](`Resolution#structfield.height`) from `resolution`). /// * `commission_percentage` mustn't be more than 100. /// * `ft_program` address mustn't be [`ActorId::zero()`]. /// * `nft_program` address mustn't be [`ActorId::zero()`]. #[derive(Decode, Encode, TypeInfo, Clone)] #[codec(crate = gstd::codec)] #[scale_info(crate = gstd::scale_info)] pub struct InitNFTPixelboard { /// An address of a pixelboard owner to which minting fees and commissions /// on resales will be transferred. pub owner: ActorId, /// A block side length. /// /// To avoid a canvas clogging with one pixel NFTs, blocks are used instead /// of pixels to set NFT [`Rectangle`]s. This parameter is used to set a /// side length of these pixel blocks. If blocks aren't needed, then this /// parameter can be set to 1, so the block side length will equal a pixel. pub block_side_length: BlockSideLength, /// The price of a free pixel. It'll be used to calculate a minting price. pub pixel_price: u128, /// A canvas (pixelboard) [width](`Resolution#structfield.width`) & /// [height](`Resolution#structfield.height`). pub resolution: Resolution, /// A commission percentage that'll be included in each NFT resale. pub commission_percentage: u8, /// A painting that'll be displayed on the free territory of a pixelboard. pub painting: Vec, /// A FT program address. pub ft_program: ActorId, /// An NFT program address. pub nft_program: ActorId, } ``` Actions [#actions] ```rust title="nft-pixelboard/io/src/lib.rs" // Sends a program info about what it should do. #[derive(Decode, Encode, TypeInfo, Clone, PartialEq, Eq)] #[codec(crate = gstd::codec)] #[scale_info(crate = gstd::scale_info)] pub enum NFTPixelboardAction { /// Mints one NFT on a pixelboard with given `token_metadata` & `painting`. /// /// Transfers a minted NFT to [`msg::source()`]. /// /// # Requirements /// * `rectangle` coordinates mustn't be out of a canvas. /// * `rectangle` coordinates mustn't be mixed up or belong to wrong /// corners. /// * `rectangle` coordinates must observe a block layout. In other words, /// each `rectangle` coordinate must be a multiple of a block side length in /// the canvas. The block side length can be obtained by /// [`block_side_length()`](../nft_pixelboard_state/metafns/fn.block_side_length.html). /// * NFT `rectangle` mustn't collide with already minted one. /// * `painting` length must equal a pixel count in an NFT /// (which can be calculated by multiplying a [width](`Rectangle::width`) & /// [height](`Rectangle::height`) from `rectangle`). /// * [`msg::source()`] must have enough fungible tokens to buy all free /// pixels that `rectangle` will occupy. An enough number of tokens can be /// calculated by multiplying a `rectangle` area and the price of a free /// pixel. The area can be calculated by multiplying a /// [width](`Rectangle::width`) & [height](`Rectangle::height`) from /// `rectangle`. The price of a free pixel can be obtained by /// [`pixel_price()`](../nft_pixelboard_state/metafns/fn.pixel_price.html). /// /// On success, returns [`NFTPixelboardEvent::Minted`]. /// /// [`msg::source()`]: gstd::msg::source Mint { rectangle: Rectangle, token_metadata: TokenMetadata, /// A painting that'll be displayed in a place of an NFT on a pixelboard /// after a successful minting. painting: Vec, }, /// Buys an NFT minted on a pixelboard. /// /// Transfers a purchased NFT from a pixelboard program to /// [`msg::source()`]. /// /// **Note:** If [`msg::source()`] has enough fungible tokens to pay a /// resale commission but not the entire NFT, then the commission will still /// be withdrawn from its account. /// /// # Requirements /// * An NFT must be minted on a pixelboard. /// * An NFT must be for sale. This can be found out by /// [`token_info()`]. See also the documentation of /// [`TokenInfo#structfield.pixel_price`]. /// * [`msg::source()`] must have enough fungible tokens to buy all pixels /// that an NFT occupies. This can be found out by /// [`token_info()`]. See also the documentation of /// [`TokenInfo#structfield.pixel_price`]. /// /// On success, returns [`NFTPixelboardEvent::Bought`]. /// /// [`msg::source()`]: gstd::msg::source /// [`token_info()`]: ../nft_pixelboard_state/metafns/fn.token_info.html Buy(TokenId), /// Changes a sale state of an NFT minted on a pixelboard. /// /// There are 3 options of a sale state change: /// * Putting up for sale\ /// If an NFT is **not** for sale, then assigning `pixel_price` to [`Some`] /// price will transfer it to a pixelboard program & put it up for sale. /// * Updating a pixel price\ /// If an NFT is for sale, then assigning `pixel_price` to [`Some`] price /// will update its pixel price. /// * Removing from sale\ /// Assigning the `pixel_price` to [`None`] will transfer an NFT back to its /// owner & remove an NFT from sale. /// /// **Note:** A commission is included in each NFT resale, so a seller /// will receive not all fungible tokens but tokens with a commission /// deduction. A commission percentage can be obtained by /// [`commission_percentage()`](../nft_pixelboard_state/metafns/fn.commission_percentage.html). /// /// # Requirements /// * An NFT must be minted on a pixelboard. /// * [`msg::source()`](gstd::msg::source) must be the owner of an NFT. /// * `pixel_price` mustn't be more than [`MAX_PIXEL_PRICE`]. /// /// On success, returns [`NFTPixelboardEvent::SaleStateChanged`]. ChangeSaleState { token_id: TokenId, /// A price of each pixel that an NFT occupies. To calculate a price of /// the entire NFT, see the documentation of /// [`TokenInfo#structfield.pixel_price`]. pixel_price: Option, }, /// Paints with `painting` an NFT minted on a pixelboard. /// /// # Requirements /// * An NFT must be minted on a pixelboard. /// * [`msg::source()`](gstd::msg::source) must be the owner of an NFT. /// * `painting` length must equal a pixel count in an NFT. The count can be /// calculated by multiplying a [width](`Rectangle::width`) & /// [height](`Rectangle::height`) from a rectangle of the NFT. The NFT /// rectangle can be obtained by [`token_info()`](../nft_pixelboard_state/metafns/fn.token_info.html). /// /// On success, returns [`NFTPixelboardEvent::Painted`]. Paint { token_id: TokenId, painting: Vec, }, } ``` Source code [#source-code] The source code of the NFT pixelboard smart contract and an implementation of its testing is available on [GitHub](https://github.com/gear-foundation/dapps/tree/a357635b61e27c52f46908885fecb048dc8424e5/contracts/nft-pixelboard). They can be used as is or modified to suit your own scenarios. For more details about testing smart contracts written on Gear, refer to the [Program Testing](/docs/developing/build/testing) article. # On-chain NFT Introduction [#introduction] This example of an NFT smart contract shows a method where token assets are stored on the blockchain itself. To learn about a common implementation of gNFT smart contracts, refer to [vNFT-721](/docs/vara-network/examples/Standards/vnft). When a user wants to transfer ownership of a specific token ID to another user, confirming ownership and assigning the token to a new owner is straightforward. Usually, NFT images (or other essential resources) are stored elsewhere, like on IPFS, with only the metadata stored in the contract. The metadata includes a name, an ID, and links to external resources where the images are stored. However, this introduces another method. At times, you can directly store NFTs on the blockchain without relying on external storage. This approach ensures that you don't lose your NFT in case there are issues with external storage. This article explains the programming interface, data structure, basic functions, and their purposes. You can use it as is or adapt it for your specific situations. The source code is accessible on [GitHub](https://github.com/gear-foundation/dapps/tree/a357635b61e27c52f46908885fecb048dc8424e5/contracts/on-chain-nft). Approach [#approach] To implement this approach successfully, you need several things. First, when starting a collection, provide all possible images for all layers in the collection. Second, when minting with small metadata, give a combination of layers used for a specific NFT. This method may seem expensive initially, but it is relatively inexpensive during the minting process. Developing on-chain non-fungible-token contract [#developing-on-chain-non-fungible-token-contract] The functions that must be supported by each non-fungible-token contract: * *transfer(to, token\_id)* - is a function that allows you to transfer a token with the *token\_id* number to the *to* account; * *approve(approved\_account, token\_id)* - is a function that allows you to give the right to dispose of the token to the specified *approved\_account*. This functionality can be useful on marketplaces or auctions as when the owner wants to sell his token, they can put it on a marketplace/auction, so the contract will be able to send this token to the new owner at some point; * *mint(to, token\_id, metadata)* is a function that creates a new token. Metadata can include any information about the token: it can be a link to a specific resource, a description of the token, etc; * *burn(from, token\_id)* is a function that removes the token with the mentioned *token\_id* from the contract. The default implementation of the NFT contract is provided in the gear library: [gear-lib/non\_fungible\_token](https://github.com/gear-foundation/dapps/tree/a357635b61e27c52f46908885fecb048dc8424e5/contracts/gear-lib/src/tokens). To use the default implementation you should include the packages into your *Cargo.toml* file: ```toml gear-lib = { git = "https://github.com/gear-foundation/dapps", tag = "0.3.3" } ``` First, we start by modifying the state and the init message: ```rust title="on-chain-nft/src/lib.rs" #[derive(Debug, Default, NFTStateKeeper, NFTCore, NFTMetaState)] pub struct OnChainNFT { #[NFTStateField] pub token: NFTState, pub token_id: TokenId, pub owner: ActorId, pub base_image: String, pub layers: HashMap>, pub nfts: HashMap>, pub nfts_existence: HashSet, } ``` ```rust title="on-chain-nft/io/src/lib.rs" /// Initializes on-chain NFT /// Requirements: /// * all fields except `royalties` should be specified #[derive(Debug, Encode, Decode, TypeInfo)] #[codec(crate = gstd::codec)] #[scale_info(crate = gstd::scale_info)] pub struct InitOnChainNFT { /// NFT name pub name: String, /// NFT symbol pub symbol: String, /// NFT base_uri (not applicable in on-chain) pub base_uri: String, /// Base Image is base64encoded svg. /// Provides a base layer for all future nfts. pub base_image: String, /// Layers map - mapping of layerid the list of layer items. /// Each layer item is a base64encoded svg. pub layers: Vec<(LayerId, Vec)>, /// Royalties for NFT pub royalties: Option, } ``` Next, rewrite the following functions: `mint`, `burn` and `token_uri`. The `mint` and `burn` functions will behave as one woud expect them to with the addition of slight state modification (e.g. checking against the state, adding/removing). `token_uri` will return an NFT's metadata as well as all the layer content provided for a specified NFT: ```rust title="on-chain-nft/io/src/lib.rs" #[derive(Debug, Encode, Decode, TypeInfo)] #[codec(crate = gstd::codec)] #[scale_info(crate = gstd::scale_info)] pub enum OnChainNFTQuery { /// Returns an NFT for a specified `token_id`. /// /// Requirements: /// * `token_id` MUST exist /// /// Arguments: /// * `token_id` - is the id of the NFT /// /// On success, returns TokenURI struct. TokenURI { token_id: TokenId }, /// Base NFT query. Derived from gear-lib. Base(NFTQuery), } #[derive(Debug, Encode, Decode, TypeInfo)] #[codec(crate = gstd::codec)] #[scale_info(crate = gstd::scale_info)] pub enum OnChainNFTAction { /// Mints an NFT consisting of layers provided in the `description` parameter. /// /// Requirements: /// * `description` MUST contain layers and layers' items that EXIST /// /// Arguments: /// * `token_metadata` - is a default token metadata from gear-lib. /// * `description` - is the vector of layer's item id, where /// the index i is the layer id. /// /// On success, returns NFTEvent::Mint from gear-lib. Mint { /// Metadata token_metadata: TokenMetadata, /// Layers description of an NFT description: Vec, }, /// Burns an NFT. /// /// Requirements: /// * `token_id` MUST exist /// Arguments: /// /// * `token_id` - is the id of the burnt token /// /// On success, returns NFTEvent::Burn from gear-lib. Burn { /// Token id to burn. token_id: TokenId, }, /// Transfers an NFT. /// /// Requirements: /// * `token_id` MUST exist /// * `to` MUST be a non-zero addresss /// /// Arguments: /// * `token_id` - is the id of the transferred token /// /// On success, returns NFTEvent::Transfer from gear-lib. Transfer { /// A recipient address. to: ActorId, /// Token id to transfer. token_id: TokenId, }, /// Approves an account to perform operation upon the specifiefd NFT. /// /// Requirements: /// * `token_id` MUST exist /// * `to` MUST be a non-zero addresss /// /// Arguments: /// * `token_id` - is the id of the transferred token /// /// On success, returns NFTEvent::Approval from gear-lib. Approve { /// An account being approved. to: ActorId, /// Token id approved for the account. token_id: TokenId, }, /// Transfers payouts from an NFT to an account. /// /// Requirements: /// * `token_id` MUST exist /// * `to` MUST be a non-zero addresss /// * `amount` MUST be a non-zero number /// /// Arguments: /// * `token_id` - is the id of the transferred token /// /// On success, returns NFTEvent::Approval from gear-lib. TransferPayout { /// Payout recipient to: ActorId, /// Token id to get the payout from. token_id: TokenId, /// Payout amount. amount: u128, }, } #[derive(Debug, Encode, Decode, TypeInfo)] #[codec(crate = gstd::codec)] #[scale_info(crate = gstd::scale_info)] pub struct TokenURI { /// Token metadata derived from gear-lib pub metadata: TokenMetadata, /// List of base64encoded svgs representing different layers of an NFT. pub content: Vec, } ``` The `TokenMetadata` is also defined in the Gear NFT library: ```rust title="gear-lib-old/src/non_fungible_token/token.rs" pub struct TokenMetadata { // ex. "CryptoKitty #100" pub name: String, // free-form description pub description: String, // URL to associated media, preferably to decentralized, content-addressed storage pub media: String, // URL to an off-chain JSON file with more info. pub reference: String, } ``` Define a trait for the new functions that will extend the default `NFTCore` trait: ```rust title="on-chain-nft/src/lib.rs" pub trait OnChainNFTCore: NFTCore { fn mint(&mut self, description: Vec, metadata: TokenMetadata) -> NFTTransfer; fn burn(&mut self, token_id: TokenId) -> NFTTransfer; fn token_uri(&mut self, token_id: TokenId) -> Option>; } ``` and write the implementation of that trait: ```rust title="on-chain-nft/src/lib.rs" impl OnChainNFTCore for OnChainNFT { /// Mint an NFT on chain. /// `description` - is the vector of ids , /// where each index represents a layer id, and element represents a layer item id. /// `metadata` - is the default metadata provided by gear-lib. fn mint(&mut self, description: Vec, metadata: TokenMetadata) -> NFTTransfer { // precheck if the layers actually exist for (layer_id, layer_item_id) in description.iter().enumerate() { if layer_id > self.layers.len() { panic!("No such layer"); } if *layer_item_id > self .layers .get(&(layer_id as u128)) .expect("No such layer") .len() as u128 { panic!("No such item"); } } // also check if description has all layers provided if description.len() != self.layers.len() { panic!("The number of layers must be equal to the number of layers in the contract"); } // precheck if there is already an nft with such description let key = description .clone() .into_iter() .map(|i| i.to_string()) .collect::(); if self.nfts_existence.contains(&key) { panic!("Such nft already exists"); } self.nfts_existence.insert(key); let transfer = NFTCore::mint(self, &msg::source(), self.token_id, Some(metadata)); self.nfts.insert(self.token_id, description); self.token_id = self.token_id.saturating_add(U256::one()); transfer } /// Burns an NFT. /// `token_id` - is the id of a token. MUST exist. fn burn(&mut self, token_id: TokenId) -> NFTTransfer { let transfer = NFTCore::burn(self, token_id); let key = self .nfts .get(&token_id) .expect("No such token") .iter() .map(|i| i.to_string()) .collect::(); self.nfts.remove(&token_id); self.nfts_existence.remove(&key); transfer } /// Returns token information - metadata and all the content of all the layers for the NFT. /// `token_id` - is the id of a token. MUST exist. fn token_uri(&mut self, token_id: TokenId) -> Option> { let mut metadata = TokenMetadata::default(); if let Some(Some(mtd)) = self.token.token_metadata_by_id.get(&token_id) { metadata = mtd.clone(); } // construct media let mut content: Vec = Vec::new(); // check if exists let nft = self.nfts.get(&token_id).expect("No such nft"); for (layer_id, layer_item_id) in nft.iter().enumerate() { let layer_content = self .layers .get(&(layer_id as u128)) .expect("No such layer") .iter() .nth(*layer_item_id as usize) .expect("No such layer item"); content.push(layer_content.clone()); } Some(TokenURI { metadata, content }.encode()) } } ``` Accordingly, it is necessary to make changes to the `handle` and `meta_state` functions: ```rust title="on-chain-nft/src/lib.rs" #[no_mangle] extern fn handle() { let action: OnChainNFTAction = msg::load().expect("Could not load OnChainNFTAction"); let nft = unsafe { CONTRACT.get_or_insert(Default::default()) }; match action { OnChainNFTAction::Mint { description, token_metadata, } => msg::reply( OnChainNFTEvent::Transfer(OnChainNFTCore::mint(nft, description, token_metadata)), 0, ), OnChainNFTAction::Burn { token_id } => msg::reply( OnChainNFTEvent::Transfer(OnChainNFTCore::burn(nft, token_id)), 0, ), OnChainNFTAction::Transfer { to, token_id } => msg::reply( OnChainNFTEvent::Transfer(NFTCore::transfer(nft, &to, token_id)), 0, ), OnChainNFTAction::TransferPayout { to, token_id, amount, } => msg::reply( OnChainNFTEvent::TransferPayout(NFTCore::transfer_payout(nft, &to, token_id, amount)), 0, ), OnChainNFTAction::Approve { to, token_id } => msg::reply( OnChainNFTEvent::Approval(NFTCore::approve(nft, &to, token_id)), 0, ), } .expect("Error during replying with `OnChainNFTEvent`"); } ``` Programm metadata and state [#programm-metadata-and-state] Metadata interface description: ```rust title="on-chain-nft/io/src/lib.rs" pub struct ContractMetadata; impl Metadata for ContractMetadata { type Init = In; type Handle = InOut; type Reply = (); type Others = (); type Signal = (); type State = Out; } ``` To display the full contract state information, the `state()` function is used: ```rust title="on-chain-nft/src/lib.rs" #[no_mangle] extern fn state() { let contract = unsafe { CONTRACT.take().expect("Unexpected error in taking state") }; msg::reply::(contract.into(), 0) .expect("Failed to encode or reply with `State` from `state()`"); } ``` To display only necessary certain values from the state, you need to write a separate crate. In this crate, specify functions that will return the desired values from the `State` state. For example - [gear-foundation/dapps/on-chain-nft/state](https://github.com/gear-foundation/dapps/tree/master/contracts/on-chain-nft/state): ```rust title="on-chain-nft/state/src/lib.rs" #[gmeta::metawasm] pub mod metafns { pub type State = on_chain_nft_io::State; pub fn token_uri(state: State, token_id: TokenId) -> Option> { let metadata = state .token .token_metadata_by_id .iter() .find(|(id, _)| token_id.eq(id)) .and_then(|(_id, metadata)| metadata.clone()) .unwrap_or_default(); // construct media let mut content: Vec = Vec::new(); // check if exists if let Some((_id, nft)) = state.nfts.iter().find(|(id, _)| token_id.eq(id)) { for (i, layer_item_id) in nft.iter().enumerate() { if let Some((_id, layer_content)) = state.layers.iter().find(|(id, _)| (i as u128).eq(id)) { let s = layer_content .get(*layer_item_id as usize) .expect("No such layer item"); content.push(s.clone()); } } } Some(TokenURI { metadata, content }.encode()) } pub fn base(state: State, query: NFTQuery) -> Option> { let encoded = match query { NFTQuery::NFTInfo => NFTQueryReply::NFTInfo { name: state.token.name.clone(), symbol: state.token.symbol.clone(), base_uri: state.token.base_uri, }, NFTQuery::Token { token_id } => NFTQueryReply::Token { token: state.token.token(token_id), }, NFTQuery::TokensForOwner { owner } => NFTQueryReply::TokensForOwner { tokens: state.token.tokens_for_owner(&owner), }, NFTQuery::TotalSupply => NFTQueryReply::TotalSupply { total_supply: state.token.total_supply(), }, NFTQuery::SupplyForOwner { owner } => NFTQueryReply::SupplyForOwner { supply: state.token.supply_for_owner(&owner), }, NFTQuery::AllTokens => NFTQueryReply::AllTokens { tokens: state.token.all_tokens(), }, NFTQuery::ApprovedTokens { account } => NFTQueryReply::ApprovedTokens { tokens: state.token.approved_tokens(&account), }, } .encode(); Some(encoded) } } ``` Conclusion [#conclusion] Gear provides a reusable [library](https://github.com/gear-foundation/dapps/tree/a357635b61e27c52f46908885fecb048dc8424e5/contracts/gear-lib/src) with core functionality for the gNFT protocol. By using object composition, that library can be utilized within a custom NFT contract implementation in order to minimize duplication of community available code. A source code of the on-chain NFT example is available on GitHub: [on-chain-nft](https://github.com/gear-foundation/dapps/tree/a357635b61e27c52f46908885fecb048dc8424e5/contracts/on-chain-nft). See also an example of the smart contract testing implementation based on `gtest`: [on-chain-nft/tests](https://github.com/gear-foundation/dapps/tree/a357635b61e27c52f46908885fecb048dc8424e5/contracts/on-chain-nft/tests). For more details about testing smart contracts written on Gear, refer to this article: [Program Testing](/docs/developing/build/testing). # Rock Paper Scissors Introduction [#introduction] Rock Paper Scissors (also known by other orderings of the three items, with "rock" sometimes being called "stone", or as Rochambeau, roshambo, or ro-sham-bo) is a hand game originating from China, usually played between two people, in which each player simultaneously forms one of three shapes with an outstretched hand. These shapes are "rock" (a closed fist), "paper" (a flat hand), and "scissors" (a fist with the index finger and middle finger extended, forming a V). A simultaneous, zero-sum game, it has three possible outcomes: a draw, a win or a loss. A player who decides to play rock will beat another player who has chosen scissors ("rock crushes scissors" or "breaks scissors" or sometimes "blunts scissors"), but will lose to one who has played paper ("paper covers rock"); a play of paper will lose to a play of scissors ("scissors cuts paper"). If both players choose the same shape, the game is tied and is usually immediately replayed to break the tie. The game spread from China while developing different variants in signs over time. One popular five-weapon expansion is "rock paper scissors Spock lizard", invented by Sam Kass and Karen Bryla, which adds "Spock" and "lizard" to the standard three choices. "Spock" is signified with the Star Trek Vulcan salute, while "lizard" is shown by forming the hand into a sock-puppet-like mouth. Spock smashes scissors and vaporizes rock; he is poisoned by lizard and disproved by paper. Lizard poisons Spock and eats paper; it is crushed by rock and decapitated by scissors. This variant was mentioned in a 2005 article in The Times of London and was later the subject of an episode of the American sitcom The Big Bang Theory in 2008 (as rock-paper-scissors-lizard-Spock). Anyone can easily create their own decentralized game application and run it on the Gear Network. To do this, Gear made a "rock paper scissors Spock lizard" game version for multiple players, in which the winner can be determined in several rounds of tense struggle. The source code is available on [GitHub](https://github.com/gear-foundation/dapps/tree/a357635b61e27c52f46908885fecb048dc8424e5/contracts/rock-paper-scissors). This article explains the programming interface, data structure, basic functions and explains their purpose. It can be used as is or modified to suit your own scenarios. Logic [#logic] Configuration [#configuration] First of all someone(admin) should deploy a "Rock Paper Scissors Lizard spock" program with a set of game parameters. ```rust title="rock-paper-scissors/io/src/lib.rs" pub struct GameConfig { pub bet_size: u128, pub players_count_limit: u8, pub entry_timeout_ms: u64, pub move_timeout_ms: u64, pub reveal_timeout_ms: u64, } ``` Change Next Game Config [#change-next-game-config] Admin at all game stages can change next game configuration by using `ChangeNextGameConfig(GameConfig)` action. When the current game ends, this config will be applied. Stop The Game [#stop-the-game] Admin at all game stages can stop the game by using `StopGame` action. This action can be used, for example, to change the configuration of the game, or if the players have gone on strike and do not want to continue playing, or if the game has gone on for a long time. When the administrator stops the game, all funds are distributed among the players remaining in the game. If the game is in the registration stage, bets will be returned to the entire lobby. Registration [#registration] Then players can register for the game by paying a bet(of bet\_size) with sending `Register` action. The registration stage continues `entry_timeout_ms` milliseconds from the moment the program was deployed(or the end of the previous game). After that the game begins and the players can make a move. > If time of registration stage is over, but only 1 or 0 players has registered, stage will be extended by `entry_timeout_ms`. And the player won't be unregistered. Moves [#moves] During the move phase, players must choose one of five move options(Rock Paper Scissors Lizard Spock). To submit a player's choice, the service allowing this must enable the player to create or input a password, saving it locally. This password is crucial for safeguarding the user's move from other players keen on observing it in the blockchain. Once the password is either generated or entered, the service combines it with the numerical representation of the move (Rock - '0', Paper - '1', Scissors - '2', Lizard - '3', Spock - '4') to form a string, such as "2pass." Subsequently, the service hashes this string using a 256-bit blake2b, transforms it into binary, and transmits it to the blockchain via the `MakeMove(Vec)` function with the hash embedded. > The player is unable to alter their move. The moves stage persists until all players have completed their moves or the specified `move_timeout_ms` milliseconds have elapsed since registration ended or the round began. Following this, the reveal phase initiates, allowing players to display their moves for the program to ascertain the winner. > If the moves stage time elapses with no moves made, the stage extends by `move_timeout_ms`, enabling players to proceed with their moves. > > In a scenario where the moves stage time elapses, but only one player has made a move, that player is declared the winner and receives the full reward. Reveal [#reveal] Reveal is a necessary step in protecting the game from cheating. During this phase, players are required to confirm their moves. To achieve this, they need to reiterate the password (or allow the service to retrieve it from their storage) and restate the move (or let the service obtain it from their storage). The service then concatenates the number corresponding to the move (Rock - '0', Paper - '1', Scissors - '2', Lizard - '3', Spock - '4') with the password, creating a string-like `2pass`. Subsequently, the program transforms this string into a UTF-8-encoded byte array and transmits it to the blockchain through the `Reveal(Vec)` action. In this stage, the program verifies that the hash submitted during the move phase matches a hashed open string and records this move (the first character from the string) to determine the winners. Once all players have completed their moves or the allotted time has elapsed, the program identifies the winning move to determine the players advancing to the next round. Participants selecting the winning move progress to the subsequent round. > Certain scenarios exist where the program cannot determine the winning move, such as in the case of a Stone move, a Paper move, and a Scissors move. In such situations, where each move counters the others, all players move on to the next round. After determining the players advancing to the next round, the move stage restarts, and advanced players make their moves. If only one player remains, the game concludes, and the entire reward is awarded to that player. Upon the conclusion of the game, a new game promptly commences with the new configuration established by `ChangeNextGameConfig(GameConfig)`. If no new configuration is set, the previous one remains in effect. > In instances where the reveal stage's time expires, but no moves have been made, the stage is extended by `reveal_timeout_ms`, allowing players to reveal their moves. > > If the reveal stage's time elapses, but only one player reveals a move, that player is declared the winner, receiving the full reward, and a new game begins immediately. Interface [#interface] Actions [#actions] ```rust title="rock-paper-scissors/io/src/lib.rs" pub enum Action { /// Registers a player for the game. /// Player must send value to be registered /// /// # Requirements: /// * Game is not in progress yet. E.g. the `GameStage` must be `GameStage::Preparation` /// * `msg::value()` is greater or equal to `bet_size` in the config(refund will return to user). /// * Player not registred yet. /// * Lobby is not full. /// /// On success replies `Event::PlayerRegistred`. Register, /// Submits player's move to the program in encrypted form. /// Player can't change his move after it. /// /// # Arguments: /// * `Vec`: is the binary 256-bit blake2b hash of move("0" or "1" or "2" or "3" or "4") + "password". /// /// # Requirements: /// * The `GameStage` must be `GameStage::InProgress(StageDesciption)` where `StageDescription::anticipated_players` must contains `msg::source()` /// /// On success replies `Event::SuccessfulReveal(RevealResult)` where `RevealResult` will correspond to the situation after this reveal. MakeMove(Vec), /// Reveals the move of the player, with which players must confirm their moves. /// In this step the program validates that the hash submitted during the moves stage is equal /// to a hashed open string and save this move(first character from string) to determine the winners. /// /// # Arguments: /// * `Vec`: is the binary move("0" or "1" or "2" or "3" or "4") + "password" that should be equal to binary that was sent in `MakeMove(Vec)` without hashing. /// /// # Requirements: /// * The hashed(by program) `Reveal` binary must be equal to this round `MakeMove` binary. /// * The `GameStage` must be `GameStage::Reveal(StageDesciption)` where `StageDescription::anticipated_players` must contains `msg::source()` /// /// On success replies `Event::SuccessfulMove(ActorId)` where `ActorId` is the moved player's address. Reveal(Vec), /// Changes the game config of the next game. /// When the current game ends, this config will be applied. /// /// # Arguments: /// * `GameConfig`: is the config that will be applied to the next game. /// /// # Requirements: /// * The `msg::source()` must be the owner of the program. /// * `players_count_limit` of the `GameConfig` must be greater than 1 /// * `entry_timeout` of the `GameConfig` must be greater than 5000(5 sec) /// * `move_timeout` of the `GameConfig` must be greater than 5000(5 sec) /// * `reveal_timeout` of the `GameConfig` must be greater than 5000(5 sec) /// /// On success replies `Event::GameConfigChanged`. ChangeNextGameConfig(GameConfig), /// Stops the game. /// This action can be used, for example, to change the configuration of the game, /// or if the players have gone on strike and do not want to continue playing, /// or if the game has gone on for a long time. /// When the admin stops the game, all funds are distributed among the players remaining in the game. /// If the game is in the registration stage, bets will be returned to the entire lobby. /// /// # Requirements: /// * The `msg::source()` must be the owner of the program. /// /// On success replies `Event::GameWasStopped(BTreeSet)` where inside are the players who got the money. StopGame, } ``` * `Register` is an action for player to get registered in lobby of this game. * `MakeMove` is an action to send blake2b hash of player's move concatenated with password. * `Reveal` is an action to reveal player's move to the program. * `ChangeNextGameConfig` is an action that allows an owner to change next game configuration that will start immediately after current game. * `StopGame` is an action that allows an owner to end current game and give current players their part of reward. Events [#events] ```rust title="rock-paper-scissors/io/src/lib.rs" pub enum Event { PlayerRegistered, SuccessfulMove(ActorId), SuccessfulReveal(RevealResult), GameConfigChanged, GameStopped(BTreeSet), } ``` ```rust title="rock-paper-scissors/io/src/lib.rs" pub enum RevealResult { Continue, NextRoundStarted { players: BTreeSet }, GameOver { winner: ActorId }, } ``` * `PlayerRegistred` is an event that occurs when someone uses `Register` action successfully. * `SuccessfulMove(ActorId)` is an event that occurs when someone uses `MakeMove` action successfully, it returns an ActorId of the player who made this move. * `SuccessfulReveal` is an event that occurs when someone uses `Reveal` action successfully, it returns reveal result with actual game stage after this reveal. * `GameConfigChanged` is an event that occurs when someone uses `ChangeNextGameConfig` action successfully. * `GameStopped` is an event that occurs when the wallet uses `StopGame` action successfully, it returns the IDs of the players who got their reward or got their bet back. Programm metadata and state [#programm-metadata-and-state] Metadata interface description: ```rust title="rock-paper-scissors/io/src/lib.rs" pub struct ContractMetadata; impl Metadata for ContractMetadata { type Init = In; type Handle = InOut; type Reply = (); type Others = (); type Signal = (); type State = Out; } ``` To display the full program state information, the `state()` function is used: ```rust title="rock-paper-scissors/src/lib.rs" #[no_mangle] extern fn state() { let game = unsafe { RPS_GAME.take().expect("Unexpected error in taking state") }; msg::reply::(game.into(), 0) .expect("Failed to encode or reply with `ContractState` from `state()`"); } ``` To display only necessary certain values from the state, you need to write a separate crate. In this crate, specify functions that will return the desired values from the `ContractState` state. For example - [rock-paper-scissors/state](https://github.com/gear-foundation/dapps/tree/a357635b61e27c52f46908885fecb048dc8424e5/contracts/rock-paper-scissors/state): ```rust title="rock-paper-scissors/state/src/lib.rs" #[gmeta::metawasm] pub mod metafns { pub type State = ContractState; pub fn config(state: State) -> GameConfig { state.game_config } pub fn lobby_list(state: State) -> Vec { state.lobby } pub fn game_stage(state: State) -> GameStage { state.stage } pub fn current_stage_start_timestamp(state: State) -> u64 { state.current_stage_start_timestamp } } ``` Source code [#source-code] The source code of Rock Paper Scissors implementation and the example of an implementation of its testing is available on [GitHub](https://github.com/gear-foundation/dapps/tree/a357635b61e27c52f46908885fecb048dc8424e5/contracts/rock-paper-scissors). For more details about testing programs written on Vara, refer to the [Program Testing](/docs/developing/build/testing) article. # Supply Chain In logistics, a supply chain is a system for tracking and delivering to an end consumer various items. As a rule, such systems can't work without a lot of paperwork and other layers of bureaucracy. All of this costs a lot of time and money and increases the likelihood of an accidental error or, worst of all, a fraud. With the help of smart contracts (programs) and blockchain technologies, it is possible to eliminate these problems by making a supply chain more efficient, reliable and transparent. * [Supply chain program](https://github.com/gear-foundation/dapps/tree/a357635b61e27c52f46908885fecb048dc8424e5/contracts/supply-chain). * [Supply chain UI](https://github.com/gear-foundation/dapps/tree/3f59be8c871dd1eceea5f5768a7dc275c3a29f0a/frontend/apps/supply-chain). How to run [#how-to-run] ⚒️ Build programs [#️-build-programs] Upload Supply chain program requires build two auxiliary programs: * Build [NFT contract](https://github.com/gear-foundation/dapps/tree/a357635b61e27c52f46908885fecb048dc8424e5/contracts/non-fungible-token) as described in `README.md` * Build [Sharded FT contract](https://github.com/gear-foundation/dapps/tree/a357635b61e27c52f46908885fecb048dc8424e5/contracts/sharded-fungible-token) as described in `README.md` * Build [Supply Chain program](https://github.com/gear-foundation/dapps/tree/a357635b61e27c52f46908885fecb048dc8424e5/contracts/supply-chain) as described in `README.md` 🏗️ Upload programs [#️-upload-programs] You can deploy a program using [idea.gear-tech.io](https://idea.gear-tech.io/). In the network selector, choose `Vara Network Mainnet` or `Development` (in this case, you should have a local node running). ***Non-Fungible Token*** 1. Upload program `nft.opt.wasm` from `/target/wasm32-unknown-unknown/release/` 2. Upload metadata file `meta.txt` 3. Specify `init payload` and calculate gas! * name `Str` - NFT collection name * symbol `Str` - NFT collection symbol * base\_uri `Str` - NFT collection base URI * royalties `Option` - Optional param to specify accounts to pay royalties ***Sharded Fungible Token*** 1. Upload code `ft_logic.opt.wasm` from `/target/wasm32-unknown-unknown/release/` 2. Upload code `ft_storage.opt.wasm` from `/target/wasm32-unknown-unknown/release/` 3. Upload program `ft_main.opt.wasm` from `/target/wasm32-unknown-unknown/release/` 4. Upload metadata file `meta.txt` 5. Specify `init payload` and calculate gas! * storage\_code\_hash (H256) - storage code ID * ft\_logic\_code\_hash (H256) - logic code ID ***Supply Chain*** 1. Upload code `supply_chain.opt.wasm` from `/target/wasm32-unknown-unknown/release/` 🖥️ Run UI [#️-run-ui] 1. Install packages npm pnpm yarn bun ```bash npm install ``` ```bash pnpm install ``` ```bash yarn install ``` ```bash bun install ``` 2. Configure .evn file. Specify network address and program ID like in the example below: For proper application functioning, one needs to adjust an environment variable parameters. An example is available [here](https://github.com/gear-foundation/dapps/tree/3f59be8c871dd1eceea5f5768a7dc275c3a29f0a/frontend/apps/supply-chain/.env.example). ```sh REACT_APP_NODE_ADDRESS=wss://rpc-node.gear-tech.io REACT_APP_CODE_ADDRESS=0x7141c1f9eb7c48063fc7d3a00e7ef157c4a95a83d656efaee910104d429b0de9 ``` 3. Run app ```sh yarn start ``` Logic [#logic] * Each newly produced item gets the NFT in Gear's context - [Vara non-fungible token (VNFT)](/docs/vara-network/examples/Standards/vnft) and its ID equals an ID of the item. Then, as an item moves along a supply chain, an item's NFT transfers between a supply chain program, item's producer, and future distributor, retailer and end consumer. * Anyone who knows an item's ID can get item info. * Sale, purchase, delivery is made in [Vara fungible tokens (VFT)](/docs/vara-network/examples/Standards/vft). Item info has the following struct: ```rust title="supply-chain/io/src/lib.rs" pub struct ItemInfo { /// Item’s producer [`ActorId`]. pub producer: ActorId, /// [`ActorId`] of an item’s current or past distributor (depends on item’s /// `state`). If it equals [`ActorId::zero()`], then it means that an item /// has never had a distributor. pub distributor: ActorId, /// [`ActorId`] of an item’s current or past retailer (depends on item’s /// `state`). If it equals [`ActorId::zero()`], then it means that an item /// has never had a retailer. pub retailer: ActorId, pub state: ItemState, /// An item’s price. If it equals 0, then, depending on item’s `state`, an /// item is sold for free or has never been put up for sale. pub price: u128, /// Milliseconds during which a current seller should deliver an item. pub delivery_time: u64, } ``` And `ItemState` has the following struct and inner enums: ```rust title="supply-chain/io/src/lib.rs" pub struct ItemState { pub state: ItemEventState, pub by: Role, } ``` ```rust title="supply-chain/io/src/lib.rs" pub enum ItemEventState { #[default] Produced, Purchased, Received, Processed, Packaged, ForSale, Approved, Shipped, } ``` ```rust title="supply-chain/io/src/lib.rs" pub enum Role { Producer, Distributor, Retailer, #[default] Consumer, } ``` Item route [#item-route] 1. The item is produced with `ProducerAction::Produce`. 2. The item is put up for sale with `ProducerAction::PutUpForSale`. 3. The item is purchased with `DistributorAction::Purchase`. 4. The purchase is approved or not with `ProducerAction::Approve`. In the latter case, the item returns on the **2** step. 5. The item is shipped with `ProducerAction::Ship`. 6. The item is received with `DistributorAction::Receive`. 7. The item is processed with `DistributorAction::Process`. 8. The item is packaged with `DistributorAction::Package`. 9. The item is put up for sale with `DistributorAction::PutUpForSale`. 10. The item is purchased with `RetailerAction::Purchase`. 11. The purchase is approved or not with `DistributorAction::Approve`. In the latter case, the item returns on the **9** step. 12. The item is shipped with `DistributorAction::Ship`. 13. The item is received with `RetailerAction::Receive`. 14. The item is put up for sale with `RetailerAction::PutUpForSale`. 15. The item is purchased with `ConsumerAction::Purchase`. The end! Interface [#interface] Initialization [#initialization] ```rust title="supply-chain/io/src/lib.rs" /// Initializes the Supply chain program. /// /// # Requirements /// - Each [`ActorId`] of `producers`, `distributors`, and `retailers` mustn't /// equal [`ActorId::zero()`]. #[derive(Encode, Decode, Hash, TypeInfo, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Clone)] #[codec(crate = gstd::codec)] #[scale_info(crate = gstd::scale_info)] pub struct Initialize { /// IDs of actors that'll have the right to interact with a supply chain on /// behalf of a producer. pub producers: Vec, /// IDs of actors that'll have the right to interact with a supply chain on /// behalf of a distributor. pub distributors: Vec, /// IDs of actors that'll have the right to interact with a supply chain on /// behalf of a retailer. pub retailers: Vec, /// A FT contract [`ActorId`]. pub fungible_token: ActorId, /// An NFT contract [`ActorId`]. pub non_fungible_token: ActorId, } ``` Actions [#actions] ```rust title="supply-chain/io/src/lib.rs" /// Sends the program info about what it should do. #[derive(Encode, Decode, TypeInfo, Debug, PartialEq, Eq, PartialOrd, Ord, Clone)] #[codec(crate = gstd::codec)] #[scale_info(crate = gstd::scale_info)] pub struct Action { pub action: InnerAction, pub kind: TransactionKind, } ``` ```rust title="supply-chain/io/src/lib.rs" /// A part of [`Action`]. /// /// Determines how an action will be processed. /// /// The program has a transaction caching mechanism for a continuation of /// partially processed asynchronous actions. Most often, the reason of an /// underprocession is the lack of gas. /// /// Important notes: /// - Only the last sent asynchronous action for /// [`msg::source()`](gstd::msg::source) is cached. /// - Non-asynchronous actions are never cached. /// - There's no guarantee every underprocessed asynchronous action will be /// cached. Use /// [`is_action_cached()`](../supply_chain_state/metafns/fn.is_action_cached.html) /// to check if some action is cached for some [`ActorId`]. /// - It's possible to send a retry action with a different payload, and it'll /// continue with it because, for some action, not all payload is saved in the /// cache (see [`CachedAction`]). /// - The cache memory has a limit, so when it's reached every oldest cached /// action is replaced with a new one. #[derive( Default, Encode, Decode, TypeInfo, Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Hash, )] #[codec(crate = gstd::codec)] #[scale_info(crate = gstd::scale_info)] pub enum TransactionKind { #[default] New, Retry, } ``` ```rust title="supply-chain/io/src/lib.rs" /// A part of [`Action`]. #[derive(Encode, Decode, TypeInfo, Debug, PartialEq, Eq, PartialOrd, Ord, Clone)] #[codec(crate = gstd::codec)] #[scale_info(crate = gstd::scale_info)] pub enum InnerAction { Producer(ProducerAction), Distributor(DistributorAction), Retailer(RetailerAction), Consumer(ConsumerAction), } ``` ```rust title="supply-chain/io/src/lib.rs" /// Actions for a producer. /// /// Should be used inside [`InnerAction::Producer`]. #[derive(Encode, Decode, TypeInfo, Debug, PartialEq, Eq, PartialOrd, Ord, Clone)] #[codec(crate = gstd::codec)] #[scale_info(crate = gstd::scale_info)] pub enum ProducerAction { /// Produces one item and a corresponding NFT with given `token_metadata`. /// /// Transfers the created NFT for the item to a producer /// ([`msg::source()`]). /// /// # Requirements /// - [`msg::source()`] must be a producer in a supply chain. /// /// On success, replies with [`Event`] where [`ItemState`] contains /// [`ItemEventState::Produced`] & [`Role::Producer`]. /// /// [`msg::source()`]: gstd::msg::source Produce { token_metadata: TokenMetadata }, /// Puts a produced item up for sale to distributors for given `price` on /// behalf of a producer. /// /// Transfers an item's NFT to the Supply chain program /// ([`exec::program_id()`](gstd::exec::program_id)). /// /// # Requirements /// - The item must exist in a supply chain. /// - [`msg::source()`](gstd::msg::source) must be the producer of the item. /// - Item's [`ItemState`] must contain [`ItemEventState::Produced`] & /// [`Role::Producer`]. /// /// On success, replies with [`Event`] where [`ItemState`] contains /// [`ItemEventState::ForSale`] & [`Role::Producer`]. PutUpForSale { item_id: ItemId, price: u128 }, /// Approves or not a distributor's purchase on behalf of a producer. /// /// If the purchase is approved, then item's [`ItemEventState`] changes to /// [`Approved`](ItemEventState::Approved) and, from that moment, an item /// can be shipped (by [`ProducerAction::Ship`]). /// /// If the purchase is **not** approved, then fungible tokens for it are /// refunded from the Supply chain program /// ([`exec::program_id()`](gstd::exec::program_id)) to the item's /// distributor and item's [`ItemEventState`] changes back to /// [`ForSale`](ItemEventState::ForSale). /// /// # Requirements /// - The item must exist in a supply chain. /// - [`msg::source()`](gstd::msg::source) must be the producer of the item. /// - Item's [`ItemState`] must contain [`ItemEventState::Produced`] & /// [`Role::Distributor`]. /// /// On success, replies with [`Event`] where [`ItemState`] contains /// [`ItemEventState::Approved`]/[`ItemEventState::ForSale`] & /// [`Role::Producer`]. Approve { item_id: ItemId, /// Yes ([`true`]) or no ([`false`]). approve: bool, }, /// Starts a shipping of a purchased item to a distributor on behalf of a /// producer. /// /// Starts the countdown for the delivery time specified for the item in /// [`DistributorAction::Purchase`]. /// /// # Requirements /// - The item must exist in a supply chain. /// - [`msg::source()`](gstd::msg::source) must be the producer of the item. /// - Item's [`ItemState`] must contain [`ItemEventState::Approved`] & /// [`Role::Producer`]. /// /// On success, replies with [`Event`] where [`ItemState`] contains /// [`ItemEventState::Shipped`] & [`Role::Producer`]. Ship(ItemId), } ``` ```rust title="supply-chain/io/src/lib.rs" /// Actions for a distributor. /// /// Should be used inside [`InnerAction::Distributor`]. #[derive(Encode, Decode, TypeInfo, Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Hash)] #[codec(crate = gstd::codec)] #[scale_info(crate = gstd::scale_info)] pub enum DistributorAction { /// Purchases an item from a producer on behalf of a distributor. /// /// Transfers fungible tokens for purchasing the item to the Supply chain /// contract ([`exec::program_id()`](gstd::exec::program_id)) until the item /// is received (by [`DistributorAction::Receive`]). /// /// **Note:** the item's producer must approve or not this purchase by /// [`ProducerAction::Approve`]. /// /// # Requirements /// - The item must exist in a supply chain. /// - [`msg::source()`](gstd::msg::source) must be a distributor. /// - Item's [`ItemState`] must contain [`ItemEventState::ForSale`] & /// [`Role::Producer`]. /// /// On success, replies with [`Event`] where [`ItemState`] contains /// [`ItemEventState::Purchased`] & [`Role::Distributor`]. Purchase { item_id: ItemId, /// Milliseconds during which the producer of an item should deliver it. /// A countdown starts after [`ProducerAction::Ship`] is executed. delivery_time: u64, }, /// Receives a shipped item from a producer on behalf of a distributor. /// /// Depending on the time spent on a delivery, transfers fungible tokens for /// purchasing the item from the Supply chain program /// ([`exec::program_id()`](gstd::exec::program_id)) to the item's producer /// or, as a penalty for being late, refunds a half or all of them to the /// item's distributor ([`msg::source()`]). /// /// Transfers an item's NFT to the distributor ([`msg::source()`]). /// /// # Requirements /// - The item must exist in a supply chain. /// - [`msg::source()`] must be the distributor of the item. /// - Item's [`ItemState`] must contain [`ItemEventState::Shipped`] & /// [`Role::Producer`]. /// /// On success, replies with [`Event`] where [`ItemState`] contains /// [`ItemEventState::Received`] & [`Role::Distributor`]. /// /// [`msg::source()`]: gstd::msg::source Receive(ItemId), /// Processes a received item on behalf of a distributor. /// /// # Requirements /// - The item must exist in a supply chain. /// - [`msg::source()`](gstd::msg::source) must be the distributor of the /// item. /// - Item's [`ItemState`] must contain [`ItemEventState::Received`] & /// [`Role::Distributor`]. /// /// On success, replies with [`Event`] where [`ItemState`] contains /// [`ItemEventState::Processed`] & [`Role::Distributor`]. Process(ItemId), /// Packages a processed item on behalf of a distributor. /// /// # Requirements /// - The item must exist in a supply chain. /// - [`msg::source()`](gstd::msg::source) must be the distributor of the /// item. /// - Item's [`ItemState`] must contain [`ItemEventState::Processed`] & /// [`Role::Distributor`]. /// /// On success, replies with [`Event`] where [`ItemState`] contains /// [`ItemEventState::Packaged`] & [`Role::Distributor`]. Package(ItemId), /// Puts a packaged item up for sale to retailers for given `price` on /// behalf of a distributor. /// /// Transfers an item's NFT to the Supply chain program /// ([`exec::program_id()`](gstd::exec::program_id)). /// /// # Requirements /// - The item must exist in a supply chain. /// - [`msg::source()`](gstd::msg::source) must be the distributor of the /// item. /// - Item's [`ItemState`] must contain [`ItemEventState::Packaged`] & /// [`Role::Distributor`]. /// /// On success, replies with [`Event`] where [`ItemState`] contains /// [`ItemEventState::ForSale`] & [`Role::Distributor`]. PutUpForSale { item_id: ItemId, price: u128 }, /// Approves or not a retailer's purchase on behalf of a distributor. /// /// If the purchase is approved, then item's [`ItemEventState`] changes to /// [`Approved`](ItemEventState::Approved) and, from that moment, an item /// can be shipped (by [`DistributorAction::Ship`]). /// /// If the purchase is **not** approved, then fungible tokens for it are /// refunded from the Supply chain program /// ([`exec::program_id()`](gstd::exec::program_id)) to the item's retailer /// and item's [`ItemEventState`] changes back to /// [`ForSale`](ItemEventState::ForSale). /// /// # Requirements /// - The item must exist in a supply chain. /// - [`msg::source()`](gstd::msg::source) must be the distributor of the /// item. /// - Item's [`ItemState`] must contain [`ItemEventState::Purchased`] & /// [`Role::Retailer`]. /// /// On success, replies with [`Event`] where [`ItemState`] contains /// [`ItemEventState::Approved`]/[`ItemEventState::ForSale`] & /// [`Role::Distributor`]. Approve { item_id: ItemId, /// Yes ([`true`]) or no ([`false`]). approve: bool, }, /// Starts a shipping of a purchased item to a retailer on behalf of a /// distributor. /// /// Starts the countdown for the delivery time specified for the item in /// [`RetailerAction::Purchase`]. /// /// # Requirements /// - The item must exist in a supply chain. /// - [`msg::source()`](gstd::msg::source) must be the distributor of the /// item. /// - Item's [`ItemState`] must contain [`ItemEventState::Approved`] & /// [`Role::Distributor`]. /// /// On success, replies with [`Event`] where [`ItemState`] contains /// [`ItemEventState::Shipped`] & [`Role::Distributor`]. Ship(ItemId), } ``` ```rust title="supply-chain/io/src/lib.rs" /// Actions for a retailer. /// /// Should be used inside [`InnerAction::Retailer`]. #[derive(Encode, Decode, TypeInfo, Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Hash)] #[codec(crate = gstd::codec)] #[scale_info(crate = gstd::scale_info)] pub enum RetailerAction { /// Purchases an item from a distributor on behalf of a retailer. /// /// Transfers fungible tokens for purchasing the item to the Supply chain /// contract ([`exec::program_id()`](gstd::exec::program_id)) until the item /// is received (by [`RetailerAction::Receive`]). /// /// **Note:** the item's distributor must approve or not this purchase by /// [`DistributorAction::Approve`]. /// /// # Requirements /// - The item must exist in a supply chain. /// - [`msg::source()`](gstd::msg::source) must be a retailer. /// - Item's [`ItemState`] must contain [`ItemEventState::ForSale`] & /// [`Role::Distributor`]. /// /// On success, replies with [`Event`] where [`ItemState`] contains /// [`ItemEventState::Purchased`] & [`Role::Retailer`]. Purchase { item_id: ItemId, /// Milliseconds during which the distributor of an item should deliver /// it. A countdown starts after [`DistributorAction::Ship`] is /// executed. delivery_time: u64, }, /// Receives a shipped item from a distributor on behalf of a retailer. /// /// Depending on the time spent on a delivery, transfers fungible tokens for /// purchasing the item from the Supply chain program /// ([`exec::program_id()`](gstd::exec::program_id)) to the item's /// distributor or, as a penalty for being late, refunds a half or all of /// them to the item's retailer ([`msg::source()`]). /// /// Transfers an item's NFT to the retailer ([`msg::source()`]). /// /// # Requirements /// - The item must exist in a supply chain. /// - [`msg::source()`] must be the retailer of the item. /// - Item's [`ItemState`] must contain [`ItemEventState::Shipped`] & /// [`Role::Distributor`]. /// /// On success, replies with [`Event`] where [`ItemState`] contains /// [`ItemEventState::Received`] & [`Role::Retailer`]. /// /// [`msg::source()`]: gstd::msg::source Receive(ItemId), /// Puts a received item up for sale to consumers for given `price` on /// behalf of a retailer. /// /// Transfers an item's NFT to the Supply chain program /// ([`exec::program_id()`](gstd::exec::program_id)). /// /// # Requirements /// - The item must exist in a supply chain. /// - [`msg::source()`](gstd::msg::source) must be the retailer of the item. /// - Item's [`ItemState`] must contain [`ItemEventState::Received`] & /// [`Role::Retailer`]. /// /// On success, replies with [`Event`] where [`ItemState`] contains /// [`ItemEventState::ForSale`] & [`Role::Retailer`]. PutUpForSale { item_id: ItemId, price: u128 }, } ``` ```rust title="supply-chain/io/src/lib.rs" /// Actions for a consumer. /// /// Should be used inside [`InnerAction::Consumer`]. #[derive(Encode, Decode, TypeInfo, Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Hash)] #[codec(crate = gstd::codec)] #[scale_info(crate = gstd::scale_info)] pub enum ConsumerAction { /// Purchases an item from a retailer. /// /// Transfers fungible tokens for purchasing the item to its retailer. /// /// Transfers an item's NFT to the consumer /// ([`msg::source()`](gstd::msg::source)). /// /// # Requirements /// - The item must exist in a supply chain. /// - Item's [`ItemState`] must contain [`ItemEventState::ForSale`] & /// [`Role::Retailer`]. /// /// On success, replies with [`Event`] where [`ItemState`] contains /// [`ItemEventState::Purchased`] & [`Role::Consumer`]. Purchase(ItemId), } ``` Program metadata and state [#program-metadata-and-state] Metadata interface description: ```rust title="supply-chain/io/src/lib.rs" pub struct ContractMetadata; impl Metadata for ContractMetadata { type Init = InOut>; type Handle = InOut>; type Reply = (); type Others = (); type Signal = (); type State = Out; } ``` To display the full program state information, the `state()` function is used: ```rust title="supply-chain/src/lib.rs" #[no_mangle] extern fn state() { let state: State = generate_state(); msg::reply(state, 0).expect("Failed to encode or reply with `State` from `state()`"); } ``` To display only necessary certain values from the state, you need to write a separate crate. In this crate, specify functions that will return the desired values from the `State` struct. For example - [supply-chain/state](https://github.com/gear-foundation/dapps/tree/a357635b61e27c52f46908885fecb048dc8424e5/contracts/supply-chain/state): ```rust title="supply-chain/state/src/lib.rs" #[gmeta::metawasm] pub mod metafns { pub type State = supply_chain_io::State; pub fn item_info(state: State, item_id: ItemId) -> Option { state .items .into_iter() .find_map(|(some_item_id, item_info)| (some_item_id == item_id).then_some(item_info)) } pub fn participants(state: State) -> Participants { Participants { producers: state.producers, distributors: state.distributors, retailers: state.retailers, } } pub fn roles(state: State, actor: ActorId) -> Vec { let mut roles = vec![Role::Consumer]; if state.producers.contains(&actor) { roles.push(Role::Producer); } if state.distributors.contains(&actor) { roles.push(Role::Distributor); } if state.retailers.contains(&actor) { roles.push(Role::Retailer); } roles } pub fn existing_items(state: State) -> Vec<(ItemId, ItemInfo)> { state.items } pub fn fungible_token(state: State) -> ActorId { state.fungible_token } pub fn non_fungible_token(state: State) -> ActorId { state.non_fungible_token } pub fn is_action_cached(state: State, actor: ActorId, action: InnerAction) -> bool { if let Some(action) = action.into() { state.cached_actions.contains(&(actor, action)) } else { false } } } ``` Source code [#source-code] The source code of this example of a supply chain program and an implementation of its testing is available on [GitHub](https://github.com/gear-foundation/dapps/tree/a357635b61e27c52f46908885fecb048dc8424e5/contracts/supply-chain). They can be used as is or modified to suit your own scenarios. For more details about testing programs written on Vara, refer to the [Program Testing](/docs/developing/build/testing) article. # Tamagotchi Battle tamagotchi-battle Welcome to the exciting world of Tamagotchi Battle! In this fascinating game your faithful companions - Tamagotchi - are ready to fight each other in pursuit of glory and victory. In this game, you will be able to use the tamagotchi that was described in [previous article](/docs/vara-network/examples/archive/tamagotchi). To win, you'll have to skillfully manage your Tamagotchi and apply strategy. Take turns making moves, pave your way to success and, of course, rely on luck. In the Tamagotchi Battle Game, your engaging and exciting strategy can take you to the top. The article explains the programming interface, data structure, basic functions and explains their purpose. It can be used as is or modified to suit your own scenarios. Anyone can easily create their own application and run it on the Vara Network. The source code is available on [GitHub](https://github.com/gear-foundation/dapps/tree/master/contracts/tamagotchi-battle). How to run [#how-to-run] 1. Build a program > Additional details regarding this matter can be located within the [README](https://github.com/gear-foundation/dapps/tree/master/contracts/tamagotchi-battle/README.md) directory of the program. 2. Upload the program to the [Vara Network Mainnet](https://idea.gear-tech.io/programs?node=wss%3A%2F%2Frpc.vara.network) > Further details regarding the process of program uploading can be located within the [Getting Started](/docs/vara-network/quick-start#deploy-your-program-to-vara-mainnet) section. 3. Build and run user interface > More information about this can be found in the [README](https://github.com/gear-foundation/dapps/blob/master/frontend/apps/tamagotchi-battle/README.md) directory of the frontend. Implementation details [#implementation-details] Tamagotchi Battle program description [#tamagotchi-battle-program-description] The Tamagotchi Battle program contains the following information ```rust title="tamagotchi-battle/src/lib.rs" struct Battle { admins: Vec, players: BTreeMap, players_ids: Vec, current_players: Vec, state: BattleState, current_winner: ActorId, pairs: BTreeMap, players_to_pairs: BTreeMap>, completed_games: u8, reservations: BTreeMap, } ``` * `admins` - administrators, who can start the battle and each new round * `players` - list of player parameters * `players_ids` - the actual tamagotchi id's that are playing * `current_players` - list of all registered tamagotchi * `state` - current battle stage * `current_winner` - winner of the last battle * `pairs` - list of pair parameters * `players_to_pairs` - matching players and their pair\_ids * `completed_games` - the number of pairs that finished the battle * `reservations` - reservations id for delayed messages `Player` structure looks as follows: ```rust title="tamagotchi-battle/io/src/lib.rs" pub struct Player { pub owner: ActorId, pub name: String, pub date_of_birth: u64, pub tmg_id: TamagotchiId, pub defence: u16, pub power: u16, pub health: u16, pub color: String, pub victories: u32, } ``` * `owner` - owner's address * `name` - Tamagotchi name * `date_of_birth` - Tamagotchi's birthday * `tmg_id` - Tamagotchi id * `defence` - level of protection * `power` - level of power * `health` - level of health * `color` - tamagotchi color * `victories` - number of victories `Pair` structure looks as follows: ```rust title="tamagotchi-battle/io/src/lib.rs" pub struct Pair { pub owner_ids: Vec, pub tmg_ids: Vec, pub moves: Vec>, pub rounds: u8, pub game_is_over: bool, pub winner: ActorId, pub move_deadline: u64, pub msg_id: MessageId, } ``` * `owner_ids` - it's a Tamagotchi owner's id * `tmg_ids` - it's a Tamagotchi id * `moves` - history of moves * `rounds` - current round number/number of rounds * `game_is_over` - end-of-game identifier * `winner` - the winner of the battle (in case there is no winner equals to zero address) * `move_deadline` - time limit for making a move * `msg_id` - message id for actions with delayed messages Action [#action] The Tamagotchi Battle program offers the following activities: ```rust title="tamagotchi-battle/io/src/lib.rs" pub enum BattleAction { StartRegistration, Register { tmg_id: TamagotchiId, }, MakeMove { pair_id: PairId, tmg_move: Move, }, // the following actions are only available to the admin StartBattle, AddAdmin(ActorId), CheckIfMoveMade { pair_id: PairId, tmg_id: Option, }, } ``` ```rust title="tamagotchi-battle/io/src/lib.rs" pub enum Move { Attack, Defence, } ``` Event [#event] For each activity, the program responds with the following replies: ```rust title="tamagotchi-battle/io/src/lib.rs" pub enum BattleEvent { RegistrationStarted, Registered { tmg_id: TamagotchiId }, MoveMade, GoToWaitingState, GameIsOver, InfoUpdated, NewGame, BattleStarted, RoundResult((PairId, u16, u16, Option, Option)), NewRound, AdminAdded, } ``` Logic [#logic] Before the start of the battle, it is necessary to initiate the registration process, during which the status of the game is checked and the terms of the program are determined. ```rust title="tamagotchi-battle/src/lib.rs" fn start_registration(&mut self) { assert_eq!( self.state, BattleState::GameIsOver, "The previous game must be over" ); self.state = BattleState::Registration; self.current_winner = ActorId::zero(); self.players_ids = Vec::new(); self.completed_games = 0; self.players_to_pairs = BTreeMap::new(); self.current_players = Vec::new(); self.pairs = BTreeMap::new(); msg::reply(BattleEvent::RegistrationStarted, 0) .expect("Error in sending a reply `BattleEvent::RegistrationStarted`"); } ``` After that, registration for players will be available `BattleAction::Register { tmg_id: TamagotchiId }`, where players must provide the ActorId of their Tamagotchi. The registration function performs a series of checks before including a Tamagotchi in the list of participants. Tamagotchi power and defense are randomly generated during registration. ```rust title="tamagotchi-battle/src/lib.rs" async fn register(&mut self, tmg_id: &TamagotchiId) { assert_eq!( self.state, BattleState::Registration, "The game is not in Registration stage" ); assert!( self.players_ids.len() < MAX_PARTICIPANTS as usize, "Maximum number of players was reached" ); if self.players_ids.contains(tmg_id) { panic!("This tamagotchi is already in game!"); } let (owner, name, date_of_birth) = get_tmg_info(tmg_id).await; if owner != msg::source() { panic!("It is not your Tamagotchi"); } if !self.players.contains_key(tmg_id) { let power = generate_power(*tmg_id); let defence = MAX_POWER - power; // ... ``` To start the game, the admin must send the `BattleAction::StartBattle`, where first checks will be carried out, and then there will be a split into pairs. ```rust title="tamagotchi-battle/src/lib.rs" fn start_battle(&mut self) { assert!( self.state == BattleState::Registration || self.state == BattleState::WaitNextRound, "The battle can be started from registration stage of after completing a round" ); assert!( self.players_ids.len() > 1, "At least 2 players must be in the game" ); if !self.admins.contains(&msg::source()) { panic!("Only admin can start the battle"); } // Clear the state if the state is `BattleState::WaitNextRound` self.pairs = BTreeMap::new(); self.players_to_pairs = BTreeMap::new(); self.completed_games = 0; self.split_into_pairs(); self.state = BattleState::GameIsOn; msg::reply(BattleEvent::BattleStarted, 0) .expect("Error in a reply `BattleEvent::BattleStarted`"); } ``` The game uses **Gas Reservation** and **Delayed Messaging** to enforce turn limits: * **Mechanism:** The `split_into_pairs` function schedules a delayed move check via `msg::send_delayed_from_reservation`. * **Consequence:** If a player fails to move within the allotted time, they are automatically **penalized** and take **health damage**. This ensures that games cannot be stalled by inactive participants. ```rust title="tamagotchi-battle/src/lib.rs" fn send_delayed_msg_from_rsv( reservation_id: ReservationId, pair_id: PairId, tmg_id: &ActorId, ) -> MessageId { msg::send_delayed_from_reservation( reservation_id, exec::program_id(), BattleAction::CheckIfMoveMade { pair_id, tmg_id: Some(*tmg_id), }, 0, TIME_FOR_MOVE + 1, ) .expect("Error in sending a delayed message `BattleAction::CheckIfModeMade`") } ``` To make a move, a player needs to send a message `BattleAction::MakeMove { pair_id: PairId, tmg_move: Move}` that contains the pair id and the move the player wants to make. ```rust title="tamagotchi-battle/src/lib.rs" fn make_move(&mut self, pair_id: PairId, tmg_move: Move) { assert_eq!( self.state, BattleState::GameIsOn, "The game is not in `GameIsOn` state" ); let pair = self.pairs.get(&pair_id).expect("Pair does not exist"); assert!(!pair.game_is_over, "The game for this pair is over"); let current_turn = pair.moves.len(); let owner = pair.owner_ids[current_turn]; assert_eq!( owner, msg::source(), "It is not your turn or not your game!" ); // ... let game_is_over = self.make_move_internal(pair_id, Some(tmg_move)); if !game_is_over { let msg_id = send_delayed_msg_with_gas(pair_id); self.pairs .entry(pair_id) .and_modify(|pair| pair.msg_id = msg_id); } reply_move_made(); } ``` After the move is repeated, the delayed messages are used for the same purpose, but in this case, a different type of delayed message is used `msg::send_with_gas_delayed`, where the gas is applied separately. ```rust title="tamagotchi-battle/src/lib.rs" fn send_delayed_msg_with_gas(pair_id: PairId) -> MessageId { msg::send_with_gas_delayed( exec::program_id(), BattleAction::CheckIfMoveMade { pair_id, tmg_id: None, }, GAS_AMOUNT, 0, TIME_FOR_MOVE + 1, ) .expect("Error in sending a delayed message `BattleAction::CheckIfModeMade`") } ``` After the battle and determination of the winner, the game status is switched to `BattleState::WaitNextRound`, where the winners will fight each other to determine the strongest. Important details [#important-details] 1. If after a certain number of rounds (`MAX_STEPS_IN_ROUND`), it was not possible to determine the winner, then the one with more health wins. ```rust title="tamagotchi-battle/src/lib.rs" if pair.rounds == MAX_STEPS_IN_ROUND && winner.is_none() { winner = if players[0].health >= players[1].health { players[1].health = 0; Some(0) } else { players[0].health = 0; Some(1) }; } ``` 2. After each round, it re-generates the power and defense of the Tamagotchi. ```rust title="tamagotchi-battle/src/lib.rs" players[0].power = generate_power(pair.tmg_ids[0]); players[0].defence = MAX_POWER - players[0].power; players[1].power = generate_power(pair.tmg_ids[1]); players[1].defence = MAX_POWER - players[1].power; ``` 3. When a player makes an attack, and the other player doesn't respond, the damage accumulates for both the attacker's strike and the opponent's penalty. ```rust title="tamagotchi-battle/src/lib.rs" [Some(Move::Attack), None] => { let player_0_power = players[0].power / 6; // penalty for skipping the move let penalty = generate_damage(); health_loss_1 = players[1].health.saturating_sub( players[1] .health .saturating_sub(player_0_power) .saturating_sub(penalty), ); players[1].health = players[1] .health .saturating_sub(player_0_power) .saturating_sub(penalty); if players[1].health == 0 { winner = Some(0); } (winner, health_loss_0, health_loss_1) } ``` Program metadata and state [#program-metadata-and-state] Metadata interface description: ```rust title="tamagotchi-battle/io/src/lib.rs" pub struct BattleMetadata; impl Metadata for BattleMetadata { type Init = (); type Handle = InOut; type Others = (); type Reply = (); type Signal = (); type State = Out; } ``` To display the program state information, the `state()` function is used: ```rust title="tamagotchi-battle/src/lib.rs" #[no_mangle] extern fn state() { let battle = unsafe { BATTLE.take().expect("Unexpected error in taking state") }; msg::reply(battle, 0).expect("Failed to share state"); } ``` Source code [#source-code] The source code of this example of Tamagotchi Battle program and the example of an implementation of its testing is available on [gear-foundation/dapp/contracts/tamagotchi-battle](https://github.com/gear-foundation/dapps/tree/master/contracts/tamagotchi-battle). See also an example of the smart program testing implementation based on `gtest`: [gear-foundation/dapps/tamagotchi-battle/tests](https://github.com/gear-foundation/dapps/tree/master/contracts/tamagotchi-battle/tests). For more details about testing programs written on Vara, refer to the [Program Testing](/docs/developing/build/testing) article. # Tamagotchi tamagotchi Tamagotchi is a popular virtual pet game that was created in Japan in the late 1990s. This game provides an opportunity to care for a virtual pet, which is a tiny digital creature that lives on the screen of the device. The main purpose of the game is to provide care, nurturing and care for your Tamagotchi. The pet requires constant attention: you need to feed it, groom it, play with it and monitor its overall condition. If you don't pay enough attention to your Tamagotchi, it may even die. The game develops responsibility and care in the player, and promotes time management skills. Tamagotchi has become an iconic game and has many different versions and modifications. The article explains the programming interface, data structure, basic functions and explains their purpose. It can be used as is or modified to suit your own scenarios. Anyone can easily create their own application and run it on the Vara Network. The source code is available on [GitHub](https://github.com/gear-foundation/dapps/tree/master/contracts/tamagotchi). How to run [#how-to-run] 1. Build a program > Additional details regarding this matter can be located within the [README](https://github.com/gear-foundation/dapps/tree/master/contracts/tamagotchi/README.md) directory of the program. 2. Upload the program to the [Vara Network Mainnet](https://idea.gear-tech.io/programs?node=wss%3A%2F%2Frpc.vara.network) > Further details regarding the process of program uploading can be located within the [Getting Started](/docs/vara-network/quick-start#deploy-your-program-to-vara-mainnet) section. 3. Build and run user interface > More information about this can be found in the [README](https://github.com/gear-foundation/dapps/blob/master/frontend/apps/tamagotchi/README.md) directory of the frontend. Implementation details [#implementation-details] Tamagotchi program description [#tamagotchi-program-description] The Tamagotchi program contains the following information ```rust title="tamagotchi/src/lib.rs" struct Tamagotchi { name: String, date_of_birth: u64, owner: ActorId, fed: u64, fed_block: u64, entertained: u64, entertained_block: u64, rested: u64, rested_block: u64, } ``` * `name` - pet name * `date_of_birth` - pet's date of birth * `owner` - pet owner * `fed` - is the level of satiety * `fed_block` - is the last feeding time * `entertained` - is the level of entertainment satisfaction * `entertained_block` - is the time of last entertainment * `rested` - is the level of relaxation * `rested_block` - is the final resting time Initialization [#initialization] To initialize a game program, it is only necessary to provide the pet's name. ```rust title="tamagotchi/io/src/lib.rs" pub struct TmgInit { pub name: String, } ``` During initialization, the pet is created with a complete set of all states. ```rust title="tamagotchi/src/lib.rs" #[no_mangle] extern fn init() { let TmgInit { name } = msg::load().expect("Failed to decode Tamagotchi name"); let current_block = exec::block_timestamp(); let tmg = Tamagotchi { name, date_of_birth: current_block, owner: msg::source(), fed: MAX_VALUE, fed_block: current_block, entertained: MAX_VALUE, entertained_block: current_block, rested: MAX_VALUE, rested_block: current_block, }; unsafe { TAMAGOTCHI = Some(tmg); } } ``` Action [#action] The Tamagotchi program offers the following activities: ```rust title="tamagotchi/io/src/lib.rs" pub enum TmgAction { // get a pet name Name, // get the age of the pet Age, // feed the pet Feed, // play with the pet Play, // let the pet sleep Sleep, // get basic information about the pet TmgInfo, } ``` Reply [#reply] For each activity, the program responds with the following replies: ```rust title="tamagotchi/io/src/lib.rs" pub enum TmgReply { Name(String), Age(u64), Fed, Entertained, Slept, TmgInfo { owner: ActorId, name: String, date_of_birth: u64, }, } ``` Before proceeding with any action, it is necessary to check whether the pet is alive. For this purpose, values indicating the health of the pet are calculated ```rust title="tamagotchi/src/lib.rs" fn calculate_hunger(&self) -> u64 { HUNGER_PER_BLOCK * ((exec::block_timestamp() - self.fed_block) / 1_000) } fn calculate_boredom(&self) -> u64 { BOREDOM_PER_BLOCK * ((exec::block_timestamp() - self.entertained_block) / 1000) } fn calculate_energy(&self) -> u64 { ENERGY_PER_BLOCK * ((exec::block_timestamp() - self.rested_block) / 1000) } ``` If the value of all three tamagotchi state parameters is too high, the tamagotchi will die ```rust title="tamagotchi/src/lib.rs" fn tmg_is_dead(&self) -> bool { let fed = self.fed.saturating_sub(self.calculate_hunger()); let entertained = self.entertained.saturating_sub(self.calculate_boredom()); let rested = self.rested.saturating_sub(self.calculate_energy()); fed == 0 && entertained == 0 && rested == 0 } ``` Program metadata and state [#program-metadata-and-state] Metadata interface description: ```rust title="tamagotchi/io/src/lib.rs" pub struct ProgramMetadata; impl Metadata for ProgramMetadata { type Init = In; type Handle = InOut; type Reply = (); type Others = (); type Signal = (); type State = Out; } ``` To display the program state information, the `state()` function is used: ```rust title="tamagotchi/src/lib.rs" #[no_mangle] extern fn state() { let tmg = unsafe { TAMAGOTCHI.take().expect("Unexpected error in taking state") }; msg::reply(tmg, 0).expect("Failed to share state"); } ``` Source code [#source-code] The source code of this example of Tamagotchi program and the example of an implementation of its testing is available on [gear-foundation/dapp/contracts/tamagotchi](https://github.com/gear-foundation/dapps/tree/master/contracts/tamagotchi). See also an example of the program testing implementation based on `gtest`: [gear-foundation/dapps/tamagotchi/tests](https://github.com/gear-foundation/dapps/tree/master/contracts/tamagotchi/tests). For more details about testing programs written on Vara, refer to the [Program Testing](/docs/developing/build/testing) article. # Tequila Train Tequila Train is a game similar to the Mexican Train game but has several differences in rules. Tequila Train involves a hub, trains, and tequila shots, with each player receiving between 4 to 8 tiles at the start. The game has a playable limit of 8 players using 91 tiles. Tequila Train Game Typically, the program's state undergoes alterations during its utilization. An integral feature of the software implementation of this game is the ability to maintain individual timers for each player and to closely monitor time intervals, thus increasing the dynamism and intrigue of the game. Such functionality is achieved through the utilization of [Delayed messages](/docs/developing/build/gstd/delayed-messages), which constitute one among several distinctive attributes of the Gear Protocol. Rules [#rules] Each player receives their dominoes, and the number of tiles they receive is automatically calculated based on the number of players. * 2...4 players: 8 tiles per each player * 5 players: 7 tiles per each player * 6 players: 6 tiles per each player * 7 players: 5 tiles per each player * 8 players: 4 tiles per each player Also, each player gets their own train differentiated by the color. Each player revise their tiles and see if they have matching dot numbers on both sides of one tile. 12 dots is the maximum number of each side. The player with the highest double places it in the center of the Tequila Train Hub. In case of nobody has a tile with matching numbers, each player will receive one additional tile, each player will continue receiving one additional tile till someone will receive the tile with the matching numbers on both sides. The domino must be placed so that one end is touching the end of a domino already on the table and such that the end of the new domino matches (shows the same number of dots) the end of the domino it is adjacent to. Unless the tile is a double, the tile can be placed square in any one of the three directions as long as the two matching sides are touching fully. First player only places the first tile (maximum double) and then the turn goes to the next player. If, in a later turn, the player draws a domino that enables them to start their train, they can only play this one domino. The player can remove their train but they are free to leave it as is (see below). If a player is unable to play and the boneyard is empty, the player must simply pass and ensure that a marker is placed upon their train. In case if a player doesn’t have a matching tile they put a train in their line and skip their turn. Since they have a train another player can put their tile in the line. For the next turn the player with the train can take additional tile from the tiles pool. Anyone who wants to get their train back after playing a domino to their track should drink one shot of tequila or they will leave their train on the board. The aim is to be the first player to get rid of all the dominoes in their hand. As soon as this happens, even if the last tile is a Double, the game ends. How to run [#how-to-run] Source code can be found in the [GitHub](https://github.com/gear-foundation/dapps/tree/master/contracts/tequila-train). 1. Build a program > Additional details regarding this matter can be located within the [README](https://github.com/gear-foundation/dapps/tree/master/contracts/tequila-train/README.md) directory of the program. 2. Upload the program to the [Vara Network Mainnet](https://idea.gear-tech.io/programs?node=wss%3A%2F%2Frpc.vara.network) > Further details regarding the process of program uploading can be located within the [Getting Started](/docs/vara-network/quick-start#deploy-your-program-to-vara-mainnet) section. 3. Build and run user interface > More information about this can be found in the [README](https://github.com/gear-foundation/dapps/blob/master/frontend/apps/tequila-train/README.md) directory of the frontend. Implementation details [#implementation-details] ```rust title="tequila-train/src/lib.rs" pub struct GameLauncher { pub games: HashMap, pub players_to_game_id: HashMap, pub config: Config, } ``` * `games` - list of all games according to the address of the game's creator * `player_to_game_id` - list of players and the games in which they participate * `config` - application configuration The configuration contains information about how much time participants are given to move, and how much gas is needed for delayed messages: ```rust title="tequila-train/io/src/lib.rs" pub struct Config { pub time_to_move: u32, pub gas_to_check_game: u64, } ``` `Game` information looks as follows: ```rust title="tequila-train/io/src/lib.rs" pub struct Game { pub admin: ActorId, pub game_state: Option, pub initial_players: Vec, pub state: State, pub is_started: bool, pub bid: u128, } ``` * `admin` - game administrator's address * `game_state` - all information is stored if the game has started * `initial_players` - addresses of all players in the game * `state` - game status * `is_started` - start flag * `bid` - gaming bet ```rust title="tequila-train/io/src/lib.rs" pub struct GameState { pub players: Vec, pub tracks: Vec, pub shots: Vec, pub start_tile: u32, pub current_player: u32, pub tile_to_player: BTreeMap, pub tiles: Vec, pub remaining_tiles: BTreeSet, pub time_to_move: u32, pub last_activity_time: u64, } ``` * `players` - all player information * `tracks` - track information * `shots` - number of shots of each player * `start_tile` - starter tile * `current_player` - player who is currently on a turn * `tile_to_player` - a list of tiles and their owners * `tiles` - list of all tiles * `remaining_tiles` - list of tiles that have not yet participated in the game * `time_to_move` - time to move * `last_activity_time` - time of last activity ```rust title="tequila-train/io/src/lib.rs" /// Information about the player's track pub struct TrackData { pub tiles: Vec, pub has_train: bool, } ``` ```rust title="tequila-train/io/src/lib.rs" /// Domino tile pub struct Tile { pub left: Face, pub right: Face, } ``` ```rust title="tequila-train/io/src/lib.rs" /// Tile's face (number of dots) pub enum Face { Zero, One, Two, Three, Four, Five, Six, Seven, Eight, Nine, Ten, Eleven, Twelve, } ``` ```rust title="tequila-train/io/src/lib.rs" /// The state of the game pub enum State { Playing, Winners(Vec), #[default] Registration, } ``` Initialization [#initialization] ```rust title="tequila-train/src/lib.rs" #[no_mangle] extern fn init() { let config: Config = msg::load().expect("Unable to decode the initial msg"); let game_launcher = GameLauncher { config, ..Default::default() }; unsafe { GAME_LAUNCHER = Some(game_launcher) }; } ``` Command [#command] ```rust title="tequila-train/io/src/lib.rs" pub enum Command { CreateGame, Skip { creator: ActorId, }, Place { creator: ActorId, tile_id: u32, track_id: u32, remove_train: bool, }, Register { creator: ActorId, }, CancelRegistration { creator: ActorId, }, DeletePlayer { player_id: ActorId, }, CheckGame { game_id: ActorId, last_activity_time: u64, }, StartGame, CancelGame, LeaveGame, } ``` Event [#event] ```rust title="tequila-train/io/src/lib.rs" pub enum Event { GameFinished { winners: Vec, all_participants: Vec, }, GameCreated, Skipped, Placed { tile_id: u32, track_id: u32, remove_train: bool, }, Registered { player: ActorId, }, RegistrationCanceled, PlayerDeleted { player_id: ActorId, }, GameStarted, GameCanceled, GameLeft, Checked, } ``` Game session creation: ```rust title="tequila-train/src/lib.rs" // creating a game session, after this action other users can register to the game using the creator's address. pub fn create_game(&mut self, msg_source: ActorId, msg_value: u128) -> Result { if self.players_to_game_id.contains_key(&msg_source) { return Err(Error::SeveralGames); } let mut game = Game { admin: msg_source, bid: msg_value, ..Default::default() }; game.initial_players.push(msg_source); self.games.insert(msg_source, game); self.players_to_game_id.insert(msg_source, msg_source); Ok(Event::GameCreated) } ``` After creating a game session other players can register for the game: ```rust title="tequila-train/src/lib.rs" pub fn register( &mut self, msg_source: ActorId, msg_value: u128, creator: ActorId, ) -> Result { if self.players_to_game_id.contains_key(&msg_source) { return Err(Error::SeveralGames); } let game = self .games .get_mut(&creator) .ok_or(Error::GameDoesNotExist)?; if game.is_started { return Err(Error::GameHasAlreadyStarted); } if msg_value != game.bid { return Err(Error::WrongBid); } if game.initial_players.contains(&msg_source) { return Err(Error::YouAlreadyRegistered); } if game.initial_players.len() >= 8 { return Err(Error::LimitHasBeenReached); } game.initial_players.push(msg_source); self.players_to_game_id.insert(msg_source, creator); Ok(Event::Registered { player: msg_source }) } ``` Program makes all preparations during the start of the game. 1. Each player gets their dominoes; the tile count is calculated automatically depending on the player count. 2. The program tries to find the maximum double through users. If it doesn’t, it adds one tile to each user and repeats this step until double has been found. 3. Program chooses the double and selects the first user. ```rust title="tequila-train/src/lib.rs" pub fn start(&mut self) -> Result { let msg_src = msg::source(); let game = self .games .get_mut(&msg_src) .ok_or(Error::GameDoesNotExist)?; if game.is_started { return Err(Error::GameHasAlreadyStarted); } if game.initial_players.len() < 2 { return Err(Error::NotEnoughPlayers); } game.is_started = true; game.game_state = GameState::new( game.initial_players.clone(), self.config.time_to_move, exec::block_timestamp(), ); game.state = State::Playing; // send a delayed message to check if the current player has made a move within the `config.time_to_move` limit msg::send_with_gas_delayed( exec::program_id(), Command::CheckGame { game_id: msg_src, last_activity_time: game.game_state.clone().unwrap().last_activity_time, }, self.config.gas_to_check_game, 0, self.config.time_to_move / 3000, ) .expect("Error in sending delayed message"); Ok(Event::GameStarted) } ``` Every player move is the command message sent to the program: 1. Pass: skip the turn if there is no tile to place. 2. Turn: place selected tile to the selected track. Additionally, in certain circumstances the player may get their train back. On a successful turn or pass, a delayed message is sent to check if the next player has made a move. This is done in order to thoroughly check the state of the game every `time_on_move` ```rust title="tequila-train/src/lib.rs" pub fn check_game( &mut self, game_id: ActorId, last_activity_time: u64, ) -> Result { let program_id = exec::program_id(); if msg::source() != program_id { return Err(Error::OnlyProgramCanSend); } let game = self .games .get_mut(&game_id) .ok_or(Error::GameDoesNotExist)?; let game_state = game .game_state .as_mut() .ok_or(Error::GameHasNotStartedYet)?; // use the `last_activity_time` variable as an identifier of whether a move has been made if game_state.last_activity_time == last_activity_time { let current_player = game_state.current_player; game_state.players[current_player as usize].lose = true; // count how many players are left in the game let count_players_is_live = game_state .players .iter() .filter(|&player| !player.lose) .count(); if count_players_is_live > 1 { // change the current player to the next player who has not dropped out of the game game_state.current_player = game_state .next_player(current_player) .expect("Live players more than 0"); // change the time of last activity game_state.last_activity_time = exec::block_timestamp(); msg::send_delayed( program_id, Command::CheckGame { game_id, last_activity_time: game_state.last_activity_time, }, 0, self.config.time_to_move / 3000, ) .expect("Error in sending delayed message"); } else { let winner_index = game_state .next_player(current_player) .expect("Live players more than 0"); let winner = game_state.players[winner_index as usize].id; let prize = game.bid; if game.bid != 0 { send_value(winner, prize * game.initial_players.len() as u128); } game.state = State::Winners(vec![winner]); msg::send( winner, Ok::(Event::GameFinished { winners: vec![winner], all_participants: game.initial_players.clone(), }), 0, ) .expect("Error in sending message"); } } Ok(Event::Checked) } ``` User interface gets the program state after every action and renders it in the browser. Program metadata and state [#program-metadata-and-state] Metadata interface description: ```rust title="tequila-train/io/src/lib.rs" pub struct ContractMetadata; impl Metadata for ContractMetadata { type Init = In; type Handle = InOut>; type Others = (); type Reply = (); type Signal = (); type State = InOut; } ``` To display the program state information, the `state()` function is used: ```rust title="tequila-train/src/lib.rs" #[no_mangle] extern fn state() { let game_launcher = unsafe { GAME_LAUNCHER .take() .expect("Game launcher is not initialized") }; let query: StateQuery = msg::load().expect("Unable to load the state query"); let reply = match query { StateQuery::All => StateReply::All(game_launcher.into()), StateQuery::GetGame { player_id } => { if let Some(creator_id) = game_launcher.players_to_game_id.get(&player_id) { let game_reply = game_launcher .games .get(creator_id) .map(|game| { let last_activity_time_diff = game.game_state.as_ref().and_then(|state| { (game_launcher.config.time_to_move as u64) .checked_sub(exec::block_timestamp() - state.last_activity_time) }); (game.clone(), last_activity_time_diff) }) .map(Some) .unwrap_or(None); StateReply::Game(game_reply) } else { StateReply::Game(None) } } }; msg::reply(reply, 0).expect("Failed to encode or reply with the game state"); } ``` Source code [#source-code] The source code of this example of Tequila-train Game program and the example of an implementation of its testing is available on [gear-foundation/dapp/contracts/tequila-train](https://github.com/gear-foundation/dapps/tree/master/contracts/tequila-train). See also an example of the program testing implementation based on `gtest`: [gear-foundation/dapps/tequila-train/tests](https://github.com/gear-foundation/dapps/tree/master/contracts/tequila-train/tests). For more details about testing programs written on Vara, refer to the [Program Testing](/docs/developing/build/testing) article. # Battleship Game battleship Battleship is a strategic board game in which two players compete to sink an opponent's fleet hidden from their view on the playing field. Players place their ships on a grid and then take turns shooting at squares on the opponent's field to locate and sink their ships. The goal of the game is to be the first to destroy all of your opponent's ships. The game of Battleship requires logical thinking, strategy, and luck. Players must position their ships to remain well hidden from the enemy, with each ship having a different length, adding variety to the tactics of the game. Battleship is a popular game that operates **entirely on-chain**. The primary game version does not have a backend or centralized components. However, there is an option to include [vouchers](/docs/developing/api/vouchers) that allow users to play with a zero tokens on their balance (**gasless transactions**). This requires activation of the backend to issue vouchers (instructions for voucher integration will be detailed in the article). Another feature of this example is an option that allos anyone to interact with the dApp without needing to sign in (**signless transactions**). The dApp transaction operates as usual with one difference — a voucher is issued not directly to a user, but to a temporarily created account (sub-account) to which the user grants temporary rights to sign transactions on their behalf in this application. These features allow users to interact with the game for a certain period without the need to individually sign each transaction. By eliminating the repetitive signing process, this approach streamlines interactions and significantly improves the overall UX efficiency. For more details about how to use Gasless and Signless features in your dApps, visit [**EZ-Transactions package**](/docs/developing/api/tooling/gasless-txs) article. The source code is available on [GitHub](https://github.com/gear-foundation/dapps/tree/master/contracts/battleship). This article describes the program interface, data structure, basic functions and explains their purpose. It can be used as is or modified to suit your own scenarios. The system consists of two programs: `game` and `bot`. To deploy them successfully to the [Vara Network Mainnet](https://idea.gear-tech.io/programs?node=wss%3A%2F%2Frpc.vara.network): 1. **Upload the bot program first.** 2. During the main game program's initialization, provide the bot's address (ID). Failure to follow this order will prevent the game from functioning correctly. How to run [#how-to-run] 1. Build a program > Additional details regarding this matter can be located within the [README](https://github.com/gear-foundation/dapps/tree/master/contracts/battleship/README.md) directory of the program. 2. Upload the program to the [Vara Network Mainnet](https://idea.gear-tech.io/programs?node=wss%3A%2F%2Frpc.vara.network) > Initiate the process by uploading the bot program, followed by the subsequent upload of the main program. Further details regarding the process of program uploading can be located within the [Getting Started](/docs/vara-network/quick-start#deploy-your-program-to-vara-mainnet) section. 3. Build and run user interface > More information about this can be found in the [README](https://github.com/gear-foundation/dapps/blob/master/frontend/apps/battleship/README.md) directory of the frontend. 4. **Optional**. Build and run the backend to release vouchers > Comprehensive instructions on the voucher execution process are provided within the [README](https://github.com/gear-foundation/dapps-battleship-backend). Implementation details [#implementation-details] Battleship program description [#battleship-program-description] The Battleship program contains the following information: ```rust title="battleship/src/contract.rs" struct Battleship { pub games: HashMap, pub msg_id_to_game_id: BTreeMap, pub bot_address: ActorId, pub admin: ActorId, pub sessions: HashMap, pub config: Config, } ``` * `games` - this field contains the addresses of the players and information about their games * `msg_id_to_game_id` - this field is responsible for tracking the bot's reply messages * `bot_address` - bot address * `admin` - admin address Where the structure of the "Game" is defined as follows ```rust title="battleship/io/src/lib.rs" pub struct Game { pub player_board: Vec, pub bot_board: Vec, pub player_ships: Ships, pub bot_ships: Ships, pub turn: Option, pub start_time: u64, pub end_time: u64, pub total_shots: u64, pub game_over: bool, pub game_result: Option, } ``` * `player_board` - is a vector with the status of cells of the player's board * `bot_board` - is a vector with the status of cells of the bot's board * `player_ships` - the location of the player ships * `bot_ships` - the location of the bot ships * `turn` - is a field indicating the turn queue of a move * `start_time` - game starting time * `end_time` - end game time * `total_shots` - number of shots * `game_over` - is a field indicating the end of the game * `game_result` - is a field indicating who won the game Field cells can take on different values: ```rust title="battleship/io/src/lib.rs" pub enum Entity { Empty, Unknown, Occupied, Ship, Boom, BoomShip, DeadShip, } ``` Initialization [#initialization] To initialize the game program, it only needs to be passed the bot's program address (this program will be discussed a little later) ```rust title="battleship/io/src/lib.rs" pub struct BattleshipInit { pub bot_address: ActorId, pub config: Config, } ``` Action [#action] ```rust title="battleship/io/src/lib.rs" pub enum BattleshipAction { // To start the game StartGame { ships: Ships, session_for_account: Option, }, // In order to make a move Turn { step: u8, session_for_account: Option, }, // Change the bot program (available only for admin) ChangeBot { bot: ActorId, }, // Clean the program state (available only for admin); // leave_active_games specifies how to clean it ClearState { leave_active_games: bool, }, // Deletion of a player's game DeleteGame { player_address: ActorId, }, CreateSession { key: ActorId, duration: u64, allowed_actions: Vec, }, DeleteSessionFromProgram { account: ActorId, }, DeleteSessionFromAccount, UpdateConfig { gas_for_start: Option, gas_for_move: Option, gas_to_delete_session: Option, block_duration_ms: Option, }, } ``` Reply [#reply] ```rust title="battleship/io/src/lib.rs" pub enum BattleshipReply { // Every time a message is sent to the bot's program. MessageSentToBot, // When the game is over EndGame(BattleshipParticipants), // When the bot's program address changes BotChanged(ActorId), SessionCreated, SessionDeleted, ConfigUpdated, } ``` Logic [#logic] Change to “After initialization of the program, the function to start the game will be available `BattleshipAction::StartGame { ships: Ships }`, where the location of the ships are transmitted. The program verifies the proper placement of ships and ensures the absence of any ongoing games for the user. Upon successful completion of these checks, the program initiates the game creation process and dispatches a message, `msg::send_with_gas`, to the bot program, prompting it to create the game. ```rust title="battleship/src/contract.rs" fn start_game(&mut self, mut ships: Ships, session_for_account: Option) { // ... let msg_id = msg::send_with_gas( self.bot_address, BotBattleshipAction::Start, self.config.gas_for_start, 0, ) .expect("Error in sending a message"); self.msg_id_to_game_id.insert(msg_id, player); msg::reply(BattleshipReply::MessageSentToBot, 0).expect("Error in sending a reply"); } ``` The `msg_id` is stored in the `msg_id_to_game_id` variable to use it in `handle_reply` (One of the message processing functions that Gear provides) ```rust title="battleship/src/contract.rs" #[no_mangle] extern fn handle_reply() { let reply_to = msg::reply_to().expect("Failed to query reply_to data"); let battleship = unsafe { BATTLESHIP.as_mut().expect("The game is not initialized") }; let game_id = battleship .msg_id_to_game_id .remove(&reply_to) .expect("Unexpected reply"); let game = battleship .games .get_mut(&game_id) .expect("Unexpected: Game does not exist"); ``` Using `handle_reply`, the bot retrieves response messages, while `msg_id_to_game_id` facilitates the retrieval of a particular game for state modification. After the game starts, the `BattleshipAction::Turn { step: u8 }` function becomes available for executing moves. This function requires the passage of the cell number at which the player intends to shoot. The program conducts thorough checks on the entered data and the game state to ensure a valid move can be made. If the checks pass successfully, the program updates the field states, shifts the turn to the bot and dispatches `msg::send_with_gas` to the bot program for it to make its move. ```rust title="battleship/src/contract.rs" fn player_move(&mut self, step: u8, session_for_account: Option) { // ... game.turn = Some(BattleshipParticipants::Bot); let board = game.get_hidden_field(); let msg_id = msg::send_with_gas( self.bot_address, BotBattleshipAction::Turn(board), self.config.gas_for_move, 0, ) .expect("Error in sending a message"); self.msg_id_to_game_id.insert(msg_id, player); msg::reply(BattleshipReply::MessageSentToBot, 0).expect("Error in sending a reply"); ``` Just as when starting a game, a reply message from the bot regarding its move is received in `handle_reply`. In summary, the interaction between the two programs is reduced to the ability to receive response messages in a separate function, denoted as `handle_reply()`. The whole implementation of the function looks as follows: ```rust title="battleship/src/contract.rs" #[no_mangle] extern fn handle_reply() { let reply_to = msg::reply_to().expect("Failed to query reply_to data"); let battleship = unsafe { BATTLESHIP.as_mut().expect("The game is not initialized") }; let game_id = battleship .msg_id_to_game_id .remove(&reply_to) .expect("Unexpected reply"); let game = battleship .games .get_mut(&game_id) .expect("Unexpected: Game does not exist"); let action: BattleshipAction = msg::load().expect("Failed to decode `BattleshipAction` message."); match action { BattleshipAction::StartGame { ships, session_for_account: _, } => game.start_bot(ships), BattleshipAction::Turn { step, session_for_account: _, } => { game.turn(step); game.turn = Some(BattleshipParticipants::Player); if game.player_ships.check_end_game() { game.game_over = true; game.game_result = Some(BattleshipParticipants::Bot); game.end_time = exec::block_timestamp(); msg::send( game_id, BattleshipReply::EndGame(BattleshipParticipants::Bot), 0, ) .expect("Unable to send the message about game over"); } } _ => (), } } ``` Bot program [#bot-program] As for the bot program, it has two functions: ```rust title="battleship/bot/src/lib.rs" pub enum BotBattleshipAction { Start, Turn(Vec), } ``` After receiving these messages in `handle()`, the program splits them into the appropriate Action and processes them accordingly. In the case of `BotBattleshipAction::Start`, the program takes on the task of generating a random field and then responds by providing the coordinates of the ship. In the other case of `BotBattleshipAction::Turn`, the program is responsible for obtaining the current state of the field, and after careful analysis, it formulates a response by providing the coordinate of the cell where the shot is fired. ```rust title="battleship/bot/src/lib.rs" #[no_mangle] extern fn handle() { let action: BotBattleshipAction = msg::load().expect("Unable to load the message"); match action { BotBattleshipAction::Start => { let ships = generate_field(); msg::reply( BattleshipAction::StartGame { ships, session_for_account: None, }, 0, ) .expect("Error in sending a reply"); } BotBattleshipAction::Turn(board) => { let step = move_analysis(board); msg::reply( BattleshipAction::Turn { step, session_for_account: None, }, 0, ) .expect("Error in sending a reply"); } } } ``` Program metadata and state [#program-metadata-and-state] Metadata interface description: ```rust title="battleship/io/src/lib.rs" pub struct BattleshipMetadata; impl Metadata for BattleshipMetadata { type Init = In; type Handle = InOut; type Others = (); type Reply = (); type Signal = (); type State = InOut; } ``` One of Gear's features is reading partial states. ```rust title="battleship/io/src/lib.rs" pub enum StateQuery { All, Game(ActorId), BotContractId, SessionForTheAccount(ActorId), } ``` ```rust title="battleship/io/src/lib.rs" pub enum StateReply { All(BattleshipState), Game(Option), BotContractId(ActorId), SessionForTheAccount(Option), } ``` To display the program state information, the `state()` function is used: ```rust title="battleship/src/contract.rs" #[no_mangle] extern fn state() { let battleship = unsafe { BATTLESHIP.take().expect("Unexpected error in taking state") }; let query: StateQuery = msg::load().expect("Unable to load the state query"); match query { StateQuery::All => { msg::reply(StateReply::All(battleship.into()), 0).expect("Unable to share the state"); } StateQuery::Game(player_id) => { let game_state = battleship.games.get(&player_id).map(|game| GameState { player_board: game.player_board.clone(), bot_board: game.bot_board.clone(), player_ships: game.player_ships.count_alive_ships(), bot_ships: game.bot_ships.count_alive_ships(), turn: game.turn.clone(), start_time: game.start_time, total_shots: game.total_shots, end_time: game.end_time, game_over: game.game_over, game_result: game.game_result.clone(), }); msg::reply(StateReply::Game(game_state), 0).expect("Unable to share the state"); } StateQuery::BotContractId => { msg::reply(StateReply::BotContractId(battleship.bot_address), 0) .expect("Unable to share the state"); } StateQuery::SessionForTheAccount(account) => { msg::reply( StateReply::SessionForTheAccount(battleship.sessions.get(&account).cloned()), 0, ) .expect("Unable to share the state"); } } } ``` Gaming sessions (signless transactions) [#gaming-sessions-signless-transactions] To further enhance the gaming experience and make it more user-friendly, the introduction of gaming sessions offers a significant improvement. Players now have the option to create a trusted game session, during which they are not required to sign every transaction individually. This trusted session creates a new temporary private key, valid for a specific time window, which empowers the temporary account to sign and send specific transactions on behalf of the player. These sessions allow users to establish predefined rules for interacting with a Dapp, offering the flexibility for unrestricted usage within these guidelines, eliminating the need to authorize each transaction separately. This approach not only facilitates a seamless Dapp experience but also ensures the security of assets, as users can specify the permissible actions for the Dapp. At the session's conclusion, whether it's the end of the indicated window or when the user decides to terminate it, the temporary key becomes obsolete and is discarded. To initiate a session, a player sends a message to the program (assuming the program supports this message type): {/* TODO: Uncomment `title` after adding the code to the master branch */} ```rust #title="battleship/src/contract.rs" CreateSession { key: ActorId, duration: u64, allowed_actions: Vec, }, ``` where: * `key` is the temporary account playing on the user's behalf; * `duration` is the time frame for the session's validity; * `allowed_actions` are the messages the temporary account can send. A session is structured as follows, allowing a player to set it up before starting the game: {/* TODO: Uncomment `title` after adding the code to the master branch */} ```rust #title="battleship/src/contract.rs" pub struct Session { // the address of the player who will play on behalf of the user pub key: ActorId, // until what time the session is valid pub expires: u64, // what messages are allowed to be sent by the account (key) pub allowed_actions: Vec, } ``` After creating a session, the player must also fund the temporary account, which can be done via a voucher. In this example, the messages: ```rust StartGame { ships: Ships }, Turn { step: u8 }, ``` are now expanded to: ```rust StartGame { ships: Ships, session_for_account: Option }, Turn { step: u8, session_for_account: Option }, ``` introducing the option to play either personally or using a pre-established game session. If `session_for_account` is `None`, the player plays on their own. If the game proceeds through a session, the address of the player who will be represented in the game should be specified in `session_for_account`. The program then verifies: * whether the specified player has a session; * whether the message sender matches the key of this session; * whether the session is still valid. Players have the option to delete their session before the game ends, further enhancing control and flexibility in the gaming experience. Source code [#source-code] The source code of this example of Battleship Game program and the example of an implementation of its testing is available on [gear-foundation/dapp/contracts/battleship](https://github.com/gear-foundation/dapps/tree/master/contracts/battleship). See also an example of the program testing implementation based on `gtest`: [gear-foundation/dapps/battleship/tests](https://github.com/gear-foundation/dapps/tree/master/contracts/battleship/tests). For more details about testing programs written on Vara, refer to the [Program Testing](/docs/developing/build/testing) article. [def]: /docs/developing/api/vouchers.md # ZK-Battleship Game Introduction [#introduction] [The previous paper](/docs/vara-network/examples/Gaming/Battleship) explored the implementation of a Battleship game, where all data, including the positions of players' ships, were openly stored on a blockchain. While this approach demonstrated the potential of decentralized gaming, it had a significant drawback: the ship positions were publicly accessible, undermining the strategic nature of the game. To address this issue and preserve the confidentiality of players' data, the new version of the game integrates **Zero-Knowledge Proofs (zk-SNARKs)**. With this technology, players' boards are stored locally, and no information about ship positions is transmitted to the network, ensuring both fairness and security in gameplay. The game example uses the [**EZ-Transactions package**](/docs/developing/api/tooling/gasless-txs) that simplifies blockchain interactions by enabling gasless and signless transactions. Anyone can use it to integrate into their dApp projects. For more details, visit the [GitHub page](https://github.com/gear-foundation/dapps/tree/vt-update-ez-transactions/frontend/packages/ez-transactions). What Are Zero-Knowledge Proofs? [#what-are-zero-knowledge-proofs] **Zero-Knowledge Proofs (zk-SNARKs)** are a cryptographic technology that allows one party to prove to another that a certain statement is true without revealing any additional information. In simple terms, zk-proofs enable a player to prove that they possess certain information (such as the correct arrangement of ships on their board) and are performing the required actions (like responding to an opponent’s move) without disclosing the information itself. This ensures the privacy and security of the game while maintaining its functionality and fairness. Implementation details [#implementation-details] The source code is available on [GitHub](https://github.com/gear-foundation/dapps/tree/master/contracts/zk-battleship). This article describes the program interface, data structure, basic functions and explains their purpose. It can be used as is or modified to suit your own scenarios. Problem Statement [#problem-statement] The game of Battleship inherently relies on the mutual trust between players to accurately place their ships on the board and honestly record hits on their ships. The necessary guarantees for a fair game are as follows: 1. **Valid Board Configuration**: Each player's board must be valid, meaning the ships are correctly placed according to the game's rules. 2. **Board Integrity**: The board configuration of each player must remain unchanged throughout the entire game. 3. **Opponent's Board Privacy**: A player must not be able to see their opponent's board at any point in the game. 4. **Honest Reporting**: A player should not be able to lie about whether an opponent's guess was a hit or a miss. These guarantees ensure a fair and strategic gameplay experience while maintaining the confidentiality of each player’s board. Circom and Zero-Knowledge Proofs [#circom-and-zero-knowledge-proofs] To implement these guarantees, let 's turn to [Circom](https://iden3.github.io/circom/), a domain-specific language for creating Zero-Knowledge circuits. Circom allows us to generate zk-SNARKs, enabling us to create proofs that verify the above conditions without revealing any information about the board's configuration or the game state. Using Circom, it is possible to create a system in which: * Each player's board is verified to be valid at the start of the game. * The integrity of the board is maintained, ensuring no alterations are made during gameplay. * Players can prove the result of each opponent’s guess without revealing the underlying board configuration. * These proofs can be verified by the opponent or a neutral party without compromising the privacy of the game. The next section will discuss, the implementation of the necessary schemes in the Circom language to create the required zk-proofs to ensure a fair and secure Battleship game. Battleship Placement Proof in Circom [#battleship-placement-proof-in-circom] The provided Circom code implements a Zero-Knowledge proof to validate the placement of ships in a Battleship game. The goal is to ensure that the ships are correctly placed according to the game rules, without revealing the actual positions of the ships. Inputs [#inputs] ```text template BattleshipPlacement(N1, N2, N3, N4) { signal input ship_1[N1]; signal input ship_2[N2]; signal input ship_3[N3]; signal input ship_4[N4]; signal input hash; ``` * **Ship Coordinates**: `ship_1`, `ship_2`, `ship_3`, and `ship_4` represent the coordinates of the ships on the board. * **Hash**: is a cryptographic hash of all the ship coordinates combined. Example input: ```json { "ship_1": ["4"], "ship_2": ["15", "20"], "ship_3": ["5", "6"], "ship_4": ["13", "18", "23"], "hash": "52217276415269335215074497398359620454856162108003890482549690012292772807126" } ``` Key Components and Logic [#key-components-and-logic] 1. **Uniqueness of Coordinates:** The code uses the `IsUniqueArray` template to ensure that each ship’s coordinates are unique, preventing overlapping or duplicate placements. ```text template IsUniqueArray(N) { signal input arr[N]; for (var i = 0; i < N; i++) { for (var j = i + 1; j < N; j++) { assert(arr[i] != arr[j]); } } } ``` 2. **Edge Constraints:** The templates `NotRightEdge`, `NotLeftEdge`, and `NotHalfEdge` are utilized as utility components to assist in validating board conditions within the game. These templates do not directly prevent the placement of ships at the board's edges; rather, they help ensure that subsequent checks and operations involving coordinates are correctly applied based on the board's structure. * NotRightEdge: This template checks if a given coordinate is not on the right edge of the board. It does so by comparing the coordinate against specific values corresponding to the rightmost cells in each row. If the coordinate is not at the right edge, the output is 1, otherwise it's 0. * NotLeftEdge: Similarly, this template checks if a coordinate is not on the left edge of the board by comparing against the leftmost cells in each row. If the coordinate is not on the left edge, the output is 1, otherwise it's 0. * NotHalfEdge: This template is used to check if a coordinate is not near a boundary that would cause wrapping when calculating neighboring cells. It compares the coordinate against specific values that are susceptible to such wrapping issues. If the coordinate is not near such an edge, the output is 1. `NotRightEdge()` template example: ```text template NotRightEdge() { signal input coord; signal output out; component is_equal_4 = IsEqual(); is_equal_4.in[0] <== coord; is_equal_4.in[1] <== 4; // Additional edge checks... out <== (1 - is_equal_4.out - ...); } ``` These templates are then incorporated into more complex validation logic, such as: * `CheckDistance`: This function uses the `NotRightEdge` and `NotLeftEdge` templates to adjust the calculation of surrounding cell coordinates based on the position of the cell on the board. This ensures that the calculation correctly respects the board's boundaries and prevents erroneous checks across the edges. * `IntegrityEdgeSize2` and `IntegrityEdgeSize3`: These functions verify that a ship of size 2 or 3 is placed correctly on the board, without wrapping around edges. The `NotRightEdge` and `NotHalfEdge` templates are used to ensure that the coordinates of the ship's cells are aligned either horizontally or vertically within the bounds of the board. 3. **Distance Check Between Ships:** The `CheckDistance` template is used to ensure that ships are not placed too close to each other. This is done by checking the surrounding cells of each coordinate to ensure no other ship occupies these cells. ```text template CheckDistance(N) { signal input coord; signal input arr[N]; signal cells[9]; component not_left_edge = NotLeftEdge(); not_left_edge.coord <== coord; // Additional distance checks... for (var i = 0; i < 8; i++) { for (var j = 0; j < N; j++) { assert(cells[i] != arr[j]); } } } ``` 4. **Ship Integrity:** The `IntegrityEdgeSize2`, `IntegrityEdgeSize3`, and `Integrity` templates ensure that each ship is placed in a continuous line either horizontally or vertically and does not "break up" due to edge constraints. ```text template IntegrityEdgeSize2() { signal input arr[2]; component not_edge = NotRightEdge(); not_edge.coord <== arr[0]; var horizontal = (arr[1] == arr[0] + 1) * not_edge.out; var vertical = (arr[1] == arr[0] + 5); assert((horizontal+vertical) == 1); } ``` 5. **Poseidon Hash for Commitment:** The use of the `Poseidon` hash function allows the players to commit to their board configuration in a way that can later be verified without revealing the actual configuration. ```text component poseidon = Poseidon(N1 + N2 + N3 + N4); for (var i = 0; i < N1 + N2 + N3 + N4; i++) { poseidon.inputs[i] <== combinedShips[i]; } hash === poseidon.out; ``` 6. **Main Circuit:** The `BattleshipPlacement` template brings all these components together, taking in the coordinates of the ships and producing a hash as the public output. It checks for valid ship placement, non-overlapping ships, and compliance with the game rules. ```text component main {public [hash]} = BattleshipPlacement(1, 2, 2, 3); ``` This code provides a comprehensive circuit for verifying Battleship game board setups using zk-SNARKs. It ensures that ship placements are valid, the board configuration is committed to with a Poseidon hash, and that the integrity and distance rules of the game are upheld. This approach allows third parties or opponents to verify the board without revealing the actual positions of the ships, preserving the secrecy and fairness of the game.\ The full code discussed above can be found on [GitHub](https://github.com/gear-foundation/dapps/tree/master/contracts/zk-battleship/circom/placement.circom). Battleship Hit Proof in Circom [#battleship-hit-proof-in-circom] The `BattleshipHit` circuit is designed to verify if a shot fired in a game of Battleship hits a ship, misses entirely, or sinks the ship. It accepts the coordinates of multiple ships, previous hit coordinates, and a new shot coordinate, and outputs a result indicating whether the shot was a miss, a hit, or a sinking blow. Inputs and Outputs [#inputs-and-outputs] ```text template BattleshipHit(N1, N2, N3, N4, MAX_HITS) { signal input ship_1[N1]; signal input ship_2[N2]; signal input ship_3[N3]; signal input ship_4[N4]; signal input hits[MAX_HITS]; // Previous hits signal input hit; // Current shot coordinate signal input hash; // Hash on each element of coordinates signal output is_hit; // 0 = miss, 1 = hit, 2 = sunk ``` * **Ship Coordinates**: `ship_1`, `ship_2`, `ship_3`, and `ship_4` represent the coordinates of the ships on the board. * **Hit Coordinates**: `hits` stores the coordinates of previous shots, and `hit` is the current shot coordinate. * **Hash**: is a cryptographic hash of all the ship coordinates combined. * **Output (`is_hit`)**: The circuit outputs `0` for a miss, `1` for a hit, and `2` for a sunk ship. Example input: ```json { "ship_1": ["4"], "ship_2": ["15", "20"], "ship_3": ["5", "6"], "ship_4": ["13", "18", "23"], "hits": ["18", "23", "15", "5", "-1", "-1", "-1", "-1", "-1", "-1", "-1", "-1", "-1", "-1", "-1", "-1", "-1", "-1", "-1", "-1", "-1", "-1", "-1", "-1", "-1"], "hit": "6", "hash": "52217276415269335215074497398359620454856162108003890482549690012292772807126" } ``` Key Components and Logic [#key-components-and-logic-1] 1. **Hash Verification** The combined ship coordinates are hashed using the Poseidon hash function, and the result is compared with the provided hash to verify the validity of the ship positions. ```text component poseidon = Poseidon(N1 + N2 + N3 + N4); for (var i = 0; i < N1 + N2 + N3 + N4; i++) { poseidon.inputs[i] <== combinedShips[i]; } hash === poseidon.out; ``` 2. **Hit Detection** For each ship, the circuit checks whether the current shot matches any of the ship's coordinates and counts the number of hits. It also tracks previous hits on each ship. ```text // Check hits on ship 1 var hits_on_ship1 = 0; component is_equal_ship1[N1]; for (var j = 0; j < N1; j++) { is_equal_ship1[j] = IsEqual(); is_equal_ship1[j].in[0] <== hit; is_equal_ship1[j].in[1] <== ship_1[j]; hits_on_ship1 += is_equal_ship1[j].out; } signal hit_ship1 <== hits_on_ship1; // ... (similarly for ship_2, ship_3, and ship_4) ``` 3. **Sinking Verification** The circuit calculates the total hits on each ship by summing previous hits with the current hit and then checks if this total equals the ship's length, indicating the ship is sunk. ```text component ge_total_hits1 = GreaterEqThan(N1); ge_total_hits1.in[0] <== total_hits_on_ship1; ge_total_hits1.in[1] <== N1; signal sunk_ship1 <== ge_total_hits1.out; // ... (similarly for ship_2, ship_3, and ship_4) ``` 4. **Determining the Final Result** The circuit uses a series of `Mux1` components to determine the final output (`is_hit`). It checks whether each ship was hit or sunk and sets the output accordingly. ```text component mux_main4 = Mux1(); mux_main4.c[0] <== mux_main3.out; mux_main4.c[1] <== mux_hit4.out; mux_main4.s <== hit_ship4; is_hit <== mux_main4.out; ``` This circuit verifies if a shot in Battleship hits a ship and whether that ship is sunk. It uses a series of equality checks and logic gates to aggregate hits, compare them against ship coordinates, and output the correct result.\ The full code discussed above can be found on [GitHub](https://github.com/gear-foundation/dapps/tree/master/contracts/zk-battleship/circom/hit.circom). Proof Generation and Verification Key Details [#proof-generation-and-verification-key-details] In zk-SNARKs the proof generation and verification process is essential for maintaining privacy and integrity in decentralized systems. Proof Generation [#proof-generation] The process of generating a zk-SNARK proof involves compiling a circuit, calculating a witness, and then using a proving key to create the proof. For a detailed explanation of witness generation, you can refer to the steps [here](https://docs.circom.io/getting-started/computing-the-witness/#what-is-a-witness), and for the proof generation process, see this [link](https://docs.circom.io/getting-started/proving-circuits/). Verification Key Generation [#verification-key-generation] A verification key is generated during the setup phase, which corresponds to the specific circuit and proving key. This verification key is then provided to the program during its initialization, enabling the program to verify zk-SNARK proofs submitted by users. Verification Process in Programs [#verification-process-in-programs] When proving circuits with ZK, two key outputs are generated: proof and public. * Proof: This is the zk-SNARK proof, a cryptographic proof that verifies the correctness of the computation. * Public: This contains the public inputs or outputs of the circuit. These are open data that the program uses for verification. For instance, this could include information about a hit or miss in a game, or a hash representing the ship placement. The program uses this data to validate the proof and ensure consistency, such as checking that the ship placement hash remains unchanged throughout the game. These outputs are then passed to the program, which securely verifies the computations without revealing private data, ensuring transparent and fair interactions. Program Workflow Explanation [#program-workflow-explanation] The program's primary responsibility in the Battleship game is to verify zero-knowledge proofs (ZKPs) that ensure the game's integrity while maintaining player privacy. The program is initialized with verification keys that are essential for validating these proofs during gameplay. Program Initialization [#program-initialization] During the program's initialization, the following parameters are passed: ```rust constructor { New : ( verification_key_for_start: VerifyingKeyBytes, verification_key_for_move: VerifyingKeyBytes, // other initialization fields ); }; ``` * **verification\_key\_for\_start**: This key is used to verify the proof of the initial ship placement. * **verification\_key\_for\_move**: This key is used to verify the proof for each move or shot taken during the game. Data Structures for Verification [#data-structures-for-verification] The program uses several data structures to handle verification processes: ```rust pub struct VerifyingKeyBytes { pub alpha_g1_beta_g2: Vec, pub gamma_g2_neg_pc: Vec, pub delta_g2_neg_pc: Vec, pub ic: Vec>, } ``` This structure represents the verification keys, which are necessary to validate proofs. Ship Placement Verification [#ship-placement-verification] When verifying the placement of ships, the program receives: ```rust proof: services::verify::ProofBytes, public_input: services::verify::PublicStartInput, ``` **ProofBytes**: Contains the proof generated by the player. ```rust pub struct ProofBytes { pub a: Vec, pub b: Vec, pub c: Vec, } ``` * `a`, `b`, and `c`: These fields represent the proof components in the elliptic curve pairing used in ZKPs. **PublicStartInput**: Contains the public input required for verification. ```rust pub struct PublicStartInput { pub hash: Vec, } ``` * `hash`: This is the hash of the ship placement, ensuring that the layout remains confidential while allowing the program to verify its correctness. Move Verification [#move-verification] For each move the program processes the following: ```rust pub struct PublicMoveInput { pub out: u8, pub hit: u8, pub hash: Vec, } ``` * `out`: Represents the outcome of the shot, where 0 indicates a miss, 1 indicates a hit, and 2 indicates a sunk ship. * `hit`: The coordinate of the attempted shot. * `hash`: The hash of the ship placement, which ensures that the integrity of the board state is maintained throughout the game. Verification Process [#verification-process] The verification process is central to the program's function. When a player submits a move or sets up their ships, the program checks the provided proof against the public inputs using the stored verification keys. If the proof is valid, the program acknowledges the action as legitimate; otherwise, it rejects the action. This process ensures game integrity by preventing players from altering ship placements or falsifying shot outcomes. By moving the logic on-chain, the game maintains a **trustless environment** where cheating is technically impossible. Use of BLS12-381 in the Program [#use-of-bls12-381-in-the-program] The program leverages the BLS12-381 elliptic curve for verifying zero-knowledge proofs, which is a computationally intensive process. BLS cryptography is known for its ability to efficiently handle signature aggregation and verification at scale using Elliptic Curve cryptography. In the context of this program, performing BLS computations directly on-chain would significantly increase gas costs and processing time, potentially requiring over 30 blocks on the Vara network to complete. To address this, the program utilizes the [BLS12-381 Built-in Actor](/docs/developing/build/builtinactors/bia-bls), a specialized module integrated into the Vara runtime. This module allows the program to offload BLS cryptographic operations to be executed in native mode off-chain by the validators. By sending a message with the necessary arguments to the BLS12-381 actor's address, the program ensures that these intensive computations are processed efficiently and within the single block time of the Vara network (which is 3 seconds). Once the computation is complete, the result is returned to the program, minimizing on-chain resource usage and reducing gas costs. Code Explanation [#code-explanation] The following Rust code snippet illustrates the process: ```rust // Verifies the proof by comparing the result of the exponentiation with the expected value. let miller_out = calculate_multi_miller_loop(a.encode(), b.encode(), builtin_bls381_address).await; let exp = calculate_exponentiation(miller_out, builtin_bls381_address).await; if exp != alpha_g1_beta_g2 { ext::panic("Verification failed"); } ``` 1. **Multi-Miller Loop Calculation:** The first step involves calculating the multi-Miller loop using the G1 and G2 points (`a` and `b`) of the proof. This operation is offloaded to the BLS12-381 Built-in Actor for efficient execution. ```rust async fn calculate_multi_miller_loop( g1: Vec, g2: Vec, builtin_bls381_address: ActorId, ) -> Vec { let request = Request::MultiMillerLoop { a: g1, b: g2 }.encode(); let reply = msg::send_bytes_for_reply(builtin_bls381_address, &request, 0, 0) .expect("Failed to send message") .await .expect("Received error reply"); let response = Response::decode(&mut reply.as_slice()).expect("Error: decode response"); match response { Response::MultiMillerLoop(v) => v, _ => unreachable!(), } } ``` * A request is encoded and sent to the BLS12-381 Built-in Actor. * The response is awaited and decoded to retrieve the output of the Miller loop. 2. **Final Exponentiation:** After obtaining the result from the Miller loop, the next step is to perform the final exponentiation, which transforms the pairing result into a form that can be compared to the expected value. ```rust async fn calculate_exponentiation( f: Vec, builtin_bls381_address: ActorId, ) -> ArkScale<::TargetField> { let request = Request::FinalExponentiation { f }.encode(); let reply = msg::send_bytes_for_reply(builtin_bls381_address, &request, 0, 0) .expect("Failed to send message") .await .expect("Received error reply"); let response = Response::decode(&mut reply.as_slice()).expect("Error: decode response"); let exp = match response { Response::FinalExponentiation(v) => { ArkScale::<::TargetField>::decode(&mut v.as_slice()) .expect("Error: decode ArkScale") } _ => unreachable!(), }; exp } ``` * Similar to the Miller loop, a request is sent to the BLS12-381 Built-in Actor to perform the exponentiation. * The result is decoded and compared with the expected `alpha_g1_beta_g2` value. If the result does not match, the verification fails. This implementation leverages **off-chain computation** via the **BLS12-381 Built-in Actor**. By offloading complex cryptographic operations to the runtime, the program significantly **optimizes performance** and **reduces gas costs**. Overview of Application Workflow [#overview-of-application-workflow] This diagram represents the overall workflow of an application that integrates zero-knowledge proofs (ZKPs) into a decentralized environment. The workflow is divided into three main sections: **Circuit**, **Program**, and **Frontend**, each of which plays a crucial role in the end-to-end process of generating, verifying, and utilizing ZKPs within the application. zk general scheme 1. Circuit [#1-circuit] * Write Circuit:\ The workflow starts with defining the circuit, which sets the logical constraints and conditions necessary for the zero-knowledge proof. This circuit acts as the mathematical foundation of the proof system. * Compile to Intermediate Representation:\ After the circuit is written, it is compiled into an intermediate format. This representation acts as a transitional phase between the abstract design of the circuit and its practical application in key generation. * Powers of Tau Trusted Setup:\ A trusted setup, commonly known as "Powers of Tau," is conducted to produce a setup file. This step is essential for establishing the security and integrity of the zero-knowledge proof system. * Generate Proving Key:\ Using the intermediate representation and the trusted setup file, a proving key is generated. This key is essential for producing proofs that the program can subsequently verify. 2. Program [#2-program] * Program initialization with verifier: The program is initialized with a verifier that can check the validity of the proofs generated by the circuit. This verifier is crucial for integrating the proof system into the program. * Deployed program: The program, along with the verifier, is then deployed. This makes the verification process available for real-time interactions. * Transaction result: When the proof is sent, the program processes the transaction and produces a result. This step not only verifies the proof, but also updates the state of the game or application based on the verified proof. 3. Frontend [#3-frontend] * User inputs in frontend: Users interact with the application through the frontend, providing the necessary inputs that will be used to generate the proof. * Calculate witness and generate proof: Based on the user inputs, the frontend calculates the witness (the private inputs) and generates a proof. This proof encapsulates the information required for the verifier to confirm the validity of the transaction. * Submit transaction with proof: * The generated proof is submitted to the deployed program as a transaction. The program then verifies this proof using the verifier. * Frontend update: Based on the result of the transaction, the frontend is updated to reflect the outcome, providing feedback to the user. This structured workflow ensures that the application can securely and efficiently handle zero-knowledge proofs, from circuit creation to proof verification and user interaction. Conclusion [#conclusion] Overall, this application demonstrates how advanced cryptographic techniques like ZKPs can be effectively incorporated into interactive applications, ensuring high security and data integrity without revealing sensitive information. For more details, refer to the [GitHub](https://github.com/gear-foundation/dapps/tree/master/contracts/zk-battleship) repository. # Dia Randomness Oracle Randomness in Blockchain [#randomness-in-blockchain] Randomness in blockchains is crucial for a fair and unpredictable distribution of validator responsibilities. Computers are deterministic devices, meaning the same input always produces the same output, making true randomness difficult to achieve. What is commonly referred to as random numbers on a computer are actually pseudo-random. These numbers depend on a sufficiently random seed provided by the user or another type of oracle, such as a weather station for atmospheric noise, heart rate, or even lava lamps. From this seed, a series of seemingly random numbers can be generated, but given the same seed, the same sequence will always be produced. However, there are distributed systems that try to solve the problem of "predictability" of randomness, one of which is [drand](https://drand.love/). The given dia-oracle implementation is an example of safe randomization. The source code, developed using the [Sails](/docs/developing/build/sails) framework, is available on [GitHub](https://github.com/gear-foundation/dapps/tree/master/contracts/oracle/randomness). State [#state] The `Random` structure stores a random value, its cryptographic signature, and the signature of the previous value, ensuring integrity and traceability. ```rust title="oracle/randomness/app/src/lib.rs" pub struct Random { pub randomness: RandomSeed, pub signature: String, pub prev_signature: String, } ``` The `RandomnessOracle` structure maintains the overall oracle state, tracking ownership, stored random values, the last updated round, and the manager responsible for updates. ```rust title="oracle/randomness/app/src/lib.rs" pub struct RandomnessOracle { pub owner: ActorId, pub values: HashMap, pub last_round: u128, pub manager: ActorId, } ``` Actions [#actions] ```rust title="oracle/randomness/app/src/lib.rs" pub fn set_random_value(&mut self, round: u128, value: Random); pub fn get_last_round_with_random_value(&mut self) -> (u128, u128); pub fn update_manager(&mut self, new_manager: ActorId); ``` * The `set_random_value` method securely assigns a new random value to a specific round, ensuring sequential updates. * The `get_last_round_with_random_value` method retrieves the most recent round and its associated random value. * The `update_manager` method changes the manager, allowing controlled delegation of responsibility. ```rust title="oracle/randomness/app/src/lib.rs" pub fn get_owner(&self) -> ActorId; pub fn get_last_round(&self) -> u128; pub fn get_manager(&self) -> ActorId; pub fn get_values(&self) -> Vec<(u128, Random)>; ``` Methods like `get_owner`, `get_last_round`, `get_manager`, and `get_values` provide convenient access to the oracle's current state. Events [#events] The `Event` enum defines key events: updating the manager, adding a new random value, and retrieving the last round's random value, ensuring clear system behavior tracking. ```rust title="oracle/randomness/app/src/lib.rs" pub enum Event { NewManager(ActorId), NewRandomValue { round: u128, value: Random }, LastRoundWithRandomValue { round: u128, random_value: u128 }, } ``` Initialisation [#initialisation] The `init` method initializes the oracle with the given manager and sets up its default state, defining ownership and control. ```rust title="oracle/randomness/app/src/lib.rs" pub async fn init(manager: ActorId) -> Self { unsafe { RANDOMNESS_ORACLE = Some(RandomnessOracle { owner: msg::source(), manager, ..Default::default() }); } Self(()) } ``` Conclusion [#conclusion] The source code of this example of a Dia Randomness Oracle program is available on GitHub: [gear-foundation/dapps/contracts/oracle/randomness](https://github.com/gear-foundation/dapps/blob/master/contracts/oracle/randomness). See also an example of the program testing implementation based on `gtest`: [gear-foundation/dapps/contracts/oracle/randomness/tests/gtest.rs](https://github.com/gear-foundation/dapps/blob/master/contracts/oracle/randomness/tests/gtest.rs). For more details about testing programs written on Vara, refer to this article: [Program Testing](/docs/developing/build/testing). # Vara Oracle What is Oracle? [#what-is-oracle] Blockchain oracles are a combination of programs (smart contracts) and off-chain entities that connect blockchains to external systems (APIs, etc.), allowing other programs to execute depending on real-world inputs and outputs. Oracles give the Web3 ecosystem a method to connect to existing legacy systems, data sources, and advanced calculations. These programs can obtain external data which can't exist in blockchain space. In general, oracles are used for: * Fetching aggregated token prices in fiat (USD, EUR, etc.) * Querying Web2 APIs * Obtaining prices for different securities Moreover, oracles allow the creation of lending/DEX protocols, which form an important part of DeFi. This article provides an example of the native implementation of a randomness oracle, which provides the ability to use random numbers in programs. It can be used as is or modified to suit specific scenarios. Anyone can easily create their own oracle and run it on the Vara Network. The source code, developed using the [Sails](/docs/developing/build/sails) framework, is available on [GitHub](https://github.com/gear-foundation/dapps/tree/master/contracts/oracle). Storage Structure [#storage-structure] The `Oracle` structure defines the storage for the program, including the owner, the manager responsible for operations, and optional [dDNS](/docs/vara-network/examples/Infra/dein) information. ```rust title="oracle/app/src/lib.rs" pub struct Oracle { owner: ActorId, manager: ActorId, dns_info: Option<(ActorId, String)>, } ``` Action [#action] The oracle exposes methods to interact with its state and fetch randomness values from the designated manager. ```rust title="oracle/app/src/lib.rs" pub async fn request_value(&mut self) -> u128; pub fn change_manager(&mut self, new_manager: ActorId); ``` * `request_value`: Asynchronously retrieves the latest random value from the manager and emits a `NewValue` event. * `change_manager`: Updates the manager responsible for providing randomness values, ensuring operational flexibility. The oracle also provides accessors to retrieve its internal state: ```rust title="oracle/app/src/lib.rs" pub fn get_owner(&self) -> ActorId; pub fn get_manager(&self) -> ActorId; pub fn get_dns_info(&self) -> Option<(ActorId, String)>; ``` * `get_owner`: Returns the oracle owner's ActorId. * `get_manager`: Returns the current manager's ActorId. * `get_dns_info`: Retrieves the DNS integration details. Event [#event] The `Event` enum captures significant actions within the oracle, enabling better system monitoring and interaction. ```rust title="oracle/app/src/lib.rs" #[derive(Debug, Encode, Decode, TypeInfo)] pub enum Event { NewValue { value: u128 }, NewManager(ActorId), } ``` * `NewValue`: Emitted when a new random value is fetched and stored. * `NewManager`: Triggered when the manager is changed, indicating a shift in operational responsibility. Conclusion [#conclusion] The source code of this example of a Vara Oracle program is available on GitHub: [gear-foundation/dapps/contracts/oracle](https://github.com/gear-foundation/dapps/tree/master/contracts/oracle). See also an example of the program testing implementation based on `gtest`: [gear-foundation/dapps/contracts/oracle](https://github.com/gear-foundation/dapps-oracle/tree/wip/oracle/tests). For more details about testing programs written on Vara, refer to this article: [Program Testing](/docs/developing/build/testing). # NFT Marketplace img alt NFT marketplace is a program where anyone can buy and sell non-fungible tokens for fungible tokens. The program also supports holding the NFT auctions and making/accepting purchase offers on NFTs. The following are program examples available on GitHub: * [Vara Non-Fungible Token](https://github.com/gear-foundation/standards/tree/master/extended-vnft). * [Vara Fungible Token](https://github.com/gear-foundation/standards/tree/master/extended-vft). * [NFT marketplace](https://github.com/gear-foundation/dapps/tree/master/contracts/nft-marketplace). * Marketplace UI available on [Github](https://github.com/gear-foundation/dapps/tree/master/frontend/apps/nft-marketplace) Anyone can easily create their own NFT marketplace application and run it on Vara Network. The project code is built using the [Sails](/docs/developing/build/sails) framework, which simplifies program development on Gear Protocol. How to run [#how-to-run] ⚒️ Build programs [#️-build-programs] * Build [NFT contract](https://github.com/gear-foundation/standards/tree/master/extended-vnft) as described in `README.md` * Build [Marketplace program](https://github.com/gear-foundation/dapps/tree/a357635b61e27c52f46908885fecb048dc8424e5/contracts/nft-marketplace) as described in `README.md` 🏗️ Upload programs [#️-upload-programs] A program can be deployed to the network using [idea.gear-tech.io](https://idea.gear-tech.io/). In the network selector, choose `Vara Network Mainnet` or `Development` (in this case, the application will connect to a locally running Vara node). 🖥️ Run UI [#️-run-ui] 1. Install packages npm pnpm yarn bun ```bash npm install ``` ```bash pnpm install ``` ```bash yarn install ``` ```bash bun install ``` 2. Configure .evn file. Specify network address and program ID like in the example below: For proper application functioning, one needs to adjust an environment variable parameters. An example is available [here](https://github.com/gear-foundation/dapps/blob/master/frontend/apps/nft-marketplace/.env.example). ```sh REACT_APP_NODE_ADDRESS=wss://rpc.vara.network REACT_APP_IPFS_ADDRESS=https://stg-pinata-proxy.vara.network/files/upload REACT_APP_IPFS_GATEWAY_ADDRESS=https://gear.mypinata.cloud/ipfs REACT_APP_MARKETPLACE_CONTRACT_ADDRESS=0xe33f5124f1200f6cd728acc14f7dfcd6e59d584271415f15dbe9106af7e2d1e6 REACT_APP_NFT_CONTRACT_ADDRESS=0x3d3d0b5c597d6d767294cc93e0a3489d848ae32cbf851fa40756800d28e4cd37 ``` * `REACT_APP_NODE_ADDRESS` is Vara Network address (for mainnet it is - `wss://rpc.vara.network`) * `REACT_APP_IPFS_ADDRESS` is address of IPFS to store NFT assets ([https://stg-pinata-proxy.vara.network/files/upload](https://stg-pinata-proxy.vara.network/files/upload) that was used for this implementation) * `REACT_APP_IPFS_GATEWAY_ADDRESS` is IPFS Gateway address ([https://gear.mypinata.cloud/ipfs](https://gear.mypinata.cloud/ipfs)) * `REACT_APP_MARKETPLACE_CONTRACT_ADDRESS` is NFT Marketplace program address in Vara Network * `REACT_APP_NFT_CONTRACT_ADDRESS` is Vara Non-Fungible Token contract address in Vara Network 3. Run app ```sh yarn start ``` Marketplace program logic [#marketplace-program-logic] This article explains the programming interface, data structure, basic functions and explains their purpose. It can be used as is or modified to suit your own scenarios. Logic [#logic] The program state: ```rust title="nft-marketplace/app/src/utils.rs" pub struct Market { pub admin_id: ActorId, pub items: HashMap<(ContractId, TokenId), Item>, pub approved_nft_contracts: HashSet, pub approved_ft_contracts: HashSet, } ``` * `admin_id` - an account who has the right to approve non-fungible-token and fungible-tokens contracts that can be used in the marketplace program; * `items` - listed NFTs; * `approved_nft_contracts` - NFT contracts accounts that can be listed on the marketplace; * `approved_ft_contracts` - fungible token accounts for which it is possible to buy marketplace items; The marketplace item has the following struct: ```rust title="nft-marketplace/app/src/utils.rs" pub struct Item { pub frozen: bool, pub token_id: TokenId, pub owner: ActorId, pub ft_contract_id: Option, pub price: Option, pub auction: Option, pub offers: HashMap<(Option, Price), ActorId>, } ``` * `frozen` is freeze indicator (required for asynchronous messages) * `token_id` is the ID of the NFT within its contract * `owner` - an NFT item owner * `ft_contract_id` - a contract of fungible tokens for which that item can be bought. If that field is `None` then the item can be sold for native Vara tokens * `price` - the item price. `None` field means that the item is not on the sale * `auction` - a field containing information on the current auction. `None` field means that there is no current auction on the item * `offers` - purchase offers made on that item Listing NFTs, changing the price or stopping the sale. [#listing-nfts-changing-the-price-or-stopping-the-sale] To list NFTs on the marketplace or modify the terms of sale, invoke the following function: ```rust title="nft-marketplace/app/src/lib.rs" /// Adds data on market item. /// If the item of that NFT does not exist on the marketplace then it will be listed. /// If the item exists then that action is used to change the price or suspend the sale. /// /// # Requirements /// * [`msg::source()`](gstd::msg::source) must be the NFT owner /// * `nft_contract_id` must be added to `approved_nft_contracts` /// * if item already exists, then it cannot be changed if there is an active auction /// /// On success triggers the event [`MarketEvent::MarketDataAdded`]. pub async fn add_market_data(&mut self, nft_contract_id: ContractId, ft_contract_id: Option, token_id: TokenId, price: Option) { let market = self.get_mut(); add_market_data(market, nft_contract_id, ft_contract_id, token_id, price).await; self.emit_event(MarketEvent::MarketDataAdded { nft_contract_id, token_id, price }) .expect("Notification Error"); } ``` Withdrawal of an item from market [#withdrawal-of-an-item-from-market] To withdraw an item from the marketplace and reclaim the associated token, invoke the following function: ```rust title="nft-marketplace/app/src/lib.rs" /// Removes data on market item. /// /// # Requirements /// * [`msg::source()`](gstd::msg::source) must be the item.owner /// * There must be no open auction on the item. /// /// On success triggers the event [`MarketEvent::MarketDataRemoved`]. pub async fn remove_market_data(&mut self, nft_contract_id: ContractId, token_id: TokenId) { let market = self.get_mut(); let msg_src = msg::source(); remove_market_data(market, &nft_contract_id, token_id, msg_src).await; self.emit_event(MarketEvent::MarketDataRemoved { owner: msg_src, nft_contract_id, token_id, }) .expect("Notification Error"); } ``` NFT purchase. [#nft-purchase] To buy NFTs, invoke the following function: ```rust title="nft-marketplace/app/src/lib.rs" /// Sells the NFT. /// /// # Requirements: /// * The NFT item must exist and be on sale. /// * If the NFT is sold for a native Vara token, then a buyer must attach a value equal to the price. /// * If the NFT is sold for fungible tokens then a buyer must have enough tokens in the fungible token contract. /// * There must be no open auction on the item. /// /// On success triggers the event [`MarketEvent::ItemSold`]. pub async fn buy_item(&mut self, nft_contract_id: ContractId, token_id: TokenId) { let market = self.get_mut(); let msg_src = msg::source(); buy_item(market, &nft_contract_id, token_id, msg_src).await; self.emit_event(MarketEvent::ItemSold { owner: msg_src, nft_contract_id, token_id }) .expect("Notification Error"); } ``` NFT auction. [#nft-auction] The marketplace program includes the *English auction*. *English auction* is an open auction at an increasing price, where participants openly bid against each other, with each subsequent bid being greater than the previous one. The auction has the following struct: ```rust title="nft-marketplace/app/src/utils.rs" pub struct Auction { pub started_at: u64, pub ended_at: u64, pub current_price: Price, pub current_winner: ActorId, } ``` * `started_at` - auction start time; * `ended_at` - auction end time; * `current_price` - the current offered price for the NFT; * `current_winner` - the current auction winner The auction is started with the following function: ```rust title="nft-marketplace/app/src/lib.rs" /// Creates an auction for selected item. /// If the NFT item doesn't exist on the marketplace then it will be listed /// /// Requirements: /// * Only the item owner can start the auction. /// * `nft_contract_id` must be in the list of `approved_nft_contracts` /// * There must be no active auction /// /// On success triggers the event [`MarketEvent::AuctionCreated`]. pub async fn create_auction(&mut self, nft_contract_id: ContractId, ft_contract_id: Option, token_id: TokenId, min_price: u128, duration: u64) { let market = self.get_mut(); create_auction(market, &nft_contract_id, ft_contract_id, token_id, min_price, duration).await; self.emit_event(MarketEvent::AuctionCreated { nft_contract_id, token_id, price: min_price, }).expect("Notification Error"); } ``` Use the following function to add a bid to the currency auction: ```rust title="nft-marketplace/app/src/lib.rs" /// Adds a bid to an ongoing auction. /// /// # Requirements: /// * The item must exist. /// * The auction must exist on the item. /// * If the NFT is sold for a native Vara token, then a buyer must attach a value equal to the price indicated in the arguments. /// * If the NFT is sold for fungible tokens then a buyer must have enough tokens in the fungible token contract. /// * `price` must be greater than the current offered price for that item. /// /// On success triggers the event [`MarketEvent::BidAdded`]. pub async fn add_bid(&mut self, nft_contract_id: ContractId, token_id: TokenId, price: u128) { let market = self.get_mut(); add_bid(market, &nft_contract_id, token_id, price).await; self.emit_event(MarketEvent::BidAdded { nft_contract_id, token_id, price, }).expect("Notification Error"); } ``` If the auction period is over, anyone can send the message `SettleAuction` that will send the NFT to the winner and pay the owner: ```rust title="nft-marketplace/app/src/lib.rs" /// Settles the auction. /// /// Requirements: /// * The auction must be over. /// /// On successful auction triggers the event [`MarketEvent::AuctionSettled`]. /// If no bids were made triggers the event [`MarketEvent::AuctionCancelled`]. pub async fn settle_auction(&mut self, nft_contract_id: ContractId, token_id: TokenId) { let market = self.get_mut(); let event = settle_auction(market, &nft_contract_id, token_id).await; self.emit_event(event).expect("Notification Error"); } ``` Offers. [#offers] Use the following function to make an offer on the marketplace item: ```rust title="nft-marketplace/app/src/lib.rs" /// Adds a price offer to the item. /// /// Requirements: /// * NFT items must exist and be listed on the marketplace. /// * There must be no ongoing auction on the item. /// * If a user makes an offer in native Vara token, then he must attach a value equal to the price indicated in the arguments. /// * If a user makes an offer in fungible tokens then he must have enough tokens in the fungible token contract. /// * The price can not be equal to 0. /// * There must be no identical offers on the item. /// /// On success triggers the event [`MarketEvent::OfferAdded`]. pub async fn add_offer(&mut self, nft_contract_id: ContractId, ft_contract_id: Option, token_id: TokenId, price: u128) { let market = self.get_mut(); add_offer(market, &nft_contract_id, ft_contract_id, token_id, price).await; self.emit_event(MarketEvent::OfferAdded { nft_contract_id, ft_contract_id, token_id, price, }).expect("Notification Error"); } ``` The item owner can accept the offer: ```rust title="nft-marketplace/app/src/lib.rs" /// Accepts an offer. /// /// Requirements: /// * NFT items must exist and be listed on the marketplace. /// * Only the owner can accept the offer. /// * There must be no ongoing auction. /// * The offer with indicated hash must exist. /// /// On success triggers the event [`MarketEvent::OfferAccepted`]. pub async fn accept_offer(&mut self, nft_contract_id: ContractId, ft_contract_id: Option, token_id: TokenId, price: u128) { let market = self.get_mut(); let new_owner = accept_offer(market, &nft_contract_id, ft_contract_id, token_id, price).await; self.emit_event(MarketEvent::OfferAccepted { nft_contract_id, token_id, new_owner, price, }).expect("Notification Error"); } ``` The user who made the offer can also withdraw their tokens: ```rust title="nft-marketplace/app/src/lib.rs" /// Withdraws tokens. /// /// Requirements: /// * NFT items must exist and be listed on the marketplace. /// * Only the offer creator can withdraw his tokens. /// * The offer with indicated hash must exist. /// /// On success triggers the event [`MarketEvent::TokensWithdrawn`]. pub async fn withdraw(&mut self, nft_contract_id: ContractId, ft_contract_id: Option, token_id: TokenId, price: u128) { let market = self.get_mut(); withdraw(market, &nft_contract_id, ft_contract_id, token_id, price).await; self.emit_event(MarketEvent::Withdraw { nft_contract_id, token_id, price, }).expect("Notification Error"); } ``` Consistency of program states [#consistency-of-program-states] The `market` program interfaces with both `fungible` and `non-fungible` token contracts. Each transaction that alters the states of multiple programs is temporarily stored until its completion. Whenever a user engages with an item, the marketplace program verifies any pending transactions. If there is one, it prompts the user to finalize it, preventing the initiation of a new transaction. The idempotency inherent in the token contracts enables the restarting of a transaction without redundant changes, ensuring the consistent state of all three programs. Query [#query] ```rust title="nft-marketplace/app/src/lib.rs" pub fn get_market(&self) -> MarketState { self.get().clone().into() } ``` ```rust title="nft-marketplace/app/src/utils.rs" pub struct MarketState { pub admin_id: ActorId, pub items: Vec<((ContractId, TokenId), ItemState)>, pub approved_nft_contracts: Vec, pub approved_ft_contracts: Vec, } ``` Source code [#source-code] The source code of this example of VaraMan Game program and the example of an implementation of its testing is available on [gear-foundation/dapp/contracts/nft-marketplace](https://github.com/gear-foundation/dapps/tree/master/contracts/nft-marketplace). See also an example of the smart contract testing implementation based on `gtest`: [gear-foundation/dapps/vara-man/tests](https://github.com/gear-foundation/dapps/tree/master/contracts/nft-marketplace/tests). For more details about testing programs written on Gear, refer to the [Program Testing](/docs/developing/build/testing) article. # NFT Frontend Application This guide explains how to create a `React` application and connect it to an [extended NFT smart contract](/docs/vara-network/examples/Standards/vnft#extended-vnft-implementation) running in the blockchain. Preparation [#preparation] 1. First install one of the [templates](https://github.com/gear-foundation/dapps/tree/master/frontend/templates). Install [Node.js](https://nodejs.org/en/download/) and [NPM](https://docs.npmjs.com/downloading-and-installing-node-js-and-npm). Make sure the latest LTS version of the Node.js is installed. 2. Then install dependencies: npm pnpm yarn bun ```bash npm install ``` ```bash pnpm install ``` ```bash yarn install ``` ```bash bun install ``` 3. There is an `.env.example` file. Create your own `.env` file and copy the contents of `.env.example` to your `.env` file. It contains the following variables: * `VITE_NODE_ADDRESS`: This variable defines the node we'll be working on. You have to add next varibles as well: * `VITE_CONTRACT_ADDRESS`: The address of the contract uploaded to the chain. * `VITE_IPFS_ADDRESS` and `VITE_IPFS_GATEWAY_ADDRESS`: These variables are needed when uploading and reading media files to IPFS An example of environment variables is shown below: ```sh VITE_NODE_ADDRESS=wss://rpc.vara.network VITE_CONTRACT_ADDRESS=0x3d3d0b5c597d6d767294cc93e0a3489d848ae32cbf851fa40756800d28e4cd37 VITE_IPFS_ADDRESS=http://localhost:5001/api/v0 VITE_IPFS_GATEWAY_ADDRESS=https://ipfs.io/ipfs ``` 4. In a root `consts.ts` file, specify newly added environment variables: ```typescript const ADDRESS = { NODE: import.meta.env.VITE_NODE_ADDRESS as string, CONTRACT_ADDRESS: import.meta.env.VITE_CONTRACT_ADDRESS as `0x${string}`, IPFS_ADDRESS: import.meta.env.VITE_IPFS_ADDRESS as string, IPFS_GATEWAY_ADDRESS: import.meta.env.VITE_IPFS_GATEWAY_ADDRESS as string, }; ``` 5. Install `kubo-rpc-client` library to handle IPFS requests: npm pnpm yarn bun ```bash npm i kubo-rpc-client ``` ```bash pnpm add kubo-rpc-client ``` ```bash yarn add kubo-rpc-client ``` ```bash bun add kubo-rpc-client ``` Next, create a context to integrate IPFS in a React-friendly way. In the `context` folder, create a file named `index.tsx` and add the following code: ```tsx import { create, KuboRPCClient } from "kubo-rpc-client"; import { createContext, ReactNode, useContext, useRef } from "react"; import { ADDRESS } from "@/consts"; type Props = { children: ReactNode; }; const IPFSContext = createContext({} as KuboRPCClient); function IPFSProvider({ children }: Props) { const ipfsRef = useRef(create({ url: ADDRESS.IPFS_ADDRESS })); const { Provider } = IPFSContext; return {children}; } const useIPFS = () => useContext(IPFSContext); export { IPFSProvider, useIPFS }; ``` Include `IPFSProvider` in the providers array in the `hocs/index.tsx` file: ```typescript import { IPFSProvider } from '@/context'; const providers = [..., IPFSProvider]; ``` 6. Build and upload the [contract](https://github.com/gear-foundation/standards/tree/master/extended-vnft#%EF%B8%8F-building) to the chain and set up the address in the `.env` file. Place the `extended_vnft.idl` file in the `api/sails` folder and run the Sails CLI: ```bash cargo sails client-js src/api/sails/extended_vnft.idl src/api/sails/lib.ts ``` Check the newly created lib.ts file and add the following two lines at the top: ```ts /* eslint-disable @typescript-eslint/no-floating-promises */ /* eslint-disable @typescript-eslint/no-explicit-any */ ``` 7. Run the application: ```shell yarn start ``` 8. The main file `App.tsx` is simple. It checks whether the application is connected to the chain: ```typescript const { isApiReady } = useApi(); ``` It checks whether the account is connected to the application through the web extension: ```typescript const { isAccountReady } = useAccount(); ``` 9. If the `api` is ready and the `account` is connected, it displays the application's pages. Navigate to the pages folder. The project has only one page `Home`. The routing configuration is located in the `pages/index.tsx` file. Create-NFT page [#create-nft-page] 1. Create a page for NFT creation using the code below: ```shell mkdir src/pages/create-nft touch src/pages/create-nft/CreateNft.tsx ``` 2. Start writing the `CreateNft.tsx`: ```tsx export function CreateNft() { return
Create NFT
; } ``` 3. Declare this page in the `index.tsx` file and also add the route for it: ```tsx import { CreateNft } from "./create-nft/CreateNft"; const routes = [ { path: "/", Page: Home }, { path: "/create-nft", Page: CreateNft }, ]; ``` 4. Create a link to the `CreateNft` page from the `Header` component. In the `src\components\layout\header\Header.tsx` file, write: ```tsx import { Link } from "react-router-dom"; ... function Header() { return (

Create NFT

...
); } export { Header }; ``` 5. Go back to the `CreateNft` page. Create a form that includes the NFT `title`, `description`, and `image`: ```tsx import { Button, FileInput, Input } from "@gear-js/ui"; export function CreateNft() { return ( <>

Create NFT

); } ``` 6. Create a state that will store the NFT's title, description, and image, and add the functions `handleInputChange` and `handleImageChange` that will update this state: ```tsx import { Button, FileInput, Input } from "@gear-js/ui"; import { useState } from "react"; const NftInitialState = { title: "", description: "", }; export function CreateNft() { const [nftForm, setNftForm] = useState(NftInitialState); const [image, setImage] = useState(); const { title, description } = nftForm; const handleInputChange = (e: React.ChangeEvent) => { const { name, value } = e.target; setNftForm((prevForm) => ({ ...prevForm, [name]: value })); }; return ( <>

Create NFT

); } ``` 7. Add the image preview for the uploaded image: ```tsx ... export function CreateNft() { ... return ( <>

Create NFT

... {image ? (
nft
) : (

No image set for this NFT

)}
); } ``` Upload image and mint NFT [#upload-image-and-mint-nft] 1. Next, upload the image to IPFS and send a `Mint` message to the contract. Install the [IPFS Desktop App](https://docs.ipfs.tech/install/ipfs-desktop/#windows). 2. Navigate to `Settings`: Locate `IPFS config`: and configure the `API` of your node: ```json "API": { "HTTPHeaders": { "Access-Control-Allow-Methods": [ "PUT", "GET", "POST" ], "Access-Control-Allow-Origin": [ "*", "https://webui.ipfs.io", "http://webui.ipfs.io.ipns.localhost:8080", "http://127.0.0.1:5001" ] } }, ``` 3. Now you can upload the files from the application. Start writing the function: ```tsx ... import { useIPFS } from "@/context"; ... export function CreateNft() { ... const ipfs = useIPFS(); const createNft = async (e: React.FormEvent) => { e.preventDefault(); if (image) { try { const cid = await ipfs.add(image); } catch (error) { console.error(error); } } }; ... } ``` 4. Continue writing the `createNft` function. Create the `payload` message and send it to the contract. The complete code of the `CreateNft` page is as follows: ```tsx import { useAccount, useProgram, useSendProgramTransaction } from "@gear-js/react-hooks"; import { Button, FileInput, Input } from "@gear-js/ui"; import { useState } from "react"; import { useNavigate } from "react-router-dom"; import { Program } from "@/api/sails/lib"; import { ADDRESS } from "@/consts"; import { useIPFS } from "@/context"; const NftInitialState = { title: "", description: "", }; export function CreateNft() { const [nftForm, setNftForm] = useState(NftInitialState); const [image, setImage] = useState(); const ipfs = useIPFS(); const { account } = useAccount(); const navigate = useNavigate(); const { data: program } = useProgram({ library: Program, id: ADDRESS.CONTRACT_ADDRESS }); const { sendTransactionAsync } = useSendProgramTransaction({ program, serviceName: "vnft", functionName: "mint", }); const { title, description } = nftForm; const resetForm = () => { setNftForm(NftInitialState); setImage(null); }; const createNft = async (e: React.FormEvent) => { e.preventDefault(); if (!account?.decodedAddress) return; if (image) { try { const cid = await ipfs.add(image); const tokenMetadata = { name: title, description, media: cid?.cid.toString(), reference: "", }; await sendTransactionAsync({ args: [account.decodedAddress, tokenMetadata], }); resetForm(); navigate("/"); } catch (error) { console.error(error); } } }; const handleInputChange = (e: React.ChangeEvent) => { const { name, value } = e.target; setNftForm((prevForm) => ({ ...prevForm, [name]: value })); }; return ( <>

Create NFT

{image ? (
nft
) : (

No image set for this NFT

)}
); } ``` The next section covers the creation of the `Home` page for reading and displaying the minted NFTs. Home page [#home-page] 1. Start writing the `Home` page: ```tsx import { useAccount, useProgram, useProgramQuery } from "@gear-js/react-hooks"; import { Program } from "@/api/sails/lib"; import { Loader } from "@/components"; import { ADDRESS } from "@/consts"; function Home() { const { account } = useAccount(); const { data: program } = useProgram({ library: Program, id: ADDRESS.CONTRACT_ADDRESS }); // Read nfts from the contract const { data: nfts, isFetched: isNftStateRead } = useProgramQuery({ program, serviceName: "vnft", functionName: "tokensForOwner", args: [account!.decodedAddress], }); // Check whether the contract has tokens const isAnyNft = !!nfts?.length; return ( <>

NFTs

{isNftStateRead ? ( <> {isAnyNft &&
    Display NFTs here
} {!isAnyNft &&

There are no NFTs at the moment

} ) : ( )} ); } export { Home }; ``` 2. Create a component that will display the NFT: ```shell mkdir src/pages/home/nft touch src/pages/home/nft/Nft.tsx ``` Write the component: ```tsx import { Link } from "react-router-dom"; import { ADDRESS } from "@/consts"; type Props = { id: string; name: string; media: string; }; function NFT({ id, name, media }: Props) { const to = `/nft/${id}`; const src = `${ADDRESS.IPFS_GATEWAY_ADDRESS}/${media}`; const text = `#${id}`; return ( {name}

{name}

{text}

); } export { NFT }; ``` 3. Write a function for retrieving all NFTs from the contract in the `Home.tsx` file: ```tsx ... import { NFT } from './nft/Nft'; function Home() { ... const getNFTs = () => nfts?.map(([id, { name, media }]) => (
  • )); ... } ... ``` The whole code of the `Home` page: ```tsx import { useAccount, useProgram, useProgramQuery } from "@gear-js/react-hooks"; import { Program } from "@/api/sails/lib"; import { Loader } from "@/components"; import { ADDRESS } from "@/consts"; import { NFT } from "./nft/Nft"; function Home() { const { data: program } = useProgram({ library: Program, id: ADDRESS.CONTRACT_ADDRESS, }); const { account } = useAccount(); const { data: nfts, isFetched: isNftStateRead } = useProgramQuery({ program, serviceName: "vnft", functionName: "tokensForOwner", args: [account!.decodedAddress], }); const isAnyNft = Boolean(nfts?.length); const getNFTs = () => nfts?.map(([id, { name, media }]) => (
  • )); return ( <>

    NFTs

    {isNftStateRead ? ( <> {isAnyNft &&
      {getNFTs()}
    } {!isAnyNft &&

    There are no NFTs at the moment

    } ) : ( )} ); } export { Home }; ``` # Subsquid indexing Creating a good and usable frontend for an NFT marketplace web-application requires an indexer of on-chain programs. It will enable historical data to be displayed in the fastest way possible. Subsquid helps to achieve this goal. Explore the [Subsquid documentation](https://docs.subsquid.io/overview/) to learn about their architecture Historical data that needs to be indexed, stored, and displayed in the user interface can be (but not limited to): * Token transfers between accounts * Auctions and their bids * Offers to buy tokens A dApp developer may also want to store other parameters of NFTs, such as - the token owner, the token description, links to media and other references too. With Subsquid, these data points can be quickly accessed without having to query the state of the programs on-chain. Subsquid provides two main components: * An Archive that indexes all blockchain calls and events. * A Squid that gets the data from the Archive and implements specific business logic. The Archive works out of the box so we focused on the development of the Squid. The Squid includes, firstly, a processor that receives data from the Archive and creates its own entities that are stored in a database and secondly, GraphQL API through which the data can be retrieved. As for the latter, Subsquid provides a package that can be used to run the GraphQL API without any additional implementation. The Processor must be developed as a regular NodeJS package. Subsquid provides a set of packages that facilitate the development process. Firstly, community developers created a GraphQL scheme describing all entities and relations between them. [Here](https://github.com/gear-foundation/integrations-subsquid/blob/master/Subsquid/nft-marketplace/schema.graphql) is an example of a scheme that can be used to generate Typeorm models for a database using the `@subsquid/typeorm-codegen` package provided by Subsquid. They chose a Postgres database for storing all the data. The next step is to create the Processor. Subsquid provides a really simple way to obtain data from an Archive. Using `@subsquid/substrate-processor` you can easily subscribe to necessary data. The Class `SubstrateBatchProcessor` lets you select the range of blocks that you want to index and specify the event or extrinsic that you want to receive. In the community developer’s cases, they only used `UserMessageSent` events, because they contained all the necessary data. The payloads of messages received through the processor are represented as bytes and require program metadata to decode the bytes. The class called [Meta](https://github.com/gear-foundation/integrations-subsquid/blob/master/Subsquid/nft-marketplace/src/meta.ts) was created to decode these bytes. The `SubsquidBatchProcessor` operates by returning data in batches. To minimize the number of read/write operations in the database, it is advisable to compile and prepare all the data within each batch and subsequently save it after the entire batch has been processed. A [BatchState](https://github.com/gear-foundation/integrations-subsquid/blob/master/Subsquid/nft-marketplace/src/state.ts) class was introduced to facilitate this, storing the prepared data until it is ready to be written into the database. In case a new NFT program is registered in the marketplace, you may want to index it to get all its historical data. To do this, the [indexNft](https://github.com/gear-foundation/integrations-subsquid/blob/master/Subsquid/nft-marketplace/src/indexNft.ts) function was created to query the relevant data from the Archive and write it into the database. The source code of the Subsquid-based indexer components is available on GitHub [here](https://github.com/gear-foundation/integrations-subsquid/tree/master/Subsquid). The separate implementation of the NFT marketplace application example that is using indexing data via Subscquid is available in: `TBD` # Delayed Self-Messages with Version Guards import MermaidDiagram from "@/components/mermaid"; About [#about] Delayed self-messages are a common tool for implementing deferred actions in Sails-based programs. Typical use cases include lock expiration, cooldown completion, timeout processing, delayed cleanup, and deferred state transitions. The mechanism is simple: a program schedules a message to itself and handles it at a later block. The main engineering risk lies in stale delayed messages. A message scheduled under an earlier state may arrive after the state has already changed. Without additional protection, such a message may apply an outdated transition and corrupt the current state. This pattern introduces a minimal and reliable solution based on a versioned state record. Every scheduled delayed message carries the version that was current at scheduling time. Every meaningful state change bumps the stored version. When the delayed message arrives, the program applies the transition only if the version still matches and the expiration condition is still valid. Why the pattern matters [#why-the-pattern-matters] A delayed action is executed in the future, not at the moment it is scheduled. During that gap, the underlying state may change through renewal, cancellation, or replacement. As a result, a delayed message that was valid at scheduling time may become invalid at execution time. A version guard solves that problem without queue-level cancellation. The program allows delayed messages to arrive and rejects them safely when they no longer match the current state. Pattern overview [#pattern-overview] The pattern stores one lock record per user. Each record contains an active flag, an expiration block, and a monotonically increasing version. Every delayed self-message carries the user and the version observed when expiration was scheduled. Starting a lock schedules a delayed expiration and stores the active record. Renewing a lock schedules a new delayed expiration with a higher version. Cancelling a lock deactivates the record and bumps the version. When a delayed expiration arrives, the program applies it only if the caller is the program itself, the lock is still active, the version still matches, and the current block has reached the recorded expiration point. Architecture [#architecture] |start / renew / cancel| P P -->|read / update lock| S P -->|schedule delayed self-message| Q Q -->|later delivery| E E -->|validate self-call, version, block| S E -->|deactivate if current| S `} /> Main components [#main-components] Lock record [#lock-record] The lock record is the core state unit of the pattern. It captures whether the lock is currently active, the block at which expiration is allowed, and the current version used to invalidate stale delayed messages. ```rust #[derive(Debug, Clone, PartialEq, Eq, Encode, Decode, TypeInfo)] #[codec(crate = sails_rs::scale_codec)] #[scale_info(crate = sails_rs::scale_info)] pub struct Lock { pub active: bool, pub expires_at: u32, pub version: u64, } ``` The `version` field is the critical protection mechanism. Any state transition that changes the semantic meaning of the lock must produce a new version. State storage [#state-storage] The service keeps one map of locks indexed by user address. ```rust pub struct State { locks: HashMap, } ``` This layout is intentionally minimal. The pattern focuses on correctness of delayed execution rather than on domain-specific storage complexity. Manual request encoding [#manual-request-encoding] The delayed self-message is built manually as an internal Sails request for the `Expire` call. The payload includes the target user and the expected lock version. ```rust fn encode_expire_request(user: ActorId, version: u64) -> Vec { let mut request = Vec::new(); SERVICE_NAME.encode_to(&mut request); EXPIRE_ACTION.encode_to(&mut request); (user, version).encode_to(&mut request); request } ``` That design makes the delayed message explicit and easy to reason about. The expiration handler receives all data required for stale-message validation. Delayed scheduling [#delayed-scheduling] The service schedules the delayed self-message through `msg::send_bytes_with_gas_delayed(...)`. ```rust fn schedule_expire( &self, user: ActorId, version: u64, delay_blocks: u32, ) -> Result<(), Error> { let request = Self::encode_expire_request(user, version); msg::send_bytes_with_gas_delayed( exec::program_id(), request, DELAY_GAS, 0, delay_blocks, ) .map_err(|_| Error::ScheduleFailed)?; Ok(()) } ``` The delayed message targets `exec::program_id()`, which guarantees that expiration is handled internally by the same program. Lifecycle operations [#lifecycle-operations] Start [#start] A `start(delay_blocks)` call creates a new active lock for the caller. The operation rejects zero delay and rejects an already active lock. If the user had an older inactive record, the next version is derived from the previous one. The service first schedules the delayed self-message and only then persists the new state. This ordering prevents the program from storing an active lock if scheduling fails. >Service: start(delay_blocks) Service->>Service: validate non-zero delay Service->>State: read existing lock Service->>Queue: schedule expire(user, version) Service->>State: store active lock Service-->>User: Event::Started `} /> Renew [#renew] A `renew(delay_blocks)` call extends an existing active lock. The operation requires an active record and bumps the version before scheduling the new delayed expiration. Any previously scheduled expiration for the older version becomes stale immediately. >Service: renew(delay_blocks) Service->>State: load active lock Service->>Service: next_version = old_version + 1 Service->>Queue: schedule expire(user, next_version) Service->>State: overwrite lock with new version and expires_at Service-->>User: Event::Renewed `} /> Cancel [#cancel] A `cancel()` call deactivates the lock and bumps the version. That single state transition is enough to invalidate all delayed expirations created for earlier versions. ```rust fn deactivate(lock: &mut Lock) { lock.active = false; lock.version = lock.version.saturating_add(1); } ``` Cancellation does not attempt to remove already scheduled delayed messages. Instead, it guarantees that any future arrival of those messages is stale by construction. Expire [#expire] An `expire(user, version)` call is the delayed handler. It may be executed only by the program itself. The handler ignores four cases without error: missing lock, already inactive lock, stale version, and early arrival before `expires_at`. Only a current, active, self-triggered, non-early message may deactivate the lock and emit `Event::Expired`. Expiration flow [#expiration-flow] >Program: expire(user, version) Program->>Program: verify msg::source() == exec::program_id() Program->>State: load lock by user Program->>Program: check active flag Program->>Program: check version match Program->>Program: check block_height >= expires_at Program->>State: deactivate lock Program-->>Queue: Event::Expired `} /> Version-based stale message protection [#version-based-stale-message-protection] The version field is the core correctness mechanism of the pattern. A delayed message is scheduled with the version that is current at scheduling time. Any later renewal or cancellation increments the stored version. When the delayed message arrives, a version mismatch proves that the message belongs to an outdated state transition. > Only the delayed message created for the current lock version may expire the lock. Effective safety checks [#effective-safety-checks] The expiration handler combines several independent checks: * self-call verification through `msg::source() == exec::program_id()`; * existence of a lock record for the target user; * active-state verification; * version equality between message and storage; * block-height verification against `expires_at`. Each check blocks a distinct class of failure. Together, they make delayed expiration idempotent and safe under renewals, cancellations, repeated deliveries, and race-like timing conditions. Integration in business logic [#integration-in-business-logic] This pattern is suitable for any deferred transition that must remain valid only while a specific state version remains current. The same approach can be reused for lock expiration, reservation release, delayed order invalidation, cooldown completion, deferred cleanup of temporary state, and timeout-based workflow transitions. The domain-specific state may be more complex than a single lock record, but the integration principle remains the same: store a versioned record, schedule a self-message carrying that version, and reject stale executions on arrival. Security requirements [#security-requirements] * Accept delayed expiration only from `exec::program_id()`. * Treat version mismatches as stale-message rejections. * Recheck `expires_at` at execution time, even for delayed messages. * Bump the version on every state change that invalidates older delayed actions. * Reject zero-delay scheduling when the pattern requires a real defer boundary. Practical guidance for production systems [#practical-guidance-for-production-systems] This pattern is appropriate for deferred transitions that may be superseded by later user actions. Recommended practices include keeping the state record small, bumping the version on every invalidating change, and treating stale delayed messages as expected no-op outcomes rather than exceptional failures. Production implementations should also size delayed-call gas conservatively, test early-delivery and stale-delivery paths explicitly, and emit lifecycle events that allow off-chain systems to reconstruct the current lock state reliably. Source code [#source-code] Pattern repository: * [`vara-dapp-patterns/contracts/delayed-self-message`](https://github.com/gear-foundation/vara-dapp-patterns/tree/master/contracts/delayed-self-message) Related documentation: * [Sails](/docs/developing/build/sails) Summary [#summary] The Delayed Self-Messages with Version Guards pattern provides a compact way to implement deferred self-execution safely in Sails-based programs. By coupling every delayed action with a state version and revalidating that version on arrival, the program prevents outdated messages from applying stale transitions. This approach is well suited to expiration, cooldown, timeout, and deferred cleanup flows where scheduled actions may be superseded before execution. # Factory Pattern for Child Program Instantiation import MermaidDiagram from "@/components/mermaid"; About [#about] A factory program is a common pattern for deploying multiple child programs from a single on-chain entry point. Instead of creating each child program through external orchestration, the factory keeps the template `code_id` in storage and instantiates new child programs on demand. This approach is useful when a system needs many isolated program instances that share the same code template. Typical examples include user-specific vaults, strategy instances, game rooms, isolated markets, or tenant-specific service programs. The minimal pattern in this example stores only one value in the factory state: the `code_id` of the child template. A service method then uses `ProgramGenerator::create_program_bytes_with_gas_for_reply(...)` to create a new child program, waits for the initialization reply, emits an event with the new `ActorId`, and returns the address to the caller. Why the pattern matters [#why-the-pattern-matters] A factory centralizes program creation and makes deployment flows deterministic from the perspective of clients and indexers. Instead of requiring off-chain logic to manage template identifiers and child initialization, the factory exposes a stable contract-level interface for instantiation. This structure is especially useful when many child programs must be created from the same template, tracked through events, and initialized through a consistent payload format. Pattern overview [#pattern-overview] The pattern contains two programs: a factory and a child template. The factory stores the child template `code_id` and exposes a `create_program()` command. The child template exposes its own constructor and service methods after instantiation. When `create_program()` is called, the factory reads the stored `code_id`, builds the initialization payload, starts child creation through `ProgramGenerator`, waits for the reply, emits `FactoryEvents::ProgramCreated(address)`, and returns the newly created program address. That design keeps child deployment logic inside the factory while keeping child business logic inside the child program itself. Architecture [#architecture] |"create_program()"| F F -->|read code_id| S F -->|create_program_bytes_with_gas_for_reply| G G -->|instantiate from code_id| C C -->|constructor payload| N F -->|emit event| E F -->|return ActorId| U `} /> Main components [#main-components] Factory storage [#factory-storage] The factory keeps a minimal state object containing only the child template identifier. ```rust struct Storage { code_id: CodeId, } ``` That design is sufficient when the factory does not need ownership metadata, registries of created children, or per-user deployment state. The example focuses only on the instantiation path. Factory service [#factory-service] The factory service provides the command that creates a child program. It borrows the factory storage, reads the stored `code_id`, prepares the constructor payload, and performs the async child creation flow. ```rust struct FactoryService<'a> { storage: &'a RefCell, } ``` The service also exposes a small helper for read-only storage access through `RefCell`. Child template [#child-template] The child program is a normal Sails program with its own constructor and its own exposed service. In this minimal example, the child constructor takes no parameters and the child service exposes one command that returns a string. ```rust #[sails_rs::service] impl FactoryChild { #[export] pub fn do_something(&mut self) -> String { "Hello from FactoryChild!".to_string() } } ``` The factory does not implement child business logic. Its responsibility is only to instantiate the child correctly. Child program structure [#child-program-structure] The child template remains independent from the factory. It defines its own constructor and its own service exposure. ```rust #[derive(Default)] pub struct Program(()); #[sails_rs::program] impl Program { pub fn new() -> Self { Self(()) } pub fn factory_child(&self) -> FactoryChild { FactoryChild::new() } } ``` That separation is important. The factory controls creation, while the child program controls post-deployment behavior. Creation flow [#creation-flow] The core factory method is `create_program()`. ```rust #[export(unwrap_result)] pub async fn create_program(&mut self) -> Result { let code_id = { self.get().code_id }; let payload = "New".encode(); let create_program_future = ProgramGenerator::create_program_bytes_with_gas_for_reply( code_id, payload, 10_000_000_000, 0, 10_000_000_000, ) .map_err(|_e| FactoryError::CreateProgramStartFailed)?; let (address, _) = create_program_future .await .map_err(|_e| FactoryError::CreateProgramReplyFailed)?; self.emit_event(FactoryEvents::ProgramCreated(address)) .map_err(|_e| FactoryError::EmitEventFailed)?; Ok(address) } ``` The method performs four steps: 1. read the stored child template `code_id`; 2. encode the constructor payload for the child program; 3. start child creation and wait for the initialization reply; 4. emit an event and return the new child address. The payload is encoded manually as `"New".encode()`, which matches the child constructor route used in this minimal example. Creation sequence [#creation-sequence] >Factory: create_program() Factory->>Storage: read code_id Factory->>Factory: payload = "New".encode() Factory->>Generator: create_program_bytes_with_gas_for_reply(...) Generator->>Child: instantiate template Child->>Instance: run constructor Generator-->>Factory: reply(address, reply_payload) Factory-->>User: return ActorId `} /> Constructor payload and initialization contract [#constructor-payload-and-initialization-contract] The factory and the child template must agree on the constructor payload format. In this pattern, the factory sends `"New".encode()`, which assumes that the child program accepts the corresponding constructor route with no additional parameters. For example, if the child constructor accepts one parameter such as `owner: ActorId`, the payload must include both the constructor route and that argument: ```rust let payload = { let owner = msg::source(); let mut payload = Vec::new(); "New".encode_to(&mut payload); owner.encode_to(&mut payload); payload }; ``` In that case, the child constructor would look conceptually like this: ```rust #[sails_rs::program] impl Program { pub fn new(owner: ActorId) -> Self { Self(()) } } ``` In other words, the factory and the child template must agree on the constructor ABI: the route name, the parameter types, and the parameter order. That contract is one of the most important parts of the pattern. If the child constructor changes, the factory payload must be updated accordingly. The factory and the child template must therefore evolve together. Security requirements [#security-requirements] * Store only a trusted child template `code_id`. * Keep the factory payload format aligned with the child constructor format. * Treat child creation and child initialization as separate failure boundaries. * Emit creation events so off-chain systems can track new instances reliably. * Size gas for both creation start and constructor reply conservatively. Practical guidance for production systems [#practical-guidance-for-production-systems] This pattern is a good fit when many child programs must be created from one template and tracked through a stable contract interface. Production implementations often extend the minimal version with access control, per-user deployment rules, registries of created children, template upgrades, or richer constructor payloads. In more advanced systems, the factory may also persist metadata about each deployed child, enforce caller permissions, or support several child templates rather than one. The minimal example here is still useful because it isolates the core mechanics of on-chain program instantiation and reply handling. Source code [#source-code] Pattern repository: * [`vara-dapp-patterns/contracts/factory`](https://github.com/gear-foundation/vara-dapp-patterns/tree/master/contracts/factory) * [`vara-dapp-patterns/contracts/factory-child`](https://github.com/gear-foundation/vara-dapp-patterns/tree/master/contracts/factory-child) Related documentation: * [Sails](/docs/developing/build/sails) Summary [#summary] The Factory Pattern for Child Program Instantiation provides a compact way to deploy multiple child programs from a single Sails-based factory. The factory stores the child template `code_id`, constructs the initialization payload, creates the child through `ProgramGenerator`, waits for the constructor reply, and emits an event with the new address. This structure keeps deployment logic centralized, keeps child business logic separate, and gives clients a stable interface for creating new program instances. It is a strong foundation for more advanced factory systems that require access control, registries, richer initialization parameters, or multi-template support. # Contract Patterns The contract examples in `vara-dapp-patterns/contracts` focus on small, production-oriented Sails patterns that solve one architectural problem at a time. Included patterns [#included-patterns] | Pattern | Problem it solves | Source | | ----------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------ | | [Delayed Self-Messages with Version Guards](/docs/vara-network/examples/Patterns/contracts/delayed-message) | Safe deferred actions when delayed messages may arrive after state changed | [`contracts/delayed-self-message`](https://github.com/gear-foundation/vara-dapp-patterns/tree/master/contracts/delayed-self-message) | | [Factory Pattern for Child Program Instantiation](/docs/vara-network/examples/Patterns/contracts/factory) | Creating many isolated program instances from one template `CodeId` | [`contracts/factory`](https://github.com/gear-foundation/vara-dapp-patterns/tree/master/contracts/factory) | | [Service Wrapper Pattern](/docs/vara-network/examples/Patterns/contracts/service-wrapper-pattern) | Injecting policy into a reusable base service without rewriting domain logic | [`contracts/module-toggles`](https://github.com/gear-foundation/vara-dapp-patterns/tree/master/contracts/module-toggles) | | [Signless & Gasless Sessions](/docs/vara-network/examples/Patterns/contracts/signless-gasless) | Delegated execution with action allowlists and temporary session keys | [`contracts/signless-gasless`](https://github.com/gear-foundation/vara-dapp-patterns/tree/master/contracts/signless-gasless) | What makes these examples useful [#what-makes-these-examples-useful] Each example stays small enough to audit quickly, but still shows the moving parts that matter in a real app: storage ownership, explicit error boundaries, internal scheduling, service composition, and delegation flow. These patterns are most useful when you are not looking for a full product template, but for one reliable building block you can transplant into a larger program. # Service Wrapper Pattern import MermaidDiagram from "@/components/mermaid"; About [#about] This pattern shows how to keep a base service reusable, inject policy through a separate wrapper, and compose higher-level services around the wrapped interface. The example uses three roles: a base service that owns one domain capability, a wrapper service that overrides selected methods to enforce policy, and a consumer service that depends on the wrapped exposure while keeping its own independent state. The important idea is architectural rather than domain-specific. The base service should remain small and reusable. The wrapper should add policy without rewriting the base module. The consumer should depend on the selected exposure rather than duplicating logic or reaching into shared storage directly. In this example, the base service is an oracle, the wrapper adds admin-only writes, and the consumer is a market module. The same pattern can be reused for many other domains where core logic, access control, and higher-level workflows should remain separate. Why the pattern matters [#why-the-pattern-matters] Many contract systems need the same core module under different policy regimes. One deployment may expose the base service directly for testing. Another may require an admin-only wrapper. A third may place the same module behind multisig or governance checks. If policy is embedded directly into the base module, reuse becomes harder and domain logic becomes entangled with authorization logic. The wrapper pattern avoids that coupling. The base service keeps its domain primitives, the wrapper owns policy, and consumer services depend on the chosen wrapped interface. The program boundary then becomes the toggle point that decides which exposure is public and how dependent modules are wired. Pattern overview [#pattern-overview] The pattern has three layers. A base service owns the core logic and storage access. A wrapper service extends the base service and overrides selected methods to add policy. A consumer service depends on the wrapped service and applies application-level rules while persisting its own local state. In this example, the base `OracleService` stores the current price and exposes `update_price` and `get_price`. The wrapper service extends the oracle and restricts `update_price` to the admin account. The consumer `MarketService` reads the wrapped oracle price, applies quote and slippage logic, and stores market-specific state independently from oracle storage. Architecture [#architecture] |guarded write path| W W -->|override selected methods| B B -->|read / write| BS U -->|read or command flow| C C -->|depends on wrapper exposure| W C -->|persist consumer state| CS `} /> The example implementation maps these roles as follows: * **Base service** → `OracleService` * **Wrapper service** → `admin_oracle` * **Consumer service** → `MarketService` Main components [#main-components] Base service [#base-service] The base service owns one domain capability and remains free of policy-specific decisions. In the example below, the base service is an oracle that stores and returns a price. ```rust #[sails_rs::service] impl<'a> OracleService<'a> { #[export] pub fn update_price(&mut self, new_price: u128) { *self.get_mut_price() = new_price; } #[export] pub fn get_price(&self) -> u128 { *self.price.borrow() } } ``` That design keeps the module reusable. Another program may expose it directly, wrap it with access control, or place it behind governance without changing the base implementation. Wrapper service [#wrapper-service] The wrapper extends the base service and overrides only the methods that require additional policy. In this example, the wrapper checks that the caller is the admin before allowing price updates. ```rust #[sails_rs::service(extends = [OracleService<'a>])] impl<'a> Service<'a> { #[export(unwrap_result)] pub fn update_price(&mut self, value: u128) -> Result<(), AdminError> { self.ensure_admin()?; *self.oracle.get_mut_price() = value; Ok(()) } } ``` The wrapper reuses the base read path and injects policy only where needed. That is the core idea of the pattern. For `extends = [OracleService<'a>]`, Sails also requires a conversion from the extending service into the extended one: ```rust impl<'a> From> for OracleService<'a> { fn from(s: Service<'a>) -> Self { s.oracle } } ``` That conversion enables typed extension while keeping the wrapper strongly typed and explicit. Consumer service [#consumer-service] The consumer depends on the wrapped service rather than on raw storage or duplicated base logic. In this example, the market service reads the oracle price through the wrapped exposure and maintains its own independent state. ```rust pub struct MarketService<'a> { admin_oracle: ServiceExposure>, market: &'a RefCell, } ``` This is an important part of the pattern. The consumer does not implement its own price storage and does not reapply wrapper policy manually. It depends on the selected service exposure provided by the program. Independent consumer storage [#independent-consumer-storage] The consumer keeps its own state independently from the base service. ```rust pub struct MarketStorage { pub last_quote_usd: u128, pub last_trader: ActorId, } ``` This separation preserves clear ownership boundaries. The base service remains responsible for domain primitives, while the consumer remains responsible for workflow-specific state. Program composition [#program-composition] The program boundary is where the actual module toggle happens. The program decides whether the base service is exposed directly, wrapped, or kept internal. ```rust pub struct Program { oracle: RefCell, market: RefCell, admin: ActorId, } #[sails_rs::program] impl Program { pub fn new() -> Self { Self { oracle: RefCell::new(0), market: RefCell::new(MarketStorage::default()), admin: msg::source(), } } pub fn admin_oracle(&self) -> Service<'_> { Service::new(OracleService::new(&self.oracle), self.admin) } pub fn market(&self) -> MarketService<'_> { MarketService::new(self.admin_oracle(), &self.market) } } ``` In this composition, the base oracle is not exposed directly. The wrapped `admin_oracle()` becomes the intended mutation path, and the consumer `market()` is wired against that wrapped exposure. Guarded override flow [#guarded-override-flow] >Wrapper: guarded update Wrapper->>Wrapper: enforce policy Wrapper->>Base: call base write path Base->>State: write state Wrapper-->>Privileged: success `} /> The example implementation uses an admin check, but the same structure could enforce multisig approval, governance gating, pause checks, feature flags, or role-based authorization. Consumer dependency flow [#consumer-dependency-flow] >Consumer: user command Consumer->>Wrapper: read through wrapped exposure Wrapper->>Base: delegated read path Base->>BState: read domain value Base-->>Consumer: current value Consumer->>Consumer: apply consumer-specific checks Consumer->>CState: persist local state Consumer-->>User: result `} /> The key point is not the domain-specific quote itself. The key point is that the consumer depends on the wrapped interface rather than on raw shared storage or duplicated base logic. Cross-service read path [#cross-service-read-path] The quote path in the example is a simple illustration of a consumer reading from the wrapped base service. ```rust #[export(unwrap_result)] pub fn quote_usd(&self, amount_tokens: u128) -> Result { let price = self.admin_oracle.oracle.get_price(); if *price == 0 { return Err(MarketError::OraclePriceIsZero); } Ok(amount_tokens.saturating_mul(*price) / PRICE_SCALE) } ``` What matters here is not the quote formula itself. The important part is that the consumer pulls the current value through the selected service exposure and applies its own local rules on top. Command path with local persistence [#command-path-with-local-persistence] The command path uses the same dependency but persists consumer-specific state. ```rust #[export(unwrap_result)] pub fn open_position( &mut self, amount_tokens: u128, max_acceptable_price: u128, ) -> Result { let price = self.admin_oracle.oracle.get_price(); if *price == 0 { return Err(MarketError::OraclePriceIsZero); } if *price > max_acceptable_price { return Err(MarketError::SlippageExceeded); } let quote = amount_tokens.saturating_mul(*price) / PRICE_SCALE; let mut m = self.market_mut(); m.last_quote_usd = quote; m.last_trader = msg::source(); Ok(quote) } ``` This keeps responsibilities separate. The base service owns the primitive value, the wrapper owns policy, and the consumer owns application-specific validation and persistence. Security requirements [#security-requirements] * Do not expose the raw base write path if the wrapper is intended to be the only mutation path. * Override only the methods that require additional policy. * Keep policy checks localized in the wrapper rather than duplicating them across consumers. * Ensure that consumer services depend on the intended wrapped exposure. * Document domain-specific invariants separately from the wrapper itself. Practical guidance for production systems [#practical-guidance-for-production-systems] This pattern is a good fit when a reusable module must support multiple policy variants or multiple consumer modules. The strongest production use of the pattern comes from preserving a strict separation of responsibilities: the base module owns domain primitives, the wrapper owns policy, and the consumer owns application-specific validation and storage. Production implementations often extend this structure with richer base data, admin rotation, multisig or governance wrappers, consumer-specific validation modules, and tests that verify both unauthorized access rejection and correct consumer behavior under the selected wrapped exposure. Source code [#source-code] Pattern repository: * [`vara-dapp-patterns/contracts/module-toggles/oracle`](https://github.com/gear-foundation/vara-dapp-patterns/tree/master/contracts/module-toggles/oracle) * [`vara-dapp-patterns/contracts/module-toggles/oracle-admin-wrapper`](https://github.com/gear-foundation/vara-dapp-patterns/tree/master/contracts/module-toggles/oracle-admin-wrapper) Related documentation: * [Sails](/docs/developing/build/sails) Summary [#summary] The Service Wrapper Pattern makes it possible to keep a base module reusable while injecting policy through a separate wrapper layer. A consumer service can then depend on the wrapped interface without duplicating the base logic or sharing responsibility for policy checks. In this example, the base service is an oracle, the wrapper adds admin-only writes, and the consumer is a market module. The same structure can be reused in many other domains where core logic, policy, and higher-level composition should remain separate. # Signless & Gasless Sessions import MermaidDiagram from "@/components/mermaid"; About [#about] Many production scenarios require delegated execution with a narrow permission scope. Examples include mobile flows, relayer-based UX, temporary automation, and game or application sessions. A direct transfer of full control to a delegate key creates unnecessary risk. A repeated requirement for direct user signatures creates unnecessary friction. The Signless & Gasless Sessions pattern introduces a constrained delegation layer for Sails-based programs on Vara. A session key receives permission to execute a limited set of actions on behalf of an original account for a limited period. Runtime validation enforces key ownership, expiration, and action allowlisting. The pattern in this repository uses a reusable session-management crate, [`signless-gasless-session-service`](https://github.com/gear-foundation/signless-gasless-session-service.git), together with a generated session subsystem produced by the `generate_session_system!(ActionsForSession)` macro. The application-facing integration surface remains small: a business method accepts an optional `session_for_account` parameter and resolves the effective account through generated validation logic. Why the pattern matters [#why-the-pattern-matters] A session-based model allows delegated execution without granting unlimited authority to a secondary key. It also avoids the need for repeated direct signatures for every user action. The result is a more practical authorization model for applications that require temporary access, restricted permissions, and low-friction execution. This approach is especially useful when session infrastructure must remain isolated from domain logic while still supporting both direct session creation and delegated setup through signed authorization. Pattern overview [#pattern-overview] The pattern consists of four logical parts: 1. **Original account**\ The principal account on whose behalf an operation is executed. 2. **Session key**\ The delegate account allowed to submit a restricted set of calls. 3. **Generated session subsystem**\ Storage, service methods, configuration, validation helpers, events, and errors generated by the session-service macro. 4. **Business service**\ Domain logic that relies on session validation only when delegated execution is requested. A practical consequence follows from that split: domain code remains compact, while authorization rules remain centralized and consistent. Architecture [#architecture] |direct call or off-chain authorization| SS SK -->|delegated business call| BS SS -->|create / delete / query| ST BS -->|resolve effective account| ST ST -->|validation result| BS BS -->|state update| DS `} /> Main components [#main-components] ActionsForSession [#actionsforsession] A domain-specific enum defines the actions eligible for delegation. Only actions represented in that enum may pass runtime validation. Typical shape: ```rust #[derive(Debug, Clone, Encode, Decode, TypeInfo, PartialEq, Eq)] #[codec(crate = sails_rs::scale_codec)] #[scale_info(crate = sails_rs::scale_info)] pub enum ActionsForSession { IncreaseCounter, } ``` The enum represents the authorization boundary. Any mismatch between an executed method and the corresponding action variant weakens the security model. generate_session_system!(ActionsForSession) [#generate_session_systemactionsforsession] The session-service repository provides a convenience macro that generates the reusable session layer after declaration of the action enum. The generated subsystem includes session storage, service methods, configuration types, session and signature data structures, typed errors, events, and helpers for effective-account resolution. This macro removes repetitive boilerplate and standardizes storage layout, creation flow, deletion flow, and validation semantics across applications. Session storage [#session-storage] The generated storage keeps session records and configuration. Each session record contains, at minimum: * approved session key * expiration timestamp * allowed actions * derived block-based expiration marker for delayed deletion Business storage [#business-storage] Business storage remains independent from session infrastructure. The demo pattern uses a minimal counter state, but the same integration approach applies to balances, game state, order placement, staking actions, or administrative workflows. Generated session subsystem [#generated-session-subsystem] The signless-gasless-session-service repository is designed as a reusable dependency: ```toml [dependencies] session-service = { git = "https://github.com/gear-foundation/signless-gasless-session-service.git" } ``` Minimal integration shape: ```rust #![no_std] use sails_rs::prelude::*; use session_service::*; #[derive(Debug, Clone, Encode, Decode, TypeInfo, PartialEq, Eq)] #[codec(crate = sails_rs::scale_codec)] #[scale_info(crate = sails_rs::scale_info)] pub enum ActionsForSession { IncreaseCounter, } generate_session_system!(ActionsForSession); #[derive(Default)] pub struct CounterStorage { pub counter: u64, } pub struct Program { /// Storage used by the generated session system (sessions map + config) session_storage: RefCell, /// Storage used by the business logic (the demo counter) counter_storage: RefCell, } #[sails_rs::program] impl Program { pub async fn new(session_config: SessionConfig) -> Self { Self { session_storage: RefCell::new(SessionStorage::new(session_config)), counter_storage: RefCell::new(CounterStorage::default()), } } pub fn signless_gasless(&self) -> ServiceUsingSession<'_> { ServiceUsingSession::new(&self.session_storage, &self.counter_storage) } pub fn session(&self) -> SessionService<'_> { SessionService::new(&self.session_storage) } } ``` Session model [#session-model] A session represents a temporary grant from an original account to a session key. Required properties [#required-properties] * **Approved key** — the delegate key allowed to submit the delegated call. * **Allowed actions** — an explicit allowlist of action variants. * **Timestamp-based expiration** — runtime rejection after the configured lifetime ends. * **Block-based expiration marker** — a scheduling aid for delayed deletion through self-messaging. Configuration [#configuration] `SessionConfig` governs operational behavior. Typical fields include: * `gas_to_delete_session` * `minimum_session_duration_ms` * `ms_per_block` Those values support safe session cleanup and duration validation. Effective-account resolution [#effective-account-resolution] A business method supporting delegated execution accepts an optional account parameter: ```rust pub fn increase_counter( &mut self, session_for_account: Option, ) -> Result ``` The business method then resolves the effective account through generated validation logic. Conceptual flow: >Business: call(method, session_for_account) Business->>Sessions: get_original_address(msg::source(), session_for_account, action) Sessions-->>Business: resolved original account or error Business->>Domain: apply business mutation Domain-->>Business: updated state Business-->>Caller: success response `} /> Validation cases [#validation-cases] When `session_for_account` is `None`, the call follows the normal execution path. In that case, the effective account is simply `msg::source()`, and no session lookup is required. When `session_for_account` is `Some(account)`, the program switches to delegated validation. The session must exist for the specified account, remain non-expired, include the requested action in its allowlist, and match the immediate caller through the stored session key. Only a successful validation produces the original account for downstream business logic. Session creation modes [#session-creation-modes] Two creation modes exist in the pattern. Signature-based session creation [#signature-based-session-creation] Use case: delegated or relayed setup without a direct on-chain call from the original account. High-level model: 1. original account authorizes session creation off-chain; 2. session key or relayer submits `create_session(...)`; 3. contract verifies the expected signature payload; 4. contract stores the session and emits `SessionCreated`. Architecture: >Delegate: signed authorization payload Delegate->>Session: create_session(signature_data, signature) Session->>Session: verify signature Session->>Session: store session Session-->>Delegate: SessionCreated `} /> No-signature session creation [#no-signature-session-creation] Use case: direct setup by the original account in a standard wallet flow. High-level model: * original account sends `create_session(...)` directly; * provided key becomes the approved session key; * no off-chain signature verification occurs; * contract stores the session and emits `SessionCreated`. Architecture: >Session: create_session(signature_data, None) Session->>Session: validate duration and parameters Session->>Session: store session Session-->>Original: SessionCreated `} /> Operational result: * `msg::source()` acts as the original account; * the provided key acts as the delegate; * delegated usage starts immediately after successful creation. Integration in business logic [#integration-in-business-logic] A business method should remain focused on domain behavior. Session handling should be limited to effective-account resolution and action binding. Recommended structure: ```rust #[sails_rs::service] impl<'a> ServiceUsingSession<'a> { /// Increments a counter, with optional session-based delegation #[export(route = "increase_counter_with_possibility_of_sessions", unwrap_result)] pub fn increase_counter( &mut self, session_for_account: Option, ) -> Result { let msg_src = msg::source(); // Resolve the “original” account for which this call is executed let original_addr = { let storage = self.get_session_storage(); storage.get_original_address( &msg_src, &session_for_account, ActionsForSession::IncreaseCounter, )? }; // Business logic: increment the counter let new_value = { let mut c = self.get_counter_storage_mut(); c.counter = c.counter.saturating_add(1); c.counter }; Ok(format!( "Original address: {original_addr}; counter: {new_value}!" )) } } ``` Recommended properties of that structure: * no duplication of session validation logic in domain methods; * explicit binding between the method and one action variant; * clear separation between authorization and state mutation. Session lifecycle [#session-lifecycle] Creation [#creation] A successful session creation flow stores: * delegate key * duration-derived expiration * action allowlist * block marker used for cleanup A `SessionCreated` event confirms successful activation. Usage [#usage] A delegated business call remains valid only when all validation checks pass. A rejected session produces an error before domain mutation. Deletion [#deletion] The pattern supports both manual deletion and delayed automatic cleanup. Deletion paths: 1. explicit deletion requested through session service methods; 2. scheduled self-message after expiration. That cleanup design requires enough gas for the delayed deletion path and a coherent `ms_per_block` approximation. Security requirements [#security-requirements] * Bind each delegated method to the correct `ActionsForSession` variant. * Keep the action set narrow and explicit. * Preserve exact signature payload compatibility in signature-based flows. * Treat session validation as an authorization layer, not a replacement for domain checks. * Configure expiration and cleanup parameters carefully. Practical guidance for production systems [#practical-guidance-for-production-systems] Recommended practices for production use: * Keep `ActionsForSession` small and domain-specific. * Use short session durations by default. * Add explicit revocation flows where user control is important. * Isolate session infrastructure from business storage and business logic. * Cover negative scenarios in integration tests before deployment. Typical use cases include mobile applications, relayer-based UX, game sessions, and temporary delegated actions with limited scope. Source code [#source-code] Pattern repository: * [`vara-dapp-patterns/contracts/signless-gasless`](https://github.com/gear-foundation/vara-dapp-patterns/tree/master/contracts/signless-gasless) Reusable session-service repository: * [`signless-gasless-session-service`](https://github.com/gear-foundation/signless-gasless-session-service) Related documentation: * [Sails](/docs/developing/build/sails) Summary [#summary] The Signless & Gasless Sessions pattern provides a reusable model for temporary and restricted delegated execution in Sails-based contracts on Vara. The generated session subsystem reduces boilerplate, centralizes validation, and keeps business methods compact. The result is a cleaner separation between session authorization and domain logic, with a structure suitable for production-grade contract systems. # Gasless Voucher Server About [#about] The gasless voucher server is the off-chain service that makes voucher-based UX practical. It holds the issuer account, submits voucher extrinsics, exposes a REST API for frontend clients, and manages the lifecycle of vouchers tied to a specific target program. Without this backend layer, a frontend can know that gasless UX is desirable, but it has no trusted place to keep the issuer key or coordinate voucher issuance safely. Why the pattern matters [#why-the-pattern-matters] Gasless UX is not a single call. A real application needs to manage the full voucher lifecycle: * issue a voucher for a user, * avoid issuing duplicates unnecessarily, * inspect whether a voucher is active, * prolong it when more balance or time is needed, * revoke it when the session or entitlement should end. That is why the example is a server pattern rather than a one-off helper. High-level flow [#high-level-flow] There are two layers in the implementation: 1. the HTTP server in `src/index.ts`, 2. the chain-facing `GaslessService` in `src/lib.ts`. The flow for voucher issuance looks like this: 1. frontend calls `POST /gasless/voucher/request`, 2. Express validates the request body, 3. the route calls `gaslessService.issueIfNeeded(...)`, 4. `GaslessService` checks whether an active voucher already exists, 5. if none exists, it builds `api.voucher.issue(...)`, 6. the backend signer submits the extrinsic, 7. the service waits for `VoucherIssued`, 8. the route returns `{ voucherId }` to the client. The HTTP route in `src/index.ts` stays intentionally small: ```ts app.post("/gasless/voucher/request", async (req, res) => { const { account, amount = 20_000_000_000_000, durationInSec = 3_600 } = req.body; if (!account) { return res.status(400).json({ error: "account is required" }); } const voucherId: HexString = await gaslessService.issueIfNeeded( account, programId, amount, Number(durationInSec) ); return res.status(200).json({ voucherId }); }); ``` That is a good backend sign: the route owns input validation and HTTP response shape, while the service owns every chain-specific concern. Why issueIfNeeded(...) matters [#why-issueifneeded-matters] The route uses an idempotent entrypoint: ```ts const voucherId: HexString = await gaslessService.issueIfNeeded( account, programId, amount, Number(durationInSec) ); ``` This is more than a convenience. Frontends often request gasless support on mount, reconnect, or page refresh. Without an idempotent server-side path, those UX patterns could burn issuer funds by creating duplicate vouchers. The implementation protects against duplication in two different ways: 1. `inFlightVoucherIssues` prevents the same `(account, programId)` request from being started twice concurrently, 2. `api.voucher.getAllForAccount(account)` checks whether an acceptable voucher already exists on-chain. That is exactly the kind of backend logic that is easy to miss if the article only describes the endpoint shape. Service-level architecture [#service-level-architecture] `GaslessService` owns: * the Gear API connection, * the voucher issuer account, * an in-memory submission queue, * in-flight deduplication for voucher creation, * helper methods for issue, status, prolong, revoke, and lookup. That separation keeps HTTP concerns out of the chain-integration layer. The service constructor makes that boundary explicit: ```ts constructor() { this.api = new GearApi({ providerAddress: process.env.NODE_URL }); this.voucherAccount = this.getVoucherAccount(); } ``` The route layer never deals with API construction or signer loading directly. Deduplication and queueing [#deduplication-and-queueing] Two details in `GaslessService` are especially important for a production-oriented design. In-flight deduplication [#in-flight-deduplication] The service stores in-flight voucher requests in: ```ts private readonly inFlightVoucherIssues = new Map>(); ``` This prevents duplicate concurrent issuance attempts for the same `(account, programId)` pair. Submission queue [#submission-queue] The service also serializes issuer operations through: ```ts private submissionQueue: Promise = Promise.resolve(); ``` That matters because nonce handling on a single signer account becomes fragile under concurrency. These two fields are some of the most important backend-specific code in the example. Without them, the service would appear correct in light testing and then become unstable under concurrent usage. Core issuance flow [#core-issuance-flow] The chain-side issuance happens through `issueInternal(...)`: 1. wait for API and crypto readiness, 2. convert duration from seconds to blocks, 3. normalize the spender account, 4. build `api.voucher.issue(...)`, 5. fetch the next nonce, 6. sign and send with the issuer account, 7. wait for `VoucherIssued`, 8. cache voucher metadata for later reads. The core fragment is: ```ts const { extrinsic } = await this.api.voucher.issue( accountId, amount, durationInBlocks, [programId], false ); ``` Then the service resolves only when the block contains `VoucherIssued`. The event-driven promise is the most important code block in the file: ```ts const voucherId = await new Promise((resolve, reject) => { extrinsic.signAndSend( this.voucherAccount, { nonce }, ({ events, status }) => { if (!status.isInBlock) return; const viEvent = events.find( ({ event }) => event.method === "VoucherIssued" ); if (viEvent) { const data = viEvent.event.data as any; const id = data.voucherId.toHex() as HexString; voucherInfoStorage[id] = { durationInSec, amount }; resolve(id); return; } const efEvent = events.find( ({ event }) => event.method === "ExtrinsicFailed" ); reject( efEvent ? this.api.getExtrinsicFailedError(efEvent.event) : new Error("VoucherIssued event not found in block") ); } ); }); ``` That is what lets the backend return a concrete `voucherId` only after it has real evidence of success. Why event-based resolution is used [#why-event-based-resolution-is-used] The service does not assume that submitting the extrinsic is enough. It waits for chain events so that it can: * extract the actual `voucherId`, * distinguish success from `ExtrinsicFailed`, * return a concrete result to the frontend. That mirrors the same philosophy as the stronger contract/frontend patterns: real success should be tied to the relevant event boundary. Status and normalization flow [#status-and-normalization-flow] The status route contains one more small but useful backend detail: ```ts const normalizedId = voucherId.startsWith("0x") ? (voucherId as `0x${string}`) : (`0x${voucherId}` as `0x${string}`); ``` This makes the HTTP interface slightly more forgiving while keeping the service layer strict about identifier format. Status, prolong, and revoke flows [#status-prolong-and-revoke-flows] The server also exposes: * `GET /gasless/voucher/:voucherId/status` * `POST /prolong` * `POST /revoke` These routes are important because gasless UX is rarely just “issue once and forget.” Long-lived sessions and repeated interactions often require voucher inspection or renewal. Even though those routes are short, they expose non-trivial service behavior: * sequenced signer operations, * voucher lifecycle mutation, * error mapping from chain failures into HTTP responses, * one place for nonce-sensitive issuer activity. Production guidance [#production-guidance] * Make issuance idempotent from the server side, not only from the frontend side. * Serialize submissions per issuer account or use a dedicated nonce manager. * Scope vouchers tightly to the intended program. * Keep issuer keys only on the backend. * Treat event-based confirmation as the real success boundary for issuance. * Keep route handlers tiny and push event handling, nonce sensitivity, and voucher lifecycle rules into the service layer. Source code [#source-code] * [`infrastructure/gasless-server`](https://github.com/gear-foundation/vara-dapp-patterns/tree/master/infrastructure/gasless-server) # Backend Patterns The backend examples in `vara-dapp-patterns/infrastructure` cover the trusted off-chain services that often sit beside a Vara application. Included patterns [#included-patterns] | Pattern | Focus | Source | | --------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------- | | [REST Gateway for Sails Programs](/docs/vara-network/examples/Patterns/backend/rest-gateway) | Expose on-chain commands and queries through a conventional HTTP API | [`infrastructure/gateway`](https://github.com/gear-foundation/vara-dapp-patterns/tree/master/infrastructure/gateway) | | [Gasless Voucher Server](/docs/vara-network/examples/Patterns/backend/gasless-voucher-server) | Issue, prolong, revoke, and inspect vouchers on behalf of users | [`infrastructure/gasless-server`](https://github.com/gear-foundation/vara-dapp-patterns/tree/master/infrastructure/gasless-server) | | [Token-Gated Authentication](/docs/vara-network/examples/Patterns/backend/token-gated-auth) | Verify wallet signatures, read VFT balances, and issue JWT access tokens | [`infrastructure/token-gate-server`](https://github.com/gear-foundation/vara-dapp-patterns/tree/master/infrastructure/token-gate-server) | When you need these patterns [#when-you-need-these-patterns] Use a backend pattern when your architecture needs one of the following: * a trusted signer that should not live in the browser, * voucher lifecycle management for gasless UX, * server-side authentication or authorization based on on-chain state, * a conventional REST interface for frontend or third-party clients. # REST Gateway for Sails Programs About [#about] The REST gateway pattern exposes Vara programs through a conventional HTTP API. Instead of making every frontend client understand Sails call construction, SCALE-encoded arguments, signing, and reply handling, a trusted backend translates HTTP requests into on-chain program calls. The example in `infrastructure/gateway` uses Express plus Sails-JS to implement that translation layer. Why the pattern matters [#why-the-pattern-matters] This pattern is useful whenever an application needs a trusted off-chain actor: * automated flows, * server-managed signer keys, * multi-step orchestration, * HTTP access for third-party clients, * simplified frontend integration. It is the backend counterpart to the contract and frontend patterns: the contract defines the ABI, the frontend defines UX, and the gateway defines a trusted integration surface. High-level architecture [#high-level-architecture] At startup, the server performs one-time initialization: 1. connect to the Vara network, 2. load the backend wallet, 3. create Sails instances for the relevant programs, 4. store those dependencies in `app.locals`. Then request handling follows a layered flow: 1. Express route receives an HTTP request, 2. controller reads shared dependencies from `req.app.locals`, 3. service constructs a typed Sails call, 4. service estimates gas, 5. service signs and submits the extrinsic, 6. service waits for the program reply, 7. controller returns JSON to the client. In practice this pattern is split into three code layers: * `src/index.ts` for bootstrapping and shared dependency ownership, * `src/controllers/contract.controller.ts` for HTTP-facing request handling, * `src/services/contract.service.ts` for Sails-JS and on-chain orchestration. Startup flow [#startup-flow] The most important structural decision is that initialization happens once and the result is shared: ```ts this.app.locals.api = api; this.app.locals.factorySails = factorySails; this.app.locals.poolFactorySails = poolFactorySails; this.app.locals.signer = signer; ``` Why this matters: * controllers do not need to import globals, * startup and request-time concerns stay separate, * tests can replace `app.locals` with mocks or fixtures. The startup method in `src/index.ts` is the core of that architecture: ```ts private async initializeVaraNetwork(): Promise { const api = await createGearApi(NETWORK); const signer = await gearKeyringByWalletData(WALLET_NAME, WALLET_MNEMONIC); const factorySails = await sailsInstance( api, FACTORY_CONTRACT_ID, FACTORY_IDL ); const poolFactorySails = await sailsInstance( api, POOL_FACTORY_CONTRACT_ID, POOL_FACTORY_IDL ); this.app.locals.api = api; this.app.locals.factorySails = factorySails; this.app.locals.poolFactorySails = poolFactorySails; this.app.locals.signer = signer; } ``` That is where raw configuration turns into live chain dependencies that every request can reuse. Sails instance creation [#sails-instance-creation] The gateway relies on preconfigured Sails instances that bind: * a network API connection, * a program ID, * the corresponding IDL. That lets request handlers use typed function and query calls rather than building payloads manually on every request. The controller layer then becomes intentionally small: read request parameters, grab dependencies from `req.app.locals`, call the service, and return structured JSON. Conceptually it looks like: ```ts const { factorySails, signer } = req.app.locals; const result = await ContractService.createProgram(factorySails, signer, req.body); return res.json({ success: true, data: result }); ``` That is a healthy backend boundary: controllers own HTTP, services own chain logic. Command flow in detail [#command-flow-in-detail] For write operations, the gateway follows the same command lifecycle: 1. Build the typed Sails command. 2. Attach the backend signer. 3. Attach value if the program call requires it. 4. Calculate gas. 5. Submit the extrinsic. 6. Wait for the program-level reply. That sequence is important because program reply handling is part of the contract-level success condition, not just an optional extra. Core command fragment [#core-command-fragment] The pattern is summarized well by this call chain: ```ts const transaction = await sails.services.Service.functions .CreateProgram(initConfig) .withAccount(signer) .withValue(BigInt(ONE_VARA)) .calculateGas(); const { response } = await transaction.signAndSend(); const result = await response(); ``` Each stage means something distinct: * `CreateProgram(initConfig)` builds the typed message, * `withAccount(signer)` selects the trusted backend signer, * `withValue(...)` attaches the value required by the runtime or contract, * `calculateGas()` simulates and sizes the call, * `signAndSend()` submits the extrinsic, * `response()` waits for the actual program reply. The same file also shows a more complete backend orchestration flow in `createPool(...)`: ```ts const transaction = await factorySails.services.Service.functions .CreatePool(tokenA, tokenB) .withAccount(signer) .withValue(BigInt(ONE_VARA)) .calculateGas(); const { response } = await transaction.signAndSend(); await response(); const pairAddress = await this.getPairAddress( poolFactorySails, tokenA, tokenB ); ``` This is a good example of why a backend gateway is useful. It can combine: * one mutating call, * one follow-up query, * one clean HTTP response. The client does not need to reproduce that orchestration itself. Query flow [#query-flow] Read-only operations follow a simpler path: * no signer, * no gas calculation, * no extrinsic submission, * only a `.call()` against the query. This distinction is one of the cleanest reasons to keep the service layer explicit: command and query paths are structurally different, even when they belong to the same contract. A minimal query helper from the service layer looks like this: ```ts static async getAdmins(sails: Sails): Promise { return sails.services.Service.queries.Admins().call() as Promise; } ``` That makes the command/query split visually obvious in the codebase: * `.functions.*` for state-changing operations, * `.queries.*.call()` for read-only operations. Why app.locals is a good fit [#why-applocals-is-a-good-fit] The gateway stores shared dependencies in `app.locals` rather than in module-level singletons. That keeps ownership explicit: * server startup owns dependency creation, * controllers own request parsing, * services own blockchain interaction. That separation makes the code easier to reason about and much easier to test. Composite backend operations [#composite-backend-operations] One of the strongest examples in the service layer is `createProgramAndPool(...)`. It performs a two-step dependent workflow: 1. deploy a new VFT program, 2. extract the returned address, 3. create a pool for that newly deployed token. Core fragment: ```ts const programResponse = await this.createProgram( factorySails, signer, initConfig ); const response = programResponse as { programCreated?: { address: HexString } }; const tokenAddress = response.programCreated.address; const pairAddress = await this.createPoolWithRegisteredToken( factorySails, poolFactorySails, signer, tokenAddress, registeredToken ); ``` This is where the gateway clearly stops being a thin proxy and becomes an orchestration service. Production guidance [#production-guidance] * Keep signer keys only on the backend. * Treat `calculateGas()` as part of the normal command flow, not as an optional optimization. * Keep controller code thin and move blockchain logic into services. * Return structured JSON responses that reflect both HTTP and program-level success. * Be explicit about which routes are trusted backend operations and which are safe read-only queries. * Prefer service methods that model business operations rather than one-to-one wrappers for every low-level call. Source code [#source-code] * [`infrastructure/gateway`](https://github.com/gear-foundation/vara-dapp-patterns/tree/master/infrastructure/gateway) # Token-Gated Authentication About [#about] The token-gated authentication pattern implements a Vara-native sign-in-with-wallet flow and combines it with on-chain access control. A user signs a structured message, the server verifies the signature, checks the user’s VFT balance on-chain, and then issues a JWT that downstream services can trust for a limited time. This is the backend equivalent of taking on-chain ownership and turning it into a practical Web application session. Why the pattern matters [#why-the-pattern-matters] Many apps want access to depend on token ownership, but they do not want to re-query the chain on every request. That would be expensive, slow, and difficult to operationalize across many downstream endpoints. This pattern solves that by separating responsibilities: * the chain decides whether the user currently satisfies the balance threshold, * the auth server decides whether to issue or refresh a JWT, * downstream services consume the JWT rather than reading the chain directly on every request. High-level authentication flow [#high-level-authentication-flow] The server’s authentication flow is: 1. issue a nonce, 2. have the client sign a structured message containing nonce, domain, chain ID, and timestamps, 3. consume and validate the nonce, 4. verify signature correctness, 5. query on-chain token balance, 6. compare against the configured threshold, 7. issue JWT on success. That flow combines standard web-session mechanics with Vara-native signature verification and on-chain reads. The implementation is split into three focused modules: * `src/server.ts` owns HTTP routing and auth lifecycle, * `src/auth.ts` owns nonce, schema, signature, and JWT helpers, * `src/gear.ts` owns address normalization and VFT balance reads. The nonce layer [#the-nonce-layer] The nonce system exists to prevent replay attacks. `src/auth.ts` implements: ```ts export const nonceStore = new Map(); export function issueNonce(ttlSec: number): string { const nonce = randomUUID(); nonceStore.set(nonce, Date.now() + ttlSec * 1_000); return nonce; } export function consumeNonce(nonce: string): boolean { const exp = nonceStore.get(nonce); if (!exp) return false; nonceStore.delete(nonce); return exp > Date.now(); } ``` The key property is delete-on-read semantics: once a nonce is consumed, it cannot be reused even if the original signed message is intercepted. That small property is what turns a signed message into a real login challenge instead of a replayable proof. Verification flow in detail [#verification-flow-in-detail] The `/auth/verify` route is the security-critical path. Its order of operations matters: 1. validate the request body with Zod, 2. extract signed fields from the message, 3. consume the nonce, 4. validate domain and chain ID if configured, 5. validate message freshness, 6. verify the wallet signature, 7. query VFT balance on-chain, 8. issue JWT if the threshold is met. The route code follows that order directly: ```ts const { address, message, signature } = SignedMessageSchema.parse(req.body); if (!consumeNonce(nonce)) { return sendError(res, 400, "Invalid or expired nonce"); } if (!verifySignature(address, message, signature)) { return sendError(res, 401, "Invalid signature"); } const balRaw = await getVFTBalance(address, api); ``` This order is deliberate. Cheap structural checks happen before cryptography, and cryptography happens before the chain query. The route also enforces server-level message binding before querying the chain: ```ts if (EXPECTED_DOMAIN && domain !== EXPECTED_DOMAIN) { return sendError(res, 400, "Invalid domain"); } if (EXPECTED_CHAIN_ID && chainId !== EXPECTED_CHAIN_ID) { return sendError(res, 400, "Invalid chainId"); } ``` That prevents the same signed payload from being accepted in the wrong application or wrong chain context when those guards are configured. Signature verification [#signature-verification] Signature verification uses Polkadot/Substrate cryptography: ```ts export function verifySignature( address: string, message: string, signature: string ): boolean { const { isValid } = signatureVerify(message, signature, address); return isValid; } ``` That is what makes the pattern Vara-native rather than an Ethereum-style SIWE clone. The request schema is also validated before cryptography begins: ```ts export const SignedMessageSchema = z.object({ address: z.string().min(3), message: z.string().min(5), signature: z.string().min(10), }); ``` That keeps malformed inputs out of the expensive and security-critical parts of the flow. On-chain entitlement check [#on-chain-entitlement-check] After signature verification, the server reads token balance with a generated Sails client: ```ts const program = new Program(api, VFT_PROGRAM_ID as HexString); const svc = new Service(program); const normalized = toHexAddress(accountAddress); const result = await svc.balanceOf(normalized); ``` That is an important detail. The server is not just using a generic storage query; it is using the program’s own client-facing API shape. The address normalization helper is part of that flow: ```ts export const toHexAddress = (addr: string): HexString => { try { if (addr?.startsWith("0x") && addr.length > 2) return addr as HexString; const decoded = decodeAddress(addr); return u8aToHex(decoded) as HexString; } catch { return addr as HexString; } }; ``` That lets the auth server accept both SS58-style wallet addresses and hex account IDs while still querying the VFT contract in the format it expects. JWT issuance and refresh [#jwt-issuance-and-refresh] On success, the server issues: ```ts return jwt.sign({ sub: address, hasAccess: true }, secret, { expiresIn: `${ttlMin}m`, }); ``` This is what lets downstream services treat the auth server as the source of entitlement for a bounded time window. The refresh path adds another useful layer. If `RECHECK_ON_REFRESH` is enabled, the server queries the balance again before renewing the token. That creates a soft revocation model: * if the user still meets the threshold, refresh succeeds, * if the user sold or transferred the tokens, refresh fails and access naturally expires. The refresh route makes that policy explicit: ```ts if (remain > REFRESH_MIN_REMAIN) { return res.json({ jwt: token, remainingSec: remain, refreshed: false }); } if (RECHECK_ON_REFRESH) { const api = await getApi(); const balRaw = await getVFTBalance(payload.sub, api); // compare balance against threshold again before renewing } ``` This is a good example of chain state being used as an input to backend auth policy rather than as the auth session itself. Why this is a good backend pattern [#why-this-is-a-good-backend-pattern] This example is not just about one endpoint. It models a full auth lifecycle: * nonce issuance, * signature-based login, * entitlement check, * JWT refresh, * optional chain revalidation on refresh. That makes it a strong analogue to the more detailed contract articles, because it explains both the building blocks and the full operational flow. The `/entitlement` route closes the loop by giving downstream services a lightweight authorization check: ```ts app.get("/entitlement", (req, res) => { const payload = jwt.verify(token, JWT_SECRET) as { sub: string; hasAccess: boolean; }; return res.json({ ok: true, address: payload.sub, hasAccess: payload.hasAccess }); }); ``` So the backend pattern is not only about login. It is about turning on-chain ownership into reusable Web authorization. Production guidance [#production-guidance] * Keep nonce handling single-use and short-lived. * Bind signed messages to expected domain and chain ID where possible. * Re-check balance on refresh if access should track token ownership over time. * Keep JWT TTL short enough that off-chain access cannot drift too far from on-chain reality. * Normalize addresses before contract reads so the auth layer accepts both SS58 and hex-style inputs safely. * Keep nonce helpers, chain-read helpers, and HTTP routes in separate modules so the auth path stays auditable. Source code [#source-code] * [`infrastructure/token-gate-server`](https://github.com/gear-foundation/vara-dapp-patterns/tree/master/infrastructure/token-gate-server) # EZ Transactions About [#about] The `gear-ez-transactions` example is the most complete frontend pattern in `vara-dapp-patterns`. It combines two UX goals that often appear together in consumer-facing dApps: * gasless execution, where a voucher pays network fees, * signless execution, where an ephemeral browser key signs repeated actions instead of opening the wallet for every click. Unlike the smaller hook patterns, this example is about the full end-to-end flow rather than one isolated abstraction. Why the pattern matters [#why-the-pattern-matters] Gasless and signless are tightly related but solve different problems: * gasless answers “who pays for execution?”, * signless answers “who signs repeated actions?”. The critical architectural detail is that signless session creation itself still costs gas. That means the correct sequence is not arbitrary: 1. voucher first, 2. signless session second, 3. repeated signless actions afterward. This pattern is valuable because it makes that sequencing explicit in frontend code. High-level flow [#high-level-flow] ```text Wallet connected ↓ Frontend requests voucher from backend ↓ Voucher becomes active for target program ↓ User enables signless session ↓ Frontend creates ephemeral key pair in browser ↓ Session creation call is sent on-chain ↓ Subsequent calls use ephemeral key + voucher-funded gas ``` Provider-level architecture [#provider-level-architecture] The app-level provider stack looks like this: ```tsx ``` That order matters: * the API provider gives chain access, * the account provider gives wallet context, * the ez-transactions provider builds on both. Main example flow [#main-example-flow] The example component `SwitchSignless.tsx` coordinates several moving parts: * reads the connected account, * loads the target program, * automatically requests a gasless voucher, * exposes a UI toggle for signless session creation, * prepares transaction parameters through `usePrepareEzTransactionParams`, * sends the prepared transaction through a custom `useSignAndSend` helper. The point is not just to show the hooks, but to show the order in which they cooperate. The component starts by reading wallet, signless, and gasless state: ```tsx const { account } = useAccount(); const signless = useSignlessTransactions(); const gasless = useGaslessTransactions(); ``` Then it loads the target Sails-generated program client: ```tsx const { data: program } = useProgram({ library: Program, id: import.meta.env.VITE_PROGRAMID, }); ``` This is where the frontend binds the UI flow to a concrete deployed program. Automatic voucher request [#automatic-voucher-request] When the user connects and gasless mode is enabled, the component requests a voucher only once per account: ```tsx const hasRequestedOnceRef = useRef(false); useEffect(() => { if (!account?.address || !gasless.isEnabled || hasRequestedOnceRef.current) return; hasRequestedOnceRef.current = true; const requestVoucherSafely = async () => { if (gasless.voucherStatus?.enabled) return; await gasless.requestVoucher(account.address); }; void requestVoucherSafely(); }, [account?.address, gasless.isEnabled]); ``` Why this matters: * React 18 StrictMode can mount components twice in development, * page-level effects can rerun unexpectedly, * the app should not create duplicate vouchers for the same user. The `useRef` flag is a simple guard against duplicate issuance attempts from the frontend side. The example also resets that guard when the connected account changes: ```tsx useEffect(() => { hasRequestedOnceRef.current = false; }, [account?.address]); ``` Without that reset, switching wallets could accidentally prevent the new account from receiving its own voucher request. Preparing ez-transaction parameters [#preparing-ez-transaction-parameters] The bridge between session state and normal Gear JS transaction preparation is `usePrepareEzTransactionParams()`. Its role is to provide the extra parameters needed when delegated execution is active, such as: * `sessionForAccount`, * account or signer adjustments, * gasless voucher information. That lets the final transaction-preparation step remain close to the normal Gear JS API instead of branching everywhere in UI code. The actual send handler shows the full orchestration: ```tsx const handleSendHello = async () => { if (!signless.isActive) { alert("Activate the signless session first"); return; } setLoading(true); try { const { sessionForAccount, ...params } = await prepareEzTransactionParams(false); if (!sessionForAccount) throw new Error("Missing sessionForAccount"); const { transaction } = await prepareTransactionAsync({ args: [null], value: 0n, ...params, }); signAndSend(transaction, { onSuccess: () => setLoading(false), onError: () => setLoading(false), }); } catch (e) { setLoading(false); } }; ``` This is the core frontend flow: * verify signless state, * request ez-transaction parameters, * prepare a normal program transaction with those parameters, * hand the prepared transaction to the custom sender. Balance resolution before send [#balance-resolution-before-send] The custom `useSignAndSend` helper composes ez-transaction state with a balance guard: ```ts const { checkBalance } = useCheckBalance({ signlessPairVoucherId: signless.voucher?.id, gaslessVoucherId: gasless.voucherId, }); ``` Then, before submission, it reads the estimated gas directly from the prepared extrinsic: ```ts const calculatedGas = BigInt(transaction.extrinsic.args[2].toString()); ``` That is useful because the prepared transaction already contains the gas estimate. The hook does not need to recalculate it. The sender then waits for the program response before reporting success: ```ts void transaction .signAndSend() .then(({ response }) => response().then(() => { onSuccess?.(); }) ) .catch((error: unknown) => { onError?.(); alert.error("Transaction failed"); }); ``` This is more than a raw extrinsic submission. The frontend waits for the Gear/Sails response before treating the action as successful. The balance-check flow [#the-balance-check-flow] `useCheckBalance(...)` resolves the balance source in a strict order: 1. gasless voucher, 2. signless pair voucher, 3. user wallet balance. Then it computes the minimum required balance: ```ts required = existentialDeposit + gasLimit * valuePerGas ``` Only if the balance source can cover that amount does the callback continue into `signAndSend()`. This is one of the most practical parts of the example, because it prevents avoidable on-chain failures before the transaction is submitted. The actual balance guard is: ```ts const chainBalance = BigInt(balance?.toString() ?? "0"); const valuePerGas = BigInt(api.valuePerGas.toString()); const chainEDeposit = BigInt(api.existentialDeposit.toString()); const required = chainEDeposit + limit * valuePerGas; if (chainBalance < required) { alert.error( `Low balance on ${stringShorten(addressToCheck ?? "", 8)}` ); onError?.(); return; } callback(); ``` That makes the balance check explicit and user-facing before the app attempts the on-chain call. Signless session UI [#signless-session-ui] The `EnableSignlessSession` component handles the signless lifecycle: * generate ephemeral key pair in the browser, * submit `create_session(...)`, * store the browser-side key material, * later delete the session and local key material when signless mode is turned off. The allowed actions array must match the contract-side `ActionsForSession` variants exactly. That alignment between frontend and contract allowlist is the key security boundary of the signless model. In the example, the allowlist is visible at the component boundary: ```tsx ``` That makes the connection between frontend UX and contract-side permissions easy to audit. What this pattern teaches [#what-this-pattern-teaches] This example is useful because it shows the full coordination problem, not just one helper: * provider setup, * voucher bootstrap, * signless bootstrap, * delegated transaction parameter preparation, * balance check, * final submission. That makes it much closer in spirit to the detailed contract articles. Practical guidance for production apps [#practical-guidance-for-production-apps] * Treat gasless and signless as separate but coordinated layers. * Do not create signless sessions before gas funding is ready. * Prevent duplicate voucher requests in the frontend even if the backend is also idempotent. * Keep the signless allowlist narrow and aligned with contract-side action enums. * Show the user clear state transitions: voucher pending, signless active, delegated execution ready. Source code [#source-code] * [`frontend/patterns/ez-transactions`](https://github.com/gear-foundation/vara-dapp-patterns/tree/master/frontend/patterns/ez-transactions) # Frontend Patterns The frontend examples in `vara-dapp-patterns/frontend` show how to organize React-side interaction with Vara programs without hiding the important execution boundaries. Included patterns [#included-patterns] | Pattern | Focus | Source | | --------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | [Preparing Program Transactions](/docs/vara-network/examples/Patterns/frontend/prepare-program-tx) | Turn UI parameters into a ready-to-sign transaction | [`frontend/patterns/hooks/prepare-program-tx`](https://github.com/gear-foundation/vara-dapp-patterns/tree/master/frontend/patterns/hooks/prepare-program-tx) | | [Executing Prepared Transactions with React Query](/docs/vara-network/examples/Patterns/frontend/program-tx-mutation) | Wrap `signAndSend()` into a mutation with consistent state | [`frontend/patterns/hooks/program-tx-mutation`](https://github.com/gear-foundation/vara-dapp-patterns/tree/master/frontend/patterns/hooks/program-tx-mutation) | | [Verified Sign & Send](/docs/vara-network/examples/Patterns/frontend/verified-sign-and-send) | Confirm real program-level success, not just block inclusion | [`frontend/patterns/hooks/verified-sign-and-send`](https://github.com/gear-foundation/vara-dapp-patterns/tree/master/frontend/patterns/hooks/verified-sign-and-send) | | [Provider Composition](/docs/vara-network/examples/Patterns/frontend/provider-composition) | Centralize API, account, alert, query, router, and theme providers | [`frontend/patterns/providers/with-providers`](https://github.com/gear-foundation/vara-dapp-patterns/tree/master/frontend/patterns/providers/with-providers) | | [Query Provider](/docs/vara-network/examples/Patterns/frontend/query-provider) | Configure React Query for explicit blockchain-style invalidation | [`frontend/patterns/providers/query-provider`](https://github.com/gear-foundation/vara-dapp-patterns/tree/master/frontend/patterns/providers/query-provider) | | [EZ Transactions](/docs/vara-network/examples/Patterns/frontend/ez-transactions) | Combine signless sessions and gasless vouchers in the browser | [`frontend/patterns/ez-transactions`](https://github.com/gear-foundation/vara-dapp-patterns/tree/master/frontend/patterns/ez-transactions) | A useful mental model [#a-useful-mental-model] These examples intentionally separate three stages: 1. Prepare a valid extrinsic. 2. Execute it in a controlled way. 3. Verify that the program actually succeeded. That split makes React code easier to test, easier to reuse across components, and less likely to mix UI state with chain-specific error handling. # Preparing Program Transactions About [#about] Frontend code that interacts with Vara programs usually repeats the same preparation work in many places: check that the program client exists, check that a wallet is connected, transform UI state into ordered contract arguments, and then call Gear JS hooks to build a ready-to-send transaction. The `usePrepareProgramTx` pattern isolates that work into one dedicated hook. It owns the preparation phase only. It does not sign, send, retry, or verify the result. That separation is important because transaction preparation and transaction execution fail for different reasons and should usually be debugged independently. Why the pattern matters [#why-the-pattern-matters] Without a preparation layer, React code often ends up with the same logic duplicated across forms, action buttons, drawers, and modals: * validation of whether a program client was loaded, * validation of whether an account is connected, * manual mapping from domain parameters into tuple arguments, * manual forwarding of optional gas settings. That makes the app harder to evolve. If the target method signature changes, several UI components may need to be updated at once. A small wrapper hook gives the app one consistent place where “UI params become extrinsic params.” Pattern overview [#pattern-overview] The pattern wraps `usePrepareProgramTransaction(...)` from `@gear-js/react-hooks`. High-level flow: 1. A component passes in `program`, `serviceName`, `functionName`, and `mapArgs`. 2. The hook reads the currently connected account through `useAccount()`. 3. The hook exposes `canPrepare`, which becomes `true` only when both the program and account are available. 4. The hook exposes `prepare(params)`. 5. `prepare(params)` converts `params` into the ordered argument array expected by the program method. 6. The hook calls `prepareTransactionAsync(...)` and returns the prepared transaction object. Core code [#core-code] The actual hook is intentionally compact: ```ts export function usePrepareProgramTx(options: PrepareProgramTxOptions) { const { program, serviceName, functionName, mapArgs, gasLimit } = options; const { account } = useAccount(); const { prepareTransactionAsync } = usePrepareProgramTransaction({ program, serviceName, functionName, }); const canPrepare = Boolean(program && account); const prepare = useCallback( async (params: TParams): Promise => { if (!program) throw new Error('Program instance is not available'); if (!account) throw new Error('No account connected'); const { transaction } = (await prepareTransactionAsync({ args: mapArgs(params), ...(gasLimit ? { gasLimit } : {}), })) as PrepareTransactionAsyncResult; return transaction; }, [program, account, prepareTransactionAsync, mapArgs, gasLimit], ); return { program, account, canPrepare, prepare, }; } ``` Even though it is short, it creates a clean architectural boundary. Main components [#main-components] Program Instance [#program-instance] `program` is the loaded Sails or Gear JS client instance for the target contract. Typical usage with a generated program client looks like this: ```tsx const { data: program } = useProgram({ library: Program, id: import.meta.env.VITE_PROGRAM_ID, }); const transferTx = usePrepareProgramTx({ program, serviceName: 'Vft', functionName: 'transfer', mapArgs: ({ to, amount }) => [to, amount], }); ``` If `program` is missing, the app is not ready to prepare a transaction. That usually means one of the following: * the program client is still loading, * the program ID is wrong, * the app failed to initialize the generated client. The hook treats that as a preparation failure rather than letting the error leak deeper into the submission phase. Service and Function Names [#service-and-function-names] These identify the exact exported method that should be prepared. For example, a VFT transfer targets the `Vft` service and its `transfer` function: ```tsx usePrepareProgramTx({ program, serviceName: 'Vft', functionName: 'transfer', mapArgs: ({ to, amount }) => [to, amount], }); ``` That is useful because the same component-level pattern can be reused for multiple commands. The hook is not coupled to any domain-specific method name. Argument Mapper [#argument-mapper] `mapArgs` is the heart of the pattern. It transforms UI-friendly input into the ordered tuple expected by the contract method. ```tsx const { prepare, canPrepare } = usePrepareProgramTx({ program, serviceName, functionName, mapArgs: (params) => [params.to, params.amount], }); ``` That lets the component keep its own domain model while the hook owns the ABI-facing model. A fuller example with a typed form model: ```tsx type TransferForm = { to: string; amount: bigint; }; const transferTx = usePrepareProgramTx({ program, serviceName: 'Vft', functionName: 'transfer', mapArgs: (form) => [form.to, form.amount], }); ``` Optional Gas Limit [#optional-gas-limit] Some flows want to override gas explicitly, while others let the underlying tooling estimate it. The hook supports both paths without mixing them into the UI component. ```tsx const prepared = usePrepareProgramTx({ program, serviceName: 'Service', functionName: 'heavyOperation', gasLimit: 100_000_000_000n, mapArgs: ({ id }) => [id], }); ``` Full flow in a component [#full-flow-in-a-component] A typical flow looks like this: ```tsx const { prepare, canPrepare } = usePrepareProgramTx({ program, serviceName: 'Vft', functionName: 'transfer', mapArgs: ({ to, amount }) => [to, amount], }); const onTransfer = async () => { if (!canPrepare) return; const tx = await prepare({ to, amount }); await tx.signAndSend(); }; ``` The important detail is that the component still controls when to execute, but it does not own the low-level preparation rules. What prepareTransactionAsync(...) receives [#what-preparetransactionasync-receives] The call into Gear JS hooks is small but important: ```ts const { transaction } = await prepareTransactionAsync({ args: mapArgs(params), ...(gasLimit ? { gasLimit } : {}), }); ``` The hook intentionally builds only the object needed by the lower-level transaction builder: * `args` is the method argument source after mapping, * `gasLimit` is included only when the caller explicitly provided it, * signing information is not attached here, * callbacks and UI state are not handled here. That means the resulting transaction can be sent by a simple mutation, by a verified sender, or by a custom signless/gasless execution layer. Reusable command-specific hook [#reusable-command-specific-hook] In a real app, you would often wrap the generic pattern with a domain-specific hook: ```tsx export function usePrepareVftTransfer(program: unknown) { return usePrepareProgramTx({ program, serviceName: 'Vft', functionName: 'transfer', mapArgs: ({ to, amount }: { to: string; amount: bigint }) => [to, amount], }); } ``` That gives components a business-friendly API while keeping the generic transaction-preparation logic in one place. Failure boundaries [#failure-boundaries] This pattern is useful precisely because it makes preparation errors explicit. Typical preparation failures include: * missing program instance, * missing connected account, * invalid argument mapping, * unsupported gas override shape. Those are different from later execution failures such as wallet rejection, RPC errors, or program-level reply errors. Relationship with other frontend patterns [#relationship-with-other-frontend-patterns] This pattern is usually the first stage in a larger frontend flow: 1. [Preparing Program Transactions](/docs/vara-network/examples/Patterns/frontend/prepare-program-tx) 2. [Executing Prepared Transactions with React Query](/docs/vara-network/examples/Patterns/frontend/program-tx-mutation) or [Verified Sign & Send](/docs/vara-network/examples/Patterns/frontend/verified-sign-and-send) 3. Query invalidation or optimistic UI updates That staged design mirrors what is already done in the contract articles: one pattern, one responsibility. Practical guidance for production apps [#practical-guidance-for-production-apps] * Keep `mapArgs` as close as possible to the ABI boundary. * Do not mix alerts, retries, or mutation state into the preparation hook. * Treat `canPrepare` as a UI hint, not as your only guard. The runtime checks inside `prepare(...)` still matter. * Reuse one preparation hook per command shape instead of rebuilding argument tuples inline in components. Source code [#source-code] * [`frontend/patterns/hooks/prepare-program-tx`](https://github.com/gear-foundation/vara-dapp-patterns/tree/master/frontend/patterns/hooks/prepare-program-tx) # Executing Prepared Transactions with React Query About [#about] After a Vara transaction has been prepared, the UI still needs to execute it, track pending state, react to errors, and run post-success callbacks. The `useProgramTxMutation` pattern solves that layer by wrapping a prepared transaction’s `signAndSend()` method in a React Query mutation. This pattern is intentionally simple. It does not know how the transaction was built, and it does not attempt deep program-level verification. Its responsibility is to make execution state predictable. Why the pattern matters [#why-the-pattern-matters] Many dApps start with component-local code such as: ```tsx setLoading(true); try { const tx = await prepare(params); await tx.signAndSend(); // success handling } catch (error) { // error handling } finally { setLoading(false); } ``` That works at first, but the state model becomes inconsistent as the app grows. Different components interpret loading, retry, and error behavior differently. React Query gives the app one shared abstraction for mutation lifecycle: * pending, * success, * error, * retries, * `onSuccess`, * `onError`, * `onSettled`. Pattern overview [#pattern-overview] The flow is deliberately narrow: 1. Another layer prepares a transaction. 2. `useProgramTxMutation()` receives that prepared transaction. 3. The mutation checks that the object has a callable `signAndSend`. 4. The mutation executes `signAndSend()`. 5. React Query manages the resulting lifecycle state. Core code [#core-code] ```ts export function useProgramTxMutation( mutationOptions?: ProgramTxMutationOptions, ) { return useMutation>({ mutationFn: (transaction) => { if (!transaction?.signAndSend) { throw new Error('Invalid transaction object provided'); } return transaction.signAndSend(); }, ...mutationOptions, }); } ``` That small wrapper is enough to move execution state out of local component logic and into a shared mutation model. Main components [#main-components] Prepared transaction input [#prepared-transaction-input] The mutation expects a value shaped like: ```ts type SignAndSendableTransaction = { signAndSend: () => Promise; }; ``` That means this pattern stays decoupled from the exact preparation hook that produced the transaction. Mutation lifecycle [#mutation-lifecycle] Because this is a normal React Query mutation, consumers automatically get: * `mutate`, * `mutateAsync`, * `isPending`, * `isSuccess`, * `isError`, * `error`, * retry and callback support. This makes it a good fit for ordinary “click button, send command” flows. Full flow in a component [#full-flow-in-a-component] ```tsx const { prepare } = usePrepareProgramTx({ program, serviceName: 'Vft', functionName: 'transfer', mapArgs: ({ to, amount }) => [to, amount], }); const execute = useProgramTxMutation({ onSuccess: () => { // invalidate queries, close modal, show success alert }, }); const onTransfer = async () => { const tx = await prepare({ to, amount }); await execute.mutateAsync(tx); }; ``` Notice how this keeps the flow staged: * prepare first, * execute second, * react to execution state third. Mutation options in practice [#mutation-options-in-practice] Because the hook forwards normal React Query mutation options, the component can keep domain reactions outside the execution primitive: ```tsx const execute = useProgramTxMutation({ onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['vft-balance', account?.address] }); alert.success('Transfer submitted'); }, onError: (error) => { alert.error(error.message); }, }); ``` This is the main benefit of using React Query here. The hook stays generic, while each screen decides how to respond to success or failure. Why the input guard exists [#why-the-input-guard-exists] The mutation starts with: ```ts if (!transaction?.signAndSend) { throw new Error('Invalid transaction object provided'); } ``` That protects the execution layer from accidentally receiving a failed preparation result, a plain object, or an undefined value from a conditional flow. What this pattern does not do [#what-this-pattern-does-not-do] This pattern deliberately avoids solving three other concerns: * It does not prepare the transaction. * It does not inspect chain events or Gear replies. * It does not decide whether the UI should trust block inclusion as success. That restraint is useful. If you need stronger guarantees, the next layer is [Verified Sign & Send](/docs/vara-network/examples/Patterns/frontend/verified-sign-and-send). When this pattern is enough [#when-this-pattern-is-enough] Use this pattern when: * a standard runtime submission is sufficient for UX, * the app already performs verification elsewhere, * you want React Query’s lifecycle management without adding reply decoding logic. Production guidance [#production-guidance] * Use `mutateAsync` when you want linear async control flow in the component. * Use `onSuccess` to trigger invalidation of related queries. * Keep domain-specific alerts and navigation outside the hook via mutation callbacks. * Upgrade to a verified execution pattern for flows where block inclusion is not a strong enough success condition. Source code [#source-code] * [`frontend/patterns/hooks/program-tx-mutation`](https://github.com/gear-foundation/vara-dapp-patterns/tree/master/frontend/patterns/hooks/program-tx-mutation) # Provider Composition About [#about] React apps on Vara often need the same global stack: network API context, wallet/account context, alerts, query cache, routing, theming, and sometimes extra protocol-specific providers such as signless or gasless helpers. The `withProviders` pattern centralizes that stack into one reusable wrapper. That sounds cosmetic at first, but it becomes architectural once provider order starts to matter. Why the pattern matters [#why-the-pattern-matters] When providers are assembled ad hoc in `main.tsx` or `App.tsx`, three problems appear quickly: * provider order becomes accidental, * test setup becomes repetitive, * app bootstrapping logic spreads across multiple files. On Vara, provider order is especially important because later layers often assume the presence of earlier blockchain-specific context. High-level flow [#high-level-flow] The pattern works in three steps: 1. Define the core provider stack around Vara-specific dependencies. 2. Allow application-specific providers to be appended in a controlled order. 3. Return a higher-order component that wraps the application tree consistently everywhere. Core code [#core-code] ```tsx export function createWithProviders({ config, extraProviders = [], alertTemplate, alertContainerClassName, }: WithProvidersOptions) { const providers: AnyProvider[] = [ ({ children }) => {children}, ({ children }) => {children}, ({ children }) => ( {children} ), ...extraProviders, ]; return function withProviders(Component: ComponentType) { return function Wrapped() { return providers.reduceRight( (children, Provider) => {children}, , ); }; }; } ``` The pattern keeps the core Gear/Vara providers fixed and makes the app-specific layers extensible. Type shape [#type-shape] The helper deliberately accepts only providers that wrap `children`: ```ts type AnyProvider = ComponentType; type WithProvidersOptions = { config: AppRuntimeConfig; extraProviders?: AnyProvider[]; alertTemplate?: ComponentType; alertContainerClassName?: string; }; ``` That constraint keeps provider composition predictable. Each extra provider can be inserted into the stack without custom props at the composition site. Main components [#main-components] API provider [#api-provider] The API provider establishes the WebSocket connection to the Vara node: ```tsx function ApiProvider({ children, endpoint }: ProviderProps & { endpoint: string }) { return {children}; } ``` Everything else that depends on network access builds on top of this layer. Account provider [#account-provider] The account provider exposes wallet state and extension integration: ```tsx function AccountProvider({ children, appName }: ProviderProps & { appName: string }) { return {children}; } ``` Without it, transaction helpers cannot access the connected account or signer. Alert provider [#alert-provider] The alert provider centralizes notification behavior while still allowing the application to inject custom UI templates. Extra providers [#extra-providers] `extraProviders` are where application-level composition happens: * `QueryProvider`, * router provider, * theme provider, * `EzTransactionsProvider`, * analytics or app-specific context providers. That keeps the boundary between protocol bootstrapping and app bootstrapping clear. How reduceRight builds the tree [#how-reduceright-builds-the-tree] The final wrapper uses `reduceRight(...)`: ```tsx return providers.reduceRight( (children, Provider) => {children}, , ); ``` That means the first provider in the array becomes the outermost provider in the rendered tree. Provider order is encoded directly in the array instead of being scattered across JSX. Recommended provider order [#recommended-provider-order] 1. API provider 2. Account provider 3. Alert provider 4. Query provider 5. Router, theme, and protocol-specific providers Why this order: * API must exist before account-aware or chain-aware logic. * Account must exist before execution helpers. * Alerts can sit above most app behavior. * Query and router providers are application-facing layers built on top of the base protocol stack. Full app-level usage [#full-app-level-usage] ```tsx const withProviders = createWithProviders({ config: { nodeEndpoint: 'wss://rpc.vara.network', appName: 'My Vara App', }, extraProviders: [QueryProvider, BrowserRouter], }); export default withProviders(App); ``` This makes entrypoint code cleaner while keeping the provider graph explicit. Practical guidance for production apps [#practical-guidance-for-production-apps] * Keep the core provider list minimal and protocol-focused. * Push app-specific providers into `extraProviders` rather than hardcoding them into the helper. * Treat provider order as part of the app architecture, not as incidental JSX nesting. * Reuse the same wrapper in tests so component behavior matches the real app environment. Source code [#source-code] * [`frontend/patterns/providers/with-providers`](https://github.com/gear-foundation/vara-dapp-patterns/tree/master/frontend/patterns/providers/with-providers) # Query Provider About [#about] React Query is often configured with assumptions that work well for REST dashboards: background refetching, automatic retries, and cache freshness windows measured in seconds or minutes. Blockchain applications tend to need a different model. State changes are often driven by explicit transactions, and data refresh is usually safest when tied to known lifecycle points such as “a mutation just succeeded” or “a block-based event finished.” The `QueryProvider` pattern gives the app one React Query client configured around those assumptions. Why the pattern matters [#why-the-pattern-matters] If background cache behavior is too aggressive in a Vara app, the UI can become noisy or misleading: * a tab focus event may trigger an unexpected RPC read, * retries may hide deterministic contract errors, * stale/fresh heuristics may conflict with explicit transaction-driven invalidation. This pattern chooses predictability over automation. Core code [#core-code] ```tsx const queryClient = new QueryClient({ defaultOptions: { queries: { gcTime: 0, staleTime: Infinity, refetchOnWindowFocus: false, retry: false, }, }, }); export function QueryProvider({ children }: PropsWithChildren) { return {children}; } ``` The implementation is small, but the defaults are opinionated. Default configuration explained [#default-configuration-explained] Infinite Stale Time [#infinite-stale-time] Queries are treated as fresh until the app explicitly invalidates them. That matches blockchain UX well because successful transactions are often the natural invalidation trigger. Immediate Garbage Collection [#immediate-garbage-collection] Once no component is observing a query, React Query may garbage-collect it immediately. This keeps the cache lifecycle easy to reason about: active screens hold data; inactive screens re-read or get invalidated explicitly. No Refetch on Window Focus [#no-refetch-on-window-focus] Switching tabs should not silently trigger a new round of chain queries unless the app deliberately wants that behavior. No Automatic Retry [#no-automatic-retry] In blockchain flows, many errors are deterministic: * bad arguments, * contract rejection, * insufficient balance, * invalid reply decoding assumptions. Automatically retrying those failures often makes the UX worse rather than better. How this pattern fits into the larger flow [#how-this-pattern-fits-into-the-larger-flow] In a typical Vara app: 1. Query data is loaded and held by React Query. 2. A mutation executes a transaction. 3. After success, the app invalidates only the affected queries. That gives the app an explicit cause-and-effect model instead of a background polling model. Mutation-driven invalidation example [#mutation-driven-invalidation-example] The intended usage is to invalidate data after known successful execution: ```tsx const queryClient = useQueryClient(); const execute = useProgramTxMutation({ onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['program-state', programId] }); }, }); ``` That is different from “refetch whenever the tab gains focus.” The UI updates because a specific chain operation succeeded. When to override defaults [#when-to-override-defaults] Some screens may still need more aggressive behavior. For example, a block explorer-style screen may want polling: ```tsx useQuery({ queryKey: ['latest-events'], queryFn: fetchLatestEvents, refetchInterval: 3000, }); ``` The pattern keeps global defaults conservative and lets individual queries opt into special behavior. Production guidance [#production-guidance] * Invalidate queries after known successful mutations instead of relying on freshness timers. * Add query-specific overrides only when a screen truly needs different behavior. * Keep the global client conservative. It is easier to opt a query into more aggressive behavior than to unwind too much global automation later. Source code [#source-code] * [`frontend/patterns/providers/query-provider`](https://github.com/gear-foundation/vara-dapp-patterns/tree/master/frontend/patterns/providers/query-provider) # Verified Sign & Send About [#about] Basic extrinsic helpers often stop as soon as a transaction is included in a block. On Vara, that is not always enough. An extrinsic may succeed at the runtime level and still fail at the program level through a reply error, queued-message error, or decoded contract-level rejection. The `useVerifiedSignAndSend` pattern is designed for the stronger success condition: not just “the chain accepted the extrinsic,” but “the intended program interaction completed without an error reply.” Why the pattern matters [#why-the-pattern-matters] This distinction is especially important in Gear-style flows where: * the extrinsic submits a message, * the runtime queues one or more program messages, * the actual contract result appears later as a reply event. If the frontend treats `inBlock` as final success, it can easily show a false positive. High-level flow [#high-level-flow] The hook executes an extrinsic and then verifies success in several stages: 1. wait until the result reaches `inBlock` or `finalized`, 2. scan events for `ExtrinsicFailed`, 3. collect all `gear.MessageQueued` events, 4. resolve the block hash, 5. fetch reply events for queued messages, 6. decode reply errors with the correct type registry, 7. reject if any reply indicates program-level failure. That full chain is what makes the pattern meaningfully stronger than a plain submission helper. Core code [#core-code] The two key parts are event collection and reply decoding. The hook first builds an O(1) lookup table for program registries: ```ts const registryByProgramId = useMemo( () => new Map(programs.map((p) => [p.programId, p.registry] as const)), [programs], ); ``` That matters because the same extrinsic can queue messages for more than one program, and each reply must be decoded against the correct registry. Runtime and queue scanning: ```ts for (const { event } of events) { const { method, section } = event; if (section === 'gear' && method === 'MessageQueued') { queued.push(event.data as MessageQueuedData); } if (method === 'ExtrinsicFailed') { const error = api.getExtrinsicFailedError(event); settleOnce(() => reject(new Error(`${error.method}: ${docs}`))); return; } } ``` Reply verification: ```ts const reply = await api.message.getReplyEvent(programId, id.toHex(), blockHash); const detailsOpt = reply.data.message.details; if (detailsOpt.isNone) return; const { details, payload } = reply.data.message; return throwOnErrorReply(details.unwrap().code, payload, api.specVersion, registry); ``` The first part asks whether the extrinsic failed at the runtime level. The second asks whether the program failed after the message was accepted. Submission callback in detail [#submission-callback-in-detail] The central callback waits for the selected status boundary and then performs verification: ```ts const statusCallback = (result: ISubmittableResult) => { if (!shouldResolve(result)) return; const { events, status } = result; const queued: MessageQueuedData[] = []; for (const { event } of events) { // collect MessageQueued and reject on ExtrinsicFailed } const blockHash = (status.isInBlock ? status.asInBlock : status.asFinalized).toHex(); checkErrorReplies(blockHash, queued) .then(() => settleOnce(() => resolve())) .catch((err) => settleOnce(() => reject(err))); }; ``` This is the frontend-side bridge between Substrate status updates and Gear reply verification. Signer paths [#signer-paths] The hook supports two signing paths. If `addressOrPair` is provided, it signs directly: ```ts const unsub = await extrinsic.signAndSend(addressOrPair, statusCallback); ``` If not, it uses the connected extension account: ```ts const { address, signer } = account!; const unsub = await extrinsic.signAndSend(address, { signer }, statusCallback); ``` This makes the pattern usable for normal wallet flows and for custom account-pair flows, including session-style execution. Settle-once handling [#settle-once-handling] The hook guards promise resolution with `settleOnce(...)`: ```ts let settled = false; const settleOnce = (fn: () => void) => { if (settled) return; settled = true; fn(); }; ``` That is important because `signAndSend` callbacks can fire multiple times as status changes. Main components [#main-components] Program Registries [#program-registries] The hook accepts a list of `{ programId, registry }` pairs: ```ts export type ProgramRegistry = { programId: HexString; registry: TypeRegistry; }; ``` That mapping is necessary because reply payloads must be decoded with the right registry for the program that emitted them. Typical usage: ```tsx const verifiedSend = useVerifiedSignAndSend({ programs: [ { programId: VFT_PROGRAM_ID, registry: vftProgram.registry, }, ], }); ``` If a program is omitted, the hook can still catch runtime failures, but it cannot decode typed reply errors for that program. Resolution Boundary [#resolution-boundary] `resolveOn` chooses the status boundary: * `inBlock`: faster, * `finalized`: slower but stronger. This is a practical tradeoff between responsiveness and certainty. ```tsx const fastSend = useVerifiedSignAndSend({ programs, resolveOn: 'inBlock', }); const finalitySend = useVerifiedSignAndSend({ programs, resolveOn: 'finalized', }); ``` Use `inBlock` for responsive UX and `finalized` when the operation is high-value or should wait for the stronger chain boundary. Signer Source [#signer-source] The submission path supports either: * the connected extension account, or * an explicit `addressOrPair`. That makes the pattern reusable in both standard wallet flows and more specialized signing flows. Standard extension-account execution: ```tsx await verifiedSend.mutateAsync({ extrinsic, }); ``` Explicit keypair or session signer execution: ```tsx await verifiedSend.mutateAsync({ extrinsic, addressOrPair: sessionPair, }); ``` Full execution flow [#full-execution-flow] Internally the hook performs a Promise-based orchestration around `extrinsic.signAndSend(...)`: 1. choose the signer source, 2. subscribe to status updates, 3. ignore updates until the configured status boundary, 4. inspect events, 5. build the block hash, 6. query replies, 7. resolve only if no runtime or program-level error is found, 8. unsubscribe once the Promise settles. The unsubscribe cleanup matters because `signAndSend(...)` often returns an unsubscribe handle that should not be left open after success or failure. When to use this pattern [#when-to-use-this-pattern] This pattern is a strong fit for flows where “real success” matters: * token transfers, * session creation, * factory-style multi-step interactions, * operations that drive user-visible balances or state transitions. It is especially useful when the frontend should not continue until it is sure the contract logic accepted the operation. Practical guidance for production apps [#practical-guidance-for-production-apps] * Provide registries for every program whose replies matter. * Prefer `finalized` for high-value flows where reorg resistance matters. * Keep transaction preparation separate; this hook should stay focused on execution and verification. * Surface decoded reply errors to the user rather than collapsing everything into a generic submission failure. Source code [#source-code] * [`frontend/patterns/hooks/verified-sign-and-send`](https://github.com/gear-foundation/vara-dapp-patterns/tree/master/frontend/patterns/hooks/verified-sign-and-send) # Vara Fungible Token (VFT) Standard and Extended Implementation The **Vara Fungible Token (VFT)** standard is the equivalent of Ethereum's **ERC-20** on the Vara Network. The Vara Fungible Token Standard outlines a unified API for implementing fungible token functionalities in programs. The initial section of this document provides a comprehensive examination of the core VFT service, which serves as a foundational framework. It covers essential operations such as token transfers and approvals for third-party spending, detailing the contract state, interface, and key methods involved. The source code of the standard-service is avaiable on the [GitHub](https://github.com/gear-foundation/standards/tree/master/vft-service). The subsequent section expands on how to leverage and extend this core service to develop a fully functional token application. It illustrates the process of adding advanced features like minting and burning, demonstrating how to build upon the core VFT service to create a comprehensive and customizable token system. This extension highlights the flexibility and potential of the core standard, providing a pathway to develop more sophisticated and tailored token solutions. The source code of the extended version is avaiable on the [GitHub](https://github.com/gear-foundation/standards/tree/master/extended-vft). A frontend guide showing how to use the `React Sails Hooks` with extended VFT program is available [here](/docs/vara-network/examples/Standards/vft/vft-frontend). The project code is built using the [Sails](/docs/developing/build/sails) framework, which simplifies program development on Gear Protocol. Public CodeIds [#public-codeids] You can instantiate the extended VFT using the public CodeId below. The instantiating account becomes the initial admin/minter/burner. * **CodeId**: `0x81663df58f48684923777cd8cf281bfd2e4ee427926abc52a1fcf4ecd41be7ad` * Mainnet: [link](https://idea.gear-tech.io/code/0x81663df58f48684923777cd8cf281bfd2e4ee427926abc52a1fcf4ecd41be7ad?node=wss%3A%2F%2Frpc.vara.network) > Open Gear IDEA → **Codes**, select the network, paste the CodeId, and click **Create Program**. Core VFT Service [#core-vft-service] Functions [#functions] The VFT service includes the following functions: * `Approve(spender, value)` * `Transfer(to, value)` * `TransferFrom(from, to, value)` * `Allowance(owner, spender)` * `BalanceOf(owner)` * `Decimals()` * `Name()` * `Symbol()` * `TotalSupply()` Events [#events] The core service also defines the following events: * `Approval(owner, spender, value)` * `Transfer(from, to, value)` Key Methods [#key-methods] Approve [#approve] ```rust pub fn approve(&mut self, spender: ActorId, value: U256) -> bool ``` This function allows a designated spender to withdraw up to a specified value of tokens from the caller's account, multiple times up to the specified amount. A subsequent call resets the allowance to the new value. A boolean value indicates the success of the operation, and the function triggers an `Approval` event upon successful execution. Transfer [#transfer] ```rust pub fn transfer(&mut self, to: ActorId, value: U256) -> bool ``` This function transfers a specified value of tokens to a designated account. It returns a boolean value indicating the success of the operation and triggers a `Transfer` event upon successful execution. TransferFrom [#transferfrom] ```rust pub fn transfer_from(&mut self, from: ActorId, to: ActorId, value: U256) -> bool ``` This function transfers a specified value of tokens from one account to another, using the allowance mechanism. The transferred value is deducted from the caller’s allowance. A boolean value indicates the success of the operation, and a `Transfer` event is generated upon successful execution. Query methods [#query-methods] name [#name] Returns the name of the token. ```rust pub fn name(&self) -> String ``` symbol [#symbol] Returns the symbol of the token. ```rust pub fn symbol(&self) -> String ``` decimals [#decimals] Returns the decimals of the token. ```rust pub fn decimals(&self) -> u8 ``` total_supply [#total_supply] Returns the total supply of the token. ```rust pub fn total_supply(&self) -> U256 ``` balance_of [#balance_of] Returns the token balance of the `owner` address. ```rust pub fn balance_of(&self, account: ActorId) -> U256 ``` allowance [#allowance] Returns the number of tokens the `spender` account is authorized to spend on behalf of the `owner`. ```rust pub fn allowance(&self, owner: ActorId, spender: ActorId) -> U256 ``` This service is a **foundational base** for creating your own token. It is designed to be extended and inherited to add custom functionality. * **Source Code:** Available on [GitHub](https://github.com/gear-foundation/standards/tree/master/vft-service). * **Extended Version:** Look for the extended example to see how to transform this core logic into a fully functional application. Extended VFT Implementation [#extended-vft-implementation] Additional Features [#additional-features] The extended implementation introduces new functions and events that enhance the basic VFT service. The additional features include: Functions [#functions-1] * `Mint(to, value)` * `Burn(from, value)` * `GrantAdminRole(to)` * `GrantBurnerRole(to)` * `GrantMinterRole(to)` * `RevokeAdminRole(from)` * `RevokeBurnerRole(from)` * `RevokeMinterRole(from)` * `Admins()` * `Burners()` * `Minters()` Events [#events-1] * `Minted(to, value)` * `Burned(from, value)` Implementation Details [#implementation-details] To incorporate the extended functionality, the VFT service is first added as a dependency in the `Cargo.toml` file: ```toml vft-service = { git = "https://github.com/gear-foundation/standards" } ``` A new service is created that extends the existing VFT service: ```rust use vft_service::Service as VftService; pub struct ExtendedService { vft: VftService, } ``` The `#[service(extends = VftService, events = Event)]` attribute is used to ensure that the `ExtendedService` inherits methods and properties from `VftService`, making all core functionalities available within the extended service. Key Methods [#key-methods-1] Mint [#mint] ```rust pub fn mint(&mut self, to: ActorId, value: U256) -> bool ``` This function mints new tokens and assigns them to a specified actor. The function checks if the caller has the minter role and triggers a `Minted` event if the minting is successful. Burn [#burn] ```rust pub fn burn(&mut self, from: ActorId, value: U256) -> bool ``` This function burns tokens from a specified actor. It checks if the caller has the burner role and triggers a `Burned` event if the burning is successful. Role Management [#role-management] * `grant_admin_role(&mut self, to: ActorId)` * `grant_minter_role(&mut self, to: ActorId)` * `grant_burner_role(&mut self, to: ActorId)` * `revoke_admin_role(&mut self, from: ActorId)` * `revoke_minter_role(&mut self, from: ActorId)` * `revoke_burner_role(&mut self, from: ActorId)` These methods manage the assignment and revocation of administrative, minting, and burning roles, ensuring that only authorized actors can perform sensitive operations. Query methods [#query-methods-1] minters [#minters] Returns a list of all actors who have the minter role. ```rust pub fn minters(&self) -> Vec ``` burners [#burners] Returns a list of all actors who have the burner role. ```rust pub fn burners(&self) -> Vec ``` admins [#admins] Returns a list of all actors who have the admin role. ```rust pub fn admins(&self) -> Vec ``` Contract Interface [#contract-interface] The extended service incorporates the following interface: ```rust !@sails: 1.0.0 service Vft { events { Minted { to: actor, value: u256 }, Burned { from: actor, value: u256 }, Approval { owner: actor, spender: actor, value: u256 }, Transfer { from: actor, to: actor, value: u256 }, } functions { Burn(from: actor, value: u256) -> bool; GrantAdminRole(to: actor); GrantBurnerRole(to: actor); GrantMinterRole(to: actor); Mint(to: actor, value: u256) -> bool; RevokeAdminRole(from: actor); RevokeBurnerRole(from: actor); RevokeMinterRole(from: actor); Approve(spender: actor, value: u256) -> bool; Transfer(to: actor, value: u256) -> bool; TransferFrom(from: actor, to: actor, value: u256) -> bool; @query Admins() -> [actor]; @query Burners() -> [actor]; @query Minters() -> [actor]; @query Allowance(owner: actor, spender: actor) -> u256; @query BalanceOf(account: actor) -> u256; @query Decimals() -> u8; @query Name() -> String; @query Symbol() -> String; @query TotalSupply() -> u256; } } program VftProgram { constructors { New(name: String, symbol: String, decimals: u8); } services { Vft: Vft; } } ``` The **Extended VNFT** service illustrates how the core logic can be expanded with advanced features such as **minting, burning, and role management**. This implementation provides a versatile framework for building robust, custom token systems within the Vara ecosystem. **Source Code:** [GitHub Repository](https://github.com/gear-foundation/standards/tree/master/extended-vnft) Conclusion [#conclusion] The core [VFT service](https://github.com/gear-foundation/standards/tree/master/vft-service) establishes a robust foundation for implementing fungible tokens within the Vara ecosystem, encompassing essential functionalities that adhere to recognized token standards. This service functions as a fundamental core, with the [Extended VFT](https://github.com/gear-foundation/standards/tree/master/extended-vft) illustrating how it can be expanded to incorporate advanced capabilities, including minting, burning, and role management. Together, these implementations provide a comprehensive framework for developers, facilitating the creation of tailored and sophisticated token systems. By utilizing both the core service and its extended functionalities, developers are well-positioned to design flexible and secure token solutions that address specific requirements and enhance overall system capabilities. # Quick Token Launch What Is Vara Fungible Token (VFT)? [#what-is-vara-fungible-token-vft] Vara Fungible Token (VFT) is the Vara network's equivalent of Ethereum's ERC-20 standard. You can find detailed specifications [above](/docs/vara-network/examples/Standards/vft). For use with the [Vara↔Ethereum Bridge](/docs/vara-network/bridge), an extended version of the VFT standard is utilized, which includes additional methods beyond the base spec. This guide shows you how to launch a fungible token on the Vara network from the pre-compiled code while **preserving your ownership and admin rights** over it. How to Upload the VFT to Vara [#how-to-upload-the-vft-to-vara] Prerequisites [#prerequisites] 1. A Vara [account](/docs/vara-network/account) using any Substrate-compatible wallet (e.g., SubWallet, Polkadot.js extension). 2. A small amount of VARA tokens to pay gas fees on **Vara Mainnet**. If your wallet is new, claim the one-time grant of **100 free VARA** in [Gear IDEA](https://idea.gear-tech.io/programs?node=wss%3A%2F%2Frpc.vara.network): * Click the **Balance** button (top-right). * Then click **Get Balance**. test balance Uploading the VFT Program [#uploading-the-vft-program] To simplify the process, the VFT code is already deployed to the network. You just need to instantiate a program from it. 1. Open the VFT Code Page: [VFT Code on IDEA Platform](https://idea.gear-tech.io/code/0x30a6da0a6074c3530838ad30827a470e65194dbd9753bbb196af24f207c1601b?node=wss%3A%2F%2Frpc.vara.network) 2. Scroll down and click **Create Program**. 3. Fill in the required parameters (see example on the page): test balance 4. Click **Calculate** (gas) and then **Submit**. 5. Sign the transaction in your wallet—your fungible token is now live on the Vara network! Finalizing Setup for Bridge Compatibility [#finalizing-setup-for-bridge-compatibility] To ensure compatibility with the bridge and other tools, you must send two additional messages to the program: **First message:** 1. Click **Send Message** 2. **Service**: `VftExtension` 3. **Function**: `AllocateNextBalancesShard` 4. Click **Calculate**, then **Send Message** 5. Sign the transaction\ test balance **Second message:** 1. Click **Send Message** again 2. **Service**: `VftExtension` 3. **Function**: `AllocateNextAllowancesShard` 4. Click **Calculate**, then **Send Message** 5. Sign the transaction\ test balance What's Next? [#whats-next] * Your deployed token will appear in the **Programs** tab. * It can also be accessed directly via its on-chain address (e.g., 0x2291a95a56293a51227ff2f18931d5439175cf98adc7cdbef24110d4e88d998b). * To inspect its current state, go to **Read State**, select the appropriate **Service** and **Query** function.\ test balance Admin Capabilities [#admin-capabilities] As the creator (admin), you can now send privileged messages to the contract to: * Mint or burn tokens * Add or remove other admins * Assign user roles or permissions * Configure token parameters * Intergrate to your application This gives you full control to manage the token as needed. # VFT Frontend Guide VFT frontend app example This guide explains how to build a frontend application in React that interacts with the VFT Extended (Vara Fungible Token) contract, using sails-js React hooks. The document provides a step-by-step approach, code samples, and configuration tips for quickly integrating contract calls, queries, and event listeners. **Who is this for?** * React developers integrating smart contract features into a DApp on the Vara blockchain. * Anyone needing a working example and best practices for `sails-js hooks` with VFT contracts. **You will learn:** * How to set up a project from a template * How to generate a contract TypeScript client interface * How to implement and use hooks for transactions, queries, and events * How to build UI components for interacting with the VFT contract [See VFT Standard description](/docs/vara-network/examples/Standards/vft) Prerequisites [#prerequisites] Before starting, make sure your environment is ready: * **Node.js** (version 18+ recommended) * **Yarn** or **npm** * **Git** * [**Gear IDEA**](https://idea.gear-tech.io) (for contract deployment, not needed for frontend only) * Access to **Vara Network** (a node endpoint and some VARA to pay for transactions) * Installed browser wallet (e.g., [Polkadot.js extension](https://polkadot.js.org/extension/) or compatible with Substrate network) * **Basic knowledge** of React, TypeScript, and smart contract architecture Before starting the frontend development, ensure your contract is **uploaded to the Vara Network** and you have its **Program ID** ready. Interaction with the network is impossible without a deployed contract. Project Setup [#project-setup] Create Frontend App from Template [#create-frontend-app-from-template] Use the official Vara React app [template](/docs/developing/api/tooling/create-gear-app). Run in your terminal: ```bash npx degit gear-foundation/dapps/frontend/templates/create-vara-app dApp cd dApp yarn install # or npm install yarn start # or npm start ``` The provided template is production-ready and includes: * **Wallet Connection:** Pre-configured interaction with Vara-compatible wallets. * **Node Provider:** Pre-set connection to the Vara Network. * **Alert System:** Built-in notifications for user feedback. * **Contract Ready:** Fully prepared for smart contract integration. Project Structure Overview [#project-structure-overview] After setup, important files and folders are: ``` .env # Environment variables (node, contract addresses) src/consts.ts # App-level constants from env src/hooks/ # Your custom and generated React hooks src/components/ # UI components src/App.tsx # Main app entry ``` Generating TypeScript Client (lib.ts) for Your Contract [#generating-typescript-client-libts-for-your-contract] Deploy Your Contract [#deploy-your-contract] Make sure your VFT contract is already deployed on Vara Mainnet (via [Gear IDEA](https://idea.gear-tech.io)) and you have its address (ProgramId). Generate Client Library [#generate-client-library] Follow the client generation instructions: [Client Generation Docs](/docs/developing/sails-js/client-generation) ```bash npx @gear-js/sails codegen --program --output src/hooks/lib.ts ``` * `--program` is your deployed contract ID (0x...). * `--output` is the path for generated TS client code. Add Library to Hooks [#add-library-to-hooks] Import the generated client (ABI) in your hooks, e.g.: ```ts import { SailsProgram } from './lib'; ``` The generated file (IDL) provides a full description of your contract's API. Use it to enable **type-safety** on the frontend, ensuring that your calls match the contract's expected arguments and return types. Environment Configuration [#environment-configuration] Create a `.env` file in the project root: ```text VITE_NODE_ADDRESS=wss://rpc.vara.network VITE_CONTRACT=0x... # your deployed contract address ``` Add a config file (`consts.ts`) for environment variables: ```ts export const ENV = { NODE: import.meta.env.VITE_NODE_ADDRESS as string, CONTRACT: import.meta.env.VITE_CONTRACT as `0x${string}`, }; ``` Implementing React Hooks for Contract Interaction [#implementing-react-hooks-for-contract-interaction] For detailed documentation on **Sails React Hooks** and up-to-date API examples, check out these links: * [React Sails Hooks Documentation](/docs/developing/sails-js/react-hooks) * [Full hooks/api.ts source on GitHub](https://github.com/gear-foundation/dapps/blob/master/frontend/apps/vft-extended/src/hooks/api.ts) Your hooks (e.g., `hooks/api.ts`) wrap all main contract actions, queries, and event subscriptions. Program Instance [#program-instance] ```ts import { useProgram } from '@gear-js/react-hooks'; import { SailsProgram } from './lib'; import { ENV } from '../consts'; export function useProgramInstance() { return useProgram({ library: SailsProgram, id: ENV.CONTRACT, }); } ``` Token Actions (Mint, Burn, Transfer) [#token-actions-mint-burn-transfer] ```ts import { useSendProgramTransaction } from '@gear-js/react-hooks'; export function useSendMintTransaction() { const { data: program } = useProgramInstance(); return useSendProgramTransaction({ program, serviceName: 'vft', functionName: 'mint', }); } export function useSendBurnTransaction() { const { data: program } = useProgramInstance(); return useSendProgramTransaction({ program, serviceName: 'vft', functionName: 'burn', }); } export function useSendTransferTransaction() { const { data: program } = useProgramInstance(); return useSendProgramTransaction({ program, serviceName: 'vft', functionName: 'transfer', }); } ``` Token Queries [#token-queries] ```ts import { useProgramQuery } from '@gear-js/react-hooks'; export function useTokenQueries() { const { data: program } = useProgramInstance(); const { data: name, isPending: isNamePending } = useProgramQuery({ program, serviceName: 'vft', functionName: 'name', args: [], }); // ... (symbol, decimals, totalSupply) return { name, /* ...other fields */ }; } ``` For a full list of query methods and detailed usage, see the [React Hooks API Reference](/docs/developing/sails-js/react-hooks). Query with Parameters [#query-with-parameters] ```ts export function useBalanceOfQuery(address: `0x${string}`) { const { data: program } = useProgramInstance(); return useProgramQuery({ program, serviceName: 'vft', functionName: 'balanceOf', args: [address], watch: false, }); } ``` The `watch` parameter determines how frequently your data updates: * **Use `watch: true`:** For dynamic data like **balances** or values affected by frequent user actions and contract events. * **Skip `watch`:** For static data such as **token names, symbols, or decimals** to improve app performance. Listen to Events [#listen-to-events] ```ts import { useProgramEvent } from '@gear-js/react-hooks'; export function useTokenEvents(callbacks) { const { data: program } = useProgramInstance(); useProgramEvent({ program, serviceName: 'vft', functionName: 'subscribeToMintedEvent', onData: (data) => callbacks.onMinted?.(data), }); // ... subscribe to burned, transferred, approval } ``` For a complete, production-ready example of the hooks setup, refer to the source code: [hooks/api.ts on GitHub](https://github.com) Making Transactions [#making-transactions] ```ts const { sendTransactionAsync: sendMint, isPending: mintPending } = useSendMintTransaction(); const { sendTransactionAsync: sendBurn, isPending: burnPending } = useSendBurnTransaction(); const { sendTransactionAsync: sendTransfer, isPending: transferPending } = useSendTransferTransaction(); await sendMint({ args: [toAddress, amount] }); await sendBurn({ args: [fromAddress, amount] }); await sendTransfer({ args: [toAddress, amount] }); ``` Handle UI loading states and errors: ```ts if (mintPending) { /* show spinner */ } try { await sendMint({ args: [account.decodedAddress, '1000'] }); // show success } catch (e) { // show error } ``` Making Queries [#making-queries] Use `useTokenQueries()` and `useBalanceOfQuery()` to read contract state: ```ts const { name, symbol, decimals, totalSupply, isLoading } = useTokenQueries(); const balanceQuery = useBalanceOfQuery(someAddress); if (balanceQuery.isPending) { /* loading */ } else { /* show balanceQuery.data */ } ``` Listening to Contract Events [#listening-to-contract-events] Another way to keep your frontend application in sync with the blockchain is by **listening to contract events**.\ When the contract emits events (e.g., on mint, burn, transfer), you can subscribe to them in your React code and automatically react to changes — for example, to refresh balances or update state. Unlike the `watch` option in queries (which polls data on every block), **event listeners** let your app respond immediately when something of interest happens in the contract, without unnecessary polling. Subscribe to contract events and trigger UI updates: ```ts useTokenEvents({ onMinted: () => refetchTotalSupply?.(), onBurned: () => refetchTotalSupply?.(), // onTransfer, onApproval... }); ``` This ensures real-time UI updates after mint/burn operations. UI Example: Home Component [#ui-example-home-component] A minimal example of how to use the new transaction hooks and queries in a component: ```tsx import { useAccount, useAlert } from '@gear-js/react-hooks'; import { useState } from 'react'; import { useSendMintTransaction, useSendBurnTransaction, useSendTransferTransaction, useTokenQueries, useTokenEvents, useBalanceOfQuery, } from '../../hooks'; function Home() { const { account } = useAccount(); const { name, symbol, decimals, totalSupply, isLoading, refetchTotalSupply } = useTokenQueries(); const alert = useAlert(); const [transferTo, setTransferTo] = useState(''); const [transferValue, setTransferValue] = useState(''); const [balanceAddr, setBalanceAddr] = useState(''); const { sendTransactionAsync: sendMint, isPending: mintPending } = useSendMintTransaction(); const { sendTransactionAsync: sendBurn, isPending: burnPending } = useSendBurnTransaction(); const { sendTransactionAsync: sendTransfer, isPending: transferPending } = useSendTransferTransaction(); useTokenEvents({ onMinted: (data) => alert.info(`Mint event: ${JSON.stringify(data)}`), onBurned: (data) => alert.info(`Burn event: ${JSON.stringify(data)}`), }); // ...handlers for mint, burn, transfer, balance // ...render UI with all actions, inputs, loading states, errors } export { Home }; ``` For the full and up-to-date example, see [**Home.tsx on GitHub**](https://github.com/gear-foundation/dapps/blob/master/frontend/apps/vft-extended/src/pages/home/Home.tsx) *** For Vara UI Kit component details and usage, see\ [vara-ui on GitHub](https://github.com/gear-tech/gear-js/tree/main/utils/vara-ui). Notes and Best Practices [#notes-and-best-practices] * Handle async errors to improve UX. * Use event subscriptions to keep UI state in sync with blockchain. * For production: review security, error boundaries, and consider adding better wallet management and notifications. **Never hard-code private keys** or sensitive data in the frontend. Always use environment variables (`.env`) for node endpoints and contract addresses to keep your application secure and configurable.