From db41d064a430f978ae9416273e1869642268f33a Mon Sep 17 00:00:00 2001 From: aristides Date: Mon, 22 Jan 2024 15:06:34 -0700 Subject: [PATCH] Invoke host fn tweaks (#23) * tweaks the response for invoke host fns to expose fn name and args * triggering build * triggering build take 2 --- src/helper/soroban-rpc.ts | 36 ++++++++++++++ src/service/mercury/helpers/transformers.ts | 52 +++++++++++++-------- src/service/mercury/index.ts | 19 +++++--- src/service/mercury/queries.ts | 18 +++++-- 4 files changed, 95 insertions(+), 30 deletions(-) diff --git a/src/helper/soroban-rpc.ts b/src/helper/soroban-rpc.ts index a36356a..1e03dc9 100644 --- a/src/helper/soroban-rpc.ts +++ b/src/helper/soroban-rpc.ts @@ -11,6 +11,7 @@ import { scValToNative, xdr, SorobanRpc, + StrKey, } from "stellar-sdk"; import { NetworkNames } from "./validate"; @@ -139,8 +140,43 @@ const buildTransfer = ( return tx.build(); }; +// https://github.com/stellar/soroban-examples/blob/main/token/src/contract.rs +enum SorobanTokenInterface { + transfer = "transfer", + mint = "mint", +} + +const getOpArgs = (fnName: string, args: xdr.ScVal[]) => { + let amount: number; + let from; + let to; + + switch (fnName) { + case SorobanTokenInterface.transfer: + from = StrKey.encodeEd25519PublicKey( + args[0].address().accountId().ed25519() + ); + to = StrKey.encodeEd25519PublicKey( + args[1].address().accountId().ed25519() + ); + amount = scValToNative(args[2]); + break; + case SorobanTokenInterface.mint: + to = StrKey.encodeEd25519PublicKey( + args[0].address().accountId().ed25519() + ); + amount = scValToNative(args[1]).toString(); + break; + default: + amount = 0; + } + + return { from, to, amount }; +}; + export { buildTransfer, + getOpArgs, getServer, getTokenBalance, getTokenDecimals, diff --git a/src/service/mercury/helpers/transformers.ts b/src/service/mercury/helpers/transformers.ts index 572f5bd..004ea42 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, scValToNative, xdr } from "stellar-sdk"; +import { Horizon, StrKey, scValToNative, xdr } from "stellar-sdk"; import BigNumber from "bignumber.js"; import { BASE_RESERVE, @@ -8,6 +8,7 @@ import { getAssetType, } from "../../../helper/horizon-rpc"; import { formatTokenAmount } from "../../../helper/format"; +import { getOpArgs } from "../../../helper/soroban-rpc"; // Transformers take an API response, and transform it/augment it for frontend consumption @@ -153,7 +154,9 @@ interface MercuryAccountHistory { auth: string; hostFunction: string; sorobanMeta: string; - source: string; + accountBySource: { + publickey: string; + }; tx: string; opId: string; txInfoByTx: { @@ -863,23 +866,34 @@ const transformAccountHistory = async ( ): Promise[]> => { const invokeHostFnEdges = rawResponse.data?.invokeHostFnByPublicKey.edges || []; - const invokeHostFn = invokeHostFnEdges.map( - (edge) => - ({ - auth: edge.node.auth, - hostFunction: edge.node.hostFunction, - sorobanMeta: edge.node.sorobanMeta, - source: edge.node.source, - tx: edge.node.tx, - type: "invoke_host_function", - type_i: 24, - id: edge.node.opId, - transaction_attr: { - operation_count: edge.node.txInfoByTx.opCount, - fee_charged: edge.node.txInfoByTx.fee, - }, - } as Partial) - ); + const invokeHostFn = invokeHostFnEdges.map((edge) => { + const hostFn = xdr.HostFunction.fromXDR( + Buffer.from(edge.node.hostFunction, "base64") + ); + const invocation = hostFn.invokeContract(); + const fnName = invocation.functionName().toString(); + return { + auth: edge.node.auth, + created_at: new Date( + edge.node.txInfoByTx.ledgerByLedger.closeTime * 1000 + ).toISOString(), + sorobanMeta: edge.node.sorobanMeta, + source_account: edge.node.accountBySource.publickey, + tx: edge.node.tx, + type: "invoke_host_function", + type_i: 24, + id: edge.node.opId, + transaction_attr: { + contractId: StrKey.encodeContract( + invocation.contractAddress().contractId() + ), + fnName, + args: getOpArgs(fnName, invocation.args()), + operation_count: edge.node.txInfoByTx.opCount, + fee_charged: edge.node.txInfoByTx.fee, + }, + } as Partial; + }); const createAccountEdges = rawResponse.data?.createAccountByPublicKey.edges || []; diff --git a/src/service/mercury/index.ts b/src/service/mercury/index.ts index 8183ea6..3404ff5 100644 --- a/src/service/mercury/index.ts +++ b/src/service/mercury/index.ts @@ -432,11 +432,18 @@ export class MercuryClient { } catch (error) { this.logger.error(error); const _error = JSON.stringify(error); - this.rpcErrorCounter - .labels({ - rpc: "Horizon", - }) - .inc(); + if (error && typeof error === "object" && "message" in error) { + const err = JSON.parse(error.message as string); + // Not found errors are normal for unfunded accounts, dont alert + if (err.name !== "NotFoundError") { + this.rpcErrorCounter + .labels({ + rpc: "Horizon", + }) + .inc(); + } + } + return { data: null, error: _error, @@ -446,11 +453,9 @@ export class MercuryClient { getAccountHistoryMercury = 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) { diff --git a/src/service/mercury/queries.ts b/src/service/mercury/queries.ts index 8c1ba06..471e638 100644 --- a/src/service/mercury/queries.ts +++ b/src/service/mercury/queries.ts @@ -58,9 +58,17 @@ export const query = { contractId keyXdr valueXdr - ledgerTimestamp - ledger entryDurability + txInfoByTx { + fee + opCount + txHash + ledger + ledgerByLedger { + closeTime + sequence + } + } } } ` @@ -68,14 +76,16 @@ export const query = { } `, getAccountHistory: ` - query GetAccountHistory($pubKey: String!, $xdrPubKey: String!) { + query GetAccountHistory($pubKey: String!) { invokeHostFnByPublicKey(publicKeyText: $pubKey) { edges { node { auth hostFunction sorobanMeta - source + accountBySource { + publickey + } tx opId txInfoByTx {