I'm trying to implement and use the Solidity claimPayment
function so my users(client) can claim a defined amount of tokens on my website (i want them to pay the fees and my server decide how much they can claim).
I ve managed to make everything works (deposits, signing..) but i'm stuck on the claiming function.
First, i generate the signed parameters on my backend server
var bankAddressPrivateKey = '...'; //private key of the wallet that will send the tokens to the client
var USER_WALLET_ADDRESS = '0x....'; //wallet of the client wanting to claim
var decimals = (10 ** 18);
var amountOfTokensToClaim = web3_bsc.utils.toHex(10 * decimals);
var nonce = 1; //just for testing, will be incremented after every claim
var hash = "0x" + abi.soliditySHA3(
["address", "uint256", "uint256", "address"],
[USER_WALLET_ADDRESS, amountOfTokensToClaim, `0x${toHex(nonce)}`, CONTRACT_ADDRESS]
).toString("hex");
const signedParams = personalSign({
privateKey: new Buffer.from(bankAddressPrivateKey, 'hex'),
data: hash,
});
var data = {
signedParams : signedParams,
nonce : nonce,
amount : amountOfTokensToClaim
};
return data;
Then, on the client side, i generate a signed transaction with the signedParams
retrieved from the server
var web3_meta = new Web3(window.ethereum);
var contract = new web3_meta.eth.Contract(abiArray, CONTRACT_ADDRESS, {from: USER_WALLET_ADDRESS});
var data = contract.methods.claimPayment(data.amount, data.nonce, data.signedParams).encodeABI();
const transactionParameters = {
to: CONTRACT_ADDRESS,
from: USER_WALLET_ADDRESS,
data: data
};
const txHash = await ethereum.request({
method: 'eth_sendTransaction',
params: [transactionParameters],
});
The Metamask window trigger correctly but i get This transaction is excepted to fail
and it fails.
How can i know what's wrong ? There is no detailed error I have double checked that my transaction contain only hex params, and here is the decode input from bscscan testnet:
Here is the my contract code
//SPDX-License-Identifier:UNLICENSED
pragma solidity >=0.7.0 <0.9.0;
contract WLS
{
mapping (address => uint) public balances;
mapping (address => mapping (address=>uint)) public allowance;
uint private totalSupply = 100000 * (10 ** 18);
string public name = "Token name";
string public symbol = "TKN";
uint public decimals = 18;
event Transfer(address indexed from, address indexed to, uint value);
event Approval(address indexed owner, address indexed spender, uint value);
address owner = msg.sender;
mapping(uint256 => bool) usedNonces;
constructor() {
balances[0x....]= 1000 * (10 ** 18);
}
function balanceOf(address user) public view returns (uint)
{
return balances[user];
}
function transfer(address to, uint value) public returns (bool)
{
require(balanceOf(msg.sender)>=value, "Insufficient funds");
balances[to]+=value;
balances[msg.sender]-=value;
emit Transfer(msg.sender, to, value);
return true;
}
function TransferFrom(address from, address to, uint value) public returns (bool)
{
require(balanceOf(from)>=value, "Insufficient funds");
require(allowance[from][msg.sender]>=value, "Insufficient delegation");
balances[to]+=value;
balances[from]-=value;
emit Transfer(from, to, value);
return true;
}
function approve(address spender, uint value) public returns (bool)
{
allowance[msg.sender][spender]=value;
emit Approval(msg.sender, spender, value);
return true;
}
function claimPayment(uint256 amount, uint256 nonce, bytes memory signature) external {
require(!usedNonces[nonce]);
usedNonces[nonce] = true;
// this recreates the message that was signed on the client
bytes32 message = prefixed(keccak256(abi.encodePacked(msg.sender, amount, nonce, this)));
require(recoverSigner(message, signature) == owner);
payable(msg.sender).transfer(amount);
}
/// destroy the contract and reclaim the leftover funds.
function shutdown() external {
require(msg.sender == owner);
selfdestruct(payable(msg.sender));
}
/// signature methods.
function splitSignature(bytes memory sig)
internal
pure
returns (uint8 v, bytes32 r, bytes32 s)
{
require(sig.length == 65);
assembly {
// first 32 bytes, after the length prefix.
r := mload(add(sig, 32))
// second 32 bytes.
s := mload(add(sig, 64))
// final byte (first byte of the next 32 bytes).
v := byte(0, mload(add(sig, 96)))
}
return (v, r, s);
}
function recoverSigner(bytes32 message, bytes memory sig)
internal
pure
returns (address)
{
(uint8 v, bytes32 r, bytes32 s) = splitSignature(sig);
return ecrecover(message, v, r, s);
}
/// builds a prefixed hash to mimic the behavior of eth_sign.
function prefixed(bytes32 hash) internal pure returns (bytes32) {
return keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32", hash));
}
}