Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
bf2cca0
Add SVM / Solana integration initial design doc
JakeHartnell May 17, 2026
08acd07
types(solana): SolanaAddress, SolanaCommitment, and SOLANA namespace
JakeHartnell May 17, 2026
ad2b2d5
types(solana): SolanaChainConfig and AnyChainConfig::Solana variant
JakeHartnell May 17, 2026
394c328
wit+types(solana): Trigger::SolanaProgramEvent and engine bindings
JakeHartnell May 17, 2026
a5d5e97
build(examples): rebuild WASM components for new trigger variant
JakeHartnell May 17, 2026
f53fcdb
deps: add solana-client for trigger stream + RPC probes
JakeHartnell May 17, 2026
4eb9b88
feat(solana): lookup table keying for SolanaProgramEvent
JakeHartnell May 17, 2026
aaa91c2
feat(solana): trigger stream + dispatcher wiring
JakeHartnell May 17, 2026
750c7da
feat(solana): CLI service-JSON validator + account-info
JakeHartnell May 17, 2026
966dc93
feat(solana): health probe via getHealth + getSlot
JakeHartnell May 17, 2026
f5cf154
style: cargo fmt + retire remaining (slice 2) markers
JakeHartnell May 17, 2026
ee38ff2
examples(solana): Anchor fixture program for v1 trigger demo
JakeHartnell May 17, 2026
789e407
examples(solana): operator component for event relay
JakeHartnell May 18, 2026
72f42fc
just: start-solana-validator + deploy-solana-fixture targets
JakeHartnell May 18, 2026
335714a
feat(layer-tests): e2e runner support for Solana program-event triggers
JakeHartnell May 18, 2026
717945d
feat(layer-tests): end-to-end Solana → EVM submission demo test
JakeHartnell May 18, 2026
1d35785
test(solana): replay protection regression test for v1 acceptance #4
JakeHartnell May 18, 2026
cedef2b
docs(examples): worked demo READMEs for the v1 SVM trigger flow
JakeHartnell May 18, 2026
a03c914
style: cargo fmt across slice 3 additions
JakeHartnell May 18, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2,880 changes: 2,691 additions & 189 deletions Cargo.lock

Large diffs are not rendered by default.

29 changes: 29 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,19 @@ members = [
"examples/components/_types",
"examples/components/kv-store",
"examples/components/simple-aggregator",
"examples/components/solana-event-relay",
"examples/components/timer-aggregator",
"examples/contracts/cosmwasm/trigger/api",
"examples/contracts/cosmwasm/trigger/simple",
"examples/contracts/cosmwasm/mock/api",
"examples/contracts/cosmwasm/mock/service-handler",
"app/src-tauri",
]
exclude = [
# Solana fixture program (Anchor) — has its own workspace; built via
# `anchor build`, not `cargo build --workspace`. See README.
"examples/contracts/solana/event-emitter",
]
resolver = "2"

[profile.dev]
Expand Down Expand Up @@ -150,6 +156,28 @@ alloy-eip2930 = "=0.2.1"
alloy-eip7702 = "=0.6.1"
alloy-json-rpc = "=1.6.3"

# Solana-specific dependencies (used by the trigger stream; v1 is
# trigger-only, so we intentionally avoid pulling in the full solana-sdk
# crate constellation - just the pubsub client, RPC client, and the
# Pubkey / commitment primitives we need).
solana-client = "3.1.14"
solana-pubkey = "3"
solana-commitment-config = "3"
solana-rpc-client-api = "3.1.14"
solana-transaction-status-client-types = "3.1.14"

# Slice 3: transaction-construction primitives. Only pulled into the
# layer-tests + integration-test crates so the runtime trigger surface
# stays slim.
solana-keypair = "3.1.2"
solana-signer = "3"
solana-signature = "3"
solana-message = "3"
solana-transaction = "3"
solana-instruction = "3"
solana-hash = "4"
solana-sha256-hasher = "3"

layer-climb = "0.9.0"
layer-climb-address = { version = "0.9.0", features = [
"cw-storage",
Expand Down Expand Up @@ -205,6 +233,7 @@ bip39 = { version = "2.2.0", features = ["rand"] }
sha2 = "0.10.9"
const-hex = "1.16.0"
base64 = "0.22"
bs58 = "0.5.1"
ripemd = "0.1.3"
hypercore-protocol = { version = "0.6.1", default-features = false, features = [
"tokio",
Expand Down
20 changes: 10 additions & 10 deletions checksums.txt
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
5306b31c131cbbfc07a49f0ab43774c13ef5da1ff584c1af549bb7dff16d2223 ./examples/build/components/chain_trigger_lookup.wasm
4c828e793e771289e820ea4227712ea51eb5fe8ce050ac6ceb7c35a04a7e3a72 ./examples/build/components/cosmos_query.wasm
ae5956a15e43f9f0f1e33b457829fa72d6343a4dd20e6049d209b0849f941cb1 ./examples/build/components/echo_block_interval.wasm
1bc1fa1af778d535a00ce2174ae3fa24ddd2ef961abe859832959155efb08636 ./examples/build/components/echo_cron_interval.wasm
8e1ec1471fe0845a2d8d158f6610f6f6134d4f090569b7b1911fcc7258f8eb01 ./examples/build/components/echo_data.wasm
3ccd4f8b6817dcb2c28e688effea2597590228ff7060d180205240db0765a333 ./examples/build/components/kv_store.wasm
88f28f046e2e379032a93d6a98abb70b6fa7a037e23ddd4382a14dfe25c48d37 ./examples/build/components/permissions.wasm
f9e1c1144bbfbfe7139d9a0be6890452058c1a700ce96bb5b6fc8f50535109f1 ./examples/build/components/simple_aggregator.wasm
c2d31fe21f971a71983354c32127852af5cff011dd4686abf98c956d3361b682 ./examples/build/components/square.wasm
1fc8363742b207eb7ddb32caa629e1e8ac7a953e755afb85c67fa0d729923f66 ./examples/build/components/timer_aggregator.wasm
73ecaeaf63e4c8c8f74bbc704ed43a76d8177cedaed315f571c3e19f67594efa ./examples/build/components/chain_trigger_lookup.wasm
cedee652e4f929b6447819a555f2a52164329650dcac6ab2e93ac25c3e80e2d1 ./examples/build/components/cosmos_query.wasm
a4c1f583fa06169adab882bb548f8bf4b733915f508d1fe9a32d048cb7022093 ./examples/build/components/echo_block_interval.wasm
ffe6158696dd2bf42fefdddb4a5907be9d22ec088eb89bca70989f921f9499f2 ./examples/build/components/echo_cron_interval.wasm
130f70f789a180bcab9292ab884af05793301c6cb859ddaad99bef694dfa5413 ./examples/build/components/echo_data.wasm
bf2634ca72b738facfe503e9f5553f381a15883b4c232fbe7a9c379efe71cedd ./examples/build/components/kv_store.wasm
f89ff8172f04af1e88cf5b7be7b7fbe6cebe453ca999917cc5bb7c6c7de32a5f ./examples/build/components/permissions.wasm
c04c984c1b7e767dd2ff70e5a8122016d1b5f3edc216a72e765ed50fab5f6cf4 ./examples/build/components/simple_aggregator.wasm
dbd0d89e2738c31daf7880caff76d39dc7254b42ff37792e55459f71193b686b ./examples/build/components/square.wasm
47e7e0d8ac19bcc2f6a3a08d454224c6b0badd681dc3a2859612ae969fd1d868 ./examples/build/components/timer_aggregator.wasm
144 changes: 144 additions & 0 deletions docs/design/svm-support.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
# Solana / SVM Support — Design Doc

**Status:** Draft, 2026-05-17
**Tracking issue:** [Lay3rLabs/WAVS#1149](https://github.com/Lay3rLabs/WAVS/issues/1149)
**Scope:** Boundaries and v1 scope. Not a spec — does not define PDAs, WIT diffs, or program layouts.

## Summary

WAVS supports EVM and Cosmos chains today. This doc proposes making Solana a first-class chain alongside them, in two phases:

- **v1 — trigger-only.** A Solana program event triggers a WAVS component; submission happens through the existing EVM or Cosmos paths. No on-chain Solana writes, no middleware program. Validates abstraction boundaries before committing to the harder half.
- **v2 — full bidi.** Solana submission path, middleware program (signature verification + operator registry + replay protection on-program), and a `wavs-anchor-template`. Open decisions on signature scheme, operator-set shape, and Anchor-vs-native are listed but not resolved in this doc.

The v1/v2 split is deliberate. Trigger-only is roughly one-third of the work and unlocks "Solana event → EVM handler" demos that prove the design without on-chain Solana write infrastructure.

## Context

Issue #1149 asks for SVM support that is **a peer of EVM and Cosmos, not an overload of either.** Eight implementation areas are called out: chain config, trigger source, submission target, service-manager program, operator signing/quorum, WIT/SDK exposure, CLI/service-JSON/UX, and a test harness against `solana-test-validator`. The acceptance criteria in the issue all sit in the trigger half except the service-manager program and the submission path.

The repo already contains an empty toehold: `contracts/svm-middleware/` is an Anchor 0.32.1 scaffold with a single empty `initialize` instruction (`programs/svm-middleware/src/lib.rs`, 16 lines) and a `PLAN.md` (7 lines) committing to "two programs, BLS + ECDSA, mirroring `cw-middleware` and `poa-middleware`." Nothing in `WAVS/packages/` references Solana, SVM, or Anchor today. The only `ed25519` mentions are "future:" comments in `packages/types/src/service.rs` (around lines 524 and 560).

## Abstraction Boundaries

These are the seams in WAVS core where chain type forks today. Every one needs a Solana arm in either v1 or v2.

| Concern | File | What forks today |
|---|---|---|
| Chain config | `packages/types/src/chain_config.rs:85` | `AnyChainConfig::{Cosmos, Evm}` |
| Service manager | `packages/types/src/service.rs:102` | `ServiceManager::{Evm, Cosmos}` |
| Trigger (declared) | `packages/types/src/service.rs` | `Trigger::{EvmContractEvent, CosmosContractEvent, BlockInterval, Cron, AtProtoEvent, HypercoreAppend, Manual}` |
| TriggerData (delivered) | `packages/types/src/service.rs:446` | mirror of the above, plus `Raw` |
| Signature kind | `packages/types/src/service.rs:558` | `SignatureAlgorithm::Secp256k1`, `SignaturePrefix::Eip191` only |
| Submission path | `packages/wavs/src/subsystems/submission.rs` | EVM-signer only (`MissingEvmSigner` at :161, `FailedToCreateEvmSigner` at :259) |
| Trigger streams | `packages/wavs/src/subsystems/trigger/streams/` | `evm_stream.rs`, `cosmos_stream.rs`, `atproto_jetstream.rs`, `hypercore_stream.rs`, `cron_stream.rs`, `local_command_stream.rs` |
| Engine ↔ component bindings | `packages/engine/src/bindings/types/` | mirrors the type enums |
| WIT | `wit-definitions/types/wit/{service,events,chain}.wit` | `service-manager`, `trigger`, `trigger-data`, address types |
| CLI service JSON validation | `packages/cli/src/service_json.rs` | per-variant matches |
| WASI helper utils | `packages/wasi-utils/src/evm/` | EVM-only `event.rs`, `provider.rs` |

Two things to note:

1. The trigger surface is well-trodden. Non-chain triggers (`Cron`, `AtProtoEvent`, `HypercoreAppend`) already live next to the EVM/Cosmos triggers, so adding a Solana trigger source does not require carving new abstraction — it follows an existing pattern.
2. The submission surface is **single-target and EVM-shaped.** `submission.rs` ties the signer type to EVM. v2 will need a Solana submitter alongside it.

## v1 — Trigger-Only Solana Support

### Goal

A WAVS service can declare a Solana chain and a Solana program-event trigger. When the configured Solana program emits the matching event at the configured commitment level, WAVS observes it exactly once, hands typed trigger data to the operator component via WIT bindings, and the operator submits back through an existing EVM or Cosmos service manager.

The "exactly once" property is enforced by a trigger identity composed of `(slot, signature, instruction_index, inner_instruction_index, log_index)` — sufficient to dedupe across reorgs at the configured commitment.

### Work Breakdown (inventory only — no code in this doc)

- **Types** — add `AnyChainConfig::Solana(SolanaChainConfig)`, `Trigger::SolanaProgramEvent { chain, program_id, event_discriminator | log_filter, commitment }`, and matching `TriggerData::SolanaProgramEvent { chain, slot, signature, instruction_index, inner_instruction_index, log_index, data }`. Extend `type_name` and `chain()` accessors.
- **Trigger stream** — new `solana_stream.rs` peer to `evm_stream.rs` and `cosmos_stream.rs`, using `solana_client::nonblocking::pubsub_client::PubsubClient` (`logs_subscribe`, optionally `program_subscribe` for state-change triggers in a later iteration). Commitment is a config knob, default `confirmed`.
- **Lookup** — extend `subsystems/trigger/lookup.rs` to key Solana triggers by `(chain, program_id, event_discriminator)`.
- **WIT** — add `solana-address`/`pubkey` type in `chain.wit`, a `solana-program-event` record in `events.wit`, and the corresponding `trigger`/`trigger-data` variants in `service.wit`. Regenerate component bindings.
- **CLI** — branch in `service_json.rs` validators for the new variant; surface in service-JSON examples.
- **Dev loop** — `solana-test-validator` wired into a `justfile` target alongside `start-anvil`. A worked example (Solana program → WAVS component → EVM submission) that doubles as the integration test.

### Acceptance Criteria

1. A service.json can declare `chain: solana`, configure RPC/commitment, and a `SolanaProgramEvent` trigger.
2. A test fixture program running on `solana-test-validator` emits an event; the WAVS node observes it.
3. The matching operator component receives typed `TriggerData::SolanaProgramEvent` via WIT bindings.
4. Replay protection: the same `(slot, signature, instruction_index, log_index)` does not re-fire the operator. Verified by deliberately reconnecting the subscription.
5. Submission to an EVM service manager from the operator works end-to-end.
6. `just` target spins up `solana-test-validator` + WAVS + the EVM submission target locally.

### Non-Goals for v1

- No Solana submission target. Operators must submit to EVM or Cosmos.
- No Solana middleware program work beyond what is already in `contracts/svm-middleware/`.
- No Solana operator key support. Operators continue to sign with their EVM key; the trigger doesn't care.
- No reorg recovery beyond what `confirmed`/`finalized` commitment provides. Reorgs above the chosen commitment are treated as user error.
- No `programSubscribe`/state-change triggers in v1 — log/event triggers only. State triggers are a v1.5 follow-up if demand exists.

## v2 Sketch — Submission + Middleware Program

This section is non-binding. It frames the work so v1 boundaries don't paint v2 into a corner.

### Submission

A `solana_submitter` peer to the EVM/Cosmos paths in `submission.rs`. Materially different mechanics from EVM:

- Recent-blockhash refresh and expiry retry loop
- Compute budget + priority fee instructions
- Optional durable nonce account for high-latency submissions
- Idempotency must be enforced **on the program** (replay PDA) — Solana has no equivalent to EVM nonce-as-ordering
- New credential env var (`WAVS_AGGREGATOR_SOLANA_CREDENTIAL`)
- Fee-payer key separate from signing key, by convention

### Middleware Program

Today's `contracts/svm-middleware/` is a placeholder. The v2 program needs, at minimum: service-config PDA, operator/quorum registry PDA(s), submission replay PDA, a `submit` instruction that verifies aggregated signatures against the registry, and an upgrade-authority policy.

Mirror reference: `contracts/cw-middleware/` and its Mock / ECDSA / BLS triad. The cleanest path likely follows the same progression: ship a POA variant first (operator addresses set by an admin key), then add signature-verifying variants once the operator-set shape is settled.

### Operator Signing — Three Options, One Recommendation

| Option | Pros | Cons |
|---|---|---|
| **Mirror EVM keys (ECDSA)** | Shared operator set across all chains. Works via `secp256k1_recover` syscall. | Operators sign for Solana with a non-native key — awkward UX. |
| **Native Ed25519** | Idiomatic on Solana, cheap on-chain verify via the Ed25519 sig-verify program. | Forks operator identity. Operators have to manage a second key. |
| **BLS aggregate** | Constant-size aggregate verification. | On Solana, BLS verification is hard — needs precompile or a custom verifier. Likely v3 territory. |

**Recommended default:** start with POA (admin-managed operator set, no on-program signature verification — operator membership is the only check). Add ECDSA verification next, mirroring `poa-middleware → wavs-middleware → cw-middleware` progression. Defer BLS until there is a concrete need.

This recommendation should be re-evaluated when v2 kicks off — it's the cheapest path to a working end-to-end demo but it is not the right answer for production multi-chain operator sets, which want either ECDSA-shared or BLS-aggregate.

### Anchor vs. Native

`contracts/svm-middleware/` is Anchor 0.32.1 today. Switching to native means rewriting the toehold. Recommendation: **stay on Anchor for v2 unless we hit a concrete blocker** — the existing scaffold, audit familiarity, and `wavs-anchor-template` future make Anchor the lower-risk choice.

## Open Decisions

Listed with recommended defaults so v1 isn't blocked. Each decision is revisitable before v2 kickoff.

| # | Decision | Recommended default | Revisit before |
|---|---|---|---|
| 1 | v1 scope: trigger-only vs. full bidi | **Trigger-only** | — (this doc decides) |
| 2 | Trigger commitment level | `confirmed` for triggers, `finalized` for v2 submission | v1 implementation |
| 3 | Anchor vs. native program | Anchor | v2 kickoff |
| 4 | Signature scheme | POA first, then ECDSA-mirrored, then BLS | v2 kickoff |
| 5 | Operator registry shape | Mirror cw-middleware progression (Mock/ECDSA/BLS) | v2 kickoff |
| 6 | Driving example | "Solana event → EVM handler" demo | v1 kickoff |
| 7 | State-change triggers (`programSubscribe`) | Deferred to v1.5 | v1.5 if demand exists |

## Non-Goals for This Doc

- Does not specify WIT diffs. v1 implementation owns the exact records and field names.
- Does not specify PDA layouts or program instructions. v2 design memo owns this.
- Does not commit to a final signature scheme. v2 design memo owns this.
- Does not specify operator key onboarding flow. Follows from the signature-scheme decision.
- Does not pick a Solana cluster strategy for hosted demos. Local `solana-test-validator` is sufficient for v1.

## References

- Issue: https://github.com/Lay3rLabs/WAVS/issues/1149
- Existing scaffold: `contracts/svm-middleware/programs/svm-middleware/src/lib.rs`, `contracts/svm-middleware/PLAN.md`
- Mirror references: `contracts/poa-middleware/` (audited, simplest), `contracts/cw-middleware/` (Mock/ECDSA/BLS triad)
- Trigger-stream patterns to copy from: `packages/wavs/src/subsystems/trigger/streams/evm_stream.rs`, `cosmos_stream.rs`, `atproto_jetstream.rs`
- Boundary files: `packages/types/src/service.rs`, `packages/types/src/chain_config.rs`, `packages/wavs/src/subsystems/submission.rs`, `wit-definitions/types/wit/{service,events,chain}.wit`
Binary file modified examples/build/components/chain_trigger_lookup.wasm
Binary file not shown.
Binary file modified examples/build/components/cosmos_query.wasm
Binary file not shown.
Binary file modified examples/build/components/echo_block_interval.wasm
Binary file not shown.
Binary file modified examples/build/components/echo_cron_interval.wasm
Binary file not shown.
Binary file modified examples/build/components/echo_data.wasm
Binary file not shown.
Binary file modified examples/build/components/kv_store.wasm
Binary file not shown.
Binary file modified examples/build/components/permissions.wasm
Binary file not shown.
Binary file modified examples/build/components/simple_aggregator.wasm
Binary file not shown.
Binary file modified examples/build/components/square.wasm
Binary file not shown.
Binary file modified examples/build/components/timer_aggregator.wasm
Binary file not shown.
16 changes: 16 additions & 0 deletions examples/components/solana-event-relay/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
[package]
name = "solana-event-relay"
edition.workspace = true
version.workspace = true
authors.workspace = true
rust-version.workspace = true
repository.workspace = true

[dependencies]
example-helpers = { workspace = true }

[lib]
crate-type = ["rlib", "cdylib"]

[package.metadata.component]
package = "wavs-tests:solana-event-relay"
18 changes: 18 additions & 0 deletions examples/components/solana-event-relay/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# `solana-event-relay`

WAVS operator component for the v1 SVM trigger demo. Receives a
`TriggerData::SolanaProgramEvent` from the Solana trigger stream, strips
the Anchor `MessageEmitted` framing
(`sha256("event:MessageEmitted")[..8]` discriminator + borsh `Vec<u8>`
length prefix), and emits a `DataWithId`-encoded `WasmResponse` for the
existing `SimpleSubmit` EVM service handler.

`triggerId` is the Solana slot — keeps the EVM `DataWithId` monotonic
per-slot and disambiguates distinct events without requiring the relay
to allocate its own ids. The full `(slot, signature, instruction_index,
inner_instruction_index, log_index)` replay-protection tuple is
enforced upstream in the dispatcher (see
`packages/wavs/src/subsystems/trigger.rs::SolanaReplayCache`).

See `examples/contracts/solana/event-emitter/README.md` for the
end-to-end demo flow.
Loading
Loading