@aibtc/mcp-server 1.33.2 → 1.33.4
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/dist/config/contracts.d.ts +35 -32
- package/dist/config/contracts.d.ts.map +1 -1
- package/dist/config/contracts.js +58 -102
- package/dist/config/contracts.js.map +1 -1
- package/dist/config/pillar.d.ts.map +1 -1
- package/dist/config/pillar.js +3 -1
- package/dist/config/pillar.js.map +1 -1
- package/dist/index.js +32 -20
- package/dist/index.js.map +1 -1
- package/dist/services/defi.service.d.ts +73 -40
- package/dist/services/defi.service.d.ts.map +1 -1
- package/dist/services/defi.service.js +234 -259
- package/dist/services/defi.service.js.map +1 -1
- package/dist/tools/defi.tools.d.ts.map +1 -1
- package/dist/tools/defi.tools.js +40 -80
- package/dist/tools/defi.tools.js.map +1 -1
- package/dist/tools/index.d.ts +1 -8
- package/dist/tools/index.d.ts.map +1 -1
- package/dist/tools/index.js +33 -63
- package/dist/tools/index.js.map +1 -1
- package/dist/tools/pillar-direct.tools.d.ts.map +1 -1
- package/dist/tools/pillar-direct.tools.js +100 -113
- package/dist/tools/pillar-direct.tools.js.map +1 -1
- package/dist/tools/yield-hunter.tools.d.ts.map +1 -1
- package/dist/tools/yield-hunter.tools.js +15 -18
- package/dist/tools/yield-hunter.tools.js.map +1 -1
- package/dist/utils/validation.d.ts +1 -1
- package/dist/yield-hunter/index.js +2 -2
- package/dist/yield-hunter/index.js.map +1 -1
- package/package.json +1 -1
- package/skill/SKILL.md +1 -1
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { uintCV, contractPrincipalCV, cvToJSON, hexToCV, PostConditionMode, Pc, principalCV, broadcastTransaction, makeContractCall,
|
|
1
|
+
import { uintCV, contractPrincipalCV, cvToJSON, hexToCV, PostConditionMode, Pc, principalCV, broadcastTransaction, makeContractCall, noneCV, someCV, } from "@stacks/transactions";
|
|
2
2
|
import { STACKS_MAINNET } from "@stacks/network";
|
|
3
3
|
import { AlexSDK, Currency } from "alex-sdk";
|
|
4
4
|
import { getHiroApi } from "./hiro-api.js";
|
|
5
|
-
import { getAlexContracts, getZestContracts, parseContractId, ZEST_ASSETS,
|
|
5
|
+
import { getAlexContracts, getZestContracts, parseContractId, ZEST_ASSETS, ZEST_V2_MARKET, ZEST_V2_MARKET_VAULT, } from "../config/index.js";
|
|
6
6
|
import { callContract } from "../transactions/builder.js";
|
|
7
7
|
// ============================================================================
|
|
8
8
|
// ALEX DEX Service (using alex-sdk)
|
|
@@ -212,13 +212,12 @@ export class AlexDexService {
|
|
|
212
212
|
}
|
|
213
213
|
}
|
|
214
214
|
// ============================================================================
|
|
215
|
-
// Zest Protocol Service
|
|
215
|
+
// Zest Protocol v2 Service
|
|
216
216
|
// ============================================================================
|
|
217
217
|
export class ZestProtocolService {
|
|
218
218
|
network;
|
|
219
219
|
hiro;
|
|
220
220
|
contracts;
|
|
221
|
-
assetsListCache = null;
|
|
222
221
|
constructor(network) {
|
|
223
222
|
this.network = network;
|
|
224
223
|
this.hiro = getHiroApi(network);
|
|
@@ -244,79 +243,7 @@ export class ZestProtocolService {
|
|
|
244
243
|
throw new Error(`Unknown Zest asset: ${assetOrSymbol}. Use zest_list_assets to see available assets.`);
|
|
245
244
|
}
|
|
246
245
|
/**
|
|
247
|
-
*
|
|
248
|
-
* This is a list of tuples containing (asset, lp-token, oracle) for all supported assets
|
|
249
|
-
* Result is cached since ZEST_ASSETS_LIST is static
|
|
250
|
-
*/
|
|
251
|
-
buildAssetsListCV() {
|
|
252
|
-
if (this.assetsListCache) {
|
|
253
|
-
return this.assetsListCache;
|
|
254
|
-
}
|
|
255
|
-
this.assetsListCache = listCV(ZEST_ASSETS_LIST.map((asset) => {
|
|
256
|
-
const [assetAddr, assetName] = parseContractIdTuple(asset.token);
|
|
257
|
-
const [lpAddr, lpName] = parseContractIdTuple(asset.lpToken);
|
|
258
|
-
const [oracleAddr, oracleName] = parseContractIdTuple(asset.oracle);
|
|
259
|
-
return tupleCV({
|
|
260
|
-
asset: contractPrincipalCV(assetAddr, assetName),
|
|
261
|
-
"lp-token": contractPrincipalCV(lpAddr, lpName),
|
|
262
|
-
oracle: contractPrincipalCV(oracleAddr, oracleName),
|
|
263
|
-
});
|
|
264
|
-
}));
|
|
265
|
-
return this.assetsListCache;
|
|
266
|
-
}
|
|
267
|
-
/**
|
|
268
|
-
* Pyth price feed IDs used by Zest's oracle contracts.
|
|
269
|
-
* BTC and STX feeds cover all current Zest assets.
|
|
270
|
-
*/
|
|
271
|
-
// BTC/USD and STX/USD are sufficient for all current Zest assets.
|
|
272
|
-
// Stablecoin assets (aeUSDC, sUSDT, USDA, USDh) use on-chain fixed-price oracles
|
|
273
|
-
// rather than Pyth feeds, so no additional feed IDs are needed here.
|
|
274
|
-
static PYTH_FEED_IDS = [
|
|
275
|
-
"0xe62df6c8b4a85fe1a67db44dc12de5db330f7ac66b72dc658afedf0f4a415b43", // BTC/USD
|
|
276
|
-
"0xec7a775f46379b5e943c3526b1c8d54cd49749176b0b98e02dde68d1bd335c17", // STX/USD
|
|
277
|
-
];
|
|
278
|
-
priceFeedCache = null;
|
|
279
|
-
static PRICE_FEED_TTL_MS = 30_000; // 30s cache — oracle rejects >360s
|
|
280
|
-
/**
|
|
281
|
-
* Fetch fresh Pyth price update VAA from Hermes API.
|
|
282
|
-
* Caches for 30s to avoid redundant requests when multiple ops run in sequence.
|
|
283
|
-
* Returns someCV(bufferCV(...)) for the price-feed-bytes parameter,
|
|
284
|
-
* or noneCV() if the fetch fails (graceful degradation).
|
|
285
|
-
*/
|
|
286
|
-
async fetchPriceFeedBytes() {
|
|
287
|
-
if (this.priceFeedCache && Date.now() - this.priceFeedCache.timestamp < ZestProtocolService.PRICE_FEED_TTL_MS) {
|
|
288
|
-
return this.priceFeedCache.value;
|
|
289
|
-
}
|
|
290
|
-
try {
|
|
291
|
-
const ids = ZestProtocolService.PYTH_FEED_IDS
|
|
292
|
-
.map((id) => `ids[]=${id}`)
|
|
293
|
-
.join("&");
|
|
294
|
-
const controller = new AbortController();
|
|
295
|
-
const timeout = setTimeout(() => controller.abort(), 5_000);
|
|
296
|
-
const res = await fetch(`https://hermes.pyth.network/v2/updates/price/latest?${ids}&encoding=hex`, { signal: controller.signal });
|
|
297
|
-
clearTimeout(timeout);
|
|
298
|
-
if (!res.ok) {
|
|
299
|
-
console.warn(`Pyth Hermes returned HTTP ${res.status}, falling back to noneCV()`);
|
|
300
|
-
return noneCV();
|
|
301
|
-
}
|
|
302
|
-
const data = await res.json();
|
|
303
|
-
const hex = data?.binary?.data?.[0];
|
|
304
|
-
if (!hex) {
|
|
305
|
-
console.warn("Pyth Hermes response missing binary data, falling back to noneCV()");
|
|
306
|
-
return noneCV();
|
|
307
|
-
}
|
|
308
|
-
const value = someCV(bufferCV(Buffer.from(hex, "hex")));
|
|
309
|
-
this.priceFeedCache = { value, timestamp: Date.now() };
|
|
310
|
-
return value;
|
|
311
|
-
}
|
|
312
|
-
catch (err) {
|
|
313
|
-
console.warn("Failed to fetch Pyth price feed, falling back to noneCV():", err);
|
|
314
|
-
return noneCV();
|
|
315
|
-
}
|
|
316
|
-
}
|
|
317
|
-
/**
|
|
318
|
-
* Get all supported assets from Zest Protocol
|
|
319
|
-
* Returns the hardcoded asset list with full metadata
|
|
246
|
+
* Get all supported assets from Zest Protocol v2
|
|
320
247
|
*/
|
|
321
248
|
async getAssets() {
|
|
322
249
|
this.ensureMainnet();
|
|
@@ -331,7 +258,6 @@ export class ZestProtocolService {
|
|
|
331
258
|
* Resolve an asset symbol or contract ID to a full contract ID
|
|
332
259
|
*/
|
|
333
260
|
async resolveAsset(assetOrSymbol) {
|
|
334
|
-
// If it looks like a contract ID, return as-is
|
|
335
261
|
if (assetOrSymbol.includes(".")) {
|
|
336
262
|
return assetOrSymbol;
|
|
337
263
|
}
|
|
@@ -339,185 +265,239 @@ export class ZestProtocolService {
|
|
|
339
265
|
return config.token;
|
|
340
266
|
}
|
|
341
267
|
/**
|
|
342
|
-
* Get user's
|
|
343
|
-
*
|
|
344
|
-
* Supply positions are tracked as LP token balances (e.g. zsbtc-v2-0.get-balance),
|
|
345
|
-
* not in pool-0-reserve-v2-0.get-user-reserve-data which only tracks borrow-side debt.
|
|
346
|
-
* Borrow positions are read from pool-borrow-v2-3.get-user-reserve-data.
|
|
268
|
+
* Get user's full position on Zest v2 via the data helper contract.
|
|
269
|
+
* Returns collateral, debt, health factor, and LTV data in a single call.
|
|
347
270
|
*/
|
|
348
271
|
async getUserPosition(asset, userAddress) {
|
|
349
272
|
this.ensureMainnet();
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
try {
|
|
356
|
-
const lpResult = await this.hiro.callReadOnlyFunction(assetConfig.lpToken, "get-balance", [principalCV(userAddress)], userAddress);
|
|
357
|
-
if (lpResult.okay && lpResult.result) {
|
|
358
|
-
const lpDecoded = cvToJSON(hexToCV(lpResult.result));
|
|
359
|
-
// get-balance returns (response uint uint) — success value is the balance
|
|
360
|
-
if (lpDecoded?.success && lpDecoded.value?.value !== undefined) {
|
|
361
|
-
supplied = lpDecoded.value.value;
|
|
362
|
-
}
|
|
363
|
-
else if (lpDecoded?.value !== undefined && typeof lpDecoded.value === "string") {
|
|
364
|
-
supplied = lpDecoded.value;
|
|
365
|
-
}
|
|
366
|
-
}
|
|
273
|
+
const assetConfig = this.getAssetConfig(asset);
|
|
274
|
+
try {
|
|
275
|
+
const result = await this.hiro.callReadOnlyFunction(this.contracts.data, "get-user-position", [principalCV(userAddress)], userAddress);
|
|
276
|
+
if (!result.okay || !result.result) {
|
|
277
|
+
return null;
|
|
367
278
|
}
|
|
368
|
-
|
|
369
|
-
|
|
279
|
+
const decoded = cvToJSON(hexToCV(result.result));
|
|
280
|
+
if (!decoded || decoded.success === false) {
|
|
281
|
+
return null;
|
|
370
282
|
}
|
|
283
|
+
// Unwrap (ok ...) → tuple with nested .value from cvToJSON
|
|
284
|
+
const position = decoded.value?.value ?? decoded.value;
|
|
285
|
+
if (!position) {
|
|
286
|
+
return null;
|
|
287
|
+
}
|
|
288
|
+
// Extract collateral shares for this asset from collateral list
|
|
289
|
+
// collateral: list of { aid: uint, amount: uint }
|
|
290
|
+
// Collateral uses zToken IDs (assetId + 1): zSTX=1, zsBTC=3, zstSTX=5, etc.
|
|
291
|
+
const zTokenId = assetConfig.assetId + 1;
|
|
292
|
+
const collateralList = position["collateral"]?.value ?? [];
|
|
293
|
+
const collateralEntry = collateralList.find((c) => String(c.value?.aid?.value) === String(zTokenId));
|
|
294
|
+
const suppliedShares = collateralEntry?.value?.amount?.value ?? "0";
|
|
295
|
+
// Extract debt for this asset from debt list
|
|
296
|
+
// debt: list of { actual-debt: uint, asset-id: uint, ... }
|
|
297
|
+
const debtList = position["debt"]?.value ?? [];
|
|
298
|
+
const debtEntry = debtList.find((d) => String(d.value?.["asset-id"]?.value) === String(assetConfig.assetId));
|
|
299
|
+
const borrowed = debtEntry?.value?.["actual-debt"]?.value ?? "0";
|
|
300
|
+
return {
|
|
301
|
+
asset,
|
|
302
|
+
suppliedShares,
|
|
303
|
+
borrowed,
|
|
304
|
+
healthFactor: position["health-factor"]?.value,
|
|
305
|
+
};
|
|
306
|
+
}
|
|
307
|
+
catch {
|
|
308
|
+
return null;
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
/**
|
|
312
|
+
* Get detailed per-asset supply balances via the data helper.
|
|
313
|
+
* Returns vault share balances, underlying equivalents, and market collateral.
|
|
314
|
+
*/
|
|
315
|
+
async getUserSupplies(userAddress) {
|
|
316
|
+
this.ensureMainnet();
|
|
317
|
+
try {
|
|
318
|
+
const result = await this.hiro.callReadOnlyFunction(this.contracts.data, "get-supplies-user", [principalCV(userAddress)], userAddress);
|
|
319
|
+
if (!result.okay || !result.result) {
|
|
320
|
+
return null;
|
|
321
|
+
}
|
|
322
|
+
return cvToJSON(hexToCV(result.result));
|
|
371
323
|
}
|
|
372
|
-
|
|
373
|
-
|
|
324
|
+
catch {
|
|
325
|
+
return null;
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
/**
|
|
329
|
+
* Build post-conditions for a principal sending tokens.
|
|
330
|
+
* Handles wSTX (native STX transfers) vs FT transfers.
|
|
331
|
+
*/
|
|
332
|
+
buildSendPC(principal, amount, assetConfig, mode) {
|
|
333
|
+
if (assetConfig.isNativeStx) {
|
|
334
|
+
return mode === "eq"
|
|
335
|
+
? Pc.principal(principal).willSendEq(amount).ustx()
|
|
336
|
+
: Pc.principal(principal).willSendLte(amount).ustx();
|
|
337
|
+
}
|
|
338
|
+
const builder = mode === "eq"
|
|
339
|
+
? Pc.principal(principal).willSendEq(amount)
|
|
340
|
+
: Pc.principal(principal).willSendLte(amount);
|
|
341
|
+
return builder.ft(assetConfig.token, assetConfig.tokenAssetName);
|
|
342
|
+
}
|
|
343
|
+
/**
|
|
344
|
+
* Query the vault's convert-to-assets to predict underlying amount for a given share amount.
|
|
345
|
+
* Used to set accurate post-conditions for withdraw operations (shares appreciate over time).
|
|
346
|
+
*/
|
|
347
|
+
async getExpectedUnderlying(assetConfig, shares, senderAddress) {
|
|
374
348
|
try {
|
|
375
|
-
const
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
if (borrowDecoded && typeof borrowDecoded === "object") {
|
|
382
|
-
borrowed = borrowDecoded["current-variable-debt"]?.value || "0";
|
|
349
|
+
const result = await this.hiro.callReadOnlyFunction(assetConfig.vault, "convert-to-assets", [uintCV(shares)], senderAddress);
|
|
350
|
+
if (result.okay && result.result) {
|
|
351
|
+
const decoded = cvToJSON(hexToCV(result.result));
|
|
352
|
+
const value = decoded?.value?.value ?? decoded?.value;
|
|
353
|
+
if (value !== undefined) {
|
|
354
|
+
return BigInt(value);
|
|
383
355
|
}
|
|
384
356
|
}
|
|
385
357
|
}
|
|
386
358
|
catch {
|
|
387
|
-
//
|
|
359
|
+
// Fall back to shares as lower bound estimate
|
|
388
360
|
}
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
361
|
+
return shares;
|
|
362
|
+
}
|
|
363
|
+
/**
|
|
364
|
+
* Query the vault's convert-to-shares to predict zToken amount for a given underlying amount.
|
|
365
|
+
* Used to set accurate post-conditions for supply operations.
|
|
366
|
+
*/
|
|
367
|
+
async getExpectedShares(assetConfig, amount, senderAddress) {
|
|
368
|
+
try {
|
|
369
|
+
const result = await this.hiro.callReadOnlyFunction(assetConfig.vault, "convert-to-shares", [uintCV(amount)], senderAddress);
|
|
370
|
+
if (result.okay && result.result) {
|
|
371
|
+
const decoded = cvToJSON(hexToCV(result.result));
|
|
372
|
+
const value = decoded?.value?.value ?? decoded?.value;
|
|
373
|
+
if (value !== undefined) {
|
|
374
|
+
return BigInt(value);
|
|
375
|
+
}
|
|
376
|
+
}
|
|
392
377
|
}
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
};
|
|
378
|
+
catch {
|
|
379
|
+
// Fall back to amount as upper bound
|
|
380
|
+
}
|
|
381
|
+
return amount;
|
|
398
382
|
}
|
|
399
383
|
/**
|
|
400
|
-
* Supply assets to Zest
|
|
384
|
+
* Supply assets to Zest v2 via market's supply-collateral-add.
|
|
385
|
+
* Atomically deposits into vault and adds zTokens as collateral.
|
|
386
|
+
* This earns yield AND provides borrowing power.
|
|
387
|
+
*
|
|
388
|
+
* Token flow (3 ft-transfers):
|
|
389
|
+
* 1. user → market (underlying)
|
|
390
|
+
* 2. market → vault (underlying)
|
|
391
|
+
* 3. user → market-vault (zTokens, minted to user then transferred)
|
|
401
392
|
*
|
|
402
|
-
* Contract
|
|
393
|
+
* Contract: v0-4-market.supply-collateral-add(ft, amount, min-shares, price-feeds)
|
|
403
394
|
*/
|
|
404
|
-
async supply(account, asset, amount
|
|
395
|
+
async supply(account, asset, amount) {
|
|
405
396
|
this.ensureMainnet();
|
|
406
397
|
const assetConfig = this.getAssetConfig(asset);
|
|
407
|
-
const { address, name } = parseContractId(this.contracts.
|
|
408
|
-
const [lpAddr, lpName] = parseContractIdTuple(assetConfig.lpToken);
|
|
398
|
+
const { address, name } = parseContractId(this.contracts.market);
|
|
409
399
|
const [assetAddr, assetName] = parseContractIdTuple(assetConfig.token);
|
|
410
|
-
|
|
400
|
+
// Pre-query expected zToken shares for accurate post-conditions
|
|
401
|
+
const expectedShares = await this.getExpectedShares(assetConfig, amount, account.address);
|
|
411
402
|
const functionArgs = [
|
|
412
|
-
contractPrincipalCV(
|
|
413
|
-
principalCV(this.contracts.poolReserve), // pool-reserve
|
|
414
|
-
contractPrincipalCV(assetAddr, assetName), // asset
|
|
403
|
+
contractPrincipalCV(assetAddr, assetName), // ft (underlying token)
|
|
415
404
|
uintCV(amount), // amount
|
|
416
|
-
|
|
417
|
-
noneCV(), //
|
|
418
|
-
contractPrincipalCV(incentivesAddr, incentivesName), // incentives
|
|
405
|
+
uintCV(expectedShares > 0n ? (expectedShares * 95n) / 100n : 0n), // min-shares (5% slippage tolerance)
|
|
406
|
+
noneCV(), // price-feeds (use cached)
|
|
419
407
|
];
|
|
420
|
-
// Post-
|
|
408
|
+
// Post-conditions for all 3 ft-transfers:
|
|
409
|
+
// 1. User sends underlying → market
|
|
410
|
+
// 2. Market forwards underlying → vault
|
|
411
|
+
// 3. User sends minted zTokens → market-vault (as collateral)
|
|
421
412
|
const postConditions = [
|
|
413
|
+
this.buildSendPC(account.address, amount, assetConfig, "eq"),
|
|
414
|
+
this.buildSendPC(ZEST_V2_MARKET, amount, assetConfig, "lte"),
|
|
422
415
|
Pc.principal(account.address)
|
|
423
|
-
.
|
|
424
|
-
.ft(assetConfig.
|
|
416
|
+
.willSendLte(expectedShares)
|
|
417
|
+
.ft(assetConfig.vault, "zft"),
|
|
425
418
|
];
|
|
426
419
|
return callContract(account, {
|
|
427
420
|
contractAddress: address,
|
|
428
421
|
contractName: name,
|
|
429
|
-
functionName: "supply",
|
|
422
|
+
functionName: "supply-collateral-add",
|
|
430
423
|
functionArgs,
|
|
431
424
|
postConditionMode: PostConditionMode.Deny,
|
|
432
425
|
postConditions,
|
|
433
426
|
});
|
|
434
427
|
}
|
|
435
428
|
/**
|
|
436
|
-
* Withdraw assets from Zest
|
|
429
|
+
* Withdraw assets from Zest v2 via market's collateral-remove-redeem.
|
|
430
|
+
* Atomically removes zToken collateral and redeems for underlying.
|
|
437
431
|
*
|
|
438
|
-
*
|
|
432
|
+
* Token flow (3 ft-transfers):
|
|
433
|
+
* 1. market-vault → market (zTokens released from collateral)
|
|
434
|
+
* 2. market → vault (zTokens for redemption/burn)
|
|
435
|
+
* 3. vault → user (underlying redeemed)
|
|
436
|
+
*
|
|
437
|
+
* Contract: v0-4-market.collateral-remove-redeem(ft, amount, min-underlying, receiver, price-feeds)
|
|
438
|
+
*
|
|
439
|
+
* @param amount - Amount in zToken shares to withdraw
|
|
439
440
|
*/
|
|
440
441
|
async withdraw(account, asset, amount) {
|
|
441
442
|
this.ensureMainnet();
|
|
442
443
|
const assetConfig = this.getAssetConfig(asset);
|
|
443
|
-
const { address, name } = parseContractId(this.contracts.
|
|
444
|
-
const [
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
const
|
|
448
|
-
const priceFeedBytes = await this.fetchPriceFeedBytes();
|
|
444
|
+
const { address, name } = parseContractId(this.contracts.market);
|
|
445
|
+
const [vaultAddr, vaultName] = parseContractIdTuple(assetConfig.vault);
|
|
446
|
+
// Pre-query: how much underlying will we get for these shares?
|
|
447
|
+
// Shares appreciate over time, so underlying > shares amount.
|
|
448
|
+
const expectedUnderlying = await this.getExpectedUnderlying(assetConfig, amount, account.address);
|
|
449
449
|
const functionArgs = [
|
|
450
|
-
contractPrincipalCV(
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
principalCV(account.address), // owner
|
|
456
|
-
this.buildAssetsListCV(), // assets
|
|
457
|
-
contractPrincipalCV(incentivesAddr, incentivesName), // incentives
|
|
458
|
-
priceFeedBytes, // price-feed-bytes (Pyth VAA)
|
|
450
|
+
contractPrincipalCV(vaultAddr, vaultName), // ft (zToken / vault contract, NOT underlying)
|
|
451
|
+
uintCV(amount), // amount (zToken shares)
|
|
452
|
+
uintCV(expectedUnderlying > 0n ? (expectedUnderlying * 95n) / 100n : 0n), // min-underlying (5% slippage tolerance)
|
|
453
|
+
noneCV(), // receiver (none = tx-sender)
|
|
454
|
+
noneCV(), // price-feeds (use cached)
|
|
459
455
|
];
|
|
460
|
-
// Post-conditions:
|
|
461
|
-
// 1.
|
|
462
|
-
// 2.
|
|
463
|
-
// 3.
|
|
464
|
-
// LP tokens are minted 1:1 with supplied amount, so burning ≤ withdraw amount is safe.
|
|
465
|
-
const [lpFtContract, lpFtAssetName] = assetConfig.lpFungibleToken.split("::");
|
|
456
|
+
// Post-conditions (Deny mode requires ALL ft-transfers to be covered):
|
|
457
|
+
// 1. market-vault transfers zTokens (collateral release)
|
|
458
|
+
// 2. market transfers zTokens (internal accounting)
|
|
459
|
+
// 3. vault sends underlying → user (redemption, amount = convert-to-assets result)
|
|
466
460
|
const postConditions = [
|
|
467
|
-
Pc.principal(
|
|
461
|
+
Pc.principal(ZEST_V2_MARKET_VAULT)
|
|
468
462
|
.willSendLte(amount)
|
|
469
|
-
.ft(assetConfig.
|
|
470
|
-
Pc.principal(
|
|
471
|
-
.willSendLte(100n)
|
|
472
|
-
.ustx(),
|
|
473
|
-
Pc.principal(account.address)
|
|
463
|
+
.ft(assetConfig.vault, "zft"),
|
|
464
|
+
Pc.principal(ZEST_V2_MARKET)
|
|
474
465
|
.willSendLte(amount)
|
|
475
|
-
.ft(
|
|
466
|
+
.ft(assetConfig.vault, "zft"),
|
|
467
|
+
this.buildSendPC(assetConfig.vault, expectedUnderlying, assetConfig, "lte"),
|
|
476
468
|
];
|
|
477
469
|
return callContract(account, {
|
|
478
470
|
contractAddress: address,
|
|
479
471
|
contractName: name,
|
|
480
|
-
functionName: "
|
|
472
|
+
functionName: "collateral-remove-redeem",
|
|
481
473
|
functionArgs,
|
|
482
474
|
postConditionMode: PostConditionMode.Deny,
|
|
483
475
|
postConditions,
|
|
484
476
|
});
|
|
485
477
|
}
|
|
486
478
|
/**
|
|
487
|
-
* Borrow assets from Zest
|
|
479
|
+
* Borrow assets from Zest v2 via market's borrow function.
|
|
480
|
+
* Requires sufficient collateral to maintain healthy LTV.
|
|
488
481
|
*
|
|
489
|
-
*
|
|
482
|
+
* Token flow (1 ft-transfer):
|
|
483
|
+
* 1. vault → user (borrowed underlying)
|
|
484
|
+
*
|
|
485
|
+
* Contract: v0-4-market.borrow(ft, amount, receiver, price-feeds)
|
|
490
486
|
*/
|
|
491
487
|
async borrow(account, asset, amount) {
|
|
492
488
|
this.ensureMainnet();
|
|
493
489
|
const assetConfig = this.getAssetConfig(asset);
|
|
494
|
-
const { address, name } = parseContractId(this.contracts.
|
|
490
|
+
const { address, name } = parseContractId(this.contracts.market);
|
|
495
491
|
const [assetAddr, assetName] = parseContractIdTuple(assetConfig.token);
|
|
496
|
-
const [lpAddr, lpName] = parseContractIdTuple(assetConfig.lpToken);
|
|
497
|
-
const [oracleAddr, oracleName] = parseContractIdTuple(assetConfig.oracle);
|
|
498
|
-
const priceFeedBytes = await this.fetchPriceFeedBytes();
|
|
499
492
|
const functionArgs = [
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
this.buildAssetsListCV(), // assets
|
|
505
|
-
uintCV(amount), // amount-to-be-borrowed
|
|
506
|
-
principalCV(this.contracts.feesCalculator), // fee-calculator
|
|
507
|
-
uintCV(BigInt(0)), // interest-rate-mode (0 = variable)
|
|
508
|
-
principalCV(account.address), // owner
|
|
509
|
-
priceFeedBytes, // price-feed-bytes (Pyth VAA)
|
|
493
|
+
contractPrincipalCV(assetAddr, assetName), // ft (token to borrow)
|
|
494
|
+
uintCV(amount), // amount
|
|
495
|
+
noneCV(), // receiver (none = tx-sender)
|
|
496
|
+
noneCV(), // price-feeds (use cached)
|
|
510
497
|
];
|
|
511
|
-
// Post-
|
|
512
|
-
// 1. pool-vault sends borrowed asset (not pool-reserve)
|
|
513
|
-
// 2. sender pays small STX fee for Pyth oracle update (~2 uSTX)
|
|
498
|
+
// Post-condition: vault sends borrowed underlying to user
|
|
514
499
|
const postConditions = [
|
|
515
|
-
|
|
516
|
-
.willSendLte(amount)
|
|
517
|
-
.ft(assetConfig.token, assetName),
|
|
518
|
-
Pc.principal(account.address)
|
|
519
|
-
.willSendLte(100n)
|
|
520
|
-
.ustx(),
|
|
500
|
+
this.buildSendPC(assetConfig.vault, amount, assetConfig, "lte"),
|
|
521
501
|
];
|
|
522
502
|
return callContract(account, {
|
|
523
503
|
contractAddress: address,
|
|
@@ -529,26 +509,26 @@ export class ZestProtocolService {
|
|
|
529
509
|
});
|
|
530
510
|
}
|
|
531
511
|
/**
|
|
532
|
-
* Repay borrowed assets
|
|
512
|
+
* Repay borrowed assets on Zest v2.
|
|
513
|
+
*
|
|
514
|
+
* Token flow (1 ft-transfer):
|
|
515
|
+
* 1. user → vault (repayment, amount may be capped to actual debt on-chain)
|
|
533
516
|
*
|
|
534
|
-
* Contract
|
|
517
|
+
* Contract: v0-4-market.repay(ft, amount, on-behalf-of)
|
|
535
518
|
*/
|
|
536
519
|
async repay(account, asset, amount, onBehalfOf) {
|
|
537
520
|
this.ensureMainnet();
|
|
538
521
|
const assetConfig = this.getAssetConfig(asset);
|
|
539
|
-
const { address, name } = parseContractId(this.contracts.
|
|
522
|
+
const { address, name } = parseContractId(this.contracts.market);
|
|
540
523
|
const [assetAddr, assetName] = parseContractIdTuple(assetConfig.token);
|
|
541
524
|
const functionArgs = [
|
|
542
|
-
contractPrincipalCV(assetAddr, assetName), //
|
|
543
|
-
uintCV(amount), // amount
|
|
544
|
-
principalCV(onBehalfOf
|
|
545
|
-
principalCV(account.address), // payer
|
|
525
|
+
contractPrincipalCV(assetAddr, assetName), // ft
|
|
526
|
+
uintCV(amount), // amount
|
|
527
|
+
onBehalfOf ? someCV(principalCV(onBehalfOf)) : noneCV(), // on-behalf-of
|
|
546
528
|
];
|
|
547
|
-
// Post-condition: user
|
|
529
|
+
// Post-condition: user sends repayment (use lte since contract may cap to actual debt)
|
|
548
530
|
const postConditions = [
|
|
549
|
-
|
|
550
|
-
.willSendLte(amount)
|
|
551
|
-
.ft(assetConfig.token, assetName),
|
|
531
|
+
this.buildSendPC(account.address, amount, assetConfig, "lte"),
|
|
552
532
|
];
|
|
553
533
|
return callContract(account, {
|
|
554
534
|
contractAddress: address,
|
|
@@ -560,74 +540,69 @@ export class ZestProtocolService {
|
|
|
560
540
|
});
|
|
561
541
|
}
|
|
562
542
|
/**
|
|
563
|
-
*
|
|
543
|
+
* Deposit directly into a Zest v2 vault for yield without collateral.
|
|
544
|
+
* Mints zTokens that earn supply yield. Simpler than supply-collateral-add
|
|
545
|
+
* but the zTokens won't be usable as collateral for borrowing.
|
|
564
546
|
*
|
|
565
|
-
*
|
|
547
|
+
* Token flow (1 ft-transfer):
|
|
548
|
+
* 1. user → vault (underlying)
|
|
549
|
+
* Note: zTokens minted to recipient (ft-mint, no PC needed)
|
|
566
550
|
*
|
|
567
|
-
* Contract
|
|
551
|
+
* Contract: vault.deposit(amount, min-out, recipient)
|
|
568
552
|
*/
|
|
569
|
-
async
|
|
553
|
+
async depositToVault(account, asset, amount) {
|
|
570
554
|
this.ensureMainnet();
|
|
571
555
|
const assetConfig = this.getAssetConfig(asset);
|
|
572
|
-
const { address, name } = parseContractId(
|
|
573
|
-
const
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
//
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
const
|
|
556
|
+
const { address, name } = parseContractId(assetConfig.vault);
|
|
557
|
+
const functionArgs = [
|
|
558
|
+
uintCV(amount), // amount
|
|
559
|
+
uintCV(0n), // min-out (0 = no slippage protection)
|
|
560
|
+
principalCV(account.address), // recipient
|
|
561
|
+
];
|
|
562
|
+
// Post-condition: user sends underlying token to vault
|
|
563
|
+
const postConditions = [
|
|
564
|
+
this.buildSendPC(account.address, amount, assetConfig, "eq"),
|
|
565
|
+
];
|
|
566
|
+
return callContract(account, {
|
|
567
|
+
contractAddress: address,
|
|
568
|
+
contractName: name,
|
|
569
|
+
functionName: "deposit",
|
|
570
|
+
functionArgs,
|
|
571
|
+
postConditionMode: PostConditionMode.Deny,
|
|
572
|
+
postConditions,
|
|
573
|
+
});
|
|
574
|
+
}
|
|
575
|
+
/**
|
|
576
|
+
* Redeem zTokens from a Zest v2 vault for underlying assets.
|
|
577
|
+
*
|
|
578
|
+
* Token flow:
|
|
579
|
+
* 1. user sends zTokens → vault (ft-burn, but transfer happens first)
|
|
580
|
+
* 2. vault sends underlying → recipient
|
|
581
|
+
*
|
|
582
|
+
* Contract: vault.redeem(amount, min-out, recipient)
|
|
583
|
+
*
|
|
584
|
+
* @param amount - Amount of zTokens to redeem
|
|
585
|
+
*/
|
|
586
|
+
async redeemFromVault(account, asset, amount) {
|
|
587
|
+
this.ensureMainnet();
|
|
588
|
+
const assetConfig = this.getAssetConfig(asset);
|
|
589
|
+
const { address, name } = parseContractId(assetConfig.vault);
|
|
590
|
+
// Pre-query: shares → underlying (shares appreciate, so underlying > shares)
|
|
591
|
+
const expectedUnderlying = await this.getExpectedUnderlying(assetConfig, amount, account.address);
|
|
605
592
|
const functionArgs = [
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
contractPrincipalCV(oracleAddr, oracleName), // oracle
|
|
610
|
-
principalCV(account.address), // owner
|
|
611
|
-
this.buildAssetsListCV(), // assets
|
|
612
|
-
contractPrincipalCV(wstxAddr, wstxName), // reward-asset (wSTX)
|
|
613
|
-
contractPrincipalCV(incentivesAddr, incentivesName), // incentives
|
|
614
|
-
priceFeedBytes, // price-feed-bytes (Pyth VAA)
|
|
593
|
+
uintCV(amount), // amount (zToken shares)
|
|
594
|
+
uintCV(0n), // min-out (0 = no slippage protection)
|
|
595
|
+
principalCV(account.address), // recipient
|
|
615
596
|
];
|
|
616
597
|
// Post-conditions:
|
|
617
|
-
// 1.
|
|
618
|
-
// 2. sender pays small STX fee for Pyth oracle update (~2 uSTX)
|
|
598
|
+
// 1. Vault sends underlying to user (amount from convert-to-assets)
|
|
619
599
|
const postConditions = [
|
|
620
|
-
|
|
621
|
-
.willSendGte(0n)
|
|
622
|
-
.ft(this.contracts.wstx, wstxName),
|
|
623
|
-
Pc.principal(account.address)
|
|
624
|
-
.willSendLte(100n)
|
|
625
|
-
.ustx(),
|
|
600
|
+
this.buildSendPC(assetConfig.vault, expectedUnderlying, assetConfig, "lte"),
|
|
626
601
|
];
|
|
627
602
|
return callContract(account, {
|
|
628
603
|
contractAddress: address,
|
|
629
604
|
contractName: name,
|
|
630
|
-
functionName: "
|
|
605
|
+
functionName: "redeem",
|
|
631
606
|
functionArgs,
|
|
632
607
|
postConditionMode: PostConditionMode.Deny,
|
|
633
608
|
postConditions,
|