go port
This commit is contained in:
@@ -0,0 +1,309 @@
|
||||
// Command kisstron is a KISS-oriented TRON CLI wallet. It is a Go port of the
|
||||
// original Python kisstron.py, preserving the command-line interface, output
|
||||
// format and operation semantics.
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"code.luxferre.top/luxferre/kisstron/cli"
|
||||
"code.luxferre.top/luxferre/kisstron/ops"
|
||||
"code.luxferre.top/luxferre/kisstron/tron"
|
||||
)
|
||||
|
||||
// options holds the parsed command-line parameters.
|
||||
type options struct {
|
||||
op string
|
||||
coin string
|
||||
network string
|
||||
node string
|
||||
tgkey string
|
||||
feeLimit float64
|
||||
address string
|
||||
amount float64
|
||||
pk string
|
||||
offline bool
|
||||
passphrase string
|
||||
wordsCount int
|
||||
derivPath string
|
||||
noConfirm bool
|
||||
}
|
||||
|
||||
// usageText builds the argparse-style usage block for prog.
|
||||
func usageText(prog string) string {
|
||||
return "usage: " + prog + " [-h] [-net NETWORK] [-node TRON_NODE]\n" +
|
||||
" [-tgkey TRONGRID_API_KEY] [-fee FEE_LIMIT] [-addr ADDRESS]\n" +
|
||||
" [-amt AMOUNT] [-pk PRIVATE_KEY] [-o] [-pp PASSPHRASE]\n" +
|
||||
" [-wc WORDS_COUNT] [-dp DERIVATION_PATH] [-nc]\n" +
|
||||
" op coin\n"
|
||||
}
|
||||
|
||||
// helpText builds the argparse-style full help output for prog.
|
||||
func helpText(prog string) string {
|
||||
return usageText(prog) + "\n" +
|
||||
"kisstron: a simple CLI wallet for Tron blockchain\n\n" +
|
||||
"positional arguments:\n" +
|
||||
" op Operation: new, import, info, send, fre[eze],\n" +
|
||||
" unf[reeze], bro[adcast]\n" +
|
||||
" coin Coin to operate on: trx (default), usdt, usdc\n\n" +
|
||||
"options:\n" +
|
||||
" -h, --help show this help message and exit\n" +
|
||||
" -net, --network NETWORK\n" +
|
||||
" (op: all) Network: mainnet (default), nile, shasta\n" +
|
||||
" -node, --tron-node TRON_NODE\n" +
|
||||
" (op: all) Custom Tron node HTTP address (default\n" +
|
||||
" " + tron.DefaultNodeAddr + ")\n" +
|
||||
" -tgkey, --trongrid-api-key TRONGRID_API_KEY\n" +
|
||||
" (op: all) TronGrid API key (default None)\n" +
|
||||
" -fee, --fee-limit FEE_LIMIT\n" +
|
||||
" (op: send, fre, unf) Custom fee limit for mainnet\n" +
|
||||
" transactions, in TRX (default 100)\n" +
|
||||
" -addr, --address ADDRESS\n" +
|
||||
" (op: info, send) Tron address to send to or display\n" +
|
||||
" information about (-pk overrides this to display own\n" +
|
||||
" info)\n" +
|
||||
" -amt, --amount AMOUNT\n" +
|
||||
" (op: send, fre, unf) Amount to send/freeze/unfreeze\n" +
|
||||
" -pk, --private-key PRIVATE_KEY\n" +
|
||||
" (op: info, send, fre, unf) Private key for\n" +
|
||||
" transactions or own information, in hex\n" +
|
||||
" -o, --offline (op: send, fre, unf) Build and sign the transaction in\n" +
|
||||
" offline mode and output its JSON\n" +
|
||||
" -pp, --passphrase PASSPHRASE\n" +
|
||||
" (op: new, import) Mnemonic encryption passphrase\n" +
|
||||
" (default empty)\n" +
|
||||
" -wc, --words-count WORDS_COUNT\n" +
|
||||
" (op: new) Word count in the mnemonic: 12 (default),\n" +
|
||||
" 15, 18, 21, 24\n" +
|
||||
" -dp, --derivation-path DERIVATION_PATH\n" +
|
||||
" (op: new, import) BIP-39 derivation path for mnemonic\n" +
|
||||
" (default is m/44'/195'/0'/0/0)\n" +
|
||||
" -nc, --no-confirm (op: send, fre, unf) Do not ask to type \"yes\" for\n" +
|
||||
" transaction confirmation (dangerous)\n\n" +
|
||||
"(c) Luxferre 2024 --- No rights reserved <https://unlicense.org>\n"
|
||||
}
|
||||
|
||||
func main() {
|
||||
prog := filepath.Base(os.Args[0])
|
||||
args, err := parseArgs(prog, os.Args[1:])
|
||||
if err != nil {
|
||||
fmt.Fprint(os.Stderr, usageText(prog))
|
||||
fmt.Fprintln(os.Stderr, err)
|
||||
os.Exit(2)
|
||||
}
|
||||
os.Exit(run(args))
|
||||
}
|
||||
|
||||
// canonical maps a flag token to its canonical name.
|
||||
var canonical = map[string]string{
|
||||
"-h": "help", "--help": "help",
|
||||
"-net": "net", "--network": "net",
|
||||
"-node": "node", "--tron-node": "node",
|
||||
"-tgkey": "tgkey", "--trongrid-api-key": "tgkey",
|
||||
"-fee": "fee", "--fee-limit": "fee",
|
||||
"-addr": "addr", "--address": "addr",
|
||||
"-amt": "amt", "--amount": "amt",
|
||||
"-pk": "pk", "--private-key": "pk",
|
||||
"-o": "offline", "--offline": "offline",
|
||||
"-pp": "pp", "--passphrase": "pp",
|
||||
"-wc": "wc", "--words-count": "wc",
|
||||
"-dp": "dp", "--derivation-path": "dp",
|
||||
"-nc": "nc", "--no-confirm": "nc",
|
||||
}
|
||||
|
||||
// flagTakesValue reports whether a canonical flag consumes the next argument.
|
||||
func flagTakesValue(name string) bool {
|
||||
switch name {
|
||||
case "net", "node", "tgkey", "fee", "addr", "amt", "pk", "pp", "wc", "dp":
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// errRequired formats argparse's missing-argument error.
|
||||
func errRequired(prog string, names ...string) error {
|
||||
return fmt.Errorf("%s: error: the following arguments are required: %s", prog, strings.Join(names, ", "))
|
||||
}
|
||||
|
||||
func parseArgs(prog string, argv []string) (options, error) {
|
||||
o := options{
|
||||
coin: "trx",
|
||||
network: "mainnet",
|
||||
feeLimit: 100.0,
|
||||
wordsCount: 12,
|
||||
derivPath: "m/44'/195'/0'/0/0",
|
||||
}
|
||||
var positionals []string
|
||||
i := 0
|
||||
for i < len(argv) {
|
||||
tok := argv[i]
|
||||
name, inlineVal, hasInline := tok, "", false
|
||||
if strings.HasPrefix(tok, "--") {
|
||||
if eq := strings.IndexByte(tok, '='); eq >= 0 {
|
||||
name = tok[:eq]
|
||||
inlineVal = tok[eq+1:]
|
||||
hasInline = true
|
||||
}
|
||||
}
|
||||
canon, isFlag := canonical[name]
|
||||
if !isFlag {
|
||||
if len(tok) > 0 && tok[0] == '-' {
|
||||
return o, fmt.Errorf("%s: error: unrecognized arguments: %s", prog, tok)
|
||||
}
|
||||
positionals = append(positionals, tok)
|
||||
i++
|
||||
continue
|
||||
}
|
||||
if canon == "help" {
|
||||
fmt.Print(helpText(prog))
|
||||
os.Exit(0)
|
||||
}
|
||||
if flagTakesValue(canon) {
|
||||
var val string
|
||||
if hasInline {
|
||||
val = inlineVal
|
||||
} else {
|
||||
if i+1 >= len(argv) {
|
||||
return o, fmt.Errorf("%s: error: argument %s: expected one argument", tok, prog)
|
||||
}
|
||||
val = argv[i+1]
|
||||
i++
|
||||
}
|
||||
if err := assignValue(&o, prog, canon, val, tok); err != nil {
|
||||
return o, err
|
||||
}
|
||||
} else {
|
||||
switch canon {
|
||||
case "offline":
|
||||
o.offline = true
|
||||
case "nc":
|
||||
o.noConfirm = true
|
||||
}
|
||||
}
|
||||
i++
|
||||
}
|
||||
|
||||
switch len(positionals) {
|
||||
case 0:
|
||||
return o, errRequired(prog, "op", "coin")
|
||||
case 1:
|
||||
return o, errRequired(prog, "coin")
|
||||
case 2:
|
||||
o.op = strings.ToLower(positionals[0])
|
||||
o.coin = strings.ToLower(positionals[1])
|
||||
default:
|
||||
return o, fmt.Errorf("%s: error: unrecognized arguments: %s", prog, strings.Join(positionals[2:], " "))
|
||||
}
|
||||
return o, nil
|
||||
}
|
||||
|
||||
func assignValue(o *options, prog, canon, val, raw string) error {
|
||||
switch canon {
|
||||
case "net":
|
||||
o.network = strings.ToLower(val)
|
||||
case "node":
|
||||
o.node = val
|
||||
case "tgkey":
|
||||
o.tgkey = val
|
||||
case "addr":
|
||||
o.address = val
|
||||
case "pk":
|
||||
o.pk = val
|
||||
case "pp":
|
||||
o.passphrase = val
|
||||
case "dp":
|
||||
o.derivPath = val
|
||||
case "fee":
|
||||
f, err := strconv.ParseFloat(val, 64)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%s: error: argument -fee/--fee-limit: invalid float value: '%s'", prog, val)
|
||||
}
|
||||
o.feeLimit = f
|
||||
case "amt":
|
||||
f, err := strconv.ParseFloat(val, 64)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%s: error: argument -amt/--amount: invalid float value: '%s'", prog, val)
|
||||
}
|
||||
o.amount = f
|
||||
case "wc":
|
||||
n, err := strconv.Atoi(val)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%s: error: argument -wc/--words-count: invalid int value: '%s'", prog, val)
|
||||
}
|
||||
o.wordsCount = n
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// run dispatches the operation and prints the result. It returns the process
|
||||
// exit code.
|
||||
func run(o options) int {
|
||||
cfg := ops.Config{
|
||||
Network: o.network,
|
||||
Node: o.node,
|
||||
APIKey: o.tgkey,
|
||||
FeeLimit: int64(o.feeLimit * tron.TrxScale),
|
||||
Passphrase: o.passphrase,
|
||||
WordsCount: o.wordsCount,
|
||||
Derivation: o.derivPath,
|
||||
}
|
||||
|
||||
var res cli.Result
|
||||
switch o.op {
|
||||
case "new":
|
||||
res = ops.NewWallet(cfg)
|
||||
case "import":
|
||||
mnemonic := cli.ReadLine("Your mnemonic phrase: ")
|
||||
res = ops.ImportWallet(cfg, mnemonic)
|
||||
case "info":
|
||||
if o.offline {
|
||||
fmt.Println("Info operation is not available in offline mode!")
|
||||
return 1
|
||||
}
|
||||
if o.pk == "" {
|
||||
res = ops.InfoByAddr(cfg, o.address)
|
||||
} else {
|
||||
res = ops.InfoByPK(cfg, o.pk)
|
||||
}
|
||||
case "send":
|
||||
if !o.noConfirm {
|
||||
cli.Confirm("Enter \"yes\" to confirm sending funds: ")
|
||||
}
|
||||
if o.coin == "trx" {
|
||||
res = ops.SendTrx(cfg, o.pk, o.address, o.amount, o.offline)
|
||||
} else {
|
||||
res = ops.SendTrc20(cfg, o.pk, o.coin, o.address, o.amount, o.offline)
|
||||
}
|
||||
case "fre", "freeze":
|
||||
if !o.noConfirm {
|
||||
cli.Confirm("Enter \"yes\" to confirm freezing funds: ")
|
||||
}
|
||||
res = ops.FreezeTrx(cfg, o.pk, o.amount, o.offline)
|
||||
case "unf", "unfreeze":
|
||||
if !o.noConfirm {
|
||||
cli.Confirm("Enter \"yes\" to confirm unfreezing funds: ")
|
||||
}
|
||||
res = ops.UnfreezeTrx(cfg, o.pk, o.amount, o.offline)
|
||||
case "bro", "broadcast":
|
||||
if o.offline {
|
||||
fmt.Println("Broadcast operation is not available in offline mode!")
|
||||
return 1
|
||||
}
|
||||
tjson := cli.ReadLine("Input transaction JSON: ")
|
||||
if !o.noConfirm {
|
||||
cli.Confirm("Enter \"yes\" to confirm sending the transaction: ")
|
||||
}
|
||||
res = ops.BroadcastJSON(cfg, tjson)
|
||||
default:
|
||||
fmt.Println("Unknown operation!")
|
||||
fmt.Println("Run the script with -h parameter to see available options")
|
||||
return 1
|
||||
}
|
||||
|
||||
cli.PrintResult(res)
|
||||
return 0
|
||||
}
|
||||
Reference in New Issue
Block a user