import { Address } from '@aragon/connect-types' ; import { Contract , providers as ethersProviders , utils as ethersUtils , } from 'ethers' ; import { ErrorInvalid , ErrorUnsufficientBalance } from '../errors' ; import { erc20ABI , forwarderAbi , forwarderFeeAbi } from './abis' ; import { findMethodAbiFragment } from './abi' ; import { TokenData } from '../types' ; import App from '../entities/App' ; import Transaction from '../entities/Transaction' ; export async function createDirectTransaction ( sender , destination , methodAbiFragment , params ) { if ( methodAbiFragment . type === 'fallback' && params . length > 1 ) { throw new Error ( `Could not create transaction to fallback function due to too many parameters: ${ params }` ) ; } return new Transaction ( { from : sender , to : destination , data : new ethersUtils . Interface ( [ methodAbiFragment ] ) . encodeFunctionData ( ethersUtils . FunctionFragment . from ( methodAbiFragment ) , params ) , } ) ; } export async function createDirectTransactionForApp ( sender , app , methodSignature , params ) { if ( ! app ) { throw new ErrorInvalid ( `Could not create transaction due to missing app artifact` ) ; } const destination = app . address ; if ( ! app . abi ) { throw new ErrorInvalid ( `No ABI specified in artifact for ${ destination }` ) ; } const fragment = findMethodAbiFragment ( app . abi , methodSignature ) ; if ( ! fragment ) { throw new ErrorInvalid ( `${ methodSignature } not found on ABI for ${ destination }` ) ; } return createDirectTransaction ( sender , app . address , fragment , params ) ; } export function createForwarderTransactionBuilder ( sender , directTransaction ) { const forwarder = new ethersUtils . Interface ( forwarderAbi ) ; return ( forwarderAddress , script ) : Transaction => new Transaction ( { ... directTransaction , from : sender , to : forwarderAddress , data : forwarder . encodeFunctionData ( 'forward' , [ script ] ) , } ) ; } export async function buildApprovePreTransactions ( transaction , tokenData , provider ) { const { from , to } = transaction ; const { address : tokenAddress , value : tokenValue , spender } = tokenData ; const tokenContract = new Contract ( tokenAddress , erc20ABI , provider ) ; const balance = await tokenContract . balanceOf ( from ) ; const tokenValueBN = BigInt ( tokenValue . toString ( ) ) ; if ( BigInt ( balance ) < tokenValueBN ) { throw new ErrorUnsufficientBalance ( `Balance too low. ${ from } balance of ${ tokenAddress } token is ${ balance } (attempting to send ${ tokenValue })` ) ; } const approveSpender = spender ?? to ; const allowance = await tokenContract . allowance ( from , approveSpender ) ; const allowanceBN = BigInt ( allowance ) ; if ( allowanceBN >= tokenValueBN ) { return [ ] ; } const transactions = [ ] ; const erc20 = new ethersUtils . Interface ( erc20ABI ) ; if ( allowanceBN > BigInt ( 0 ) ) { console . warn ( `${ from } already approved ${ approveSpender } some amount, adding one extra pre-transaction to set it to zero to avoid a failing approval.` ) ; const zeroApprovalPreTransaction = new Transaction ( { from , to : tokenAddress , data : erc20 . encodeFunctionData ( 'approve' , [ approveSpender , '0' ] ) , } ) ; transactions . push ( zeroApprovalPreTransaction ) ; } const requestedApprovalPreTransaction = new Transaction ( { from , to : tokenAddress , data : erc20 . encodeFunctionData ( 'approve' , [ approveSpender , tokenValue ] ) , } ) ; transactions . push ( requestedApprovalPreTransaction ) ; return transactions ; } export async function buildForwardingFeePreTransactions ( forwardingTransaction , provider ) { const { to : forwarderAddress , from } = forwardingTransaction ; const forwarderFee = new Contract ( forwarderAddress , forwarderFeeAbi , provider ) ; const feeDetails = { amount : BigInt ( 0 ) , tokenAddress : '' } ; try { const overrides = { from , } ; const [ tokenAddress , amount ] = await forwarderFee . forwardFee ( overrides ) ; feeDetails . tokenAddress = tokenAddress ; feeDetails . amount = BigInt ( amount ) ; } catch ( err ) { } if ( feeDetails . tokenAddress && feeDetails . amount > BigInt ( 0 ) ) { const tokenData = { address : feeDetails . tokenAddress , spender : forwarderAddress , value : feeDetails . amount . toString ( ) , } ; return buildApprovePreTransactions ( forwardingTransaction , tokenData , provider ) ; } return [ ] ; }