Integrate hero_ledger for on-chain token payments #50

Closed
opened 2026-03-27 20:48:56 +00:00 by mik-tf · 6 comments
Member

Goal

Replace the in-memory wallet (UserPersistence JSON files) with hero_ledger's on-chain token system. Users get real SPORE/USDH balances, transfers are on-chain, exchange rates come from SPOREX.

Architecture

Marketplace Backend
  └─ WalletManager trait (existing)
     └─ impl_ledger/wallet_manager.rs (NEW)
        └─ hero_ledger_gateway::GatewayClient (HTTP JSON-RPC)
           └─ https://ledger.dev.projectmycelium.com (remote, no sidecar)

The gateway client is an HTTP client — no container needed. Just add the crate dependency and connect.

Reference

  • Repo: https://forge.ourworld.tf/lhumina_code/hero_ledger
  • Gateway client: clients/gateway/ — Rust SDK with reqwest HTTP, multi-URL failover
  • Networks: Local (localhost:9090), Dev (ledger.dev.projectmycelium.com), Main (ledger.projectmycelium.com)
  • Auth: ed25519 credentials (same key model as marketplace SPA vault)
  • Call types: call_public (read, no auth), call_write (signed transactions)

Gateway client methods we need

Method Purpose Replaces
tokens_balance("SPORE", account_id) Get wallet balance WalletManager::get_balance()
tokens_transfer("SPORE", receiver, amount) Transfer tokens WalletManager::transfer_credits()
tokens_metadata("SPORE") Token info WalletManager::get_wallet_info()
tokens_total_supply("SPORE") Total supply Metrics
marketplace_list_listings() On-chain listings Could sync with OSIS catalog
marketplace_get_listing(id) Listing detail Product detail

Contracts available

Contract Token Use case
SPORE Native Base currency (replaces MC)
USDH Stablecoin USD-pegged payments
GLD Rewards Staking rewards
SPOREX DEX Token exchange (replaces /api/pools)
Credit Vault Budgets Multi-bucket wallet accounting
KVS Storage On-chain key-value store

Tasks

Backend

  • L1 Add hero_ledger_gateway dependency to Cargo.toml
  • L2 Create src/services/impl_ledger/ module
  • L3 Implement WalletManager trait using GatewayClient:
    • get_balance()tokens_balance("SPORE", user_account_id)
    • get_transactions() → query on-chain TX history
    • credit()tokens_transfer() from marketplace account
    • transfer_credits()tokens_transfer() peer-to-peer
    • buy_credits() → fiat payment → mint/transfer SPORE
  • L4 Account mapping: marketplace user email → hero_ledger account_id
    • Option A: derive from user's ed25519 public key (implicit account)
    • Option B: store mapping in OSIS
  • L5 Implement PoolManager trait using SPOREX contract:
    • get_pools() → exchange pairs
    • exchange_tokens() → SPOREX swap
  • L6 Configuration: HERO_LEDGER_NETWORK=dev env var, branding.toml [ledger] section
  • L7 Feature flag: APP_WALLET_BACKEND=ledger (default stays local for dev)

SPA Frontend

  • L8 Wallet page shows on-chain balance (same API, different backend)
  • L9 Transaction history from on-chain data

Testing

  • L10 Unit tests for impl_ledger wallet manager
  • L11 Integration tests against dev gateway
  • L12 Verify existing 272 tests still pass in local mode

Key decisions

  1. Starter credits: New user registration → marketplace account transfers 100 SPORE to user (instead of setting JSON balance)
  2. Signed purchases: Already ed25519 — same key can sign on-chain TX
  3. Dual mode: APP_WALLET_BACKEND=local (OSIS, dev) vs ledger (on-chain, prod)
  4. No breaking changes: Same WalletManager trait, same API endpoints, different backend

— mik-tf

## Goal Replace the in-memory wallet (UserPersistence JSON files) with hero_ledger's on-chain token system. Users get real SPORE/USDH balances, transfers are on-chain, exchange rates come from SPOREX. ## Architecture ``` Marketplace Backend └─ WalletManager trait (existing) └─ impl_ledger/wallet_manager.rs (NEW) └─ hero_ledger_gateway::GatewayClient (HTTP JSON-RPC) └─ https://ledger.dev.projectmycelium.com (remote, no sidecar) ``` The gateway client is an HTTP client — no container needed. Just add the crate dependency and connect. ## Reference - Repo: https://forge.ourworld.tf/lhumina_code/hero_ledger - Gateway client: `clients/gateway/` — Rust SDK with reqwest HTTP, multi-URL failover - Networks: Local (localhost:9090), Dev (ledger.dev.projectmycelium.com), Main (ledger.projectmycelium.com) - Auth: ed25519 credentials (same key model as marketplace SPA vault) - Call types: `call_public` (read, no auth), `call_write` (signed transactions) ## Gateway client methods we need | Method | Purpose | Replaces | |--------|---------|----------| | `tokens_balance("SPORE", account_id)` | Get wallet balance | `WalletManager::get_balance()` | | `tokens_transfer("SPORE", receiver, amount)` | Transfer tokens | `WalletManager::transfer_credits()` | | `tokens_metadata("SPORE")` | Token info | `WalletManager::get_wallet_info()` | | `tokens_total_supply("SPORE")` | Total supply | Metrics | | `marketplace_list_listings()` | On-chain listings | Could sync with OSIS catalog | | `marketplace_get_listing(id)` | Listing detail | Product detail | ## Contracts available | Contract | Token | Use case | |----------|-------|----------| | SPORE | Native | Base currency (replaces MC) | | USDH | Stablecoin | USD-pegged payments | | GLD | Rewards | Staking rewards | | SPOREX | DEX | Token exchange (replaces /api/pools) | | Credit Vault | Budgets | Multi-bucket wallet accounting | | KVS | Storage | On-chain key-value store | ## Tasks ### Backend - [ ] **L1** Add `hero_ledger_gateway` dependency to Cargo.toml - [ ] **L2** Create `src/services/impl_ledger/` module - [ ] **L3** Implement `WalletManager` trait using `GatewayClient`: - `get_balance()` → `tokens_balance("SPORE", user_account_id)` - `get_transactions()` → query on-chain TX history - `credit()` → `tokens_transfer()` from marketplace account - `transfer_credits()` → `tokens_transfer()` peer-to-peer - `buy_credits()` → fiat payment → mint/transfer SPORE - [ ] **L4** Account mapping: marketplace user email → hero_ledger account_id - Option A: derive from user's ed25519 public key (implicit account) - Option B: store mapping in OSIS - [ ] **L5** Implement `PoolManager` trait using SPOREX contract: - `get_pools()` → exchange pairs - `exchange_tokens()` → SPOREX swap - [ ] **L6** Configuration: `HERO_LEDGER_NETWORK=dev` env var, branding.toml `[ledger]` section - [ ] **L7** Feature flag: `APP_WALLET_BACKEND=ledger` (default stays `local` for dev) ### SPA Frontend - [ ] **L8** Wallet page shows on-chain balance (same API, different backend) - [ ] **L9** Transaction history from on-chain data ### Testing - [ ] **L10** Unit tests for impl_ledger wallet manager - [ ] **L11** Integration tests against dev gateway - [ ] **L12** Verify existing 272 tests still pass in local mode ## Key decisions 1. **Starter credits**: New user registration → marketplace account transfers 100 SPORE to user (instead of setting JSON balance) 2. **Signed purchases**: Already ed25519 — same key can sign on-chain TX 3. **Dual mode**: `APP_WALLET_BACKEND=local` (OSIS, dev) vs `ledger` (on-chain, prod) 4. **No breaking changes**: Same WalletManager trait, same API endpoints, different backend — mik-tf
Author
Member

Progress: core integration complete

Done (L1-L7)

  • L1 heroledger_gateway_client dependency added
  • L2 src/services/impl_ledger/ module created
  • L3 LedgerWalletManager implements WalletManager trait:
    • get_balance() → tokens_balance("spore.marketplace.near", account)
    • credit() → tokens_transfer(marketplace → user)
    • debit() → tokens_transfer(user → marketplace)
    • SPORE 18-decimal on-chain amount conversion
  • L4 Account mapping: user email → ledger account (extensible to ed25519 key)
  • L6 Config: HERO_LEDGER_GATEWAY_URL, HERO_LEDGER_MARKETPLACE_ACCOUNT env vars
  • L7 Feature flag: APP_WALLET_BACKEND=ledger activates, unset = local OSIS (default)

Verified

  • All 272 tests pass in default mode (no regression)
  • Deployed to dev with default mode

Remaining

  • L5: PoolManager via SPOREX (future)
  • L8-L9: SPA frontend (reads same API, no change needed)
  • L10-L12: Tests against live ledger dev gateway

To activate: set APP_WALLET_BACKEND=ledger in the container env.

— mik-tf

## Progress: core integration complete ### Done (L1-L7) - [x] **L1** heroledger_gateway_client dependency added - [x] **L2** src/services/impl_ledger/ module created - [x] **L3** LedgerWalletManager implements WalletManager trait: - get_balance() → tokens_balance("spore.marketplace.near", account) - credit() → tokens_transfer(marketplace → user) - debit() → tokens_transfer(user → marketplace) - SPORE 18-decimal on-chain amount conversion - [x] **L4** Account mapping: user email → ledger account (extensible to ed25519 key) - [x] **L6** Config: HERO_LEDGER_GATEWAY_URL, HERO_LEDGER_MARKETPLACE_ACCOUNT env vars - [x] **L7** Feature flag: APP_WALLET_BACKEND=ledger activates, unset = local OSIS (default) ### Verified - All 272 tests pass in default mode (no regression) - Deployed to dev with default mode ### Remaining - L5: PoolManager via SPOREX (future) - L8-L9: SPA frontend (reads same API, no change needed) - L10-L12: Tests against live ledger dev gateway To activate: set `APP_WALLET_BACKEND=ledger` in the container env. — mik-tf
Author
Member

Status update — gateway is live, 3 fixes needed to connect

Verified

  • Dev gateway is live: https://ledger.dev.projectmycelium.com/rpc
  • Public methods work: marketplace.listListings → 6 listings
  • Auth-required methods need x-gateway-account-id header

Fixes needed

  1. URL path: Our client sends to root, gateway expects /rpc. Fix default URL or append path in client.
  2. Auth credentials: GatewayClient.set_credentials() never called. Need marketplace operator account + signing key for token operations.
  3. Deploy config: Set APP_WALLET_BACKEND=ledger + HERO_LEDGER_GATEWAY_URL=https://ledger.dev.projectmycelium.com/rpc on dev VM.

hero_compute (#51)

  • hero_compute_server is running locally (root processes) but explorer is not started
  • Cannot test until explorer is running in master mode with TCP port
  • Blocked on infra — not a code issue

— mik-tf

## Status update — gateway is live, 3 fixes needed to connect ### Verified - Dev gateway is live: `https://ledger.dev.projectmycelium.com/rpc` - Public methods work: `marketplace.listListings` → 6 listings - Auth-required methods need `x-gateway-account-id` header ### Fixes needed 1. **URL path**: Our client sends to root, gateway expects `/rpc`. Fix default URL or append path in client. 2. **Auth credentials**: `GatewayClient.set_credentials()` never called. Need marketplace operator account + signing key for token operations. 3. **Deploy config**: Set `APP_WALLET_BACKEND=ledger` + `HERO_LEDGER_GATEWAY_URL=https://ledger.dev.projectmycelium.com/rpc` on dev VM. ### hero_compute (#51) - hero_compute_server is running locally (root processes) but explorer is not started - Cannot test until explorer is running in master mode with TCP port - Blocked on infra — not a code issue — mik-tf
Author
Member

Architectural direction: hero_ledger IS the marketplace backend

Confirmed with Scott Yeager (hero_ledger maintainer) and Mik:

"I kinda feel though it makes sense for all listings to go on the ledger. Then it avoids having a whole other auth flow for interactions with the marketplace. The ledger can just be the backend for the marketplace."

What this means

hero_ledger is not just a payment rail — it is the single source of truth for all marketplace economic data:

Data Source Notes
Product listings (nodes, slices, apps, services) hero_ledger marketplace.* contract Real TFGrid nodes, real prices
Token balances (SPORE/USDH/GLD) hero_ledger tokens.* contract On-chain wallets
Transactions (purchases, transfers) hero_ledger blockchain Immutable history
Identity / auth hero_ledger ed25519 accounts Same key model as SPA vault
Exchange rates hero_ledger SPOREX contract On-chain DEX

OSIS remains for session/local state only:

Data Source Notes
Shopping cart OSIS Ephemeral, per-session
User preferences OSIS Theme, language, notifications
Messaging threads OSIS User-to-user messages
SSH keys OSIS Key management
Audit events OSIS Admin logging
Admin config OSIS Dashboard settings

Updated task list

Original tasks L1-L7 (wallet) are done. New tasks for full integration:

  • L13 Implement ProductCatalog trait backed by marketplace.listListings / marketplace.getListing
  • L14 Identity mapping: marketplace ed25519 keys → hero_ledger implicit accounts
  • L15 Implement PoolManager trait backed by SPOREX contract
  • L16 Transaction history from on-chain data
  • L17 Ledger integration test suite (tests/ledger_integration.sh)
  • L18 Document target architecture in docs/architecture.md

What doesn't change

  • Same frontend (SPA pages, admin dashboard)
  • Same API endpoints (/api/products, /api/wallet/balance, etc.)
  • Same service trait interfaces (ProductCatalog, WalletManager, etc.)
  • Just different backend implementations behind the same traits

— mik-tf

## Architectural direction: hero_ledger IS the marketplace backend Confirmed with Scott Yeager (hero_ledger maintainer) and Mik: > "I kinda feel though it makes sense for all listings to go on the ledger. Then it avoids having a whole other auth flow for interactions with the marketplace. The ledger can just be the backend for the marketplace." ### What this means hero_ledger is not just a payment rail — it is the **single source of truth** for all marketplace economic data: | Data | Source | Notes | |------|--------|-------| | Product listings (nodes, slices, apps, services) | hero_ledger `marketplace.*` contract | Real TFGrid nodes, real prices | | Token balances (SPORE/USDH/GLD) | hero_ledger `tokens.*` contract | On-chain wallets | | Transactions (purchases, transfers) | hero_ledger blockchain | Immutable history | | Identity / auth | hero_ledger ed25519 accounts | Same key model as SPA vault | | Exchange rates | hero_ledger SPOREX contract | On-chain DEX | OSIS remains for **session/local state** only: | Data | Source | Notes | |------|--------|-------| | Shopping cart | OSIS | Ephemeral, per-session | | User preferences | OSIS | Theme, language, notifications | | Messaging threads | OSIS | User-to-user messages | | SSH keys | OSIS | Key management | | Audit events | OSIS | Admin logging | | Admin config | OSIS | Dashboard settings | ### Updated task list Original tasks L1-L7 (wallet) are done. New tasks for full integration: - [ ] **L13** Implement `ProductCatalog` trait backed by `marketplace.listListings` / `marketplace.getListing` - [ ] **L14** Identity mapping: marketplace ed25519 keys → hero_ledger implicit accounts - [ ] **L15** Implement `PoolManager` trait backed by SPOREX contract - [ ] **L16** Transaction history from on-chain data - [ ] **L17** Ledger integration test suite (`tests/ledger_integration.sh`) - [ ] **L18** Document target architecture in docs/architecture.md ### What doesn't change - Same frontend (SPA pages, admin dashboard) - Same API endpoints (`/api/products`, `/api/wallet/balance`, etc.) - Same service trait interfaces (ProductCatalog, WalletManager, etc.) - Just different backend implementations behind the same traits — mik-tf
Author
Member

Account creation flow documented

How marketplace creates hero_ledger accounts

  1. Generate proper ed25519 keypair (secret seed → public key)
  2. Call account.activate with public_key hex, domain (e.g. username.mycelium), email
  3. User confirms email → account activated on-chain with funded balance
  4. Public key hex = account ID on hero_ledger
  5. Secret seed used for signing (tokens.balance, tokens.transfer)

Important: proper ed25519 keypair

The public_key sent to account.activate must be the real ed25519 public key derived from the secret seed — NOT a random hex string. Without the matching secret key, you can't sign transactions for that account.

For the marketplace integration

When a user registers on the marketplace:

  1. SPA already generates ed25519 keypair (vault)
  2. Same public key can be sent to account.activate → creates hero_ledger account
  3. User's vault key = their on-chain identity
  4. No separate auth system needed

Env vars for marketplace operator account

HERO_LEDGER_MARKETPLACE_ACCOUNT=0a431196b5d5a4d0c30501f049af2ac1b303decf9dd38d0c54c4fb53ce4f0205
HERO_LEDGER_SECRET_KEY=<stored securely, not in issues>

Account activated on devnet.

— mik-tf

## Account creation flow documented ### How marketplace creates hero_ledger accounts 1. Generate proper ed25519 keypair (secret seed → public key) 2. Call `account.activate` with public_key hex, domain (e.g. `username.mycelium`), email 3. User confirms email → account activated on-chain with funded balance 4. Public key hex = account ID on hero_ledger 5. Secret seed used for signing (tokens.balance, tokens.transfer) ### Important: proper ed25519 keypair The `public_key` sent to `account.activate` must be the real ed25519 public key derived from the secret seed — NOT a random hex string. Without the matching secret key, you can't sign transactions for that account. ### For the marketplace integration When a user registers on the marketplace: 1. SPA already generates ed25519 keypair (vault) 2. Same public key can be sent to `account.activate` → creates hero_ledger account 3. User's vault key = their on-chain identity 4. No separate auth system needed ### Env vars for marketplace operator account ```bash HERO_LEDGER_MARKETPLACE_ACCOUNT=0a431196b5d5a4d0c30501f049af2ac1b303decf9dd38d0c54c4fb53ce4f0205 HERO_LEDGER_SECRET_KEY=<stored securely, not in issues> ``` Account activated on devnet. — mik-tf
Author
Member

Full auth flow working — SPORE balance queried on-chain

Account 0a431196...4f0205 (mktplace3.mycelium) activated and tested:

{"balance": "0", "balance_formatted": "0.0000", "token": "SPORE"}

Key finding: params key order matters

The gateway verifies signatures using alphabetical key order in the params JSON. The Rust GatewayClient handles this via serde_json, but manual signing must use sorted keys.

What works end-to-end

  1. Account creation (account.activate) ✓
  2. Email confirmation ✓
  3. Authenticated balance query (tokens.balance with signed headers) ✓
  4. Compute listings from marketplace contract ✓
  5. Product catalog displaying real TFGrid nodes ✓

Balance is 0 SPORE (freshly created). Waiting for test tokens.

— mik-tf

## Full auth flow working — SPORE balance queried on-chain Account `0a431196...4f0205` (mktplace3.mycelium) activated and tested: ```json {"balance": "0", "balance_formatted": "0.0000", "token": "SPORE"} ``` ### Key finding: params key order matters The gateway verifies signatures using **alphabetical key order** in the params JSON. The Rust `GatewayClient` handles this via serde_json, but manual signing must use sorted keys. ### What works end-to-end 1. Account creation (account.activate) ✓ 2. Email confirmation ✓ 3. Authenticated balance query (tokens.balance with signed headers) ✓ 4. Compute listings from marketplace contract ✓ 5. Product catalog displaying real TFGrid nodes ✓ Balance is 0 SPORE (freshly created). Waiting for test tokens. — mik-tf
Author
Member

Code complete — impl_ledger/wallet_manager.rs and impl_ledger/product_catalog.rs merged.

  • Wallet: balance, credit, debit, account activation all implemented
  • Catalog: real TFGrid nodes from hero_ledger with 60s TTL cache
  • Account mapping: resolves user public key from OSIS for on-chain identity

Blocked on SPORE tokens from Scott (hero_ledger#41). Consolidated into mycelium_code/home#55

— mik-tf

Code complete — impl_ledger/wallet_manager.rs and impl_ledger/product_catalog.rs merged. - Wallet: balance, credit, debit, account activation all implemented - Catalog: real TFGrid nodes from hero_ledger with 60s TTL cache - Account mapping: resolves user public key from OSIS for on-chain identity Blocked on SPORE tokens from Scott (hero_ledger#41). Consolidated into https://forge.ourworld.tf/mycelium_code/home/issues/55 — mik-tf
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
coopcloud_code/home#50
No description provided.