A trust‑minimized ERC‑20 escrow / payment‑link protocol that protects users from address‑poisoning and other mis‑delivery attacks. Funds are locked in a smart contract with a configurable grace period so the sender can verify the destination before the receiver is able to withdraw.
Mainnet: https://paymatic.xyz • Testnet (dev): https://dev.paymatic.xyz
| Feature | Description |
|---|---|
| ERC‑20 escrow | Supports any ERC‑20 token by passing the token address. |
| Grace period | Default DEFAULT_TIMELOCK = 120 s (2 minutes). Custom timelock via createERC20PaymentWithTimeLock. |
| Cancelable | Sender can cancel any time until the receiver withdraws; owner can emergency cancel. |
| Protocol fee | Default 0.3 % (300 ppm). Configurable by owner through setFeeValue(). Fees auto‑sent to feeRecipient. |
| Pausable | Owner can pause/unpause all state‑changing actions. |
| Events‑first design | All state changes emit events for easy off‑chain indexing & link generation. |
graph TD
A[createERC20Payment] -->|locks funds + emits PaymentCreated| B(Payment)
B -. cancelPayment .-> C[Refund -> sender]
B -->|after unlockTime| D[settlePayment]
D -->|transfer tokens| E[Receiver]
- createERC20Payment / createERC20PaymentWithTimeLock – Sender deposits tokens & sets
receiver,token,amount, and optionaltimelockPeriod. - Contract emits
EventPaymentStatusChangeCreatedcontainingpaymentId→ build off‑chain linkhttps://paymatic.xyz/receive/<paymentId>. - During grace period the sender may call cancelPayment; owner may call emergencyCancelPayment.
- After
unlockTime, the receiver calls settlePayment to withdraw.
contracts/ 👉 Solidity sources
└─ PaymaticPayments.sol 👉 Core escrow contract
ignition/ 👉 Hardhat deployment scripts
test/ 👉 Hardhat tests
- Node.js ≥ 22
- yarn
- Hardhat
# clone repo
$ git clone https://github.com/crypto-paymatic/contracts.git
$ cd contracts
# install deps
$ yarn
# compile
$ yarn build# hardhat + chai
$ yarn test
# coverage
$ yarn coverage# set RPC and PRIVATE_KEY in .env
$ npx hardhat ignition deploy ignition/modules/PaymaticPayments.ts --network sepoliaPaymatic deployed on all chains using create2 strategy, meaning it has the same address everywhere
0x7A1Bae8b00250324D809e7Ca04dACBbBb2683c87
| Name | Fields |
|---|---|
PaymentStatus |
Default, Created, Settled, Canceled |
Payment |
id, from, to, token, amount, status, unlockTime |
| Function | Access | Description |
|---|---|---|
createERC20Payment(address to, address token, uint256 amount) |
External | Locks tokens using default timelock. |
createERC20PaymentWithTimeLock(address to, address token, uint256 amount, uint256 timelock) |
External | Same as above but custom timelock. |
cancelPayment(uint256 id) |
External | Sender only, if status == Created. |
settlePayment(uint256 id) |
External | Receiver only, post‑unlock. |
getPaymentDetails(uint256 id) |
View | Returns Payment struct. |
getFeeValue() / getFeeRecipient() |
View | Protocol‑level settings. |
pause() / unPause() |
Owner | Pauses/unpauses contract. |
setFeeRecipient(address) / setFeeValue(uint256) |
Owner | Updates protocol fee settings. |
emergencyCancelPayment(uint256 id) |
Owner | Refunds sender in emergencies. |
| Event | Trigger |
|---|---|
EventPaymentStatusChangeCreated(id, from, to, token, amount, unlockTime) |
On payment creation. |
EventPaymentStatusChangeSettled(id) |
On successful withdrawal. |
EventPaymentStatusChangeCanceled(id) |
When sender cancels. |
EventPaymentStatusChangeCanceledEmergency(id) |
When owner emergency‑cancels. |
FeeRecipientChanged(newRecipient) |
Owner updates fee recipient. |
FeeValueChanged(newValue) |
Owner updates fee rate. |
PaymentNotFound, AmountShouldBeGreaterThanZero, InvalidAddress, PaymentShouldBeInCreatedState, PaymentTimelocked, NotPaymentSender, NotPaymentRecipient, NotEnoughProtocolFees, ProtocolFeeDistributionError, NotEnoughTokenBalance, NotEnoughTokenAllowance.
feeAmount = amount × feeValue ÷ 100 000
- Default feeValue:
300→ 0.3 % (300 ppm) - Collected fees are
SafeERC20.safeTransfer‑ed tofeeRecipientimmediately upon deposit.
- Reentrancy‑safe by leveraging OpenZeppelin’s
SafeERC20(no external calls after state mutation). Pausablecircuit‑breaker controlled by owner.- Emergency cancel allows rapid sender refunds if an exploit is discovered.
- Extensive unit + fuzz tests (see
/test). - Pending 3rd‑party audit — see
SECURITY.md.
If you discover a vulnerability, please follow the disclosure guidelines in SECURITY.md.
Pull requests are welcome! Please open an issue first to discuss substantial changes.
# lint & format
$ yarn lint
$ yarn formatSee CONTRIBUTING.md for coding standards and branch workflow.
Distributed under the Business Source License 1.0 (BSL‑1.0). See LICENSE for details and change date.
Built with ❤️ by the Paymatic core team & our community.
- OpenZeppelin for battle‑tested libraries.
- Hardhat for better development tooling.
- All early Paymatic users providing feedback and testing.
Disclaimer: Contracts are provided as‑is. Use in production only after independent audits.