GET /order-status with the signature of the transaction that opened the escrow fill and the lastValidBlockHeight from the order response, until the status is terminal: expired, failed, or closed. At pendingClose nothing more can fill, and the app keeps polling until it is closed.
1
Set Up
import "dotenv/config";
const DFLOW_TRADE_API_URL = process.env.DFLOW_TRADE_API_URL ?? "https://dev-quote-api.dflow.net";
const DFLOW_API_KEY = process.env.DFLOW_API_KEY; // optional; not needed for dev endpoints
const headers: HeadersInit = {};
if (DFLOW_API_KEY) headers["x-api-key"] = DFLOW_API_KEY;
// From the transaction that opened the escrow fill and its order response.
const openingSignature = "OPENING_SIGNATURE";
const lastValidBlockHeight = Number("LAST_VALID_BLOCK_HEIGHT");
if (openingSignature === "OPENING_SIGNATURE" || Number.isNaN(lastValidBlockHeight)) {
throw new Error("Replace the placeholders with the escrow fill's opening signature and lastValidBlockHeight.");
}
type Fill = {
signature: string;
inputMint: string;
inAmount: string;
outputMint: string;
outAmount: string;
};
type Revert = { signature: string; mint: string; amount: string };
type OrderStatusResponse = {
status: "pending" | "expired" | "failed" | "open" | "pendingClose" | "closed";
inAmount: string;
outAmount: string;
fills?: Fill[];
reverts?: Revert[];
};
2
Poll the Order Status Endpoint with Backoff
const TERMINAL_STATUSES = new Set(["expired", "failed", "closed"]);
async function waitForEscrowFill(
signature: string,
lastValidBlockHeight: number
): Promise<OrderStatusResponse> {
let delayMs = 1_000;
while (true) {
const params = new URLSearchParams({
signature,
lastValidBlockHeight: lastValidBlockHeight.toString(),
});
const response = await fetch(`${DFLOW_TRADE_API_URL}/order-status?${params}`, { headers });
if (response.ok) {
const status: OrderStatusResponse = await response.json();
console.log("Status:", status.status);
if (TERMINAL_STATUSES.has(status.status)) return status;
} else if (response.status !== 429 && response.status < 500) {
throw new Error(`GET /order-status failed: ${response.status} ${await response.text()}`);
}
await new Promise((resolve) => setTimeout(resolve, delayMs));
delayMs = Math.min(delayMs * 2, 10_000);
}
}
3
Handle Each Terminal Status
const result = await waitForEscrowFill(openingSignature, lastValidBlockHeight);
switch (result.status) {
case "expired":
console.log("The opening transaction never landed. Call the order endpoint again.");
break;
case "failed":
console.log("The opening transaction failed. The user paid only the network fee.");
break;
case "closed":
console.log({ fills: result.fills ?? [], reverts: result.reverts ?? [] });
break;
}
4
Total the Fills and Reverts
function addTo(totals: Map<string, bigint>, mint: string, amount: string) {
totals.set(mint, (totals.get(mint) ?? 0n) + BigInt(amount));
}
const filledIn = new Map<string, bigint>();
const filledOut = new Map<string, bigint>();
const returned = new Map<string, bigint>();
for (const fill of result.fills ?? []) {
addTo(filledIn, fill.inputMint, fill.inAmount);
addTo(filledOut, fill.outputMint, fill.outAmount);
}
for (const revert of result.reverts ?? []) {
addTo(returned, revert.mint, revert.amount);
}
const outcome = !result.fills?.length
? "not filled, input returned"
: result.reverts?.length
? "filled with input returned"
: "filled";
console.log({ outcome, filledIn, filledOut, returned });