From 4a190fee464f09833b94902ae4fb5ce2f15cd913 Mon Sep 17 00:00:00 2001 From: ncomerci Date: Wed, 3 Dec 2025 22:39:48 -0300 Subject: [PATCH 01/22] Lib: Introduce Result and QueryResponse types --- packages/lib-ts/src/types/QueryResponse.ts | 22 ++++++++++ packages/lib-ts/src/types/Result.ts | 51 ++++++++++++++++++++++ packages/lib-ts/src/types/index.ts | 1 + 3 files changed, 74 insertions(+) create mode 100644 packages/lib-ts/src/types/QueryResponse.ts create mode 100644 packages/lib-ts/src/types/Result.ts diff --git a/packages/lib-ts/src/types/QueryResponse.ts b/packages/lib-ts/src/types/QueryResponse.ts new file mode 100644 index 00000000..7e5f69b8 --- /dev/null +++ b/packages/lib-ts/src/types/QueryResponse.ts @@ -0,0 +1,22 @@ +import { stringToBool } from '../helpers' + +@json +export class QueryResponseSerializable { + constructor( + public success: string, + public data: T, + public error: string + ) {} +} + +export class QueryResponse { + constructor( + public success: bool, + public data: T, + public error: string + ) {} + + static fromSerializable(serializable: QueryResponseSerializable): QueryResponse { + return new QueryResponse(stringToBool(serializable.success), serializable.data, serializable.error) + } +} diff --git a/packages/lib-ts/src/types/Result.ts b/packages/lib-ts/src/types/Result.ts new file mode 100644 index 00000000..092a71d0 --- /dev/null +++ b/packages/lib-ts/src/types/Result.ts @@ -0,0 +1,51 @@ +// eslint-disable-next-line no-secrets/no-secrets +// This file is based on code from "The Graph Tooling" (https://github.com/graphprotocol/graph-tooling/tree/7faa3098b2e6c61f09fc81b8b2d333e66b0080d1). +// Licensed under the MIT License. +// Copyright (c) 2018 Graph Protocol, Inc. and contributors. +// Modified by Mimic Protocol, 2025. + +/** + * The result of an operation, with a corresponding value and error type. + */ +export class Result { + constructor( + private _value: Wrapped | null, + private _error: Wrapped | null + ) {} + + static ok(value: V): Result { + return new Result(new Wrapped(value), null) + } + + static err(error: E): Result { + return new Result(null, new Wrapped(error)) + } + + get isOk(): boolean { + return this._value !== null + } + + get isError(): boolean { + return this._error !== null + } + + get value(): V { + if (this.isError) throw new Error('Trying to get a value from an error result') + return changetype>(this._value).inner + } + + get error(): E { + if (this.isOk) throw new Error('Trying to get an error from a successful result') + return changetype>(this._error).inner + } +} + +// This is used to wrap a generic so that it can be unioned with `null`, working around limitations +// with primitives. +export class Wrapped { + inner: T + + constructor(inner: T) { + this.inner = inner + } +} diff --git a/packages/lib-ts/src/types/index.ts b/packages/lib-ts/src/types/index.ts index 8a015131..d07ebe60 100644 --- a/packages/lib-ts/src/types/index.ts +++ b/packages/lib-ts/src/types/index.ts @@ -7,6 +7,7 @@ export * from './Bytes' export * from './ChainId' export * from './evm' export * from './Option' +export * from './Result' export * from './svm' export * from './TriggerType' export { JSON } From c9aa70f9c5b23ba6dbca06ef566faea2ee62ee9b Mon Sep 17 00:00:00 2001 From: ncomerci Date: Wed, 3 Dec 2025 22:42:52 -0300 Subject: [PATCH 02/22] feat: Enhance price retrieval with Result type and error handling --- packages/lib-ts/src/environment.ts | 46 +++++++++++++------ .../lib-ts/src/queries/TokenPriceQuery.ts | 5 ++ packages/lib-ts/src/tokens/TokenAmount.ts | 5 +- packages/lib-ts/src/tokens/USD.ts | 5 +- 4 files changed, 45 insertions(+), 16 deletions(-) diff --git a/packages/lib-ts/src/environment.ts b/packages/lib-ts/src/environment.ts index 7d41e56a..552c0998 100644 --- a/packages/lib-ts/src/environment.ts +++ b/packages/lib-ts/src/environment.ts @@ -14,10 +14,11 @@ import { SerializableGetAccountsInfoResponse, SubgraphQuery, SubgraphQueryResponse, + GetPriceResponseSerializable, + GetPriceResponse, } from './queries' import { BlockchainToken, Token, TokenAmount, USD } from './tokens' -import { Address, BigInt, ChainId } from './types' -import { log } from './log' +import { Address, BigInt, ChainId, Result } from './types' export namespace environment { @external('environment', '_evmCall') @@ -86,36 +87,53 @@ export namespace environment { * Tells the prices from different sources for a token in USD at a specific timestamp. * @param token - The token to get the price of * @param timestamp - The timestamp for price lookup (optional, defaults to current time) - * @returns The token prices in USD + * @returns Result containing either an array of USD prices or an error string */ - export function rawTokenPriceQuery(token: Token, timestamp: Date | null = null): USD[] { - if (token.isUSD()) return [USD.fromI32(1)] - else if (!(token instanceof BlockchainToken)) throw new Error('Price query not supported for token ' + token.toString()) - const prices = _tokenPriceQuery(JSON.stringify(TokenPriceQuery.fromToken(token as BlockchainToken, timestamp))) - return JSON.parse(prices).map((price) => USD.fromBigInt(BigInt.fromString(price))) + export function rawTokenPriceQuery(token: Token, timestamp: Date | null = null): Result { + if (token.isUSD()) return Result.ok([USD.fromI32(1)]) + else if (!(token instanceof BlockchainToken)) return Result.err('Price query not supported for token ' + token.toString()) + + const responseStr = + _tokenPriceQuery(JSON.stringify(TokenPriceQuery.fromToken(changetype(token), timestamp))) + .replaceAll("true","\"true\"") + .replaceAll("false","\"false\"") + + const response = GetPriceResponse.fromSerializable(JSON.parse(responseStr)) + + if (!response.success) return Result.err(response.error.length > 0 ? response.error : 'Unknown error getting price') + + const prices = response.data.map((price) => USD.fromBigInt(BigInt.fromString(price))) + return Result.ok(prices) } /** * Tells the median price from different sources for a token in USD at a specific timestamp. * @param token - The token to get the price of * @param timestamp - The timestamp for price lookup (optional, defaults to current time) - * @returns The token median price in USD + * @returns Result containing either the median USD price or an error string */ - export function tokenPriceQuery(token: Token, timestamp: Date | null = null): USD { - const prices = rawTokenPriceQuery(token, timestamp) - if (prices.length === 0) throw new Error('Prices not found for token ' + token.toString()) + export function tokenPriceQuery(token: Token, timestamp: Date | null = null): Result { + const pricesResult = rawTokenPriceQuery(token, timestamp) + + if (pricesResult.isError) return Result.err(pricesResult.error) + + const prices = pricesResult.value + if (prices.length === 0) return Result.err('Prices not found for token ' + token.toString()) const sortedPrices = prices.sort((a: USD, b: USD) => a.compare(b)) const length = sortedPrices.length + let median: USD if (length % 2 === 1) { - return sortedPrices[length / 2] + median = sortedPrices[length / 2] } else { const left = sortedPrices[length / 2 - 1] const right = sortedPrices[length / 2] const sum = left.plus(right) - return sum.div(BigInt.fromI32(2)) + median = sum.div(BigInt.fromI32(2)) } + + return Result.ok(median) } /** diff --git a/packages/lib-ts/src/queries/TokenPriceQuery.ts b/packages/lib-ts/src/queries/TokenPriceQuery.ts index edf4b8e3..4c4ccd5b 100644 --- a/packages/lib-ts/src/queries/TokenPriceQuery.ts +++ b/packages/lib-ts/src/queries/TokenPriceQuery.ts @@ -1,4 +1,5 @@ import { BlockchainToken } from '../tokens' +import { QueryResponse, QueryResponseSerializable } from '../types/QueryResponse' @json class TokenPriceQueryBase { @@ -26,3 +27,7 @@ export class TokenPriceQuery extends TokenPriceQueryBase { : new TokenPriceQueryBase(address, chainId) } } + +@json +export class GetPriceResponseSerializable extends QueryResponseSerializable {} +export class GetPriceResponse extends QueryResponse {} diff --git a/packages/lib-ts/src/tokens/TokenAmount.ts b/packages/lib-ts/src/tokens/TokenAmount.ts index cc87b1d3..c4a15faa 100644 --- a/packages/lib-ts/src/tokens/TokenAmount.ts +++ b/packages/lib-ts/src/tokens/TokenAmount.ts @@ -219,7 +219,10 @@ export class TokenAmount { */ toUsd(): USD { if (this.isZero()) return USD.zero() - const tokenPrice = environment.tokenPriceQuery(this.token) + const tokenPriceResult = environment.tokenPriceQuery(this.token) + if (tokenPriceResult.isError) throw new Error(tokenPriceResult.error) + + const tokenPrice = tokenPriceResult.value const amountUsd = this.amount.times(tokenPrice.value).downscale(this.decimals) return USD.fromBigInt(amountUsd) } diff --git a/packages/lib-ts/src/tokens/USD.ts b/packages/lib-ts/src/tokens/USD.ts index 57982b02..f7a64739 100644 --- a/packages/lib-ts/src/tokens/USD.ts +++ b/packages/lib-ts/src/tokens/USD.ts @@ -194,7 +194,10 @@ export class USD { */ toTokenAmount(token: Token): TokenAmount { if (this.isZero()) return TokenAmount.fromI32(token, 0) - const tokenPrice = environment.tokenPriceQuery(token) + const tokenPriceResult = environment.tokenPriceQuery(token) + if (tokenPriceResult.isError) throw new Error(tokenPriceResult.error) + + const tokenPrice = tokenPriceResult.value const tokenAmount = this.value.upscale(token.decimals).div(tokenPrice.value) return TokenAmount.fromBigInt(token, tokenAmount) } From 1dd2719dbe2ca92515438fd746452270208c1064 Mon Sep 17 00:00:00 2001 From: ncomerci Date: Thu, 4 Dec 2025 11:26:47 -0300 Subject: [PATCH 03/22] refactor: use QueryResponse class --- packages/lib-ts/src/environment.ts | 11 +++-------- packages/lib-ts/src/queries/TokenPriceQuery.ts | 5 ----- packages/lib-ts/src/types/QueryResponse.ts | 9 +++++++++ 3 files changed, 12 insertions(+), 13 deletions(-) diff --git a/packages/lib-ts/src/environment.ts b/packages/lib-ts/src/environment.ts index 552c0998..06744b6c 100644 --- a/packages/lib-ts/src/environment.ts +++ b/packages/lib-ts/src/environment.ts @@ -14,11 +14,10 @@ import { SerializableGetAccountsInfoResponse, SubgraphQuery, SubgraphQueryResponse, - GetPriceResponseSerializable, - GetPriceResponse, } from './queries' import { BlockchainToken, Token, TokenAmount, USD } from './tokens' import { Address, BigInt, ChainId, Result } from './types' +import { QueryResponse } from './types/QueryResponse' export namespace environment { @external('environment', '_evmCall') @@ -93,12 +92,8 @@ export namespace environment { if (token.isUSD()) return Result.ok([USD.fromI32(1)]) else if (!(token instanceof BlockchainToken)) return Result.err('Price query not supported for token ' + token.toString()) - const responseStr = - _tokenPriceQuery(JSON.stringify(TokenPriceQuery.fromToken(changetype(token), timestamp))) - .replaceAll("true","\"true\"") - .replaceAll("false","\"false\"") - - const response = GetPriceResponse.fromSerializable(JSON.parse(responseStr)) + const responseStr = _tokenPriceQuery(JSON.stringify(TokenPriceQuery.fromToken(changetype(token), timestamp))) + const response = QueryResponse.fromJson(responseStr) if (!response.success) return Result.err(response.error.length > 0 ? response.error : 'Unknown error getting price') diff --git a/packages/lib-ts/src/queries/TokenPriceQuery.ts b/packages/lib-ts/src/queries/TokenPriceQuery.ts index 4c4ccd5b..edf4b8e3 100644 --- a/packages/lib-ts/src/queries/TokenPriceQuery.ts +++ b/packages/lib-ts/src/queries/TokenPriceQuery.ts @@ -1,5 +1,4 @@ import { BlockchainToken } from '../tokens' -import { QueryResponse, QueryResponseSerializable } from '../types/QueryResponse' @json class TokenPriceQueryBase { @@ -27,7 +26,3 @@ export class TokenPriceQuery extends TokenPriceQueryBase { : new TokenPriceQueryBase(address, chainId) } } - -@json -export class GetPriceResponseSerializable extends QueryResponseSerializable {} -export class GetPriceResponse extends QueryResponse {} diff --git a/packages/lib-ts/src/types/QueryResponse.ts b/packages/lib-ts/src/types/QueryResponse.ts index 7e5f69b8..a99db1fd 100644 --- a/packages/lib-ts/src/types/QueryResponse.ts +++ b/packages/lib-ts/src/types/QueryResponse.ts @@ -1,6 +1,9 @@ +import { JSON } from 'json-as' + import { stringToBool } from '../helpers' @json +@final export class QueryResponseSerializable { constructor( public success: string, @@ -16,6 +19,12 @@ export class QueryResponse { public error: string ) {} + static fromJson(json: string): QueryResponse { + return this.fromSerializable( + JSON.parse>(json.replaceAll('true', `"true"`).replaceAll('false', `"false"`)) + ) + } + static fromSerializable(serializable: QueryResponseSerializable): QueryResponse { return new QueryResponse(stringToBool(serializable.success), serializable.data, serializable.error) } From 5d32b5c7aafbf48c2085a5de3ca086b4029859eb Mon Sep 17 00:00:00 2001 From: ncomerci Date: Thu, 4 Dec 2025 11:52:29 -0300 Subject: [PATCH 04/22] fix: Implement workaround for JSON boolean parsing issue --- packages/lib-ts/src/environment.ts | 6 ++-- packages/lib-ts/src/helpers/strings.ts | 39 ++++++++++++++++++++++ packages/lib-ts/src/types/QueryResponse.ts | 7 ++-- 3 files changed, 45 insertions(+), 7 deletions(-) diff --git a/packages/lib-ts/src/environment.ts b/packages/lib-ts/src/environment.ts index 06744b6c..b460a94b 100644 --- a/packages/lib-ts/src/environment.ts +++ b/packages/lib-ts/src/environment.ts @@ -18,6 +18,7 @@ import { import { BlockchainToken, Token, TokenAmount, USD } from './tokens' import { Address, BigInt, ChainId, Result } from './types' import { QueryResponse } from './types/QueryResponse' +import { replaceJsonBooleans } from './helpers' export namespace environment { @external('environment', '_evmCall') @@ -224,10 +225,9 @@ export namespace environment { ): SvmAccountsInfoQueryResponse { // There is a bug with json-as, so we have to do this with JSON booleans const responseStr = _svmAccountsInfoQuery(JSON.stringify(SvmAccountsInfoQuery.from(publicKeys, timestamp))) - .replaceAll("true",`"true"`) - .replaceAll("false",`"false"`) + const fixedResponseStr = replaceJsonBooleans(responseStr) - const response = JSON.parse(responseStr) + const response = JSON.parse(fixedResponseStr) return SvmAccountsInfoQueryResponse.fromSerializable(response) } diff --git a/packages/lib-ts/src/helpers/strings.ts b/packages/lib-ts/src/helpers/strings.ts index 7fe752ba..38e1f5a4 100644 --- a/packages/lib-ts/src/helpers/strings.ts +++ b/packages/lib-ts/src/helpers/strings.ts @@ -26,6 +26,45 @@ export function stringToBool(str: string): bool { return str === 'true' } +/** + * Workaround for json-as boolean bug: + * Converts JSON boolean values (true/false) into their string equivalents ("true"/"false") + * without touching occurrences inside JSON strings or keys. + * + * This function assumes compact JSON (no newlines between the boolean and the delimiter). + */ +export function replaceJsonBooleans(json: string): string { + return ( + json + // true after ':' (object property values) + .replaceAll(':true', ':"true"') + .replaceAll(':true,', ':"true",') + .replaceAll(':true}', ':"true"}') + .replaceAll(':true]', ':"true"]') + .replaceAll(': true,', ': "true",') + .replaceAll(': true}', ': "true"}') + .replaceAll(': true]', ': "true"]') + // true in arrays + .replaceAll('[true,', '["true",') + .replaceAll('[true]', '["true"]') + .replaceAll(',true,', ',"true",') + .replaceAll(',true]', ',"true"]') + // false after ':' (object property values) + .replaceAll(':false', ':"false"') + .replaceAll(':false,', ':"false",') + .replaceAll(':false}', ':"false"}') + .replaceAll(':false]', ':"false"]') + .replaceAll(': false,', ': "false",') + .replaceAll(': false}', ': "false"}') + .replaceAll(': false]', ': "false"]') + // false in arrays + .replaceAll('[false,', '["false",') + .replaceAll('[false]', '["false"]') + .replaceAll(',false,', ',"false",') + .replaceAll(',false]', ',"false"]') + ) +} + export function areAllZeros(str: string): boolean { for (let i = 0; i < str.length; i++) if (str.charCodeAt(i) !== 48) return false return true diff --git a/packages/lib-ts/src/types/QueryResponse.ts b/packages/lib-ts/src/types/QueryResponse.ts index a99db1fd..ea12dcff 100644 --- a/packages/lib-ts/src/types/QueryResponse.ts +++ b/packages/lib-ts/src/types/QueryResponse.ts @@ -1,6 +1,6 @@ import { JSON } from 'json-as' -import { stringToBool } from '../helpers' +import { replaceJsonBooleans, stringToBool } from '../helpers' @json @final @@ -20,9 +20,8 @@ export class QueryResponse { ) {} static fromJson(json: string): QueryResponse { - return this.fromSerializable( - JSON.parse>(json.replaceAll('true', `"true"`).replaceAll('false', `"false"`)) - ) + const fixedJson = replaceJsonBooleans(json) + return this.fromSerializable(JSON.parse>(fixedJson)) } static fromSerializable(serializable: QueryResponseSerializable): QueryResponse { From 9f3e14abc2efbce6532b7e5e9d6b6922df8e4c38 Mon Sep 17 00:00:00 2001 From: ncomerci Date: Thu, 4 Dec 2025 13:59:52 -0300 Subject: [PATCH 05/22] fix: tests --- packages/lib-ts/as-pect.config.js | 18 +++++++++++-- packages/lib-ts/src/environment.ts | 8 +++--- packages/lib-ts/src/types/QueryResponse.ts | 31 +++++++++------------- packages/lib-ts/tests/helpers.ts | 9 ++++--- 4 files changed, 38 insertions(+), 28 deletions(-) diff --git a/packages/lib-ts/as-pect.config.js b/packages/lib-ts/as-pect.config.js index f240835a..60665a64 100644 --- a/packages/lib-ts/as-pect.config.js +++ b/packages/lib-ts/as-pect.config.js @@ -69,8 +69,22 @@ export default { const timestamp = params.timestamp const key = `_tokenPriceQuery:${address}:${chainId}${timestamp ? `:${timestamp}` : ''}` - if (store.has(key)) return exports.__newString(`["${store.get(key)}"]`) - throw new Error(`Price not found for key: ${key}`) + if (store.has(key)) { + const response = JSON.stringify({ + success: true, + data: [store.get(key).toString()], + error: '', + }) + return exports.__newString(response) + } + + return exports.__newString( + JSON.stringify({ + success: false, + data: [], + error: `Price not found for key: ${key}`, + }) + ) }, _evmCallQuery: (paramsPtr) => { const paramsStr = exports.__getString(paramsPtr) diff --git a/packages/lib-ts/src/environment.ts b/packages/lib-ts/src/environment.ts index b460a94b..12c46e91 100644 --- a/packages/lib-ts/src/environment.ts +++ b/packages/lib-ts/src/environment.ts @@ -17,7 +17,7 @@ import { } from './queries' import { BlockchainToken, Token, TokenAmount, USD } from './tokens' import { Address, BigInt, ChainId, Result } from './types' -import { QueryResponse } from './types/QueryResponse' +import { PriceQueryResponse } from './types/QueryResponse' import { replaceJsonBooleans } from './helpers' export namespace environment { @@ -94,11 +94,11 @@ export namespace environment { else if (!(token instanceof BlockchainToken)) return Result.err('Price query not supported for token ' + token.toString()) const responseStr = _tokenPriceQuery(JSON.stringify(TokenPriceQuery.fromToken(changetype(token), timestamp))) - const response = QueryResponse.fromJson(responseStr) + const parsed = PriceQueryResponse.fromJson(responseStr) - if (!response.success) return Result.err(response.error.length > 0 ? response.error : 'Unknown error getting price') + if (parsed.success !== 'true') return Result.err(parsed.error.length > 0 ? parsed.error : 'Unknown error getting price') - const prices = response.data.map((price) => USD.fromBigInt(BigInt.fromString(price))) + const prices = parsed.data.map((price) => USD.fromBigInt(BigInt.fromString(price))) return Result.ok(prices) } diff --git a/packages/lib-ts/src/types/QueryResponse.ts b/packages/lib-ts/src/types/QueryResponse.ts index ea12dcff..23e9f096 100644 --- a/packages/lib-ts/src/types/QueryResponse.ts +++ b/packages/lib-ts/src/types/QueryResponse.ts @@ -1,30 +1,25 @@ import { JSON } from 'json-as' -import { replaceJsonBooleans, stringToBool } from '../helpers' +import { replaceJsonBooleans } from '../helpers' @json -@final -export class QueryResponseSerializable { +class QueryResponseBase { constructor( - public success: string, - public data: T, - public error: string - ) {} -} - -export class QueryResponse { - constructor( - public success: bool, - public data: T, + public success: string, // boolean as string due to json-as bug public error: string ) {} - static fromJson(json: string): QueryResponse { - const fixedJson = replaceJsonBooleans(json) - return this.fromSerializable(JSON.parse>(fixedJson)) + static fromJson(json: string): T { + return JSON.parse(replaceJsonBooleans(json)) } +} + +@json +export class PriceQueryResponse extends QueryResponseBase { + public data: string[] - static fromSerializable(serializable: QueryResponseSerializable): QueryResponse { - return new QueryResponse(stringToBool(serializable.success), serializable.data, serializable.error) + constructor(success: string, data: string[], error: string) { + super(success, error) + this.data = data } } diff --git a/packages/lib-ts/tests/helpers.ts b/packages/lib-ts/tests/helpers.ts index 224842da..cc5f5c71 100644 --- a/packages/lib-ts/tests/helpers.ts +++ b/packages/lib-ts/tests/helpers.ts @@ -77,14 +77,14 @@ export function randomSPLToken(chainId: ChainId = randomChainId(), decimals: u8 return SPLToken.fromAddress(randomSvmAddress(), chainId, decimals, 'TEST') } -export function randomERC20TokenWithPrice(decimals: u8, priceUsd: number): ERC20Token { +export function randomERC20TokenWithPrice(decimals: u8, priceUsd: i32): ERC20Token { const chainId = randomChainId() const token = randomERC20Token(chainId, decimals) setTokenPrice(token, priceUsd) return token } -export function randomSPLTokenWithPrice(decimals: u8, priceUsd: number): SPLToken { +export function randomSPLTokenWithPrice(decimals: u8, priceUsd: i32): SPLToken { const chainId = randomChainId() const token = randomSPLToken(chainId, decimals) setTokenPrice(token, priceUsd) @@ -93,9 +93,10 @@ export function randomSPLTokenWithPrice(decimals: u8, priceUsd: number): SPLToke declare function _setTokenPrice(address: string, chainId: ChainId, price: string): void -export function setTokenPrice(token: Token, priceUsd: number): void { +export function setTokenPrice(token: Token, priceUsd: i32): void { if (!(token instanceof BlockchainToken)) throw new Error('token must be Blockchaintoken') - const priceStr = (priceUsd * 10 ** STANDARD_DECIMALS).toString() + const priceInt = BigInt.fromI32(priceUsd) + const priceStr = priceInt.upscale(STANDARD_DECIMALS).toString() _setTokenPrice(token.address.toHexString(), (token as BlockchainToken).chainId, priceStr) } From 1046aa1d5f71583ee7ba61353d28f46a314ae5e2 Mon Sep 17 00:00:00 2001 From: ncomerci Date: Thu, 4 Dec 2025 17:05:43 -0300 Subject: [PATCH 06/22] feat: implement query response for relevant tokens --- packages/lib-ts/src/environment.ts | 27 ++++++++++++------- .../lib-ts/src/queries/RelevantTokensQuery.ts | 2 +- packages/lib-ts/src/types/QueryResponse.ts | 11 ++++++++ 3 files changed, 30 insertions(+), 10 deletions(-) diff --git a/packages/lib-ts/src/environment.ts b/packages/lib-ts/src/environment.ts index 12c46e91..388e4ca3 100644 --- a/packages/lib-ts/src/environment.ts +++ b/packages/lib-ts/src/environment.ts @@ -9,7 +9,7 @@ import { SvmAccountsInfoQueryResponse, TokenPriceQuery, RelevantTokensQuery, - RelevantTokensQueryResponse, + GetRelevantTokensResponse, RelevantTokenBalance, SerializableGetAccountsInfoResponse, SubgraphQuery, @@ -17,7 +17,7 @@ import { } from './queries' import { BlockchainToken, Token, TokenAmount, USD } from './tokens' import { Address, BigInt, ChainId, Result } from './types' -import { PriceQueryResponse } from './types/QueryResponse' +import { PriceQueryResponse, RelevantTokensQueryResponse } from './types/QueryResponse' import { replaceJsonBooleans } from './helpers' export namespace environment { @@ -139,12 +139,16 @@ export namespace environment { * @param usdMinAmount - Minimum USD value threshold for tokens (optional, defaults to zero) * @param tokensList - List of blockchain tokens to include/exclude (optional, defaults to empty array) * @param listType - Whether to include (AllowList) or exclude (DenyList) the tokens in `tokensList` (optional, defaults to DenyList) - * @returns Array of RelevantTokenBalance objects representing the relevant tokens + * @returns Result containing either an array of RelevantTokenBalance arrays or an error string */ - export function rawRelevantTokensQuery(address: Address, chainIds: ChainId[], usdMinAmount: USD, tokensList: BlockchainToken[], listType: ListType): RelevantTokenBalance[][] { + export function rawRelevantTokensQuery(address: Address, chainIds: ChainId[], usdMinAmount: USD, tokensList: BlockchainToken[], listType: ListType): Result { const responseStr = _relevantTokensQuery(JSON.stringify(RelevantTokensQuery.init(address, chainIds, usdMinAmount, tokensList, listType))) - const responses = JSON.parse(responseStr) - return responses.map((response: RelevantTokensQueryResponse) => response.balances) + const parsed = RelevantTokensQueryResponse.fromJson(responseStr) + + if (parsed.success !== 'true') return Result.err(parsed.error.length > 0 ? parsed.error : 'Unknown error getting relevant tokens') + + const responses = parsed.data + return Result.ok(responses.map((response: GetRelevantTokensResponse) => response.balances)) } /** @@ -162,8 +166,12 @@ export namespace environment { usdMinAmount: USD = USD.zero(), tokensList: BlockchainToken[] = [], listType: ListType = ListType.DenyList - ): TokenAmount[] { - const response = rawRelevantTokensQuery(address, chainIds, usdMinAmount, tokensList, listType) + ): Result { + const responseResult = rawRelevantTokensQuery(address, chainIds, usdMinAmount, tokensList, listType) + + if (responseResult.isError) return Result.err(responseResult.error) + + const response = responseResult.value const resultMap: Map = new Map() for (let i = 0; i < response.length; i++) { for (let j = 0; j < response[i].length; j++) { @@ -174,7 +182,8 @@ export namespace environment { resultMap.set(mapKey, tokenAmount) } } - return resultMap.values() + + return Result.ok(resultMap.values()) } /** diff --git a/packages/lib-ts/src/queries/RelevantTokensQuery.ts b/packages/lib-ts/src/queries/RelevantTokensQuery.ts index 257e8377..b095c436 100644 --- a/packages/lib-ts/src/queries/RelevantTokensQuery.ts +++ b/packages/lib-ts/src/queries/RelevantTokensQuery.ts @@ -54,7 +54,7 @@ export class RelevantTokenBalance { } @json -export class RelevantTokensQueryResponse { +export class GetRelevantTokensResponse { constructor( public timestamp: i64, public balances: RelevantTokenBalance[] diff --git a/packages/lib-ts/src/types/QueryResponse.ts b/packages/lib-ts/src/types/QueryResponse.ts index 23e9f096..ae5923f2 100644 --- a/packages/lib-ts/src/types/QueryResponse.ts +++ b/packages/lib-ts/src/types/QueryResponse.ts @@ -1,6 +1,7 @@ import { JSON } from 'json-as' import { replaceJsonBooleans } from '../helpers' +import { GetRelevantTokensResponse } from '../queries' @json class QueryResponseBase { @@ -23,3 +24,13 @@ export class PriceQueryResponse extends QueryResponseBase { this.data = data } } + +@json +export class RelevantTokensQueryResponse extends QueryResponseBase { + public data: GetRelevantTokensResponse[] + + constructor(success: string, data: GetRelevantTokensResponse[], error: string) { + super(success, error) + this.data = data + } +} From a793962e72d7be91b9840bbc2ef6903953175258 Mon Sep 17 00:00:00 2001 From: ncomerci Date: Tue, 9 Dec 2025 13:24:31 -0300 Subject: [PATCH 07/22] feat: implement query result for evm call --- packages/lib-ts/src/environment.ts | 9 ++++++--- packages/lib-ts/src/tokens/ERC20Token.ts | 21 +++++++++++++++++++-- packages/lib-ts/src/types/QueryResponse.ts | 12 +++++++++++- 3 files changed, 36 insertions(+), 6 deletions(-) diff --git a/packages/lib-ts/src/environment.ts b/packages/lib-ts/src/environment.ts index 388e4ca3..dd89aba4 100644 --- a/packages/lib-ts/src/environment.ts +++ b/packages/lib-ts/src/environment.ts @@ -17,7 +17,7 @@ import { } from './queries' import { BlockchainToken, Token, TokenAmount, USD } from './tokens' import { Address, BigInt, ChainId, Result } from './types' -import { PriceQueryResponse, RelevantTokensQueryResponse } from './types/QueryResponse' +import { EvmCallQueryResponse, PriceQueryResponse, RelevantTokensQueryResponse } from './types/QueryResponse' import { replaceJsonBooleans } from './helpers' export namespace environment { @@ -199,8 +199,11 @@ export namespace environment { chainId: ChainId, data: string, timestamp: Date | null = null, - ): string { - return _evmCallQuery(JSON.stringify(EvmCallQuery.from(to, chainId, timestamp, data))) + ): Result { + const responseStr = _evmCallQuery(JSON.stringify(EvmCallQuery.from(to, chainId, timestamp, data))) + const parsed = EvmCallQueryResponse.fromJson(responseStr) + if (parsed.success !== 'true') return Result.err(parsed.error.length > 0 ? parsed.error : 'Unknown error getting evm call') + return Result.ok(parsed.data) } /** diff --git a/packages/lib-ts/src/tokens/ERC20Token.ts b/packages/lib-ts/src/tokens/ERC20Token.ts index 12d8c83c..31e0ea7e 100644 --- a/packages/lib-ts/src/tokens/ERC20Token.ts +++ b/packages/lib-ts/src/tokens/ERC20Token.ts @@ -1,6 +1,7 @@ import { environment } from '../environment' import { evm } from '../evm' import { EVM_NATIVE_ADDRESS } from '../helpers' +import { log } from '../log' import { Address, ChainId, EvmDecodeParam } from '../types' import { BlockchainToken } from './BlockchainToken' @@ -117,7 +118,15 @@ export class ERC20Token extends BlockchainToken { get symbol(): string { if (this._symbol === ERC20Token.EMPTY_SYMBOL) { const response = environment.evmCallQuery(this.address, this.chainId, '0x95d89b41', this._timestamp) - this._symbol = evm.decode(new EvmDecodeParam('string', response)) + if (response.isError) { + log.warning('Failed to get symbol for token {} on chain {}: {}', [ + this.address.toString(), + this.chainId.toString(), + response.error, + ]) + return ERC20Token.EMPTY_SYMBOL + } + this._symbol = evm.decode(new EvmDecodeParam('string', response.value)) } return this._symbol } @@ -132,7 +141,15 @@ export class ERC20Token extends BlockchainToken { get decimals(): u8 { if (this._decimals == ERC20Token.EMPTY_DECIMALS) { const result = environment.evmCallQuery(this.address, this.chainId, '0x313ce567', this._timestamp) - this._decimals = u8.parse(evm.decode(new EvmDecodeParam('uint8', result))) + if (result.isError) { + log.warning('Failed to get decimals for token {} on chain {}: {}', [ + this.address.toString(), + this.chainId.toString(), + result.error, + ]) + return ERC20Token.EMPTY_DECIMALS + } + this._decimals = u8.parse(evm.decode(new EvmDecodeParam('uint8', result.value))) } return this._decimals } diff --git a/packages/lib-ts/src/types/QueryResponse.ts b/packages/lib-ts/src/types/QueryResponse.ts index ae5923f2..1ba35302 100644 --- a/packages/lib-ts/src/types/QueryResponse.ts +++ b/packages/lib-ts/src/types/QueryResponse.ts @@ -4,7 +4,7 @@ import { replaceJsonBooleans } from '../helpers' import { GetRelevantTokensResponse } from '../queries' @json -class QueryResponseBase { +export class QueryResponseBase { constructor( public success: string, // boolean as string due to json-as bug public error: string @@ -34,3 +34,13 @@ export class RelevantTokensQueryResponse extends QueryResponseBase { this.data = data } } + +@json +export class EvmCallQueryResponse extends QueryResponseBase { + public data: string + + constructor(success: string, data: string, error: string) { + super(success, error) + this.data = data + } +} From ba3dde2e3fc8e9758f428747a36c0dff916d59e7 Mon Sep 17 00:00:00 2001 From: ncomerci Date: Tue, 9 Dec 2025 15:20:30 -0300 Subject: [PATCH 08/22] fix: FunctionHandler to return Result type for EVM calls --- .../lib/AbisInterfaceGenerator/FunctionHandler.ts | 15 +++++++++++---- .../cli/src/lib/AbisInterfaceGenerator/types.ts | 1 + 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/packages/cli/src/lib/AbisInterfaceGenerator/FunctionHandler.ts b/packages/cli/src/lib/AbisInterfaceGenerator/FunctionHandler.ts index d03b5a5a..6cae29ca 100644 --- a/packages/cli/src/lib/AbisInterfaceGenerator/FunctionHandler.ts +++ b/packages/cli/src/lib/AbisInterfaceGenerator/FunctionHandler.ts @@ -122,20 +122,27 @@ export default class FunctionHandler { const methodName = fn.escapedName || fn.name const capitalizedName = this.getCapitalizedName(fn) - lines.push(` ${methodName}(${methodParams}): ${returnType} {`) + importManager.addType('environment') + importManager.addType('Result') + + const resultReturnType = returnType === 'void' ? 'Result' : `Result<${returnType}, string>` + lines.push(` ${methodName}(${methodParams}): ${resultReturnType} {`) lines.push( ` const encodedData = ${contractName}Utils.encode${capitalizedName}(${inputs.map((p) => p.escapedName!).join(', ')})` ) - importManager.addType('environment') const contractCallLine = `environment.evmCallQuery(this._address, this._chainId, encodedData.toHexString(), this._timestamp)` if (returnType === 'void') { - lines.push(` ${contractCallLine}`) + lines.push(` const response = ${contractCallLine}`) + lines.push(` if (response.isError) return Result.err(response.error)`) + lines.push(` return Result.ok(changetype(0))`) } else { lines.push(` const response = ${contractCallLine}`) - lines.push(` return ${contractName}Utils.decode${capitalizedName}(response)`) + lines.push(` if (response.isError) return Result.err<${returnType}, string>(response.error)`) + lines.push(` const decoded = ${contractName}Utils.decode${capitalizedName}(response.value)`) + lines.push(` return Result.ok<${returnType}, string>(decoded)`) } lines.push(` }`) diff --git a/packages/cli/src/lib/AbisInterfaceGenerator/types.ts b/packages/cli/src/lib/AbisInterfaceGenerator/types.ts index ec10e01d..e4363070 100644 --- a/packages/cli/src/lib/AbisInterfaceGenerator/types.ts +++ b/packages/cli/src/lib/AbisInterfaceGenerator/types.ts @@ -8,6 +8,7 @@ export type ImportedTypes = | 'EvmDecodeParam' | 'JSON' | 'EvmCallBuilder' + | 'Result' export type MapBaseTypeCallback = (param: AbiParameter) => string export type TupleDefinition = { className: string From 441e7efb25c4bcfe1d7a792dea43814c5462702a Mon Sep 17 00:00:00 2001 From: ncomerci Date: Tue, 9 Dec 2025 15:51:27 -0300 Subject: [PATCH 09/22] feat: implement query result for subgraph queries --- packages/lib-ts/src/environment.ts | 24 ++++++++++--------- .../lib-ts/src/queries/RelevantTokensQuery.ts | 6 ++--- packages/lib-ts/src/queries/SubgraphQuery.ts | 2 +- packages/lib-ts/src/types/QueryResponse.ts | 16 ++++++++++--- 4 files changed, 30 insertions(+), 18 deletions(-) diff --git a/packages/lib-ts/src/environment.ts b/packages/lib-ts/src/environment.ts index dd89aba4..d82477aa 100644 --- a/packages/lib-ts/src/environment.ts +++ b/packages/lib-ts/src/environment.ts @@ -9,16 +9,16 @@ import { SvmAccountsInfoQueryResponse, TokenPriceQuery, RelevantTokensQuery, - GetRelevantTokensResponse, - RelevantTokenBalance, - SerializableGetAccountsInfoResponse, + RelevantTokensQueryResult, + TokenBalanceQuery, + SubgraphQueryResult, SubgraphQuery, - SubgraphQueryResponse, + SerializableGetAccountsInfoResponse, } from './queries' import { BlockchainToken, Token, TokenAmount, USD } from './tokens' import { Address, BigInt, ChainId, Result } from './types' -import { EvmCallQueryResponse, PriceQueryResponse, RelevantTokensQueryResponse } from './types/QueryResponse' import { replaceJsonBooleans } from './helpers' +import { EvmCallQueryResponse, PriceQueryResponse, RelevantTokensQueryResponse, SubgraphQueryResponse } from './types/QueryResponse' export namespace environment { @external('environment', '_evmCall') @@ -141,14 +141,14 @@ export namespace environment { * @param listType - Whether to include (AllowList) or exclude (DenyList) the tokens in `tokensList` (optional, defaults to DenyList) * @returns Result containing either an array of RelevantTokenBalance arrays or an error string */ - export function rawRelevantTokensQuery(address: Address, chainIds: ChainId[], usdMinAmount: USD, tokensList: BlockchainToken[], listType: ListType): Result { + export function rawRelevantTokensQuery(address: Address, chainIds: ChainId[], usdMinAmount: USD, tokensList: BlockchainToken[], listType: ListType): Result { const responseStr = _relevantTokensQuery(JSON.stringify(RelevantTokensQuery.init(address, chainIds, usdMinAmount, tokensList, listType))) const parsed = RelevantTokensQueryResponse.fromJson(responseStr) - if (parsed.success !== 'true') return Result.err(parsed.error.length > 0 ? parsed.error : 'Unknown error getting relevant tokens') + if (parsed.success !== 'true') return Result.err(parsed.error.length > 0 ? parsed.error : 'Unknown error getting relevant tokens') const responses = parsed.data - return Result.ok(responses.map((response: GetRelevantTokensResponse) => response.balances)) + return Result.ok(responses.map((response: RelevantTokensQueryResult) => response.balances)) } /** @@ -219,9 +219,11 @@ export namespace environment { subgraphId: string, query: string, timestamp: Date | null = null, - ): SubgraphQueryResponse { - const response = _subgraphQuery(JSON.stringify(SubgraphQuery.from(chainId, subgraphId, query, timestamp))) - return JSON.parse(response) + ): Result { + const responseStr = _subgraphQuery(JSON.stringify(SubgraphQuery.from(chainId, subgraphId, query, timestamp))) + const parsed = SubgraphQueryResponse.fromJson(responseStr) + if (parsed.success !== 'true') return Result.err(parsed.error.length > 0 ? parsed.error : 'Unknown error getting subgraph query') + return Result.ok(parsed.data) } /** diff --git a/packages/lib-ts/src/queries/RelevantTokensQuery.ts b/packages/lib-ts/src/queries/RelevantTokensQuery.ts index b095c436..a0383045 100644 --- a/packages/lib-ts/src/queries/RelevantTokensQuery.ts +++ b/packages/lib-ts/src/queries/RelevantTokensQuery.ts @@ -39,7 +39,7 @@ export class RelevantTokensQuery { } @json -export class RelevantTokenBalance { +export class TokenBalanceQuery { constructor( public token: TokenQuery, public balance: string @@ -54,9 +54,9 @@ export class RelevantTokenBalance { } @json -export class GetRelevantTokensResponse { +export class RelevantTokensQueryResult { constructor( public timestamp: i64, - public balances: RelevantTokenBalance[] + public balances: TokenBalanceQuery[] ) {} } diff --git a/packages/lib-ts/src/queries/SubgraphQuery.ts b/packages/lib-ts/src/queries/SubgraphQuery.ts index 75c8c1a7..f81c9563 100644 --- a/packages/lib-ts/src/queries/SubgraphQuery.ts +++ b/packages/lib-ts/src/queries/SubgraphQuery.ts @@ -26,7 +26,7 @@ export class SubgraphQuery extends SubgraphQueryBase { } @json -export class SubgraphQueryResponse { +export class SubgraphQueryResult { constructor( public blockNumber: i64, public data: string diff --git a/packages/lib-ts/src/types/QueryResponse.ts b/packages/lib-ts/src/types/QueryResponse.ts index 1ba35302..37845388 100644 --- a/packages/lib-ts/src/types/QueryResponse.ts +++ b/packages/lib-ts/src/types/QueryResponse.ts @@ -1,7 +1,7 @@ import { JSON } from 'json-as' import { replaceJsonBooleans } from '../helpers' -import { GetRelevantTokensResponse } from '../queries' +import { RelevantTokensQueryResult, SubgraphQueryResult } from '../queries' @json export class QueryResponseBase { @@ -27,9 +27,9 @@ export class PriceQueryResponse extends QueryResponseBase { @json export class RelevantTokensQueryResponse extends QueryResponseBase { - public data: GetRelevantTokensResponse[] + public data: RelevantTokensQueryResult[] - constructor(success: string, data: GetRelevantTokensResponse[], error: string) { + constructor(success: string, data: RelevantTokensQueryResult[], error: string) { super(success, error) this.data = data } @@ -44,3 +44,13 @@ export class EvmCallQueryResponse extends QueryResponseBase { this.data = data } } + +@json +export class SubgraphQueryResponse extends QueryResponseBase { + public data: SubgraphQueryResult + + constructor(success: string, data: SubgraphQueryResult, error: string) { + super(success, error) + this.data = data + } +} From 1ab1de8e2fa778ec07ef36582f506c9e949bfeca Mon Sep 17 00:00:00 2001 From: ncomerci Date: Tue, 9 Dec 2025 16:38:38 -0300 Subject: [PATCH 10/22] feat: implement query result for svm query --- packages/lib-ts/src/environment.ts | 21 +++++++------- .../src/queries/SvmAccountsInfoQuery.ts | 28 +++++++++++++------ packages/lib-ts/src/tokens/SPLToken.ts | 8 ++++-- 3 files changed, 34 insertions(+), 23 deletions(-) diff --git a/packages/lib-ts/src/environment.ts b/packages/lib-ts/src/environment.ts index d82477aa..80effc2b 100644 --- a/packages/lib-ts/src/environment.ts +++ b/packages/lib-ts/src/environment.ts @@ -11,13 +11,12 @@ import { RelevantTokensQuery, RelevantTokensQueryResult, TokenBalanceQuery, - SubgraphQueryResult, SubgraphQuery, - SerializableGetAccountsInfoResponse, + SvmAccountsInfoQueryResult, + SubgraphQueryResult, } from './queries' import { BlockchainToken, Token, TokenAmount, USD } from './tokens' import { Address, BigInt, ChainId, Result } from './types' -import { replaceJsonBooleans } from './helpers' import { EvmCallQueryResponse, PriceQueryResponse, RelevantTokensQueryResponse, SubgraphQueryResponse } from './types/QueryResponse' export namespace environment { @@ -230,19 +229,19 @@ export namespace environment { * SVM - Gets on-chain account info * @param publicKeys - Accounts to read from chain * @param timestamp - The timestamp for the call context (optional) - * @returns The raw response from the underlying getMultipleAccountsInfo call + * @returns Result containing either the account info result or an error string */ - export function svmAccountsInfoQuery( publicKeys: Address[], timestamp: Date | null = null, - ): SvmAccountsInfoQueryResponse { - // There is a bug with json-as, so we have to do this with JSON booleans + ): Result { const responseStr = _svmAccountsInfoQuery(JSON.stringify(SvmAccountsInfoQuery.from(publicKeys, timestamp))) - const fixedResponseStr = replaceJsonBooleans(responseStr) - - const response = JSON.parse(fixedResponseStr) - return SvmAccountsInfoQueryResponse.fromSerializable(response) + const parsed = SvmAccountsInfoQueryResponse.fromJson(responseStr) + + if (parsed.success !== 'true') return Result.err(parsed.error.length > 0 ? parsed.error : 'Unknown error getting SVM accounts info') + + const result = SvmAccountsInfoQueryResult.fromSerializable(parsed.data) + return Result.ok(result) } /** diff --git a/packages/lib-ts/src/queries/SvmAccountsInfoQuery.ts b/packages/lib-ts/src/queries/SvmAccountsInfoQuery.ts index a1329200..9edcd60d 100644 --- a/packages/lib-ts/src/queries/SvmAccountsInfoQuery.ts +++ b/packages/lib-ts/src/queries/SvmAccountsInfoQuery.ts @@ -1,4 +1,5 @@ import { Address, SerializableSvmAccountInfo, SvmAccountInfo } from '../types' +import { QueryResponseBase } from '../types/QueryResponse' @json class SvmAccountsInfoQueryBase { @@ -22,15 +23,22 @@ export class SvmAccountsInfoQuery extends SvmAccountsInfoQueryBase { } } -// There is a bug with json-as, so this can't be parsed directly -export class SvmAccountsInfoQueryResponse { +@json +export class SerializableSvmAccountsInfoQueryResult { + constructor( + public accountsInfo: SerializableSvmAccountInfo[], + public slot: string + ) {} +} + +export class SvmAccountsInfoQueryResult { constructor( public accountsInfo: SvmAccountInfo[], public slot: string ) {} - static fromSerializable(serializable: SerializableGetAccountsInfoResponse): SvmAccountsInfoQueryResponse { - return new SvmAccountsInfoQueryResponse( + static fromSerializable(serializable: SerializableSvmAccountsInfoQueryResult): SvmAccountsInfoQueryResult { + return new SvmAccountsInfoQueryResult( serializable.accountsInfo.map((acc: SerializableSvmAccountInfo) => SvmAccountInfo.fromSerializable(acc)), serializable.slot ) @@ -38,9 +46,11 @@ export class SvmAccountsInfoQueryResponse { } @json -export class SerializableGetAccountsInfoResponse { - constructor( - public accountsInfo: SerializableSvmAccountInfo[], - public slot: string - ) {} +export class SvmAccountsInfoQueryResponse extends QueryResponseBase { + public data: SerializableSvmAccountsInfoQueryResult + + constructor(success: string, data: SerializableSvmAccountsInfoQueryResult, error: string) { + super(success, error) + this.data = data + } } diff --git a/packages/lib-ts/src/tokens/SPLToken.ts b/packages/lib-ts/src/tokens/SPLToken.ts index a8fb05d1..0bb20faa 100644 --- a/packages/lib-ts/src/tokens/SPLToken.ts +++ b/packages/lib-ts/src/tokens/SPLToken.ts @@ -73,7 +73,8 @@ export class SPLToken extends BlockchainToken { get decimals(): u8 { if (this._decimals == SPLToken.EMPTY_DECIMALS) { const result = environment.svmAccountsInfoQuery([this.address]) - const decimals = SvmMint.fromHex(result.accountsInfo[0].data).decimals + if (result.isError) throw new Error(result.error) + const decimals = SvmMint.fromHex(result.value.accountsInfo[0].data).decimals this._decimals = decimals } return this._decimals @@ -89,12 +90,13 @@ export class SPLToken extends BlockchainToken { get symbol(): string { if (this._symbol == SPLToken.EMPTY_SYMBOL) { const result = environment.svmAccountsInfoQuery([this.getMetadataAddress()]) - const data = result.accountsInfo[0].data + if (result.isError) throw new Error(result.error) + const data = result.value.accountsInfo[0].data // Return placeholder symbol from address if TokenMetadata standard is not used this._symbol = data === '0x' ? `${this.address.toString().slice(0, 5)}...${this.address.toString().slice(-5)}` - : SvmTokenMetadataData.fromTokenMetadataHex(result.accountsInfo[0].data).symbol + : SvmTokenMetadataData.fromTokenMetadataHex(result.value.accountsInfo[0].data).symbol } return this._symbol } From 6c2e61fa8212208deb556aa8f6a77394a7b2abcd Mon Sep 17 00:00:00 2001 From: ncomerci Date: Tue, 9 Dec 2025 16:44:42 -0300 Subject: [PATCH 11/22] refactor: query responses --- packages/lib-ts/src/queries/EvmCallQuery.ts | 12 +++++- .../lib-ts/src/queries/RelevantTokensQuery.ts | 12 +++++- packages/lib-ts/src/queries/SubgraphQuery.ts | 12 +++++- .../lib-ts/src/queries/TokenPriceQuery.ts | 11 +++++ packages/lib-ts/src/types/QueryResponse.ts | 41 ------------------- packages/lib-ts/src/types/index.ts | 1 + 6 files changed, 45 insertions(+), 44 deletions(-) diff --git a/packages/lib-ts/src/queries/EvmCallQuery.ts b/packages/lib-ts/src/queries/EvmCallQuery.ts index 7cb9088e..6f6eb06b 100644 --- a/packages/lib-ts/src/queries/EvmCallQuery.ts +++ b/packages/lib-ts/src/queries/EvmCallQuery.ts @@ -1,4 +1,4 @@ -import { Address, ChainId } from '../types' +import { Address, ChainId, QueryResponseBase } from '../types' @json class EvmCallQueryBase { @@ -25,3 +25,13 @@ export class EvmCallQuery extends EvmCallQueryBase { : new EvmCallQueryBase(address, chainId, data) } } + +@json +export class EvmCallQueryResponse extends QueryResponseBase { + public data: string + + constructor(success: string, data: string, error: string) { + super(success, error) + this.data = data + } +} diff --git a/packages/lib-ts/src/queries/RelevantTokensQuery.ts b/packages/lib-ts/src/queries/RelevantTokensQuery.ts index a0383045..c903124f 100644 --- a/packages/lib-ts/src/queries/RelevantTokensQuery.ts +++ b/packages/lib-ts/src/queries/RelevantTokensQuery.ts @@ -1,6 +1,6 @@ import { ListType } from '../helpers' import { BlockchainToken, TokenAmount, USD } from '../tokens' -import { Address, BigInt, ChainId } from '../types' +import { Address, BigInt, ChainId, QueryResponseBase } from '../types' @json class TokenQuery { @@ -60,3 +60,13 @@ export class RelevantTokensQueryResult { public balances: TokenBalanceQuery[] ) {} } + +@json +export class RelevantTokensQueryResponse extends QueryResponseBase { + public data: RelevantTokensQueryResult[] + + constructor(success: string, data: RelevantTokensQueryResult[], error: string) { + super(success, error) + this.data = data + } +} diff --git a/packages/lib-ts/src/queries/SubgraphQuery.ts b/packages/lib-ts/src/queries/SubgraphQuery.ts index f81c9563..b41cc7df 100644 --- a/packages/lib-ts/src/queries/SubgraphQuery.ts +++ b/packages/lib-ts/src/queries/SubgraphQuery.ts @@ -1,4 +1,4 @@ -import { ChainId } from '../types' +import { ChainId, QueryResponseBase } from '../types' @json class SubgraphQueryBase { @@ -32,3 +32,13 @@ export class SubgraphQueryResult { public data: string ) {} } + +@json +export class SubgraphQueryResponse extends QueryResponseBase { + public data: SubgraphQueryResult + + constructor(success: string, data: SubgraphQueryResult, error: string) { + super(success, error) + this.data = data + } +} diff --git a/packages/lib-ts/src/queries/TokenPriceQuery.ts b/packages/lib-ts/src/queries/TokenPriceQuery.ts index edf4b8e3..0e494215 100644 --- a/packages/lib-ts/src/queries/TokenPriceQuery.ts +++ b/packages/lib-ts/src/queries/TokenPriceQuery.ts @@ -1,4 +1,5 @@ import { BlockchainToken } from '../tokens' +import { QueryResponseBase } from '../types' @json class TokenPriceQueryBase { @@ -26,3 +27,13 @@ export class TokenPriceQuery extends TokenPriceQueryBase { : new TokenPriceQueryBase(address, chainId) } } + +@json +export class TokenPriceQueryResponse extends QueryResponseBase { + public data: string[] + + constructor(success: string, data: string[], error: string) { + super(success, error) + this.data = data + } +} diff --git a/packages/lib-ts/src/types/QueryResponse.ts b/packages/lib-ts/src/types/QueryResponse.ts index 37845388..f28fa053 100644 --- a/packages/lib-ts/src/types/QueryResponse.ts +++ b/packages/lib-ts/src/types/QueryResponse.ts @@ -1,7 +1,6 @@ import { JSON } from 'json-as' import { replaceJsonBooleans } from '../helpers' -import { RelevantTokensQueryResult, SubgraphQueryResult } from '../queries' @json export class QueryResponseBase { @@ -14,43 +13,3 @@ export class QueryResponseBase { return JSON.parse(replaceJsonBooleans(json)) } } - -@json -export class PriceQueryResponse extends QueryResponseBase { - public data: string[] - - constructor(success: string, data: string[], error: string) { - super(success, error) - this.data = data - } -} - -@json -export class RelevantTokensQueryResponse extends QueryResponseBase { - public data: RelevantTokensQueryResult[] - - constructor(success: string, data: RelevantTokensQueryResult[], error: string) { - super(success, error) - this.data = data - } -} - -@json -export class EvmCallQueryResponse extends QueryResponseBase { - public data: string - - constructor(success: string, data: string, error: string) { - super(success, error) - this.data = data - } -} - -@json -export class SubgraphQueryResponse extends QueryResponseBase { - public data: SubgraphQueryResult - - constructor(success: string, data: SubgraphQueryResult, error: string) { - super(success, error) - this.data = data - } -} diff --git a/packages/lib-ts/src/types/index.ts b/packages/lib-ts/src/types/index.ts index d07ebe60..b5f12c20 100644 --- a/packages/lib-ts/src/types/index.ts +++ b/packages/lib-ts/src/types/index.ts @@ -7,6 +7,7 @@ export * from './Bytes' export * from './ChainId' export * from './evm' export * from './Option' +export * from './QueryResponse' export * from './Result' export * from './svm' export * from './TriggerType' From 5812fac355938667cde94af76eb7dccdf1a4a38e Mon Sep 17 00:00:00 2001 From: ncomerci Date: Tue, 9 Dec 2025 17:07:36 -0300 Subject: [PATCH 12/22] fix: lib test --- packages/lib-ts/as-pect.config.js | 44 ++++++++++++------- packages/lib-ts/tests/tokens/SPLToken.spec.ts | 12 ++--- 2 files changed, 33 insertions(+), 23 deletions(-) diff --git a/packages/lib-ts/as-pect.config.js b/packages/lib-ts/as-pect.config.js index 60665a64..bc186b7f 100644 --- a/packages/lib-ts/as-pect.config.js +++ b/packages/lib-ts/as-pect.config.js @@ -70,29 +70,18 @@ export default { const key = `_tokenPriceQuery:${address}:${chainId}${timestamp ? `:${timestamp}` : ''}` if (store.has(key)) { - const response = JSON.stringify({ - success: true, - data: [store.get(key).toString()], - error: '', - }) + const response = JSON.stringify(Result.ok([store.get(key).toString()])) return exports.__newString(response) } - - return exports.__newString( - JSON.stringify({ - success: false, - data: [], - error: `Price not found for key: ${key}`, - }) - ) + return exports.__newString(JSON.stringify(Result.err(`Price not found for key: ${key}`))) }, _evmCallQuery: (paramsPtr) => { const paramsStr = exports.__getString(paramsPtr) const params = JSON.parse(paramsStr) const key = `_evmCallQuery:${params.to.toLowerCase()}:${params.chainId}:${params.data.toLowerCase()}` - if (store.has(key)) return exports.__newString(store.get(key)) - throw new Error(`Contract call result not found for key: ${key}`) + if (store.has(key)) return exports.__newString(JSON.stringify(Result.ok(store.get(key)))) + return exports.__newString(JSON.stringify(Result.err(`Contract call result not found for key: ${key}`))) }, _svmAccountsInfoQuery: (paramsPtr) => { const paramsStr = exports.__getString(paramsPtr) @@ -100,8 +89,13 @@ export default { const publicKeys = params.publicKeys.join(',') const key = `_svmAccountsInfoQuery:${publicKeys}` - if (store.has(key)) return exports.__newString(store.get(key)) - throw new Error(`Get accounts info result not found for key: ${key}`) + if (store.has(key)) { + const storedValue = store.get(key) + const accountsInfoData = typeof storedValue === 'string' ? JSON.parse(storedValue) : storedValue + return exports.__newString(JSON.stringify(Result.ok(accountsInfoData))) + } + + return exports.__newString(JSON.stringify(Result.err(`Get accounts info result not found for key: ${key}`))) }, _getContext: () => { const key = `_getContext` @@ -182,3 +176,19 @@ export default { */ outputBinary: false, } + +class Result { + constructor(success, data, error) { + this.success = success + this.data = data + this.error = error + } + + static ok(data) { + return new Result(true, data, '') + } + + static err(error) { + return new Result(false, '', error) + } +} diff --git a/packages/lib-ts/tests/tokens/SPLToken.spec.ts b/packages/lib-ts/tests/tokens/SPLToken.spec.ts index 99919dbc..dcf2c172 100644 --- a/packages/lib-ts/tests/tokens/SPLToken.spec.ts +++ b/packages/lib-ts/tests/tokens/SPLToken.spec.ts @@ -141,21 +141,21 @@ describe('SPLToken', () => { ) const result = `{"address":"${metadataAddr.toString()}","bump":255}` - setFindProgramAddress(params.seeds, Address.fromString(params.programId), result) + setFindProgramAddress(params.seeds, Address.fromBase58String(params.programId), result) // In reality, the svmAccountsInfoQuery returns null and then a default value with data "0x". But this is easier to mock setGetAccountsInfo( `${metadataAddr.toString()}`, `{ "accountsInfo": [ { - "executable": false, // dont care - "rentEpoch": "1234", // dont care - "owner": "${randomSvmAddress()}", // dont care - "lamports": "100", // dont care + "executable": false, + "rentEpoch": "1234", + "owner": "${randomSvmAddress()}", + "lamports": "100", "data":"0x" } ], - "slot":"12345678" // dont care + "slot":"12345678" }` ) From 7d19e25f72c51bf703879acaa9641ee5568bcb19 Mon Sep 17 00:00:00 2001 From: ncomerci Date: Tue, 9 Dec 2025 17:15:13 -0300 Subject: [PATCH 13/22] fix: cli tests --- .../cli/tests/AbisInterfaceGenerator.spec.ts | 27 ++++++++++++------- 1 file changed, 17 insertions(+), 10 deletions(-) diff --git a/packages/cli/tests/AbisInterfaceGenerator.spec.ts b/packages/cli/tests/AbisInterfaceGenerator.spec.ts index af75ab78..c085d291 100644 --- a/packages/cli/tests/AbisInterfaceGenerator.spec.ts +++ b/packages/cli/tests/AbisInterfaceGenerator.spec.ts @@ -99,7 +99,7 @@ describe('AbisInterfaceGenerator', () => { const result = AbisInterfaceGenerator.generate(abi, CONTRACT_NAME) - expect(result).to.contain(`getData(): ${LibTypes.BigInt} {`) + expect(result).to.contain(`getData(): Result<${LibTypes.BigInt}, string> {`) expect(result).to.contain(`getData_1(id: ${LibTypes.BigInt}): EvmCallBuilder {`) expect(result).to.contain( `getData_2(id: ${LibTypes.BigInt}, flag: ${AssemblyPrimitiveTypes.bool}, value: ${LibTypes.BigInt}): EvmCallBuilder {` @@ -120,10 +120,10 @@ describe('AbisInterfaceGenerator', () => { const result = AbisInterfaceGenerator.generate(abi, CONTRACT_NAME) - expect(result).to.contain(`getBalance(): ${LibTypes.BigInt} {`) + expect(result).to.contain(`getBalance(): Result<${LibTypes.BigInt}, string> {`) expect(result).to.contain(`transfer(to: ${LibTypes.Address}): EvmCallBuilder {`) - expect(result).to.contain(`getBalance_1(owner: ${LibTypes.Address}): ${LibTypes.BigInt} {`) - expect(result).to.contain(`getName(): ${AssemblyPrimitiveTypes.string} {`) + expect(result).to.contain(`getBalance_1(owner: ${LibTypes.Address}): Result<${LibTypes.BigInt}, string> {`) + expect(result).to.contain(`getName(): Result<${AssemblyPrimitiveTypes.string}, string> {`) expect(result).to.contain( `transfer_1(to: ${LibTypes.Address}, amount: ${LibTypes.BigInt}, value: ${LibTypes.BigInt}): EvmCallBuilder {` ) @@ -137,7 +137,7 @@ describe('AbisInterfaceGenerator', () => { const result = AbisInterfaceGenerator.generate(abi, CONTRACT_NAME) - expect(result).to.contain(`constructor_(): ${LibTypes.BigInt} {`) + expect(result).to.contain(`constructor_(): Result<${LibTypes.BigInt}, string> {`) expect(result).to.contain(`constructor_1(value: ${LibTypes.BigInt}): EvmCallBuilder {`) }) }) @@ -239,7 +239,9 @@ describe('AbisInterfaceGenerator', () => { expect(result).to.contain( `const response = environment.evmCallQuery(this._address, this._chainId, encodedData.toHexString(), this._timestamp)` ) - expect(result).to.contain(`return ${CONTRACT_NAME}Utils.decodeGetBalance(response)`) + expect(result).to.contain(`if (response.isError) return Result.err<${LibTypes.BigInt}, string>(response.error)`) + expect(result).to.contain(`const decoded = ${CONTRACT_NAME}Utils.decodeGetBalance(response.value)`) + expect(result).to.contain(`return Result.ok<${LibTypes.BigInt}, string>(decoded)`) expect(result).to.contain(`export class ${CONTRACT_NAME}Utils {`) expect(result).to.contain(`static encodeGetBalance(owner: Address): Bytes {`) expect(result).to.contain(`static decodeGetBalance(encodedResponse: string): BigInt {`) @@ -311,12 +313,14 @@ describe('AbisInterfaceGenerator', () => { const selector = getFunctionSelector(abi[0]) - expect(result).to.contain(`${functionName}(): void {`) + expect(result).to.contain(`${functionName}(): Result {`) expect(result).to.contain(`static encodeNoReturn(): Bytes {`) expect(result).to.contain(`return ${LibTypes.Bytes}.fromHexString('${selector}')`) expect(result).to.contain( `environment.evmCallQuery(this._address, this._chainId, encodedData.toHexString(), this._timestamp)` ) + expect(result).to.contain(`if (response.isError) return Result.err(response.error)`) + expect(result).to.contain(`return Result.ok(changetype(0))`) expect(result).not.to.contain(`_decodeNoReturnResponse`) }) }) @@ -341,6 +345,7 @@ describe('AbisInterfaceGenerator', () => { expect(importMatch).to.contain('EvmDecodeParam') expect(importMatch).to.contain('evm') expect(importMatch).to.contain('environment') + expect(importMatch).to.contain('Result') }) }) @@ -863,7 +868,7 @@ describe('AbisInterfaceGenerator', () => { expect(result).to.contain('static encodeGetBalance(owner: Address): Bytes {') expect(result).to.contain('static decodeGetBalance(encodedResponse: string): BigInt {') - expect(result).to.contain('getBalance(owner: Address): BigInt {') + expect(result).to.contain('getBalance(owner: Address): Result {') }) it('should generate encoded data method for write functions', () => { @@ -887,7 +892,7 @@ describe('AbisInterfaceGenerator', () => { const result = AbisInterfaceGenerator.generate(abi, CONTRACT_NAME) expect(result).to.contain('static encodeValidate(data: Bytes): Bytes {') - expect(result).to.contain('validate(data: Bytes): void {') + expect(result).to.contain('validate(data: Bytes): Result {') expect(result).not.to.contain('_decodeValidateResponse') }) @@ -901,7 +906,9 @@ describe('AbisInterfaceGenerator', () => { // Read function should call both helpers expect(result).to.contain(`const encodedData = ${CONTRACT_NAME}Utils.encodeGetValue()`) - expect(result).to.contain(`return ${CONTRACT_NAME}Utils.decodeGetValue(response)`) + expect(result).to.contain(`if (response.isError) return Result.err<${LibTypes.BigInt}, string>(response.error)`) + expect(result).to.contain(`const decoded = ${CONTRACT_NAME}Utils.decodeGetValue(response.value)`) + expect(result).to.contain(`return Result.ok<${LibTypes.BigInt}, string>(decoded)`) // Write function should call encoded data helper expect(result).to.contain(`const encodedData = ${CONTRACT_NAME}Utils.encodeSetValue(value)`) From 406b3a8081178783934dc76495c7c19c8a4bc7ef Mon Sep 17 00:00:00 2001 From: ncomerci Date: Tue, 9 Dec 2025 18:05:54 -0300 Subject: [PATCH 14/22] fix: handle errors in SPLToken metadata queries with logging --- packages/lib-ts/src/tokens/SPLToken.ts | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/packages/lib-ts/src/tokens/SPLToken.ts b/packages/lib-ts/src/tokens/SPLToken.ts index 0bb20faa..5d4a4da9 100644 --- a/packages/lib-ts/src/tokens/SPLToken.ts +++ b/packages/lib-ts/src/tokens/SPLToken.ts @@ -1,5 +1,6 @@ import { environment } from '../environment' import { SVM_NATIVE_ADDRESS } from '../helpers' +import { log } from '../log' import { svm } from '../svm' import { Address, ChainId, SvmMint, SvmPdaSeed, SvmTokenMetadataData } from '../types' @@ -73,7 +74,14 @@ export class SPLToken extends BlockchainToken { get decimals(): u8 { if (this._decimals == SPLToken.EMPTY_DECIMALS) { const result = environment.svmAccountsInfoQuery([this.address]) - if (result.isError) throw new Error(result.error) + if (result.isError) { + log.warning('Failed to get decimals for token {} on chain {}: {}', [ + this.address.toString(), + this.chainId.toString(), + result.error, + ]) + return SPLToken.EMPTY_DECIMALS + } const decimals = SvmMint.fromHex(result.value.accountsInfo[0].data).decimals this._decimals = decimals } @@ -90,7 +98,14 @@ export class SPLToken extends BlockchainToken { get symbol(): string { if (this._symbol == SPLToken.EMPTY_SYMBOL) { const result = environment.svmAccountsInfoQuery([this.getMetadataAddress()]) - if (result.isError) throw new Error(result.error) + if (result.isError) { + log.warning('Failed to get symbol for token {} on chain {}: {}', [ + this.address.toString(), + this.chainId.toString(), + result.error, + ]) + return SPLToken.EMPTY_SYMBOL + } const data = result.value.accountsInfo[0].data // Return placeholder symbol from address if TokenMetadata standard is not used this._symbol = From 9ef43ba2c003eb4529466b0cc421de559c4bde03 Mon Sep 17 00:00:00 2001 From: ncomerci Date: Wed, 10 Dec 2025 10:49:02 -0300 Subject: [PATCH 15/22] fix: update default values for mock functions in RunnerMock --- packages/test-ts/src/RunnerMock.ts | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/packages/test-ts/src/RunnerMock.ts b/packages/test-ts/src/RunnerMock.ts index e3d91a26..8aa1b743 100644 --- a/packages/test-ts/src/RunnerMock.ts +++ b/packages/test-ts/src/RunnerMock.ts @@ -158,15 +158,16 @@ export default class RunnerMock { } private getDefaultEnvImports(): WebAssembly.ModuleImports { + const defaultResult = (data: unknown) => '{"success":"true","data":' + JSON.stringify(data) + ',"error":""}' return { _evmCall: this.createLogFn('_evmCall'), _svmCall: this.createLogFn('_svmCall'), _swap: this.createLogFn('_swap'), _transfer: this.createLogFn('_transfer'), - _tokenPriceQuery: this.createMockFunction('_tokenPriceQuery', { default: '' }), - _relevantTokensQuery: this.createMockFunction('_relevantTokensQuery', { default: '' }), - _evmCallQuery: this.createMockFunction('_evmCallQuery', { default: '' }), - _svmAccountsInfoQuery: this.createMockFunction('_svmAccountsInfoQuery', { default: '' }), + _tokenPriceQuery: this.createMockFunction('_tokenPriceQuery', { default: defaultResult([]) }), + _relevantTokensQuery: this.createMockFunction('_relevantTokensQuery', { default: defaultResult([]) }), + _evmCallQuery: this.createMockFunction('_evmCallQuery', { default: defaultResult('0x') }), + _svmAccountsInfoQuery: this.createMockFunction('_svmAccountsInfoQuery', { default: defaultResult([]) }), _getContext: this.createMockFunction('_getContext', { default: '' }), } } From d1405b48115b54f8006a5c7f19710a431f1809ca Mon Sep 17 00:00:00 2001 From: ncomerci Date: Wed, 10 Dec 2025 11:03:46 -0300 Subject: [PATCH 16/22] fix: update mock responses --- .../integration/tests/005-get-price-query/mock.json | 8 ++++---- .../tests/006-get-relevant-tokens-query/mock.json | 2 +- .../tests/007-read-contract-calls-tests/mock.json | 2 +- .../tests/008-write-contract-calls-tests/mock.json | 4 ++-- .../tests/008-write-contract-calls-tests/src/task.ts | 11 ++++++++++- 5 files changed, 18 insertions(+), 9 deletions(-) diff --git a/packages/integration/tests/005-get-price-query/mock.json b/packages/integration/tests/005-get-price-query/mock.json index 56c0397b..26f7ae6a 100644 --- a/packages/integration/tests/005-get-price-query/mock.json +++ b/packages/integration/tests/005-get-price-query/mock.json @@ -3,10 +3,10 @@ "_tokenPriceQuery": { "log": true, "paramResponse": { - "{\"address\":\"0x2260fac5e5542a773aa44fbcfedf7c193bc2c599\",\"chainId\":1}": "[\"90000000000000000000000\"]", - "{\"address\":\"0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48\",\"chainId\":1}": "[\"1000000000000000000\"]", - "{\"address\":\"0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee\",\"chainId\":1}": "[\"2000000000000000000000\"]", - "{\"address\":\"0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48\",\"chainId\":1,\"timestamp\":1744818017000}": "[\"1000000000000000000\"]" + "{\"address\":\"0x2260fac5e5542a773aa44fbcfedf7c193bc2c599\",\"chainId\":1}": "{\"success\":\"true\",\"data\":[\"90000000000000000000000\"],\"error\":\"\"}", + "{\"address\":\"0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48\",\"chainId\":1}": "{\"success\":\"true\",\"data\":[\"1000000000000000000000\"],\"error\":\"\"}", + "{\"address\":\"0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee\",\"chainId\":1}": "{\"success\":\"true\",\"data\":[\"2000000000000000000000\"],\"error\":\"\"}", + "{\"address\":\"0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48\",\"chainId\":1,\"timestamp\":1744818017000}": "{\"success\":\"true\",\"data\":[\"1000000000000000000000\"],\"error\":\"\"}" } } } diff --git a/packages/integration/tests/006-get-relevant-tokens-query/mock.json b/packages/integration/tests/006-get-relevant-tokens-query/mock.json index 0aecedad..a47756f2 100644 --- a/packages/integration/tests/006-get-relevant-tokens-query/mock.json +++ b/packages/integration/tests/006-get-relevant-tokens-query/mock.json @@ -2,7 +2,7 @@ "environment": { "_relevantTokensQuery": { "log": true, - "default": "[]" + "default": "{\"success\":\"true\",\"data\":[],\"error\":\"\"}" } } } diff --git a/packages/integration/tests/007-read-contract-calls-tests/mock.json b/packages/integration/tests/007-read-contract-calls-tests/mock.json index f3ae2903..c6216fa1 100644 --- a/packages/integration/tests/007-read-contract-calls-tests/mock.json +++ b/packages/integration/tests/007-read-contract-calls-tests/mock.json @@ -2,7 +2,7 @@ "environment": { "_evmCallQuery": { "log": true, - "default": "0x047be3bb46f9416732fe39a05134f20235c19334" + "default": "{\"success\":\"true\",\"data\":\"0x047be3bb46f9416732fe39a05134f20235c19334\",\"error\":\"\"}" } }, "evm": { diff --git a/packages/integration/tests/008-write-contract-calls-tests/mock.json b/packages/integration/tests/008-write-contract-calls-tests/mock.json index 8d553683..5a8bc4e7 100644 --- a/packages/integration/tests/008-write-contract-calls-tests/mock.json +++ b/packages/integration/tests/008-write-contract-calls-tests/mock.json @@ -2,10 +2,10 @@ "environment": { "_evmCallQuery": { "log": true, - "default": "0x047be3bb46f9416732fe39a05134f20235c19334" + "default": "{\"success\":\"true\",\"data\":\"0x047be3bb46f9416732fe39a05134f20235c19334\",\"error\":\"\"}" }, "_getContext": "{ \"timestamp\": 1438223173000, \"consensusThreshold\": 1, \"user\": \"0x756f45e3fa69347a9a973a725e3c98bc4db0b5a0\", \"settlers\": [{\"address\": \"0xdcf1d9d12a0488dfb70a8696f44d6d3bc303963d\", \"chainId\": 10}], \"configSig\": \"682ec8210b1ce912da4d2952\"}", - "_relevantTokensQuery": "[{\"timestamp\":1438223173000,\"balances\":[{\"token\":{\"address\":\"0x625e7708f30ca75bfd92586e17077590c60eb4cd\",\"chainId\":10},\"balance\":\"100000000\"}]}]", + "_relevantTokensQuery": "{\"success\":\"true\",\"data\":[{\"timestamp\":1438223173000,\"balances\":[{\"token\":{\"address\":\"0x625e7708f30ca75bfd92586e17077590c60eb4cd\",\"chainId\":10},\"balance\":\"100000000\"}]}],\"error\":\"\"}", "_call": { "log": true, "default": "" diff --git a/packages/integration/tests/008-write-contract-calls-tests/src/task.ts b/packages/integration/tests/008-write-contract-calls-tests/src/task.ts index be798222..2ac41253 100644 --- a/packages/integration/tests/008-write-contract-calls-tests/src/task.ts +++ b/packages/integration/tests/008-write-contract-calls-tests/src/task.ts @@ -14,7 +14,16 @@ export default function main(): void { const context = environment.getContext() - const userTokens = environment.relevantTokensQuery(context.user, [chainId], USD.zero(), [aUSDC], ListType.AllowList) + const userTokensResult = environment.relevantTokensQuery( + context.user, + [chainId], + USD.zero(), + [aUSDC], + ListType.AllowList + ) + if (userTokensResult.isError) throw new Error(userTokensResult.error) + + const userTokens = userTokensResult.value const feeUsdt = TokenAmount.fromStringDecimal(USDT, inputs.usdFeeAmount) From 828e93282d6657649fa1caa77226f353d16218f5 Mon Sep 17 00:00:00 2001 From: ncomerci Date: Thu, 11 Dec 2025 14:47:42 -0300 Subject: [PATCH 17/22] refactor: update TokenAmount and USD classes to return Result type --- .../tests/005-get-price-query/src/task.ts | 2 +- packages/lib-ts/src/tokens/TokenAmount.ts | 19 +++++----- packages/lib-ts/src/tokens/USD.ts | 10 +++--- .../lib-ts/tests/tokens/TokenAmount.spec.ts | 35 +++++++++++-------- packages/lib-ts/tests/tokens/USD.spec.ts | 14 +++++--- 5 files changed, 47 insertions(+), 33 deletions(-) diff --git a/packages/integration/tests/005-get-price-query/src/task.ts b/packages/integration/tests/005-get-price-query/src/task.ts index 11cf7eaf..52bca52c 100644 --- a/packages/integration/tests/005-get-price-query/src/task.ts +++ b/packages/integration/tests/005-get-price-query/src/task.ts @@ -7,7 +7,7 @@ export default function main(): void { // Convert X amount of USDC to USD const usdcAmount = TokenAmount.fromStringDecimal(Ethereum.USDC, '1000.2') - usdcAmount.toUsd().toString() + usdcAmount.toUsd() // Convert USDC to ETH usdcAmount.toTokenAmount(Ethereum.ETH) diff --git a/packages/lib-ts/src/tokens/TokenAmount.ts b/packages/lib-ts/src/tokens/TokenAmount.ts index c4a15faa..a09900a1 100644 --- a/packages/lib-ts/src/tokens/TokenAmount.ts +++ b/packages/lib-ts/src/tokens/TokenAmount.ts @@ -1,5 +1,5 @@ import { environment } from '../environment' -import { BigInt, JSON } from '../types' +import { BigInt, JSON, Result } from '../types' import { BlockchainToken } from './BlockchainToken' import { SerializableToken, Token } from './Token' @@ -217,14 +217,14 @@ export class TokenAmount { * Converts this TokenAmount to its USD equivalent. * @returns A USD instance representing the current USD value */ - toUsd(): USD { - if (this.isZero()) return USD.zero() + toUsd(): Result { + if (this.isZero()) return Result.ok(USD.zero()) const tokenPriceResult = environment.tokenPriceQuery(this.token) - if (tokenPriceResult.isError) throw new Error(tokenPriceResult.error) + if (tokenPriceResult.isError) return Result.err(tokenPriceResult.error) const tokenPrice = tokenPriceResult.value const amountUsd = this.amount.times(tokenPrice.value).downscale(this.decimals) - return USD.fromBigInt(amountUsd) + return Result.ok(USD.fromBigInt(amountUsd)) } /** @@ -232,9 +232,12 @@ export class TokenAmount { * @param other - The target token to convert to * @returns A TokenAmount of the target token with equivalent USD value */ - toTokenAmount(other: Token): TokenAmount { - if (this.isZero()) return TokenAmount.fromI32(other, 0) - return this.toUsd().toTokenAmount(other) + toTokenAmount(other: Token): Result { + if (this.isZero()) return Result.ok(TokenAmount.fromI32(other, 0)) + const usdResult = this.toUsd() + if (usdResult.isError) return Result.err(usdResult.error) + + return usdResult.value.toTokenAmount(other) } /** diff --git a/packages/lib-ts/src/tokens/USD.ts b/packages/lib-ts/src/tokens/USD.ts index f7a64739..5d839533 100644 --- a/packages/lib-ts/src/tokens/USD.ts +++ b/packages/lib-ts/src/tokens/USD.ts @@ -1,7 +1,7 @@ import { environment } from '../environment' import { STANDARD_DECIMALS } from '../helpers' import { Token, TokenAmount } from '../tokens' -import { BigInt } from '../types' +import { BigInt, Result } from '../types' /** * Represents a USD amount with fixed decimal precision. @@ -192,13 +192,13 @@ export class USD { * @param token - The target token to convert to * @returns A TokenAmount representing the equivalent value in the target token */ - toTokenAmount(token: Token): TokenAmount { - if (this.isZero()) return TokenAmount.fromI32(token, 0) + toTokenAmount(token: Token): Result { + if (this.isZero()) return Result.ok(TokenAmount.fromI32(token, 0)) const tokenPriceResult = environment.tokenPriceQuery(token) - if (tokenPriceResult.isError) throw new Error(tokenPriceResult.error) + if (tokenPriceResult.isError) return Result.err(tokenPriceResult.error) const tokenPrice = tokenPriceResult.value const tokenAmount = this.value.upscale(token.decimals).div(tokenPrice.value) - return TokenAmount.fromBigInt(token, tokenAmount) + return Result.ok(TokenAmount.fromBigInt(token, tokenAmount)) } } diff --git a/packages/lib-ts/tests/tokens/TokenAmount.spec.ts b/packages/lib-ts/tests/tokens/TokenAmount.spec.ts index 7ed36450..91716ce3 100644 --- a/packages/lib-ts/tests/tokens/TokenAmount.spec.ts +++ b/packages/lib-ts/tests/tokens/TokenAmount.spec.ts @@ -435,7 +435,8 @@ describe('TokenAmount', () => { it('returns 0', () => { const tokenAmount = TokenAmount.fromI32(randomERC20Token(), 0) const result = tokenAmount.toUsd() - expect(result.toString()).toBe('0') + expect(result.isOk).toBe(true) + expect(result.value.toString()).toBe('0') }) }) @@ -450,7 +451,8 @@ describe('TokenAmount', () => { const result = tokenAmount.toUsd() const expectedAmount = decimalTokenAmount * price - expect(result.toString()).toBe(expectedAmount.toString()) + expect(result.isOk).toBe(true) + expect(result.value.toString()).toBe(expectedAmount.toString()) }) it('converts correctly for a token with standard decimals', () => { @@ -463,7 +465,8 @@ describe('TokenAmount', () => { const result = tokenAmount.toUsd() const expectedAmount = decimalTokenAmount * price - expect(result.toString()).toBe(expectedAmount.toString()) + expect(result.isOk).toBe(true) + expect(result.value.toString()).toBe(expectedAmount.toString()) }) it('converts correctly for a token with more than standard decimals', () => { @@ -476,7 +479,8 @@ describe('TokenAmount', () => { const result = tokenAmount.toUsd() const expectedAmount = decimalTokenAmount * price - expect(result.toString()).toBe(expectedAmount.toString()) + expect(result.isOk).toBe(true) + expect(result.value.toString()).toBe(expectedAmount.toString()) }) }) }) @@ -486,7 +490,8 @@ describe('TokenAmount', () => { it('returns 0', () => { const tokenAmount = TokenAmount.fromI32(DenominationToken.USD(), 0) const result = tokenAmount.toUsd() - expect(result.toString()).toBe('0') + expect(result.isOk).toBe(true) + expect(result.value.toString()).toBe('0') }) }) @@ -496,7 +501,8 @@ describe('TokenAmount', () => { const tokenAmount = TokenAmount.fromI32(DenominationToken.USD(), decimalTokenAmount) const result = tokenAmount.toUsd() - expect(result.toString()).toBe(decimalTokenAmount.toString()) + expect(result.isOk).toBe(true) + expect(result.value.toString()).toBe(decimalTokenAmount.toString()) }) }) }) @@ -506,18 +512,19 @@ describe('TokenAmount', () => { it('returns 0', () => { const tokenAmount = TokenAmount.fromI32(DenominationToken.USD(), 0) const result = tokenAmount.toUsd() - expect(result.toString()).toBe('0') + expect(result.isOk).toBe(true) + expect(result.value.toString()).toBe('0') }) }) describe('when not zero', () => { - it('throws', () => { - expect(() => { - const decimalTokenAmount = 100 - const token = new DenominationToken(randomEvmAddress(), 18, 'EUR') - const tokenAmount = TokenAmount.fromI32(token, decimalTokenAmount) - tokenAmount.toUsd() - }).toThrow('Price query not supported for token') + it('returns error', () => { + const decimalTokenAmount = 100 + const token = new DenominationToken(randomEvmAddress(), 18, 'EUR') + const tokenAmount = TokenAmount.fromI32(token, decimalTokenAmount) + const result = tokenAmount.toUsd() + expect(result.isError).toBe(true) + expect(result.error).toBe('Price query not supported for token EUR') }) }) }) diff --git a/packages/lib-ts/tests/tokens/USD.spec.ts b/packages/lib-ts/tests/tokens/USD.spec.ts index 1ce8c87f..171a8c2a 100644 --- a/packages/lib-ts/tests/tokens/USD.spec.ts +++ b/packages/lib-ts/tests/tokens/USD.spec.ts @@ -355,9 +355,10 @@ describe('USD', () => { it('returns 0', () => { const token = randomERC20Token() const usdAmount = USD.zero() - const tokenAmount = usdAmount.toTokenAmount(token) + const result = usdAmount.toTokenAmount(token) - expect(tokenAmount.amount.toString()).toBe('0') + expect(result.isOk).toBe(true) + expect(result.value.amount.toString()).toBe('0') }) }) @@ -372,7 +373,8 @@ describe('USD', () => { const result = usdAmount.toTokenAmount(token) const expectedAmount = BigInt.fromI32(decimalAmountUsd / price) - expect(result.amount.toString()).toBe(zeroPadded(expectedAmount, tokenDecimals)) + expect(result.isOk).toBe(true) + expect(result.value.amount.toString()).toBe(zeroPadded(expectedAmount, tokenDecimals)) }) it('converts correctly for a token with standard decimals', () => { @@ -385,7 +387,8 @@ describe('USD', () => { const result = usdAmount.toTokenAmount(token) const expectedAmount = BigInt.fromI32(decimalAmountUsd / price) - expect(result.amount.toString()).toBe(zeroPadded(expectedAmount, tokenDecimals)) + expect(result.isOk).toBe(true) + expect(result.value.amount.toString()).toBe(zeroPadded(expectedAmount, tokenDecimals)) }) it('converts correctly for a token with more than standard decimals', () => { @@ -398,7 +401,8 @@ describe('USD', () => { const result = usdAmount.toTokenAmount(token) const expectedAmount = BigInt.fromI32(decimalAmountUsd / price) - expect(result.amount.toString()).toBe(zeroPadded(expectedAmount, tokenDecimals)) + expect(result.isOk).toBe(true) + expect(result.value.amount.toString()).toBe(zeroPadded(expectedAmount, tokenDecimals)) }) }) }) From 9b637f4341e5db46c5fac706c13eb7ead6682b56 Mon Sep 17 00:00:00 2001 From: ncomerci Date: Thu, 11 Dec 2025 15:03:18 -0300 Subject: [PATCH 18/22] chore: requested changes --- packages/lib-ts/src/tokens/ERC20Token.ts | 60 ++++++++++++-------- packages/lib-ts/src/tokens/SPLToken.ts | 72 ++++++++++++++---------- 2 files changed, 78 insertions(+), 54 deletions(-) diff --git a/packages/lib-ts/src/tokens/ERC20Token.ts b/packages/lib-ts/src/tokens/ERC20Token.ts index 31e0ea7e..90eca9ab 100644 --- a/packages/lib-ts/src/tokens/ERC20Token.ts +++ b/packages/lib-ts/src/tokens/ERC20Token.ts @@ -116,18 +116,7 @@ export class ERC20Token extends BlockchainToken { * @returns A string containing the token symbol. */ get symbol(): string { - if (this._symbol === ERC20Token.EMPTY_SYMBOL) { - const response = environment.evmCallQuery(this.address, this.chainId, '0x95d89b41', this._timestamp) - if (response.isError) { - log.warning('Failed to get symbol for token {} on chain {}: {}', [ - this.address.toString(), - this.chainId.toString(), - response.error, - ]) - return ERC20Token.EMPTY_SYMBOL - } - this._symbol = evm.decode(new EvmDecodeParam('string', response.value)) - } + if (this._symbol === ERC20Token.EMPTY_SYMBOL) this._getSymbol() return this._symbol } @@ -139,18 +128,7 @@ export class ERC20Token extends BlockchainToken { * @returns A `u8` representing the number of decimals of the token. */ get decimals(): u8 { - if (this._decimals == ERC20Token.EMPTY_DECIMALS) { - const result = environment.evmCallQuery(this.address, this.chainId, '0x313ce567', this._timestamp) - if (result.isError) { - log.warning('Failed to get decimals for token {} on chain {}: {}', [ - this.address.toString(), - this.chainId.toString(), - result.error, - ]) - return ERC20Token.EMPTY_DECIMALS - } - this._decimals = u8.parse(evm.decode(new EvmDecodeParam('uint8', result.value))) - } + if (this._decimals == ERC20Token.EMPTY_DECIMALS) this._getDecimals() return this._decimals } @@ -171,4 +149,38 @@ export class ERC20Token extends BlockchainToken { isNative(): boolean { return this.equals(ERC20Token.native(this.chainId)) } + + /** + * Internal method to fetch and cache the token symbol. + * Performs an EVM call query to the token's symbol() function. + * @private + */ + private _getSymbol(): void { + const response = environment.evmCallQuery(this.address, this.chainId, '0x95d89b41', this._timestamp) + if (response.isError) { + log.warning( + `Failed to get symbol for token ${this.address.toString()} on chain ${this.chainId.toString()}: ${response.error}` + ) + this._symbol = ERC20Token.EMPTY_SYMBOL + return + } + this._symbol = evm.decode(new EvmDecodeParam('string', response.value)) + } + + /** + * Internal method to fetch and cache the token decimals. + * Performs an EVM call query to the token's decimals() function. + * @private + */ + private _getDecimals(): void { + const result = environment.evmCallQuery(this.address, this.chainId, '0x313ce567', this._timestamp) + if (result.isError) { + log.warning( + `Failed to get decimals for token ${this.address.toString()} on chain ${this.chainId.toString()}: ${result.error}` + ) + this._decimals = ERC20Token.EMPTY_DECIMALS + return + } + this._decimals = u8.parse(evm.decode(new EvmDecodeParam('uint8', result.value))) + } } diff --git a/packages/lib-ts/src/tokens/SPLToken.ts b/packages/lib-ts/src/tokens/SPLToken.ts index 5d4a4da9..0bcefb8b 100644 --- a/packages/lib-ts/src/tokens/SPLToken.ts +++ b/packages/lib-ts/src/tokens/SPLToken.ts @@ -72,19 +72,7 @@ export class SPLToken extends BlockchainToken { * @returns A `u8` representing the number of decimals of the token. */ get decimals(): u8 { - if (this._decimals == SPLToken.EMPTY_DECIMALS) { - const result = environment.svmAccountsInfoQuery([this.address]) - if (result.isError) { - log.warning('Failed to get decimals for token {} on chain {}: {}', [ - this.address.toString(), - this.chainId.toString(), - result.error, - ]) - return SPLToken.EMPTY_DECIMALS - } - const decimals = SvmMint.fromHex(result.value.accountsInfo[0].data).decimals - this._decimals = decimals - } + if (this._decimals == SPLToken.EMPTY_DECIMALS) this._getDecimals() return this._decimals } @@ -96,26 +84,50 @@ export class SPLToken extends BlockchainToken { * @returns A string containing the token symbol. */ get symbol(): string { - if (this._symbol == SPLToken.EMPTY_SYMBOL) { - const result = environment.svmAccountsInfoQuery([this.getMetadataAddress()]) - if (result.isError) { - log.warning('Failed to get symbol for token {} on chain {}: {}', [ - this.address.toString(), - this.chainId.toString(), - result.error, - ]) - return SPLToken.EMPTY_SYMBOL - } - const data = result.value.accountsInfo[0].data - // Return placeholder symbol from address if TokenMetadata standard is not used - this._symbol = - data === '0x' - ? `${this.address.toString().slice(0, 5)}...${this.address.toString().slice(-5)}` - : SvmTokenMetadataData.fromTokenMetadataHex(result.value.accountsInfo[0].data).symbol - } + if (this._symbol == SPLToken.EMPTY_SYMBOL) this._getSymbol() return this._symbol } + /** + * Internal method to fetch and cache the token decimals. + * Performs an SVM accounts info query to get the mint data. + * @private + */ + private _getDecimals(): void { + const result = environment.svmAccountsInfoQuery([this.address]) + if (result.isError) { + log.warning( + `Failed to get decimals for token ${this.address.toString()} on chain ${this.chainId.toString()}: ${result.error}` + ) + this._decimals = SPLToken.EMPTY_DECIMALS + return + } + const decimals = SvmMint.fromHex(result.value.accountsInfo[0].data).decimals + this._decimals = decimals + } + + /** + * Internal method to fetch and cache the token symbol. + * Performs an SVM accounts info query to get the token metadata. + * @private + */ + private _getSymbol(): void { + const result = environment.svmAccountsInfoQuery([this.getMetadataAddress()]) + if (result.isError) { + log.warning( + `Failed to get symbol for token ${this.address.toString()} on chain ${this.chainId.toString()}: ${result.error}` + ) + this._symbol = SPLToken.EMPTY_SYMBOL + return + } + const data = result.value.accountsInfo[0].data + // Return placeholder symbol from address if TokenMetadata standard is not used + this._symbol = + data === '0x' + ? `${this.address.toString().slice(0, 5)}...${this.address.toString().slice(-5)}` + : SvmTokenMetadataData.fromTokenMetadataHex(result.value.accountsInfo[0].data).symbol + } + /** * Derives Metaplex Metadata address for this given token */ From 5ebf027da1b3c14d471a4ddee3837a1e78e28cc9 Mon Sep 17 00:00:00 2001 From: ncomerci Date: Tue, 16 Dec 2025 11:33:33 -0300 Subject: [PATCH 19/22] feat: introduce Void type for handling void return values in Result --- .../cli/src/lib/AbisInterfaceGenerator/FunctionHandler.ts | 8 +++++--- packages/cli/src/lib/AbisInterfaceGenerator/types.ts | 1 + packages/cli/src/types.ts | 1 + packages/cli/tests/AbisInterfaceGenerator.spec.ts | 8 ++++---- packages/lib-ts/src/types/Result.ts | 5 +++++ 5 files changed, 16 insertions(+), 7 deletions(-) diff --git a/packages/cli/src/lib/AbisInterfaceGenerator/FunctionHandler.ts b/packages/cli/src/lib/AbisInterfaceGenerator/FunctionHandler.ts index 6cae29ca..e98f3c53 100644 --- a/packages/cli/src/lib/AbisInterfaceGenerator/FunctionHandler.ts +++ b/packages/cli/src/lib/AbisInterfaceGenerator/FunctionHandler.ts @@ -124,8 +124,10 @@ export default class FunctionHandler { importManager.addType('environment') importManager.addType('Result') + if (returnType === 'void') importManager.addType(LibTypes.Void) - const resultReturnType = returnType === 'void' ? 'Result' : `Result<${returnType}, string>` + const resultReturnType = + returnType === 'void' ? `Result<${LibTypes.Void}, string>` : `Result<${returnType}, string>` lines.push(` ${methodName}(${methodParams}): ${resultReturnType} {`) lines.push( @@ -136,8 +138,8 @@ export default class FunctionHandler { if (returnType === 'void') { lines.push(` const response = ${contractCallLine}`) - lines.push(` if (response.isError) return Result.err(response.error)`) - lines.push(` return Result.ok(changetype(0))`) + lines.push(` if (response.isError) return Result.err<${LibTypes.Void}, string>(response.error)`) + lines.push(` return Result.ok<${LibTypes.Void}, string>(new ${LibTypes.Void}())`) } else { lines.push(` const response = ${contractCallLine}`) lines.push(` if (response.isError) return Result.err<${returnType}, string>(response.error)`) diff --git a/packages/cli/src/lib/AbisInterfaceGenerator/types.ts b/packages/cli/src/lib/AbisInterfaceGenerator/types.ts index e4363070..59f3f8b6 100644 --- a/packages/cli/src/lib/AbisInterfaceGenerator/types.ts +++ b/packages/cli/src/lib/AbisInterfaceGenerator/types.ts @@ -9,6 +9,7 @@ export type ImportedTypes = | 'JSON' | 'EvmCallBuilder' | 'Result' + export type MapBaseTypeCallback = (param: AbiParameter) => string export type TupleDefinition = { className: string diff --git a/packages/cli/src/types.ts b/packages/cli/src/types.ts index ca4828ac..50f2aa55 100644 --- a/packages/cli/src/types.ts +++ b/packages/cli/src/types.ts @@ -29,6 +29,7 @@ export enum LibTypes { Bytes = 'Bytes', ChainId = 'ChainId', TokenAmount = 'TokenAmount', + Void = 'Void', } export enum AssemblyPrimitiveTypes { diff --git a/packages/cli/tests/AbisInterfaceGenerator.spec.ts b/packages/cli/tests/AbisInterfaceGenerator.spec.ts index c085d291..2439b380 100644 --- a/packages/cli/tests/AbisInterfaceGenerator.spec.ts +++ b/packages/cli/tests/AbisInterfaceGenerator.spec.ts @@ -313,14 +313,14 @@ describe('AbisInterfaceGenerator', () => { const selector = getFunctionSelector(abi[0]) - expect(result).to.contain(`${functionName}(): Result {`) + expect(result).to.contain(`${functionName}(): Result {`) expect(result).to.contain(`static encodeNoReturn(): Bytes {`) expect(result).to.contain(`return ${LibTypes.Bytes}.fromHexString('${selector}')`) expect(result).to.contain( `environment.evmCallQuery(this._address, this._chainId, encodedData.toHexString(), this._timestamp)` ) - expect(result).to.contain(`if (response.isError) return Result.err(response.error)`) - expect(result).to.contain(`return Result.ok(changetype(0))`) + expect(result).to.contain(`if (response.isError) return Result.err(response.error)`) + expect(result).to.contain(`return Result.ok(new Void())`) expect(result).not.to.contain(`_decodeNoReturnResponse`) }) }) @@ -892,7 +892,7 @@ describe('AbisInterfaceGenerator', () => { const result = AbisInterfaceGenerator.generate(abi, CONTRACT_NAME) expect(result).to.contain('static encodeValidate(data: Bytes): Bytes {') - expect(result).to.contain('validate(data: Bytes): Result {') + expect(result).to.contain('validate(data: Bytes): Result {') expect(result).not.to.contain('_decodeValidateResponse') }) diff --git a/packages/lib-ts/src/types/Result.ts b/packages/lib-ts/src/types/Result.ts index 092a71d0..d2cd065b 100644 --- a/packages/lib-ts/src/types/Result.ts +++ b/packages/lib-ts/src/types/Result.ts @@ -49,3 +49,8 @@ export class Wrapped { this.inner = inner } } + +// This is used to represent a void return type +export class Void { + constructor() {} +} From 25f4ecc274a6bc9687ea955ed13f9fddaa5ad0bd Mon Sep 17 00:00:00 2001 From: ncomerci Date: Tue, 16 Dec 2025 12:09:58 -0300 Subject: [PATCH 20/22] refactor: FunctionHandler --- .../AbisInterfaceGenerator/FunctionHandler.ts | 37 ++++++++++--------- 1 file changed, 19 insertions(+), 18 deletions(-) diff --git a/packages/cli/src/lib/AbisInterfaceGenerator/FunctionHandler.ts b/packages/cli/src/lib/AbisInterfaceGenerator/FunctionHandler.ts index e98f3c53..8c282681 100644 --- a/packages/cli/src/lib/AbisInterfaceGenerator/FunctionHandler.ts +++ b/packages/cli/src/lib/AbisInterfaceGenerator/FunctionHandler.ts @@ -89,10 +89,10 @@ export default class FunctionHandler { isPayable ? `${methodParams.length > 0 ? ', ' : ''}value: ${LibTypes.BigInt}` : '' ) - lines.push(` ${methodName}(${fullMethodParams}): ${returnType} {`) + lines.push(`${methodName}(${fullMethodParams}): ${returnType} {`) lines.push( - ` const encodedData = ${contractName}Utils.encode${capitalizedName}(${inputs.map((p) => p.escapedName!).join(', ')})` + `const encodedData = ${contractName}Utils.encode${capitalizedName}(${inputs.map((p) => p.escapedName!).join(', ')})` ) importManager.addType(LibTypes.Bytes) @@ -100,10 +100,10 @@ export default class FunctionHandler { if (isPayable) importManager.addType(LibTypes.BigInt) lines.push( - ` return EvmCallBuilder.forChain(this._chainId).addCall(this._address, encodedData${isPayable ? ', value' : ''})` + `return EvmCallBuilder.forChain(this._chainId).addCall(this._address, encodedData${isPayable ? ', value' : ''})` ) - lines.push(` }`) + lines.push(`}`) lines.push('') } @@ -124,30 +124,31 @@ export default class FunctionHandler { importManager.addType('environment') importManager.addType('Result') - if (returnType === 'void') importManager.addType(LibTypes.Void) + const isVoid = returnType === 'void' + if (isVoid) importManager.addType(LibTypes.Void) - const resultReturnType = - returnType === 'void' ? `Result<${LibTypes.Void}, string>` : `Result<${returnType}, string>` - lines.push(` ${methodName}(${methodParams}): ${resultReturnType} {`) + const resultReturnType = isVoid ? `Result<${LibTypes.Void}, string>` : `Result<${returnType}, string>` + lines.push(`${methodName}(${methodParams}): ${resultReturnType} {`) lines.push( - ` const encodedData = ${contractName}Utils.encode${capitalizedName}(${inputs.map((p) => p.escapedName!).join(', ')})` + `const encodedData = ${contractName}Utils.encode${capitalizedName}(${inputs.map((p) => p.escapedName!).join(', ')})` ) const contractCallLine = `environment.evmCallQuery(this._address, this._chainId, encodedData.toHexString(), this._timestamp)` - if (returnType === 'void') { - lines.push(` const response = ${contractCallLine}`) - lines.push(` if (response.isError) return Result.err<${LibTypes.Void}, string>(response.error)`) - lines.push(` return Result.ok<${LibTypes.Void}, string>(new ${LibTypes.Void}())`) + lines.push(`const response = ${contractCallLine}`) + lines.push( + `if (response.isError) return Result.err<${isVoid ? LibTypes.Void : returnType}, string>(response.error)` + ) + + if (isVoid) { + lines.push(`return Result.ok<${LibTypes.Void}, string>(new ${LibTypes.Void}())`) } else { - lines.push(` const response = ${contractCallLine}`) - lines.push(` if (response.isError) return Result.err<${returnType}, string>(response.error)`) - lines.push(` const decoded = ${contractName}Utils.decode${capitalizedName}(response.value)`) - lines.push(` return Result.ok<${returnType}, string>(decoded)`) + lines.push(`const decoded = ${contractName}Utils.decode${capitalizedName}(response.value)`) + lines.push(`return Result.ok<${returnType}, string>(decoded)`) } - lines.push(` }`) + lines.push(`}`) lines.push('') } } From b9252b8b1e506df1a7e2d3b55c14e4a6fcd90868 Mon Sep 17 00:00:00 2001 From: ncomerci Date: Thu, 18 Dec 2025 12:24:48 -0300 Subject: [PATCH 21/22] fix: rebase --- packages/lib-ts/src/environment.ts | 13 ++++++++----- packages/lib-ts/src/queries/EvmCallQuery.ts | 4 +++- .../lib-ts/src/{types => queries}/QueryResponse.ts | 0 packages/lib-ts/src/queries/RelevantTokensQuery.ts | 4 +++- packages/lib-ts/src/queries/SubgraphQuery.ts | 4 +++- packages/lib-ts/src/queries/SvmAccountsInfoQuery.ts | 3 ++- packages/lib-ts/src/queries/TokenPriceQuery.ts | 3 ++- packages/lib-ts/src/queries/index.ts | 1 + packages/lib-ts/src/types/index.ts | 1 - 9 files changed, 22 insertions(+), 11 deletions(-) rename packages/lib-ts/src/{types => queries}/QueryResponse.ts (100%) diff --git a/packages/lib-ts/src/environment.ts b/packages/lib-ts/src/environment.ts index 80effc2b..32643a0e 100644 --- a/packages/lib-ts/src/environment.ts +++ b/packages/lib-ts/src/environment.ts @@ -5,19 +5,22 @@ import { ListType } from './helpers' import { Swap, Transfer, EvmCall, SvmCall } from './intents' import { EvmCallQuery, - SvmAccountsInfoQuery, - SvmAccountsInfoQueryResponse, - TokenPriceQuery, RelevantTokensQuery, RelevantTokensQueryResult, TokenBalanceQuery, + RelevantTokensQueryResponse, SubgraphQuery, + SvmAccountsInfoQuery, + SvmAccountsInfoQueryResponse, SvmAccountsInfoQueryResult, SubgraphQueryResult, + TokenPriceQuery, + EvmCallQueryResponse, + SubgraphQueryResponse, + TokenPriceQueryResponse, } from './queries' import { BlockchainToken, Token, TokenAmount, USD } from './tokens' import { Address, BigInt, ChainId, Result } from './types' -import { EvmCallQueryResponse, PriceQueryResponse, RelevantTokensQueryResponse, SubgraphQueryResponse } from './types/QueryResponse' export namespace environment { @external('environment', '_evmCall') @@ -93,7 +96,7 @@ export namespace environment { else if (!(token instanceof BlockchainToken)) return Result.err('Price query not supported for token ' + token.toString()) const responseStr = _tokenPriceQuery(JSON.stringify(TokenPriceQuery.fromToken(changetype(token), timestamp))) - const parsed = PriceQueryResponse.fromJson(responseStr) + const parsed = TokenPriceQueryResponse.fromJson(responseStr) if (parsed.success !== 'true') return Result.err(parsed.error.length > 0 ? parsed.error : 'Unknown error getting price') diff --git a/packages/lib-ts/src/queries/EvmCallQuery.ts b/packages/lib-ts/src/queries/EvmCallQuery.ts index 6f6eb06b..f54663ac 100644 --- a/packages/lib-ts/src/queries/EvmCallQuery.ts +++ b/packages/lib-ts/src/queries/EvmCallQuery.ts @@ -1,4 +1,6 @@ -import { Address, ChainId, QueryResponseBase } from '../types' +import { Address, ChainId } from '../types' + +import { QueryResponseBase } from './QueryResponse' @json class EvmCallQueryBase { diff --git a/packages/lib-ts/src/types/QueryResponse.ts b/packages/lib-ts/src/queries/QueryResponse.ts similarity index 100% rename from packages/lib-ts/src/types/QueryResponse.ts rename to packages/lib-ts/src/queries/QueryResponse.ts diff --git a/packages/lib-ts/src/queries/RelevantTokensQuery.ts b/packages/lib-ts/src/queries/RelevantTokensQuery.ts index c903124f..e948a7b1 100644 --- a/packages/lib-ts/src/queries/RelevantTokensQuery.ts +++ b/packages/lib-ts/src/queries/RelevantTokensQuery.ts @@ -1,6 +1,8 @@ import { ListType } from '../helpers' import { BlockchainToken, TokenAmount, USD } from '../tokens' -import { Address, BigInt, ChainId, QueryResponseBase } from '../types' +import { Address, BigInt, ChainId } from '../types' + +import { QueryResponseBase } from './QueryResponse' @json class TokenQuery { diff --git a/packages/lib-ts/src/queries/SubgraphQuery.ts b/packages/lib-ts/src/queries/SubgraphQuery.ts index b41cc7df..d421c35c 100644 --- a/packages/lib-ts/src/queries/SubgraphQuery.ts +++ b/packages/lib-ts/src/queries/SubgraphQuery.ts @@ -1,4 +1,6 @@ -import { ChainId, QueryResponseBase } from '../types' +import { ChainId } from '../types' + +import { QueryResponseBase } from './QueryResponse' @json class SubgraphQueryBase { diff --git a/packages/lib-ts/src/queries/SvmAccountsInfoQuery.ts b/packages/lib-ts/src/queries/SvmAccountsInfoQuery.ts index 9edcd60d..0d893920 100644 --- a/packages/lib-ts/src/queries/SvmAccountsInfoQuery.ts +++ b/packages/lib-ts/src/queries/SvmAccountsInfoQuery.ts @@ -1,5 +1,6 @@ import { Address, SerializableSvmAccountInfo, SvmAccountInfo } from '../types' -import { QueryResponseBase } from '../types/QueryResponse' + +import { QueryResponseBase } from './QueryResponse' @json class SvmAccountsInfoQueryBase { diff --git a/packages/lib-ts/src/queries/TokenPriceQuery.ts b/packages/lib-ts/src/queries/TokenPriceQuery.ts index 0e494215..90bdbea8 100644 --- a/packages/lib-ts/src/queries/TokenPriceQuery.ts +++ b/packages/lib-ts/src/queries/TokenPriceQuery.ts @@ -1,5 +1,6 @@ import { BlockchainToken } from '../tokens' -import { QueryResponseBase } from '../types' + +import { QueryResponseBase } from './QueryResponse' @json class TokenPriceQueryBase { diff --git a/packages/lib-ts/src/queries/index.ts b/packages/lib-ts/src/queries/index.ts index 603d5634..c59a2729 100644 --- a/packages/lib-ts/src/queries/index.ts +++ b/packages/lib-ts/src/queries/index.ts @@ -1,4 +1,5 @@ export * from './EvmCallQuery' +export * from './QueryResponse' export * from './RelevantTokensQuery' export * from './SubgraphQuery' export * from './SvmAccountsInfoQuery' diff --git a/packages/lib-ts/src/types/index.ts b/packages/lib-ts/src/types/index.ts index b5f12c20..d07ebe60 100644 --- a/packages/lib-ts/src/types/index.ts +++ b/packages/lib-ts/src/types/index.ts @@ -7,7 +7,6 @@ export * from './Bytes' export * from './ChainId' export * from './evm' export * from './Option' -export * from './QueryResponse' export * from './Result' export * from './svm' export * from './TriggerType' From 33b5b4ec1385bfe38b191b848050ec50a0fa78a2 Mon Sep 17 00:00:00 2001 From: ncomerci Date: Fri, 19 Dec 2025 10:52:34 -0300 Subject: [PATCH 22/22] chore: add changeset --- .changeset/spicy-flowers-end.md | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 .changeset/spicy-flowers-end.md diff --git a/.changeset/spicy-flowers-end.md b/.changeset/spicy-flowers-end.md new file mode 100644 index 00000000..fcc646ef --- /dev/null +++ b/.changeset/spicy-flowers-end.md @@ -0,0 +1,7 @@ +--- +"@mimicprotocol/test-ts": patch +"@mimicprotocol/lib-ts": patch +"@mimicprotocol/cli": patch +--- + +Handle oracle query errors