@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
@@ -0,0 +1,47 @@
1
+ import { BlockchainToken, USD } from '../tokens'
2
+ import { BigInt, Result } from '../types'
3
+
4
+ import { QueryResponseBase } from './QueryResponse'
5
+
6
+ @json
7
+ class TokenPriceQueryBase {
8
+ constructor(
9
+ public readonly address: string,
10
+ public readonly chainId: i32
11
+ ) {}
12
+ }
13
+
14
+ @json
15
+ export class TokenPriceQuery extends TokenPriceQueryBase {
16
+ public readonly timestamp: i64
17
+
18
+ constructor(address: string, chainId: i32, timestamp: i64) {
19
+ super(address, chainId)
20
+ this.timestamp = timestamp
21
+ }
22
+
23
+ static fromToken(token: BlockchainToken, timestamp: Date | null): TokenPriceQueryBase {
24
+ const address = token.address.toString()
25
+ const chainId = token.chainId
26
+
27
+ return timestamp
28
+ ? new TokenPriceQuery(address, chainId, changetype<Date>(timestamp).getTime())
29
+ : new TokenPriceQueryBase(address, chainId)
30
+ }
31
+ }
32
+
33
+ @json
34
+ export class TokenPriceQueryResponse extends QueryResponseBase {
35
+ public data: string[]
36
+
37
+ constructor(success: string, data: string[], error: string) {
38
+ super(success, error)
39
+ this.data = data
40
+ }
41
+
42
+ toResult(): Result<USD[], string> {
43
+ return this.buildResult<string[], USD[]>(this.data, 'Unknown error getting price', (data) =>
44
+ data.map<USD>((price) => USD.fromBigInt(BigInt.fromString(price)))
45
+ )
46
+ }
47
+ }
@@ -1 +1,6 @@
1
- export * from './Call'
1
+ export * from './EvmCallQuery'
2
+ export * from './QueryResponse'
3
+ export * from './RelevantTokensQuery'
4
+ export * from './SubgraphQuery'
5
+ export * from './SvmAccountsInfoQuery'
6
+ export * from './TokenPriceQuery'
@@ -0,0 +1 @@
1
+ export * from './storage'
@@ -0,0 +1,40 @@
1
+ import { environment } from '../environment'
2
+ import { evm } from '../evm'
3
+ import { MIMIC_HELPER_ADDRESS } from '../helpers'
4
+ import { EvmCall, EvmCallBuilder } from '../intents'
5
+ import { Address, Bytes, ChainId, EvmDecodeParam, EvmEncodeParam, Result } from '../types'
6
+
7
+ const ADDRESS = Address.fromHexString(MIMIC_HELPER_ADDRESS)
8
+ const DEFAULT_CHAIN_ID = ChainId.OPTIMISM
9
+
10
+ export namespace storage {
11
+ export function createSetDataCall(
12
+ smartAccount: Address,
13
+ key: string,
14
+ data: Bytes,
15
+ chainId: ChainId = DEFAULT_CHAIN_ID
16
+ ): EvmCall {
17
+ const encodedData = Bytes.fromHexString(
18
+ '0x1c1bbd37' +
19
+ evm.encode([EvmEncodeParam.fromValue('string', Bytes.fromUTF8(key)), EvmEncodeParam.fromValue('bytes', data)])
20
+ )
21
+ return EvmCallBuilder.forChain(chainId).addUser(smartAccount).addCall(ADDRESS, encodedData).build()
22
+ }
23
+
24
+ export function getData(
25
+ smartAccount: Address,
26
+ key: string,
27
+ chainId: ChainId = DEFAULT_CHAIN_ID
28
+ ): Result<Bytes, string> {
29
+ const encodedData =
30
+ '0x53f71fd3' +
31
+ evm.encode([
32
+ EvmEncodeParam.fromValue('address', smartAccount),
33
+ EvmEncodeParam.fromValue('string', Bytes.fromUTF8(key)),
34
+ ])
35
+ const response = environment.evmCallQuery(ADDRESS, chainId, encodedData)
36
+ if (response.isError) return Result.err<Bytes, string>(response.error)
37
+ const decoded = Bytes.fromHexString(evm.decode(new EvmDecodeParam('bytes', response.unwrap())))
38
+ return Result.ok<Bytes, string>(decoded)
39
+ }
40
+ }
package/src/svm.ts ADDED
@@ -0,0 +1,27 @@
1
+ import { JSON } from 'json-as'
2
+
3
+ import {
4
+ Address,
5
+ SerializableSvmFindProgramAddressResult,
6
+ SvmFindProgramAddressParams,
7
+ SvmFindProgramAddressResult,
8
+ SvmPdaSeed,
9
+ } from './types'
10
+
11
+ export namespace svm {
12
+ @external('svm', '_findProgramAddress')
13
+ declare function _findProgramAddress(params: string): string
14
+
15
+ /**
16
+ * Calculates PDA address
17
+ * @param seeds - SvmPdaSeed array of seeds
18
+ * @param programId - Base programId to be derived of
19
+ * @returns The PDA address and bump
20
+ */
21
+ export function findProgramAddress(seeds: SvmPdaSeed[], programId: Address): SvmFindProgramAddressResult {
22
+ const params = new SvmFindProgramAddressParams(seeds, programId.toBase58String())
23
+ const result = _findProgramAddress(JSON.stringify(params))
24
+ const parsed = JSON.parse<SerializableSvmFindProgramAddressResult>(result)
25
+ return SvmFindProgramAddressResult.fromSerializable(parsed)
26
+ }
27
+ }
@@ -0,0 +1,108 @@
1
+ import { Address, ChainId, JSON } from '../types'
2
+
3
+ import { ERC20Token } from './ERC20Token'
4
+ import { SPLToken } from './SPLToken'
5
+ import { SerializableToken, Token } from './Token'
6
+
7
+ /**
8
+ * Represents a token on a blockchain network
9
+ */
10
+ export abstract class BlockchainToken extends Token {
11
+ public static readonly EMPTY_SYMBOL: string = ''
12
+ public static readonly EMPTY_DECIMALS: u8 = u8.MAX_VALUE
13
+
14
+ private _chainId: ChainId
15
+
16
+ /**
17
+ * Creates a Blockchain Token instance from an Address object.
18
+ * @param address - The contract address of the token
19
+ * @param chainId - The blockchain network identifier
20
+ * @param decimals - Number of decimal places (optional, will be queried if not provided)
21
+ * @param symbol - Token symbol (optional, will be queried if not provided)
22
+ * @returns A new Token instance
23
+ */
24
+ static fromAddress(
25
+ address: Address,
26
+ chainId: ChainId,
27
+ decimals: u8 = BlockchainToken.EMPTY_DECIMALS,
28
+ symbol: string = BlockchainToken.EMPTY_SYMBOL
29
+ ): BlockchainToken {
30
+ if (address.isEVM()) return ERC20Token.fromAddress(address, chainId, decimals, symbol)
31
+ if (chainId != ChainId.SOLANA_MAINNET) throw new Error(`SVM tokens are only supported for Solana mainnet.`)
32
+ return SPLToken.fromAddress(address, ChainId.SOLANA_MAINNET, decimals, symbol)
33
+ }
34
+
35
+ /**
36
+ * Creates a Token instance from a string address.
37
+ * @param address - The contract address as a hex string
38
+ * @param chainId - The blockchain network identifier
39
+ * @param symbol - Token symbol (optional, will be queried if not provided)
40
+ * @param decimals - Number of decimal places (optional, will be queried if not provided)
41
+ * @returns A new Blockchain Token instance
42
+ */
43
+ static fromString(
44
+ address: string,
45
+ chainId: ChainId,
46
+ decimals: u8 = BlockchainToken.EMPTY_DECIMALS,
47
+ symbol: string = BlockchainToken.EMPTY_SYMBOL
48
+ ): BlockchainToken {
49
+ return BlockchainToken.fromAddress(Address.fromString(address), chainId, decimals, symbol)
50
+ }
51
+
52
+ /**
53
+ * Creates a BlockchainToken from a serialized string.
54
+ * @param serialized - The serialized string to parse
55
+ * @returns A new BlockchainToken instance
56
+ */
57
+ static fromSerializable(serialized: string): BlockchainToken {
58
+ const data = JSON.parse<SerializableToken>(serialized)
59
+ return BlockchainToken.fromString(data.address, data.chainId)
60
+ }
61
+
62
+ /**
63
+ * Creates a new BlockchainToken instance
64
+ * @param address The token address in the corresponding blockchain (contract, mint, etc.)
65
+ * @param decimals - Number of decimal places
66
+ * @param symbol - Token symbol
67
+ * @param chainId - The blockchain network identifier
68
+ */
69
+ constructor(address: Address, decimals: u8, symbol: string, chainId: ChainId) {
70
+ super(address, decimals, symbol)
71
+ this._chainId = chainId
72
+ }
73
+
74
+ /**
75
+ * Gets the blockchain network identifier where this token is deployed.
76
+ * This value is assigned during construction and remains constant throughout the token's lifecycle.
77
+ * @returns The `ChainId` representing the token's network.
78
+ */
79
+ get chainId(): ChainId {
80
+ return this._chainId
81
+ }
82
+
83
+ /**
84
+ * Checks if this token is the USD denomination.
85
+ * @returns False always
86
+ */
87
+ isUSD(): boolean {
88
+ return false
89
+ }
90
+
91
+ /**
92
+ * Checks if this token belongs to the requested chain.
93
+ * @param chain - The chain ID asking for
94
+ * @returns True if chains are equal
95
+ */
96
+ hasChain(chain: ChainId): boolean {
97
+ return this._chainId === chain
98
+ }
99
+
100
+ /**
101
+ * Tells the string representation of this token.
102
+ * @returns The token address and chain ID along with symbol if present
103
+ */
104
+ toString(): string {
105
+ const description = 'Token ' + this.address.toString() + ' on chain ' + this.chainId.toString()
106
+ return description + (this._symbol ? ' (' + this._symbol + ')' : '')
107
+ }
108
+ }
@@ -0,0 +1,70 @@
1
+ import { Address, ChainId } from '../types'
2
+
3
+ import { Token } from './Token'
4
+
5
+ /**
6
+ * Represents a denomination token including data like symbol, decimals, and address.
7
+ */
8
+ export class DenominationToken extends Token {
9
+ /**
10
+ * Creates a Denomination Token instance representing USD.
11
+ * @returns A new Denomination Token instance for USD
12
+ */
13
+ static USD(): DenominationToken {
14
+ return new DenominationToken(Address.USD(), 18, 'USD')
15
+ }
16
+
17
+ /**
18
+ * Creates a new Denomination Token instance.
19
+ * @param address - The contract address of the token
20
+ * @param decimals - Number of decimal places
21
+ * @param symbol - Token symbol
22
+ */
23
+ constructor(address: Address, decimals: u8, symbol: string) {
24
+ super(address, decimals, symbol)
25
+ }
26
+
27
+ /**
28
+ * Checks if this token is equal to another token.
29
+ * Denomination Tokens are considered equal if they have the same address.
30
+ * @param other - The token to compare with
31
+ * @returns True if both tokens represent the denomination token
32
+ */
33
+ equals(other: Token): boolean {
34
+ return other instanceof DenominationToken && super.equals(other)
35
+ }
36
+
37
+ /**
38
+ * Checks if this token is the USD denomination.
39
+ * @returns True if the token is the USD denomination
40
+ */
41
+ isUSD(): boolean {
42
+ return this.equals(DenominationToken.USD())
43
+ }
44
+
45
+ /**
46
+ * Checks if this token is the native token.
47
+ * @returns True if the token is the native token
48
+ */
49
+ isNative(): boolean {
50
+ return false
51
+ }
52
+
53
+ /**
54
+ * Checks if this token belongs to the requested chain.
55
+ * @param chain - The chain ID asking for
56
+ * @returns True always, denomination tokens are chain-agnostic.
57
+ */
58
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
59
+ hasChain(chain: ChainId): boolean {
60
+ return true
61
+ }
62
+
63
+ /**
64
+ * Tells the string representation of this token.
65
+ * @returns The token symbol
66
+ */
67
+ toString(): string {
68
+ return this.symbol
69
+ }
70
+ }
@@ -0,0 +1,192 @@
1
+ import { environment } from '../environment'
2
+ import { evm } from '../evm'
3
+ import { EVM_NATIVE_ADDRESS } from '../helpers/constants'
4
+ import { log } from '../log'
5
+ import { Address, ChainId, EvmDecodeParam } from '../types'
6
+
7
+ import { BlockchainToken } from './BlockchainToken'
8
+ import { Token } from './Token'
9
+
10
+ /**
11
+ * Represents an ERC-20 token on a blockchain network including data like symbol, decimals, and address.
12
+ */
13
+ export class ERC20Token extends BlockchainToken {
14
+ public static readonly EMPTY_DECIMALS: u8 = u8.MAX_VALUE
15
+ public static readonly EMPTY_SYMBOL: string = ''
16
+
17
+ private _timestamp: Date | null = null
18
+
19
+ /**
20
+ * Creates a Token instance representing the native token of the specified chain.
21
+ * @param chainId - The blockchain network identifier
22
+ * @returns A new Token instance for the native token
23
+ */
24
+ static native(chainId: ChainId): ERC20Token {
25
+ switch (chainId) {
26
+ case ChainId.ETHEREUM:
27
+ case ChainId.BASE:
28
+ case ChainId.BASE_SEPOLIA:
29
+ case ChainId.ARBITRUM:
30
+ case ChainId.OPTIMISM:
31
+ return ERC20Token.fromString(EVM_NATIVE_ADDRESS, chainId, 18, 'ETH')
32
+ case ChainId.BNB:
33
+ return ERC20Token.fromString(EVM_NATIVE_ADDRESS, chainId, 18, 'BNB')
34
+ case ChainId.GNOSIS:
35
+ return ERC20Token.fromString(EVM_NATIVE_ADDRESS, chainId, 18, 'xDAI')
36
+ case ChainId.POLYGON:
37
+ return ERC20Token.fromString('0x0000000000000000000000000000000000001010', chainId, 18, 'POL')
38
+ case ChainId.AVALANCHE:
39
+ return ERC20Token.fromString(EVM_NATIVE_ADDRESS, chainId, 18, 'AVAX')
40
+ case ChainId.SONIC:
41
+ return ERC20Token.fromString(EVM_NATIVE_ADDRESS, chainId, 18, 'SONIC')
42
+ default:
43
+ throw new Error(`Unsupported chainId: ${chainId}`)
44
+ }
45
+ }
46
+
47
+ /**
48
+ * Creates a Token instance from an Address object.
49
+ * @param address - The contract address of the token
50
+ * @param chainId - The blockchain network identifier
51
+ * @param decimals - Number of decimal places (optional, will be queried if not provided)
52
+ * @param symbol - Token symbol (optional, will be queried if not provided)
53
+ * @param timestamp - Timestamp for historical queries (optional)
54
+ * @returns A new Token instance
55
+ */
56
+ static fromAddress(
57
+ address: Address,
58
+ chainId: ChainId,
59
+ decimals: u8 = ERC20Token.EMPTY_DECIMALS,
60
+ symbol: string = ERC20Token.EMPTY_SYMBOL,
61
+ timestamp: Date | null = null
62
+ ): ERC20Token {
63
+ return new ERC20Token(address, chainId, decimals, symbol, timestamp)
64
+ }
65
+
66
+ /**
67
+ * Creates a Token instance from a string address.
68
+ * @param address - The contract address as a hex string
69
+ * @param chainId - The blockchain network identifier
70
+ * @param decimals - Number of decimal places (optional, will be queried if not provided)
71
+ * @param symbol - Token symbol (optional, will be queried if not provided)
72
+ * @param timestamp - Timestamp for historical queries (optional)
73
+ * @returns A new Token instance
74
+ */
75
+ static fromString(
76
+ address: string,
77
+ chainId: ChainId,
78
+ decimals: u8 = ERC20Token.EMPTY_DECIMALS,
79
+ symbol: string = ERC20Token.EMPTY_SYMBOL,
80
+ timestamp: Date | null = null
81
+ ): ERC20Token {
82
+ return ERC20Token.fromAddress(Address.fromString(address), chainId, decimals, symbol, timestamp)
83
+ }
84
+
85
+ /**
86
+ * Creates a new Token instance.
87
+ * @param address - The contract address of the token
88
+ * @param chainId - The blockchain network identifier
89
+ * @param decimals - Number of decimal places (optional, will be queried if not provided)
90
+ * @param symbol - Token symbol (optional, will be queried if not provided)
91
+ * @param timestamp - Timestamp for historical queries (optional)
92
+ */
93
+ constructor(
94
+ address: Address,
95
+ chainId: ChainId,
96
+ decimals: u8 = ERC20Token.EMPTY_DECIMALS,
97
+ symbol: string = ERC20Token.EMPTY_SYMBOL,
98
+ timestamp: Date | null = null
99
+ ) {
100
+ if (!address.isEVM()) throw new Error(`Address ${address} must be an EVM address.`)
101
+ super(address, decimals, symbol, chainId)
102
+ this._timestamp = timestamp
103
+
104
+ // Ensure symbol and decimals are set for native or denomination tokens.
105
+ // Since queries return only the address and chainId, missing metadata must be filled
106
+ // to prevent the symbol and decimals getters from failing for native tokens.
107
+ const hasMissingSymbol = this._symbol === ERC20Token.EMPTY_SYMBOL
108
+ const hasMissingDecimals = this._decimals === ERC20Token.EMPTY_DECIMALS
109
+ const hasMissingSymbolOrDecimals = hasMissingSymbol || hasMissingDecimals
110
+ if (address.isNative() && hasMissingSymbolOrDecimals) {
111
+ const nativeToken = ERC20Token.native(this.chainId)
112
+ if (hasMissingSymbol) this._symbol = nativeToken.symbol
113
+ if (hasMissingDecimals) this._decimals = nativeToken.decimals
114
+ }
115
+ }
116
+
117
+ /**
118
+ * Gets the token’s symbol (e.g., "ETH", "USDC").
119
+ * If the symbol was not provided during construction, it will be lazily fetched
120
+ * via a `evmCallQuery` to the token’s `symbol()` function (ERC-20 standard, selector `0x95d89b41`).
121
+ * The fetched symbol is cached in the instance for future accesses.
122
+ * @returns A string containing the token symbol.
123
+ */
124
+ get symbol(): string {
125
+ if (this._symbol === ERC20Token.EMPTY_SYMBOL) this._getSymbol()
126
+ return this._symbol
127
+ }
128
+
129
+ /**
130
+ * Gets the token’s decimals (number of decimal places used).
131
+ * If decimals were not provided during construction, they will be lazily fetched
132
+ * via a `evmCallQuery` to the token’s `decimals()` function (ERC-20 standard, selector `0x313ce567`).
133
+ * The fetched value is parsed to `u8` and cached for future accesses.
134
+ * @returns A `u8` representing the number of decimals of the token.
135
+ */
136
+ get decimals(): u8 {
137
+ if (this._decimals == ERC20Token.EMPTY_DECIMALS) this._getDecimals()
138
+ return this._decimals
139
+ }
140
+
141
+ /**
142
+ * Checks if this token is equal to another token.
143
+ * ERC20 Tokens are considered equal if they have the same address on the same chain.
144
+ * @param other - The token to compare with
145
+ * @returns True if both tokens represent the same asset
146
+ */
147
+ equals(other: Token): boolean {
148
+ return other instanceof ERC20Token && super.equals(other) && this.hasChain((other as ERC20Token).chainId)
149
+ }
150
+
151
+ /**
152
+ * Checks if this token is the native token.
153
+ * @returns True if the token is the native token
154
+ */
155
+ isNative(): boolean {
156
+ return this.equals(ERC20Token.native(this.chainId))
157
+ }
158
+
159
+ /**
160
+ * Internal method to fetch and cache the token symbol.
161
+ * Performs an EVM call query to the token's symbol() function.
162
+ * @private
163
+ */
164
+ private _getSymbol(): void {
165
+ const response = environment.evmCallQuery(this.address, this.chainId, '0x95d89b41', this._timestamp)
166
+ if (response.isError) {
167
+ log.warning(
168
+ `Failed to get symbol for token ${this.address.toString()} on chain ${this.chainId.toString()}: ${response.error}`
169
+ )
170
+ this._symbol = ERC20Token.EMPTY_SYMBOL
171
+ return
172
+ }
173
+ this._symbol = evm.decode(new EvmDecodeParam('string', response.unwrap()))
174
+ }
175
+
176
+ /**
177
+ * Internal method to fetch and cache the token decimals.
178
+ * Performs an EVM call query to the token's decimals() function.
179
+ * @private
180
+ */
181
+ private _getDecimals(): void {
182
+ const result = environment.evmCallQuery(this.address, this.chainId, '0x313ce567', this._timestamp)
183
+ if (result.isError) {
184
+ log.warning(
185
+ `Failed to get decimals for token ${this.address.toString()} on chain ${this.chainId.toString()}: ${result.error}`
186
+ )
187
+ this._decimals = ERC20Token.EMPTY_DECIMALS
188
+ return
189
+ }
190
+ this._decimals = u8.parse(evm.decode(new EvmDecodeParam('uint8', result.unwrap())))
191
+ }
192
+ }
@@ -0,0 +1,162 @@
1
+ import { environment } from '../environment'
2
+ import { SVM_NATIVE_ADDRESS } from '../helpers'
3
+ import { log } from '../log'
4
+ import { svm } from '../svm'
5
+ import { Address, ChainId, SvmMint, SvmPdaSeed, SvmTokenMetadataData } from '../types'
6
+
7
+ import { BlockchainToken } from './BlockchainToken'
8
+ import { Token } from './Token'
9
+
10
+ /**
11
+ * Represents a SPL token on the Solana network including data like symbol, decimals, and address.
12
+ */
13
+ export class SPLToken extends BlockchainToken {
14
+ /**
15
+ * Creates a Token instance representing the native SOL token.
16
+ * @returns A new Token instance for the native token
17
+ */
18
+ static native(): SPLToken {
19
+ return SPLToken.fromString(SVM_NATIVE_ADDRESS, ChainId.SOLANA_MAINNET, 9, 'SOL')
20
+ }
21
+
22
+ /**
23
+ * Creates a Token instance from an Address object.
24
+ * @param address - The token mint address
25
+ * @param chainId - The blockchain network identifier (optional)
26
+ * @param decimals - Number of decimal places (optional)
27
+ * @param symbol - Token symbol (optional)
28
+ * @returns A new Token instance
29
+ */
30
+ static fromAddress(
31
+ address: Address,
32
+ chainId: ChainId = ChainId.SOLANA_MAINNET,
33
+ decimals: u8 = SPLToken.EMPTY_DECIMALS,
34
+ symbol: string = SPLToken.EMPTY_SYMBOL
35
+ ): SPLToken {
36
+ if (chainId != ChainId.SOLANA_MAINNET) throw new Error(`SPL tokens are only supported for Solana mainnet.`)
37
+ return new SPLToken(address, decimals, symbol)
38
+ }
39
+
40
+ /**
41
+ * Creates a Token instance from a string address.
42
+ * @param address - The token mint address as a base58 string
43
+ * @param chainId - The blockchain network identifier (optional)
44
+ * @param decimals - Number of decimal places (optional)
45
+ * @param symbol - Token symbol (optional)
46
+ * @returns A new Token instance
47
+ */
48
+ static fromString(
49
+ address: string,
50
+ chainId: ChainId = ChainId.SOLANA_MAINNET,
51
+ decimals: u8 = SPLToken.EMPTY_DECIMALS,
52
+ symbol: string = SPLToken.EMPTY_SYMBOL
53
+ ): SPLToken {
54
+ return SPLToken.fromAddress(Address.fromString(address), chainId, decimals, symbol)
55
+ }
56
+
57
+ /**
58
+ * Creates a new Token instance.
59
+ * @param address - The token mint address
60
+ * @param decimals - Number of decimal places
61
+ * @param symbol - Token symbol
62
+ */
63
+ constructor(address: Address, decimals: u8 = SPLToken.EMPTY_DECIMALS, symbol: string = SPLToken.EMPTY_SYMBOL) {
64
+ if (!address.isSVM()) throw new Error(`Address ${address} must be an SVM address.`)
65
+ super(address, decimals, symbol, ChainId.SOLANA_MAINNET)
66
+ }
67
+
68
+ /**
69
+ * Gets the token’s decimals (number of decimal places used).
70
+ * If decimals were not provided during construction, they will be lazily fetched
71
+ * The fetched value is parsed to `u8` and cached for future accesses.
72
+ * @returns A `u8` representing the number of decimals of the token.
73
+ */
74
+ get decimals(): u8 {
75
+ if (this._decimals == SPLToken.EMPTY_DECIMALS) this._getDecimals()
76
+ return this._decimals
77
+ }
78
+
79
+ /**
80
+ * Gets the token’s symbol (e.g., "SOL", "USDC").
81
+ * If the symbol was not provided during construction, it will be lazily fetched
82
+ * If the token doesn't have a symbol (TokenMetadata standard), an abbreviated address is returned
83
+ * The symbol is cached in the instance for future accesses.
84
+ * @returns A string containing the token symbol.
85
+ */
86
+ get symbol(): string {
87
+ if (this._symbol == SPLToken.EMPTY_SYMBOL) this._getSymbol()
88
+ return this._symbol
89
+ }
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.unwrap().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.unwrap().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.unwrap().accountsInfo[0].data).symbol
129
+ }
130
+
131
+ /**
132
+ * Derives Metaplex Metadata address for this given token
133
+ */
134
+ private getMetadataAddress(): Address {
135
+ return svm.findProgramAddress(
136
+ [
137
+ SvmPdaSeed.fromString('metadata'),
138
+ SvmPdaSeed.from(Address.fromString(SvmTokenMetadataData.METADATA_PROGRAM_ID)),
139
+ SvmPdaSeed.from(this._address),
140
+ ],
141
+ Address.fromBase58String(SvmTokenMetadataData.METADATA_PROGRAM_ID)
142
+ ).address
143
+ }
144
+
145
+ /**
146
+ * Checks if this token is equal to another token.
147
+ * SPL Tokens are considered equal if they have the same address.
148
+ * @param other - The token to compare with
149
+ * @returns True if both tokens represent the same asset
150
+ */
151
+ equals(other: Token): boolean {
152
+ return other instanceof SPLToken && super.equals(other)
153
+ }
154
+
155
+ /**
156
+ * Checks if this token is the native token.
157
+ * @returns True if the token is the native token
158
+ */
159
+ isNative(): boolean {
160
+ return this.equals(SPLToken.native())
161
+ }
162
+ }