src/servicesContracts/requestEthereum-service.ts
The RequestEthereumService class is the interface for the Request Ethereum currency contract
Properties |
|
Methods |
|
Protected abiRequestCoreLast |
abiRequestCoreLast:
|
Type : any
|
RequestCore contract's abi |
Protected abiRequestEthereumLast |
abiRequestEthereumLast:
|
Type : any
|
RequestEthereum contract's abi |
Protected addressRequestEthereumLast |
addressRequestEthereumLast:
|
Type : string
|
RequestEthereum contract's address |
Protected instanceRequestEthereumLast |
instanceRequestEthereumLast:
|
Type : any
|
RequestEthereum contract's web3 instance |
Protected ipfs |
ipfs:
|
Type : any
|
Protected requestCoreServices |
requestCoreServices:
|
Type : any
|
RequestCore service from this very lib |
Public web3Single |
web3Single:
|
Type : Web3Single
|
Public accept | ||||||||||||
accept(_requestId: string, _options?: any)
|
||||||||||||
accept a request as payer
Parameters :
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 :
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 :
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 :
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
|
||||||||||||||||||||||||||||||||||||||||
create a request as payee
Parameters :
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 :
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 :
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
Returns :
RequestEthereumService
The instance of the RequestEthereumService class. |
Public getRequest | ||||||
getRequest(_requestId: string)
|
||||||
alias of requestCoreServices.getRequest()
Parameters :
Returns :
Promise<any>
|
Public getRequestCurrencyContractInfo |
getRequestCurrencyContractInfo(requestData: any, coreContract: any)
|
Get info from currency contract (generic method)
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()
Returns :
Promise<any>
|
Public Async getRequestEventsCurrencyContractInfo | ||||||||||||||||||||
getRequestEventsCurrencyContractInfo(_request: any, _coreContract: any, _fromBlock?: number, _toBlock?: number)
|
||||||||||||||||||||
Get request events from currency contract (generic method)
Parameters :
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 :
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 :
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 :
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 :
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
|
||||||||||||||||||||||||||||||||||||
sign a request as payee
Parameters :
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 :
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 :
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));
}
}