@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 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. What the
413
- * authorization converts from the owner's coins into their address balance is, per coin, the
414
- * shortfall between what the rules can draw (`drawable`) and what that balance already holds.
415
- * Converted coins stay the owner's to spend.
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 have = (coin) => BigInt(held[normalizeStructTag(coin)] ?? '0');
419
- return Object.fromEntries(Object.entries(drawable(setup)).map(([coin, want]) => [
420
- coin,
421
- (BigInt(want) > have(coin) ? BigInt(want) - have(coin) : 0n).toString(),
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
- * SUI the owner must hold in total to sign, address balance and coin objects together: what the
426
- * rules may draw, the stipend and a fee reserve. Gas comes from either side, so nothing has to
427
- * be a coin object; the authorization consolidates what it touches into the address balance.
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(drawable(setup)[SUI]) + BigInt(setup.gasAmount) + OWNER_FEE_RESERVE).toString();
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.** Allowances draw only from the owner's address balance, never from coin
86
- objects. The review page reads both, and the signature converts coin objects into the address
87
- balance as far as each cap (plus `gasCapSui` for SUI) needs; the converted coins stay theirs. The
88
- owner needs that much in total, address balance and coin objects together, plus `gasSui` and fees.
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mysten-incubation/sui-agent-pass",
3
- "version": "0.1.9",
3
+ "version": "0.1.10",
4
4
  "type": "module",
5
5
  "description": "AI builds transactions. A session signer signs. Your pass enforces the rules.",
6
6
  "repository": {
@@ -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.** Allowances draw only from the owner's address balance, never from coin
91
- objects. The review page reads both, and the signature converts coin objects into the address
92
- balance as far as each cap (plus `gasCapSui` for SUI) needs; the converted coins stay theirs. The
93
- owner needs that much in total, address balance and coin objects together, plus `gasSui` and fees.
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.