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:
nonecreates a permanently fixed supply;selfuses 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
logoscoreas 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.