From b97a8395677fb98bd2267028494495e655f949a5 Mon Sep 17 00:00:00 2001 From: erkostandyan Date: Wed, 4 Mar 2026 23:57:29 +0400 Subject: [PATCH 1/5] solana initial commit. --- .claude/rules/key-files.md | 15 + CLAUDE.md | 97 +- csharp/TrainSolver.slnx | 1 + csharp/src/AdminPanel/wwwroot/index.html | 2 +- csharp/src/AppHost/AppHost.cs | 7 + csharp/src/AppHost/AppHost.csproj | 1 + .../Activities/ISolanaBlockchainActivities.cs | 64 ++ .../Activities/ISolanaWorkflowActivities.cs | 20 + .../Activities/SolanaBlockchainActivities.cs | 857 ++++++++++++++++ .../Activities/SolanaWorkflowActivities.cs | 80 ++ .../Activities/WorkflowActivities.cs | 111 +++ .../TrainSolverBuilderExtensions.cs | 38 + .../Helpers/SolanaAnchorHelper.cs | 247 +++++ .../Workflow.Solana/Helpers/SolanaHelper.cs | 111 +++ .../Models/SolanaHTLCModels.cs | 160 +++ .../Workflow.Solana/Models/SolanaModels.cs | 226 +++++ csharp/src/Workflow.Solana/Program.cs | 21 + .../Properties/launchSettings.json | 11 + .../SolanaListenerCapabilities.cs | 124 +++ .../Workflow.Solana/Workflow.Solana.csproj | 28 + .../Workflows/SolanaAddressGenerator.cs | 27 + .../Workflows/SolanaEventListenerBase.cs | 239 +++++ .../Workflows/SolanaNetworkRuntime.cs | 726 ++++++++++++++ .../Workflows/SolanaRPCEventListener.cs | 229 +++++ .../Workflows/SolanaTransactionProcessor.cs | 935 ++++++++++++++++++ csharp/src/Workflow.Solana/appsettings.json | 12 + .../src/treasury/solana/solana.service.ts | 10 +- 27 files changed, 4393 insertions(+), 6 deletions(-) create mode 100644 csharp/src/Workflow.Solana/Activities/ISolanaBlockchainActivities.cs create mode 100644 csharp/src/Workflow.Solana/Activities/ISolanaWorkflowActivities.cs create mode 100644 csharp/src/Workflow.Solana/Activities/SolanaBlockchainActivities.cs create mode 100644 csharp/src/Workflow.Solana/Activities/SolanaWorkflowActivities.cs create mode 100644 csharp/src/Workflow.Solana/Activities/WorkflowActivities.cs create mode 100644 csharp/src/Workflow.Solana/Extensions/TrainSolverBuilderExtensions.cs create mode 100644 csharp/src/Workflow.Solana/Helpers/SolanaAnchorHelper.cs create mode 100644 csharp/src/Workflow.Solana/Helpers/SolanaHelper.cs create mode 100644 csharp/src/Workflow.Solana/Models/SolanaHTLCModels.cs create mode 100644 csharp/src/Workflow.Solana/Models/SolanaModels.cs create mode 100644 csharp/src/Workflow.Solana/Program.cs create mode 100644 csharp/src/Workflow.Solana/Properties/launchSettings.json create mode 100644 csharp/src/Workflow.Solana/SolanaListenerCapabilities.cs create mode 100644 csharp/src/Workflow.Solana/Workflow.Solana.csproj create mode 100644 csharp/src/Workflow.Solana/Workflows/SolanaAddressGenerator.cs create mode 100644 csharp/src/Workflow.Solana/Workflows/SolanaEventListenerBase.cs create mode 100644 csharp/src/Workflow.Solana/Workflows/SolanaNetworkRuntime.cs create mode 100644 csharp/src/Workflow.Solana/Workflows/SolanaRPCEventListener.cs create mode 100644 csharp/src/Workflow.Solana/Workflows/SolanaTransactionProcessor.cs create mode 100644 csharp/src/Workflow.Solana/appsettings.json diff --git a/.claude/rules/key-files.md b/.claude/rules/key-files.md index d096aa06..8560d7cd 100644 --- a/.claude/rules/key-files.md +++ b/.claude/rules/key-files.md @@ -31,6 +31,21 @@ | IAddressGenerator | `src/Workflow.Abstractions/Workflows/IAddressGenerator.cs` | | AddressGenerator (EVM) | `src/Workflow.EVM/Workflows/AddressGenerator.cs` | | AddressGenerator (Starknet) | `js/src/Blockchain/Blockchain.Starknet/Workflows/StarknetAddressGenerator.ts` | +| AddressGenerator (Solana) | `src/Workflow.Solana/Workflows/SolanaAddressGenerator.cs` | +| Solana network runtime | `src/Workflow.Solana/Workflows/SolanaNetworkRuntime.cs` | +| Solana transaction processor | `src/Workflow.Solana/Workflows/SolanaTransactionProcessor.cs` | +| Solana blockchain activities interface | `src/Workflow.Solana/Activities/ISolanaBlockchainActivities.cs` | +| Solana blockchain activities impl | `src/Workflow.Solana/Activities/SolanaBlockchainActivities.cs` | +| Solana workflow activities interface | `src/Workflow.Solana/Activities/ISolanaWorkflowActivities.cs` | +| Solana workflow activities impl | `src/Workflow.Solana/Activities/SolanaWorkflowActivities.cs` | +| Solana RPC event listener | `src/Workflow.Solana/Workflows/SolanaRPCEventListener.cs` | +| Solana event listener base | `src/Workflow.Solana/Workflows/SolanaEventListenerBase.cs` | +| Solana listener capabilities | `src/Workflow.Solana/SolanaListenerCapabilities.cs` | +| Solana anchor helper | `src/Workflow.Solana/Helpers/SolanaAnchorHelper.cs` | +| Solana HTLC models | `src/Workflow.Solana/Models/SolanaHTLCModels.cs` | +| Solana models | `src/Workflow.Solana/Models/SolanaModels.cs` | +| Solana helper | `src/Workflow.Solana/Helpers/SolanaHelper.cs` | +| Solana DI extensions | `src/Workflow.Solana/Extensions/TrainSolverBuilderExtensions.cs` | | WalletGenerator | `src/Workflow.Swap/Workflows/WalletGenerator.cs` | | INetworkRuntime | `src/Workflow.Abstractions/Workflows/INetworkRuntime.cs` | | NetworkRuntime | `src/Workflow.EVM/Workflows/NetworkRuntime.cs` | diff --git a/CLAUDE.md b/CLAUDE.md index d180050c..11953677 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -9,7 +9,7 @@ csharp/ # .NET 9 backend (Aspire, Temporal, APIs, workflows) ├── src/ # Source projects ├── tests/ # xUnit tests └── tools/ # DataSeeder, ProtoGenerator -js/ # TypeScript workers (Aztec, Starknet network support) +js/ # TypeScript workers (Aztec, Starknet, Solana network support) treasury/ # Vault-backed key management service (JS) protos/blockchain.proto # Language-agnostic network worker contract schema ``` @@ -87,7 +87,7 @@ dotnet test tests/Workflow.Tests/Workflow.Tests.csproj ## JS Project (`js/`) -TypeScript Temporal workers for non-EVM networks (Aztec, Starknet). +TypeScript Temporal workers for non-EVM networks (Aztec, Starknet, Solana). ```bash # Install (from js/) @@ -113,6 +113,99 @@ Bare minimum integration — transfers only, no HTLC contract support yet. - **Same Dockerfile as Aztec**: Differentiated by `TrainSolver__NetworkType` env var - **AppHost**: `workflow-runner-starknet` container with `TrainSolver__NetworkSlug=starknet-sepolia` +### Solana (`js/src/Blockchain/Blockchain.Solana/`) — SUPERSEDED by C# Workflow.Solana + +**Note:** The JS Solana worker has been replaced by the C# `Workflow.Solana` project (see below). The JS code remains in the repo for reference but is no longer used. AppHost now runs a C# project instead of a JS container. + +### Workflow.Solana (`csharp/src/Workflow.Solana/`) + +C# Solana integration using SolNet SDK with `recentBlockhash` + ComputeBudget priority fees. HTLC event listener polls Anchor program transactions (no Anchor events — instruction-based detection). + +- **NetworkType**: `solana`, task queue: `solana` +- **SDK**: SolNet v6.1.0 (`Solnet.Rpc`, `Solnet.Programs`, `Solnet.Wallet`) +- **DI registration**: `.WithSolanaWorkflows()` in `TrainSolverBuilderExtensions` +- **AppHost**: `workflow-runner-solana` C# project with `TrainSolver__NetworkType=solana` + +**Transaction Pipeline (recentBlockhash + priority fees):** +Uses `getLatestBlockhash` for transaction lifetime instead of durable nonce accounts. Each transaction includes ComputeBudget instructions for priority fee bidding. Expired transactions are retried with escalated fees. +- Pipeline: Queued → GetLatestBlockhash → GetPriorityFee → Build(blockhash, ComputeBudget) → Sign → Pending → Publish → InFlight → Confirmed/Expired +- Priority fee: 75th percentile of `getRecentPrioritizationFees` (floor: 1000 microLamports) +- Expiry detection: `currentBlockHeight > lastValidBlockHeight` OR `elapsed > stuckTimeoutSeconds` +- Retry on expiry: up to 3 retries with 1.5x fee escalation (1x → 1.5x → 2.25x → fail) +- Redis used only for TP state persistence (`solana:tp-state:{slug}:{wallet}`, 7-day TTL) + +**ComputeBudget helpers** (`SolanaHelper`): +- `SetComputeUnitLimit(uint units)` — discriminant `0x02` + 4-byte LE u32 +- `SetComputeUnitPrice(ulong microLamports)` — discriminant `0x03` + 8-byte LE u64 +- Both return `TransactionInstruction` with `ComputeBudget111111111111111111111111111111` program ID + +**Transaction Processor** (`SolanaTransactionProcessor`): +- 3-queue pipeline: Queued → Pending (blockhash + signed) → InFlight (published, polling) +- Supports all HTLC signals: SubmitLock, SubmitRedeem, SubmitRefund, SubmitUserRefund, SubmitTransfer +- HTLC tx building: TP reads HTLC PDA account on-chain (for redeem/refund account metas), builds Anchor instructions via `SolanaAnchorHelper`, signs via treasury +- `SolanaQueuedRequest` carries typed request (`LockRequest`, `RedeemRequest`, `RefundRequest`, `TransferRequest`) — routed by `Type` in `BuildUnsignedTransactionForQueuedAsync` +- **No gas bumping** — Solana has no mempool replacement. Uses priority fee escalation on retry instead. +- **No cancellation tx** — no self-transfer pattern +- **No orphaned monitoring** — expired transactions are retried or failed +- `maxInFlightTransactions = 10` (configurable via DB) +- Continue-as-new at 50 iterations +- State persisted to Redis (`solana:tp-state:{slug}:{wallet}`, 7-day TTL) + +**Network Runtime** (`SolanaNetworkRuntime`): +- Bootstrap: fetch network + wallets, start TransactionProcessors + event listeners +- Listener support: bootstraps `rpc-log-event-listener` from `EventListenerConfigs`, composes subscriptions via `SolanaListenerCapabilities` +- No GasStation (Solana uses priority fees, not EVM gas estimation) +- Health check every ~1 min: re-fetch wallets, reconcile TPs/listeners, update subscriptions if filter addresses change +- Update handlers: GetBalance, GetBatchBalances, ValidateAddress, ValidateNode, and all 6 HTLC Build* methods (BuildSolverLock, BuildUserLock, BuildSolverRedeem, BuildUserRedeem, BuildSolverRefund, BuildUserRefund) via local activities +- Build* methods read HTLC PDA account on-chain for redeem/refund (to get sender/src_receiver for account metas) +- Continue-as-new at 200 operations + +**Event Listener** (`SolanaRPCEventListener`): +- Polls `getSignaturesForAddress(HTLCProgramId)` for HTLC program transactions +- Parses Anchor instruction discriminators (`sha256("global:")[..8]`) to identify function calls +- Reads HTLC PDA account data (Borsh-decoded) for hashlock, amounts, addresses +- Event mapping: `lock()`→SolverTokenLocked, `add_lock()/add_lock_sig()`→UserTokenLocked, `redeem()/refund()`→compare HTLC.sender vs solver wallets +- Skips `commit()` (no hashlock yet) and `lock_reward()` (supplementary) +- Checkpoint: slot + signature stored in Redis (`solana:checkpoint:{slug}:{type}`) +- Continue-as-new at 50 iterations +- Self-contained `SolanaEventListenerBase` (no dependency on Workflow.EVM) + +**HTLC Contract** (`7ZT5gs8CG7BAv34bLYSke31DJeg5RRUa4G7p9GNcbPE`): +- Anchor program `native_htlc` on Solana devnet +- 7 functions: commit, lock, lock_reward, add_lock, add_lock_sig, redeem, refund +- No Anchor events (`emit!()`) — detection via instruction parsing + PDA account reads +- PDA seeded by 32-byte `Id` field +- **lock() account metas**: sender(signer,mut), htlc(init,mut,PDA[Id]), system_program, rent. Id = hashlock for solver locks. +- **redeem() account metas**: user_signing(signer,mut), sender(unchecked,mut), src_receiver(unchecked,mut), htlc(mut,PDA[Id]), system_program, rent +- **refund() account metas**: user_signing(signer,mut), htlc(mut,PDA[Id]), sender(unchecked,mut), system_program, rent +- **Borsh encoding**: `SolanaAnchorHelper` provides `WriteBorshU64`, `WriteBorshString`, `WriteBorshPubkey`, `WriteBorshBytes32` for instruction data serialization +- **Instruction builders**: `BuildLockInstructionData`, `BuildRedeemInstructionData`, `BuildRefundInstructionData` in `SolanaAnchorHelper` + +**Address Generator** (`SolanaAddressGenerator`): +- Single step: call treasury `/api/treasury/solana/generate` → return address +- No funding/deployment needed (Solana addresses are just keypairs) + +**Activities** (`SolanaBlockchainActivities`): +- `GetBalanceAsync`: Native SOL via `rpcClient.GetBalanceAsync`, SPL via `GetTokenAccountsByOwnerAsync` +- `GetLatestBlockhashAsync`: Returns `SolanaBlockhashInfo { Blockhash, LastValidBlockHeight }` +- `GetRecentPriorityFeeAsync`: Raw HTTP JSON-RPC `getRecentPrioritizationFees`, returns 75th percentile (floor: 1000 µL) +- `BuildHTLCLockTransactionAsync`: Builds Anchor `lock()` instruction — PDA from hashlock, Borsh-encoded args (Id, hashlock, timelock, amount, dst_chain, dst_address, dst_asset, src_asset, src_receiver) +- `BuildHTLCRedeemTransactionAsync`: Builds Anchor `redeem()` instruction — account metas require sender + src_receiver from PDA account data +- `BuildHTLCRefundTransactionAsync`: Builds Anchor `refund()` instruction — account metas require sender from PDA account data +- `GetHTLCAccountDataAsync`: Reads HTLC PDA via `getAccountInfo` → Borsh-decodes → returns `SolanaHTLCAccount` (sender, src_receiver, hashlock, etc.) +- `GetBlockHeightAsync`: Current block height for expiry checks +- `BuildTransferTransactionAsync`: Uses `SetRecentBlockHash` + `SetComputeUnitLimit` + `SetComputeUnitPrice` instructions before `SystemProgram.Transfer`. Builds unsigned tx with zero-byte signature placeholders. +- `SignTransactionAsync`: POST `/api/treasury/solana/sign` with `{ address, unsignedTxn }` → returns `{ signedTxn }` +- `PublishTransactionAsync`: `rpcClient.SendTransactionAsync` with error classification (Published, DuplicateTransaction, InsufficientFunds, BlockhashNotFound, Failed) +- `GetHTLCEventsAsync`: Polls `getSignaturesForAddress` → `getTransaction` → parse Anchor discriminators → read HTLC PDA accounts → returns categorized events + +**Unsigned Transaction Wire Format:** +`[compact-u16 sig_count][64-zero-bytes per sig][message bytes]` — compatible with treasury's `Transaction.from(buffer)` in `@solana/web3.js` v1. + +**Native token address**: `11111111111111111111111111111111` (System Program). Also accepts EVM-style null address `0x0000000000000000000000000000000000000000`. + +**SPL token transfers**: Not yet implemented (only native SOL). Will need ATA (Associated Token Account) creation when added. + ## Treasury (`treasury/`) Vault-backed key management and signing service. diff --git a/csharp/TrainSolver.slnx b/csharp/TrainSolver.slnx index 124778ef..174f1d5c 100644 --- a/csharp/TrainSolver.slnx +++ b/csharp/TrainSolver.slnx @@ -33,6 +33,7 @@ + diff --git a/csharp/src/AdminPanel/wwwroot/index.html b/csharp/src/AdminPanel/wwwroot/index.html index fa28554d..419dd8fe 100644 --- a/csharp/src/AdminPanel/wwwroot/index.html +++ b/csharp/src/AdminPanel/wwwroot/index.html @@ -28,7 +28,7 @@ Reload 🗙 - +