packages/oo7-substrate/src/transact.js
const { Bond } = require('oo7')
const { blake2b } = require('blakejs')
const { SubscriptionBond } = require('./subscriptionBond')
const { encode } = require('./codec')
const { secretStore } = require('./secretStore')
const { TransactionEra, AccountIndex } = require('./types')
const { runtimeUp, runtime, chain } = require('./bonds')
const { bytesToHex } = require('./utils')
class TransactionBond extends SubscriptionBond {
constructor (data) {
super('author_submitAndWatchExtrinsic', ['0x' + bytesToHex(data)], null, {sending: true})
}
}
function composeTransaction (sender, call, index, era, checkpoint, senderAccount, compact) {
return new Promise((resolve, reject) => {
if (typeof sender == 'string') {
sender = ss58Decode(sender)
}
if (sender instanceof Uint8Array && sender.length == 32) {
senderAccount = sender
} else if (!senderAccount) {
reject(`Invalid senderAccount when sender is account index`)
}
console.log("composing transaction", senderAccount, index, call, era, checkpoint);
let e = encode([
index, call, era, checkpoint
], [
'Compact<Index>', 'Call', 'TransactionEra', 'Hash'
])
let legacy = runtime.version.isReady() && (
runtime.version._value.specName == 'node' && runtime.version._value.specVersion < 17
|| runtime.version._value.specName == 'polkadot' && runtime.version._value.specVersion < 107
)
if (!legacy && e.length > 256) {
console.log(`Oversize transaction (length ${e.length} bytes). Hashing.`)
e = blake2b(e, null, 32)
}
let signature = secretStore().sign(senderAccount, e)
console.log("encoding transaction", sender, index, era, call);
let signedData = encode(encode({
_type: 'Transaction',
version: 0x81,
sender,
signature,
index,
era,
call
}), 'Vec<u8>')
console.log("signed:", bytesToHex(signedData))
setTimeout(() => resolve(signedData), 1000)
})
}
// tx = {
// sender
// call
// longevity?
// index?
// }
function post(tx) {
return Bond.all([tx, chain.height, runtimeUp]).map(([o, height, unused]) => {
let {sender, call, index, longevity, compact} = o
// defaults
longevity = typeof longevity === 'undefined' ? 256 : longevity
compact = typeof compact === 'undefined' ? true : compact
let senderIsIndex = typeof sender === 'number' || sender instanceof AccountIndex
let senderAccount = senderIsIndex
? runtime.indices.lookup(sender)
: sender
if (senderIsIndex && !compact) {
sender = senderAccount
}
let era
let eraHash
if (longevity === true) {
era = new TransactionEra;
eraHash = chain.hash(0)
} else {
// use longevity with height to determine era and eraHash
let l = Math.min(15, Math.max(1, Math.ceil(Math.log2(longevity)) - 1))
let period = 2 << l
let factor = Math.max(1, period >> 12)
let Q = (n, d) => Math.floor(n / d) * d
let eraNumber = Q(height, factor)
let phase = eraNumber % period
era = new TransactionEra(period, phase)
eraHash = chain.hash(eraNumber)
}
return {
sender,
call,
era,
eraHash,
index: index || runtime.system.accountNonce(senderAccount),
senderAccount,
compact
}
}, 2).latched(false).map(o =>
o && composeTransaction(o.sender, o.call, o.index, o.era, o.eraHash, o.senderAccount, o.compact)
).map(composed => {
return composed ? new TransactionBond(composed) : { signing: true }
})
}
module.exports = { composeTransaction, post };