@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 +90 -0
- package/index.ts +7 -0
- package/package.json +26 -0
- package/src/chains/Ethereum.ts +12 -0
- package/src/chains/Optimism.ts +12 -0
- package/src/chains/Polygon.ts +12 -0
- package/src/chains/index.ts +3 -0
- package/src/context/Context.ts +29 -0
- package/src/context/index.ts +1 -0
- package/src/environment.ts +126 -0
- package/src/evm.ts +40 -0
- package/src/helpers/constants.ts +9 -0
- package/src/helpers/index.ts +3 -0
- package/src/helpers/serialize.ts +140 -0
- package/src/helpers/strings.ts +103 -0
- package/src/intents/Call.ts +238 -0
- package/src/intents/Intent.ts +79 -0
- package/src/intents/Swap.ts +419 -0
- package/src/intents/Transfer.ts +349 -0
- package/src/intents/index.ts +4 -0
- package/src/queries/Call.ts +16 -0
- package/src/queries/index.ts +1 -0
- package/src/tokens/Token.ts +191 -0
- package/src/tokens/TokenAmount.ts +248 -0
- package/src/tokens/USD.ts +201 -0
- package/src/tokens/index.ts +3 -0
- package/src/types/Address.ts +60 -0
- package/src/types/BigInt.ts +796 -0
- package/src/types/ByteArray.ts +316 -0
- package/src/types/Bytes.ts +137 -0
- package/src/types/ChainId.ts +9 -0
- package/src/types/EvmDecodeParam.ts +30 -0
- package/src/types/EvmEncodeParam.ts +54 -0
- package/src/types/index.ts +7 -0
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) ·
|
|
87
|
+
> Docs [docs.mimic.fi](https://docs.mimic.fi) ·
|
|
88
|
+
> GitHub [@mimic-fi](https://github.com/mimic-fi) ·
|
|
89
|
+
> Twitter [@mimicfi](https://twitter.com/mimicfi) ·
|
|
90
|
+
> Discord [mimic](https://discord.mimic.fi)
|
package/index.ts
ADDED
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,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,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
|
+
}
|