@zofai/zo-sdk 0.1.92
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/.claude/settings.local.json +9 -0
- package/.gitattributes +4 -0
- package/.prettierrc.js +9 -0
- package/README.md +28 -0
- package/dist/abstract/BaseAPI.cjs +206 -0
- package/dist/abstract/BaseAPI.cjs.map +1 -0
- package/dist/abstract/BaseAPI.d.cts +172 -0
- package/dist/abstract/BaseAPI.d.cts.map +1 -0
- package/dist/abstract/BaseAPI.d.mts +172 -0
- package/dist/abstract/BaseAPI.d.mts.map +1 -0
- package/dist/abstract/BaseAPI.mjs +202 -0
- package/dist/abstract/BaseAPI.mjs.map +1 -0
- package/dist/abstract/BaseDataAPI.cjs +140 -0
- package/dist/abstract/BaseDataAPI.cjs.map +1 -0
- package/dist/abstract/BaseDataAPI.d.cts +89 -0
- package/dist/abstract/BaseDataAPI.d.cts.map +1 -0
- package/dist/abstract/BaseDataAPI.d.mts +89 -0
- package/dist/abstract/BaseDataAPI.d.mts.map +1 -0
- package/dist/abstract/BaseDataAPI.mjs +136 -0
- package/dist/abstract/BaseDataAPI.mjs.map +1 -0
- package/dist/abstract/index.cjs +12 -0
- package/dist/abstract/index.cjs.map +1 -0
- package/dist/abstract/index.d.cts +7 -0
- package/dist/abstract/index.d.cts.map +1 -0
- package/dist/abstract/index.d.mts +7 -0
- package/dist/abstract/index.d.mts.map +1 -0
- package/dist/abstract/index.mjs +7 -0
- package/dist/abstract/index.mjs.map +1 -0
- package/dist/api.cjs +779 -0
- package/dist/api.cjs.map +1 -0
- package/dist/api.d.cts +75 -0
- package/dist/api.d.cts.map +1 -0
- package/dist/api.d.mts +75 -0
- package/dist/api.d.mts.map +1 -0
- package/dist/api.mjs +775 -0
- package/dist/api.mjs.map +1 -0
- package/dist/bcs.cjs +42 -0
- package/dist/bcs.cjs.map +1 -0
- package/dist/bcs.d.cts +91 -0
- package/dist/bcs.d.cts.map +1 -0
- package/dist/bcs.d.mts +91 -0
- package/dist/bcs.d.mts.map +1 -0
- package/dist/bcs.mjs +39 -0
- package/dist/bcs.mjs.map +1 -0
- package/dist/consts/deployments-shared-mainnet.json +50 -0
- package/dist/consts/deployments-shared-testnet.json +45 -0
- package/dist/consts/deployments-slp-mainnet.json +600 -0
- package/dist/consts/deployments-slp-testnet.json +87 -0
- package/dist/consts/deployments-usdz-mainnet.json +494 -0
- package/dist/consts/deployments-usdz-testnet.json +98 -0
- package/dist/consts/deployments-zbtcvc-mainnet.json +180 -0
- package/dist/consts/deployments-zlp-mainnet.json +791 -0
- package/dist/consts/deployments-zlp-testnet.json +76 -0
- package/dist/consts/index.cjs +200 -0
- package/dist/consts/index.cjs.map +1 -0
- package/dist/consts/index.d.cts +157 -0
- package/dist/consts/index.d.cts.map +1 -0
- package/dist/consts/index.d.mts +157 -0
- package/dist/consts/index.d.mts.map +1 -0
- package/dist/consts/index.mjs +189 -0
- package/dist/consts/index.mjs.map +1 -0
- package/dist/consts/price_id_to_object_id.mainnet.json +56 -0
- package/dist/consts/price_id_to_object_id.testnet.json +17 -0
- package/dist/data.cjs +919 -0
- package/dist/data.cjs.map +1 -0
- package/dist/data.d.cts +235 -0
- package/dist/data.d.cts.map +1 -0
- package/dist/data.d.mts +235 -0
- package/dist/data.d.mts.map +1 -0
- package/dist/data.mjs +915 -0
- package/dist/data.mjs.map +1 -0
- package/dist/factory/SDKFactory.cjs +228 -0
- package/dist/factory/SDKFactory.cjs.map +1 -0
- package/dist/factory/SDKFactory.d.cts +84 -0
- package/dist/factory/SDKFactory.d.cts.map +1 -0
- package/dist/factory/SDKFactory.d.mts +84 -0
- package/dist/factory/SDKFactory.d.mts.map +1 -0
- package/dist/factory/SDKFactory.mjs +222 -0
- package/dist/factory/SDKFactory.mjs.map +1 -0
- package/dist/implementations/SLPAPI.cjs +1794 -0
- package/dist/implementations/SLPAPI.cjs.map +1 -0
- package/dist/implementations/SLPAPI.d.cts +183 -0
- package/dist/implementations/SLPAPI.d.cts.map +1 -0
- package/dist/implementations/SLPAPI.d.mts +183 -0
- package/dist/implementations/SLPAPI.d.mts.map +1 -0
- package/dist/implementations/SLPAPI.mjs +1790 -0
- package/dist/implementations/SLPAPI.mjs.map +1 -0
- package/dist/implementations/SLPDataAPI.cjs +1384 -0
- package/dist/implementations/SLPDataAPI.cjs.map +1 -0
- package/dist/implementations/SLPDataAPI.d.cts +158 -0
- package/dist/implementations/SLPDataAPI.d.cts.map +1 -0
- package/dist/implementations/SLPDataAPI.d.mts +158 -0
- package/dist/implementations/SLPDataAPI.d.mts.map +1 -0
- package/dist/implementations/SLPDataAPI.mjs +1380 -0
- package/dist/implementations/SLPDataAPI.mjs.map +1 -0
- package/dist/implementations/USDZAPI.cjs +1676 -0
- package/dist/implementations/USDZAPI.cjs.map +1 -0
- package/dist/implementations/USDZAPI.d.cts +180 -0
- package/dist/implementations/USDZAPI.d.cts.map +1 -0
- package/dist/implementations/USDZAPI.d.mts +180 -0
- package/dist/implementations/USDZAPI.d.mts.map +1 -0
- package/dist/implementations/USDZAPI.mjs +1672 -0
- package/dist/implementations/USDZAPI.mjs.map +1 -0
- package/dist/implementations/USDZDataAPI.cjs +1209 -0
- package/dist/implementations/USDZDataAPI.cjs.map +1 -0
- package/dist/implementations/USDZDataAPI.d.cts +191 -0
- package/dist/implementations/USDZDataAPI.d.cts.map +1 -0
- package/dist/implementations/USDZDataAPI.d.mts +191 -0
- package/dist/implementations/USDZDataAPI.d.mts.map +1 -0
- package/dist/implementations/USDZDataAPI.mjs +1205 -0
- package/dist/implementations/USDZDataAPI.mjs.map +1 -0
- package/dist/implementations/ZBTCVCAPI.cjs +906 -0
- package/dist/implementations/ZBTCVCAPI.cjs.map +1 -0
- package/dist/implementations/ZBTCVCAPI.d.cts +107 -0
- package/dist/implementations/ZBTCVCAPI.d.cts.map +1 -0
- package/dist/implementations/ZBTCVCAPI.d.mts +107 -0
- package/dist/implementations/ZBTCVCAPI.d.mts.map +1 -0
- package/dist/implementations/ZBTCVCAPI.mjs +902 -0
- package/dist/implementations/ZBTCVCAPI.mjs.map +1 -0
- package/dist/implementations/ZBTCVCDataAPI.cjs +829 -0
- package/dist/implementations/ZBTCVCDataAPI.cjs.map +1 -0
- package/dist/implementations/ZBTCVCDataAPI.d.cts +94 -0
- package/dist/implementations/ZBTCVCDataAPI.d.cts.map +1 -0
- package/dist/implementations/ZBTCVCDataAPI.d.mts +94 -0
- package/dist/implementations/ZBTCVCDataAPI.d.mts.map +1 -0
- package/dist/implementations/ZBTCVCDataAPI.mjs +825 -0
- package/dist/implementations/ZBTCVCDataAPI.mjs.map +1 -0
- package/dist/implementations/ZLPAPI.cjs +1948 -0
- package/dist/implementations/ZLPAPI.cjs.map +1 -0
- package/dist/implementations/ZLPAPI.d.cts +192 -0
- package/dist/implementations/ZLPAPI.d.cts.map +1 -0
- package/dist/implementations/ZLPAPI.d.mts +192 -0
- package/dist/implementations/ZLPAPI.d.mts.map +1 -0
- package/dist/implementations/ZLPAPI.mjs +1944 -0
- package/dist/implementations/ZLPAPI.mjs.map +1 -0
- package/dist/implementations/ZLPDataAPI.cjs +1267 -0
- package/dist/implementations/ZLPDataAPI.cjs.map +1 -0
- package/dist/implementations/ZLPDataAPI.d.cts +193 -0
- package/dist/implementations/ZLPDataAPI.d.cts.map +1 -0
- package/dist/implementations/ZLPDataAPI.d.mts +193 -0
- package/dist/implementations/ZLPDataAPI.d.mts.map +1 -0
- package/dist/implementations/ZLPDataAPI.mjs +1263 -0
- package/dist/implementations/ZLPDataAPI.mjs.map +1 -0
- package/dist/implementations/index.cjs +26 -0
- package/dist/implementations/index.cjs.map +1 -0
- package/dist/implementations/index.d.cts +13 -0
- package/dist/implementations/index.d.cts.map +1 -0
- package/dist/implementations/index.d.mts +13 -0
- package/dist/implementations/index.d.mts.map +1 -0
- package/dist/implementations/index.mjs +15 -0
- package/dist/implementations/index.mjs.map +1 -0
- package/dist/index.cjs +69 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +51 -0
- package/dist/index.d.cts.map +1 -0
- package/dist/index.d.mts +51 -0
- package/dist/index.d.mts.map +1 -0
- package/dist/index.mjs +51 -0
- package/dist/index.mjs.map +1 -0
- package/dist/interfaces/base.cjs +7 -0
- package/dist/interfaces/base.cjs.map +1 -0
- package/dist/interfaces/base.d.cts +346 -0
- package/dist/interfaces/base.d.cts.map +1 -0
- package/dist/interfaces/base.d.mts +346 -0
- package/dist/interfaces/base.d.mts.map +1 -0
- package/dist/interfaces/base.mjs +6 -0
- package/dist/interfaces/base.mjs.map +1 -0
- package/dist/interfaces/index.cjs +31 -0
- package/dist/interfaces/index.cjs.map +1 -0
- package/dist/interfaces/index.d.cts +15 -0
- package/dist/interfaces/index.d.cts.map +1 -0
- package/dist/interfaces/index.d.mts +15 -0
- package/dist/interfaces/index.d.mts.map +1 -0
- package/dist/interfaces/index.mjs +15 -0
- package/dist/interfaces/index.mjs.map +1 -0
- package/dist/interfaces/slp.cjs +7 -0
- package/dist/interfaces/slp.cjs.map +1 -0
- package/dist/interfaces/slp.d.cts +179 -0
- package/dist/interfaces/slp.d.cts.map +1 -0
- package/dist/interfaces/slp.d.mts +179 -0
- package/dist/interfaces/slp.d.mts.map +1 -0
- package/dist/interfaces/slp.mjs +6 -0
- package/dist/interfaces/slp.mjs.map +1 -0
- package/dist/interfaces/usdz.cjs +7 -0
- package/dist/interfaces/usdz.cjs.map +1 -0
- package/dist/interfaces/usdz.d.cts +104 -0
- package/dist/interfaces/usdz.d.cts.map +1 -0
- package/dist/interfaces/usdz.d.mts +104 -0
- package/dist/interfaces/usdz.d.mts.map +1 -0
- package/dist/interfaces/usdz.mjs +6 -0
- package/dist/interfaces/usdz.mjs.map +1 -0
- package/dist/interfaces/zbtcvc.cjs +7 -0
- package/dist/interfaces/zbtcvc.cjs.map +1 -0
- package/dist/interfaces/zbtcvc.d.cts +64 -0
- package/dist/interfaces/zbtcvc.d.cts.map +1 -0
- package/dist/interfaces/zbtcvc.d.mts +64 -0
- package/dist/interfaces/zbtcvc.d.mts.map +1 -0
- package/dist/interfaces/zbtcvc.mjs +6 -0
- package/dist/interfaces/zbtcvc.mjs.map +1 -0
- package/dist/interfaces/zlp.cjs +7 -0
- package/dist/interfaces/zlp.cjs.map +1 -0
- package/dist/interfaces/zlp.d.cts +114 -0
- package/dist/interfaces/zlp.d.cts.map +1 -0
- package/dist/interfaces/zlp.d.mts +114 -0
- package/dist/interfaces/zlp.d.mts.map +1 -0
- package/dist/interfaces/zlp.mjs +6 -0
- package/dist/interfaces/zlp.mjs.map +1 -0
- package/dist/oracle.cjs +118 -0
- package/dist/oracle.cjs.map +1 -0
- package/dist/oracle.d.cts +25 -0
- package/dist/oracle.d.cts.map +1 -0
- package/dist/oracle.d.mts +25 -0
- package/dist/oracle.d.mts.map +1 -0
- package/dist/oracle.mjs +114 -0
- package/dist/oracle.mjs.map +1 -0
- package/dist/utils.cjs +129 -0
- package/dist/utils.cjs.map +1 -0
- package/dist/utils.d.cts +44 -0
- package/dist/utils.d.cts.map +1 -0
- package/dist/utils.d.mts +44 -0
- package/dist/utils.d.mts.map +1 -0
- package/dist/utils.mjs +115 -0
- package/dist/utils.mjs.map +1 -0
- package/docs/SUMMARY.md +10 -0
- package/docs/api-reference.md +32 -0
- package/docs/architecture.md +14 -0
- package/docs/common-operations.md +52 -0
- package/docs/error-handling.md +17 -0
- package/docs/getting-started.md +60 -0
- package/docs/introduction.md +15 -0
- package/docs/lp-specific-features.md +96 -0
- package/docs/type-safety.md +29 -0
- package/eslint.config.mjs +18 -0
- package/package.json +42 -0
- package/src/abstract/BaseAPI.ts +575 -0
- package/src/abstract/BaseDataAPI.ts +207 -0
- package/src/abstract/index.ts +7 -0
- package/src/api.ts +1100 -0
- package/src/bcs.ts +45 -0
- package/src/consts/deployments-shared-mainnet.json +50 -0
- package/src/consts/deployments-shared-testnet.json +45 -0
- package/src/consts/deployments-slp-mainnet.json +600 -0
- package/src/consts/deployments-slp-testnet.json +87 -0
- package/src/consts/deployments-usdz-mainnet.json +494 -0
- package/src/consts/deployments-usdz-testnet.json +98 -0
- package/src/consts/deployments-zbtcvc-mainnet.json +180 -0
- package/src/consts/deployments-zlp-mainnet.json +791 -0
- package/src/consts/deployments-zlp-testnet.json +76 -0
- package/src/consts/index.ts +345 -0
- package/src/consts/price_id_to_object_id.mainnet.json +56 -0
- package/src/consts/price_id_to_object_id.testnet.json +17 -0
- package/src/data.ts +1279 -0
- package/src/factory/SDKFactory.ts +340 -0
- package/src/implementations/SLPAPI.ts +2722 -0
- package/src/implementations/SLPDataAPI.ts +1839 -0
- package/src/implementations/USDZAPI.ts +2488 -0
- package/src/implementations/USDZDataAPI.ts +1548 -0
- package/src/implementations/ZBTCVCAPI.ts +1337 -0
- package/src/implementations/ZBTCVCDataAPI.ts +993 -0
- package/src/implementations/ZLPAPI.ts +2888 -0
- package/src/implementations/ZLPDataAPI.ts +1603 -0
- package/src/implementations/index.ts +16 -0
- package/src/index.ts +58 -0
- package/src/interfaces/base.ts +838 -0
- package/src/interfaces/index.ts +50 -0
- package/src/interfaces/slp.ts +268 -0
- package/src/interfaces/usdz.ts +181 -0
- package/src/interfaces/zbtcvc.ts +116 -0
- package/src/interfaces/zlp.ts +244 -0
- package/src/oracle.ts +153 -0
- package/src/utils.ts +168 -0
- package/tests/api.test.ts +219 -0
- package/tests/data.test.ts +156 -0
- package/tests/oracle.test.ts +33 -0
- package/tsconfig.json +22 -0
|
@@ -0,0 +1,1548 @@
|
|
|
1
|
+
/* eslint-disable no-await-in-loop */
|
|
2
|
+
/**
|
|
3
|
+
* USDZ DataAPI implementation
|
|
4
|
+
* Implements USDZ-specific data access methods
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { DynamicFieldInfo, SuiClient } from '@mysten/sui/client'
|
|
8
|
+
import type { Transaction } from '@mysten/sui/transactions'
|
|
9
|
+
import { SUI_CLOCK_OBJECT_ID } from '@mysten/sui/utils'
|
|
10
|
+
|
|
11
|
+
import { BaseDataAPI } from '../abstract'
|
|
12
|
+
import type { Network } from '../consts'
|
|
13
|
+
import { LPToken, SECONDS_PER_EIGHT_HOUR, USDZ_TOKEN_DECIMALS } from '../consts'
|
|
14
|
+
import type {
|
|
15
|
+
IBaseHistoryResponse,
|
|
16
|
+
IBaseOrderType,
|
|
17
|
+
IBaseStaked,
|
|
18
|
+
IUSDZCredential,
|
|
19
|
+
IUSDZDataAPI,
|
|
20
|
+
IUSDZFundingFeeModel,
|
|
21
|
+
IUSDZMarketInfo,
|
|
22
|
+
IUSDZMarketValuationInfo,
|
|
23
|
+
IUSDZOiFundingModel,
|
|
24
|
+
IUSDZOiFundingState,
|
|
25
|
+
IUSDZOrderCapInfo,
|
|
26
|
+
IUSDZOrderInfo,
|
|
27
|
+
IUSDZPositionCapInfo,
|
|
28
|
+
IUSDZPositionConfig,
|
|
29
|
+
IUSDZPositionInfo,
|
|
30
|
+
IUSDZPriceImpactConfig,
|
|
31
|
+
IUSDZRebaseFeeModel,
|
|
32
|
+
IUSDZReservingFeeModel,
|
|
33
|
+
IUSDZStakePool,
|
|
34
|
+
IUSDZSymbolConfig,
|
|
35
|
+
IUSDZSymbolInfo,
|
|
36
|
+
IUSDZVaultInfo,
|
|
37
|
+
} from '../interfaces'
|
|
38
|
+
import { joinSymbol, parseSymbolKey, parseValue, suiSymbolToSymbol } from '../utils'
|
|
39
|
+
|
|
40
|
+
export class USDZDataAPI extends BaseDataAPI implements IUSDZDataAPI {
|
|
41
|
+
constructor(
|
|
42
|
+
network: Network,
|
|
43
|
+
provider: SuiClient,
|
|
44
|
+
apiEndpoint: string,
|
|
45
|
+
connectionURL: string,
|
|
46
|
+
) {
|
|
47
|
+
super(network, provider, apiEndpoint, connectionURL, LPToken.USDZ)
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
public async getStaked(owner: string): Promise<IBaseStaked> {
|
|
51
|
+
let rawCredentialsData: any[] = []
|
|
52
|
+
let queryNextPage = true
|
|
53
|
+
let queryCursor: string | undefined | null
|
|
54
|
+
|
|
55
|
+
const limit = 50
|
|
56
|
+
|
|
57
|
+
while (queryNextPage) {
|
|
58
|
+
const { data, hasNextPage, nextCursor } = await this.provider.getOwnedObjects({
|
|
59
|
+
owner,
|
|
60
|
+
filter: {
|
|
61
|
+
MoveModule: {
|
|
62
|
+
package: this.sharedConfig.zoStaking.package,
|
|
63
|
+
module: 'pool',
|
|
64
|
+
},
|
|
65
|
+
},
|
|
66
|
+
options: {
|
|
67
|
+
showType: true,
|
|
68
|
+
showContent: true,
|
|
69
|
+
},
|
|
70
|
+
cursor: queryCursor,
|
|
71
|
+
limit,
|
|
72
|
+
})
|
|
73
|
+
|
|
74
|
+
queryNextPage = hasNextPage
|
|
75
|
+
queryCursor = nextCursor!
|
|
76
|
+
if (!data)
|
|
77
|
+
break
|
|
78
|
+
rawCredentialsData = [...rawCredentialsData, ...data]
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const pool = await this.getStakePool()
|
|
82
|
+
|
|
83
|
+
const credentials = rawCredentialsData
|
|
84
|
+
.filter(
|
|
85
|
+
(item: any) =>
|
|
86
|
+
item.data.type
|
|
87
|
+
=== `${this.sharedConfig.zoStaking.package}::pool::Credential<${this.consts.zoCore.package}::usdz::USDZ, 0x2::sui::SUI>`,
|
|
88
|
+
)
|
|
89
|
+
.map((item: any) =>
|
|
90
|
+
USDZDataAPI.parseCredential(item, pool),
|
|
91
|
+
)
|
|
92
|
+
return {
|
|
93
|
+
credentials,
|
|
94
|
+
amount: credentials.reduce(
|
|
95
|
+
(acc: bigint, cur: IUSDZCredential) => acc + cur.amount,
|
|
96
|
+
BigInt(0),
|
|
97
|
+
),
|
|
98
|
+
claimable: credentials.reduce(
|
|
99
|
+
(acc: bigint, cur: IUSDZCredential) => acc + cur.claimable,
|
|
100
|
+
BigInt(0),
|
|
101
|
+
),
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
public async getStakePool(): Promise<IUSDZStakePool> {
|
|
106
|
+
const poolId = this.sharedConfig.zoStaking.pools.usdz
|
|
107
|
+
const raw = await this.provider.getObject({
|
|
108
|
+
id: poolId,
|
|
109
|
+
options: {
|
|
110
|
+
showContent: true,
|
|
111
|
+
},
|
|
112
|
+
})
|
|
113
|
+
return USDZDataAPI.parseStakePool(raw)
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Creates vaults valuation for USDZ
|
|
118
|
+
*/
|
|
119
|
+
public valuateVaults(tx: Transaction) {
|
|
120
|
+
const vaultsValuation = tx.moveCall({
|
|
121
|
+
target: `${this.consts.zoCore.upgradedPackage}::market::create_vaults_valuation`,
|
|
122
|
+
typeArguments: [`${this.consts.zoCore.package}::usdz::USDZ`],
|
|
123
|
+
arguments: [
|
|
124
|
+
tx.object(SUI_CLOCK_OBJECT_ID),
|
|
125
|
+
tx.object(this.consts.zoCore.market),
|
|
126
|
+
],
|
|
127
|
+
})
|
|
128
|
+
|
|
129
|
+
for (const key of Object.keys(this.consts.zoCore.vaults)) {
|
|
130
|
+
const vault = this.consts.zoCore.vaults[key]
|
|
131
|
+
tx.moveCall({
|
|
132
|
+
target: `${this.consts.zoCore.upgradedPackage}::market::valuate_vault`,
|
|
133
|
+
typeArguments: [
|
|
134
|
+
`${this.consts.zoCore.package}::usdz::USDZ`,
|
|
135
|
+
this.consts.coins[key].module,
|
|
136
|
+
],
|
|
137
|
+
arguments: [
|
|
138
|
+
tx.object(this.consts.zoCore.market),
|
|
139
|
+
tx.object(vault.reservingFeeModel),
|
|
140
|
+
tx.object(this.consts.pythFeeder.feeder[key]),
|
|
141
|
+
vaultsValuation,
|
|
142
|
+
],
|
|
143
|
+
})
|
|
144
|
+
}
|
|
145
|
+
return vaultsValuation
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Creates symbols valuation for USDZ
|
|
150
|
+
*/
|
|
151
|
+
public valuateSymbols(tx: Transaction) {
|
|
152
|
+
const symbolsValuation = tx.moveCall({
|
|
153
|
+
target: `${this.consts.zoCore.upgradedPackage}::market::create_symbols_valuation`,
|
|
154
|
+
typeArguments: [`${this.consts.zoCore.package}::usdz::USDZ`],
|
|
155
|
+
arguments: [
|
|
156
|
+
tx.object(SUI_CLOCK_OBJECT_ID),
|
|
157
|
+
tx.object(this.consts.zoCore.market),
|
|
158
|
+
],
|
|
159
|
+
})
|
|
160
|
+
|
|
161
|
+
for (const key of Object.keys(this.consts.zoCore.symbols)) {
|
|
162
|
+
const [direction, token] = parseSymbolKey(key)
|
|
163
|
+
const symbol = this.consts.zoCore.symbols[key]
|
|
164
|
+
tx.moveCall({
|
|
165
|
+
target: `${this.consts.zoCore.upgradedPackage}::market::valuate_symbol`,
|
|
166
|
+
typeArguments: [
|
|
167
|
+
`${this.consts.zoCore.package}::usdz::USDZ`,
|
|
168
|
+
this.consts.coins[token].module,
|
|
169
|
+
`${this.consts.zoCore.package}::market::${direction.toUpperCase()}`,
|
|
170
|
+
],
|
|
171
|
+
arguments: [
|
|
172
|
+
tx.object(this.consts.zoCore.market),
|
|
173
|
+
tx.object(symbol.fundingFeeModel),
|
|
174
|
+
tx.object(this.consts.pythFeeder.feeder[token]),
|
|
175
|
+
symbolsValuation,
|
|
176
|
+
],
|
|
177
|
+
})
|
|
178
|
+
}
|
|
179
|
+
return symbolsValuation
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Creates both vaults and symbols valuation
|
|
184
|
+
*/
|
|
185
|
+
public valuate(tx: Transaction) {
|
|
186
|
+
const vaultsValuation = this.valuateVaults(tx)
|
|
187
|
+
const symbolsValuation = this.valuateSymbols(tx)
|
|
188
|
+
return { vaultsValuation, symbolsValuation }
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* Valuates the USDZ market
|
|
193
|
+
*/
|
|
194
|
+
public async valuateMarket(): Promise<IUSDZMarketValuationInfo> {
|
|
195
|
+
const marketInfo = await this.getMarketInfo()
|
|
196
|
+
let usdzPrice = 0
|
|
197
|
+
let value = 0
|
|
198
|
+
|
|
199
|
+
const vaultPromises = Object.keys(this.consts.zoCore.vaults).map(async (vault) => {
|
|
200
|
+
const vaultInfo = await this.getVaultInfo(vault)
|
|
201
|
+
const reservingFeeDelta = USDZDataAPI.calculateVaultReservingFee(vaultInfo, vaultInfo.reservingFeeModel, Date.now() / 1000)
|
|
202
|
+
return (reservingFeeDelta + vaultInfo.liquidity + vaultInfo.reservedAmount) * (await this.getOraclePrice(vault)).getPriceUnchecked().getPriceAsNumberUnchecked() / (10 ** this.consts.coins[vault].decimals)
|
|
203
|
+
})
|
|
204
|
+
|
|
205
|
+
const symbolPromises = Object.keys(this.consts.zoCore.symbols).map(async (symbol) => {
|
|
206
|
+
const [direction, tokenId] = parseSymbolKey(symbol)
|
|
207
|
+
const symbolInfo = await this.getSymbolInfo(tokenId, direction === 'long')
|
|
208
|
+
const deltaSize = USDZDataAPI.calcDeltaSize(
|
|
209
|
+
symbolInfo,
|
|
210
|
+
(await this.getOraclePrice(tokenId)).getPriceUnchecked().getPriceAsNumberUnchecked(),
|
|
211
|
+
)
|
|
212
|
+
|
|
213
|
+
// OI context for funding fee delta
|
|
214
|
+
const oiState = await this.getSymbolOiFundingState(tokenId)
|
|
215
|
+
const pairedSymbol = await this.getSymbolInfo(tokenId, !(direction === 'long'))
|
|
216
|
+
|
|
217
|
+
const fundingFeeDelta = USDZDataAPI.calculateSymbolFundingFee(
|
|
218
|
+
symbolInfo,
|
|
219
|
+
symbolInfo.fundingFeeModel,
|
|
220
|
+
(await this.getOraclePrice(tokenId)).getPriceUnchecked().getPriceAsNumberUnchecked(),
|
|
221
|
+
marketInfo.lpSupplyWithDecimals,
|
|
222
|
+
Date.now() / 1000,
|
|
223
|
+
oiState && oiState.enabled ? oiState.model : undefined,
|
|
224
|
+
pairedSymbol.openingSize,
|
|
225
|
+
)
|
|
226
|
+
|
|
227
|
+
return fundingFeeDelta + deltaSize
|
|
228
|
+
})
|
|
229
|
+
|
|
230
|
+
const [vaultValues, symbolValues] = await Promise.all([Promise.all(vaultPromises), Promise.all(symbolPromises)])
|
|
231
|
+
|
|
232
|
+
value = vaultValues.reduce((acc: number, curr: number) => acc + curr, 0)
|
|
233
|
+
value += symbolValues.reduce((acc: number, curr: number) => acc + curr, 0)
|
|
234
|
+
|
|
235
|
+
usdzPrice = value / marketInfo.lpSupplyWithDecimals
|
|
236
|
+
|
|
237
|
+
return {
|
|
238
|
+
marketCap: value,
|
|
239
|
+
price: usdzPrice,
|
|
240
|
+
supply: marketInfo.lpSupplyWithDecimals,
|
|
241
|
+
apr: Number(marketInfo.apr),
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* Gets USDZ market information
|
|
247
|
+
*/
|
|
248
|
+
public async getMarketInfo(): Promise<IUSDZMarketInfo> {
|
|
249
|
+
this.validateCache()
|
|
250
|
+
if (this.marketInfoCache) {
|
|
251
|
+
return this.marketInfoCache
|
|
252
|
+
}
|
|
253
|
+
const rawData = await this.provider.getObject({
|
|
254
|
+
id: this.consts.zoCore.market,
|
|
255
|
+
options: {
|
|
256
|
+
showContent: true,
|
|
257
|
+
},
|
|
258
|
+
})
|
|
259
|
+
return USDZDataAPI.parseMarketInfo(rawData)
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
/**
|
|
263
|
+
* Gets USDZ vault information
|
|
264
|
+
*/
|
|
265
|
+
public async getVaultInfo(vaultToken: string): Promise<IUSDZVaultInfo> {
|
|
266
|
+
this.validateCache()
|
|
267
|
+
if (this.vaultInfoCache[vaultToken]) {
|
|
268
|
+
return this.vaultInfoCache[vaultToken]
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
const rawData = await this.provider.getDynamicFieldObject({
|
|
272
|
+
parentId: this.consts.zoCore.vaultsParent,
|
|
273
|
+
name: {
|
|
274
|
+
type: `${this.consts.zoCore.package}::market::VaultName<${this.consts.coins[vaultToken].module}>`,
|
|
275
|
+
value: { dummy_field: false },
|
|
276
|
+
},
|
|
277
|
+
})
|
|
278
|
+
return await this.parseVaultInfo(rawData)
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
/**
|
|
282
|
+
* Gets USDZ symbol information
|
|
283
|
+
*/
|
|
284
|
+
public async getSymbolInfo(indexToken: string, long: boolean): Promise<IUSDZSymbolInfo> {
|
|
285
|
+
this.validateCache()
|
|
286
|
+
const symbol = joinSymbol(long ? 'long' : 'short', indexToken)
|
|
287
|
+
if (this.symbolInfoCache[symbol]) {
|
|
288
|
+
return this.symbolInfoCache[symbol]
|
|
289
|
+
}
|
|
290
|
+
const rawData = await this.provider.getDynamicFieldObject({
|
|
291
|
+
parentId: this.consts.zoCore.symbolsParent,
|
|
292
|
+
name: {
|
|
293
|
+
type: `${this.consts.zoCore.package}::market::SymbolName<${this.consts.coins[indexToken].module}, ${this.consts.zoCore.package}::market::${long ? 'LONG' : 'SHORT'}>`,
|
|
294
|
+
value: { dummy_field: false },
|
|
295
|
+
},
|
|
296
|
+
})
|
|
297
|
+
return await this.parseSymbolInfo(rawData, long)
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
public async getPositionConfig(indexToken: string, long: boolean): Promise<IUSDZPositionConfig> {
|
|
301
|
+
this.validateCache()
|
|
302
|
+
const symbol = joinSymbol(long ? 'long' : 'short', indexToken)
|
|
303
|
+
if (this.positionConfigCache[symbol]) {
|
|
304
|
+
return this.positionConfigCache[symbol]
|
|
305
|
+
}
|
|
306
|
+
const rawData = await this.provider.getObject({
|
|
307
|
+
id: this.consts.zoCore.symbols[symbol].positionConfig,
|
|
308
|
+
options: {
|
|
309
|
+
showContent: true,
|
|
310
|
+
},
|
|
311
|
+
})
|
|
312
|
+
return USDZDataAPI.parsePositionConfig(rawData)
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
/**
|
|
316
|
+
* Gets USDZ symbol configuration
|
|
317
|
+
*/
|
|
318
|
+
public async getSymbolConfig(indexToken: string, long: boolean): Promise<IUSDZSymbolConfig | null> {
|
|
319
|
+
this.validateCache()
|
|
320
|
+
try {
|
|
321
|
+
const rawData = await this.provider.getDynamicFieldObject({
|
|
322
|
+
parentId: this.consts.zoCore.market,
|
|
323
|
+
name: {
|
|
324
|
+
type: `${this.consts.zoCore.package}::market::SymbolName<${this.consts.coins[indexToken].module}, ${this.consts.zoCore.package}::market::${long ? 'LONG' : 'SHORT'}>`,
|
|
325
|
+
value: { dummy_field: false },
|
|
326
|
+
},
|
|
327
|
+
})
|
|
328
|
+
return USDZDataAPI.parseSymbolConfig(rawData)
|
|
329
|
+
}
|
|
330
|
+
catch {
|
|
331
|
+
// If the dynamic field doesn't exist, return null
|
|
332
|
+
console.error('Symbol Config Not Found')
|
|
333
|
+
return null
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
/**
|
|
338
|
+
* Gets USDZ symbol OI funding state (global per symbol)
|
|
339
|
+
*/
|
|
340
|
+
public async getSymbolOiFundingState(indexToken: string): Promise<IUSDZOiFundingState | null> {
|
|
341
|
+
this.validateCache()
|
|
342
|
+
try {
|
|
343
|
+
const rawData = await this.provider.getDynamicFieldObject({
|
|
344
|
+
parentId: this.consts.zoCore.market,
|
|
345
|
+
name: {
|
|
346
|
+
type: `0x562c9969f4c4046b33437c80c1fcdb389e8113d43de5b97c29a10b52db8f3287::funding::FundingStateKey<${this.consts.coins[indexToken].module}>`,
|
|
347
|
+
value: { dummy_field: false },
|
|
348
|
+
},
|
|
349
|
+
})
|
|
350
|
+
return USDZDataAPI.parseOiFundingState(rawData)
|
|
351
|
+
}
|
|
352
|
+
catch (e: any) {
|
|
353
|
+
console.error('Error Fetching USDZ Symbol OI Funding State:', e)
|
|
354
|
+
return null
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
/**
|
|
359
|
+
* Gets price impact configuration for a symbol.
|
|
360
|
+
* Price impact config applies to both long and short directions for the same index token.
|
|
361
|
+
*/
|
|
362
|
+
public async getPriceImpactConfig(indexToken: string): Promise<IUSDZPriceImpactConfig | null> {
|
|
363
|
+
this.validateCache()
|
|
364
|
+
try {
|
|
365
|
+
const rawData = await this.provider.getDynamicFieldObject({
|
|
366
|
+
parentId: this.consts.zoCore.market,
|
|
367
|
+
name: {
|
|
368
|
+
type: `0x28405237f444c214c03ba32778a2e1716c3311bbe517b9f216b0d83778c36729::price_impact::PriceImpactConfigKey<${this.consts.coins[indexToken].module}>`,
|
|
369
|
+
value: { dummy_field: false },
|
|
370
|
+
},
|
|
371
|
+
})
|
|
372
|
+
return USDZDataAPI.parsePriceImpactConfig(rawData)
|
|
373
|
+
}
|
|
374
|
+
catch (e: any) {
|
|
375
|
+
// If the dynamic field doesn't exist, return null (price impact not configured for this symbol)
|
|
376
|
+
console.error('Error Fetching Price Impact Config:', e)
|
|
377
|
+
return null
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
public async getPositionCapInfoList(owner: string): Promise<IUSDZPositionCapInfo[]> {
|
|
382
|
+
const positionCapInfoList: IUSDZPositionCapInfo[] = []
|
|
383
|
+
let cursor: string | undefined | null
|
|
384
|
+
let hasNextPage = true
|
|
385
|
+
|
|
386
|
+
while (hasNextPage) {
|
|
387
|
+
const positionCaps = await this.provider.getOwnedObjects({
|
|
388
|
+
owner,
|
|
389
|
+
filter: {
|
|
390
|
+
MoveModule: {
|
|
391
|
+
package: this.consts.zoCore.package,
|
|
392
|
+
module: 'market',
|
|
393
|
+
},
|
|
394
|
+
},
|
|
395
|
+
options: {
|
|
396
|
+
showType: true,
|
|
397
|
+
},
|
|
398
|
+
cursor,
|
|
399
|
+
})
|
|
400
|
+
|
|
401
|
+
for (const positionCap of positionCaps.data) {
|
|
402
|
+
if (positionCap.data?.type?.includes('PositionCap')) {
|
|
403
|
+
positionCapInfoList.push({
|
|
404
|
+
positionCapId: positionCap.data.objectId,
|
|
405
|
+
symbol0: positionCap.data.type.split('<')[1].split(',')[0].trim(),
|
|
406
|
+
symbol1: positionCap.data.type.split('<')[1].split(',')[1].split(',')[0].trim(),
|
|
407
|
+
long: positionCap.data.type.includes('LONG'),
|
|
408
|
+
})
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
// If we don't want to fetch all pages or there are no more pages, break the loop
|
|
413
|
+
hasNextPage = positionCaps.hasNextPage
|
|
414
|
+
cursor = positionCaps.nextCursor
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
return positionCapInfoList
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
public async getPositionInfoList(positionCapInfoList: IUSDZPositionCapInfo[], owner: string, batchSize = 10) {
|
|
421
|
+
const positionInfoList: IUSDZPositionInfo[] = []
|
|
422
|
+
|
|
423
|
+
// Process in batches of 10
|
|
424
|
+
for (let i = 0; i < positionCapInfoList.length; i += batchSize) {
|
|
425
|
+
const batch = positionCapInfoList.slice(i, i + batchSize)
|
|
426
|
+
|
|
427
|
+
await Promise.all(batch.map(async (positionCapInfo) => {
|
|
428
|
+
try {
|
|
429
|
+
const positionRaw = await this.provider.getDynamicFieldObject({
|
|
430
|
+
parentId: this.consts.zoCore.positionsParent,
|
|
431
|
+
name: {
|
|
432
|
+
type: `${this.consts.zoCore.package}::market::PositionName<${positionCapInfo.symbol0}, ${positionCapInfo.symbol1}, ${this.consts.zoCore.package}::market::${positionCapInfo.long ? 'LONG' : 'SHORT'}>`,
|
|
433
|
+
value: {
|
|
434
|
+
owner,
|
|
435
|
+
id: positionCapInfo.positionCapId,
|
|
436
|
+
},
|
|
437
|
+
},
|
|
438
|
+
})
|
|
439
|
+
positionInfoList.push(await this.parsePositionInfo(positionRaw, positionCapInfo.positionCapId))
|
|
440
|
+
}
|
|
441
|
+
catch (error) {
|
|
442
|
+
// Position might have been deleted after force settlement
|
|
443
|
+
console.warn(`Failed to parse position info for position cap ID ${positionCapInfo.positionCapId}: ${error}`)
|
|
444
|
+
// Continue with next position without adding this one to the list
|
|
445
|
+
}
|
|
446
|
+
}))
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
return positionInfoList.sort((a, b) => a.openTimestamp > b.openTimestamp ? 1 : -1)
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
public async getOpenPositions(): Promise<IUSDZPositionInfo[]> {
|
|
453
|
+
let positionDynamicFields: DynamicFieldInfo[] = []
|
|
454
|
+
let _continue = true
|
|
455
|
+
let cursor
|
|
456
|
+
while (_continue) {
|
|
457
|
+
// data here will be a list of dynamic fields containing name and value
|
|
458
|
+
const { data, nextCursor, hasNextPage }
|
|
459
|
+
= await this.provider.getDynamicFields({
|
|
460
|
+
parentId: this.consts.zoCore.positionsParent,
|
|
461
|
+
cursor,
|
|
462
|
+
})
|
|
463
|
+
|
|
464
|
+
positionDynamicFields = positionDynamicFields.concat(data)
|
|
465
|
+
_continue = hasNextPage
|
|
466
|
+
cursor = nextCursor
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
// then we query by dynamic field names and order by time
|
|
470
|
+
const positionInfoList: IUSDZPositionInfo[] = []
|
|
471
|
+
await Promise.all(
|
|
472
|
+
positionDynamicFields.map(async (positionDynamicField) => {
|
|
473
|
+
const positionRaw = await this.provider.getDynamicFieldObject({
|
|
474
|
+
parentId: this.consts.zoCore.positionsParent,
|
|
475
|
+
name: positionDynamicField.name,
|
|
476
|
+
})
|
|
477
|
+
|
|
478
|
+
if (positionRaw?.data?.content) {
|
|
479
|
+
const positionInfo = await this.parsePositionInfo(
|
|
480
|
+
positionRaw,
|
|
481
|
+
positionDynamicField.objectId,
|
|
482
|
+
)
|
|
483
|
+
if (positionInfo) {
|
|
484
|
+
positionInfoList.push(positionInfo)
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
}),
|
|
488
|
+
)
|
|
489
|
+
|
|
490
|
+
return positionInfoList
|
|
491
|
+
.filter(positionInfo => !positionInfo.closed)
|
|
492
|
+
.sort((a, b) => (a.openTimestamp > b.openTimestamp ? 1 : -1))
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
public async getOrderCapInfoList(owner: string): Promise<IUSDZOrderCapInfo[]> {
|
|
496
|
+
const orderCapInfoList: IUSDZOrderCapInfo[] = []
|
|
497
|
+
let cursor: string | undefined | null
|
|
498
|
+
let hasNextPage = true
|
|
499
|
+
|
|
500
|
+
while (hasNextPage) {
|
|
501
|
+
const orderCaps = await this.provider.getOwnedObjects({
|
|
502
|
+
owner,
|
|
503
|
+
filter: {
|
|
504
|
+
MoveModule: {
|
|
505
|
+
package: this.consts.zoCore.package,
|
|
506
|
+
module: 'market',
|
|
507
|
+
},
|
|
508
|
+
},
|
|
509
|
+
options: {
|
|
510
|
+
showType: true,
|
|
511
|
+
showContent: true,
|
|
512
|
+
},
|
|
513
|
+
cursor,
|
|
514
|
+
})
|
|
515
|
+
|
|
516
|
+
for (const orderCap of orderCaps.data) {
|
|
517
|
+
if (orderCap.data?.type?.includes('OrderCap')) {
|
|
518
|
+
orderCapInfoList.push({
|
|
519
|
+
orderCapId: orderCap.data.objectId,
|
|
520
|
+
symbol0: orderCap.data.type.split('<')[1].split(',')[0].trim(),
|
|
521
|
+
symbol1: orderCap.data.type.split('<')[1].split(',')[1].split(',')[0].trim(),
|
|
522
|
+
long: orderCap.data.type.includes('LONG'),
|
|
523
|
+
positionId: (orderCap.data.content as any)?.fields?.position_id,
|
|
524
|
+
})
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
hasNextPage = orderCaps.hasNextPage
|
|
529
|
+
cursor = orderCaps.nextCursor
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
return orderCapInfoList
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
public async getOrderInfoList(orderCapInfoList: IUSDZOrderCapInfo[], owner: string, batchSize = 10) {
|
|
536
|
+
const orderInfoList: IUSDZOrderInfo[] = []
|
|
537
|
+
|
|
538
|
+
// Process in batches of 10
|
|
539
|
+
for (let i = 0; i < orderCapInfoList.length; i += batchSize) {
|
|
540
|
+
const batch = orderCapInfoList.slice(i, i + batchSize)
|
|
541
|
+
|
|
542
|
+
await Promise.all(batch.map(async (orderCapInfo) => {
|
|
543
|
+
try {
|
|
544
|
+
const orderRaw = await this.provider.getDynamicFieldObject({
|
|
545
|
+
parentId: this.consts.zoCore.ordersParent,
|
|
546
|
+
name: {
|
|
547
|
+
// We have enforced collateral coin type to match with fee coin type so we can use symbol0 in the first slot and the last slot of the OrderName
|
|
548
|
+
type: `${this.consts.zoCore.package}::market::OrderName<${orderCapInfo.symbol0}, ${orderCapInfo.symbol1}, ${this.consts.zoCore.package}::market::${orderCapInfo.long ? 'LONG' : 'SHORT'}, ${orderCapInfo.symbol0}>`,
|
|
549
|
+
value: {
|
|
550
|
+
owner,
|
|
551
|
+
id: orderCapInfo.orderCapId,
|
|
552
|
+
position_id: {
|
|
553
|
+
vec: orderCapInfo.positionId ? [orderCapInfo.positionId] : [],
|
|
554
|
+
},
|
|
555
|
+
},
|
|
556
|
+
},
|
|
557
|
+
})
|
|
558
|
+
orderInfoList.push(this.parseOrderInfo(orderRaw, orderCapInfo.orderCapId))
|
|
559
|
+
}
|
|
560
|
+
catch (error) {
|
|
561
|
+
// Order might have been deleted
|
|
562
|
+
console.warn(`Failed to parse order info for order cap ID ${orderCapInfo.orderCapId}: ${error}`)
|
|
563
|
+
// Continue with next order without adding this one to the list
|
|
564
|
+
}
|
|
565
|
+
}))
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
return orderInfoList.sort((a, b) => a.createdAt > b.createdAt ? 1 : -1)
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
public async hasReferral(referree: string): Promise<boolean> {
|
|
572
|
+
const raw = await this.getReferralData(referree)
|
|
573
|
+
return !raw.error
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
public async getReferralData(referree: string): Promise<any> {
|
|
577
|
+
const raw = await this.provider.getDynamicFieldObject({
|
|
578
|
+
parentId: this.consts.zoCore.referralsParent,
|
|
579
|
+
name: {
|
|
580
|
+
type: 'address',
|
|
581
|
+
value: referree,
|
|
582
|
+
},
|
|
583
|
+
})
|
|
584
|
+
return raw
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
/**
|
|
588
|
+
* Gets rebase fee model for USDZ
|
|
589
|
+
*/
|
|
590
|
+
public async getRebaseFeeModel(): Promise<{ base: number, multiplier: number }> {
|
|
591
|
+
this.validateCache()
|
|
592
|
+
if (this.rebaseFeeModelCache) {
|
|
593
|
+
return this.rebaseFeeModelCache
|
|
594
|
+
}
|
|
595
|
+
const rawData = await this.provider.getObject({
|
|
596
|
+
id: this.consts.zoCore.rebaseFeeModel,
|
|
597
|
+
options: {
|
|
598
|
+
showContent: true,
|
|
599
|
+
},
|
|
600
|
+
})
|
|
601
|
+
return USDZDataAPI.parseRebaseFeeModel(rawData)
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
public async fundingFeeRate(indexToken: string, long: boolean): Promise<number> {
|
|
605
|
+
const oiState = await this.getSymbolOiFundingState(indexToken)
|
|
606
|
+
|
|
607
|
+
if (!oiState || !oiState.enabled) {
|
|
608
|
+
const symbol = await this.getSymbolInfo(indexToken, long)
|
|
609
|
+
if (symbol.lastUpdate <= 0) {
|
|
610
|
+
return 0
|
|
611
|
+
}
|
|
612
|
+
const price = (await this.getOraclePrice(indexToken)).getPriceUnchecked().getPriceAsNumberUnchecked()
|
|
613
|
+
const lpSupplyAmount = (await this.getMarketInfo()).lpSupplyWithDecimals
|
|
614
|
+
const model = symbol.fundingFeeModel
|
|
615
|
+
const elapsed = SECONDS_PER_EIGHT_HOUR
|
|
616
|
+
|
|
617
|
+
const deltaSize = USDZDataAPI.calcDeltaSize(symbol, price)
|
|
618
|
+
const pnlPerLp = (symbol.realisedPnl + symbol.unrealisedFundingFeeValue + deltaSize) / lpSupplyAmount
|
|
619
|
+
return USDZDataAPI.calcFundingFeeRate(model, pnlPerLp, elapsed)
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
// OI model enabled: rate based on long/short imbalance
|
|
623
|
+
const longSymbol = await this.getSymbolInfo(indexToken, true)
|
|
624
|
+
const shortSymbol = await this.getSymbolInfo(indexToken, false)
|
|
625
|
+
|
|
626
|
+
if (longSymbol.lastUpdate <= 0 && shortSymbol.lastUpdate <= 0) {
|
|
627
|
+
return 0
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
const elapsed = SECONDS_PER_EIGHT_HOUR
|
|
631
|
+
const longSize = longSymbol.openingSize
|
|
632
|
+
const shortSize = shortSymbol.openingSize
|
|
633
|
+
|
|
634
|
+
const deltaRate = USDZDataAPI.calcOiFundingFeeRate(oiState.model, longSize, shortSize, elapsed)
|
|
635
|
+
return long ? deltaRate : -deltaRate
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
public async rebaseFeeRate(collateralToken: string, increase: boolean, amount: number): Promise<number> {
|
|
639
|
+
let vaultValue = 0
|
|
640
|
+
if (!increase && amount > 0) {
|
|
641
|
+
amount = -amount
|
|
642
|
+
}
|
|
643
|
+
const value = amount * (await this.getOraclePrice(collateralToken)).getPriceUnchecked().getPriceAsNumberUnchecked() / (10 ** this.consts.coins[collateralToken].decimals)
|
|
644
|
+
const vaultPromises = Object.keys(this.consts.zoCore.vaults).map(async (vault) => {
|
|
645
|
+
const vaultInfo = await this.getVaultInfo(vault)
|
|
646
|
+
const reservingFeeDelta = USDZDataAPI.calculateVaultReservingFee(vaultInfo, vaultInfo.reservingFeeModel, Date.now() / 1000)
|
|
647
|
+
const res = (reservingFeeDelta + vaultInfo.liquidity + vaultInfo.reservedAmount) * (await this.getOraclePrice(vault)).getPriceUnchecked().getPriceAsNumberUnchecked() / (10 ** this.consts.coins[vault].decimals)
|
|
648
|
+
if (collateralToken === vault) {
|
|
649
|
+
vaultValue = res
|
|
650
|
+
}
|
|
651
|
+
return res
|
|
652
|
+
})
|
|
653
|
+
|
|
654
|
+
const vaultValues = await Promise.all(vaultPromises)
|
|
655
|
+
const totalVaultValue = vaultValues.reduce((acc, curr) => acc + curr, 0)
|
|
656
|
+
const targetRatio = Number.parseInt(
|
|
657
|
+
this.consts.zoCore.vaults[collateralToken].weight,
|
|
658
|
+
10,
|
|
659
|
+
) / Object.values(this.consts.zoCore.vaults)
|
|
660
|
+
.map(e => Number.parseInt(e.weight, 10))
|
|
661
|
+
.reduce((acc, curr) => acc + curr, 0)
|
|
662
|
+
|
|
663
|
+
return USDZDataAPI.calcRebaseFeeRate(
|
|
664
|
+
await this.getRebaseFeeModel(),
|
|
665
|
+
increase,
|
|
666
|
+
(vaultValue + value) / (totalVaultValue + value),
|
|
667
|
+
targetRatio,
|
|
668
|
+
)
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
public async reservingFeeRate(collateralToken: string, amount = 0): Promise<number> {
|
|
672
|
+
const vaultInfo = await this.getVaultInfo(collateralToken)
|
|
673
|
+
const vaultSupply = vaultInfo.liquidity + vaultInfo.reservedAmount + vaultInfo.unrealisedReservingFeeAmount + amount
|
|
674
|
+
const utilization = vaultSupply ? ((vaultInfo.reservedAmount + amount) / vaultSupply) : 0
|
|
675
|
+
return USDZDataAPI.calcReservingFeeRate(vaultInfo.reservingFeeModel, utilization, SECONDS_PER_EIGHT_HOUR)
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
public async getHistory(trader: string, page: number, limit: number, orderType?: string, symbol?: string): Promise<IBaseHistoryResponse> {
|
|
679
|
+
const params = new URLSearchParams({
|
|
680
|
+
trader,
|
|
681
|
+
page: page.toString(),
|
|
682
|
+
limit: limit.toString(),
|
|
683
|
+
})
|
|
684
|
+
|
|
685
|
+
// Add filter parameters if provided
|
|
686
|
+
if (orderType && orderType !== 'all') {
|
|
687
|
+
params.append('orderType', orderType)
|
|
688
|
+
}
|
|
689
|
+
if (symbol && symbol !== 'all') {
|
|
690
|
+
params.append('symbol', symbol)
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
params.append('lpType', 'USDZ')
|
|
694
|
+
const url = `${this.apiEndpoint}/traderEvents?${params}`
|
|
695
|
+
const res = await fetch(url, {
|
|
696
|
+
method: 'GET',
|
|
697
|
+
headers: {
|
|
698
|
+
'Content-Type': 'application/json',
|
|
699
|
+
},
|
|
700
|
+
})
|
|
701
|
+
const response = await res.json() as any
|
|
702
|
+
|
|
703
|
+
return {
|
|
704
|
+
histories: response.data?.histories || [],
|
|
705
|
+
pagination: response.data?.pagination || {
|
|
706
|
+
total: 0,
|
|
707
|
+
page: 1,
|
|
708
|
+
limit: 20,
|
|
709
|
+
pages: 0,
|
|
710
|
+
},
|
|
711
|
+
}
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
// Private helper methods
|
|
715
|
+
private static calcFundingFeeRate(model: IUSDZFundingFeeModel, pnlPerRate: number, elapsed: number): number {
|
|
716
|
+
const dailyRate = Math.min(model.multiplier * Math.abs(pnlPerRate), model.max)
|
|
717
|
+
const secondsRate = dailyRate * elapsed / SECONDS_PER_EIGHT_HOUR
|
|
718
|
+
return pnlPerRate >= 0 ? -secondsRate : secondsRate
|
|
719
|
+
}
|
|
720
|
+
|
|
721
|
+
private static calcOiFundingFeeRate(model: IUSDZOiFundingModel, longSize: number, shortSize: number, elapsed: number): number {
|
|
722
|
+
const imbalance = Math.abs(longSize - shortSize)
|
|
723
|
+
const total = longSize + shortSize > 0 ? longSize + shortSize : 1
|
|
724
|
+
const dailyRate = Math.min((model.multiplier * (imbalance ** model.exponent)) / total, model.max)
|
|
725
|
+
const secondsRate = dailyRate * elapsed / SECONDS_PER_EIGHT_HOUR
|
|
726
|
+
return longSize >= shortSize ? secondsRate : -secondsRate
|
|
727
|
+
}
|
|
728
|
+
|
|
729
|
+
private static calcAccFundingFeeRate(
|
|
730
|
+
symbol: IUSDZSymbolInfo,
|
|
731
|
+
model: IUSDZFundingFeeModel,
|
|
732
|
+
price: number,
|
|
733
|
+
lpSupplyAmount: number,
|
|
734
|
+
timestamp: number,
|
|
735
|
+
oiModel?: IUSDZOiFundingModel,
|
|
736
|
+
pairedOpeningSize?: number,
|
|
737
|
+
): number {
|
|
738
|
+
if (symbol.lastUpdate > 0) {
|
|
739
|
+
const elapsed = timestamp - symbol.lastUpdate
|
|
740
|
+
if (elapsed > 0) {
|
|
741
|
+
if (oiModel && typeof pairedOpeningSize === 'number') {
|
|
742
|
+
const longSize = symbol.long ? symbol.openingSize : pairedOpeningSize
|
|
743
|
+
const shortSize = symbol.long ? pairedOpeningSize : symbol.openingSize
|
|
744
|
+
const deltaRate = USDZDataAPI.calcOiFundingFeeRate(oiModel, longSize, shortSize, elapsed)
|
|
745
|
+
return symbol.accFundingRate + deltaRate
|
|
746
|
+
}
|
|
747
|
+
const deltaSize = USDZDataAPI.calcDeltaSize(symbol, price)
|
|
748
|
+
const pnlPerLp = (symbol.realisedPnl + symbol.unrealisedFundingFeeValue + deltaSize) / lpSupplyAmount
|
|
749
|
+
return symbol.accFundingRate + USDZDataAPI.calcFundingFeeRate(model, pnlPerLp, elapsed)
|
|
750
|
+
}
|
|
751
|
+
}
|
|
752
|
+
return symbol.accFundingRate
|
|
753
|
+
}
|
|
754
|
+
|
|
755
|
+
private static calculateSymbolFundingFee(
|
|
756
|
+
symbol: IUSDZSymbolInfo,
|
|
757
|
+
model: IUSDZFundingFeeModel,
|
|
758
|
+
price: number,
|
|
759
|
+
lpSupplyAmount: number,
|
|
760
|
+
timestamp: number,
|
|
761
|
+
oiModel?: IUSDZOiFundingModel,
|
|
762
|
+
pairedOpeningSize?: number,
|
|
763
|
+
): number {
|
|
764
|
+
const accFundingRate = USDZDataAPI.calcAccFundingFeeRate(
|
|
765
|
+
symbol,
|
|
766
|
+
model,
|
|
767
|
+
price,
|
|
768
|
+
lpSupplyAmount,
|
|
769
|
+
timestamp,
|
|
770
|
+
oiModel,
|
|
771
|
+
pairedOpeningSize,
|
|
772
|
+
)
|
|
773
|
+
return symbol.unrealisedFundingFeeValue + (accFundingRate - symbol.accFundingRate) * symbol.openingSize
|
|
774
|
+
}
|
|
775
|
+
|
|
776
|
+
private static calculatePositionReserveFee(position: IUSDZPositionInfo, vault: IUSDZVaultInfo, model: IUSDZReservingFeeModel, timestamp: number): number {
|
|
777
|
+
const accReservingRate = USDZDataAPI.calcAccReservingFeeRate(vault, model, timestamp)
|
|
778
|
+
return position.reservingFeeAmount + (accReservingRate - position.lastReservingRate) * position.reservedAmount
|
|
779
|
+
}
|
|
780
|
+
|
|
781
|
+
private static calcAccReservingFeeRate(vault: IUSDZVaultInfo, model: IUSDZReservingFeeModel, timestamp: number): number {
|
|
782
|
+
if (vault.lastUpdate > 0) {
|
|
783
|
+
const elapsed = timestamp - vault.lastUpdate
|
|
784
|
+
if (elapsed > 0) {
|
|
785
|
+
const utilization = USDZDataAPI.vaultUtilization(vault)
|
|
786
|
+
return vault.accReservingRate + USDZDataAPI.calcReservingFeeRate(model, utilization, elapsed)
|
|
787
|
+
}
|
|
788
|
+
}
|
|
789
|
+
return vault.accReservingRate
|
|
790
|
+
}
|
|
791
|
+
|
|
792
|
+
private static calcRebaseFeeRate(model: IUSDZRebaseFeeModel, increase: boolean, ratio: number, targetRatio: number): number {
|
|
793
|
+
if ((increase && ratio <= targetRatio) || (!increase && ratio >= targetRatio)) {
|
|
794
|
+
return model.base
|
|
795
|
+
}
|
|
796
|
+
return model.base + model.multiplier * Math.abs(ratio - targetRatio)
|
|
797
|
+
}
|
|
798
|
+
|
|
799
|
+
private static vaultUtilization(vault: IUSDZVaultInfo): number {
|
|
800
|
+
const supplyAmount = vault.liquidity + vault.reservedAmount + vault.unrealisedReservingFeeAmount
|
|
801
|
+
if (supplyAmount === 0) {
|
|
802
|
+
return 0
|
|
803
|
+
}
|
|
804
|
+
return vault.reservedAmount / supplyAmount
|
|
805
|
+
}
|
|
806
|
+
|
|
807
|
+
private static calcReservingFeeRate(model: IUSDZReservingFeeModel, utilization: number, elapsed: number): number {
|
|
808
|
+
return model.multiplier * utilization * elapsed / SECONDS_PER_EIGHT_HOUR
|
|
809
|
+
}
|
|
810
|
+
|
|
811
|
+
private static calcDeltaSize(symbol: IUSDZSymbolInfo, price: number): number {
|
|
812
|
+
const latestSize = symbol.openingAmount / symbol.priceConfig.precision * price
|
|
813
|
+
return symbol.long ? symbol.openingSize - latestSize : latestSize - symbol.openingSize
|
|
814
|
+
}
|
|
815
|
+
|
|
816
|
+
private static parseMarketInfo(raw: any): IUSDZMarketInfo {
|
|
817
|
+
const content = raw.data.content.fields
|
|
818
|
+
|
|
819
|
+
return {
|
|
820
|
+
lpSupply: content.lp_supply.fields.value,
|
|
821
|
+
positionId: content.positions.fields.id.id,
|
|
822
|
+
vaultId: content.vaults.fields.id.id,
|
|
823
|
+
symbolId: content.symbols.fields.id.id,
|
|
824
|
+
referralId: content.referrals.fields.id.id,
|
|
825
|
+
orderId: content.orders.fields.id.id,
|
|
826
|
+
rebaseFeeModel: content.rebase_fee_model,
|
|
827
|
+
lpSupplyWithDecimals: content.lp_supply.fields.value / (10 ** USDZ_TOKEN_DECIMALS),
|
|
828
|
+
}
|
|
829
|
+
}
|
|
830
|
+
|
|
831
|
+
private async parseVaultInfo(raw: any): Promise<IUSDZVaultInfo> {
|
|
832
|
+
const vaultFields = raw.data.content.fields.value.fields
|
|
833
|
+
const reservingFeeModelAddr = vaultFields.reserving_fee_model
|
|
834
|
+
const reservingFeeModelRaw = await this.provider.getObject({
|
|
835
|
+
id: reservingFeeModelAddr,
|
|
836
|
+
options: {
|
|
837
|
+
showContent: true,
|
|
838
|
+
},
|
|
839
|
+
})
|
|
840
|
+
const reservingFeeModel = USDZDataAPI.parseReservingFeeModel(reservingFeeModelRaw)
|
|
841
|
+
|
|
842
|
+
return {
|
|
843
|
+
liquidity: parseValue(vaultFields.liquidity),
|
|
844
|
+
reservedAmount: parseValue(vaultFields.reserved_amount),
|
|
845
|
+
unrealisedReservingFeeAmount: parseValue(
|
|
846
|
+
vaultFields.unrealised_reserving_fee_amount,
|
|
847
|
+
),
|
|
848
|
+
accReservingRate: parseValue(vaultFields.acc_reserving_rate),
|
|
849
|
+
enabled: vaultFields.enabled,
|
|
850
|
+
weight: parseValue(vaultFields.weight),
|
|
851
|
+
lastUpdate: parseValue(vaultFields.last_update),
|
|
852
|
+
reservingFeeModel,
|
|
853
|
+
priceConfig: {
|
|
854
|
+
maxInterval: parseValue(vaultFields.price_config.fields.max_interval),
|
|
855
|
+
maxConfidence: parseValue(vaultFields.price_config.fields.max_confidence),
|
|
856
|
+
precision: parseValue(vaultFields.price_config.fields.precision),
|
|
857
|
+
feeder: vaultFields.price_config.fields.feeder,
|
|
858
|
+
},
|
|
859
|
+
}
|
|
860
|
+
}
|
|
861
|
+
|
|
862
|
+
private async parseSymbolInfo(raw: any, long: boolean): Promise<IUSDZSymbolInfo> {
|
|
863
|
+
const { objectId } = raw.data
|
|
864
|
+
const { fields } = raw.data.content.fields.value
|
|
865
|
+
const fundingFeeModelAddr = fields.funding_fee_model
|
|
866
|
+
const fundingFeeModelRaw = await this.provider.getObject({
|
|
867
|
+
id: fundingFeeModelAddr,
|
|
868
|
+
options: {
|
|
869
|
+
showContent: true,
|
|
870
|
+
},
|
|
871
|
+
})
|
|
872
|
+
const fundingFeeModel = USDZDataAPI.parseFundingFeeModel(fundingFeeModelRaw)
|
|
873
|
+
|
|
874
|
+
return {
|
|
875
|
+
objectId,
|
|
876
|
+
openingSize: parseValue(fields.opening_size),
|
|
877
|
+
openingAmount: parseValue(fields.opening_amount),
|
|
878
|
+
accFundingRate: parseValue(fields.acc_funding_rate),
|
|
879
|
+
realisedPnl: parseValue(fields.realised_pnl),
|
|
880
|
+
unrealisedFundingFeeValue: parseValue(fields.unrealised_funding_fee_value),
|
|
881
|
+
openEnabled: fields.open_enabled,
|
|
882
|
+
liquidateEnabled: fields.liquidate_enabled,
|
|
883
|
+
decreaseEnabled: fields.decrease_enabled,
|
|
884
|
+
lastUpdate: parseValue(fields.last_update),
|
|
885
|
+
fundingFeeModel,
|
|
886
|
+
long,
|
|
887
|
+
priceConfig: {
|
|
888
|
+
maxInterval: parseValue(fields.price_config.fields.max_interval),
|
|
889
|
+
maxConfidence: parseValue(fields.price_config.fields.max_confidence),
|
|
890
|
+
precision: parseValue(fields.price_config.fields.precision),
|
|
891
|
+
feeder: fields.price_config.fields.feeder,
|
|
892
|
+
},
|
|
893
|
+
}
|
|
894
|
+
}
|
|
895
|
+
|
|
896
|
+
private static parsePositionConfig(raw: any): IUSDZPositionConfig {
|
|
897
|
+
const positionConfigFields = raw.data.content.fields.inner.fields
|
|
898
|
+
|
|
899
|
+
return {
|
|
900
|
+
decreaseFeeBps: parseValue(positionConfigFields.decrease_fee_bps),
|
|
901
|
+
liquidationBonus: parseValue(positionConfigFields.liquidation_bonus),
|
|
902
|
+
liquidationThreshold: parseValue(positionConfigFields.liquidation_threshold),
|
|
903
|
+
maxLeverage: parseValue(positionConfigFields.max_leverage),
|
|
904
|
+
minHoldingDuration: parseValue(positionConfigFields.min_holding_duration),
|
|
905
|
+
openFeeBps: parseValue(positionConfigFields.open_fee_bps),
|
|
906
|
+
maxReservedMultiplier: parseValue(positionConfigFields.max_reserved_multiplier),
|
|
907
|
+
minCollateralValue: parseValue(positionConfigFields.min_collateral_value),
|
|
908
|
+
}
|
|
909
|
+
}
|
|
910
|
+
|
|
911
|
+
private static parseSymbolConfig(raw: any): IUSDZSymbolConfig {
|
|
912
|
+
const { fields } = raw.data.content
|
|
913
|
+
|
|
914
|
+
return {
|
|
915
|
+
id: fields.id.id,
|
|
916
|
+
max_opening_size: parseValue(fields.max_opening_size),
|
|
917
|
+
max_opening_size_enabled: fields.max_opening_size_enabled,
|
|
918
|
+
max_opening_size_per_position: parseValue(fields.max_opening_size_per_position),
|
|
919
|
+
max_opening_size_per_position_enabled: fields.max_opening_size_per_position_enabled,
|
|
920
|
+
instant_exit_fee_config: {
|
|
921
|
+
instant_exit_tier_1_duration_threshold: parseValue(fields.instant_exit_fee_config.fields.instant_exit_tier_1_duration_threshold),
|
|
922
|
+
instant_exit_tier_1_fee_bps: parseValue(fields.instant_exit_fee_config.fields.instant_exit_tier_1_fee_bps.fields.value),
|
|
923
|
+
instant_exit_tier_1_fee_enabled: fields.instant_exit_fee_config.fields.instant_exit_tier_1_fee_enabled,
|
|
924
|
+
instant_exit_tier_2_duration_threshold: parseValue(fields.instant_exit_fee_config.fields.instant_exit_tier_2_duration_threshold),
|
|
925
|
+
instant_exit_tier_2_fee_bps: parseValue(fields.instant_exit_fee_config.fields.instant_exit_tier_2_fee_bps.fields.value),
|
|
926
|
+
instant_exit_tier_2_fee_enabled: fields.instant_exit_fee_config.fields.instant_exit_tier_2_fee_enabled,
|
|
927
|
+
instant_exit_tier_3_duration_threshold: parseValue(fields.instant_exit_fee_config.fields.instant_exit_tier_3_duration_threshold),
|
|
928
|
+
instant_exit_tier_3_fee_bps: parseValue(fields.instant_exit_fee_config.fields.instant_exit_tier_3_fee_bps.fields.value),
|
|
929
|
+
instant_exit_tier_3_fee_enabled: fields.instant_exit_fee_config.fields.instant_exit_tier_3_fee_enabled,
|
|
930
|
+
},
|
|
931
|
+
}
|
|
932
|
+
}
|
|
933
|
+
|
|
934
|
+
private async parsePositionInfo(raw: any, id_: string): Promise<IUSDZPositionInfo> {
|
|
935
|
+
const { content } = raw.data
|
|
936
|
+
const { fields } = content
|
|
937
|
+
const positionFields = fields.value.fields
|
|
938
|
+
const dataType = fields.name.type
|
|
939
|
+
|
|
940
|
+
const positionInfo = {
|
|
941
|
+
id: id_,
|
|
942
|
+
long: dataType.includes('::market::LONG'),
|
|
943
|
+
owner: fields.name.fields.owner,
|
|
944
|
+
version: Number.parseInt(raw.data.version, 10),
|
|
945
|
+
collateralToken: suiSymbolToSymbol(dataType.split('<')[1].split(',')[0].trim(), this.consts),
|
|
946
|
+
indexToken: suiSymbolToSymbol(dataType.split(',')[1].trim(), this.consts),
|
|
947
|
+
collateralAmount: parseValue(positionFields.collateral),
|
|
948
|
+
positionAmount: parseValue(positionFields.position_amount),
|
|
949
|
+
reservedAmount: parseValue(positionFields.reserved),
|
|
950
|
+
positionSize: parseValue(positionFields.position_size),
|
|
951
|
+
lastFundingRate: parseValue(positionFields.last_funding_rate),
|
|
952
|
+
lastReservingRate: parseValue(positionFields.last_reserving_rate),
|
|
953
|
+
reservingFeeAmount: parseValue(positionFields.reserving_fee_amount),
|
|
954
|
+
fundingFeeValue: parseValue(positionFields.funding_fee_value),
|
|
955
|
+
closed: positionFields.closed,
|
|
956
|
+
openTimestamp: parseValue(positionFields.open_timestamp),
|
|
957
|
+
}
|
|
958
|
+
|
|
959
|
+
positionInfo.reservingFeeAmount = USDZDataAPI.calculatePositionReserveFee(
|
|
960
|
+
positionInfo,
|
|
961
|
+
await this.getVaultInfo(positionInfo.collateralToken),
|
|
962
|
+
(await this.getVaultInfo(positionInfo.collateralToken)).reservingFeeModel,
|
|
963
|
+
Date.now() / 1000,
|
|
964
|
+
)
|
|
965
|
+
|
|
966
|
+
// OI context for funding: fetch state and paired side size when enabled
|
|
967
|
+
const oiState = await this.getSymbolOiFundingState(positionInfo.indexToken)
|
|
968
|
+
const pairedSymbol = await this.getSymbolInfo(positionInfo.indexToken, !positionInfo.long)
|
|
969
|
+
positionInfo.fundingFeeValue = USDZDataAPI.calculatePositionFundingFee(
|
|
970
|
+
positionInfo,
|
|
971
|
+
await this.getSymbolInfo(positionInfo.indexToken, positionInfo.long),
|
|
972
|
+
(await this.getSymbolInfo(positionInfo.indexToken, positionInfo.long)).fundingFeeModel,
|
|
973
|
+
(await this.getOraclePrice(positionInfo.indexToken)).getPriceUnchecked().getPriceAsNumberUnchecked(),
|
|
974
|
+
(await this.getMarketInfo()).lpSupplyWithDecimals,
|
|
975
|
+
Date.now() / 1000,
|
|
976
|
+
oiState && oiState.enabled ? oiState.model : undefined,
|
|
977
|
+
pairedSymbol.openingSize,
|
|
978
|
+
)
|
|
979
|
+
|
|
980
|
+
return positionInfo
|
|
981
|
+
}
|
|
982
|
+
|
|
983
|
+
private static calculatePositionFundingFee(
|
|
984
|
+
position: IUSDZPositionInfo,
|
|
985
|
+
symbol: IUSDZSymbolInfo,
|
|
986
|
+
model: IUSDZFundingFeeModel,
|
|
987
|
+
price: number,
|
|
988
|
+
lpSupplyAmount: number,
|
|
989
|
+
timestamp: number,
|
|
990
|
+
oiModel?: IUSDZOiFundingModel,
|
|
991
|
+
pairedOpeningSize?: number,
|
|
992
|
+
): number {
|
|
993
|
+
const accFundingRate = USDZDataAPI.calcAccFundingFeeRate(
|
|
994
|
+
symbol,
|
|
995
|
+
model,
|
|
996
|
+
price,
|
|
997
|
+
lpSupplyAmount,
|
|
998
|
+
timestamp,
|
|
999
|
+
oiModel,
|
|
1000
|
+
pairedOpeningSize,
|
|
1001
|
+
)
|
|
1002
|
+
return position.fundingFeeValue + (accFundingRate - position.lastFundingRate) * position.positionSize
|
|
1003
|
+
}
|
|
1004
|
+
|
|
1005
|
+
private static parseRebaseFeeModel(raw: any): { base: number, multiplier: number } {
|
|
1006
|
+
const { fields } = raw.data.content
|
|
1007
|
+
|
|
1008
|
+
return {
|
|
1009
|
+
base: parseValue(fields.base),
|
|
1010
|
+
multiplier: parseValue(fields.multiplier),
|
|
1011
|
+
}
|
|
1012
|
+
}
|
|
1013
|
+
|
|
1014
|
+
private static parseReservingFeeModel(raw: any): { multiplier: number } {
|
|
1015
|
+
const content = raw.data.content.fields
|
|
1016
|
+
return {
|
|
1017
|
+
multiplier: parseValue(content.multiplier),
|
|
1018
|
+
}
|
|
1019
|
+
}
|
|
1020
|
+
|
|
1021
|
+
private static parseFundingFeeModel(raw: any): { multiplier: number, max: number } {
|
|
1022
|
+
const content = raw.data.content.fields
|
|
1023
|
+
return {
|
|
1024
|
+
multiplier: parseValue(content.multiplier),
|
|
1025
|
+
max: parseValue(content.max),
|
|
1026
|
+
}
|
|
1027
|
+
}
|
|
1028
|
+
|
|
1029
|
+
private static parseOiFundingState(raw: any): IUSDZOiFundingState {
|
|
1030
|
+
const content = raw.data.content.fields
|
|
1031
|
+
return {
|
|
1032
|
+
id: content.id.id,
|
|
1033
|
+
enabled: content.enabled,
|
|
1034
|
+
last_update: parseValue(content.last_update),
|
|
1035
|
+
model: {
|
|
1036
|
+
id: content.model.fields.id.id,
|
|
1037
|
+
multiplier: parseValue(content.model.fields.multiplier),
|
|
1038
|
+
exponent: parseValue(content.model.fields.exponent),
|
|
1039
|
+
max: parseValue(content.model.fields.max),
|
|
1040
|
+
},
|
|
1041
|
+
}
|
|
1042
|
+
}
|
|
1043
|
+
|
|
1044
|
+
private static parsePriceImpactConfig(raw: any): IUSDZPriceImpactConfig {
|
|
1045
|
+
const content = raw.data.content.fields
|
|
1046
|
+
return {
|
|
1047
|
+
id: content.id.id,
|
|
1048
|
+
enabled: content.enabled,
|
|
1049
|
+
baseSpreadRate: parseValue(content.base_spread_rate),
|
|
1050
|
+
maxDynamicSpreadRate: parseValue(content.max_dynamic_spread_rate),
|
|
1051
|
+
maxTotalSpreadRate: parseValue(content.max_total_spread_rate),
|
|
1052
|
+
impactExponent: parseValue(content.impact_exponent),
|
|
1053
|
+
maxOiLong: parseValue(content.max_oi_long),
|
|
1054
|
+
maxOiShort: parseValue(content.max_oi_short),
|
|
1055
|
+
referenceSize: parseValue(content.reference_size),
|
|
1056
|
+
}
|
|
1057
|
+
}
|
|
1058
|
+
|
|
1059
|
+
/**
|
|
1060
|
+
* Computes the spread rate based on OI skew between long and short sides using point evaluation.
|
|
1061
|
+
* This is the offchain equivalent of price_impact::compute_spread_rate in the Move contract.
|
|
1062
|
+
*
|
|
1063
|
+
* NOTE: This method uses point evaluation at the final state. For large trades,
|
|
1064
|
+
* use computeAverageSpreadRate() instead, which integrates over the trade path
|
|
1065
|
+
* for more accurate pricing.
|
|
1066
|
+
*
|
|
1067
|
+
* @param config - Price impact configuration for the symbol
|
|
1068
|
+
* @param currentOiThisSide - Current OI on the side being traded (in value terms)
|
|
1069
|
+
* @param newPositionSize - Size of the new position in value terms
|
|
1070
|
+
* @param currentOiOpposite - Current OI on the opposite side (in value terms)
|
|
1071
|
+
* @param maxOiThisSide - Maximum OI for this side (from config or SymbolConfig)
|
|
1072
|
+
* @param maxOiOpposite - Maximum OI for opposite side (from config or SymbolConfig)
|
|
1073
|
+
* @returns Total spread rate to apply (as a decimal, e.g., 0.001 = 0.1%)
|
|
1074
|
+
*/
|
|
1075
|
+
public static computeSpreadRate(
|
|
1076
|
+
config: IUSDZPriceImpactConfig,
|
|
1077
|
+
currentOiThisSide: number,
|
|
1078
|
+
newPositionSize: number,
|
|
1079
|
+
currentOiOpposite: number,
|
|
1080
|
+
maxOiThisSide: number,
|
|
1081
|
+
maxOiOpposite: number,
|
|
1082
|
+
): number {
|
|
1083
|
+
// If not enabled, return zero spread
|
|
1084
|
+
if (!config.enabled) {
|
|
1085
|
+
return 0
|
|
1086
|
+
}
|
|
1087
|
+
|
|
1088
|
+
// If maxOiThisSide is zero, return base spread only (cannot compute utilization)
|
|
1089
|
+
if (maxOiThisSide === 0) {
|
|
1090
|
+
return config.baseSpreadRate
|
|
1091
|
+
}
|
|
1092
|
+
|
|
1093
|
+
// Calculate this side utilization: (current_oi + new_position) / max_oi
|
|
1094
|
+
const totalOiThis = currentOiThisSide + newPositionSize
|
|
1095
|
+
const thisUtil = Math.min(totalOiThis / maxOiThisSide, 1)
|
|
1096
|
+
|
|
1097
|
+
// Calculate opposite side utilization: current_oi / max_oi
|
|
1098
|
+
let oppositeUtil = 0
|
|
1099
|
+
if (maxOiOpposite > 0) {
|
|
1100
|
+
oppositeUtil = Math.min(currentOiOpposite / maxOiOpposite, 1)
|
|
1101
|
+
}
|
|
1102
|
+
|
|
1103
|
+
// Calculate skew_ratio = this_util - opposite_util, clamped to [0, 1]
|
|
1104
|
+
// Positive when this side is more utilized (crowded), which increases spread
|
|
1105
|
+
let skewRatio = thisUtil - oppositeUtil
|
|
1106
|
+
if (skewRatio < 0) {
|
|
1107
|
+
skewRatio = 0 // No penalty when opposite side is more crowded
|
|
1108
|
+
}
|
|
1109
|
+
else if (skewRatio > 1) {
|
|
1110
|
+
skewRatio = 1 // Cap at 1.0
|
|
1111
|
+
}
|
|
1112
|
+
|
|
1113
|
+
// Calculate dynamic_spread = max_dynamic * (skew_ratio ^ exponent)
|
|
1114
|
+
// For fractional exponents, use linear interpolation
|
|
1115
|
+
const exponent = config.impactExponent
|
|
1116
|
+
const exponentInt = Math.floor(exponent)
|
|
1117
|
+
const exponentFrac = exponent - exponentInt
|
|
1118
|
+
|
|
1119
|
+
// Compute base^exponent_int
|
|
1120
|
+
let ratioPowered = skewRatio ** exponentInt
|
|
1121
|
+
|
|
1122
|
+
// For fractional exponent, interpolate: result = base^n + frac * (base^(n+1) - base^n)
|
|
1123
|
+
if (exponentFrac > 0) {
|
|
1124
|
+
const ratioPoweredNext = skewRatio ** (exponentInt + 1)
|
|
1125
|
+
const diff = ratioPoweredNext - ratioPowered
|
|
1126
|
+
if (diff >= 0) {
|
|
1127
|
+
ratioPowered += exponentFrac * diff
|
|
1128
|
+
}
|
|
1129
|
+
}
|
|
1130
|
+
|
|
1131
|
+
// dynamic_spread = max_dynamic_spread * final_ratio
|
|
1132
|
+
const dynamicSpread = config.maxDynamicSpreadRate * ratioPowered
|
|
1133
|
+
|
|
1134
|
+
// total_spread = base_spread + dynamic_spread
|
|
1135
|
+
let totalSpread = config.baseSpreadRate + dynamicSpread
|
|
1136
|
+
|
|
1137
|
+
// Cap at max_total_spread
|
|
1138
|
+
if (totalSpread > config.maxTotalSpreadRate) {
|
|
1139
|
+
totalSpread = config.maxTotalSpreadRate
|
|
1140
|
+
}
|
|
1141
|
+
|
|
1142
|
+
return totalSpread
|
|
1143
|
+
}
|
|
1144
|
+
|
|
1145
|
+
/**
|
|
1146
|
+
* Computes the size factor multiplier based on position size.
|
|
1147
|
+
* Formula: size_factor = 1 + min(1, position_size / reference_size)
|
|
1148
|
+
*
|
|
1149
|
+
* @param positionSize - Size of the new position
|
|
1150
|
+
* @param referenceSize - Reference size for scaling (0 = disabled)
|
|
1151
|
+
* @returns Size factor multiplier (1.0 to 2.0)
|
|
1152
|
+
*/
|
|
1153
|
+
private static computeSizeFactor(
|
|
1154
|
+
positionSize: number,
|
|
1155
|
+
referenceSize: number,
|
|
1156
|
+
): number {
|
|
1157
|
+
// If reference_size is zero, size scaling is disabled
|
|
1158
|
+
if (referenceSize === 0) {
|
|
1159
|
+
return 1
|
|
1160
|
+
}
|
|
1161
|
+
|
|
1162
|
+
// Compute position_size / reference_size, capped at 1
|
|
1163
|
+
const ratio = Math.min(positionSize / referenceSize, 1)
|
|
1164
|
+
|
|
1165
|
+
// Return 1 + capped_ratio
|
|
1166
|
+
return 1 + ratio
|
|
1167
|
+
}
|
|
1168
|
+
|
|
1169
|
+
/**
|
|
1170
|
+
* Computes the average value of max_dynamic * s^n over the interval [s1, s2].
|
|
1171
|
+
* Uses the integral formula: average = max_dynamic * (s2^(n+1) - s1^(n+1)) / ((n+1) * (s2 - s1))
|
|
1172
|
+
*
|
|
1173
|
+
* @param maxDynamic - Maximum dynamic spread rate
|
|
1174
|
+
* @param s1 - Start skew ratio
|
|
1175
|
+
* @param s2 - End skew ratio
|
|
1176
|
+
* @param exponent - Impact exponent
|
|
1177
|
+
* @returns Average dynamic spread rate
|
|
1178
|
+
*/
|
|
1179
|
+
private static computeIntegralAverage(
|
|
1180
|
+
maxDynamic: number,
|
|
1181
|
+
s1: number,
|
|
1182
|
+
s2: number,
|
|
1183
|
+
exponent: number,
|
|
1184
|
+
): number {
|
|
1185
|
+
// If s1 == s2, return point evaluation
|
|
1186
|
+
if (s1 === s2) {
|
|
1187
|
+
return maxDynamic * (s1 ** exponent)
|
|
1188
|
+
}
|
|
1189
|
+
|
|
1190
|
+
// Compute n + 1
|
|
1191
|
+
const nPlus1 = exponent + 1
|
|
1192
|
+
|
|
1193
|
+
// Compute s2^(n+1) and s1^(n+1)
|
|
1194
|
+
const s2Pow = s2 ** nPlus1
|
|
1195
|
+
const s1Pow = s1 ** nPlus1
|
|
1196
|
+
|
|
1197
|
+
// Compute numerator: s2^(n+1) - s1^(n+1)
|
|
1198
|
+
const numerator = Math.abs(s2Pow - s1Pow)
|
|
1199
|
+
|
|
1200
|
+
// Compute denominator: (n+1) * (s2 - s1)
|
|
1201
|
+
const sDiff = Math.abs(s2 - s1)
|
|
1202
|
+
const denominator = nPlus1 * sDiff
|
|
1203
|
+
|
|
1204
|
+
// Avoid division by zero
|
|
1205
|
+
if (denominator === 0) {
|
|
1206
|
+
return 0
|
|
1207
|
+
}
|
|
1208
|
+
|
|
1209
|
+
// Compute average ratio and multiply by max_dynamic
|
|
1210
|
+
return maxDynamic * (numerator / denominator)
|
|
1211
|
+
}
|
|
1212
|
+
|
|
1213
|
+
/**
|
|
1214
|
+
* Computes the average spread rate over the trade path with size scaling.
|
|
1215
|
+
* This is the offchain equivalent of price_impact::compute_average_spread_rate in the Move contract.
|
|
1216
|
+
*
|
|
1217
|
+
* This function integrates the spread over the utilization change rather than
|
|
1218
|
+
* using the final-state spread, and applies a size-based multiplier to the
|
|
1219
|
+
* dynamic spread component.
|
|
1220
|
+
*
|
|
1221
|
+
* @param config - Price impact configuration for the symbol
|
|
1222
|
+
* @param currentOiThisSide - Current OI on the side being traded (in value terms)
|
|
1223
|
+
* @param newPositionSize - Size of the new position in value terms
|
|
1224
|
+
* @param currentOiOpposite - Current OI on the opposite side (in value terms)
|
|
1225
|
+
* @param maxOiThisSide - Maximum OI for this side (from config or SymbolConfig)
|
|
1226
|
+
* @param maxOiOpposite - Maximum OI for opposite side (from config or SymbolConfig)
|
|
1227
|
+
* @returns Total spread rate to apply (as a decimal, e.g., 0.001 = 0.1%)
|
|
1228
|
+
*/
|
|
1229
|
+
public static computeAverageSpreadRate(
|
|
1230
|
+
config: IUSDZPriceImpactConfig,
|
|
1231
|
+
currentOiThisSide: number,
|
|
1232
|
+
newPositionSize: number,
|
|
1233
|
+
currentOiOpposite: number,
|
|
1234
|
+
maxOiThisSide: number,
|
|
1235
|
+
maxOiOpposite: number,
|
|
1236
|
+
): number {
|
|
1237
|
+
// If not enabled, return zero spread
|
|
1238
|
+
if (!config.enabled) {
|
|
1239
|
+
return 0
|
|
1240
|
+
}
|
|
1241
|
+
|
|
1242
|
+
// If maxOiThisSide is zero, return base spread only (cannot compute utilization)
|
|
1243
|
+
if (maxOiThisSide === 0) {
|
|
1244
|
+
return config.baseSpreadRate
|
|
1245
|
+
}
|
|
1246
|
+
|
|
1247
|
+
// Calculate opposite side utilization (constant throughout the trade)
|
|
1248
|
+
let oppositeUtil = 0
|
|
1249
|
+
if (maxOiOpposite > 0) {
|
|
1250
|
+
oppositeUtil = Math.min(currentOiOpposite / maxOiOpposite, 1)
|
|
1251
|
+
}
|
|
1252
|
+
|
|
1253
|
+
// Calculate start utilization: current_oi / max_oi
|
|
1254
|
+
const startUtil = Math.min(currentOiThisSide / maxOiThisSide, 1)
|
|
1255
|
+
|
|
1256
|
+
// Calculate end utilization: (current_oi + new_position) / max_oi
|
|
1257
|
+
const totalOiThis = currentOiThisSide + newPositionSize
|
|
1258
|
+
const endUtil = Math.min(totalOiThis / maxOiThisSide, 1)
|
|
1259
|
+
|
|
1260
|
+
// Calculate start skew: max(0, min(1, start_util - opposite_util))
|
|
1261
|
+
let startSkew = startUtil - oppositeUtil
|
|
1262
|
+
if (startSkew < 0) {
|
|
1263
|
+
startSkew = 0
|
|
1264
|
+
}
|
|
1265
|
+
else if (startSkew > 1) {
|
|
1266
|
+
startSkew = 1
|
|
1267
|
+
}
|
|
1268
|
+
|
|
1269
|
+
// Calculate end skew: max(0, min(1, end_util - opposite_util))
|
|
1270
|
+
let endSkew = endUtil - oppositeUtil
|
|
1271
|
+
if (endSkew < 0) {
|
|
1272
|
+
endSkew = 0
|
|
1273
|
+
}
|
|
1274
|
+
else if (endSkew > 1) {
|
|
1275
|
+
endSkew = 1
|
|
1276
|
+
}
|
|
1277
|
+
|
|
1278
|
+
// Compute average dynamic spread via integral
|
|
1279
|
+
const avgDynamicRate = USDZDataAPI.computeIntegralAverage(
|
|
1280
|
+
config.maxDynamicSpreadRate,
|
|
1281
|
+
startSkew,
|
|
1282
|
+
endSkew,
|
|
1283
|
+
config.impactExponent,
|
|
1284
|
+
)
|
|
1285
|
+
|
|
1286
|
+
// Apply size scaling factor to dynamic spread
|
|
1287
|
+
const sizeFactor = USDZDataAPI.computeSizeFactor(newPositionSize, config.referenceSize)
|
|
1288
|
+
const scaledDynamicRate = avgDynamicRate * sizeFactor
|
|
1289
|
+
|
|
1290
|
+
// total_spread = base_spread + scaled_dynamic
|
|
1291
|
+
let totalSpread = config.baseSpreadRate + scaledDynamicRate
|
|
1292
|
+
|
|
1293
|
+
// Cap at max_total_spread
|
|
1294
|
+
if (totalSpread > config.maxTotalSpreadRate) {
|
|
1295
|
+
totalSpread = config.maxTotalSpreadRate
|
|
1296
|
+
}
|
|
1297
|
+
|
|
1298
|
+
return totalSpread
|
|
1299
|
+
}
|
|
1300
|
+
|
|
1301
|
+
/**
|
|
1302
|
+
* Applies price impact to a price.
|
|
1303
|
+
* This is the offchain equivalent of price_impact::apply_price_impact in the Move contract.
|
|
1304
|
+
*
|
|
1305
|
+
* Direction logic:
|
|
1306
|
+
* - Open Long: price INCREASES (pay more)
|
|
1307
|
+
* - Open Short: price DECREASES (receive less)
|
|
1308
|
+
* - Close Long: price DECREASES (receive less)
|
|
1309
|
+
* - Close Short: price INCREASES (pay more)
|
|
1310
|
+
*
|
|
1311
|
+
* @param originalPrice - The original price
|
|
1312
|
+
* @param spreadRate - The spread rate to apply (as a decimal, e.g., 0.001 = 0.1%)
|
|
1313
|
+
* @param isLong - Whether this is a long position
|
|
1314
|
+
* @param isOpening - Whether this is opening (true) or closing (false)
|
|
1315
|
+
* @returns Adjusted price with spread applied
|
|
1316
|
+
*/
|
|
1317
|
+
public static applyPriceImpact(
|
|
1318
|
+
originalPrice: number,
|
|
1319
|
+
spreadRate: number,
|
|
1320
|
+
isLong: boolean,
|
|
1321
|
+
isOpening: boolean,
|
|
1322
|
+
): number {
|
|
1323
|
+
// If spread is zero, return original price
|
|
1324
|
+
if (spreadRate === 0) {
|
|
1325
|
+
return originalPrice
|
|
1326
|
+
}
|
|
1327
|
+
|
|
1328
|
+
// Determine if we should increase or decrease price
|
|
1329
|
+
// Open Long or Close Short -> increase price
|
|
1330
|
+
// Open Short or Close Long -> decrease price
|
|
1331
|
+
const increasePrice = (isLong && isOpening) || (!isLong && !isOpening)
|
|
1332
|
+
|
|
1333
|
+
// Calculate spread amount: price * spread_rate
|
|
1334
|
+
const spreadAmount = originalPrice * spreadRate
|
|
1335
|
+
|
|
1336
|
+
// Apply spread
|
|
1337
|
+
if (increasePrice) {
|
|
1338
|
+
return originalPrice + spreadAmount
|
|
1339
|
+
}
|
|
1340
|
+
|
|
1341
|
+
// Ensure we don't go negative
|
|
1342
|
+
const adjusted = originalPrice - spreadAmount
|
|
1343
|
+
return Math.max(adjusted, 0)
|
|
1344
|
+
}
|
|
1345
|
+
|
|
1346
|
+
/**
|
|
1347
|
+
* Convenience method to compute the estimated execution price with price impact.
|
|
1348
|
+
* Fetches the required data and computes the adjusted price.
|
|
1349
|
+
*
|
|
1350
|
+
* @param indexToken - The index token symbol (e.g., 'sui', 'btc')
|
|
1351
|
+
* @param isLong - Whether this is a long position
|
|
1352
|
+
* @param isOpening - Whether this is opening or closing
|
|
1353
|
+
* @param positionSizeValue - Size of the position in value terms (USD)
|
|
1354
|
+
* @returns Object containing spread rate and adjusted price, or null if price impact not configured
|
|
1355
|
+
*/
|
|
1356
|
+
public async estimatePriceImpact(
|
|
1357
|
+
indexToken: string,
|
|
1358
|
+
isLong: boolean,
|
|
1359
|
+
isOpening: boolean,
|
|
1360
|
+
positionSizeValue: number,
|
|
1361
|
+
): Promise<{ spreadRate: number, originalPrice: number, adjustedPrice: number } | null> {
|
|
1362
|
+
const config = await this.getPriceImpactConfig(indexToken)
|
|
1363
|
+
if (!config) {
|
|
1364
|
+
return null
|
|
1365
|
+
}
|
|
1366
|
+
|
|
1367
|
+
// Get current OI for both sides
|
|
1368
|
+
const longSymbol = await this.getSymbolInfo(indexToken, true)
|
|
1369
|
+
const shortSymbol = await this.getSymbolInfo(indexToken, false)
|
|
1370
|
+
|
|
1371
|
+
// Get oracle price
|
|
1372
|
+
const oraclePrice = (await this.getOraclePrice(indexToken)).getPriceUnchecked().getPriceAsNumberUnchecked()
|
|
1373
|
+
|
|
1374
|
+
// Determine which side is "this" side and which is "opposite"
|
|
1375
|
+
const currentOiThisSide = isLong ? longSymbol.openingSize : shortSymbol.openingSize
|
|
1376
|
+
const currentOiOpposite = isLong ? shortSymbol.openingSize : longSymbol.openingSize
|
|
1377
|
+
const maxOiThisSide = isLong ? config.maxOiLong : config.maxOiShort
|
|
1378
|
+
const maxOiOpposite = isLong ? config.maxOiShort : config.maxOiLong
|
|
1379
|
+
|
|
1380
|
+
// Compute average spread rate (uses integration for better accuracy on large trades)
|
|
1381
|
+
const spreadRate = USDZDataAPI.computeAverageSpreadRate(
|
|
1382
|
+
config,
|
|
1383
|
+
currentOiThisSide,
|
|
1384
|
+
positionSizeValue,
|
|
1385
|
+
currentOiOpposite,
|
|
1386
|
+
maxOiThisSide,
|
|
1387
|
+
maxOiOpposite,
|
|
1388
|
+
)
|
|
1389
|
+
|
|
1390
|
+
// Apply price impact
|
|
1391
|
+
const adjustedPrice = USDZDataAPI.applyPriceImpact(oraclePrice, spreadRate, isLong, isOpening)
|
|
1392
|
+
|
|
1393
|
+
return {
|
|
1394
|
+
spreadRate,
|
|
1395
|
+
originalPrice: oraclePrice,
|
|
1396
|
+
adjustedPrice,
|
|
1397
|
+
}
|
|
1398
|
+
}
|
|
1399
|
+
|
|
1400
|
+
private parseOrderInfo(raw: any, capId: string): IUSDZOrderInfo {
|
|
1401
|
+
const { content } = raw.data
|
|
1402
|
+
const { fields } = content.fields.value
|
|
1403
|
+
|
|
1404
|
+
// Extract tokens from dataType
|
|
1405
|
+
const dataType = content?.type
|
|
1406
|
+
|
|
1407
|
+
let orderType: IBaseOrderType
|
|
1408
|
+
if (content.fields.value?.type.includes('OpenPositionOrder')) {
|
|
1409
|
+
orderType = 'OPEN_POSITION'
|
|
1410
|
+
}
|
|
1411
|
+
else if (content.fields.value?.type.includes('OpenMarketOrder')) {
|
|
1412
|
+
orderType = 'OPEN_MARKET'
|
|
1413
|
+
}
|
|
1414
|
+
else if (content.fields.value?.type.includes('DecreasePositionOrder')) {
|
|
1415
|
+
orderType = 'DECREASE_POSITION'
|
|
1416
|
+
}
|
|
1417
|
+
else {
|
|
1418
|
+
orderType = 'DECREASE_MARKET'
|
|
1419
|
+
}
|
|
1420
|
+
|
|
1421
|
+
const ret: IUSDZOrderInfo = {
|
|
1422
|
+
id: content.fields.id.id,
|
|
1423
|
+
capId,
|
|
1424
|
+
executed: fields.executed,
|
|
1425
|
+
owner: content.fields.name.fields.owner,
|
|
1426
|
+
collateralToken: suiSymbolToSymbol(dataType.split('<')[2].split(',')[0].trim(), this.consts),
|
|
1427
|
+
indexToken: suiSymbolToSymbol(dataType.split(',')[1].trim(), this.consts),
|
|
1428
|
+
feeToken: suiSymbolToSymbol(dataType.split(',')[3].split('>')[0].trim(), this.consts),
|
|
1429
|
+
// Use index_price for open orders, limited_index_price for decrease orders
|
|
1430
|
+
indexPrice: orderType === 'OPEN_MARKET'
|
|
1431
|
+
? parseValue(fields.index_price)
|
|
1432
|
+
: parseValue(fields.limited_index_price?.fields?.price || fields.limited_index_price),
|
|
1433
|
+
collateralPriceThreshold: fields.collateral_price_threshold ? parseValue(fields.collateral_price_threshold) : 0,
|
|
1434
|
+
// New index price threshold
|
|
1435
|
+
indexPriceThreshold: fields.index_price_threshold ? parseValue(fields.index_price_threshold) : undefined,
|
|
1436
|
+
feeAmount: BigInt(fields.fee),
|
|
1437
|
+
long: dataType.includes('::market::LONG'),
|
|
1438
|
+
orderType,
|
|
1439
|
+
createdAt: parseValue(fields.created_at),
|
|
1440
|
+
v11Order: !fields.limited_index_price?.fields?.price,
|
|
1441
|
+
// New: referrer and scard fields
|
|
1442
|
+
referrer: fields.referrer,
|
|
1443
|
+
scardId: (
|
|
1444
|
+
fields.scard_id?.fields?.some?.fields?.id
|
|
1445
|
+
|| fields.scard_id?.fields?.value?.fields?.id
|
|
1446
|
+
|| fields.scard_id?.fields?.id
|
|
1447
|
+
|| fields.scard_id?.id
|
|
1448
|
+
),
|
|
1449
|
+
scardRebateRate: (() => {
|
|
1450
|
+
const inner = fields.scard_rebate_rate?.fields?.some ?? fields.scard_rebate_rate?.fields?.value
|
|
1451
|
+
return inner ? parseValue(inner) : undefined
|
|
1452
|
+
})(),
|
|
1453
|
+
}
|
|
1454
|
+
|
|
1455
|
+
if (orderType === 'OPEN_POSITION' || orderType === 'OPEN_MARKET') {
|
|
1456
|
+
ret.openOrder = {
|
|
1457
|
+
reserveAmount: BigInt(fields.reserve_amount),
|
|
1458
|
+
collateralAmount: BigInt(fields.collateral),
|
|
1459
|
+
openAmount: BigInt(fields.open_amount),
|
|
1460
|
+
// New: position_config from OpenMarketOrder
|
|
1461
|
+
positionConfig: fields.position_config?.fields
|
|
1462
|
+
? {
|
|
1463
|
+
decreaseFeeBps: parseValue(fields.position_config.fields.decrease_fee_bps),
|
|
1464
|
+
liquidationBonus: parseValue(fields.position_config.fields.liquidation_bonus),
|
|
1465
|
+
liquidationThreshold: parseValue(fields.position_config.fields.liquidation_threshold),
|
|
1466
|
+
maxLeverage: parseValue(fields.position_config.fields.max_leverage),
|
|
1467
|
+
minHoldingDuration: parseValue(fields.position_config.fields.min_holding_duration),
|
|
1468
|
+
openFeeBps: parseValue(fields.position_config.fields.open_fee_bps),
|
|
1469
|
+
maxReservedMultiplier: parseValue(fields.position_config.fields.max_reserved_multiplier),
|
|
1470
|
+
minCollateralValue: parseValue(fields.position_config.fields.min_collateral_value),
|
|
1471
|
+
}
|
|
1472
|
+
: undefined,
|
|
1473
|
+
}
|
|
1474
|
+
}
|
|
1475
|
+
else if (orderType === 'DECREASE_POSITION' || orderType === 'DECREASE_MARKET') {
|
|
1476
|
+
ret.decreaseOrder = {
|
|
1477
|
+
decreaseAmount: BigInt(fields.decrease_amount),
|
|
1478
|
+
takeProfit: fields.take_profit,
|
|
1479
|
+
}
|
|
1480
|
+
}
|
|
1481
|
+
|
|
1482
|
+
return ret
|
|
1483
|
+
}
|
|
1484
|
+
|
|
1485
|
+
private static calculateVaultReservingFee(
|
|
1486
|
+
vaultInfo: IUSDZVaultInfo,
|
|
1487
|
+
reservingFeeModel: { multiplier: number },
|
|
1488
|
+
currentTime: number,
|
|
1489
|
+
): number {
|
|
1490
|
+
const timeDelta = currentTime - vaultInfo.lastUpdate
|
|
1491
|
+
const periods = Math.floor(timeDelta / SECONDS_PER_EIGHT_HOUR)
|
|
1492
|
+
return vaultInfo.unrealisedReservingFeeAmount
|
|
1493
|
+
+ (vaultInfo.reservedAmount * reservingFeeModel.multiplier * periods) / 1e18
|
|
1494
|
+
}
|
|
1495
|
+
|
|
1496
|
+
private static parseCredential(raw: any, pool: IUSDZStakePool): IUSDZCredential {
|
|
1497
|
+
const stakedAmount = BigInt(raw.data.content.fields.stake)
|
|
1498
|
+
const accRewardPerShare = BigInt(
|
|
1499
|
+
raw.data.content.fields.acc_reward_per_share,
|
|
1500
|
+
)
|
|
1501
|
+
return {
|
|
1502
|
+
id: raw.data.objectId,
|
|
1503
|
+
lockUntil: parseValue(raw.data.content.fields.lock_until),
|
|
1504
|
+
amount: stakedAmount,
|
|
1505
|
+
accRewardPerShare,
|
|
1506
|
+
claimable:
|
|
1507
|
+
((pool.accRewardPerShare - accRewardPerShare) * stakedAmount)
|
|
1508
|
+
/ BigInt(1e18),
|
|
1509
|
+
}
|
|
1510
|
+
}
|
|
1511
|
+
|
|
1512
|
+
private static parseStakePool(raw: any): IUSDZStakePool {
|
|
1513
|
+
const content = raw.data.content.fields
|
|
1514
|
+
const pool = {
|
|
1515
|
+
id: content.id.id,
|
|
1516
|
+
enabled: content.enabled,
|
|
1517
|
+
lastUpdatedTime: parseValue(content.last_updated_time),
|
|
1518
|
+
stakedAmount: BigInt(content.staked_amount),
|
|
1519
|
+
reward: BigInt(content.reward_vault),
|
|
1520
|
+
startTime: parseValue(content.start_time),
|
|
1521
|
+
endTime: parseValue(content.end_time),
|
|
1522
|
+
rewardRate: BigInt(content.reward_rate),
|
|
1523
|
+
accRewardPerShare: BigInt(content.acc_reward_per_share),
|
|
1524
|
+
lockDuration: parseValue(content.lock_duration),
|
|
1525
|
+
}
|
|
1526
|
+
USDZDataAPI.refreshPool(pool, Math.floor(Date.now() / 1000))
|
|
1527
|
+
return pool
|
|
1528
|
+
}
|
|
1529
|
+
|
|
1530
|
+
private static refreshPool(pool: IUSDZStakePool, timestamp: number): void {
|
|
1531
|
+
if (timestamp <= pool.lastUpdatedTime || timestamp < pool.startTime) {
|
|
1532
|
+
return
|
|
1533
|
+
}
|
|
1534
|
+
|
|
1535
|
+
const applicableEndTime = Math.max(pool.endTime, pool.lastUpdatedTime)
|
|
1536
|
+
|
|
1537
|
+
const calculationEndTime = Math.min(timestamp, applicableEndTime)
|
|
1538
|
+
|
|
1539
|
+
if (calculationEndTime > pool.lastUpdatedTime && pool.stakedAmount > BigInt(0) && pool.rewardRate > BigInt(0)) {
|
|
1540
|
+
const timeDiff = BigInt(calculationEndTime - pool.lastUpdatedTime)
|
|
1541
|
+
const rewardAmount = timeDiff * pool.rewardRate
|
|
1542
|
+
const rewardPerShare = (rewardAmount * BigInt(1e18)) / pool.stakedAmount
|
|
1543
|
+
pool.accRewardPerShare += rewardPerShare
|
|
1544
|
+
}
|
|
1545
|
+
|
|
1546
|
+
pool.lastUpdatedTime = calculationEndTime
|
|
1547
|
+
}
|
|
1548
|
+
}
|