-
Notifications
You must be signed in to change notification settings - Fork 115
/
Copy pathsignUnsignedTx.ts
152 lines (138 loc) · 5.69 KB
/
signUnsignedTx.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
import SignOfflineCommandBase from '../../base/SignOfflineCommandBase'
import { flags } from '@oclif/command'
import ExitCodes from '../../ExitCodes'
import { JOYSTREAM_ADDRESS_PREFIX, registry } from '@joystream/types'
import { IOFlags, ensureOutputFileIsWriteable, getInputJson, saveOutputJsonToFile } from '../../helpers/InputOutput'
import { decodeSignedTx } from '@substrate/txwrapper-core/lib/core/decode/decodeSignedTx'
import { decodeSigningPayload } from '@substrate/txwrapper-core/lib/core/decode/decodeSigningPayload'
import { Keyring } from '@polkadot/api'
// import { waitReady } from '@polkadot/wasm-crypto'
import { initWasm } from '@polkadot/wasm-crypto/initOnlyAsm'
import { KeyringOptions, KeyringPair, KeyringPair$Json } from '@polkadot/keyring/types'
import { createSignedTx, getTxHash } from '@substrate/txwrapper-core/lib/core/construct'
import { KeypairType } from '@polkadot/util-crypto/types'
import { DEFAULT_ACCOUNT_TYPE } from '../../base/AccountsCommandBase'
import { u8aToHex } from '@polkadot/util'
export default class SignUnsignedTxCommand extends SignOfflineCommandBase {
static description = 'Sign an unsigned transaction. Does not require an api connection.'
static flags = {
input: IOFlags.input,
output: flags.string({
char: 'o',
required: false,
description:
'Path to the file where the JSON with full transaction details should be saved.' +
'If omitted, only the signed transaction, the signature and the tx hash is included',
}),
mnemonic: flags.string({
required: false,
description: 'Mnemonic phrase',
exclusive: ['backupFilePath', 'seed', 'suri'],
}),
seed: flags.string({
required: false,
description: 'Secret seed',
exclusive: ['backupFilePath', 'mnemonic', 'suri'],
}),
backupFilePath: flags.string({
required: false,
description: 'Path to account backup JSON file',
exclusive: ['mnemonic', 'seed', 'suri'],
}),
suri: flags.string({
required: false,
description: 'Substrate uri',
exclusive: ['mnemonic', 'seed', 'backupFilePath'],
}),
password: flags.string({
required: false,
description: `Account password`,
dependsOn: ['backupFilePath', 'suri'],
}),
keypairType: flags.enum<KeypairType>({
required: false,
default: DEFAULT_ACCOUNT_TYPE,
description: `Account type (defaults to ${DEFAULT_ACCOUNT_TYPE})`,
options: ['sr25519', 'ed25519', 'ecdsa'],
exclusive: ['backupFilePath'],
}),
}
async run(): Promise<void> {
const {
flags: { input, output, mnemonic, seed, backupFilePath, suri, keypairType },
} = this.parse(SignUnsignedTxCommand)
ensureOutputFileIsWriteable(output)
if (!input) {
this.error('Could not fetch the input json', { exit: ExitCodes.InvalidFile })
}
const keyringOptions: KeyringOptions = {
ss58Format: JOYSTREAM_ADDRESS_PREFIX,
type: keypairType,
}
const inputFile = await this.getInputFromFile(input)
const keyring = new Keyring(keyringOptions)
const txSignerAddress = keyring.addFromAddress(inputFile.unsigned.address)
let signerPair: KeyringPair | undefined
if (this.isKeyAvailable(keyring, txSignerAddress.address)) {
this.log('Signer key available in storage')
signerPair = this.getPair(keyring, txSignerAddress.address) as KeyringPair
}
if (mnemonic) {
signerPair = keyring.addFromMnemonic(mnemonic, {}, keypairType)
} else if (seed) {
signerPair = keyring.addFromSeed(Buffer.from(seed), {}, keypairType)
} else if (suri) {
signerPair = keyring.addFromUri(suri, {}, keypairType)
} else if (backupFilePath) {
const jsonPair = await getInputJson<KeyringPair$Json>(backupFilePath)
signerPair = keyring.addFromJson(jsonPair)
} else {
this.error('Signer key not available in storage, and no input provided', {
exit: ExitCodes.NoAccountFound,
})
}
this.log(`Signer key ${keyring.encodeAddress(signerPair.address, JOYSTREAM_ADDRESS_PREFIX)} is loaded.`)
if (signerPair.address !== txSignerAddress.address) {
this.error(
`The input provided corresponds to ${signerPair.address}, whereas the signer address is ${txSignerAddress.address}`,
{
exit: ExitCodes.NoAccountFound,
}
)
}
if (signerPair.isLocked) {
await this.requestPairDecoding(signerPair)
}
const metadata = inputFile.unsigned.metadataRpc.slice(2)
await initWasm()
const signingPayloadDecoded = decodeSigningPayload(inputFile.signingPayload, {
metadataRpc: `0x${metadata}`,
registry,
})
if (signingPayloadDecoded.method.pallet === 'multisig' && inputFile.multisigTxData) {
this.multiCheck(signerPair.address, signingPayloadDecoded, inputFile.multisigTxData)
}
const encodePayload = await this.createPayloadV4(inputFile.signingPayload)
const signature = u8aToHex(encodePayload.sign(signerPair))
const signedTx = createSignedTx(inputFile.unsigned, signature, { metadataRpc: `0x${metadata}`, registry })
const txInfo = decodeSignedTx(signedTx, { metadataRpc: `0x${metadata}`, registry })
const txHash = getTxHash(signedTx)
const outputJson = {
signedTx,
signature,
unsignedTransaction: inputFile.unsigned,
signingPayload: inputFile.signingPayload,
txInfo,
txHash,
}
this.log(`The transaction has been signed.\n` + ` - Signature: ${signature}\n` + ` - TX Hash: ${txHash}\n`)
if (signedTx.length > 500) {
this.log(`The signed TX too long to log to console - see output file`)
} else {
this.log(` - Signed TX: ${signedTx}`)
}
if (output) {
saveOutputJsonToFile(output, outputJson)
}
}
}