@mimicprotocol/lib-ts 0.0.1-rc.18 → 0.0.1-rc.19

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/index.ts CHANGED
@@ -4,5 +4,6 @@ export * from './src/evm'
4
4
  export * from './src/helpers'
5
5
  export * from './src/intents'
6
6
  export * from './src/log'
7
+ export * from './src/svm'
7
8
  export * from './src/tokens'
8
9
  export * from './src/types'
package/package.json CHANGED
@@ -1,13 +1,13 @@
1
1
  {
2
2
  "name": "@mimicprotocol/lib-ts",
3
- "version": "0.0.1-rc.18",
3
+ "version": "0.0.1-rc.19",
4
4
  "license": "GPL-3.0",
5
5
  "private": false,
6
6
  "type": "module",
7
7
  "scripts": {
8
8
  "build": "asc index.ts -b build/lib.wasm --disableWarning 235 --noEmit --transform json-as/transform",
9
9
  "test": "asp",
10
- "lint": "eslint . --ignore-pattern 'src/environment.ts' --ignore-pattern 'src/evm.ts' --ignore-pattern 'src/log.ts'"
10
+ "lint": "eslint . --ignore-pattern 'src/environment.ts' --ignore-pattern 'src/evm.ts' --ignore-pattern 'src/svm.ts' --ignore-pattern 'src/log.ts'"
11
11
  },
12
12
  "files": [
13
13
  "src",
@@ -1,4 +1,5 @@
1
- import { Address, ChainId } from '../types'
1
+ import { evm } from '../evm'
2
+ import { Address, BigInt, ChainId, EvmDecodeParam, JSON } from '../types'
2
3
  import { TriggerType } from '../types/TriggerType'
3
4
 
4
5
  @json
@@ -20,12 +21,50 @@ export class Settler {
20
21
  }
21
22
  }
22
23
 
24
+ @json
25
+ export class SerializableTrigger {
26
+ constructor(
27
+ public type: u8,
28
+ public data: string
29
+ ) {}
30
+ }
31
+
32
+ export class EventTriggerData {
33
+ constructor(
34
+ public blockHash: string,
35
+ public index: BigInt,
36
+ public topics: string[],
37
+ public eventData: string
38
+ ) {}
39
+ }
40
+
23
41
  @json
24
42
  export class Trigger {
25
43
  constructor(
26
44
  public type: TriggerType,
27
45
  public data: string
28
46
  ) {}
47
+
48
+ static fromSerializable(serializable: SerializableTrigger): Trigger {
49
+ return new Trigger(serializable.type, serializable.data)
50
+ }
51
+
52
+ getCronData(): BigInt {
53
+ return Trigger.deserializeCronTriggerData(this.data)
54
+ }
55
+
56
+ getEventData(): EventTriggerData {
57
+ return Trigger.deserializeEventTriggerData(this.data)
58
+ }
59
+
60
+ static deserializeCronTriggerData(data: string): BigInt {
61
+ return BigInt.fromString(evm.decode(new EvmDecodeParam('uint256', data)))
62
+ }
63
+
64
+ static deserializeEventTriggerData(data: string): EventTriggerData {
65
+ const fields = JSON.parse<string[]>(evm.decode(new EvmDecodeParam('(bytes32,uint256,bytes32[],bytes)', data)))
66
+ return new EventTriggerData(fields[0], BigInt.fromString(fields[1]), JSON.parse<string[]>(fields[2]), fields[3])
67
+ }
29
68
  }
30
69
 
31
70
  @json
@@ -36,7 +75,7 @@ export class SerializableContext {
36
75
  public user: string,
37
76
  public settlers: SerializableSettler[],
38
77
  public configSig: string,
39
- public trigger: Trigger
78
+ public trigger: SerializableTrigger
40
79
  ) {}
41
80
  }
42
81
 
@@ -57,7 +96,7 @@ export class Context {
57
96
  Address.fromString(serializable.user),
58
97
  serializable.settlers.map<Settler>((s) => Settler.fromSerializable(s)),
59
98
  serializable.configSig,
60
- serializable.trigger
99
+ Trigger.fromSerializable(serializable.trigger)
61
100
  )
62
101
  }
63
102
 
@@ -3,7 +3,18 @@ import { JSON } from 'json-as/assembly'
3
3
  import { Context, SerializableContext } from './context'
4
4
  import { ListType } from './helpers'
5
5
  import { Swap, Transfer, Call } from './intents'
6
- import { Call as CallQuery, GetPrice, GetRelevantTokens, GetRelevantTokensResponse } from './queries'
6
+ import {
7
+ Call as CallQuery,
8
+ GetAccountsInfo,
9
+ GetAccountsInfoResponse,
10
+ GetPrice,
11
+ GetRelevantTokens,
12
+ GetRelevantTokensResponse,
13
+ RelevantTokenBalance,
14
+ SerializableGetAccountsInfoResponse,
15
+ SubgraphQuery,
16
+ SubgraphQueryResponse,
17
+ } from './queries'
7
18
  import { BlockchainToken, Token, TokenAmount, USD } from './tokens'
8
19
  import { Address, BigInt, ChainId } from './types'
9
20
 
@@ -26,6 +37,12 @@ export namespace environment {
26
37
  @external('environment', '_contractCall')
27
38
  declare function _contractCall(params: string): string
28
39
 
40
+ @external('environment', '_subgraphQuery')
41
+ declare function _subgraphQuery(params: string): string
42
+
43
+ @external('environment', '_getAccountsInfo')
44
+ declare function _getAccountsInfo(params: string): string
45
+
29
46
  @external('environment', '_getContext')
30
47
  declare function _getContext(): string
31
48
 
@@ -96,12 +113,12 @@ export namespace environment {
96
113
  * @param usdMinAmount - Minimum USD value threshold for tokens (optional, defaults to zero)
97
114
  * @param tokensList - List of blockchain tokens to include/exclude (optional, defaults to empty array)
98
115
  * @param listType - Whether to include (AllowList) or exclude (DenyList) the tokens in `tokensList` (optional, defaults to DenyList)
99
- * @param timestamp - The timestamp for relevant tokens query (optional, defaults to current time)
100
- * @returns Array of TokenAmount objects representing the relevant tokens
116
+ * @returns Array of RelevantTokenBalance objects representing the relevant tokens
101
117
  */
102
- export function getRawRelevantTokens(address: Address, chainIds: ChainId[], usdMinAmount: USD, tokensList: BlockchainToken[], listType: ListType, timestamp: Date | null): GetRelevantTokensResponse[][] {
103
- const responseStr = _getRelevantTokens(JSON.stringify(GetRelevantTokens.init(address, chainIds, usdMinAmount, tokensList, listType, timestamp)))
104
- return JSON.parse<GetRelevantTokensResponse[][]>(responseStr)
118
+ export function getRawRelevantTokens(address: Address, chainIds: ChainId[], usdMinAmount: USD, tokensList: BlockchainToken[], listType: ListType): RelevantTokenBalance[][] {
119
+ const responseStr = _getRelevantTokens(JSON.stringify(GetRelevantTokens.init(address, chainIds, usdMinAmount, tokensList, listType)))
120
+ const responses = JSON.parse<GetRelevantTokensResponse[]>(responseStr)
121
+ return responses.map((response: GetRelevantTokensResponse) => response.balances)
105
122
  }
106
123
 
107
124
  /**
@@ -111,7 +128,6 @@ export namespace environment {
111
128
  * @param usdMinAmount - Minimum USD value threshold for tokens (optional, defaults to zero)
112
129
  * @param tokensList - List of blockchain tokens to include/exclude (optional, defaults to empty array)
113
130
  * @param listType - Whether to include (AllowList) or exclude (DenyList) the tokens in `tokensList` (optional, defaults to DenyList)
114
- * @param timestamp - The timestamp for relevant tokens qery (optional, defaults to current time)
115
131
  * @returns Array of TokenAmount objects representing the relevant tokens
116
132
  */
117
133
  export function getRelevantTokens(
@@ -119,10 +135,9 @@ export namespace environment {
119
135
  chainIds: ChainId[],
120
136
  usdMinAmount: USD = USD.zero(),
121
137
  tokensList: BlockchainToken[] = [],
122
- listType: ListType = ListType.DenyList,
123
- timestamp: Date | null = null
138
+ listType: ListType = ListType.DenyList
124
139
  ): TokenAmount[] {
125
- const response = getRawRelevantTokens(address, chainIds, usdMinAmount, tokensList, listType, timestamp)
140
+ const response = getRawRelevantTokens(address, chainIds, usdMinAmount, tokensList, listType)
126
141
  const resultMap: Map<string, TokenAmount> = new Map()
127
142
  for (let i = 0; i < response.length; i++) {
128
143
  for (let j = 0; j < response[i].length; j++) {
@@ -155,6 +170,46 @@ export namespace environment {
155
170
  )
156
171
  }
157
172
 
173
+ /**
174
+ * Generates a subgraph query and returns the result.
175
+ * @param chainId - The blockchain network identifier
176
+ * @param subgraphId - The ID of the subgraph to be called
177
+ * @param query - The string representing the subgraph query to be executed
178
+ * @param timestamp - The timestamp for the query context (optional)
179
+ * @returns The subgraph query response
180
+ */
181
+ export function subgraphQuery(
182
+ chainId: ChainId,
183
+ subgraphId: string,
184
+ query: string,
185
+ timestamp: Date | null,
186
+ ): SubgraphQueryResponse {
187
+ const responseStr = _subgraphQuery(JSON.stringify(SubgraphQuery.from(chainId, subgraphId, query, timestamp)))
188
+ return JSON.parse<SubgraphQueryResponse>(responseStr)
189
+ }
190
+
191
+ /**
192
+ * SVM - Gets on-chain account info
193
+ * @param publicKeys - Accounts to read from chain
194
+ * @param timestamp - The timestamp for the call context (optional)
195
+ * @returns The raw response from the underlying getMultipleAccountsInfo call
196
+ */
197
+
198
+ export function getAccountsInfo(
199
+ publicKeys: Address[],
200
+ timestamp: Date | null
201
+ ): GetAccountsInfoResponse {
202
+ // There is a bug with json-as, so we have to do this with JSON booleans
203
+ const responseStr = _getAccountsInfo(
204
+ JSON.stringify(GetAccountsInfo.from(publicKeys, timestamp))
205
+ )
206
+ .replaceAll("true",`"true"`)
207
+ .replaceAll("false",`"false"`)
208
+
209
+ const response = JSON.parse<SerializableGetAccountsInfoResponse>(responseStr)
210
+ return GetAccountsInfoResponse.fromSerializable(response)
211
+ }
212
+
158
213
  /**
159
214
  * Tells the current execution context containing environment information.
160
215
  * @returns The Context object containing: user, settler, timestamp, and config ID
@@ -0,0 +1,133 @@
1
+ import { Address, ByteArray, Bytes } from '../types'
2
+ import { Option } from '../types/Option'
3
+
4
+ import { bytesToString } from './strings'
5
+
6
+ /**
7
+ * Unsafe class to deserialize bytes into Rust-like types
8
+ * Should be used with caution! This will only throw if there are insufficient bytes for the type
9
+ */
10
+ export class BorshDeserializer {
11
+ private _bytes: Uint8Array
12
+ private _offset: u32 = 0
13
+
14
+ constructor(bytes: Uint8Array) {
15
+ this._bytes = bytes
16
+ }
17
+
18
+ static fromHex(hex: string): BorshDeserializer {
19
+ return new BorshDeserializer(Bytes.fromHexString(hex))
20
+ }
21
+
22
+ static fromBytes(bytes: Uint8Array): BorshDeserializer {
23
+ return new BorshDeserializer(bytes)
24
+ }
25
+
26
+ tryBool(): bool {
27
+ if (this._offset >= this.getBytesLength()) throw new Error('Insufficient bytes for bool')
28
+ const value = this._bytes.at(this._offset)
29
+ this._offset += 1
30
+ return value === 1
31
+ }
32
+
33
+ tryU8(): u8 {
34
+ if (this._offset >= this.getBytesLength()) throw new Error('Insufficient bytes for u8')
35
+ const value = this._bytes.at(this._offset)
36
+ this._offset += 1
37
+ return value
38
+ }
39
+
40
+ tryU16(): u16 {
41
+ if (this._offset + 1 >= this.getBytesLength()) throw new Error('Insufficient bytes for u16')
42
+ const subarray = changetype<ByteArray>(this._bytes.subarray(this._offset, this._offset + 2))
43
+ this._offset += 2
44
+ return subarray.toU16()
45
+ }
46
+
47
+ tryU32(): u32 {
48
+ if (this._offset + 3 >= this.getBytesLength()) throw new Error('Insufficient bytes for u32')
49
+ const subarray = changetype<ByteArray>(this._bytes.subarray(this._offset, this._offset + 4))
50
+ this._offset += 4
51
+ return subarray.toU32()
52
+ }
53
+
54
+ tryU64(): u64 {
55
+ if (this._offset + 7 >= this.getBytesLength()) throw new Error('Insufficient bytes for u64')
56
+ const subarray = changetype<ByteArray>(this._bytes.subarray(this._offset, this._offset + 8))
57
+ this._offset += 8
58
+ return subarray.toU64()
59
+ }
60
+
61
+ tryPubkey(): Address {
62
+ if (this._offset + 31 >= this.getBytesLength()) throw new Error('Insufficient bytes for pubkey')
63
+ const pubkey = Address.fromBytes(Bytes.fromUint8Array(this._bytes.subarray(this._offset, this._offset + 32)))
64
+ this._offset += 32
65
+ return pubkey
66
+ }
67
+
68
+ tryString(): string {
69
+ const length = this.tryU32()
70
+ if (this._offset + length - 1 >= this.getBytesLength())
71
+ throw new Error(`Insufficient bytes for string of size ${length}`)
72
+ const str = bytesToString(this._bytes.subarray(this._offset, this._offset + length), true)
73
+ this._offset += length
74
+ return str
75
+ }
76
+
77
+ tryOptionBool(): Option<bool> {
78
+ const tag = this.tryU32()
79
+ if (tag === 0) return Option.none<bool>()
80
+ return Option.some<bool>(this.tryBool())
81
+ }
82
+
83
+ tryOptionU8(): Option<u8> {
84
+ const tag = this.tryU32()
85
+ if (tag === 0) return Option.none<u8>()
86
+ return Option.some<u8>(this.tryU8())
87
+ }
88
+
89
+ tryOptionU16(): Option<u16> {
90
+ const tag = this.tryU32()
91
+ if (tag === 0) return Option.none<u16>()
92
+ return Option.some<u16>(this.tryU16())
93
+ }
94
+
95
+ tryOptionU32(): Option<u32> {
96
+ const tag = this.tryU32()
97
+ if (tag === 0) return Option.none<u32>()
98
+ return Option.some<u32>(this.tryU32())
99
+ }
100
+
101
+ tryOptionU64(): Option<u64> {
102
+ const tag = this.tryU32()
103
+ if (tag === 0) return Option.none<u64>()
104
+ return Option.some<u64>(this.tryU64())
105
+ }
106
+
107
+ tryOptionPubkey(): Option<Address> {
108
+ const tag = this.tryU32()
109
+ if (tag === 0) return Option.none<Address>(Address.fromBytes(new Bytes(32)))
110
+ return Option.some<Address>(this.tryPubkey())
111
+ }
112
+
113
+ private getBytesLength(): u32 {
114
+ return this._bytes.length as u32
115
+ }
116
+
117
+ getOffset(): u32 {
118
+ return this._offset
119
+ }
120
+
121
+ setOffset(offset: u32): void {
122
+ if (offset > this.getBytesLength()) throw new Error('Offset overruns buffer length')
123
+ this._offset = offset
124
+ }
125
+
126
+ isEmpty(): bool {
127
+ return this._offset >= this.getBytesLength()
128
+ }
129
+
130
+ getLength(): u32 {
131
+ return this._bytes.length
132
+ }
133
+ }
@@ -1,3 +1,4 @@
1
+ export * from './BorshDeserializer'
1
2
  export * from './constants'
2
3
  export * from './serialize'
3
4
  export * from './strings'
@@ -1,5 +1,4 @@
1
- import { evm } from '../evm'
2
- import { BigInt, EvmDecodeParam } from '../types'
1
+ import { Bytes } from '../types'
3
2
 
4
3
  export interface Stringable {
5
4
  toString(): string
@@ -9,13 +8,13 @@ export interface Serializable {
9
8
  serialize(): string
10
9
  }
11
10
 
11
+ export interface Byteable {
12
+ toBytes(): Bytes
13
+ }
14
+
12
15
  export function serialize<T extends Stringable>(elem: T): string {
13
16
  // eslint-disable-next-line
14
17
  // @ts-ignore
15
18
  if (elem instanceof Serializable) return elem.serialize()
16
19
  return elem.toString()
17
20
  }
18
-
19
- export function deserializeCronTriggerData(data: string): BigInt {
20
- return BigInt.fromString(evm.decode(new EvmDecodeParam('uint256', data)))
21
- }
@@ -2,8 +2,8 @@ import { decode, encode } from 'as-base58/assembly/index'
2
2
 
3
3
  import { ByteArray } from '../types'
4
4
 
5
- export function bytesToString(bytes: Uint8Array): string {
6
- return String.UTF8.decodeUnsafe(bytes.dataStart, bytes.length)
5
+ export function bytesToString(bytes: Uint8Array, nullTerminated: bool = false): string {
6
+ return String.UTF8.decodeUnsafe(bytes.dataStart, bytes.length, nullTerminated)
7
7
  }
8
8
 
9
9
  export function bytesToHexString(bytes: Uint8Array): string {
@@ -21,6 +21,11 @@ export function bytesFromBase58String(base58: string): ByteArray {
21
21
  return changetype<ByteArray>(decode(base58))
22
22
  }
23
23
 
24
+ export function stringToBool(str: string): bool {
25
+ if (str !== 'true' && str !== 'false') throw new Error(`Invalid boolean: ${str}`)
26
+ return str === 'true'
27
+ }
28
+
24
29
  export function areAllZeros(str: string): boolean {
25
30
  for (let i = 0; i < str.length; i++) if (str.charCodeAt(i) !== 48) return false
26
31
  return true
@@ -1,7 +1,6 @@
1
1
  import { environment } from '../environment'
2
2
  import { TokenAmount } from '../tokens'
3
- import { ChainId } from '../types'
4
- import { Address, BigInt, Bytes } from '../types'
3
+ import { Address, BigInt, Bytes, ChainId } from '../types'
5
4
 
6
5
  import { Intent, IntentBuilder, IntentEvent, MaxFee, OperationType } from './Intent'
7
6
 
@@ -0,0 +1,46 @@
1
+ import { Address, SerializableSvmAccountInfo, SvmAccountInfo } from '../types'
2
+
3
+ @json
4
+ class GetAccountsInfoBase {
5
+ constructor(public readonly publicKeys: string[]) {}
6
+ }
7
+
8
+ @json
9
+ export class GetAccountsInfo extends GetAccountsInfoBase {
10
+ public readonly timestamp: i64
11
+
12
+ constructor(publicKeys: string[], timestamp: i64) {
13
+ super(publicKeys)
14
+ this.timestamp = timestamp
15
+ }
16
+
17
+ static from(publicKeys: Address[], timestamp: Date | null): GetAccountsInfoBase {
18
+ const strPublicKeys = publicKeys.map((pk: Address) => pk.toString())
19
+ return timestamp
20
+ ? new GetAccountsInfo(strPublicKeys, changetype<Date>(timestamp).getTime())
21
+ : new GetAccountsInfoBase(strPublicKeys)
22
+ }
23
+ }
24
+
25
+ // There is a bug with json-as, so this can't be parsed directly
26
+ export class GetAccountsInfoResponse {
27
+ constructor(
28
+ public accountsInfo: SvmAccountInfo[],
29
+ public slot: string
30
+ ) {}
31
+
32
+ static fromSerializable(serializable: SerializableGetAccountsInfoResponse): GetAccountsInfoResponse {
33
+ return new GetAccountsInfoResponse(
34
+ serializable.accountsInfo.map((acc: SerializableSvmAccountInfo) => SvmAccountInfo.fromSerializable(acc)),
35
+ serializable.slot
36
+ )
37
+ }
38
+ }
39
+
40
+ @json
41
+ export class SerializableGetAccountsInfoResponse {
42
+ constructor(
43
+ public accountsInfo: SerializableSvmAccountInfo[],
44
+ public slot: string
45
+ ) {}
46
+ }
@@ -6,7 +6,7 @@ import { Address, BigInt, ChainId } from '../types'
6
6
  class TokenQuery {
7
7
  constructor(
8
8
  public address: string,
9
- public chainId: i32
9
+ public chainId: ChainId
10
10
  ) {}
11
11
 
12
12
  static fromToken(token: BlockchainToken): TokenQuery {
@@ -15,7 +15,7 @@ class TokenQuery {
15
15
  }
16
16
 
17
17
  @json
18
- class GetRelevantTokensBase {
18
+ export class GetRelevantTokens {
19
19
  constructor(
20
20
  public readonly owner: string,
21
21
  public readonly chainIds: ChainId[],
@@ -23,51 +23,23 @@ class GetRelevantTokensBase {
23
23
  public readonly tokens: TokenQuery[],
24
24
  public readonly tokenFilter: ListType
25
25
  ) {}
26
- }
27
-
28
- @json
29
- export class GetRelevantTokens extends GetRelevantTokensBase {
30
- public readonly timestamp: i64
31
-
32
- constructor(
33
- owner: string,
34
- chainIds: ChainId[],
35
- usdMinAmount: string,
36
- tokens: TokenQuery[],
37
- tokenFilter: ListType,
38
- timestamp: i64
39
- ) {
40
- super(owner, chainIds, usdMinAmount, tokens, tokenFilter)
41
- this.timestamp = timestamp
42
- }
43
26
 
44
27
  static init(
45
28
  owner: Address,
46
29
  chainIds: ChainId[],
47
30
  usdMinAmount: USD,
48
31
  tokens: BlockchainToken[],
49
- tokenFilter: ListType,
50
- timestamp: Date | null = null
51
- ): GetRelevantTokensBase {
32
+ tokenFilter: ListType
33
+ ): GetRelevantTokens {
52
34
  const ownerStr = owner.toString()
53
35
  const usdMinAmountStr = usdMinAmount.toString()
54
36
  const tokensQueries = tokens.map<TokenQuery>((token) => TokenQuery.fromToken(token))
55
-
56
- return timestamp
57
- ? new GetRelevantTokens(
58
- ownerStr,
59
- chainIds,
60
- usdMinAmountStr,
61
- tokensQueries,
62
- tokenFilter,
63
- changetype<Date>(timestamp).getTime()
64
- )
65
- : new GetRelevantTokensBase(ownerStr, chainIds, usdMinAmountStr, tokensQueries, tokenFilter)
37
+ return new GetRelevantTokens(ownerStr, chainIds, usdMinAmountStr, tokensQueries, tokenFilter)
66
38
  }
67
39
  }
68
40
 
69
41
  @json
70
- export class GetRelevantTokensResponse {
42
+ export class RelevantTokenBalance {
71
43
  constructor(
72
44
  public token: TokenQuery,
73
45
  public amount: string
@@ -80,3 +52,11 @@ export class GetRelevantTokensResponse {
80
52
  )
81
53
  }
82
54
  }
55
+
56
+ @json
57
+ export class GetRelevantTokensResponse {
58
+ constructor(
59
+ public timestamp: i64,
60
+ public balances: RelevantTokenBalance[]
61
+ ) {}
62
+ }
@@ -0,0 +1,34 @@
1
+ import { ChainId } from '../types'
2
+
3
+ @json
4
+ class SubgraphQueryBase {
5
+ constructor(
6
+ public readonly chainId: ChainId,
7
+ public readonly subgraphId: string,
8
+ public readonly query: string
9
+ ) {}
10
+ }
11
+
12
+ @json
13
+ export class SubgraphQuery extends SubgraphQueryBase {
14
+ public readonly timestamp: i64
15
+
16
+ constructor(chainId: ChainId, timestamp: i64, subgraphId: string, query: string) {
17
+ super(chainId, subgraphId, query)
18
+ this.timestamp = timestamp
19
+ }
20
+
21
+ static from(chainId: ChainId, subgraphId: string, query: string, timestamp: Date | null): SubgraphQueryBase {
22
+ return timestamp
23
+ ? new SubgraphQuery(chainId, changetype<Date>(timestamp).getTime(), subgraphId, query)
24
+ : new SubgraphQueryBase(chainId, subgraphId, query)
25
+ }
26
+ }
27
+
28
+ @json
29
+ export class SubgraphQueryResponse {
30
+ constructor(
31
+ public blockNumber: i64,
32
+ public data: string
33
+ ) {}
34
+ }
@@ -1,3 +1,5 @@
1
1
  export * from './Call'
2
+ export * from './GetAccountsInfo'
2
3
  export * from './GetPrice'
3
4
  export * from './GetRelevantTokens'
5
+ export * from './SubgraphQuery'
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
+ }
@@ -1,5 +1,7 @@
1
+ import { environment } from '../environment'
1
2
  import { SVM_NATIVE_ADDRESS } from '../helpers'
2
- import { Address, ChainId } from '../types'
3
+ import { svm } from '../svm'
4
+ import { Address, ChainId, SvmMint, SvmPdaSeed, SvmTokenMetadataData } from '../types'
3
5
 
4
6
  import { BlockchainToken } from './BlockchainToken'
5
7
  import { Token } from './Token'
@@ -27,8 +29,8 @@ export class SPLToken extends BlockchainToken {
27
29
  static fromAddress(
28
30
  address: Address,
29
31
  chainId: ChainId = ChainId.SOLANA_MAINNET,
30
- decimals: u8 = BlockchainToken.EMPTY_DECIMALS,
31
- symbol: string = BlockchainToken.EMPTY_SYMBOL
32
+ decimals: u8 = SPLToken.EMPTY_DECIMALS,
33
+ symbol: string = SPLToken.EMPTY_SYMBOL
32
34
  ): SPLToken {
33
35
  if (chainId != ChainId.SOLANA_MAINNET) throw new Error(`SPL tokens are only supported for Solana mainnet.`)
34
36
  return new SPLToken(address, decimals, symbol)
@@ -45,8 +47,8 @@ export class SPLToken extends BlockchainToken {
45
47
  static fromString(
46
48
  address: string,
47
49
  chainId: ChainId = ChainId.SOLANA_MAINNET,
48
- decimals: u8 = BlockchainToken.EMPTY_DECIMALS,
49
- symbol: string = BlockchainToken.EMPTY_SYMBOL
50
+ decimals: u8 = SPLToken.EMPTY_DECIMALS,
51
+ symbol: string = SPLToken.EMPTY_SYMBOL
50
52
  ): SPLToken {
51
53
  return SPLToken.fromAddress(Address.fromString(address), chainId, decimals, symbol)
52
54
  }
@@ -57,11 +59,60 @@ export class SPLToken extends BlockchainToken {
57
59
  * @param decimals - Number of decimal places
58
60
  * @param symbol - Token symbol
59
61
  */
60
- constructor(address: Address, decimals: u8, symbol: string) {
62
+ constructor(address: Address, decimals: u8 = SPLToken.EMPTY_DECIMALS, symbol: string = SPLToken.EMPTY_SYMBOL) {
61
63
  if (!address.isSVM()) throw new Error(`Address ${address} must be an SVM address.`)
62
64
  super(address, decimals, symbol, ChainId.SOLANA_MAINNET)
63
65
  }
64
66
 
67
+ /**
68
+ * Gets the token’s decimals (number of decimal places used).
69
+ * If decimals were not provided during construction, they will be lazily fetched
70
+ * The fetched value is parsed to `u8` and cached for future accesses.
71
+ * @returns A `u8` representing the number of decimals of the token.
72
+ */
73
+ get decimals(): u8 {
74
+ if (this._decimals == SPLToken.EMPTY_DECIMALS) {
75
+ const result = environment.getAccountsInfo([this.address], null)
76
+ const decimals = SvmMint.fromHex(result.accountsInfo[0].data).decimals
77
+ this._decimals = decimals
78
+ }
79
+ return this._decimals
80
+ }
81
+
82
+ /**
83
+ * Gets the token’s symbol (e.g., "SOL", "USDC").
84
+ * If the symbol was not provided during construction, it will be lazily fetched
85
+ * If the token doesn't have a symbol (TokenMetadata standard), an abbreviated address is returned
86
+ * The symbol is cached in the instance for future accesses.
87
+ * @returns A string containing the token symbol.
88
+ */
89
+ get symbol(): string {
90
+ if (this._symbol == SPLToken.EMPTY_SYMBOL) {
91
+ const result = environment.getAccountsInfo([this.getMetadataAddress()], null)
92
+ const data = result.accountsInfo[0].data
93
+ // Return placeholder symbol from address if TokenMetadata standard is not used
94
+ this._symbol =
95
+ data === '0x'
96
+ ? `${this.address.toString().slice(0, 5)}...${this.address.toString().slice(-5)}`
97
+ : SvmTokenMetadataData.fromTokenMetadataHex(result.accountsInfo[0].data).symbol
98
+ }
99
+ return this._symbol
100
+ }
101
+
102
+ /**
103
+ * Derives Metaplex Metadata address for this given token
104
+ */
105
+ private getMetadataAddress(): Address {
106
+ return svm.findProgramAddress(
107
+ [
108
+ SvmPdaSeed.fromString('metadata'),
109
+ SvmPdaSeed.from(Address.fromString(SvmTokenMetadataData.METADATA_PROGRAM_ID)),
110
+ SvmPdaSeed.from(this._address),
111
+ ],
112
+ Address.fromBase58String(SvmTokenMetadataData.METADATA_PROGRAM_ID)
113
+ ).address
114
+ }
115
+
65
116
  /**
66
117
  * Checks if this token is equal to another token.
67
118
  * SPL Tokens are considered equal if they have the same address.
@@ -8,6 +8,7 @@ import { EVM_NATIVE_ADDRESS, isHex, USD_ADDRESS } from '../helpers'
8
8
 
9
9
  import { ByteArray } from './ByteArray'
10
10
  import { Bytes } from './Bytes'
11
+ import { Option } from './Option'
11
12
 
12
13
  /**
13
14
  * Represents an EVM or SVM address, a fixed-length 20 or 32-byte value.
@@ -21,6 +22,15 @@ export class Address extends Bytes {
21
22
  return changetype<Address>(self)
22
23
  }
23
24
 
25
+ /**
26
+ * Returns a None variant for the Option type, representing no address
27
+ * @param length 32 by default (SVM)
28
+ * @returns Option.none with empty bytes
29
+ */
30
+ static none(length: u32 = 32): Option<Address> {
31
+ return Option.none<Address>(Address.fromBytes(new Bytes(length)))
32
+ }
33
+
24
34
  /**
25
35
  * Returns the USD denomination address.
26
36
  */
@@ -111,4 +121,11 @@ export class Address extends Bytes {
111
121
  toString(): string {
112
122
  return this.isEVM() ? super.toHexString() : super.toBase58String()
113
123
  }
124
+
125
+ /**
126
+ * Returns the address as its underlying bytes
127
+ */
128
+ toBytes(): Bytes {
129
+ return changetype<Bytes>(this.slice(0))
130
+ }
114
131
  }
@@ -176,6 +176,29 @@ export class ByteArray extends Uint8Array implements Serializable {
176
176
  return !(this == other)
177
177
  }
178
178
 
179
+ /**
180
+ * Interprets the byte array as a little-endian U16.
181
+ * Throws in case of overflow.
182
+ */
183
+ toU16(): u16 {
184
+ for (let i = 2; i < this.length; i++) {
185
+ if (this[i] != 0) {
186
+ assert(false, 'overflow converting ' + this.toHexString() + ' to u16')
187
+ }
188
+ }
189
+ const paddedBytes = new Bytes(2)
190
+ paddedBytes[0] = 0
191
+ paddedBytes[1] = 0
192
+ const minLen = paddedBytes.length < this.length ? paddedBytes.length : this.length
193
+ for (let i = 0; i < minLen; i++) {
194
+ paddedBytes[i] = this[i]
195
+ }
196
+ let x: u16 = 0
197
+ x = (x | paddedBytes[1]) << 8
198
+ x = x | paddedBytes[0]
199
+ return x
200
+ }
201
+
179
202
  /**
180
203
  * Interprets the byte array as a little-endian U32.
181
204
  * Throws in case of overflow.
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Simple wrapper for Option<T> Rust types
3
+ * To be used in tandem with BorshDeserializer
4
+ */
5
+ export class Option<T> {
6
+ constructor(
7
+ private _value: T,
8
+ public readonly isSome: bool
9
+ ) {}
10
+
11
+ static some<T>(value: T): Option<T> {
12
+ return new Option<T>(value, true)
13
+ }
14
+
15
+ static none<T>(defaultValue: T = 0 as T): Option<T> {
16
+ return new Option<T>(defaultValue, false)
17
+ }
18
+
19
+ unwrap(): T {
20
+ if (this.isSome) return this._value
21
+ throw new Error("Can't unwrap None variant")
22
+ }
23
+
24
+ eq(other: Option<T>): bool {
25
+ if (this.isSome != other.isSome) return false
26
+ if (!this.isSome && !other.isSome) return true
27
+ return this._value == other._value
28
+ }
29
+
30
+ toString(): string {
31
+ if (!this.isSome) return 'None'
32
+ // @ts-expect-error: AssemblyScript lacks runtime reflection, so we assume toString exists
33
+ return `Some(${this._value.toString ? this._value.toString() : '[Object]'})`
34
+ }
35
+ }
@@ -1,4 +1,4 @@
1
1
  export enum TriggerType {
2
- CRON,
3
- EVENT,
2
+ CRON = 0,
3
+ EVENT = 1,
4
4
  }
@@ -1,6 +1,6 @@
1
1
  import { JSON } from 'json-as'
2
2
 
3
- import { serialize, Stringable } from '../helpers'
3
+ import { serialize, Stringable } from '../../helpers'
4
4
 
5
5
  @json
6
6
  export class EvmEncodeParam {
@@ -0,0 +1,2 @@
1
+ export * from './EvmDecodeParam'
2
+ export * from './EvmEncodeParam'
@@ -5,6 +5,7 @@ export * from './BigInt'
5
5
  export * from './ByteArray'
6
6
  export * from './Bytes'
7
7
  export * from './ChainId'
8
- export * from './EvmDecodeParam'
9
- export * from './EvmEncodeParam'
8
+ export * from './evm'
9
+ export * from './Option'
10
+ export * from './svm'
10
11
  export { JSON }
@@ -0,0 +1,32 @@
1
+ import { stringToBool } from '../../helpers'
2
+
3
+ export class SvmAccountInfo {
4
+ constructor(
5
+ public owner: string,
6
+ public lamports: string,
7
+ public data: string,
8
+ public rentEpoch: string,
9
+ public executable: bool
10
+ ) {}
11
+
12
+ static fromSerializable(serializable: SerializableSvmAccountInfo): SvmAccountInfo {
13
+ return new SvmAccountInfo(
14
+ serializable.owner,
15
+ serializable.lamports,
16
+ serializable.data,
17
+ serializable.rentEpoch,
18
+ stringToBool(serializable.executable)
19
+ )
20
+ }
21
+ }
22
+
23
+ @json
24
+ export class SerializableSvmAccountInfo {
25
+ constructor(
26
+ public owner: string,
27
+ public lamports: string,
28
+ public data: string,
29
+ public rentEpoch: string,
30
+ public executable: string
31
+ ) {}
32
+ }
@@ -0,0 +1,32 @@
1
+ import { Address } from './../Address'
2
+ import { SvmPdaSeed } from './SvmPdaSeed'
3
+
4
+ @json
5
+ export class SvmFindProgramAddressParams {
6
+ public readonly seeds: SvmPdaSeed[]
7
+ public readonly programId: string
8
+
9
+ constructor(seeds: SvmPdaSeed[], programId: string) {
10
+ this.seeds = seeds.slice()
11
+ this.programId = programId
12
+ }
13
+ }
14
+
15
+ export class SvmFindProgramAddressResult {
16
+ constructor(
17
+ public address: Address,
18
+ public bump: u8
19
+ ) {}
20
+
21
+ static fromSerializable(serializable: SerializableSvmFindProgramAddressResult): SvmFindProgramAddressResult {
22
+ return new SvmFindProgramAddressResult(Address.fromString(serializable.address), serializable.bump)
23
+ }
24
+ }
25
+
26
+ @json
27
+ export class SerializableSvmFindProgramAddressResult {
28
+ constructor(
29
+ public address: string,
30
+ public bump: u8
31
+ ) {}
32
+ }
@@ -0,0 +1,44 @@
1
+ import { BorshDeserializer } from '../../helpers'
2
+
3
+ import { Address } from './../Address'
4
+ import { Bytes } from './../Bytes'
5
+ import { Option } from './../Option'
6
+
7
+ /**
8
+ * State layout for Mint TokenProgram PDAs
9
+ */
10
+ export class SvmMint {
11
+ static DECIMALS_OFFSET: u32 = 44
12
+
13
+ constructor(
14
+ public mintAuthority: Option<Address>,
15
+ public supply: u64,
16
+ public decimals: u8,
17
+ public isInitialized: bool,
18
+ public freezeAuthority: Option<Address>
19
+ ) {}
20
+
21
+ static fromHex(hex: string): SvmMint {
22
+ return this.fromBytes(Bytes.fromHexString(hex))
23
+ }
24
+
25
+ static fromBytes(bytes: Bytes): SvmMint {
26
+ const deserializer = BorshDeserializer.fromBytes(bytes)
27
+
28
+ const mintAuthorityFlag = deserializer.tryU32() === 1
29
+ const mintAuthority = deserializer.tryPubkey()
30
+ const supply = deserializer.tryU64()
31
+ const decimals = deserializer.tryU8()
32
+ const isInitialized = deserializer.tryBool()
33
+ const freezeAuthorityFlag = deserializer.tryU32() === 1
34
+ const freezeAuthority = deserializer.tryPubkey()
35
+
36
+ return new SvmMint(
37
+ mintAuthorityFlag ? Option.some(mintAuthority) : Address.none(),
38
+ supply,
39
+ decimals,
40
+ isInitialized,
41
+ freezeAuthorityFlag ? Option.some(freezeAuthority) : Address.none()
42
+ )
43
+ }
44
+ }
@@ -0,0 +1,19 @@
1
+ import { Byteable, bytesToHexString } from '../../helpers'
2
+
3
+ /**
4
+ * Type for findProgramAddress seeds
5
+ * As we need all seeds to be decoded as bytes in the same way as in Rust,
6
+ * this class provides a simple interface for end-users to abstract all this
7
+ */
8
+ @json
9
+ export class SvmPdaSeed {
10
+ constructor(public readonly hex: string) {}
11
+
12
+ static fromString(str: string): SvmPdaSeed {
13
+ return new SvmPdaSeed(bytesToHexString(Uint8Array.wrap(String.UTF8.encode(str))))
14
+ }
15
+
16
+ static from<T extends Byteable>(t: T): SvmPdaSeed {
17
+ return new SvmPdaSeed(bytesToHexString(t.toBytes()))
18
+ }
19
+ }
@@ -0,0 +1,29 @@
1
+ import { BorshDeserializer } from '../../helpers'
2
+
3
+ import { Bytes } from './../Bytes'
4
+
5
+ /**
6
+ * Partial state layout for Data Metaplex PDA (Metadata PDA "data" field)
7
+ */
8
+ export class SvmTokenMetadataData {
9
+ static DATA_OFFSET: u32 = 65
10
+ // eslint-disable-next-line no-secrets/no-secrets
11
+ static METADATA_PROGRAM_ID: string = 'metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s'
12
+
13
+ constructor(
14
+ public name: string,
15
+ public symbol: string,
16
+ public uri: string
17
+ ) {}
18
+
19
+ static fromTokenMetadataHex(hex: string): SvmTokenMetadataData {
20
+ return this.fromTokenMetadataBytes(Bytes.fromHexString(hex))
21
+ }
22
+
23
+ static fromTokenMetadataBytes(bytes: Bytes): SvmTokenMetadataData {
24
+ const deserializer = BorshDeserializer.fromBytes(bytes)
25
+ deserializer.setOffset(this.DATA_OFFSET)
26
+
27
+ return new SvmTokenMetadataData(deserializer.tryString(), deserializer.tryString(), deserializer.tryString())
28
+ }
29
+ }
@@ -0,0 +1,5 @@
1
+ export * from './SvmAccountInfo'
2
+ export * from './SvmFindProgramAddress'
3
+ export * from './SvmMint'
4
+ export * from './SvmPdaSeed'
5
+ export * from './SvmTokenMetadataData'