import { BigNumber } from 'bignumber.js'; import { IRpcWallet, TransactionReceipt, Wallet } from "../wallet"; import * as Contracts from "../contracts"; import * as Utils from "../utils"; import { registerSendTxEvents } from '../utils'; import { ITokenObject } from '../types'; const approveERC20Max = async (token: ITokenObject, spenderAddress: string, callback?: any, confirmationCallback?: any) => { let wallet: any = Wallet.getInstance(); let amount = new BigNumber(2).pow(256).minus(1); let erc20 = new Contracts.ERC20(wallet, token.address); registerSendTxEvents({ transactionHash: callback, confirmation: confirmationCallback }) let receipt = await erc20.approve({ spender: spenderAddress, amount }); return receipt; } export const getERC20Allowance = async (wallet: IRpcWallet, token: ITokenObject, spenderAddress: string) => { if (!token?.address || token.address === Utils.nullAddress) return null; let erc20 = new Contracts.ERC20(wallet, token.address); let allowance = await erc20.allowance({ owner: wallet.account.address, spender: spenderAddress }); return Utils.fromDecimals(allowance, token.decimals || 18); } export interface IERC20ApprovalEventOptions { sender: any; payAction: () => Promise; onToBeApproved: (token: ITokenObject, data?: any) => Promise; onToBePaid: (token: ITokenObject, data?: any) => Promise; onApproving: (token: ITokenObject, receipt?: string, data?: any) => Promise; onApproved: (token: ITokenObject, data?: any, receipt?: TransactionReceipt) => Promise; onPaying: (receipt?: string, data?: any) => Promise; onPaid: (data?: any, receipt?: TransactionReceipt) => Promise; onApprovingError: (token: ITokenObject, err: Error) => Promise; onPayingError: (err: Error) => Promise; } export interface IERC20ApprovalOptions extends IERC20ApprovalEventOptions { spenderAddress: string; } export interface IERC20ApprovalAction { doApproveAction: (token: ITokenObject, inputAmount: string, data?: any) => Promise; doPayAction: (data?: any) => Promise; checkAllowance: (token: ITokenObject, inputAmount: string, data?: any) => Promise; } export class ERC20ApprovalModel { private wallet: IRpcWallet; private options: IERC20ApprovalOptions = { sender: null, spenderAddress: '', payAction: async () => {}, onToBeApproved: async (token: ITokenObject, data?: any) => {}, onToBePaid: async (token: ITokenObject, data?: any) => {}, onApproving: async (token: ITokenObject, receipt?: string, data?: any) => {}, onApproved: async (token: ITokenObject, data?: any, receipt?: TransactionReceipt) => {}, onPaying: async (receipt?: string, data?: any) => {}, onPaid: async (data?: any, receipt?: TransactionReceipt) => {}, onApprovingError: async (token: ITokenObject, err: Error) => {}, onPayingError: async (err: Error) => {} }; constructor(wallet: IRpcWallet, options: IERC20ApprovalOptions) { this.wallet = wallet; this.options = options; } set spenderAddress(value: string) { this.options.spenderAddress = value } private checkAllowance = async (token: ITokenObject, inputAmount: string, data?: any) => { let allowance = await getERC20Allowance(this.wallet, token, this.options.spenderAddress); if (!allowance) { await this.options.onToBePaid.bind(this.options.sender)(token, data); } else if (new BigNumber(inputAmount).gt(allowance)) { await this.options.onToBeApproved.bind(this.options.sender)(token, data); } else { await this.options.onToBePaid.bind(this.options.sender)(token, data); } } private doApproveAction = async (token: ITokenObject, inputAmount: string, data?: any) => { const txHashCallback = async (err: Error, receipt?: string) => { if (err) { await this.options.onApprovingError.bind(this.options.sender)(token, err); } else { await this.options.onApproving.bind(this.options.sender)(token, receipt, data); } } const confirmationCallback = async (receipt: TransactionReceipt) => { await this.options.onApproved.bind(this.options.sender)(token, data, receipt); await this.checkAllowance(token, inputAmount, data); } approveERC20Max(token, this.options.spenderAddress, txHashCallback, confirmationCallback) } private doPayAction = async (data?: any) => { const txHashCallback = async (err: Error, receipt?: string) => { if (err) { await this.options.onPayingError.bind(this.options.sender)(err); } else { await this.options.onPaying.bind(this.options.sender)(receipt, data); } } const confirmationCallback = async (receipt: TransactionReceipt) => { await this.options.onPaid.bind(this.options.sender)(data, receipt); } registerSendTxEvents({ transactionHash: txHashCallback, confirmation: confirmationCallback }) await this.options.payAction.bind(this.options.sender)(); } public getAction = (): IERC20ApprovalAction => { return { doApproveAction: this.doApproveAction, doPayAction: this.doPayAction, checkAllowance: this.checkAllowance } } }