@mysten-incubation/sui-agent-pass 0.1.2 → 0.1.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/describe.js +27 -18
- package/dist/domain.js +30 -0
- package/dist/plugin.js +19 -6
- package/docs/PLAYBOOK.md +190 -161
- package/package.json +1 -1
- package/web-dist/assets/index-CQ1TBNXe.css +1 -0
- package/web-dist/assets/{index--e_95z7d.js → index-eefC1BJ8.js} +71 -71
- package/web-dist/assets/{mount-1CCTGYLA.js → mount-BV2Q3os4.js} +1 -1
- package/web-dist/index.html +2 -2
- package/web-dist/assets/index-B_RBIaFM.css +0 -1
package/dist/describe.js
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
*/
|
|
8
8
|
import { bcs } from '@mysten/sui/bcs';
|
|
9
9
|
import { fromBase64, normalizeSuiAddress } from '@mysten/sui/utils';
|
|
10
|
-
import {
|
|
10
|
+
import { limitDisplay, priceTerms, sameCoin, showCoin, SUI, } from './domain.js';
|
|
11
11
|
const healthFactor = (ray) => (Number(BigInt(ray) / 10n ** 25n) / 100).toFixed(2);
|
|
12
12
|
/** The reviewed rules, or the installed policies once `live` is read from chain. */
|
|
13
13
|
export function describePolicy(d, p, live = null) {
|
|
@@ -34,32 +34,37 @@ export function describePolicy(d, p, live = null) {
|
|
|
34
34
|
for (const t of p.transfers) {
|
|
35
35
|
const l = live?.transfers.find((x) => sameCoin(x.coinType, t.coin));
|
|
36
36
|
rows.push({
|
|
37
|
+
starts: true,
|
|
37
38
|
label: `Pay ${symbol(t.coin)} to`,
|
|
38
39
|
value: (l?.recipients ?? t.recipients).map((r) => `${r.name} → ${r.address}`).join('\n'),
|
|
39
40
|
}, {
|
|
40
|
-
label:
|
|
41
|
-
value: `${show(t.coin, l?.perTransfer ?? t.perTransfer)}
|
|
41
|
+
label: `Max ${symbol(t.coin)} per transfer / per UTC day`,
|
|
42
|
+
value: `${show(t.coin, l?.perTransfer ?? t.perTransfer)} / ${show(t.coin, l?.daily ?? t.daily)}`,
|
|
42
43
|
});
|
|
43
44
|
}
|
|
44
45
|
for (const sw of p.swaps) {
|
|
45
46
|
const l = live?.swaps.find((x) => sameCoin(x.coinIn, sw.coinIn) && sameCoin(x.coinOut, sw.coinOut));
|
|
46
|
-
const minPrice = l?.minPrice ?? sw.minPrice;
|
|
47
|
+
const minPrice = l?.minPrice ?? sw.minPrice, terms = priceTerms(d, sw.coinIn, sw.coinOut);
|
|
47
48
|
rows.push({
|
|
48
|
-
|
|
49
|
+
starts: true,
|
|
50
|
+
label: terms.limit === 'max'
|
|
51
|
+
? `Buy ${symbol(sw.coinOut)} with ${symbol(sw.coinIn)} on`
|
|
52
|
+
: `Sell ${symbol(sw.coinIn)} for ${symbol(sw.coinOut)} on`,
|
|
49
53
|
value: (l?.venues ?? sw.pools).map(poolName).join(', '),
|
|
50
54
|
}, {
|
|
51
|
-
label:
|
|
52
|
-
value: `${show(sw.coinIn, l?.perTrade ?? sw.perTrade)}
|
|
55
|
+
label: `Max ${symbol(sw.coinIn)} ${terms.limit === 'max' ? 'spent' : 'sold'} per trade / per UTC day`,
|
|
56
|
+
value: `${show(sw.coinIn, l?.perTrade ?? sw.perTrade)} / ${show(sw.coinIn, l?.daily ?? sw.daily)}`,
|
|
53
57
|
}, {
|
|
54
|
-
label: 'Lowest price accepted',
|
|
55
|
-
value: `${
|
|
58
|
+
label: terms.limit === 'max' ? 'Highest price paid' : 'Lowest price accepted',
|
|
59
|
+
value: `${limitDisplay(d, sw.coinIn, sw.coinOut, minPrice)} ${symbol(terms.quote)} per ${symbol(terms.base)}`,
|
|
56
60
|
});
|
|
57
61
|
}
|
|
58
62
|
for (const le of p.lending) {
|
|
59
63
|
const l = live?.lending.find((x) => sameCoin(x.coinType, le.coin));
|
|
60
64
|
rows.push({
|
|
61
|
-
|
|
62
|
-
|
|
65
|
+
starts: true,
|
|
66
|
+
label: `Max NAVI ${le.allowBorrow ? 'borrowing' : 'collateral'} in ${symbol(le.coin)} per action / per UTC day`,
|
|
67
|
+
value: `${show(le.coin, l?.perAction ?? le.perAction)} / ${show(le.coin, l?.daily ?? le.daily)}`,
|
|
63
68
|
});
|
|
64
69
|
const floor = l ? healthFactor(l.minHealthFactor) : le.minHealthFactor?.toFixed(2);
|
|
65
70
|
if (floor && floor !== '0.00')
|
|
@@ -69,11 +74,12 @@ export function describePolicy(d, p, live = null) {
|
|
|
69
74
|
const l = live?.staking[0];
|
|
70
75
|
const validators = l?.validators ?? p.staking.validators;
|
|
71
76
|
rows.push({
|
|
77
|
+
starts: true,
|
|
72
78
|
label: 'Stake SUI with',
|
|
73
79
|
value: validators.length ? validators.join('\n') : 'Any active validator',
|
|
74
80
|
}, {
|
|
75
|
-
label: 'SUI per stake
|
|
76
|
-
value: `${show(SUI, l?.perStake ?? p.staking.perStake)}
|
|
81
|
+
label: 'Max SUI per stake / per UTC day',
|
|
82
|
+
value: `${show(SUI, l?.perStake ?? p.staking.perStake)} / ${show(SUI, l?.daily ?? p.staking.daily)}`,
|
|
77
83
|
});
|
|
78
84
|
}
|
|
79
85
|
return rows;
|
|
@@ -88,6 +94,7 @@ export function describeSetup(d, setup) {
|
|
|
88
94
|
return [
|
|
89
95
|
...describePolicy(d, setup.policy),
|
|
90
96
|
{
|
|
97
|
+
starts: true,
|
|
91
98
|
label: 'Authorization ends',
|
|
92
99
|
value: `${utc.format(setup.expiresMs)} UTC (${setup.ttlMs / 86_400_000} ${setup.ttlMs === 86_400_000 ? 'day' : 'days'}); you can revoke earlier`,
|
|
93
100
|
},
|
|
@@ -95,11 +102,11 @@ export function describeSetup(d, setup) {
|
|
|
95
102
|
label: 'Agent may ever withdraw (lifetime caps)',
|
|
96
103
|
value: Object.entries(setup.allowances)
|
|
97
104
|
.map(([coin, cap]) => showCoin(d, coin, cap))
|
|
98
|
-
.join('
|
|
105
|
+
.join(', '),
|
|
99
106
|
},
|
|
100
107
|
{
|
|
101
|
-
label: '
|
|
102
|
-
value: `${showCoin(d, SUI, setup.gasCap)}
|
|
108
|
+
label: 'Max gas allowance, lifetime / per UTC day',
|
|
109
|
+
value: `${showCoin(d, SUI, setup.gasCap)} / ${showCoin(d, SUI, setup.gasDaily)}`,
|
|
103
110
|
},
|
|
104
111
|
{
|
|
105
112
|
label: 'Agent gas stipend (outside the rules)',
|
|
@@ -107,7 +114,9 @@ export function describeSetup(d, setup) {
|
|
|
107
114
|
},
|
|
108
115
|
];
|
|
109
116
|
}
|
|
110
|
-
export const renderRows = (rows) => rows
|
|
117
|
+
export const renderRows = (rows) => rows
|
|
118
|
+
.map(({ label, value, starts }, i) => `${starts && i > 0 ? '\n' : ''}- ${label}: ${value.replaceAll('\n', '; ')}`)
|
|
119
|
+
.join('\n');
|
|
111
120
|
const OWNER_ONLY = new Set([
|
|
112
121
|
'deposit_object',
|
|
113
122
|
'withdraw_object',
|
|
@@ -317,7 +326,7 @@ export function describeRefusal(d, error, recipient) {
|
|
|
317
326
|
0: 'Not a pool you listed',
|
|
318
327
|
1: 'Over your per-trade limit',
|
|
319
328
|
3: "Below the agent's own minimum output",
|
|
320
|
-
4: '
|
|
329
|
+
4: 'Worse than your price limit',
|
|
321
330
|
8: 'The venue filled nothing (order under its minimum size)',
|
|
322
331
|
},
|
|
323
332
|
lending_policy: {
|
package/dist/domain.js
CHANGED
|
@@ -349,6 +349,36 @@ export function priceDisplay(d, coinIn, coinOut, minPrice) {
|
|
|
349
349
|
? display(minPrice, shift)
|
|
350
350
|
: (BigInt(minPrice) * 10n ** BigInt(-shift)).toString();
|
|
351
351
|
}
|
|
352
|
+
/** Dollar stablecoins by symbol: USDC, USDT, USDY, USDsui and the like. */
|
|
353
|
+
export const isStable = (symbol) => /^(USD|AUSD$|FDUSD$|BUCK$)/i.test(symbol);
|
|
354
|
+
/**
|
|
355
|
+
* How a swap rule's price reads to a person. When exactly one side is a stablecoin the price is
|
|
356
|
+
* in that stablecoin per one of the other coin, so a buy reads as the most paid (the policy's
|
|
357
|
+
* floor inverted) and a sale as the least accepted. Otherwise it is `coinOut` per one `coinIn`.
|
|
358
|
+
*/
|
|
359
|
+
export function priceTerms(d, coinIn, coinOut) {
|
|
360
|
+
const stableIn = isStable(coinInfo(d, coinIn).symbol), stableOut = isStable(coinInfo(d, coinOut).symbol);
|
|
361
|
+
return stableIn && !stableOut
|
|
362
|
+
? { base: coinOut, quote: coinIn, limit: 'max' }
|
|
363
|
+
: { base: coinIn, quote: coinOut, limit: 'min' };
|
|
364
|
+
}
|
|
365
|
+
/** The policy floor for a person's price in `priceTerms`; a buy's maximum rounds the floor up. */
|
|
366
|
+
export function limitUnits(d, coinIn, coinOut, price) {
|
|
367
|
+
if (priceTerms(d, coinIn, coinOut).limit === 'min')
|
|
368
|
+
return priceUnits(d, coinIn, coinOut, price);
|
|
369
|
+
const paid = BigInt(units(price, coinInfo(d, coinIn).decimals));
|
|
370
|
+
if (paid === 0n)
|
|
371
|
+
throw new Error('A price must be above 0');
|
|
372
|
+
const scale = 10n ** BigInt(coinInfo(d, coinOut).decimals) * 10n ** 9n;
|
|
373
|
+
return ((scale + paid - 1n) / paid).toString();
|
|
374
|
+
}
|
|
375
|
+
/** A policy floor as a person reads it, in `priceTerms`, without the coin names. */
|
|
376
|
+
export function limitDisplay(d, coinIn, coinOut, minPrice) {
|
|
377
|
+
if (priceTerms(d, coinIn, coinOut).limit === 'min')
|
|
378
|
+
return priceDisplay(d, coinIn, coinOut, minPrice);
|
|
379
|
+
const floor = BigInt(minPrice), scale = 10n ** BigInt(coinInfo(d, coinOut).decimals) * 10n ** 9n;
|
|
380
|
+
return display(((scale * 2n + floor) / (floor * 2n)).toString(), coinInfo(d, coinIn).decimals);
|
|
381
|
+
}
|
|
352
382
|
/** Health factors are entered with two decimals and stored on chain in ray units. */
|
|
353
383
|
export const healthFactorRay = (hf) => hf === null ? '0' : ((BigInt(units(hf.toFixed(2), 2)) * RAY) / 100n).toString();
|
|
354
384
|
/** Gas top-ups per UTC day when the owner names none: a fifth of the lifetime cap, so a runaway
|
package/dist/plugin.js
CHANGED
|
@@ -20,9 +20,9 @@ import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/
|
|
|
20
20
|
import { Transaction } from '@mysten/sui/transactions';
|
|
21
21
|
import { fromBase64, normalizeSuiAddress } from '@mysten/sui/utils';
|
|
22
22
|
import { z } from 'zod';
|
|
23
|
-
import { clientFor, deepbookSizes, readStatus, withMarketFacts } from './chain.js';
|
|
23
|
+
import { clientFor, deepbookSizes, quoteSwap, readStatus, withMarketFacts } from './chain.js';
|
|
24
24
|
import { describePolicy, describeSetup, renderRows } from './describe.js';
|
|
25
|
-
import { address, coinBySymbol, coinInfo, DAY_MS, defaultGasDaily, DeploymentSchema, display,
|
|
25
|
+
import { address, coinBySymbol, coinInfo, DAY_MS, defaultGasDaily, DeploymentSchema, display, isStable, limitUnits, poolsFor, recipientName, sameCoin, SetupSchema, showCoin, units, VenueSchema, } from './domain.js';
|
|
26
26
|
import { passesByAgent } from './explorer.js';
|
|
27
27
|
import { passEnded, returnGasFloat } from './float.js';
|
|
28
28
|
import { failure, INSTRUCTIONS, registerDocs, result, sessionInfo, SIGN_ANNOTATIONS, SIGN_DESCRIPTION, } from './mcp.js';
|
|
@@ -236,7 +236,7 @@ const rulesInput = {
|
|
|
236
236
|
.describe('pool object ids for a pair the deployment does not list (a DeepBook or Cetus pool holding exactly these two coins); verified on chain'),
|
|
237
237
|
perTrade: decimal.describe('sold coin per trade'),
|
|
238
238
|
daily: decimal.describe('sold coin per UTC day'),
|
|
239
|
-
|
|
239
|
+
limitPrice: decimal.describe("the owner's price limit. With a stablecoin on one side it is that stablecoin per one of the other coin: the most paid when buying, the least accepted when selling. Otherwise it is bought coin per one sold coin, the least accepted. Required, above 0, and only the owner sets it: ask them, showing the pool's current price from list_passes"),
|
|
240
240
|
})
|
|
241
241
|
.strict())
|
|
242
242
|
.default([])
|
|
@@ -319,7 +319,7 @@ async function toSetup(client, d0, input) {
|
|
|
319
319
|
pools: pools.map((p) => p.poolId),
|
|
320
320
|
perTrade: inUnits(sw.sell, sw.perTrade),
|
|
321
321
|
daily: inUnits(sw.sell, sw.daily),
|
|
322
|
-
minPrice:
|
|
322
|
+
minPrice: limitUnits(d, coinIn, coinOut, sw.limitPrice),
|
|
323
323
|
};
|
|
324
324
|
}),
|
|
325
325
|
lending: input.lending.map((l) => {
|
|
@@ -416,13 +416,26 @@ export function createPluginMcp(registry) {
|
|
|
416
416
|
annotations: { readOnlyHint: true },
|
|
417
417
|
}, async () => {
|
|
418
418
|
const path = registry.deploymentPath();
|
|
419
|
+
// What the pool pays for ten units of its non-stable coin (coinA when neither or both are
|
|
420
|
+
// stablecoins), quoted like a rule's price; ten clears DeepBook's minimum order.
|
|
421
|
+
const price = async (d, p) => {
|
|
422
|
+
const flip = isStable(coinInfo(d, p.coinA).symbol) && !isStable(coinInfo(d, p.coinB).symbol);
|
|
423
|
+
const a = coinInfo(d, flip ? p.coinB : p.coinA), b = coinInfo(d, flip ? p.coinA : p.coinB);
|
|
424
|
+
const amountIn = (10n * 10n ** BigInt(a.decimals)).toString();
|
|
425
|
+
const q = await quoteSwap(clientFor(d), d, p, a.type, amountIn).catch(() => null);
|
|
426
|
+
if (!q || q.amountOut === '0')
|
|
427
|
+
return {};
|
|
428
|
+
const perUnit = (BigInt(q.amountOut) * 10n ** BigInt(a.decimals)) / BigInt(q.amountIn);
|
|
429
|
+
return { price: `${display(perUnit.toString(), b.decimals)} ${b.symbol} per ${a.symbol}` };
|
|
430
|
+
};
|
|
419
431
|
const markets = async (d) => ({
|
|
420
432
|
network: d.network,
|
|
421
433
|
coins: d.coins,
|
|
422
|
-
pools: d.pools.map((p) => ({
|
|
434
|
+
pools: await Promise.all(d.pools.map(async (p) => ({
|
|
423
435
|
venue: p.venue,
|
|
424
436
|
pair: `${coinInfo(d, p.coinA).symbol}/${coinInfo(d, p.coinB).symbol}`,
|
|
425
|
-
|
|
437
|
+
...(await price(d, p)),
|
|
438
|
+
}))),
|
|
426
439
|
lending: Object.keys(d.protocols.navi.reserves).map((c) => coinInfo(d, c).symbol),
|
|
427
440
|
...(d.pools.length || Object.keys(d.protocols.navi.reserves).length
|
|
428
441
|
? {}
|