# 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!_ 🚀