@mimicprotocol/lib-ts 0.0.1-rc.28 → 0.0.1-rc.30

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,20 @@
1
1
  # @mimicprotocol/lib-ts
2
2
 
3
+ ## 0.0.1-rc.30
4
+
5
+ ### Patch Changes
6
+
7
+ - 213f37f: Fix class names overlapping
8
+
9
+ ## 0.0.1-rc.29
10
+
11
+ ### Patch Changes
12
+
13
+ - 72bb607: Fix relevant tokens query price filter
14
+ - 52b567d: Align oracle types naming convention
15
+ - 73a9cde: Fix npx init
16
+ - 0a6602a: Handle oracle query errors
17
+
3
18
  ## 0.0.1-rc.28
4
19
 
5
20
  ### Patch Changes
package/README.md CHANGED
@@ -57,7 +57,7 @@ import { environment, ERC20Token } from '@mimicprotocol/lib-ts'
57
57
 
58
58
  const USDC = ERC20Token.fromString('0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48', 1)
59
59
 
60
- environment.getPrice(USDC, new Date(1744818017000))
60
+ environment.tokenPriceQuery(USDC, new Date(1744818017000))
61
61
  ```
62
62
 
63
63
  For full task development guide and examples please visit [docs.mimic.fi](https://docs.mimic.fi/)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mimicprotocol/lib-ts",
3
- "version": "0.0.1-rc.28",
3
+ "version": "0.0.1-rc.30",
4
4
  "license": "GPL-3.0",
5
5
  "private": false,
6
6
  "type": "module",
@@ -4,20 +4,23 @@ import { Context, SerializableContext } from './context'
4
4
  import { ListType } from './helpers'
5
5
  import { Swap, Transfer, EvmCall, SvmCall } from './intents'
6
6
  import {
7
- Call as CallQuery,
8
- GetAccountsInfo,
9
- GetAccountsInfoResponse,
10
- GetPrice,
11
- GetRelevantTokens,
12
- GetRelevantTokensResponse,
13
- RelevantTokenBalance,
14
- SerializableGetAccountsInfoResponse,
7
+ EvmCallQuery,
8
+ RelevantTokensQuery,
9
+ RelevantTokensQueryResult,
10
+ TokenBalanceQuery,
11
+ RelevantTokensQueryResponse,
15
12
  SubgraphQuery,
13
+ SvmAccountsInfoQuery,
14
+ SvmAccountsInfoQueryResponse,
15
+ SvmAccountsInfoQueryResult,
16
+ SubgraphQueryResult,
17
+ TokenPriceQuery,
18
+ EvmCallQueryResponse,
16
19
  SubgraphQueryResponse,
20
+ TokenPriceQueryResponse,
17
21
  } from './queries'
18
22
  import { BlockchainToken, Token, TokenAmount, USD } from './tokens'
19
- import { Address, BigInt, ChainId } from './types'
20
- import { log } from './log'
23
+ import { Address, BigInt, ChainId, Result } from './types'
21
24
 
22
25
  export namespace environment {
23
26
  @external('environment', '_evmCall')
@@ -32,20 +35,20 @@ export namespace environment {
32
35
  @external('environment', '_transfer')
33
36
  declare function _transfer(params: string): void
34
37
 
35
- @external('environment', '_getPrice')
36
- declare function _getPrice(params: string): string
38
+ @external('environment', '_tokenPriceQuery')
39
+ declare function _tokenPriceQuery(params: string): string
37
40
 
38
- @external('environment', '_getRelevantTokens')
39
- declare function _getRelevantTokens(params: string): string
41
+ @external('environment', '_relevantTokensQuery')
42
+ declare function _relevantTokensQuery(params: string): string
40
43
 
41
- @external('environment', '_contractCall')
42
- declare function _contractCall(params: string): string
44
+ @external('environment', '_evmCallQuery')
45
+ declare function _evmCallQuery(params: string): string
43
46
 
44
47
  @external('environment', '_subgraphQuery')
45
48
  declare function _subgraphQuery(params: string): string
46
49
 
47
- @external('environment', '_getAccountsInfo')
48
- declare function _getAccountsInfo(params: string): string
50
+ @external('environment', '_svmAccountsInfoQuery')
51
+ declare function _svmAccountsInfoQuery(params: string): string
49
52
 
50
53
  @external('environment', '_getContext')
51
54
  declare function _getContext(): string
@@ -86,36 +89,49 @@ export namespace environment {
86
89
  * Tells the prices from different sources for a token in USD at a specific timestamp.
87
90
  * @param token - The token to get the price of
88
91
  * @param timestamp - The timestamp for price lookup (optional, defaults to current time)
89
- * @returns The token prices in USD
92
+ * @returns Result containing either an array of USD prices or an error string
90
93
  */
91
- export function getRawPrice(token: Token, timestamp: Date | null = null): USD[] {
92
- if (token.isUSD()) return [USD.fromI32(1)]
93
- else if (!(token instanceof BlockchainToken)) throw new Error('Price query not supported for token ' + token.toString())
94
- const prices = _getPrice(JSON.stringify(GetPrice.fromToken(token as BlockchainToken, timestamp)))
95
- return JSON.parse<string[]>(prices).map<USD>((price) => USD.fromBigInt(BigInt.fromString(price)))
94
+ export function rawTokenPriceQuery(token: Token, timestamp: Date | null = null): Result<USD[], string> {
95
+ if (token.isUSD()) return Result.ok<USD[], string>([USD.fromI32(1)])
96
+ else if (!(token instanceof BlockchainToken)) return Result.err<USD[], string>('Price query not supported for token ' + token.toString())
97
+
98
+ const responseStr = _tokenPriceQuery(JSON.stringify(TokenPriceQuery.fromToken(changetype<BlockchainToken>(token), timestamp)))
99
+ const parsed = TokenPriceQueryResponse.fromJson<TokenPriceQueryResponse>(responseStr)
100
+
101
+ if (parsed.success !== 'true') return Result.err<USD[], string>(parsed.error.length > 0 ? parsed.error : 'Unknown error getting price')
102
+
103
+ const prices = parsed.data.map<USD>((price) => USD.fromBigInt(BigInt.fromString(price)))
104
+ return Result.ok<USD[], string>(prices)
96
105
  }
97
106
 
98
107
  /**
99
108
  * Tells the median price from different sources for a token in USD at a specific timestamp.
100
109
  * @param token - The token to get the price of
101
110
  * @param timestamp - The timestamp for price lookup (optional, defaults to current time)
102
- * @returns The token median price in USD
111
+ * @returns Result containing either the median USD price or an error string
103
112
  */
104
- export function getPrice(token: Token, timestamp: Date | null = null): USD {
105
- const prices = getRawPrice(token, timestamp)
106
- if (prices.length === 0) throw new Error('Prices not found for token ' + token.toString())
113
+ export function tokenPriceQuery(token: Token, timestamp: Date | null = null): Result<USD, string> {
114
+ const pricesResult = rawTokenPriceQuery(token, timestamp)
115
+
116
+ if (pricesResult.isError) return Result.err<USD, string>(pricesResult.error)
117
+
118
+ const prices = pricesResult.value
119
+ if (prices.length === 0) return Result.err<USD, string>('Prices not found for token ' + token.toString())
107
120
 
108
121
  const sortedPrices = prices.sort((a: USD, b: USD) => a.compare(b))
109
122
 
110
123
  const length = sortedPrices.length
124
+ let median: USD
111
125
  if (length % 2 === 1) {
112
- return sortedPrices[length / 2]
126
+ median = sortedPrices[length / 2]
113
127
  } else {
114
128
  const left = sortedPrices[length / 2 - 1]
115
129
  const right = sortedPrices[length / 2]
116
130
  const sum = left.plus(right)
117
- return sum.div(BigInt.fromI32(2))
131
+ median = sum.div(BigInt.fromI32(2))
118
132
  }
133
+
134
+ return Result.ok<USD, string>(median)
119
135
  }
120
136
 
121
137
  /**
@@ -125,12 +141,16 @@ export namespace environment {
125
141
  * @param usdMinAmount - Minimum USD value threshold for tokens (optional, defaults to zero)
126
142
  * @param tokensList - List of blockchain tokens to include/exclude (optional, defaults to empty array)
127
143
  * @param listType - Whether to include (AllowList) or exclude (DenyList) the tokens in `tokensList` (optional, defaults to DenyList)
128
- * @returns Array of RelevantTokenBalance objects representing the relevant tokens
144
+ * @returns Result containing either an array of RelevantTokenBalance arrays or an error string
129
145
  */
130
- export function getRawRelevantTokens(address: Address, chainIds: ChainId[], usdMinAmount: USD, tokensList: BlockchainToken[], listType: ListType): RelevantTokenBalance[][] {
131
- const responseStr = _getRelevantTokens(JSON.stringify(GetRelevantTokens.init(address, chainIds, usdMinAmount, tokensList, listType)))
132
- const responses = JSON.parse<GetRelevantTokensResponse[]>(responseStr)
133
- return responses.map((response: GetRelevantTokensResponse) => response.balances)
146
+ export function rawRelevantTokensQuery(address: Address, chainIds: ChainId[], usdMinAmount: USD, tokensList: BlockchainToken[], listType: ListType): Result<TokenBalanceQuery[][], string> {
147
+ const responseStr = _relevantTokensQuery(JSON.stringify(RelevantTokensQuery.init(address, chainIds, usdMinAmount, tokensList, listType)))
148
+ const parsed = RelevantTokensQueryResponse.fromJson<RelevantTokensQueryResponse>(responseStr)
149
+
150
+ if (parsed.success !== 'true') return Result.err<TokenBalanceQuery[][], string>(parsed.error.length > 0 ? parsed.error : 'Unknown error getting relevant tokens')
151
+
152
+ const responses = parsed.data
153
+ return Result.ok<TokenBalanceQuery[][], string>(responses.map((response: RelevantTokensQueryResult) => response.balances))
134
154
  }
135
155
 
136
156
  /**
@@ -142,14 +162,18 @@ export namespace environment {
142
162
  * @param listType - Whether to include (AllowList) or exclude (DenyList) the tokens in `tokensList` (optional, defaults to DenyList)
143
163
  * @returns Array of TokenAmount objects representing the relevant tokens
144
164
  */
145
- export function getRelevantTokens(
165
+ export function relevantTokensQuery(
146
166
  address: Address,
147
167
  chainIds: ChainId[],
148
168
  usdMinAmount: USD = USD.zero(),
149
169
  tokensList: BlockchainToken[] = [],
150
170
  listType: ListType = ListType.DenyList
151
- ): TokenAmount[] {
152
- const response = getRawRelevantTokens(address, chainIds, usdMinAmount, tokensList, listType)
171
+ ): Result<TokenAmount[], string> {
172
+ const responseResult = rawRelevantTokensQuery(address, chainIds, usdMinAmount, tokensList, listType)
173
+
174
+ if (responseResult.isError) return Result.err<TokenAmount[], string>(responseResult.error)
175
+
176
+ const response = responseResult.value
153
177
  const resultMap: Map<string, TokenAmount> = new Map()
154
178
  for (let i = 0; i < response.length; i++) {
155
179
  for (let j = 0; j < response[i].length; j++) {
@@ -160,7 +184,8 @@ export namespace environment {
160
184
  resultMap.set(mapKey, tokenAmount)
161
185
  }
162
186
  }
163
- return resultMap.values()
187
+
188
+ return Result.ok<TokenAmount[], string>(resultMap.values())
164
189
  }
165
190
 
166
191
  /**
@@ -171,15 +196,16 @@ export namespace environment {
171
196
  * @param data - The encoded function call data
172
197
  * @returns The raw response from the contract call
173
198
  */
174
- export function contractCall(
199
+ export function evmCallQuery(
175
200
  to: Address,
176
201
  chainId: ChainId,
177
202
  data: string,
178
203
  timestamp: Date | null = null,
179
- ): string {
180
- return _contractCall(
181
- JSON.stringify(CallQuery.from(to, chainId, timestamp, data))
182
- )
204
+ ): Result<string, string> {
205
+ const responseStr = _evmCallQuery(JSON.stringify(EvmCallQuery.from(to, chainId, timestamp, data)))
206
+ const parsed = EvmCallQueryResponse.fromJson<EvmCallQueryResponse>(responseStr)
207
+ if (parsed.success !== 'true') return Result.err<string, string>(parsed.error.length > 0 ? parsed.error : 'Unknown error getting evm call')
208
+ return Result.ok<string, string>(parsed.data)
183
209
  }
184
210
 
185
211
  /**
@@ -195,31 +221,30 @@ export namespace environment {
195
221
  subgraphId: string,
196
222
  query: string,
197
223
  timestamp: Date | null = null,
198
- ): SubgraphQueryResponse {
224
+ ): Result<SubgraphQueryResult, string> {
199
225
  const responseStr = _subgraphQuery(JSON.stringify(SubgraphQuery.from(chainId, subgraphId, query, timestamp)))
200
- return JSON.parse<SubgraphQueryResponse>(responseStr)
226
+ const parsed = SubgraphQueryResponse.fromJson<SubgraphQueryResponse>(responseStr)
227
+ if (parsed.success !== 'true') return Result.err<SubgraphQueryResult, string>(parsed.error.length > 0 ? parsed.error : 'Unknown error getting subgraph query')
228
+ return Result.ok<SubgraphQueryResult, string>(parsed.data)
201
229
  }
202
230
 
203
231
  /**
204
232
  * SVM - Gets on-chain account info
205
233
  * @param publicKeys - Accounts to read from chain
206
234
  * @param timestamp - The timestamp for the call context (optional)
207
- * @returns The raw response from the underlying getMultipleAccountsInfo call
235
+ * @returns Result containing either the account info result or an error string
208
236
  */
209
-
210
- export function getAccountsInfo(
237
+ export function svmAccountsInfoQuery(
211
238
  publicKeys: Address[],
212
239
  timestamp: Date | null = null,
213
- ): GetAccountsInfoResponse {
214
- // There is a bug with json-as, so we have to do this with JSON booleans
215
- const responseStr = _getAccountsInfo(
216
- JSON.stringify(GetAccountsInfo.from(publicKeys, timestamp))
217
- )
218
- .replaceAll("true",`"true"`)
219
- .replaceAll("false",`"false"`)
220
-
221
- const response = JSON.parse<SerializableGetAccountsInfoResponse>(responseStr)
222
- return GetAccountsInfoResponse.fromSerializable(response)
240
+ ): Result<SvmAccountsInfoQueryResult, string> {
241
+ const responseStr = _svmAccountsInfoQuery(JSON.stringify(SvmAccountsInfoQuery.from(publicKeys, timestamp)))
242
+ const parsed = SvmAccountsInfoQueryResponse.fromJson<SvmAccountsInfoQueryResponse>(responseStr)
243
+
244
+ if (parsed.success !== 'true') return Result.err<SvmAccountsInfoQueryResult, string>(parsed.error.length > 0 ? parsed.error : 'Unknown error getting SVM accounts info')
245
+
246
+ const result = SvmAccountsInfoQueryResult.fromSerializable(parsed.data)
247
+ return Result.ok<SvmAccountsInfoQueryResult, string>(result)
223
248
  }
224
249
 
225
250
  /**
@@ -26,6 +26,45 @@ export function stringToBool(str: string): bool {
26
26
  return str === 'true'
27
27
  }
28
28
 
29
+ /**
30
+ * Workaround for json-as boolean bug:
31
+ * Converts JSON boolean values (true/false) into their string equivalents ("true"/"false")
32
+ * without touching occurrences inside JSON strings or keys.
33
+ *
34
+ * This function assumes compact JSON (no newlines between the boolean and the delimiter).
35
+ */
36
+ export function replaceJsonBooleans(json: string): string {
37
+ return (
38
+ json
39
+ // true after ':' (object property values)
40
+ .replaceAll(':true', ':"true"')
41
+ .replaceAll(':true,', ':"true",')
42
+ .replaceAll(':true}', ':"true"}')
43
+ .replaceAll(':true]', ':"true"]')
44
+ .replaceAll(': true,', ': "true",')
45
+ .replaceAll(': true}', ': "true"}')
46
+ .replaceAll(': true]', ': "true"]')
47
+ // true in arrays
48
+ .replaceAll('[true,', '["true",')
49
+ .replaceAll('[true]', '["true"]')
50
+ .replaceAll(',true,', ',"true",')
51
+ .replaceAll(',true]', ',"true"]')
52
+ // false after ':' (object property values)
53
+ .replaceAll(':false', ':"false"')
54
+ .replaceAll(':false,', ':"false",')
55
+ .replaceAll(':false}', ':"false"}')
56
+ .replaceAll(':false]', ':"false"]')
57
+ .replaceAll(': false,', ': "false",')
58
+ .replaceAll(': false}', ': "false"}')
59
+ .replaceAll(': false]', ': "false"]')
60
+ // false in arrays
61
+ .replaceAll('[false,', '["false",')
62
+ .replaceAll('[false]', '["false"]')
63
+ .replaceAll(',false,', ',"false",')
64
+ .replaceAll(',false]', ',"false"]')
65
+ )
66
+ }
67
+
29
68
  export function areAllZeros(str: string): boolean {
30
69
  for (let i = 0; i < str.length; i++) if (str.charCodeAt(i) !== 48) return false
31
70
  return true
@@ -0,0 +1,39 @@
1
+ import { Address, ChainId } from '../types'
2
+
3
+ import { QueryResponseBase } from './QueryResponse'
4
+
5
+ @json
6
+ class EvmCallQueryBase {
7
+ constructor(
8
+ public readonly to: string,
9
+ public readonly chainId: ChainId,
10
+ public readonly data: string
11
+ ) {}
12
+ }
13
+
14
+ @json
15
+ export class EvmCallQuery extends EvmCallQueryBase {
16
+ public readonly timestamp: i64
17
+
18
+ constructor(to: string, chainId: ChainId, timestamp: i64, data: string) {
19
+ super(to, chainId, data)
20
+ this.timestamp = timestamp
21
+ }
22
+
23
+ static from(to: Address, chainId: ChainId, timestamp: Date | null, data: string): EvmCallQueryBase {
24
+ const address = to.toString()
25
+ return timestamp
26
+ ? new EvmCallQuery(address, chainId, changetype<Date>(timestamp).getTime(), data)
27
+ : new EvmCallQueryBase(address, chainId, data)
28
+ }
29
+ }
30
+
31
+ @json
32
+ export class EvmCallQueryResponse extends QueryResponseBase {
33
+ public data: string
34
+
35
+ constructor(success: string, data: string, error: string) {
36
+ super(success, error)
37
+ this.data = data
38
+ }
39
+ }
@@ -0,0 +1,15 @@
1
+ import { JSON } from 'json-as'
2
+
3
+ import { replaceJsonBooleans } from '../helpers'
4
+
5
+ @json
6
+ export class QueryResponseBase {
7
+ constructor(
8
+ public success: string, // boolean as string due to json-as bug
9
+ public error: string
10
+ ) {}
11
+
12
+ static fromJson<T extends QueryResponseBase>(json: string): T {
13
+ return JSON.parse<T>(replaceJsonBooleans(json))
14
+ }
15
+ }
@@ -2,6 +2,8 @@ import { ListType } from '../helpers'
2
2
  import { BlockchainToken, TokenAmount, USD } from '../tokens'
3
3
  import { Address, BigInt, ChainId } from '../types'
4
4
 
5
+ import { QueryResponseBase } from './QueryResponse'
6
+
5
7
  @json
6
8
  class TokenQuery {
7
9
  constructor(
@@ -15,7 +17,7 @@ class TokenQuery {
15
17
  }
16
18
 
17
19
  @json
18
- export class GetRelevantTokens {
20
+ export class RelevantTokensQuery {
19
21
  constructor(
20
22
  public readonly owner: string,
21
23
  public readonly chainIds: ChainId[],
@@ -30,16 +32,16 @@ export class GetRelevantTokens {
30
32
  usdMinAmount: USD,
31
33
  tokens: BlockchainToken[],
32
34
  tokenFilter: ListType
33
- ): GetRelevantTokens {
35
+ ): RelevantTokensQuery {
34
36
  const ownerStr = owner.toString()
35
- const usdMinAmountStr = usdMinAmount.toString()
37
+ const usdMinAmountStr = usdMinAmount.value.toString()
36
38
  const tokensQueries = tokens.map<TokenQuery>((token) => TokenQuery.fromToken(token))
37
- return new GetRelevantTokens(ownerStr, chainIds, usdMinAmountStr, tokensQueries, tokenFilter)
39
+ return new RelevantTokensQuery(ownerStr, chainIds, usdMinAmountStr, tokensQueries, tokenFilter)
38
40
  }
39
41
  }
40
42
 
41
43
  @json
42
- export class RelevantTokenBalance {
44
+ export class TokenBalanceQuery {
43
45
  constructor(
44
46
  public token: TokenQuery,
45
47
  public balance: string
@@ -54,9 +56,19 @@ export class RelevantTokenBalance {
54
56
  }
55
57
 
56
58
  @json
57
- export class GetRelevantTokensResponse {
59
+ export class RelevantTokensQueryResult {
58
60
  constructor(
59
61
  public timestamp: i64,
60
- public balances: RelevantTokenBalance[]
62
+ public balances: TokenBalanceQuery[]
61
63
  ) {}
62
64
  }
65
+
66
+ @json
67
+ export class RelevantTokensQueryResponse extends QueryResponseBase {
68
+ public data: RelevantTokensQueryResult[]
69
+
70
+ constructor(success: string, data: RelevantTokensQueryResult[], error: string) {
71
+ super(success, error)
72
+ this.data = data
73
+ }
74
+ }
@@ -1,5 +1,7 @@
1
1
  import { ChainId } from '../types'
2
2
 
3
+ import { QueryResponseBase } from './QueryResponse'
4
+
3
5
  @json
4
6
  class SubgraphQueryBase {
5
7
  constructor(
@@ -26,9 +28,19 @@ export class SubgraphQuery extends SubgraphQueryBase {
26
28
  }
27
29
 
28
30
  @json
29
- export class SubgraphQueryResponse {
31
+ export class SubgraphQueryResult {
30
32
  constructor(
31
33
  public blockNumber: i64,
32
34
  public data: string
33
35
  ) {}
34
36
  }
37
+
38
+ @json
39
+ export class SubgraphQueryResponse extends QueryResponseBase {
40
+ public data: SubgraphQueryResult
41
+
42
+ constructor(success: string, data: SubgraphQueryResult, error: string) {
43
+ super(success, error)
44
+ this.data = data
45
+ }
46
+ }
@@ -0,0 +1,57 @@
1
+ import { Address, SerializableSvmAccountInfo, SvmAccountInfo } from '../types'
2
+
3
+ import { QueryResponseBase } from './QueryResponse'
4
+
5
+ @json
6
+ class SvmAccountsInfoQueryBase {
7
+ constructor(public readonly publicKeys: string[]) {}
8
+ }
9
+
10
+ @json
11
+ export class SvmAccountsInfoQuery extends SvmAccountsInfoQueryBase {
12
+ public readonly timestamp: i64
13
+
14
+ constructor(publicKeys: string[], timestamp: i64) {
15
+ super(publicKeys)
16
+ this.timestamp = timestamp
17
+ }
18
+
19
+ static from(publicKeys: Address[], timestamp: Date | null): SvmAccountsInfoQueryBase {
20
+ const strPublicKeys = publicKeys.map((pk: Address) => pk.toString())
21
+ return timestamp
22
+ ? new SvmAccountsInfoQuery(strPublicKeys, changetype<Date>(timestamp).getTime())
23
+ : new SvmAccountsInfoQueryBase(strPublicKeys)
24
+ }
25
+ }
26
+
27
+ @json
28
+ export class SerializableSvmAccountsInfoQueryResult {
29
+ constructor(
30
+ public accountsInfo: SerializableSvmAccountInfo[],
31
+ public slot: string
32
+ ) {}
33
+ }
34
+
35
+ export class SvmAccountsInfoQueryResult {
36
+ constructor(
37
+ public accountsInfo: SvmAccountInfo[],
38
+ public slot: string
39
+ ) {}
40
+
41
+ static fromSerializable(serializable: SerializableSvmAccountsInfoQueryResult): SvmAccountsInfoQueryResult {
42
+ return new SvmAccountsInfoQueryResult(
43
+ serializable.accountsInfo.map((acc: SerializableSvmAccountInfo) => SvmAccountInfo.fromSerializable(acc)),
44
+ serializable.slot
45
+ )
46
+ }
47
+ }
48
+
49
+ @json
50
+ export class SvmAccountsInfoQueryResponse extends QueryResponseBase {
51
+ public data: SerializableSvmAccountsInfoQueryResult
52
+
53
+ constructor(success: string, data: SerializableSvmAccountsInfoQueryResult, error: string) {
54
+ super(success, error)
55
+ this.data = data
56
+ }
57
+ }
@@ -0,0 +1,40 @@
1
+ import { BlockchainToken } from '../tokens'
2
+
3
+ import { QueryResponseBase } from './QueryResponse'
4
+
5
+ @json
6
+ class TokenPriceQueryBase {
7
+ constructor(
8
+ public readonly address: string,
9
+ public readonly chainId: i32
10
+ ) {}
11
+ }
12
+
13
+ @json
14
+ export class TokenPriceQuery extends TokenPriceQueryBase {
15
+ public readonly timestamp: i64
16
+
17
+ constructor(address: string, chainId: i32, timestamp: i64) {
18
+ super(address, chainId)
19
+ this.timestamp = timestamp
20
+ }
21
+
22
+ static fromToken(token: BlockchainToken, timestamp: Date | null): TokenPriceQueryBase {
23
+ const address = token.address.toString()
24
+ const chainId = token.chainId
25
+
26
+ return timestamp
27
+ ? new TokenPriceQuery(address, chainId, changetype<Date>(timestamp).getTime())
28
+ : new TokenPriceQueryBase(address, chainId)
29
+ }
30
+ }
31
+
32
+ @json
33
+ export class TokenPriceQueryResponse extends QueryResponseBase {
34
+ public data: string[]
35
+
36
+ constructor(success: string, data: string[], error: string) {
37
+ super(success, error)
38
+ this.data = data
39
+ }
40
+ }
@@ -1,5 +1,6 @@
1
- export * from './Call'
2
- export * from './GetAccountsInfo'
3
- export * from './GetPrice'
4
- export * from './GetRelevantTokens'
1
+ export * from './EvmCallQuery'
2
+ export * from './QueryResponse'
3
+ export * from './RelevantTokensQuery'
5
4
  export * from './SubgraphQuery'
5
+ export * from './SvmAccountsInfoQuery'
6
+ export * from './TokenPriceQuery'
@@ -1,6 +1,7 @@
1
1
  import { environment } from '../environment'
2
2
  import { evm } from '../evm'
3
3
  import { EVM_NATIVE_ADDRESS } from '../helpers'
4
+ import { log } from '../log'
4
5
  import { Address, ChainId, EvmDecodeParam } from '../types'
5
6
 
6
7
  import { BlockchainToken } from './BlockchainToken'
@@ -110,30 +111,24 @@ export class ERC20Token extends BlockchainToken {
110
111
  /**
111
112
  * Gets the token’s symbol (e.g., "ETH", "USDC").
112
113
  * If the symbol was not provided during construction, it will be lazily fetched
113
- * via a `contractCall` to the token’s `symbol()` function (ERC-20 standard, selector `0x95d89b41`).
114
+ * via a `evmCallQuery` to the token’s `symbol()` function (ERC-20 standard, selector `0x95d89b41`).
114
115
  * The fetched symbol is cached in the instance for future accesses.
115
116
  * @returns A string containing the token symbol.
116
117
  */
117
118
  get symbol(): string {
118
- if (this._symbol === ERC20Token.EMPTY_SYMBOL) {
119
- const response = environment.contractCall(this.address, this.chainId, '0x95d89b41', this._timestamp)
120
- this._symbol = evm.decode(new EvmDecodeParam('string', response))
121
- }
119
+ if (this._symbol === ERC20Token.EMPTY_SYMBOL) this._getSymbol()
122
120
  return this._symbol
123
121
  }
124
122
 
125
123
  /**
126
124
  * Gets the token’s decimals (number of decimal places used).
127
125
  * If decimals were not provided during construction, they will be lazily fetched
128
- * via a `contractCall` to the token’s `decimals()` function (ERC-20 standard, selector `0x313ce567`).
126
+ * via a `evmCallQuery` to the token’s `decimals()` function (ERC-20 standard, selector `0x313ce567`).
129
127
  * The fetched value is parsed to `u8` and cached for future accesses.
130
128
  * @returns A `u8` representing the number of decimals of the token.
131
129
  */
132
130
  get decimals(): u8 {
133
- if (this._decimals == ERC20Token.EMPTY_DECIMALS) {
134
- const result = environment.contractCall(this.address, this.chainId, '0x313ce567', this._timestamp)
135
- this._decimals = u8.parse(evm.decode(new EvmDecodeParam('uint8', result)))
136
- }
131
+ if (this._decimals == ERC20Token.EMPTY_DECIMALS) this._getDecimals()
137
132
  return this._decimals
138
133
  }
139
134
 
@@ -154,4 +149,38 @@ export class ERC20Token extends BlockchainToken {
154
149
  isNative(): boolean {
155
150
  return this.equals(ERC20Token.native(this.chainId))
156
151
  }
152
+
153
+ /**
154
+ * Internal method to fetch and cache the token symbol.
155
+ * Performs an EVM call query to the token's symbol() function.
156
+ * @private
157
+ */
158
+ private _getSymbol(): void {
159
+ const response = environment.evmCallQuery(this.address, this.chainId, '0x95d89b41', this._timestamp)
160
+ if (response.isError) {
161
+ log.warning(
162
+ `Failed to get symbol for token ${this.address.toString()} on chain ${this.chainId.toString()}: ${response.error}`
163
+ )
164
+ this._symbol = ERC20Token.EMPTY_SYMBOL
165
+ return
166
+ }
167
+ this._symbol = evm.decode(new EvmDecodeParam('string', response.value))
168
+ }
169
+
170
+ /**
171
+ * Internal method to fetch and cache the token decimals.
172
+ * Performs an EVM call query to the token's decimals() function.
173
+ * @private
174
+ */
175
+ private _getDecimals(): void {
176
+ const result = environment.evmCallQuery(this.address, this.chainId, '0x313ce567', this._timestamp)
177
+ if (result.isError) {
178
+ log.warning(
179
+ `Failed to get decimals for token ${this.address.toString()} on chain ${this.chainId.toString()}: ${result.error}`
180
+ )
181
+ this._decimals = ERC20Token.EMPTY_DECIMALS
182
+ return
183
+ }
184
+ this._decimals = u8.parse(evm.decode(new EvmDecodeParam('uint8', result.value)))
185
+ }
157
186
  }
@@ -1,5 +1,6 @@
1
1
  import { environment } from '../environment'
2
2
  import { SVM_NATIVE_ADDRESS } from '../helpers'
3
+ import { log } from '../log'
3
4
  import { svm } from '../svm'
4
5
  import { Address, ChainId, SvmMint, SvmPdaSeed, SvmTokenMetadataData } from '../types'
5
6
 
@@ -71,11 +72,7 @@ export class SPLToken extends BlockchainToken {
71
72
  * @returns A `u8` representing the number of decimals of the token.
72
73
  */
73
74
  get decimals(): u8 {
74
- if (this._decimals == SPLToken.EMPTY_DECIMALS) {
75
- const result = environment.getAccountsInfo([this.address])
76
- const decimals = SvmMint.fromHex(result.accountsInfo[0].data).decimals
77
- this._decimals = decimals
78
- }
75
+ if (this._decimals == SPLToken.EMPTY_DECIMALS) this._getDecimals()
79
76
  return this._decimals
80
77
  }
81
78
 
@@ -87,18 +84,50 @@ export class SPLToken extends BlockchainToken {
87
84
  * @returns A string containing the token symbol.
88
85
  */
89
86
  get symbol(): string {
90
- if (this._symbol == SPLToken.EMPTY_SYMBOL) {
91
- const result = environment.getAccountsInfo([this.getMetadataAddress()])
92
- const data = result.accountsInfo[0].data
93
- // Return placeholder symbol from address if TokenMetadata standard is not used
94
- this._symbol =
95
- data === '0x'
96
- ? `${this.address.toString().slice(0, 5)}...${this.address.toString().slice(-5)}`
97
- : SvmTokenMetadataData.fromTokenMetadataHex(result.accountsInfo[0].data).symbol
98
- }
87
+ if (this._symbol == SPLToken.EMPTY_SYMBOL) this._getSymbol()
99
88
  return this._symbol
100
89
  }
101
90
 
91
+ /**
92
+ * Internal method to fetch and cache the token decimals.
93
+ * Performs an SVM accounts info query to get the mint data.
94
+ * @private
95
+ */
96
+ private _getDecimals(): void {
97
+ const result = environment.svmAccountsInfoQuery([this.address])
98
+ if (result.isError) {
99
+ log.warning(
100
+ `Failed to get decimals for token ${this.address.toString()} on chain ${this.chainId.toString()}: ${result.error}`
101
+ )
102
+ this._decimals = SPLToken.EMPTY_DECIMALS
103
+ return
104
+ }
105
+ const decimals = SvmMint.fromHex(result.value.accountsInfo[0].data).decimals
106
+ this._decimals = decimals
107
+ }
108
+
109
+ /**
110
+ * Internal method to fetch and cache the token symbol.
111
+ * Performs an SVM accounts info query to get the token metadata.
112
+ * @private
113
+ */
114
+ private _getSymbol(): void {
115
+ const result = environment.svmAccountsInfoQuery([this.getMetadataAddress()])
116
+ if (result.isError) {
117
+ log.warning(
118
+ `Failed to get symbol for token ${this.address.toString()} on chain ${this.chainId.toString()}: ${result.error}`
119
+ )
120
+ this._symbol = SPLToken.EMPTY_SYMBOL
121
+ return
122
+ }
123
+ const data = result.value.accountsInfo[0].data
124
+ // Return placeholder symbol from address if TokenMetadata standard is not used
125
+ this._symbol =
126
+ data === '0x'
127
+ ? `${this.address.toString().slice(0, 5)}...${this.address.toString().slice(-5)}`
128
+ : SvmTokenMetadataData.fromTokenMetadataHex(result.value.accountsInfo[0].data).symbol
129
+ }
130
+
102
131
  /**
103
132
  * Derives Metaplex Metadata address for this given token
104
133
  */
@@ -1,5 +1,5 @@
1
1
  import { environment } from '../environment'
2
- import { BigInt, JSON } from '../types'
2
+ import { BigInt, JSON, Result } from '../types'
3
3
 
4
4
  import { BlockchainToken } from './BlockchainToken'
5
5
  import { SerializableToken, Token } from './Token'
@@ -217,11 +217,14 @@ export class TokenAmount {
217
217
  * Converts this TokenAmount to its USD equivalent.
218
218
  * @returns A USD instance representing the current USD value
219
219
  */
220
- toUsd(): USD {
221
- if (this.isZero()) return USD.zero()
222
- const tokenPrice = environment.getPrice(this.token)
220
+ toUsd(): Result<USD, string> {
221
+ if (this.isZero()) return Result.ok<USD, string>(USD.zero())
222
+ const tokenPriceResult = environment.tokenPriceQuery(this.token)
223
+ if (tokenPriceResult.isError) return Result.err<USD, string>(tokenPriceResult.error)
224
+
225
+ const tokenPrice = tokenPriceResult.value
223
226
  const amountUsd = this.amount.times(tokenPrice.value).downscale(this.decimals)
224
- return USD.fromBigInt(amountUsd)
227
+ return Result.ok<USD, string>(USD.fromBigInt(amountUsd))
225
228
  }
226
229
 
227
230
  /**
@@ -229,9 +232,12 @@ export class TokenAmount {
229
232
  * @param other - The target token to convert to
230
233
  * @returns A TokenAmount of the target token with equivalent USD value
231
234
  */
232
- toTokenAmount(other: Token): TokenAmount {
233
- if (this.isZero()) return TokenAmount.fromI32(other, 0)
234
- return this.toUsd().toTokenAmount(other)
235
+ toTokenAmount(other: Token): Result<TokenAmount, string> {
236
+ if (this.isZero()) return Result.ok<TokenAmount, string>(TokenAmount.fromI32(other, 0))
237
+ const usdResult = this.toUsd()
238
+ if (usdResult.isError) return Result.err<TokenAmount, string>(usdResult.error)
239
+
240
+ return usdResult.value.toTokenAmount(other)
235
241
  }
236
242
 
237
243
  /**
package/src/tokens/USD.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import { environment } from '../environment'
2
2
  import { STANDARD_DECIMALS } from '../helpers'
3
3
  import { Token, TokenAmount } from '../tokens'
4
- import { BigInt } from '../types'
4
+ import { BigInt, Result } from '../types'
5
5
 
6
6
  /**
7
7
  * Represents a USD amount with fixed decimal precision.
@@ -192,10 +192,13 @@ export class USD {
192
192
  * @param token - The target token to convert to
193
193
  * @returns A TokenAmount representing the equivalent value in the target token
194
194
  */
195
- toTokenAmount(token: Token): TokenAmount {
196
- if (this.isZero()) return TokenAmount.fromI32(token, 0)
197
- const tokenPrice = environment.getPrice(token)
195
+ toTokenAmount(token: Token): Result<TokenAmount, string> {
196
+ if (this.isZero()) return Result.ok<TokenAmount, string>(TokenAmount.fromI32(token, 0))
197
+ const tokenPriceResult = environment.tokenPriceQuery(token)
198
+ if (tokenPriceResult.isError) return Result.err<TokenAmount, string>(tokenPriceResult.error)
199
+
200
+ const tokenPrice = tokenPriceResult.value
198
201
  const tokenAmount = this.value.upscale(token.decimals).div(tokenPrice.value)
199
- return TokenAmount.fromBigInt(token, tokenAmount)
202
+ return Result.ok<TokenAmount, string>(TokenAmount.fromBigInt(token, tokenAmount))
200
203
  }
201
204
  }
@@ -0,0 +1,56 @@
1
+ // eslint-disable-next-line no-secrets/no-secrets
2
+ // This file is based on code from "The Graph Tooling" (https://github.com/graphprotocol/graph-tooling/tree/7faa3098b2e6c61f09fc81b8b2d333e66b0080d1).
3
+ // Licensed under the MIT License.
4
+ // Copyright (c) 2018 Graph Protocol, Inc. and contributors.
5
+ // Modified by Mimic Protocol, 2025.
6
+
7
+ /**
8
+ * The result of an operation, with a corresponding value and error type.
9
+ */
10
+ export class Result<V, E> {
11
+ constructor(
12
+ private _value: Wrapped<V> | null,
13
+ private _error: Wrapped<E> | null
14
+ ) {}
15
+
16
+ static ok<V, E>(value: V): Result<V, E> {
17
+ return new Result<V, E>(new Wrapped<V>(value), null)
18
+ }
19
+
20
+ static err<V, E>(error: E): Result<V, E> {
21
+ return new Result<V, E>(null, new Wrapped<E>(error))
22
+ }
23
+
24
+ get isOk(): boolean {
25
+ return this._value !== null
26
+ }
27
+
28
+ get isError(): boolean {
29
+ return this._error !== null
30
+ }
31
+
32
+ get value(): V {
33
+ if (this.isError) throw new Error('Trying to get a value from an error result')
34
+ return changetype<Wrapped<V>>(this._value).inner
35
+ }
36
+
37
+ get error(): E {
38
+ if (this.isOk) throw new Error('Trying to get an error from a successful result')
39
+ return changetype<Wrapped<E>>(this._error).inner
40
+ }
41
+ }
42
+
43
+ // This is used to wrap a generic so that it can be unioned with `null`, working around limitations
44
+ // with primitives.
45
+ export class Wrapped<T> {
46
+ inner: T
47
+
48
+ constructor(inner: T) {
49
+ this.inner = inner
50
+ }
51
+ }
52
+
53
+ // This is used to represent a void return type
54
+ export class Void {
55
+ constructor() {}
56
+ }
@@ -7,6 +7,7 @@ export * from './Bytes'
7
7
  export * from './ChainId'
8
8
  export * from './evm'
9
9
  export * from './Option'
10
+ export * from './Result'
10
11
  export * from './svm'
11
12
  export * from './TriggerType'
12
13
  export { JSON }
@@ -1,27 +0,0 @@
1
- import { Address, ChainId } from '../types'
2
-
3
- @json
4
- class CallBase {
5
- constructor(
6
- public readonly to: string,
7
- public readonly chainId: ChainId,
8
- public readonly data: string
9
- ) {}
10
- }
11
-
12
- @json
13
- export class Call extends CallBase {
14
- public readonly timestamp: i64
15
-
16
- constructor(to: string, chainId: ChainId, timestamp: i64, data: string) {
17
- super(to, chainId, data)
18
- this.timestamp = timestamp
19
- }
20
-
21
- static from(to: Address, chainId: ChainId, timestamp: Date | null, data: string): CallBase {
22
- const address = to.toString()
23
- return timestamp
24
- ? new Call(address, chainId, changetype<Date>(timestamp).getTime(), data)
25
- : new CallBase(address, chainId, data)
26
- }
27
- }
@@ -1,46 +0,0 @@
1
- import { Address, SerializableSvmAccountInfo, SvmAccountInfo } from '../types'
2
-
3
- @json
4
- class GetAccountsInfoBase {
5
- constructor(public readonly publicKeys: string[]) {}
6
- }
7
-
8
- @json
9
- export class GetAccountsInfo extends GetAccountsInfoBase {
10
- public readonly timestamp: i64
11
-
12
- constructor(publicKeys: string[], timestamp: i64) {
13
- super(publicKeys)
14
- this.timestamp = timestamp
15
- }
16
-
17
- static from(publicKeys: Address[], timestamp: Date | null): GetAccountsInfoBase {
18
- const strPublicKeys = publicKeys.map((pk: Address) => pk.toString())
19
- return timestamp
20
- ? new GetAccountsInfo(strPublicKeys, changetype<Date>(timestamp).getTime())
21
- : new GetAccountsInfoBase(strPublicKeys)
22
- }
23
- }
24
-
25
- // There is a bug with json-as, so this can't be parsed directly
26
- export class GetAccountsInfoResponse {
27
- constructor(
28
- public accountsInfo: SvmAccountInfo[],
29
- public slot: string
30
- ) {}
31
-
32
- static fromSerializable(serializable: SerializableGetAccountsInfoResponse): GetAccountsInfoResponse {
33
- return new GetAccountsInfoResponse(
34
- serializable.accountsInfo.map((acc: SerializableSvmAccountInfo) => SvmAccountInfo.fromSerializable(acc)),
35
- serializable.slot
36
- )
37
- }
38
- }
39
-
40
- @json
41
- export class SerializableGetAccountsInfoResponse {
42
- constructor(
43
- public accountsInfo: SerializableSvmAccountInfo[],
44
- public slot: string
45
- ) {}
46
- }
@@ -1,28 +0,0 @@
1
- import { BlockchainToken } from '../tokens'
2
-
3
- @json
4
- class GetPriceBase {
5
- constructor(
6
- public readonly address: string,
7
- public readonly chainId: i32
8
- ) {}
9
- }
10
-
11
- @json
12
- export class GetPrice extends GetPriceBase {
13
- public readonly timestamp: i64
14
-
15
- constructor(address: string, chainId: i32, timestamp: i64) {
16
- super(address, chainId)
17
- this.timestamp = timestamp
18
- }
19
-
20
- static fromToken(token: BlockchainToken, timestamp: Date | null): GetPriceBase {
21
- const address = token.address.toString()
22
- const chainId = token.chainId
23
-
24
- return timestamp
25
- ? new GetPrice(address, chainId, changetype<Date>(timestamp).getTime())
26
- : new GetPriceBase(address, chainId)
27
- }
28
- }