UNPKG

@moveflow/widget

Version:

> ⚠️ **This is a testnet version** of the MoveFlow Checkout Widget. It is intended for development and testing purposes only. Do not use for mainnet payments.

148 lines (147 loc) 6.13 kB
// subscriptionUtils.ts import { ethers, parseUnits } from "ethers"; import { abi as SubscriptionAbi } from "../abis/subscription"; import erc20 from "../abis/erc20.json"; import { useState } from "react"; import { convertRateTypeToSeconds } from "../helper/rateInSeconds"; export const useSubscription = (contractAddress, coinAddress, decimal) => { const [state, setState] = useState({ isCreatingSubscription: false, tokensApproved: false, subscriptionCreated: false, networkError: "", }); const resetState = () => { setState({ isCreatingSubscription: false, tokensApproved: false, subscriptionCreated: false, networkError: "", }); }; const handleCreateSubscription = async (payment, chainName) => { setState((prev) => ({ ...prev, isCreatingSubscription: true })); try { const provider = new ethers.BrowserProvider(window.ethereum); const signer = await provider.getSigner(); const networkId = await provider.getNetwork(); const currentBlock = await provider.getBlock("latest"); const currentTime = currentBlock.timestamp; // Network validation const expectedChainId = getChainId(chainName); if (networkId.chainId !== expectedChainId) { throw new Error(`Please switch to ${chainName} network`); } // Convert times to seconds and validate const startTimeSeconds = Math.floor(payment.startTime / 1000); const endTimeSeconds = Math.floor(payment.endTime / 1000); validateTimestamps(startTimeSeconds, endTimeSeconds, currentTime); // Approve tokens (without allowance check) await approveTokens(signer, contractAddress, coinAddress, payment, decimal); // Create subscription await createSubscription(signer, contractAddress, coinAddress, payment, decimal, startTimeSeconds, endTimeSeconds); } catch (error) { handleSubscriptionError(error); } finally { setState((prev) => ({ ...prev, isCreatingSubscription: false })); } }; const getChainId = (chainName) => { switch (chainName) { case "Viction": return BigInt(89); case "Lightlink": return BigInt(1891); case "Pharos": return BigInt(50002); case "Goerli": return BigInt(5); default: throw new Error(`Unsupported chain: ${chainName}`); } }; const validateTimestamps = (start, end, current) => { if (start <= current) { throw new Error("Start time must be in the future"); } if (end <= start) { throw new Error("End time must be after start time"); } if (end - start < 86400) { throw new Error("Subscription duration must be at least 1 day"); } }; const approveTokens = async (signer, contractAddress, coinAddress, payment, decimal) => { const tokenContract = new ethers.Contract(coinAddress, erc20, signer); try { const approvalTx = await tokenContract.approve(contractAddress, ethers.parseUnits("99", 6), { gasLimit: 100000 } // Fixed gas limit for approval ); setState((prev) => ({ ...prev, txHash: approvalTx.hash })); const receipt = await approvalTx.wait(); if (receipt.status !== 1) { throw new Error("Token approval failed"); } } catch (error) { console.error("Approval error:", error); throw new Error(`Token approval failed: ${error.message}`); } }; const createSubscription = async (signer, contractAddress, coinAddress, payment, decimal, startTimeSeconds, endTimeSeconds) => { const contract = new ethers.Contract(contractAddress, SubscriptionAbi, signer); const deposit = BigInt(parseUnits(payment.amountType === "fixed" ? payment.streamRate.toString() : payment.depositAmount.toString(), decimal).toString()); try { const args = [ payment.receiver, deposit, coinAddress, startTimeSeconds, endTimeSeconds, convertRateTypeToSeconds(payment.rateType), payment.amountType === "fixed" ? 0 : 0, ]; // console.log("args", args); // Get gas estimate first const estimatedGas = await contract .getFunction("createSubscription") .estimateGas(...args); // Add 20% buffer const gasLimit = (estimatedGas * 12n) / 10n; // Then execute with the calculated limit const tx = await contract.createSubscription(...args, { gasLimit }); setState((prev) => ({ ...prev, txHash: tx.hash })); const receipt = await tx.wait(); if (receipt.status !== 1) { throw new Error("Transaction failed on-chain"); } setState((prev) => ({ ...prev, subscriptionCreated: true })); return receipt; } catch (error) { console.error("Create subscription error:", error); throw error; } }; const handleSubscriptionError = (error) => { console.error("Subscription error:", error); let errorMessage = "Failed to create subscription"; if (error.info?.error?.message) { errorMessage += `: ${error.info.error.message}`; } else if (error.message) { errorMessage += `: ${error.message}`; } setState((prev) => ({ ...prev, networkError: errorMessage })); setTimeout(() => setState((prev) => ({ ...prev, networkError: "" })), 5000); throw error; }; return { state, resetState, handleCreateSubscription, }; };