{
  "schema_version": "v1",
  "generated_by": "msg_whitepaper_pipeline_v1",
  "collection": "chain_integration_facts",
  "collection_definition": "Stable, integration-facing facts for the MSG Chain network: chain identity, address and denom encoding, node port allocation, route ownership, transaction and signature model, and current availability. Values describe the current local/controlled development contract and are not a statement that a public production endpoint has been accepted.",
  "chain": {
    "chain_id": "msg-chain-1",
    "numeric_chain_id": 1,
    "chain_name": "MSG Chain"
  },
  "native_denom": {
    "symbol": "MSG",
    "minimal_denom": "umsg",
    "decimals": 9,
    "note": "The native bank denom umsg has 9 decimals. State which asset you are quoting, because the CW20 token below uses a different scale.",
    "cw20_token": {
      "contract": "msg_token_cw20",
      "decimals": 18,
      "note": "The msg_token_cw20 contract has 18 decimals. A consumer that assumes one decimal scale for both the native denom and this CW20 token will mis-price amounts."
    },
    "internal_gas_pricing_decimals": 18,
    "internal_gas_pricing_note": "The 18-decimal internal attoMSG unit is used for gas pricing arithmetic only and is not a user-facing denomination."
  },
  "address": {
    "two_formats_warning": "There are two address formats on this chain and they must not be mixed. An account address is not bech32; a contract address is bech32. A validator for one form rejects the other.",
    "account_address": {
      "prefix": "msg",
      "encoding": "custom hex with checksum",
      "format": "msg + 40 hex characters + 1 checksum character",
      "length_chars": 44,
      "is_bech32": false,
      "validator": "the official SDK's isValidMsgAddress accepts only this form"
    },
    "contract_address": {
      "prefix": "msg",
      "encoding": "bech32 (BIP-173)",
      "format": "bech32 with human-readable part msg and a 20-byte payload",
      "length_chars": 42,
      "is_bech32": true,
      "example_shape": "msg1...",
      "derivation": "bech32.Encode(\"msg\", ConvertBits(sha3_512(creator || codeID || instanceID)[:20], 8, 5, true))",
      "validator": "the official SDK's isValidMsgContractAddress accepts only this form"
    }
  },
  "node_ports": {
    "description": "Default primary-node port allocation. Additional nodes on the same machine must not reuse the same port set.",
    "rpc": 13148,
    "agent_api": 13197,
    "health": 15197,
    "rest": 16197,
    "metrics": 17197,
    "pprof": 18197,
    "grpc": 19197,
    "p2p": 19238,
    "operator_console": 20197
  },
  "route_ownership": [
    {
      "route_group": "/api/v1/*",
      "canonical_port": 13148,
      "canonical_owner": "rpc"
    },
    {
      "route_group": "/cosmos/*, /cosmwasm/*",
      "canonical_port": 16197,
      "canonical_owner": "rest"
    },
    {
      "route_group": "/agent/v1/*",
      "canonical_port": 13197,
      "canonical_owner": "agent_api"
    }
  ],
  "route_ownership_note": "The contract-call group (/api/v1/contracts/*), the receipt paths and three read paths carry the same request and response shape on both 13148 and 16197, so a client may pin a single port and reuse one parser. The REST port also carries a production-readiness gate and returns a service-unavailable response for most non-diagnostic business paths until that gate is satisfied; business clients should prefer the canonical owner listed above.",
  "event_subscription": {
    "path": "/agent/v1/events/subscribe",
    "transport": "websocket"
  },
  "tls": {
    "default_enabled": false
  },
  "transaction_model": {
    "encoding": "json",
    "message_type": "types.Transaction",
    "is_protobuf_tx_raw": false,
    "signature_algorithm": "CRYSTALS-Dilithium-5 round-3 (cloudflare/circl mode5)",
    "signature_bytes": 4595,
    "public_key_bytes": 2592,
    "replay_protection": "chain_id bound",
    "broadcast_note": "The broadcast endpoints accept MSG native signed transactions only. A standard Cosmos or CosmJS signed transaction is rejected rather than adapted.",
    "signature_variant_note": "FIPS-204 ML-DSA-87 (signature 4627 bytes) is NOT wire-compatible with this chain.",
    "hash_signature_public_key_encoding": "hex, never base64",
    "hash_profile_required": "msgchain_tx_hash_v2",
    "hash_profile_note": "A client-produced transaction must set hash_profile to msgchain_tx_hash_v2. An empty hash_profile is interpreted as the legacy v1 hash and fails the node hash check.",
    "sender_address_derivation": "the node re-derives the sender address from the Dilithium-5 public key using the account algorithm (sha3_512(pk) first 20 bytes plus checksum)",
    "native_signed_rule": "a transaction is native signed only when from, type and chain_id are non-empty and hash, signature and public_key are non-empty"
  },
  "signing_clients": {
    "rule": "The only client that can sign an MSG transaction is the official SDK, because MSG requires native Dilithium-5 signatures.",
    "official_sdk": {
      "package": "@msg-chain/sdk",
      "source_dir": "sdk/",
      "can_sign": true,
      "availability": "local_development_candidate",
      "release_note": "Alpha surface published for local development. No signed public release, and not yet production-verified."
    },
    "keplr": {
      "can_sign": false,
      "access": "read-only",
      "reason": "A browser extension wallet cannot produce Dilithium-5 signatures for this chain."
    },
    "cosmjs": {
      "can_sign": false,
      "access": "read-only",
      "reason": "A standard Cosmos signing stack produces secp256k1 transactions, which this chain rejects."
    },
    "metamask": {
      "can_sign": false,
      "access": "read-only",
      "reason": "An EVM wallet cannot produce Dilithium-5 signatures for this chain."
    }
  },
  "availability": {
    "local_devnet": "available",
    "api_contract": "frozen",
    "public_endpoint": "not_published",
    "public_endpoint_status": "not_published",
    "public_network": "not_launched",
    "production_readiness": "not_production_verified",
    "note": "No public endpoint is published yet. Do not connect a client to any public address, and do not treat a local development network address as a production address.",
    "no_public_token_grant": "There is no public token-grant endpoint: the distribution endpoint is admin-only and fails closed, and third-party gas is pre-funded by the operator from real block revenue. Do not design an onboarding flow that depends on a public grant endpoint."
  },
  "boundary": [
    "These facts describe the current integration contract and local/controlled development surfaces; they are not a claim that a public production endpoint has been accepted.",
    "Mainnet status is declared in the public status file and flips only on the official launch announcement.",
    "Port numbers are the default allocation, not a promise that a given host exposes them publicly.",
    "Availability, domain control, certificates, service-level objectives and release permission still require review before production integration."
  ],
  "related": [
    "api_specs/error_codes.json",
    "api_specs/openapi/agent_surface.yaml",
    "api_specs/openapi/contract_surface.yaml",
    "api_specs/openapi/public_query.yaml",
    "api_specs/rpc_methods.json",
    "chain_config/network_presets.json",
    "docs/developer/third-party-api-contract-v1.md"
  ],
  "metadata_profile": "public_stable",
  "nonce_source": {
    "rule": "The nonce is per-sender and strictly increasing, and it is also domain-scoped: a sender has a separate nonce per transaction domain. Fetch the value for the domain you are signing in before you sign, and fail closed if it cannot be read.",
    "endpoints": [
      {
        "path": "/agent/v1/query/account/{address}",
        "port": 13197,
        "returns": "the default-domain nonce, as a number; this is the authoritative value for a default-domain transaction",
        "gateway_note": "On the mainnet deployment the full 13197 route set sits behind a trusted gateway and requires the header X-MSG-Agent-Gateway-Key (pkg/quantum/agent_api.go, agentGatewayKeyHeader)."
      },
      {
        "path": "/api/v1/auth/accounts/{address}",
        "port": 13148,
        "returns": "sequence as a string, for example \"0\"",
        "boundary": "sequence is domain-agnostic: it counts transactions for the sender across domains and is a reference only. Do not use it as the nonce of a specific domain, and do not conflate it with the agent-API nonce."
      }
    ],
    "boundary": "Which domain a transaction belongs to is decided by the node from the transaction itself (for example a lifecycle-domained wasm_migrate carries the domain marker); there is no read-only endpoint for every domain.",
    "read_only_scope": "A read-only integration does not need the agent gateway: the frozen-contract public_reads on 13148 (/api/v1/*) and 16197 (/cosmos/*, /cosmwasm/*) need no gateway header and no API key. The agent API (13197) is required only for agent-only functions such as the WebSocket subscription events/subscribe. Reported by the development team in its round-7 reply; recorded here as the current chain fact."
  },
  "shared_identical_routes": [
    {
      "methods": "POST, GET",
      "paths": [
        "/api/v1/contracts/query",
        "/api/v1/contracts/{address}/query"
      ]
    },
    {
      "methods": "POST",
      "paths": [
        "/api/v1/contracts/execute",
        "/api/v1/contracts/{address}/execute"
      ]
    },
    {
      "methods": "GET",
      "paths": [
        "/api/v1/receipts/{txHash}"
      ]
    },
    {
      "methods": "GET",
      "paths": [
        "/api/v1/blocks/height"
      ],
      "note": "read parity; added to the mux by THIRD-PARTY-API-MUX-READ-PARITY-001"
    },
    {
      "methods": "GET",
      "paths": [
        "/api/v1/txs/{hash}"
      ],
      "note": "read parity"
    },
    {
      "methods": "GET",
      "paths": [
        "/api/v1/accounts/{address}/txs"
      ],
      "note": "read parity"
    }
  ],
  "shared_identical_note": "Both surfaces render these bodies with one shared builder, so the shapes cannot drift. The three read paths matter on a mainnet-production node because the REST readiness gate blocks most non-diagnostic paths until it is satisfied, while the canonical mux owner has no such gate.",
  "devnet_auth": {
    "rule": "The local development-network launcher does not inherit the host's REST auth tokens: it pins an empty token-file variable and publishes its own deterministic development token in the launcher output, so a write probe behaves the same on every machine.",
    "read_paths": "Public read endpoints under /api/v1/* and /cosmos/* do not require the token.",
    "write_paths": "Protected write endpoints (POST /api/v1/txs, POST /api/v1/txs/estimate, POST /api/v1/contracts/*, POST /api/v1/sign/message, and the RPC mux contract-write paths) require the header Authorization: Bearer <token>.",
    "where_it_is_published": "The launcher writes the token into its development-network endpoints file, next to the smoke result.",
    "recorded_as": "THIRD-PARTY-DEVNET-AUTH-DETERMINISM-001 (local slice, AuditPassed)",
    "boundary": "Local development network only. This is not a public endpoint and not a production credential; the public endpoint has not been published."
  },
  "contract_deployment": {
    "two_paths": "There are two different deploy paths and they are not interchangeable. Path A is the administrator REST write path (POST /api/v1/contracts/store|instantiate|deploy|execute on the REST surface); it is signed by the node wallet and the contract admin becomes the node, so it is for official operations and local self-test only. Path B is third-party self-deployment: the project builds and signs its own wasm_store / wasm_instantiate transactions and submits them through broadcast_tx_* on the RPC surface, so that project owns the contract admin.",
    "third_party_path_requires_admission": "Path B is admitted only when the node runs with a third-party deploy allowlist. Modes: disabled (default; ordinary wallet self-service store/instantiate is rejected), allowlist (listed signer addresses are admitted, others fail closed), permissionless (governance-switchable). The allowlist artifact is generated node-side, pinned by SHA-256, and bound to the chain id and the real Block0 hash.",
    "storage_permissions_note": "Before third-party self-deployment is enabled by governance, a project cannot store or instantiate its own contract by self-service; the enabled state is a chain-side admission setting, not a client-side option.",
    "local_devnet_boundary": "The local development network used for this onboarding has no block producer: committed height stays at 1 and the block-production status is consensus_required. A successful admission there proves the admission boundary only; there is no on-chain commit and no receipt. Real commits and receipt readback require the public chain.",
    "receipt_readback": "A deploy is only final when the transaction is committed: use broadcast_tx_commit and require deliver_tx.code == 0 with height > 0, then read GET /api/v1/receipts/{txHash} and require success == true and block_height > 0. broadcast_tx_sync returning code 0 only means the transaction was accepted, not that it was committed.",
    "gas_price_floor": "Transactions below the minimum gas price floor are rejected as anti-spam. The floor is MinGasPriceAtto = 100000000 atto per gas.",
    "code_hash_layers": "code_hash is layered and the layers must not be mixed: the store transaction receipt event reports a SHA-256 digest, while the genesis canonical store and the SDK wasmCodeHash() helper use SHA3-512. Compare against the code_hash of the store receipt you actually obtained.",
    "admin_change_surface": "There is no top-level wasm_update_admin transaction type. update_admin and clear_admin are only handled as sub-messages emitted in a contract execution response (pkg/quantum/transaction_executor.go), so a client must not send an admin change as its own transaction; the top-level execute path recognises only wasm_store, wasm_instantiate, wasm_execute and wasm_migrate. An admin change is also not one of the controlled types in the third-party allowlist, which covers the signers of wasm_store / wasm_instantiate / contract_deploy / contract_instantiate.",
    "public_path_status": "The public deploy path is not open. Configuration values for it (REST / RPC / Agent API base URLs and the allowlist distribution) arrive with the published window parameters; until then do not connect a client to any public address and do not describe a contract as deployed on-chain.",
    "boundary": "All of the above is local or controlled-environment behaviour. It is not a public endpoint, not a production credential, and not an availability guarantee.",
    "admin_immutability": "An instantiate whose admin field is missing or explicitly null produces an immutable contract instance (pkg/quantum/transaction_executor.go calls types.ParseWasmInstantiateAdmin, which returns immutable=true for a missing or null admin). The two supported third-party strategies are therefore: set data.admin to a long-lived address you control, or deploy immutable with admin null.",
    "allowlist_scope": "The third-party deploy allowlist validates the transaction signer only. It does not carry the contract admin, so a client must read the admin back from the deployment transaction: GET /api/v1/txs/{hash} returns data as hex-encoded JSON, and decoding it yields .admin (pkg/api/rest/server.go FormatTransactionResponse hex-encodes json.Marshal(tx.Data)). The contract-state query field contract_info.admin is a hard-coded empty string and must not be used as the admin; a read-only contract-admin endpoint is a long-term candidate with no schedule. Reported by the development team in its round-7 reply; recorded here as the current chain fact.",
    "third_party_allowlist_artifact": {
      "filename": "third_party_deploy_allowlist.json (same name as on the development network)",
      "checksums": "A one-file SHA256SUMS in the standard sha256sum format, one line per artifact, with the sha256 also written inline in the JSON body.",
      "load_semantics": "The node loads the artifact once at startup; changing it requires a node restart.",
      "boundary": "Reported by the development team in the round-6 reply and recorded here as the current chain fact; it is not a roadmap commitment."
    }
  },
  "contract_instantiation_limits": {
    "sub_message_instantiate_unsupported": "Contract-issued Wasm instantiation sub-messages (WasmMsg::Instantiate and Instantiate2) are not supported by the node: they are classified as unsupported cosmos messages and rejected (pkg/quantum/transaction_executor.go, hasUnsupportedCosmosMsg).",
    "factory_pattern_unavailable": "Because of the rule above, the factory pattern (one contract instantiating another token or pair contract at runtime) is not currently available on this chain. A contract that depends on it cannot work as designed.",
    "user_visible_impact": "The user-visible surface is narrow. Across the registered contracts exactly one depends directly on a contract-issued instantiate sub-message: dex_factory_v1 (contracts/cosmwasm/all/dex_factory_v1/src/lib.rs emits WasmMsg::Instantiate). That contract is not local_implemented, so the affected path is the DEX factory pool-creation path only.",
    "contracts_not_affected": "dex_pair_v1 and dex_router_v1 use WasmMsg::Execute only (the router's factory is a query/lookup, not an instantiation), and personal_token_v1 instantiates cw20_base in-process (personal_token_v1/src/contract.rs) instead of through a sub-message, so none of them is blocked by this limit. Their unfinished status has other causes and must not be attributed to it.",
    "public_wording": "State it as: a factory contract can only register and validate; a CW20 must be deployed by its own separate transaction. Do not attribute other contracts' unfinished status to this limit.",
    "other_unsupported_sub_messages": "The same check also treats IBC, staking, governance, distribution, stargate and any/unknown custom sub-messages as unsupported, so a contract cannot rely on them either.",
    "boundary": "This is current chain capability, stated by the development team and re-checked here against the source. It is not a roadmap commitment and it is not an upgrade promise."
  },
  "resource_market_design_status": {
    "status": "The published resource-market product scope now lists three kinds of resource package: node_release, skill_package and file_bundle (arbitrary file bundles, xxx.msgfiles.aitch). The file_bundle kind is frozen at the design level only: it is not implemented, not deployed and not enabled, and the first-release enablement scope is still undecided.",
    "scope": "Product scope and capability are stated separately. Only node_release and skill_package carry local verification closure today; file_bundle appears in the scope as a designed kind, so a client must not treat it as an available, deployable or published resource kind.",
    "not_a_launch_condition": "The design freeze and this scope entry neither change the mainnet verdict nor constitute evidence for any capability surface, contract or launch gate."
  },
  "contract_lifecycle_nonce_domain": {
    "domain": "admin_wasm_lifecycle",
    "granularity": "per sender and per domain",
    "fresh_address": "A brand-new address has no high-water mark in this domain and therefore starts at 0.",
    "window_rule": "While a lifecycle window is open the node accepts a signed nonce strictly above the committed high-water mark and within a bounded look-ahead window: high_water < nonce <= high_water + 16.",
    "no_read_only_endpoint": "There is no read-only endpoint that returns this domain's high-water mark, so a client cannot read the exact next value; it must treat the window as a range and handle rejection by re-deriving the next value from its own last accepted transaction.",
    "failed_but_included_tx": "A transaction that was included in a block but failed is still discoverable read-only through GET /api/v1/receipts/{txHash}, which reports success=false. Use it to distinguish 'never included' from 'included and failed'.",
    "boundary": "Reported by the development team in the round-6 reply and recorded here as the current chain fact; it is not a roadmap commitment.",
    "client_requirement": "Because there is no on-chain read-only path to this domain's high-water mark, a client must persist the nonces it has used and rebuild its next value from its own receipts (GET /api/v1/receipts/{txHash}) rather than trying to query the chain for it. Reported by the development team in its round-7 reply; recorded here as the current chain fact."
  },
  "window_parameters_publication": {
    "form": "A separate official document delivered through the three-project return channel, with machine-readable attachments (the allowlist artifact, its SHA256SUMS, and CW20 receipt / WebSocket event samples).",
    "not_in_repo_or_package": "It is not a repository path and it is not shipped inside a node release package.",
    "trigger": "Window close-out -> collector PASS -> both nodes connect to the chain and activate the allowlist -> publication. The close-out date is published with the window-parameters document itself; this public fact deliberately does not carry a day-level date.",
    "current_state": "not_published",
    "boundary": "Until that document is delivered, do not point a client at any public address and do not treat an allowlist as available."
  },
  "read_contract_labels": {
    "status": "contract_frozen",
    "no_stable_exploratory_tags": "The read-only contract surface has no stable / exploratory tags.",
    "rule": "A read-only contract listed in public_reads is frozen; a read-only contract that is not listed is not supported.",
    "boundary": "Reported by the development team in the round-6 reply and recorded here as the current chain fact; it is not a roadmap commitment."
  },
  "contract_self_registration": {
    "register_developer_call_site": "register_developer can only be called by the contract itself (the sender must be the contract address), so a contract that needs its developer share registered must do it by self-call inside its instantiate handler (contracts/cosmwasm/all/gas_fee_distribution_v1/src/contract.rs).",
    "receipt_signal": "The call shows up as the response attribute action=register_developer; there is no separate DeveloperRegistered event to subscribe to.",
    "boundary": "Reported by the development team in the round-6 reply and recorded here as the current chain fact; it is not a roadmap commitment."
  }
}