Files
lez-programs/modules/token

Token core module

token_module is a headless Logos core module for the LEZ Token Program. It has no UI. Basecamp UI modules and logoscore use the same generated API.

The module supports all current Token Program instructions, including fixed or mintable fungibles, metadata-backed fungibles, non-fungible definitions, NFT printing, transfers, minting, burning, and authority changes. It also decodes Token Definition, Token Holding, and Token Metadata accounts.

Architecture

Basecamp UI / logoscore
          |
     token_module       Qt-free C++ transport/orchestration
       /      \
token_ffi      logos_execution_zone
Rust codecs    shared wallet reads, account listing, transaction submission
and planners

token_ffi constructs token_core::Instruction values directly and serializes them with RISC Zero. This is intentional: the current Token IDL cannot fully describe new_definition_with_metadata, and its final three instruction indexes do not match the Rust enum's serialized order.

The module reuses the host-loaded logos_execution_zone instance. It never opens a second wallet and never creates account keys. Callers create fresh public accounts through the wallet module, then pass their IDs to Token operations.

API

Every method returns a map. Success starts with:

{ "status": "ok", "error": "" }

Failure is:

{ "status": "error", "error": "<stable_code>" }

Mutating success adds transactionId.

Read and discovery

Method Arguments Result payload
programInfo none Token Program ID in base58 and hex
inspectDefinition definition ID decoded fungible or non-fungible definition
inspectHolding holding ID decoded fungible, NFT master, or NFT printed-copy holding
inspectMetadata metadata ID decoded standard, URI, creators, and primary-sale value
walletTokenAccounts none all uniquely decodable Token-owned accounts in the connected wallet

Read results expose operator-facing base58 IDs and matching lowercase *Hex fields. Raw supplies, balances, print balances, and primary-sale values are decimal strings.

There is no global token registry. Explicit inspect methods can read any public account. walletTokenAccounts discovers only accounts present in the connected wallet.

Definition creation

Method Arguments
createFungible definition target, holding target, name, total supply raw, mint authority
createFungibleWithMetadata definition target, holding target, metadata target, name, total supply raw, mint authority, standard, URI, creators
createNonFungible definition target, master holding target, metadata target, name, printable supply raw, standard, URI, creators

Mint-authority values:

  • none creates a permanently fixed supply;
  • self uses the definition account itself;
  • an account ID assigns an external authority.

Metadata standard is simple or expanded.

Holding and supply operations

Method Arguments Notes
initializeHolding definition, fresh holding target NFT definitions create an unowned printed-copy holding
transfer sender holding, recipient holding, amount raw supports fungible, NFT master, and printed-copy rules
burn definition, holding, amount raw supports fungible and NFT variants
mint definition, holding, amount raw self-authority fungible path
mintWithAuthority definition, holding, current authority, amount raw external-authority fungible path
setAuthority definition, new authority self-authority path
setAuthorityWithAuthority definition, current authority, new authority external-authority path
printNft master holding, fresh printed holding target target must not be initialized first

new authority accepts the same none, self, or account-ID values as definition creation. Revocation with none is permanent.

Account ID and amount conventions

Account inputs accept base58 or 64 hexadecimal characters. Hex is normalized to lowercase.

Raw u128 arguments are exposed as JSON-compatible values:

  • small values may be passed as bare integers, for example 1000;
  • large values must be passed to logoscore as quote-wrapped decimal strings, for example '"12345678901234567890123456"'.

The CLI converts large bare numbers to floating point. The module rejects all floats instead of submitting a rounded amount. A UI should always pass raw amounts as decimal strings.

Fresh account workflow

Creation, explicit initialization, and NFT printing require fresh public wallet accounts. Create each one before calling the token method:

logoscore call logos_execution_zone create_account_public --json

For transfer and mint, an initialized destination needs no destination signature. A fresh destination is accepted only when the connected wallet owns its key, so it can authorize the Token Program claim.

The execution-zone provider may represent an absent public account as an all-zero owner/balance/nonce with empty data. The module treats that exact response as not_found, then still requires the target ID to belong to the connected wallet before submitting.

Build and test

Build from repository root; the root flake supplies token_ffi:

RISC0_DEV_MODE=1 cargo +1.94.0 test -p token_ffi
RISC0_SKIP_BUILD=1 cargo +1.94.0 clippy -p token_ffi --all-targets -- -D warnings
nix build path:.#token_ffi -L
nix build path:.#token-module -L

path:. is useful while new files are untracked. After files are tracked, nix build .#token-module is equivalent.

Runtime configuration

Set either TOKEN_PROGRAM_ID or TOKEN_PROGRAM_BIN on the process hosting the module:

# Use the deployed Token Program ID directly (base58 or 64-character hex).
TOKEN_PROGRAM_ID=F8sGbDbjcxvJHpUQJcArEaY7EbLMVmqZgRm3fXPw3jb3 \
  logoscore -D -m ./modules

# Or derive the ID from the exact deployable binary.
TOKEN_PROGRAM_BIN=/absolute/path/to/token.bin logoscore -D -m ./modules

If both variables are set, they must resolve to the same program ID. The binary must be the exact deployable .bin running on the target sequencer. Its RISC Zero image ID is the Token Program ID. Rebuilding the guest changes that identity; accounts owned by an older deployment must be read with the matching program-ID configuration.

Set TOKEN_DEBUG=1 on the daemon to emit safe adapter diagnostics to module stderr. Debug logging never includes wallet storage or recovery material.

Headless logoscore smoke test

1. Build both modules

# Token module (from the repo root; output under result/lib/).
nix build .#token-module -L
ls result/lib/          # token_module_plugin.dylib  libtoken_ffi.dylib

# The wallet module it depends on — the SAME pin the repo-root flake and
# amm_module use, with its inner monorepo input overridden to the rev the target
# sequencer runs (415964d7). See apps/amm/README.md for the fuller build notes.
nix build 'github:gravityblast/logos-execution-zone-module?ref=fix/generic-tx-instruction-bstr' \
  --override-input logos-execution-zone \
  'github:logos-blockchain/logos-execution-zone?rev=415964d7f9043a1bfe28da8d0e8b3a6f64abb258' \
  --out-link result-lez
ls result-lez/lib/      # logos_execution_zone_plugin.dylib  libwallet_ffi.dylib

2. Stage a modules directory

Core modules get no .lgx from the builder, so stage a directory by hand — one subdir per module with manifest.json + variant + the plugin dylib and its sibling FFI dylib (rpath is @loader_path, so the FFI lib must sit beside the plugin). The daemon discovers modules from this layout:

modules/
  token_module/
    token_module_plugin.dylib
    libtoken_ffi.dylib
    variant                     # one line: darwin-arm64-dev
    manifest.json
  logos_execution_zone/
    logos_execution_zone_plugin.dylib
    libwallet_ffi.dylib
    variant
    manifest.json

token_module/manifest.json:

{
  "name": "token_module", "type": "core", "version": "0.1.0",
  "manifestVersion": "0.2.0", "dependencies": ["logos_execution_zone"],
  "main": { "darwin-arm64-dev": "token_module_plugin.dylib" }
}

Copy the dylibs into place (result/lib/*modules/token_module/, result-lez/lib/*modules/logos_execution_zone/) and write each variant as a single line (darwin-arm64-dev on arm64 macOS). The logos_execution_zone/manifest.json mirrors this with its own name and plugin.

3. Start the daemon and load (dependency first)

Set TOKEN_PROGRAM_ID or TOKEN_PROGRAM_BIN on the daemon, then load the dependency before the module:

TOKEN_PROGRAM_ID=F8sGbDbjcxvJHpUQJcArEaY7EbLMVmqZgRm3fXPw3jb3 \
logoscore -D -m ./modules --persistence-path ./data

logoscore load-module logos_execution_zone   # dependency first
logoscore load-module token_module
logoscore module-info token_module --json
logoscore call token_module programInfo --json
logoscore call token_module inspectDefinition deadbeef --json

The final call must return invalid_account_id without crashing, and programInfo should return the configured or binary-derived ID.

4. Chain reads

Open a wallet configured for the target sequencer, then pass a real account ID:

logoscore call logos_execution_zone open \
  /path/to/wallet_config.json /path/to/storage.json "$WALLET_PASSWORD" --json

logoscore call token_module inspectDefinition \
  7b464ff9dd0d3bc07f7e2e0b0667ccd066d85ad12be4c79fc55687a863910aa6 --json

That example ID was a fixed-supply fungible on a historical testnet deployment; verify programInfo matches the intended deployment before interpreting it. Do not run mutating examples on a shared network without explicit operator authorization.

Current limitations

  • Public wallet accounts only; no private/shielded token flow.
  • No token registry, HTTP metadata fetch, symbol, or decimals model.
  • On-chain state can change between a read/preflight and transaction inclusion; the Token Program remains final authority.