@mysten-incubation/sui-agent-pass 0.1.9 → 0.1.10
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 +2 -0
- package/dist/domain.js +34 -14
- package/dist/plugin.js +17 -3
- package/docs/PLAYBOOK.md +10 -4
- package/package.json +1 -1
- package/skills/agent-pass/SKILL.md +10 -4
- package/web-dist/assets/{index-Bt0vwsup.js → index-B-mJDYLu.js} +62 -62
- package/web-dist/assets/{mount-D_hAnWmA.js → mount-Dgq2YumU.js} +1 -1
- package/web-dist/index.html +1 -1
package/dist/describe.js
CHANGED
|
@@ -277,6 +277,8 @@ const normalizeType = (type) => type.replace(/^0x0*([0-9a-f]+)::/i, (_, a) => `$
|
|
|
277
277
|
/** The owner's rule behind an abort, from the codes the policy modules define. */
|
|
278
278
|
export function describeRefusal(d, error, recipient) {
|
|
279
279
|
const abort = error.MoveAbort;
|
|
280
|
+
if (error.message === 'InsufficientBalanceForWithdraw')
|
|
281
|
+
return "The owner's address balance does not hold enough of this coin right now. This is not one of the owner's rules: the action can run once the balance covers it (InsufficientBalanceForWithdraw)";
|
|
280
282
|
if (!abort?.location?.module)
|
|
281
283
|
return error.message;
|
|
282
284
|
const code = Number(abort.cleverError?.errorCode ?? abort.abortCode);
|
package/dist/domain.js
CHANGED
|
@@ -409,25 +409,45 @@ export function drawable(setup) {
|
|
|
409
409
|
return Object.fromEntries(Object.entries(need).map(([c, v]) => [c, v.toString()]));
|
|
410
410
|
}
|
|
411
411
|
/**
|
|
412
|
-
* Allowances draw only from the owner's address balance, never from coin objects.
|
|
413
|
-
* authorization converts
|
|
414
|
-
*
|
|
415
|
-
*
|
|
412
|
+
* Allowances draw only from the owner's address balance, never from coin objects. The
|
|
413
|
+
* authorization converts coin objects into that balance, per coin, as far as the rules can draw
|
|
414
|
+
* (`drawable`) and the owner has them; the caps are limits, not money that must exist at signing.
|
|
415
|
+
* SUI keeps the stipend and the fee reserve in coin objects for the transaction itself. `mintable`
|
|
416
|
+
* is a demo coin the transaction mints instead, converted in full. Converted coins stay the
|
|
417
|
+
* owner's to spend.
|
|
416
418
|
*/
|
|
417
|
-
export function conversion(setup, held) {
|
|
418
|
-
const
|
|
419
|
-
return Object.fromEntries(Object.entries(drawable(setup)).map(([coin, want]) =>
|
|
420
|
-
coin
|
|
421
|
-
|
|
422
|
-
|
|
419
|
+
export function conversion(setup, held, mintable = null) {
|
|
420
|
+
const of = (coin) => held[normalizeStructTag(coin)];
|
|
421
|
+
return Object.fromEntries(Object.entries(drawable(setup)).map(([coin, want]) => {
|
|
422
|
+
const short = BigInt(want) - BigInt(of(coin)?.addressBalance ?? '0');
|
|
423
|
+
if (short <= 0n)
|
|
424
|
+
return [coin, '0'];
|
|
425
|
+
if (mintable && sameCoin(mintable, coin))
|
|
426
|
+
return [coin, short.toString()];
|
|
427
|
+
let spare = BigInt(of(coin)?.coinBalance ?? '0');
|
|
428
|
+
if (sameCoin(coin, SUI))
|
|
429
|
+
spare -= BigInt(setup.gasAmount) + OWNER_FEE_RESERVE;
|
|
430
|
+
return [coin, (spare <= 0n ? 0n : short < spare ? short : spare).toString()];
|
|
431
|
+
}));
|
|
423
432
|
}
|
|
424
433
|
/**
|
|
425
|
-
*
|
|
426
|
-
*
|
|
427
|
-
*
|
|
434
|
+
* What the rules can draw beyond what the address balance will hold right after signing, per
|
|
435
|
+
* coin. Not an error: withdrawals work while the balance covers them, and swap proceeds land in
|
|
436
|
+
* it. Coins with nothing uncovered are left out.
|
|
437
|
+
*/
|
|
438
|
+
export function uncovered(setup, held, mintable = null) {
|
|
439
|
+
const convert = conversion(setup, held, mintable);
|
|
440
|
+
return Object.fromEntries(Object.entries(drawable(setup)).flatMap(([coin, want]) => {
|
|
441
|
+
const covered = BigInt(held[normalizeStructTag(coin)]?.addressBalance ?? '0') + BigInt(convert[coin] ?? '0');
|
|
442
|
+
return covered >= BigInt(want) ? [] : [[coin, { drawable: want, covered: covered.toString() }]];
|
|
443
|
+
}));
|
|
444
|
+
}
|
|
445
|
+
/**
|
|
446
|
+
* SUI the owner must hold to sign, address balance and coin objects together: only what this
|
|
447
|
+
* transaction moves, the agent's gas stipend, plus a fee reserve. The caps need nothing up front.
|
|
428
448
|
*/
|
|
429
449
|
export function requiredSui(setup) {
|
|
430
|
-
return (BigInt(
|
|
450
|
+
return (BigInt(setup.gasAmount) + OWNER_FEE_RESERVE).toString();
|
|
431
451
|
}
|
|
432
452
|
/** Set aside for the owner's own transaction fees when checking what they hold. */
|
|
433
453
|
export const OWNER_FEE_RESERVE = 50000000n;
|
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, findPools, quoteSwap, readAllHoldings, readStatus, withMarketFacts, } from './chain.js';
|
|
23
|
+
import { clientFor, deepbookSizes, findPools, quoteSwap, readAllHoldings, readHoldings, readStatus, withMarketFacts, } from './chain.js';
|
|
24
24
|
import { describePolicy, describeSetup, renderRows } from './describe.js';
|
|
25
|
-
import { address, coinBySymbol, coinInfo, DAY_MS, defaultGasDaily, DeploymentSchema, display, isStable, limitUnits, poolsFor, recipientName, sameCoin, SetupSchema, showCoin, units, VenueSchema, } from './domain.js';
|
|
25
|
+
import { address, coinBySymbol, coinInfo, DAY_MS, defaultGasDaily, DeploymentSchema, display, isStable, limitUnits, poolsFor, recipientName, requiredSui, sameCoin, SetupSchema, showCoin, SUI, uncovered, 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, serverInfo, sessionInfo, SIGN_ANNOTATIONS, SIGN_DESCRIPTION, } from './mcp.js';
|
|
@@ -529,9 +529,12 @@ export function createPluginMcp(registry) {
|
|
|
529
529
|
.string()
|
|
530
530
|
.optional()
|
|
531
531
|
.describe('path of a private deployment.json; overrides network'),
|
|
532
|
+
owner: address
|
|
533
|
+
.optional()
|
|
534
|
+
.describe("the owner's address, when they gave it: adds warnings where the caps reach past what they hold, or when they cannot sign yet"),
|
|
532
535
|
},
|
|
533
536
|
annotations: { readOnlyHint: false, destructiveHint: false },
|
|
534
|
-
}, async ({ deployment, network, ...input }) => {
|
|
537
|
+
}, async ({ deployment, network, owner, ...input }) => {
|
|
535
538
|
try {
|
|
536
539
|
const d0 = await registry.deployment(deployment, network);
|
|
537
540
|
const { setup, d } = await toSetup(clientFor(d0), d0, input);
|
|
@@ -557,6 +560,17 @@ export function createPluginMcp(registry) {
|
|
|
557
560
|
warnings.push(`DeepBook takes its fee from the input and fills nothing under its minimum order, so a sale needs at least ${showCoin(d, sw.coinIn, floor)} and this pass allows ${showCoin(d, sw.coinIn, sw.perTrade)}: no sale could ever happen. Propose again with Cetus added to venues or a larger perTrade, unless the owner wants exactly this.`);
|
|
558
561
|
}
|
|
559
562
|
}
|
|
563
|
+
if (owner) {
|
|
564
|
+
const holdings = await readHoldings(clientFor(d), d, owner).catch(() => null);
|
|
565
|
+
if (holdings) {
|
|
566
|
+
const sui = holdings[SUI];
|
|
567
|
+
const total = BigInt(sui?.addressBalance ?? '0') + BigInt(sui?.coinBalance ?? '0');
|
|
568
|
+
if (total < BigInt(requiredSui(setup)))
|
|
569
|
+
warnings.push(`The owner holds ${showCoin(d, SUI, total.toString())} and signing needs ${showCoin(d, SUI, requiredSui(setup))} for the agent's gas stipend and the fee; they must add SUI before they can sign.`);
|
|
570
|
+
for (const [coin, { drawable: most, covered }] of Object.entries(uncovered(setup, holdings, d.demoFaucet?.coin ?? null)))
|
|
571
|
+
warnings.push(`The rules can draw up to ${showCoin(d, coin, most)} but the owner's address balance will hold ${showCoin(d, coin, covered)} after signing. Signing still works; each action runs only while the balance covers it, and swap proceeds that land there count. Tell the owner before they sign.`);
|
|
572
|
+
}
|
|
573
|
+
}
|
|
560
574
|
return result({
|
|
561
575
|
pass: session.id,
|
|
562
576
|
sessionAddress: session.store.state.sessionAddress,
|
package/docs/PLAYBOOK.md
CHANGED
|
@@ -82,10 +82,16 @@ ask for something like "restake with a better validator".
|
|
|
82
82
|
to the agent key, outside the rules. `gasCapSui` (default 0.1) is a separate allowance the key
|
|
83
83
|
draws on to top up its gas, and `gasDailySui` (default a fifth of `gasCapSui`) its daily limit.
|
|
84
84
|
|
|
85
|
-
**What the owner needs.**
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
85
|
+
**What the owner needs.** Signing moves only the agent's gas stipend and pays the fee, so that is
|
|
86
|
+
all the owner must hold, in SUI. The caps are limits on what the rules may draw later, not money
|
|
87
|
+
needed now. Allowances draw only from the owner's address balance, never from coin objects; the
|
|
88
|
+
signature converts the owner's coin objects into that balance as far as each cap needs and they
|
|
89
|
+
have them, and the converted coins stay theirs. When a cap reaches past what the owner holds,
|
|
90
|
+
signing still works: each action runs while the balance covers it, and swap proceeds landing
|
|
91
|
+
there count right away, which is how a chain like TAKE to SUI to USDC funds its later legs. Pass
|
|
92
|
+
`owner` to `propose_authorization` when you know the address, and tell the owner about any
|
|
93
|
+
`warnings` it returns before they sign. An action refused because the balance was short is not
|
|
94
|
+
one of the owner's rules; say so.
|
|
89
95
|
|
|
90
96
|
Before you call anything, show the owner the rules you intend to propose as one short list and
|
|
91
97
|
ask for a yes, unless they already stated every rule explicitly.
|
package/package.json
CHANGED
|
@@ -87,10 +87,16 @@ ask for something like "restake with a better validator".
|
|
|
87
87
|
to the agent key, outside the rules. `gasCapSui` (default 0.1) is a separate allowance the key
|
|
88
88
|
draws on to top up its gas, and `gasDailySui` (default a fifth of `gasCapSui`) its daily limit.
|
|
89
89
|
|
|
90
|
-
**What the owner needs.**
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
90
|
+
**What the owner needs.** Signing moves only the agent's gas stipend and pays the fee, so that is
|
|
91
|
+
all the owner must hold, in SUI. The caps are limits on what the rules may draw later, not money
|
|
92
|
+
needed now. Allowances draw only from the owner's address balance, never from coin objects; the
|
|
93
|
+
signature converts the owner's coin objects into that balance as far as each cap needs and they
|
|
94
|
+
have them, and the converted coins stay theirs. When a cap reaches past what the owner holds,
|
|
95
|
+
signing still works: each action runs while the balance covers it, and swap proceeds landing
|
|
96
|
+
there count right away, which is how a chain like TAKE to SUI to USDC funds its later legs. Pass
|
|
97
|
+
`owner` to `propose_authorization` when you know the address, and tell the owner about any
|
|
98
|
+
`warnings` it returns before they sign. An action refused because the balance was short is not
|
|
99
|
+
one of the owner's rules; say so.
|
|
94
100
|
|
|
95
101
|
Before you call anything, show the owner the rules you intend to propose as one short list and
|
|
96
102
|
ask for a yes, unless they already stated every rule explicitly.
|