# VIBECODING A LINERA CONTRACT 🚀 A complete guide to building, deploying, and interacting with a simple message storage application on Linera. ## Table of Contents - [What We Built](#what-we-built) - [Project Structure](#project-structure) - [Step 1: Understanding the Linera Application Architecture](#step-1-understanding-the-linera-application-architecture) - [Step 2: Defining the State](#step-2-defining-the-state) - [Step 3: Creating Operations](#step-3-creating-operations) - [Step 4: Implementing the Contract Logic](#step-4-implementing-the-contract-logic) - [Step 5: Building the GraphQL Service](#step-5-building-the-graphql-service) - [Step 6: Writing Tests](#step-6-writing-tests) - [Step 7: Building for WebAssembly](#step-7-building-for-webassembly) - [Step 8: Deploying to Testnet](#step-8-deploying-to-testnet) - [Step 9: Interacting with Your Application](#step-9-interacting-with-your-application) - [Key Takeaways](#key-takeaways) --- ## What We Built A simple message storage contract that allows users to: - ✅ Store a message on-chain during deployment - ✅ Update the message using GraphQL mutations - ✅ Retrieve the current message using GraphQL queries - ✅ Persist message state across sessions **Deployed Application:** - Chain ID: `d79aa094242881a3727d36ac12d026caf886438041784b70448fe13adeb3afd4` - Application ID: `eab9b22ac9c1e177ffa821937bb6cfdcdd7107860ca40bd9476e461d5804177e` --- ## Project Structure ``` backend/ ├── Cargo.toml # Dependencies and build configuration ├── src/ │ ├── lib.rs # ABI definition and operations │ ├── state.rs # Application state │ ├── contract.rs # Contract logic (business layer) │ └── service.rs # GraphQL service (query layer) └── tests/ └── single_chain.rs # Integration tests ``` --- ## Step 1: Understanding the Linera Application Architecture Linera applications consist of two main components: ### Contract (contract.rs) - Executes operations and modifies state - Runs in a secure, sandboxed environment - Creates blocks on the blockchain ### Service (service.rs) - Provides read-only access to state - Exposes GraphQL API for queries - Runs on the node service Both are compiled to WebAssembly and deployed together. --- ## Step 2: Defining the State **File:** `src/state.rs` We defined a simple state that stores a message using `RegisterView<String>`: ```rust use linera_sdk::views::{linera_views, RegisterView, RootView, ViewStorageContext}; #[derive(RootView, async_graphql::SimpleObject)] #[view(context = ViewStorageContext)] pub struct BackendState { pub message: RegisterView<String>, } ``` ### Key Points: - `RootView` - Marks this as the root of the application's state - `RegisterView<String>` - Provides persistent storage for a single String value - `async_graphql::SimpleObject` - Makes the state queryable via GraphQL --- ## Step 3: Creating Operations **File:** `src/lib.rs` We defined two operations users can perform: ```rust use async_graphql::{Request, Response}; use linera_sdk::{ graphql::GraphQLMutationRoot, linera_base_types::{ContractAbi, ServiceAbi}, }; use serde::{Deserialize, Serialize}; pub struct BackendAbi; impl ContractAbi for BackendAbi { type Operation = Operation; type Response = (); } impl ServiceAbi for BackendAbi { type Query = Request; type QueryResponse = Response; } #[derive(Debug, Deserialize, Serialize, GraphQLMutationRoot)] pub enum Operation { SetMessage { message: String }, UpdateMessage { message: String }, } ``` ### Key Points: - `Operation` enum defines all possible mutations - `GraphQLMutationRoot` automatically generates GraphQL mutations - Both operations do the same thing - we included both for demonstration --- ## Step 4: Implementing the Contract Logic **File:** `src/contract.rs` The contract handles operation execution and state management: ```rust #![cfg_attr(target_arch = "wasm32", no_main)] mod state; use linera_sdk::{ linera_base_types::WithContractAbi, views::{RootView, View}, Contract, ContractRuntime, }; use backend::Operation; use self::state::BackendState; pub struct BackendContract { state: BackendState, runtime: ContractRuntime<Self>, } linera_sdk::contract!(BackendContract); impl WithContractAbi for BackendContract { type Abi = backend::BackendAbi; } impl Contract for BackendContract { type Message = (); type Parameters = (); type InstantiationArgument = String; type EventValue = (); async fn load(runtime: ContractRuntime<Self>) -> Self { let state = BackendState::load(runtime.root_view_storage_context()) .await .expect("Failed to load state"); BackendContract { state, runtime } } async fn instantiate(&mut self, argument: Self::InstantiationArgument) { self.runtime.application_parameters(); self.state.message.set(argument); } async fn execute_operation(&mut self, operation: Self::Operation) -> Self::Response { match operation { Operation::SetMessage { message } => { self.state.message.set(message); } Operation::UpdateMessage { message } => { self.state.message.set(message); } } } async fn execute_message(&mut self, _message: Self::Message) {} async fn store(mut self) { self.state.save().await.expect("Failed to save state"); } } ``` ### Key Points: - `InstantiationArgument = String` - Requires an initial message when deploying - `execute_operation` - Handles both SetMessage and UpdateMessage - `store` - Persists state changes to storage --- ## Step 5: Building the GraphQL Service **File:** `src/service.rs` The service exposes queries and mutations via GraphQL: ```rust #![cfg_attr(target_arch = "wasm32", no_main)] mod state; use std::sync::Arc; use async_graphql::{EmptySubscription, Object, Schema}; use linera_sdk::{ graphql::GraphQLMutationRoot, linera_base_types::WithServiceAbi, views::View, Service, ServiceRuntime, }; use backend::Operation; use self::state::BackendState; pub struct BackendService { state: BackendState, runtime: Arc<ServiceRuntime<Self>>, } linera_sdk::service!(BackendService); impl WithServiceAbi for BackendService { type Abi = backend::BackendAbi; } impl Service for BackendService { type Parameters = (); async fn new(runtime: ServiceRuntime<Self>) -> Self { let state = BackendState::load(runtime.root_view_storage_context()) .await .expect("Failed to load state"); BackendService { state, runtime: Arc::new(runtime), } } async fn handle_query(&self, query: Self::Query) -> Self::QueryResponse { Schema::build( QueryRoot { message: self.state.message.get().clone(), }, Operation::mutation_root(self.runtime.clone()), EmptySubscription, ) .finish() .execute(query) .await } } struct QueryRoot { message: String, } #[Object] impl QueryRoot { async fn message(&self) -> &String { &self.message } } ``` ### Key Points: - `QueryRoot` - Defines what queries are available - `handle_query` - Builds the GraphQL schema dynamically - `Operation::mutation_root()` - Automatically generates mutations from operations --- ## Step 6: Writing Tests **File:** `src/contract.rs` (test module) We wrote comprehensive tests for our contract: ```rust #[cfg(test)] mod tests { use futures::FutureExt as _; use linera_sdk::{util::BlockingWait, views::View, Contract, ContractRuntime}; use backend::Operation; use super::{BackendContract, BackendState}; #[test] fn set_message_operation() { let initial_message = "Hello, Linera!".to_string(); let mut app = create_and_instantiate_app(initial_message.clone()); let new_message = "Updated message".to_string(); let _response = app .execute_operation(Operation::SetMessage { message: new_message.clone() }) .now_or_never() .expect("Execution of application operation should not await anything"); assert_eq!(app.state.message.get(), &new_message); } #[test] fn update_message_operation() { let initial_message = "Hello, Linera!".to_string(); let mut app = create_and_instantiate_app(initial_message.clone()); let updated_message = "Message updated successfully".to_string(); let _response = app .execute_operation(Operation::UpdateMessage { message: updated_message.clone() }) .now_or_never() .expect("Execution of application operation should not await anything"); assert_eq!(app.state.message.get(), &updated_message); } fn create_and_instantiate_app(initial_message: String) -> BackendContract { let runtime = ContractRuntime::new().with_application_parameters(()); let mut contract = BackendContract { state: BackendState::load(runtime.root_view_storage_context()) .blocking_wait() .expect("Failed to read from mock key value store"), runtime, }; contract .instantiate(initial_message.clone()) .now_or_never() .expect("Initialization of application state should not await anything"); assert_eq!(contract.state.message.get(), &initial_message); contract } } ``` --- ## Step 7: Building for WebAssembly ### Build Command ```bash cd backend cargo build --release --target wasm32-unknown-unknown ``` This compiles your Rust code to WebAssembly bytecode that can run on Linera validators. ### Output Files The build creates two WASM files: - `target/wasm32-unknown-unknown/release/backend_contract.wasm` - Contract bytecode - `target/wasm32-unknown-unknown/release/backend_service.wasm` - Service bytecode --- ## Step 8: Deploying to Testnet ### Setup Wallet and Chain ```bash # Initialize wallet linera wallet init --faucet https://faucet.testnet-conway.linera.net # Request a new chain with tokens linera wallet request-chain --faucet https://faucet.testnet-conway.linera.net ``` ### Deploy the Application The critical part is the `--json-argument` flag: ```bash linera publish-and-create \ target/wasm32-unknown-unknown/release/backend_{contract,service}.wasm \ --json-argument '"Hello, Linera!"' ``` ### Common Deployment Issues We Solved #### Issue 1: Missing JSON Argument ❌ **Error:** `Failed to deserialize instantiation argument [110, 117, 108, 108]` **Solution:** Always provide the `--json-argument` flag with a properly formatted JSON string. #### Issue 2: Malformed JSON String ❌ **Error:** `EOF while parsing a string at line 1 column 14` **Cause:** ```bash --json-argument "\"Hello, Linera" # Missing closing quotes! ``` ✅ **Solution:** ```bash --json-argument '"Hello, Linera!"' # Single quotes outside, double inside ``` --- ## Step 9: Interacting with Your Application ### Start the Node Service ```bash linera service --port 8080 ``` **Common Issue:** Port already in use ``` Error is Address already in use (os error 48) ``` **Solution:** Use a different port ```bash linera service --port 8081 ``` ### Access GraphiQL Navigate to: ``` http://localhost:8080/chains/<CHAIN_ID>/applications/<APP_ID> ``` For our deployment: ``` http://localhost:8080/chains/d79aa094242881a3727d36ac12d026caf886438041784b70448fe13adeb3afd4/applications/eab9b22ac9c1e177ffa821937bb6cfdcdd7107860ca40bd9476e461d5804177e ``` ### Query the Message ```graphql query { message } ``` **Response:** ```json { "data": { "message": "Hello, Linera!" } } ``` ### Update the Message ```graphql mutation { setMessage(message: "My updated message!") } ``` ### Verify the Update ```graphql query { message } ``` **Response:** ```json { "data": { "message": "My updated message!" } } ``` --- ## Key Takeaways ### ✅ What We Learned 1. **Linera Architecture** - Contracts handle state mutations (write operations) - Services provide read-only access via GraphQL - Both compile to WebAssembly 2. **State Management** - Use `RegisterView<T>` for single values - State persists across sessions - Each mutation creates a new block 3. **GraphQL Integration** - Operations automatically become mutations - State fields automatically become queries - GraphiQL provides interactive exploration 4. **Deployment Process** - Build for `wasm32-unknown-unknown` target - Initialize wallet and request chain - Deploy with `publish-and-create` - Provide initialization arguments as JSON 5. **Testing** - Write unit tests in contract modules - Test operations and state changes - Use Linera's testing utilities ### 🎯 Best Practices 1. **Always provide initialization arguments** when deploying (unless Optional) 2. **Use single quotes** for JSON arguments to avoid escaping issues 3. **Test locally** before deploying to testnet 4. **Keep operations simple** - complex logic can be expensive 5. **Use descriptive names** for operations and state fields ### 🚀 Next Steps - Add more complex state (multiple fields, collections) - Implement cross-chain messaging - Add event emission for notifications - Build a frontend to interact with the contract - Explore advanced features like cross-application calls --- ## Complete File Reference ### Cargo.toml ```toml [package] name = "backend" version = "0.1.0" edition = "2021" [dependencies] async-graphql = { version = "=7.0.17", default-features = false } linera-sdk = "0.15.3" futures = { version = "0.3 "} serde = { version = "1.0", features = ["derive"] } serde_json = { version = "1.0" } [dev-dependencies] linera-sdk = { version = "0.15.3", features = ["test", "wasmer"] } tokio = { version = "1.40", features = ["rt", "sync"] } [[bin]] name = "backend_contract" path = "src/contract.rs" [[bin]] name = "backend_service" path = "src/service.rs" [profile.release] debug = true lto = true opt-level = 'z' strip = 'debuginfo' ``` --- ## Useful Commands Reference ```bash # Build the application cargo build --release --target wasm32-unknown-unknown # Run tests cargo test # Initialize wallet (first time) linera wallet init --faucet https://faucet.testnet-conway.linera.net # Request a chain linera wallet request-chain --faucet https://faucet.testnet-conway.linera.net # Deploy application linera publish-and-create \ target/wasm32-unknown-unknown/release/backend_{contract,service}.wasm \ --json-argument '"Your initial message"' # Start node service linera service --port 8080 # Check wallet status linera wallet show # Sync with network linera sync # Query chain balance linera query-balance ``` --- ## Resources - [Linera Developer Documentation](https://linera.dev) - [Linera SDK Documentation](https://docs.rs/linera-sdk) - [GraphQL Documentation](https://graphql.org/learn/) - [Linera GitHub Repository](https://github.com/linera-io/linera-protocol) --- **Built with 💙 by vibecoding on Linera** _Remember: Every mutation creates a block, every query reads state, and every deployment is permanent. Code with intention!_ 🚀