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
6 changes: 5 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -91,8 +91,12 @@ jobs:
steps:
- uses: actions/checkout@v4

# Pin the Rust toolchain: newer stable (1.97+) fails to compile
# `cargo install --locked stellar-cli --version 22.8.1` (ethnum 1.5.0
# transmute error). 1.96.1 is the last known-good version for the
# pinned soroban-sdk 22.x stack and keeps builds reproducible.
- name: Setup Rust
uses: dtolnay/rust-toolchain@stable
uses: dtolnay/rust-toolchain@1.96.1
with:
targets: wasm32-unknown-unknown,wasm32v1-none

Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -274,6 +274,7 @@ threat model.
| [`docs/METRICS_SCHEMA.md`](docs/METRICS_SCHEMA.md) | Public metrics schema: definitions, units, sources, cadence, and privacy boundaries |
| [`docs/UNIT_ECONOMICS.md`](docs/UNIT_ECONOMICS.md) | Resolver cost model, bootstrap incentives, break-even ranges, and metrics needed before stronger claims |
| [`docs/RESOLVERS.md`](docs/RESOLVERS.md) | How to run your own resolver |
| [`docs/RISK_REGISTER.md`](docs/RISK_REGISTER.md) | **Investor-grade risk register** — 16 concrete risks with likelihood, impact, mitigations, evidence links, and next actions |
| [`docs/SECURITY.md`](docs/SECURITY.md) | STRIDE threat model, audit prep checklist, bug bounty |
| [`docs/DEPLOYMENT.md`](docs/DEPLOYMENT.md) | Testnet + mainnet deployment, env var reference, network configuration |
| [`docs/REVIEW_RESPONSE.md`](docs/REVIEW_RESPONSE.md) | Direct response to v1 reviewer feedback, item by item |
Expand Down
20 changes: 0 additions & 20 deletions coordinator/src/persistence/orders-repo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -595,24 +595,4 @@ function deriveTransitions(status: OrderStatus): string[] {
default:
return [status];
}

async getMetrics(): Promise<OrderMetrics> {
const byStatus = await this.all<{ status: string; count: number }>(this.metricsByStatus);
const totalRow = await this.get<{ count: number }>(this.metricsTotal);
const lastUpdatedRow = await this.get<{ ts: number | null }>(this.metricsLastUpdated);

const statusMap: Record<string, number> = {};
for (const row of byStatus) {
statusMap[row.status] = Number(row.count);
}

return {
totalOrders: Number(totalRow?.count ?? 0),
byStatus: statusMap,
completedOrders: statusMap["completed"] ?? 0,
refundedOrders: statusMap["refunded"] ?? 0,
staleExpiredOrders: (statusMap["expired"] ?? 0) + (statusMap["failed"] ?? 0),
lastUpdatedTimestamp: lastUpdatedRow?.ts != null ? Number(lastUpdatedRow.ts) : null
};
}
}
15 changes: 15 additions & 0 deletions coordinator/src/server/routes/orders.ts
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,21 @@ export function ordersRoutes(orders: OrderService): Router {
}
});

router.get("/orders/:id/transitions", async (req, res, next) => {
const id = req.params.id;
try {
const order = await orders.get(id);
if (!order) {
res.status(404).json({ error: "not_found" });
return;
}
const transitions = await orders.getTransitions(id);
res.json({ transitions });
} catch (err) {
next(err);
}
});

// Parameterized routes come AFTER specific routes
router.get("/orders/:id", async (req, res, next) => {
const id = req.params.id;
Expand Down
8 changes: 4 additions & 4 deletions coordinator/test/order-transitions.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,14 +55,14 @@ describe("OrderService transition summaries", () => {
orderId: "src-1",
txHash: "0xsrc",
blockNumber: 1,
timelock: 1000
timelock: 2000
});
await orders.recordDstLock({
publicId: order.publicId,
orderId: "dst-1",
txHash: "0xdst",
blockNumber: 2,
timelock: 2000,
timelock: 1000,
resolver: null
});
await orders.recordSecret(order.publicId, PREIMAGE, "0xsecret");
Expand Down Expand Up @@ -144,14 +144,14 @@ describe("GET /api/orders/:id/transitions", () => {
orderId: "src-3",
txHash: "0xsrc3",
blockNumber: 4,
timelock: 4000
timelock: 5000
});
await orders.recordDstLock({
publicId: order.publicId,
orderId: "dst-3",
txHash: "0xdst3",
blockNumber: 5,
timelock: 5000,
timelock: 4000,
resolver: null
});
await orders.recordSecret(order.publicId, PREIMAGE, "0xsecret3");
Expand Down
23 changes: 14 additions & 9 deletions coordinator/test/snapshot.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ describe("OrderService.getSnapshots", () => {
orderId: "0",
txHash: "0xsrctx",
blockNumber: 100,
timelock: 200
timelock: 900
});
await orders.recordDstLock({
publicId: order.publicId,
Expand Down Expand Up @@ -104,7 +104,7 @@ describe("OrderService.getSnapshots", () => {
orderId: "1",
txHash: "0xsrc",
blockNumber: 1,
timelock: 0
timelock: 600
});
await orders.recordDstLock({
publicId: order.publicId,
Expand Down Expand Up @@ -136,7 +136,7 @@ describe("OrderService.getSnapshots", () => {
orderId: "2",
txHash: "0xsrcrtx",
blockNumber: 50,
timelock: 150
timelock: 800
});
await orders.recordDstLock({
publicId: order.publicId,
Expand Down Expand Up @@ -196,7 +196,7 @@ describe("OrderService.getSnapshots", () => {
orderId: "c1",
txHash: "0xc1src",
blockNumber: 1,
timelock: 0
timelock: 600
});
await orders.recordDstLock({
publicId: completed1.publicId,
Expand Down Expand Up @@ -230,11 +230,16 @@ describe("OrderService.getSnapshots", () => {
const refundedSnapshot = snapshots.find((s) => s.orderId === refunded1.publicId)!;
expect(completedSnapshot.currentState).toBe("completed");
expect(refundedSnapshot.currentState).toBe("refunded");
// Order is by updated_at DESC; completed1 gets more transitions so its
// updatedAt should be >= refunded1's.
expect(completedSnapshot.timestamps.updatedAt).toBeGreaterThanOrEqual(
refundedSnapshot.timestamps.updatedAt
);
// updated_at is second-granularity (strftime('%s','now')), and the two
// orders can straddle a second boundary, so assert the sort invariant
// directly instead of comparing the two orders' timestamps against each
// other (which flakes whenever the boundary falls mid-test).
const updatedAts = snapshots.map((s) => s.timestamps.updatedAt);
for (let i = 1; i < updatedAts.length; i++) {
const prev = updatedAts[i - 1]!;
const curr = updatedAts[i]!;
expect(prev).toBeGreaterThanOrEqual(curr);
}
// The array is sorted DESC, so whichever has the higher updatedAt is first.
const [first, second] = snapshots as [OrderSnapshot, OrderSnapshot];
const isCompletedFirst = first.orderId === completed1.publicId;
Expand Down
Loading
Loading