A payment gateway for any
IERC20
tokens (USDT) with testbed on top of TestERC20 token
Important
Made by using react-declarative to solve your problems. ⭐Star and 💻Fork It on github will be appreciated
- Run
npm run start:ganache
in separated terminal - Run
npm run deploy:contracts
to deploy contracts to ganache - Run this DApp by running
npm start
in client folder. Send some tokens - Check admin menu by typing secret cheat-code
133337
- If you need test eth's on a balance check config.json. The smart-contract is going to mint 10_000 tokens for the first address in the list.
Solidity
contract PaymentGatewayContract {
IERC20 private erc20;
address public owner;
uint256 public deployBlock;
constructor(address _erc20Address) {
erc20 = IERC20(_erc20Address);
owner = msg.sender;
deployBlock = block.number;
}
function sendUSDT(uint256 _amount, bytes32 _data) public {
uint256 allowance = erc20.allowance(msg.sender, address(this));
require(allowance >= _amount, "ERC20 allowance not sufficient");
bool transferSuccess = erc20.transferFrom(msg.sender, owner, _amount);
require(transferSuccess, "Failed to transfer ERC20");
emit Transfer(msg.sender, _amount, _data);
}
event Transfer(address indexed sender, uint256 amount, bytes32 _data);
}
TypeScript
export class ContractService {
private readonly ethersService = inject<EthersService>(TYPES.ethersService);
public readonly transferSubject = new Subject<{
sender: string;
amount: number;
data: string;
}>();
private _instance: IContract = null as never;
get isContractConnected() {
return !!this._instance;
};
constructor() {
makeAutoObservable(this);
};
getDeployBlock = async () => Number(await this._instance.deployBlock());
sendUSDT = async (_amount: number, _data: string) => {
const result = await this._instance.sendUSDT(String(_amount), toBytes32(_data));
const rc = await result.wait();
const event = rc.events.find((event: any) => event.event === 'Transfer');
const [sender, amount, data] = event.args;
return {
sender,
amount: Number(amount),
data: fromBytes32(data),
};
};
getTransferList = async () => {
const eventSignature = 'Transfer(address,uint256,bytes32)';
const eventTopic = ethers.utils.id(eventSignature);
const deployBlock = await this.getDeployBlock();
const currentBlock = await this.ethersService.provider.getBlockNumber();
const parser = new ethers.utils.Interface(CC_CONTRACT_ABI);
const rawLogs = await this.ethersService.provider.getLogs({
address: CC_CONTRACT_ADDRESS,
topics: [eventTopic],
fromBlock: deployBlock,
toBlock: currentBlock,
});
return rawLogs.map((log, idx) => {
const parsedLog = parser.parseLog(log);
const [sender, amount, data] = parsedLog.args;
return {
id: `${idx}`,
sender,
amount: Number(amount),
data: fromBytes32(data),
};
});
};
prefetch = singleshot(async () => {
console.log("ContractService prefetch started");
try {
const deployedCode = await this.ethersService.getCode(CC_CONTRACT_ADDRESS);
if (deployedCode === '0x') {
throw new Error('ContractService contract not deployed');
}
const instance = new ethers.Contract(
CC_CONTRACT_ADDRESS,
CC_CONTRACT_ABI,
this.ethersService.getSigner(),
) as IContract;
runInAction(() => this._instance = instance);
this.ethersService.provider.once("block", () => {
instance.on('Transfer', (sender: string, amount: BigInt, data: string) => {
this.transferSubject.next({
sender,
amount: Number(amount),
data: fromBytes32(data),
});
});
});
} catch (e) {
console.warn('ContractService prefetch failed', e);
}
});
};