@mimicprotocol/lib-ts 0.0.1-rc.9

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/README.md ADDED
@@ -0,0 +1,90 @@
1
+ <h1 align="center">
2
+ <a href="https://mimic.fi"><img src="https://www.mimic.fi/logo.png" alt="Mimic Protocol" width="200"></a>
3
+ </h1>
4
+
5
+ <h4 align="center">Blockchain automation protocol</h4>
6
+
7
+ <p align="center">
8
+ <a href="https://github.com/mimic-protocol/tooling/actions/workflows/ci.yml">
9
+ <img src="https://github.com/mimic-protocol/tooling/actions/workflows/ci.yml/badge.svg" alt="Build">
10
+ </a>
11
+ <a href="https://discord.mimic.fi">
12
+ <img alt="Discord" src="https://img.shields.io/discord/989984112397922325">
13
+ </a>
14
+ </p>
15
+
16
+ <p align="center">
17
+ <a href="#content">Content</a> •
18
+ <a href="#setup">Setup</a> •
19
+ <a href="#usage">Usage</a> •
20
+ <a href="#security">Security</a> •
21
+ <a href="#license">License</a>
22
+ </p>
23
+
24
+ ---
25
+
26
+ ## Content
27
+
28
+ This package provides a lightweight standard library for writing Mimic Protocol tasks in AssemblyScript. It includes:
29
+
30
+ - Typed primitives to interact with oracles and contracts
31
+ - Safe and minimal bindings for blockchain-specific operations
32
+ - Utility helpers for developing deterministic, deployable task logic
33
+
34
+ ## Setup
35
+
36
+ To set up this project you'll need [git](https://git-scm.com) and [yarn](https://classic.yarnpkg.com) installed.
37
+
38
+ Install the library from the root of the monorepo:
39
+
40
+ ```bash
41
+ # Clone this repository
42
+ $ git clone https://github.com/mimic-protocol/tooling
43
+
44
+ # Go into the repository
45
+ $ cd tooling
46
+
47
+ # Install dependencies
48
+ $ yarn
49
+ ```
50
+
51
+ ## Usage
52
+
53
+ Here’s an example of how to use the library in a Mimic task:
54
+
55
+ ```ts
56
+ import { environment, Token } from '@mimicprotocol/lib-ts'
57
+
58
+ const USDC = Token.fromString('0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48', 1)
59
+
60
+ environment.getPrice(USDC, new Date(1744818017000))
61
+ ```
62
+
63
+ For full task development guide and examples please visit [docs.mimic.fi](https://docs.mimic.fi/)
64
+
65
+ ## Security
66
+
67
+ To read more about our auditing and related security processes please refer to the [security section](https://docs.mimic.fi/miscellaneous/security) of our docs site.
68
+
69
+ However, if you found any potential issue in any of our smart contracts or in any piece of code you consider critical
70
+ for the safety of the protocol, please contact us through <a href="mailto:security@mimic.fi">security@mimic.fi</a>.
71
+
72
+ ## License
73
+
74
+ This project is licensed under the GNU General Public License v3.0.
75
+ See the [LICENSE](../../LICENSE) file for details.
76
+
77
+ ### Third-Party Code
78
+
79
+ This project includes code from [The Graph Tooling](https://github.com/graphprotocol/graph-tooling), licensed under the MIT License.
80
+ See the [LICENSE-MIT](https://github.com/graphprotocol/graph-tooling/blob/27659e56adfa3ef395ceaf39053dc4a31e6d86b7/LICENSE-MIT) file for details.
81
+ Their original license and attribution are preserved.
82
+
83
+
84
+ ---
85
+
86
+ > Website [mimic.fi](https://mimic.fi) &nbsp;&middot;&nbsp;
87
+ > Docs [docs.mimic.fi](https://docs.mimic.fi) &nbsp;&middot;&nbsp;
88
+ > GitHub [@mimic-fi](https://github.com/mimic-fi) &nbsp;&middot;&nbsp;
89
+ > Twitter [@mimicfi](https://twitter.com/mimicfi) &nbsp;&middot;&nbsp;
90
+ > Discord [mimic](https://discord.mimic.fi)
package/index.ts ADDED
@@ -0,0 +1,7 @@
1
+ export * from './src/chains'
2
+ export * from './src/environment'
3
+ export * from './src/evm'
4
+ export * from './src/helpers'
5
+ export * from './src/intents'
6
+ export * from './src/tokens'
7
+ export * from './src/types'
package/package.json ADDED
@@ -0,0 +1,26 @@
1
+ {
2
+ "name": "@mimicprotocol/lib-ts",
3
+ "version": "0.0.1-rc.9",
4
+ "license": "GPL-3.0",
5
+ "private": false,
6
+ "type": "module",
7
+ "scripts": {
8
+ "build": "asc index.ts -b build/lib.wasm --disableWarning 235 --noEmit --transform json-as/transform",
9
+ "test": "asp",
10
+ "lint": "eslint . --ignore-pattern 'src/environment.ts' --ignore-pattern 'src/evm.ts'"
11
+ },
12
+ "files": [
13
+ "src",
14
+ "index.ts",
15
+ "asconfig.json"
16
+ ],
17
+ "devDependencies": {
18
+ "@as-pect/cli": "8.1.0",
19
+ "assemblyscript": "0.27.36"
20
+ },
21
+ "dependencies": {
22
+ "eslint-config-mimic": "^0.0.3",
23
+ "json-as": "1.0.7",
24
+ "visitor-as": "0.11.4"
25
+ }
26
+ }
@@ -0,0 +1,12 @@
1
+ import { Token } from '../tokens'
2
+ import { ChainId } from '../types'
3
+
4
+ export namespace Ethereum {
5
+ export const CHAIN_ID = ChainId.ETHEREUM
6
+ export const ETH = Token.native(CHAIN_ID)
7
+ export const USDC = Token.fromString('0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48', CHAIN_ID, 6, 'USDC')
8
+ export const USDT = Token.fromString('0xdac17f958d2ee523a2206206994597c13d831ec7', CHAIN_ID, 6, 'USDT')
9
+ export const DAI = Token.fromString('0x6b175474e89094c44da98b954eedeac495271d0f', CHAIN_ID, 18, 'DAI')
10
+ export const WBTC = Token.fromString('0x2260fac5e5542a773aa44fbcfedf7c193bc2c599', CHAIN_ID, 8, 'WBTC')
11
+ export const WETH = Token.fromString('0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2', CHAIN_ID, 18, 'WETH')
12
+ }
@@ -0,0 +1,12 @@
1
+ import { Token } from '../tokens'
2
+ import { ChainId } from '../types'
3
+
4
+ export namespace Optimism {
5
+ export const CHAIN_ID = ChainId.OPTIMISM
6
+ export const ETH = Token.native(CHAIN_ID)
7
+ export const USDC = Token.fromString('0x0b2c639c533813f4aa9d7837caf62653d097ff85', CHAIN_ID, 6, 'USDC')
8
+ export const USDT = Token.fromString('0x94b008aa00579c1307b0ef2c499ad98a8ce58e58', CHAIN_ID, 6, 'USDT')
9
+ export const DAI = Token.fromString('0xda10009cbd5d07dd0cecc66161fc93d7c9000da1', CHAIN_ID, 18, 'DAI')
10
+ export const WBTC = Token.fromString('0x68f180fcce6836688e9084f035309e29bf0a2095', CHAIN_ID, 8, 'WBTC')
11
+ export const WETH = Token.fromString('0x4200000000000000000000000000000000000006', CHAIN_ID, 18, 'WETH')
12
+ }
@@ -0,0 +1,12 @@
1
+ import { Token } from '../tokens'
2
+ import { ChainId } from '../types'
3
+
4
+ export namespace Polygon {
5
+ export const CHAIN_ID = ChainId.POLYGON
6
+ export const POL = Token.native(CHAIN_ID)
7
+ export const USDC = Token.fromString('0x3c499c542cef5e3811e1192ce70d8cc03d5c3359', CHAIN_ID, 6, 'USDC')
8
+ export const USDT = Token.fromString('0xc2132d05d31c914a87c6611c10748aeb04b58e8f', CHAIN_ID, 6, 'USDT')
9
+ export const DAI = Token.fromString('0x8f3cf7ad23cd3cadbd9735aff958023239c6a063', CHAIN_ID, 18, 'DAI')
10
+ export const WBTC = Token.fromString('0x1bfd67037b42cf73acf2047067bd4f2c47d9bfd6', CHAIN_ID, 8, 'WBTC')
11
+ export const WETH = Token.fromString('0x7ceb23fd6bc0add59e62ac25578270cff1b9f619', CHAIN_ID, 18, 'WETH')
12
+ }
@@ -0,0 +1,3 @@
1
+ export * from './Ethereum'
2
+ export * from './Optimism'
3
+ export * from './Polygon'
@@ -0,0 +1,29 @@
1
+ import { Address } from '../types'
2
+
3
+ @json
4
+ export class SerializableContext {
5
+ constructor(
6
+ public readonly timestamp: u64,
7
+ public user: string,
8
+ public settler: string,
9
+ public configId: string
10
+ ) {}
11
+ }
12
+
13
+ export class Context {
14
+ constructor(
15
+ public readonly timestamp: u64,
16
+ public user: Address,
17
+ public settler: Address,
18
+ public configId: string
19
+ ) {}
20
+
21
+ static fromSerializable(serializable: SerializableContext): Context {
22
+ return new Context(
23
+ serializable.timestamp,
24
+ Address.fromString(serializable.user),
25
+ Address.fromString(serializable.settler),
26
+ serializable.configId
27
+ )
28
+ }
29
+ }
@@ -0,0 +1 @@
1
+ export * from './Context'
@@ -0,0 +1,126 @@
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
+ import { JSON } from 'json-as/assembly'
7
+ import { Context, SerializableContext } from './context'
8
+
9
+ export namespace environment {
10
+ @external('environment', '_call')
11
+ declare function _call(params: string): void
12
+
13
+ @external('environment', '_swap')
14
+ declare function _swap(params: string): void
15
+
16
+ @external('environment', '_transfer')
17
+ declare function _transfer(params: string): void
18
+
19
+ @external('environment', '_getPrice')
20
+ declare function _getPrice(params: string): string
21
+
22
+ @external('environment', '_getRelevantTokens')
23
+ declare function _getRelevantTokens(params: string): string
24
+
25
+ @external('environment', '_contractCall')
26
+ declare function _contractCall(params: string): string
27
+
28
+ @external('environment', '_getContext')
29
+ declare function _getContext(): string
30
+
31
+ /**
32
+ * Generates a Call intent containing contract calls on the blockchain.
33
+ * @param call - The Call intent to generate
34
+ */
35
+ export function call(call: Call): void {
36
+ _call(JSON.stringify(call))
37
+ }
38
+
39
+ /**
40
+ * Generates a Swap intent for token exchange operations.
41
+ * @param swap - The Swap intent to generate
42
+ */
43
+ export function swap(swap: Swap): void {
44
+ _swap(JSON.stringify(swap))
45
+ }
46
+
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
+ }
54
+
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))
64
+ }
65
+
66
+ /**
67
+ * Tells the balances of an address for the specified tokens and chains.
68
+ * @param address - The address to query balances for
69
+ * @param chainIds - Array of chain ids to search
70
+ * @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)
72
+ * @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
74
+ */
75
+ export function getRelevantTokens(
76
+ address: Address,
77
+ chainIds: ChainId[],
78
+ 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('')])
87
+ )
88
+ const rows = response.split('\n')
89
+ const tokenAmounts: TokenAmount[] = []
90
+
91
+ for (let i = 0; i < rows.length; i++) {
92
+ if (rows[i].length === 0) continue
93
+ tokenAmounts.push(TokenAmount.parse(rows[i]))
94
+ }
95
+
96
+ return tokenAmounts
97
+ }
98
+
99
+ /**
100
+ * Generates a contract call of a read function on the blockchain and returns the result.
101
+ * @param to - The contract address to call
102
+ * @param chainId - The blockchain network identifier
103
+ * @param timestamp - The timestamp for the call context (optional)
104
+ * @param data - The encoded function call data
105
+ * @returns The raw response from the contract call
106
+ */
107
+ export function contractCall(
108
+ to: Address,
109
+ chainId: ChainId,
110
+ timestamp: Date | null,
111
+ data: string
112
+ ): string {
113
+ return _contractCall(
114
+ JSON.stringify(new CallQuery(to, chainId, timestamp, data))
115
+ )
116
+ }
117
+
118
+ /**
119
+ * Tells the current execution context containing environment information.
120
+ * @returns The Context object containing: user, settler, timestamp, and config ID
121
+ */
122
+ export function getContext(): Context {
123
+ const context = JSON.parse<SerializableContext>(_getContext())
124
+ return Context.fromSerializable(context)
125
+ }
126
+ }
package/src/evm.ts ADDED
@@ -0,0 +1,40 @@
1
+ import { EvmDecodeParam, EvmEncodeParam } from './types'
2
+ import { join, serialize, serializeArray } from './helpers'
3
+
4
+ export namespace evm {
5
+ @external('evm', '_encode')
6
+ declare function _encode(params: string): string
7
+
8
+ @external('evm', '_decode')
9
+ declare function _decode(params: string): string
10
+
11
+ @external('evm', '_keccak')
12
+ declare function _keccak(params: string): string
13
+
14
+ /**
15
+ * Encodes parameters for EVM smart contract function calls using ABI encoding.
16
+ * @param callParameters - Array of parameters to encode for the contract call
17
+ * @returns The ABI-encoded data as a hex string
18
+ */
19
+ export function encode(callParameters: EvmEncodeParam[]): string {
20
+ return _encode(join([serializeArray(callParameters)]))
21
+ }
22
+
23
+ /**
24
+ * Decodes EVM contract call response data according to specified types.
25
+ * @param encodedData - The encoded data configuration specifying how to decode the response
26
+ * @returns The decoded data as a formatted string
27
+ */
28
+ export function decode(encodedData: EvmDecodeParam): string {
29
+ return _decode(serialize(encodedData))
30
+ }
31
+
32
+ /**
33
+ * Computes the Keccak-256 hash of the input data.
34
+ * @param data - The input data to hash
35
+ * @returns The Keccak-256 hash as a hex string
36
+ */
37
+ export function keccak(data: string): string {
38
+ return _keccak(data)
39
+ }
40
+ }
@@ -0,0 +1,9 @@
1
+ export const NULL_ADDRESS = '0x0000000000000000000000000000000000000000'
2
+ export const NATIVE_ADDRESS = '0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee'
3
+
4
+ export const STANDARD_DECIMALS: u8 = 18
5
+
6
+ export enum ListType {
7
+ AllowList = 0,
8
+ DenyList = 1,
9
+ }
@@ -0,0 +1,3 @@
1
+ export * from './constants'
2
+ export * from './serialize'
3
+ export * from './strings'
@@ -0,0 +1,140 @@
1
+ const SEPARATOR = ','
2
+
3
+ export interface Stringable {
4
+ toString(): string
5
+ }
6
+
7
+ export interface Serializable {
8
+ serialize(): string
9
+ }
10
+
11
+ export function serialize<T extends Stringable>(elem: T): string {
12
+ // eslint-disable-next-line
13
+ // @ts-ignore
14
+ if (elem instanceof Serializable) return elem.serialize()
15
+ return elem.toString()
16
+ }
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
+ }
@@ -0,0 +1,103 @@
1
+ export function bytesToString(bytes: Uint8Array): string {
2
+ return String.UTF8.decodeUnsafe(bytes.dataStart, bytes.length)
3
+ }
4
+
5
+ export function bytesToHexString(bytes: Uint8Array): string {
6
+ let hex = '0x'
7
+ for (let i = 0; i < bytes.length; i++) hex += bytes[i].toString(16).padStart(2, '0')
8
+ return hex
9
+ }
10
+
11
+ export function areAllZeros(str: string): boolean {
12
+ for (let i = 0; i < str.length; i++) if (str.charCodeAt(i) !== 48) return false
13
+ return true
14
+ }
15
+
16
+ export function normalizeScientificNotation(input: string): string {
17
+ let len = input.length
18
+ if (len === 0) return input
19
+
20
+ let i = 0
21
+ let sign = ''
22
+ if (input.charAt(0) === '-' || input.charAt(0) === '+') {
23
+ sign = input.charAt(0)
24
+ i++
25
+ }
26
+
27
+ let intPart = ''
28
+ let fracPart = ''
29
+ let exponentStr = ''
30
+ let hasDot = false
31
+ let hasExponent = false
32
+ let exponentNegative = false
33
+
34
+ // Parse integer & fraction parts
35
+ for (; i < len; i++) {
36
+ const c = input.charAt(i)
37
+ if (c === '.') {
38
+ hasDot = true
39
+ continue
40
+ }
41
+
42
+ if (c === 'e' || c === 'E') {
43
+ hasExponent = true
44
+ i++
45
+ break
46
+ }
47
+
48
+ if (hasDot) {
49
+ fracPart += c
50
+ } else {
51
+ intPart += c
52
+ }
53
+ }
54
+
55
+ // Parse exponent if present
56
+ if (hasExponent) {
57
+ if (i < len && (input.charAt(i) === '+' || input.charAt(i) === '-')) {
58
+ exponentNegative = input.charAt(i) === '-'
59
+ i++
60
+ }
61
+
62
+ for (; i < len; i++) {
63
+ const char = input.charAt(i)
64
+ if (char < '0' || char > '9') throw new Error(`Invalid character in exponent part: ${char}`)
65
+ exponentStr += char
66
+ }
67
+ }
68
+
69
+ if (exponentStr.length === 0) return input
70
+
71
+ const exponent = I32.parseInt(exponentStr)
72
+ const fullDigits = intPart + fracPart
73
+ const shift = exponentNegative ? -exponent : exponent
74
+ const newDecimalPos = intPart.length + shift
75
+
76
+ if (newDecimalPos <= 0) {
77
+ const zeros = '0'.repeat(-newDecimalPos)
78
+ return sign + '0.' + zeros + fullDigits
79
+ } else if (newDecimalPos >= fullDigits.length) {
80
+ const zeros = '0'.repeat(newDecimalPos - fullDigits.length)
81
+ return sign + fullDigits + zeros
82
+ } else {
83
+ return sign + fullDigits.substring(0, newDecimalPos) + '.' + fullDigits.substring(newDecimalPos)
84
+ }
85
+ }
86
+
87
+ export function isHex(str: string, strict: boolean = false): boolean {
88
+ const hasPrefix = str.startsWith('0x')
89
+
90
+ if (strict && !hasPrefix) return false
91
+
92
+ const value = hasPrefix ? str.slice(2) : str
93
+
94
+ for (let i = 0; i < value.length; i++) {
95
+ const c = value.charCodeAt(i)
96
+ const isDigit = c >= '0'.charCodeAt(0) && c <= '9'.charCodeAt(0)
97
+ const isLetter =
98
+ (c >= 'a'.charCodeAt(0) && c <= 'f'.charCodeAt(0)) || (c >= 'A'.charCodeAt(0) && c <= 'F'.charCodeAt(0))
99
+ if (!(isDigit || isLetter)) return false
100
+ }
101
+
102
+ return true
103
+ }