@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.
- package/CHANGELOG.md +144 -0
- package/README.md +7 -7
- package/constants.d.ts +1 -0
- package/constants.js +1 -0
- package/index.ts +3 -0
- package/package.json +18 -4
- package/src/chains/Arbitrum.ts +14 -0
- package/src/chains/Avalanche.ts +15 -0
- package/src/chains/BNB.ts +15 -0
- package/src/chains/Base.ts +14 -0
- package/src/chains/BaseSepolia.ts +7 -0
- package/src/chains/Ethereum.ts +7 -7
- package/src/chains/Gnosis.ts +14 -0
- package/src/chains/Optimism.ts +7 -7
- package/src/chains/Polygon.ts +10 -7
- package/src/chains/Sonic.ts +13 -0
- package/src/chains/index.ts +7 -0
- package/src/context/Context.ts +102 -7
- package/src/environment.ts +150 -71
- package/src/evm.ts +5 -4
- package/src/helpers/BorshDeserializer.ts +133 -0
- package/src/helpers/consensus.ts +35 -0
- package/src/helpers/constants.ts +7 -1
- package/src/helpers/index.ts +5 -0
- package/src/helpers/math.ts +20 -0
- package/src/helpers/serialize.ts +5 -125
- package/src/helpers/strings.ts +82 -5
- package/src/intents/Call/EvmCall.ts +199 -0
- package/src/intents/Call/EvmDynamicCall.ts +272 -0
- package/src/intents/Call/SvmCall.ts +204 -0
- package/src/intents/Call/index.ts +3 -0
- package/src/intents/Intent.ts +347 -35
- package/src/intents/Operation.ts +114 -0
- package/src/intents/Swap.ts +127 -114
- package/src/intents/Transfer.ts +72 -123
- package/src/intents/index.ts +1 -0
- package/src/log.ts +83 -0
- package/src/queries/EvmCallQuery.ts +43 -0
- package/src/queries/QueryResponse.ts +26 -0
- package/src/queries/RelevantTokensQuery.ts +82 -0
- package/src/queries/SubgraphQuery.ts +50 -0
- package/src/queries/SvmAccountsInfoQuery.ts +65 -0
- package/src/queries/TokenPriceQuery.ts +47 -0
- package/src/queries/index.ts +6 -1
- package/src/storage/index.ts +1 -0
- package/src/storage/storage.ts +40 -0
- package/src/svm.ts +27 -0
- package/src/tokens/BlockchainToken.ts +108 -0
- package/src/tokens/DenominationToken.ts +70 -0
- package/src/tokens/ERC20Token.ts +192 -0
- package/src/tokens/SPLToken.ts +162 -0
- package/src/tokens/Token.ts +55 -155
- package/src/tokens/TokenAmount.ts +72 -30
- package/src/tokens/TokenProvider.ts +54 -0
- package/src/tokens/Tokens.ts +186 -0
- package/src/tokens/USD.ts +9 -6
- package/src/tokens/index.ts +6 -0
- package/src/types/Address.ts +86 -14
- package/src/types/BigInt.ts +14 -22
- package/src/types/ByteArray.ts +41 -3
- package/src/types/Bytes.ts +7 -0
- package/src/types/ChainId.ts +9 -1
- package/src/types/Option.ts +35 -0
- package/src/types/Result.ts +68 -0
- package/src/types/TriggerType.ts +4 -0
- package/src/types/evm/EvmDecodeParam.ts +7 -0
- package/src/types/evm/EvmEncodeParam.ts +31 -0
- package/src/types/evm/index.ts +2 -0
- package/src/types/index.ts +8 -2
- package/src/types/svm/SvmAccountInfo.ts +32 -0
- package/src/types/svm/SvmAccountMeta.ts +28 -0
- package/src/types/svm/SvmFindProgramAddress.ts +32 -0
- package/src/types/svm/SvmMint.ts +44 -0
- package/src/types/svm/SvmPdaSeed.ts +19 -0
- package/src/types/svm/SvmTokenMetadataData.ts +29 -0
- package/src/types/svm/index.ts +5 -0
- package/src/intents/Call.ts +0 -238
- package/src/queries/Call.ts +0 -16
- package/src/types/EvmDecodeParam.ts +0 -30
- 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
|
+
}
|
package/src/queries/index.ts
CHANGED
|
@@ -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
|
+
}
|