@ember-finance/sdk 2.25.0 → 2.27.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -39,13 +39,12 @@ export interface EmberShareSwapConfig {
39
39
  /**
40
40
  * Minimum gross collateral value per swap (collateral units). A swap with
41
41
  * `gross < minSwapGross` reverts `SwapAmountTooSmall`. 0/undefined disables the floor.
42
- * EVM-only (no Sui equivalent yet) — omit on Sui.
43
42
  */
44
43
  minSwapGross?: bigint;
45
44
  /**
46
45
  * TVL-independent absolute ceiling on this source share's post-swap held value
47
46
  * (collateral units). A swap pushing `currentHeldValue + gross` above it reverts
48
- * `EmberShareHeldValueCapExceeded`. 0/undefined disables it. EVM-only — omit on Sui.
47
+ * `EmberShareHeldValueCapExceeded`. 0/undefined disables it.
49
48
  */
50
49
  maxHeldValueAbsolute?: bigint;
51
50
  }
@@ -2,6 +2,18 @@ import { BaseClient as SuiClient } from "@mysten/sui/client";
2
2
  import { FeeMultiplierTier, InstantWithdrawQuote } from "../../atomic/instant-withdraw.js";
3
3
  /** Strategy an atomic vault's idle collateral can be supplied to. */
4
4
  export type SuiAtomicStrategy = "none" | "suilend";
5
+ /** An atomic vault's strategy and, for Suilend, the market it supplies to. */
6
+ export interface SuiAtomicStrategyTarget {
7
+ strategy: SuiAtomicStrategy;
8
+ /** The vault's collateral coin type (`T`). */
9
+ collateralType: string;
10
+ /** Collateral held in the vault, in base units. */
11
+ idleCollateral: bigint;
12
+ /** Suilend only: the LendingMarket object id. */
13
+ suilendMarketId?: string;
14
+ /** Suilend only: the market's pool type (`P`). */
15
+ suilendPoolType?: string;
16
+ }
5
17
  /**
6
18
  * Read-only views + instant-withdraw quotes for the Sui Atomic Liquidity Vault (BET-4111).
7
19
  *
@@ -39,9 +51,20 @@ export declare class SuiAtomicLiquidityReader {
39
51
  epochStartedAt: bigint;
40
52
  epochUsedAmount: bigint;
41
53
  feeMultiplierCurve: FeeMultiplierTier[];
54
+ minSwapGross: bigint;
55
+ maxHeldValueAbsolute: bigint;
42
56
  } | null>;
43
57
  /** Caller's ALV (non-transferable) share balance from the `shares` table. */
44
58
  shareOf(vaultId: string, account: string): Promise<bigint>;
59
+ /**
60
+ * The strategy an atomic vault is on, and for Suilend the market it supplies to.
61
+ * Plain object reads, no simulation.
62
+ * @param vaultId Object id of the atomic vault
63
+ * @returns The strategy, the vault's collateral coin type and idle collateral, and
64
+ * for Suilend the market id and its pool type (`P`)
65
+ */
66
+ getStrategyTarget(vaultId: string): Promise<SuiAtomicStrategyTarget>;
67
+ private readStrategyTarget;
45
68
  /**
46
69
  * Where an atomic vault's collateral sits: idle in the vault, or supplied to its
47
70
  * strategy.
@@ -99,7 +99,9 @@ export class SuiAtomicLiquidityReader {
99
99
  maxUtilizationPercentage: toBig(cfg.max_utilization_percentage),
100
100
  epochStartedAt: toBig(cfg.epoch_started_at),
101
101
  epochUsedAmount: toBig(cfg.epoch_used_amount),
102
- feeMultiplierCurve: curve
102
+ feeMultiplierCurve: curve,
103
+ minSwapGross: toBig(cfg.min_swap_gross),
104
+ maxHeldValueAbsolute: toBig(cfg.max_held_value_absolute)
103
105
  };
104
106
  }
105
107
  }
@@ -112,14 +114,18 @@ export class SuiAtomicLiquidityReader {
112
114
  return toBig(await this.readTableValue(tableId, "address", account));
113
115
  }
114
116
  /**
115
- * Where an atomic vault's collateral sits: idle in the vault, or supplied to its
116
- * strategy.
117
+ * The strategy an atomic vault is on, and for Suilend the market it supplies to.
118
+ * Plain object reads, no simulation.
117
119
  * @param vaultId Object id of the atomic vault
118
- * @returns The active strategy, idle collateral and strategy assets, in collateral
119
- * base units. `suilendMarketId` and `suilendPoolType` (the market's `P`) are set when
120
- * the strategy is Suilend.
120
+ * @returns The strategy, the vault's collateral coin type and idle collateral, and
121
+ * for Suilend the market id and its pool type (`P`)
121
122
  */
122
- async getStrategyState(vaultId) {
123
+ async getStrategyTarget(vaultId) {
124
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
125
+ const { packageId, ...target } = await this.readStrategyTarget(vaultId);
126
+ return target;
127
+ }
128
+ async readStrategyTarget(vaultId) {
123
129
  const res = await this.suiClient.core.getObject({
124
130
  objectId: vaultId,
125
131
  include: { json: true }
@@ -131,16 +137,14 @@ export class SuiAtomicLiquidityReader {
131
137
  if (!strategy) {
132
138
  throw new Error(`Unknown strategy kind ${fields.strategy_kind} on ${vaultId}`);
133
139
  }
140
+ const vaultType = res.object.type;
141
+ // The package that defines the vault type is the one with the views.
142
+ const packageId = vaultType.split("::")[0];
143
+ const collateralType = vaultType.slice(vaultType.indexOf("<") + 1, -1);
134
144
  const idleCollateral = toBig(fields.balance);
135
145
  if (strategy === "none") {
136
- return { strategy, idleCollateral, strategyAssets: 0n };
146
+ return { strategy, collateralType, idleCollateral, packageId };
137
147
  }
138
- // The vault's type names the package that defines it, which is the one with
139
- // the views. The position is valued by Suilend against the reserve's current
140
- // interest, so it is simulated rather than read from fields.
141
- const vaultType = res.object.type;
142
- const collateralType = vaultType.slice(vaultType.indexOf("<") + 1, -1);
143
- const packageId = vaultType.split("::")[0];
144
148
  const marketField = await this.suiClient.core.getDynamicField({
145
149
  parentId: vaultId,
146
150
  name: {
@@ -154,13 +158,41 @@ export class SuiAtomicLiquidityReader {
154
158
  const suilendMarketId = bcs.Address.parse(marketField.dynamicField.value.bcs);
155
159
  const market = await this.suiClient.core.getObject({ objectId: suilendMarketId });
156
160
  const marketType = market.object.type;
157
- const poolType = marketType.slice(marketType.indexOf("<") + 1, -1);
161
+ return {
162
+ strategy,
163
+ collateralType,
164
+ idleCollateral,
165
+ packageId,
166
+ suilendMarketId,
167
+ suilendPoolType: marketType.slice(marketType.indexOf("<") + 1, -1)
168
+ };
169
+ }
170
+ /**
171
+ * Where an atomic vault's collateral sits: idle in the vault, or supplied to its
172
+ * strategy.
173
+ * @param vaultId Object id of the atomic vault
174
+ * @returns The active strategy, idle collateral and strategy assets, in collateral
175
+ * base units. `suilendMarketId` and `suilendPoolType` (the market's `P`) are set when
176
+ * the strategy is Suilend.
177
+ */
178
+ async getStrategyState(vaultId) {
179
+ const target = await this.readStrategyTarget(vaultId);
180
+ const { strategy, idleCollateral, suilendMarketId, suilendPoolType, packageId } = target;
181
+ if (strategy === "none") {
182
+ return { strategy, idleCollateral, strategyAssets: 0n };
183
+ }
184
+ // Suilend values the position against the reserve's current interest, so it is
185
+ // simulated rather than read from fields.
158
186
  const tx = new Transaction();
159
187
  tx.setSender(SUI_ZERO_ADDRESS);
160
188
  tx.moveCall({
161
189
  target: `${packageId}::atomic_liquidity_vault::get_suilend_strategy_assets`,
162
- typeArguments: [collateralType, poolType],
163
- arguments: [tx.object(vaultId), tx.object(suilendMarketId), tx.object.clock()]
190
+ typeArguments: [target.collateralType, suilendPoolType],
191
+ arguments: [
192
+ tx.object(vaultId),
193
+ tx.object(suilendMarketId),
194
+ tx.object.clock()
195
+ ]
164
196
  });
165
197
  const sim = await this.suiClient.core.simulateTransaction({
166
198
  transaction: tx,
@@ -176,7 +208,7 @@ export class SuiAtomicLiquidityReader {
176
208
  idleCollateral,
177
209
  strategyAssets: BigInt(bcs.u64().parse(returned)),
178
210
  suilendMarketId,
179
- suilendPoolType: poolType
211
+ suilendPoolType
180
212
  };
181
213
  }
182
214
  /**
@@ -218,7 +250,9 @@ export class SuiAtomicLiquidityReader {
218
250
  feePercentage: config.feePercentage,
219
251
  holdbackPercentage: config.holdbackPercentage,
220
252
  maxUtilizationPercentage: config.maxUtilizationPercentage,
221
- feeMultiplierCurve: config.feeMultiplierCurve
253
+ feeMultiplierCurve: config.feeMultiplierCurve,
254
+ minSwapGross: config.minSwapGross,
255
+ maxHeldValueAbsolute: config.maxHeldValueAbsolute
222
256
  },
223
257
  feeWaived: feeWaived.includes(owner),
224
258
  holdbackWaived: holdbackWaived.includes(owner)
@@ -87,7 +87,8 @@ export declare class UserCalls extends OnChainCalls {
87
87
  */
88
88
  atomicCancelPendingWithdrawalRequest(vaultId: string, sequenceNumber: NumStr, options?: IVaultOptionalParams & SuiTransactionBlockResponseOptions): Promise<OnChainCallResponse>;
89
89
  /**
90
- * Instantly withdraw collateral from an atomic liquidity vault (no strategy)
90
+ * Instantly withdraw collateral from an atomic liquidity vault. Routes to the
91
+ * entry point for the vault's current strategy.
91
92
  * @param vaultId The id of the atomic vault
92
93
  * @param shares The number of shares to withdraw
93
94
  * @param options Optional tx execution params
@@ -109,7 +110,8 @@ export declare class UserCalls extends OnChainCalls {
109
110
  receiver?: SuiAddress;
110
111
  } & SuiTransactionBlockResponseOptions): Promise<OnChainCallResponse>;
111
112
  /**
112
- * Swap ember shares (source vault receipt coin) into an atomic vault
113
+ * Swap ember shares (source vault receipt coin) into an atomic vault. Routes to the
114
+ * entry point for the vault's current strategy.
113
115
  * @param vaultId The id of the atomic vault
114
116
  * @param sourceReceiptType The receipt coin type of the source vault (R)
115
117
  * @param sourceVaultId Object id of the source Vault<T, R>
@@ -1,6 +1,11 @@
1
1
  import { OnChainCalls } from "./onchain-calls.js";
2
2
  import { TransactionBlock, bigNumber, CoinUtils, SUI_NATIVE_BASE } from "@firefly-exchange/library-sui";
3
3
  import { Vault } from "./vault.js";
4
+ import { SuiAtomicLiquidityReader } from "./atomic-liquidity.js";
5
+ const isSuiCoinType = (coinType) => {
6
+ const [address, module, name] = coinType.split("::");
7
+ return module === "sui" && name === "SUI" && /^0x0*2$/.test(address);
8
+ };
4
9
  export class UserCalls extends OnChainCalls {
5
10
  constructor(_network, _suiClient, _deployment, _signer, _walletAddress, _isUIWallet) {
6
11
  super(_network, _suiClient, _deployment, _signer, _walletAddress, _isUIWallet);
@@ -186,7 +191,8 @@ export class UserCalls extends OnChainCalls {
186
191
  return this.execCall(txb, options);
187
192
  }
188
193
  /**
189
- * Instantly withdraw collateral from an atomic liquidity vault (no strategy)
194
+ * Instantly withdraw collateral from an atomic liquidity vault. Routes to the
195
+ * entry point for the vault's current strategy.
190
196
  * @param vaultId The id of the atomic vault
191
197
  * @param shares The number of shares to withdraw
192
198
  * @param options Optional tx execution params
@@ -194,10 +200,20 @@ export class UserCalls extends OnChainCalls {
194
200
  */
195
201
  async atomicInstantWithdraw(vaultId, shares, options) {
196
202
  const txb = options?.txBlock || new TransactionBlock();
197
- this.txBuilder.atomicInstantWithdraw(vaultId, shares, options?.receiver ?? null, {
198
- ...options,
199
- txBlock: txb
200
- });
203
+ const target = await new SuiAtomicLiquidityReader(this.suiClient).getStrategyTarget(vaultId);
204
+ // The plain entry point asserts the vault has no strategy (EStrategyMismatch);
205
+ // a strategy vault must be called with its market so liquidity can be pulled.
206
+ if (target.strategy === "suilend") {
207
+ if (isSuiCoinType(target.collateralType)) {
208
+ this.txBuilder.atomicInstantWithdrawWithSuilendSui(vaultId, target.suilendPoolType, target.suilendMarketId, shares, options?.receiver ?? this.walletAddress, { ...options, txBlock: txb });
209
+ }
210
+ else {
211
+ this.txBuilder.atomicInstantWithdrawWithSuilend(vaultId, target.suilendPoolType, target.suilendMarketId, shares, options?.receiver ?? null, { ...options, txBlock: txb });
212
+ }
213
+ }
214
+ else {
215
+ this.txBuilder.atomicInstantWithdraw(vaultId, shares, options?.receiver ?? null, { ...options, txBlock: txb });
216
+ }
201
217
  return this.execCall(txb, options);
202
218
  }
203
219
  /**
@@ -215,7 +231,8 @@ export class UserCalls extends OnChainCalls {
215
231
  return this.execCall(txb, options);
216
232
  }
217
233
  /**
218
- * Swap ember shares (source vault receipt coin) into an atomic vault
234
+ * Swap ember shares (source vault receipt coin) into an atomic vault. Routes to the
235
+ * entry point for the vault's current strategy.
219
236
  * @param vaultId The id of the atomic vault
220
237
  * @param sourceReceiptType The receipt coin type of the source vault (R)
221
238
  * @param sourceVaultId Object id of the source Vault<T, R>
@@ -242,7 +259,20 @@ export class UserCalls extends OnChainCalls {
242
259
  [split, merged] = await CoinUtils.validateCoinBalance(this.suiClient, txb, bigNumber(amount).toFixed(0), coinId);
243
260
  coinId = split;
244
261
  }
245
- this.txBuilder.atomicSwapEmberShares(vaultId, sourceReceiptType, sourceVaultId, coinId, options?.receiver ?? null, minNetOut, { ...options, txBlock: txb });
262
+ const target = await new SuiAtomicLiquidityReader(this.suiClient).getStrategyTarget(vaultId);
263
+ // The plain entry point asserts the vault has no strategy (EStrategyMismatch);
264
+ // a strategy vault must be called with its market so liquidity can be pulled.
265
+ if (target.strategy === "suilend") {
266
+ if (isSuiCoinType(target.collateralType)) {
267
+ this.txBuilder.atomicSwapEmberSharesWithSuilendSui(vaultId, sourceReceiptType, target.suilendPoolType, sourceVaultId, target.suilendMarketId, coinId, options?.receiver ?? this.walletAddress, minNetOut, { ...options, txBlock: txb });
268
+ }
269
+ else {
270
+ this.txBuilder.atomicSwapEmberSharesWithSuilend(vaultId, sourceReceiptType, target.suilendPoolType, sourceVaultId, target.suilendMarketId, coinId, options?.receiver ?? null, minNetOut, { ...options, txBlock: txb });
271
+ }
272
+ }
273
+ else {
274
+ this.txBuilder.atomicSwapEmberShares(vaultId, sourceReceiptType, sourceVaultId, coinId, options?.receiver ?? null, minNetOut, { ...options, txBlock: txb });
275
+ }
246
276
  if (merged) {
247
277
  txb.transferObjects([merged], this.walletAddress);
248
278
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@ember-finance/sdk",
3
3
  "description": "Ember Protocol SDK",
4
- "version": "2.25.0",
4
+ "version": "2.27.0",
5
5
  "type": "module",
6
6
  "module": "./dist/index.js",
7
7
  "main": "./dist/index.js",