import { Client } from "@xmtp/xmtp-js"; import { GrpcApiClient } from "@xmtp/grpc-api-client"; import { Wallet } from "ethers"; const XMTP_RATE_LIMIT = 1000; const XMTP_RATE_LIMIT_TIME = 60 * 1000; // 1 minute const XMTP_RATE_LIMIT_TIME_INCREASE = XMTP_RATE_LIMIT_TIME * 5; // 5 minutes const BROADCAST_AMOUNT = 10000; const broadcastAddresses = new Array<string>(BROADCAST_AMOUNT).fill(""); const delay = (ms: number) => new Promise<void>((resolve) => setTimeout(resolve, ms)); const run = async () => { const startTime = Date.now(); const wallet = Wallet.createRandom(); // Create the client with your wallet. This will connect to the XMTP development network by default console.log("Creating client"); const client = await Client.create(wallet, { apiClientFactory: GrpcApiClient.fromOptions, }); const batches: string[][] = []; let batch: string[] = []; const canMessageAddresses = await client.canMessage(broadcastAddresses); let errorCount = 0; for (let i = 0; i < canMessageAddresses.length; i++) { if (canMessageAddresses[i]) { batch.push(broadcastAddresses[i]); } // Add a batch of 500 addresses to the batches array // An introduction message is sent for new contacts, so each new message will actually be 2 messages in this case // We want to send 1000 messages per minute, so we split the batches in half // Additional optimization can be done to send messages to contacts that have already been introduced if (batch.length === XMTP_RATE_LIMIT) { batches.push(batch); batch = []; } } if (batch.length > 0) { batches.push(batch); } let currentRateLimitWaitTime = XMTP_RATE_LIMIT_TIME; for (let i = 0; i < batches.length; i++) { let batchWaitTime = currentRateLimitWaitTime; const batchResponse = await Promise.allSettled( batches[i].map(async (address, index) => { const conversation = await client.conversations.newConversation( address ); try { await conversation.send("Hello from XMTP!"); } catch (err) { errorCount++; console.log(`Rate limited, waiting ${batchWaitTime}`); await delay(batchWaitTime); try { await conversation.send("Hello from XMTP!"); } catch (err) { errorCount++; currentRateLimitWaitTime = XMTP_RATE_LIMIT_TIME_INCREASE; batchWaitTime = currentRateLimitWaitTime; console.log(`Rate limited more, waiting ${batchWaitTime}`); await delay(batchWaitTime); await conversation.send("Hello from XMTP!"); } } console.log(`Sent message for batch ${i} index ${index} to ${address}`); }) ); for (let j = 0; j < batchResponse.length; j++) { const element = batchResponse[j]; if (element.status === "rejected") { errorCount++; console.error(element.reason); // Add error handling here } } if (i !== batches.length - 1) { // Wait between batches console.log(`Waiting between batches ${i} and ${i + 1}`); await delay(currentRateLimitWaitTime); } } const endTime = Date.now(); console.log(`Total time: ${endTime - startTime}ms with ${errorCount} errors`); }; const runBatches = async () => { const startTime = Date.now(); const wallet = Wallet.createRandom(); // Create the client with your wallet. This will connect to the XMTP development network by default console.log("Creating client"); const client = await Client.create(wallet, { apiClientFactory: GrpcApiClient.fromOptions, }); const batches: string[][] = []; let batch: string[] = []; const canMessageAddresses = await client.canMessage(broadcastAddresses); let errorCount = 0; for (let i = 0; i < canMessageAddresses.length; i++) { if (canMessageAddresses[i]) { batch.push(broadcastAddresses[i]); } // Add a batch of 500 addresses to the batches array // An introduction message is sent for new contacts, so each new message will actually be 2 messages in this case // We want to send 1000 messages per minute, so we split the batches in half // Additional optimization can be done to send messages to contacts that have already been introduced if (batch.length === XMTP_RATE_LIMIT / 2) { batches.push(batch); batch = []; } } if (batch.length > 0) { batches.push(batch); } for (let i = 0; i < batches.length; i++) { const batch: string[] = []; await Promise.all( batches[i].map(async (address, index) => { const conversation = await client.conversations.newConversation( address ); try { await conversation.send("Hello from XMTP!"); console.log( `Sent message for batch ${i} index ${index} to ${address}` ); } catch (err) { errorCount++; console.error(err); batch.push(address); // Add error handling here } }) ); if (i !== batches.length - 1) { // Wait between batches console.log(`Waiting between batches ${i} and ${i + 1}`); await delay(XMTP_RATE_LIMIT_TIME_INCREASE); } if (batch.length > 0) { batches.push(batch); } } const endTime = Date.now(); console.log(`Total time: ${endTime - startTime}ms with ${errorCount} errors`); }; const runWait = async () => { const startTime = Date.now(); const wallet = Wallet.createRandom(); // Create the client with your wallet. This will connect to the XMTP development network by default console.log("Creating client"); const client = await Client.create(wallet, { apiClientFactory: GrpcApiClient.fromOptions, }); const canMessageAddresses = await client.canMessage(broadcastAddresses); let errorCount = 0; let currentWait = 0; for (let i = 0; i < canMessageAddresses.length; i++) { if (canMessageAddresses[i]) { const conversation = await client.conversations.newConversation( broadcastAddresses[i] ); try { await conversation.send("Hello from XMTP!"); } catch (err) { errorCount++; console.error(err); if (currentWait === 0) { currentWait = XMTP_RATE_LIMIT_TIME; } else if (currentWait === XMTP_RATE_LIMIT_TIME) { currentWait = XMTP_RATE_LIMIT_TIME_INCREASE; } await delay(currentWait); try { await conversation.send("Hello from XMTP!"); } catch (err) { errorCount++; console.error(err); await delay(XMTP_RATE_LIMIT_TIME_INCREASE); try { await conversation.send("Hello from XMTP!"); } catch (err) { errorCount++; console.error(err); } } } console.log(`Sent message ${i} to ${broadcastAddresses[i]}`); } } const endTime = Date.now(); // Total time awaiting each send: 3421425ms with 8 errors ~57 minutes // Total time awaiting each send: 8232861ms with 24702 errors console.log( `Total time awaiting each send: ${ endTime - startTime }ms with ${errorCount} errors` ); }; runBatches(); // runWait: Total time awaiting each send: 3421425ms with 8 errors ~57 minutes // runBatch: Total time awaiting each send: 3354697ms with 0 errors // Run: Total time batch greedy: 8232861ms with 24702 errors