Files
lez-programs/modules/token/README.md

266 lines
9.9 KiB
Markdown
Raw Permalink Normal View History

2026-08-19 01:36:02 -03:00
# 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
```text
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:
```json
{ "status": "ok", "error": "" }
```
Failure is:
```json
{ "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:
```bash
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`:
```bash
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:
```bash
# 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
```bash
# 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`:
```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:
```bash
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:
```bash
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.