File

src/servicesContracts/requestEthereum-service.ts

Description

The RequestEthereumService class is the interface for the Request Ethereum currency contract

Index

Properties
Methods

Properties

Protected abiRequestCoreLast
abiRequestCoreLast: any
Type : any

RequestCore contract's abi

Protected abiRequestEthereumLast
abiRequestEthereumLast: any
Type : any

RequestEthereum contract's abi

Protected addressRequestEthereumLast
addressRequestEthereumLast: string
Type : string

RequestEthereum contract's address

Protected instanceRequestEthereumLast
instanceRequestEthereumLast: any
Type : any

RequestEthereum contract's web3 instance

Protected ipfs
ipfs: any
Type : any
Protected requestCoreServices
requestCoreServices: any
Type : any

RequestCore service from this very lib

Public web3Single
web3Single: Web3Single
Type : Web3Single

Methods

Public accept
accept(_requestId: string, _options?: any)

accept a request as payer

Parameters :
Name Type Optional Description
_requestId string No

requestId of the payer

_options any Yes

options for the method (gasPrice, gas, value, from, numberOfConfirmation)

Returns : PromiseEventEmitter<literal type>

promise of the object containing the request and the transaction hash ({request, transactionHash})

Public additionalAction
additionalAction(_requestId: string, _additionals: any[], _options?: any)

add additionals to a request as payer

Parameters :
Name Type Optional Description
_requestId string No

requestId of the payer

_additionals any[] No

amounts of additionals in wei for each payee

_options any Yes

options for the method (gasPrice, gas, value, from, numberOfConfirmation)

Returns : PromiseEventEmitter<literal type>

promise of the object containing the request and the transaction hash ({request, transactionHash})

Public broadcastSignedRequestAsPayer
broadcastSignedRequestAsPayer(_signedRequest: any, _amountsToPay?: any[], _additions?: any[], _options?: any)

broadcast a signed transaction and fill it with his address as payer

Parameters :
Name Type Optional Description
_signedRequest any No

object signed request

_amountsToPay any[] Yes

amounts to pay in wei for each payee (optional)

_additions any[] Yes

amounts of additional in wei for each payee (optional)

_options any Yes

options for the method (gasPrice, gas, value, from, numberOfConfirmation)

Returns : PromiseEventEmitter<literal type>

promise of the object containing the request and the transaction hash ({request, transactionHash})

Public cancel
cancel(_requestId: string, _options?: any)

cancel a request as payer or payee

Parameters :
Name Type Optional Description
_requestId string No

requestId of the payer

_options any Yes

options for the method (gasPrice, gas, value, from, numberOfConfirmation)

Returns : PromiseEventEmitter<literal type>

promise of the object containing the request and the transaction hash ({request, transactionHash})

Public createRequestAsPayee
createRequestAsPayee(_payeesIdAddress: string[], _expectedAmounts: any[], _payer: string, _payeesPaymentAddress?: Array, _payerRefundAddress?: string, _data?: string, _extension?: string, _extensionParams?: any[], _options?: any)

create a request as payee

Parameters :
Name Type Optional Description
_payeesIdAddress string[] No

ID addresses of the payees (the position 0 will be the main payee, must be the broadcaster address)

_expectedAmounts any[] No

amount initial expected per payees for the request

_payer string No

address of the payer

_payeesPaymentAddress Array<string | undefined> Yes

payment addresses of the payees (the position 0 will be the main payee) (optional)

_payerRefundAddress string Yes

refund address of the payer (optional)

_data string Yes

Json of the request's details (optional)

_extension string Yes

address of the extension contract of the request (optional) NOT USED YET

_extensionParams any[] Yes

array of parameters for the extension (optional) NOT USED YET

_options any Yes

options for the method (gasPrice, gas, value, from, numberOfConfirmation)

Returns : PromiseEventEmitter<literal type>

promise of the object containing the request and the transaction hash ({request, transactionHash})

Public createRequestAsPayer
createRequestAsPayer(_payeesIdAddress: string[], _expectedAmounts: any[], _payerRefundAddress?: string, _amountsToPay?: any[], _additions?: any[], _data?: string, _extension?: string, _extensionParams?: any[], _options?: any)

create a request as payer

Parameters :
Name Type Optional Description
_payeesIdAddress string[] No

ID addresses of the payees (the position 0 will be the main payee)

_expectedAmounts any[] No

amount initial expected per payees for the request

_payerRefundAddress string Yes

refund address of the payer (optional)

_amountsToPay any[] Yes

amounts to pay in wei for each payee (optional)

_additions any[] Yes

amounts of additional in wei for each payee (optional)

_data string Yes

Json of the request's details (optional)

_extension string Yes

address of the extension contract of the request (optional) NOT USED YET

_extensionParams any[] Yes

array of parameters for the extension (optional) NOT USED YET

_options any Yes

options for the method (gasPrice, gas, value, from, numberOfConfirmation)

Returns : PromiseEventEmitter<literal type>

promise of the object containing the request and the transaction hash ({request, transactionHash})

Public decodeInputData
decodeInputData(_address: string, _data: any)

decode data from input tx (generic method)

Parameters :
Name Type Optional Description
_address string No
_data any No

requestId of the request

Returns : any

return an object with the name of the function and the parameters

Static destroy
destroy()
Returns : void
Static getInstance
getInstance()

get the instance of RequestEthereumService

The instance of the RequestEthereumService class.

Public getRequest
getRequest(_requestId: string)

alias of requestCoreServices.getRequest()

Parameters :
Name Type Optional
_requestId string No
Returns : Promise<any>
Public getRequestCurrencyContractInfo
getRequestCurrencyContractInfo(requestData: any, coreContract: any)

Get info from currency contract (generic method)

Parameters :
Name Type Optional
requestData any No
coreContract any No
Returns : Promise<any>

promise of the information from the currency contract of the request (always {} here)

Public getRequestEvents
getRequestEvents(_requestId: string, _fromBlock?: number, _toBlock?: number)

alias of requestCoreServices.getRequestEvents()

Parameters :
Name Type Optional
_requestId string No
_fromBlock number Yes
_toBlock number Yes
Returns : Promise<any>
Public Async getRequestEventsCurrencyContractInfo
getRequestEventsCurrencyContractInfo(_request: any, _coreContract: any, _fromBlock?: number, _toBlock?: number)

Get request events from currency contract (generic method)

Parameters :
Name Type Optional Description
_request any No
_coreContract any No
_fromBlock number Yes

search events from this block (optional)

_toBlock number Yes

search events until this block (optional)

Returns : Promise<any>

promise of the object containing the events from the currency contract of the request (always {} here)

Public increaseExpectedAmounts
increaseExpectedAmounts(_requestId: string, _amounts: any[], _options?: any)

Increase the amount due to each payee. This can be called by the payer e.g. to add extra payments to the Request for tips or bonuses.

Parameters :
Name Type Optional Description
_requestId string No

ID of Request

_amounts any[] No

Extra payment amounts in wei for each payee

_options any Yes

Transaction options (gasPrice, gas, value, from, numberOfConfirmation)

Returns : PromiseEventEmitter<literal type>

promise of the object containing the request and the transaction hash ({request, transactionHash})

Public paymentAction
paymentAction(_requestId: string, _amountsToPay: any[], _additions?: any[], _options?: any)

pay a request

Parameters :
Name Type Optional Description
_requestId string No

requestId of the payer

_amountsToPay any[] No

amounts to pay in wei for each payee

_additions any[] Yes

amounts of additional in wei for each payee (optional)

_options any Yes

options for the method (gasPrice, gas, value, from, numberOfConfirmation)

Returns : PromiseEventEmitter<literal type>

promise of the object containing the request and the transaction hash ({request, transactionHash})

Public reduceExpectedAmounts
reduceExpectedAmounts(_requestId: string, _amounts: any[], _options?: any)

Reduce the amount due to each payee. This can be called by the payee e.g. to apply discounts or special offers.

Parameters :
Name Type Optional Description
_requestId string No

ID of the Request

_amounts any[] No

Array of reduction amounts in wei for each payee

_options any Yes

options for the method (gasPrice, gas, value, from, numberOfConfirmation)

Returns : PromiseEventEmitter<literal type>

promise of the object containing the request and the transaction hash ({request, transactionHash})

Public refundAction
refundAction(_requestId: string, _amountToRefund: any, _options?: any)

refund a request as payee

Parameters :
Name Type Optional Description
_requestId string No

requestId of the payer

_amountToRefund any No

amount to refund in wei

_options any Yes

options for the method (gasPrice, gas, value, from, numberOfConfirmation)

Returns : PromiseEventEmitter<literal type>

promise of the object containing the request and the transaction hash ({request, transactionHash})

Public signRequestAsPayee
signRequestAsPayee(_payeesIdAddress: string[], _expectedAmounts: any[], _expirationDate: number, _payeesPaymentAddress?: Array, _data?: string, _extension?: string, _extensionParams?: any[], _from?: string)

sign a request as payee

Parameters :
Name Type Optional Description
_payeesIdAddress string[] No

ID addresses of the payees (the position 0 will be the main payee, must be the broadcaster address)

_expectedAmounts any[] No

amount initial expected per payees for the request

_expirationDate number No

timestamp in second of the date after which the signed request is not broadcastable

_payeesPaymentAddress Array<string | undefined> Yes

payment addresses of the payees (the position 0 will be the main payee) (optional)

_data string Yes

Json of the request's details (optional)

_extension string Yes

address of the extension contract of the request (optional) NOT USED YET

_extensionParams any[] Yes

array of parameters for the extension (optional) NOT USED YET

_from string Yes

address of the payee, default account will be used otherwise (optional)

Returns : PromiseEventEmitter<literal type>

promise of the object containing the request signed

Public subtractAction
subtractAction(_requestId: string, _subtracts: any[], _options?: any)

add subtracts to a request as payee

Parameters :
Name Type Optional Description
_requestId string No

requestId of the payer

_subtracts any[] No

amounts of subtracts in wei for each payee

_options any Yes

options for the method (gasPrice, gas, value, from, numberOfConfirmation)

Returns : PromiseEventEmitter<literal type>

promise of the object containing the request and the transaction hash ({request, transactionHash})

Public validateSignedRequest
validateSignedRequest(_signedRequest: any, _payer: string)

check if a signed request is valid

Parameters :
Name Type Optional Description
_signedRequest any No

Signed request

_payer string No

Payer of the request

Returns : string

return a string with the error, or ''

import RequestCoreService from '../servicesCore/requestCore-service';
import Ipfs from '../servicesExternal/ipfs-service';

import Web3Single from '../servicesExternal/web3-single';

import * as Types from '../types';

import * as ETH_UTIL from 'ethereumjs-util';
// @ts-ignore
import * as Web3PromiEvent from 'web3-core-promievent';

// @ts-ignore
const ETH_ABI = require('../lib/ethereumjs-abi-perso.js');

const BN = Web3Single.BN();

const EMPTY_BYTES_20 = '0x0000000000000000000000000000000000000000';

/**
 * The RequestEthereumService class is the interface for the Request Ethereum currency contract
 */
export default class RequestEthereumService {
    /**
     * get the instance of RequestEthereumService
     * @return  The instance of the RequestEthereumService class.
     */
    public static getInstance() {
        if (!RequestEthereumService._instance) {
            RequestEthereumService._instance = new this();
        }
        return RequestEthereumService._instance;
    }

    public static destroy() {
        RequestEthereumService._instance = null;
    }

    private static _instance: RequestEthereumService|null;

    public web3Single: Web3Single;

    protected ipfs: any;

    // RequestCore on blockchain
    /**
     * RequestCore contract's abi
     */
    protected abiRequestCoreLast: any;
    /**
     * RequestCore service from this very lib
     */
    protected requestCoreServices: any;

    // RequestEthereum on blockchain
    /**
     * RequestEthereum contract's abi
     */
    protected abiRequestEthereumLast: any;
    /**
     * RequestEthereum contract's address
     */
    protected addressRequestEthereumLast: string;
    /**
     * RequestEthereum contract's web3 instance
     */
    protected instanceRequestEthereumLast: any;

    /**
     * constructor to Instantiates a new RequestEthereumService
     */
    private constructor() {
        this.web3Single = Web3Single.getInstance();
        this.ipfs = Ipfs.getInstance();

        this.abiRequestCoreLast = this.web3Single.getContractInstance('last-RequestCore').abi;
        this.requestCoreServices = RequestCoreService.getInstance();

        const requestEthereumLastArtifact = this.web3Single.getContractInstance('last-RequestEthereum');
        if (!requestEthereumLastArtifact) {
            throw Error('RequestEthereum Artifact: no config for network : "' + this.web3Single.networkName + '"');
        }
        this.abiRequestEthereumLast = requestEthereumLastArtifact.abi;
        this.addressRequestEthereumLast = requestEthereumLastArtifact.address;
        this.instanceRequestEthereumLast = requestEthereumLastArtifact.instance;
    }

    /**
     * create a request as payee
     * @dev emit the event 'broadcasted' with {transaction: {hash}} when the transaction is submitted
     * @param   _payeesIdAddress           ID addresses of the payees (the position 0 will be the main payee, must be the broadcaster address)
     * @param   _expectedAmounts           amount initial expected per payees for the request
     * @param   _payer                     address of the payer
     * @param   _payeesPaymentAddress      payment addresses of the payees (the position 0 will be the main payee) (optional)
     * @param   _payerRefundAddress        refund address of the payer (optional)
     * @param   _data                      Json of the request's details (optional)
     * @param   _extension                 address of the extension contract of the request (optional) NOT USED YET
     * @param   _extensionParams           array of parameters for the extension (optional) NOT USED YET
     * @param   _options                   options for the method (gasPrice, gas, value, from, numberOfConfirmation)
     * @return  promise of the object containing the request and the transaction hash ({request, transactionHash})
     */
    public createRequestAsPayee(
        _payeesIdAddress: string[],
        _expectedAmounts: any[],
        _payer: string,
        _payeesPaymentAddress ?: Array<string|undefined>,
        _payerRefundAddress ?: string,
        _data ?: string,
        _extension ?: string,
        _extensionParams ?: any[] ,
        _options ?: any,
        ): PromiseEventEmitter<{request: Request, transaction: any}> {
        const promiEvent = Web3PromiEvent();
        _expectedAmounts = _expectedAmounts.map((amount) => new BN(amount));

        let _payeesPaymentAddressParsed: string[] = [];
        if (_payeesPaymentAddress) {
            _payeesPaymentAddressParsed = _payeesPaymentAddress.map((addr) => addr ? addr : EMPTY_BYTES_20);
        }

        const expectedAmountsTotal = _expectedAmounts.reduce((a, b) => a.add(b), new BN(0));

        _options = this.web3Single.setUpOptions(_options);

        this.web3Single.getDefaultAccountCallback(async (err, defaultAccount) => {
            if (!_options.from && err) return promiEvent.reject(err);
            const account = _options.from || defaultAccount;

            if (_payeesIdAddress.length !== _expectedAmounts.length) {
                return promiEvent.reject(Error('_payeesIdAddress and _expectedAmounts must have the same size'));
            }
            if (_payeesPaymentAddress && _payeesIdAddress.length < _payeesPaymentAddress.length) {
                return promiEvent.reject(Error('_payeesPaymentAddress cannot be bigger than _payeesIdAddress'));
            }

            if (!this.web3Single.isArrayOfAddressesNoChecksum(_payeesIdAddress)) {
                return promiEvent.reject(Error('_payeesIdAddress must be valid eth addresses'));
            }
            if (!this.web3Single.isArrayOfAddressesNoChecksum(_payeesPaymentAddressParsed)) {
                return promiEvent.reject(Error('_payeesPaymentAddress must be valid eth addresses'));
            }

            if ( !this.web3Single.areSameAddressesNoChecksum(account, _payeesIdAddress[0]) ) {
                return promiEvent.reject(Error('account broadcaster must be the main payee'));
            }

            if (_expectedAmounts.filter((amount) => amount.isNeg()).length !== 0) {
                return promiEvent.reject(Error('_expectedAmounts must be positive integers'));
            }

            if (!this.web3Single.isAddressNoChecksum(_payer)) {
                return promiEvent.reject(Error('_payer must be a valid eth address'));
            }
            if (_payerRefundAddress && !this.web3Single.isAddressNoChecksum(_payerRefundAddress)) {
                return promiEvent.reject(Error('_payerRefundAddress must be a valid eth address'));
            }

            if (_extension) {
                return promiEvent.reject(Error('extensions are disabled for now'));
            }

            if ( this.web3Single.areSameAddressesNoChecksum(account, _payer) ) {
                return promiEvent.reject(Error('_from must be different than _payer'));
            }
            // get the amount to collect
            try {
                const collectEstimation = await this.instanceRequestEthereumLast.methods.collectEstimation(expectedAmountsTotal).call();

                _options.value = collectEstimation;

                // add file to ipfs
                const hashIpfs = await this.ipfs.addFile(_data);

                const method = this.instanceRequestEthereumLast.methods.createRequestAsPayee(
                    _payeesIdAddress,
                    _payeesPaymentAddressParsed,
                    _expectedAmounts,
                    _payer,
                    _payerRefundAddress,
                    hashIpfs);

                // submit transaction
                this.web3Single.broadcastMethod(
                    method,
                    (hash: string) => {
                        return promiEvent.eventEmitter.emit('broadcasted', {transaction: {hash}});
                    },
                    (receipt: any) => {
                        // we do nothing here!
                    },
                    async (confirmationNumber: number, receipt: any) => {
                        if (confirmationNumber === _options.numberOfConfirmation) {
                            const eventRaw = receipt.events[0];
                            const event = this.web3Single.decodeEvent(this.abiRequestCoreLast, 'Created', eventRaw);
                            try {
                                const requestAfter = await this.getRequest(event.requestId);
                                promiEvent.resolve({request: requestAfter, transaction: {hash: receipt.transactionHash}});
                            } catch (e) {
                                return promiEvent.reject(e);
                            }
                        }
                    },
                    (errBroadcast) => {
                        return promiEvent.reject(errBroadcast);
                    },
                    _options);
            } catch (e) {
                promiEvent.reject(e);
            }
        });
        return promiEvent.eventEmitter;
    }

    /**
     * create a request as payer
     * @dev emit the event 'broadcasted' with {transaction: {hash}} when the transaction is submitted
     * @param   _payeesIdAddress           ID addresses of the payees (the position 0 will be the main payee)
     * @param   _expectedAmounts           amount initial expected per payees for the request
     * @param   _payerRefundAddress        refund address of the payer (optional)
     * @param   _amountsToPay              amounts to pay in wei for each payee (optional)
     * @param   _additions               amounts of additional in wei for each payee (optional)
     * @param   _data                      Json of the request's details (optional)
     * @param   _extension                 address of the extension contract of the request (optional) NOT USED YET
     * @param   _extensionParams           array of parameters for the extension (optional) NOT USED YET
     * @param   _options                   options for the method (gasPrice, gas, value, from, numberOfConfirmation)
     * @return  promise of the object containing the request and the transaction hash ({request, transactionHash})
     */
    public createRequestAsPayer(
        _payeesIdAddress: string[],
        _expectedAmounts: any[],
        _payerRefundAddress ?: string,
        _amountsToPay ?: any[],
        _additions ?: any[],
        _data ?: string,
        _extension ?: string,
        _extensionParams ?: any[],
        _options ?: any): PromiseEventEmitter<{request: Request, transaction: any}> {
        const promiEvent = Web3PromiEvent();

        _expectedAmounts = _expectedAmounts.map((amount) => new BN(amount));
        let amountsToPayParsed: any[] = [];
        if (_amountsToPay) {
            amountsToPayParsed = _amountsToPay.map((amount) => new BN(amount || 0));
        }
        let additionsParsed: any[] = [];
        if (_additions) {
            additionsParsed = _additions.map((amount) => new BN(amount || 0));
        }
        const expectedAmountsTotal = _expectedAmounts.reduce((a, b) => a.add(b), new BN(0));
        const amountsToPayTotal = amountsToPayParsed.reduce((a, b) => a.add(b), new BN(0));

        _options = this.web3Single.setUpOptions(_options);

        this.web3Single.getDefaultAccountCallback(async (err, defaultAccount) => {
            if (!_options.from && err) return promiEvent.reject(err);
            const account = _options.from || defaultAccount;

            // some controls on the arrays
            if (_payeesIdAddress.length !== _expectedAmounts.length) {
                return promiEvent.reject(Error('_payeesIdAddress and _expectedAmounts must have the same size'));
            }
            if (_amountsToPay && _payeesIdAddress.length < _amountsToPay.length) {
                return promiEvent.reject(Error('_amountsToPay cannot be bigger than _payeesIdAddress'));
            }
            if (_additions && _payeesIdAddress.length < _additions.length) {
                return promiEvent.reject(Error('_additions cannot be bigger than _payeesIdAddress'));
            }
            if (!this.web3Single.isArrayOfAddressesNoChecksum(_payeesIdAddress)) {
                return promiEvent.reject(Error('_payeesIdAddress must be valid eth addresses'));
            }
            if (_expectedAmounts.filter((amount) => amount.isNeg()).length !== 0) {
                return promiEvent.reject(Error('_expectedAmounts must be positive integers'));
            }
            if (amountsToPayParsed.filter((amount) => amount.isNeg()).length !== 0) {
                return promiEvent.reject(Error('_amountsToPay must be positive integers'));
            }
            if (additionsParsed.filter((amount) => amount.isNeg()).length !== 0) {
                return promiEvent.reject(Error('_additions must be positive integers'));
            }
            if (_extension) {
                return promiEvent.reject(Error('extensions are disabled for now'));
            }
            if (_payerRefundAddress && !this.web3Single.isAddressNoChecksum(_payerRefundAddress)) {
                return promiEvent.reject(Error('_payerRefundAddress must be a valid eth address'));
            }
            if (this.web3Single.areSameAddressesNoChecksum(account, _payeesIdAddress[0]) ) {
                return promiEvent.reject(Error('_from must be different than the main payee'));
            }

            try {
                // get the amount to collect
                const collectEstimation = await this.instanceRequestEthereumLast.methods.collectEstimation(expectedAmountsTotal).call();

                _options.value = amountsToPayTotal.add(new BN(collectEstimation));

                // add file to ipfs
                const hashIpfs = await this.ipfs.addFile(_data);

                const method = this.instanceRequestEthereumLast.methods.createRequestAsPayer(
                    _payeesIdAddress,
                    _expectedAmounts,
                    _payerRefundAddress,
                    amountsToPayParsed,
                    additionsParsed,
                    hashIpfs);

                // submit transaction
                this.web3Single.broadcastMethod(
                    method,
                    (hash: string) => {
                        return promiEvent.eventEmitter.emit('broadcasted', {transaction: {hash}});
                    },
                    (receipt: any) => {
                        // we do nothing here!
                    },
                    async (confirmationNumber: number, receipt: any) => {
                        if (confirmationNumber === _options.numberOfConfirmation) {
                            const eventRaw = receipt.events[0];
                            const event = this.web3Single.decodeEvent(this.abiRequestCoreLast, 'Created', eventRaw);
                            try {
                                const requestAfter = await this.getRequest(event.requestId);
                                promiEvent.resolve({request: requestAfter, transaction: {hash: receipt.transactionHash}});
                            } catch (e) {
                                return promiEvent.reject(e);
                            }
                        }
                    },
                    (errBroadcast) => {
                        return promiEvent.reject(errBroadcast);
                    },
                    _options);
            } catch (e) {
                promiEvent.reject(e);
            }
        });
        return promiEvent.eventEmitter;
    }

    /**
     * sign a request as payee
     * @param   _payeesIdAddress           ID addresses of the payees (the position 0 will be the main payee, must be the broadcaster address)
     * @param   _expectedAmounts           amount initial expected per payees for the request
     * @param   _expirationDate            timestamp in second of the date after which the signed request is not broadcastable
     * @param   _payeesPaymentAddress      payment addresses of the payees (the position 0 will be the main payee) (optional)
     * @param   _data                      Json of the request's details (optional)
     * @param   _extension                 address of the extension contract of the request (optional) NOT USED YET
     * @param   _extensionParams           array of parameters for the extension (optional) NOT USED YET
     * @param   _from                      address of the payee, default account will be used otherwise (optional)
     * @return  promise of the object containing the request signed
     */
    public signRequestAsPayee(
        _payeesIdAddress: string[],
        _expectedAmounts: any[],
        _expirationDate: number,
        _payeesPaymentAddress ?: Array<string|undefined>,
        _data ?: string,
        _extension ?: string,
        _extensionParams ?: any[],
        _from ?: string,
        ): PromiseEventEmitter<{request: Request, transaction: any}> {
        const promiEvent = Web3PromiEvent();

        _expectedAmounts = _expectedAmounts.map((amount) => new BN(amount));

        let payeesPaymentAddressParsed: string[] = [];
        if (_payeesPaymentAddress) {
            payeesPaymentAddressParsed = _payeesPaymentAddress.map((addr) => addr ? addr : EMPTY_BYTES_20);
        }

        this.web3Single.getDefaultAccountCallback(async (err, defaultAccount) => {
            if (!_from && err) return promiEvent.reject(err);
            const account: string = _from || defaultAccount || '';

            if (_payeesIdAddress.length !== _expectedAmounts.length) {
                return promiEvent.reject(Error('_payeesIdAddress and _expectedAmounts must have the same size'));
            }
            if (_payeesPaymentAddress && _payeesIdAddress.length < _payeesPaymentAddress.length) {
                return promiEvent.reject(Error('_payeesPaymentAddress cannot be bigger than _payeesIdAddress'));
            }

            const todaySolidityTime: number = (new Date().getTime()) / 1000;
            if ( _expirationDate <= todaySolidityTime ) {
                return promiEvent.reject(Error('_expirationDate must be greater than now'));
            }

            if (_expectedAmounts.filter((amount) => amount.isNeg()).length !== 0) {
                return promiEvent.reject(Error('_expectedAmounts must be positive integers'));
            }
            if ( !this.web3Single.areSameAddressesNoChecksum(account, _payeesIdAddress[0]) ) {
                return promiEvent.reject(Error('account broadcaster must be the main payee'));
            }
            if (!this.web3Single.isArrayOfAddressesNoChecksum(_payeesIdAddress)) {
                return promiEvent.reject(Error('_payeesIdAddress must be valid eth addresses'));
            }
            if (!this.web3Single.isArrayOfAddressesNoChecksum(payeesPaymentAddressParsed)) {
                return promiEvent.reject(Error('_payeesPaymentAddress must be valid eth addresses'));
            }
            if (_extension) {
                return promiEvent.reject(Error('extensions are disabled for now'));
            }

            try {
                // add file to ipfs
                const hashIpfs = await this.ipfs.addFile(_data);

                const signedRequest = await this.createSignedRequest(
                                this.addressRequestEthereumLast,
                                _payeesIdAddress,
                                _expectedAmounts,
                                payeesPaymentAddressParsed,
                                _expirationDate,
                                hashIpfs,
                                '',
                                []);

                promiEvent.resolve(signedRequest);
            } catch (e) {
                promiEvent.reject(e);
            }
        });
        return promiEvent.eventEmitter;
    }

    /**
     * broadcast a signed transaction and fill it with his address as payer
     * @dev emit the event 'broadcasted' with {transaction: {hash}} when the transaction is submitted
     * @param   _signedRequest     object signed request
     * @param   _amountsToPay      amounts to pay in wei for each payee (optional)
     * @param   _additions       amounts of additional in wei for each payee (optional)
     * @param   _options           options for the method (gasPrice, gas, value, from, numberOfConfirmation)
     * @return  promise of the object containing the request and the transaction hash ({request, transactionHash})
     */
    public broadcastSignedRequestAsPayer(
        _signedRequest: any,
        _amountsToPay ?: any[],
        _additions ?: any[],
        _options ?: any,
        ): PromiseEventEmitter<{request: Request, transaction: any}> {
        const promiEvent = Web3PromiEvent();

        let amountsToPayParsed: any[] = [];
        if (_amountsToPay) {
            amountsToPayParsed = _amountsToPay.map((amount) => new BN(amount || 0));
        }
        let additionsParsed: any[] = [];
        if (_additions) {
            additionsParsed = _additions.map((amount) => new BN(amount || 0));
        }
        const amountsToPayTotal = amountsToPayParsed.reduce((a, b) => a.add(b), new BN(0));

        _signedRequest.expectedAmounts = _signedRequest.expectedAmounts.map((amount: any) => new BN(amount));
        const expectedAmountsTotal = _signedRequest.expectedAmounts.reduce((a: any, b: any) => a.add(b), new BN(0));

        if (_signedRequest.payeesPaymentAddress) {
            _signedRequest.payeesPaymentAddress = _signedRequest.payeesPaymentAddress.map((addr: any) => addr ? addr : EMPTY_BYTES_20);
        } else {
            _signedRequest.payeesPaymentAddress = [];
        }

        _signedRequest.data = _signedRequest.data ? _signedRequest.data : '';
        _options = this.web3Single.setUpOptions(_options);

        this.web3Single.getDefaultAccountCallback(async (err, defaultAccount) => {
            if (!_options.from && err) return promiEvent.reject(err);
            const account = _options.from || defaultAccount;
            const error = this.validateSignedRequest(_signedRequest, account);
            if (error !== '') return promiEvent.reject(Error(error));

            if (_amountsToPay && _signedRequest.payeesIdAddress.length < _amountsToPay.length) {
                return promiEvent.reject(Error('_amountsToPay cannot be bigger than _payeesIdAddress'));
            }
            if (_additions && _signedRequest.payeesIdAddress.length < _additions.length) {
                return promiEvent.reject(Error('_additions cannot be bigger than _payeesIdAddress'));
            }
            if (amountsToPayParsed.filter((amount) => amount.isNeg()).length !== 0) {
                return promiEvent.reject(Error('_amountsToPay must be positive integers'));
            }
            if (additionsParsed.filter((amount) => amount.isNeg()).length !== 0) {
                return promiEvent.reject(Error('_additions must be positive integers'));
            }
            if (this.web3Single.areSameAddressesNoChecksum(account, _signedRequest.payeesIdAddress[0]) ) {
                return promiEvent.reject(Error('_from must be different than the main payee'));
            }
            if (_signedRequest.extension) {
                return promiEvent.reject(Error('extensions are disabled for now'));
            }

            try {
                // get the amount to collect
                const collectEstimation = await this.instanceRequestEthereumLast.methods.collectEstimation(expectedAmountsTotal).call();

                _options.value = amountsToPayTotal.add(new BN(collectEstimation));

                const method = this.instanceRequestEthereumLast.methods.broadcastSignedRequestAsPayer(
                                                    this.requestCoreServices.createBytesRequest(_signedRequest.payeesIdAddress, _signedRequest.expectedAmounts, 0, _signedRequest.data),
                                                    _signedRequest.payeesPaymentAddress,
                                                    amountsToPayParsed,
                                                    additionsParsed,
                                                    _signedRequest.expirationDate,
                                                    _signedRequest.signature);

                // submit transaction
                this.web3Single.broadcastMethod(
                    method,
                    (hash: string) => {
                        return promiEvent.eventEmitter.emit('broadcasted', {transaction: {hash}});
                    },
                    (receipt: any) => {
                        // we do nothing here!
                    },
                    async (confirmationNumber: number, receipt: any) => {
                        if (confirmationNumber === _options.numberOfConfirmation) {
                            const eventRaw = receipt.events[0];
                            const event = this.web3Single.decodeEvent(this.abiRequestCoreLast, 'Created', eventRaw);
                            try {
                                const requestAfter = await this.getRequest(event.requestId);
                                promiEvent.resolve({request: requestAfter, transaction: {hash: receipt.transactionHash}});
                            } catch (e) {
                                return promiEvent.reject(e);
                            }
                        }
                    },
                    (errBroadcast) => {
                        return promiEvent.reject(errBroadcast);
                    },
                    _options);
            } catch (e) {
                promiEvent.reject(e);
            }
        });
        return promiEvent.eventEmitter;
    }

    /**
     * accept a request as payer
     * @dev emit the event 'broadcasted' with {transaction: {hash}} when the transaction is submitted
     * @param   _requestId         requestId of the payer
     * @param   _options           options for the method (gasPrice, gas, value, from, numberOfConfirmation)
     * @return  promise of the object containing the request and the transaction hash ({request, transactionHash})
     */
    public accept(
        _requestId: string,
        _options ?: any): PromiseEventEmitter<{request: Request, transaction: any}> {
        const promiEvent = Web3PromiEvent();
        _options = this.web3Single.setUpOptions(_options);

        this.web3Single.getDefaultAccountCallback(async (err, defaultAccount) => {
            if (!_options.from && err) return promiEvent.reject(err);
            const account = _options.from || defaultAccount;

            try {
                const request = await this.getRequest(_requestId);

                if (request.state !== Types.State.Created) {
                    return promiEvent.reject(Error('request state is not \'created\''));
                }
                if (!this.web3Single.areSameAddressesNoChecksum(account, request.payer) ) {
                    return promiEvent.reject(Error('account must be the payer'));
                }

                const contract = this.web3Single.getContractInstance(request.currencyContract.address);
                const method = contract.instance.methods.accept(_requestId);

                this.web3Single.broadcastMethod(
                    method,
                    (hash: string) => {
                        return promiEvent.eventEmitter.emit('broadcasted', {transaction: {hash}});
                    },
                    (receipt: any) => {
                        // we do nothing here!
                    },
                    async (confirmationNumber: number, receipt: any) => {
                        if (confirmationNumber === _options.numberOfConfirmation) {
                            const eventRaw = receipt.events[0];
                            const coreContract = this.requestCoreServices.getCoreContractFromRequestId(request.requestId);
                            const event = this.web3Single.decodeEvent(coreContract.abi, 'Accepted', eventRaw);
                            try {
                                const requestAfter = await this.getRequest(event.requestId);
                                promiEvent.resolve({request: requestAfter, transaction: {hash: receipt.transactionHash}});
                            } catch (e) {
                                return promiEvent.reject(e);
                            }
                        }
                    },
                    (error: Error) => {
                        return promiEvent.reject(error);
                    },
                    _options);
            } catch (e) {
                promiEvent.reject(e);
            }
        });

        return promiEvent.eventEmitter;
    }

    /**
     * cancel a request as payer or payee
     * @dev emit the event 'broadcasted' with {transaction: {hash}} when the transaction is submitted
     * @param   _requestId         requestId of the payer
     * @param   _options           options for the method (gasPrice, gas, value, from, numberOfConfirmation)
     * @return  promise of the object containing the request and the transaction hash ({request, transactionHash})
     */
    public cancel(
        _requestId: string,
        _options ?: any): PromiseEventEmitter<{request: Request, transaction: any}> {
        const promiEvent = Web3PromiEvent();
        _options = this.web3Single.setUpOptions(_options);

        this.web3Single.getDefaultAccountCallback( async (err, defaultAccount) => {
            if (!_options.from && err) return promiEvent.reject(err);
            const account = _options.from || defaultAccount;

            try {
                const request = await this.getRequest(_requestId);

                if ( !this.web3Single.areSameAddressesNoChecksum(account, request.payer)
                        && !this.web3Single.areSameAddressesNoChecksum(account, request.payee.address) ) {
                    return promiEvent.reject(Error('account must be the payer or the payee'));
                }
                if ( this.web3Single.areSameAddressesNoChecksum(account, request.payer)
                        && request.state !== Types.State.Created ) {
                    return promiEvent.reject(Error('payer can cancel request in state \'created\''));
                }
                if ( this.web3Single.areSameAddressesNoChecksum(account, request.payee.address)
                        && request.state === Types.State.Canceled ) {
                    return promiEvent.reject(Error('payee cannot cancel request already canceled'));
                }

                let balanceTotal = request.payee.balance;
                for (const subPayee of request.subPayees) {
                   balanceTotal = balanceTotal.add(subPayee.balance);
                }

                if ( !balanceTotal.isZero() ) {
                    return promiEvent.reject(Error('impossible to cancel a Request with a balance !== 0'));
                }

                const contract = this.web3Single.getContractInstance(request.currencyContract.address);
                const method = contract.instance.methods.cancel(_requestId);

                this.web3Single.broadcastMethod(
                    method,
                    (hash: string) => {
                        return promiEvent.eventEmitter.emit('broadcasted', {transaction: {hash}});
                    },
                    (receipt: any) => {
                        // we do nothing here!
                    },
                    async (confirmationNumber: number, receipt: any) => {
                        if (confirmationNumber === _options.numberOfConfirmation) {
                            const eventRaw = receipt.events[0];
                            const coreContract = this.requestCoreServices.getCoreContractFromRequestId(request.requestId);
                            const event = this.web3Single.decodeEvent(coreContract.abi, 'Canceled', eventRaw);
                            try {
                                const requestAfter = await this.getRequest(event.requestId);
                                promiEvent.resolve({request: requestAfter, transaction: {hash: receipt.transactionHash}});
                            } catch (e) {
                                return promiEvent.reject(e);
                            }
                        }
                    },
                    (error: Error) => {
                        return promiEvent.reject(error);
                    },
                    _options);
            } catch (e) {
                promiEvent.reject(e);
            }
        });

        return promiEvent.eventEmitter;
    }

    /**
     * pay a request
     * @dev emit the event 'broadcasted' with {transaction: {hash}} when the transaction is submitted
     * @param   _requestId         requestId of the payer
     * @param   _amountsToPay      amounts to pay in wei for each payee
     * @param   _additions       amounts of additional in wei for each payee (optional)
     * @param   _options           options for the method (gasPrice, gas, value, from, numberOfConfirmation)
     * @return  promise of the object containing the request and the transaction hash ({request, transactionHash})
     */
    public paymentAction(
        _requestId: string,
        _amountsToPay: any[],
        _additions ?: any[],
        _options ?: any): PromiseEventEmitter<{request: Request, transaction: any}> {
        const promiEvent = Web3PromiEvent();

        let amountsToPayParsed: any[] = [];
        if (_amountsToPay) {
            amountsToPayParsed = _amountsToPay.map((amount) => new BN(amount || 0));
        }
        let additionsParsed: any[] = [];
        if (_additions) {
            additionsParsed = _additions.map((amount) => new BN(amount || 0));
        }
        const amountsToPayTotal = amountsToPayParsed.reduce((a, b) => a.add(b), new BN(0));
        const additionalsTotal = additionsParsed.reduce((a, b) => a.add(b), new BN(0));
        _options = this.web3Single.setUpOptions(_options);

        this.web3Single.getDefaultAccountCallback(async (err, defaultAccount) => {
            if (!_options.from && err) return promiEvent.reject(err);
            const account = _options.from || defaultAccount;

            try {
                const request = await this.getRequest(_requestId);

                if (_amountsToPay && request.subPayees.length + 1 < _amountsToPay.length) {
                    return promiEvent.reject(Error('_amountsToPay cannot be bigger than _payeesIdAddress'));
                }
                if (_additions && request.subPayees.length + 1 < _additions.length) {
                    return promiEvent.reject(Error('_additions cannot be bigger than _payeesIdAddress'));
                }
                if (amountsToPayParsed.filter((amount) => amount.isNeg()).length !== 0) {
                    return promiEvent.reject(Error('_amountsToPay must be positive integers'));
                }
                if (additionsParsed.filter((amount) => amount.isNeg()).length !== 0) {
                    return promiEvent.reject(Error('_additions must be positive integers'));
                }
                if ( request.state === Types.State.Canceled ) {
                    return promiEvent.reject(Error('request cannot be canceled'));
                }
                if ( !additionalsTotal.isZero() && !this.web3Single.areSameAddressesNoChecksum(account, request.payer) ) {
                    return promiEvent.reject(Error('only payer can add additions'));
                }

                _options.value = amountsToPayTotal;

                const contract = this.web3Single.getContractInstance(request.currencyContract.address);
                const method = contract.instance.methods.paymentAction(
                                                                    _requestId,
                                                                    amountsToPayParsed,
                                                                    additionsParsed);

                this.web3Single.broadcastMethod(
                    method,
                    (hash: string) => {
                        return promiEvent.eventEmitter.emit('broadcasted', {transaction: {hash}});
                    },
                    (receipt: any) => {
                        // we do nothing here!
                    },
                    async (confirmationNumber: number, receipt: any) => {
                        if (confirmationNumber === _options.numberOfConfirmation) {
                            const coreContract = this.requestCoreServices.getCoreContractFromRequestId(request.requestId);
                            const event = this.web3Single.decodeEvent(coreContract.abi, 'UpdateBalance',
                                        request.state === Types.State.Created && account === request.payer ? receipt.events[1] : receipt.events[0]);
                            try {
                                const requestAfter = await this.getRequest(event.requestId);
                                promiEvent.resolve({request: requestAfter, transaction: {hash: receipt.transactionHash}});
                            } catch (e) {
                                return promiEvent.reject(e);
                            }
                        }
                    },
                    (error: Error) => {
                        return promiEvent.reject(error);
                    },
                    _options);
            } catch (e) {
                promiEvent.reject(e);
            }
        });

        return promiEvent.eventEmitter;
    }

    /**
     * refund a request as payee
     * @dev emit the event 'broadcasted' with {transaction: {hash}} when the transaction is submitted
     * @dev only addresses from payeesIdAddress and payeesPaymentAddress can refund a request
     * @param   _requestId         requestId of the payer
     * @param   _amountToRefund    amount to refund in wei
     * @param   _options           options for the method (gasPrice, gas, value, from, numberOfConfirmation)
     * @return  promise of the object containing the request and the transaction hash ({request, transactionHash})
     */
    public refundAction(
        _requestId: string,
        _amountToRefund: any,
        _options ?: any): PromiseEventEmitter<{request: Request, transaction: any}> {
        const promiEvent = Web3PromiEvent();
        _options = this.web3Single.setUpOptions(_options);
        _options.value = new BN(_amountToRefund);

        this.web3Single.getDefaultAccountCallback(async (err, defaultAccount) => {
            if (!_options.from && err) return promiEvent.reject(err);
            const account = _options.from || defaultAccount;

            try {
                const request = await this.getRequest(_requestId);

                if (_options.value.isNeg()) return promiEvent.reject(Error('_amount must a positive integer'));

                if ( request.state === Types.State.Canceled ) {
                    return promiEvent.reject(Error('request cannot be canceled'));
                }

                if (!this.web3Single.areSameAddressesNoChecksum(account, request.payee.address) && !this.web3Single.areSameAddressesNoChecksum(account, request.currencyContract.payeePaymentAddress) ) {
                    let foundInSubPayee = false;
                    for (const subPayee of request.subPayees) {
                        if (this.web3Single.areSameAddressesNoChecksum(account, subPayee.address)) {
                            foundInSubPayee = true;
                        }
                    }
                    for (const subPayee of request.currencyContract.subPayeesPaymentAddress) {
                        if (this.web3Single.areSameAddressesNoChecksum(account, subPayee)) {
                            foundInSubPayee = true;
                        }
                    }
                    if (!foundInSubPayee) {
                        return promiEvent.reject(Error('account must be a payee'));
                    }
                }

                const contract = this.web3Single.getContractInstance(request.currencyContract.address);
                const method = contract.instance.methods.refundAction(_requestId);

                this.web3Single.broadcastMethod(
                    method,
                    (hash: string) => {
                        return promiEvent.eventEmitter.emit('broadcasted', {transaction: {hash}});
                    },
                    (receipt: any) => {
                        // we do nothing here!
                    },
                    async (confirmationNumber: number, receipt: any) => {
                        if (confirmationNumber === _options.numberOfConfirmation) {
                            const coreContract = this.requestCoreServices.getCoreContractFromRequestId(request.requestId);
                            const event = this.web3Single.decodeEvent(coreContract.abi,
                                                                        'UpdateBalance',
                                                                        receipt.events[0]);
                            try {
                                const requestAfter = await this.getRequest(event.requestId);
                                promiEvent.resolve({request: requestAfter, transaction: {hash: receipt.transactionHash}});
                            } catch (e) {
                                return promiEvent.reject(e);
                            }
                        }
                    },
                    (error: Error) => {
                        return promiEvent.reject(error);
                    },
                    _options);
            } catch (e) {
                promiEvent.reject(e);
            }
        });

        return promiEvent.eventEmitter;
    }

    /**
     * Reduce the amount due to each payee. This can be called by the payee e.g. to apply discounts or
     * special offers.
     *
     * @dev emit the event 'broadcasted' with {transaction: {hash}} when the transaction is submitted
     * @param   _requestId      ID of the Request
     * @param   _amounts        Array of reduction amounts in wei for each payee
     * @param   _options        options for the method (gasPrice, gas, value, from, numberOfConfirmation)
     * @return  promise of the object containing the request and the transaction hash ({request, transactionHash})
     */
    public reduceExpectedAmounts(
        _requestId: string,
        _amounts: any[],
        _options ?: any): PromiseEventEmitter<{request: Request, transaction: any}> {
        const promiEvent = Web3PromiEvent();
        _options = this.web3Single.setUpOptions(_options);

        let amountsParsed: any[] = [];
        if (_amounts) {
            amountsParsed = _amounts.map((amount) => new BN(amount || 0));
        }

        this.web3Single.getDefaultAccountCallback(async (err, defaultAccount) => {
            if (!_options.from && err) return promiEvent.reject(err);
            const account = _options.from || defaultAccount;

            try {
                const request = await this.getRequest(_requestId);

                if (_amounts && request.subPayees.length + 1 < _amounts.length) {
                    return promiEvent.reject(Error('amounts can not be bigger than _payeesIdAddress'));
                }
                if (amountsParsed.filter((amount) => amount.isNeg()).length !== 0) {
                    return promiEvent.reject(Error('amounts must be positive integers'));
                }
                if ( request.state === Types.State.Canceled ) {
                    return promiEvent.reject(Error('request must be accepted or created'));
                }
                if ( !this.web3Single.areSameAddressesNoChecksum(account, request.payee.address) ) {
                    return promiEvent.reject(Error('account must be payee'));
                }
                if (request.payee.expectedAmount.lt(amountsParsed[0])) {
                    return promiEvent.reject(Error('amounts must be lower than expected amounts'));
                }
                let amountTooHigh = false;
                let amountsTooLong = false;
                for (const k in amountsParsed) {
                    if (k === '0') continue;
                    if (!request.subPayees.hasOwnProperty(parseInt(k, 10) - 1)) {
                        amountsTooLong = true;
                        break;
                    }
                    if (request.subPayees[parseInt(k, 10) - 1].expectedAmount.lt(amountsParsed[k])) {
                        amountTooHigh = true;
                        break;
                    }
                }
                if (amountsTooLong) {
                    return promiEvent.reject(Error('amounts size must be lower than number of payees'));
                }
                if (amountTooHigh) {
                    return promiEvent.reject(Error('amounts must be lower than expected amounts'));
                }

                const contract = this.web3Single.getContractInstance(request.currencyContract.address);
                const method = contract.instance.methods.subtractAction(_requestId, amountsParsed);

                this.web3Single.broadcastMethod(
                    method,
                    (hash: string) => {
                        return promiEvent.eventEmitter.emit('broadcasted', {transaction: {hash}});
                    },
                    (receipt: any) => {
                        // we do nothing here!
                    },
                    async (confirmationNumber: number, receipt: any) => {
                        if (confirmationNumber === _options.numberOfConfirmation) {
                            const coreContract = this.requestCoreServices.getCoreContractFromRequestId(request.requestId);
                            const event = this.web3Single.decodeEvent(coreContract.abi,
                                                                        'UpdateExpectedAmount',
                                                                        receipt.events[0]);
                            try {
                                const requestAfter = await this.getRequest(event.requestId);
                                promiEvent.resolve({request: requestAfter, transaction: {hash: receipt.transactionHash}});
                            } catch (e) {
                                return promiEvent.reject(e);
                            }
                        }
                    },
                    (error: Error) => {
                        return promiEvent.reject(error);
                    },
                    _options);
            } catch (e) {
                promiEvent.reject(e);
            }
        });

        return promiEvent.eventEmitter;
    }

    /**
     * add subtracts to a request as payee
     * @deprecated('Renamed to reduceExpectedAmounts')
     * @dev emit the event 'broadcasted' with {transaction: {hash}} when the transaction is submitted
     * @param   _requestId         requestId of the payer
     * @param   _subtracts         amounts of subtracts in wei for each payee
     * @param   _options           options for the method (gasPrice, gas, value, from, numberOfConfirmation)
     * @return  promise of the object containing the request and the transaction hash ({request, transactionHash})
     */
    public subtractAction(
        _requestId: string,
        _subtracts: any[],
        _options ?: any): PromiseEventEmitter<{request: Request, transaction: any}> {

        console.warn('Deprecated. See reduceExpectedAmounts');
        return this.reduceExpectedAmounts(_requestId, _subtracts, _options)
    }

    /**
     * Increase the amount due to each payee. This can be called by the payer e.g. to add extra
     * payments to the Request for tips or bonuses.
     *
     * @dev emit the event 'broadcasted' with {transaction: {hash}} when the transaction is submitted
     * @param   _requestId     ID of Request
     * @param   _amounts       Extra payment amounts in wei for each payee
     * @param   _options       Transaction options (gasPrice, gas, value, from, numberOfConfirmation)
     * @return  promise of the object containing the request and the transaction hash ({request, transactionHash})
     */
    public increaseExpectedAmounts(
        _requestId: string,
        _amounts: any[],
        _options ?: any): PromiseEventEmitter<{request: Request, transaction: any}> {
        const promiEvent = Web3PromiEvent();
        _options = this.web3Single.setUpOptions(_options);

        let amountsParsed: any[] = [];
        if (_amounts) {
            amountsParsed = _amounts.map((amount) => new BN(amount || 0));
        }

        this.web3Single.getDefaultAccountCallback(async (err, defaultAccount) => {
            if (!_options.from && err) return promiEvent.reject(err);
            const account = _options.from || defaultAccount;

            try {
                const request = await this.getRequest(_requestId);

                if (_amounts && request.subPayees.length + 1 < _amounts.length) {
                    return promiEvent.reject(Error('amounts can not be bigger than _payeesIdAddress'));
                }
                if (amountsParsed.filter((amount) => amount.isNeg()).length !== 0) {
                    return promiEvent.reject(Error('amounts must be positive integers'));
                }
                if ( request.state === Types.State.Canceled ) {
                    return promiEvent.reject(Error('request must be accepted or created'));
                }
                if ( !this.web3Single.areSameAddressesNoChecksum(account, request.payer) ) {
                    return promiEvent.reject(Error('account must be payer'));
                }

                let amountsTooLong = false;
                for (const k in amountsParsed) {
                    if (k === '0') continue;
                    if (!request.subPayees.hasOwnProperty(parseInt(k, 10) - 1)) {
                        amountsTooLong = true;
                        break;
                    }
                }
                if (amountsTooLong) {
                    return promiEvent.reject(Error('amounts size must be lower than number of payees'));
                }

                const contract = this.web3Single.getContractInstance(request.currencyContract.address);
                const method = contract.instance.methods.additionalAction(_requestId, amountsParsed);

                this.web3Single.broadcastMethod(
                    method,
                    (hash: string) => {
                        return promiEvent.eventEmitter.emit('broadcasted', {transaction: {hash}});
                    },
                    (receipt: any) => {
                        // we do nothing here!
                    },
                    async (confirmationNumber: number, receipt: any) => {
                        if (confirmationNumber === _options.numberOfConfirmation) {
                            const eventRaw = receipt.events[0];
                            const coreContract = this.requestCoreServices.getCoreContractFromRequestId(request.requestId);
                            const event = this.web3Single.decodeEvent(coreContract.abi,
                                                                        'UpdateExpectedAmount',
                                                                        eventRaw);
                            try {
                                const requestAfter = await this.getRequest(event.requestId);
                                promiEvent.resolve({request: requestAfter, transaction: {hash: receipt.transactionHash}});
                            } catch (e) {
                                return promiEvent.reject(e);
                            }
                        }
                    },
                    (error: Error) => {
                        return promiEvent.reject(error);
                    },
                    _options);
            } catch (e) {
                promiEvent.reject(e);
            }
        });

        return promiEvent.eventEmitter;
    }

    /**
     * add additionals to a request as payer
     * @deprecated('Renamed to increaseExpectedAmounts')
     * @dev emit the event 'broadcasted' with {transaction: {hash}} when the transaction is submitted
     * @param   _requestId         requestId of the payer
     * @param   _additionals       amounts of additionals in wei for each payee
     * @param   _options           options for the method (gasPrice, gas, value, from, numberOfConfirmation)
     * @return  promise of the object containing the request and the transaction hash ({request, transactionHash})
     */
    public additionalAction(
        _requestId: string,
        _additionals: any[],
        _options ?: any): PromiseEventEmitter<{request: Request, transaction: any}> {

        console.warn('Deprecated. See increaseExpectedAmounts');
        return this.increaseExpectedAmounts(_requestId, _additionals, _options)
    }

    /**
     * Get info from currency contract (generic method)
     * @dev return {} always
     * @param   _requestId    requestId of the request
     * @return  promise of the information from the currency contract of the request (always {} here)
     */
    public getRequestCurrencyContractInfo(
        requestData: any,
        coreContract: any): Promise < any > {
        return new Promise(async (resolve, reject) => {
            try {
                const currencyContract = this.web3Single.getContractInstance(requestData.currencyContract);

                let payeePaymentAddress: string|undefined = await currencyContract.instance.methods.payeesPaymentAddress(requestData.requestId, 0).call();
                payeePaymentAddress = payeePaymentAddress !== EMPTY_BYTES_20 ? payeePaymentAddress : undefined;

                // get subPayees payment addresses
                const subPayeesCount = await coreContract.instance.methods.getSubPayeesCount(requestData.requestId).call();
                const subPayeesPaymentAddress: string[] = [];
                for (let i = 0; i < subPayeesCount; i++) {
                    const paymentAddress = await currencyContract.instance.methods.payeesPaymentAddress(requestData.requestId, i + 1).call();
                    subPayeesPaymentAddress.push(paymentAddress !== EMPTY_BYTES_20 ? paymentAddress : undefined);
                }

                let payerRefundAddress: string|undefined = await currencyContract.instance.methods.payerRefundAddress(requestData.requestId).call();
                payerRefundAddress = payerRefundAddress !== EMPTY_BYTES_20 ? payerRefundAddress : undefined;

                requestData.currencyContract = {payeePaymentAddress, subPayeesPaymentAddress, payerRefundAddress, address: requestData.currencyContract};

                return resolve(requestData);
            } catch (e) {
                return reject(e);
            }
        });
    }

    /**
     * alias of requestCoreServices.getRequest()
     */
    public getRequest(_requestId: string): Promise < any > {
        return this.requestCoreServices.getRequest(_requestId);
    }

    /**
     * alias of requestCoreServices.getRequestEvents()
     */
    public getRequestEvents(
        _requestId: string,
        _fromBlock ?: number,
        _toBlock ?: number): Promise < any > {
        return this.requestCoreServices.getRequestEvents(_requestId, _fromBlock, _toBlock);
    }

    /**
     * decode data from input tx (generic method)
     * @param   _data    requestId of the request
     * @return  return an object with the name of the function and the parameters
     */
    public decodeInputData(_address: string, _data: any): any {
        const contract = this.web3Single.getContractInstance(_address);
        return this.web3Single.decodeInputData(contract.abi, _data);
    }

    /**
     * check if a signed request is valid
     * @param   _signedRequest    Signed request
     * @param   _payer             Payer of the request
     * @return  return a string with the error, or ''
     */
    public validateSignedRequest(_signedRequest: any, _payer: string): string {
        _signedRequest.expectedAmounts = _signedRequest.expectedAmounts.map((amount: any) => new BN(amount));

        if (_signedRequest.payeesPaymentAddress) {
            _signedRequest.payeesPaymentAddress = _signedRequest.payeesPaymentAddress.map((addr: any) => addr ? addr : EMPTY_BYTES_20);
        } else {
            _signedRequest.payeesPaymentAddress = [];
        }

        const hashComputed = this.hashRequest(
                        _signedRequest.currencyContract,
                        _signedRequest.payeesIdAddress,
                        _signedRequest.expectedAmounts,
                        '',
                        _signedRequest.payeesPaymentAddress,
                        _signedRequest.data ? _signedRequest.data : '',
                        _signedRequest.expirationDate);

        if (!_signedRequest) {
            return '_signedRequest must be defined';
        }
        // some controls on the arrays
        if (_signedRequest.payeesIdAddress.length !== _signedRequest.expectedAmounts.length) {
            return '_payeesIdAddress and _expectedAmounts must have the same size';
        }

        if (_signedRequest.expectedAmounts.filter((amount: any) => amount.isNeg()).length !== 0) {
            return '_expectedAmounts must be positive integers';
        }
        if (!this.web3Single.areSameAddressesNoChecksum(this.addressRequestEthereumLast, _signedRequest.currencyContract)) {
            return 'currencyContract must be the last currencyContract of requestEthereum';
        }
        if (_signedRequest.expirationDate < (new Date().getTime()) / 1000) {
            return 'expirationDate must be greater than now';
        }
        if (hashComputed !== _signedRequest.hash) {
            return 'hash is not valid';
        }
        if (!this.web3Single.isArrayOfAddressesNoChecksum(_signedRequest.payeesIdAddress)) {
            return 'payeesIdAddress must be valid eth addresses';
        }
        if (!this.web3Single.isArrayOfAddressesNoChecksum(_signedRequest.payeesPaymentAddress)) {
            return 'payeesPaymentAddress must be valid eth addresses';
        }
        if (this.web3Single.areSameAddressesNoChecksum(_payer, _signedRequest.payeesIdAddress[0])) {
            return '_from must be different than main payee';
        }
        if (!this.web3Single.isValidSignatureForSolidity(_signedRequest.signature, _signedRequest.hash, _signedRequest.payeesIdAddress[0])) {
            return 'payee is not the signer';
        }
        return '';
    }

    /**
     * Get request events from currency contract (generic method)
     * @param   _requestId    requestId of the request
     * @param   _fromBlock    search events from this block (optional)
     * @param   _toBlock    search events until this block (optional)
     * @return  promise of the object containing the events from the currency contract of the request (always {} here)
     */
    public async getRequestEventsCurrencyContractInfo(
        _request: any,
        _coreContract: any,
        _fromBlock ?: number,
        _toBlock ?: number): Promise < any > {
        return Promise.resolve([]);
    }

    /**
     * internal create the object signed request
     * @param   currencyContract          Address of the ethereum currency contract
     * @param   payeesIdAddress           ID addresses of the payees (the position 0 will be the main payee, must be the signer address)
     * @param   expectedAmounts           amount initial expected per payees for the request
     * @param   payeesPaymentAddress      payment addresses of the payees (the position 0 will be the main payee)
     * @param   expirationDate            timestamp in second of the date after which the signed request is not broadcastable
     * @param   data                      Json of the request's details (optional)
     * @param   extension                 address of the extension contract of the request (optional) NOT USED YET
     * @param   extensionParams           array of parameters for the extension (optional) NOT USED YET
     * @return  promise of the object containing the request signed
     */
    private async createSignedRequest(
        currencyContract: string,
        payeesIdAddress: string[],
        expectedAmounts: any[],
        payeesPaymentAddress: Array<string|undefined>,
        expirationDate: number,
        data ?: string,
        extension ?: string,
        extensionParams ?: any[]): Promise<any> {

        const hash = this.hashRequest(currencyContract,
                                        payeesIdAddress,
                                        expectedAmounts,
                                        '',
                                        payeesPaymentAddress,
                                        data ? data : '',
                                        expirationDate);

        const signature = await this.web3Single.sign(hash, payeesIdAddress[0]);

        extension = extension ? extension : undefined;
        extensionParams = extension ? extensionParams : undefined;
        data = data ? data : undefined;

        for (const k in expectedAmounts) {
            if (expectedAmounts.hasOwnProperty(k)) {
                expectedAmounts[k] = expectedAmounts[k].toString();
            }
        }

        for (const k in payeesPaymentAddress) {
            if (payeesPaymentAddress.hasOwnProperty(k)) {
                payeesPaymentAddress[k] = payeesPaymentAddress[k] === EMPTY_BYTES_20 ? undefined : payeesPaymentAddress[k];
            }
        }

        return {currencyContract,
                data,
                expectedAmounts,
                expirationDate,
                extension,
                extensionParams,
                hash,
                payeesIdAddress,
                payeesPaymentAddress,
                signature};
    }

    /**
     * internal compute the hash of the request
     * @param   currencyContract          Address of the ethereum currency contract
     * @param   payees                    ID addresses of the payees (the position 0 will be the main payee, must be the signer address)
     * @param   expectedAmounts           amount initial expected per payees for the request
     * @param   payer                     payer of the request
     * @param   payeesPaymentAddress      payment addresses of the payees (the position 0 will be the main payee)
     * @param   data                      Json of the request's details (optional)
     * @param   expirationDate            timestamp in second of the date after which the signed request is not broadcastable
     * @return  promise of the object containing the request's hash
     */
    private hashRequest(currencyContract: string,
                        payees: string[],
                        expectedAmounts: any[],
                        payer: string,
                        payeesPayment: any[],
                        data: string,
                        expirationDate: number): any {
        interface InterfaceAbi {
            value: any;
            type: string;
        }

        const requestParts: InterfaceAbi[] = [
            {value: currencyContract, type: 'address'},
            {value: payees[0], type: 'address'},
            {value: payer, type: 'address'},
            {value: payees.length, type: 'uint8'}];

        for (const k in payees) {
            if (payees.hasOwnProperty(k)) {
                requestParts.push({value: payees[k], type: 'address'});
                requestParts.push({value: expectedAmounts[k], type: 'int256'});
            }
        }

        requestParts.push({value: data.length, type: 'uint8'});
        requestParts.push({value: data, type: 'string'});

        requestParts.push({value: payeesPayment, type: 'address[]'});
        requestParts.push({value: expirationDate, type: 'uint256'});

        const types: any[] = [];
        const values: any[] = [];
        requestParts.forEach((o, i) => {
            types.push(o.type);
            values.push(o.value);
        });

        return this.web3Single.web3.utils.bytesToHex(ETH_ABI.soliditySHA3(types, values));
    }
}

result-matching ""

    No results matching ""