@hybrd/xmtp 1.4.5 → 2.0.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.
package/dist/index.js CHANGED
@@ -17,18 +17,17 @@ var DEFAULT_AMOUNT = "0.1";
17
17
  var MAX_USDC_AMOUNT = 10;
18
18
 
19
19
  // src/client.ts
20
+ import { getRandomValues } from "crypto";
21
+ import fs from "fs";
22
+ import path from "path";
20
23
  import { logger } from "@hybrd/utils";
21
24
  import { ReactionCodec } from "@xmtp/content-type-reaction";
22
25
  import { ReplyCodec } from "@xmtp/content-type-reply";
23
26
  import { TransactionReferenceCodec } from "@xmtp/content-type-transaction-reference";
24
27
  import { WalletSendCallsCodec } from "@xmtp/content-type-wallet-send-calls";
25
28
  import { Client as Client2 } from "@xmtp/node-sdk";
26
- import { getRandomValues } from "crypto";
27
- import fs from "fs";
28
- import path from "path";
29
- import { fileURLToPath } from "url";
30
29
  import { fromString, toString as uint8arraysToString } from "uint8arrays";
31
- import { createWalletClient, http, toBytes } from "viem";
30
+ import { http, createWalletClient, toBytes as toBytes2 } from "viem";
32
31
  import { privateKeyToAccount } from "viem/accounts";
33
32
  import { sepolia } from "viem/chains";
34
33
 
@@ -75,10 +74,10 @@ async function revokeOldInstallations(signer, inboxId) {
75
74
  }
76
75
  }
77
76
  async function main() {
78
- const { XMTP_WALLET_KEY } = process.env;
77
+ const { AGENT_WALLET_KEY } = process.env;
79
78
  const inboxId = process.argv[2];
80
- if (!XMTP_WALLET_KEY) {
81
- console.error("\u274C XMTP_WALLET_KEY is required");
79
+ if (!AGENT_WALLET_KEY) {
80
+ console.error("\u274C AGENT_WALLET_KEY is required");
82
81
  process.exit(1);
83
82
  }
84
83
  if (!inboxId) {
@@ -86,7 +85,7 @@ async function main() {
86
85
  console.error("Usage: tsx revoke-installations.ts <inboxId>");
87
86
  process.exit(1);
88
87
  }
89
- const signer = createSigner(XMTP_WALLET_KEY);
88
+ const signer = createSigner(AGENT_WALLET_KEY);
90
89
  const identifier = await signer.getIdentifier();
91
90
  const address = identifier.identifier;
92
91
  console.log(`\u{1F511} Wallet Address: ${address}`);
@@ -99,16 +98,36 @@ async function main() {
99
98
  process.exit(1);
100
99
  }
101
100
  }
102
- if (import.meta.url === `file://${process.argv[1]}`) {
103
- main().catch((error) => {
104
- console.error("\u{1F4A5} Fatal error:", error);
105
- process.exit(1);
106
- });
101
+ try {
102
+ if (import.meta.url === `file://${process.argv[1]}`) {
103
+ main().catch((error) => {
104
+ console.error("\u{1F4A5} Fatal error:", error);
105
+ process.exit(1);
106
+ });
107
+ }
108
+ } catch {
109
+ }
110
+
111
+ // src/lib/secret.ts
112
+ import { toBytes } from "viem";
113
+ import { HDKey } from "viem/accounts";
114
+ function deriveAgentSecret(walletKey) {
115
+ const keyBytes = toBytes(walletKey);
116
+ const hdKey = HDKey.fromMasterSeed(keyBytes);
117
+ const child = hdKey.derive("m/44'/60'/0'/0/41");
118
+ if (!child.privateKey) {
119
+ throw new Error("Failed to derive child key from wallet key");
120
+ }
121
+ return Buffer.from(child.privateKey).toString("hex");
122
+ }
123
+ function resolveAgentSecret(walletKey) {
124
+ if (!walletKey) {
125
+ throw new Error("walletKey is required to derive the encryption secret");
126
+ }
127
+ return deriveAgentSecret(walletKey);
107
128
  }
108
129
 
109
130
  // src/client.ts
110
- var __filename = fileURLToPath(import.meta.url);
111
- var __dirname = path.dirname(__filename);
112
131
  var createUser = (key) => {
113
132
  const account = privateKeyToAccount(key);
114
133
  return {
@@ -139,7 +158,7 @@ var createSigner = (key) => {
139
158
  message,
140
159
  account: user.account
141
160
  });
142
- return toBytes(signature);
161
+ return toBytes2(signature);
143
162
  }
144
163
  };
145
164
  };
@@ -150,8 +169,7 @@ async function clearXMTPDatabase(address, env) {
150
169
  if (customStoragePath) {
151
170
  return path.isAbsolute(customStoragePath) ? customStoragePath : path.resolve(process.cwd(), customStoragePath);
152
171
  }
153
- const projectRoot = process.env.PROJECT_ROOT || path.resolve(__dirname, "../../..");
154
- return path.join(projectRoot, ".data/xmtp");
172
+ return path.join(process.cwd(), ".hybrid", ".xmtp");
155
173
  };
156
174
  const dbPattern = `${env}-${address}.db3`;
157
175
  const storageDir = getStorageDirectory();
@@ -160,6 +178,8 @@ async function clearXMTPDatabase(address, env) {
160
178
  // Legacy fallback paths for backward compatibility
161
179
  path.join(process.cwd(), ".data", "xmtp"),
162
180
  path.join(process.cwd(), "..", ".data", "xmtp"),
181
+ path.join(process.cwd(), "..", "..", ".data", "xmtp"),
182
+ // Monorepo root fallback
163
183
  path.join(process.cwd(), "..", "..", ".data", "xmtp")
164
184
  ];
165
185
  for (const dir of possiblePaths) {
@@ -189,10 +209,11 @@ async function createXMTPClient(privateKey, opts) {
189
209
  const signer = createSigner(privateKey);
190
210
  if (!signer) {
191
211
  throw new Error(
192
- "No signer provided and XMTP_WALLET_KEY environment variable is not set"
212
+ "No signer provided and AGENT_WALLET_KEY environment variable is not set"
193
213
  );
194
214
  }
195
- const { XMTP_DB_ENCRYPTION_KEY, XMTP_ENV } = process.env;
215
+ const { XMTP_ENV } = process.env;
216
+ const agentSecret = resolveAgentSecret(privateKey);
196
217
  const identifier = await signer.getIdentifier();
197
218
  const address = identifier.identifier;
198
219
  while (attempt < maxRetries) {
@@ -205,12 +226,7 @@ async function createXMTPClient(privateKey, opts) {
205
226
  "Stateless mode is not supported. XMTP client must run in persistent mode to properly receive and process messages. Set persist: true or remove the persist option to use the default persistent mode."
206
227
  );
207
228
  }
208
- if (!XMTP_DB_ENCRYPTION_KEY) {
209
- throw new Error(
210
- "XMTP_DB_ENCRYPTION_KEY must be set for persistent mode"
211
- );
212
- }
213
- const dbEncryptionKey = getEncryptionKeyFromHex(XMTP_DB_ENCRYPTION_KEY);
229
+ const dbEncryptionKey = getEncryptionKeyFromHex(agentSecret);
214
230
  const dbPath = await getDbPath(
215
231
  `${XMTP_ENV || "dev"}-${address}`,
216
232
  storagePath
@@ -239,7 +255,7 @@ async function createXMTPClient(privateKey, opts) {
239
255
  return client;
240
256
  } catch (error) {
241
257
  attempt++;
242
- if (error instanceof Error && error.message.includes("5/5 installations")) {
258
+ if (error instanceof Error && (error.message.includes("installations") || error.message.match(/\d+\/\d+\s+installations/))) {
243
259
  console.log(
244
260
  `\u{1F4A5} Installation limit reached (attempt ${attempt}/${maxRetries})`
245
261
  );
@@ -282,7 +298,7 @@ async function createXMTPClient(privateKey, opts) {
282
298
  console.log("\u{1F527} Attempting automatic identity refresh...");
283
299
  try {
284
300
  console.log("\u{1F4DD} Creating persistent client to refresh identity...");
285
- const tempEncryptionKey = XMTP_DB_ENCRYPTION_KEY ? getEncryptionKeyFromHex(XMTP_DB_ENCRYPTION_KEY) : getEncryptionKeyFromHex(generateEncryptionKeyHex());
301
+ const tempEncryptionKey = getEncryptionKeyFromHex(agentSecret);
286
302
  const tempClient = await Client2.create(signer, {
287
303
  dbEncryptionKey: tempEncryptionKey,
288
304
  env: XMTP_ENV,
@@ -324,10 +340,6 @@ async function createXMTPClient(privateKey, opts) {
324
340
  }
325
341
  throw new Error("Max retries exceeded");
326
342
  }
327
- var generateEncryptionKeyHex = () => {
328
- const uint8Array = getRandomValues(new Uint8Array(32));
329
- return uint8arraysToString(uint8Array, "hex");
330
- };
331
343
  var getEncryptionKeyFromHex = (hex) => {
332
344
  return fromString(hex, "hex");
333
345
  };
@@ -339,8 +351,7 @@ var getDbPath = async (description = "xmtp", storagePath) => {
339
351
  } else if (storagePath) {
340
352
  volumePath = path.isAbsolute(storagePath) ? storagePath : path.resolve(process.cwd(), storagePath);
341
353
  } else {
342
- const projectRoot = process.env.PROJECT_ROOT || path.resolve(__dirname, "../../..");
343
- volumePath = path.join(projectRoot, ".data/xmtp");
354
+ volumePath = path.join(process.cwd(), ".hybrid", ".xmtp");
344
355
  }
345
356
  const dbPath = `${volumePath}/${description}.db3`;
346
357
  if (typeof globalThis !== "undefined" && "XMTP_STORAGE" in globalThis) {
@@ -585,8 +596,8 @@ import {
585
596
  createSigner as createSigner2,
586
597
  createUser as createUser2
587
598
  } from "@xmtp/agent-sdk";
588
- import { logger as logger2 } from "@hybrd/utils";
589
599
  import { randomUUID } from "crypto";
600
+ import { logger as logger2 } from "@hybrd/utils";
590
601
  async function sendResponse(conversation, text, originalMessageId, behaviorContext) {
591
602
  const shouldThread = behaviorContext?.sendOptions?.threaded ?? false;
592
603
  if (shouldThread) {
@@ -617,23 +628,16 @@ function XMTPPlugin() {
617
628
  name: "xmtp",
618
629
  description: "Provides XMTP messaging functionality",
619
630
  apply: async (app, context) => {
620
- const {
621
- XMTP_WALLET_KEY,
622
- XMTP_DB_ENCRYPTION_KEY,
623
- XMTP_ENV = "production"
624
- } = process.env;
631
+ const { AGENT_WALLET_KEY, XMTP_ENV = "production" } = process.env;
625
632
  const { agent } = context;
626
633
  const pluginContext = context;
627
- if (!XMTP_WALLET_KEY) {
628
- throw new Error("XMTP_WALLET_KEY must be set");
634
+ if (!AGENT_WALLET_KEY) {
635
+ throw new Error("AGENT_WALLET_KEY must be set");
629
636
  }
630
- if (!XMTP_DB_ENCRYPTION_KEY) {
631
- throw new Error("XMTP_DB_ENCRYPTION_KEY must be set");
632
- }
633
- const user = createUser2(XMTP_WALLET_KEY);
637
+ const user = createUser2(AGENT_WALLET_KEY);
634
638
  const signer = createSigner2(user);
635
639
  const xmtpClient = await createXMTPClient(
636
- XMTP_WALLET_KEY
640
+ AGENT_WALLET_KEY
637
641
  );
638
642
  const address = user.account.address.toLowerCase();
639
643
  const agentDbPath = await getDbPath(
@@ -644,6 +648,8 @@ function XMTPPlugin() {
644
648
  env: XMTP_ENV,
645
649
  dbPath: agentDbPath
646
650
  });
651
+ const botInboxId = xmtp.client?.inboxId;
652
+ const MAX_HISTORY = 20;
647
653
  xmtp.on("reaction", async ({ conversation, message }) => {
648
654
  try {
649
655
  const text = message.content.content;
@@ -657,29 +663,52 @@ function XMTPPlugin() {
657
663
  const baseRuntime = {
658
664
  conversation,
659
665
  message,
660
- xmtpClient
666
+ xmtpClient,
667
+ ...context.scheduler ? { scheduler: context.scheduler } : {}
661
668
  };
662
669
  const runtime = await agent.createRuntimeContext(baseRuntime);
670
+ let behaviorContext;
663
671
  if (context.behaviors) {
664
- const behaviorContext2 = {
672
+ behaviorContext = {
665
673
  runtime,
666
674
  client: xmtpClient,
667
675
  conversation,
668
676
  message
669
677
  };
670
- await context.behaviors.executeBefore(behaviorContext2);
678
+ await context.behaviors.executeBefore(behaviorContext);
679
+ if (behaviorContext.stopped) {
680
+ logger2.debug(
681
+ `\u{1F507} [XMTP Plugin] Skipping reaction response due to behavior chain being stopped`
682
+ );
683
+ return;
684
+ }
685
+ if (behaviorContext.sendOptions?.filtered) {
686
+ logger2.debug(
687
+ `\u{1F507} [XMTP Plugin] Skipping reaction response due to message being filtered`
688
+ );
689
+ return;
690
+ }
671
691
  }
672
692
  const { text: reply } = await agent.generate(messages, { runtime });
673
- let behaviorContext;
674
693
  if (context.behaviors) {
675
- behaviorContext = {
676
- runtime,
677
- client: xmtpClient,
678
- conversation,
679
- message,
680
- response: reply
681
- };
694
+ if (behaviorContext) {
695
+ behaviorContext.response = reply;
696
+ } else {
697
+ behaviorContext = {
698
+ runtime,
699
+ client: xmtpClient,
700
+ conversation,
701
+ message,
702
+ response: reply
703
+ };
704
+ }
682
705
  await context.behaviors.executeAfter(behaviorContext);
706
+ if (behaviorContext.stopped) {
707
+ logger2.debug(
708
+ `\u{1F507} [XMTP Plugin] Skipping reaction response due to post-behavior chain being stopped`
709
+ );
710
+ return;
711
+ }
683
712
  } else {
684
713
  behaviorContext = {
685
714
  runtime,
@@ -689,12 +718,6 @@ function XMTPPlugin() {
689
718
  response: reply
690
719
  };
691
720
  }
692
- if (behaviorContext?.sendOptions?.filtered) {
693
- logger2.debug(
694
- `\u{1F507} [XMTP Plugin] Skipping reaction response due to message being filtered`
695
- );
696
- return;
697
- }
698
721
  await sendResponse(
699
722
  conversation,
700
723
  reply,
@@ -718,7 +741,8 @@ function XMTPPlugin() {
718
741
  const baseRuntime = {
719
742
  conversation,
720
743
  message,
721
- xmtpClient
744
+ xmtpClient,
745
+ ...context.scheduler ? { scheduler: context.scheduler } : {}
722
746
  };
723
747
  const runtime = await agent.createRuntimeContext(baseRuntime);
724
748
  let behaviorContext;
@@ -779,13 +803,38 @@ function XMTPPlugin() {
779
803
  xmtp.on("text", async ({ conversation, message }) => {
780
804
  try {
781
805
  const text = message.content;
806
+ let historyMessages = [];
807
+ try {
808
+ console.log("[xmtp] fetching conversation history...");
809
+ const history = await conversation.messages({
810
+ limit: MAX_HISTORY + 1,
811
+ direction: 1
812
+ });
813
+ console.log(
814
+ `[xmtp] got ${history.length} messages from conversation.messages()`
815
+ );
816
+ const filtered = history.filter((msg) => msg.id !== message.id).filter(
817
+ (msg) => msg.content && typeof msg.content === "string"
818
+ ).slice(0, MAX_HISTORY).reverse();
819
+ console.log(`[xmtp] after filter: ${filtered.length} messages`);
820
+ historyMessages = filtered.map((msg) => ({
821
+ id: msg.id,
822
+ role: msg.senderInboxId === botInboxId ? "assistant" : "user",
823
+ parts: [{ type: "text", text: msg.content }]
824
+ }));
825
+ } catch (historyErr) {
826
+ console.error(`[xmtp] history error:`, historyErr);
827
+ }
782
828
  const messages = [
829
+ ...historyMessages,
783
830
  { id: randomUUID(), role: "user", parts: [{ type: "text", text }] }
784
831
  ];
832
+ console.log(`[xmtp] sending ${messages.length} messages to agent`);
785
833
  const baseRuntime = {
786
834
  conversation,
787
835
  message,
788
- xmtpClient
836
+ xmtpClient,
837
+ ...context.scheduler ? { scheduler: context.scheduler } : {}
789
838
  };
790
839
  const runtime = await agent.createRuntimeContext(baseRuntime);
791
840
  let behaviorContext;
@@ -843,6 +892,7 @@ function XMTPPlugin() {
843
892
  logger2.error("\u274C Error handling text:", err);
844
893
  }
845
894
  });
895
+ context.xmtpClient = xmtpClient;
846
896
  void xmtp.start().then(() => logger2.debug("\u2705 XMTP agent listener started")).catch(
847
897
  (err) => console.error("\u274C XMTP agent listener failed to start:", err)
848
898
  );
@@ -851,23 +901,23 @@ function XMTPPlugin() {
851
901
  }
852
902
 
853
903
  // src/lib/jwt.ts
854
- import jwt from "jsonwebtoken";
855
904
  import { logger as logger3 } from "@hybrd/utils";
905
+ import jwt from "jsonwebtoken";
856
906
  function getJwtSecret() {
857
- const secret = process.env.XMTP_DB_ENCRYPTION_KEY;
907
+ const walletKey = process.env.AGENT_WALLET_KEY;
908
+ if (walletKey) {
909
+ return resolveAgentSecret(walletKey);
910
+ }
858
911
  const nodeEnv = process.env.NODE_ENV || "development";
859
- if (nodeEnv === "production" && !secret) {
912
+ if (nodeEnv === "production") {
860
913
  throw new Error(
861
- "XMTP_DB_ENCRYPTION_KEY environment variable is required in production. Generate a secure random secret for JWT token signing."
862
- );
863
- }
864
- if (!secret) {
865
- logger3.warn(
866
- "\u26A0\uFE0F [SECURITY] Using fallback JWT secret for development. Set XMTP_DB_ENCRYPTION_KEY environment variable for production."
914
+ "AGENT_WALLET_KEY must be set in production for JWT token signing."
867
915
  );
868
- return "fallback-secret-for-dev-only";
869
916
  }
870
- return secret;
917
+ logger3.warn(
918
+ "\u26A0\uFE0F [SECURITY] Using fallback JWT secret for development. Set AGENT_WALLET_KEY for production."
919
+ );
920
+ return "fallback-secret-for-dev-only";
871
921
  }
872
922
  var JWT_EXPIRY = 5 * 60;
873
923
  function generateXMTPToolsToken(payload) {
@@ -933,10 +983,13 @@ export {
933
983
  createUser3 as createUser,
934
984
  createXMTPClient,
935
985
  createSigner as createXMTPSigner,
986
+ deriveAgentSecret,
936
987
  filter,
937
988
  generateXMTPToolsToken,
989
+ getDbPath,
938
990
  getTestUrl,
939
991
  logAgentDetails,
992
+ resolveAgentSecret,
940
993
  validateEnvironment
941
994
  };
942
995
  //# sourceMappingURL=index.js.map