zo-sdk 0.0.3

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 (65) hide show
  1. package/.gitattributes +4 -0
  2. package/.prettierrc.js +5 -0
  3. package/LICENSE +21 -0
  4. package/README.md +1 -0
  5. package/babel.config.js +11 -0
  6. package/dist/api.cjs +466 -0
  7. package/dist/api.d.cts +26 -0
  8. package/dist/api.d.cts.map +1 -0
  9. package/dist/api.d.mts +26 -0
  10. package/dist/api.d.mts.map +1 -0
  11. package/dist/api.mjs +462 -0
  12. package/dist/consts/deployments-mainnet.json +28 -0
  13. package/dist/consts/deployments-testnet.json +93 -0
  14. package/dist/consts/index.cjs +101 -0
  15. package/dist/consts/index.d.cts +66 -0
  16. package/dist/consts/index.d.cts.map +1 -0
  17. package/dist/consts/index.d.mts +66 -0
  18. package/dist/consts/index.d.mts.map +1 -0
  19. package/dist/consts/index.mjs +94 -0
  20. package/dist/consts/price_id_to_object_id.mainnet.json +1 -0
  21. package/dist/consts/price_id_to_object_id.testnet.json +17 -0
  22. package/dist/consts/staking/deployments-mainnet.json +12 -0
  23. package/dist/consts/staking/deployments-testnet.json +11 -0
  24. package/dist/data.cjs +844 -0
  25. package/dist/data.d.cts +221 -0
  26. package/dist/data.d.cts.map +1 -0
  27. package/dist/data.d.mts +221 -0
  28. package/dist/data.d.mts.map +1 -0
  29. package/dist/data.mjs +840 -0
  30. package/dist/index.cjs +21 -0
  31. package/dist/index.d.cts +6 -0
  32. package/dist/index.d.cts.map +1 -0
  33. package/dist/index.d.mts +6 -0
  34. package/dist/index.d.mts.map +1 -0
  35. package/dist/index.mjs +5 -0
  36. package/dist/oracle.cjs +178 -0
  37. package/dist/oracle.d.cts +24 -0
  38. package/dist/oracle.d.cts.map +1 -0
  39. package/dist/oracle.d.mts +24 -0
  40. package/dist/oracle.d.mts.map +1 -0
  41. package/dist/oracle.mjs +173 -0
  42. package/dist/utils.cjs +144 -0
  43. package/dist/utils.d.cts +46 -0
  44. package/dist/utils.d.cts.map +1 -0
  45. package/dist/utils.d.mts +46 -0
  46. package/dist/utils.d.mts.map +1 -0
  47. package/dist/utils.mjs +129 -0
  48. package/jest.config.js +7 -0
  49. package/package.json +29 -0
  50. package/src/api.ts +544 -0
  51. package/src/consts/deployments-mainnet.json +28 -0
  52. package/src/consts/deployments-testnet.json +93 -0
  53. package/src/consts/index.ts +162 -0
  54. package/src/consts/price_id_to_object_id.mainnet.json +1 -0
  55. package/src/consts/price_id_to_object_id.testnet.json +17 -0
  56. package/src/consts/staking/deployments-mainnet.json +12 -0
  57. package/src/consts/staking/deployments-testnet.json +11 -0
  58. package/src/data.ts +1059 -0
  59. package/src/index.ts +5 -0
  60. package/src/oracle.ts +161 -0
  61. package/src/utils.ts +184 -0
  62. package/tests/api.test.ts +219 -0
  63. package/tests/data.test.ts +156 -0
  64. package/tests/oracle.test.ts +33 -0
  65. package/tsconfig.json +25 -0
package/src/index.ts ADDED
@@ -0,0 +1,5 @@
1
+ export * from './api'
2
+ export * from './consts'
3
+ export * from './data'
4
+ export * from './oracle'
5
+ export * from './utils'
package/src/oracle.ts ADDED
@@ -0,0 +1,161 @@
1
+ import type { SuiClient } from '@mysten/sui/client'
2
+ import { Transaction } from '@mysten/sui/transactions'
3
+ import type { PriceFeed } from '@pythnetwork/pyth-sui-js'
4
+ import { SuiPriceServiceConnection, SuiPythClient } from '@pythnetwork/pyth-sui-js'
5
+
6
+ import type { IConsts } from './consts'
7
+ import { getConsts, getPriceIdToPythFeeder, getPythFeederToId, getPythFeederToPriceId, Network } from './consts'
8
+ import jsonFile from './consts/price_id_to_object_id.mainnet.json'
9
+ import { createJsonRpcProvider } from './utils'
10
+
11
+ export class OracleAPI {
12
+ network: Network
13
+ consts: IConsts
14
+ connectionURL: string
15
+ PythFeederToPriceId: Record<string, string>
16
+ PythFeederToId: Record<string, string>
17
+ provider: SuiClient
18
+ client: SuiPythClient
19
+
20
+ private priceCache: Record<string, PriceFeed> = {}
21
+
22
+ constructor(
23
+ network: Network,
24
+ provider: SuiClient | null,
25
+ // todo 需要从配置文件中获取
26
+ connectionURL: string,
27
+ ) {
28
+ this.network = network
29
+ this.consts = getConsts(network)
30
+ this.connectionURL = connectionURL
31
+ this.PythFeederToPriceId = getPythFeederToPriceId(network)
32
+ this.PythFeederToId = getPythFeederToId(network)
33
+ this.provider = provider || createJsonRpcProvider(network)
34
+ this.client = new SuiPythClient(this.provider, this.consts.pythFeeder.state, this.consts.pythFeeder.wormhole.state)
35
+ }
36
+
37
+ validateCache() {
38
+ const now = Date.now() / 1000
39
+ for (const key in this.priceCache) {
40
+ if (now - (this.priceCache[key].getPriceUnchecked().publishTime || 0) > 7) {
41
+ delete this.priceCache[key]
42
+ }
43
+ }
44
+ }
45
+
46
+ async getOraclePrice(tokenId: string) {
47
+ this.validateCache()
48
+ if (this.priceCache[tokenId]) {
49
+ return this.priceCache[tokenId]
50
+ }
51
+ const res = await this.getOraclePrices([tokenId])
52
+ if (!res || !res[0]) {
53
+ throw new Error(`Unknown token: ${tokenId}`)
54
+ }
55
+ this.priceCache[tokenId] = res[0]
56
+ return res[0]
57
+ }
58
+
59
+ async getOraclePrices(tokens: string[]) {
60
+ const connection = new SuiPriceServiceConnection(this.connectionURL, {
61
+ priceFeedRequestConfig: {
62
+ binary: true,
63
+ },
64
+ })
65
+ const pythObjectIds = tokens.map(token => this.consts.pythFeeder.feeder[token])
66
+ const priceFeedIds = pythObjectIds.map(pythObjectId => `0x${this.PythFeederToPriceId[pythObjectId]}`)
67
+ const price = await connection.getLatestPriceFeeds(priceFeedIds)
68
+ return price
69
+ }
70
+
71
+ // memory leak
72
+ async subOraclePrices(tokens: string[], callback: (price: PriceFeed) => void) {
73
+ const connection = new SuiPriceServiceConnection(this.connectionURL, {
74
+ priceFeedRequestConfig: {
75
+ binary: true,
76
+ },
77
+ })
78
+ const pythObjectIds = tokens.map(token => this.consts.pythFeeder.feeder[token])
79
+ const priceFeedIds = pythObjectIds.map(pythObjectId => `0x${this.PythFeederToPriceId[pythObjectId]}`)
80
+ await connection.subscribePriceFeedUpdates(priceFeedIds, (price) => {
81
+ price.id = this.PythFeederToId[getPriceIdToPythFeeder(this.network)[price.id]]
82
+ this.priceCache[price.id] = price
83
+ callback(price)
84
+ })
85
+
86
+ return () => {
87
+ connection.unsubscribePriceFeedUpdates(priceFeedIds)
88
+ }
89
+ }
90
+
91
+ async initOracleTxb(tokens: string[], tx?: Transaction) {
92
+ let tx_ = tx
93
+ if (!tx_) {
94
+ tx_ = new Transaction()
95
+ }
96
+ // Remove redundant tokens first
97
+ const uniqueTokens = [...new Set(tokens)]
98
+
99
+ const connection = new SuiPriceServiceConnection(this.connectionURL, {
100
+ priceFeedRequestConfig: {
101
+ binary: true,
102
+ },
103
+ })
104
+ const pythObjectIds = uniqueTokens.map(token => this.consts.pythFeeder.feeder[token])
105
+ const needUpdateObjectIds = (await this.provider.multiGetObjects({
106
+ ids: pythObjectIds,
107
+ options: {
108
+ showContent: true,
109
+ },
110
+ })).map(pythObject => [
111
+ Number.parseInt((pythObject.data?.content as any).fields.price_info.fields.arrival_time || 0, 10) - Date.now() / 1000,
112
+ pythObject.data?.objectId,
113
+ ]).filter((x: any) => Math.abs(x[0]) >= 0).map(x => x[1] as string)
114
+
115
+ if (needUpdateObjectIds.length === 0) {
116
+ return tx_
117
+ }
118
+
119
+ const priceFeedIds = needUpdateObjectIds
120
+ .map(pythObjectId => this.PythFeederToPriceId[pythObjectId])
121
+ .filter(Boolean)
122
+ .map(x => `0x${x}`)
123
+
124
+ const priceUpdateData = await connection.getPriceFeedsUpdateData(priceFeedIds)
125
+
126
+ await this.client.updatePriceFeeds(tx_, priceUpdateData, priceFeedIds)
127
+ return tx_
128
+ }
129
+ }
130
+
131
+ export async function generatePriceIdToObjectIdJson() {
132
+ const provider = createJsonRpcProvider(Network.MAINNET)
133
+ const wormholeStateId = '0xaeab97f96cf9877fee2883315d459552b2b921edc16d7ceac6eab944dd88919c'
134
+ const pythStateId = '0x1f9310238ee9298fb703c3419030b35b22bb1cc37113e3bb5007c99aec79e5b8'
135
+ const client = new SuiPythClient(provider, pythStateId, wormholeStateId)
136
+ const ret: any = {}
137
+
138
+ const entries = Object.keys(jsonFile)
139
+
140
+ // 使用 Promise.all 并行处理所有请求
141
+ await Promise.all(
142
+ entries.map(async (key) => {
143
+ try {
144
+ const pythObjectId = await client.getPriceFeedObjectId(key)
145
+ if (pythObjectId) {
146
+ ret[key] = pythObjectId
147
+ }
148
+ else {
149
+ console.warn(`Failed to get pythObjectId for key: ${key}`)
150
+ }
151
+ }
152
+ catch (error) {
153
+ console.error(`Error getting pythObjectId for key ${key}:`, error)
154
+ }
155
+ }),
156
+ )
157
+
158
+ return ret
159
+ }
160
+
161
+ // generatePriceIdToObjectIdJson()
package/src/utils.ts ADDED
@@ -0,0 +1,184 @@
1
+ import { SuiClient, SuiHTTPTransport } from '@mysten/sui/client'
2
+ import { HermesClient } from '@pythnetwork/hermes-client'
3
+
4
+ import type { IConsts } from './consts'
5
+ import { Network } from './consts'
6
+
7
+ export function createJsonRpcProvider(network: Network) {
8
+ let url = ''
9
+ switch (network) {
10
+ case Network.DEVNET: {
11
+ url = 'https://explorer-rpc.devnet.sui.io/'
12
+ break
13
+ }
14
+ case Network.TESTNET: {
15
+ url = 'https://sui-testnet.blockvision.org/v1/2sXCJEJNOCdIJhLaDtyhKzqwn6k'
16
+ break
17
+ }
18
+ case Network.MAINNET: {
19
+ url = 'https://rpc-mainnet.suiscan.xyz'
20
+ break
21
+ }
22
+ default: {
23
+ url = 'https://explorer-rpc.devnet.sui.io/'
24
+ }
25
+ }
26
+
27
+ return new SuiClient({
28
+ transport: new SuiHTTPTransport({ url }),
29
+ })
30
+ }
31
+
32
+ export function createPythConnection(network: Network) {
33
+ let priceConnectionUrl
34
+ switch (network) {
35
+ case Network.TESTNET: {
36
+ priceConnectionUrl = 'https://xc-testnet.pyth.network'
37
+ break
38
+ }
39
+ case Network.MAINNET: {
40
+ priceConnectionUrl = 'https://xc-mainnet.pyth.network'
41
+ break
42
+ }
43
+ default: {
44
+ priceConnectionUrl = 'https://xc-testnet.pyth.network'
45
+ }
46
+ }
47
+
48
+ return new HermesClient(priceConnectionUrl)
49
+ }
50
+
51
+ export function decimalToObject(decimal: { value: string }) {
52
+ return Number(BigInt(decimal.value)) / 1e18
53
+ }
54
+
55
+ export function rateToObject(rate: { value: string }) {
56
+ return Number(BigInt(rate.value)) / 1e18
57
+ }
58
+
59
+ interface SRate {
60
+ fields: {
61
+ is_positive: boolean
62
+ value: {
63
+ fields: {
64
+ value: string | number
65
+ }
66
+ }
67
+ }
68
+ }
69
+
70
+ export function sRateToObject(sRate: SRate) {
71
+ const sign = sRate.fields.is_positive ? 1 : -1
72
+ return Number(BigInt(sRate.fields.value.fields.value)) / 1e18 * sign
73
+ }
74
+
75
+ interface SuiDecimal {
76
+ fields: {
77
+ is_positive: boolean
78
+ value: {
79
+ fields: {
80
+ value: string
81
+ }
82
+ }
83
+ }
84
+ }
85
+
86
+ export function sDecimalToObject(sDecimal: SuiDecimal) {
87
+ const sign = sDecimal.fields.is_positive ? 1 : -1
88
+ return Number(BigInt(sDecimal.fields.value.fields.value)) / 1e18 * sign
89
+ }
90
+
91
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
92
+ export function parseValue(field: any): number {
93
+ if (field.type && field.type.endsWith('::decimal::Decimal')) {
94
+ return decimalToObject({ value: field.fields.value })
95
+ }
96
+ else if (field.type && field.type.endsWith('::rate::Rate')) {
97
+ return rateToObject({ value: field.fields.value })
98
+ }
99
+ else if (field.type && field.type.endsWith('::srate::SRate')) {
100
+ return sRateToObject(field)
101
+ }
102
+ else if (field.type && field.type.endsWith('::sdecimal::SDecimal')) {
103
+ return sDecimalToObject(field)
104
+ }
105
+ return Number.parseInt(field, 10)
106
+ }
107
+
108
+ export type ReversedKeyValue<T extends object> = {
109
+ [K in keyof T]: T[K] extends keyof T
110
+ ? keyof T
111
+ : T[K] extends keyof T | infer V
112
+ ? V
113
+ : never;
114
+ }
115
+
116
+ export function reverseKeyValue<T extends object>(obj: T): ReversedKeyValue<T> {
117
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
118
+ const reversed: any = {}
119
+
120
+ for (const key in obj) {
121
+ if (Object.hasOwn(obj, key)) {
122
+ const value = obj[key]
123
+ reversed[value] = key
124
+ }
125
+ }
126
+
127
+ return reversed
128
+ }
129
+
130
+ export function parseSymbolKey(input: string): string[] {
131
+ // This regex will match uppercase letters
132
+ const regex = /[A-Z]/
133
+ let result: string[] = []
134
+ let wordStart = 0
135
+
136
+ for (let i = 1; i < input.length; i += 1) {
137
+ if (regex.test(input[i])) {
138
+ // Found an uppercase letter, so we split the string here
139
+ result.push(input.slice(wordStart, i))
140
+ wordStart = i
141
+ }
142
+ }
143
+
144
+ // Add the last word to the result array
145
+ result.push(input.slice(wordStart))
146
+
147
+ // Convert the words to lowercase
148
+ result = result.map(word => word.toLowerCase())
149
+
150
+ return result
151
+ }
152
+
153
+ export function upperFirstCharacter(word: string): string {
154
+ return word.charAt(0).toUpperCase() + word.slice(1)
155
+ }
156
+
157
+ export function joinSymbol(direction: string, token: string): string {
158
+ return `${direction}${upperFirstCharacter(token)}`
159
+ }
160
+
161
+ export function suiSymbolToSymbol(symbol: string, consts: IConsts): string {
162
+ if (symbol === '0x2::sui::SUI') {
163
+ return 'sui'
164
+ }
165
+ const ret: Record<string, string> = {}
166
+ for (const key of Object.keys(consts.coins)) {
167
+ ret[consts.coins[key].module] = key
168
+ }
169
+ return ret[symbol]
170
+ }
171
+
172
+ export function base64ToUint8Array(base64: string) {
173
+ // Decode Base64 to binary
174
+ const binary = atob(base64)
175
+
176
+ // Create a Uint8Array from the binary data
177
+ const uint8Array = new Uint8Array(binary.length)
178
+ // Populate the Uint8Array with the binary data
179
+ for (let i = 0; i < binary.length; i += 1) {
180
+ uint8Array[i] = binary.codePointAt(i)!
181
+ }
182
+
183
+ return uint8Array
184
+ }
@@ -0,0 +1,219 @@
1
+ import { Ed25519Keypair, RawSigner } from '@mysten/sui'
2
+ import consola from 'consola'
3
+ import { describe, it } from 'vitest'
4
+
5
+ import { API } from '../src/api'
6
+ import { getConsts } from '../src/consts'
7
+ import { base64ToUint8Array, createJsonRpcProvider } from '../src/utils'
8
+
9
+ function getSigner() {
10
+ const privKey = process.env.PRIVKEY as string
11
+ if (!privKey)
12
+ throw new Error('no privkey')
13
+ const keypair = Ed25519Keypair.fromSecretKey(base64ToUint8Array(privKey))
14
+ const provider = createJsonRpcProvider('mainnet')
15
+ return new RawSigner(keypair, provider)
16
+ }
17
+
18
+ describe('API', () => {
19
+ it('should deposit', async () => {
20
+ const signer = getSigner()
21
+ const api = new API('testnet')
22
+ const tx = await api.deposit(
23
+ 'usdt',
24
+ ['0x25ea697633f012d29b8eb04839fa3b1237b3f912c5c9a68790e1bece09a09cf5'],
25
+ 10000 * 1e9,
26
+ 0,
27
+ )
28
+ tx.setGasBudget(1e9)
29
+ const txid = await signer.signAndExecuteTransactionBlock({
30
+ transactionBlock: tx,
31
+ })
32
+ consola.log(txid)
33
+ }, 30000)
34
+
35
+ it('should withdraw', async () => {
36
+ const signer = getSigner()
37
+ const api = new API('testnet')
38
+ const tx = await api.withdraw(
39
+ 'fsui',
40
+ ['0x7efdadea2bd3c14afba47506bdd1a798c67421a5d1d90e7b1e5e151ebfa7f6e8'],
41
+ 100,
42
+ 0,
43
+ )
44
+ tx.setGasBudget(1e9)
45
+ const txid = await signer.signAndExecuteTransactionBlock({
46
+ transactionBlock: tx,
47
+ })
48
+ consola.log(txid)
49
+ }, 30000)
50
+
51
+ it('should open position', async () => {
52
+ const signer = getSigner()
53
+ const api = new API('testnet')
54
+ const tx = await api.openPosition(
55
+ 'fsui',
56
+ 'btc',
57
+ BigInt(0.2 * 1e9),
58
+ BigInt(1000 * 1e9),
59
+ ['0x29a8ec9fb0482baae50832a4116d3245ffc5531ee00e65792feaca8c83b0401a'],
60
+ true,
61
+ BigInt(10000 * 1e9),
62
+ 25000,
63
+ 0.6,
64
+ 0.3,
65
+ true,
66
+ )
67
+ tx.setGasBudget(1e9)
68
+ const txid = await signer.signAndExecuteTransactionBlock({
69
+ transactionBlock: tx,
70
+ })
71
+ consola.log(txid)
72
+ }, 30000)
73
+
74
+ it('should pledge in position', async () => {
75
+ const signer = getSigner()
76
+ const api = new API('testnet')
77
+ const tx = await api.pledgeInPosition(
78
+ '0xd6918be43aa57a1f67b80f175e0d374be13fb732efc978415b3460af26235dee',
79
+ 'fsui',
80
+ 'fsui',
81
+ 100 * 1e9,
82
+ ['0x29a8ec9fb0482baae50832a4116d3245ffc5531ee00e65792feaca8c83b0401a'],
83
+ true,
84
+ )
85
+ tx.setGasBudget(1e9)
86
+ const txid = await signer.signAndExecuteTransactionBlock({
87
+ transactionBlock: tx,
88
+ })
89
+ consola.log(txid)
90
+ }, 30000)
91
+
92
+ it('should redeem from position', async () => {
93
+ const signer = getSigner()
94
+ const api = new API('testnet')
95
+ const tx = await api.redeemFromPosition(
96
+ '0xd6918be43aa57a1f67b80f175e0d374be13fb732efc978415b3460af26235dee',
97
+ 'fsui',
98
+ 'fsui',
99
+ 100 * 1e9,
100
+ true,
101
+ )
102
+ tx.setGasBudget(1e9)
103
+ const txid = await signer.signAndExecuteTransactionBlock({
104
+ transactionBlock: tx,
105
+ })
106
+ consola.log(txid)
107
+ }, 30000)
108
+
109
+ it('should decrease position', async () => {
110
+ const signer = getSigner()
111
+ const api = new API('testnet')
112
+ const tx = await api.decreasePosition(
113
+ '0xd6918be43aa57a1f67b80f175e0d374be13fb732efc978415b3460af26235dee',
114
+ 'fsui',
115
+ 'btc',
116
+ BigInt(100 * 1e9),
117
+ true,
118
+ 30000,
119
+ 0.6,
120
+ true,
121
+ true,
122
+ false,
123
+ 1,
124
+ )
125
+ tx.setGasBudget(1e9)
126
+ const txid = await signer.signAndExecuteTransactionBlock({
127
+ transactionBlock: tx,
128
+ })
129
+ consola.log(txid)
130
+ }, 30000)
131
+
132
+ it('should close position', async () => {
133
+ const signer = getSigner()
134
+ const api = new API('testnet')
135
+ const tx = await api.decreasePosition(
136
+ '0xd6918be43aa57a1f67b80f175e0d374be13fb732efc978415b3460af26235dee',
137
+ 'fsui',
138
+ 'fsui',
139
+ BigInt(100 * 1e9),
140
+ true,
141
+ 0.6,
142
+ 0.6,
143
+ false,
144
+ false,
145
+ false,
146
+ 1,
147
+ )
148
+ tx.setGasBudget(1e9)
149
+ const txid = await signer.signAndExecuteTransactionBlock({
150
+ transactionBlock: tx,
151
+ })
152
+ consola.log(txid)
153
+ }, 30000)
154
+
155
+ it('should cancel order', async () => {
156
+ const signer = getSigner()
157
+ const api = new API('testnet')
158
+ const tx = await api.cancelOrder(
159
+ '0xdd7c2928545783697113c7e62ca62b622461590428db84cf164e915052455a83',
160
+ 'fsui',
161
+ 'btc',
162
+ true,
163
+ 'OPEN_POSITION',
164
+ )
165
+ tx.setGasBudget(1e9)
166
+ const txid = await signer.signAndExecuteTransactionBlock({
167
+ transactionBlock: tx,
168
+ })
169
+ consola.log(txid)
170
+ }, 30000)
171
+
172
+ it('should stake', async () => {
173
+ const signer = getSigner()
174
+ const api = new API('testnet')
175
+ const consts = getConsts('testnet')
176
+ const tx = await api.stake(
177
+ ['0xaebfd8e05909880b7dd3e5b326f35e273510773b2219c73c41fd9ee3778f14f8'],
178
+ BigInt(100000),
179
+ consts.zoStaking.pool,
180
+ )
181
+ tx.setGasBudget(1e9)
182
+ const txid = await signer.signAndExecuteTransactionBlock({
183
+ transactionBlock: tx,
184
+ })
185
+
186
+ consola.log(txid)
187
+ }, 30000)
188
+
189
+ it('should unstake', async () => {
190
+ const signer = getSigner()
191
+ const api = new API('testnet')
192
+ const consts = getConsts('testnet')
193
+ const tx = await api.unstake(
194
+ (await api.getStaked(await signer.getAddress())).credentials,
195
+ BigInt(0),
196
+ consts.zoStaking.pool,
197
+ )
198
+ tx.setGasBudget(1e9)
199
+ const txid = await signer.signAndExecuteTransactionBlock({
200
+ transactionBlock: tx,
201
+ })
202
+
203
+ consola.log(txid)
204
+ }, 30000)
205
+
206
+ it('should referral', async () => {
207
+ const signer = getSigner()
208
+ const api = new API('mainnet')
209
+ const tx = await api.addReferral(
210
+ '0x2ccaa4a34c46e1bf6c22a52732ddbb589caefb9c5c0c0f3da485348a5441deb0',
211
+ )
212
+ tx.setGasBudget(0.01 * 1e9)
213
+ const txid = await signer.signAndExecuteTransactionBlock({
214
+ transactionBlock: tx,
215
+ })
216
+
217
+ consola.log(txid)
218
+ }, 30000)
219
+ })