import Web3 from 'web3' ; import RewardSafeDelegateABI from '../../contracts/abi/v0.8.7/reward-safe-delegate-implementation' ; import RewardManagerABI from '../../contracts/abi/v0.8.7/reward-manager' ; import { Contract , ContractOptions } from 'web3-eth-contract' ; import { getAddress } from '../../contracts/addresses' ; import { AbiItem , randomHex , toChecksumAddress , fromWei , toWei } from 'web3-utils' ; import { isTransactionHash , TransactionOptions , waitForSubgraphIndexWithTxnReceipt } from '../utils/general-utils' ; import { getSDK } from '../version-resolver' ; import { TransactionReceipt } from 'web3-core' ; import { EventABI , getParamsFromEvent , SendPayload , getSendPayload , getNextNonceFromEstimate , executeSendWithRateLock , GnosisExecTx , executeSend , gasEstimate , executeTransaction , Operation , } from '../utils/safe-utils' ; import { Signature , signPrepaidCardSendTx } from '../utils/signing-utils' ; import BN from 'bn.js' ; import ERC20ABI from '../../contracts/abi/erc-20' ; import { signRewardSafe , createEIP1271VerifyingData } from '../utils/signing-utils' ; import { ZERO_ADDRESS } from '../constants' ; import { WithSymbol , RewardTokenBalance } from '@cardstack/cardpay-sdk' ; import { query } from '../utils/graphql' ; export interface RewardProgramInfo { rewardProgramId : string ; rewardProgramAdmin : string ; locked : boolean ; rule : string ; tokenBalances : WithSymbol < RewardTokenBalance > [ ] ; } const rewardProgramQuery = `
  query {
    rewardPrograms {
      id
    }
    _meta {
       block {
         number
       }
     }
  }
` ; export default class RewardManager { private rewardManager : Contract | undefined ; constructor ( private layer2Web3 ) { } async registerRewardProgram ( txnHash ) ; async registerRewardProgram ( prepaidCardAddress , admin , txnOptions ? , contractOptions ? ) ; async registerRewardProgram ( prepaidCardAddressOrTxnHash , admin ? , txnOptions ? , contractOptions ? ) { let rewardManager = await getSDK ( 'RewardManager' , this . layer2Web3 ) ; let rewardProgramId = await rewardManager . newRewardProgramId ( ) ; if ( isTransactionHash ( prepaidCardAddressOrTxnHash ) ) { let txnHash = prepaidCardAddressOrTxnHash ; return { rewardProgramId , txReceipt : await waitForSubgraphIndexWithTxnReceipt ( this . layer2Web3 , txnHash ) , } ; } if ( ! prepaidCardAddressOrTxnHash ) { throw new Error ( 'prepaidCardAddress is required' ) ; } let prepaidCardAddress = prepaidCardAddressOrTxnHash ; let { nonce , onNonce , onTxnHash } = txnOptions ?? { } ; let from = contractOptions ?. from ?? ( await this . layer2Web3 . eth . getAccounts ( ) ) [ 0 ] ; let rewardProgramRegistrationFees = await rewardManager . getRewardProgramRegistrationFees ( ) ; let prepaidCardAPI = await getSDK ( 'PrepaidCard' , this . layer2Web3 ) ; await prepaidCardAPI . convertFromSpendForPrepaidCard ( prepaidCardAddress , rewardProgramRegistrationFees , ( issuingToken , balanceAmount , requiredTokenAmount , symbol ) => new Error ( `Prepaid card does not have enough balance to register reward program. The issuing token ${ issuingToken } balance of prepaid card ${ prepaidCardAddress } is ${ fromWei ( balanceAmount ) } ${ symbol }, payment amount in issuing token is ${ fromWei ( requiredTokenAmount ) } ${ symbol }` ) ) ; let prepaidCardMgr = await prepaidCardAPI . getPrepaidCardMgr ( ) ; let rewardProgramAdmin = admin ?? ( await prepaidCardMgr . methods . getPrepaidCardOwner ( prepaidCardAddress ) . call ( ) ) ; let gnosisResult = await executeSendWithRateLock ( this . layer2Web3 , prepaidCardAddress , async ( rateLock ) => { let payload = await this . getRegisterRewardProgramPayload ( prepaidCardAddress , rewardProgramAdmin , rewardProgramId , rewardProgramRegistrationFees , rateLock ) ; if ( nonce == null ) { nonce = getNextNonceFromEstimate ( payload ) ; if ( typeof onNonce === 'function' ) { onNonce ( nonce ) ; } } return await this . executeRegisterRewardProgram ( prepaidCardAddress , rewardProgramAdmin , rewardProgramId , rewardProgramRegistrationFees , rateLock , payload , await signPrepaidCardSendTx ( this . layer2Web3 , prepaidCardAddress , payload , nonce , from ) , nonce ) ; } ) ; if ( ! gnosisResult ) { throw new Error ( `Unable to obtain a gnosis transaction result for register reward program from prepaid card ${ prepaidCardAddressOrTxnHash } to merchant safe ${ prepaidCardAddress } for ${ rewardProgramRegistrationFees } SPEND` ) ; } let txnHash = gnosisResult . ethereumTx . txHash ; if ( typeof onTxnHash === 'function' ) { await onTxnHash ( txnHash ) ; } return { rewardProgramId , txReceipt : await waitForSubgraphIndexWithTxnReceipt ( this . layer2Web3 , txnHash ) , } ; } async registerRewardee ( txnHash ) ; async registerRewardee ( prepaidCardAddress , rewardProgramId , txnOptions ? , contractOptions ? ) ; async registerRewardee ( prepaidCardAddressOrTxnHash , rewardProgramId ? , txnOptions ? , contractOptions ? ) { if ( isTransactionHash ( prepaidCardAddressOrTxnHash ) ) { let txnHash = prepaidCardAddressOrTxnHash ; return { rewardSafe : await this . getRewardSafeFromTxn ( txnHash ) , txReceipt : await waitForSubgraphIndexWithTxnReceipt ( this . layer2Web3 , txnHash ) , } ; } if ( ! prepaidCardAddressOrTxnHash ) { throw new Error ( 'prepaidCardAddress is required' ) ; } if ( ! rewardProgramId ) { throw new Error ( 'rewardProgramId is required' ) ; } let prepaidCardAddress = prepaidCardAddressOrTxnHash ; let { nonce , onNonce , onTxnHash } = txnOptions ?? { } ; let from = contractOptions ?. from ?? ( await this . layer2Web3 . eth . getAccounts ( ) ) [ 0 ] ; let gnosisResult = await executeSendWithRateLock ( this . layer2Web3 , prepaidCardAddress , async ( rateLock ) => { let payload = await this . getRegisterRewardeePayload ( prepaidCardAddress , rewardProgramId , rateLock ) ; if ( nonce == null ) { nonce = getNextNonceFromEstimate ( payload ) ; if ( typeof onNonce === 'function' ) { onNonce ( nonce ) ; } } return await this . executeRegisterRewardee ( prepaidCardAddress , rewardProgramId , rateLock , payload , await signPrepaidCardSendTx ( this . layer2Web3 , prepaidCardAddress , payload , nonce , from ) , nonce ) ; } ) ; if ( ! gnosisResult ) { throw new Error ( `Unable to obtain a gnosis transaction result for register rewardee payment from prepaid card ${ prepaidCardAddress }` ) ; } let txnHash = gnosisResult . ethereumTx . txHash ; if ( typeof onTxnHash === 'function' ) { await onTxnHash ( txnHash ) ; } return { rewardSafe : await this . getRewardSafeFromTxn ( gnosisResult . ethereumTx . txHash ) , txReceipt : await waitForSubgraphIndexWithTxnReceipt ( this . layer2Web3 , txnHash ) , } ; } async lockRewardProgram ( txnHash ) ; async lockRewardProgram ( prepaidCardAddress , rewardProgramId , txnOptions ? , contractOptions ? ) ; async lockRewardProgram ( prepaidCardAddressOrTxnHash , rewardProgramId ? , txnOptions ? , contractOptions ? ) { if ( isTransactionHash ( prepaidCardAddressOrTxnHash ) ) { let txnHash = prepaidCardAddressOrTxnHash ; return await waitForSubgraphIndexWithTxnReceipt ( this . layer2Web3 , txnHash ) ; } if ( ! prepaidCardAddressOrTxnHash ) { throw new Error ( 'prepaidCardAddress is required' ) ; } if ( ! rewardProgramId ) { throw new Error ( 'rewardProgramId is required' ) ; } let prepaidCardAddress = prepaidCardAddressOrTxnHash ; let { nonce , onNonce , onTxnHash } = txnOptions ?? { } ; let from = contractOptions ?. from ?? ( await this . layer2Web3 . eth . getAccounts ( ) ) [ 0 ] ; await this . checkPrepaidCardOwnerIsAdmin ( rewardProgramId , prepaidCardAddress ) ; let gnosisResult = await executeSendWithRateLock ( this . layer2Web3 , prepaidCardAddress , async ( rateLock ) => { let payload = await this . getLockRewardProgramPayload ( prepaidCardAddress , rewardProgramId , rateLock ) ; if ( nonce == null ) { nonce = getNextNonceFromEstimate ( payload ) ; if ( typeof onNonce === 'function' ) { onNonce ( nonce ) ; } } return await this . executeLockRewardProgram ( prepaidCardAddress , rewardProgramId , rateLock , payload , await signPrepaidCardSendTx ( this . layer2Web3 , prepaidCardAddress , payload , nonce , from ) , nonce ) ; } ) ; if ( ! gnosisResult ) { throw new Error ( `Unable to obtain a gnosis transaction result for lock reward program from prepaid card ${ prepaidCardAddress }` ) ; } let txnHash = gnosisResult . ethereumTx . txHash ; if ( typeof onTxnHash === 'function' ) { await onTxnHash ( txnHash ) ; } return await waitForSubgraphIndexWithTxnReceipt ( this . layer2Web3 , txnHash ) ; } async updateRewardProgramAdmin ( txnHash ) ; async updateRewardProgramAdmin ( prepaidCardAddress , rewardProgramId , newAdmin , txnOptions ? , contractOptions ? ) ; async updateRewardProgramAdmin ( prepaidCardAddressOrTxnHash , rewardProgramId ? , newAdmin ? , txnOptions ? , contractOptions ? ) { if ( isTransactionHash ( prepaidCardAddressOrTxnHash ) ) { let txnHash = prepaidCardAddressOrTxnHash ; return await waitForSubgraphIndexWithTxnReceipt ( this . layer2Web3 , txnHash ) ; } if ( ! prepaidCardAddressOrTxnHash ) { throw new Error ( 'prepaidCardAddress is required' ) ; } if ( ! rewardProgramId ) { throw new Error ( 'rewardProgramId is required' ) ; } if ( ! newAdmin ) { throw new Error ( 'newAdmin is required' ) ; } let prepaidCardAddress = prepaidCardAddressOrTxnHash ; let { nonce , onNonce , onTxnHash } = txnOptions ?? { } ; let from = contractOptions ?. from ?? ( await this . layer2Web3 . eth . getAccounts ( ) ) [ 0 ] ; await this . checkPrepaidCardOwnerIsAdmin ( rewardProgramId , prepaidCardAddress ) ; let gnosisResult = await executeSendWithRateLock ( this . layer2Web3 , prepaidCardAddress , async ( rateLock ) => { let payload = await this . getUpdateRewardProgramAdminPayload ( prepaidCardAddress , rewardProgramId , newAdmin , rateLock ) ; if ( nonce == null ) { nonce = getNextNonceFromEstimate ( payload ) ; if ( typeof onNonce === 'function' ) { onNonce ( nonce ) ; } } return await this . executeUpdateRewardProgramAdmin ( prepaidCardAddress , rewardProgramId , newAdmin , rateLock , payload , await signPrepaidCardSendTx ( this . layer2Web3 , prepaidCardAddress , payload , nonce , from ) , nonce ) ; } ) ; if ( ! gnosisResult ) { throw new Error ( `Unable to obtain a gnosis transaction result for update reward program admin from prepaid card ${ prepaidCardAddress }` ) ; } let txnHash = gnosisResult . ethereumTx . txHash ; if ( typeof onTxnHash === 'function' ) { await onTxnHash ( txnHash ) ; } return await waitForSubgraphIndexWithTxnReceipt ( this . layer2Web3 , txnHash ) ; } async addRewardRule ( txnHash ) ; async addRewardRule ( prepaidCardAddress , rewardProgramId , blob , txnOptions ? , contractOptions ? ) ; async addRewardRule ( prepaidCardAddressOrTxnHash , rewardProgramId ? , blob ? , txnOptions ? , contractOptions ? ) { if ( isTransactionHash ( prepaidCardAddressOrTxnHash ) ) { let txnHash = prepaidCardAddressOrTxnHash ; return await waitForSubgraphIndexWithTxnReceipt ( this . layer2Web3 , txnHash ) ; } if ( ! prepaidCardAddressOrTxnHash ) { throw new Error ( 'prepaidCardAddress is required' ) ; } if ( ! rewardProgramId ) { throw new Error ( 'rewardProgramId is required' ) ; } if ( ! blob ) { throw new Error ( 'blob is required' ) ; } let prepaidCardAddress = prepaidCardAddressOrTxnHash ; let { nonce , onNonce , onTxnHash } = txnOptions ?? { } ; let from = contractOptions ?. from ?? ( await this . layer2Web3 . eth . getAccounts ( ) ) [ 0 ] ; await this . checkPrepaidCardOwnerIsAdmin ( rewardProgramId , prepaidCardAddress ) ; let gnosisResult = await executeSendWithRateLock ( this . layer2Web3 , prepaidCardAddress , async ( rateLock ) => { let payload = await this . getAddRewardRulePayload ( prepaidCardAddress , rewardProgramId , blob , rateLock ) ; if ( nonce == null ) { nonce = getNextNonceFromEstimate ( payload ) ; if ( typeof onNonce === 'function' ) { onNonce ( nonce ) ; } } return await this . executeAddRewardRule ( prepaidCardAddress , rewardProgramId , blob , rateLock , payload , await signPrepaidCardSendTx ( this . layer2Web3 , prepaidCardAddress , payload , nonce , from ) , nonce ) ; } ) ; if ( ! gnosisResult ) { throw new Error ( `Unable to obtain a gnosis transaction result for lock reward program from prepaid card ${ prepaidCardAddress }` ) ; } let txnHash = gnosisResult . ethereumTx . txHash ; if ( typeof onTxnHash === 'function' ) { await onTxnHash ( txnHash ) ; } return await waitForSubgraphIndexWithTxnReceipt ( this . layer2Web3 , txnHash ) ; } async withdraw ( txnHash ) ; async withdraw ( safeAddress , to , tokenAddress , amount , txnOptions ? , contractOptions ? ) ; async withdraw ( safeAddressOrTxnHash , to ? , tokenAddress ? , amount ? , txnOptions ? , contractOptions ? ) { if ( isTransactionHash ( safeAddressOrTxnHash ) ) { let txnHash = safeAddressOrTxnHash ; return await waitForSubgraphIndexWithTxnReceipt ( this . layer2Web3 , txnHash ) ; } if ( ! safeAddressOrTxnHash ) { throw new Error ( 'safeAddress is required' ) ; } if ( ! tokenAddress ) { throw new Error ( 'tokenAddress must be provided' ) ; } if ( ! amount ) { throw new Error ( 'amount must be provided' ) ; } if ( ! to ) { throw new Error ( 'to must be provided' ) ; } let safeAddress = safeAddressOrTxnHash ; let rewardManager = await getSDK ( 'RewardManager' , this . layer2Web3 ) ; let rewardManagerAddress = await this . address ( ) ; let from = contractOptions ?. from ?? ( await this . layer2Web3 . eth . getAccounts ( ) ) [ 0 ] ; let rewardSafeOwner = await rewardManager . getRewardSafeOwner ( safeAddress ) ; let token = new this . layer2Web3 . eth . Contract ( ERC20ABI as AbiItem [ ] , tokenAddress ) ; let safeBalance = new BN ( await token . methods . balanceOf ( safeAddress ) . call ( ) ) ; let rewardSafeDelegateAddress = await this . getRewardSafeDelegateAddress ( ) ; let rewardSafeDelegate = new this . layer2Web3 . eth . Contract ( RewardSafeDelegateABI as AbiItem [ ] , rewardSafeDelegateAddress ) ; if ( ! ( rewardSafeOwner == from ) ) { throw new Error ( `Reward safe owner is NOT the signer of transaction.
The owner of reward safe ${ safeAddress } is ${ rewardSafeOwner }, but the signer is ${ from }` ) ; } let weiAmount = amount ? new BN ( toWei ( amount ) ) : safeBalance ; if ( weiAmount . gt ( safeBalance ) ) { throw new Error ( `Insufficient funds inside reward safe. safeBalance = ${ fromWei ( safeBalance ) } and amount = ${ amount }` ) ; } let withdraw = await rewardSafeDelegate . methods . withdraw ( rewardManagerAddress , tokenAddress , to , weiAmount ) ; let withdrawPayload = withdraw . encodeABI ( ) ; let estimate = await gasEstimate ( this . layer2Web3 , safeAddress , rewardSafeDelegateAddress , '0' , withdrawPayload , Operation . DELEGATECALL , tokenAddress ) ; let { nonce , onNonce , onTxnHash } = txnOptions ?? { } ; if ( nonce == null ) { nonce = getNextNonceFromEstimate ( estimate ) ; if ( typeof onNonce === 'function' ) { onNonce ( nonce ) ; } } let gasCost = new BN ( estimate . safeTxGas ) . add ( new BN ( estimate . baseGas ) ) . mul ( new BN ( estimate . gasPrice ) ) ; if ( safeBalance . lt ( gasCost . add ( weiAmount ) ) ) { throw new Error ( `Reward safe does not have enough to pay for gas when claiming rewards. The reward safe ${ safeAddress } balance for token ${ tokenAddress } is ${ fromWei ( safeBalance ) }, amount being claimed is ${ amount }, the gas cost is ${ fromWei ( gasCost ) }` ) ; } let fullSignature = await signRewardSafe ( this . layer2Web3 , rewardSafeDelegateAddress , 0 , withdrawPayload , Operation . DELEGATECALL , estimate , tokenAddress , ZERO_ADDRESS , nonce , rewardSafeOwner , safeAddress , rewardManagerAddress ) ; let eip1271Data = createEIP1271VerifyingData ( this . layer2Web3 , rewardSafeDelegateAddress , '0' , withdrawPayload , Operation . DELEGATECALL . toString ( ) , estimate . safeTxGas , estimate . baseGas , estimate . gasPrice , tokenAddress , ZERO_ADDRESS , nonce . toString ( ) ) ; let gnosisTxn = await executeTransaction ( this . layer2Web3 , safeAddress , rewardSafeDelegateAddress , withdrawPayload , Operation . DELEGATECALL , estimate , nonce , fullSignature , eip1271Data ) ; if ( typeof onTxnHash === 'function' ) { await onTxnHash ( gnosisTxn . ethereumTx . txHash ) ; } return await waitForSubgraphIndexWithTxnReceipt ( this . layer2Web3 , gnosisTxn . ethereumTx . txHash ) ; } async transfer ( txnHash ) ; async transfer ( safeAddress , newOwner , txnOptions ? , contractOptions ? ) ; async transfer ( safeAddressOrTxnHash , newOwner ? , txnOptions ? , contractOptions ? ) { if ( isTransactionHash ( safeAddressOrTxnHash ) ) { let txnHash = safeAddressOrTxnHash ; return await waitForSubgraphIndexWithTxnReceipt ( this . layer2Web3 , txnHash ) ; } if ( ! safeAddressOrTxnHash ) { throw new Error ( 'safeAddress is required' ) ; } if ( ! newOwner ) { throw new Error ( 'newOwner must be provided' ) ; } let safeAddress = safeAddressOrTxnHash ; let rewardManager = await getSDK ( 'RewardManager' , this . layer2Web3 ) ; let rewardManagerAddress = await this . address ( ) ; let from = contractOptions ?. from ?? ( await this . layer2Web3 . eth . getAccounts ( ) ) [ 0 ] ; let rewardSafeOwner = await rewardManager . getRewardSafeOwner ( safeAddress ) ; let rewardProgramId = await this . getRewardProgram ( '0x853fD3376b6f0b2b839Bd841FbdC6C1f93B3BFBD' ) ; if ( rewardProgramId == ZERO_ADDRESS ) { throw new Error ( 'reward safe does not does not have reward program' ) ; } if ( ! ( await this . isValidRewardSafe ( safeAddress , rewardProgramId ) ) ) { throw new Error ( 'reward safe is not valid' ) ; } let gasTokenAddress = await getAddress ( 'cardCpxd' , this . layer2Web3 ) ; let gasToken = new this . layer2Web3 . eth . Contract ( ERC20ABI as AbiItem [ ] , gasTokenAddress ) ; let safeBalance = new BN ( await gasToken . methods . balanceOf ( safeAddress ) . call ( ) ) ; let rewardSafeDelegateAddress = await this . getRewardSafeDelegateAddress ( ) ; let rewardSafeDelegate = new this . layer2Web3 . eth . Contract ( RewardSafeDelegateABI as AbiItem [ ] , rewardSafeDelegateAddress ) ; if ( ! ( rewardSafeOwner == from ) ) { throw new Error ( `Reward safe owner is NOT the signer of transaction.
The owner of reward safe ${ safeAddress } is ${ rewardSafeOwner }, but the signer is ${ from }` ) ; } let payload = await rewardSafeDelegate . methods . swapOwner ( rewardManagerAddress , rewardManagerAddress , rewardSafeOwner , newOwner ) . encodeABI ( ) ; let estimate = await gasEstimate ( this . layer2Web3 , safeAddress , rewardSafeDelegateAddress , '0' , payload , Operation . DELEGATECALL , gasTokenAddress ) ; let { nonce , onNonce , onTxnHash } = txnOptions ?? { } ; if ( nonce == null ) { nonce = getNextNonceFromEstimate ( estimate ) ; if ( typeof onNonce === 'function' ) { onNonce ( nonce ) ; } } let gasCost = new BN ( estimate . safeTxGas ) . add ( new BN ( estimate . baseGas ) ) . mul ( new BN ( estimate . gasPrice ) ) ; if ( safeBalance . lt ( gasCost ) ) { throw new Error ( `Reward safe does not have enough to pay for gas when claiming rewards. The reward safe ${ safeAddress } balance for token ${ gasTokenAddress } is ${ fromWei ( safeBalance ) }, the gas cost is ${ fromWei ( gasCost ) }` ) ; } let fullSignature = await signRewardSafe ( this . layer2Web3 , rewardSafeDelegateAddress , 0 , payload , Operation . DELEGATECALL , estimate , gasTokenAddress , ZERO_ADDRESS , nonce , rewardSafeOwner , safeAddress , rewardManagerAddress ) ; let eip1271Data = createEIP1271VerifyingData ( this . layer2Web3 , rewardSafeDelegateAddress , '0' , payload , Operation . DELEGATECALL . toString ( ) , estimate . safeTxGas , estimate . baseGas , estimate . gasPrice , gasTokenAddress , ZERO_ADDRESS , nonce . toString ( ) ) ; let gnosisTxn = await executeTransaction ( this . layer2Web3 , safeAddress , rewardSafeDelegateAddress , payload , Operation . DELEGATECALL , estimate , nonce , fullSignature , eip1271Data ) ; if ( typeof onTxnHash === 'function' ) { await onTxnHash ( gnosisTxn . ethereumTx . txHash ) ; } return await waitForSubgraphIndexWithTxnReceipt ( this . layer2Web3 , gnosisTxn . ethereumTx . txHash ) ; } private async getRegisterRewardProgramPayload ( prepaidCardAddress , admin , rewardProgramId , spendAmount , rate ) { return getSendPayload ( this . layer2Web3 , prepaidCardAddress , spendAmount , rate , 'registerRewardProgram' , this . layer2Web3 . eth . abi . encodeParameters ( [ 'address' , 'address' ] , [ admin , rewardProgramId ] ) ) ; } private async getRegisterRewardeePayload ( prepaidCardAddress , rewardProgramId , rate ) { return getSendPayload ( this . layer2Web3 , prepaidCardAddress , 0 , rate , 'registerRewardee' , this . layer2Web3 . eth . abi . encodeParameters ( [ 'address' ] , [ rewardProgramId ] ) ) ; } private async getLockRewardProgramPayload ( prepaidCardAddress , rewardProgramId , rate ) { return getSendPayload ( this . layer2Web3 , prepaidCardAddress , 0 , rate , 'lockRewardProgram' , this . layer2Web3 . eth . abi . encodeParameters ( [ 'address' ] , [ rewardProgramId ] ) ) ; } private async getUpdateRewardProgramAdminPayload ( prepaidCardAddress , rewardProgramId , newAdmin , rate ) { return getSendPayload ( this . layer2Web3 , prepaidCardAddress , 0 , rate , 'updateRewardProgramAdmin' , this . layer2Web3 . eth . abi . encodeParameters ( [ 'address' , 'address' ] , [ rewardProgramId , newAdmin ] ) ) ; } private async getAddRewardRulePayload ( prepaidCardAddress , rewardProgramId , blob , rate ) { return getSendPayload ( this . layer2Web3 , prepaidCardAddress , 0 , rate , 'addRewardRule' , this . layer2Web3 . eth . abi . encodeParameters ( [ 'address' , 'bytes' ] , [ rewardProgramId , blob ] ) ) ; } private async executeRegisterRewardProgram ( prepaidCardAddress , admin , rewardProgramId , spendAmount , rate , payload , signatures , nonce ) { return await executeSend ( this . layer2Web3 , prepaidCardAddress , spendAmount , rate , payload , 'registerRewardProgram' , this . layer2Web3 . eth . abi . encodeParameters ( [ 'address' , 'address' ] , [ admin , rewardProgramId ] ) , signatures , nonce ) ; } private async executeRegisterRewardee ( prepaidCardAddress , rewardProgramId , rate , payload , signatures , nonce ) { return await executeSend ( this . layer2Web3 , prepaidCardAddress , 0 , rate , payload , 'registerRewardee' , this . layer2Web3 . eth . abi . encodeParameters ( [ 'address' ] , [ rewardProgramId ] ) , signatures , nonce ) ; } private async executeLockRewardProgram ( prepaidCardAddress , rewardProgramId , rate , payload , signatures , nonce ) { return await executeSend ( this . layer2Web3 , prepaidCardAddress , 0 , rate , payload , 'lockRewardProgram' , this . layer2Web3 . eth . abi . encodeParameters ( [ 'address' ] , [ rewardProgramId ] ) , signatures , nonce ) ; } private async executeUpdateRewardProgramAdmin ( prepaidCardAddress , rewardProgramId , newAdmin , rate , payload , signatures , nonce ) { return await executeSend ( this . layer2Web3 , prepaidCardAddress , 0 , rate , payload , 'updateRewardProgramAdmin' , this . layer2Web3 . eth . abi . encodeParameters ( [ 'address' , 'address' ] , [ rewardProgramId , newAdmin ] ) , signatures , nonce ) ; } private async executeAddRewardRule ( prepaidCardAddress , rewardProgramId , blob , rate , payload , signatures , nonce ) { return await executeSend ( this . layer2Web3 , prepaidCardAddress , 0 , rate , payload , 'addRewardRule' , this . layer2Web3 . eth . abi . encodeParameters ( [ 'address' , 'bytes' ] , [ rewardProgramId , blob ] ) , signatures , nonce ) ; } async getRewardProgramInfo ( rewardProgramId ) { if ( ! ( await this . isRewardProgram ( rewardProgramId ) ) ) { throw new Error ( 'Not an existing reward program' ) ; } const locked = await this . isLocked ( rewardProgramId ) ; const rewardProgramAdmin = await this . getRewardProgramAdmin ( rewardProgramId ) ; const rule = await this . getRewardRule ( rewardProgramId ) ; let rewardPool = await getSDK ( 'RewardPool' , this . layer2Web3 ) ; const tokenBalances = await rewardPool . balances ( rewardProgramId ) ; return { rewardProgramId , rewardProgramAdmin , locked , rule , tokenBalances , } ; } async getRewardProgramsInfo ( ) { let rewardProgramIds = await this . getRewardPrograms ( ) ; let promises = [ ] ; rewardProgramIds . map ( ( rewardProgramId ) => { promises . push ( this . getRewardProgramInfo ( rewardProgramId ) ) ; } ) ; return await Promise . all ( promises ) ; } async getRewardPrograms ( ) { let { data : { rewardPrograms } , } = await query ( this . layer2Web3 , rewardProgramQuery ) ; return rewardPrograms . reduce ( ( accum , o ) => { return [ ... accum , o . id ] ; } , [ ] ) ; } private async getRewardSafeFromTxn ( txnHash ) { let rewardMgrAddress = await getAddress ( 'rewardManager' , this . layer2Web3 ) ; let txnReceipt = await waitForSubgraphIndexWithTxnReceipt ( this . layer2Web3 , txnHash ) ; return getParamsFromEvent ( this . layer2Web3 , txnReceipt , this . rewardeeRegisteredABI ( ) , rewardMgrAddress ) [ 0 ] . rewardSafe ; } async getRewardProgramRegistrationFees ( ) { return Number ( await ( await this . getRewardManager ( ) ) . methods . rewardProgramRegistrationFeeInSPEND ( ) . call ( ) ) ; } async isRewardProgram ( rewardProgramId ) { return ( await this . getRewardManager ( ) ) . methods . isRewardProgram ( rewardProgramId ) . call ( ) ; } async isValidRewardSafe ( rewardSafe , rewardProgramId ) { return ( await this . getRewardManager ( ) ) . methods . isValidRewardSafe ( rewardSafe , rewardProgramId ) . call ( ) ; } async isLocked ( rewardProgramId ) { return ( await this . getRewardManager ( ) ) . methods . rewardProgramLocked ( rewardProgramId ) . call ( ) ; } async newRewardProgramId ( ) { let rewardProgramIdExists ; let rewardProgramId ; do { rewardProgramId = toChecksumAddress ( randomHex ( 20 ) ) ; rewardProgramIdExists = await this . isRewardProgram ( rewardProgramId ) ; } while ( rewardProgramIdExists ) ; return rewardProgramId ; } async getRewardSafeOwner ( rewardSafeAddress ) { return await ( await this . getRewardManager ( ) ) . methods . getRewardSafeOwner ( rewardSafeAddress ) . call ( ) ; } async getRewardProgramAdmin ( rewardProgramId ) { return await ( await this . getRewardManager ( ) ) . methods . rewardProgramAdmins ( rewardProgramId ) . call ( ) ; } async checkPrepaidCardOwnerIsAdmin ( rewardProgramId , prepaidCardAddress ) { let prepaidCardAPI = await getSDK ( 'PrepaidCard' , this . layer2Web3 ) ; let prepaidCardMgr = await prepaidCardAPI . getPrepaidCardMgr ( ) ; let prepaidCardOwner = await prepaidCardMgr . methods . getPrepaidCardOwner ( prepaidCardAddress ) . call ( ) ; let rewardProgramAdmin = await this . getRewardProgramAdmin ( rewardProgramId ) ; if ( ! ( prepaidCardOwner == rewardProgramAdmin ) ) { throw new Error ( `Owner ${ prepaidCardOwner } of prepaid card ${ prepaidCardAddress } is not the reward program admin ${ rewardProgramAdmin }` ) ; } } async getGovernanceAdmin ( ) { return await ( await this . getRewardManager ( ) ) . methods . governanceAdmin ( ) . call ( ) ; } async getRewardRule ( rewardProgramId ) { return await ( await this . getRewardManager ( ) ) . methods . rule ( rewardProgramId ) . call ( ) ; } async getRewardSafeDelegateAddress ( ) { return await ( await this . getRewardManager ( ) ) . methods . safeDelegateImplementation ( ) . call ( ) ; } async getRewardProgram ( rewardSafe ) { return await ( await this . getRewardManager ( ) ) . methods . rewardProgramsForRewardSafes ( rewardSafe ) . call ( ) ; } async address ( ) { return await getAddress ( 'rewardManager' , this . layer2Web3 ) ; } private async getRewardManager ( ) { if ( this . rewardManager ) { return this . rewardManager ; } this . rewardManager = new this . layer2Web3 . eth . Contract ( RewardManagerABI as AbiItem [ ] , await getAddress ( 'rewardManager' , this . layer2Web3 ) ) ; return this . rewardManager ; } private rewardeeRegisteredABI ( ) { return { topic : this . layer2Web3 . eth . abi . encodeEventSignature ( 'RewardeeRegistered(address,address,address)' ) , abis : [ { type : 'address' , name : 'rewardProgramId' , } , { type : 'address' , name : 'rewardee' , } , { type : 'address' , name : 'rewardSafe' , } , ] , } ; } }