six-caver-js
Version:
caver-js is a JavaScript API library that allows developers to interact with a Klaytn node
218 lines (181 loc) • 9.46 kB
JavaScript
/*
Copyright 2020 The caver-js Authors
This file is part of the caver-js library.
The caver-js library is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
The caver-js library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with the caver-js. If not, see <http://www.gnu.org/licenses/>.
*/
const _ = require('lodash')
const RLP = require('eth-lib/lib/rlp')
const Hash = require('eth-lib/lib/hash')
const Bytes = require('eth-lib/lib/bytes')
const TransactionHasher = require('../transactionHasher/transactionHasher')
const AbstractTransaction = require('./abstractTransaction')
const { refineSignatures, typeDetectionFromRLPEncoding } = require('../transactionHelper/transactionHelper')
const Keyring = require('../../../caver-wallet/src/keyring/keyringFactory')
const SingleKeyring = require('../../../caver-wallet/src/keyring/singleKeyring')
const MultipleKeyring = require('../../../caver-wallet/src/keyring/multipleKeyring')
const RoleBasedKeyring = require('../../../caver-wallet/src/keyring/roleBasedKeyring')
const { KEY_ROLE } = require('../../../caver-wallet/src/keyring/keyringHelper')
const utils = require('../../../caver-utils/src')
const SignatureData = require('../../../caver-wallet/src/keyring/signatureData')
/**
* Abstract class that implements common logic for each fee delegated transaction type.
* @class
*/
class AbstractFeeDelegatedTransaction extends AbstractTransaction {
/**
* Abstract class that implements common logic for each fee-delegated transaction type.
* In this constructor, feePayer and feePayerSignatures are set as transaction member variables.
*
* @constructor
* @param {string} typeString - The type string of transaction.
* @param {object} createTxObj - The parameters to create an instance of transaction.
*/
constructor(typeString, createTxObj) {
super(typeString, createTxObj)
this.feePayer = createTxObj.feePayer
this.feePayerSignatures = createTxObj.feePayerSignatures || []
}
/**
* @type {string}
*/
get feePayer() {
return this._feePayer
}
set feePayer(f) {
if (!f || f === '0x') f = '0x0000000000000000000000000000000000000000'
if (!utils.isAddress(f)) throw new Error(`Invalid address of fee payer: ${f}`)
this._feePayer = f.toLowerCase()
}
/**
* @type {Array.<Array.<string>>}
*/
get feePayerSignatures() {
return this._feePayerSignatures
}
set feePayerSignatures(sigs) {
this._feePayerSignatures = refineSignatures(sigs)
}
/**
* Signs to the transaction with private key(s) in `key` as a fee payer.
* @async
* @param {Keyring|string} key - The instance of Keyring, private key string or KlaytnWalletKey string.
* @param {number} [index] - The index of private key to use. If index is undefined, all private keys in keyring will be used.
* @param {function} [hasher] - The function to get the transaction hash.
* @return {Transaction}
*/
async signAsFeePayer(key, index, hasher = TransactionHasher.getHashForFeePayerSignature) {
// User parameter input cases
// (key) / (key index) / (key hasher) / (key index hasher)
if (_.isFunction(index)) {
hasher = index
index = undefined
}
let keyring = key
if (_.isString(key)) {
keyring = Keyring.createFromPrivateKey(key)
}
if (!(keyring instanceof SingleKeyring) && !(keyring instanceof MultipleKeyring) && !(keyring instanceof RoleBasedKeyring))
throw new Error(
`Unsupported key type. The key parameter of the signAsFeePayer must be a single private key string, KlaytnWalletKey string, or Keyring instance.`
)
if (!this.feePayer || this.feePayer === '0x' || this.feePayer === '0x0000000000000000000000000000000000000000')
this.feePayer = keyring.address
if (this.feePayer.toLowerCase() !== keyring.address.toLowerCase())
throw new Error(`The feePayer address of the transaction is different with the address of the keyring to use.`)
await this.fillTransaction()
const hash = hasher(this)
const sig = keyring.sign(hash, this.chainId, KEY_ROLE.roleFeePayerKey, index)
this.appendFeePayerSignatures(sig)
return this
}
/**
* Appends feePayerSignatures to the transaction.
*
* @param {SignatureData|Array.<SignatureData>|Array.<string>|Array.<Array.<string>>} signatures - An array of feePayerSignatures to append to the transaction.
* One feePayerSignature can be defined in the form of a one-dimensional array or two-dimensional array,
* and more than one feePayerSignatures should be defined in the form of a two-dimensional array.
*/
appendFeePayerSignatures(signatures) {
let sig = signatures
if (_.isString(sig)) sig = utils.resolveSignature(sig)
if (sig instanceof SignatureData) sig = [sig]
if (!_.isArray(sig)) throw new Error(`Failed to append signatures: invalid signatures format ${sig}`)
if (_.isString(sig[0])) sig = [sig]
this.feePayerSignatures = this.feePayerSignatures.concat(sig)
}
/**
* Combines RLP-encoded transactions (rawTransaction) to the transaction from RLP-encoded transaction strings and returns a single transaction with all signatures combined.
* When combining the signatures into a transaction instance,
* an error is thrown if the decoded transaction contains different value except signatures.
*
* @param {Array.<string>} rlpEncodedTxs - An array of RLP-encoded transaction strings.
* @return {string}
*/
combineSignedRawTransactions(rlpEncodedTxs) {
if (!_.isArray(rlpEncodedTxs)) throw new Error(`The parameter must be an array of RLP encoded transaction strings.`)
// If the signatures are empty, there may be an undefined member variable.
// In this case, the empty information is filled with the decoded result.
let fillVariables = false
if (utils.isEmptySig(this.signatures) || utils.isEmptySig(this.feePayerSignatures)) fillVariables = true
for (const encoded of rlpEncodedTxs) {
const type = typeDetectionFromRLPEncoding(encoded)
if (this.type !== type) throw new Error(`Transaction type mismatch: Signatures from different transactions cannot be combined.`)
const decoded = this.constructor.decode(encoded)
// Signatures can only be combined for the same transaction.
// Therefore, compare whether the decoded transaction is the same as this.
for (const k in decoded) {
if (k === '_signatures' || k === '_feePayerSignatures') continue
if (k === '_feePayer') {
const emtpyAddress = '0x0000000000000000000000000000000000000000'
if (
((decoded[k] !== '0x' && decoded[k] !== emtpyAddress) || (this[k] === '0x' || this[k] === emtpyAddress)) &&
fillVariables
)
this[k] = decoded[k]
if (decoded[k] === '0x' || decoded[k] === emtpyAddress) continue
}
if (this[k] === undefined && fillVariables) this[k] = decoded[k]
const differentTxError = `Transactions containing different information cannot be combined.`
// Compare with the RLP-encoded accountKey string, because 'account' is an object.
if (k === '_account') {
if (this[k].getRLPEncodingAccountKey() !== decoded[k].getRLPEncodingAccountKey()) throw new Error(differentTxError)
continue
}
if (this[k] !== decoded[k]) throw new Error(differentTxError)
}
this.appendSignatures(decoded.signatures)
this.appendFeePayerSignatures(decoded.feePayerSignatures)
}
return this.getRLPEncoding()
}
/**
* Returns a senderTxHash of transaction
*
* @return {string}
*/
getSenderTxHash() {
const rlpEncoded = this.getRLPEncoding()
const type = rlpEncoded.slice(0, 4)
const typeDetached = `0x${rlpEncoded.slice(4)}`
const data = RLP.decode(typeDetached)
return Hash.keccak256(type + RLP.encode(data.slice(0, data.length - 2)).slice(2))
}
/**
* Returns an RLP-encoded transaction string for making signature as a fee payer
*
* @return {string}
*/
getRLPEncodingForFeePayerSignature() {
return RLP.encode([this.getCommonRLPEncodingForSignature(), this.feePayer, Bytes.fromNat(this.chainId), '0x', '0x'])
}
}
module.exports = AbstractFeeDelegatedTransaction