135 Commits

Author SHA1 Message Date
r4bbit
34c6c5e821
fix(wallet): send tx instruction as a byte string, not QVariantList<u32>
LogosWalletProvider::submitPublicTransaction passed the RISC0 instruction
words to send_generic_public_transaction as a QVariantList<u32>. That param
is a byte string (bstr): the module's QtRO glue mangles a list-of-u32 crossing
the module process boundary, so the guest deserialized a corrupted Instruction
variant and panicked, e.g.

  Guest panicked: called `Result::unwrap()` on an `Err` value:
  Custom("invalid value: integer `53765`, expected variant index 0 <= i < 10")

(53765 = 0x0000D205 — the guest read a 4-byte word where 0x05, the AddLiquidity
variant, is a single byte.) This broke every submit through the shared provider,
including the new-position / add-liquidity flow.

Send the little-endian bytes of the u32 words as a QByteArray instead — the same
encoding the AMM swap path already uses (it calls the module directly and never
went through this provider). signingRequirements stays a QVariantList<bool>;
only the instruction needed the bstr encoding.
2026-07-24 18:11:17 +02:00
r4bbit
44b355ac7d
factor(amm-ui): source new-position network context from AMM_PROGRAM_BIN/TOKENS_CONFIG/wallet
The create-pool / new-position flow carried its own network layer:
AMM_UI_NETWORK + AMM_UI_DEVNET_FILE (a devnet.json) or a bundled
config/networks.json supplied the AMM program id and token set, and a
JSON-RPC channel/checkpoint "identity probe" gated the flow to a verified
network. Main already exposes all of this the way the Swap view consumes it,
so collapse onto those sources instead of a parallel system:

- ammProgramId  <- $AMM_PROGRAM_BIN (derived like swapExactInput's program id;
                   doubles as the quote's network fingerprint so a quote can't
                   be replayed against a different deployment)
- tokenIds      <- $TOKENS_CONFIG (amm-tokens.json), same as the Swap picker
- sequencer     <- the wallet config (already surfaced via syncWalletState)

networkSnapshot() builds the ActiveNetworkSnapshot from those; status is
"ready"/"config_missing", gated to "loading" until wallet state resolves so no
module reads happen during construction. The channel probe is gone — submit
needs no channelId (the wallet module supplies the channel via
submitPublicTransaction), so the whole verification apparatus was overhead.

Removes: AMM_UI_NETWORK / AMM_UI_DEVNET_FILE, devnet.json / networks.json, the
ActiveNetwork class (+ its test) and its QNetwork channel probe, and Qt6Network.
ActiveNetwork.h keeps only the ActiveNetworkSnapshot struct. Run command drops
the AMM_UI_* vars:
```
  LEE_WALLET_HOME_DIR=… AMM_PROGRAM_BIN=… TOKENS_CONFIG=… nix run .#amm-ui
```
2026-07-24 17:33:37 +02:00
r4bbit
e01878faf7
chore: remove unnecessary macos prerequisites docs 2026-07-24 14:45:15 +02:00
Ricardo Guilherme Schmidt
81d20cef5b
fix(amm): align execution zone dependencies 2026-07-24 14:27:49 +02:00
Ricardo Guilherme Schmidt
6cadc86b54
fix(amm-ui): require explicit liquidity inputs
Keep wallet assets as token choices without automatically selecting a pair. Keep pool-probe amounts internal so active-pool quotes do not populate the form before user input.
2026-07-24 14:00:47 +02:00
r4bbit
b2753aa0c9
feat(apps/amm): add create-pool / new liquidity position flow
Add a "Create Pool" flow to the AMM app that lets a user open a new
liquidity position — seeding a pool's initial liquidity — from token
selection and amount entry, through a confirmation dialog, to on-chain
submission.

- client crate (apps/amm/client): pure, testable protocol logic — account
  decoding, pair/position modelling, and quote/plan computation — exposed to
  the app over a C ABI (config/networks.json drives network selection).
- C++ runtime + backend: AmmClient, ActiveNetwork, and NewPositionRuntime,
  wired into AmmUiBackend (new resolve/quote/submit slots).
- QML flow: NewPositionForm, NewPositionFlow state, NewPositionConfirmation-
  Dialog, TokenSelectorModal, and reusable Amm* presentational components
  (theme, surfaces, buttons) + AmountMath.js.
- tests: C++ (NewPositionRuntimeTest, ActiveNetworkTest) and QML
  (tst_NewPositionForm, tst_LiquidityPage, tst_TokenAmountInput, …).
2026-07-24 14:00:42 +02:00
r4bbit
64b7e8a197
fix(apps/amm): ensure changing endpoint works 2026-07-24 10:07:34 +02:00
r4bbit
46f50a3f81
fix(apps/wallet): use @loader_path rpath for the QML plugin on macOS
The Logos.Wallet QML plugin was installed with an ELF "$ORIGIN" rpath.
macOS dyld does not expand "$ORIGIN", so loading the plugin failed with

  Cannot load library liblogos_wallet_qmlplugin.dylib:
  Library not loaded: @rpath/liblogos_wallet_qml.dylib

even though liblogos_wallet_qml.dylib sits in the same directory. Select
the rpath per platform: @loader_path on Apple, $ORIGIN elsewhere, so
@rpath/liblogos_wallet_qml.dylib resolves to the sibling library.
2026-07-24 09:21:34 +02:00
Ricardo Guilherme Schmidt
dfd19a9796
feat(wallet): add reusable wallet modules
Add program-neutral wallet access, a stable account model, reusable QML controls, transaction confirmation, submitted-transaction presentation, and isolated contract tests.
2026-07-24 09:21:25 +02:00
Andrea Franz
e03164baf3 fix(amm): LE-serialize instruction words; correct ELF→ProgramBinary docs; clarify sharedWalletIsOpen 2026-07-23 16:17:25 +02:00
Andrea Franz
f1fc061664 style(amm): cargo fmt 2026-07-23 16:17:25 +02:00
Andrea Franz
cf92e5d111 fix(amm): guard resolvePool against stale callbacks; clarify deadline-ms and program-binary docs 2026-07-23 16:17:25 +02:00
Andrea Franz
fe41baf210 fix(amm): address Copilot review — exact BigInt min_out, fail on oversized pool fee, sync flake run docs 2026-07-23 16:17:25 +02:00
Andrea Franz
9b7a3dcaac feat(amm): swap via d70225ced program_id_hex API; pin wallet-module core to sequencer rev 415964d7 2026-07-23 16:17:25 +02:00
Andrea Franz
69cd58d377 build(amm): fork logos_execution_zone for QtRO byte-string tx args + align amm_client_ffi to lee_core v0.2.0 2026-07-23 16:17:25 +02:00
Andrea Franz
8f67c4c4b6 build(amm): load amm_client_ffi via absolute store-path id + DYLD fallback on macOS 2026-07-23 16:17:25 +02:00
Andrea Franz
c0568e3a88 feat(amm): byte-encode swap instruction for QtRO + AMM_DEBUG tracing + token config 2026-07-23 16:17:25 +02:00
Andrea Franz
caf53d0409 fix(amm): order swap holdings/reserves by pool token order; accept base58 ids 2026-07-23 16:17:25 +02:00
Andrea Franz
906aa65b4f feat(amm): config-driven token picker wired to on-chain swaps 2026-07-23 16:17:25 +02:00
Andrea Franz
a0c8983302 feat(amm): wire Swap UI to on-chain resolvePool + swapExactInput 2026-07-23 16:17:25 +02:00
Andrea Franz
b51a71ddf2 feat(amm): resolvePool + swapExactInput backend slots for on-chain swaps 2026-07-23 16:17:25 +02:00
Andrea Franz
cfb62e4d2f build(amm): link amm_client_ffi into the AMM UI module 2026-07-23 16:17:25 +02:00
Andrea Franz
f3a14f051a feat(amm): decode_config in amm_client_ffi (reads token/twap program ids from AMM config) 2026-07-23 16:17:25 +02:00
Andrea Franz
3b9ca241c2 feat(amm): metal-safe amm_client_ffi crate + root flake for on-chain swap calls 2026-07-23 16:17:25 +02:00
Ricardo Guilherme Schmidt
cca063ce2d refactor(amm)!: select swap direction by input holding, sign only the input
Replace the `token_definition_id_in` argument with a role-based account
interface: swaps now take a `user_input_holding` and a `user_output_holding`
instead of positional token-A/token-B holdings. Direction is derived from the
input holding's own token definition, and the input slot is a framework-level
`#[account(signer)]` so authorization is enforced before execution rather than
delegated solely to the downstream token transfer. The output holding only
receives and needs no signature.

This removes the ambiguity the previous arg-based model carried (the arg could
disagree with the signed holding) and makes the IDL express the signing rule:
`user_input_holding` is `signer: true`, `user_output_holding` is `signer: false`.

The two user-holding post-states are echoed in the guest's declared slot order
(input, then output); the framework matches post-states to accounts by
position, so the internal A/B mapping used for reserve bookkeeping must not leak
into the returned order.

BREAKING CHANGE: The AMM swap instruction interface changed and the guest
ImageID/ProgramId changes as a result.
2026-07-21 09:17:29 +02:00
r4bbit
63d2fe6f0b chore(release): v1.0.0 v1.0.0 2026-07-16 10:48:25 +02:00
r4bbit
25b8b86103 chore(amm): add Logos Basecamp support
Provides the necessary instructions to run the AMM app inside Logos
Basecamp.

Closes #29
2026-07-15 18:31:39 +02:00
r4bbit
ff89025ead build(deps): bump spel to v0.6.0 and logos-execution-zone to v0.2.0
Move the spel-framework dependency from the 0x-r4bbit/spel fork
(v0.5.0 @ 91023c9, the refactor/lez-v020-compat branch) to the released
logos-co/spel tag v0.6.0.

spel v0.6.0 is built against the final logos-execution-zone v0.2.0, not
the v0.2.0-rc6 this repo pinned. The two must match: with rc6, the guest
ELF build fails because spel's lee_core::program::ValidityWindow and the
repo's own copy are distinct types the #[lez_program] macro can't unify.
So this also bumps every logos-execution-zone pin (lee/lee_core aliased
as nssa/nssa_core, plus clock_core) from v0.2.0-rc6 to the final v0.2.0.

- 10 Cargo.toml switched to logos-co/spel tag v0.6.0
- 24 logos-execution-zone pins across 18 Cargo.toml moved rc6 -> v0.2.0
- All 7 Cargo.lock files re-resolved (root + 5 guest workspaces +
  benchmark); pulls in lee_core v0.1.0 (v0.2.0) as a spel transitive dep

Guest ImageIDs change as a result: the ID hashes the whole guest ELF,
which links the updated spel-framework and lee_core object code, even
though the program sources are unchanged. Update any ImageID-derived
values (deployed program IDs, PDA addresses, AMM/ATA program-id inputs)
before submitting transactions.
2026-07-15 15:11:47 +02:00
Andrea Franz
5b82a52c6b feat(stablecoin): add ProtocolParameters account type
closes #158
2026-07-07 10:52:30 +02:00
r4bbit
4a6192d84f build: pin enum-ordinalize to 4.3.2 in the AMM guest lockfile
The AMM guest lockfile had drifted to enum-ordinalize 4.4.1 (and
enum-ordinalize-derive 4.4.1), both of which require rustc 1.89. The
RISC Zero guest toolchain is 1.88.0-dev, so `cargo +risc0 build
--locked` failed the "build programs" CI task:

    error: rustc 1.88.0-dev is not supported by the following packages:
      enum-ordinalize@4.4.1 requires rustc 1.89
      enum-ordinalize-derive@4.4.1 requires rustc 1.89

enum-ordinalize is pulled in transitively via educe 0.6.0 (^4.3), so
4.3.2 satisfies the requirement and matches what every other guest
lockfile already resolves to. Pin both crates back to 4.3.2.

The host workspace Cargo.lock keeps 4.4.1 — it builds on 1.94.0 per
rust-toolchain.toml and is unaffected.
2026-07-03 12:51:46 +02:00
bristinWild
fe4c7a96da feat(token): add mint authority model to token program
Add an optional mint authority to fungible tokens for controlled supply:
create with a designated minter, mint additional supply, rotate the
authority to a new key, or permanently revoke it to fix the supply.

The authority is stored inline on `TokenDefinition::Fungible` as
`authority: Option<AccountId>` (`Some(id)` = mintable by `id`, `None` =
fixed supply). Keeping it a plain `Option<AccountId>` rather than a custom
wrapper type leaves account state decodable by `spel inspect`; the
require/rotate/revoke guard logic lives inline in the handlers.

LEZ rejects a transaction that lists the same account id twice, so one
instruction cannot statically express both "the definition account is the
authority and signs" (self/PDA authority) and "a distinct rotated account
signs" (external authority) — they need opposite signer markers. Each
privileged operation is therefore split into a self and an external
variant:

- `Mint` / `SetAuthority` — the definition account is the signer.
- `MintWithAuthority` / `SetAuthorityWithAuthority` — a distinct authority
  account is the signer; the definition account does not sign.

Creation via `NewFungibleDefinition { mint_authority, .. }`; an all-zero
authority id is rejected. The AMM's LP token uses self/PDA authority — its
stored authority is the LP definition PDA, minted only by the pool via
chained calls.

Covered by token unit tests and zkVM integration tests: creation with and
without an authority, self- and external-authority mint, rotation, and
external rotate/revoke. IDLs regenerated.
2026-07-02 19:19:23 +02:00
r4bbit
751d4ac530 feat(amm): wire the AMM app to the LEZ wallet module
Turns the dummy-data AMM UI into a real client of the on-chain LEZ wallet.
Adds a hand-written ui_qml C++ backend (src/AmmUi*) over the core
logos_execution_zone module: create/open a local wallet, create and list
public/private accounts, and a navbar Connect / Connected + account-selector
+ Disconnect flow. Onboarding is password-only (no path picking) with a
per-app wallet at ~/.lee/amm-wallet (override: AMM_WALLET_HOME_DIR);
standalone gets its own wallet, Basecamp shares accounts via adopt-on-start.

Requires Nix with flakes; macOS also needs `sandbox = false` (the default).
The logos_execution_zone input is pinned to a module rev whose LEZ (lssa)
already includes the macOS Metal-build fix, so no `--override-input` is
needed — plain `nix run .` works:

    cd apps/amm
    nix run .

- create_new now returns the new wallet's BIP39 mnemonic (not an int status);
  the app currently discards it, so the wallet can't yet be recovered. Surfacing
  it in onboarding (+ restore_storage) is a follow-up.
- The wallet password is currently a no-op upstream (storage.rs: "TODO: use
  password for storage encryption"); storage.json is plaintext. So Disconnect
  is a UI-level lock and reconnect does not (cannot yet) re-prompt for it.
- wallet-ffi requires explicit config/storage paths; a *_default() FFI would
  let the app drop its path handling.
- Bundled network config: connects to whatever WalletConfig::default() points
  at; real testnet endpoints still TBD.
2026-07-02 18:57:01 +02:00
Ricardo Guilherme Schmidt
a26debd592 build: add shared guest program build 2026-07-02 18:16:40 +02:00
r4bbit
0a120bd42c docs: add a testnet run book to show how to deploy and use the programs 2026-06-30 15:13:47 +02:00
r4bbit
0fa2b49880 chore: add helper examples to calculate program PDAs
These is needed because SPEL currently doesn't support PDA calculation
the way our programs do (we wrap our seeds in SHA256)
2026-06-30 15:13:47 +02:00
r4bbit
2308681dcf fix(amm): compute pool arithmetic in u256 to avoid u128 overflow
The AMM multiplied amounts in u128 — `token_a * token_b` for the initial
LP in `new_definition`, `reserve * amount` in swaps, and the mul/div steps
in add/remove liquidity. For realistic 18-decimal token amounts the
intermediate product exceeds `u128::MAX` (~3.4e38): opening a pool with
100/200 tokens is `1e20 * 2e20 = 2e40`, which panicked and caused the
sequencer to skip the transaction.

Widen the intermediate arithmetic, not the stored types. Add
`mul_div_floor`, `mul_div_ceil`, and `isqrt_product` to `amm_core` (using
`alloy_primitives::U256`, as `spot_price_q64_64` already does): they
compute the product/division/sqrt in U256 and downcast the result back to
u128. Route `new_definition`, `swap_exact_input`/`swap_exact_output`,
`add_liquidity`, and `remove_liquidity` through them. `swap_exact_output`
keeps its ceil rounding (required input rounded up, in the pool's favour)
via `mul_div_ceil`.

Balances, reserves, and LP supply stay u128, so account data formats,
IDLs, and the token/ata/stablecoin programs are unchanged. This lifts the
usable amount range to the full u128.
2026-06-30 15:13:47 +02:00
r4bbit
091ea5a5d0 chore: update to LEZ v0.2.0-rc6
Bump the LEZ dependency from the `lez-core-v0.2.0` tag to `v0.2.0-rc6` across
the workspace and all guest manifests (still resolving via the renamed
`lee_core`/`lee` packages), and regenerate the lockfiles to match.

rc6 moved the clock program out of `nssa` into a separate system-programs crate
(gated behind the guest-building `artifacts` feature), so adapt the tests:

- Import `ClockAccountData` and `CLOCK_01_PROGRAM_ACCOUNT_ID` from `clock_core`
  instead of `nssa`, and build clock data via `ClockAccountData::to_bytes()`
  rather than hand-encoding the Borsh layout.
- `V03State::new()` no longer auto-creates the clock account, so AMM tests seed
  the canonical 1-block clock explicitly before ops that read it.
- `advance_clock` now writes the clock account directly via
  `force_insert_account` (the clock can no longer be ticked with a real
  transaction), matching how upstream rc6 state-machine tests seed accounts.
- Add the `clock_core` dependency to integration_tests/benchmark.
2026-06-30 15:13:47 +02:00
r4bbit
c42d4b6c07 refactor: migrate programs to LEZ lez-core-v0.2.0
Bump the LEZ dependency from the `v0.2.0-rc3` tags to the released
`lez-core-v0.2.0` tag across the workspace and all guest manifests. The crate
was renamed upstream, so `nssa_core`/`nssa` now resolve via the `lee_core`/`lee`
packages, and spel-framework points at the `refactor/lez-v020-compat` fork
branch for compatibility.

Adapt the integration tests to the new API surface:

- `NssaError` is now `LeeError` (error variants unchanged).
- Account inputs move from numeric mask vectors (`vec![2, 0, 0]`) to typed
  `InputAccountIdentity` values (e.g. `PrivateUnauthorized { epk, view_tag,
  npk, ssk, identifier }`).
- `ViewingPublicKey::from_scalar` → `from_seed(d, z)`; `AccountId::from(&npk)`
  → `AccountId::for_regular_private_account(&npk, 0)`; ephemeral-key/shared-
  secret setup → `SharedSecretKey::encapsulate_deterministic(...)` with the
  circuit filling the EPK.

Regenerate all guest Cargo.lock files and the workspace lockfile to match.
2026-06-30 15:13:47 +02:00
r4bbit
c9fbb626ea refactor(twap_oracle): match instruction function order to Instruction enum
`idl-gen` emits IDL instructions in source order, and `spel` uses each
instruction's IDL position as its serde variant index. When the
`#[instruction]` function order diverges from the `twap_oracle_core::Instruction`
enum order, spel addresses the wrong instruction.

Move `update_current_tick` ahead of the TWAP-computation instruction so the
function order in twap_oracle.rs lines up with the enum variant order, and
regenerate artifacts/twap_oracle-idl.json to match.

No behavioral change — the instruction bodies are unchanged, only reordered.
2026-06-30 15:13:47 +02:00
r4bbit
c8f061e4a8 fix(amm): require signer on user token holdings in swap and add-liquidity
The swap and add-liquidity instructions debited user-owned token holdings
without requiring those accounts to be signers. Mark them `signer` so a
transaction can't move a user's tokens without their authorization:

- add liquidity: `user_holding_lp` is now `#[account(mut, signer)]`
- swap (both directions): `user_holding_a` and `user_holding_b` are now
  `#[account(mut, signer)]`

Regenerate artifacts/amm-idl.json to reflect the new signer metadata.

Update integration tests accordingly: swaps now sign and supply nonces for
both user holdings (incrementing both nonces), and
`amm_new_definition_precreated_zero_balance_user_lp` becomes
`amm_new_definition_precreated_user_lp_unsigned_fails`, asserting an unsigned
pre-existing LP holding is rejected and the transaction reverts.
2026-06-30 15:13:47 +02:00
r4bbit
065a4e4937 chore: add integration test task to make file 2026-06-29 08:54:32 +02:00
Ricardo Guilherme Schmidt
497e13db85 build(guest): strip release symbols
Configure guest release profiles with debug = 0 and strip = "symbols" so deployed RISC Zero artifacts use stripped binaries.

Document that release-profile ImageIDs are canonical for testnet and mainnet deployments and dependent values must be refreshed.
2026-06-28 20:57:02 -03:00
Ricardo Guilherme Schmidt
255f87f38f fix(idl): align LEZ account metadata
Mark unconditional signer, init, and mutable accounts in guest entrypoints.

Regenerate IDL artifacts for token, ATA, stablecoin, TWAP oracle, and AMM.
2026-06-28 15:24:29 +02:00
r4bbit
e4447617f6 chore: pin ruint dep 2026-06-26 14:33:47 -03:00
Andrea Franz
e93db419c4 chore(stablecoin): use alloy primitives 2026-06-25 15:52:03 +02:00
Andrea Franz
3eab96a217 feat(stablecoin): add fixed-point math utilities
closes #157
2026-06-25 15:52:03 +02:00
r4bbit
bd8064a587 test(twap): cover CreateOraclePriceAccount and PublishPrice end-to-end
Add the first zkVM-path coverage of the oracle's price-account output, which
previously existed only as native unit tests:

- amm_twap_create_oracle_price_account: creates the OraclePriceAccount via a
  signing price source and checks the initialized state (price, timestamp,
  source/base/quote, confidence).
- amm_twap_publish_price_publishes_window_average: full pipeline — real swaps +
  RecordTick build the observations, then PublishPrice consumes them. With the
  clock at the newest observation (empty tail) the published price is the
  stored-window average tick converted to a Q64.64 price, stamped with now.
- amm_twap_publish_price_extrapolates_tail_to_now: advances the clock past the
  last record with no new observation; asserts the published timestamp is now
  (a fresh price, not a stale window) and the value reflects the extrapolated
  tail.
- amm_twap_publish_price_noop_with_fewer_than_two_observations: PublishPrice
  leaves the price account untouched when there is nothing to average.
2026-06-24 22:29:02 +02:00
r4bbit
53e563f8e3 feat(amm): create TWAP oracle price account on behalf of the pool
Add a CreateOraclePriceAccount instruction mirroring CreatePriceObservations:
anyone can register the consumer-facing OraclePriceAccount for a pool feed, and
the AMM authorizes the pool as the price source via its pool PDA seed through a
single chained call to the configured TWAP oracle program.
2026-06-24 21:52:59 +02:00
r4bbit
3774d5112c docs(stablecoin): move design document into docs folder 2026-06-23 16:55:36 +02:00
r4bbit
9d5eea2b41 test(twap): cover RecordTick end-to-end and add zkVM cycle benchmark
Add the first end-to-end coverage of the oracle's RecordTick path, which
previously existed only as native unit tests:

- amm_twap_observations_accumulate_across_swaps_and_yield_time_weighted_average:
  drives swaps + RecordTick across simulated time, then checks the cumulative
  accumulator and the consulted time-weighted average.
- amm_twap_record_tick_sampling_guard_skips_calls_below_min_interval: exercises
  the min-interval sampling guard through the real instruction path.

Running RecordTick through the zkVM surfaced that committing the oracle-owned
~100 KiB observations account costs ~50.9M cycles — over the 2^25 (~33.5M)
public-execution limit — so the instruction aborted on chain. Reduce
OBSERVATIONS_CAPACITY 6396 -> 2048 (~16.8M cycles, ~half the limit); window
coverage is unchanged, only sampling resolution.

Add programs/benchmark, a standalone crate (excluded from the workspace so CI
and the Makefile skip it) that runs the guest ELF through the RISC Zero
executor and reports the per-instruction cycle split, reproducing the on-chain
pass/fail at the limit. Its cost-vs-capacity sweep still spans to 6396, guarding
against bumping capacity back into the over-budget range.
2026-06-23 16:30:18 +02:00