Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .changeset/clever-parents-talk.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
"@mimicprotocol/lib-ts": patch
"@mimicprotocol/cli": patch
"@mimicprotocol/test-ts": patch
---

Add getNativeBalance
14 changes: 14 additions & 0 deletions packages/lib-ts/as-pect.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,13 @@ export default {
},
},
evm: {
_encode: (paramsPtr) => {
const paramsStr = exports.__getString(paramsPtr)
const params = JSON.parse(paramsStr)[0]
const key = `_evmDecode:${params.abiType}:${params.value}`
if (store.has(key)) return exports.__newString(store.get(key))
throw new Error(`Encode value not found for key: ${key}`)
},
_decode: (paramsPtr) => {
const paramsStr = exports.__getString(paramsPtr)
const params = JSON.parse(paramsStr)
Expand Down Expand Up @@ -117,6 +124,13 @@ export default {
const key = `_evmCallQuery:${to}:${chainId}:${data}`
store.set(key, result)
},
setEvmEncode: (abiTypePtr, dataPtr, encodedPtr) => {
const abiType = exports.__getString(abiTypePtr)
const data = exports.__getString(dataPtr)
const key = `_evmDecode:${abiType}:${data}`
const encoded = exports.__getString(encodedPtr)
store.set(key, encoded)
},
setEvmDecode: (abiTypePtr, hexPtr, decodedPtr) => {
const abiType = exports.__getString(abiTypePtr)
const hex = exports.__getString(hexPtr)
Expand Down
55 changes: 41 additions & 14 deletions packages/lib-ts/src/environment.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { JSON } from 'json-as/assembly'

import { Context, SerializableContext } from './context'
import { Consensus, ListType } from './helpers'
import { evm } from './evm'
import { Consensus, ListType, MIMIC_HELPER_ADDRESS } from './helpers'
import { EvmCall, SvmCall, Swap, Transfer } from './intents'
import {
EvmCallQuery,
Expand All @@ -19,7 +20,7 @@ import {
TokenPriceQueryResponse,
} from './queries'
import { BlockchainToken, Token, TokenAmount, USD } from './tokens'
import { Address, ChainId, Result } from './types'
import { Address, BigInt, ChainId, EvmDecodeParam, EvmEncodeParam, Result } from './types'

export namespace environment {
@external('environment', '_evmCall')
Expand Down Expand Up @@ -93,15 +94,23 @@ export namespace environment {
* @param consensusFn - Optional. A function for aggregating the price values (default is median).
* @returns A `Result` containing either the consensus USD price or an error string.
*/
export function tokenPriceQuery(token: Token, timestamp: Date | null = null, consensusFn: (values: USD[]) => USD = Consensus.medianUSD): Result<USD, string> {
export function tokenPriceQuery(
token: Token,
timestamp: Date | null = null,
consensusFn: (values: USD[]) => USD = Consensus.medianUSD
): Result<USD, string> {
if (token.isUSD()) return Result.ok<USD, string>(USD.fromI32(1))
if (!(token instanceof BlockchainToken)) return Result.err<USD, string>('Price query not supported for token ' + token.toString())

const responseStr = _tokenPriceQuery(JSON.stringify(TokenPriceQuery.fromToken(changetype<BlockchainToken>(token), timestamp)))
if (!(token instanceof BlockchainToken)) {
return Result.err<USD, string>('Price query not supported for token ' + token.toString())
}

const responseStr = _tokenPriceQuery(
JSON.stringify(TokenPriceQuery.fromToken(changetype<BlockchainToken>(token), timestamp))
)
const pricesResult = TokenPriceQueryResponse.fromJson<TokenPriceQueryResponse>(responseStr).toResult()

if (pricesResult.isError) return Result.err<USD, string>(pricesResult.error)

const prices = pricesResult.unwrap()
if (prices.length === 0) return Result.err<USD, string>('Prices not found for token ' + token.toString())

Expand All @@ -126,9 +135,11 @@ export namespace environment {
listType: ListType = ListType.DenyList,
consensusFn: (amounts: TokenBalanceQuery[][]) => TokenAmount[] = Consensus.uniqueTokenAmounts
): Result<TokenAmount[], string> {
const responseStr = _relevantTokensQuery(JSON.stringify(RelevantTokensQuery.init(address, chainIds, usdMinAmount, tokensList, listType)))
const responseStr = _relevantTokensQuery(
JSON.stringify(RelevantTokensQuery.init(address, chainIds, usdMinAmount, tokensList, listType))
)
const responseResult = RelevantTokensQueryResponse.fromJson<RelevantTokensQueryResponse>(responseStr).toResult()

if (responseResult.isError) return Result.err<TokenAmount[], string>(responseResult.error)

return Result.ok<TokenAmount[], string>(consensusFn(responseResult.unwrap()))
Expand All @@ -146,7 +157,7 @@ export namespace environment {
to: Address,
chainId: ChainId,
data: string,
timestamp: Date | null = null,
timestamp: Date | null = null
): Result<string, string> {
const responseStr = _evmCallQuery(JSON.stringify(EvmCallQuery.from(to, chainId, timestamp, data)))
return EvmCallQueryResponse.fromJson<EvmCallQueryResponse>(responseStr).toResult()
Expand All @@ -164,12 +175,12 @@ export namespace environment {
chainId: ChainId,
subgraphId: string,
query: string,
timestamp: Date | null = null,
timestamp: Date | null = null
): Result<SubgraphQueryResult, string> {
const responseStr = _subgraphQuery(JSON.stringify(SubgraphQuery.from(chainId, subgraphId, query, timestamp)))
return SubgraphQueryResponse.fromJson<SubgraphQueryResponse>(responseStr).toResult()
}

/**
* Returns on-chain account info for Solana accounts.
* @param publicKeys - Accounts to read from chain
Expand All @@ -178,7 +189,7 @@ export namespace environment {
*/
export function svmAccountsInfoQuery(
publicKeys: Address[],
timestamp: Date | null = null,
timestamp: Date | null = null
): Result<SvmAccountsInfoQueryResult, string> {
const responseStr = _svmAccountsInfoQuery(JSON.stringify(SvmAccountsInfoQuery.from(publicKeys, timestamp)))
return SvmAccountsInfoQueryResponse.fromJson<SvmAccountsInfoQueryResponse>(responseStr).toResult()
Expand All @@ -192,4 +203,20 @@ export namespace environment {
const context = JSON.parse<SerializableContext>(_getContext())
return Context.fromSerializable(context)
}

/**
* Returns the native token balance of target account.
* @param chainId - Chain id to check balance
* @param target - Address to get balance from
* @returns The native token balance in wei
*/
export function getNativeTokenBalance(chainId: ChainId, target: Address): Result<BigInt, string> {
if (chainId === ChainId.SOLANA_MAINNET) return Result.err<BigInt, string>('Solana not supported')
const data = '0xeffd663c' + evm.encode([EvmEncodeParam.fromValue('address', target)])
const response = evmCallQuery(Address.fromHexString(MIMIC_HELPER_ADDRESS), chainId, data)
if (response.isError) return Result.err<BigInt, string>(response.error)
const decodedResponse = evm.decode(new EvmDecodeParam('uint256', response.unwrap()))
const decoded = BigInt.fromString(decodedResponse)
return Result.ok<BigInt, string>(decoded)
}
}
5 changes: 3 additions & 2 deletions packages/lib-ts/src/evm.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { EvmDecodeParam, EvmEncodeParam } from './types'
import { JSON } from 'json-as/assembly'

import { EvmDecodeParam, EvmEncodeParam } from './types'

export namespace evm {
@external('evm', '_encode')
declare function _encode(params: string): string
Expand All @@ -10,7 +11,7 @@ export namespace evm {

@external('evm', '_keccak')
declare function _keccak(params: string): string

/**
* Encodes parameters for EVM smart contract function calls using ABI encoding.
* @param callParameters - Array of parameters to encode for the contract call
Expand Down
2 changes: 2 additions & 0 deletions packages/lib-ts/src/helpers/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,5 @@ export enum ListType {
AllowList = 0,
DenyList = 1,
}

export const MIMIC_HELPER_ADDRESS = '0x4766EF65d66E8D2d92F3089Ee42e5705e8817FF0'
4 changes: 2 additions & 2 deletions packages/lib-ts/src/log.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
// Copyright (c) 2018 Graph Protocol, Inc. and contributors.
// Modified by Mimic Protocol, 2025.

import { Stringable } from "./helpers"
import { Stringable } from './helpers'

export namespace log {
@external('log', '_log')
Expand Down Expand Up @@ -69,7 +69,7 @@ export namespace log {
function format<T extends Stringable>(fmt: string, args: Array<T>): string {
let out = ''
let argIndex = 0
const argsStr = args.map<string>(a => a.toString())
const argsStr = args.map<string>((a) => a.toString())
for (let i: i32 = 0, len: i32 = fmt.length; i < len; i++) {
if (i < len - 1 && fmt.charCodeAt(i) == 0x7b /* '{' */ && fmt.charCodeAt(i + 1) == 0x7d /* '}' */) {
if (argIndex >= argsStr.length) throw new Error('Too few arguments for format string: ' + fmt)
Expand Down
46 changes: 46 additions & 0 deletions packages/lib-ts/tests/environment.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { environment } from '../src/environment'
import { MIMIC_HELPER_ADDRESS } from '../src/helpers'
import { Address, ChainId } from '../src/types'

import { randomSvmAddress, setContractCall, setEvmDecode, setEvmEncode } from './helpers'

describe('environment', () => {
describe('getNativeTokenBalance', () => {
describe('when chainId is evm', () => {
const address = '0x9b6c444f5bbfe10680fb015e1a23bfc6193ae163'
const chainId = ChainId.OPTIMISM

beforeEach(() => {
setEvmEncode('address', address, '0x0')
})

describe('when the evm call is an error', () => {
it('throws an error', () => {
const result = environment.getNativeTokenBalance(chainId, Address.fromHexString(address))
expect(result.isError).toBe(true)
})
})

describe('when evm call is successful', () => {
beforeEach(() => {
setContractCall(MIMIC_HELPER_ADDRESS, chainId, '0xeffd663c0x0', '0x100')
setEvmDecode('uint256', '0x100', '100')
})

it('returns the decoded value', () => {
const result = environment.getNativeTokenBalance(chainId, Address.fromHexString(address))
expect(result.isError).toBe(false)
expect(result.unwrap().toString()).toBe('100')
})
})
})

describe('when chainId is solana', () => {
it('should throw an error', () => {
const result = environment.getNativeTokenBalance(ChainId.SOLANA_MAINNET, randomSvmAddress())
expect(result.isError).toBe(true)
expect(result.error).toBe('Solana not supported')
})
})
})
})
2 changes: 2 additions & 0 deletions packages/lib-ts/tests/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,8 @@ declare function _setFindProgramAddress(params: string, result: string): void

export declare function setEvmDecode(abiType: string, hex: string, decoded: string): void

export declare function setEvmEncode(abiType: string, data: string, encoded: string): void

export declare function _setContext(
timestamp: u64,
consensusThreshold: u8,
Expand Down