On-chain contracts powering the DMpay.eth paid messaging protocol on Ethereum mainnet.
Frontend: app.dmpay.me · DMpay-Frontend
DMpay.eth is a decentralised paid direct messaging protocol. Users register profiles on-chain, set a USDC price for receiving messages, and senders pay that price to open a conversation. The protocol integrates with ENS for identity (subdomains under dmpay.eth) and XMTP for end-to-end encrypted messaging.
All funds flow directly between sender and recipient — DMpay takes a 2.5% protocol fee, the remaining 97.5% goes to the recipient. No custody, no intermediaries.
Manages user profiles and ENS subdomain registration.
Mainnet: 0x58d02e17bdCf0fdae2e134Da280e6084552F76f5
Key functions:
// Register a new profile with an ENS handle and USDC price
function registerProfile(string calldata handle, uint256 price) external
// Update the IPFS contenthash for your ENS subdomain
function updateIPFSHash(bytes calldata contenthash) external
// Look up a profile by handle
function getProfile(string calldata handle) external view returns (Profile memory)
// Look up a profile by wallet address
function getProfileByWallet(address wallet) external view returns (Profile memory)On registration, the contract:
- Creates an ENS subdomain
handle.dmpay.ethvia the ENS registry - Sets the resolver to the ENS Public Resolver
- Sets the ETH address record on the resolver
After IPFS pinning, updateIPFSHash sets the ENS contenthash, enabling handle.dmpay.eth to resolve to a personalised IPFS profile page.
Handles pay-to-message payments in USDC.
Mainnet: 0x588C943Bd4f59888B2F6ECA0b2BfB123B57b0a10
Key functions:
// Open a paid conversation (sender pays recipient's price in USDC)
// Requires prior USDC approval for this contract
function openConversation(address recipient) external
// Check if a conversation between sender and recipient is open
function isConversationOpen(address sender, address recipient) external view returns (bool)Payment flow:
- Sender calls
openConversation(recipient)after approving USDC - Contract reads recipient's price from
DMPayRegistry - 2.5% fee sent to protocol treasury
- 97.5% sent directly to recipient
- Conversation flagged as open on-chain
| Contract | Address |
|---|---|
| DMPayRegistry | 0x58d02e17bdCf0fdae2e134Da280e6084552F76f5 |
| DMPayMessaging | 0x588C943Bd4f59888B2F6ECA0b2BfB123B57b0a10 |
| ENS: dmpay.eth node | 0x2059bd34c28c7a6645dd35be7e5dcc4b3e4999849bbe8b2c8b838f2d4cfc3ec8 |
| ENS Public Resolver | 0xF29100983E058B709F3D539b0c765937B804AC15 |
| USDC | 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48 |
| Contract | Address |
|---|---|
| DMPayRegistry | (deploy via Ignition — see below) |
| DMPayMessaging | (deploy via Ignition — see below) |
| MockUSDC | Deploy MockUSDC first for testing |
- Node.js 20+
- An Ethereum RPC URL (Alchemy or Infura recommended)
- A funded deployer wallet private key
git clone https://github.com/RWA-ID/DMpay-Protocol.git
cd DMpay-Protocol/contracts
npm installCreate a .env file inside contracts/:
MAINNET_RPC_URL=https://eth-mainnet.g.alchemy.com/v2/your_key
SEPOLIA_RPC_URL=https://eth-sepolia.g.alchemy.com/v2/your_key
PRIVATE_KEY=your_deployer_private_key
ETHERSCAN_API_KEY=your_etherscan_keynpx hardhat compilenpx hardhat testnpx hardhat ignition deploy ignition/modules/DMPay.ts --network sepolianpx hardhat ignition deploy ignition/modules/DMPayMainnet.ts --network mainnetcontracts/
├── contracts/
│ ├── DMPayRegistry.sol # Profile registry + ENS subdomain manager
│ └── DMPayMessaging.sol # Pay-to-message USDC payment handler
├── ignition/
│ └── modules/
│ ├── DMPay.ts # Sepolia deployment module
│ └── DMPayMainnet.ts # Mainnet deployment module
├── scripts/
│ └── send-op-tx.ts # Utility for sending ops transactions
├── setContenthash.mjs # Script to manually set ENS contenthash (Sepolia)
├── setContenthashMainnet.mjs # Script to manually set ENS contenthash (Mainnet)
├── setSubdomainContent.mjs # Script to set subdomain content records
├── hardhat.config.ts # Hardhat configuration
└── test/
└── Counter.ts # Contract tests
Sender DMPayMessaging DMPayRegistry
│ │ │
│── openConversation ────>│ │
│ │── getProfile ─────────>│
│ │<── price ─────────────-│
│ │ │
│ │── transferFrom(sender, recipient, 97.5%)
│ │── transferFrom(sender, treasury, 2.5%)
│ │── mark conversation open
│<── success ────────────-│ │
Each registered user gets a subdomain under dmpay.eth:
- Registry node:
keccak256(dmpay.eth namehash + keccak256(handle)) - Resolver: ENS Public Resolver (
0xF29100983E058B709F3D539b0c765937B804AC15) - Address record: Set to user's wallet address
- Contenthash: Set to IPFS CIDv1 after profile page is pinned
The dmpay.eth parent domain is managed by the DMpay deployer wallet. DMPayRegistry is approved via setApprovalForAll on the ENS registry, allowing it to create subdomains on behalf of the owner.
- No admin keys for individual user profiles — once registered, only the wallet owner can update their IPFS hash
- Protocol fee rate is set at deployment and enforced by the immutable smart contract
- All USDC flows are atomic — payment and conversation-open happen in a single transaction
- No contract upgradeability — what is deployed is what runs