Open peterduhon opened 1 week ago
First, install the necessary dependencies:
npm install @xmtp/xmtp-js ethers@5.7.2
Create a new file src/services/xmtpService.js
:
import { Client } from '@xmtp/xmtp-js';
import { ethers } from 'ethers';
let xmtp;
let conversations = new Map();
export const initializeXMTP = async (signer) => {
xmtp = await Client.create(signer, { env: 'production' });
return xmtp;
};
export const startConversation = async (peerAddress) => {
if (!xmtp) throw new Error('XMTP client not initialized');
const conversation = await xmtp.conversations.newConversation(peerAddress);
conversations.set(peerAddress, conversation);
return conversation;
};
export const sendMessage = async (peerAddress, message) => {
const conversation = conversations.get(peerAddress) || await startConversation(peerAddress);
await conversation.send(message);
};
export const listenForMessages = (peerAddress, callback) => {
const conversation = conversations.get(peerAddress);
if (!conversation) throw new Error('Conversation not found');
const stream = conversation.streamMessages();
stream.on('message', callback);
return () => stream.return(); // Call this function to stop listening
};
Create a new file src/components/PrivateMessaging.js
:
import React, { useState, useEffect, useCallback } from 'react';
import { initializeXMTP, sendMessage, listenForMessages } from '../services/xmtpService';
const PrivateMessaging = ({ signer, peerAddress }) => {
const [messages, setMessages] = useState([]);
const [newMessage, setNewMessage] = useState('');
const [xmtpClient, setXmtpClient] = useState(null);
useEffect(() => {
const setup = async () => {
const client = await initializeXMTP(signer);
setXmtpClient(client);
};
setup();
}, [signer]);
useEffect(() => {
if (!xmtpClient || !peerAddress) return;
const unsubscribe = listenForMessages(peerAddress, (message) => {
setMessages((prev) => [...prev, message]);
});
return unsubscribe;
}, [xmtpClient, peerAddress]);
const handleSend = useCallback(async () => {
if (!newMessage.trim()) return;
await sendMessage(peerAddress, newMessage);
setNewMessage('');
}, [newMessage, peerAddress]);
return (
<div className="private-messaging">
<div className="message-list">
{messages.map((msg, index) => (
<div key={index} className="message">
<span className="sender">{msg.senderAddress === xmtpClient.address ? 'You' : 'Peer'}:</span>
<span className="content">{msg.content}</span>
</div>
))}
</div>
<div className="message-input">
<input
type="text"
value={newMessage}
onChange={(e) => setNewMessage(e.target.value)}
placeholder="Type a message..."
/>
<button onClick={handleSend}>Send</button>
</div>
</div>
);
};
export default PrivateMessaging;
Update your main game component or wherever you want to include private messaging:
import React from 'react';
import PrivateMessaging from './PrivateMessaging';
const GameInterface = ({ signer, opponent }) => {
return (
<div className="game-interface">
{/* Other game components */}
<PrivateMessaging signer={signer} peerAddress={opponent.address} />
</div>
);
};
export default GameInterface;
Here's a basic test file to get you started. Create src/components/PrivateMessaging.test.js
:
import React from 'react';
import { render, screen, fireEvent } from '@testing-library/react';
import PrivateMessaging from './PrivateMessaging';
import { initializeXMTP, sendMessage, listenForMessages } from '../services/xmtpService';
jest.mock('../services/xmtpService');
describe('PrivateMessaging', () => {
const mockSigner = {};
const mockPeerAddress = '0x1234...';
beforeEach(() => {
initializeXMTP.mockResolvedValue({});
listenForMessages.mockImplementation((_, callback) => {
callback({ senderAddress: mockPeerAddress, content: 'Test message' });
return jest.fn();
});
});
it('renders without crashing', () => {
render(<PrivateMessaging signer={mockSigner} peerAddress={mockPeerAddress} />);
expect(screen.getByPlaceholderText('Type a message...')).toBeInTheDocument();
});
it('sends a message when the send button is clicked', async () => {
render(<PrivateMessaging signer={mockSigner} peerAddress={mockPeerAddress} />);
fireEvent.change(screen.getByPlaceholderText('Type a message...'), { target: { value: 'Hello' } });
fireEvent.click(screen.getByText('Send'));
expect(sendMessage).toHaveBeenCalledWith(mockPeerAddress, 'Hello');
});
it('displays received messages', async () => {
render(<PrivateMessaging signer={mockSigner} peerAddress={mockPeerAddress} />);
expect(await screen.findByText('Test message')).toBeInTheDocument();
});
});
To run these tests, make sure you have Jest and React Testing Library installed, then run:
npm test
XMTP handles encryption automatically, but you should still:
Remember to always use HTTPS in production to protect data in transit.
cc: @JW-dev0505
@tetyana-pol
An update about where to get the signer
and peerAddress
for the PrivateMessaging component. Let me clarify how to integrate this into our poker game:
The signer
should come from your Web3 provider (like Web3Auth or MetaMask). You typically get this when a user connects their wallet.
The peerAddress
is the Ethereum address of the player you want to message (usually your opponent in the game).
Here's how you can integrate this into your main game component:
PokerGame.js
) like this:
import React, { useState, useEffect } from 'react';
import { ethers } from 'ethers';
import PrivateMessaging from './PrivateMessaging';
import { Web3Auth } from "@web3auth/modal";
// Import other necessary components
const PokerGame = () => {
const [signer, setSigner] = useState(null);
const [opponent, setOpponent] = useState(null);
const [web3auth, setWeb3auth] = useState(null);
useEffect(() => {
const initWeb3Auth = async () => {
const web3auth = new Web3Auth({
clientId: "YOUR_WEB3AUTH_CLIENT_ID",
chainConfig: {
// ... your Morph chain configuration
},
});
setWeb3auth(web3auth);
await web3auth.initModal();
};
initWeb3Auth();
}, []);
const connectWallet = async () => {
if (!web3auth) {
console.log("web3auth not initialized yet");
return;
}
const web3authProvider = await web3auth.connect();
const ethersProvider = new ethers.providers.Web3Provider(web3authProvider);
const signer = ethersProvider.getSigner();
setSigner(signer);
};
// This function would be called when a game starts or when an opponent is assigned
const setGameOpponent = (opponentAddress) => {
setOpponent(opponentAddress);
};
return (
<div>
{!signer ? (
<button onClick={connectWallet}>Connect Wallet</button>
) : (
<div>
{/* Your main game UI components */}
{opponent && (
<PrivateMessaging signer={signer} peerAddress={opponent} />
)}
</div>
)}
</div>
);
};
export default PokerGame;
This setup above does the following:
setGameOpponent
function would be called when a game starts or an opponent is assigned. You'll need to implement the logic for this based on your game flow.Remember to replace "YOUR_WEB3AUTH_CLIENT_ID" with your actual Web3Auth client ID.
Let me know if you need any clarification or have any questions about integrating this into your existing code!
Hi @tetyana-pol ,
I wanted to clarify a couple of points about the XMTP and Web3Auth integration:
XMTP and Web3Auth Connection: The Web3Auth integration provides the signer (user's wallet) that XMTP needs for messaging. Essentially, Web3Auth handles the user authentication and wallet connection, while XMTP uses that connection for secure messaging.
GameInterface Component: I wanted to confirm if you're still using a structure similar to this for your game interface:
// GameInterface.js
import React, { useState, useEffect } from 'react';
import { ethers } from 'ethers';
import PrivateMessaging from './PrivateMessaging';
import GameBoard from './GameBoard';
import PlayerInfo from './PlayerInfo';
import ActionButtons from './ActionButtons';
const GameInterface = ({ web3Provider, gameContract, playerAddress }) => {
const [opponent, setOpponent] = useState(null);
const [signer, setSigner] = useState(null);
useEffect(() => {
const setupSigner = async () => {
if (web3Provider) {
const signerInstance = web3Provider.getSigner();
setSigner(signerInstance);
}
};
const getOpponent = async () => {
if (gameContract) {
// This is a placeholder. You'll need to implement a method to get the opponent's address
const opponentAddress = await gameContract.getOpponent(playerAddress);
setOpponent(opponentAddress);
}
};
setupSigner();
getOpponent();
}, [web3Provider, gameContract, playerAddress]);
if (!signer || !opponent) {
return <div>Loading game interface...</div>;
}
return (
<div className="game-interface">
<div className="game-board-container">
<GameBoard contract={gameContract} playerAddress={playerAddress} />
</div>
<div className="sidebar">
<PlayerInfo playerAddress={playerAddress} />
<ActionButtons contract={gameContract} playerAddress={playerAddress} />
<PrivateMessaging signer={signer} peerAddress={opponent} />
</div>
</div>
);
};
export default GameInterface;
import { Client } from "@xmtp/xmtp-js";
let xmtp;
export const initializeXMTP = async (signer) => {
xmtp = await Client.create(signer, { env: "production" });
return xmtp;
};
export const sendMessage = async (peerAddress, message) => {
if (!xmtp) throw new Error("XMTP client not initialized");
const conversation = await xmtp.conversations.newConversation(peerAddress);
await conversation.send(message);
};
export const listenForMessages = (callback) => {
if (!xmtp) throw new Error("XMTP client not initialized");
const stream = xmtp.conversations.streamAllMessages();
stream.on("message", callback);
return () => stream.removeListener("message", callback);
};
import React, { useEffect, useState } from 'react';
import { initializeXMTP, sendMessage, listenForMessages } from './xmtpService';
const GameComponent = ({ web3 }) => {
const [xmtpClient, setXmtpClient] = useState(null);
const [messages, setMessages] = useState([]);
useEffect(() => {
if (web3) {
const initXmtp = async () => {
const signer = web3.eth.personal.sign.bind(web3.eth.personal);
const client = await initializeXMTP(signer);
setXmtpClient(client);
};
initXmtp();
}
}, [web3]);
useEffect(() => {
if (xmtpClient) {
const unsubscribe = listenForMessages((message) => {
setMessages(prevMessages => [...prevMessages, message]);
});
return () => unsubscribe();
}
}, [xmtpClient]);
const handleSendMessage = async (recipientAddress, messageContent) => {
if (xmtpClient) {
await sendMessage(recipientAddress, messageContent);
}
};
// Rest of your game component...
};
export default GameComponent;
The xmtpService.js
file should be created to handle XMTP functionality. This service initializes the XMTP client, sends messages, and listens for incoming messages.
In the game component (or wherever the chat functionality is needed), import and use the XMTP service. The waiting room. The component should:
The XMTP client initialization uses the signer from Web3, which is already set up in the AuthPage.jsx file. Make sure to pass the Web3 instance to the game component after successful authentication.
This setup allows for chat functionality in the waiting room before the game starts, as per your requirements.
@tetyana-pol please install the XMTP package:
npm install @xmtp/xmtp-js
Hi @tetyana-pol ! Here's how to get started with the XMTP chat component:
Create a new file called XMTPChat.jsx
in your components directory and copy the provided code into it.
Install necessary dependencies:
npm install @xmtp/xmtp-js @web3auth/modal @web3auth/base
Replace "YOUR_WEB3AUTH_CLIENT_ID"
with the actual client ID from the Web3Auth dashboard.
Import and use the component in your main App or relevant page:
import XMTPChat from './components/XMTPChat';
function App() {
return (
<div>
<h1>Skia Poker</h1>
<XMTPChat />
</div>
);
}
You can start using this component without smart contract addresses or ABIs. It will allow users to connect their wallet and send messages using XMTP.
For testing, you can hardcode a recipient address in the sendMessage
function. Replace "0x1234..."
with a test Ethereum address.
Run your React app locally to test the XMTP chat functionality.
Note: This setup uses Web3Auth for authentication and XMTP for messaging. It's configured to work with the Morph testnet, but actual blockchain interactions aren't implemented yet.
Next steps:
Let us know if you have any questions or run into any issues!
Hi @JW-dev0505
To help Tetiana integrate the smart contracts with the frontend, please provide the following information:
Deployed Contract Addresses:
Contract ABIs:
Any specific instructions for interacting with the contracts:
Test Accounts:
Please compile this information and share it with Tetiana. You can create a markdown file with the following structure:
# Smart Contract Integration Information
## Deployed Addresses
- GameLogic: 0x...
- PlayerManagement: 0x...
- (Other contracts...)
## ABIs
(Paste the ABIs here, one for each contract)
## Interaction Instructions
1. First, connect to the GameLogic contract...
2. Then, register a player using the PlayerManagement contract...
3. (Any other relevant instructions)
## Test Accounts
- Address: 0x...
Private Key: 0x... (only if it's a throwaway account)
- (Add more if available)
This information will allow @tetyana-pol to create the necessary contract instances in the frontend and start integrating the blockchain functionality with the UI.
@peterduhon @tetyana-pol Here is deployed addresses. AIPlayerManagement deployed to: 0x7bAcEEC35b09138b46b0d4edbe823E1d685691fE RoomManagement deployed to: 0x1d2AffDb37dfdb61442d6DDE7a4b3A242198ad6C UserManagement deployed to: 0x8EE046ef044C7fd8D41777259024f6d52Ba1d5b4 CardManagement deployed to: 0xdF100bFA4d60A7c26bF78E1987efeB1DaDC6De79 BettingAndPotManagement deployed to: 0x5ed17243C07BE0D9Ed8EdCcd82B3d25525cd7010 PokerGameProxy deployed to: 0xD708db9782597D6BB59D6D6B631a845cb891168f
And other necessary credentials. PRIVATE_KEY=736e5b9672babec26309c6d294861eaffcc2c9f4e91535b356c80bf569251cd9 API_URL=https://rpc.ankr.com/eth_holesky PRIVATE_KEY_LOCAL=0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 (Test Acount)
[SP-12] Implement Private Messaging with XMTP Summary: Implement secure private messaging between players using XMTP with end-to-end encryption. Tasks: Set up XMTP messaging infrastructure. Develop UI components for private messaging. Integrate messaging into the existing UI. Conduct security and functionality testing. Validate secure communication and encryption