Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
78 changes: 72 additions & 6 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,27 +1,93 @@
# ── Build artefacts ────────────────────────────────────────────────────────────
target/
**/target/
*.wasm
*.wasm.gz

# Test snapshots generated by soroban-sdk snapshot testing
# ── Soroban / Stellar test snapshots ──────────────────────────────────────────
# Generated by soroban-sdk snapshot testing; must not be committed.
**/test_snapshots/
**/.soroban/
**/testdata/snapshots/
snapshot_*.json
*.snapshot.json

# Environment secrets
# ── Cargo lock files (inner workspaces) ───────────────────────────────────────
# The root Cargo.lock is kept (recommended for binary crates / reproducible CI).
# Inner workspace Cargo.lock files for independent sub-crates are excluded.
api-server/Cargo.lock

# ── Environment / secrets ─────────────────────────────────────────────────────
.env
.env.local
.env.*.local
*.pem
*.key
*.p12
secrets.toml

# Editor/IDE
# ── Editor / IDE ──────────────────────────────────────────────────────────────
.vscode/
.idea/
*.swp
*.swo
*.orig
*.bak
.project
.classpath

# OS
# ── Operating system ──────────────────────────────────────────────────────────
.DS_Store
.DS_Store?
._*
Thumbs.db
ehthumbs.db
Desktop.ini

# ── Node.js (JS test tooling under src/) ──────────────────────────────────────
node_modules/
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
package-lock.json
yarn.lock
.pnp/
.pnp.js

# ── Jest / Vitest test artefacts ──────────────────────────────────────────────
coverage/
.nyc_output/
**/__snapshots__/
jest-results.json
test-results/
*.lcov

# Fuzz artifacts
# ── Fuzz artefacts ────────────────────────────────────────────────────────────
fuzz/artifacts/
fuzz/corpus/*/crashes/
fuzz/corpus/*/queue/
fuzz/corpus/*/hangs/

# ── Deployment / CI output ────────────────────────────────────────────────────
deploy_output/
.stellar/
testnet-addresses.json
deployment-*.json

# ── Log files ─────────────────────────────────────────────────────────────────
*.log
logs/

# ── Temporary / scratch files ─────────────────────────────────────────────────
*.tmp
*.temp
*.cache
.cache/
scratch/
tmp/

# Project issues file
# ── Project-internal notes (not for the repo) ─────────────────────────────────
vrickish.md
TODO.local.md
NOTES.md
50 changes: 42 additions & 8 deletions api-server/src/handlers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,34 @@ use axum::{
use once_cell::sync::Lazy;
use serde_json::Value;
use std::collections::HashSet;
use std::sync::Arc;
use tokio::time::{Duration, Instant};
use tracing::instrument;
use crate::cache;
use crate::deduplication::{create_store, DeduplicationStore};
use crate::schemas::*;
use crate::soroban_rpc::{
self, LiveSorobanRpcClient, MockSorobanRpcClient, SorobanRpcClient,
};
use crate::webhook;

// ── Shared Soroban RPC client ─────────────────────────────────────────────────
//
// In production (`SOROBAN_RPC_URL` and `IP_REGISTRY_CONTRACT` are set) this
// uses the live reqwest-backed client. In test environments where those env
// vars are absent the mock client is substituted automatically so unit tests
// never require a live network.
static SOROBAN_CLIENT: Lazy<Arc<dyn SorobanRpcClient>> = Lazy::new(|| {
let use_mock = std::env::var("IP_REGISTRY_CONTRACT")
.map(|v| v.is_empty())
.unwrap_or(true);
if use_mock {
Arc::new(MockSorobanRpcClient::default()) as Arc<dyn SorobanRpcClient>
} else {
Arc::new(LiveSorobanRpcClient::from_env()) as Arc<dyn SorobanRpcClient>
}
});

// #523: Per-handler idempotency store for batch swap operations.
static BATCH_SWAP_IDEMPOTENCY: Lazy<DeduplicationStore> = Lazy::new(create_store);

Expand All @@ -28,17 +49,30 @@ static BATCH_SWAP_IDEMPOTENCY: Lazy<DeduplicationStore> = Lazy::new(create_store
responses(
(status = 200, description = "IP committed successfully, returns assigned ip_id", body = u64),
(status = 400, description = "Invalid request (zero hash, duplicate hash)", body = ErrorResponse),
(status = 503, description = "Soroban RPC node unavailable", body = ErrorResponse),
)
)]
#[instrument(skip(body))]
pub async fn commit_ip(Json(body): Json<CommitIpRequest>) -> Result<Json<u64>, (StatusCode, Json<ErrorResponse>)> {
// TODO: Call Soroban RPC to invoke ip_registry.commit_ip
Err((
StatusCode::BAD_REQUEST,
Json(ErrorResponse {
error: "commit_ip not yet implemented".to_string(),
}),
))
pub async fn commit_ip(
Json(body): Json<CommitIpRequest>,
) -> Result<Json<u64>, (StatusCode, Json<ErrorResponse>)> {
// Delegate to the Soroban RPC client. The client validates inputs before
// making the network call, so validation errors are surfaced as 400 without
// a round-trip to the RPC node.
let ip_id = SOROBAN_CLIENT
.commit_ip(&body.owner, &body.commitment_hash)
.await
.map_err(|err| {
let status = soroban_rpc::map_rpc_error_to_status(&err);
(
status,
Json(ErrorResponse {
error: err.to_string(),
}),
)
})?;

Ok(Json(ip_id))
}

/// Retrieve an IP record by ID.
Expand Down
1 change: 1 addition & 0 deletions api-server/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ pub mod middleware_pipeline;
pub mod request_signing;
pub mod rate_limit;
pub mod schemas;
pub mod soroban_rpc;
pub mod tracing_middleware;
pub mod versioning;
pub mod webhook;
Expand Down
1 change: 1 addition & 0 deletions api-server/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ mod handlers;
mod metrics;
mod middleware_pipeline;
mod schemas;
mod soroban_rpc;
mod tracing_middleware;
mod versioning;
mod webhook;
Expand Down
Loading