From 207156e04c51f8982fbb6b5cc3aef64f7ecde24f Mon Sep 17 00:00:00 2001 From: Aristides Staffieri Date: Thu, 14 Dec 2023 14:44:22 -0700 Subject: [PATCH 1/4] adds route and helper to submit transaction to the horizon rpc --- src/helper/horizon-rpc.ts | 33 +++++++++++++++++++++++++++- src/route/index.ts | 46 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 78 insertions(+), 1 deletion(-) diff --git a/src/helper/horizon-rpc.ts b/src/helper/horizon-rpc.ts index 221b4be..91b7a4f 100644 --- a/src/helper/horizon-rpc.ts +++ b/src/helper/horizon-rpc.ts @@ -1,5 +1,5 @@ import BigNumber from "bignumber.js"; -import { AssetType, Horizon } from "stellar-sdk"; +import { AssetType, Horizon, TransactionBuilder } from "stellar-sdk"; export const BASE_RESERVE = 0.5; export const BASE_RESERVE_MIN_COUNT = 2; @@ -229,3 +229,34 @@ export const fetchAccountHistory = async ( throw new Error(JSON.stringify(error)); } }; + +export const submitTransaction = async ( + signedXDR: string, + networkUrl: string, + networkPassphrase: string +): Promise<{ + data: Horizon.HorizonApi.SubmitTransactionResponse | null; + error: unknown; +}> => { + const tx = TransactionBuilder.fromXDR(signedXDR, networkPassphrase); + const server = new Horizon.Server(networkUrl); + + try { + const data = await server.submitTransaction(tx); + return { + data, + error: null, + }; + } catch (e: any) { + if (e.response.status === 504) { + // in case of 504, keep retrying this tx until submission succeeds or we get a different error + // https://developers.stellar.org/api/errors/http-status-codes/horizon-specific/timeout + // https://developers.stellar.org/docs/encyclopedia/error-handling + return await submitTransaction(signedXDR, networkUrl, networkPassphrase); + } + return { + data: null, + error: e, + }; + } +}; diff --git a/src/route/index.ts b/src/route/index.ts index b2f05a1..e0ac13f 100644 --- a/src/route/index.ts +++ b/src/route/index.ts @@ -12,6 +12,7 @@ import { isNetwork, NetworkNames, } from "../helper/validate"; +import { submitTransaction } from "../helper/horizon-rpc"; const API_VERSION = "v1"; @@ -259,6 +260,51 @@ export function initApiServer( }, }); + instance.route({ + method: "POST", + url: "/submit-tx", + schema: { + body: { + type: "object", + properties: { + signed_xdr: { type: "string" }, + network_url: { type: "string" }, + network_passphrase: { type: "string" }, + }, + }, + response: { + 200: { + type: "object", + properties: { + data: { type: "object" }, + }, + }, + }, + }, + handler: async ( + request: FastifyRequest<{ + Body: { + signed_xdr: string; + network_url: string; + network_passphrase: string; + }; + }>, + reply + ) => { + const { signed_xdr, network_url, network_passphrase } = request.body; + const { data, error } = await submitTransaction( + signed_xdr, + network_url, + network_passphrase + ); + if (error) { + reply.code(400).send(error); + } else { + reply.code(200).send(data); + } + }, + }); + next(); }, { prefix: `/api/${API_VERSION}` } From 29ded5d9a115094db037c4b4bd69dd636778d72c Mon Sep 17 00:00:00 2001 From: Aristides Staffieri Date: Fri, 15 Dec 2023 14:44:45 -0700 Subject: [PATCH 2/4] removes redundant response annotation for submit-tx route --- src/route/index.ts | 8 -------- 1 file changed, 8 deletions(-) diff --git a/src/route/index.ts b/src/route/index.ts index a9e679f..2773b4f 100644 --- a/src/route/index.ts +++ b/src/route/index.ts @@ -292,14 +292,6 @@ export function initApiServer( network_passphrase: { type: "string" }, }, }, - response: { - 200: { - type: "object", - properties: { - data: { type: "object" }, - }, - }, - }, }, handler: async ( request: FastifyRequest<{ From 375ef54fdd1d1ac99d0b03e1ca5ba571f0b05b67 Mon Sep 17 00:00:00 2001 From: Aristides Staffieri Date: Mon, 18 Dec 2023 08:56:02 -0700 Subject: [PATCH 3/4] adds simulate tx route --- src/route/index.ts | 52 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/src/route/index.ts b/src/route/index.ts index 2773b4f..e79b0f6 100644 --- a/src/route/index.ts +++ b/src/route/index.ts @@ -13,6 +13,15 @@ import { NetworkNames, } from "../helper/validate"; import { submitTransaction } from "../helper/horizon-rpc"; +import { + Memo, + MemoType, + Operation, + SorobanRpc, + Transaction, + TransactionBuilder, +} from "stellar-sdk"; +import { simulateTx } from "../helper/soroban-rpc"; const API_VERSION = "v1"; @@ -317,6 +326,49 @@ export function initApiServer( }, }); + instance.route({ + method: "POST", + url: "/simulate-tx", + schema: { + body: { + type: "object", + properties: { + signed_xdr: { type: "string" }, + network_url: { type: "string" }, + network_passphrase: { type: "string" }, + }, + }, + }, + handler: async ( + request: FastifyRequest<{ + Body: { + signed_xdr: string; + network_url: string; + network_passphrase: string; + }; + }>, + reply + ) => { + const { signed_xdr, network_url, network_passphrase } = request.body; + + try { + const tx = TransactionBuilder.fromXDR( + signed_xdr, + network_passphrase + ); + const server = new SorobanRpc.Server(network_url); + + const data = await simulateTx( + tx as Transaction, Operation[]>, + server + ); + reply.code(200).send(data); + } catch (error) { + reply.code(400).send(error); + } + }, + }); + next(); }, { prefix: `/api/${API_VERSION}` } From 12a98e940b980057735994c3f83653847d3dd59e Mon Sep 17 00:00:00 2001 From: Aristides Staffieri Date: Mon, 18 Dec 2023 09:34:11 -0700 Subject: [PATCH 4/4] adds token detail route --- src/route/index.ts | 52 ++++++++++++++++++++++++++++++++++++ src/service/mercury/index.ts | 5 ++-- 2 files changed, 55 insertions(+), 2 deletions(-) diff --git a/src/route/index.ts b/src/route/index.ts index e79b0f6..d8e1e2a 100644 --- a/src/route/index.ts +++ b/src/route/index.ts @@ -160,6 +160,58 @@ export function initApiServer( }, }); + instance.route({ + method: "GET", + url: "/token-details/:contractId", + schema: { + params: { + ["contractId"]: { + type: "string", + validator: (qStr: string) => isContractId(qStr), + }, + }, + querystring: { + ["pub_key"]: { + type: "string", + validator: (qStr: string) => isPubKey(qStr), + }, + ["network"]: { + type: "string", + validator: (qStr: string) => isNetwork(qStr), + }, + ["soroban_url"]: { + type: "string", + }, + }, + }, + handler: async ( + request: FastifyRequest<{ + Params: { ["contractId"]: string }; + Querystring: { + ["contract_ids"]: string; + ["pub_key"]: string; + ["network"]: NetworkNames; + ["soroban_url"]?: string; + }; + }>, + reply + ) => { + const contractId = request.params["contractId"]; + const { network, pub_key, soroban_url } = request.query; + try { + const data = await mercuryClient.tokenDetails( + pub_key, + contractId, + network, + soroban_url + ); + reply.code(200).send(data); + } catch (error) { + reply.code(400).send(error); + } + }, + }); + instance.route({ method: "POST", url: "/subscription/token", diff --git a/src/service/mercury/index.ts b/src/service/mercury/index.ts index 32945a2..6a9a833 100644 --- a/src/service/mercury/index.ts +++ b/src/service/mercury/index.ts @@ -332,7 +332,8 @@ export class MercuryClient { tokenDetails = async ( pubKey: string, contractId: string, - network: NetworkNames + network: NetworkNames, + customRpcUrl?: string ): Promise< { name: string; symbol: string; decimals: number } | undefined > => { @@ -345,7 +346,7 @@ export class MercuryClient { return JSON.parse(tokenDetails); } } - const server = await getServer(network); + const server = await getServer(network, customRpcUrl); // we need a builder per operation, 1 op per tx in Soroban const decimalsBuilder = await getTxBuilder(pubKey, network, server); const decimals = await getTokenDecimals(