Initia Appchain Dev
Deliver practical guidance for full-stack Initia development: contracts, frontend integration, and appchain operations.
Command examples in this file assume the working directory is
; use
paths accordingly.
Intake Questions (Ask First)
Collect missing inputs before implementation:
- Which VM is required (, , )?
- Which network is targeted ( or )?
- Is this a fresh rollup launch or operation/debug on an existing rollup?
- For frontend work, is this an EVM JSON-RPC app or an InterwovenKit wallet/bridge app?
- What chain-specific values are known (, RPC URL, module address, denom)?
If critical values are missing, ask concise follow-up questions before generating final code/config.
If
/endpoints/VM are missing, run the discovery flow in
references/runtime-discovery.md
before assuming defaults.
If
is installed but fails with shell-level errors, continue discovery
with
~/.minitia/artifacts/config.json
and direct
commands instead
of blocking on
.
Then ask a context-specific confirmation:
- Frontend task: "I found a local rollup config/runtime. Should I use this rollup for frontend integration?"
- Non-frontend task: "I found local runtime values (VM, chain ID, endpoints). Should I use these for this task?"
Environment Setup Workflow (One-Prompt Setup)
When the user asks to "set up my environment for the [Track] track" (Step 5), execute this sequence:
1. Identify Track Requirements & Prerequisites
- [MOVE] Track: repo -> . Requires .
- [EVM] Track: repo -> . Requires , .
- [WASM] Track: repo -> . Requires , .
2. Check System Prerequisites
Check prerequisites by selected track (always check
for tool installer compatibility):
- [MOVE] Track: ,
- [EVM] Track: , ,
- [WASM] Track: , ,
For each required tool in the selected track:
- If missing: Inform the user and propose the installation command (e.g., "I see you're missing Cargo. Would you like me to install it for you using ?").
- If present: Proceed silently.
3. Install Core Initia Tools
Run
to install
,
, and
(L1).
- Security: If the script requires , explain this to the user before running.
- If the required tools are already present, prefer verifying versions over reinstalling. Pinned installer versions may lag behind what is already installed on the machine.
4. Build VM-Specific Binary ()
Clone, build, and clean up the relevant VM from source.
Run the build from the repository directory itself. Do not rely on shell-chained
examples if your execution environment manages working directories separately.
- [MOVE]:
sh
git clone --depth 1 https://github.com/initia-labs/minimove.git /tmp/minimove
cd /tmp/minimove
make install
rm -rf /tmp/minimove
- [EVM]:
sh
git clone --depth 1 https://github.com/initia-labs/minievm.git /tmp/minievm
cd /tmp/minievm
make install
rm -rf /tmp/minievm
- [WASM]:
sh
git clone --depth 1 https://github.com/initia-labs/miniwasm.git /tmp/miniwasm
cd /tmp/miniwasm
make install
rm -rf /tmp/miniwasm
5. Configure PATH
- Ensure is in the user's .
- Check shell config files (, ) and suggest the command if missing.
- After updating shell config, tell the user to run (or open a new terminal) to apply changes in their current shell.
- For verification, you may run
zsh -lc 'source ~/.zshrc && <command>'
in a single command; this does not persist across separate assistant commands.
6. Final Verification
Run:
minitiad version --long | rg '^(name|server_name|version|commit):'
Required VM match:
- [EVM] track:
- [MOVE] track:
- [WASM] track: verify the reported matches the Wasm VM you built
Do not treat a successful
command by itself as sufficient verification. The binary on
may still be from a different VM track.
Opinionated Defaults
| Area | Default | Notes |
|---|
| VM | | Use / only when requested |
| Move Version | | Uses . Prefer omitting from unless a specific compiler version requires it. |
| Network | | Use only when explicitly requested |
| Frontend (EVM VM) | wagmi + viem JSON-RPC | Default for pure EVM apps |
| Frontend (Move/Wasm) | @initia/interwovenkit-react
| Use when InterwovenKit features are required |
| Tx UX | | Prefer confirmation UX; use for local dev robustness. |
| Provider order | Wagmi -> Query -> InterwovenKit | Stable path for Initia SDKs |
| Rollup DA | | Prefer Celestia only when explicitly needed |
| Keys & Keyring | / | Default key and for hackathon tools |
| Denoms | (EVM) / (Move) | Typical defaults for test/internal rollups |
Strict Constraints (NEVER VIOLATE)
Tagging Standard
- Use VM-first tags for VM-specific guidance: , , , .
- Add optional context tags after VM: , , , , , , , .
- Prefer stacked tags (example: ) over combined tags (for example, avoid ).
- Required workflow for any skill markdown edit: run before changes and run it again before handoff.
Initia Usernames (STRICTLY OPT-IN)
- You MUST NOT implement username support in any scaffold, component, or code snippet unless explicitly requested (e.g., "add username support").
- When requested, use
useInterwovenKit().username
only for the connected wallet's own display name.
- Pattern:
{username ? username : shortenAddress(initiaAddress)}
- For resolving usernames of arbitrary wallet addresses (for example, MemoBoard sender rows), use
useUsernameQuery(address?)
with the sender address; this requires @initia/interwovenkit-react
or newer.
- For feeds, boards, or any rendered list of messages/accounts, resolve sender usernames inside a child row component (for example ) and call
useUsernameQuery(address)
there. Do NOT call hooks directly inside a parent component's callback or conditional loop body.
- Do NOT resolve via REST unless hook-based resolution is insufficient.
- behavior:
- No param: resolves connected wallet address ( fallback).
- With param: resolves the provided address.
Workspace Hygiene (CRITICAL)
- You MUST NOT leave temporary files or metadata JSON files (e.g., , , ) in the project directory after a task.
- Delete binary files used for deployment before finishing.
InterwovenKit Local Appchains (CRITICAL)
- When configuring a frontend for a local appchain, you MUST use BOTH the AND
customChains: [customChain]
properties in .
- Example Usage:
jsx
<InterwovenKitProvider
{...TESTNET}
customChain={customChain}
customChains={[customChain]}
>
<App />
</InterwovenKitProvider>
- Bridge Support: To ensure the bridge can resolve public chains (like ), ALWAYS spread the preset (imported from
@initia/interwovenkit-react
) into the : <InterwovenKitProvider {...TESTNET} ... />
.
- Address Prefix: MUST include a top-level string (e.g., ). This is mandatory for all appchain types.
- Metadata Completeness: To avoid "Chain not found" errors, the object MUST include , , (with
low/average/high_gas_price: 0
), and arrays.
- API Requirements: The object MUST include , , AND (use a placeholder if needed) to satisfy the kit's discovery logic.
- Bridge Support (openBridge): When using , ONLY specify and (e.g., and ). Avoid specifying a local as it may cause resolution errors if the local chain is not yet indexed.
- Example Structure:
javascript
const customChain = {
chain_id: '<INSERT_APPCHAIN_ID_HERE>',
chain_name: '<INSERT_APP_NAME_HERE>',
network_type: 'testnet', // MANDATORY
bech32_prefix: 'init',
apis: {
rpc: [{ address: 'http://localhost:26657' }],
rest: [{ address: 'http://localhost:1317' }],
indexer: [{ address: 'http://localhost:8080' }], // MANDATORY
'json-rpc': [{ address: 'http://localhost:8545' }],
},
fees: { fee_tokens: [{ denom: 'umin', fixed_min_gas_price: 0, low_gas_price: 0, average_gas_price: 0, high_gas_price: 0 }] },
staking: { staking_tokens: [{ denom: 'umin' }] },
native_assets: [{ denom: 'umin', name: 'Token', symbol: 'TKN', decimals: 6 }],
metadata: { is_l1: false, minitia: { type: 'minimove' } }
}
Frontend Requirements (CRITICAL)
- Placeholder Sync: Immediately after scaffolding a frontend, you MUST update all placeholders in (like
<INSERT_APPCHAIN_ID_HERE>
, <INSERT_NATIVE_DENOM_HERE>
, etc.) with the actual values discovered during the Research phase (e.g., , , ).
- Runtime Config Sync: If the frontend depends on a deployed contract address, you MUST wire the resolved live address into runtime config (for example, / ) instead of leaving only placeholders or examples. If values are added or changed for a running Vite app, tell the user to restart the dev server.
- Hook Exports: exports , , , , , , , , and .
- Transaction Guards: Before calling or , you MUST verify that is defined.
- [ALL-VM] Sender Address: In Initia, the field for all message types (, , ) MUST be the Bech32 address. Use for this field to ensure compatibility across EVM, Move, and Wasm appchains. Using the hex on an EVM chain for the field in a Cosmos-style message will cause an "empty address string" or "decoding bech32 failed" error.
Security & Key Protection (STRICTLY ENFORCED)
- You MUST NOT export raw private keys from the keyring.
- [MOVE][DEV] Development (Building for Publish): Before publishing, you MUST rebuild the module using the intended deployer's Hex address:
minitiad move build --named-addresses <name>=0x<hex_addr>
.
- [MOVE][DEV][BUILD] Named Address Reassignment: If in is hardcoded (for example ) and you pass a different
--named-addresses <name>=0x...
, compilation fails with a named-address reassignment error. Prefer in and keep local test defaults in .
- [MOVE][DEV] Dependency Resolution: If fails to resolve, use:
{ git = "https://github.com/initia-labs/movevm.git", subdir = "precompile/modules/initia_stdlib", rev = "main" }
.
- [MOVE][DEV] Package Scaffolding: can write the package into the current working directory instead of creating a sibling directory. If the user wants a specific folder such as , create and enter that folder first before running .
- [MOVE][DEV] Clean (Non-Interactive Shells): may prompt for confirmation and panic without a TTY. In automated workflows, remove the package directory directly if a clean rebuild is required.
- [MOVE][CLI] Deploy Semantics: Move modules do not have a separate instantiate transaction. "Instantiation" is typically the first state-changing entry call (for example, creating per-player resources on first ).
- [EVM][CLI] Deployment: For EVM deployment, use with .
- [EVM][CLI] Bytecode Extraction: Extract bytecode from Foundry artifacts using ; ensure NO prefix and NO trailing newlines in files.
- [EVM][DEV][CLI] Input Shape: The positional argument to is a bytecode file path. If you want to pass raw bytecode directly, use . Passing raw hex as the positional argument can fail with .
- [ALL-VM][SECURITY] Private Key Handling: If a tool requires a private key, find an alternative workflow using Initia CLI or .
Frontend Requirements (ADDITIONAL)
- Polyfills: Use
vite-plugin-node-polyfills
in with globals: { Buffer: true, process: true }
. Also set to ['react', 'react-dom', 'wagmi', '@tanstack/react-query', 'viem']
to avoid provider context splits in Vite. If using manual polyfills, define and global polyfills at the TOP of .
javascript
import { Buffer } from "buffer";
window.Buffer = Buffer;
window.process = { env: {} };
- Styles:
- Import the CSS:
import "@initia/interwovenkit-react/styles.css"
.
- Import the style data:
import InterwovenKitStyles from "@initia/interwovenkit-react/styles.js"
.
- Import the injector function:
import { injectStyles } from "@initia/interwovenkit-react"
.
- Inject them:
injectStyles(InterwovenKitStyles)
.
- Note: is a DEFAULT export from the styles subpath, while is a NAMED export from the main package.
- Provider Order: -> -> .
- Wallet Modal:
- Use (not ) to open the connection modal (v2.4.0+).
- Connected State (CRITICAL): When is present, ALWAYS provide a clickable UI element (e.g., a button with a shortened address) that calls to allow the user to manage their connection or disconnect.
- Auto-Sign Implementation (STRICTLY OPT-IN):
- You MUST NOT implement auto-sign support in any scaffold, provider, or component unless explicitly requested (e.g., "add auto-sign support").
- When requested:
- Provider: Pass to .
- Hook: Destructure the object (not functions) from .
- Safety: Use optional chaining () and check status via
autoSign?.isEnabledByChain[chainId]
.
- Actions:
await autoSign?.enable(chainId)
and await autoSign?.disable(chainId)
are asynchronous.
- Permissions (CRITICAL): To ensure the session key can sign specific message types, ALWAYS include explicit permissions in :
javascript
await autoSign.enable(chainId, { permissions: ["/initia.move.v1.MsgExecute"] })
- Error Handling: If fails with "authorization not found", handle it by calling with the required permissions to reset the session.
- REST Client: Instantiate from manually; it is NOT exported from the hook.
- CRITICAL: Do NOT attempt to destructure or from . These objects are NOT available in the hook. Always define your REST/RPC endpoints manually or via your own configuration.
Transaction Message Flow (CRITICAL)
- [WASM][INTERWOVENKIT] Transaction Envelope: ALWAYS include . Prefer .
- [WASM][INTERWOVENKIT] Payload Encoding: expects the field as bytes (). Use
new TextEncoder().encode(JSON.stringify(msg))
.
- [ALL-VM][INTERWOVENKIT] Auto-Sign (Headless): To ensure auto-signed transactions are "headless" (no fee selection prompt), ALWAYS include an explicit (e.g., ) AND the flag in the request:
javascript
await requestTxSync({
chainId,
autoSign: true, // CRITICAL: Required for silent signing flow
messages: [...]
})
- [EVM] Sender (MsgCall): Use bech32 address for in , but hex for .
- Normalization: ALWAYS lowercase the bech32 sender address to avoid "decoding bech32 failed" errors.
- [EVM][INTERWOVENKIT] Payload: Use plain objects with
typeUrl: "/minievm.evm.v1.MsgCall"
. The actual message fields MUST be wrapped inside a key.
- Incorrect:
{ typeUrl: "...", sender: "...", contractAddr: "..." }
- Correct:
{ typeUrl: "...", value: { sender: "...", contractAddr: "...", ... } }
- [EVM][INTERWOVENKIT] Key: ALWAYS use (plural), not . Passing can fail with
Cannot read properties of undefined (reading 'map')
.
- [EVM] Field Naming: Use camelCase for fields (, , ) and include empty arrays for lists.
- [MOVE][INTERWOVENKIT] MsgExecute Field Naming: Use camelCase for fields; MUST be bech32.
- [MOVE][INTERWOVENKIT] Mandatory Arrays: ALWAYS include and even if they are empty. Omitting these fields in a Move execution message will cause a (Cannot read properties of undefined reading 'length') during the Amino conversion process in the frontend.
Wasm REST Queries (CRITICAL)
- [WASM][REST] Query Encoding: When querying Wasm contract state using the (e.g.,
rest.wasm.smartContractState
), the query message MUST be a Base64-encoded string.
- The query JSON and execute JSON MUST match the contract's Rust message schema exactly. Do NOT assume names like or specific field names such as or ; for example, MemoBoard variants commonly use with
post_message: { message: ... }
, while other contracts may use different field names.
- Example Implementation:
javascript
const queryData = Buffer.from(JSON.stringify({ get_messages: {} })).toString("base64");
const res = await rest.wasm.smartContractState(CONTRACT_ADDR, queryData);
- [WASM][REST] Response Shape: commonly returns the decoded payload directly (for example ) rather than nesting it under . Do not assume a wrapper unless you have verified the concrete response shape.
- [WASM][REST] Method Name: ALWAYS use . Methods like are NOT available in the Initia .
- [WASM][CLI] Fallback: If
minitiad query wasm contract-state smart <CONTRACT_ADDRESS> ...
fails with a Bech32 checksum error even though the address came from the instantiate event or , treat the chain-emitted address as the source of truth. Verify it with minitiad query wasm list-contract-by-code <CODE_ID>
and continue with the REST endpoint path or instead of blocking on the CLI parser.
Wasm Rust Testing (CRITICAL)
- [WASM][TEST] Address Comparison: When writing unit tests for Wasm contracts, ALWAYS use when comparing with a string literal or . does NOT implement or directly.
- Incorrect:
assert_eq!("user1", value.sender);
- Correct:
assert_eq!("user1", value.sender.as_str());
Token Precision & Funding (EVM SPECIFIC)
- [EVM] Precision: Assume standard EVM precision ($10^{18}$ base units) for all native tokens on EVM appchains (e.g., ).
- [EVM] Funding Requests: When a user asks for "N tokens" on an EVM chain:
- Frontend: Multiply by $10^{18}$ (e.g., ).
- CLI: You MUST manually scale the value. For "100 tokens", use (100 + 18 zeros) in the command.
- [EVM][FRONTEND] Human-Readable UI: ALWAYS use from to display EVM balances. NEVER display raw base units in the UI.
- [EVM][FRONTEND][DEV] / Usage: and are valid shorthand ONLY when the chain token uses exactly 18 decimals. If decimals might vary, use
parseUnits(amount, decimals)
and formatUnits(balance, decimals)
from runtime config.
EVM Queries & Transactions (CRITICAL)
- [EVM][FRONTEND] ABI Sync: Treat compiled ABI/function names as the source of truth. Do NOT assume names like ; confirm names/signatures from the generated artifact (for example
out/<Contract>.sol/<Contract>.json
) before wiring frontend calls.
- [EVM][RPC] State Queries: Prefer standard JSON-RPC over for EVM state queries to avoid property-undefined errors.
- [EVM][FRONTEND][RPC] Read Path Provider: For read-only EVM queries (), use a configured JSON-RPC endpoint (for example ) instead of relying on injected wallet providers (). This avoids "EVM wallet provider not found" failures when no EVM extension is injected.
- [EVM][RPC] Address Conversion: When querying EVM state (e.g., ), ALWAYS convert the bech32 address to hex using and ensure the hex address is lowercased.
- [EVM][CLI] Sender Format: expects a bech32 sender () as the first argument.
- [EVM][CLI] Query Output Field:
minitiad query evm call -o json
returns the call result under (hex string).
- [EVM][CLI] Tx Lookup Timing: Immediately after broadcast, may briefly return ; retry briefly before failing.
- [EVM][CLI] Calldata Encoding:
- [EVM][FRONTEND] Preferred: Prefer (e.g., ) for generating contract hex.
- [EVM][CLI] Preferred: ALWAYS use (e.g.,
$(cast calldata "func(type)" arg)
) for generating contract hex. Manual encoding (e.g., ) is brittle and MUST be avoided.
- [EVM][CLI] Manual Padding: If manual encoding is unavoidable, ensure values are converted to hex and padded to exactly 64 characters for arguments.
Move REST Queries (CRITICAL)
- [MOVE][REST] Module Address Format: When querying Move contract state using the (e.g., ), the module address MUST be in bech32 format.
- [MOVE][REST] Struct Tag Address Format: When querying Move resources using , the resource owner remains bech32, but the struct tag module address MUST be hex (). Do NOT build a struct tag with a bech32 module address.
- [MOVE][REST] Address Arguments: Address arguments in MUST be converted to hex, stripped of , padded to 64 chars (32 bytes), and then Base64-encoded.
- Example Implementation:
javascript
const b64Addr = Buffer.from(
AccAddress.toHex(addr).replace('0x', '').padStart(64, '0'),
'hex'
).toString('base64');
const res = await rest.move.view(mod_bech32, mod_name, func_name, [], [b64Addr]);
- Resource Query Example:
javascript
const structTag = `${AccAddress.toHex(moduleBech32)}::items::Inventory`;
const res = await rest.move.resource(walletBech32, structTag);
- [MOVE][REST] Response Parsing: The response from is a object; you MUST parse to access the actual values.
- [MOVE][REST] Missing Resource Handling: For first-use state such as inventories, a resource may not exist yet. Treat a "not found" response from as a valid zero/default state instead of surfacing it as a hard failure.
- [MOVE][REST] Troubleshooting (400 Bad Request): If returns a 400 error, it is almost ALWAYS because:
- The module address is not bech32.
- The address arguments in are not correctly hex-padded-base64 encoded.
- [MOVE][INTERWOVENKIT] Auto-Sign (No message types configured): If fails with "No message types configured", ensure:
- is set correctly (e.g., , ).
- in matches your .
- is present at the top level of .
Operating Procedure (How To Execute Tasks)
- Classify Layer: Contract, Frontend, Appchain Ops, or Integration.
- Environment Check: Verify tools (, , ) are in PATH. Use absolute paths if needed.
- Workspace Awareness: Check for existing or before scaffolding. Use provided scripts for non-interactive scaffolding.
- Pre-Deployment Checklist (CLI): Before deploying contracts or sending tokens via CLI, verify the actual environment (set from runtime discovery):
- Chain ID:
curl -s "${RPC_ENDPOINT}/status" | jq -r '.result.node_info.network'
- Native Denom:
minitiad q bank total --node "${RPC_ENDPOINT}"
- Balance: Ensure the account has enough of the actual native denom.
- Scaffolding Cleanup: Delete placeholder modules/contracts after scaffolding.
- Appchain Health: If RPC is down, attempt and verify with
scripts/verify-appchain.sh
.
- [MOVE][DEV] Acquires Annotation: Every function that accesses global storage (using , , , or calling a function that does) MUST include the annotation for that resource type.
- [MOVE][DEV] Reference Rules: You MUST NOT return a reference (mutable or immutable) derived from a global resource (e.g., via ) from a function unless it is passed as a parameter. Inline the logic or pass the resource as a parameter if needed.
- [MOVE][DEV] Syntax: Place doc comments () AFTER attributes like or .
- [MOVE][CLI] Publish: The command does NOT use a flag. Pass the path to the compiled file as a positional argument:
minitiad tx move publish <path_to_file>.mv ...
.
- [MOVE][DEV][CLI] Republish Compatibility: If republishing from the same account fails with
BACKWARD_INCOMPATIBLE_MODULE_UPDATE
, preserve existing public APIs (for example, keep prior public entry/view functions as compatibility wrappers) or rename the module before publishing.
- [ALL-VM][CLI] Broadcast Mode Compatibility: In current Initia CLIs, broadcast mode supports ; do not assume is available.
- [MOVE][CLI] Tx Lookup Timing: Immediately after broadcast, may briefly return ; poll/retry before treating it as failed.
- [WASM][BUILD] Optimization: ALWAYS use the CosmWasm optimizer Docker image for production-ready binaries.
- Visual Polish: Prioritize sticky glassmorphism headers, centered app-card layouts, and clear visual hierarchy.
- UX Excellence: Feed ordering (newest first), input accessibility (above feed), and interactive feedback (hover/focus).
- [ALL-VM][INTERWOVENKIT] Bridge Support: Use from . Default to a public testnet (e.g., ) for local demos.
- Validation: Run
scripts/verify-appchain.sh --gas-station --bots
and confirm transaction success before handoff.
Progressive Disclosure (Read When Needed)
- Common Tasks (Funding, Addresses, Precision):
references/common-tasks.md
- Contracts (Move/Wasm/EVM):
- Frontend (InterwovenKit):
references/frontend-interwovenkit.md
- Frontend (EVM JSON-RPC):
references/frontend-evm-rpc.md
- End-to-End Recipes:
references/e2e-recipes.md
- Runtime Discovery:
references/runtime-discovery.md
- Weave CLI Reference:
references/weave-commands.md
- Rollup Config Schema:
references/weave-config-schema.md
- Troubleshooting & Recovery:
references/troubleshooting.md
Documentation Fallback
- Core docs:
- InterwovenKit docs:
https://docs.initia.xyz/interwovenkit
Script Usage
- Scaffolding:
scripts/scaffold-contract.sh
, scripts/scaffold-frontend.sh
- Health:
scripts/verify-appchain.sh
- Utils:
scripts/convert-address.py
, , scripts/generate-system-keys.py
- Setup: ,
Expected Deliverables
- Exact files changed.
- Commands for setup/build/test.
- Verification steps and outputs.
- Risk notes (security, keys, fees).