How can I allow ERC20 token payment inside javascript function - reactjs

I'm working on a smart contract that allows users to pay for monthly subscriptions like Netflix, Amazon, etc... with ERC20 tokens.
Everything works well when I tested it. but when I implemented the pay function inside the front-end, so users can interact with the smart contract. the problem that I've faced is that when I clicked on a button to fire off the pay function, I had Metamask asking to confirm the approval, then once the approval is confirmed, I had to confirm the transfer of tokens. **I had to confirm the transfer of tokens. then I got another transaction to confirm the equivalent amount of tokens in ETH.
I made a lot of research but I got stuck, on can I allow the pay function to transfer tokens between two addresses only once.
am I missing something?
PS: blockchain.account & blockchain.smartContract are imported from redux object state.
solidity function
function pay(uint planId) external {
Subscription storage subscription = subscriptions[subscriber][planId];
Plan storage plan = plans[planId];
IERC20 token = IERC20(plan.token);
require(
block.timestamp > subscription.nextPayment,
'not due yet'
);
token.transferFrom(subscriber, plan.merchant, plan.amount);
emit PaymentSent(
msg.sender,
plan.merchant,
plan.amount,
planId,
block.timestamp
);
subscription.nextPayment = subscription.nextPayment + plan.frequency;
}
React function
async function pay() {
showAlert(true, "Happy to see you, Your payment is processing...!");
const data = await blockchain.smartContract.methods.subscriptions(Id).call();
let monthlyPayment = String(data.monthlyPayment);
let tokenAddress = data.amount;
// instance of ERC20 contract
let currency = new web3.eth.Contract(tokenIbi, tokenAddress);
currency.methods.approve("0x1b4eAe2DC7Ca0b68643A26177bfC9c069B3D6E04",
amount).send({from: blockchain.account})
.then(
await currency.methods.transfer("0x1b4eAe2DC7Ca0b68643A26177bfC9c069B3D6E04",
amount).send({from:blockchain.account})
)
blockchain.smartContract.methods.pay(Id).send({from: blockchain.account})
.once("error", (err)=> {
console.log(err);
showAlert(true, "Something went wrong...!");
})
.then((receipt)=> {
console.log(receipt);
showAlert(true, "Congratulations, You monthly payment has been submitted successfully");
dispatch(fetchData(blockchain.account));
})
}
Pay button
<button className="btn"
id="launchApp-btn"
onClick={(e)=> {
e.preventDefault();
pay();
}}>

You are making two transaction on each monthly purchase
Approve the allowance of erc20 tokens
Transfer of erc20 token
Each one has its own gas cost. You may multiply the allowance of erc20 on user's first purchase (Which is not fair and brings the security issue) or continue doing the same scenario Approve&Transfer

Related

How to get Stripe payment intent ID for updating payment intent for PaymentElement

Is there a canonical way to get the PaymentIntent ID in order to update a PaymentIntent? (i.e., to maintain security, etc...).
Specifically, all of the documentation and examples I can find of using Payment Element have you set up a payment intent early and only return a client_secret to the client.
export default async function createPaymentIntentHandler(req, res) {
const stripe = new Stripe(STRIPE_SECRET_KEY));
const body = JSON.parse(req.body);
const paymentIntent = await stripe.paymentIntents.create({
currency: 'USD',
amount: 100,
automatic_payment_methods: {
enabled: true,
},
});
res.status(200).send({clientSecret: paymentIntent.client_secret})
}
In order to update that (e.g., if the user changes the quantity on an order) you need the PaymentIntent ID. Now, the PaymentIntent created on the backend already has it, so you could just return it at the same time as the clientSecret:
res.status(200).send({
clientSecret: paymentIntent.client_secret,
pi_ID: paymentIntent.id
})
and have the client send that when hitting the update payment intent API. I guess the main question is, is there any reason not to do that?
Because there seem to be at least two other ways. First, although this seems like it's probably a bad idea, one could just parse it on the server from the client secret. That is, the client secret takes the form <payment_intent_id>_<secret>, so you could just continue sending only the client secret back to the client on the create request, and then extract the id when the client calls the update api with their client secret.
Second, there exists a client-side Stripe API for querying a payment intent. So, when the client wants to update the payment intent, it could
stripe
.retrievePaymentIntent('{PAYMENT_INTENT_CLIENT_SECRET}')
.then(function(result) {
// call update API
});
This latter seems like unnecessary overhead compared to just sending the ID back as part of the original create request, but maybe there's some reason this is actually preferred?

Firestore Security rules with payment form

I'm creating a raffle website. The user connects his wallet and pays for a raffle ticket. After the blockchain transaction confirmation, I add his raffle ticket in a collection in firestore.
It causes a security issue because if I allow the user to write to the raffle ticket collection in my firebase security rules, he could create his own tickets without paying.
I need tickets to be added to the database only if payment has been successfully made.
I don't know how websites that have means of payment do it. Maybe firebase isn't a good solution ?
My project is in react/typescript.
You say you do the payment over the blockchain and I assume you use solidity as your smart contract language?
Why don't you emit an event in your smart contract?
You then listen for these events on a (seperate) server.
That updates your (firebase) database whenever an event was emitted.
(Untested) Sample Code:
How do you emit events in solidity? (raffle.sol)
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract Raffle {
event PaymentCompletion(address buyer, uint256 amountOfTickets);
function buyTickets() external payable {
emit PaymentCompletion(msg.sender, msg.value)
}
}
How do you listen to these events?
when using web3js:
const contract = new web3.eth.Contract(CONTRACT_ABI, CONTRACT_ADDRESS);
const lastBlock = await web3.eth.getBlockNumber()
// paymentEvents is an array containing the payments of the last 500 blocks.
const paymentEvents = await contract.getPastEvents(
'PaymentCompletion', // change if your looking for a different event
{ fromBlock: latestBlock - 500, toBlock: 'latest' }
);
now iterate through these events and put them into your database. You can also set up a subscription which notifies you whenever a new block was created, so you can check if new events were inside of the current block.
This is what it would look like if you add the first blockchain event to the firebase realtime database.
var db = admin.database();
var ref = db.ref("/payments");
// ...
ref.child("path/to/transaction").set({
buyer: paymentEvents[0].buyer,
amountOfTickets: paymentEvents[0].amountOfTickets,
// put the rest of your data here
}, (err) => {
if (err) {
console.error(err)
}
})
Alternatively (if you don't want to handle the payment on the blockchain) you could also take a look at stripe, it also has a firebase plugin for easy integration. (but I've never tried it out). However, imo using the blockchain for handling the payment would be the cleanest solution. (+ you don't have the handling fees stripe uses).
I hope I could give you some good clues! Firebase should be definitely suitable for this.

Ethers js transferring ERC20 between contracts

I have 2 contracts, first one is openzeppelin ERC20 token and second one is a lottery contract where players can bet on a number.
lottery.sol
pragma solidity ^0.8.4;
import "./Token.sol"; //import ERC20 token
import "#openzeppelin/contracts/token/ERC20/ERC20.sol";
import "#openzeppelin/contracts/access/Ownable.sol";
contract Lottery is Ownable {
Token token;
constructor(Token _token) public {
token = _token;
}
// store information about player's bet
struct PlayersStruct {
uint betAmount;
uint betOnNumber;
}
mapping(address => PlayersStruct) public balances;
function enterLottery(string memory _betOnNumber) public payable {
address player = msg.sender;
uint amount = msg.value;
// transfer token from player's wallet to lottery contract
token.transferFrom(player, address(this), betAmount);
balances[player].betAmount += amount ;
balances[player].betOnNumber = _betOnNumber;
}
And this is how I call it from ReactJS
async function stakeBet() {
const amount = ethers.utils.parseEther("10");
const maxAmount = ethers.utils.parseEther("1000000");
// approve token once so player can save on gas in future
await token.approve(stakingContract.address, maxAmount);
// bet 10 tokens on number 20
await lottery.enterLottery(20, {value: amount,});
}
There are 2 problems with this code:
I have to approve the contract every time, even I'm approving maxAmount higher than betting amount. How do I let Metamask know that contract was already approved?
After approving the ERC20 token, the token for transfer is actually ETH and not ERC20 token defined in Token.sol, how do I specify that ERC20 is the one to transfer?
I'm testing on Kovan test net.
In your stakeBet function, you are calling those functions in order:
await token.approve(stakingContract.address, maxAmount);
// bet 10 tokens on number 20
await lottery.enterLottery(20, {value: amount,});
When you call approve, you are actually updating the allowance mapping. Let the contract know that, you are allowing certain amount for the allowed address. It should be implemented like this:
function approve(address _spender, uint _value)public returns (bool success){
// allowance tells how many tokens can be sent
allowance[msg.sender][_spender]=_value;
// This event must trigger when a successful call is made to the approve function.
emit Approval(msg.sender,_spender,_value);
return true;
}
Token transfer or coin transfer is actually updating the state inside the contracts. With the approve function you updated the allowance. Now
token.transferFrom should be implemented like this:
// my address is allowing your address for this much token
mapping(address=>mapping(address=>uint)) public allowance;
function transferFrom(address _from, address _to, uint256 _value) public returns (bool success){
// check the allowance
require(_value <=allowance[_from][msg.sender]);
// update the balances
balanceOf[_to]+=_value;
balanceOf[_from]-=_value;
allowance[_from][msg.sender]-=_value;
// emitting an event
emit Transfer(_from,_to,_value);
return true;
}
ETH is not ERC20 token. Instead you have to implement WETH token and transfer WETH token. WETH is wrapped version of eth. https://weth.io/
All Ethereum wallet addresses are ERC20 compatible. Moreover, this means that every ERC20 transfer can happen between two Ethereum wallet addresses, or ERC20-compatible addresses. This typically includes all EVM-compatible blockchains. You send weth token and user can swap in metamask:
For the 1st part, you have done what you intended to do, i.e, you have set the allowance to the maximum amount so that the user doesn't have to pay for calling transaction for allow() each time.
The MetaMask is asking your permission for gas fees (in ETH) for sending "betAmount" amount of tokens to the contract.
I am new to ethereum, but I have faced similar circumstances in my projects. So, this is according to my understanding.
Also, for the 2nd problem, as I said earlier, MetaMask is asking for your permission for paying the gas fees (which it takes in ETH, but actual transfer of tokens must also be taking place. Only, some amount of ETH is spent for the "Gas Fees".
I got a real good article discussing the same thing.
Link: https://medium.com/ethex-market/erc20-approve-allow-explained-88d6de921ce9
You can see the demonstration of what I just said. You can see that the gas fees are taken in ETH. Also, if you had not set the allowance earlier to maximum, you would have to pay gas fees two times for a transaction to your contract to happen, first to call approve() for allowance, then to get the "betAmount" amount of tokens transferred to the contract.

stripe paymentRequest with total amount 0$

when I'm trying to make payment with payment request button I need to set 0$ amount when creating payment intent for it, but I can't, cause for creating payment intent stripe requires min 0.50$. I need it cause I'm using it for metered subscription type and don't have to charge client when subscribing. I found solution to refund after successfully subscription, but I don't like it.
I'm creating payment_intent with this api
app.post('/api/client-secret', async (req, res) => {
try {
const { currency, amount } = req.body;
console.log(req.body)
const paymentIntent = await stripe.paymentIntents.create({
amount,
currency: 'usd',
payment_method_types: ['card'],
});
res.json({ clientSecret: paymentIntent.client_secret });
} catch (error) {
console.log("error1", error)
}
});
maybe there is other way or method to create client_secret for stripe.paymentRequest()
The payment_intent object has a field called capture_method. You can set this manual, in which case the payment is not immediately captured by stripe. Pay attention to the parenthesis:
(Not all payment methods support this)
On a different note, if this is a subscription, then why not use the subscription api or even better, sessions. I suggest sessions because it handles all sorts of issues that may arise during a transaction, and all you need to do is wait for the session to complete, and everything will be taken care of.
Also note that the price object (which represents the items you are selling) comes with a field called usage_type which allows you specify that the item is metered, so stripe can handle the billing for you if someone purchases that item.
The stripe api is vast and well documented, so I hope this answer helps you discover a solution quickly.
If you're not charging the user upfront, you should use SetupIntents to save and attach card details to the Customer for charging them in the future — when they’re offline : https://stripe.com/docs/payments/save-and-reuse

How to call Solidity Function to return Ether from Smart Contract?

I have deployed a smart contract on a local truffle project and I am trying to interact with it in a React project using web3. The following solidity function should send Ether what was previously deposited in the contract to a user address on a boolean condition:
function Payout() public{
require( voteEndTime< block.timestamp, "Voting Time is not up. Please come back later" );
Voter storage sender = voters[msg.sender];
if (negativeVotes > positiveVotes){
require(!sender.option, "Wrong Vote. Stake is distributed among winners");
payable(address(msg.sender)).transfer((stakes*sender.amount) / negativeStakes);
}
else if (positiveVotes > negativeVotes){
require(sender.option, "Wrong Vote. Stake is distributed among winners");
payable(address(msg.sender)).transfer((stakes*sender.amount) / positiveStakes);
}
else{
payable(address(msg.sender)).transfer((stakes*sender.amount) / stakes);
}
}
The contract is definitely able to read the user's address using msg.sender because it has worked in the other functions I have. Every other function in the contract is also working fine. I can interact with it and I am able to send Ether to it. The problem occurs when I am trying to return the Ether stored in the contract to an account. I am trying to call my Payout() function using the following web3 call in React on button click:
var response = await BallotContract.methods.Payout().send({ from: account, gas: 310000 })
I have specified a higher gas limit, because the contract runs out of gas if I try to use the gas estimation seen below. The function this call is present in looks like this:
const giveMeMoney = async (e) => {
const web3 = await new Web3(window.ethereum);
await window.ethereum.enable();
var Accounts = await web3.eth.getAccounts()
account = Accounts[0]
console.log(account)
const gas = await BallotContract.methods.Payout().estimateGas();
console.log(gas)
var response = await BallotContract.methods.Payout().send({ from: account, gas: 310000 })
}
I am able to access the function from the frontend and it is returning the correct string if a "require" condition is not met. My problem is that the contract does not return any Ether if the conditions are met and this line:
payable(address(msg.sender)).transfer((stakes*sender.amount) / positiveStakes);
...is accessed. I am getting the following error:
Uncaught (in promise) Error: Returned error: VM Exception while processing transaction: revert
at Object.ErrorResponse (errors.js:30)
at onJsonrpcResult (index.js:162)
at XMLHttpRequest.request.onreadystatechange (index.js:123)
ErrorResponse # errors.js:30
Now I am unsure what could be the problem, because the contract is running perfectly fine if I test it in Remix. Does anybody see the problem or have a workaround for this kind of problem?
The "out of gas" error is caused by the transfer function. This function has a gas limit of 2100; I recommend you use call, you can check how here.
Also the boolean value of the Voter struct defaults to false. So if the negative votes win, but the user didn't vote, they still can try to claim the reward, even if the amount is 0. I recommend you check how to use an enum, it can be very useful.

Resources