diff --git a/src/rpc/client.ts b/src/rpc/client.ts index 7f928e6b..2d76d613 100644 --- a/src/rpc/client.ts +++ b/src/rpc/client.ts @@ -2,6 +2,7 @@ import { Contract, rpc, xdr, + Transaction, TransactionBuilder, Networks, Account, @@ -14,6 +15,53 @@ import { import { getLogger } from "../logging/index.js"; // CostSummary removed — no longer exported from costs.js +// ── Local helper types to replace `any` casts ────────────────────────────── + +/** Error-like shape accessed in catch blocks. */ +interface ErrorLike { + code?: string; + message?: string; + response?: { status?: number }; +} + +/** Soroban meta with optional resource-counter accessors. */ +interface SorobanMetaLike { + cpuInstructions?: () => unknown; + memoryBytes?: () => unknown; +} + +/** Cost fields present on raw (unparsed) simulation responses. */ +interface SimulateTransactionCost { + cpuInsns?: string | number; + memBytes?: string | number; + readBytes?: string | number; + writeBytes?: string | number; +} + +/** Simulation success response that may carry raw cost fields. */ +interface SimulateWithCost extends rpc.Api.SimulateTransactionSuccessResponse { + cost?: SimulateTransactionCost; +} + +/** Send-transaction error response with raw diagnostic fields. */ +interface SendTransactionErrorResult { + errorResult?: unknown; + diagnosticEventsXdr?: string; +} + +/** Get-transaction response with raw (string-based) fields. + * + * The SDK types parse `resultMetaXdr` as `xdr.TransactionMeta`, but at runtime + * the RPC may still return a base64 string. The `as unknown as + * GetTransactionRawFields` casts in `pollTransaction` handle this mismatch + * intentionally — `extractResourceCosts` expects a raw base64 string. */ +interface GetTransactionRawFields { + resultMetaXdr?: string; + feeCharged?: string | number; + ledger?: number; + latestLedger?: number; +} + export function assertSimulationSuccess(sim: rpc.Api.SimulateTransactionResponse): asserts sim is rpc.Api.SimulateTransactionSuccessResponse { if (rpc.Api.isSimulationError(sim)) { if (sim.error?.includes("txBadSeq")) { @@ -41,9 +89,10 @@ export async function executeWithRetry(action: () => Promise): Promise for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) { try { return await action(); - } catch (error: any) { - const isTimeout = error?.code === "ETIMEDOUT" || error?.code === "ECONNRESET" || error?.message?.includes("timeout"); - const status = error?.response?.status; + } catch (error: unknown) { + const err = error as ErrorLike; + const isTimeout = err.code === "ETIMEDOUT" || err.code === "ECONNRESET" || err.message?.includes("timeout"); + const status = err.response?.status ?? 0; const isRetryableHttp = status === 429 || (status >= 500 && status < 600); if ((isTimeout || isRetryableHttp) && attempt < MAX_RETRIES) { @@ -201,9 +250,9 @@ export function extractResourceCosts(resultMetaXdrBase64: string): { cpuInstruct if (v3) { const sorobanMeta = typeof v3.sorobanMeta === 'function' ? v3.sorobanMeta() : undefined; if (sorobanMeta) { - const anyMeta = sorobanMeta as any; - const cpuInstructions = typeof anyMeta.cpuInstructions === 'function' ? Number(anyMeta.cpuInstructions()) : undefined; - const memoryBytes = typeof anyMeta.memoryBytes === 'function' ? Number(anyMeta.memoryBytes()) : undefined; + const meta = sorobanMeta as SorobanMetaLike; + const cpuInstructions = typeof meta.cpuInstructions === 'function' ? Number(meta.cpuInstructions()) : undefined; + const memoryBytes = typeof meta.memoryBytes === 'function' ? Number(meta.memoryBytes()) : undefined; if (cpuInstructions !== undefined && memoryBytes !== undefined) { return { cpuInstructions, memoryBytes }; @@ -253,11 +302,10 @@ export class StellarRpcClient { } async getCurrentLedger(): Promise { - const serverAny = this.server as any; - if (typeof serverAny.getLatestLedger === "function") { + if (typeof this.server.getLatestLedger === "function") { try { - const response = await this.withRateLimit(() => serverAny.getLatestLedger()); - const seq = (response as any)?.sequence; + const response = await this.withRateLimit(() => this.server.getLatestLedger()); + const seq = response?.sequence; if (typeof seq === "number" && seq > 0) return seq; } catch (error) { logger.debug("getLatestLedger failed, falling back to getHealth", error); @@ -265,8 +313,8 @@ export class StellarRpcClient { } const health = await this.withRateLimit(() => this.server.getHealth()); - if (health && typeof (health as any).latestLedger === "number") { - return (health as any).latestLedger; + if (health && typeof health.latestLedger === "number") { + return health.latestLedger; } throw new Error("Unable to determine latest ledger from RPC server"); @@ -274,12 +322,11 @@ export class StellarRpcClient { async getFeeStats(): Promise { return await this.withRateLimit(async () => { - const serverAny = this.server as any; - if (typeof serverAny.getFeeStats !== "function") { + if (typeof this.server.getFeeStats !== "function") { throw new Error("RPC server does not support getFeeStats"); } - const response = await serverAny.getFeeStats(); + const response = await this.server.getFeeStats(); const inclusionFee = response.sorobanInclusionFee ?? response.inclusionFee; if (!inclusionFee) { throw new Error("RPC fee stats response did not include inclusion fee data"); @@ -532,10 +579,10 @@ export class StellarRpcClient { return { success: true, minResourceFee: Number(successSim.minResourceFee ?? 0), - cpuInstructions: Number((successSim as any).cost?.cpuInsns ?? 0), - memoryBytes: Number((successSim as any).cost?.memBytes ?? 0), - readBytes: Number((successSim as any).cost?.readBytes ?? 0), - writeBytes: Number((successSim as any).cost?.writeBytes ?? 0), + cpuInstructions: Number((successSim as SimulateWithCost).cost?.cpuInsns ?? 0), + memoryBytes: Number((successSim as SimulateWithCost).cost?.memBytes ?? 0), + readBytes: Number((successSim as SimulateWithCost).cost?.readBytes ?? 0), + writeBytes: Number((successSim as SimulateWithCost).cost?.writeBytes ?? 0), }; } @@ -627,19 +674,19 @@ export class StellarRpcClient { retryPrepared.sign(keypair); const retrySendResult = await this.server.sendTransaction(retryPrepared); if (retrySendResult.status === "ERROR") { - const diagnostics = (retrySendResult as any).errorResult ?? (retrySendResult as any).diagnosticEventsXdr ?? ""; - return { success: false, txHash: retrySendResult.hash, ledger: 0, cpuInsns: Number((retrySim as any).cost?.cpuInsns ?? 0), memBytes: Number((retrySim as any).cost?.memBytes ?? 0), error: `Transaction send error: ${diagnostics || retrySendResult.status}` }; + const diagnostics = (retrySendResult as SendTransactionErrorResult).errorResult ?? (retrySendResult as SendTransactionErrorResult).diagnosticEventsXdr ?? ""; + return { success: false, txHash: retrySendResult.hash, ledger: 0, cpuInsns: Number((retrySim as SimulateWithCost).cost?.cpuInsns ?? 0), memBytes: Number((retrySim as SimulateWithCost).cost?.memBytes ?? 0), error: `Transaction send error: ${diagnostics || retrySendResult.status}` }; } const txResult = await this.pollTransaction(retrySendResult.hash); return txResult.success ? this.addResourcesToSuccess(txResult, retrySim as rpc.Api.SimulateTransactionSuccessResponse) : txResult; } - const diagnostics = (sendResult as any).errorResult ?? (sendResult as any).diagnosticEventsXdr ?? ""; + const diagnostics = (sendResult as SendTransactionErrorResult).errorResult ?? (sendResult as SendTransactionErrorResult).diagnosticEventsXdr ?? ""; return { success: false, txHash: sendResult.hash, ledger: 0, - cpuInsns: Number((sim as any).cost?.cpuInsns ?? 0), - memBytes: Number((sim as any).cost?.memBytes ?? 0), + cpuInsns: Number((sim as SimulateWithCost).cost?.cpuInsns ?? 0), + memBytes: Number((sim as SimulateWithCost).cost?.memBytes ?? 0), error: `Transaction send error: ${diagnostics || sendResult.status}`, }; } @@ -650,7 +697,7 @@ export class StellarRpcClient { // Helper to add resource usage to a successful transaction result private addResourcesToSuccess(result: SubmitTransactionResult, sim: rpc.Api.SimulateTransactionSuccessResponse): SubmitTransactionResult { - return { ...result, cpuInsns: Number((sim as any).cost?.cpuInsns ?? 0), memBytes: Number((sim as any).cost?.memBytes ?? 0) }; + return { ...result, cpuInsns: Number((sim as SimulateWithCost).cost?.cpuInsns ?? 0), memBytes: Number((sim as SimulateWithCost).cost?.memBytes ?? 0) }; } /** @@ -687,7 +734,7 @@ export class StellarRpcClient { return { success: false, txHash: "", ledger: 0, cpuInsns: 0, memBytes: 0, error: sim.error ?? "Simulation failed" }; } - const buildAndSignFeeBump = (innerTx: any, simResult: any) => { + const buildAndSignFeeBump = (innerTx: Transaction, simResult: rpc.Api.SimulateTransactionSuccessResponse) => { const prepared = rpc.assembleTransaction(innerTx, simResult).build(); prepared.sign(keypair); const feeBump = TransactionBuilder.buildFeeBumpTransaction( @@ -714,19 +761,19 @@ export class StellarRpcClient { const retryFeeBump = buildAndSignFeeBump(retryTx, retrySim); const retrySendResult = await this.server.sendTransaction(retryFeeBump); if (retrySendResult.status === "ERROR") { - const diagnostics = (retrySendResult as any).errorResult ?? (retrySendResult as any).diagnosticEventsXdr ?? ""; - return { success: false, txHash: retrySendResult.hash, ledger: 0, cpuInsns: Number((retrySim as any).cost?.cpuInsns ?? 0), memBytes: Number((retrySim as any).cost?.memBytes ?? 0), error: `Transaction send error: ${diagnostics || retrySendResult.status}` }; + const diagnostics = (retrySendResult as SendTransactionErrorResult).errorResult ?? (retrySendResult as SendTransactionErrorResult).diagnosticEventsXdr ?? ""; + return { success: false, txHash: retrySendResult.hash, ledger: 0, cpuInsns: Number((retrySim as SimulateWithCost).cost?.cpuInsns ?? 0), memBytes: Number((retrySim as SimulateWithCost).cost?.memBytes ?? 0), error: `Transaction send error: ${diagnostics || retrySendResult.status}` }; } const txResult = await this.pollTransaction(retrySendResult.hash); return txResult.success ? this.addResourcesToSuccess(txResult, retrySim as rpc.Api.SimulateTransactionSuccessResponse) : txResult; } - const diagnostics = (sendResult as any).errorResult ?? (sendResult as any).diagnosticEventsXdr ?? ""; + const diagnostics = (sendResult as SendTransactionErrorResult).errorResult ?? (sendResult as SendTransactionErrorResult).diagnosticEventsXdr ?? ""; return { success: false, txHash: sendResult.hash, ledger: 0, - cpuInsns: Number((sim as any).cost?.cpuInsns ?? 0), - memBytes: Number((sim as any).cost?.memBytes ?? 0), + cpuInsns: Number((sim as SimulateWithCost).cost?.cpuInsns ?? 0), + memBytes: Number((sim as SimulateWithCost).cost?.memBytes ?? 0), error: `Transaction send error: ${diagnostics || sendResult.status}`, }; } @@ -786,19 +833,19 @@ export class StellarRpcClient { retryPrepared.sign(keypair); const retrySendResult = await this.server.sendTransaction(retryPrepared); if (retrySendResult.status === "ERROR") { - const diagnostics = (retrySendResult as any).errorResult ?? (retrySendResult as any).diagnosticEventsXdr ?? ""; - return { success: false, txHash: retrySendResult.hash, ledger: 0, cpuInsns: Number((retrySim as any).cost?.cpuInsns ?? 0), memBytes: Number((retrySim as any).cost?.memBytes ?? 0), error: `Transaction send error: ${diagnostics || retrySendResult.status}` }; + const diagnostics = (retrySendResult as SendTransactionErrorResult).errorResult ?? (retrySendResult as SendTransactionErrorResult).diagnosticEventsXdr ?? ""; + return { success: false, txHash: retrySendResult.hash, ledger: 0, cpuInsns: Number((retrySim as SimulateWithCost).cost?.cpuInsns ?? 0), memBytes: Number((retrySim as SimulateWithCost).cost?.memBytes ?? 0), error: `Transaction send error: ${diagnostics || retrySendResult.status}` }; } const txResult = await this.pollTransaction(retrySendResult.hash); return txResult.success ? this.addResourcesToSuccess(txResult, retrySim as rpc.Api.SimulateTransactionSuccessResponse) : txResult; } - const diagnostics = (sendResult as any).errorResult ?? (sendResult as any).diagnosticEventsXdr ?? ""; + const diagnostics = (sendResult as SendTransactionErrorResult).errorResult ?? (sendResult as SendTransactionErrorResult).diagnosticEventsXdr ?? ""; return { success: false, txHash: sendResult.hash, ledger: 0, - cpuInsns: Number((sim as any).cost?.cpuInsns ?? 0), - memBytes: Number((sim as any).cost?.memBytes ?? 0), + cpuInsns: Number((sim as SimulateWithCost).cost?.cpuInsns ?? 0), + memBytes: Number((sim as SimulateWithCost).cost?.memBytes ?? 0), error: `Transaction send error: ${diagnostics || sendResult.status}`, }; } @@ -843,7 +890,7 @@ export class StellarRpcClient { const sendResult = await this.server.sendTransaction(tx); if (sendResult.status === "ERROR") { - const diagnostics = (sendResult as any).errorResult ?? ""; + const diagnostics = (sendResult as SendTransactionErrorResult).errorResult ?? ""; return { success: false, txHash: sendResult.hash, @@ -860,14 +907,14 @@ export class StellarRpcClient { * Returns true if the sendTransaction ERROR response indicates a txBadSeq result code. * The SDK parses errorResultXdr into `errorResult` as an xdr.TransactionResult. */ - private isBadSeqError(sendResult: any): boolean { + private isBadSeqError(sendResult: SendTransactionErrorResult): boolean { try { const errorResult = sendResult.errorResult; if (!errorResult) return false; // errorResult may be a base64 string or a pre-parsed xdr.TransactionResult const parsed = typeof errorResult === "string" ? xdr.TransactionResult.fromXDR(errorResult, "base64") - : errorResult; + : (errorResult as xdr.TransactionResult); return parsed.result().switch().name === "txBadSeq"; } catch { return false; @@ -911,7 +958,7 @@ export class StellarRpcClient { const txResponse = await this.withRateLimit(() => this.server.getTransaction(txHash)); if (txResponse.status === "SUCCESS") { - const resultMetaXdr = (txResponse as any).resultMetaXdr; + const resultMetaXdr = (txResponse as unknown as GetTransactionRawFields).resultMetaXdr; let cpuInstructions: number | undefined = undefined; let memoryBytes: number | undefined = undefined; @@ -928,13 +975,13 @@ export class StellarRpcClient { } } - const rawFee = (txResponse as any).feeCharged; + const rawFee = (txResponse as unknown as GetTransactionRawFields).feeCharged; const feeCharged = rawFee !== undefined ? Number(rawFee) : undefined; return { success: true, txHash, - ledger: (txResponse as any).ledger ?? txResponse.latestLedger, + ledger: (txResponse as unknown as GetTransactionRawFields).ledger ?? txResponse.latestLedger, cpuInstructions, memoryBytes, feeCharged, @@ -945,7 +992,7 @@ export class StellarRpcClient { return { success: false, txHash, - ledger: (txResponse as any).ledger ?? txResponse.latestLedger, + ledger: (txResponse as unknown as GetTransactionRawFields).ledger ?? txResponse.latestLedger, error: "Transaction failed on-chain", }; }