agentwallet-mcp 1.14.1 → 1.14.2

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/README.md CHANGED
@@ -384,7 +384,7 @@ A per-transaction cap bounds one signature. It cannot see that the five hundredt
384
384
 
385
385
  An unknown key stops the server, so a misspelled `budgets` cannot run unbounded. `spend_policy` reports the file, its sha256 (compare it with the file you reviewed), what each budget has spent and has left, and which tools the policy hid. `quote_x402` answers "would this be paid?" for a URL before anything is signed.
386
386
 
387
- **The ledger.** Budgets are enforced from an append-only, hash-chained JSONL file (`AGENTWALLET_LEDGER`, or the policy's `ledger`, or `agentwallet-ledger.jsonl` beside the policy when a budget needs one). A spend is reserved on it before the signature exists; a reservation whose signing fails is voided by a later line; the endpoint's or chain's answer is settled onto it afterwards. On startup the chain is verified, and a ledger that does not verify refuses every spend until the operator has looked at it. `list_payments` reads it, `export_receipts` turns it into CSV or JSONL, and `AGENTWALLET_WEBHOOK_URL` receives every line as it is written.
387
+ **The ledger.** Budgets are enforced from an append-only, hash-chained JSONL file (`AGENTWALLET_LEDGER`, or the policy's `ledger`, or `agentwallet-ledger.jsonl` beside the policy when a budget needs one). A spend is reserved on it before the signature exists; a reservation whose signing fails is voided by a later line; the endpoint's or chain's answer is settled onto it afterwards. The chain is verified on every read, and a ledger that does not verify refuses every spend until the operator has looked at it. Several agentwallet-mcp processes may share one ledger (two MCP clients on one wallet): the file is re-read before every check, and the check-and-append step holds an exclusive lock file (`<ledger>.lock`) across processes, so the budget is one budget however many processes enforce it; a lock older than 30 seconds is treated as left by a dead process. A transaction signed without being broadcast (`sign_transaction`) is reserved and counted like a send, since whoever holds it can broadcast it. `list_payments` reads it, `export_receipts` turns it into CSV or JSONL, and `AGENTWALLET_WEBHOOK_URL` receives every line as it is written.
388
388
 
389
389
  ### Encrypted keyfiles
390
390
 
package/build/index.js CHANGED
@@ -444,12 +444,16 @@ async function api(path, method = 'GET', body, extraHeaders) {
444
444
  delete extraHeaders['X-AGW-CTX'];
445
445
  }
446
446
  let reserved = null;
447
- const m = /^\/wallets\/([^/]+)\/send(\?|$)/.exec(path);
448
- if (method === 'POST' && m && body) {
447
+ // /sign with a transaction body is the same spend as /send: the caller gets
448
+ // a broadcast-ready raw transaction (2026-10-14 report, 1.14.2). A message
449
+ // signature (body.message) moves nothing and is not gated.
450
+ const m = /^\/wallets\/([^/]+)\/(send|sign)(\?|$)/.exec(path);
451
+ const signsTx = m !== null && (m[2] === 'send' || typeof body?.message !== 'string');
452
+ if (method === 'POST' && m && body && signsTx) {
449
453
  const chainId = localChainId(body, new URLSearchParams(path.split('?')[1] || ''));
450
454
  const d = await describeSend(body, chainId);
451
455
  assertSendTarget(d, chainId);
452
- const input = spendInputOf(d, chainId, ctx.tool || 'send', m[1], ctx);
456
+ const input = spendInputOf(d, chainId, ctx.tool || (m[2] === 'sign' ? 'sign_transaction' : 'send'), m[1], ctx);
453
457
  if (input)
454
458
  reserved = reserveSpend(input);
455
459
  }
@@ -457,7 +461,10 @@ async function api(path, method = 'GET', body, extraHeaders) {
457
461
  const out = await apiInner(path, method, body, extraHeaders);
458
462
  if (reserved !== null) {
459
463
  const r = out;
460
- LEDGER.settle(reserved, 'broadcast', typeof r?.tx_hash === 'string' ? r.tx_hash : typeof r?.signature === 'string' ? r.signature : null);
464
+ if (m[2] === 'sign')
465
+ LEDGER.settle(reserved, 'signed', 'raw transaction returned to the caller; not broadcast by this process, so it stays counted');
466
+ else
467
+ LEDGER.settle(reserved, 'broadcast', typeof r?.tx_hash === 'string' ? r.tx_hash : typeof r?.signature === 'string' ? r.signature : null);
461
468
  }
462
469
  return out;
463
470
  }
@@ -1111,7 +1118,7 @@ function resolveChainId(network) {
1111
1118
  const AddressSchema = z.string().regex(/^(0x[a-fA-F0-9]{40}|[1-9A-HJ-NP-Za-km-z]{32,44})$/, 'Must be a 0x-prefixed 40-hex EVM address or a base58 Solana address');
1112
1119
  const server = new McpServer({
1113
1120
  name: 'agentwallet',
1114
- version: '1.14.1',
1121
+ version: '1.14.2',
1115
1122
  }, {
1116
1123
  instructions: `AgentWallet gives AI agents their own blockchain wallets. ${anyLocalMode()
1117
1124
  ? 'This server runs in LOCAL (self-custody) mode: the operator supplied the key, signing happens in this process, and the only spend guards are the environment caps (AGENTWALLET_MAX_TX_NATIVE / _TOKEN / _SOL per transaction, AGENTWALLET_MAX_AUTOPAY and AGENTWALLET_AUTOPAY_ASSETS for x402, AGENTWALLET_X402_MAX_TIMEOUT for authorization windows). There is no server-side pause, daily limit or approval email; pause_wallet, unpause_wallet and delete_wallet are refused.'
@@ -1225,7 +1232,8 @@ tool('get_balance', 'Get the native token balance for a wallet on a specific cha
1225
1232
  // ─── Tool: sign_transaction ──────────────────────────────────────
1226
1233
  tool('sign_transaction', 'Sign a transaction with a wallet\'s private key. ' +
1227
1234
  'For EVM: returns signed raw transaction hex. For Solana: returns base64 signed transaction. ' +
1228
- 'Does NOT broadcast — use send_transaction for sign + broadcast.', {
1235
+ 'Does NOT broadcast. Use send_transaction for sign + broadcast. ' +
1236
+ 'A signed transaction is a spend: under a spend policy it is checked, reserved on the ledger and counted against the budgets exactly as send_transaction is, because the caller can broadcast it.', {
1229
1237
  wallet_id: z.number().int().describe('Wallet ID'),
1230
1238
  to: AddressSchema.describe('Destination address (0x-prefixed for EVM, Base58 for Solana)'),
1231
1239
  chain_id: z.number().int().optional().describe('Chain ID (defaults to wallet\'s default)'),
package/build/ledger.d.ts CHANGED
@@ -76,42 +76,59 @@ export interface Limits {
76
76
  } | null;
77
77
  unpriced: 'refuse' | 'allow';
78
78
  }
79
+ export interface LedgerOptions {
80
+ /** Tests shorten the wait for a held lock. */
81
+ lockWaitMs?: number;
82
+ }
79
83
  export declare class Ledger {
80
84
  readonly path: string | null;
81
85
  private entries;
82
86
  private last;
83
87
  private seq;
84
- /** null = intact; otherwise what broke. */
88
+ /** null = intact; otherwise what broke. Refreshed from the file on every read. */
85
89
  integrity: string | null;
86
90
  private webhook;
91
+ private lockWaitMs;
87
92
  constructor(path: string | null, webhook?: {
88
93
  url: string;
89
94
  secret: string;
90
- } | null);
91
- private load;
95
+ } | null, opts?: LedgerOptions);
96
+ /**
97
+ * Re-read the file so the sums include what every other process wrote.
98
+ * The file must still begin with what this process has seen, line for
99
+ * line; otherwise it was rewritten underneath us and nothing is adopted.
100
+ */
101
+ private sync;
102
+ /** Take the exclusive lock file; returns the release. A lock a dead process left behind is cleared after LOCK_STALE_MS. */
103
+ private lock;
92
104
  private append;
93
105
  private notify;
94
106
  /** Spends not voided, newest last. */
95
107
  private liveSpends;
96
- /** Dollar sums over rolling windows; nowMs for tests. */
108
+ /** Dollar sums over rolling windows from a fresh read of the file; nowMs for tests. */
97
109
  spentUsd(nowMs?: number, host?: string): Spent;
98
110
  spentNative(chainId: number, nowMs?: number): Spent;
111
+ private sumUsd;
112
+ private sumNative;
99
113
  private sum;
100
- /** Would this spend fit every limit? The reason names the first it does not. */
114
+ /** Would this spend fit every limit, given what every process has written so far? The reason names the first limit it does not. */
101
115
  check(input: ReserveInput, limits: Limits, nowMs?: number): {
102
116
  ok: true;
103
117
  } | {
104
118
  ok: false;
105
119
  reason: string;
106
120
  };
107
- /** Check and record in one synchronous step. Returns the spend's seq. */
121
+ private evaluate;
122
+ /** Re-read, check and record in one step under the lock. Returns the spend's seq. */
108
123
  reserve(input: ReserveInput, limits: Limits): number;
124
+ /** A follow-up line, written under the lock after a fresh read so it chains onto whatever any process wrote last. */
125
+ private follow;
109
126
  /** The spend never happened (signing or broadcast failed before anything left this process). */
110
127
  void(ref: number, detail: string): void;
111
128
  /** What the endpoint or chain said afterwards. Never throws: the payment already happened. */
112
129
  settle(ref: number, outcome: string, detail: string | null): void;
113
130
  size(): number;
114
- /** Spends newest first with their latest follow-up folded in. */
131
+ /** Spends newest first with their latest follow-up folded in, from a fresh read. */
115
132
  payments(opts: {
116
133
  limit: number;
117
134
  host?: string;
package/build/ledger.js CHANGED
@@ -3,69 +3,146 @@
3
3
  * process reserved, and what became of it.
4
4
  *
5
5
  * Budgets are enforced from it, so a reservation is written BEFORE the
6
- * signature exists: check and append happen in one synchronous step, which in
7
- * a single-threaded process means two concurrent calls cannot both fit the
8
- * last dollar. A reservation whose signing fails is voided by a later line;
9
- * the spend line itself is never edited, so the chain stays verifiable.
6
+ * signature exists. Check and append happen in one step under an exclusive
7
+ * lock file, after the file has been re-read and the chain re-verified, so
8
+ * two concurrent calls cannot both fit the last dollar, whether they come
9
+ * from one process or from several agentwallet-mcp processes sharing the
10
+ * file (two MCP clients configured with the same wallet and ledger). A
11
+ * reservation whose signing fails is voided by a later line; the spend line
12
+ * itself is never edited, so the chain stays verifiable.
10
13
  *
11
14
  * Each line carries the sha256 of the previous line's hash plus its own body.
12
- * On load the chain is verified; a broken chain with a budget configured
13
- * refuses every spend until the operator looks at the file.
15
+ * The chain is verified on every read; a broken chain with a budget
16
+ * configured refuses every spend until the operator looks at the file, and a
17
+ * file that no longer holds what this process wrote is refused the same way.
14
18
  */
15
- import { appendFileSync, existsSync, readFileSync, openSync, closeSync } from 'node:fs';
19
+ import { appendFileSync, existsSync, readFileSync, openSync, closeSync, writeSync, unlinkSync, statSync } from 'node:fs';
16
20
  import { createHash, createHmac } from 'node:crypto';
17
21
  import { DAY_MS, MONTH_MS, units, rawToUnits, human } from './policy.js';
18
22
  const GENESIS = '0'.repeat(64);
19
23
  const MAX_LINE = 16 * 1024;
24
+ /** How long a spend waits for another process to finish its own check-and-append. */
25
+ const LOCK_WAIT_MS = 10_000;
26
+ /** A lock older than this was left by a process that died holding it. */
27
+ const LOCK_STALE_MS = 30_000;
28
+ const sleeper = new Int32Array(new SharedArrayBuffer(4));
29
+ function sleepSync(ms) { Atomics.wait(sleeper, 0, 0, ms); }
20
30
  function hashOf(prev, body) {
21
31
  return createHash('sha256').update(prev + '\n' + JSON.stringify(body)).digest('hex');
22
32
  }
33
+ /** Parse and verify a ledger file's text. A trailing line without its newline is still being written by another process and is not read. */
34
+ function parseChain(text) {
35
+ const complete = text.endsWith('\n') || !text.includes('\n') && text.trim() === '' ? text : text.slice(0, text.lastIndexOf('\n') + 1);
36
+ const lines = complete.split('\n').filter(l => l.trim());
37
+ const out = { entries: [], last: GENESIS, seq: 0, integrity: null };
38
+ let prev = GENESIS;
39
+ for (let i = 0; i < lines.length; i++) {
40
+ let e;
41
+ try {
42
+ e = JSON.parse(lines[i]);
43
+ }
44
+ catch {
45
+ out.integrity = `line ${i + 1} is not JSON`;
46
+ break;
47
+ }
48
+ const { hash, ...body } = e;
49
+ if (e.prev !== prev || hashOf(prev, body) !== hash) {
50
+ out.integrity = `chain broken at line ${i + 1} (seq ${e.seq})`;
51
+ break;
52
+ }
53
+ if (e.seq !== out.seq + 1) {
54
+ out.integrity = `sequence gap at line ${i + 1}`;
55
+ break;
56
+ }
57
+ out.entries.push(e);
58
+ out.seq = e.seq;
59
+ prev = hash;
60
+ }
61
+ out.last = prev;
62
+ return out;
63
+ }
23
64
  export class Ledger {
24
65
  path;
25
66
  entries = [];
26
67
  last = GENESIS;
27
68
  seq = 0;
28
- /** null = intact; otherwise what broke. */
69
+ /** null = intact; otherwise what broke. Refreshed from the file on every read. */
29
70
  integrity = null;
30
71
  webhook = null;
31
- constructor(path, webhook) {
72
+ lockWaitMs = LOCK_WAIT_MS;
73
+ constructor(path, webhook, opts) {
32
74
  this.path = path;
33
75
  this.webhook = webhook ?? null;
34
- if (path)
35
- this.load();
76
+ if (opts?.lockWaitMs !== undefined)
77
+ this.lockWaitMs = opts.lockWaitMs;
78
+ if (path) {
79
+ // Create it now so a permissions problem shows at startup, not at the first spend.
80
+ if (!existsSync(path))
81
+ closeSync(openSync(path, 'a', 0o600));
82
+ this.sync();
83
+ }
36
84
  }
37
- load() {
85
+ /**
86
+ * Re-read the file so the sums include what every other process wrote.
87
+ * The file must still begin with what this process has seen, line for
88
+ * line; otherwise it was rewritten underneath us and nothing is adopted.
89
+ */
90
+ sync() {
91
+ if (!this.path)
92
+ return;
38
93
  if (!existsSync(this.path)) {
39
- // Create it now so a permissions problem shows at startup, not at the first spend.
40
- closeSync(openSync(this.path, 'a', 0o600));
94
+ this.integrity = `the file was removed while this process was running (it held seq ${this.seq})`;
41
95
  return;
42
96
  }
43
- const text = readFileSync(this.path, 'utf8');
44
- const lines = text.split('\n').filter(l => l.trim());
45
- let prev = GENESIS;
46
- for (let i = 0; i < lines.length; i++) {
47
- let e;
97
+ const c = parseChain(readFileSync(this.path, 'utf8'));
98
+ const n = this.entries.length;
99
+ if (c.entries.length < n || (n > 0 && c.entries[n - 1].hash !== this.last)) {
100
+ this.integrity = c.integrity && c.entries.length < n ? c.integrity : `the file was rewritten underneath this process (it held seq ${this.seq} ending ${this.last.slice(0, 12)}, the file now ends at seq ${c.seq})`;
101
+ return;
102
+ }
103
+ this.entries = c.entries;
104
+ this.seq = c.seq;
105
+ this.last = c.last;
106
+ this.integrity = c.integrity;
107
+ }
108
+ /** Take the exclusive lock file; returns the release. A lock a dead process left behind is cleared after LOCK_STALE_MS. */
109
+ lock() {
110
+ if (!this.path)
111
+ return () => { };
112
+ const lockPath = this.path + '.lock';
113
+ const deadline = Date.now() + this.lockWaitMs;
114
+ for (;;) {
48
115
  try {
49
- e = JSON.parse(lines[i]);
116
+ const fd = openSync(lockPath, 'wx', 0o600);
117
+ try {
118
+ writeSync(fd, `${process.pid} ${new Date().toISOString()}\n`);
119
+ }
120
+ finally {
121
+ closeSync(fd);
122
+ }
123
+ return () => { try {
124
+ unlinkSync(lockPath);
125
+ }
126
+ catch { /* already gone */ } };
50
127
  }
51
- catch {
52
- this.integrity = `line ${i + 1} is not JSON`;
53
- break;
128
+ catch (e) {
129
+ if (e.code !== 'EEXIST')
130
+ throw e;
54
131
  }
55
- const { hash, ...body } = e;
56
- if (e.prev !== prev || hashOf(prev, body) !== hash) {
57
- this.integrity = `chain broken at line ${i + 1} (seq ${e.seq})`;
58
- break;
132
+ try {
133
+ if (Date.now() - statSync(lockPath).mtimeMs > LOCK_STALE_MS) {
134
+ unlinkSync(lockPath);
135
+ continue;
136
+ }
137
+ }
138
+ catch {
139
+ continue; /* released between the two calls */
59
140
  }
60
- if (e.seq !== this.seq + 1) {
61
- this.integrity = `sequence gap at line ${i + 1}`;
62
- break;
141
+ if (Date.now() >= deadline) {
142
+ throw new Error(`The ledger at ${this.path} is locked by another agentwallet-mcp process (${lockPath}) that did not finish within ${this.lockWaitMs} ms. Nothing was signed. If no other process is running, remove the lock file.`);
63
143
  }
64
- this.entries.push(e);
65
- this.seq = e.seq;
66
- prev = hash;
144
+ sleepSync(5);
67
145
  }
68
- this.last = prev;
69
146
  }
70
147
  append(body) {
71
148
  if (this.integrity)
@@ -103,11 +180,19 @@ export class Ledger {
103
180
  const voided = new Set(this.entries.filter(e => e.kind === 'void').map(e => e.ref));
104
181
  return this.entries.filter((e) => e.kind === 'spend' && !voided.has(e.seq));
105
182
  }
106
- /** Dollar sums over rolling windows; nowMs for tests. */
183
+ /** Dollar sums over rolling windows from a fresh read of the file; nowMs for tests. */
107
184
  spentUsd(nowMs = Date.now(), host) {
108
- return this.sum(nowMs, e => (host === undefined || e.host === host) && e.usd !== null ? units(e.usd) : 0n);
185
+ this.sync();
186
+ return this.sumUsd(nowMs, host);
109
187
  }
110
188
  spentNative(chainId, nowMs = Date.now()) {
189
+ this.sync();
190
+ return this.sumNative(chainId, nowMs);
191
+ }
192
+ sumUsd(nowMs, host) {
193
+ return this.sum(nowMs, e => (host === undefined || e.host === host) && e.usd !== null ? units(e.usd) : 0n);
194
+ }
195
+ sumNative(chainId, nowMs) {
111
196
  return this.sum(nowMs, e => e.chain_id === chainId && e.asset === '' ? rawToUnits(e.raw, nativeDecimalsOf(chainId)) : 0n);
112
197
  }
113
198
  sum(nowMs, f) {
@@ -125,13 +210,17 @@ export class Ledger {
125
210
  }
126
211
  return out;
127
212
  }
128
- /** Would this spend fit every limit? The reason names the first it does not. */
213
+ /** Would this spend fit every limit, given what every process has written so far? The reason names the first limit it does not. */
129
214
  check(input, limits, nowMs = Date.now()) {
215
+ this.sync();
216
+ return this.evaluate(input, limits, nowMs);
217
+ }
218
+ evaluate(input, limits, nowMs) {
130
219
  if (this.integrity)
131
220
  return { ok: false, reason: `The ledger at ${this.path} is not intact (${this.integrity}); every spend is refused until the operator has looked at it.` };
132
221
  const amount18 = rawToUnits(input.raw, input.decimals);
133
222
  if (input.asset === '') {
134
- const r = refusal(limits.native, this.spentNative(input.chainId, nowMs), amount18, `native_budget.${input.chainId}`);
223
+ const r = refusal(limits.native, this.sumNative(input.chainId, nowMs), amount18, `native_budget.${input.chainId}`);
135
224
  if (r)
136
225
  return { ok: false, reason: r };
137
226
  }
@@ -142,33 +231,51 @@ export class Ledger {
142
231
  }
143
232
  if (input.usd !== null) {
144
233
  const usd18 = units(input.usd);
145
- const r = refusal(limits.usd, this.spentUsd(nowMs), usd18, 'budget');
234
+ const r = refusal(limits.usd, this.sumUsd(nowMs), usd18, 'budget');
146
235
  if (r)
147
236
  return { ok: false, reason: r };
148
237
  if (limits.host && input.host) {
149
- const h = refusal({ daily: limits.host.daily, monthly: limits.host.monthly }, this.spentUsd(nowMs, input.host), usd18, `per_host["${limits.host.pattern}"]`);
238
+ const h = refusal({ daily: limits.host.daily, monthly: limits.host.monthly }, this.sumUsd(nowMs, input.host), usd18, `per_host["${limits.host.pattern}"]`);
150
239
  if (h)
151
240
  return { ok: false, reason: h };
152
241
  }
153
242
  }
154
243
  return { ok: true };
155
244
  }
156
- /** Check and record in one synchronous step. Returns the spend's seq. */
245
+ /** Re-read, check and record in one step under the lock. Returns the spend's seq. */
157
246
  reserve(input, limits) {
158
- const c = this.check(input, limits);
159
- if (!c.ok)
160
- throw new Error(c.reason);
161
- const amount = human(rawToUnits(input.raw, input.decimals));
162
- const e = this.append({
163
- kind: 'spend', tool: input.tool, mode: input.mode, wallet: input.wallet, chain_id: input.chainId, asset: input.asset, asset_label: input.assetLabel,
164
- amount, raw: input.raw, usd: input.usd, to: input.to, host: input.host, resource: input.resource, method: input.method, proof: input.proof ?? null,
165
- });
166
- return e.seq;
247
+ const unlock = this.lock();
248
+ try {
249
+ this.sync();
250
+ const c = this.evaluate(input, limits, Date.now());
251
+ if (!c.ok)
252
+ throw new Error(c.reason);
253
+ const amount = human(rawToUnits(input.raw, input.decimals));
254
+ const e = this.append({
255
+ kind: 'spend', tool: input.tool, mode: input.mode, wallet: input.wallet, chain_id: input.chainId, asset: input.asset, asset_label: input.assetLabel,
256
+ amount, raw: input.raw, usd: input.usd, to: input.to, host: input.host, resource: input.resource, method: input.method, proof: input.proof ?? null,
257
+ });
258
+ return e.seq;
259
+ }
260
+ finally {
261
+ unlock();
262
+ }
263
+ }
264
+ /** A follow-up line, written under the lock after a fresh read so it chains onto whatever any process wrote last. */
265
+ follow(body) {
266
+ const unlock = this.lock();
267
+ try {
268
+ this.sync();
269
+ this.append(body);
270
+ }
271
+ finally {
272
+ unlock();
273
+ }
167
274
  }
168
275
  /** The spend never happened (signing or broadcast failed before anything left this process). */
169
276
  void(ref, detail) {
170
277
  try {
171
- this.append({ kind: 'void', ref, outcome: 'void', detail: detail.slice(0, 300) });
278
+ this.follow({ kind: 'void', ref, outcome: 'void', detail: detail.slice(0, 300) });
172
279
  }
173
280
  catch (e) {
174
281
  console.error(`[agentwallet] ledger void failed: ${e.message}`);
@@ -177,15 +284,16 @@ export class Ledger {
177
284
  /** What the endpoint or chain said afterwards. Never throws: the payment already happened. */
178
285
  settle(ref, outcome, detail) {
179
286
  try {
180
- this.append({ kind: 'settle', ref, outcome: outcome.slice(0, 40), detail: detail ? detail.slice(0, 300) : null });
287
+ this.follow({ kind: 'settle', ref, outcome: outcome.slice(0, 40), detail: detail ? detail.slice(0, 300) : null });
181
288
  }
182
289
  catch (e) {
183
290
  console.error(`[agentwallet] ledger settle failed: ${e.message}`);
184
291
  }
185
292
  }
186
- size() { return this.entries.length; }
187
- /** Spends newest first with their latest follow-up folded in. */
293
+ size() { this.sync(); return this.entries.length; }
294
+ /** Spends newest first with their latest follow-up folded in, from a fresh read. */
188
295
  payments(opts) {
296
+ this.sync();
189
297
  const follow = new Map();
190
298
  for (const e of this.entries)
191
299
  if (e.kind !== 'spend')
package/package.json CHANGED
@@ -5,7 +5,7 @@
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/hifriendbot/agentwallet-mcp.git"
7
7
  },
8
- "version": "1.14.1",
8
+ "version": "1.14.2",
9
9
  "description": "MCP server for AgentWallet: self-custody local signing on EVM and Solana, or hosted wallets. Sign, broadcast, pay x402 invoices, and accept x402 payments.",
10
10
  "type": "module",
11
11
  "main": "build/index.js",