@mimicprotocol/lib-ts 0.0.1-rc.9 → 0.1.0

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.
Files changed (80) hide show
  1. package/CHANGELOG.md +144 -0
  2. package/README.md +7 -7
  3. package/constants.d.ts +1 -0
  4. package/constants.js +1 -0
  5. package/index.ts +3 -0
  6. package/package.json +18 -4
  7. package/src/chains/Arbitrum.ts +14 -0
  8. package/src/chains/Avalanche.ts +15 -0
  9. package/src/chains/BNB.ts +15 -0
  10. package/src/chains/Base.ts +14 -0
  11. package/src/chains/BaseSepolia.ts +7 -0
  12. package/src/chains/Ethereum.ts +7 -7
  13. package/src/chains/Gnosis.ts +14 -0
  14. package/src/chains/Optimism.ts +7 -7
  15. package/src/chains/Polygon.ts +10 -7
  16. package/src/chains/Sonic.ts +13 -0
  17. package/src/chains/index.ts +7 -0
  18. package/src/context/Context.ts +102 -7
  19. package/src/environment.ts +150 -71
  20. package/src/evm.ts +5 -4
  21. package/src/helpers/BorshDeserializer.ts +133 -0
  22. package/src/helpers/consensus.ts +35 -0
  23. package/src/helpers/constants.ts +7 -1
  24. package/src/helpers/index.ts +5 -0
  25. package/src/helpers/math.ts +20 -0
  26. package/src/helpers/serialize.ts +5 -125
  27. package/src/helpers/strings.ts +82 -5
  28. package/src/intents/Call/EvmCall.ts +199 -0
  29. package/src/intents/Call/EvmDynamicCall.ts +272 -0
  30. package/src/intents/Call/SvmCall.ts +204 -0
  31. package/src/intents/Call/index.ts +3 -0
  32. package/src/intents/Intent.ts +347 -35
  33. package/src/intents/Operation.ts +114 -0
  34. package/src/intents/Swap.ts +127 -114
  35. package/src/intents/Transfer.ts +72 -123
  36. package/src/intents/index.ts +1 -0
  37. package/src/log.ts +83 -0
  38. package/src/queries/EvmCallQuery.ts +43 -0
  39. package/src/queries/QueryResponse.ts +26 -0
  40. package/src/queries/RelevantTokensQuery.ts +82 -0
  41. package/src/queries/SubgraphQuery.ts +50 -0
  42. package/src/queries/SvmAccountsInfoQuery.ts +65 -0
  43. package/src/queries/TokenPriceQuery.ts +47 -0
  44. package/src/queries/index.ts +6 -1
  45. package/src/storage/index.ts +1 -0
  46. package/src/storage/storage.ts +40 -0
  47. package/src/svm.ts +27 -0
  48. package/src/tokens/BlockchainToken.ts +108 -0
  49. package/src/tokens/DenominationToken.ts +70 -0
  50. package/src/tokens/ERC20Token.ts +192 -0
  51. package/src/tokens/SPLToken.ts +162 -0
  52. package/src/tokens/Token.ts +55 -155
  53. package/src/tokens/TokenAmount.ts +72 -30
  54. package/src/tokens/TokenProvider.ts +54 -0
  55. package/src/tokens/Tokens.ts +186 -0
  56. package/src/tokens/USD.ts +9 -6
  57. package/src/tokens/index.ts +6 -0
  58. package/src/types/Address.ts +86 -14
  59. package/src/types/BigInt.ts +14 -22
  60. package/src/types/ByteArray.ts +41 -3
  61. package/src/types/Bytes.ts +7 -0
  62. package/src/types/ChainId.ts +9 -1
  63. package/src/types/Option.ts +35 -0
  64. package/src/types/Result.ts +68 -0
  65. package/src/types/TriggerType.ts +4 -0
  66. package/src/types/evm/EvmDecodeParam.ts +7 -0
  67. package/src/types/evm/EvmEncodeParam.ts +31 -0
  68. package/src/types/evm/index.ts +2 -0
  69. package/src/types/index.ts +8 -2
  70. package/src/types/svm/SvmAccountInfo.ts +32 -0
  71. package/src/types/svm/SvmAccountMeta.ts +28 -0
  72. package/src/types/svm/SvmFindProgramAddress.ts +32 -0
  73. package/src/types/svm/SvmMint.ts +44 -0
  74. package/src/types/svm/SvmPdaSeed.ts +19 -0
  75. package/src/types/svm/SvmTokenMetadataData.ts +29 -0
  76. package/src/types/svm/index.ts +5 -0
  77. package/src/intents/Call.ts +0 -238
  78. package/src/queries/Call.ts +0 -16
  79. package/src/types/EvmDecodeParam.ts +0 -30
  80. package/src/types/EvmEncodeParam.ts +0 -54
@@ -1,126 +1,205 @@
1
- import { join, ListType, serialize, serializeArray } from './helpers'
2
- import { Token, TokenAmount, USD } from './tokens'
3
- import { Address, BigInt, ChainId } from './types'
4
- import { Swap, Transfer, Call } from './intents'
5
- import { Call as CallQuery } from './queries'
6
1
  import { JSON } from 'json-as/assembly'
2
+
7
3
  import { Context, SerializableContext } from './context'
4
+ import { evm } from './evm'
5
+ import { Consensus, ListType, MIMIC_HELPER_ADDRESS } from './helpers'
6
+ import { Intent } from './intents'
7
+ import {
8
+ EvmCallQuery,
9
+ EvmCallQueryResponse,
10
+ RelevantTokensQuery,
11
+ RelevantTokensQueryResponse,
12
+ SubgraphQuery,
13
+ SubgraphQueryResponse,
14
+ SubgraphQueryResult,
15
+ SvmAccountsInfoQuery,
16
+ SvmAccountsInfoQueryResponse,
17
+ SvmAccountsInfoQueryResult,
18
+ TokenBalanceQuery,
19
+ TokenPriceQuery,
20
+ TokenPriceQueryResponse,
21
+ } from './queries'
22
+ import { BlockchainToken, Token, TokenAmount, USD } from './tokens'
23
+ import { Address, BigInt, Bytes, ChainId, EvmDecodeParam, EvmEncodeParam, Result } from './types'
8
24
 
9
25
  export namespace environment {
10
- @external('environment', '_call')
11
- declare function _call(params: string): void
26
+ @external('environment', '_sendIntent')
27
+ declare function _sendIntent(params: string): void
12
28
 
13
- @external('environment', '_swap')
14
- declare function _swap(params: string): void
29
+ @external('environment', '_tokenPriceQuery')
30
+ declare function _tokenPriceQuery(params: string): string
15
31
 
16
- @external('environment', '_transfer')
17
- declare function _transfer(params: string): void
32
+ @external('environment', '_relevantTokensQuery')
33
+ declare function _relevantTokensQuery(params: string): string
18
34
 
19
- @external('environment', '_getPrice')
20
- declare function _getPrice(params: string): string
35
+ @external('environment', '_evmCallQuery')
36
+ declare function _evmCallQuery(params: string): string
21
37
 
22
- @external('environment', '_getRelevantTokens')
23
- declare function _getRelevantTokens(params: string): string
38
+ @external('environment', '_subgraphQuery')
39
+ declare function _subgraphQuery(params: string): string
24
40
 
25
- @external('environment', '_contractCall')
26
- declare function _contractCall(params: string): string
41
+ @external('environment', '_svmAccountsInfoQuery')
42
+ declare function _svmAccountsInfoQuery(params: string): string
27
43
 
28
44
  @external('environment', '_getContext')
29
45
  declare function _getContext(): string
30
46
 
31
47
  /**
32
- * Generates a Call intent containing contract calls on the blockchain.
33
- * @param call - The Call intent to generate
48
+ * Sends an intent containing one or more operations to the execution environment.
49
+ * @param intent - The intent to send
34
50
  */
35
- export function call(call: Call): void {
36
- _call(JSON.stringify(call))
51
+ export function sendIntent(intent: Intent): void {
52
+ _sendIntent(JSON.stringify(intent))
37
53
  }
38
54
 
39
55
  /**
40
- * Generates a Swap intent for token exchange operations.
41
- * @param swap - The Swap intent to generate
56
+ * Returns an aggregated price (by consensus function) for a token in USD at a specific timestamp.
57
+ * By default, returns the median USD price across multiple sources.
58
+ *
59
+ * @param token - The token to get the USD price for.
60
+ * @param timestamp - Optional. The timestamp for price lookup (defaults to current time if not provided).
61
+ * @param consensusFn - Optional. A function for aggregating the price values (default is median).
62
+ * @returns A `Result` containing either the consensus USD price or an error string.
42
63
  */
43
- export function swap(swap: Swap): void {
44
- _swap(JSON.stringify(swap))
45
- }
64
+ export function tokenPriceQuery(
65
+ token: Token,
66
+ timestamp: Date | null = null,
67
+ consensusFn: (values: USD[]) => USD = Consensus.medianUSD
68
+ ): Result<USD, string> {
69
+ if (token.isUSD()) return Result.ok<USD, string>(USD.fromI32(1))
70
+ if (!(token instanceof BlockchainToken)) {
71
+ return Result.err<USD, string>('Price query not supported for token ' + token.toString())
72
+ }
46
73
 
47
- /**
48
- * Generates a Transfer intent for sending tokens to recipients.
49
- * @param transfer - The Transfer intent to generate
50
- */
51
- export function transfer(transfer: Transfer): void {
52
- _transfer(JSON.stringify(transfer))
53
- }
74
+ const responseStr = _tokenPriceQuery(
75
+ JSON.stringify(TokenPriceQuery.fromToken(changetype<BlockchainToken>(token), timestamp))
76
+ )
77
+ const pricesResult = TokenPriceQueryResponse.fromJson<TokenPriceQueryResponse>(responseStr).toResult()
54
78
 
55
- /**
56
- * Tells the price of a token in USD at a specific timestamp.
57
- * @param token - The token to get the price of
58
- * @param timestamp - The timestamp for price lookup (optional, defaults to current time)
59
- * @returns The token price in USD
60
- */
61
- export function getPrice(token: Token, timestamp: Date | null = null): USD {
62
- const price = _getPrice(join([serialize(token.address), serialize(token.chainId), serialize(timestamp ? timestamp.getTime().toString() : '')]))
63
- return USD.fromBigInt(BigInt.fromString(price))
79
+ if (pricesResult.isError) return Result.err<USD, string>(pricesResult.error)
80
+
81
+ const prices = pricesResult.unwrap()
82
+ if (prices.length === 0) return Result.err<USD, string>('Prices not found for token ' + token.toString())
83
+
84
+ return Result.ok<USD, string>(consensusFn(prices))
64
85
  }
65
86
 
66
87
  /**
67
- * Tells the balances of an address for the specified tokens and chains.
88
+ * Returns the balances of an address for the specified tokens and chains.
68
89
  * @param address - The address to query balances for
69
90
  * @param chainIds - Array of chain ids to search
70
91
  * @param usdMinAmount - Minimum USD value threshold for tokens (optional, defaults to zero)
71
- * @param tokensList - List of tokens to include/exclude (optional, defaults to empty array)
92
+ * @param tokensList - List of blockchain tokens to include/exclude (optional, defaults to empty array)
72
93
  * @param listType - Whether to include (AllowList) or exclude (DenyList) the tokens in `tokensList` (optional, defaults to DenyList)
73
- * @returns Array of TokenAmount objects representing the relevant tokens
94
+ * @param consensusFn - Optional. A function for aggregating the token amounts
95
+ * @returns Result containing either an array of TokenAmount objects representing the relevant tokens or an error string
74
96
  */
75
- export function getRelevantTokens(
97
+ export function relevantTokensQuery(
76
98
  address: Address,
77
99
  chainIds: ChainId[],
78
100
  usdMinAmount: USD = USD.zero(),
79
- tokensList: Token[] = [],
80
- listType: ListType = ListType.DenyList
81
- ): TokenAmount[] {
82
- const response = _getRelevantTokens(
83
- // NOTE: The runner expects an optional timestamp that the user will not be able to input
84
- // that's why serialize('') is used
85
- // this is a workaround until a decision is made regarding the timestamp
86
- join([serialize(address), serializeArray(chainIds), serialize(usdMinAmount.value), serializeArray(tokensList), serialize(listType), serialize('')])
101
+ tokensList: BlockchainToken[] = [],
102
+ listType: ListType = ListType.DenyList,
103
+ consensusFn: (amounts: TokenBalanceQuery[][]) => TokenAmount[] = Consensus.uniqueTokenAmounts
104
+ ): Result<TokenAmount[], string> {
105
+ const responseStr = _relevantTokensQuery(
106
+ JSON.stringify(RelevantTokensQuery.init(address, chainIds, usdMinAmount, tokensList, listType))
87
107
  )
88
- const rows = response.split('\n')
89
- const tokenAmounts: TokenAmount[] = []
108
+ const responseResult = RelevantTokensQueryResponse.fromJson<RelevantTokensQueryResponse>(responseStr).toResult()
90
109
 
91
- for (let i = 0; i < rows.length; i++) {
92
- if (rows[i].length === 0) continue
93
- tokenAmounts.push(TokenAmount.parse(rows[i]))
94
- }
110
+ if (responseResult.isError) return Result.err<TokenAmount[], string>(responseResult.error)
95
111
 
96
- return tokenAmounts
112
+ return Result.ok<TokenAmount[], string>(consensusFn(responseResult.unwrap()))
97
113
  }
98
114
 
99
115
  /**
100
- * Generates a contract call of a read function on the blockchain and returns the result.
116
+ * Executes a read-only contract call on the blockchain and returns the result.
101
117
  * @param to - The contract address to call
102
118
  * @param chainId - The blockchain network identifier
103
119
  * @param timestamp - The timestamp for the call context (optional)
104
120
  * @param data - The encoded function call data
105
121
  * @returns The raw response from the contract call
106
122
  */
107
- export function contractCall(
123
+ export function evmCallQuery(
108
124
  to: Address,
109
125
  chainId: ChainId,
110
- timestamp: Date | null,
111
- data: string
112
- ): string {
113
- return _contractCall(
114
- JSON.stringify(new CallQuery(to, chainId, timestamp, data))
115
- )
126
+ data: string,
127
+ timestamp: Date | null = null
128
+ ): Result<string, string> {
129
+ const responseStr = _evmCallQuery(JSON.stringify(EvmCallQuery.from(to, chainId, timestamp, data)))
130
+ return EvmCallQueryResponse.fromJson<EvmCallQueryResponse>(responseStr).toResult()
116
131
  }
117
132
 
118
133
  /**
119
- * Tells the current execution context containing environment information.
120
- * @returns The Context object containing: user, settler, timestamp, and config ID
134
+ * Executes a subgraph query and returns the result.
135
+ * @param chainId - The blockchain network identifier
136
+ * @param subgraphId - The ID of the subgraph to be called
137
+ * @param query - The string representing the subgraph query to be executed
138
+ * @param timestamp - The timestamp for the query context (optional)
139
+ * @returns The subgraph query response
140
+ */
141
+ export function subgraphQuery(
142
+ chainId: ChainId,
143
+ subgraphId: string,
144
+ query: string,
145
+ timestamp: Date | null = null
146
+ ): Result<SubgraphQueryResult, string> {
147
+ const responseStr = _subgraphQuery(JSON.stringify(SubgraphQuery.from(chainId, subgraphId, query, timestamp)))
148
+ return SubgraphQueryResponse.fromJson<SubgraphQueryResponse>(responseStr).toResult()
149
+ }
150
+
151
+ /**
152
+ * Returns on-chain account info for Solana accounts.
153
+ * @param publicKeys - Accounts to read from chain
154
+ * @param timestamp - The timestamp for the call context (optional)
155
+ * @returns Result containing either the account info result or an error string
156
+ */
157
+ export function svmAccountsInfoQuery(
158
+ publicKeys: Address[],
159
+ timestamp: Date | null = null
160
+ ): Result<SvmAccountsInfoQueryResult, string> {
161
+ const responseStr = _svmAccountsInfoQuery(JSON.stringify(SvmAccountsInfoQuery.from(publicKeys, timestamp)))
162
+ return SvmAccountsInfoQueryResponse.fromJson<SvmAccountsInfoQueryResponse>(responseStr).toResult()
163
+ }
164
+
165
+ /**
166
+ * Returns the current execution context containing environment information.
167
+ * @returns The Context object containing: user, settler, timestamp, consensusThreshold and triggerPayload
121
168
  */
122
169
  export function getContext(): Context {
123
170
  const context = JSON.parse<SerializableContext>(_getContext())
124
171
  return Context.fromSerializable(context)
125
172
  }
173
+
174
+ /**
175
+ * Returns the native token balance of target account.
176
+ * @param chainId - Chain id to check balance
177
+ * @param target - Address to get balance from
178
+ * @returns The native token balance in wei
179
+ */
180
+ export function getNativeTokenBalance(chainId: ChainId, target: Address): Result<BigInt, string> {
181
+ if (chainId === ChainId.SOLANA_MAINNET) return Result.err<BigInt, string>('Solana not supported')
182
+ const data = '0xeffd663c' + evm.encode([EvmEncodeParam.fromValue('address', target)])
183
+ const response = evmCallQuery(Address.fromHexString(MIMIC_HELPER_ADDRESS), chainId, data)
184
+ if (response.isError) return Result.err<BigInt, string>(response.error)
185
+ const decodedResponse = evm.decode(new EvmDecodeParam('uint256', response.unwrap()))
186
+ const decoded = BigInt.fromString(decodedResponse)
187
+ return Result.ok<BigInt, string>(decoded)
188
+ }
189
+
190
+ /**
191
+ * Returns the code of the target account.
192
+ * @param chainId - Chain id to check code
193
+ * @param target - Address to get code from
194
+ * @returns The code of the target account
195
+ */
196
+ export function getCode(chainId: ChainId, target: Address): Result<Bytes, string> {
197
+ if (chainId === ChainId.SOLANA_MAINNET) return Result.err<Bytes, string>('Solana not supported')
198
+ const data = '0x7e105ce2' + evm.encode([EvmEncodeParam.fromValue('address', target)])
199
+ const response = evmCallQuery(Address.fromHexString(MIMIC_HELPER_ADDRESS), chainId, data)
200
+ if (response.isError) return Result.err<Bytes, string>(response.error)
201
+ const decodedResponse = evm.decode(new EvmDecodeParam('bytes', response.unwrap()))
202
+ const decoded = Bytes.fromHexString(decodedResponse)
203
+ return Result.ok<Bytes, string>(decoded)
204
+ }
126
205
  }
package/src/evm.ts CHANGED
@@ -1,5 +1,6 @@
1
+ import { JSON } from 'json-as/assembly'
2
+
1
3
  import { EvmDecodeParam, EvmEncodeParam } from './types'
2
- import { join, serialize, serializeArray } from './helpers'
3
4
 
4
5
  export namespace evm {
5
6
  @external('evm', '_encode')
@@ -10,14 +11,14 @@ export namespace evm {
10
11
 
11
12
  @external('evm', '_keccak')
12
13
  declare function _keccak(params: string): string
13
-
14
+
14
15
  /**
15
16
  * Encodes parameters for EVM smart contract function calls using ABI encoding.
16
17
  * @param callParameters - Array of parameters to encode for the contract call
17
18
  * @returns The ABI-encoded data as a hex string
18
19
  */
19
20
  export function encode(callParameters: EvmEncodeParam[]): string {
20
- return _encode(join([serializeArray(callParameters)]))
21
+ return _encode(JSON.stringify(callParameters))
21
22
  }
22
23
 
23
24
  /**
@@ -26,7 +27,7 @@ export namespace evm {
26
27
  * @returns The decoded data as a formatted string
27
28
  */
28
29
  export function decode(encodedData: EvmDecodeParam): string {
29
- return _decode(serialize(encodedData))
30
+ return _decode(JSON.stringify(encodedData))
30
31
  }
31
32
 
32
33
  /**
@@ -0,0 +1,133 @@
1
+ import { Address, ByteArray, Bytes } from '../types'
2
+ import { Option } from '../types/Option'
3
+
4
+ import { bytesToString } from './strings'
5
+
6
+ /**
7
+ * Unsafe class to deserialize bytes into Rust-like types
8
+ * Should be used with caution! This will only throw if there are insufficient bytes for the type
9
+ */
10
+ export class BorshDeserializer {
11
+ private _bytes: Uint8Array
12
+ private _offset: u32 = 0
13
+
14
+ constructor(bytes: Uint8Array) {
15
+ this._bytes = bytes
16
+ }
17
+
18
+ static fromHex(hex: string): BorshDeserializer {
19
+ return new BorshDeserializer(Bytes.fromHexString(hex))
20
+ }
21
+
22
+ static fromBytes(bytes: Uint8Array): BorshDeserializer {
23
+ return new BorshDeserializer(bytes)
24
+ }
25
+
26
+ tryBool(): bool {
27
+ if (this._offset >= this.getBytesLength()) throw new Error('Insufficient bytes for bool')
28
+ const value = this._bytes.at(this._offset)
29
+ this._offset += 1
30
+ return value === 1
31
+ }
32
+
33
+ tryU8(): u8 {
34
+ if (this._offset >= this.getBytesLength()) throw new Error('Insufficient bytes for u8')
35
+ const value = this._bytes.at(this._offset)
36
+ this._offset += 1
37
+ return value
38
+ }
39
+
40
+ tryU16(): u16 {
41
+ if (this._offset + 1 >= this.getBytesLength()) throw new Error('Insufficient bytes for u16')
42
+ const subarray = changetype<ByteArray>(this._bytes.subarray(this._offset, this._offset + 2))
43
+ this._offset += 2
44
+ return subarray.toU16()
45
+ }
46
+
47
+ tryU32(): u32 {
48
+ if (this._offset + 3 >= this.getBytesLength()) throw new Error('Insufficient bytes for u32')
49
+ const subarray = changetype<ByteArray>(this._bytes.subarray(this._offset, this._offset + 4))
50
+ this._offset += 4
51
+ return subarray.toU32()
52
+ }
53
+
54
+ tryU64(): u64 {
55
+ if (this._offset + 7 >= this.getBytesLength()) throw new Error('Insufficient bytes for u64')
56
+ const subarray = changetype<ByteArray>(this._bytes.subarray(this._offset, this._offset + 8))
57
+ this._offset += 8
58
+ return subarray.toU64()
59
+ }
60
+
61
+ tryPubkey(): Address {
62
+ if (this._offset + 31 >= this.getBytesLength()) throw new Error('Insufficient bytes for pubkey')
63
+ const pubkey = Address.fromBytes(Bytes.fromUint8Array(this._bytes.subarray(this._offset, this._offset + 32)))
64
+ this._offset += 32
65
+ return pubkey
66
+ }
67
+
68
+ tryString(): string {
69
+ const length = this.tryU32()
70
+ if (this._offset + length - 1 >= this.getBytesLength())
71
+ throw new Error(`Insufficient bytes for string of size ${length}`)
72
+ const str = bytesToString(this._bytes.subarray(this._offset, this._offset + length), true)
73
+ this._offset += length
74
+ return str
75
+ }
76
+
77
+ tryOptionBool(): Option<bool> {
78
+ const tag = this.tryU32()
79
+ if (tag === 0) return Option.none<bool>()
80
+ return Option.some<bool>(this.tryBool())
81
+ }
82
+
83
+ tryOptionU8(): Option<u8> {
84
+ const tag = this.tryU32()
85
+ if (tag === 0) return Option.none<u8>()
86
+ return Option.some<u8>(this.tryU8())
87
+ }
88
+
89
+ tryOptionU16(): Option<u16> {
90
+ const tag = this.tryU32()
91
+ if (tag === 0) return Option.none<u16>()
92
+ return Option.some<u16>(this.tryU16())
93
+ }
94
+
95
+ tryOptionU32(): Option<u32> {
96
+ const tag = this.tryU32()
97
+ if (tag === 0) return Option.none<u32>()
98
+ return Option.some<u32>(this.tryU32())
99
+ }
100
+
101
+ tryOptionU64(): Option<u64> {
102
+ const tag = this.tryU32()
103
+ if (tag === 0) return Option.none<u64>()
104
+ return Option.some<u64>(this.tryU64())
105
+ }
106
+
107
+ tryOptionPubkey(): Option<Address> {
108
+ const tag = this.tryU32()
109
+ if (tag === 0) return Option.none<Address>(Address.fromBytes(new Bytes(32)))
110
+ return Option.some<Address>(this.tryPubkey())
111
+ }
112
+
113
+ private getBytesLength(): u32 {
114
+ return this._bytes.length as u32
115
+ }
116
+
117
+ getOffset(): u32 {
118
+ return this._offset
119
+ }
120
+
121
+ setOffset(offset: u32): void {
122
+ if (offset > this.getBytesLength()) throw new Error('Offset overruns buffer length')
123
+ this._offset = offset
124
+ }
125
+
126
+ isEmpty(): bool {
127
+ return this._offset >= this.getBytesLength()
128
+ }
129
+
130
+ getLength(): u32 {
131
+ return this._bytes.length
132
+ }
133
+ }
@@ -0,0 +1,35 @@
1
+ import { TokenBalanceQuery } from '../queries/RelevantTokensQuery'
2
+ import { TokenAmount, USD } from '../tokens'
3
+ import { BigInt } from '../types'
4
+
5
+ import { median } from './math'
6
+
7
+ /**
8
+ * Computes the median value from an array of USD values.
9
+ * @param values - Array of USD values to compute the median from
10
+ * @returns The median USD value
11
+ * @throws Error if the array is empty
12
+ */
13
+ export function medianUSD(values: USD[]): USD {
14
+ return USD.fromBigInt(median(values.map<BigInt>((value: USD) => value.value)))
15
+ }
16
+
17
+ /**
18
+ * Deduplicates token balances by token address.
19
+ * Converts TokenBalanceQuery[][] to TokenAmount[] keeping only the first occurrence of each token address.
20
+ * @param balances - Array of arrays of TokenBalanceQuery to deduplicate
21
+ * @returns Array of unique TokenAmount objects
22
+ */
23
+ export function uniqueTokenAmounts(balances: TokenBalanceQuery[][]): TokenAmount[] {
24
+ const resultMap: Map<string, TokenAmount> = new Map()
25
+ for (let i = 0; i < balances.length; i++) {
26
+ for (let j = 0; j < balances[i].length; j++) {
27
+ const tokenAmount = balances[i][j].toTokenAmount()
28
+ const mapKey = tokenAmount.token.address.toString()
29
+
30
+ if (resultMap.has(mapKey)) continue
31
+ resultMap.set(mapKey, tokenAmount)
32
+ }
33
+ }
34
+ return resultMap.values()
35
+ }
@@ -1,5 +1,9 @@
1
1
  export const NULL_ADDRESS = '0x0000000000000000000000000000000000000000'
2
- export const NATIVE_ADDRESS = '0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee'
2
+ export const EVM_NATIVE_ADDRESS = '0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee'
3
+ export const SVM_NATIVE_ADDRESS = 'Nativeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee'
4
+ export const USD_ADDRESS = '0x0000000000000000000000000000000000000348'
5
+
6
+ export const MAX_UINT256_HEX = '0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff'
3
7
 
4
8
  export const STANDARD_DECIMALS: u8 = 18
5
9
 
@@ -7,3 +11,5 @@ export enum ListType {
7
11
  AllowList = 0,
8
12
  DenyList = 1,
9
13
  }
14
+
15
+ export const MIMIC_HELPER_ADDRESS = '0x5cf82cbed1110fc2f75b3413d53abac492931804'
@@ -1,3 +1,8 @@
1
+ import * as Consensus from './consensus'
2
+ import * as Math from './math'
3
+
4
+ export * from './BorshDeserializer'
1
5
  export * from './constants'
2
6
  export * from './serialize'
3
7
  export * from './strings'
8
+ export { Consensus, Math }
@@ -0,0 +1,20 @@
1
+ import { BigInt } from '../types'
2
+
3
+ /**
4
+ * Computes the median value from an array of BigInt values.
5
+ * @param values - Array of BigInt values to compute the median from
6
+ * @returns The median BigInt value
7
+ * @throws Error if the array is empty
8
+ */
9
+ export function median(values: BigInt[]): BigInt {
10
+ if (values.length === 0) throw new Error('Cannot compute median of empty array')
11
+
12
+ const sorted = values.sort((a: BigInt, b: BigInt) => BigInt.compare(a, b))
13
+ const len = sorted.length
14
+
15
+ if (len % 2 === 1) return sorted[len / 2]
16
+
17
+ const left = sorted[len / 2 - 1]
18
+ const right = sorted[len / 2]
19
+ return left.plus(right).div(BigInt.fromI32(2))
20
+ }
@@ -1,4 +1,4 @@
1
- const SEPARATOR = ','
1
+ import { Bytes } from '../types'
2
2
 
3
3
  export interface Stringable {
4
4
  toString(): string
@@ -8,133 +8,13 @@ export interface Serializable {
8
8
  serialize(): string
9
9
  }
10
10
 
11
+ export interface Byteable {
12
+ toBytes(): Bytes
13
+ }
14
+
11
15
  export function serialize<T extends Stringable>(elem: T): string {
12
16
  // eslint-disable-next-line
13
17
  // @ts-ignore
14
18
  if (elem instanceof Serializable) return elem.serialize()
15
19
  return elem.toString()
16
20
  }
17
-
18
- export function serializeArray<T extends Stringable>(array: T[]): string {
19
- const serializedElems: (string | null)[] = []
20
- for (let i = 0; i < array.length; i++) {
21
- serializedElems.push(serialize(array[i]))
22
- }
23
- return 'Array(' + join(serializedElems) + ')'
24
- }
25
-
26
- export function join(lst: (string | null)[]): string {
27
- return lst.join(SEPARATOR)
28
- }
29
-
30
- /**
31
- * Parses a CSV string into an array of tokens, handling nested structures,
32
- * null values, and stripping one layer of outer parentheses if they wrap the entire string.
33
- *
34
- * @param csvString - String containing comma-separated values, nested structures (e.g., "(1,2)"), and nulls
35
- * @returns Array of strings (trimmed) and nulls, preserving internal nesting
36
- * @throws {Error} If parentheses are unbalanced
37
- */
38
- export function parseCSV(csvString: string): (string | null)[] {
39
- const tokens: (string | null)[] = []
40
- const currentTokenChars: string[] = []
41
- const len = csvString.length
42
- let depth: i32 = 0
43
- let isEmpty = true
44
-
45
- let shouldStripOuter = false
46
- if (len > 1 && csvString.charAt(0) == '(' && csvString.charAt(len - 1) == ')') {
47
- let balance = 0
48
- let firstParenMatchesLast = true
49
- for (let k = 0; k < len; k++) {
50
- if (csvString.charAt(k) == '(') {
51
- balance++
52
- } else if (csvString.charAt(k) == ')') {
53
- balance--
54
- }
55
- if (balance == 0 && k < len - 1) {
56
- firstParenMatchesLast = false
57
- break
58
- }
59
- }
60
- if (firstParenMatchesLast && balance == 0) {
61
- shouldStripOuter = true
62
- }
63
- }
64
-
65
- for (let i = 0; i < len; i++) {
66
- const char = csvString.charAt(i)
67
- const charCode = char.charCodeAt(0)
68
-
69
- switch (charCode) {
70
- case '('.charCodeAt(0):
71
- if (shouldStripOuter && i == 0) {
72
- depth++
73
- break
74
- }
75
- depth++
76
- currentTokenChars.push(char)
77
- isEmpty = false
78
- break
79
-
80
- case ')'.charCodeAt(0):
81
- if (shouldStripOuter && i == len - 1) {
82
- depth--
83
- break
84
- }
85
- depth--
86
- if (depth < 0) throw new Error(`Unbalanced parentheses at position ${i}`)
87
- currentTokenChars.push(char)
88
- isEmpty = false
89
- break
90
-
91
- case SEPARATOR.charCodeAt(0):
92
- const isTopLevelComma = (shouldStripOuter && depth == 1) || (!shouldStripOuter && depth == 0)
93
- if (isTopLevelComma) {
94
- if (isEmpty) {
95
- tokens.push(null)
96
- } else {
97
- tokens.push(currentTokenChars.join('').trim())
98
- }
99
- currentTokenChars.length = 0
100
- isEmpty = true
101
- } else {
102
- currentTokenChars.push(char)
103
- isEmpty = false
104
- }
105
- break
106
-
107
- default:
108
- currentTokenChars.push(char)
109
- isEmpty = false
110
- break
111
- }
112
- }
113
-
114
- if (depth != 0) throw new Error('Unbalanced parentheses at the end of the string')
115
-
116
- if (len > 0) {
117
- if (isEmpty) {
118
- tokens.push(null)
119
- } else {
120
- tokens.push(currentTokenChars.join('').trim())
121
- }
122
- }
123
-
124
- return tokens
125
- }
126
-
127
- /**
128
- * Parses a CSV string into an array of strings, throwing an error if any null values are present.
129
- *
130
- * @param csvString - String containing comma-separated values
131
- * @returns Array of strings
132
- * @throws {Error} If null values are present
133
- */
134
-
135
- export function parseCSVNotNullable(csvString: string): string[] {
136
- const tokens = parseCSV(csvString)
137
- if (tokens.some((token) => token === null)) throw new Error('Null value found in CSV string')
138
-
139
- return changetype<string[]>(tokens)
140
- }