@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/README.md +209 -319
- package/dist/index.cjs +138 -83
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +19 -1
- package/dist/index.d.ts +19 -1
- package/dist/index.js +131 -78
- package/dist/index.js.map +1 -1
- package/package.json +10 -6
- package/src/client.ts +19 -39
- package/src/index.ts +8 -5
- package/src/lib/jwt.ts +16 -23
- package/src/lib/secret.ts +33 -0
- package/src/plugin.ts +98 -39
- package/src/index.ts.old +0 -145
- package/src/lib/message-listener.test.ts.old +0 -369
- package/src/lib/message-listener.ts.old +0 -343
- package/src/localStorage.ts.old +0 -203
- package/src/plugin.filters.test.ts +0 -158
- package/src/service-client.ts.old +0 -309
- package/src/transactionMonitor.ts.old +0 -275
- package/src/types.ts.old +0 -157
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 {
|
|
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 {
|
|
77
|
+
const { AGENT_WALLET_KEY } = process.env;
|
|
79
78
|
const inboxId = process.argv[2];
|
|
80
|
-
if (!
|
|
81
|
-
console.error("\u274C
|
|
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(
|
|
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
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
212
|
+
"No signer provided and AGENT_WALLET_KEY environment variable is not set"
|
|
193
213
|
);
|
|
194
214
|
}
|
|
195
|
-
const {
|
|
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
|
-
|
|
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("
|
|
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 =
|
|
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
|
-
|
|
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 (!
|
|
628
|
-
throw new Error("
|
|
634
|
+
if (!AGENT_WALLET_KEY) {
|
|
635
|
+
throw new Error("AGENT_WALLET_KEY must be set");
|
|
629
636
|
}
|
|
630
|
-
|
|
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
|
-
|
|
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
|
-
|
|
672
|
+
behaviorContext = {
|
|
665
673
|
runtime,
|
|
666
674
|
client: xmtpClient,
|
|
667
675
|
conversation,
|
|
668
676
|
message
|
|
669
677
|
};
|
|
670
|
-
await context.behaviors.executeBefore(
|
|
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
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
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
|
|
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"
|
|
912
|
+
if (nodeEnv === "production") {
|
|
860
913
|
throw new Error(
|
|
861
|
-
"
|
|
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
|
-
|
|
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
|