@jaw.id/cli 0.0.7 → 0.1.0

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.
Files changed (62) hide show
  1. package/README.md +24 -24
  2. package/dist/base-command.js +28 -8
  3. package/dist/base-command.js.map +1 -1
  4. package/dist/commands/config/set.js +28 -38
  5. package/dist/commands/config/set.js.map +1 -1
  6. package/dist/commands/config/show.js +29 -12
  7. package/dist/commands/config/show.js.map +1 -1
  8. package/dist/commands/config/write.js +288 -0
  9. package/dist/commands/config/write.js.map +1 -0
  10. package/dist/commands/disconnect.js +42 -54
  11. package/dist/commands/disconnect.js.map +1 -1
  12. package/dist/commands/mcp/index.js +62 -102
  13. package/dist/commands/mcp/index.js.map +1 -1
  14. package/dist/commands/rpc/call.js +218 -92
  15. package/dist/commands/rpc/call.js.map +1 -1
  16. package/dist/commands/session/revoke.js +727 -0
  17. package/dist/commands/session/revoke.js.map +1 -0
  18. package/dist/commands/session/setup.js +960 -0
  19. package/dist/commands/session/setup.js.map +1 -0
  20. package/dist/commands/session/status.js +206 -0
  21. package/dist/commands/session/status.js.map +1 -0
  22. package/dist/commands/version.js +28 -8
  23. package/dist/commands/version.js.map +1 -1
  24. package/dist/index.js +42 -67
  25. package/dist/index.js.map +1 -1
  26. package/dist/lib/bridge-singleton.js +47 -61
  27. package/dist/lib/bridge-singleton.js.map +1 -1
  28. package/dist/lib/config.js +17 -13
  29. package/dist/lib/config.js.map +1 -1
  30. package/dist/lib/crypto.js +3 -15
  31. package/dist/lib/crypto.js.map +1 -1
  32. package/dist/lib/keystore.js +64 -0
  33. package/dist/lib/keystore.js.map +1 -0
  34. package/dist/lib/output.js +1 -16
  35. package/dist/lib/output.js.map +1 -1
  36. package/dist/lib/paths.js +3 -1
  37. package/dist/lib/paths.js.map +1 -1
  38. package/dist/lib/session-bridge.js +168 -0
  39. package/dist/lib/session-bridge.js.map +1 -0
  40. package/dist/lib/session-config.js +52 -0
  41. package/dist/lib/session-config.js.map +1 -0
  42. package/dist/lib/validation.js +62 -27
  43. package/dist/lib/validation.js.map +1 -1
  44. package/dist/lib/ws-bridge.js +15 -46
  45. package/dist/lib/ws-bridge.js.map +1 -1
  46. package/dist/mcp/handlers/config.js +28 -29
  47. package/dist/mcp/handlers/config.js.map +1 -1
  48. package/dist/mcp/handlers/daemon.js +41 -51
  49. package/dist/mcp/handlers/daemon.js.map +1 -1
  50. package/dist/mcp/handlers/resources.js +1 -3
  51. package/dist/mcp/handlers/resources.js.map +1 -1
  52. package/dist/mcp/handlers/rpc.js +63 -79
  53. package/dist/mcp/handlers/rpc.js.map +1 -1
  54. package/dist/mcp/helpers.js.map +1 -1
  55. package/dist/mcp/server.js +61 -99
  56. package/dist/mcp/server.js.map +1 -1
  57. package/dist/mcp/tools.js +2 -4
  58. package/dist/mcp/tools.js.map +1 -1
  59. package/oclif.manifest.json +304 -4
  60. package/package.json +19 -1
  61. package/dist/lib/session-store.js +0 -80
  62. package/dist/lib/session-store.js.map +0 -1
@@ -1,5 +1,5 @@
1
1
  import { Command, Flags, Args } from '@oclif/core';
2
- import * as fs2 from 'fs';
2
+ import * as fs from 'fs';
3
3
  import * as path from 'path';
4
4
  import * as os from 'os';
5
5
  import * as crypto from 'crypto';
@@ -11,7 +11,9 @@ var PATHS = {
11
11
  root: JAW_DIR,
12
12
  config: path.join(JAW_DIR, "config.json"),
13
13
  session: path.join(JAW_DIR, "session.json"),
14
- relay: path.join(JAW_DIR, "relay.json")
14
+ relay: path.join(JAW_DIR, "relay.json"),
15
+ keystore: path.join(JAW_DIR, "keystore.json"),
16
+ sessionConfig: path.join(JAW_DIR, "session-config.json")
15
17
  };
16
18
 
17
19
  // src/lib/validation.ts
@@ -39,22 +41,39 @@ function isValidRelayUrl(url) {
39
41
 
40
42
  // src/lib/config.ts
41
43
  function ensureDir(dir) {
42
- fs2.mkdirSync(dir, { recursive: true, mode: 448 });
43
- fs2.chmodSync(dir, 448);
44
+ fs.mkdirSync(dir, { recursive: true, mode: 448 });
45
+ fs.chmodSync(dir, 448);
46
+ }
47
+ function migrateConfig(config) {
48
+ if (config.paymasterUrl && !config.paymasters) {
49
+ const chainId = config.defaultChain ?? 1;
50
+ config.paymasters = { [chainId]: { url: config.paymasterUrl } };
51
+ delete config.paymasterUrl;
52
+ saveConfig(config);
53
+ }
54
+ return config;
44
55
  }
45
56
  function loadConfig() {
46
- if (!fs2.existsSync(PATHS.config)) {
57
+ if (!fs.existsSync(PATHS.config)) {
47
58
  return {};
48
59
  }
49
- const raw = fs2.readFileSync(PATHS.config, "utf-8");
60
+ const raw = fs.readFileSync(PATHS.config, "utf-8");
50
61
  try {
51
- return JSON.parse(raw);
62
+ const config = JSON.parse(raw);
63
+ return migrateConfig(config);
52
64
  } catch {
53
65
  throw new Error(
54
66
  `Config file at ${PATHS.config} is not valid JSON. Run \`jaw config set apiKey=<key>\` to reset it.`
55
67
  );
56
68
  }
57
69
  }
70
+ function saveConfig(config) {
71
+ ensureDir(PATHS.root);
72
+ fs.writeFileSync(PATHS.config, JSON.stringify(config, null, 2) + "\n", {
73
+ encoding: "utf-8",
74
+ mode: 384
75
+ });
76
+ }
58
77
 
59
78
  // src/lib/output.ts
60
79
  function formatOutput(data, format) {
@@ -127,18 +146,14 @@ var BaseCommand = class extends Command {
127
146
  resolveApiKey(flags) {
128
147
  const apiKey = flags["api-key"] ?? loadConfig().apiKey;
129
148
  if (!apiKey) {
130
- this.error(
131
- "API key required. Set via --api-key, JAW_API_KEY env, or `jaw config set apiKey <key>`"
132
- );
149
+ this.error("API key required. Set via --api-key, JAW_API_KEY env, or `jaw config set apiKey <key>`");
133
150
  }
134
151
  return apiKey;
135
152
  }
136
153
  resolveChainId(flags) {
137
154
  const chainId = flags.chain ?? loadConfig().defaultChain;
138
155
  if (!chainId) {
139
- this.error(
140
- "Chain ID required. Set via --chain, JAW_CHAIN_ID env, or `jaw config set defaultChain <id>`"
141
- );
156
+ this.error("Chain ID required. Set via --chain, JAW_CHAIN_ID env, or `jaw config set defaultChain <id>`");
142
157
  }
143
158
  return chainId;
144
159
  }
@@ -151,11 +166,7 @@ var BaseCommand = class extends Command {
151
166
  // src/lib/crypto.ts
152
167
  var subtle = globalThis.crypto.subtle;
153
168
  async function generateKeyPair() {
154
- return subtle.generateKey(
155
- { name: "ECDH", namedCurve: "P-256" },
156
- true,
157
- ["deriveKey"]
158
- );
169
+ return subtle.generateKey({ name: "ECDH", namedCurve: "P-256" }, true, ["deriveKey"]);
159
170
  }
160
171
  async function deriveSharedSecret(privateKey, peerPublicKey) {
161
172
  return subtle.deriveKey(
@@ -169,11 +180,7 @@ async function deriveSharedSecret(privateKey, peerPublicKey) {
169
180
  async function encryptMessage(sharedSecret, payload) {
170
181
  const iv = globalThis.crypto.getRandomValues(new Uint8Array(12));
171
182
  const plaintext = new TextEncoder().encode(JSON.stringify(payload));
172
- const cipherBuf = await subtle.encrypt(
173
- { name: "AES-GCM", iv },
174
- sharedSecret,
175
- plaintext
176
- );
183
+ const cipherBuf = await subtle.encrypt({ name: "AES-GCM", iv }, sharedSecret, plaintext);
177
184
  return {
178
185
  iv: bufferToBase64(iv),
179
186
  ciphertext: bufferToBase64(new Uint8Array(cipherBuf))
@@ -182,11 +189,7 @@ async function encryptMessage(sharedSecret, payload) {
182
189
  async function decryptMessage(sharedSecret, envelope) {
183
190
  const iv = Buffer.from(envelope.iv, "base64");
184
191
  const ciphertext = Buffer.from(envelope.ciphertext, "base64");
185
- const plainBuf = await subtle.decrypt(
186
- { name: "AES-GCM", iv },
187
- sharedSecret,
188
- ciphertext
189
- );
192
+ const plainBuf = await subtle.decrypt({ name: "AES-GCM", iv }, sharedSecret, ciphertext);
190
193
  return JSON.parse(new TextDecoder().decode(plainBuf));
191
194
  }
192
195
  async function exportKeyToHex(type, key) {
@@ -278,11 +281,7 @@ var WSBridge = class {
278
281
  let expectingKeyExchange = !this.peerPublicKeyHex;
279
282
  const timer = setTimeout(() => {
280
283
  ws.close();
281
- reject(
282
- new Error(
283
- "Browser did not connect in time.\nRun `jaw disconnect` then try again."
284
- )
285
- );
284
+ reject(new Error("Browser did not connect in time.\nRun `jaw disconnect` then try again."));
286
285
  }, 3e4);
287
286
  const sendEncryptedInit = async () => {
288
287
  if (!this.sharedSecret) return;
@@ -305,10 +304,7 @@ var WSBridge = class {
305
304
  if (!msg) return;
306
305
  if (msg.type === "encrypted" && this.sharedSecret) {
307
306
  try {
308
- const inner = await decryptMessage(
309
- this.sharedSecret,
310
- msg
311
- );
307
+ const inner = await decryptMessage(this.sharedSecret, msg);
312
308
  if (inner.type === "ready") {
313
309
  clearTimeout(readyTimer);
314
310
  ws.off("message", onMsg);
@@ -346,6 +342,10 @@ var WSBridge = class {
346
342
  expectingKeyExchange = true;
347
343
  onBrowserNeeded().catch(() => {
348
344
  });
345
+ } else if (!onBrowserNeeded) {
346
+ clearTimeout(timer);
347
+ ws.close();
348
+ reject(new Error("Browser not connected \u2014 relay session is stale."));
349
349
  }
350
350
  } else if (msg.type === "browser_connected") {
351
351
  expectingKeyExchange = true;
@@ -395,9 +395,7 @@ var WSBridge = class {
395
395
  return new Promise((resolve, reject) => {
396
396
  const timer = setTimeout(() => {
397
397
  reject(
398
- new Error(
399
- `Request timed out after ${this.timeout / 1e3}s. Did you complete the action in the browser?`
400
- )
398
+ new Error(`Request timed out after ${this.timeout / 1e3}s. Did you complete the action in the browser?`)
401
399
  );
402
400
  this.close();
403
401
  }, this.timeout);
@@ -405,10 +403,7 @@ var WSBridge = class {
405
403
  const msg = safeParse(data);
406
404
  if (!msg || msg.type !== "encrypted" || !this.sharedSecret) return;
407
405
  try {
408
- const inner = await decryptMessage(
409
- this.sharedSecret,
410
- msg
411
- );
406
+ const inner = await decryptMessage(this.sharedSecret, msg);
412
407
  if (inner.type === "rpc_response" && inner.id === id) {
413
408
  clearTimeout(timer);
414
409
  ws.off("message", onMessage);
@@ -416,11 +411,7 @@ var WSBridge = class {
416
411
  resolve(inner.data);
417
412
  } else {
418
413
  const err = inner.error;
419
- reject(
420
- new Error(
421
- err ? `[${err.code}] ${err.message}` : "Request failed"
422
- )
423
- );
414
+ reject(new Error(err ? `[${err.code}] ${err.message}` : "Request failed"));
424
415
  }
425
416
  }
426
417
  } catch {
@@ -430,9 +421,6 @@ var WSBridge = class {
430
421
  this.sendRaw(ws, serialized);
431
422
  });
432
423
  }
433
- isOpen() {
434
- return this.ws?.readyState === WebSocket.OPEN;
435
- }
436
424
  async shutdown() {
437
425
  this.disposed = true;
438
426
  if (this.ws?.readyState === WebSocket.OPEN && this.sharedSecret) {
@@ -440,10 +428,7 @@ var WSBridge = class {
440
428
  const envelope = await encryptMessage(this.sharedSecret, {
441
429
  type: "shutdown"
442
430
  });
443
- this.sendRaw(
444
- this.ws,
445
- JSON.stringify({ type: "encrypted", ...envelope })
446
- );
431
+ this.sendRaw(this.ws, JSON.stringify({ type: "encrypted", ...envelope }));
447
432
  } catch {
448
433
  }
449
434
  }
@@ -521,10 +506,8 @@ var WSBridge = class {
521
506
  this.reconnectAttempts++;
522
507
  setTimeout(() => {
523
508
  if (this.disposed) return;
524
- this.connectInternal(this.onBrowserNeeded, this.onPeerKeyChanged).catch(
525
- () => {
526
- }
527
- );
509
+ this.connectInternal(this.onBrowserNeeded, this.onPeerKeyChanged).catch(() => {
510
+ });
528
511
  }, delay);
529
512
  }
530
513
  /** Send a raw string over the WebSocket, enforcing message size limits. */
@@ -534,10 +517,7 @@ var WSBridge = class {
534
517
  async deriveSecret() {
535
518
  if (!this.peerPublicKeyHex) return;
536
519
  const privateKey = await importKeyFromHex("private", this.privateKeyHex);
537
- const peerPublicKey = await importKeyFromHex(
538
- "public",
539
- this.peerPublicKeyHex
540
- );
520
+ const peerPublicKey = await importKeyFromHex("public", this.peerPublicKeyHex);
541
521
  this.sharedSecret = await deriveSharedSecret(privateKey, peerPublicKey);
542
522
  }
543
523
  };
@@ -563,8 +543,8 @@ var DEFAULT_KEYS_URL = "https://keys.jaw.id";
563
543
  var DEFAULT_RELAY_URL = "wss://relay.jaw.id";
564
544
  function loadRelaySession() {
565
545
  try {
566
- if (!fs2.existsSync(PATHS.relay)) return null;
567
- const raw = fs2.readFileSync(PATHS.relay, "utf-8");
546
+ if (!fs.existsSync(PATHS.relay)) return null;
547
+ const raw = fs.readFileSync(PATHS.relay, "utf-8");
568
548
  const parsed = JSON.parse(raw);
569
549
  if (!parsed.session || !parsed.relayUrl || !parsed.privateKey || !parsed.publicKey) {
570
550
  return null;
@@ -576,14 +556,14 @@ function loadRelaySession() {
576
556
  }
577
557
  function saveRelaySession(info) {
578
558
  ensureDir(PATHS.root);
579
- fs2.writeFileSync(PATHS.relay, JSON.stringify(info, null, 2) + "\n", {
559
+ fs.writeFileSync(PATHS.relay, JSON.stringify(info, null, 2) + "\n", {
580
560
  encoding: "utf-8",
581
561
  mode: 384
582
562
  });
583
563
  }
584
564
  function deleteRelaySession() {
585
565
  try {
586
- if (fs2.existsSync(PATHS.relay)) fs2.unlinkSync(PATHS.relay);
566
+ if (fs.existsSync(PATHS.relay)) fs.unlinkSync(PATHS.relay);
587
567
  } catch {
588
568
  }
589
569
  }
@@ -599,17 +579,19 @@ async function getBridge(options) {
599
579
  throw new Error(`Untrusted relayUrl: ${relayUrl}. Must be wss://*.jaw.id or ws://localhost.`);
600
580
  }
601
581
  let relaySession = loadRelaySession();
602
- if (relaySession && relaySession.relayUrl === relayUrl) {
582
+ if (relaySession && relaySession.relayUrl === relayUrl && relaySession.peerPublicKey) {
603
583
  try {
604
- return await connectBridge(relaySession, options, chainId, keysUrl, relayUrl);
584
+ return await connectBridge(relaySession, options, chainId, keysUrl, relayUrl, false);
605
585
  } catch {
606
586
  deleteRelaySession();
607
587
  relaySession = null;
608
588
  }
589
+ } else if (relaySession) {
590
+ deleteRelaySession();
609
591
  }
610
592
  const session = await createNewSession(relayUrl);
611
593
  saveRelaySession(session);
612
- return await connectBridge(session, options, chainId, keysUrl, relayUrl);
594
+ return await connectBridge(session, options, chainId, keysUrl, relayUrl, true);
613
595
  }
614
596
  async function createNewSession(relayUrl) {
615
597
  const kp = await generateKeyPair();
@@ -624,7 +606,7 @@ async function createNewSession(relayUrl) {
624
606
  startedAt: (/* @__PURE__ */ new Date()).toISOString()
625
607
  };
626
608
  }
627
- async function connectBridge(relaySession, options, chainId, keysUrl, relayUrl) {
609
+ async function connectBridge(relaySession, options, chainId, keysUrl, relayUrl, openBrowser) {
628
610
  const config = loadConfig();
629
611
  const bridge = new WSBridge({
630
612
  relayUrl,
@@ -634,19 +616,19 @@ async function connectBridge(relaySession, options, chainId, keysUrl, relayUrl)
634
616
  apiKey: options.apiKey,
635
617
  chainId,
636
618
  ens: options.ens ?? config.ens,
637
- paymasterUrl: options.paymasterUrl ?? config.paymasterUrl
619
+ paymasterUrl: options.paymasterUrl ?? config.paymasters?.[chainId]?.url
638
620
  },
639
621
  privateKeyHex: relaySession.privateKey,
640
622
  publicKeyHex: relaySession.publicKey,
641
623
  peerPublicKeyHex: relaySession.peerPublicKey
642
624
  });
643
625
  await bridge.connect(
644
- // onBrowserNeeded
645
- async () => {
626
+ // onBrowserNeeded — only open a browser for new sessions
627
+ openBrowser ? async () => {
646
628
  const bridgeUrl = buildBridgeUrl(keysUrl, relaySession.session, relayUrl, relaySession.publicKey);
647
629
  const { default: open } = await import('open');
648
630
  await open(bridgeUrl);
649
- },
631
+ } : void 0,
650
632
  // onPeerKeyChanged
651
633
  (newPeerKey) => {
652
634
  relaySession.peerPublicKey = newPeerKey;
@@ -682,16 +664,141 @@ var BROWSER_REQUIRED_METHODS = /* @__PURE__ */ new Set([
682
664
  function requiresBrowser(method) {
683
665
  return BROWSER_REQUIRED_METHODS.has(method);
684
666
  }
667
+ var SESSION_SUPPORTED_METHODS = /* @__PURE__ */ new Set([
668
+ "eth_requestAccounts",
669
+ "eth_accounts",
670
+ "wallet_sendCalls",
671
+ "wallet_getCallsStatus",
672
+ "personal_sign",
673
+ "eth_signTypedData_v4"
674
+ ]);
675
+ function supportsSessionMode(method) {
676
+ return SESSION_SUPPORTED_METHODS.has(method);
677
+ }
678
+ function loadSessionKey() {
679
+ if (!fs.existsSync(PATHS.keystore)) {
680
+ throw new Error("No session configured. Run `jaw session setup` first.");
681
+ }
682
+ const contents = fs.readFileSync(PATHS.keystore, "utf-8");
683
+ let parsed;
684
+ try {
685
+ parsed = JSON.parse(contents);
686
+ } catch {
687
+ throw new Error(`Keystore at ${PATHS.keystore} is corrupted. Run \`jaw session setup\` to recreate it.`);
688
+ }
689
+ return parsed.privateKey;
690
+ }
691
+ function loadSessionConfig() {
692
+ if (!fs.existsSync(PATHS.sessionConfig)) {
693
+ throw new Error("No session configured. Run `jaw session setup` first.");
694
+ }
695
+ const raw = fs.readFileSync(PATHS.sessionConfig, "utf-8");
696
+ try {
697
+ return JSON.parse(raw);
698
+ } catch {
699
+ throw new Error(`Session config at ${PATHS.sessionConfig} is corrupted. Run \`jaw session setup\` to recreate it.`);
700
+ }
701
+ }
702
+
703
+ // src/lib/session-bridge.ts
704
+ var SessionBridge = class {
705
+ options;
706
+ session = null;
707
+ constructor(options) {
708
+ this.options = { ...options };
709
+ if (!this.options.paymasterUrl) {
710
+ const config = loadConfig();
711
+ const pm = config.paymasters?.[this.options.chainId];
712
+ if (pm) {
713
+ this.options.paymasterUrl = pm.url;
714
+ this.options.paymasterContext = pm.context;
715
+ }
716
+ }
717
+ }
718
+ async getSession() {
719
+ if (this.session) {
720
+ this.checkExpiry(this.session.config);
721
+ return this.session;
722
+ }
723
+ const config = loadSessionConfig();
724
+ this.checkExpiry(config);
725
+ if (config.chainId !== this.options.chainId) {
726
+ throw new Error(
727
+ `Session was created for chain ${config.chainId}, but --chain ${this.options.chainId} was requested. Run \`jaw session setup --chain ${this.options.chainId}\` to create a session for that chain.`
728
+ );
729
+ }
730
+ let privateKeyHex = loadSessionKey();
731
+ const { privateKeyToAccount } = await import('viem/accounts');
732
+ const localAccount = privateKeyToAccount(privateKeyHex);
733
+ privateKeyHex = null;
734
+ const { Account } = await import('@jaw.id/core');
735
+ const account = await Account.fromLocalAccount(
736
+ {
737
+ chainId: this.options.chainId,
738
+ apiKey: this.options.apiKey,
739
+ paymasterUrl: this.options.paymasterUrl,
740
+ paymasterContext: this.options.paymasterContext
741
+ },
742
+ localAccount
743
+ );
744
+ this.session = { account, config };
745
+ return this.session;
746
+ }
747
+ checkExpiry(config) {
748
+ if (config.expiry <= Date.now() / 1e3) {
749
+ const expiryDate = new Date(config.expiry * 1e3).toISOString();
750
+ throw new Error(`Session expired on ${expiryDate}. Run \`jaw session setup\` to create a new session.`);
751
+ }
752
+ }
753
+ async request(method, params) {
754
+ const { account, config } = await this.getSession();
755
+ switch (method) {
756
+ case "eth_requestAccounts":
757
+ case "eth_accounts":
758
+ return [config.sessionAddress];
759
+ case "wallet_sendCalls": {
760
+ const payload = Array.isArray(params) ? params[0] : params;
761
+ const { calls } = payload;
762
+ return account.sendCalls(calls, {
763
+ permissionId: config.permissionId
764
+ });
765
+ }
766
+ case "wallet_getCallsStatus": {
767
+ const batchId = Array.isArray(params) ? params[0] : params;
768
+ return account.getCallStatus(batchId);
769
+ }
770
+ case "personal_sign": {
771
+ const message = Array.isArray(params) ? params[0] : params;
772
+ return account.signMessage(message);
773
+ }
774
+ case "eth_signTypedData_v4": {
775
+ const asArray = Array.isArray(params) ? params : [params];
776
+ const raw = asArray.length > 1 ? asArray[1] : asArray[0];
777
+ const typedData = typeof raw === "string" ? JSON.parse(raw) : raw;
778
+ return account.signTypedData(typedData);
779
+ }
780
+ case "wallet_grantPermissions":
781
+ throw new Error("Requires browser \u2014 run `jaw session setup`.");
782
+ case "wallet_revokePermissions":
783
+ throw new Error("Requires browser \u2014 run `jaw session revoke`.");
784
+ default:
785
+ throw new Error(`Method ${method} is not supported in auto mode.`);
786
+ }
787
+ }
788
+ close() {
789
+ }
790
+ };
685
791
 
686
792
  // src/commands/rpc/call.ts
687
793
  var RpcCall = class _RpcCall extends BaseCommand {
688
- static description = "Execute any JAW.id RPC method via the browser bridge.";
794
+ static description = "Execute any JAW.id RPC method via the browser bridge or local session key.";
689
795
  static examples = [
690
796
  `<%= config.bin %> rpc call wallet_sendCalls '{"calls":[{"to":"0x...","value":"0x0"}]}'`,
691
797
  `<%= config.bin %> rpc call personal_sign '"Hello World"'`,
692
798
  "<%= config.bin %> rpc call wallet_getAssets",
693
799
  "<%= config.bin %> rpc call eth_requestAccounts",
694
- `<%= config.bin %> rpc call wallet_getCallsStatus '"0x..."'`
800
+ `<%= config.bin %> rpc call wallet_getCallsStatus '"0x..."'`,
801
+ `<%= config.bin %> rpc call wallet_sendCalls '{"calls":[...]}' --session`
695
802
  ];
696
803
  static args = {
697
804
  method: Args.string({
@@ -709,6 +816,12 @@ var RpcCall = class _RpcCall extends BaseCommand {
709
816
  char: "t",
710
817
  description: "Request timeout in seconds",
711
818
  default: 120
819
+ }),
820
+ session: Flags.boolean({
821
+ char: "s",
822
+ description: "Use local session key (auto mode)",
823
+ default: false,
824
+ env: "JAW_SESSION"
712
825
  })
713
826
  };
714
827
  async run() {
@@ -725,21 +838,34 @@ var RpcCall = class _RpcCall extends BaseCommand {
725
838
  const format = flags.output;
726
839
  const config = loadConfig();
727
840
  const apiKey = this.resolveApiKey(flags);
728
- const bridge = await getBridge({
729
- keysUrl: config.keysUrl,
730
- apiKey,
731
- chainId: flags.chain ?? config.defaultChain,
732
- ens: config.ens,
733
- paymasterUrl: config.paymasterUrl,
734
- timeout: flags.timeout * 1e3
735
- });
736
- if (!flags.quiet) {
737
- if (requiresBrowser(method)) {
738
- this.log(
739
- `Sending ${method}... Check your browser to approve the request.`
841
+ const chainId = flags.chain ?? config.defaultChain ?? 1;
842
+ let bridge;
843
+ if (flags.session) {
844
+ if (!supportsSessionMode(method)) {
845
+ this.error(
846
+ `Method ${method} is not supported in session mode. Use without --session to route through the browser bridge.`
740
847
  );
741
- } else {
742
- this.log(`Sending ${method}...`);
848
+ }
849
+ bridge = new SessionBridge({ apiKey, chainId });
850
+ if (!flags.quiet) {
851
+ this.log(`Sending ${method} (session mode)...`);
852
+ }
853
+ } else {
854
+ const pm = config.paymasters?.[chainId];
855
+ bridge = await getBridge({
856
+ keysUrl: config.keysUrl,
857
+ apiKey,
858
+ chainId,
859
+ ens: config.ens,
860
+ paymasterUrl: pm?.url,
861
+ timeout: flags.timeout * 1e3
862
+ });
863
+ if (!flags.quiet) {
864
+ if (requiresBrowser(method)) {
865
+ this.log(`Sending ${method}... Check your browser to approve the request.`);
866
+ } else {
867
+ this.log(`Sending ${method}...`);
868
+ }
743
869
  }
744
870
  }
745
871
  try {