caver-js
Version:
caver-js is a JavaScript API library that allows developers to interact with a Kaia node
756 lines (707 loc) • 26.6 kB
JavaScript
/*
Copyright 2018 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 RLP = require('eth-lib/lib/rlp')
const Hash = require('eth-lib/lib/hash')
const utils = require('../../../caver-utils')
const {
rlpEncodeForLegacyTransaction,
rlpEncodeForValueTransfer,
rlpEncodeForValueTransferMemo,
rlpEncodeForFeeDelegatedValueTransferMemoWithRatio,
rlpEncodeForFeeDelegatedValueTransfer,
rlpEncodeForFeeDelegatedValueTransferWithRatio,
rlpEncodeForFeeDelegatedValueTransferMemo,
rlpEncodeForAccountUpdate,
rlpEncodeForContractDeploy,
rlpEncodeForContractExecution,
rlpEncodeForFeeDelegatedAccountUpdate,
rlpEncodeForFeeDelegatedAccountUpdateWithRatio,
rlpEncodeForFeeDelegatedSmartContractDeploy,
rlpEncodeForFeeDelegatedSmartContractDeployWithRatio,
rlpEncodeForFeeDelegatedSmartContractExecution,
rlpEncodeForFeeDelegatedSmartContractExecutionWithRatio,
rlpEncodeForCancel,
rlpEncodeForFeeDelegatedCancel,
rlpEncodeForFeeDelegatedCancelWithRatio,
rlpEncodeForChainDataAnchoring,
parseAccountKey,
} = require('./transactionType')
const creationNotSupportError = 'ACCOUNT_CREATION transaction type is not supported yet.'
function encodeRLPByTxType(transaction) {
transaction.type = transaction.senderRawTransaction
? utils.getTxTypeStringFromRawTransaction(transaction.senderRawTransaction)
: transaction.type
switch (transaction.type) {
case 'ACCOUNT_CREATION':
throw new Error(creationNotSupportError)
case 'ACCOUNT_UPDATE':
return rlpEncodeForAccountUpdate(transaction)
case 'FEE_DELEGATED_ACCOUNT_UPDATE':
return rlpEncodeForFeeDelegatedAccountUpdate(transaction)
case 'FEE_DELEGATED_ACCOUNT_UPDATE_WITH_RATIO':
return rlpEncodeForFeeDelegatedAccountUpdateWithRatio(transaction)
case 'VALUE_TRANSFER':
return rlpEncodeForValueTransfer(transaction)
case 'VALUE_TRANSFER_MEMO':
return rlpEncodeForValueTransferMemo(transaction)
case 'FEE_DELEGATED_VALUE_TRANSFER':
return rlpEncodeForFeeDelegatedValueTransfer(transaction)
case 'FEE_DELEGATED_VALUE_TRANSFER_WITH_RATIO':
return rlpEncodeForFeeDelegatedValueTransferWithRatio(transaction)
case 'FEE_DELEGATED_VALUE_TRANSFER_MEMO':
return rlpEncodeForFeeDelegatedValueTransferMemo(transaction)
case 'FEE_DELEGATED_VALUE_TRANSFER_MEMO_WITH_RATIO':
return rlpEncodeForFeeDelegatedValueTransferMemoWithRatio(transaction)
case 'FEE_DELEGATED_SMART_CONTRACT_DEPLOY':
return rlpEncodeForFeeDelegatedSmartContractDeploy(transaction)
case 'SMART_CONTRACT_DEPLOY':
return rlpEncodeForContractDeploy(transaction)
case 'FEE_DELEGATED_SMART_CONTRACT_DEPLOY_WITH_RATIO':
return rlpEncodeForFeeDelegatedSmartContractDeployWithRatio(transaction)
case 'SMART_CONTRACT_EXECUTION':
return rlpEncodeForContractExecution(transaction)
case 'FEE_DELEGATED_SMART_CONTRACT_EXECUTION':
return rlpEncodeForFeeDelegatedSmartContractExecution(transaction)
case 'FEE_DELEGATED_SMART_CONTRACT_EXECUTION_WITH_RATIO':
return rlpEncodeForFeeDelegatedSmartContractExecutionWithRatio(transaction)
case 'CANCEL':
return rlpEncodeForCancel(transaction)
case 'FEE_DELEGATED_CANCEL':
return rlpEncodeForFeeDelegatedCancel(transaction)
case 'FEE_DELEGATED_CANCEL_WITH_RATIO':
return rlpEncodeForFeeDelegatedCancelWithRatio(transaction)
case 'CHAIN_DATA_ANCHORING':
return rlpEncodeForChainDataAnchoring(transaction)
case 'LEGACY':
default:
return rlpEncodeForLegacyTransaction(transaction)
}
}
// case1) sig === [v, r, s]
// case2) sig ===
function makeRawTransaction(rlpEncoded, sig, transaction) {
const decodedValues = RLP.decode(rlpEncoded)
let rawTx
transaction.type = transaction.senderRawTransaction
? utils.getTxTypeStringFromRawTransaction(transaction.senderRawTransaction)
: transaction.type
switch (transaction.type) {
case 'ACCOUNT_CREATION':
throw new Error(creationNotSupportError)
case 'VALUE_TRANSFER':
case 'VALUE_TRANSFER_MEMO':
case 'ACCOUNT_UPDATE':
case 'SMART_CONTRACT_DEPLOY':
case 'SMART_CONTRACT_EXECUTION':
case 'CANCEL':
case 'CHAIN_DATA_ANCHORING':
return _combineSenderRawTransaction(rlpEncoded, sig)
case 'FEE_DELEGATED_VALUE_TRANSFER':
case 'FEE_DELEGATED_VALUE_TRANSFER_WITH_RATIO':
case 'FEE_DELEGATED_VALUE_TRANSFER_MEMO':
case 'FEE_DELEGATED_VALUE_TRANSFER_MEMO_WITH_RATIO':
case 'FEE_DELEGATED_SMART_CONTRACT_DEPLOY':
case 'FEE_DELEGATED_SMART_CONTRACT_DEPLOY_WITH_RATIO':
case 'FEE_DELEGATED_CANCEL':
case 'FEE_DELEGATED_CANCEL_WITH_RATIO':
case 'FEE_DELEGATED_ACCOUNT_UPDATE':
case 'FEE_DELEGATED_ACCOUNT_UPDATE_WITH_RATIO':
case 'FEE_DELEGATED_SMART_CONTRACT_EXECUTION':
case 'FEE_DELEGATED_SMART_CONTRACT_EXECUTION_WITH_RATIO': {
if (transaction.senderRawTransaction) {
const decoded = decodeFromRawTransaction(transaction.senderRawTransaction)
return _combineFeePayerRawTransaction(rlpEncoded, sig, transaction, decoded.signatures)
}
if (
transaction.feePayer &&
transaction.feePayer !== '0x' &&
transaction.feePayer !== '0x0000000000000000000000000000000000000000' &&
transaction.feePayerSignatures
) {
return _combineFeePayerRawTransaction(rlpEncoded, transaction.feePayerSignatures, transaction, sig)
}
return _combineSenderRawTransaction(rlpEncoded, sig)
}
case 'LEGACY':
default:
rawTx = decodedValues.slice(0, 6).concat(sig[0])
return {
rawTransaction: RLP.encode(rawTx),
signatures: sig[0],
feePayerSignatures: undefined,
}
}
}
function _combineSenderRawTransaction(rlpEncoded, sig) {
const decodedValues = RLP.decode(rlpEncoded)
const [data] = decodedValues
let [txType, ...rawTx] = RLP.decode(data)
if (!Array.isArray(sig[0])) sig = [sig]
sig = refineSignatures(sig)
rawTx = [...rawTx, sig]
// set default feepayer's information in rawTx
const typeString = utils.getTxTypeStringFromRawTransaction(txType)
if (typeString !== undefined && typeString.includes('FEE_DELEGATED'))
rawTx = [...rawTx, '0x0000000000000000000000000000000000000000', [['0x01', '0x', '0x']]]
return {
rawTransaction: txType + RLP.encode(rawTx).slice(2),
signatures: sig,
feePayerSignatures: undefined,
}
}
function _combineFeePayerRawTransaction(rlpEncoded, feePayerSignatures, transaction, senderSignature) {
const decodedValues = RLP.decode(rlpEncoded)
const [data] = decodedValues
let [txType, ...rawTx] = RLP.decode(data)
if (!Array.isArray(feePayerSignatures[0])) feePayerSignatures = [feePayerSignatures]
senderSignature = refineSignatures(senderSignature)
feePayerSignatures = refineSignatures(feePayerSignatures)
rawTx = [...rawTx, senderSignature, transaction.feePayer.toLowerCase(), feePayerSignatures]
return {
rawTransaction: txType + RLP.encode(rawTx).slice(2),
signatures: senderSignature,
feePayerSignatures,
}
}
// refineSignatures removes duplication and empty signatures
function refineSignatures(sigArray) {
const set = new Set()
let result = []
for (const sig of sigArray) {
if (sig.length > 0 && !utils.isEmptySig(sig)) {
const sigString = sig.join('')
if (!set.has(sigString)) {
set.add(sigString, true)
result.push(sig)
}
}
}
if (result.length === 0) result = [['0x01', '0x', '0x']]
return result
}
function extractSignatures(rawTransaction) {
let senderSignatures = []
let feePayerSignatures = []
const decoded = _decodeFromRawTransaction(rawTransaction)
senderSignatures = senderSignatures.concat(decoded.signatures)
if (decoded.feePayerSignatures) {
feePayerSignatures = feePayerSignatures.concat(decoded.feePayerSignatures)
}
return { senderSignatures, feePayerSignatures, decodedTransaction: decoded }
}
function splitFeePayer(rawTransaction) {
const typeString = utils.getTxTypeStringFromRawTransaction(rawTransaction)
if (!typeString || !typeString.includes('FEE_DELEGATED'))
throw new Error(`Failed to split fee payer: not a fee delegated transaction type('${typeString || 'LEGACY'}')`)
const txType = rawTransaction.slice(0, 4)
const decodedValues = RLP.decode(utils.addHexPrefix(rawTransaction.slice(4)))
const detachFeePayer = decodedValues.splice(0, decodedValues.length - 2)
detachFeePayer.push('0x0000000000000000000000000000000000000000')
detachFeePayer.push([['0x01', '0x', '0x']])
return {
senderRawTransaction: txType + RLP.encode(detachFeePayer).slice(2),
feePayer: decodedValues[0],
feePayerSignatures: decodedValues[1],
}
}
function decodeFromRawTransaction(rawTransaction, type) {
let decodeResult = _decodeFromRawTransaction(rawTransaction, type)
switch (decodeResult.type) {
case 'ACCOUNT_UPDATE':
case 'FEE_DELEGATED_ACCOUNT_UPDATE':
case 'FEE_DELEGATED_ACCOUNT_UPDATE_WITH_RATIO': {
decodeResult = parseAccountKey(decodeResult)
}
}
return decodeResult
}
function _decodeFromRawTransaction(rawTransaction, type) {
let typeString = type
if (typeString === undefined || typeString !== 'LEGACY') {
typeString = utils.getTxTypeStringFromRawTransaction(rawTransaction)
if (typeString === undefined) {
typeString = 'LEGACY'
} else {
rawTransaction = `0x${rawTransaction.slice(4)}`
}
}
switch (typeString) {
case 'LEGACY': {
const [nonce, gasPrice, gas, to, value, data, v, r, s] = RLP.decode(rawTransaction)
return {
type: typeString,
nonce,
gasPrice,
gas,
to,
value,
data,
v,
r,
s,
signatures: [v, r, s],
}
}
case 'VALUE_TRANSFER': {
const [nonce, gasPrice, gas, to, value, from, signatures] = RLP.decode(rawTransaction)
return {
type: typeString,
nonce,
gasPrice,
gas,
to,
value,
from,
v: signatures[0][0],
r: signatures[0][1],
s: signatures[0][2],
signatures,
}
}
case 'FEE_DELEGATED_VALUE_TRANSFER': {
const [nonce, gasPrice, gas, to, value, from, signatures, feePayer, feePayerSignatures] = RLP.decode(rawTransaction)
return {
type: typeString,
nonce,
gasPrice,
gas,
to,
value,
from,
v: signatures[0][0],
r: signatures[0][1],
s: signatures[0][2],
signatures,
feePayer,
payerV: feePayerSignatures[0][0],
payerR: feePayerSignatures[0][1],
payerS: feePayerSignatures[0][2],
feePayerSignatures,
}
}
case 'FEE_DELEGATED_VALUE_TRANSFER_WITH_RATIO': {
const [nonce, gasPrice, gas, to, value, from, feeRatio, signatures, feePayer, feePayerSignatures] = RLP.decode(rawTransaction)
return {
type: typeString,
nonce,
gasPrice,
gas,
to,
value,
from,
feeRatio,
v: signatures[0][0],
r: signatures[0][1],
s: signatures[0][2],
signatures,
feePayer,
payerV: feePayerSignatures[0][0],
payerR: feePayerSignatures[0][1],
payerS: feePayerSignatures[0][2],
feePayerSignatures,
}
}
case 'VALUE_TRANSFER_MEMO': {
const [nonce, gasPrice, gas, to, value, from, data, signatures] = RLP.decode(rawTransaction)
return {
type: typeString,
nonce,
gasPrice,
gas,
to,
value,
from,
data,
v: signatures[0][0],
r: signatures[0][1],
s: signatures[0][2],
signatures,
}
}
case 'FEE_DELEGATED_VALUE_TRANSFER_MEMO': {
const [nonce, gasPrice, gas, to, value, from, data, signatures, feePayer, feePayerSignatures] = RLP.decode(rawTransaction)
return {
type: typeString,
nonce,
gasPrice,
gas,
to,
value,
from,
data,
v: signatures[0][0],
r: signatures[0][1],
s: signatures[0][2],
signatures,
feePayer,
payerV: feePayerSignatures[0][0],
payerR: feePayerSignatures[0][1],
payerS: feePayerSignatures[0][2],
feePayerSignatures,
}
}
case 'FEE_DELEGATED_VALUE_TRANSFER_MEMO_WITH_RATIO': {
const [nonce, gasPrice, gas, to, value, from, data, feeRatio, signatures, feePayer, feePayerSignatures] = RLP.decode(
rawTransaction
)
return {
type: typeString,
nonce,
gasPrice,
gas,
to,
value,
from,
data,
feeRatio,
v: signatures[0][0],
r: signatures[0][1],
s: signatures[0][2],
signatures,
feePayer,
payerV: feePayerSignatures[0][0],
payerR: feePayerSignatures[0][1],
payerS: feePayerSignatures[0][2],
feePayerSignatures,
}
}
case 'ACCOUNT_CREATION': {
throw new Error(creationNotSupportError)
}
case 'ACCOUNT_UPDATE': {
const [nonce, gasPrice, gas, from, accountKey, signatures] = RLP.decode(rawTransaction)
return {
type: typeString,
nonce,
gasPrice,
gas,
from,
accountKey,
v: signatures[0][0],
r: signatures[0][1],
s: signatures[0][2],
signatures,
}
}
case 'FEE_DELEGATED_ACCOUNT_UPDATE': {
const [nonce, gasPrice, gas, from, accountKey, signatures, feePayer, feePayerSignatures] = RLP.decode(rawTransaction)
return {
type: typeString,
nonce,
gasPrice,
gas,
from,
accountKey,
v: signatures[0][0],
r: signatures[0][1],
s: signatures[0][2],
signatures,
feePayer,
payerV: feePayerSignatures[0][0],
payerR: feePayerSignatures[0][1],
payerS: feePayerSignatures[0][2],
feePayerSignatures,
}
}
case 'FEE_DELEGATED_ACCOUNT_UPDATE_WITH_RATIO': {
const [nonce, gasPrice, gas, from, accountKey, feeRatio, signatures, feePayer, feePayerSignatures] = RLP.decode(rawTransaction)
return {
type: typeString,
nonce,
gasPrice,
gas,
from,
accountKey,
feeRatio,
v: signatures[0][0],
r: signatures[0][1],
s: signatures[0][2],
signatures,
feePayer,
payerV: feePayerSignatures[0][0],
payerR: feePayerSignatures[0][1],
payerS: feePayerSignatures[0][2],
feePayerSignatures,
}
}
case 'SMART_CONTRACT_DEPLOY': {
const [nonce, gasPrice, gas, to, value, from, data, humanReadable, codeFormat, signatures] = RLP.decode(rawTransaction)
return {
type: typeString,
nonce,
gasPrice,
gas,
to,
value,
from,
data,
humanReadable: humanReadable === '0x01',
codeFormat,
v: signatures[0][0],
r: signatures[0][1],
s: signatures[0][2],
signatures,
}
}
case 'FEE_DELEGATED_SMART_CONTRACT_DEPLOY': {
const [
nonce,
gasPrice,
gas,
to,
value,
from,
data,
humanReadable,
codeFormat,
signatures,
feePayer,
feePayerSignatures,
] = RLP.decode(rawTransaction)
return {
type: typeString,
nonce,
gasPrice,
gas,
to,
value,
from,
data,
humanReadable: humanReadable === '0x01',
codeFormat,
v: signatures[0][0],
r: signatures[0][1],
s: signatures[0][2],
signatures,
feePayer,
payerV: feePayerSignatures[0][0],
payerR: feePayerSignatures[0][1],
payerS: feePayerSignatures[0][2],
feePayerSignatures,
}
}
case 'FEE_DELEGATED_SMART_CONTRACT_DEPLOY_WITH_RATIO': {
const [
nonce,
gasPrice,
gas,
to,
value,
from,
data,
humanReadable,
feeRatio,
codeFormat,
signatures,
feePayer,
feePayerSignatures,
] = RLP.decode(rawTransaction)
return {
type: typeString,
nonce,
gasPrice,
gas,
to,
value,
from,
data,
humanReadable: humanReadable === '0x01',
feeRatio,
codeFormat,
v: signatures[0][0],
r: signatures[0][1],
s: signatures[0][2],
signatures,
feePayer,
payerV: feePayerSignatures[0][0],
payerR: feePayerSignatures[0][1],
payerS: feePayerSignatures[0][2],
feePayerSignatures,
}
}
case 'SMART_CONTRACT_EXECUTION': {
const [nonce, gasPrice, gas, to, value, from, data, signatures] = RLP.decode(rawTransaction)
return {
type: typeString,
nonce,
gasPrice,
gas,
to,
value,
from,
data,
v: signatures[0][0],
r: signatures[0][1],
s: signatures[0][2],
signatures,
}
}
case 'FEE_DELEGATED_SMART_CONTRACT_EXECUTION': {
const [nonce, gasPrice, gas, to, value, from, data, signatures, feePayer, feePayerSignatures] = RLP.decode(rawTransaction)
return {
type: typeString,
nonce,
gasPrice,
gas,
to,
value,
from,
data,
v: signatures[0][0],
r: signatures[0][1],
s: signatures[0][2],
signatures,
feePayer,
payerV: feePayerSignatures[0][0],
payerR: feePayerSignatures[0][1],
payerS: feePayerSignatures[0][2],
feePayerSignatures,
}
}
case 'FEE_DELEGATED_SMART_CONTRACT_EXECUTION_WITH_RATIO': {
const [nonce, gasPrice, gas, to, value, from, data, feeRatio, signatures, feePayer, feePayerSignatures] = RLP.decode(
rawTransaction
)
return {
type: typeString,
nonce,
gasPrice,
gas,
to,
value,
from,
data,
feeRatio,
v: signatures[0][0],
r: signatures[0][1],
s: signatures[0][2],
signatures,
feePayer,
payerV: feePayerSignatures[0][0],
payerR: feePayerSignatures[0][1],
payerS: feePayerSignatures[0][2],
feePayerSignatures,
}
}
case 'CANCEL': {
const [nonce, gasPrice, gas, from, signatures] = RLP.decode(rawTransaction)
return {
type: typeString,
nonce,
gasPrice,
gas,
from,
v: signatures[0][0],
r: signatures[0][1],
s: signatures[0][2],
signatures,
}
}
case 'FEE_DELEGATED_CANCEL': {
const [nonce, gasPrice, gas, from, signatures, feePayer, feePayerSignatures] = RLP.decode(rawTransaction)
return {
type: typeString,
nonce,
gasPrice,
gas,
from,
v: signatures[0][0],
r: signatures[0][1],
s: signatures[0][2],
signatures,
feePayer,
payerV: feePayerSignatures[0][0],
payerR: feePayerSignatures[0][1],
payerS: feePayerSignatures[0][2],
feePayerSignatures,
}
}
case 'FEE_DELEGATED_CANCEL_WITH_RATIO': {
const [nonce, gasPrice, gas, from, feeRatio, signatures, feePayer, feePayerSignatures] = RLP.decode(rawTransaction)
return {
type: typeString,
nonce,
gasPrice,
gas,
from,
feeRatio,
v: signatures[0][0],
r: signatures[0][1],
s: signatures[0][2],
signatures,
feePayer,
payerV: feePayerSignatures[0][0],
payerR: feePayerSignatures[0][1],
payerS: feePayerSignatures[0][2],
feePayerSignatures,
}
}
case 'CHAIN_DATA_ANCHORING': {
const [nonce, gasPrice, gas, from, data, signatures] = RLP.decode(rawTransaction)
return {
type: typeString,
nonce,
gasPrice,
gas,
from,
data: data,
v: signatures[0][0],
r: signatures[0][1],
s: signatures[0][2],
signatures,
}
}
}
}
function overwriteSignature(rawTransaction, txObj, signature, feePayerSignature) {
if (signature === undefined) {
signature = [txObj.v, txObj.r, txObj.s]
}
if (txObj.type.includes('FEE_DELEGATED') && feePayerSignature === undefined) {
feePayerSignature = [txObj.payerV, txObj.payerR, txObj.payerS]
}
if (txObj.type === 'LEGACY') {
let decodeLegacy = RLP.decode(rawTransaction)
decodeLegacy = decodeLegacy.slice(0, 6).concat(signature)
return RLP.encode(decodeLegacy)
}
const type = rawTransaction.slice(0, 4)
const typeDetached = `0x${rawTransaction.slice(4)}`
const data = RLP.decode(typeDetached)
if (txObj.type.includes('FEE_DELEGATED')) {
data[data.length - 3] = [signature]
data[data.length - 1] = [feePayerSignature]
} else {
data[data.length - 1] = [signature]
}
return type + RLP.encode(data).slice(2)
}
function getSenderTxHash(rawTransaction) {
const typeString = utils.getTxTypeStringFromRawTransaction(rawTransaction)
if (typeString === undefined || !typeString.includes('FEE_DELEGATED')) return Hash.keccak256(rawTransaction)
const type = rawTransaction.slice(0, 4)
const typeDetached = `0x${rawTransaction.slice(4)}`
const data = RLP.decode(typeDetached)
return Hash.keccak256(type + RLP.encode(data.slice(0, data.length - 2)).slice(2))
}
module.exports = {
encodeRLPByTxType,
makeRawTransaction,
decodeFromRawTransaction,
overwriteSignature,
getSenderTxHash,
splitFeePayer,
extractSignatures,
}