Skip to content

Commit f66ae40

Browse files
authored
Update to node RC (#44)
* update node * update to rc * added readme update * helper for address * helper for address * update keys * fix
1 parent 16109f0 commit f66ae40

18 files changed

+174
-131
lines changed

README.md

+41-20
Original file line numberDiff line numberDiff line change
@@ -26,11 +26,11 @@ ENCRYPTION_KEY= # encryption key for the local database
2626
You can generate random keys with the following command:
2727

2828
```bash
29-
yarn gen:keys
29+
yarn gen:keys <name>
3030
```
3131

32-
> [!WARNING]
33-
> Running the `gen:keys` script will overwrite the existing `.env` file.
32+
> [!TIP]
33+
> Running the `gen:keys` or `gen:keys <name>` command will append keys to your existing `.env` file.
3434
3535
### Fetching messages
3636

@@ -52,31 +52,51 @@ await client.conversations.sync();
5252
await client.conversations.messages();
5353
```
5454

55-
### Working with addresses
55+
### Conversations can be of type `Group` or `Dm`
5656

57-
Conversations in XMTP can be `DMs` or `Groups`. The underlying technicalities are the same, but DMs are essentially groups locked between two users that can be reused - basically a fixed group of 2. This is how MLS works.
58-
59-
Each member of a conversation has the following properties:
57+
The new `Group` and `Dm` classes extend the `Conversation` class and provide specific functionality based on the conversation type.
6058

6159
```tsx
62-
inboxId: string; // unique identifier from the XMTP network
63-
accountAddresses: Array<string>; // ethereum network addresses
64-
installationIds: Array<string>; // How many active devices the user has
65-
permissionLevel: PermissionLevel; // In the context of a group, if it's admin or not
66-
consentState: ConsentState; // If it's blocked or allowed via consent
60+
const conversations: (Group | Dm)[] = await client.conversations.list();
61+
62+
for (const conversation of conversations) {
63+
// narrow the type to Group to access the group name
64+
if (conversation instanceof Group) {
65+
console.log(group.name);
66+
}
67+
68+
// narrow the type to Dm to access the peer inboxId
69+
if (conversation instanceof Dm) {
70+
console.log(conversation.peerInboxId);
71+
}
72+
}
6773
```
6874

69-
To fetch an ethereum address in a DM, you can use a script like the following:
75+
## Working with addresses
76+
77+
Because XMTP is interoperable, you may interact with inboxes that are not on your app. In these scenarios, you will need to find the appropriate inbox ID or address.
7078

7179
```tsx
72-
const address =
73-
(await group.members?.find(
74-
(member: any) => member.inboxId === dm.dmPeerInboxId,
75-
)?.accountAddresses[0]) || "";
76-
```
80+
// get an inbox ID from an address
81+
const inboxId = await getInboxIdForIdentifier({
82+
identifier: "0x1234567890abcdef1234567890abcdef12345678",
83+
identifierKind: IdentifierKind.Ethereum,
84+
});
85+
86+
// find the addresses associated with an inbox ID
87+
const inboxState = await client.inboxStateFromInboxIds([inboxId]);
88+
89+
interface InboxState {
90+
inboxId: string;
91+
recoveryIdentifier: Identifier;
92+
installations: Installation[];
93+
identifiers: Identifier[];
94+
}
7795

78-
> [!WARNING]
79-
> XMTP is working on integrating passkeys as a pillar of identity. Expect a breaking change soon as XMTP prepares for the first v3 stable release.
96+
const addresses = inboxState.identifiers
97+
.filter((i) => i.identifierKind === IdentifierKind.Ethereum)
98+
.map((i) => i.identifier);
99+
```
80100

81101
## Web inbox
82102

@@ -97,5 +117,6 @@ Interact with the XMTP network using [xmtp.chat](https://xmtp.chat), the officia
97117
Examples integrating XMTP with external libraries from the ecosystem
98118

99119
- [grok](/integrations/grok/): Integrate XMTP to the Grok API
120+
- [gaia](/integrations/gaia/): Integrate XMTP to the Gaia API
100121

101122
> See all the available [integrations](/integrations/).

examples/gated-group/README.md

+1-4
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,6 @@ You can generate random keys with the following command:
2020
yarn gen:keys
2121
```
2222

23-
> [!WARNING]
24-
> Running the `gen:keys` script will overwrite the existing `.env` file.
25-
2623
## Start the XMTP agent
2724

2825
Start your XMTP client and begin listening to messages. The bot responds to the following commands:
@@ -33,7 +30,7 @@ Start your XMTP client and begin listening to messages. The bot responds to the
3330
if (message.content === "/create") {
3431
console.log("Creating group");
3532
const group = await client.conversations.newGroup([]);
36-
await group.addMembersByInboxId([message.senderInboxId]);
33+
await group.addMembers([message.senderInboxId]);
3734
await group.addSuperAdmin(message.senderInboxId);
3835

3936
await conversation.send(

examples/gated-group/index.ts

+10-7
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { Client, type XmtpEnv } from "@xmtp/node-sdk";
1+
import { Client, type Group, type XmtpEnv } from "@xmtp/node-sdk";
22
import { Alchemy, Network } from "alchemy-sdk";
33
import { createSigner, getEncryptionKeyFromHex } from "@/helpers";
44

@@ -36,8 +36,11 @@ async function main() {
3636
console.log("Syncing conversations...");
3737
await client.conversations.sync();
3838

39+
const identifier = await signer.getIdentifier();
40+
const address = identifier.identifier;
41+
3942
console.log(
40-
`Agent initialized on ${client.accountAddress}\nSend a message on http://xmtp.chat/dm/${client.accountAddress}`,
43+
`Agent initialized on ${address}\nSend a message on http://xmtp.chat/dm/${address}`,
4144
);
4245

4346
console.log("Waiting for messages...");
@@ -56,8 +59,8 @@ async function main() {
5659
`Received message: ${message.content as string} by ${message.senderInboxId}`,
5760
);
5861

59-
const conversation = client.conversations.getConversationById(
60-
message.conversationId,
62+
const conversation = client.conversations.getDmByInboxId(
63+
message.senderInboxId,
6164
);
6265

6366
if (!conversation) {
@@ -74,7 +77,7 @@ async function main() {
7477
const group = await client.conversations.newGroup([]);
7578
console.log("Group created", group.id);
7679
// First add the sender to the group
77-
await group.addMembersByInboxId([message.senderInboxId]);
80+
await group.addMembers([message.senderInboxId]);
7881
// Then make the sender a super admin
7982
await group.addSuperAdmin(message.senderInboxId);
8083
console.log(
@@ -98,7 +101,7 @@ async function main() {
98101
await conversation.send("Please provide a group id");
99102
return;
100103
}
101-
const group = client.conversations.getConversationById(groupId);
104+
const group = await client.conversations.getConversationById(groupId);
102105
if (!group) {
103106
await conversation.send("Please provide a valid group id");
104107
return;
@@ -113,7 +116,7 @@ async function main() {
113116
console.log("User can't be added to the group");
114117
return;
115118
} else {
116-
await group.addMembers([walletAddress]);
119+
await (group as Group).addMembers([walletAddress]);
117120
await conversation.send(
118121
`User added to the group\n- Group ID: ${groupId}\n- Wallet Address: ${walletAddress}`,
119122
);

examples/gm/README.md

+3-1
Original file line numberDiff line numberDiff line change
@@ -36,8 +36,10 @@ async function main() {
3636
/* Sync the conversations from the network to update the local db */
3737
await client.conversations.sync();
3838

39+
const identifier = await signer.getIdentifier();
40+
const address = identifier.identifier;
3941
console.log(
40-
`Agent initialized on ${client.accountAddress}\nSend a message on http://xmtp.chat/dm/${client.accountAddress}?env=${env}`,
42+
`Agent initialized on ${address}\nSend a message on http://xmtp.chat/dm/${address}`,
4143
);
4244

4345
console.log("Waiting for messages...");

examples/gm/index.ts

+13-5
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
import { Client, type XmtpEnv } from "@xmtp/node-sdk";
2-
import { createSigner, getEncryptionKeyFromHex } from "@/helpers";
2+
import {
3+
createSigner,
4+
getAddressOfMember,
5+
getEncryptionKeyFromHex,
6+
} from "@/helpers";
37

48
/* Get the wallet key associated to the public key of
59
* the agent and the encryption key for the local db
@@ -30,8 +34,10 @@ async function main() {
3034
/* Sync the conversations from the network to update the local db */
3135
await client.conversations.sync();
3236

37+
const identifier = await signer.getIdentifier();
38+
const address = identifier.identifier;
3339
console.log(
34-
`Agent initialized on ${client.accountAddress}\nSend a message on http://xmtp.chat/dm/${client.accountAddress}?env=${env}`,
40+
`Agent initialized on ${address}\nSend a message on http://xmtp.chat/dm/${address}`,
3541
);
3642

3743
console.log("Waiting for messages...");
@@ -52,16 +58,18 @@ async function main() {
5258
);
5359

5460
/* Get the conversation by id */
55-
const conversation = client.conversations.getConversationById(
56-
message.conversationId,
61+
const conversation = client.conversations.getDmByInboxId(
62+
message.senderInboxId,
5763
);
5864

5965
if (!conversation) {
6066
console.log("Unable to find conversation, skipping");
6167
continue;
6268
}
69+
const members = await conversation.members();
6370

64-
console.log(`Sending "gm" response...`);
71+
const address = getAddressOfMember(members, message.senderInboxId);
72+
console.log(`Sending "gm" response to ${address}...`);
6573
/* Send a message to the conversation */
6674
await conversation.send("gm");
6775

examples/gpt/README.md

+1-4
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,6 @@ You can generate random keys with the following command:
1818
yarn gen:keys
1919
```
2020

21-
> [!WARNING]
22-
> Running the `gen:keys` script will overwrite the existing `.env` file.
23-
2421
## Usage
2522

2623
```tsx
@@ -60,7 +57,7 @@ async function main() {
6057
await client.conversations.sync();
6158

6259
console.log(
63-
`Agent initialized on ${client.accountAddress}\nSend a message on http://xmtp.chat/dm/${client.accountAddress}`,
60+
`Agent initialized on ${address}\nSend a message on http://xmtp.chat/dm/${address}`,
6461
);
6562

6663
console.log("Waiting for messages...");

examples/gpt/index.ts

+5-3
Original file line numberDiff line numberDiff line change
@@ -46,8 +46,10 @@ async function main() {
4646
/* Sync the conversations from the network to update the local db */
4747
await client.conversations.sync();
4848

49+
const identifier = await signer.getIdentifier();
50+
const address = identifier.identifier;
4951
console.log(
50-
`Agent initialized on ${client.accountAddress}\nSend a message on http://xmtp.chat/dm/${client.accountAddress}?env=${env}`,
52+
`Agent initialized on ${address}\nSend a message on http://xmtp.chat/dm/${address}`,
5153
);
5254

5355
console.log("Waiting for messages...");
@@ -68,8 +70,8 @@ async function main() {
6870
);
6971

7072
/* Get the conversation from the local db */
71-
const conversation = client.conversations.getConversationById(
72-
message.conversationId,
73+
const conversation = client.conversations.getDmByInboxId(
74+
message.senderInboxId,
7375
);
7476

7577
/* If the conversation is not found, skip the message */

helpers/index.ts

+32-8
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,22 @@
11
import { getRandomValues } from "node:crypto";
2-
import type { Signer } from "@xmtp/node-sdk";
2+
import { IdentifierKind, type GroupMember } from "@xmtp/node-bindings";
3+
import { type Signer } from "@xmtp/node-sdk";
34
import { fromString, toString } from "uint8arrays";
45
import { createWalletClient, http, toBytes } from "viem";
56
import { privateKeyToAccount } from "viem/accounts";
67
import { sepolia } from "viem/chains";
78

89
interface User {
9-
key: string;
10+
key: `0x${string}`;
1011
account: ReturnType<typeof privateKeyToAccount>;
1112
wallet: ReturnType<typeof createWalletClient>;
1213
}
1314

14-
export const createUser = (key: string): User => {
15-
const account = privateKeyToAccount(key as `0x${string}`);
15+
export const createUser = (key: `0x${string}`): User => {
16+
const accountKey = key;
17+
const account = privateKeyToAccount(accountKey);
1618
return {
17-
key,
19+
key: accountKey,
1820
account,
1921
wallet: createWalletClient({
2022
account,
@@ -24,11 +26,14 @@ export const createUser = (key: string): User => {
2426
};
2527
};
2628

27-
export const createSigner = (key: string): Signer => {
29+
export const createSigner = (key: `0x${string}`): Signer => {
2830
const user = createUser(key);
2931
return {
30-
walletType: "EOA",
31-
getAddress: () => user.account.address,
32+
type: "EOA",
33+
getIdentifier: () => ({
34+
identifierKind: IdentifierKind.Ethereum,
35+
identifier: user.account.address.toLowerCase(),
36+
}),
3237
signMessage: async (message: string) => {
3338
const signature = await user.wallet.signMessage({
3439
message,
@@ -39,6 +44,25 @@ export const createSigner = (key: string): Signer => {
3944
};
4045
};
4146

47+
/**
48+
* Get the address of a member
49+
* @param members - The members of the group
50+
* @param inboxId - The inboxId of the member
51+
* @returns The address of the member
52+
*/
53+
export function getAddressOfMember(members: GroupMember[], inboxId: string) {
54+
for (const member of members) {
55+
for (const identifier of member.accountIdentifiers) {
56+
if (
57+
identifier.identifierKind === IdentifierKind.Ethereum &&
58+
member.inboxId === inboxId
59+
) {
60+
return identifier.identifier;
61+
}
62+
}
63+
}
64+
}
65+
4266
/**
4367
* Generate a random encryption key
4468
* @returns The encryption key

integrations/gaia/README.md

+13-8
Original file line numberDiff line numberDiff line change
@@ -22,17 +22,20 @@ You can generate random keys with the following command:
2222
yarn gen:keys
2323
```
2424

25-
> [!WARNING]
26-
> Running the `gen:keys` script will overwrite the existing `.env` file.
27-
2825
## Usage
2926

3027
```tsx
3128
import { Client, type XmtpEnv } from "@xmtp/node-sdk";
3229
import OpenAI from "openai";
3330
import { createSigner, getEncryptionKeyFromHex } from "@/helpers";
3431

35-
const { WALLET_KEY, ENCRYPTION_KEY, GAIA_NODE_URL, GAIA_API_KEY, GAIA_MODEL_NAME } = process.env;
32+
const {
33+
WALLET_KEY,
34+
ENCRYPTION_KEY,
35+
GAIA_NODE_URL,
36+
GAIA_API_KEY,
37+
GAIA_MODEL_NAME,
38+
} = process.env;
3639

3740
if (!WALLET_KEY) {
3841
throw new Error("WALLET_KEY must be set");
@@ -59,9 +62,9 @@ if (!GAIA_MODEL_NAME) {
5962

6063
const signer = createSigner(WALLET_KEY);
6164
const encryptionKey = getEncryptionKeyFromHex(ENCRYPTION_KEY);
62-
const openai = new OpenAI({
63-
baseURL: GAIA_NODE_URL,
64-
apiKey: GAIA_API_KEY
65+
const openai = new OpenAI({
66+
baseURL: GAIA_NODE_URL,
67+
apiKey: GAIA_API_KEY,
6568
});
6669

6770
/* Set the environment to dev or production */
@@ -77,8 +80,10 @@ async function main() {
7780
/* Sync the conversations from the network to update the local db */
7881
await client.conversations.sync();
7982

83+
const identifier = await signer.getIdentifier();
84+
const address = identifier.identifier;
8085
console.log(
81-
`Agent initialized on ${client.accountAddress}\nSend a message on http://xmtp.chat/dm/${client.accountAddress}`,
86+
`Agent initialized on ${address}\nSend a message on http://xmtp.chat/dm/${address}`,
8287
);
8388

8489
console.log("Waiting for messages...");

integrations/gaia/index.ts

+3-1
Original file line numberDiff line numberDiff line change
@@ -65,8 +65,10 @@ async function main() {
6565
/* Sync the conversations from the network to update the local db */
6666
await client.conversations.sync();
6767

68+
const identifier = await signer.getIdentifier();
69+
const address = identifier.identifier;
6870
console.log(
69-
`Agent initialized on ${client.accountAddress}\nSend a message on http://xmtp.chat/dm/${client.accountAddress}`,
71+
`Agent initialized on ${address}\nSend a message on http://xmtp.chat/dm/${address}`,
7072
);
7173

7274
console.log("Waiting for messages...");

0 commit comments

Comments
 (0)