@tech-bureau/mijin-catapult-tools 0.0.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env/dev.ts +23 -0
- package/.env/prod.ts +23 -0
- package/.github/workflows/main.yml +37 -0
- package/.prettierrc.json +5 -0
- package/LICENSE +21 -0
- package/README.md +646 -0
- package/bin/mijin-catapult-tools.js +3 -0
- package/dist/main.js +2 -0
- package/jest.config.js +8 -0
- package/package.json +55 -0
- package/src/__tests__/service/AccountServices.spec.ts +63 -0
- package/src/__tests__/service/CertificateServices.spec.ts +20 -0
- package/src/__tests__/service/MessageServices.spec.ts +48 -0
- package/src/__tests__/service/MosaicServices.spec.ts +61 -0
- package/src/cli.ts +18 -0
- package/src/commands/account/account.ts +16 -0
- package/src/commands/account/accountGenerate.ts +19 -0
- package/src/commands/account/accountInfo.ts +16 -0
- package/src/commands/mosaic/mosaic.ts +16 -0
- package/src/commands/mosaic/mosaicCreate.ts +20 -0
- package/src/commands/mosaic/mosaicInfo.ts +13 -0
- package/src/commands/transaction/transaction.ts +18 -0
- package/src/commands/transaction/transactionStatus.ts +13 -0
- package/src/commands/transaction/transfer.ts +16 -0
- package/src/commands/votingkey/votingkey.ts +18 -0
- package/src/commands/votingkey/votingkeyCreate.ts +15 -0
- package/src/commands/votingkey/votingkeyInfo.ts +13 -0
- package/src/commands/votingkey/votingkeyUpdate.ts +15 -0
- package/src/infrastructure/account/accountGenerate.ts +183 -0
- package/src/infrastructure/account/accountInfo.ts +154 -0
- package/src/infrastructure/mosaic/mosaicCreate.ts +149 -0
- package/src/infrastructure/mosaic/mosaicInfo.ts +40 -0
- package/src/infrastructure/transaction/transactionStatus.ts +57 -0
- package/src/infrastructure/transaction/transfer.ts +168 -0
- package/src/infrastructure/voting/votingCreate.ts +88 -0
- package/src/infrastructure/voting/votingInfo.ts +44 -0
- package/src/infrastructure/voting/votingUpdate.ts +94 -0
- package/src/service/AccountServices.ts +68 -0
- package/src/service/CertificateServices.ts +165 -0
- package/src/service/CmdServices.ts +17 -0
- package/src/service/Logger.ts +26 -0
- package/src/service/MessageServices.ts +17 -0
- package/src/service/MosaicServices.ts +62 -0
- package/src/service/RepositoryFactory.ts +107 -0
- package/src/service/TransactionServices.ts +268 -0
- package/src/service/VotingServices.ts +184 -0
- package/src/types/AccountInfo.ts +19 -0
- package/src/types/AccountType.ts +9 -0
- package/src/types/ConfigFile.ts +24 -0
- package/src/types/Environment.ts +5 -0
- package/src/types/IAccountOption.ts +16 -0
- package/src/types/ILinkOption.ts +3 -0
- package/src/types/IMosaicOption.ts +18 -0
- package/src/types/ITransactionOption.ts +5 -0
- package/src/types/ITransferOption.ts +9 -0
- package/src/types/IVotingOption.ts +21 -0
- package/src/types/index.ts +9 -0
- package/src/utils.ts +38 -0
- package/tsconfig.json +24 -0
- package/webpack.config.js +24 -0
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Currency,
|
|
3
|
+
Mosaic,
|
|
4
|
+
MosaicId,
|
|
5
|
+
UInt64,
|
|
6
|
+
MosaicNonce,
|
|
7
|
+
Address,
|
|
8
|
+
MosaicFlags,
|
|
9
|
+
MosaicRepository,
|
|
10
|
+
MosaicInfo,
|
|
11
|
+
} from '@tech-bureau/symbol-sdk'
|
|
12
|
+
import { Observable, firstValueFrom } from 'rxjs'
|
|
13
|
+
|
|
14
|
+
export default class MosaicServices {
|
|
15
|
+
constructor() {}
|
|
16
|
+
|
|
17
|
+
static create(mosaicId: string, amount: number) {
|
|
18
|
+
return new Mosaic(new MosaicId(mosaicId), UInt64.fromUint(amount))
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
static createCurencyToAbsolute(currency: Currency, amount: number) {
|
|
22
|
+
return currency.createAbsolute(amount)
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
static createCurencyToRelative(currency: Currency, amount: number) {
|
|
26
|
+
return currency.createRelative(amount)
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
static createMosaicId(nonce: MosaicNonce, address: Address) {
|
|
30
|
+
return MosaicId.createFromNonce(nonce, address)
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
static createMosaicNonce() {
|
|
34
|
+
return MosaicNonce.createRandom()
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
static createMosaicFlags(
|
|
38
|
+
supplyMutable: boolean,
|
|
39
|
+
transferable: boolean,
|
|
40
|
+
restrictable?: boolean | undefined,
|
|
41
|
+
revokable?: boolean | undefined
|
|
42
|
+
) {
|
|
43
|
+
return MosaicFlags.create(supplyMutable, transferable, restrictable, revokable)
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
static async getMosaic(mosaicId: string, mosaicRepository: MosaicRepository) {
|
|
47
|
+
try {
|
|
48
|
+
return await firstValueFrom(mosaicRepository.getMosaic(new MosaicId(mosaicId)))
|
|
49
|
+
} catch (error) {
|
|
50
|
+
throw new Error(`Mosaic Not Found`)
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
static mosaicInfo(mosaicInfo: MosaicInfo) {
|
|
55
|
+
return {
|
|
56
|
+
ownerAddress: mosaicInfo.ownerAddress.plain(),
|
|
57
|
+
mosaicId: mosaicInfo.id.toHex(),
|
|
58
|
+
supply: mosaicInfo.supply.compact(),
|
|
59
|
+
divisibility: mosaicInfo.divisibility,
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ChainInfo,
|
|
3
|
+
FinalizationProof,
|
|
4
|
+
NetworkConfiguration,
|
|
5
|
+
NetworkCurrencies,
|
|
6
|
+
NetworkType,
|
|
7
|
+
RepositoryFactoryHttp,
|
|
8
|
+
TransactionFees,
|
|
9
|
+
} from '@tech-bureau/symbol-sdk'
|
|
10
|
+
import { firstValueFrom } from 'rxjs'
|
|
11
|
+
|
|
12
|
+
let networkType: NetworkType
|
|
13
|
+
let currency: NetworkCurrencies
|
|
14
|
+
let generationHash: string
|
|
15
|
+
let epoch: number
|
|
16
|
+
let repo: RepositoryFactoryHttp
|
|
17
|
+
let tfees: TransactionFees
|
|
18
|
+
let networkProperties: NetworkConfiguration
|
|
19
|
+
let finalizationEpoch: number
|
|
20
|
+
|
|
21
|
+
export default class RepositoryFactory {
|
|
22
|
+
constructor(public url: string) {}
|
|
23
|
+
|
|
24
|
+
async init() {
|
|
25
|
+
repo = new RepositoryFactoryHttp(this.url)
|
|
26
|
+
networkType = await firstValueFrom(repo.getNetworkType())
|
|
27
|
+
currency = await firstValueFrom(repo.getCurrencies())
|
|
28
|
+
generationHash = await firstValueFrom(repo.getGenerationHash())
|
|
29
|
+
epoch = await firstValueFrom(repo.getEpochAdjustment())
|
|
30
|
+
tfees = await firstValueFrom(repo.createNetworkRepository().getTransactionFees())
|
|
31
|
+
networkProperties = await firstValueFrom(this.createNetwork().getNetworkProperties())
|
|
32
|
+
finalizationEpoch = (await firstValueFrom(this.createChainRepository().getChainInfo())).latestFinalizedBlock
|
|
33
|
+
.finalizationEpoch
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
getNetwork() {
|
|
37
|
+
return networkType
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
getCurrency() {
|
|
41
|
+
return currency
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
getGnerationHash() {
|
|
45
|
+
return generationHash
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
getEpoch() {
|
|
49
|
+
return epoch
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
getEinalizationEpoch() {
|
|
53
|
+
return finalizationEpoch
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
getTransactionFees() {
|
|
57
|
+
return tfees
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
getNetworkProperties() {
|
|
61
|
+
return networkProperties
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
createNetwork() {
|
|
65
|
+
return repo.createNetworkRepository()
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
createLitener() {
|
|
69
|
+
return repo.createListener()
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
createTransactionRepository() {
|
|
73
|
+
return repo.createTransactionRepository()
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
createReceiptRepository() {
|
|
77
|
+
return repo.createReceiptRepository()
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
createAccountRepository() {
|
|
81
|
+
return repo.createAccountRepository()
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
createNamespaceRepository() {
|
|
85
|
+
return repo.createNamespaceRepository()
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
createMultisigRepository() {
|
|
89
|
+
return repo.createMultisigRepository()
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
createFinalizationRepository() {
|
|
93
|
+
return repo.createFinalizationRepository()
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
createBlockRepository() {
|
|
97
|
+
return repo.createBlockRepository()
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
createChainRepository() {
|
|
101
|
+
return repo.createChainRepository()
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
createMosaicRepository() {
|
|
105
|
+
return repo.createMosaicRepository()
|
|
106
|
+
}
|
|
107
|
+
}
|
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
import {
|
|
2
|
+
AggregateTransaction,
|
|
3
|
+
Deadline,
|
|
4
|
+
InnerTransaction,
|
|
5
|
+
NetworkType,
|
|
6
|
+
Account,
|
|
7
|
+
CosignatureTransaction,
|
|
8
|
+
TransactionAnnounceResponse,
|
|
9
|
+
TransactionHttp,
|
|
10
|
+
AccountKeyLinkTransaction,
|
|
11
|
+
LinkAction,
|
|
12
|
+
NodeKeyLinkTransaction,
|
|
13
|
+
VotingKeyLinkTransaction,
|
|
14
|
+
VrfKeyLinkTransaction,
|
|
15
|
+
HashLockTransaction,
|
|
16
|
+
Mosaic,
|
|
17
|
+
SignedTransaction,
|
|
18
|
+
UInt64,
|
|
19
|
+
MultisigAccountModificationTransaction,
|
|
20
|
+
Address,
|
|
21
|
+
Message,
|
|
22
|
+
TransferTransaction,
|
|
23
|
+
IListener,
|
|
24
|
+
ReceiptRepository,
|
|
25
|
+
TransactionRepository,
|
|
26
|
+
TransactionService,
|
|
27
|
+
MosaicDefinitionTransaction,
|
|
28
|
+
MosaicNonce,
|
|
29
|
+
MosaicId,
|
|
30
|
+
MosaicFlags,
|
|
31
|
+
MosaicSupplyChangeTransaction,
|
|
32
|
+
MosaicSupplyChangeAction,
|
|
33
|
+
TransactionGroup,
|
|
34
|
+
TransactionStatusHttp,
|
|
35
|
+
TransactionStatus,
|
|
36
|
+
Transaction,
|
|
37
|
+
} from '@tech-bureau/symbol-sdk'
|
|
38
|
+
import { ChronoUnit } from '@js-joda/core'
|
|
39
|
+
import { firstValueFrom } from 'rxjs'
|
|
40
|
+
import CreateAccount from './AccountServices'
|
|
41
|
+
|
|
42
|
+
export default class TransactionServices {
|
|
43
|
+
constructor() {}
|
|
44
|
+
|
|
45
|
+
static async getTransactionStatus(url: string, transactionId: string): Promise<TransactionStatus> {
|
|
46
|
+
const transactionStatusHttp = new TransactionStatusHttp(url)
|
|
47
|
+
return await firstValueFrom(transactionStatusHttp.getTransactionStatus(transactionId))
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
static async getTransaction(url: string, transactionId: string, transactionStatus: string): Promise<Transaction> {
|
|
51
|
+
const transactionHttp = new TransactionHttp(url)
|
|
52
|
+
return await firstValueFrom(
|
|
53
|
+
transactionHttp.getTransaction(
|
|
54
|
+
transactionId,
|
|
55
|
+
transactionStatus === 'confirmed'
|
|
56
|
+
? TransactionGroup.Confirmed
|
|
57
|
+
: transactionStatus === 'partial'
|
|
58
|
+
? TransactionGroup.Partial
|
|
59
|
+
: TransactionGroup.Unconfirmed
|
|
60
|
+
)
|
|
61
|
+
)
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
static async announceCosign(
|
|
65
|
+
url: string,
|
|
66
|
+
account: Account,
|
|
67
|
+
transaction: AggregateTransaction
|
|
68
|
+
): Promise<TransactionAnnounceResponse> {
|
|
69
|
+
const transactionHttp = new TransactionHttp(url)
|
|
70
|
+
const signdTransaction = account.signCosignatureTransaction(CosignatureTransaction.create(transaction))
|
|
71
|
+
return await firstValueFrom(transactionHttp.announceAggregateBondedCosignature(signdTransaction))
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
static async announceHashLockAggregateBonded(
|
|
75
|
+
signedHashLockTransaction: SignedTransaction,
|
|
76
|
+
signedTransaction: SignedTransaction,
|
|
77
|
+
listener: IListener,
|
|
78
|
+
transactionRepository: TransactionRepository,
|
|
79
|
+
receiptRepository: ReceiptRepository
|
|
80
|
+
) {
|
|
81
|
+
const service = new TransactionService(transactionRepository, receiptRepository)
|
|
82
|
+
|
|
83
|
+
await listener.open()
|
|
84
|
+
const aggtx = await firstValueFrom(
|
|
85
|
+
service.announceHashLockAggregateBonded(signedHashLockTransaction, signedTransaction, listener)
|
|
86
|
+
).catch((err) => {
|
|
87
|
+
listener.close()
|
|
88
|
+
throw new Error(err)
|
|
89
|
+
})
|
|
90
|
+
listener.close()
|
|
91
|
+
return aggtx
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
static async announce(
|
|
95
|
+
signedTransaction: SignedTransaction,
|
|
96
|
+
listener: IListener,
|
|
97
|
+
transactionRepository: TransactionRepository,
|
|
98
|
+
receiptRepository: ReceiptRepository
|
|
99
|
+
) {
|
|
100
|
+
const service = new TransactionService(transactionRepository, receiptRepository)
|
|
101
|
+
|
|
102
|
+
await listener.open()
|
|
103
|
+
const tx = await firstValueFrom(service.announce(signedTransaction, listener)).catch((err) => {
|
|
104
|
+
listener.close()
|
|
105
|
+
throw new Error(err)
|
|
106
|
+
})
|
|
107
|
+
listener.close()
|
|
108
|
+
return tx
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
static createHashLock(
|
|
112
|
+
epoch: number,
|
|
113
|
+
mosaic: Mosaic,
|
|
114
|
+
signedTransaction: SignedTransaction,
|
|
115
|
+
networkType: NetworkType
|
|
116
|
+
): HashLockTransaction {
|
|
117
|
+
return HashLockTransaction.create(
|
|
118
|
+
Deadline.create(epoch, 6, ChronoUnit.HOURS),
|
|
119
|
+
mosaic,
|
|
120
|
+
UInt64.fromUint(5760),
|
|
121
|
+
signedTransaction,
|
|
122
|
+
networkType
|
|
123
|
+
)
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
static createBonded(epoch: number, transactions: InnerTransaction[], networkType: NetworkType): AggregateTransaction {
|
|
127
|
+
return AggregateTransaction.createBonded(
|
|
128
|
+
Deadline.create(epoch, 24, ChronoUnit.HOURS),
|
|
129
|
+
transactions,
|
|
130
|
+
networkType,
|
|
131
|
+
[]
|
|
132
|
+
)
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
static createComplete(
|
|
136
|
+
epoch: number,
|
|
137
|
+
transactions: InnerTransaction[],
|
|
138
|
+
networkType: NetworkType
|
|
139
|
+
): AggregateTransaction {
|
|
140
|
+
return AggregateTransaction.createComplete(
|
|
141
|
+
Deadline.create(epoch, 24, ChronoUnit.HOURS),
|
|
142
|
+
transactions,
|
|
143
|
+
networkType,
|
|
144
|
+
[]
|
|
145
|
+
)
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
static createVrfKey(epoch: number, linkPublickey: string, networkType: NetworkType): VrfKeyLinkTransaction {
|
|
149
|
+
return VrfKeyLinkTransaction.create(Deadline.create(epoch), linkPublickey, LinkAction.Link, networkType)
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
static createVotingKey(epoch: number, linkPublickey: string, networkType: NetworkType): VotingKeyLinkTransaction {
|
|
153
|
+
return VotingKeyLinkTransaction.create(
|
|
154
|
+
Deadline.create(epoch),
|
|
155
|
+
linkPublickey,
|
|
156
|
+
1,
|
|
157
|
+
360,
|
|
158
|
+
LinkAction.Link,
|
|
159
|
+
networkType,
|
|
160
|
+
1
|
|
161
|
+
)
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
static createAccountKey(epoch: number, linkPublickey: string, networkType: NetworkType): AccountKeyLinkTransaction {
|
|
165
|
+
return AccountKeyLinkTransaction.create(Deadline.create(epoch), linkPublickey, LinkAction.Link, networkType)
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
static createNodeKey(epoch: number, linkPublickey: string, networkType: NetworkType): NodeKeyLinkTransaction {
|
|
169
|
+
return NodeKeyLinkTransaction.create(Deadline.create(epoch), linkPublickey, LinkAction.Link, networkType)
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
static createMultisig(
|
|
173
|
+
epoch: number,
|
|
174
|
+
minApproval: number,
|
|
175
|
+
minRemoval: number,
|
|
176
|
+
addAddresses: string[] | [],
|
|
177
|
+
delAddresses: string[] | [],
|
|
178
|
+
networkType: NetworkType
|
|
179
|
+
) {
|
|
180
|
+
const addressAdditions =
|
|
181
|
+
addAddresses.length > 0 ? CreateAccount.createPublicAccountArray(addAddresses, networkType) : []
|
|
182
|
+
const addressDeletions =
|
|
183
|
+
delAddresses.length > 0 ? CreateAccount.createPublicAccountArray(delAddresses, networkType) : []
|
|
184
|
+
return MultisigAccountModificationTransaction.create(
|
|
185
|
+
Deadline.create(epoch, 6, ChronoUnit.HOURS),
|
|
186
|
+
minApproval,
|
|
187
|
+
minRemoval,
|
|
188
|
+
addressAdditions,
|
|
189
|
+
addressDeletions,
|
|
190
|
+
networkType
|
|
191
|
+
)
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
static createTransfer(
|
|
195
|
+
epoch: number,
|
|
196
|
+
recipientAddress: Address,
|
|
197
|
+
mosaic: Mosaic[] | [],
|
|
198
|
+
message: Message,
|
|
199
|
+
networkType: NetworkType
|
|
200
|
+
): TransferTransaction {
|
|
201
|
+
return TransferTransaction.create(
|
|
202
|
+
Deadline.create(epoch, 6, ChronoUnit.HOURS),
|
|
203
|
+
recipientAddress,
|
|
204
|
+
mosaic,
|
|
205
|
+
message,
|
|
206
|
+
networkType
|
|
207
|
+
)
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
static createMosaicDefinition(
|
|
211
|
+
epoch: number,
|
|
212
|
+
nonce: MosaicNonce,
|
|
213
|
+
mosaicId: MosaicId,
|
|
214
|
+
mosaicFlags: MosaicFlags,
|
|
215
|
+
divisibility: number,
|
|
216
|
+
networkType: NetworkType,
|
|
217
|
+
duration?: number | undefined
|
|
218
|
+
): MosaicDefinitionTransaction {
|
|
219
|
+
return MosaicDefinitionTransaction.create(
|
|
220
|
+
Deadline.create(epoch),
|
|
221
|
+
nonce,
|
|
222
|
+
mosaicId,
|
|
223
|
+
mosaicFlags,
|
|
224
|
+
divisibility,
|
|
225
|
+
duration ? UInt64.fromUint(duration) : UInt64.fromUint(0),
|
|
226
|
+
networkType
|
|
227
|
+
)
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
static createMosaicSupplyChange(
|
|
231
|
+
epoch: number,
|
|
232
|
+
mosaicId: MosaicId,
|
|
233
|
+
mosaicSupply: number,
|
|
234
|
+
networkType: NetworkType
|
|
235
|
+
): MosaicSupplyChangeTransaction {
|
|
236
|
+
return MosaicSupplyChangeTransaction.create(
|
|
237
|
+
Deadline.create(epoch),
|
|
238
|
+
mosaicId,
|
|
239
|
+
MosaicSupplyChangeAction.Increase,
|
|
240
|
+
UInt64.fromUint(mosaicSupply),
|
|
241
|
+
networkType
|
|
242
|
+
)
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
static deleteVrf(epoch: number, linkPublickey: string, networkType: NetworkType): VrfKeyLinkTransaction {
|
|
246
|
+
return VrfKeyLinkTransaction.create(Deadline.create(epoch), linkPublickey, LinkAction.Unlink, networkType)
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
static deleteVoting(epoch: number, linkPublickey: string, networkType: NetworkType): VotingKeyLinkTransaction {
|
|
250
|
+
return VotingKeyLinkTransaction.create(
|
|
251
|
+
Deadline.create(epoch),
|
|
252
|
+
linkPublickey,
|
|
253
|
+
1,
|
|
254
|
+
26280,
|
|
255
|
+
LinkAction.Unlink,
|
|
256
|
+
networkType,
|
|
257
|
+
1
|
|
258
|
+
)
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
static deleteAccount(epoch: number, linkPublickey: string, networkType: NetworkType): AccountKeyLinkTransaction {
|
|
262
|
+
return AccountKeyLinkTransaction.create(Deadline.create(epoch), linkPublickey, LinkAction.Unlink, networkType)
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
static deleteNode(epoch: number, linkPublickey: string, networkType: NetworkType): NodeKeyLinkTransaction {
|
|
266
|
+
return NodeKeyLinkTransaction.create(Deadline.create(epoch), linkPublickey, LinkAction.Unlink, networkType)
|
|
267
|
+
}
|
|
268
|
+
}
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright 2022 Fernando Boucquez
|
|
3
|
+
*
|
|
4
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
* you may not use this file except in compliance with the License.
|
|
6
|
+
* You may obtain a copy of the License at
|
|
7
|
+
*
|
|
8
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
*
|
|
10
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
* See the License for the specific language governing permissions and
|
|
14
|
+
* limitations under the License.
|
|
15
|
+
*/
|
|
16
|
+
import * as noble from '@noble/ed25519'
|
|
17
|
+
import { existsSync, lstatSync, readdirSync, readFileSync } from 'fs'
|
|
18
|
+
import { join } from 'path'
|
|
19
|
+
import { Convert, Crypto } from '@tech-bureau/symbol-sdk'
|
|
20
|
+
import * as nacl from 'tweetnacl'
|
|
21
|
+
|
|
22
|
+
export interface KeyPair {
|
|
23
|
+
privateKey: Uint8Array
|
|
24
|
+
publicKey: Uint8Array
|
|
25
|
+
}
|
|
26
|
+
export interface CryptoImplementation {
|
|
27
|
+
name: string
|
|
28
|
+
createKeyPairFromPrivateKey: (privateKey: Uint8Array) => Promise<KeyPair>
|
|
29
|
+
sign: (keyPair: KeyPair, data: Uint8Array) => Promise<Uint8Array>
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export interface VotingKeyAccount {
|
|
33
|
+
readonly startEpoch: number
|
|
34
|
+
readonly endEpoch: number
|
|
35
|
+
readonly publicKey: string
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export type VotingKeyFile = VotingKeyAccount & { filename: string }
|
|
39
|
+
|
|
40
|
+
export class VotingUtils {
|
|
41
|
+
public static nobleImplementation: CryptoImplementation = {
|
|
42
|
+
name: 'Noble',
|
|
43
|
+
createKeyPairFromPrivateKey: async (privateKey: Uint8Array): Promise<KeyPair> => {
|
|
44
|
+
const publicKey = await noble.getPublicKey(privateKey)
|
|
45
|
+
return { privateKey, publicKey: publicKey }
|
|
46
|
+
},
|
|
47
|
+
sign: async (keyPair: KeyPair, data: Uint8Array): Promise<Uint8Array> => {
|
|
48
|
+
return await noble.sign(data, keyPair.privateKey)
|
|
49
|
+
},
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
public static tweetNaClImplementation: CryptoImplementation = {
|
|
53
|
+
name: 'TweetNaCl',
|
|
54
|
+
createKeyPairFromPrivateKey: async (privateKey: Uint8Array): Promise<KeyPair> => {
|
|
55
|
+
const { publicKey } = nacl.sign.keyPair.fromSeed(privateKey)
|
|
56
|
+
return { privateKey, publicKey }
|
|
57
|
+
},
|
|
58
|
+
|
|
59
|
+
sign: async (keyPair: KeyPair, data: Uint8Array): Promise<Uint8Array> => {
|
|
60
|
+
const secretKey = new Uint8Array(64)
|
|
61
|
+
secretKey.set(keyPair.privateKey)
|
|
62
|
+
secretKey.set(keyPair.publicKey, 32)
|
|
63
|
+
return nacl.sign.detached(data, secretKey)
|
|
64
|
+
},
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
public static implementations = [VotingUtils.nobleImplementation, VotingUtils.tweetNaClImplementation]
|
|
68
|
+
|
|
69
|
+
constructor(private readonly implementation: CryptoImplementation = VotingUtils.nobleImplementation) {}
|
|
70
|
+
public insert(result: Uint8Array, value: Uint8Array, index: number): number {
|
|
71
|
+
result.set(value, index)
|
|
72
|
+
return index + value.length
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
public async createVotingFile(
|
|
76
|
+
secret: string,
|
|
77
|
+
votingKeyStartEpoch: number,
|
|
78
|
+
votingKeyEndEpoch: number,
|
|
79
|
+
unitTestPrivateKeys: Uint8Array[] | undefined = undefined
|
|
80
|
+
): Promise<Uint8Array> {
|
|
81
|
+
const items = votingKeyEndEpoch - votingKeyStartEpoch + 1
|
|
82
|
+
const headerSize = 64 + 16
|
|
83
|
+
const itemSize = 32 + 64
|
|
84
|
+
const totalSize = headerSize + items * itemSize
|
|
85
|
+
const rootPrivateKey = await this.implementation.createKeyPairFromPrivateKey(Convert.hexToUint8(secret))
|
|
86
|
+
const result = new Uint8Array(totalSize)
|
|
87
|
+
//start-epoch (8b),
|
|
88
|
+
let index = 0
|
|
89
|
+
index = this.insert(result, Convert.numberToUint8Array(votingKeyStartEpoch, 8), index)
|
|
90
|
+
|
|
91
|
+
//end-epoch (8b),
|
|
92
|
+
index = this.insert(result, Convert.numberToUint8Array(votingKeyEndEpoch, 8), index)
|
|
93
|
+
|
|
94
|
+
// could it have other values????
|
|
95
|
+
//last key identifier (8b) - for fresh file this is 0xFFFF'FFFF'FFFF'FFFF (a.k.a. Invalid_Id)
|
|
96
|
+
index = this.insert(result, Convert.hexToUint8('FFFFFFFFFFFFFFFF'), index)
|
|
97
|
+
|
|
98
|
+
//last wipe key identifier (8b) - again, for fresh file this is 0xFFFF'FFFF'FFFF'FFFF (Invalid_Id)
|
|
99
|
+
index = this.insert(result, Convert.hexToUint8('FFFFFFFFFFFFFFFF'), index)
|
|
100
|
+
|
|
101
|
+
// root public key (32b) - this is root public key that is getting announced via vote link tx
|
|
102
|
+
index = this.insert(result, rootPrivateKey.publicKey, index)
|
|
103
|
+
// start-epoch (8b), \ those two are exactly same one, as top level, reason is this was earlier a tree,
|
|
104
|
+
index = this.insert(result, Convert.numberToUint8Array(votingKeyStartEpoch, 8), index)
|
|
105
|
+
|
|
106
|
+
//end-epoch (8b), / and each level holds this separately, so we left it as is
|
|
107
|
+
index = this.insert(result, Convert.numberToUint8Array(votingKeyEndEpoch, 8), index)
|
|
108
|
+
/// what follows are bound keys, there are (end - start + 1) of them.
|
|
109
|
+
|
|
110
|
+
// each key is:
|
|
111
|
+
for (let i = 0; i < items; i++) {
|
|
112
|
+
// random PRIVATE key (32b)
|
|
113
|
+
const randomPrivateKey = unitTestPrivateKeys ? unitTestPrivateKeys[i] : Crypto.randomBytes(32)
|
|
114
|
+
if (randomPrivateKey.length != 32) {
|
|
115
|
+
throw new Error(`Invalid private key size ${randomPrivateKey.length}!`)
|
|
116
|
+
}
|
|
117
|
+
const randomKeyPar = await this.implementation.createKeyPairFromPrivateKey(randomPrivateKey)
|
|
118
|
+
index = this.insert(result, randomPrivateKey, index)
|
|
119
|
+
// signature (64b)
|
|
120
|
+
// now the signature is usual signature done using ROOT private key on a following data:
|
|
121
|
+
// (public key (32b), identifier (8b))
|
|
122
|
+
//
|
|
123
|
+
// identifier is simply epoch, but, most importantly keys are written in REVERSE order.
|
|
124
|
+
//
|
|
125
|
+
// i.e. say your start-epoch = 2, end-epoch = 42
|
|
126
|
+
const identifier = Convert.numberToUint8Array(votingKeyEndEpoch - i, 8)
|
|
127
|
+
const signature = await this.implementation.sign(
|
|
128
|
+
rootPrivateKey,
|
|
129
|
+
Uint8Array.from([...randomKeyPar.publicKey, ...identifier])
|
|
130
|
+
)
|
|
131
|
+
index = this.insert(result, signature, index)
|
|
132
|
+
}
|
|
133
|
+
//
|
|
134
|
+
// root private key is discarded after file is created.
|
|
135
|
+
// header:
|
|
136
|
+
// 2, 42, ff.., ff..., (root pub), 2, 42
|
|
137
|
+
// keys:
|
|
138
|
+
// (priv key 42, sig 42), (priv key 41, sig 31), ..., (priv key 2, sig 2)
|
|
139
|
+
//
|
|
140
|
+
// every priv key should be cryptographically random,
|
|
141
|
+
|
|
142
|
+
return result
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
public readVotingFile(file: Uint8Array): VotingKeyAccount {
|
|
146
|
+
//start-epoch (8b),
|
|
147
|
+
const votingKeyStartEpoch = Convert.uintArray8ToNumber(file.slice(0, 8))
|
|
148
|
+
//end-epoch (8b),
|
|
149
|
+
const votingKeyEndEpoch = Convert.uintArray8ToNumber(file.slice(8, 16))
|
|
150
|
+
const votingPublicKey = Convert.uint8ToHex(file.slice(32, 64))
|
|
151
|
+
|
|
152
|
+
const items = votingKeyEndEpoch - votingKeyStartEpoch + 1
|
|
153
|
+
const headerSize = 64 + 16
|
|
154
|
+
const itemSize = 32 + 64
|
|
155
|
+
const totalSize = headerSize + items * itemSize
|
|
156
|
+
|
|
157
|
+
if (file.length != totalSize) {
|
|
158
|
+
throw new Error(`Unexpected voting key file. Expected ${totalSize} but got ${file.length}`)
|
|
159
|
+
}
|
|
160
|
+
return {
|
|
161
|
+
publicKey: votingPublicKey,
|
|
162
|
+
startEpoch: votingKeyStartEpoch,
|
|
163
|
+
endEpoch: votingKeyEndEpoch,
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
public loadVotingFiles(folder: string): VotingKeyFile[] {
|
|
168
|
+
if (!existsSync(folder)) {
|
|
169
|
+
return []
|
|
170
|
+
}
|
|
171
|
+
return readdirSync(folder)
|
|
172
|
+
.map((filename: string) => {
|
|
173
|
+
const currentPath = join(folder, filename)
|
|
174
|
+
if (lstatSync(currentPath).isFile() && filename.startsWith('private_key_tree') && filename.endsWith('.dat')) {
|
|
175
|
+
return { ...this.readVotingFile(readFileSync(currentPath)), filename }
|
|
176
|
+
} else {
|
|
177
|
+
return undefined
|
|
178
|
+
}
|
|
179
|
+
})
|
|
180
|
+
.filter((i) => i)
|
|
181
|
+
.map((i) => i as VotingKeyFile)
|
|
182
|
+
.sort((a, b) => a.startEpoch - b.startEpoch)
|
|
183
|
+
}
|
|
184
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export interface AccountInfo {
|
|
2
|
+
url: string,
|
|
3
|
+
network: string,
|
|
4
|
+
account: string,
|
|
5
|
+
address: string,
|
|
6
|
+
mosaics: mosaics[],
|
|
7
|
+
linkedKeys: {
|
|
8
|
+
linked: string,
|
|
9
|
+
node: string,
|
|
10
|
+
vrf: string,
|
|
11
|
+
voting: string
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
interface mosaics {
|
|
16
|
+
mosaic: string;
|
|
17
|
+
amount: string;
|
|
18
|
+
namespaceAlias: string;
|
|
19
|
+
}[]
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
export interface ConfigFile {
|
|
2
|
+
url: string
|
|
3
|
+
workAccount: ConfigAccount
|
|
4
|
+
balanceAccount: ConfigAccount
|
|
5
|
+
mainAccount: ConfigAccount
|
|
6
|
+
keylink: {
|
|
7
|
+
vrf: ConfigAccount
|
|
8
|
+
voting: ConfigAccount
|
|
9
|
+
}
|
|
10
|
+
multisig?: {
|
|
11
|
+
minApproval?: number
|
|
12
|
+
minRemoval?: number
|
|
13
|
+
addCosigner?: ConfigAccount[]
|
|
14
|
+
delCosigner?: ConfigAccount[]
|
|
15
|
+
}
|
|
16
|
+
test1Account: ConfigAccount
|
|
17
|
+
test2Account: ConfigAccount
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
interface ConfigAccount {
|
|
21
|
+
publicKey: string
|
|
22
|
+
privateKey: string
|
|
23
|
+
address: string
|
|
24
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export interface IAccountGenerateOption {
|
|
2
|
+
url?: string
|
|
3
|
+
nodename?: string
|
|
4
|
+
readfile?: string
|
|
5
|
+
writefile?: string
|
|
6
|
+
certsdir?: string
|
|
7
|
+
privatekey?: string
|
|
8
|
+
service: boolean
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export interface IAccountInfoOption {
|
|
12
|
+
type?: string
|
|
13
|
+
url?: string
|
|
14
|
+
readfile?: string
|
|
15
|
+
address?: string
|
|
16
|
+
}
|