@aeternity/aepp-sdk
Version:
SDK for the æternity blockchain
544 lines (486 loc) • 19.1 kB
JavaScript
/*
* ISC License (ISC)
* Copyright (c) 2018 aeternity developers
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
* REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
* AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
* INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
* LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
* OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
* PERFORMANCE OF THIS SOFTWARE.
*/
/**
* Transaction module
* @module @aeternity/aepp-sdk/es/tx/tx
* @export Transaction
* @example import { Transaction } from '@aeternity/aepp-sdk'
*/
import * as R from 'ramda'
import ChainNode from '../chain/node'
import Tx from './'
import { buildTx, calculateFee } from './builder'
import { ABI_VERSIONS, MIN_GAS_PRICE, PROTOCOL_VM_ABI, TX_TYPE, TX_TTL } from './builder/schema'
import { buildContractId } from './builder/helpers'
import { TxObject } from './tx-object'
async function spendTx ({ senderId, recipientId, amount, payload = '' }) {
// Calculate fee, get absolute ttl (ttl + height), get account nonce
const { fee, ttl, nonce } = await this.prepareTxParams(TX_TYPE.spend, { senderId, ...R.head(arguments), payload })
// Build transaction using sdk (if nativeMode) or build on `AETERNITY NODE` side
const { tx } = this.nativeMode
? {
tx: TxObject({
params: R.merge(R.head(arguments), {
recipientId,
senderId,
nonce,
ttl,
payload
}),
type: TX_TYPE.spend
}).encodedTx
}
: await this.api.postSpend(R.merge(R.head(arguments), {
amount: parseInt(amount),
recipientId,
senderId,
nonce,
ttl,
fee: parseInt(fee),
payload
}))
return tx
}
async function namePreclaimTx ({ accountId, commitmentId }) {
// Calculate fee, get absolute ttl (ttl + height), get account nonce
const { fee, ttl, nonce } = await this.prepareTxParams(TX_TYPE.namePreClaim, { senderId: accountId, ...R.head(arguments) })
// Build transaction using sdk (if nativeMode) or build on `AETERNITY NODE` side
const { tx } = this.nativeMode
? {
tx: TxObject({
params: R.merge(R.head(arguments), { nonce, ttl, fee }),
type: TX_TYPE.namePreClaim
}).encodedTx
}
: await this.api.postNamePreclaim(R.merge(R.head(arguments), { nonce, ttl, fee: parseInt(fee) }))
return tx
}
async function nameClaimTx ({ accountId, name, nameSalt, vsn = 2 }) {
// Calculate fee, get absolute ttl (ttl + height), get account nonce
const { fee, ttl, nonce } = await this.prepareTxParams(TX_TYPE.nameClaim, { senderId: accountId, ...R.head(arguments), vsn })
// Build transaction using sdk (if nativeMode) or build on `AETERNITY NODE` side
const { tx } = this.nativeMode
? {
tx: TxObject({
params: R.merge(R.head(arguments), { nonce, ttl, fee, vsn }),
type: TX_TYPE.nameClaim
}).encodedTx
}
: await this.api.postNameClaim(R.merge(R.head(arguments), { nonce, ttl, fee: parseInt(fee) }))
return tx
}
async function nameTransferTx ({ accountId, nameId, recipientId }) {
// Calculate fee, get absolute ttl (ttl + height), get account nonce
const { fee, ttl, nonce } = await this.prepareTxParams(TX_TYPE.nameTransfer, { senderId: accountId, ...R.head(arguments) })
// Build transaction using sdk (if nativeMode) or build on `AETERNITY NODE` side
const { tx } = this.nativeMode
? {
tx: TxObject({
params: R.merge(R.head(arguments), { recipientId, nonce, ttl, fee }),
type: TX_TYPE.nameTransfer
}).encodedTx
}
: await this.api.postNameTransfer(R.merge(R.head(arguments), { recipientId, nonce, ttl, fee: parseInt(fee) }))
return tx
}
async function nameUpdateTx ({ accountId, nameId, nameTtl, pointers, clientTtl }) {
// Calculate fee, get absolute ttl (ttl + height), get account nonce
const { fee, ttl, nonce } = await this.prepareTxParams(TX_TYPE.nameUpdate, { senderId: accountId, ...R.head(arguments) })
// Build transaction using sdk (if nativeMode) or build on `AETERNITY NODE` side
const { tx } = this.nativeMode
? {
tx: TxObject({
params: R.merge(R.head(arguments), { nonce, ttl, fee }),
type: TX_TYPE.nameUpdate
}).encodedTx
}
: await this.api.postNameUpdate(R.merge(R.head(arguments), { nonce, ttl, fee: parseInt(fee) }))
return tx
}
async function nameRevokeTx ({ accountId, nameId }) {
// Calculate fee, get absolute ttl (ttl + height), get account nonce
const { fee, ttl, nonce } = await this.prepareTxParams(TX_TYPE.nameRevoke, { senderId: accountId, ...R.head(arguments) })
// Build transaction using sdk (if nativeMode) or build on `AETERNITY NODE` side
const { tx } = this.nativeMode
? {
tx: TxObject({
params: R.merge(R.head(arguments), { nonce, ttl, fee }),
type: TX_TYPE.nameRevoke
}).encodedTx
}
: await this.api.postNameRevoke(R.merge(R.head(arguments), { nonce, ttl, fee: parseInt(fee) }))
return tx
}
async function contractCreateTx ({ ownerId, code, vmVersion, abiVersion, deposit, amount, gas, gasPrice = MIN_GAS_PRICE, callData }) {
// Get VM_ABI version
const ctVersion = this.getVmVersion(TX_TYPE.contractCreate, R.head(arguments))
// Calculate fee, get absolute ttl (ttl + height), get account nonce
const { fee, ttl, nonce } = await this.prepareTxParams(TX_TYPE.contractCreate, { senderId: ownerId, ...R.head(arguments), ctVersion, gasPrice })
// Build transaction using sdk (if nativeMode) or build on `AETERNITY NODE` side
return this.nativeMode
? {
tx: TxObject({
params: R.merge(R.head(arguments), { nonce, ttl, fee, ctVersion, gasPrice }),
type: TX_TYPE.contractCreate
}).encodedTx,
contractId: buildContractId(ownerId, nonce)
}
: this.api.postContractCreate(R.merge(R.head(arguments), { nonce, ttl, fee: parseInt(fee), gas: parseInt(gas), gasPrice, vmVersion: ctVersion.vmVersion, abiVersion: ctVersion.abiVersion }))
}
async function contractCallTx ({ callerId, contractId, abiVersion, amount, gas, gasPrice = MIN_GAS_PRICE, callData }) {
const ctVersion = this.getVmVersion(TX_TYPE.contractCall, R.head(arguments))
// Calculate fee, get absolute ttl (ttl + height), get account nonce
const { fee, ttl, nonce } = await this.prepareTxParams(TX_TYPE.contractCall, { senderId: callerId, ...R.head(arguments), gasPrice, abiVersion: ctVersion.abiVersion })
// Build transaction using sdk (if nativeMode) or build on `AETERNITY NODE` side
const { tx } = this.nativeMode
? {
tx: TxObject({
params: R.merge(R.head(arguments), { nonce, ttl, fee, abiVersion: ctVersion.abiVersion, gasPrice }),
type: TX_TYPE.contractCall
}).encodedTx
}
: await this.api.postContractCall(R.merge(R.head(arguments), {
nonce,
ttl,
fee: parseInt(fee),
gas: parseInt(gas),
gasPrice,
abiVersion: ctVersion.abiVersion
}))
return tx
}
async function oracleRegisterTx ({ accountId, queryFormat, responseFormat, queryFee, oracleTtl, abiVersion = ABI_VERSIONS.NO_ABI }) {
// const { abiVersion: abi } = this.getVmVersion(TX_TYPE.oracleRegister, R.head(arguments))
// Calculate fee, get absolute ttl (ttl + height), get account nonce
const { fee, ttl, nonce } = await this.prepareTxParams(TX_TYPE.oracleRegister, { senderId: accountId, ...R.head(arguments), abiVersion })
// Build transaction using sdk (if nativeMode) or build on `AETERNITY NODE` side
const { tx } = this.nativeMode
? {
tx: TxObject({
params: {
accountId,
queryFee,
abiVersion,
fee,
oracleTtl,
nonce,
ttl,
queryFormat,
responseFormat
},
type: TX_TYPE.oracleRegister
}).encodedTx
}
: await this.api.postOracleRegister({
accountId,
queryFee,
abiVersion,
fee: parseInt(fee),
oracleTtl,
nonce,
ttl,
queryFormat,
responseFormat
})
return tx
}
async function oracleExtendTx ({ oracleId, callerId, oracleTtl }) {
// Calculate fee, get absolute ttl (ttl + height), get account nonce
const { fee, ttl, nonce } = await this.prepareTxParams(TX_TYPE.oracleExtend, { senderId: callerId, ...R.head(arguments) })
// Build transaction using sdk (if nativeMode) or build on `AETERNITY NODE` side
const { tx } = this.nativeMode
? {
tx: TxObject({
params: { oracleId, fee, oracleTtl, nonce, ttl },
type: TX_TYPE.oracleExtend
}).encodedTx
}
: await this.api.postOracleExtend({ oracleId, fee: parseInt(fee), oracleTtl, nonce, ttl })
return tx
}
async function oraclePostQueryTx ({ oracleId, responseTtl, query, queryTtl, queryFee, senderId }) {
// Calculate fee, get absolute ttl (ttl + height), get account nonce
const { fee, ttl, nonce } = await this.prepareTxParams(TX_TYPE.oracleQuery, { senderId, ...R.head(arguments) })
// Build transaction using sdk (if nativeMode) or build on `AETERNITY NODE` side
const { tx } = this.nativeMode
? {
tx: TxObject({
params: { oracleId, responseTtl, query, queryTtl, fee, queryFee, ttl, nonce, senderId },
type: TX_TYPE.oracleQuery
}).encodedTx
}
: await this.api.postOracleQuery({
oracleId,
responseTtl,
query,
queryTtl,
fee: parseInt(fee),
queryFee,
ttl,
nonce,
senderId
})
return tx
}
async function oracleRespondTx ({ oracleId, callerId, responseTtl, queryId, response }) {
// Calculate fee, get absolute ttl (ttl + height), get account nonce
const { fee, ttl, nonce } = await this.prepareTxParams(TX_TYPE.oracleResponse, { senderId: callerId, ...R.head(arguments) })
// Build transaction using sdk (if nativeMode) or build on `AETERNITY NODE` side
const { tx } = this.nativeMode
? {
tx: TxObject({
params: { oracleId, responseTtl, queryId, response, fee, ttl, nonce },
type: TX_TYPE.oracleResponse
}).encodedTx
}
: await this.api.postOracleRespond({ oracleId, responseTtl, queryId, response, fee: parseInt(fee), ttl, nonce })
return tx
}
async function channelCloseSoloTx ({ channelId, fromId, payload, poi }) {
// Calculate fee, get absolute ttl (ttl + height), get account nonce
const { fee, ttl, nonce } = await this.prepareTxParams(TX_TYPE.channelCloseSolo, { senderId: fromId, ...R.head(arguments), payload })
// Build transaction using sdk (if nativeMode) or build on `AETERNITY NODE` side
const { tx } = this.nativeMode
? buildTx(R.merge(R.head(arguments), {
channelId,
fromId,
payload,
poi,
ttl,
fee,
nonce
}), TX_TYPE.channelCloseSolo)
: await this.api.postChannelCloseSolo(R.merge(R.head(arguments), {
channelId,
fromId,
payload,
poi,
ttl,
fee: parseInt(fee),
nonce
}))
return tx
}
async function channelSlashTx ({ channelId, fromId, payload, poi }) {
// Calculate fee, get absolute ttl (ttl + height), get account nonce
const { fee, ttl, nonce } = await this.prepareTxParams(TX_TYPE.channelSlash, { senderId: fromId, ...R.head(arguments), payload })
// Build transaction using sdk (if nativeMode) or build on `AETERNITY NODE` side
const { tx } = this.nativeMode
? buildTx(R.merge(R.head(arguments), {
channelId,
fromId,
payload,
poi,
ttl,
fee,
nonce
}), TX_TYPE.channelSlash)
: await this.api.postChannelSlash(R.merge(R.head(arguments), {
channelId,
fromId,
payload,
poi,
ttl,
fee: parseInt(fee),
nonce
}))
return tx
}
async function channelSettleTx ({ channelId, fromId, initiatorAmountFinal, responderAmountFinal }) {
// Calculate fee, get absolute ttl (ttl + height), get account nonce
const { fee, ttl, nonce } = await this.prepareTxParams(TX_TYPE.channelSettle, { senderId: fromId, ...R.head(arguments) })
// Build transaction using sdk (if nativeMode) or build on `AETERNITY NODE` side
const { tx } = this.nativeMode
? buildTx(R.merge(R.head(arguments), {
channelId,
fromId,
initiatorAmountFinal,
responderAmountFinal,
ttl,
fee,
nonce
}), TX_TYPE.channelSettle)
: await this.api.postChannelSettle(R.merge(R.head(arguments), {
channelId,
fromId,
initiatorAmountFinal: parseInt(initiatorAmountFinal),
responderAmountFinal: parseInt(responderAmountFinal),
ttl,
fee: parseInt(fee),
nonce
}))
return tx
}
async function channelSnapshotSoloTx ({ channelId, fromId, payload }) {
// Calculate fee, get absolute ttl (ttl + height), get account nonce
const { fee, ttl, nonce } = await this.prepareTxParams(TX_TYPE.channelSnapshotSolo, { senderId: fromId, ...R.head(arguments), payload })
// Build transaction using sdk (if nativeMode) or build on `AETERNITY NODE` side
const { tx } = this.nativeMode
? buildTx(R.merge(R.head(arguments), {
channelId,
fromId,
payload,
ttl,
fee,
nonce
}), TX_TYPE.channelSnapshotSolo)
: await this.api.postChannelSnapshotSolo(R.merge(R.head(arguments), {
channelId,
fromId,
payload,
ttl,
fee: parseInt(fee),
nonce
}))
return tx
}
async function gaAttachTx ({ ownerId, code, vmVersion, abiVersion, authFun, gas, gasPrice = MIN_GAS_PRICE, callData }) {
// Get VM_ABI version
const ctVersion = this.getVmVersion(TX_TYPE.contractCreate, R.head(arguments))
// Calculate fee, get absolute ttl (ttl + height), get account nonce
const { fee, ttl, nonce } = await this.prepareTxParams(TX_TYPE.gaAttach, { senderId: ownerId, ...R.head(arguments), ctVersion, gasPrice })
// Build transaction using sdk (if nativeMode) or build on `AETERNITY NODE` side
return {
tx: TxObject({
params: R.merge(R.head(arguments), { nonce, ttl, fee, ctVersion, gasPrice }),
type: TX_TYPE.gaAttach
}).encodedTx,
contractId: buildContractId(ownerId, nonce)
}
}
/**
* Validated vm/abi version or get default based on transaction type and NODE version
*
* @param {string} txType Type of transaction
* @param {object} vmAbi Object with vm and abi version fields
* @return {object} Object with vm/abi version ({ vmVersion: number, abiVersion: number })
*/
function getVmVersion (txType, { vmVersion, abiVersion } = {}) {
const { consensusProtocolVersion } = this.getNodeInfo()
const supportedProtocol = PROTOCOL_VM_ABI[consensusProtocolVersion]
if (!supportedProtocol) throw new Error('Not supported consensus protocol version')
const protocolForTX = supportedProtocol[txType]
if (!protocolForTX) throw new Error('Not supported tx type')
const ctVersion = {
abiVersion: abiVersion || protocolForTX.abiVersion[0],
vmVersion: vmVersion || protocolForTX.vmVersion[0]
}
if (protocolForTX.vmVersion.length && !R.contains(ctVersion.vmVersion, protocolForTX.vmVersion)) throw new Error(`VM VERSION ${ctVersion.vmVersion} do not support by this node. Supported: [${protocolForTX.vmVersion}]`)
if (protocolForTX.abiVersion.length && !R.contains(ctVersion.abiVersion, protocolForTX.abiVersion)) throw new Error(`ABI VERSION ${ctVersion.abiVersion} do not support by this node. Supported: [${protocolForTX.abiVersion}]`)
return ctVersion
}
/**
* Compute the absolute ttl by adding the ttl to the current height of the chain
*
* @param {number} ttl
* @param {boolean} relative ttl is absolute or relative(default: true(relative))
* @return {number} Absolute Ttl
*/
async function calculateTtl (ttl = TX_TTL, relative = true) {
if (ttl === 0) return 0
if (ttl < 0) throw new Error('ttl must be greater than 0')
if (relative) {
const { height } = await this.api.getCurrentKeyBlock()
return +(height) + ttl
}
return ttl
}
/**
* Get the next nonce to be used for a transaction for an account
*
* @param {string} accountId
* @param {number} nonce
* @return {number} Next Nonce
*/
async function getAccountNonce (accountId, nonce) {
if (nonce) return nonce
const { nonce: accountNonce } = await this.api.getAccountByPubkey(accountId).catch(() => ({ nonce: 0 }))
return accountNonce + 1
}
/**
* Calculate fee, get absolute ttl (ttl + height), get account nonce
*
* @param {String} txType Type of transaction
* @param {Object} params Object which contains all tx data
* @return {Object} { ttl, nonce, fee } Object with account nonce, absolute ttl and transaction fee
*/
async function prepareTxParams (txType, { senderId, nonce: n, ttl: t, fee: f, gas, absoluteTtl, vsn }) {
const account = await this.getAccount(senderId).catch(e => ({ nonce: 0 }))
// Is GA account
if (account.contractId) {
n = 0
} else {
n = n || (account.nonce + 1)
}
const ttl = await calculateTtl.call(this, t, !absoluteTtl)
const fee = calculateFee(f, txType, { showWarning: this.showWarning, gas, params: R.merge(R.last(arguments), { nonce: n, ttl }), vsn })
return { fee, ttl, nonce: n }
}
/**
* Transaction Stamp
*
* This is implementation of [Tx](api/tx.md) relays
* the creation of transactions to {@link module:@aeternity/aepp-sdk/es/Node}.
* This stamp provide ability to create native transaction's,
* or transaction's using Node API.
* As there is no built-in security between Node and client communication,
* creating transaction using {@link module:@aeternity/aepp-sdk/es/Node} API
* must never be used for production but can be very useful to verify other
* implementations.
* @function
* @alias module:@aeternity/aepp-sdk/es/tx/tx
* @rtype Stamp
* @param {Object} [options={}] - Initializer object
* @param {Boolean} [options.nativeMode=true] options.nativeMode - Use Native build of transaction's
* @param {String} options.url - Node url
* @param {String} options.internalUrl - Node internal url
* @return {Object} Transaction instance
* @example Transaction({url: 'https://testnet.aeternity.io/'})
*/
const Transaction = ChainNode.compose(Tx, {
init ({ nativeMode = true, showWarning = false }) {
this.nativeMode = nativeMode
this.showWarning = showWarning
},
props: {
nativeMode: true,
showWarning: false
},
methods: {
spendTx,
namePreclaimTx,
nameClaimTx,
nameTransferTx,
nameUpdateTx,
nameRevokeTx,
contractCreateTx,
contractCallTx,
prepareTxParams,
oracleRegisterTx,
oracleExtendTx,
oraclePostQueryTx,
oracleRespondTx,
channelCloseSoloTx,
channelSlashTx,
channelSettleTx,
channelSnapshotSoloTx,
gaAttachTx,
getAccountNonce,
getVmVersion
}
})
export default Transaction