From a8f793dc98f6e456945306bcc5396f38ce6e8935 Mon Sep 17 00:00:00 2001 From: Aristides Staffieri Date: Mon, 20 Nov 2023 15:07:51 -0700 Subject: [PATCH 01/20] adds newly supported classic ops, tweaks route and query for history --- src/route/index.ts | 4 +- src/service/mercury/index.ts | 8 ++++ src/service/mercury/queries.ts | 70 +++++++++++++++++++++++++++++++--- 3 files changed, 76 insertions(+), 6 deletions(-) diff --git a/src/route/index.ts b/src/route/index.ts index cc50fde..a04a776 100644 --- a/src/route/index.ts +++ b/src/route/index.ts @@ -91,7 +91,9 @@ export function initApiServer( reply ) => { const pubKey = request.params["pubKey"]; - const contractIds = request.query["contract_ids"].split(","); + const contractIds = request.query["contract_ids"] + ? request.query["contract_ids"].split(",") + : []; const { data, error } = await mercuryClient.getAccountBalances( pubKey, contractIds, diff --git a/src/service/mercury/index.ts b/src/service/mercury/index.ts index 4c45354..4d81523 100644 --- a/src/service/mercury/index.ts +++ b/src/service/mercury/index.ts @@ -337,9 +337,11 @@ export class MercuryClient { getAccountHistory = async (pubKey: string) => { try { + const xdrPubKey = new Address(pubKey).toScVal().toXDR("base64"); const getData = async () => { const data = await this.urqlClient.query(query.getAccountHistory, { pubKey, + xdrPubKey, }); const errorMessage = getGraphQlError(data.error); if (errorMessage) { @@ -369,6 +371,12 @@ export class MercuryClient { contractIds: string[], network: NetworkNames ) => { + if (contractIds.length < 1) { + return { + data: [], + error: null, + }; + } // TODO: once classic subs include balance, add query try { const getData = async () => { diff --git a/src/service/mercury/queries.ts b/src/service/mercury/queries.ts index 103c289..b8dab8b 100644 --- a/src/service/mercury/queries.ts +++ b/src/service/mercury/queries.ts @@ -51,7 +51,7 @@ export const query = { } `, getAccountHistory: ` - query GetAccountHistory($pubKey: String!) { + query GetAccountHistory($pubKey: String!, $xdrPubKey: String!) { mintEvent: eventByTopic(t1: "AAAADgAAAARtaW50") { edges { node { @@ -66,7 +66,7 @@ export const query = { } } } - transferToEvent: eventByTopic(t1: "AAAADgAAAAh0cmFuc2Zlcg==", t2: $pubKey) { + transferToEvent: eventByTopic(t1: "AAAADgAAAAh0cmFuc2Zlcg==", t2: $xdrPubKey) { edges { node { contractId @@ -80,7 +80,7 @@ export const query = { } } } - transferFromEvent: eventByTopic(t1: "AAAADgAAAAh0cmFuc2Zlcg==", t3: $pubKey) { + transferFromEvent: eventByTopic(t1: "AAAADgAAAAh0cmFuc2Zlcg==", t3: $xdrPubKey) { edges { node { contractId @@ -464,13 +464,73 @@ export const query = { limit lineNative poolshareByLinePoolShare { - assetA - assetB + assetByA { + code + } + assetByB { + code + } fee } } } + changeTrustByPublicKey(publicKeyText: $pubKey) { + edges { + node { + limit + lineAsset + lineNative + linePoolShare + source + sourceMuxed + } + } + } + + accountMergeByPublicKey(publicKeyText: $pubKey) { + edges { + node { + destination + destinationMuxed + source + sourceMuxed + } + } + } + + bumpSequenceByPublicKey(publicKeyText: $pubKey) { + edges { + node { + source + sourceMuxed + bumpTo + } + } + } + + claimClaimableBalanceByPublicKey(publicKeyText: $pubKey) { + edges { + node { + source + sourceMuxed + balanceId + } + } + } + + createClaimableBalanceByPublicKey(publicKeyText: $pubKey) { + edges { + node { + amount + asset + assetNative + source + sourceMuxed + } + } + } + } `, }; From 4ef8f68f6655e4a7f894b70f93f12f27ac0fc2c2 Mon Sep 17 00:00:00 2001 From: Aristides Staffieri Date: Mon, 20 Nov 2023 15:16:31 -0700 Subject: [PATCH 02/20] uses correct xdr key for transfer from --- src/service/mercury/queries.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/service/mercury/queries.ts b/src/service/mercury/queries.ts index b8dab8b..e434fc3 100644 --- a/src/service/mercury/queries.ts +++ b/src/service/mercury/queries.ts @@ -80,7 +80,7 @@ export const query = { } } } - transferFromEvent: eventByTopic(t1: "AAAADgAAAAh0cmFuc2Zlcg==", t3: $xdrPubKey) { + transferFromEvent: eventByTopic(t1: "AAAADwAAAAh0cmFuc2Zlcg==", t3: $xdrPubKey) { edges { node { contractId From 304c16c9422c3f353e1885ff2e23c532b59740d3 Mon Sep 17 00:00:00 2001 From: Aristides Staffieri Date: Tue, 21 Nov 2023 11:34:23 -0700 Subject: [PATCH 03/20] adds classic side balances query, adds history formatter first key --- src/service/mercury/helpers/transformers.ts | 36 ++++++++++++++++++++- src/service/mercury/index.ts | 13 ++++++-- src/service/mercury/queries.ts | 20 +++++++++++- 3 files changed, 64 insertions(+), 5 deletions(-) diff --git a/src/service/mercury/helpers/transformers.ts b/src/service/mercury/helpers/transformers.ts index fd8b609..74bb0c4 100644 --- a/src/service/mercury/helpers/transformers.ts +++ b/src/service/mercury/helpers/transformers.ts @@ -1,4 +1,5 @@ import { OperationResult } from "@urql/core"; +import { scValToNative, xdr } from "soroban-client"; // Transformers take an API response, and transform it/augment it for frontend consumption @@ -33,4 +34,37 @@ const transformAccountBalances = async ( }); }; -export { transformAccountBalances }; +interface MercuryAccountHistory { + transferFromEvent: { + edges: { + node: { + contractId: string; + data: string; + topic1: string; // to + topic2: string; // from + topic3: string; // amount + }; + }[]; + }; +} + +const transformAccountHistory = ( + rawResponse: OperationResult +) => { + const edges = rawResponse.data?.transferFromEvent.edges || []; + const transferFrom = edges.map((edge) => { + const amountBigInt = scValToNative( + xdr.ScVal.fromXDR(edge.node.data, "base64") + ) as BigInt; + return { + contractId: edge.node.contractId, + to: scValToNative(xdr.ScVal.fromXDR(edge.node.topic3, "base64")), + from: scValToNative(xdr.ScVal.fromXDR(edge.node.topic2, "base64")), + amount: amountBigInt.toString(), + }; + }); + + return [...transferFrom]; +}; + +export { transformAccountBalances, transformAccountHistory }; diff --git a/src/service/mercury/index.ts b/src/service/mercury/index.ts index 4d81523..23b31ac 100644 --- a/src/service/mercury/index.ts +++ b/src/service/mercury/index.ts @@ -12,7 +12,10 @@ import { getTokenSymbol, getTxBuilder, } from "../../helper/soroban-rpc"; -import { transformAccountBalances } from "./helpers/transformers"; +import { + transformAccountBalances, + transformAccountHistory, +} from "./helpers/transformers"; type NetworkNames = keyof typeof Networks; @@ -353,7 +356,7 @@ export class MercuryClient { const data = await this.renewAndRetry(getData); return { - data, + data: transformAccountHistory(data), error: null, }; } catch (error) { @@ -381,7 +384,11 @@ export class MercuryClient { try { const getData = async () => { const response = await this.urqlClient.query( - query.getAccountBalances(this.tokenBalanceKey(pubKey), contractIds), + query.getAccountBalances( + pubKey, + this.tokenBalanceKey(pubKey), + contractIds + ), {} ); const errorMessage = getGraphQlError(response.error); diff --git a/src/service/mercury/queries.ts b/src/service/mercury/queries.ts index e434fc3..473bbfb 100644 --- a/src/service/mercury/queries.ts +++ b/src/service/mercury/queries.ts @@ -31,7 +31,11 @@ export const query = { } } `, - getAccountBalances: (ledgerKey: string, contractIds: string[]) => ` + getAccountBalances: ( + publicKey: string, + ledgerKey: string, + contractIds: string[] + ) => ` query AccountBalances { ${contractIds.map( (id) => @@ -48,6 +52,18 @@ export const query = { } ` )} + + balanceByPublicKey(publicKeyText: "${publicKey}") { + edges { + node { + account + asset + balance + limit + lpShare + } + } + } } `, getAccountHistory: ` @@ -531,6 +547,8 @@ export const query = { } } + + } `, }; From 3afa0c22336ea84b9e3b53df5f066d3f0726447a Mon Sep 17 00:00:00 2001 From: Aristides Staffieri Date: Tue, 21 Nov 2023 12:35:46 -0700 Subject: [PATCH 04/20] adds keys for account history items in history transformer --- src/helper/test-helper.ts | 4 +- src/route/index.test.ts | 2 +- src/service/mercury/helpers/transformers.ts | 571 +++++++++++++++++++- src/service/mercury/index.test.ts | 10 +- src/service/mercury/queries.ts | 13 - 5 files changed, 576 insertions(+), 24 deletions(-) diff --git a/src/helper/test-helper.ts b/src/helper/test-helper.ts index f71dcb0..5a48dea 100644 --- a/src/helper/test-helper.ts +++ b/src/helper/test-helper.ts @@ -141,7 +141,7 @@ jest.spyOn(client, "query").mockImplementation((_query: any): any => { error: null, }); } - case query.getAccountBalances(tokenBalanceLedgerKey, [ + case query.getAccountBalances(pubKey, tokenBalanceLedgerKey, [ "CCWAMYJME4H5CKG7OLXGC2T4M6FL52XCZ3OQOAV6LL3GLA4RO4WH3ASP", ]): { return Promise.resolve({ @@ -149,7 +149,7 @@ jest.spyOn(client, "query").mockImplementation((_query: any): any => { error: null, }); } - case query.getAccountBalances(tokenBalanceLedgerKey, [ + case query.getAccountBalances(pubKey, tokenBalanceLedgerKey, [ "CCWAMYJME4H5CKG7OLXGC2T4M6FL52XCZ3OQOAV6LL3GLA4RO4WH3ASP", "CBGTG7XFRY3L6OKAUTR6KGDKUXUQBX3YDJ3QFDYTGVMOM7VV4O7NCODG", ]): { diff --git a/src/route/index.test.ts b/src/route/index.test.ts index 3d14f69..519a4ba 100644 --- a/src/route/index.test.ts +++ b/src/route/index.test.ts @@ -2,7 +2,7 @@ import { getDevServer, queryMockResponse, pubKey } from "../helper/test-helper"; import { query } from "../service/mercury/queries"; describe("API routes", () => { - describe("/account-history/:pubKey", () => { + describe.skip("/account-history/:pubKey", () => { it("can fetch an account history for a pub key", async () => { const server = await getDevServer(); const response = await fetch( diff --git a/src/service/mercury/helpers/transformers.ts b/src/service/mercury/helpers/transformers.ts index 74bb0c4..c3d93d9 100644 --- a/src/service/mercury/helpers/transformers.ts +++ b/src/service/mercury/helpers/transformers.ts @@ -35,6 +35,16 @@ const transformAccountBalances = async ( }; interface MercuryAccountHistory { + mintEvent: { + edges: { + node: { + contractId: string; + data: string; + topic1: string; // to + topic2: string; // amount + }; + }[]; + }; transferFromEvent: { edges: { node: { @@ -46,13 +56,450 @@ interface MercuryAccountHistory { }; }[]; }; + transferToEvent: { + edges: { + node: { + contractId: string; + data: string; + topic1: string; // to + topic2: string; // from + topic3: string; // amount + }; + }[]; + }; + createAccountByPublicKey: { + edges: { + node: { + destination: string; + }; + }[]; + }; + createAccountToPublicKey: { + edges: { + node: { + destination: string; + }; + }[]; + }; + paymentsByPublicKey: { + edges: { + node: { + amount: string; + assetNative: string; + assetByAsset: { + code: string; + issuer: string; + }; + accountBySource: { + publickey: string; + }; + accountByDestination: { + publickey: string; + }; + }; + }[]; + }; + paymentsToPublicKey: { + edges: { + node: { + amount: string; + assetNative: string; + assetByAsset: { + code: string; + issuer: string; + }; + accountBySource: { + publickey: string; + }; + accountByDestination: { + publickey: string; + }; + }; + }[]; + }; + pathPaymentsStrictSendByPublicKey: { + nodes: { + accountBySource: { + publickey: string; + }; + accountByDestination: { + publickey: string; + }; + assetByDestAsset: { + code: string; + issuer: string; + }; + assetByPath1: { + code: string; + issuer: string; + }; + assetByPath2: { + code: string; + issuer: string; + }; + assetByPath3: { + issuer: string; + code: string; + }; + assetByPath4: { + issuer: string; + code: string; + }; + assetByPath5: { + issuer: string; + code: string; + }; + assetBySendAsset: { + code: string; + issuer: string; + }; + destAssetNative: string; + destMin: string; + path1Native: string; + path2Native: string; + path3Native: string; + path4Native: string; + path5Native: string; + sendAmount: string; + sendAssetNative: string; + }[]; + }; + pathPaymentsStrictSendToPublicKey: { + nodes: { + accountBySource: { + publickey: string; + }; + accountByDestination: { + publickey: string; + }; + assetByDestAsset: { + code: string; + issuer: string; + }; + assetByPath1: { + code: string; + issuer: string; + }; + assetByPath2: { + code: string; + issuer: string; + }; + assetByPath3: { + issuer: string; + code: string; + }; + assetByPath4: { + issuer: string; + code: string; + }; + assetByPath5: { + issuer: string; + code: string; + }; + assetBySendAsset: { + code: string; + issuer: string; + }; + destAssetNative: string; + destMin: string; + path1Native: string; + path2Native: string; + path3Native: string; + path4Native: string; + path5Native: string; + sendAmount: string; + sendAssetNative: string; + }[]; + }; + pathPaymentsStrictReceiveByPublicKey: { + nodes: { + accountBySource: { + publickey: string; + }; + accountByDestination: { + publickey: string; + }; + assetByDestAsset: { + code: string; + issuer: string; + }; + assetByPath1: { + code: string; + issuer: string; + }; + assetByPath2: { + code: string; + issuer: string; + }; + assetByPath3: { + issuer: string; + code: string; + }; + assetByPath4: { + issuer: string; + code: string; + }; + assetByPath5: { + issuer: string; + code: string; + }; + assetBySendAsset: { + code: string; + issuer: string; + }; + destAssetNative: string; + destMin: string; + path1Native: string; + path2Native: string; + path3Native: string; + path4Native: string; + path5Native: string; + sendAmount: string; + sendAssetNative: string; + }[]; + }; + pathPaymentsStrictReceiveToPublicKey: { + nodes: { + accountBySource: { + publickey: string; + }; + accountByDestination: { + publickey: string; + }; + assetByDestAsset: { + code: string; + issuer: string; + }; + assetByPath1: { + code: string; + issuer: string; + }; + assetByPath2: { + code: string; + issuer: string; + }; + assetByPath3: { + issuer: string; + code: string; + }; + assetByPath4: { + issuer: string; + code: string; + }; + assetByPath5: { + issuer: string; + code: string; + }; + assetBySendAsset: { + code: string; + issuer: string; + }; + destAssetNative: string; + destMin: string; + path1Native: string; + path2Native: string; + path3Native: string; + path4Native: string; + path5Native: string; + sendAmount: string; + sendAssetNative: string; + }[]; + }; + manageBuyOfferByPublicKey: { + edges: { + node: { + buyingNative: boolean; + accountBySource: { + publickey: string; + }; + assetByBuying: { + issuer: string; + code: string; + }; + assetBySelling: { + code: string; + issuer: string; + }; + ledgerByLedger: { + closeTime: string; + sequence: string; + }; + muxedaccountBySourceMuxed: { + id: string; + publickey: string; + }; + offerId: string; + priceD: string; + priceN: string; + sellingNative: boolean; + }; + }[]; + }; + manageSellOfferByPublicKey: { + edges: { + node: { + buyingNative: boolean; + accountBySource: { + publickey: string; + }; + assetByBuying: { + issuer: string; + code: string; + }; + assetBySelling: { + code: string; + issuer: string; + }; + ledgerByLedger: { + closeTime: string; + sequence: string; + }; + muxedaccountBySourceMuxed: { + id: string; + publickey: string; + }; + offerId: string; + priceD: string; + priceN: string; + sellingNative: boolean; + }; + }[]; + }; + createPassiveSellOfferByPublicKey: { + nodes: { + accountBySource: { + publickey: string; + }; + amount: string; + assetByBuying: { + code: string; + issuer: string; + }; + assetBySelling: { + code: string; + issuer: string; + }; + buyingNative: boolean; + ledgerByLedger: { + closeTime: string; + sequence: string; + }; + muxedaccountBySourceMuxed: { + id: string; + publickey: string; + }; + priceD: string; + priceN: string; + sellingNative: boolean; + }[]; + }; + changeTrustByPublicKey: { + nodes: { + accountBySource: { + publickey: string; + }; + assetByLineAsset: { + issuer: string; + code: string; + }; + ledgerByLedger: { + closeTime: string; + sequence: string; + }; + limit: string; + lineNative: boolean; + poolshareByLinePoolShare: { + assetByA: { + code: string; + }; + assetByB: { + code: string; + }; + fee: string; + }; + }[]; + }; + accountMergeByPublicKey: { + edges: { + node: { + destination: string; + destinationMuxed: string; + source: string; + sourceMuxed: string; + }; + }[]; + }; + bumpSequenceByPublicKey: { + edges: { + node: { + source: string; + sourceMuxed: string; + bumpTo: string; + }; + }[]; + }; + claimClaimableBalanceByPublicKey: { + edges: { + node: { + source: string; + sourceMuxed: string; + balanceId: string; + }; + }[]; + }; + createClaimableBalanceByPublicKey: { + edges: { + node: { + amount: string; + asset: string; + assetNative: boolean; + source: string; + sourceMuxed: string; + }; + }[]; + }; } const transformAccountHistory = ( rawResponse: OperationResult ) => { - const edges = rawResponse.data?.transferFromEvent.edges || []; - const transferFrom = edges.map((edge) => { + const transferFromEdges = rawResponse.data?.transferFromEvent.edges || []; + const transferToEdges = rawResponse.data?.transferToEvent.edges || []; + const mintEdges = rawResponse.data?.mintEvent.edges || []; + const createAccountEdges = + rawResponse.data?.createAccountByPublicKey.edges || []; + const createAccountToEdges = + rawResponse.data?.createAccountToPublicKey.edges || []; + const paymentsByPublicKeyEdges = + rawResponse.data?.paymentsByPublicKey.edges || []; + const paymentsToPublicKeyEdges = + rawResponse.data?.paymentsToPublicKey.edges || []; + const pathPaymentsStrictSendByPublicKeyEdges = + rawResponse.data?.pathPaymentsStrictSendByPublicKey.nodes || []; + const pathPaymentsStrictSendToPublicKeyEdges = + rawResponse.data?.pathPaymentsStrictSendToPublicKey.nodes || []; + const pathPaymentsStrictReceiveByPublicKeyEdges = + rawResponse.data?.pathPaymentsStrictReceiveByPublicKey.nodes || []; + const pathPaymentsStrictReceiveToPublicKeyEdges = + rawResponse.data?.pathPaymentsStrictReceiveToPublicKey.nodes || []; + const manageBuyOfferByPublicKeyEdges = + rawResponse.data?.manageBuyOfferByPublicKey.edges || []; + const manageSellOfferByPublicKeyEdges = + rawResponse.data?.manageSellOfferByPublicKey.edges || []; + const createPassiveSellOfferByPublicKeyEdges = + rawResponse.data?.createPassiveSellOfferByPublicKey.nodes || []; + const changeTrustByPublicKeyEdges = + rawResponse.data?.changeTrustByPublicKey.nodes || []; + const accountMergeByPublicKeyEdges = + rawResponse.data?.accountMergeByPublicKey.edges || []; + const bumpSequenceByPublicKeyEdges = + rawResponse.data?.bumpSequenceByPublicKey.edges || []; + const claimClaimableBalanceByPublicKeyEdges = + rawResponse.data?.claimClaimableBalanceByPublicKey.edges || []; + const createClaimableBalanceByPublicKeyEdges = + rawResponse.data?.createClaimableBalanceByPublicKey.edges || []; + + const transferFrom = transferFromEdges.map((edge) => { const amountBigInt = scValToNative( xdr.ScVal.fromXDR(edge.node.data, "base64") ) as BigInt; @@ -64,7 +511,125 @@ const transformAccountHistory = ( }; }); - return [...transferFrom]; + const transferTo = transferToEdges.map((edge) => { + const amountBigInt = scValToNative( + xdr.ScVal.fromXDR(edge.node.data, "base64") + ) as BigInt; + return { + contractId: edge.node.contractId, + to: scValToNative(xdr.ScVal.fromXDR(edge.node.topic3, "base64")), + from: scValToNative(xdr.ScVal.fromXDR(edge.node.topic2, "base64")), + amount: amountBigInt.toString(), + }; + }); + + const mint = mintEdges.map((edge) => { + const amountBigInt = scValToNative( + xdr.ScVal.fromXDR(edge.node.data, "base64") + ) as BigInt; + return { + contractId: edge.node.contractId, + to: scValToNative(xdr.ScVal.fromXDR(edge.node.topic1, "base64")), + amount: amountBigInt.toString(), + }; + }); + + const createAccount = createAccountEdges.map((edge) => ({ + destination: edge.node.destination, + })); + + const createAccountTo = createAccountToEdges.map((edge) => ({ + destination: edge.node.destination, + })); + + const paymentsByPublicKey = paymentsByPublicKeyEdges.map((edge) => ({ + ...edge.node, + })); + + const paymentsToPublicKey = paymentsToPublicKeyEdges.map((edge) => ({ + ...edge.node, + })); + + const pathPaymentsStrictSendByPublicKey = + pathPaymentsStrictSendByPublicKeyEdges.map((edge) => ({ + ...edge, + })); + + const pathPaymentsStrictSendToPublicKey = + pathPaymentsStrictSendToPublicKeyEdges.map((edge) => ({ + ...edge, + })); + + const pathPaymentsStrictReceiveByPublicKey = + pathPaymentsStrictReceiveByPublicKeyEdges.map((edge) => ({ + ...edge, + })); + + const pathPaymentsStrictReceiveToPublicKey = + pathPaymentsStrictReceiveToPublicKeyEdges.map((edge) => ({ + ...edge, + })); + + const manageBuyOfferByPublicKey = manageBuyOfferByPublicKeyEdges.map( + (edge) => ({ + ...edge, + }) + ); + + const manageSellOfferByPublicKey = manageSellOfferByPublicKeyEdges.map( + (edge) => ({ + ...edge, + }) + ); + + const createPassiveSellOfferByPublicKey = + createPassiveSellOfferByPublicKeyEdges.map((edge) => ({ + ...edge, + })); + + const changeTrustByPublicKey = changeTrustByPublicKeyEdges.map((edge) => ({ + ...edge, + })); + + const accountMergeByPublicKey = accountMergeByPublicKeyEdges.map((edge) => ({ + ...edge, + })); + + const bumpSequenceByPublicKey = bumpSequenceByPublicKeyEdges.map((edge) => ({ + ...edge, + })); + + const claimClaimableBalanceByPublicKey = + claimClaimableBalanceByPublicKeyEdges.map((edge) => ({ + ...edge, + })); + + const createClaimableBalanceByPublicKey = + createClaimableBalanceByPublicKeyEdges.map((edge) => ({ + ...edge, + })); + + return [ + ...transferFrom, + ...mint, + ...transferTo, + ...createAccount, + ...createAccountTo, + ...paymentsByPublicKey, + ...paymentsToPublicKey, + ...pathPaymentsStrictSendByPublicKey, + ...pathPaymentsStrictSendToPublicKey, + ...pathPaymentsStrictReceiveByPublicKey, + ...pathPaymentsStrictReceiveToPublicKey, + ...manageBuyOfferByPublicKey, + ...manageSellOfferByPublicKey, + ...createPassiveSellOfferByPublicKey, + ...changeTrustByPublicKey, + ...accountMergeByPublicKey, + ...bumpSequenceByPublicKey, + ...claimClaimableBalanceByPublicKey, + ...createClaimableBalanceByPublicKey, + ]; }; export { transformAccountBalances, transformAccountHistory }; diff --git a/src/service/mercury/index.test.ts b/src/service/mercury/index.test.ts index 936ca2d..0a677ce 100644 --- a/src/service/mercury/index.test.ts +++ b/src/service/mercury/index.test.ts @@ -8,11 +8,11 @@ import { } from "../../helper/test-helper"; describe("Mercury Service", () => { - it("can fetch account history with a payment-to in history", async () => { - const { data } = await mockMercuryClient.getAccountHistory(pubKey); - const paymentsToPublicKey = data?.data.paymentsToPublicKey.edges[0].node; - expect(paymentsToPublicKey.accountByDestination.publickey).toEqual(pubKey); - expect(paymentsToPublicKey.amount).toBe("50000000"); + it.skip("can fetch account history with a payment-to in history", async () => { + // const { data } = await mockMercuryClient.getAccountHistory(pubKey); + // const paymentsToPublicKey = data?.data.paymentsToPublicKey.edges[0].node; + // expect(paymentsToPublicKey.accountByDestination.publickey).toEqual(pubKey); + // expect(paymentsToPublicKey.amount).toBe("50000000"); }); it("can add new full account subscription", async () => { diff --git a/src/service/mercury/queries.ts b/src/service/mercury/queries.ts index 473bbfb..ccdf556 100644 --- a/src/service/mercury/queries.ts +++ b/src/service/mercury/queries.ts @@ -491,19 +491,6 @@ export const query = { } } - changeTrustByPublicKey(publicKeyText: $pubKey) { - edges { - node { - limit - lineAsset - lineNative - linePoolShare - source - sourceMuxed - } - } - } - accountMergeByPublicKey(publicKeyText: $pubKey) { edges { node { From ed0585332ec6637e6f5f4d0220915111bad12914 Mon Sep 17 00:00:00 2001 From: Aristides Staffieri Date: Wed, 22 Nov 2023 11:44:25 -0700 Subject: [PATCH 05/20] adds rpc compatible formatter for token op details --- src/route/index.ts | 5 +- src/service/mercury/helpers/transformers.ts | 114 ++++++++++++++------ src/service/mercury/index.ts | 10 +- 3 files changed, 90 insertions(+), 39 deletions(-) diff --git a/src/route/index.ts b/src/route/index.ts index a04a776..592c325 100644 --- a/src/route/index.ts +++ b/src/route/index.ts @@ -57,7 +57,10 @@ export function initApiServer( reply ) => { const pubKey = request.params["pubKey"]; - const { data, error } = await mercuryClient.getAccountHistory(pubKey); + const { data, error } = await mercuryClient.getAccountHistory( + pubKey, + NETWORK + ); if (error) { reply.code(400).send(error); } else { diff --git a/src/service/mercury/helpers/transformers.ts b/src/service/mercury/helpers/transformers.ts index c3d93d9..f099b04 100644 --- a/src/service/mercury/helpers/transformers.ts +++ b/src/service/mercury/helpers/transformers.ts @@ -1,5 +1,7 @@ import { OperationResult } from "@urql/core"; -import { scValToNative, xdr } from "soroban-client"; +import { Networks, scValToNative, xdr } from "soroban-client"; + +type NetworkNames = keyof typeof Networks; // Transformers take an API response, and transform it/augment it for frontend consumption @@ -460,8 +462,11 @@ interface MercuryAccountHistory { }; } -const transformAccountHistory = ( - rawResponse: OperationResult +const transformAccountHistory = async ( + rawResponse: OperationResult, + pubKey: string, + network: NetworkNames, + getTokenDetails: any // todo ) => { const transferFromEdges = rawResponse.data?.transferFromEvent.edges || []; const transferToEdges = rawResponse.data?.transferToEvent.edges || []; @@ -499,40 +504,79 @@ const transformAccountHistory = ( const createClaimableBalanceByPublicKeyEdges = rawResponse.data?.createClaimableBalanceByPublicKey.edges || []; - const transferFrom = transferFromEdges.map((edge) => { - const amountBigInt = scValToNative( - xdr.ScVal.fromXDR(edge.node.data, "base64") - ) as BigInt; - return { - contractId: edge.node.contractId, - to: scValToNative(xdr.ScVal.fromXDR(edge.node.topic3, "base64")), - from: scValToNative(xdr.ScVal.fromXDR(edge.node.topic2, "base64")), - amount: amountBigInt.toString(), - }; - }); + const transferFrom = await Promise.all( + transferFromEdges.map(async (edge) => { + const tokenDetails = await getTokenDetails( + pubKey, + edge.node.contractId, + network + ); + const amountBigInt = scValToNative( + xdr.ScVal.fromXDR(edge.node.data, "base64") + ) as BigInt; + return { + source_account: pubKey, + asset_issuer: edge.node.contractId, + asset_code: tokenDetails.symbol, + type: "invoke_host_function", + type_i: 24, + contractId: edge.node.contractId, + fnName: scValToNative(xdr.ScVal.fromXDR(edge.node.topic1, "base64")), + to: scValToNative(xdr.ScVal.fromXDR(edge.node.topic3, "base64")), + from: scValToNative(xdr.ScVal.fromXDR(edge.node.topic2, "base64")), + amount: amountBigInt.toString(), + }; + }) + ); - const transferTo = transferToEdges.map((edge) => { - const amountBigInt = scValToNative( - xdr.ScVal.fromXDR(edge.node.data, "base64") - ) as BigInt; - return { - contractId: edge.node.contractId, - to: scValToNative(xdr.ScVal.fromXDR(edge.node.topic3, "base64")), - from: scValToNative(xdr.ScVal.fromXDR(edge.node.topic2, "base64")), - amount: amountBigInt.toString(), - }; - }); + const transferTo = await Promise.all( + transferToEdges.map(async (edge) => { + const tokenDetails = await getTokenDetails( + pubKey, + edge.node.contractId, + network + ); + const amountBigInt = scValToNative( + xdr.ScVal.fromXDR(edge.node.data, "base64") + ) as BigInt; + return { + source_account: pubKey, + asset_issuer: edge.node.contractId, + asset_code: tokenDetails.symbol, + type: "invoke_host_function", + type_i: 24, + contractId: edge.node.contractId, + fnName: scValToNative(xdr.ScVal.fromXDR(edge.node.topic1, "base64")), + to: scValToNative(xdr.ScVal.fromXDR(edge.node.topic3, "base64")), + from: scValToNative(xdr.ScVal.fromXDR(edge.node.topic2, "base64")), + amount: amountBigInt.toString(), + }; + }) + ); - const mint = mintEdges.map((edge) => { - const amountBigInt = scValToNative( - xdr.ScVal.fromXDR(edge.node.data, "base64") - ) as BigInt; - return { - contractId: edge.node.contractId, - to: scValToNative(xdr.ScVal.fromXDR(edge.node.topic1, "base64")), - amount: amountBigInt.toString(), - }; - }); + const mint = await Promise.all( + mintEdges.map(async (edge) => { + const tokenDetails = await getTokenDetails( + pubKey, + edge.node.contractId, + network + ); + const amountBigInt = scValToNative( + xdr.ScVal.fromXDR(edge.node.data, "base64") + ) as BigInt; + return { + source_account: pubKey, + asset_issuer: edge.node.contractId, + asset_code: tokenDetails.symbol, + type: "invoke_host_function", + type_i: 24, + contractId: edge.node.contractId, + fnName: scValToNative(xdr.ScVal.fromXDR(edge.node.topic1, "base64")), + to: scValToNative(xdr.ScVal.fromXDR(edge.node.topic2, "base64")), + amount: amountBigInt.toString(), + }; + }) + ); const createAccount = createAccountEdges.map((edge) => ({ destination: edge.node.destination, diff --git a/src/service/mercury/index.ts b/src/service/mercury/index.ts index 23b31ac..673ef13 100644 --- a/src/service/mercury/index.ts +++ b/src/service/mercury/index.ts @@ -338,7 +338,7 @@ export class MercuryClient { } }; - getAccountHistory = async (pubKey: string) => { + getAccountHistory = async (pubKey: string, network: NetworkNames) => { try { const xdrPubKey = new Address(pubKey).toScVal().toXDR("base64"); const getData = async () => { @@ -354,9 +354,13 @@ export class MercuryClient { return data; }; const data = await this.renewAndRetry(getData); - return { - data: transformAccountHistory(data), + data: await transformAccountHistory( + data, + pubKey, + network, + this.tokenDetails + ), error: null, }; } catch (error) { From 9b80708308698aaebf65bd6cb7613681052d9b66 Mon Sep 17 00:00:00 2001 From: Aristides Staffieri Date: Wed, 22 Nov 2023 13:57:50 -0700 Subject: [PATCH 06/20] adds remaining classic side ops to history --- src/service/mercury/helpers/transformers.ts | 197 ++++++++++++++++++-- src/service/mercury/queries.ts | 103 ++++++++++ 2 files changed, 285 insertions(+), 15 deletions(-) diff --git a/src/service/mercury/helpers/transformers.ts b/src/service/mercury/helpers/transformers.ts index f099b04..01881e4 100644 --- a/src/service/mercury/helpers/transformers.ts +++ b/src/service/mercury/helpers/transformers.ts @@ -460,6 +460,102 @@ interface MercuryAccountHistory { }; }[]; }; + allowTrustByPublicKey: { + edges: { + node: { + authorize: boolean; + code: string; + source: string; + sourceMuxed: string; + trustor: string; + }; + }[]; + }; + manageDataByPublicKey: { + edges: { + node: { + dataName: string; + dataValue: string; + source: string; + sourceMuxed: string; + }; + }[]; + }; + beginSponsoringFutureReservesByPublicKey: { + edges: { + node: { + source: string; + sourceMuxed: string; + }; + }[]; + }; + endSponsoringFutureReservesByPublicKey: { + edges: { + node: { + source: string; + sourceMuxed: string; + }; + }[]; + }; + revokeSponsorshipByPublicKey: { + edges: { + node: { + source: string; + sourceMuxed: string; + sponsorship: string; + }; + }[]; + }; + clawbackByPublicKey: { + edges: { + node: { + amount: string; + asset: string; + assetNative: boolean; + from: string; + fromMuxed: string; + source: string; + sourceMuxed: string; + }; + }[]; + }; + setTrustLineFlagsByPublicKey: { + edges: { + node: { + asset: string; + assetNative: boolean; + clearFlags: boolean; + setFlags: boolean; + source: string; + sourceMuxed: string; + trustor: string; + }; + }[]; + }; + liquidityPoolDepositByPublicKey: { + edges: { + node: { + maxAmountA: string; + maxAmountB: string; + maxPriceD: string; + maxPriceN: string; + minPriceD: string; + source: string; + sourceMuxed: string; + }; + }[]; + }; + liquidityPoolWithdrawByPublicKey: { + edges: { + node: { + amount: string; + minAmountA: string; + minAmountB: string; + source: string; + sourceMuxed: string; + }; + }[]; + }; } const transformAccountHistory = async ( @@ -503,6 +599,24 @@ const transformAccountHistory = async ( rawResponse.data?.claimClaimableBalanceByPublicKey.edges || []; const createClaimableBalanceByPublicKeyEdges = rawResponse.data?.createClaimableBalanceByPublicKey.edges || []; + const allowTrustByPublicKeyEdges = + rawResponse.data?.allowTrustByPublicKey.edges || []; + const manageDataByPublicKeyEdges = + rawResponse.data?.manageDataByPublicKey.edges || []; + const beginSponsoringFutureReservesByPublicKeyEdges = + rawResponse.data?.beginSponsoringFutureReservesByPublicKey.edges || []; + const endSponsoringFutureReservesByPublicKeyEdges = + rawResponse.data?.endSponsoringFutureReservesByPublicKey.edges || []; + const revokeSponsorshipByPublicKeyEdges = + rawResponse.data?.revokeSponsorshipByPublicKey.edges || []; + const clawbackByPublicKeyEdges = + rawResponse.data?.clawbackByPublicKey.edges || []; + const setTrustLineFlagsByPublicKeyEdges = + rawResponse.data?.setTrustLineFlagsByPublicKey.edges || []; + const liquidityPoolDepositByPublicKeyEdges = + rawResponse.data?.liquidityPoolDepositByPublicKey.edges || []; + const liquidityPoolWithdrawByPublicKeyEdges = + rawResponse.data?.liquidityPoolWithdrawByPublicKey.edges || []; const transferFrom = await Promise.all( transferFromEdges.map(async (edge) => { @@ -653,26 +767,79 @@ const transformAccountHistory = async ( ...edge, })); + const allowTrustByPublicKey = allowTrustByPublicKeyEdges.map((edge) => ({ + ...edge, + })); + + const manageDataByPublicKey = manageDataByPublicKeyEdges.map((edge) => ({ + ...edge, + })); + + const beginSponsoringFutureReservesByPublicKey = + beginSponsoringFutureReservesByPublicKeyEdges.map((edge) => ({ + ...edge, + })); + + const endSponsoringFutureReservesByPublicKey = + endSponsoringFutureReservesByPublicKeyEdges.map((edge) => ({ + ...edge, + })); + + const revokeSponsorshipByPublicKey = revokeSponsorshipByPublicKeyEdges.map( + (edge) => ({ + ...edge, + }) + ); + + const clawbackByPublicKey = clawbackByPublicKeyEdges.map((edge) => ({ + ...edge, + })); + + const setTrustLineFlagsByPublicKey = setTrustLineFlagsByPublicKeyEdges.map( + (edge) => ({ + ...edge, + }) + ); + + const liquidityPoolDepositByPublicKey = + liquidityPoolDepositByPublicKeyEdges.map((edge) => ({ + ...edge, + })); + + const liquidityPoolWithdrawByPublicKey = + liquidityPoolWithdrawByPublicKeyEdges.map((edge) => ({ + ...edge, + })); + return [ - ...transferFrom, - ...mint, - ...transferTo, - ...createAccount, - ...createAccountTo, - ...paymentsByPublicKey, - ...paymentsToPublicKey, - ...pathPaymentsStrictSendByPublicKey, - ...pathPaymentsStrictSendToPublicKey, - ...pathPaymentsStrictReceiveByPublicKey, - ...pathPaymentsStrictReceiveToPublicKey, - ...manageBuyOfferByPublicKey, - ...manageSellOfferByPublicKey, - ...createPassiveSellOfferByPublicKey, - ...changeTrustByPublicKey, ...accountMergeByPublicKey, + ...allowTrustByPublicKey, + ...beginSponsoringFutureReservesByPublicKey, ...bumpSequenceByPublicKey, + ...changeTrustByPublicKey, ...claimClaimableBalanceByPublicKey, + ...clawbackByPublicKey, + ...createAccount, + ...createAccountTo, ...createClaimableBalanceByPublicKey, + ...createPassiveSellOfferByPublicKey, + ...endSponsoringFutureReservesByPublicKey, + ...liquidityPoolDepositByPublicKey, + ...liquidityPoolWithdrawByPublicKey, + ...manageBuyOfferByPublicKey, + ...manageDataByPublicKey, + ...manageSellOfferByPublicKey, + ...mint, + ...pathPaymentsStrictReceiveByPublicKey, + ...pathPaymentsStrictReceiveToPublicKey, + ...pathPaymentsStrictSendByPublicKey, + ...pathPaymentsStrictSendToPublicKey, + ...paymentsByPublicKey, + ...paymentsToPublicKey, + ...revokeSponsorshipByPublicKey, + ...setTrustLineFlagsByPublicKey, + ...transferFrom, + ...transferTo, ]; }; diff --git a/src/service/mercury/queries.ts b/src/service/mercury/queries.ts index ccdf556..bc2637c 100644 --- a/src/service/mercury/queries.ts +++ b/src/service/mercury/queries.ts @@ -534,7 +534,110 @@ export const query = { } } + allowTrustByPublicKey(publicKeyText: $pubKey) { + edges { + node { + authorize + code + source + sourceMuxed + trustor + } + } + } + + manageDataByPublicKey(publicKeyText: $pubKey) { + edges { + node { + dataName + dataValue + source + sourceMuxed + } + } + } + beginSponsoringFutureReservesByPublicKey(publicKeyText: $pubKey) { + edges { + node { + source + sourceMuxed + } + } + } + + endSponsoringFutureReservesByPublicKey(publicKeyText: $pubKey) { + edges { + node { + source + sourceMuxed + } + } + } + + revokeSponsorshipByPublicKey(publicKeyText: $pubKey) { + edges { + node { + source + sourceMuxed + sponsorship + } + } + } + + clawbackByPublicKey(publicKeyText: $pubKey) { + edges { + node { + amount + asset + assetNative + from + fromMuxed + source + sourceMuxed + } + } + } + + setTrustLineFlagsByPublicKey(publicKeyText: $pubKey) { + edges { + node { + asset + assetNative + clearFlags + setFlags + source + sourceMuxed + trustor + } + } + } + + liquidityPoolDepositByPublicKey(publicKeyText: $pubKey) { + edges { + node { + maxAmountA + maxAmountB + maxPriceD + maxPriceN + minPriceD + source + sourceMuxed + } + } + } + + liquidityPoolWithdrawByPublicKey(publicKeyText: $pubKey) { + edges { + node { + amount + minAmountA + minAmountB + source + sourceMuxed + } + } + } } `, From 9e735fff10cbe5d88e2bd42dd3fe1025ca1eebd9 Mon Sep 17 00:00:00 2001 From: Aristides Staffieri Date: Wed, 29 Nov 2023 10:32:41 -0700 Subject: [PATCH 07/20] updates test for new wuery patterns and transform patterns --- src/helper/test-helper.ts | 76 ++++++++++++++++++++- src/route/index.test.ts | 26 ++++++- src/service/mercury/helpers/transformers.ts | 14 +++- src/service/mercury/index.test.ts | 19 ++++-- src/service/mercury/index.ts | 14 ++-- 5 files changed, 133 insertions(+), 16 deletions(-) diff --git a/src/helper/test-helper.ts b/src/helper/test-helper.ts index a7b7341..7baa241 100644 --- a/src/helper/test-helper.ts +++ b/src/helper/test-helper.ts @@ -94,7 +94,13 @@ const queryMockResponse = { }, }, [query.getAccountHistory]: { - eventByContractId: { + transferFromEvent: { + edges: [], + }, + transferToEvent: { + edges: [], + }, + mintEvent: { edges: [], }, createAccountByPublicKey: { @@ -116,6 +122,11 @@ const queryMockResponse = { publickey: "CCWAMYJME4H5CKG7OLXGC2T4M6FL52XCZ3OQOAV6LL3GLA4RO4WH3ASP", }, + assetByAsset: { + code: "DT", + issuer: + "CCWAMYJME4H5CKG7OLXGC2T4M6FL52XCZ3OQOAV6LL3GLA4RO4WH3ASP", + }, accountByDestination: { publickey: pubKey, }, @@ -123,6 +134,69 @@ const queryMockResponse = { }, ], }, + pathPaymentsStrictSendByPublicKey: { + nodes: [], + }, + pathPaymentsStrictSendToPublicKey: { + nodes: [], + }, + pathPaymentsStrictReceiveByPublicKey: { + nodes: [], + }, + pathPaymentsStrictReceiveToPublicKey: { + nodes: [], + }, + manageBuyOfferByPublicKey: { + edges: [], + }, + manageSellOfferByPublicKey: { + edges: [], + }, + createPassiveSellOfferByPublicKey: { + nodes: [], + }, + changeTrustByPublicKey: { + nodes: [], + }, + accountMergeByPublicKey: { + edges: [], + }, + bumpSequenceByPublicKey: { + edges: [], + }, + claimClaimableBalanceByPublicKey: { + edges: [], + }, + createClaimableBalanceByPublicKey: { + edges: [], + }, + allowTrustByPublicKey: { + edges: [], + }, + manageDataByPublicKey: { + edges: [], + }, + beginSponsoringFutureReservesByPublicKey: { + edges: [], + }, + endSponsoringFutureReservesByPublicKey: { + edges: [], + }, + revokeSponsorshipByPublicKey: { + edges: [], + }, + clawbackByPublicKey: { + edges: [], + }, + setTrustLineFlagsByPublicKey: { + edges: [], + }, + liquidityPoolDepositByPublicKey: { + edges: [], + }, + liquidityPoolWithdrawByPublicKey: { + edges: [], + }, }, }; diff --git a/src/route/index.test.ts b/src/route/index.test.ts index 9c2b519..82fbf98 100644 --- a/src/route/index.test.ts +++ b/src/route/index.test.ts @@ -1,18 +1,38 @@ import { getDevServer, queryMockResponse, pubKey } from "../helper/test-helper"; +import { transformAccountHistory } from "../service/mercury/helpers/transformers"; import { query } from "../service/mercury/queries"; describe("API routes", () => { - describe.skip("/account-history/:pubKey", () => { + describe("/account-history/:pubKey", () => { it("can fetch an account history for a pub key", async () => { + const tokenDetails = { + CCWAMYJME4H5CKG7OLXGC2T4M6FL52XCZ3OQOAV6LL3GLA4RO4WH3ASP: { + name: "Test Token", + symbol: "TST", + decimals: 7, + }, + CBGTG7XFRY3L6OKAUTR6KGDKUXUQBX3YDJ3QFDYTGVMOM7VV4O7NCODG: { + name: "Test Token 2", + symbol: "TST", + decimals: 7, + }, + }; const server = await getDevServer(); const response = await fetch( `http://localhost:${ (server?.server?.address() as any).port }/api/v1/account-history/${pubKey}` ); - const { data } = await response.json(); + const data = await response.json(); expect(response.status).toEqual(200); - expect(data).toMatchObject(queryMockResponse[query.getAccountHistory]); + expect(data).toMatchObject( + transformAccountHistory( + { data: queryMockResponse[query.getAccountHistory] } as any, + pubKey, + "TESTNET", + tokenDetails as any + ) + ); server.close(); }); diff --git a/src/service/mercury/helpers/transformers.ts b/src/service/mercury/helpers/transformers.ts index e792259..04ebbcc 100644 --- a/src/service/mercury/helpers/transformers.ts +++ b/src/service/mercury/helpers/transformers.ts @@ -771,7 +771,12 @@ const transformAccountHistory = async ( const paymentsByPublicKey = paymentsByPublicKeyEdges.map( (edge) => ({ - ...edge.node, + from: edge.node.accountBySource.publickey, + to: edge.node.accountByDestination.publickey, + asset_type: undefined, // TODO, get asset type in Mercury + asset_code: edge.node.assetByAsset.code, + asset_issuer: edge.node.assetByAsset.code, + amount: edge.node.amount, } as Partial) ); @@ -780,7 +785,12 @@ const transformAccountHistory = async ( const paymentsToPublicKey = paymentsToPublicKeyEdges.map( (edge) => ({ - ...edge.node, + from: edge.node.accountBySource.publickey, + to: edge.node.accountByDestination.publickey, + asset_type: undefined, // TODO, get asset type in Mercury + asset_code: edge.node.assetByAsset.code, + asset_issuer: edge.node.assetByAsset.code, + amount: edge.node.amount, } as Partial) ); diff --git a/src/service/mercury/index.test.ts b/src/service/mercury/index.test.ts index 1680107..0c0809c 100644 --- a/src/service/mercury/index.test.ts +++ b/src/service/mercury/index.test.ts @@ -1,4 +1,4 @@ -import { scValToNative, xdr } from "stellar-sdk"; +import { Horizon, scValToNative, xdr } from "stellar-sdk"; import { mutation } from "./queries"; import { @@ -9,11 +9,18 @@ import { import { transformAccountBalances } from "./helpers/transformers"; describe("Mercury Service", () => { - it.skip("can fetch account history with a payment-to in history", async () => { - // const { data } = await mockMercuryClient.getAccountHistory(pubKey); - // const paymentsToPublicKey = data?.data.paymentsToPublicKey.edges[0].node; - // expect(paymentsToPublicKey.accountByDestination.publickey).toEqual(pubKey); - // expect(paymentsToPublicKey.amount).toBe("50000000"); + it("can fetch account history with a payment-to in history", async () => { + const { data } = await mockMercuryClient.getAccountHistory( + pubKey, + "TESTNET" + ); + const payment = data.data?.find((d) => { + if ("asset_code" in d && d.asset_code === "DT") { + return true; + } + return false; + }) as Partial; + expect(payment.amount).toEqual("50000000"); }); it("can build a balance ledger key for a pub key", async () => { diff --git a/src/service/mercury/index.ts b/src/service/mercury/index.ts index 42a0615..088b279 100644 --- a/src/service/mercury/index.ts +++ b/src/service/mercury/index.ts @@ -423,11 +423,17 @@ export class MercuryClient { getAccountHistory = async (pubKey: string, network: NetworkNames) => { if (hasIndexerSupport(network)) { - const res = await this.getAccountHistoryMercury(pubKey, network); - return res; + const data = await this.getAccountHistoryMercury(pubKey, network); + return { + data, + error: null, + }; } else { - const res = await this.getAccountHistoryHorizon(pubKey, network); - return res; + const data = await this.getAccountHistoryHorizon(pubKey, network); + return { + data, + error: null, + }; } }; From a672def5be45c9256855731b3673d298ce4424c5 Mon Sep 17 00:00:00 2001 From: Aristides Staffieri Date: Wed, 29 Nov 2023 13:13:38 -0700 Subject: [PATCH 08/20] use symbols instead of strings when subscribing to token events --- src/service/mercury/index.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/service/mercury/index.ts b/src/service/mercury/index.ts index 088b279..99a1271 100644 --- a/src/service/mercury/index.ts +++ b/src/service/mercury/index.ts @@ -172,19 +172,19 @@ export class MercuryClient { const transferToSub = { contract_id: contractId, max_single_size: 200, - topic1: nativeToScVal("transfer").toXDR("base64"), - topic2: nativeToScVal(pubKey).toXDR("base64"), + topic1: xdr.ScVal.scvSymbol("transfer").toXDR("base64"), + topic2: xdr.ScVal.scvSymbol(pubKey).toXDR("base64"), }; const transferFromSub = { contract_id: contractId, max_single_size: 200, - topic1: nativeToScVal("transfer").toXDR("base64"), - topic3: nativeToScVal(pubKey).toXDR("base64"), + topic1: xdr.ScVal.scvSymbol("transfer").toXDR("base64"), + topic3: xdr.ScVal.scvSymbol(pubKey).toXDR("base64"), }; const mintSub = { contract_id: contractId, max_single_size: 200, - topic1: nativeToScVal("mint").toXDR("base64"), + topic1: xdr.ScVal.scvSymbol("mint").toXDR("base64"), }; try { From 3b076c8baa853660755fa02b74f0cbbdddd0cd18 Mon Sep 17 00:00:00 2001 From: Aristides Staffieri Date: Wed, 29 Nov 2023 13:14:20 -0700 Subject: [PATCH 09/20] removes unused import --- src/service/mercury/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/service/mercury/index.ts b/src/service/mercury/index.ts index 99a1271..e377c7c 100644 --- a/src/service/mercury/index.ts +++ b/src/service/mercury/index.ts @@ -1,7 +1,7 @@ import { Client, CombinedError, fetchExchange } from "@urql/core"; import axios from "axios"; import { Logger } from "pino"; -import { Address, Horizon, Networks, nativeToScVal, xdr } from "stellar-sdk"; +import { Address, Horizon, Networks, xdr } from "stellar-sdk"; import { Redis } from "ioredis"; import BigNumber from "bignumber.js"; From 476d03dab99ed38f8decdee0a4b9d840067624ef Mon Sep 17 00:00:00 2001 From: Aristides Staffieri Date: Wed, 29 Nov 2023 13:43:07 -0700 Subject: [PATCH 10/20] add formatter to xlm balance in transformer to match horizon schema --- src/helper/format.ts | 21 +++++++++++++++++++++ src/service/mercury/helpers/transformers.ts | 3 ++- 2 files changed, 23 insertions(+), 1 deletion(-) create mode 100644 src/helper/format.ts diff --git a/src/helper/format.ts b/src/helper/format.ts new file mode 100644 index 0000000..b0a61ae --- /dev/null +++ b/src/helper/format.ts @@ -0,0 +1,21 @@ +import BigNumber from "bignumber.js"; + +// Adopted from https://github.com/ethers-io/ethers.js/blob/master/packages/bignumber/src.ts/fixednumber.ts#L27 +export const formatTokenAmount = (amount: BigNumber, decimals: number) => { + let formatted = amount.toString(); + + if (decimals > 0) { + formatted = amount.shiftedBy(-decimals).toFixed(decimals).toString(); + + // Trim trailing zeros + while (formatted[formatted.length - 1] === "0") { + formatted = formatted.substring(0, formatted.length - 1); + } + + if (formatted.endsWith(".")) { + formatted = formatted.substring(0, formatted.length - 1); + } + } + + return formatted; +}; diff --git a/src/service/mercury/helpers/transformers.ts b/src/service/mercury/helpers/transformers.ts index 942f220..b4c3412 100644 --- a/src/service/mercury/helpers/transformers.ts +++ b/src/service/mercury/helpers/transformers.ts @@ -6,6 +6,7 @@ import { BASE_RESERVE_MIN_COUNT, NativeBalance, } from "../../../helper/horizon-rpc"; +import { formatTokenAmount } from "../../../helper/format"; type NetworkNames = keyof typeof Networks; @@ -82,7 +83,7 @@ const transformAccountBalances = async ( const accountBalance = { native: { token: { type: "native", code: "XLM" }, - total: new BigNumber(accountObject.nativeBalance), + total: formatTokenAmount(new BigNumber(accountObject.nativeBalance), 7), available: new BigNumber(BASE_RESERVE_MIN_COUNT) .plus(accountObject.numSubEntries) .plus(numSponsoring) From 6441b535c6a8c49591bb5275666af330f68fd791 Mon Sep 17 00:00:00 2001 From: Aristides Staffieri Date: Thu, 30 Nov 2023 09:28:18 -0700 Subject: [PATCH 11/20] remove decimals for classic balances, clients assume 7 currently --- src/service/mercury/helpers/transformers.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/service/mercury/helpers/transformers.ts b/src/service/mercury/helpers/transformers.ts index b4c3412..0404ed9 100644 --- a/src/service/mercury/helpers/transformers.ts +++ b/src/service/mercury/helpers/transformers.ts @@ -130,7 +130,6 @@ const transformAccountBalances = async ( key: curr.assetByAsset.issuer, }, }, - decimals: "7", total: new BigNumber(curr.balance), available: new BigNumber(curr.balance), }; From 28c62fe0d5763b31934adc7c4771bd07f4f96a4f Mon Sep 17 00:00:00 2001 From: Aristides Staffieri Date: Thu, 30 Nov 2023 09:50:10 -0700 Subject: [PATCH 12/20] uses ascii version of asset code for composite key in map --- src/service/mercury/helpers/transformers.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/service/mercury/helpers/transformers.ts b/src/service/mercury/helpers/transformers.ts index 0404ed9..2481e0e 100644 --- a/src/service/mercury/helpers/transformers.ts +++ b/src/service/mercury/helpers/transformers.ts @@ -123,7 +123,7 @@ const transformAccountBalances = async ( curr.assetByAsset.code.substring(2), "hex" ).toString("utf8"); - prev[`${curr.assetByAsset.code}:${curr.assetByAsset.issuer}`] = { + prev[`${codeAscii}:${curr.assetByAsset.issuer}`] = { token: { code: codeAscii, issuer: { From 1321caed13e716f0793145f913f9ac4b7eda8690 Mon Sep 17 00:00:00 2001 From: Aristides Staffieri Date: Thu, 30 Nov 2023 10:51:07 -0700 Subject: [PATCH 13/20] tweaks response for balances when asset is classic side --- src/service/mercury/helpers/transformers.ts | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/service/mercury/helpers/transformers.ts b/src/service/mercury/helpers/transformers.ts index 2481e0e..6282c3a 100644 --- a/src/service/mercury/helpers/transformers.ts +++ b/src/service/mercury/helpers/transformers.ts @@ -202,7 +202,7 @@ interface MercuryAccountHistory { assetByAsset: { code: string; issuer: string; - }; + } | null; accountBySource: { publickey: string; }; @@ -220,7 +220,7 @@ interface MercuryAccountHistory { assetByAsset: { code: string; issuer: string; - }; + } | null; accountBySource: { publickey: string; }; @@ -758,6 +758,7 @@ const transformAccountHistory = async ( (edge) => ({ destination: edge.node.destination, + starting_balance: "", // TODO: need from Mercury } as Partial) ); @@ -778,8 +779,8 @@ const transformAccountHistory = async ( from: edge.node.accountBySource.publickey, to: edge.node.accountByDestination.publickey, asset_type: undefined, // TODO, get asset type in Mercury - asset_code: edge.node.assetByAsset.code, - asset_issuer: edge.node.assetByAsset.code, + asset_code: edge.node.assetByAsset?.code, + asset_issuer: edge.node.assetByAsset?.code, amount: edge.node.amount, } as Partial) ); @@ -792,8 +793,8 @@ const transformAccountHistory = async ( from: edge.node.accountBySource.publickey, to: edge.node.accountByDestination.publickey, asset_type: undefined, // TODO, get asset type in Mercury - asset_code: edge.node.assetByAsset.code, - asset_issuer: edge.node.assetByAsset.code, + asset_code: edge.node.assetByAsset?.code, + asset_issuer: edge.node.assetByAsset?.code, amount: edge.node.amount, } as Partial) ); From e8b6c6b0cfc76e9aaec4c4b2938985f525790b0b Mon Sep 17 00:00:00 2001 From: Aristides Staffieri Date: Thu, 30 Nov 2023 14:33:48 -0700 Subject: [PATCH 14/20] adds starting balance to payments queries --- src/service/mercury/helpers/transformers.ts | 5 ++++- src/service/mercury/queries.ts | 4 ++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/src/service/mercury/helpers/transformers.ts b/src/service/mercury/helpers/transformers.ts index 6282c3a..3009979 100644 --- a/src/service/mercury/helpers/transformers.ts +++ b/src/service/mercury/helpers/transformers.ts @@ -184,6 +184,7 @@ interface MercuryAccountHistory { edges: { node: { destination: string; + startingBalance: string; }; }[]; }; @@ -191,6 +192,7 @@ interface MercuryAccountHistory { edges: { node: { destination: string; + startingBalance: string; }; }[]; }; @@ -758,7 +760,7 @@ const transformAccountHistory = async ( (edge) => ({ destination: edge.node.destination, - starting_balance: "", // TODO: need from Mercury + starting_balance: edge.node.startingBalance, } as Partial) ); @@ -768,6 +770,7 @@ const transformAccountHistory = async ( (edge) => ({ destination: edge.node.destination, + starting_balance: edge.node.startingBalance, } as Partial) ); diff --git a/src/service/mercury/queries.ts b/src/service/mercury/queries.ts index 73ecff5..14ee9ce 100644 --- a/src/service/mercury/queries.ts +++ b/src/service/mercury/queries.ts @@ -115,6 +115,8 @@ export const query = { edges { node { destination + startingBalance + } } } @@ -122,6 +124,8 @@ export const query = { edges { node { destination + startingBalance + } } } From ecaa84c4eb990116b0e0647a154f3883e8c0d22e Mon Sep 17 00:00:00 2001 From: Aristides Staffieri Date: Fri, 1 Dec 2023 11:02:57 -0700 Subject: [PATCH 15/20] adds asset type, asset type interface, and balance type to tx history --- src/helper/horizon-rpc.ts | 10 ++++ src/service/mercury/helpers/transformers.ts | 55 ++++++++++++++++++++- 2 files changed, 63 insertions(+), 2 deletions(-) diff --git a/src/helper/horizon-rpc.ts b/src/helper/horizon-rpc.ts index 5a0580d..221b4be 100644 --- a/src/helper/horizon-rpc.ts +++ b/src/helper/horizon-rpc.ts @@ -76,6 +76,16 @@ export function getBalanceIdentifier( } } +export function getAssetType(code?: string) { + if (!code) { + return "native"; + } + if (code.length > 4) { + return "credit_alphanum12"; + } + return "credit_alphanum4"; +} + export const makeDisplayableBalances = ( accountDetails: Horizon.ServerApi.AccountRecord ) => { diff --git a/src/service/mercury/helpers/transformers.ts b/src/service/mercury/helpers/transformers.ts index 3009979..8c3d331 100644 --- a/src/service/mercury/helpers/transformers.ts +++ b/src/service/mercury/helpers/transformers.ts @@ -5,6 +5,7 @@ import { BASE_RESERVE, BASE_RESERVE_MIN_COUNT, NativeBalance, + getAssetType, } from "../../../helper/horizon-rpc"; import { formatTokenAmount } from "../../../helper/format"; @@ -761,6 +762,8 @@ const transformAccountHistory = async ( ({ destination: edge.node.destination, starting_balance: edge.node.startingBalance, + type: "create_account", + type_i: 0, } as Partial) ); @@ -771,6 +774,8 @@ const transformAccountHistory = async ( ({ destination: edge.node.destination, starting_balance: edge.node.startingBalance, + type: "create_account", + type_i: 0, } as Partial) ); @@ -781,10 +786,12 @@ const transformAccountHistory = async ( ({ from: edge.node.accountBySource.publickey, to: edge.node.accountByDestination.publickey, - asset_type: undefined, // TODO, get asset type in Mercury + asset_type: getAssetType(edge.node.assetByAsset?.code), asset_code: edge.node.assetByAsset?.code, asset_issuer: edge.node.assetByAsset?.code, amount: edge.node.amount, + type: "payment", + type_i: 1, } as Partial) ); @@ -795,10 +802,12 @@ const transformAccountHistory = async ( ({ from: edge.node.accountBySource.publickey, to: edge.node.accountByDestination.publickey, - asset_type: undefined, // TODO, get asset type in Mercury + asset_type: getAssetType(edge.node.assetByAsset?.code), asset_code: edge.node.assetByAsset?.code, asset_issuer: edge.node.assetByAsset?.code, amount: edge.node.amount, + type: "payment", + type_i: 1, } as Partial) ); @@ -809,6 +818,8 @@ const transformAccountHistory = async ( (edge) => ({ ...edge, + type: "path_payment_strict_send", + type_i: 13, } as Partial) ); @@ -819,6 +830,8 @@ const transformAccountHistory = async ( (edge) => ({ ...edge, + type: "path_payment_strict_send", + type_i: 13, } as Partial) ); @@ -829,6 +842,8 @@ const transformAccountHistory = async ( (edge) => ({ ...edge, + type: "path_payment_strict_receive", + type_i: 2, } as Partial) ); @@ -839,6 +854,8 @@ const transformAccountHistory = async ( (edge) => ({ ...edge, + type: "path_payment_strict_receive", + type_i: 2, } as Partial) ); @@ -848,6 +865,8 @@ const transformAccountHistory = async ( (edge) => ({ ...edge.node, + type: "manage_sell_offer", + type_i: 4, } as Partial) ); @@ -857,6 +876,8 @@ const transformAccountHistory = async ( (edge) => ({ ...edge.node, + type: "manage_sell_offer", + type_i: 4, } as Partial) ); @@ -867,6 +888,8 @@ const transformAccountHistory = async ( (edge) => ({ ...edge, + type: "create_passive_sell_offer", + type_i: 3, } as Partial) ); @@ -876,6 +899,8 @@ const transformAccountHistory = async ( (edge) => ({ ...edge, + type: "change_trust", + type_i: 6, } as Partial) ); @@ -885,6 +910,8 @@ const transformAccountHistory = async ( (edge) => ({ ...edge.node, + type: "account_merge", + type_i: 8, } as Partial) ); @@ -894,6 +921,8 @@ const transformAccountHistory = async ( (edge) => ({ ...edge.node, + type: "bump_sequence", + type_i: 11, } as Partial) ); @@ -904,6 +933,8 @@ const transformAccountHistory = async ( (edge) => ({ ...edge.node, + type: "claim_claimable_balance", + type_i: 15, } as Partial) ); @@ -914,6 +945,8 @@ const transformAccountHistory = async ( (edge) => ({ ...edge.node, + type: "create_claimable_balance", + type_i: 14, } as Partial) ); @@ -923,6 +956,8 @@ const transformAccountHistory = async ( (edge) => ({ ...edge.node, + type: "allow_trust", + type_i: 7, } as Partial) ); @@ -932,6 +967,8 @@ const transformAccountHistory = async ( (edge) => ({ ...edge.node, + type: "manage_data", + type_i: 10, } as Partial) ); @@ -942,6 +979,8 @@ const transformAccountHistory = async ( (edge) => ({ ...edge.node, + type: "begin_sponsoring_future_reserves", + type_i: 16, } as Partial) ); @@ -952,6 +991,8 @@ const transformAccountHistory = async ( (edge) => ({ ...edge.node, + type: "end_sponsoring_future_reserves", + type_i: 17, } as Partial) ); @@ -961,6 +1002,8 @@ const transformAccountHistory = async ( (edge) => ({ ...edge.node, + type: "revoke_sponsorship", + type_i: 18, } as Partial) ); @@ -970,6 +1013,8 @@ const transformAccountHistory = async ( (edge) => ({ ...edge.node, + type: "clawback", + type_i: 19, } as Partial) ); @@ -979,6 +1024,8 @@ const transformAccountHistory = async ( (edge) => ({ ...edge.node, + type: "set_trust_line_flags", + type_i: 21, } as Partial) ); @@ -989,6 +1036,8 @@ const transformAccountHistory = async ( (edge) => ({ ...edge.node, + type: "liquidity_pool_deposit", + type_i: 22, } as Partial) ); @@ -999,6 +1048,8 @@ const transformAccountHistory = async ( (edge) => ({ ...edge.node, + type: "liquidity_pool_withdraw", + type_i: 23, } as Partial) ); From 51e5f4e70ca0ae620c22a769c250752b2def353c Mon Sep 17 00:00:00 2001 From: Aristides Staffieri Date: Tue, 5 Dec 2023 08:59:57 -0700 Subject: [PATCH 16/20] adds network option to all routes, updates test --- src/helper/soroban-rpc.ts | 3 +- src/helper/validate.ts | 10 +++- src/route/index.test.ts | 3 +- src/route/index.ts | 63 ++++++++++++++++----- src/service/mercury/helpers/transformers.ts | 5 +- src/service/mercury/index.ts | 26 +++++++-- 6 files changed, 83 insertions(+), 27 deletions(-) diff --git a/src/helper/soroban-rpc.ts b/src/helper/soroban-rpc.ts index afa6086..d23d694 100644 --- a/src/helper/soroban-rpc.ts +++ b/src/helper/soroban-rpc.ts @@ -12,8 +12,7 @@ import { xdr, SorobanRpc, } from "stellar-sdk"; - -type NetworkNames = keyof typeof Networks; +import { NetworkNames } from "./validate"; const SOROBAN_RPC_URLS: { [key in keyof typeof Networks]?: string } = { TESTNET: "https://soroban-testnet.stellar.org/", diff --git a/src/helper/validate.ts b/src/helper/validate.ts index d760c96..cf8a012 100644 --- a/src/helper/validate.ts +++ b/src/helper/validate.ts @@ -1,4 +1,6 @@ -import { StrKey } from "stellar-sdk"; +import { Networks, StrKey } from "stellar-sdk"; + +export type NetworkNames = keyof typeof Networks; const isContractId = (contractId: string) => { try { @@ -18,4 +20,8 @@ const isPubKey = (pubKey: string) => { } }; -export { isContractId, isPubKey }; +const isNetwork = (network: string): network is NetworkNames => { + return Object.keys(Networks).includes(network); +}; + +export { isContractId, isPubKey, isNetwork }; diff --git a/src/route/index.test.ts b/src/route/index.test.ts index 82fbf98..b9d694c 100644 --- a/src/route/index.test.ts +++ b/src/route/index.test.ts @@ -55,7 +55,7 @@ describe("API routes", () => { const response = await fetch( `http://localhost:${ (server?.server?.address() as any).port - }/api/v1/account-balances/${pubKey}?contract_ids=CCWAMYJME4H5CKG7OLXGC2T4M6FL52XCZ3OQOAV6LL3GLA4RO4WH3ASP` + }/api/v1/account-balances/${pubKey}?contract_ids=CCWAMYJME4H5CKG7OLXGC2T4M6FL52XCZ3OQOAV6LL3GLA4RO4WH3ASP&network=TESTNET` ); expect(response.status).toEqual(200); server.close(); @@ -67,6 +67,7 @@ describe("API routes", () => { "CCWAMYJME4H5CKG7OLXGC2T4M6FL52XCZ3OQOAV6LL3GLA4RO4WH3ASP", "CBGTG7XFRY3L6OKAUTR6KGDKUXUQBX3YDJ3QFDYTGVMOM7VV4O7NCODG", ], + network: "TESTNET", }; const server = await getDevServer(); const response = await fetch( diff --git a/src/route/index.ts b/src/route/index.ts index 317d326..b2f05a1 100644 --- a/src/route/index.ts +++ b/src/route/index.ts @@ -6,10 +6,14 @@ import { Redis } from "ioredis"; import { MercuryClient } from "../service/mercury"; import { ajv } from "./validators"; -import { isContractId, isPubKey } from "../helper/validate"; +import { + isContractId, + isPubKey, + isNetwork, + NetworkNames, +} from "../helper/validate"; const API_VERSION = "v1"; -const NETWORK = "TESTNET"; // hardcode testnet for now, not sure how Mercury will change the schema for multi-network support yet export function initApiServer( mercuryClient: MercuryClient, @@ -49,17 +53,25 @@ export function initApiServer( validator: (qStr: string) => isPubKey(qStr), }, }, + querystring: { + ["network"]: { + type: "string", + validator: (qStr: string) => isNetwork(qStr), + }, + }, }, handler: async ( request: FastifyRequest<{ Params: { ["pubKey"]: string }; + Querystring: { ["network"]: NetworkNames }; }>, reply ) => { const pubKey = request.params["pubKey"]; + const network = request.query["network"]; const { data, error } = await mercuryClient.getAccountHistory( pubKey, - NETWORK + network ); if (error) { reply.code(400).send(error); @@ -84,23 +96,31 @@ export function initApiServer( type: "string", validator: (qStr: string) => qStr.split(",").every(isContractId), }, + ["network"]: { + type: "string", + validator: (qStr: string) => isNetwork(qStr), + }, }, }, handler: async ( request: FastifyRequest<{ Params: { ["pubKey"]: string }; - Querystring: { ["contract_ids"]: string }; + Querystring: { + ["contract_ids"]: string; + ["network"]: NetworkNames; + }; }>, reply ) => { const pubKey = request.params["pubKey"]; + const network = request.query["network"]; const contractIds = request.query["contract_ids"] ? request.query["contract_ids"].split(",") : []; const { data, error } = await mercuryClient.getAccountBalances( pubKey, contractIds, - NETWORK + network ); if (error) { reply.code(400).send(error); @@ -119,6 +139,7 @@ export function initApiServer( properties: { contract_id: { type: "string" }, pub_key: { type: "string" }, + network: { type: "string" }, }, }, response: { @@ -132,14 +153,19 @@ export function initApiServer( }, handler: async ( request: FastifyRequest<{ - Body: { contract_id: string; pub_key: string }; + Body: { + contract_id: string; + pub_key: string; + network: NetworkNames; + }; }>, reply ) => { - const { contract_id, pub_key } = request.body; + const { contract_id, pub_key, network } = request.body; const { data, error } = await mercuryClient.tokenSubscription( contract_id, - pub_key + pub_key, + network ); if (error) { reply.code(400).send(error); @@ -157,6 +183,7 @@ export function initApiServer( type: "object", properties: { pub_key: { type: "string" }, + network: { type: "string" }, }, }, response: { @@ -169,12 +196,15 @@ export function initApiServer( }, }, handler: async ( - request: FastifyRequest<{ Body: { pub_key: string } }>, + request: FastifyRequest<{ + Body: { pub_key: string; network: NetworkNames }; + }>, reply ) => { - const { pub_key } = request.body; + const { pub_key, network } = request.body; const { data, error } = await mercuryClient.accountSubscription( - pub_key + pub_key, + network ); if (error) { reply.code(400).send(error); @@ -193,6 +223,7 @@ export function initApiServer( properties: { contract_id: { type: "string" }, pub_key: { type: "string" }, + network: { type: "string" }, }, }, response: { @@ -206,15 +237,19 @@ export function initApiServer( }, handler: async ( request: FastifyRequest<{ - Body: { pub_key: string; contract_id: string }; + Body: { + pub_key: string; + contract_id: string; + network: NetworkNames; + }; }>, reply ) => { - const { pub_key, contract_id } = request.body; + const { pub_key, contract_id, network } = request.body; const { data, error } = await mercuryClient.tokenBalanceSubscription( contract_id, pub_key, - NETWORK + network ); if (error) { reply.code(400).send(error); diff --git a/src/service/mercury/helpers/transformers.ts b/src/service/mercury/helpers/transformers.ts index 8c3d331..37ff2b9 100644 --- a/src/service/mercury/helpers/transformers.ts +++ b/src/service/mercury/helpers/transformers.ts @@ -1,5 +1,5 @@ import { OperationResult } from "@urql/core"; -import { Horizon, Networks, scValToNative, xdr } from "stellar-sdk"; +import { Horizon, scValToNative, xdr } from "stellar-sdk"; import BigNumber from "bignumber.js"; import { BASE_RESERVE, @@ -8,8 +8,7 @@ import { getAssetType, } from "../../../helper/horizon-rpc"; import { formatTokenAmount } from "../../../helper/format"; - -type NetworkNames = keyof typeof Networks; +import { NetworkNames } from "../../../helper/validate"; // Transformers take an API response, and transform it/augment it for frontend consumption diff --git a/src/service/mercury/index.ts b/src/service/mercury/index.ts index e377c7c..395a73c 100644 --- a/src/service/mercury/index.ts +++ b/src/service/mercury/index.ts @@ -1,7 +1,7 @@ import { Client, CombinedError, fetchExchange } from "@urql/core"; import axios from "axios"; import { Logger } from "pino"; -import { Address, Horizon, Networks, xdr } from "stellar-sdk"; +import { Address, Horizon, xdr } from "stellar-sdk"; import { Redis } from "ioredis"; import BigNumber from "bignumber.js"; @@ -22,8 +22,7 @@ import { fetchAccountDetails, fetchAccountHistory, } from "../../helper/horizon-rpc"; - -type NetworkNames = keyof typeof Networks; +import { NetworkNames } from "../../helper/validate"; enum NETWORK_URLS { PUBLIC = "https://horizon.stellar.org", @@ -167,7 +166,17 @@ export class MercuryClient { } }; - tokenSubscription = async (contractId: string, pubKey: string) => { + tokenSubscription = async ( + contractId: string, + pubKey: string, + network: NetworkNames + ) => { + if (!hasIndexerSupport(network)) { + return { + data: null, + error: `network not currently supported: ${network}`, + }; + } // Token transfer topics are - 1: transfer, 2: from, 3: to, 4: assetName, data(amount) const transferToSub = { contract_id: contractId, @@ -239,7 +248,14 @@ export class MercuryClient { } }; - accountSubscription = async (pubKey: string) => { + accountSubscription = async (pubKey: string, network: NetworkNames) => { + if (!hasIndexerSupport(network)) { + return { + data: null, + error: `network not currently supported: ${network}`, + }; + } + try { const subscribe = async () => { const config = { From 45fccf8128de7f89c804848944cd9c177956b0e5 Mon Sep 17 00:00:00 2001 From: Aristides Staffieri Date: Tue, 5 Dec 2023 09:46:30 -0700 Subject: [PATCH 17/20] fixes bad logic in fallback horizon response builder --- src/service/mercury/index.ts | 44 +++++++++++++++++++++++++++--------- 1 file changed, 33 insertions(+), 11 deletions(-) diff --git a/src/service/mercury/index.ts b/src/service/mercury/index.ts index 395a73c..58e45d6 100644 --- a/src/service/mercury/index.ts +++ b/src/service/mercury/index.ts @@ -485,7 +485,7 @@ export class MercuryClient { available: new BigNumber(balance.balance), }; } - return balances; + return balanceMap; }; getAccountBalancesHorizon = async (pubKey: string, network: NetworkNames) => { @@ -500,6 +500,8 @@ export class MercuryClient { allowHttp: !networkUrl.includes("https"), }); const resp = await fetchAccountDetails(pubKey, server); + balances = resp.balances; + subentryCount = resp.subentryCount; for (let i = 0; i < Object.keys(resp.balances).length; i++) { const k = Object.keys(resp.balances)[i]; @@ -595,15 +597,33 @@ export class MercuryClient { error: null, }; } else { - const classicBalances = await this.getAccountBalancesHorizon( - pubKey, - network - ); - const tokenBalances = await this.getTokenBalancesSorobanRPC( - pubKey, - contractIds, - network - ); + let tokenBalances = {}; + let classicBalances = { + balances: [], + isFunded: false, + subentryCount: 0, + }; + try { + classicBalances = await this.getAccountBalancesHorizon(pubKey, network); + } catch (error) { + this.logger.error(error); + this.logger.error( + `failed to fetch token classic balances from Horizon: ${pubKey}, ${network}` + ); + } + + try { + tokenBalances = await this.getTokenBalancesSorobanRPC( + pubKey, + contractIds, + network + ); + } catch (error) { + this.logger.error(error); + this.logger.error( + `failed to fetch token token balances from Soroban RPC: ${pubKey}, ${network}` + ); + } const data = { balances: { @@ -614,7 +634,9 @@ export class MercuryClient { subentryCount: classicBalances.subentryCount, }; return { - data, + data: { + data, + }, error: null, }; } From ce46724b35804ae35e3780f56c90bcc180227230 Mon Sep 17 00:00:00 2001 From: Aristides Staffieri Date: Wed, 6 Dec 2023 16:37:43 -0800 Subject: [PATCH 18/20] removes extra log in service --- src/service/mercury/index.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/service/mercury/index.ts b/src/service/mercury/index.ts index 58e45d6..b9c5954 100644 --- a/src/service/mercury/index.ts +++ b/src/service/mercury/index.ts @@ -394,7 +394,6 @@ export class MercuryClient { } catch (error) { this.logger.error(error); const _error = JSON.stringify(error); - this.logger.error(_error); return { data: null, error: _error, From 88eded115f79415e7e3de0c7d7c64de5795059d5 Mon Sep 17 00:00:00 2001 From: Aristides Staffieri Date: Wed, 13 Dec 2023 10:00:13 -0700 Subject: [PATCH 19/20] adds txInfoByTx to all classic side op queries in history --- src/service/mercury/queries.ts | 152 ++++++++++++++++++++++++++++++++- 1 file changed, 150 insertions(+), 2 deletions(-) diff --git a/src/service/mercury/queries.ts b/src/service/mercury/queries.ts index 14ee9ce..c53e95b 100644 --- a/src/service/mercury/queries.ts +++ b/src/service/mercury/queries.ts @@ -116,7 +116,12 @@ export const query = { node { destination startingBalance - + txInfoByTx { + fee + opCount + txHash + ledger + } } } } @@ -125,7 +130,12 @@ export const query = { node { destination startingBalance - + txInfoByTx { + fee + opCount + txHash + ledger + } } } } @@ -144,6 +154,12 @@ export const query = { accountByDestination { publickey } + txInfoByTx { + fee + opCount + txHash + ledger + } } } } @@ -162,6 +178,12 @@ export const query = { accountByDestination { publickey } + txInfoByTx { + fee + opCount + txHash + ledger + } } } } @@ -215,6 +237,12 @@ export const query = { path5Native sendAmount sendAssetNative + txInfoByTx { + fee + opCount + txHash + ledger + } } } @@ -267,6 +295,12 @@ export const query = { path5Native sendAmount sendAssetNative + txInfoByTx { + fee + opCount + txHash + ledger + } } } @@ -319,6 +353,12 @@ export const query = { sendAssetNative destAmount sendMax + txInfoByTx { + fee + opCount + txHash + ledger + } } } @@ -371,6 +411,12 @@ export const query = { sendAssetNative destAmount sendMax + txInfoByTx { + fee + opCount + txHash + ledger + } } } @@ -403,6 +449,12 @@ export const query = { priceD priceN sellingNative + txInfoByTx { + fee + opCount + txHash + ledger + } } } } @@ -436,6 +488,12 @@ export const query = { priceD priceN sellingNative + txInfoByTx { + fee + opCount + txHash + ledger + } } } } @@ -466,6 +524,12 @@ export const query = { priceD priceN sellingNative + txInfoByTx { + fee + opCount + txHash + ledger + } } } @@ -493,6 +557,12 @@ export const query = { } fee } + txInfoByTx { + fee + opCount + txHash + ledger + } } } @@ -503,6 +573,12 @@ export const query = { destinationMuxed source sourceMuxed + txInfoByTx { + fee + opCount + txHash + ledger + } } } } @@ -513,6 +589,12 @@ export const query = { source sourceMuxed bumpTo + txInfoByTx { + fee + opCount + txHash + ledger + } } } } @@ -523,6 +605,12 @@ export const query = { source sourceMuxed balanceId + txInfoByTx { + fee + opCount + txHash + ledger + } } } } @@ -535,6 +623,12 @@ export const query = { assetNative source sourceMuxed + txInfoByTx { + fee + opCount + txHash + ledger + } } } } @@ -547,6 +641,12 @@ export const query = { source sourceMuxed trustor + txInfoByTx { + fee + opCount + txHash + ledger + } } } } @@ -558,6 +658,12 @@ export const query = { dataValue source sourceMuxed + txInfoByTx { + fee + opCount + txHash + ledger + } } } } @@ -567,6 +673,12 @@ export const query = { node { source sourceMuxed + txInfoByTx { + fee + opCount + txHash + ledger + } } } } @@ -576,6 +688,12 @@ export const query = { node { source sourceMuxed + txInfoByTx { + fee + opCount + txHash + ledger + } } } } @@ -586,6 +704,12 @@ export const query = { source sourceMuxed sponsorship + txInfoByTx { + fee + opCount + txHash + ledger + } } } } @@ -600,6 +724,12 @@ export const query = { fromMuxed source sourceMuxed + txInfoByTx { + fee + opCount + txHash + ledger + } } } } @@ -614,6 +744,12 @@ export const query = { source sourceMuxed trustor + txInfoByTx { + fee + opCount + txHash + ledger + } } } } @@ -628,6 +764,12 @@ export const query = { minPriceD source sourceMuxed + txInfoByTx { + fee + opCount + txHash + ledger + } } } } @@ -640,6 +782,12 @@ export const query = { minAmountB source sourceMuxed + txInfoByTx { + fee + opCount + txHash + ledger + } } } } From 52f73bec3dae40229677e558a6ba33e91951e762 Mon Sep 17 00:00:00 2001 From: Aristides Staffieri Date: Wed, 13 Dec 2023 10:04:07 -0700 Subject: [PATCH 20/20] adds ledger close time to all tx info blocks --- src/service/mercury/queries.ts | 100 +++++++++++++++++++++++++++++++++ 1 file changed, 100 insertions(+) diff --git a/src/service/mercury/queries.ts b/src/service/mercury/queries.ts index c53e95b..6d9bce3 100644 --- a/src/service/mercury/queries.ts +++ b/src/service/mercury/queries.ts @@ -121,6 +121,10 @@ export const query = { opCount txHash ledger + ledgerByLedger { + closeTime + sequence + } } } } @@ -135,6 +139,10 @@ export const query = { opCount txHash ledger + ledgerByLedger { + closeTime + sequence + } } } } @@ -159,6 +167,10 @@ export const query = { opCount txHash ledger + ledgerByLedger { + closeTime + sequence + } } } } @@ -183,6 +195,10 @@ export const query = { opCount txHash ledger + ledgerByLedger { + closeTime + sequence + } } } } @@ -242,6 +258,10 @@ export const query = { opCount txHash ledger + ledgerByLedger { + closeTime + sequence + } } } } @@ -300,6 +320,10 @@ export const query = { opCount txHash ledger + ledgerByLedger { + closeTime + sequence + } } } } @@ -358,6 +382,10 @@ export const query = { opCount txHash ledger + ledgerByLedger { + closeTime + sequence + } } } } @@ -416,6 +444,10 @@ export const query = { opCount txHash ledger + ledgerByLedger { + closeTime + sequence + } } } } @@ -454,6 +486,10 @@ export const query = { opCount txHash ledger + ledgerByLedger { + closeTime + sequence + } } } } @@ -493,6 +529,10 @@ export const query = { opCount txHash ledger + ledgerByLedger { + closeTime + sequence + } } } } @@ -529,6 +569,10 @@ export const query = { opCount txHash ledger + ledgerByLedger { + closeTime + sequence + } } } } @@ -562,6 +606,10 @@ export const query = { opCount txHash ledger + ledgerByLedger { + closeTime + sequence + } } } } @@ -578,6 +626,10 @@ export const query = { opCount txHash ledger + ledgerByLedger { + closeTime + sequence + } } } } @@ -594,6 +646,10 @@ export const query = { opCount txHash ledger + ledgerByLedger { + closeTime + sequence + } } } } @@ -610,6 +666,10 @@ export const query = { opCount txHash ledger + ledgerByLedger { + closeTime + sequence + } } } } @@ -628,6 +688,10 @@ export const query = { opCount txHash ledger + ledgerByLedger { + closeTime + sequence + } } } } @@ -646,6 +710,10 @@ export const query = { opCount txHash ledger + ledgerByLedger { + closeTime + sequence + } } } } @@ -663,6 +731,10 @@ export const query = { opCount txHash ledger + ledgerByLedger { + closeTime + sequence + } } } } @@ -678,6 +750,10 @@ export const query = { opCount txHash ledger + ledgerByLedger { + closeTime + sequence + } } } } @@ -693,6 +769,10 @@ export const query = { opCount txHash ledger + ledgerByLedger { + closeTime + sequence + } } } } @@ -709,6 +789,10 @@ export const query = { opCount txHash ledger + ledgerByLedger { + closeTime + sequence + } } } } @@ -729,6 +813,10 @@ export const query = { opCount txHash ledger + ledgerByLedger { + closeTime + sequence + } } } } @@ -749,6 +837,10 @@ export const query = { opCount txHash ledger + ledgerByLedger { + closeTime + sequence + } } } } @@ -769,6 +861,10 @@ export const query = { opCount txHash ledger + ledgerByLedger { + closeTime + sequence + } } } } @@ -787,6 +883,10 @@ export const query = { opCount txHash ledger + ledgerByLedger { + closeTime + sequence + } } } }