| name | gridtokenx-blockchain-core |
| description | Use this skill when working in the `gridtokenx-blockchain-core` Rust crate, including changes to BlockchainService, ChainBridgeProvider, gRPC Chain Bridge integration, NATS JetStream transaction submission, WalletService encryption, SPIFFE ServiceRole RBAC, priority fees, instruction builders, PDA derivation, token/ATA handling, transaction submission, or proto/chain_bridge.proto. Do not use for the on-chain Anchor programs themselves. |
GridTokenX Blockchain Core
This repository is a Rust library crate for shared GridTokenX blockchain access. It is the stable client surface used by backend services to interact with Solana through the Chain Bridge. Treat it as infrastructure: small API changes can affect multiple services.
Repository Map
src/lib.rs: public API and re-exports.
src/auth.rs: SPIFFE-based ServiceRole RBAC helpers.
src/config.rs: Solana program ID configuration.
src/wallet.rs: keypair loading and legacy wallet encryption helpers.
src/policy.rs: policy-related helpers.
src/instructions.rs: top-level instruction helpers.
src/rpc.rs: BlockchainService facade and Chain Bridge client exports.
src/rpc/: Chain Bridge providers, NATS schemas, transaction handling, metrics, token/account queries, priority fees, and instruction builders.
proto/chain_bridge.proto: gRPC Chain Bridge protocol.
build.rs: tonic client generation during Cargo builds.
examples/: example utilities only.
Standard Commands
Run the narrowest useful command first, then broaden before finishing risky changes:
cargo build
cargo test
cargo test --features mocks
cargo fmt --check
cargo clippy --all-targets --all-features
cargo build regenerates tonic code from proto/chain_bridge.proto.
Coding Conventions
- Use Rust 2021 idioms and standard
rustfmt formatting.
- Keep functions/modules
snake_case, types/traits PascalCase, and constants SCREAMING_SNAKE_CASE.
- Keep public exports centralized in
src/lib.rs or src/rpc.rs.
- Preserve the existing async style with
tokio.
- Use
anyhow::Result for fallible service flows unless a local typed error already exists.
- Use
thiserror where typed errors are already established.
Core Invariants
BlockchainService is the write path
Do not add new direct Solana writes outside BlockchainService and the Chain Bridge provider path. The crate exists so services do not talk to Solana RPC directly. WalletService has legacy direct RPC behavior for local/dev wallet flows; do not expand that footprint for new service features.
Writes use NATS; reads use gRPC
NatsChainBridgeProvider is hybrid:
submit_transaction and simulate_transaction go through NATS JetStream.
- Query/read methods delegate to the inner gRPC
RealChainBridgeProvider.
When adding a Chain Bridge method, decide whether it mutates on-chain state. Mutating submission flows belong on the NATS path; reads should remain gRPC. Preserve the fallback in BlockchainService::new: if NATS_URL is unset or the connection fails, the service falls back to gRPC-only.
Chain Bridge mTLS stays default
Chain Bridge uses mTLS by default. CHAIN_BRIDGE_INSECURE=true is only valid for local http:// endpoints. Do not relax this guard or make insecure mode work against production-like URLs.
The TLS verification name defaults to the host of CHAIN_BRIDGE_URL (so a correct-SAN server cert needs no extra config); CHAIN_BRIDGE_TLS_DOMAIN overrides it for setups where the dialed name differs from the cert (e.g. port-forwards).
NATS envelopes are signed with the mTLS client key
In secure mode BlockchainService::new builds the EnvelopeSigner from the same PEMs that authenticated the gRPC channel and fails startup if the key is not ECDSA P-256 — a silent fallback to unsigned would split the gRPC and NATS identities. The warn-and-unsigned fallback exists only in insecure/dev mode. The canonical-bytes functions in src/rpc/envelope_auth.rs destructure their message structs exhaustively on purpose: adding a field to nats_schema.rs must be a compile error there until the field is signed or explicitly excluded, otherwise it ships unauthenticated. Keep that property.
PolicyEngine allowlist derives from env
PolicyEngine::validate_transaction builds the per-role allowlist from SolanaProgramsConfig::from_env(). The base allowlist always contains the System and ComputeBudget programs — every write path prepends priority-fee instructions, so removing ComputeBudget rejects all writes in enforced mode. A malformed SOLANA_*_PROGRAM_ID is a hard validation error (named, not warn-and-skip); a set-but-empty var falls back to the default. The defaults in src/config.rs must stay in sync with trading-core/src/config/mod.rs and the deployed Anchor.toml ids — drift gets every submit rejected as "Unauthorized program ID".
Instruction discriminators are Anchor discriminators
Instruction builders use 8-byte Anchor discriminators: sha256("global:<ix_name>")[..8]. If an Anchor instruction name changes, the bytes in this crate must change too. A mismatch fails on chain with an instruction fallback error.
To derive a discriminator:
import hashlib
list(hashlib.sha256(b"global:register_user").digest()[:8])
PDA seeds must match Anchor exactly
PDA helpers must mirror the on-chain #[derive(Accounts)] seeds byte-for-byte. Before changing a PDA helper, check the corresponding Anchor program account constraints.
Important current seeds:
| Helper | Seeds |
|---|
get_market_pda | [b"market"] |
get_registry_pda | [b"registry"] |
get_user_account_pda | [b"user", wallet.as_ref()] |
get_registry_shard_pda | [b"registry_shard", &[shard_id]] |
get_mint_pda | [b"mint_2022"] |
get_token_info_pda | [b"token_info_2022"] |
get_poa_config_pubkey | [b"governance_config"] (seed migrated from poa_config, see anchor e1d89b3) |
get_erc_certificate_pubkey | [b"erc_certificate", certificate_id.as_bytes()] |
get_oracle_data_pda | [b"oracle_data"] |
For zone market derivation, preserve zone_id.to_le_bytes().
Token program IDs can differ intentionally
The crate uses both classic SPL Token and Token-2022 IDs depending on mint ownership and instruction requirements. Do not "normalize" these without checking which token program owns the mint/account on chain.
WalletService encryption is migration-sensitive
WalletService encryption is used by IAM for encrypted custodial keys. Do not change salt length, IV length, PBKDF2 iteration count, HMAC shape, or AES-GCM parameters without a migration plan for existing encrypted records.
Empty signers means bridge-signing
OnChainManager::build_and_send_transaction with an empty signer list expects Chain Bridge to sign server-side with the configured key. Keep both local-signing and bridge-signing paths working.
Generation mint batches are chunked
execute_generation_mint_batch splits inputs into chunks of GENERATION_MINT_CHUNK recipients (one transaction each, compute budget sized per chunk) because a Solana transaction is capped at 1232 bytes. Chunks submit independently; GenerationMintBatchOutcome::submitted_indices reports exactly which inputs landed so callers evict those and retry the rest. Do not regress to one unbounded transaction or to all-or-nothing eviction. build_mint_to_wallet_instruction takes rec_validator: Option<Pubkey> mirroring the program's Option<Signer> slot — None encodes as the program id (non-signer placeholder); never drop the slot, the account list shifts by one.
Common Change Patterns
Add a Chain Bridge gRPC method
- Update
proto/chain_bridge.proto.
- Run
cargo build to regenerate tonic client code.
- Add the method to
ChainBridgeProvider in src/rpc/transaction.rs.
- Implement it for
RealChainBridgeProvider.
- Implement it for
NatsChainBridgeProvider: delegate for reads, add a NATS schema/envelope for writes.
- Update the mock provider in
src/rpc/transaction.rs tests.
- Expose it through
TransactionHandler or BlockchainService only if downstream services need it.
Add an instruction builder
- Derive the 8-byte Anchor discriminator.
- Add the
InstructionBuilder method.
- Match Anchor account ordering exactly.
- Reuse PDA helpers or add helpers with exact seed parity.
- Test discriminator bytes, PDA derivation, and account ordering.
Change NATS transaction submission
Review src/rpc/nats_provider.rs and src/rpc/nats_schema.rs together. Preserve correlation IDs, reply subjects, timeout handling, and the TxSubmitMessage / TxResultMessage schema contract unless the Chain Bridge service changes at the same time.
Testing Guidance
- Place focused unit tests beside the code under
#[cfg(test)] mod tests.
- Use
#[tokio::test] for async behavior.
- When adding methods to
ChainBridgeProvider, update MockChainBridgeProvider in src/rpc/transaction.rs tests so the test suite compiles.
- For instruction builders, cover discriminators, PDA derivation, and account order.
- For proto/API changes, run at least
cargo build plus the relevant tests.
Release And PR Notes
Prefer Conventional Commits for new work:
feat(rpc): ...
fix(wallet): ...
test(policy): ...
PRs should include behavior summary, linked issue/ticket when available, proto/API change notes, and exact checks run.