/
walletcmd.go
443 lines (394 loc) · 15.3 KB
/
walletcmd.go
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
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
package client
import (
"encoding/json"
"fmt"
"os"
"strconv"
tbtypes "github.com/threefoldfoundation/tfchain/extensions/threebot/types"
"github.com/threefoldtech/rivine/pkg/cli"
"github.com/threefoldtech/rivine/pkg/client"
rivinecli "github.com/threefoldtech/rivine/pkg/client"
rivinetypes "github.com/threefoldtech/rivine/types"
"github.com/spf13/cobra"
)
// CreateWalletCmds creates the threebot wallet root command as well as its transaction creation sub commands.
func CreateWalletCmds(ccli *client.CommandLineClient) error {
bc, err := client.NewLazyBaseClientFromCommandLineClient(ccli)
if err != nil {
return err
}
walletCmd := &walletCmd{
cli: ccli,
walletClient: rivinecli.NewWalletClient(bc),
txPoolClient: rivinecli.NewTransactionPoolClient(bc),
tbClient: NewPluginExplorerClient(bc),
}
// define commands
var (
sendBotRegistrationTxCmd = &cobra.Command{
Use: "botregistration",
Short: "Create, sign and send a new 3bot registration transaction",
Long: `Create, sign and send a new 3bot registration transaction, prepaying 1 month by default.
The coin inputs are funded and signed using the wallet of this daemon.
By default a public key is generated from this wallet's primary seed as well,
however, it is also allowed for you to give a public key that is already loaded in this wallet,
for the creation of the 3bot.
Addresses and names are added as flags, and at least one of both is required.
Multiple addresses and names are allowed as well, of course.
Should you want to prepay more than 1 month, this has to be specified as a flag as well.
One might want to do this, as the ThreefoldFoundation gives 30% discount for 12+ (bot) months,
and 50% discount for 24 (bot) months (the maximum).
All fees are automatically added.
If this command returns without errors, the Tx is signed and sent,
and you'll receive the TxID and PublicKey which will allow you to look it up in an explorer.
The public key is to be used to get to know the unique ID assigned to your registered bot (if succesfull).
`,
Run: rivinecli.Wrap(walletCmd.sendBotRegistrationTxCmd),
}
sendBotRecordUpdateTxCmd = &cobra.Command{
Use: "botupdate (id|publickey)",
Short: "Create, sign and send a 3bot record update transaction",
Long: `Create, sign and send a 3bot record update transaction, updating an existing 3bot.
The coin inputs are funded and signed using the wallet of this daemon.
The Public key linked to the 3bot has to be loaded into the wallet in order to be able to sign.
Addresses and names to be removed/added are defined as flags, and at least one
update is required (defining NrOfMonths to add (and pay) to the 3bot record counts as an update as well).
> NOTE: a name can only be removed if owned (which implies the 3bot has to be active at the point of the update).
Should you want to prepay more than 1 month at once, this is possible and
the ThreefoldFoundation gives 30% discount for 12+ (bot) months,
and 50% discount for 24 (bot) months (the maximum).
All fees are automatically added.
If this command returns without errors, the Tx is signed and sent,
and you'll receive the TxID which will allow you to look it up in an explorer.
`,
Run: rivinecli.Wrap(walletCmd.sendBotRecordUpdateTxCmd),
}
createBotNameTransferTxCmd = &cobra.Command{
Use: "botnametransfer (id|publickey) (id|publickey) names...",
Args: cobra.MinimumNArgs(3),
Short: "Create and optionally sign a 3bot name transfer transaction",
Long: `Create and optionally sign a 3bot name transfer transaction, involving two active 3bots.
The coin inputs are funded and signed using the wallet of this daemon.
The Public key linked to the 3bot has to be loaded into the wallet in order to be able to sign.
The first positional argument identifies the sender, nad the second positional argument identifies the receiver.
All other positional arguments (at least one more is required) define the names to be transfered.
At least one name has to be transferred.
All fees are automatically added.
If this command returns without errors, the Tx (optionally signed)
is printed to the STDOUT.
`,
Run: walletCmd.createBotNameTransferTxCmd,
}
)
// add commands as wallet sub commands
ccli.WalletCmd.RootCmdCreate.AddCommand(
createBotNameTransferTxCmd,
)
ccli.WalletCmd.RootCmdSend.AddCommand(
sendBotRegistrationTxCmd,
sendBotRecordUpdateTxCmd,
)
// register flags
NetworkAddressArrayFlagVar(
sendBotRegistrationTxCmd.Flags(),
&walletCmd.sendBotRegistrationTxCfg.Addresses,
"address",
"add one or multiple addresses, each address defined as seperate flag arguments",
)
BotNameArrayFlagVar(
sendBotRegistrationTxCmd.Flags(),
&walletCmd.sendBotRegistrationTxCfg.Names,
"name",
"add one or multiple names, each name defined as seperate flag arguments",
)
sendBotRegistrationTxCmd.Flags().Uint8VarP(
&walletCmd.sendBotRegistrationTxCfg.NrOfMonths, "months", "m", 1,
"the amount of months to prepay, required to be in the inclusive interval [1, 24]")
PublicKeyFlagVar(
sendBotRegistrationTxCmd.Flags(),
&walletCmd.sendBotRegistrationTxCfg.PublicKey,
"public-key",
"define a public key to use (of which the private key is loaded in this daemon's wallet)",
)
sendBotRegistrationTxCmd.Flags().Var(
cli.NewEncodingTypeFlag(0, &walletCmd.sendBotRegistrationTxCfg.EncodingType, cli.EncodingTypeHuman|cli.EncodingTypeJSON), "encoding",
cli.EncodingTypeFlagDescription(cli.EncodingTypeHuman|cli.EncodingTypeJSON))
NetworkAddressArrayFlagVar(
sendBotRecordUpdateTxCmd.Flags(),
&walletCmd.sendBotRecordUpdateTxCfg.AddressesToAdd,
"add-address",
"add one or multiple addresses, each address defined as seperate flag arguments",
)
NetworkAddressArrayFlagVar(
sendBotRecordUpdateTxCmd.Flags(),
&walletCmd.sendBotRecordUpdateTxCfg.AddressesToRemove,
"remove-address",
"remove one or multiple addresses, each address defined as seperate flag arguments",
)
BotNameArrayFlagVar(
sendBotRecordUpdateTxCmd.Flags(),
&walletCmd.sendBotRecordUpdateTxCfg.NamesToAdd,
"add-name",
"add one or multiple names, each name defined as seperate flag arguments",
)
BotNameArrayFlagVar(
sendBotRecordUpdateTxCmd.Flags(),
&walletCmd.sendBotRecordUpdateTxCfg.NamesToRemove,
"remove-name",
"remove one or multiple names owned, each name defined as seperate flag arguments",
)
sendBotRecordUpdateTxCmd.Flags().Uint8VarP(
&walletCmd.sendBotRecordUpdateTxCfg.NrOfMonthsToAdd, "add-months", "m", 0,
"the amount of months to add and pay, required to be in the inclusive interval [0, 24]")
sendBotRecordUpdateTxCmd.Flags().Var(
cli.NewEncodingTypeFlag(0, &walletCmd.sendBotRecordUpdateTxCfg.EncodingType, cli.EncodingTypeHuman|cli.EncodingTypeJSON), "encoding",
cli.EncodingTypeFlagDescription(cli.EncodingTypeHuman|cli.EncodingTypeJSON))
createBotNameTransferTxCmd.Flags().Var(
cli.NewEncodingTypeFlag(0, &walletCmd.createBotNameTransferTxCfg.EncodingType, cli.EncodingTypeHuman|cli.EncodingTypeJSON), "encoding",
cli.EncodingTypeFlagDescription(cli.EncodingTypeHuman|cli.EncodingTypeJSON))
createBotNameTransferTxCmd.Flags().BoolVar(
&walletCmd.createBotNameTransferTxCfg.Sign, "sign", false,
"optionally sign the transaction (as sender/receiver) prior to printing it")
return nil
}
type walletCmd struct {
cli *rivinecli.CommandLineClient
walletClient *rivinecli.WalletClient
txPoolClient *rivinecli.TransactionPoolClient
tbClient *PluginClient
sendBotRegistrationTxCfg struct {
Addresses []tbtypes.NetworkAddress
Names []tbtypes.BotName
NrOfMonths uint8
PublicKey rivinetypes.PublicKey
EncodingType cli.EncodingType
}
sendBotRecordUpdateTxCfg struct {
AddressesToAdd []tbtypes.NetworkAddress
AddressesToRemove []tbtypes.NetworkAddress
NamesToAdd []tbtypes.BotName
NamesToRemove []tbtypes.BotName
NrOfMonthsToAdd uint8
EncodingType cli.EncodingType
}
createBotNameTransferTxCfg struct {
EncodingType cli.EncodingType
Sign bool
}
}
func (walletCmd *walletCmd) sendBotRegistrationTxCmd() {
// validate the flags
if len(walletCmd.sendBotRegistrationTxCfg.Addresses) == 0 && len(walletCmd.sendBotRegistrationTxCfg.Names) == 0 {
cli.Die("the registration of a 3bot requires at least one name or address to be defined")
return
}
if walletCmd.sendBotRegistrationTxCfg.NrOfMonths == 0 || walletCmd.sendBotRegistrationTxCfg.NrOfMonths > 24 {
cli.Die("the number of (prepaid) (bot) months has to be in the inclusive interval [1,24]")
return
}
var err error
pk := walletCmd.sendBotRegistrationTxCfg.PublicKey
if pk.Algorithm == 0 && len(pk.Key) == 0 {
pk, err = walletCmd.walletClient.NewPublicKey()
if err != nil {
cli.DieWithError("failed to generate new public key", err)
return
}
}
// create the registration Tx
tx := tbtypes.BotRegistrationTransaction{
Addresses: walletCmd.sendBotRegistrationTxCfg.Addresses,
Names: walletCmd.sendBotRegistrationTxCfg.Names,
NrOfMonths: walletCmd.sendBotRegistrationTxCfg.NrOfMonths,
TransactionFee: walletCmd.cli.Config.MinimumTransactionFee,
Identification: tbtypes.PublicKeySignaturePair{
PublicKey: pk,
},
}
// compute the additional (bot) fee, such that we can fund it all
fee := tx.RequiredBotFee(walletCmd.cli.Config.CurrencyUnits.OneCoin)
// fund the coin inputs
tx.CoinInputs, tx.RefundCoinOutput, err = walletCmd.walletClient.FundCoins(fee.Add(walletCmd.cli.Config.MinimumTransactionFee), nil, false)
if err != nil {
cli.DieWithError("failed to fund the bot registration Tx", err)
return
}
// sign the Tx
rtx := tx.Transaction(walletCmd.cli.Config.CurrencyUnits.OneCoin)
err = walletCmd.walletClient.GreedySignTx(&rtx)
if err != nil {
cli.DieWithError("failed to sign the bot registration Tx", err)
return
}
// submit the Tx
txID, err := walletCmd.txPoolClient.AddTransactiom(rtx)
if err != nil {
b, _ := json.Marshal(rtx)
fmt.Fprintln(os.Stderr, "bad tx: "+string(b))
cli.DieWithError("failed to submit the bot registration Tx to the Tx Pool", err)
return
}
// encode depending on the encoding flag
var encode func(interface{}) error
switch walletCmd.sendBotRegistrationTxCfg.EncodingType {
case cli.EncodingTypeHuman:
e := json.NewEncoder(os.Stdout)
e.SetIndent("", " ")
encode = e.Encode
case cli.EncodingTypeJSON:
encode = json.NewEncoder(os.Stdout).Encode
}
err = encode(map[string]interface{}{
"publickey": pk,
"transactionid": txID,
})
if err != nil {
cli.DieWithError("failed to encode result", err)
}
}
func (walletCmd *walletCmd) sendBotRecordUpdateTxCmd(str string) {
id, err := walletCmd.botIDFromPosArgStr(str)
if err != nil {
cli.DieWithError("failed to parse/fetch unique ID", err)
return
}
// create the record update Tx
tx := tbtypes.BotRecordUpdateTransaction{
Identifier: id,
Addresses: tbtypes.BotRecordAddressUpdate{
Add: walletCmd.sendBotRecordUpdateTxCfg.AddressesToAdd,
Remove: walletCmd.sendBotRecordUpdateTxCfg.AddressesToRemove,
},
Names: tbtypes.BotRecordNameUpdate{
Add: walletCmd.sendBotRecordUpdateTxCfg.NamesToAdd,
Remove: walletCmd.sendBotRecordUpdateTxCfg.NamesToRemove,
},
NrOfMonths: walletCmd.sendBotRecordUpdateTxCfg.NrOfMonthsToAdd,
TransactionFee: walletCmd.cli.Config.MinimumTransactionFee,
}
// compute the additional (bot) fee, such that we can fund it all
fee := tx.RequiredBotFee(walletCmd.cli.Config.CurrencyUnits.OneCoin)
// fund the coin inputs
tx.CoinInputs, tx.RefundCoinOutput, err = walletCmd.walletClient.FundCoins(fee.Add(walletCmd.cli.Config.MinimumTransactionFee), nil, false)
if err != nil {
cli.DieWithError("failed to fund the bot record update Tx", err)
return
}
// sign the Tx
rtx := tx.Transaction(walletCmd.cli.Config.CurrencyUnits.OneCoin)
err = walletCmd.walletClient.GreedySignTx(&rtx)
if err != nil {
cli.DieWithError("failed to sign the bot record update Tx", err)
return
}
// submit the Tx
txID, err := walletCmd.txPoolClient.AddTransactiom(rtx)
if err != nil {
b, _ := json.Marshal(rtx)
fmt.Fprintln(os.Stderr, "bad tx: "+string(b))
cli.DieWithError("failed to submit the bot record update Tx to the Tx Pool", err)
return
}
// encode depending on the encoding flag
var encode func(interface{}) error
switch walletCmd.sendBotRecordUpdateTxCfg.EncodingType {
case cli.EncodingTypeHuman:
e := json.NewEncoder(os.Stdout)
e.SetIndent("", " ")
encode = e.Encode
case cli.EncodingTypeJSON:
encode = json.NewEncoder(os.Stdout).Encode
}
err = encode(map[string]interface{}{
"transactionid": txID,
})
if err != nil {
cli.DieWithError("failed to encode result", err)
}
}
// create botnametransfer (publickey|id) (publickey|id) names...
// arguments in order: sender, receiver and a slice of names (at least one name is required),
// hence this command requires a minimum of 3 arguments
func (walletCmd *walletCmd) createBotNameTransferTxCmd(cmd *cobra.Command, args []string) {
senderID, err := walletCmd.botIDFromPosArgStr(args[0])
if err != nil {
cli.DieWithError("failed to parse/fetch unique (sender bot) ID", err)
return
}
receiverID, err := walletCmd.botIDFromPosArgStr(args[1])
if err != nil {
cli.DieWithError("failed to parse/fetch unique (receiver bot) ID", err)
return
}
names := make([]tbtypes.BotName, len(args[2:]))
for idx, str := range args[2:] {
err = names[idx].LoadString(str)
if err != nil {
cli.DieWithError("failed to parse (pos arg) bot name #"+strconv.Itoa(idx+1), err)
return
}
}
// create the bot name transfer Tx
tx := tbtypes.BotNameTransferTransaction{
Sender: tbtypes.BotIdentifierSignaturePair{
Identifier: senderID,
},
Receiver: tbtypes.BotIdentifierSignaturePair{
Identifier: receiverID,
},
Names: names,
TransactionFee: walletCmd.cli.Config.MinimumTransactionFee,
}
// compute the additional (bot) fee, such that we can fund it all
fee := tx.RequiredBotFee(walletCmd.cli.Config.CurrencyUnits.OneCoin)
// fund the coin inputs
tx.CoinInputs, tx.RefundCoinOutput, err = walletCmd.walletClient.FundCoins(fee.Add(walletCmd.cli.Config.MinimumTransactionFee), nil, false)
if err != nil {
cli.DieWithError("failed to fund the bot name transfer Tx", err)
return
}
rtx := tx.Transaction(walletCmd.cli.Config.CurrencyUnits.OneCoin)
if walletCmd.createBotNameTransferTxCfg.Sign {
// optionally sign the Tx
err = walletCmd.walletClient.GreedySignTx(&rtx)
if err != nil {
cli.DieWithError("failed to sign the bot name transfer Tx", err)
return
}
}
// encode depending on the encoding flag
var encode func(interface{}) error
switch walletCmd.createBotNameTransferTxCfg.EncodingType {
case cli.EncodingTypeHuman:
e := json.NewEncoder(os.Stdout)
e.SetIndent("", " ")
encode = e.Encode
case cli.EncodingTypeJSON:
encode = json.NewEncoder(os.Stdout).Encode
}
err = encode(rtx)
if err != nil {
cli.DieWithError("failed to encode result", err)
}
}
func (walletCmd *walletCmd) botIDFromPosArgStr(str string) (tbtypes.BotID, error) {
if len(str) < 16 {
// assume bot ID if the less than 16, seems to short for a public key,
// so simply return it (as well as the possible parsing error for assuming wrong)
var botID tbtypes.BotID
err := botID.LoadString(str)
return botID, err
}
// assume a public key was meant,
// so we need to get the (bot) record in order to know the (unique) ID
var pk rivinetypes.PublicKey
err := pk.LoadString(str)
if err != nil {
return 0, err
}
record, err := walletCmd.tbClient.GetRecordForKey(pk)
if err != nil {
return 0, err
}
return record.ID, nil
}