@mysten-incubation/sui-agent-pass 0.1.7 → 0.1.9
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/deployments/mainnet.json +4 -2
- package/dist/chain.js +64 -0
- package/dist/domain.js +11 -1
- package/dist/mcp.js +40 -3
- package/dist/plugin.js +54 -19
- package/docs/PLAYBOOK.md +18 -11
- package/package.json +1 -1
- package/skills/agent-pass/SKILL.md +18 -11
- package/web-dist/assets/{index-eefC1BJ8.js → index-Bt0vwsup.js} +10 -10
- package/web-dist/assets/{mount-BV2Q3os4.js → mount-D_hAnWmA.js} +1 -1
- package/web-dist/deployments/mainnet.json +4 -2
- package/web-dist/index.html +1 -1
package/deployments/mainnet.json
CHANGED
|
@@ -35,12 +35,14 @@
|
|
|
35
35
|
"protocols": {
|
|
36
36
|
"deepbook": {
|
|
37
37
|
"packageId": "0x2c8d603bc51326b8c13cef9dd07031a408a48dddb541963357661df5d3204809",
|
|
38
|
-
"publishedAt": "0x0e735f8c93a95722efd73521aca7a7652c0bb71ed1daf41b26dfd7d1ff71f748"
|
|
38
|
+
"publishedAt": "0x0e735f8c93a95722efd73521aca7a7652c0bb71ed1daf41b26dfd7d1ff71f748",
|
|
39
|
+
"indexerUrl": "https://deepbook-indexer.mainnet.mystenlabs.com"
|
|
39
40
|
},
|
|
40
41
|
"cetus": {
|
|
41
42
|
"packageId": "0x1eabed72c53feb3805120a081dc15963c204dc8d091542592abaf7a35689b2fb",
|
|
42
43
|
"publishedAt": "0x260693ec785a6e6c9d81d58c7d2ff72f1288ae0fa6a9725abe05a6478b11f084",
|
|
43
|
-
"configId": "0xdaa46292632c3c4d8f31f23ea0f9b36a28ff3677e9684980e4438403a67a3d8f"
|
|
44
|
+
"configId": "0xdaa46292632c3c4d8f31f23ea0f9b36a28ff3677e9684980e4438403a67a3d8f",
|
|
45
|
+
"apiUrl": "https://api-sui.cetus.zone"
|
|
44
46
|
},
|
|
45
47
|
"navi": {
|
|
46
48
|
"packageId": "0x66c91a8560cd64d73d93dd1ec7b61f3f21ad2f66553dd3d7038ca69255479bb7",
|
package/dist/chain.js
CHANGED
|
@@ -661,6 +661,70 @@ export async function withMarketFacts(client, d, needs) {
|
|
|
661
661
|
}
|
|
662
662
|
return { ...d, coins, pools };
|
|
663
663
|
}
|
|
664
|
+
/**
|
|
665
|
+
* Pools on DeepBook and Cetus that trade `coin` (against `other` when given), largest Cetus TVL
|
|
666
|
+
* first. The venues' public listings only nominate candidates; each one is then read from chain
|
|
667
|
+
* by `withMarketFacts`, which drops anything that is not a pool of those coins. Returns the
|
|
668
|
+
* deployment extended with the pools and coins found.
|
|
669
|
+
*/
|
|
670
|
+
export async function findPools(client, d, coin, other) {
|
|
671
|
+
const want = normalizeStructTag(coin), with_ = other ? normalizeStructTag(other) : null;
|
|
672
|
+
const fits = (a, b) => {
|
|
673
|
+
const [x, y] = [normalizeStructTag(a), normalizeStructTag(b)];
|
|
674
|
+
return (x === want && (!with_ || y === with_)) || (y === want && (!with_ || x === with_));
|
|
675
|
+
};
|
|
676
|
+
const candidates = new Map();
|
|
677
|
+
for (const p of d.pools)
|
|
678
|
+
if (fits(p.coinA, p.coinB))
|
|
679
|
+
candidates.set(p.poolId, null);
|
|
680
|
+
const { deepbook, cetus } = d.protocols;
|
|
681
|
+
const listings = await Promise.all([
|
|
682
|
+
deepbook.indexerUrl
|
|
683
|
+
? fetch(`${deepbook.indexerUrl}/get_pools`, { signal: AbortSignal.timeout(8_000) })
|
|
684
|
+
.then((r) => r.json())
|
|
685
|
+
.then((all) => all
|
|
686
|
+
.filter((p) => fits(p.base_asset_id, p.quote_asset_id))
|
|
687
|
+
.map((p) => [p.pool_id, null]))
|
|
688
|
+
.catch(() => [])
|
|
689
|
+
: [],
|
|
690
|
+
cetus.apiUrl
|
|
691
|
+
? fetch(`${cetus.apiUrl}/v3/sui/clmm/stats_pools`, {
|
|
692
|
+
method: 'POST',
|
|
693
|
+
headers: { 'content-type': 'application/json' },
|
|
694
|
+
body: JSON.stringify({
|
|
695
|
+
filter: '',
|
|
696
|
+
sortBy: 'tvl',
|
|
697
|
+
sortOrder: 'desc',
|
|
698
|
+
limit: 20,
|
|
699
|
+
offset: 0,
|
|
700
|
+
coinTypes: [want],
|
|
701
|
+
}),
|
|
702
|
+
signal: AbortSignal.timeout(8_000),
|
|
703
|
+
})
|
|
704
|
+
.then((r) => r.json())
|
|
705
|
+
.then((r) => (r.data?.list ?? [])
|
|
706
|
+
.filter((p) => fits(p.coinA.coinType, p.coinB.coinType))
|
|
707
|
+
.map((p) => [p.pool, p.tvl ?? null]))
|
|
708
|
+
.catch(() => [])
|
|
709
|
+
: [],
|
|
710
|
+
]);
|
|
711
|
+
for (const [id, tvl] of listings.flat())
|
|
712
|
+
candidates.set(normalizeSuiAddress(id), tvl);
|
|
713
|
+
const ranked = [...candidates]
|
|
714
|
+
.sort(([, a], [, b]) => Number(b ?? 0) - Number(a ?? 0))
|
|
715
|
+
.slice(0, 10);
|
|
716
|
+
let extended = d;
|
|
717
|
+
const found = [];
|
|
718
|
+
for (const [id, tvl] of ranked) {
|
|
719
|
+
const next = await withMarketFacts(client, extended, { coins: [], pools: [id] }).catch(() => null);
|
|
720
|
+
const pool = next?.pools.find((p) => p.poolId === normalizeSuiAddress(id));
|
|
721
|
+
if (!next || !pool || !fits(pool.coinA, pool.coinB))
|
|
722
|
+
continue;
|
|
723
|
+
extended = next;
|
|
724
|
+
found.push({ pool, tvlUsd: tvl === null ? null : Number(tvl).toFixed(0) });
|
|
725
|
+
}
|
|
726
|
+
return { d: extended, found };
|
|
727
|
+
}
|
|
664
728
|
function structTagString(t) {
|
|
665
729
|
const params = t.typeParams.map((p) => (typeof p === 'string' ? p : structTagString(p)));
|
|
666
730
|
return `${t.address}::${t.module}::${t.name}${params.length ? `<${params.join(',')}>` : ''}`;
|
package/dist/domain.js
CHANGED
|
@@ -164,10 +164,20 @@ export const DeploymentSchema = z
|
|
|
164
164
|
packageId: address,
|
|
165
165
|
/** The latest upgrade, for calls made from a PTB; the original is version-gated. */
|
|
166
166
|
publishedAt: address,
|
|
167
|
+
/** DeepBook's public indexer, which lists every pool; used only to find candidates. */
|
|
168
|
+
indexerUrl: z.string().url().optional(),
|
|
167
169
|
})
|
|
168
170
|
.strict(),
|
|
169
171
|
/** `packageId` defines the pool type; `publishedAt` is the upgrade that answers calls. */
|
|
170
|
-
cetus: z
|
|
172
|
+
cetus: z
|
|
173
|
+
.object({
|
|
174
|
+
packageId: address,
|
|
175
|
+
publishedAt: address,
|
|
176
|
+
configId: address,
|
|
177
|
+
/** Cetus's public API, which lists pools by coin; used only to find candidates. */
|
|
178
|
+
apiUrl: z.string().url().optional(),
|
|
179
|
+
})
|
|
180
|
+
.strict(),
|
|
171
181
|
navi: z
|
|
172
182
|
.object({
|
|
173
183
|
/** The package that defines `account::AccountCap`, which the pass holds. */
|
package/dist/mcp.js
CHANGED
|
@@ -6,7 +6,7 @@ import { fileURLToPath } from 'node:url';
|
|
|
6
6
|
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
7
7
|
import { z } from 'zod';
|
|
8
8
|
import { clientFor, deepbookSizes, readAllHoldings, readStatus } from './chain.js';
|
|
9
|
-
import { coinInfo } from './domain.js';
|
|
9
|
+
import { coinInfo, DAY_MS } from './domain.js';
|
|
10
10
|
export const result = (value) => ({
|
|
11
11
|
content: [{ type: 'text', text: JSON.stringify(value) }],
|
|
12
12
|
});
|
|
@@ -45,14 +45,51 @@ async function sdkInfo(directory) {
|
|
|
45
45
|
note: published ? null : `${root} must be built (npm run build) before installing from it`,
|
|
46
46
|
};
|
|
47
47
|
}
|
|
48
|
-
|
|
49
|
-
|
|
48
|
+
let latest = null;
|
|
49
|
+
/** The newest version on npm, asked at most once a day per process; null when npm is unreachable. */
|
|
50
|
+
async function latestVersion(name) {
|
|
51
|
+
if (latest && Date.now() - latest.at < DAY_MS)
|
|
52
|
+
return latest.version;
|
|
53
|
+
const version = await fetch(`https://registry.npmjs.org/${name.replace('/', '%2F')}/latest`, {
|
|
54
|
+
signal: AbortSignal.timeout(2_000),
|
|
55
|
+
})
|
|
56
|
+
.then((r) => (r.ok ? r.json() : null))
|
|
57
|
+
.then((j) => j?.version ?? null)
|
|
58
|
+
.catch(() => null);
|
|
59
|
+
latest = { at: Date.now(), version };
|
|
60
|
+
return version;
|
|
61
|
+
}
|
|
62
|
+
const newer = (a, b) => {
|
|
63
|
+
const [x, y] = [a, b].map((v) => v.split('-')[0].split('.').map(Number));
|
|
64
|
+
for (let i = 0; i < 3; i++)
|
|
65
|
+
if (x[i] !== y[i])
|
|
66
|
+
return (x[i] ?? 0) > (y[i] ?? 0);
|
|
67
|
+
return false;
|
|
68
|
+
};
|
|
69
|
+
/**
|
|
70
|
+
* Which code answers: the package version and when it was built, so a stale server shows, and
|
|
71
|
+
* for an installed package a newer release on npm with the command that takes it.
|
|
72
|
+
*/
|
|
73
|
+
export async function serverInfo() {
|
|
50
74
|
const pkg = JSON.parse(await readFile(new URL('../package.json', import.meta.url), 'utf8'));
|
|
51
75
|
const built = await stat(fileURLToPath(import.meta.url)).then((s) => s.mtime.toISOString());
|
|
76
|
+
const installed = fileURLToPath(import.meta.url)
|
|
77
|
+
.split(sep)
|
|
78
|
+
.includes('node_modules');
|
|
79
|
+
const available = installed ? await latestVersion(pkg.name) : null;
|
|
52
80
|
return {
|
|
53
81
|
version: pkg.version,
|
|
54
82
|
builtAt: built,
|
|
55
83
|
note: 'This process keeps the code it started with. After this package is rebuilt or upgraded, restart the agent client so it starts a new server; compare builtAt.',
|
|
84
|
+
...(available && newer(available, pkg.version)
|
|
85
|
+
? {
|
|
86
|
+
update: {
|
|
87
|
+
latest: available,
|
|
88
|
+
command: `npm install -g ${pkg.name}@latest`,
|
|
89
|
+
then: 'Restart the agent client so it starts the new server; the agent-pass skill is linked to the package and follows it.',
|
|
90
|
+
},
|
|
91
|
+
}
|
|
92
|
+
: {}),
|
|
56
93
|
};
|
|
57
94
|
}
|
|
58
95
|
/**
|
package/dist/plugin.js
CHANGED
|
@@ -20,12 +20,12 @@ 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, quoteSwap, readAllHoldings, readStatus, withMarketFacts, } from './chain.js';
|
|
23
|
+
import { clientFor, deepbookSizes, findPools, quoteSwap, readAllHoldings, readStatus, withMarketFacts, } from './chain.js';
|
|
24
24
|
import { describePolicy, describeSetup, renderRows } from './describe.js';
|
|
25
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
|
-
import { failure, INSTRUCTIONS, registerDocs, result, sessionInfo, SIGN_ANNOTATIONS, SIGN_DESCRIPTION, } from './mcp.js';
|
|
28
|
+
import { failure, INSTRUCTIONS, registerDocs, result, serverInfo, sessionInfo, SIGN_ANNOTATIONS, SIGN_DESCRIPTION, } from './mcp.js';
|
|
29
29
|
import { authenticated, body, ownerPageUrl, startServer } from './server.js';
|
|
30
30
|
import { SessionSigner } from './signer.js';
|
|
31
31
|
import { SessionStore, token } from './storage.js';
|
|
@@ -233,7 +233,7 @@ const rulesInput = {
|
|
|
233
233
|
pools: z
|
|
234
234
|
.array(address)
|
|
235
235
|
.optional()
|
|
236
|
-
.describe('pool
|
|
236
|
+
.describe('pool ids from find_pools for a pair the network does not list; never ask the owner for one. 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"),
|
|
@@ -311,7 +311,7 @@ async function toSetup(client, d0, input) {
|
|
|
311
311
|
(!sw.pools || sw.pools.some((id) => normalizeSuiAddress(id) === p.poolId)));
|
|
312
312
|
if (!pools.length)
|
|
313
313
|
throw new Error(d.pools.length
|
|
314
|
-
? `No pool trades ${sw.sell} for ${sw.buy}${sw.venues ? ` on ${sw.venues.join(', ')}` : ''}${sw.pools ? ` among ${sw.pools.join(', ')}` : ''};
|
|
314
|
+
? `No pool trades ${sw.sell} for ${sw.buy}${sw.venues ? ` on ${sw.venues.join(', ')}` : ''}${sw.pools ? ` among ${sw.pools.join(', ')}` : ''}; look one up with find_pools and pass its id in pools`
|
|
315
315
|
: `Swaps are not offered on ${d.network}: it lists no pools. Propose a pass on a network that does, or leave swaps out`);
|
|
316
316
|
return {
|
|
317
317
|
coinIn,
|
|
@@ -405,6 +405,20 @@ export async function forgetSession(directory) {
|
|
|
405
405
|
await registry.all();
|
|
406
406
|
return registry.forget(basename(resolve(directory)));
|
|
407
407
|
}
|
|
408
|
+
/**
|
|
409
|
+
* What a pool pays for ten units of its non-stable coin (coinA when neither or both are
|
|
410
|
+
* stablecoins), quoted like a rule's price; ten clears DeepBook's minimum order.
|
|
411
|
+
*/
|
|
412
|
+
async function poolPrice(d, p) {
|
|
413
|
+
const flip = isStable(coinInfo(d, p.coinA).symbol) && !isStable(coinInfo(d, p.coinB).symbol);
|
|
414
|
+
const a = coinInfo(d, flip ? p.coinB : p.coinA), b = coinInfo(d, flip ? p.coinA : p.coinB);
|
|
415
|
+
const amountIn = (10n * 10n ** BigInt(a.decimals)).toString();
|
|
416
|
+
const q = await quoteSwap(clientFor(d), d, p, a.type, amountIn).catch(() => null);
|
|
417
|
+
if (!q || q.amountOut === '0')
|
|
418
|
+
return {};
|
|
419
|
+
const perUnit = (BigInt(q.amountOut) * 10n ** BigInt(a.decimals)) / BigInt(q.amountIn);
|
|
420
|
+
return { price: `${display(perUnit.toString(), b.decimals)} ${b.symbol} per ${a.symbol}` };
|
|
421
|
+
}
|
|
408
422
|
export function createPluginMcp(registry) {
|
|
409
423
|
const server = new McpServer({ name: 'agent-pass', version: '0.1.0' }, {
|
|
410
424
|
instructions: `${INSTRUCTIONS} This server also runs the owner flow: propose_authorization turns reviewed rules into a pass proposal and opens the owner's wallet page; session_info (with wait) reports when the owner has signed and the on-chain policies match; revoke_pass reopens the owner page for revocation. Show the owner the exact "rules" text returned by propose_authorization before they sign; never paraphrase amounts.`,
|
|
@@ -420,30 +434,18 @@ export function createPluginMcp(registry) {
|
|
|
420
434
|
annotations: { readOnlyHint: true },
|
|
421
435
|
}, async ({ owner }) => {
|
|
422
436
|
const path = registry.deploymentPath();
|
|
423
|
-
// What the pool pays for ten units of its non-stable coin (coinA when neither or both are
|
|
424
|
-
// stablecoins), quoted like a rule's price; ten clears DeepBook's minimum order.
|
|
425
|
-
const price = async (d, p) => {
|
|
426
|
-
const flip = isStable(coinInfo(d, p.coinA).symbol) && !isStable(coinInfo(d, p.coinB).symbol);
|
|
427
|
-
const a = coinInfo(d, flip ? p.coinB : p.coinA), b = coinInfo(d, flip ? p.coinA : p.coinB);
|
|
428
|
-
const amountIn = (10n * 10n ** BigInt(a.decimals)).toString();
|
|
429
|
-
const q = await quoteSwap(clientFor(d), d, p, a.type, amountIn).catch(() => null);
|
|
430
|
-
if (!q || q.amountOut === '0')
|
|
431
|
-
return {};
|
|
432
|
-
const perUnit = (BigInt(q.amountOut) * 10n ** BigInt(a.decimals)) / BigInt(q.amountIn);
|
|
433
|
-
return { price: `${display(perUnit.toString(), b.decimals)} ${b.symbol} per ${a.symbol}` };
|
|
434
|
-
};
|
|
435
437
|
const markets = async (d) => ({
|
|
436
438
|
network: d.network,
|
|
437
439
|
coins: d.coins,
|
|
438
440
|
pools: await Promise.all(d.pools.map(async (p) => ({
|
|
439
441
|
venue: p.venue,
|
|
440
442
|
pair: `${coinInfo(d, p.coinA).symbol}/${coinInfo(d, p.coinB).symbol}`,
|
|
441
|
-
...(await
|
|
443
|
+
...(await poolPrice(d, p)),
|
|
442
444
|
}))),
|
|
443
445
|
lending: Object.keys(d.protocols.navi.reserves).map((c) => coinInfo(d, c).symbol),
|
|
444
446
|
...(d.pools.length || Object.keys(d.protocols.navi.reserves).length
|
|
445
447
|
? {
|
|
446
|
-
otherCoins: 'These are the coins and pools this package lists, not all it can use. Any other coin the owner holds goes in a transfer or swap rule by its full type; a swap
|
|
448
|
+
otherCoins: 'These are the coins and pools this package lists, not all it can use. Any other coin the owner holds goes in a transfer or swap rule by its full type; for a swap, find_pools looks up the DeepBook and Cetus pools for it, and their ids go in pools. Never ask the owner for a pool id. The server reads the coin and the pool from chain. Lending stays with the listed coins.',
|
|
447
449
|
}
|
|
448
450
|
: {
|
|
449
451
|
note: 'Transfers and staking only: this network lists no pools and no NAVI reserves, so swap and lending rules are refused here.',
|
|
@@ -465,11 +467,44 @@ export function createPluginMcp(registry) {
|
|
|
465
467
|
}
|
|
466
468
|
: {}),
|
|
467
469
|
deployment: { path, exists: own.length > 0 },
|
|
470
|
+
server: await serverInfo(),
|
|
468
471
|
networks: [...own, ...bundled.filter((b) => !own.some((o) => o.network === b.network))],
|
|
469
472
|
});
|
|
470
473
|
});
|
|
474
|
+
server.registerTool('find_pools', {
|
|
475
|
+
description: "Find the DeepBook and Cetus pools that trade a coin, optionally against one other coin, for a swap rule. Each result is read from chain and carries its venue, id, pair, both coin types, Cetus TVL in USD when known and the pool's current price. Use it instead of asking the owner for a pool; prefer the deepest pool, and when the coin is ambiguous show the owner their balance of it and this price so they can confirm it is the right token.",
|
|
476
|
+
inputSchema: {
|
|
477
|
+
coin: symbol.describe('the coin, by symbol when list_passes lists it, else by full type'),
|
|
478
|
+
with: symbol.optional().describe('the other coin of the pair, same form; any when omitted'),
|
|
479
|
+
network: z
|
|
480
|
+
.enum(['mainnet', 'testnet', 'devnet', 'localnet'])
|
|
481
|
+
.optional()
|
|
482
|
+
.describe('defaults to the network a new pass would use'),
|
|
483
|
+
},
|
|
484
|
+
annotations: { readOnlyHint: true, openWorldHint: true },
|
|
485
|
+
}, async ({ coin, with: other, network }) => {
|
|
486
|
+
try {
|
|
487
|
+
const d0 = await registry.deployment(undefined, network);
|
|
488
|
+
const type = (s) => (/::/.test(s) ? s : coinBySymbol(d0, s).type);
|
|
489
|
+
const { d, found } = await findPools(clientFor(d0), d0, type(coin), other ? type(other) : undefined);
|
|
490
|
+
const priced = await Promise.all(found.map(async ({ pool, tvlUsd }) => ({
|
|
491
|
+
venue: pool.venue,
|
|
492
|
+
poolId: pool.poolId,
|
|
493
|
+
pair: `${coinInfo(d, pool.coinA).symbol}/${coinInfo(d, pool.coinB).symbol}`,
|
|
494
|
+
coinA: pool.coinA,
|
|
495
|
+
coinB: pool.coinB,
|
|
496
|
+
tvlUsd,
|
|
497
|
+
...(await poolPrice(d, pool)),
|
|
498
|
+
})));
|
|
499
|
+
// A pool that cannot price ten units holds too little to trade against.
|
|
500
|
+
return result({ network: d.network, pools: priced.filter((p) => 'price' in p) });
|
|
501
|
+
}
|
|
502
|
+
catch (error) {
|
|
503
|
+
return failure(error, 'Pool lookup failed');
|
|
504
|
+
}
|
|
505
|
+
});
|
|
471
506
|
server.registerTool('propose_authorization', {
|
|
472
|
-
description: "Create a new pass proposal from rules in human units (coins by symbol when list_passes lists them for the network, else by full type, with pool ids for a pair the network list lacks; amounts as decimal strings) and open the owner's review page, where they connect Slush, check the same rules and sign one transaction. That transaction creates the pass, converts the owner's coins into their own address balance as far as the caps need, issues the allowances the agent will draw on, installs the policies and gives the new agent key its gas; the owner's coins never enter the pass. Returns the exact rules text to show the owner, the review URL and the session address. Nothing is on chain until the owner signs; poll session_info with wait afterwards.",
|
|
507
|
+
description: "Create a new pass proposal from rules in human units (coins by symbol when list_passes lists them for the network, else by full type, with pool ids from find_pools for a pair the network list lacks; amounts as decimal strings) and open the owner's review page, where they connect Slush, check the same rules and sign one transaction. That transaction creates the pass, converts the owner's coins into their own address balance as far as the caps need, issues the allowances the agent will draw on, installs the policies and gives the new agent key its gas; the owner's coins never enter the pass. Returns the exact rules text to show the owner, the review URL and the session address. Nothing is on chain until the owner signs; poll session_info with wait afterwards.",
|
|
473
508
|
inputSchema: {
|
|
474
509
|
name: z
|
|
475
510
|
.string()
|
package/docs/PLAYBOOK.md
CHANGED
|
@@ -54,14 +54,17 @@ pool), so list Cetus as well when trades will be small. A network may list no po
|
|
|
54
54
|
reserves; `list_passes` says so, and swap or lending rules are refused there.
|
|
55
55
|
|
|
56
56
|
**Coins and pools.** The coins and pools `list_passes` shows for a network are the ones this
|
|
57
|
-
package lists, not the only ones it can use. Name a listed coin by symbol
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
57
|
+
package lists, not the only ones it can use. Name a listed coin by symbol, and any other coin the
|
|
58
|
+
owner holds by its full type. To find its type and balance, ask the owner for their address and
|
|
59
|
+
call `list_passes` with `owner`: it adds every coin that address holds per network. For a swap,
|
|
60
|
+
find the pool with `find_pools`, which looks up DeepBook and Cetus, reads each pool from chain and
|
|
61
|
+
quotes its current price; pick the deepest one for the pair and pass its id in `pools`. Never ask
|
|
62
|
+
the owner for a pool id. When you are unsure you have the right token (several coins share a
|
|
63
|
+
symbol, or the owner named it loosely), show the owner their balance of it and its current price
|
|
64
|
+
and ask them to confirm. The server and the review page read a coin's symbol and decimals and a
|
|
65
|
+
pool's venue from chain, never from you, and the page shows an unlisted coin with its type so
|
|
66
|
+
the owner can tell a look-alike from the real one. Lending is limited to the coins the network
|
|
67
|
+
lists as NAVI reserves.
|
|
65
68
|
|
|
66
69
|
**Lifetime caps.** `caps` gives, per coin, the total the key may ever withdraw, for exactly the
|
|
67
70
|
coins the rules spend (staking spends SUI). A daily limit may not exceed its coin's cap. Size
|
|
@@ -89,9 +92,9 @@ ask for a yes, unless they already stated every rule explicitly.
|
|
|
89
92
|
|
|
90
93
|
## 2. Propose the pass and wait for the signature
|
|
91
94
|
|
|
92
|
-
`propose_authorization`, `propose_change`, `list_passes`, `revoke_pass` and
|
|
93
|
-
the stdio server (`agent-pass mcp`), which keeps every session on the
|
|
94
|
-
signer has only `session_info` and `sign_transaction`.
|
|
95
|
+
`propose_authorization`, `propose_change`, `list_passes`, `find_pools`, `revoke_pass` and
|
|
96
|
+
`forget_pass` live on the stdio server (`agent-pass mcp`), which keeps every session on the
|
|
97
|
+
machine. A per-session HTTP signer has only `session_info` and `sign_transaction`.
|
|
95
98
|
|
|
96
99
|
The pass goes on the network the owner names (`network`, one of `list_passes.networks`), else on
|
|
97
100
|
the first network listed. A private publish of one's own takes a `deployment` path instead.
|
|
@@ -122,6 +125,10 @@ Start with `session_info`. It returns:
|
|
|
122
125
|
- `directory`, `sdk` and `server`: the session directory, how to install and import the SDK,
|
|
123
126
|
and this server's version (after an upgrade the client must restart the server).
|
|
124
127
|
|
|
128
|
+
**Updates.** When `server.update` is present in `list_passes` or `session_info`, a newer release
|
|
129
|
+
is out. Tell the owner once per conversation, with `server.update.command` and the reminder to
|
|
130
|
+
restart the agent afterwards, then carry on with the task; never run the command yourself.
|
|
131
|
+
|
|
125
132
|
Act by writing a Node script against the SDK. Run `sdk.install` once in your working directory,
|
|
126
133
|
then:
|
|
127
134
|
|
package/package.json
CHANGED
|
@@ -59,14 +59,17 @@ pool), so list Cetus as well when trades will be small. A network may list no po
|
|
|
59
59
|
reserves; `list_passes` says so, and swap or lending rules are refused there.
|
|
60
60
|
|
|
61
61
|
**Coins and pools.** The coins and pools `list_passes` shows for a network are the ones this
|
|
62
|
-
package lists, not the only ones it can use. Name a listed coin by symbol
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
62
|
+
package lists, not the only ones it can use. Name a listed coin by symbol, and any other coin the
|
|
63
|
+
owner holds by its full type. To find its type and balance, ask the owner for their address and
|
|
64
|
+
call `list_passes` with `owner`: it adds every coin that address holds per network. For a swap,
|
|
65
|
+
find the pool with `find_pools`, which looks up DeepBook and Cetus, reads each pool from chain and
|
|
66
|
+
quotes its current price; pick the deepest one for the pair and pass its id in `pools`. Never ask
|
|
67
|
+
the owner for a pool id. When you are unsure you have the right token (several coins share a
|
|
68
|
+
symbol, or the owner named it loosely), show the owner their balance of it and its current price
|
|
69
|
+
and ask them to confirm. The server and the review page read a coin's symbol and decimals and a
|
|
70
|
+
pool's venue from chain, never from you, and the page shows an unlisted coin with its type so
|
|
71
|
+
the owner can tell a look-alike from the real one. Lending is limited to the coins the network
|
|
72
|
+
lists as NAVI reserves.
|
|
70
73
|
|
|
71
74
|
**Lifetime caps.** `caps` gives, per coin, the total the key may ever withdraw, for exactly the
|
|
72
75
|
coins the rules spend (staking spends SUI). A daily limit may not exceed its coin's cap. Size
|
|
@@ -94,9 +97,9 @@ ask for a yes, unless they already stated every rule explicitly.
|
|
|
94
97
|
|
|
95
98
|
## 2. Propose the pass and wait for the signature
|
|
96
99
|
|
|
97
|
-
`propose_authorization`, `propose_change`, `list_passes`, `revoke_pass` and
|
|
98
|
-
the stdio server (`agent-pass mcp`), which keeps every session on the
|
|
99
|
-
signer has only `session_info` and `sign_transaction`.
|
|
100
|
+
`propose_authorization`, `propose_change`, `list_passes`, `find_pools`, `revoke_pass` and
|
|
101
|
+
`forget_pass` live on the stdio server (`agent-pass mcp`), which keeps every session on the
|
|
102
|
+
machine. A per-session HTTP signer has only `session_info` and `sign_transaction`.
|
|
100
103
|
|
|
101
104
|
The pass goes on the network the owner names (`network`, one of `list_passes.networks`), else on
|
|
102
105
|
the first network listed. A private publish of one's own takes a `deployment` path instead.
|
|
@@ -127,6 +130,10 @@ Start with `session_info`. It returns:
|
|
|
127
130
|
- `directory`, `sdk` and `server`: the session directory, how to install and import the SDK,
|
|
128
131
|
and this server's version (after an upgrade the client must restart the server).
|
|
129
132
|
|
|
133
|
+
**Updates.** When `server.update` is present in `list_passes` or `session_info`, a newer release
|
|
134
|
+
is out. Tell the owner once per conversation, with `server.update.command` and the reminder to
|
|
135
|
+
restart the agent afterwards, then carry on with the task; never run the command yourself.
|
|
136
|
+
|
|
130
137
|
Act by writing a Node script against the SDK. Run `sdk.install` once in your working directory,
|
|
131
138
|
then:
|
|
132
139
|
|