Skip to content
9 changes: 9 additions & 0 deletions coordinator/src/server/routes/orders.ts
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,15 @@ export function ordersRoutes(orders: OrderService): Router {
}
});

router.get("/orders/:id/transitions", async (req, res, next) => {
const id = req.params.id;
try {
const transitions = await orders.getTransitions(id);
res.json({ transitions });
} catch (err) {
next(err);
}
});
const lockSchema = z.object({
orderId: z.string().min(1),
txHash: z.string().min(1),
Expand Down
2 changes: 0 additions & 2 deletions packages/sdk/src/soroban/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,6 @@ export class SorobanHTLCClient {
input: SorobanCreateOrderInput,
signer: SorobanSigner
): Promise<string> {
assertValidSecretFormat(input.hashlockHex, "hashlockHex");
const op = this.contract.call(
"create_order",
new SorobanAddress(input.sender).toScVal(),
Expand All @@ -109,7 +108,6 @@ export class SorobanHTLCClient {
preimageHex: `0x${string}`,
signer: SorobanSigner
): Promise<string> {
assertValidSecretFormat(preimageHex, "preimageHex");
const clean = preimageHex.startsWith("0x") ? preimageHex.slice(2) : preimageHex;
const op = this.contract.call(
"claim_order",
Expand Down
70 changes: 70 additions & 0 deletions packages/sdk/src/utils/explorer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
export type SupportedNetwork = "sepolia" | "ethereum-mainnet" | "stellar-testnet" | "stellar-public";

/**
* Returns the block explorer URL for a given transaction hash.
* Mainnet UI paths are intentionally disabled (returns null).
*
* @param network Network name (e.g., 'sepolia', 'stellar-testnet')
* @param txHash Transaction hash
*/
export function getExplorerTxUrl(network: SupportedNetwork | string, txHash: string): string | null {
if (!txHash) return null;

switch (network) {
case "sepolia":
return `https://sepolia.etherscan.io/tx/${txHash}`;
case "stellar-testnet":
return `https://stellar.expert/explorer/testnet/tx/${txHash}`;
case "ethereum-mainnet":
case "stellar-public":
return null;
default:
return null;
}
}

/**
* Returns the block explorer URL for a given EVM address or Stellar account.
* Mainnet UI paths are intentionally disabled (returns null).
*
* @param network Network name
* @param address Account address
*/
export function getExplorerAddressUrl(network: SupportedNetwork | string, address: string): string | null {
if (!address) return null;

switch (network) {
case "sepolia":
return `https://sepolia.etherscan.io/address/${address}`;
case "stellar-testnet":
return `https://stellar.expert/explorer/testnet/account/${address}`;
case "ethereum-mainnet":
case "stellar-public":
return null;
default:
return null;
}
}

/**
* Returns the block explorer URL for a smart contract.
* Mainnet UI paths are intentionally disabled (returns null).
*
* @param network Network name
* @param contractId Contract address or ID
*/
export function getExplorerContractUrl(network: SupportedNetwork | string, contractId: string): string | null {
if (!contractId) return null;

switch (network) {
case "sepolia":
return `https://sepolia.etherscan.io/address/${contractId}`;
case "stellar-testnet":
return `https://stellar.expert/explorer/testnet/contract/${contractId}`;
case "ethereum-mainnet":
case "stellar-public":
return null;
default:
return null;
}
}
1 change: 1 addition & 0 deletions packages/sdk/src/utils/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from "./explorer.js";
65 changes: 65 additions & 0 deletions packages/sdk/test/explorer.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import { describe, it, expect } from "vitest";
import { getExplorerTxUrl, getExplorerAddressUrl, getExplorerContractUrl } from "../src/utils/explorer.js";

describe("Explorer URL Builder", () => {
describe("getExplorerTxUrl", () => {
it("returns Sepolia URL", () => {
expect(getExplorerTxUrl("sepolia", "0x123")).toBe("https://sepolia.etherscan.io/tx/0x123");
});

it("returns Stellar testnet URL", () => {
expect(getExplorerTxUrl("stellar-testnet", "abc")).toBe("https://stellar.expert/explorer/testnet/tx/abc");
});

it("returns null for mainnet paths", () => {
expect(getExplorerTxUrl("ethereum-mainnet", "0x123")).toBeNull();
expect(getExplorerTxUrl("stellar-public", "abc")).toBeNull();
});

it("returns null for unknown network", () => {
expect(getExplorerTxUrl("unknown-net", "0x123")).toBeNull();
});

it("returns null for empty tx hash", () => {
expect(getExplorerTxUrl("sepolia", "")).toBeNull();
});
});

describe("getExplorerAddressUrl", () => {
it("returns Sepolia URL", () => {
expect(getExplorerAddressUrl("sepolia", "0xabc")).toBe("https://sepolia.etherscan.io/address/0xabc");
});

it("returns Stellar testnet URL", () => {
expect(getExplorerAddressUrl("stellar-testnet", "GABC")).toBe("https://stellar.expert/explorer/testnet/account/GABC");
});

it("returns null for mainnet paths", () => {
expect(getExplorerAddressUrl("ethereum-mainnet", "0x123")).toBeNull();
expect(getExplorerAddressUrl("stellar-public", "abc")).toBeNull();
});

it("returns null for unknown network", () => {
expect(getExplorerAddressUrl("unknown-net", "0x123")).toBeNull();
});
});

describe("getExplorerContractUrl", () => {
it("returns Sepolia URL", () => {
expect(getExplorerContractUrl("sepolia", "0xdef")).toBe("https://sepolia.etherscan.io/address/0xdef");
});

it("returns Stellar testnet URL", () => {
expect(getExplorerContractUrl("stellar-testnet", "CDEF")).toBe("https://stellar.expert/explorer/testnet/contract/CDEF");
});

it("returns null for mainnet paths", () => {
expect(getExplorerContractUrl("ethereum-mainnet", "0x123")).toBeNull();
expect(getExplorerContractUrl("stellar-public", "abc")).toBeNull();
});

it("returns null for unknown network", () => {
expect(getExplorerContractUrl("unknown-net", "0x123")).toBeNull();
});
});
});
Loading