Skip to main content

Example Patterns

These examples assume a client has been created through createMessagingGroupsClient() and a relayer is running. See Setup for client configuration and Relayer for running the relayer.

Create a group and send messages​

import { Ed25519Keypair } from '@mysten/sui/keypairs/ed25519';

const keypair = Ed25519Keypair.generate();

// Create a group with initial members
await client.messaging.createAndShareGroup({
signer: keypair,
name: 'Project Chat',
initialMembers: ['0xAlice...', '0xBob...'],
});

// Send a message
const { messageId } = await client.messaging.sendMessage({
signer: keypair,
groupRef: { uuid: 'project-chat-uuid' },
text: 'Hello team!',
});

// Fetch recent messages
const { messages, hasNext } = await client.messaging.getMessages({
signer: keypair,
groupRef: { uuid: 'project-chat-uuid' },
limit: 50,
});

for (const msg of messages) {
console.log(`${msg.senderAddress}: ${msg.text} (verified: ${msg.senderVerified})`);
}

Real-time subscription​

const controller = new AbortController();

for await (const msg of client.messaging.subscribe({
signer: keypair,
groupRef: { uuid: 'project-chat-uuid' },
signal: controller.signal,
})) {
console.log(`[${msg.senderAddress}] ${msg.text}`);

if (msg.isEdited) {
console.log('(edited)');
}
}

// To stop the subscription
controller.abort();

// Disconnect the transport when done (cleans up underlying resources)
client.messaging.disconnect();

Resume from a known position by passing afterOrder:

for await (const msg of client.messaging.subscribe({
signer: keypair,
groupRef: { uuid: 'project-chat-uuid' },
afterOrder: lastSeenOrder,
signal: controller.signal,
})) {
// Only messages after lastSeenOrder
}

File attachments​

Requires an attachments config with a StorageAdapter at client creation. See Setup. For the full encryption model, see Attachments.

// Send a message with files
const fileData = new TextEncoder().encode('Report content...');

await client.messaging.sendMessage({
signer: keypair,
groupRef: { uuid: 'project-chat-uuid' },
text: 'Here is the report',
files: [
{ fileName: 'report.txt', mimeType: 'text/plain', data: fileData },
],
});

// Download attachments from a received message
for (const attachment of msg.attachments) {
console.log(`${attachment.fileName} (${attachment.fileSize} bytes)`);
const bytes = await attachment.data(); // download + decrypt on demand
}

Edit and delete messages​

// Edit a message (only the original sender can edit)
await client.messaging.editMessage({
signer: keypair,
groupRef: { uuid: 'project-chat-uuid' },
messageId: 'abc123',
text: 'Updated text',
});

// Edit with attachment changes
await client.messaging.editMessage({
signer: keypair,
groupRef: { uuid: 'project-chat-uuid' },
messageId: 'abc123',
text: 'Updated with new attachment',
attachments: {
current: originalMsg.attachments.map(a => a.wire),
remove: ['old-storage-id'],
new: [{ fileName: 'new.pdf', mimeType: 'application/pdf', data: pdfBytes }],
},
});

// Soft-delete a message (only the original sender can delete)
await client.messaging.deleteMessage({
signer: keypair,
groupRef: { uuid: 'project-chat-uuid' },
messageId: 'abc123',
});

Member management and key rotation​

import { messagingPermissionTypes } from '@mysten/sui-stack-messaging';

const perms = messagingPermissionTypes(MESSAGING_PACKAGE_ID);

// Add a member with specific permissions (via the groups extension)
await client.groups.grantPermissions({
signer: keypair,
groupId: '0x...',
member: '0xNewMember...',
permissionTypes: [perms.MessagingSender, perms.MessagingReader],
});

// Remove a member and rotate the encryption key atomically
await client.messaging.removeMembersAndRotateKey({
signer: keypair,
groupRef: { uuid: 'project-chat-uuid' },
members: ['0xFormerMember...'],
});

// Rotate the key without removing anyone (periodic rotation)
await client.messaging.rotateEncryptionKey({
signer: keypair,
groupRef: { uuid: 'project-chat-uuid' },
});

See Security for why removeMembersAndRotateKey() is recommended over standalone member removal.

Using tx.* with dApp Kit​

When integrating with @mysten/dapp-kit-react, use tx.* methods to get a Transaction object for the wallet to sign:

// In a React component using dapp-kit-react
import { useDAppKit } from '@mysten/dapp-kit-react';

const dAppKit = useDAppKit();

const handleCreateGroup = async () => {
const uuid = crypto.randomUUID();

const tx = client.messaging.tx.createAndShareGroup({
name: groupName,
uuid,
initialMembers: selectedMembers,
});

await dAppKit.signAndExecuteTransaction({ transaction: tx });
};

const handleRotateKey = async () => {
const tx = client.messaging.tx.rotateEncryptionKey({
uuid: groupUuid,
});

await dAppKit.signAndExecuteTransaction({ transaction: tx });
};

const handleRemoveMember = async (memberAddress: string) => {
const tx = client.messaging.tx.removeMembersAndRotateKey({
uuid: groupUuid,
members: [memberAddress],
});

await dAppKit.signAndExecuteTransaction({ transaction: tx });
};

signAndExecuteTransaction resolves with a $kind discriminated union rather than throwing when a transaction fails onchain. Check result.$kind === 'FailedTransaction' before treating it as a success.

Composing with call.* thunks​

Use call.* with tx.add() to compose operations into a single PTB. This follows the MystenLabs SDK transaction thunks pattern.

import { Transaction } from '@mysten/sui/transactions';

const tx = new Transaction();

// Create an unshared group (returns TransactionArguments for the objects)
const [group, encryptionHistory] = tx.add(
client.messaging.call.createGroup({ name: 'Team Chat' }),
);

// Share both objects
tx.add(client.messaging.call.shareGroup({ group, encryptionHistory }));

await keypair.signAndExecuteTransaction({ transaction: tx, client });

For granting extra permissions beyond the initial members, use a separate transaction after the group is created, or pass all initial members through the initialMembers parameter.

Filtering system objects from member lists​

Groups contain system actor objects (GroupLeaver, GroupManager) as members. Filter them for user-facing displays:

const { members } = await client.groups.view.getMembers({
groupId: '0x...',
exhaustive: true,
});

const systemAddresses = client.messaging.derive.systemObjectAddresses();
const humanMembers = members.filter(m => !systemAddresses.has(m.address));

Archive a group​

Permanently freeze a group by pausing it and burning the UnpauseCap:

await client.messaging.archiveGroup({
signer: keypair,
groupId: '0x...',
});

// The group is now permanently paused. No further mutations are possible.
// Existing messages remain readable by members.

Recover messages from Walrus​

Requires a RecoveryTransport configured at client creation. See Archive and Recovery.

const { messages, hasNext } = await client.messaging.recoverMessages({
groupRef: { uuid: 'project-chat-uuid' },
limit: 100,
});

for (const msg of messages) {
// Recovered messages are decrypted and verified like real-time messages
console.log(`${msg.text} (verified: ${msg.senderVerified})`);
}