Skip to content

Commit

Permalink
add skip tx tracking edge functions
Browse files Browse the repository at this point in the history
  • Loading branch information
jonator committed Sep 19, 2024
1 parent c0894d0 commit 4f23e3f
Show file tree
Hide file tree
Showing 6 changed files with 147 additions and 36 deletions.
3 changes: 2 additions & 1 deletion packages/bridge/src/skip/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {
export class SkipApiClient {
constructor(
readonly env: BridgeEnvironment,
protected readonly apiKey = process.env.SKIP_API_KEY,
readonly baseUrl = "https://api.skip.money"
) {}

Expand Down Expand Up @@ -122,7 +123,7 @@ export class SkipApiClient {
return apiClient<Response>(args[0], args[1]);
}

const key = process.env.SKIP_API_KEY;
const key = this.apiKey;
if (!key) throw new Error("SKIP_API_KEY is not set");

return apiClient<Response>(args[0], {
Expand Down
1 change: 1 addition & 0 deletions packages/bridge/src/skip/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -936,4 +936,5 @@ export class SkipBridgeProvider implements BridgeProvider {
}
}

export * from "./client";
export * from "./transfer-status";
87 changes: 53 additions & 34 deletions packages/bridge/src/skip/transfer-status.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,27 @@ import type {
TransferStatusProvider,
TransferStatusReceiver,
} from "../interface";
import { SkipApiClient } from "./client";
import { SkipBridgeProvider } from "./index";
import { SkipTxStatusResponse } from "./types";

type Transaction = {
chainID: string;
txHash: string;
env: BridgeEnvironment;
};

export type SkipStatusProvider = {
transactionStatus: ({
chainID,
txHash,
env,
}: Transaction) => Promise<SkipTxStatusResponse>;
trackTransaction: ({
chainID,
txHash,
env,
}: Transaction) => Promise<Promise<void>>;
};

/** Tracks (polls skip endpoint) and reports status updates on Skip bridge transfers. */
export class SkipTransferStatusProvider implements TransferStatusProvider {
Expand All @@ -19,12 +38,13 @@ export class SkipTransferStatusProvider implements TransferStatusProvider {

statusReceiverDelegate?: TransferStatusReceiver | undefined;

readonly skipClient: SkipApiClient;
readonly axelarScanBaseUrl: string;

constructor(env: BridgeEnvironment, protected readonly chainList: Chain[]) {
this.skipClient = new SkipApiClient(env);

constructor(
protected readonly env: BridgeEnvironment,
protected readonly chainList: Chain[],
protected readonly skipStatusProvider: SkipStatusProvider
) {
this.axelarScanBaseUrl =
env === "mainnet"
? "https://axelarscan.io"
Expand All @@ -40,39 +60,38 @@ export class SkipTransferStatusProvider implements TransferStatusProvider {

await poll({
fn: async () => {
try {
const txStatus = await this.skipClient.transactionStatus({
chainID: fromChainId.toString(),
txHash: sendTxHash,
const tx = {
chainID: fromChainId.toString(),
txHash: sendTxHash,
env: this.env,
};

const txStatus = await this.skipStatusProvider
.transactionStatus(tx)
.catch(async (error) => {
if (error instanceof Error && error.message.includes("not found")) {
// if we get an error that it's not found, prompt skip to track it first
// then try again
await this.skipStatusProvider.trackTransaction(tx);
return this.skipStatusProvider.transactionStatus(tx);
}

throw error;
});

let status: TransferStatus = "pending";
if (txStatus.state === "STATE_COMPLETED_SUCCESS") {
status = "success";
}

if (txStatus.state === "STATE_COMPLETED_ERROR") {
status = "failed";
}

return {
id: sendTxHash,
status,
};
} catch (error: any) {
if ("message" in error) {
if (error.message.includes("not found")) {
await this.skipClient.trackTransaction({
chainID: fromChainId.toString(),
txHash: sendTxHash,
});

return undefined;
}
}
let status: TransferStatus = "pending";
if (txStatus.state === "STATE_COMPLETED_SUCCESS") {
status = "success";
}

throw error;
if (txStatus.state === "STATE_COMPLETED_ERROR") {
status = "failed";
}

return {
id: sendTxHash,
status,
};
},
validate: (incomingStatus) => {
if (!incomingStatus) {
Expand Down
32 changes: 32 additions & 0 deletions packages/web/pages/api/skip-track-tx.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { BridgeEnvironment, SkipApiClient } from "@osmosis-labs/bridge";
import { NextApiRequest, NextApiResponse } from "next";

/** This edge function is necessary to invoke the SkipApiClient on the server
* as a secret API key is required for the client.
*/
export default async function skipTrackTx(
req: NextApiRequest,
res: NextApiResponse
) {
const { chainID, txHash, env } = req.query as {
chainID: string;
txHash: string;
env: BridgeEnvironment;
};

if (!chainID || !txHash || !env) {
return res.status(400).json({ error: "Missing required query parameters" });
}

const skipClient = new SkipApiClient(env);

try {
const status = await skipClient.trackTransaction({ chainID, txHash });
return res.status(200).json(status);
} catch (error) {
if (error instanceof Error) {
return res.status(500).json({ error: error.message });
}
return res.status(500).json({ error: "An unknown error occurred" });
}
}
32 changes: 32 additions & 0 deletions packages/web/pages/api/skip-tx-status.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { BridgeEnvironment, SkipApiClient } from "@osmosis-labs/bridge";
import { NextApiRequest, NextApiResponse } from "next";

/** This edge function is necessary to invoke the SkipApiClient on the server
* as a secret API key is required for the client.
*/
export default async function skipTxStatus(
req: NextApiRequest,
res: NextApiResponse
) {
const { chainID, txHash, env } = req.query as {
chainID: string;
txHash: string;
env: BridgeEnvironment;
};

if (!chainID || !txHash || !env) {
return res.status(400).json({ error: "Missing required query parameters" });
}

const skipClient = new SkipApiClient(env);

try {
const status = await skipClient.transactionStatus({ chainID, txHash });
return res.status(200).json(status);
} catch (error) {
if (error instanceof Error) {
return res.status(500).json({ error: error.message });
}
return res.status(500).json({ error: "An unknown error occurred" });
}
}
28 changes: 27 additions & 1 deletion packages/web/stores/root.ts
Original file line number Diff line number Diff line change
Expand Up @@ -249,7 +249,33 @@ export class RootStore {
),
new SkipTransferStatusProvider(
IS_TESTNET ? "testnet" : "mainnet",
ChainList
ChainList,
{
transactionStatus: async ({ chainID, txHash, env }) => {
const response = await fetch(
`/api/skip-tx-status?chainID=${chainID}&txHash=${txHash}&env=${env}`
);
const responseJson = await response.json();
if (!response.ok) {
throw new Error(
"Failed to fetch transaction status: " + responseJson.error
);
}
return responseJson;
},
trackTransaction: async ({ chainID, txHash, env }) => {
const response = await fetch(
`/api/skip-track-tx?chainID=${chainID}&txHash=${txHash}&env=${env}`
);
const responseJson = await response.json();
if (!response.ok) {
throw new Error(
"Failed to track transaction: " + responseJson.error
);
}
return responseJson;
},
}
),
new IbcTransferStatusProvider(ChainList, AssetLists),
];
Expand Down

0 comments on commit 4f23e3f

Please sign in to comment.