Skip to content
Merged
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
1 change: 1 addition & 0 deletions .cspell/custom-dictionary.txt
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,4 @@ Solana
struct
structs
Unichain
Redoc
91 changes: 91 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,97 @@ The API spec (TBD) for the off-chain service should be adhered to by all Relay P

Explorers should be updated to track execution requests and link to their designated provider via quoter address.

## API Documentation

The Messaging Executor provides HTTP APIs for interacting with the relay service. The API specification is defined using [TypeSpec](https://typespec.io/) and can be generated as OpenAPI documentation.

### Building API Documentation

```bash
# Navigate to the api-docs directory
cd api-docs

# Install dependencies
npm ci

# Compile TypeSpec to generate OpenAPI spec and JS client & models
npm run compile
# or
tsp compile .
```

This will generate:

- **OpenAPI Specification**: `tsp-output/schema/openapi.yaml`
- **JavaScript Client & Models**: `tsp-output/clients/js/`

### Viewing API Documentation

After building, you can view the API documentation using:

1. **Swagger Editor** (Online)

- Visit https://editor.swagger.io/
- Copy and paste the contents of `tsp-output/schema/openapi.yaml`

2. **Redoc** (Online)
- Visit https://redocly.github.io/redoc/
- Upload your `openapi.yaml` file

### API Endpoints

The API provides three main endpoints:

#### GET `/capabilities`

Returns the capabilities for all supported chains, including:

- Supported request prefixes (ERV1, ERN1, ERC1, ERC2)
- Gas limits and message value limits
- Supported destination chains

#### POST `/quote`

Generates a signed quote for cross-chain execution:

- **Request**: Source chain, destination chain, optional relay instructions
- **Response**: Signed quote and estimated cost

#### POST `/status/tx`

Retrieves the status of an execution request:

- **Request**: Transaction hash and optional chain ID
- **Response**: Relay transaction details including status, costs, and execution results

### Using the JavaScript Client

The generated JavaScript client can be used to interact with the API programmatically:

```javascript
// Import the generated client
import { Client } from "./api-docs/tsp-output/clients/js";

// Create a client instance
const client = new Client({ baseUrl: "http://localhost:3000/v0" });

// Get capabilities
const capabilities = await client.capabilities.list();

// Generate a quote
const quote = await client.quote.create({
srcChain: 1,
dstChain: 2,
relayInstructions: "0x...",
});

// Check transaction status
const status = await client.status.getTransaction({
txHash: "0x...",
chainId: 1,
});
```

### API / Database Schema

#### Off-Chain Quote
Expand Down
9 changes: 9 additions & 0 deletions api-docs/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# MacOS
.DS_Store

# Default TypeSpec output
tsp-output/
dist/

# Dependency directories
node_modules/
271 changes: 271 additions & 0 deletions api-docs/main.tsp
Original file line number Diff line number Diff line change
@@ -0,0 +1,271 @@
import "@typespec/http";
import "@typespec/openapi";

using Http;
using OpenAPI;

@service()
@info(#{
title: "Messaging Executor API",
version: "1.0.0",
})
@server("http://localhost:3000/v0", "Local development server")
namespace MessagingExecutor;

// ============= Models =============

model Capabilities {
@doc("Request prefixes enabled for given chain")
requestPrefixes: string[];

@doc("Gas Drop-off limit, native chain token bigint represented as string")
gasDropOffLimit?: string;

@doc("Maximum Gas limit")
maxGasLimit?: string;

@doc("Max message value, native chain token bigint represented as string")
maxMsgValue?: string;
}

@doc("Object with chain IDs (Wormhole Chain ID) as keys and their capabilities as values")
model CapabilitiesResponse is Record<Capabilities>;

model QuoteRequest {
@doc("Source wormhole chain ID")
srcChain: int32;

@doc("Destination wormhole chain ID")
dstChain: int32;

@doc("Optional hex-encoded relay instructions for cost estimation")
relayInstructions?: string;
}

model QuoteResponse {
@doc("Hex-encoded signed quote")
signedQuote: string;

@doc("Estimated cost in source chain native token units")
estimatedCost?: string;
}

model StatusRequest {
@doc("Wormhole chain ID for the transaction")
chainId?: int32;

@doc("Transaction hash")
txHash: string;
}

model TxInfo {
@doc("Relayed transaction hash")
txHash: string;

@doc("Wormhole chain ID for the transaction")
chainId: int32;

@doc("Block number (or slot/epoch)")
blockNumber: string;

@doc("Block time as timestamp")
blockTime?: string;

@doc("Relay cost in native token units (wei/lamports/etc)")
cost: string;
}

model RequestForExecution {
@doc("Public key of Quoter")
quoterAddress: string;

@doc("Amount paid for the relay in source chain native token")
amtPaid: string;

@doc("Destination Wormhole chain ID")
dstChain: int32;

@doc("Destination address to execute the transaction")
dstAddr: string;

@doc("Refund address for excess payment")
refundAddr: string;

@doc("Hex-encoded signed quote bytes")
signedQuoteBytes: string;

@doc("Hex-encoded request bytes")
requestBytes: string;

@doc("Hex-encoded relay instruction bytes")
relayInstructionsBytes: string;

@doc("Timestamp when the request was created")
timestamp: string;
}

model SignedQuote {
@doc("Quote details")
quote: {
@doc("Quote prefix identifier (e.g., 'EQ01')")
prefix: string;

@doc("Address of the quoter providing the quote")
quoterAddress: string;

@doc("Address that will receive payment")
payeeAddress: string;

@doc("Source Wormhole chain ID")
srcChain: int32;

@doc("Destination Wormhole chain ID")
dstChain: int32;

@doc("Quote expiration time")
expiryTime: string;

@doc("Base fee for the relay")
baseFee: string;

@doc("Gas price on destination chain")
dstGasPrice: string;

@doc("Source chain token price")
srcPrice: string;

@doc("Destination chain token price")
dstPrice: string;
};

@doc("Cryptographic signature of the quote")
signature?: string;
}

// Request instruction variants based on prefix
model ERV1Request {
@doc("Request prefix identifier")
prefix: "ERV1";

@doc("Chain ID")
chain: int32;

@doc("Contract address")
address: string;

@doc("Sequence number")
sequence: string;
}

model ERN1Request {
@doc("Request prefix identifier")
prefix: "ERN1";

@doc("Source chain ID")
srcChain: int32;

@doc("Source manager address")
srcManager: string;

@doc("Message identifier")
messageId: string;
}

model ERC1Request {
@doc("Request prefix identifier")
prefix: "ERC1";

@doc("Source domain")
sourceDomain: int32;

@doc("Nonce value")
nonce: string;
}

model ERC2Request {
@doc("Request prefix identifier")
prefix: "ERC2";

@doc("CCTP V2 request configuration")
cctpV2Request: {
@doc("CCTP V2 request prefix mode")
cctpV2RequestPrefix: string;
};
}

@doc("Request instruction details that vary by prefix type")
model RequestInstruction {
@doc("The request details, structure depends on the prefix type")
request: ERV1Request | ERN1Request | ERC1Request | ERC2Request;
}

model RelayTransaction {
@doc("Wormhole chain ID where the transaction originated")
chainId: int32;

@doc("Estimated cost for the relay in source chain native token")
estimatedCost: string;

@doc("Unique identifier for the relay transaction")
id: string;

@doc("Timestamp when the transaction was indexed")
indexedAt: string;

@doc("Parsed relay instruction details")
instruction?: RequestInstruction;

@doc("Original request for execution details")
requestForExecution: RequestForExecution;

@doc("Signed quote used for this transaction")
signedQuote: SignedQuote;

@doc("Current status of the relay (pending, submitted, failed, etc.)")
status: string;

@doc("Original transaction hash")
txHash: string;

@doc("Reason for failure if status is 'failed'")
failureCause?: string;

@doc("List of destination chain transactions executed")
txs?: TxInfo[];
}

@error
model ErrorResponse {
message: string;
statusCode?: int32;
}

// ============= Interfaces =============

@route("/capabilities")
@tag("Capabilities")
interface CapabilitiesInterface {
@summary("Get capabilities for all supported chains")
@doc("Retrieves the supported features and capabilities for all supported blockchains (as wormhole chain ID)")
@get
list(): CapabilitiesResponse | ErrorResponse;
}

@route("/quote")
@tag("Quote")
interface QuoteInterface {
@summary("Generate a quote for cross-chain transaction")
@doc("Provides a signed quote for cross-chain relay with optional cost estimation")
@post
create(@body body: QuoteRequest): QuoteResponse | ErrorResponse;
}

@route("/status")
@tag("Status")
interface StatusInterface {
@summary("Get status of a transaction")
@doc("Retrieves the status of a request for execution transaction and potentially initiates processing (when first time statusing and chain ID provided)")
@route("tx")
@post
getTransaction(@body body: StatusRequest): RelayTransaction[] | ErrorResponse;
}
Loading
Loading