End-to-end Initia development and operations guide...
Deliver practical guidance for full-stack Initia development: contracts, frontend integration, and appchain operations.
Command examples in this file assume the working directory is skill/; use scripts/... paths accordingly.
Collect missing inputs before implementation:
evm, move, wasm)?testnet or mainnet)?chain_id, RPC URL, deployed contract address, denom)?If critical values are missing, ask concise follow-up questions before generating final code/config.
If chain_id/endpoints/VM are missing, run the discovery flow in references/runtime-discovery.md before assuming defaults.
If weave is installed but fails with shell-level errors, continue discovery
with ~/.minitia/artifacts/config.json and direct minitiad commands instead
of blocking on weave.
Then ask a context-specific confirmation:
When the user asks to "set up my environment for the [Track] track" (Step 5), execute this sequence:
minimove repo -> minitiad. Requires go.minievm repo -> minitiad. Requires go, foundry.miniwasm repo -> minitiad. Requires go, rust/cargo.Check prerequisites by selected track (always check docker for tool installer compatibility):
go, dockergo, docker, foundrygo, docker, cargoFor each required tool in the selected track:
rustup?").Run scripts/install-tools.sh to install jq, weave, and initiad (L1).
sudo, explain this to the user before running.minitiad)Clone, build, and clean up the relevant VM from source.
Run the build from the repository directory itself. Do not rely on shell-chained cd ... && make install examples if your execution environment manages working directories separately.
git clone --depth 1 https://github.com/initia-labs/minimove.git /tmp/minimove
cd /tmp/minimove
make install
rm -rf /tmp/minimove
git clone --depth 1 https://github.com/initia-labs/minievm.git /tmp/minievm
cd /tmp/minievm
make install
rm -rf /tmp/minievm
git clone --depth 1 https://github.com/initia-labs/miniwasm.git /tmp/miniwasm
cd /tmp/miniwasm
make install
rm -rf /tmp/miniwasm
~/go/bin is in the user's PATH..zshrc, .bashrc) and suggest the export command if missing.source ~/.zshrc (or open a new terminal) to apply changes in their current shell.zsh -lc 'source ~/.zshrc && <command>' in a single command; this does not persist across separate assistant commands.Run:
weave versioninitiad versionminitiad version --long | rg '^(name|server_name|version|commit):'Required VM match:
name: minievmname: minimovename matches the Wasm VM you builtDo not treat a successful minitiad version command by itself as sufficient verification. The binary on PATH may still be from a different VM track.
| Area | Default | Notes |
|---|---|---|
| VM | evm |
Use move/wasm only when requested |
| Move Version | 2.1 |
Uses minitiad move build. Prefer omitting edition from Move.toml unless a specific compiler version requires it. |
| Network | testnet |
Use mainnet 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 | requestTxBlock |
Prefer confirmation UX; use requestTxSync for local dev robustness. |
| Provider order | Wagmi -> Query -> InterwovenKit | Stable path for Initia SDKs |
| Rollup DA | INITIA |
Prefer Celestia only when explicitly needed |
| Keys & Keyring | gas-station / test |
Default key and --keyring-backend test for hackathon tools |
| Denoms | GAS (EVM) / umin (Move) |
Typical defaults for test/internal rollups |
[EVM], [MOVE], [WASM], [ALL-VM].[CLI], [DEV], [REST], [RPC], [FRONTEND], [INTERWOVENKIT], [BUILD], [TEST].[EVM][CLI]) over combined tags (for example, avoid [EVM CLI]).scripts/lint-tags.sh before changes and run it again before handoff.useInterwovenKit().username only for the connected wallet's own display name.{username ? username : shortenAddress(initiaAddress)}useUsernameQuery(address?) with the sender address; this requires @initia/interwovenkit-react 2.4.6 or newer.MessageRow) and call useUsernameQuery(address) there. Do NOT call hooks directly inside a parent component's .map() callback or conditional loop body.useUsernameQuery behavior:useAddress() fallback).store_tx.json, tx.json, .bin) in the project directory after a task.customChain AND customChains: [customChain] properties in InterwovenKitProvider.<InterwovenKitProvider
{...TESTNET}
customChain={customChain}
customChains={[customChain]}
>
<App />
</InterwovenKitProvider>
initiation-2), ALWAYS spread the {...TESTNET} preset (imported from @initia/interwovenkit-react) into the InterwovenKitProvider: <InterwovenKitProvider {...TESTNET} ... />.customChain MUST include a top-level bech32_prefix string (e.g., bech32_prefix: "init"). This is mandatory for all appchain types.customChain object MUST include network_type: 'testnet', staking, fees (with low/average/high_gas_price: 0), and native_assets arrays.apis object MUST include rpc, rest, AND indexer (use a placeholder if needed) to satisfy the kit's discovery logic.openBridge, ONLY specify srcChainId and srcDenom (e.g., initiation-2 and uinit). Avoid specifying a local dstChainId as it may cause resolution errors if the local chain is not yet indexed.customChain Structure: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' } }
}
main.jsx (like <INSERT_APPCHAIN_ID_HERE>, <INSERT_NATIVE_DENOM_HERE>, etc.) with the actual values discovered during the Research phase (e.g., bank-1, GAS, minievm)..env / VITE_*) instead of leaving only placeholders or examples. For Move-specific APIs and code, keep the term module address where that distinction matters. If .env values are added or changed for a running Vite app, tell the user to restart the dev server.useInterwovenKit exports initiaAddress, address, username, openConnect, openWallet, openBridge, requestTxBlock, requestTxSync, and autoSign.requestTxBlock or requestTxSync, you MUST verify that initiaAddress is defined.sender field for all message types (MsgCall, MsgExecute, MsgExecuteContract) MUST be the Bech32 address. Use initiaAddress for this field to ensure compatibility across EVM, Move, and Wasm appchains. Using the hex address on an EVM chain for the sender field in a Cosmos-style message will cause an "empty address string" or "decoding bech32 failed" error.minitiad move build --named-addresses <name>=0x<hex_addr>.[addresses].<name> in Move.toml is hardcoded (for example 0x42) and you pass a different --named-addresses <name>=0x..., compilation fails with a named-address reassignment error. Prefer "<name>" = "_" in [addresses] and keep local test defaults in [dev-addresses].InitiaStdlib fails to resolve, use: { git = "https://github.com/initia-labs/movevm.git", subdir = "precompile/modules/initia_stdlib", rev = "main" }.minitiad move new <NAME> can write the package into the current working directory instead of creating a sibling directory. If the user wants a specific folder such as blockforge/, create and enter that folder first before running minitiad move new.minitiad move clean may prompt for confirmation and panic without a TTY. In automated workflows, remove the package build/ directory directly if a clean rebuild is required.mint_shard).minitiad tx evm create with --from.jq; ensure NO 0x prefix and NO trailing newlines in .bin files.tx evm create Input Shape: The positional argument to minitiad tx evm create is a bytecode file path. If you want to pass raw bytecode directly, use --input 0x.... Passing raw hex as the positional argument can fail with file name too long.InterwovenKit.vite-plugin-node-polyfills in vite.config.js with globals: { Buffer: true, process: true }. Also set resolve.dedupe to ['react', 'react-dom', 'wagmi', '@tanstack/react-query', 'viem'] to avoid provider context splits in Vite. If using manual polyfills, define Buffer and process global polyfills at the TOP of main.jsx.import { Buffer } from "buffer";
window.Buffer = Buffer;
window.process = { env: {} };
import "@initia/interwovenkit-react/styles.css".import InterwovenKitStyles from "@initia/interwovenkit-react/styles.js".import { injectStyles } from "@initia/interwovenkit-react".injectStyles(InterwovenKitStyles).InterwovenKitStyles is a DEFAULT export from the styles subpath, while injectStyles is a NAMED export from the main package.WagmiProvider -> QueryClientProvider -> InterwovenKitProvider.openConnect (not openModal) to open the connection modal (v2.4.0+).initiaAddress is present, ALWAYS provide a clickable UI element (e.g., a button with a shortened address) that calls openWallet to allow the user to manage their connection or disconnect.enableAutoSign={true} to InterwovenKitProvider.autoSign object (not functions) from useInterwovenKit.autoSign?.) and check status via autoSign?.isEnabledByChain[chainId].await autoSign?.enable(chainId) and await autoSign?.disable(chainId) are asynchronous.autoSign.enable:await autoSign.enable(chainId, { permissions: ["/initia.move.v1.MsgExecute"] })
autoSign.disable fails with "authorization not found", handle it by calling autoSign.enable with the required permissions to reset the session.RESTClient from @initia/initia.js manually; it is NOT exported from the hook.networks or rest from useInterwovenKit. These objects are NOT available in the hook. Always define your REST/RPC endpoints manually or via your own configuration.chainId. Prefer requestTxSync.MsgExecuteContract expects the msg field as bytes (Uint8Array). Use new TextEncoder().encode(JSON.stringify(msg)).feeDenom (e.g., feeDenom: "umin") AND the autoSign: true flag in the request:await requestTxSync({
chainId,
autoSign: true, // CRITICAL: Required for silent signing flow
messages: [...]
})
sender in MsgCall, but hex for contractAddr. typeUrl: "/minievm.evm.v1.MsgCall". The actual message fields MUST be wrapped inside a value key.{ typeUrl: "...", sender: "...", contractAddr: "..." }{ typeUrl: "...", value: { sender: "...", contractAddr: "...", ... } }requestTxBlock Key: ALWAYS use messages (plural), not msgs. Passing msgs can fail with Cannot read properties of undefined (reading 'map').contractAddr, accessList, authList) and include empty arrays for lists.moduleAddress MUST be bech32.typeArgs: [] and args: [] even if they are empty. Omitting these fields in a Move execution message will cause a TypeError (Cannot read properties of undefined reading 'length') during the Amino conversion process in the frontend.RESTClient (e.g., rest.wasm.smartContractState), the query message MUST be a Base64-encoded string.all_messages or specific field names such as text or message; for example, MemoBoard variants commonly use get_messages with post_message: { message: ... }, while other contracts may use different field names.const queryData = Buffer.from(JSON.stringify({ get_messages: {} })).toString("base64");
const res = await rest.wasm.smartContractState(CONTRACT_ADDR, queryData);
smartContractState commonly returns the decoded payload directly (for example res.messages) rather than nesting it under res.data. Do not assume a .data wrapper unless you have verified the concrete response shape.smartContractState. Methods like queryContractSmart are NOT available in the Initia RESTClient.minitiad query wasm contract-state smart <CONTRACT_ADDRESS> ... fails with a Bech32 checksum error even though the address came from the instantiate event or list-contract-by-code, 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 RESTClient instead of blocking on the CLI parser..as_str() when comparing cosmwasm_std::Addr with a string literal or String. Addr does NOT implement PartialEq<&str> or PartialEq<String> directly.assert_eq!("user1", value.sender);assert_eq!("user1", value.sender.as_str());GAS).parseUnits(amount, 18)).100000000000000000000GAS (100 + 18 zeros) in the bank send command.formatUnits(balance, 18) from viem to display EVM balances. NEVER display raw base units in the UI.parseEther / formatEther Usage: parseEther and formatEther 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.getMyBalance; confirm names/signatures from the generated artifact (for example out/<Contract>.sol/<Contract>.json) before wiring frontend encodeFunctionData calls.eth_call over RESTClient for EVM state queries to avoid property-undefined errors.eth_call), use a configured JSON-RPC endpoint (for example VITE_JSON_RPC_URL) instead of relying on injected wallet providers (window.ethereum). This avoids "EVM wallet provider not found" failures when no EVM extension is injected.eth_call), ALWAYS convert the bech32 address to hex using AccAddress.toHex(addr) and ensure the hex address is lowercased.minitiad query evm call expects a bech32 sender (init1...) as the first argument.minitiad query evm call -o json returns the call result under .response (hex string).minitiad query tx <hash> may briefly return tx not found; retry briefly before failing.viem (e.g., encodeFunctionData) for generating contract input hex. cast calldata (e.g., $(cast calldata "func(type)" arg)) for generating contract input hex. Manual encoding (e.g., printf) is brittle and MUST be avoided.BigInt values are converted to hex and padded to exactly 64 characters for uint256 arguments.RESTClient (e.g., rest.move.view), the module address MUST be in bech32 format.rest.move.resource, the resource owner remains bech32, but the struct tag module address MUST be hex (0x...::module::Struct). Do NOT build a struct tag with a bech32 module address.args MUST be converted to hex, stripped of 0x, padded to 64 chars (32 bytes), and then Base64-encoded.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]);
const structTag = `${AccAddress.toHex(moduleBech32)}::items::Inventory`;
const res = await rest.move.resource(walletBech32, structTag);
rest.move.view is a ViewResponse object; you MUST parse JSON.parse(res.data) to access the actual values.rest.move.resource as a valid zero/default state instead of surfacing it as a hard failure.rest.move.view returns a 400 error, it is almost ALWAYS because:args are not correctly hex-padded-base64 encoded.autoSign.enable fails with "No message types configured", ensure:metadata.minitia.type is set correctly (e.g., minimove, minievm).defaultChainId in InterwovenKitProvider matches your customChain.chain_id.bech32_prefix is present at the top level of customChain.cargo, forge, minitiad) are in PATH. Use absolute paths if needed.Move.toml or package.json before scaffolding. Use provided scripts for non-interactive scaffolding.RPC_ENDPOINT from runtime discovery):curl -s "${RPC_ENDPOINT}/status" | jq -r '.result.node_info.network'minitiad q bank total --node "${RPC_ENDPOINT}"from account has enough of the actual native denom.weave rollup start -d and verify with scripts/verify-appchain.sh.borrow_global, borrow_global_mut, move_from, or calling a function that does) MUST include the acquires annotation for that resource type.borrow_global_mut) from a function unless it is passed as a parameter. Inline the logic or pass the resource as a parameter if needed.///) AFTER attributes like #[view] or #[test].minitiad tx move publish command does NOT use a --path flag. Pass the path to the compiled .mv file as a positional argument: minitiad tx move publish <path_to_file>.mv ....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.sync|async; do not assume block is available.minitiad broadcast, minitiad query tx <hash> may briefly return tx not found; poll/retry before treating it as failed.openBridge from useInterwovenKit. Default srcChainId to a public testnet (e.g., initiation-2) for local demos.scripts/verify-appchain.sh --gas-station --bots and confirm transaction success before handoff.references/common-tasks.mdreferences/contracts.mdreferences/frontend-interwovenkit.mdreferences/frontend-evm-rpc.mdreferences/e2e-recipes.mdreferences/runtime-discovery.mdreferences/weave-commands.mdreferences/weave-config-schema.mdreferences/troubleshooting.mdhttps://docs.initia.xyzhttps://docs.initia.xyz/interwovenkitscripts/scaffold-contract.sh, scripts/scaffold-frontend.shscripts/verify-appchain.shscripts/convert-address.py, scripts/to_hex.py, scripts/generate-system-keys.pyscripts/install-tools.sh, scripts/fund-user.sh