Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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 install

# 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