diff --git a/.coderabbit.yaml b/.coderabbit.yaml new file mode 100644 index 000000000..04735f662 --- /dev/null +++ b/.coderabbit.yaml @@ -0,0 +1,57 @@ +# yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json +language: en-US +early_access: true +tone_instructions: > + Be concise and direct. Focus on correctness, security, and Go idioms. +reviews: + profile: chill + request_changes_workflow: true + high_level_summary: true + sequence_diagrams: true + collapse_walkthrough: true + poem: false + review_status: true + auto_review: + enabled: true + drafts: false + base_branches: + - master + path_filters: + - "!**/*.pb.go" + - "!vendor/**" + path_instructions: + - path: "pkg/proto/**" + instructions: "Auto-generated protobuf code. Do not review for style or conventions." + - path: "cmd/**" + instructions: "CLI layer using Cobra framework. Focus on UX, flag handling, and error messages." + - path: "pkg/client/**" + instructions: > + Core gRPC client for TRON blockchain. Focus on error handling, + resource management, and API correctness. + - path: "pkg/keys/**" + instructions: > + Cryptographic key management with HD wallets (BIP39/BIP44). + Pay close attention to security: key material handling, + constant-time comparisons, and proper zeroing of sensitive data. + - path: "pkg/keystore/**" + instructions: > + Encrypted keystore implementation. Review for secure storage + practices, proper encryption, and safe key derivation. + - path: "pkg/address/**" + instructions: "TRON address encoding/decoding with Base58 checksum validation." + tools: + gitleaks: + enabled: true + actionlint: + enabled: true + yamllint: + enabled: true + checkmake: + enabled: true +chat: + auto_reply: true +knowledge_base: + learnings: + scope: local + issues: + scope: local diff --git a/.githooks/commit-msg b/.githooks/commit-msg new file mode 100755 index 000000000..0e138ea10 --- /dev/null +++ b/.githooks/commit-msg @@ -0,0 +1,33 @@ +#!/bin/bash +set -e + +# Colors for output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[0;33m' +NC='\033[0m' # No Color + +COMMIT_MSG_FILE=$1 +COMMIT_MSG=$(cat $COMMIT_MSG_FILE) + +# Define regex patterns for commit message +CONVENTIONAL_PATTERN='^(feat|fix|docs|style|refactor|perf|test|build|ci|chore|revert)(\(.+\))?: .{1,100}' +TICKET_PATTERN='(TRON-[0-9]+)' + +# Check if commit message follows conventional commits pattern +if ! [[ $COMMIT_MSG =~ $CONVENTIONAL_PATTERN ]]; then + echo -e "${RED}Error:${NC} Commit message does not follow conventional commits format." + echo -e "${YELLOW}Format:${NC} type(scope): description" + echo -e "${YELLOW}Example:${NC} feat(cli): add new transaction command" + echo -e "${YELLOW}Types:${NC} feat, fix, docs, style, refactor, perf, test, build, ci, chore, revert" + exit 1 +fi + +# Warn if no ticket number is found (but don't block commit) +if ! [[ $COMMIT_MSG =~ $TICKET_PATTERN ]]; then + echo -e "${YELLOW}Warning:${NC} No TRON-XXX ticket number found in commit message." + echo -e "${YELLOW}Consider:${NC} Including ticket number like TRON-123 in description or scope" +fi + +echo -e "${GREEN}✓${NC} Commit message format is valid" +exit 0 \ No newline at end of file diff --git a/.githooks/install.sh b/.githooks/install.sh new file mode 100644 index 000000000..1709e45e1 --- /dev/null +++ b/.githooks/install.sh @@ -0,0 +1,34 @@ +#!/bin/bash +set -e + +HOOK_DIR=$(git rev-parse --git-path hooks) +REPO_ROOT=$(git rev-parse --show-toplevel) +HOOKS_DIR="$REPO_ROOT/.githooks" + +# Make sure hooks are executable +chmod +x "$HOOKS_DIR/pre-commit" +chmod +x "$HOOKS_DIR/commit-msg" +chmod +x "$HOOKS_DIR/prepare-commit-msg" + +# Create symlinks to hooks +ln -sf "$HOOKS_DIR/pre-commit" "$HOOK_DIR/pre-commit" +ln -sf "$HOOKS_DIR/commit-msg" "$HOOK_DIR/commit-msg" +ln -sf "$HOOKS_DIR/prepare-commit-msg" "$HOOK_DIR/prepare-commit-msg" + +echo "✅ Git hooks installed successfully!" +echo "Pre-commit hook will run: format, lint and test on staged files" +echo "Commit-msg hook will enforce conventional commit format" +echo "Prepare-commit-msg hook will provide a commit message template" + +# Check for required tools +echo "Checking required tools..." + +if ! command -v goimports &> /dev/null; then + echo "⚠️ goimports not found. Please install with:" + echo " go install golang.org/x/tools/cmd/goimports@latest" +fi + +if ! command -v golangci-lint &> /dev/null; then + echo "⚠️ golangci-lint not found. Please install with:" + echo " curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(go env GOPATH)/bin" +fi \ No newline at end of file diff --git a/.githooks/pre-commit b/.githooks/pre-commit new file mode 100755 index 000000000..659fe6600 --- /dev/null +++ b/.githooks/pre-commit @@ -0,0 +1,63 @@ +#!/bin/bash +set -e + +echo "==> Running pre-commit checks..." + +# Get staged Go files +STAGED_GO_FILES=$(git diff --cached --name-only --diff-filter=ACM | grep '\.go$' | grep -v '\.pb\.go$' || true) + +# Skip if no Go files are staged +if [ -z "$STAGED_GO_FILES" ]; then + echo "No Go files staged. Skipping pre-commit checks." + exit 0 +fi + +# Check for goimports +if ! command -v goimports &> /dev/null; then + echo "goimports not found! Please install with:" + echo "go install golang.org/x/tools/cmd/goimports@latest" + exit 1 +fi + +# Check for golangci-lint +if ! command -v golangci-lint &> /dev/null; then + echo "golangci-lint not found! Please install with:" + echo "curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(go env GOPATH)/bin" + exit 1 +fi + +# Format imports +echo "Running goimports..." +make goimports + +# Run go mod tidy +echo "Checking go.mod and go.sum..." +go mod tidy +if [ -n "$(git diff --name-only go.mod go.sum)" ]; then + echo "go.mod or go.sum was modified. Please stage the changes." + git diff --name-only go.mod go.sum + exit 1 +fi + +# Get unique packages from staged files +STAGED_PACKAGES=$(echo "$STAGED_GO_FILES" | xargs -I{} dirname {} | sort -u) + +# Run linter on affected packages +echo "Running linter on staged packages..." +for pkg in $STAGED_PACKAGES; do + golangci-lint run --fast ./$pkg +done + +# Run tests affected by staged files +echo "Running relevant tests..." + +# Run tests only for affected packages +for pkg in $STAGED_PACKAGES; do + if [ -n "$(find $pkg -name '*_test.go' -type f | head -1)" ]; then + echo "Testing package: $pkg" + go test -race ./$pkg + fi +done + +echo "✅ Pre-commit checks passed" +exit 0 \ No newline at end of file diff --git a/.githooks/prepare-commit-msg b/.githooks/prepare-commit-msg new file mode 100755 index 000000000..4b13dc667 --- /dev/null +++ b/.githooks/prepare-commit-msg @@ -0,0 +1,38 @@ +#!/bin/bash + +COMMIT_MSG_FILE=$1 +COMMIT_SOURCE=$2 +SHA1=$3 + +# Only add template if this is not from a merge, amend, etc. +if [ -z "$COMMIT_SOURCE" ]; then + # Check if the commit message already has content (non-commented lines) + if ! grep -q '^[^#]' "$COMMIT_MSG_FILE"; then + # Get the current branch name + BRANCH_NAME=$(git symbolic-ref --short HEAD 2>/dev/null) + + # Extract ticket number from branch name if it follows pattern like feature/TRON-123-description + TICKET="" + if [[ $BRANCH_NAME =~ (TRON-[0-9]+) ]]; then + TICKET=${BASH_REMATCH[1]} + fi + + # Add commit message template + cat > "$COMMIT_MSG_FILE" << EOF +# Select commit type and add optional scope and description +# Format: type(scope): description +# Example: feat(cli): add new transaction feature +# Types: feat, fix, docs, style, refactor, perf, test, build, ci, chore, revert +# +# If applicable, include the TRON ticket number in scope or description +# Current branch: $BRANCH_NAME +EOF + + # If we found a ticket number in the branch name, add it to the template + if [ -n "$TICKET" ]; then + echo "feat($TICKET): " > "$COMMIT_MSG_FILE.tmp" + cat "$COMMIT_MSG_FILE" >> "$COMMIT_MSG_FILE.tmp" + mv "$COMMIT_MSG_FILE.tmp" "$COMMIT_MSG_FILE" + fi + fi +fi \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/SECURITY.md b/.github/ISSUE_TEMPLATE/SECURITY.md new file mode 100644 index 000000000..851b4d96d --- /dev/null +++ b/.github/ISSUE_TEMPLATE/SECURITY.md @@ -0,0 +1,33 @@ +# Security Policy + +## Supported Versions + +The following versions of gotron-sdk are currently being supported with security updates: + +| Version | Supported | +| ------- | ------------------ | +| 2.3.x | :white_check_mark: | +| 2.2.x | :white_check_mark: | +| 2.1.x | :x: | +| < 2.0 | :x: | + +## Reporting a Vulnerability + +We take the security of gotron-sdk seriously. If you believe you've found a security vulnerability, please follow these steps: + +1. **Do not disclose the vulnerability publicly** +2. **Email us directly** at security@cryptochain.network +3. **Include the following information**: + - A description of the vulnerability + - Steps to reproduce the issue + - Potential impact of the vulnerability + - If possible, a suggested fix or mitigation + +## What to expect + +- We will acknowledge receipt of your vulnerability report within 3 business days +- We will provide an initial assessment of the report within 10 business days +- We will keep you informed about our progress throughout the process +- We will notify you when the issue is fixed + +Thank you for helping keep gotron-sdk and its users safe! \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 000000000..e69de29bb diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 000000000..a7634aa50 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,8 @@ +blank_issues_enabled: false +contact_links: + - name: TRON SDK Documentation + url: https://github.com/fbsobreira/gotron-sdk/wiki + about: Check if your question is answered in the documentation. + - name: TRON Network Documentation + url: https://developers.tron.network/ + about: Learn more about TRON network specifications and APIs. \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 000000000..045ee3faf --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,32 @@ +--- +name: Feature request +about: Suggest an idea for this project +title: '[FEATURE] ' +labels: enhancement +assignees: '' +--- + +## Is your feature request related to a problem? Please describe. + + +## Describe the solution you'd like + + +## Describe alternatives you've considered + + +## API Design (if applicable) + +```go +// Example API design +func NewFeature(param1 string, param2 int) (Result, error) { + // Description of what this would do +} +``` + +## Ticket + +TRON- + +## Additional context + \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/question.md b/.github/ISSUE_TEMPLATE/question.md new file mode 100644 index 000000000..4541c460f --- /dev/null +++ b/.github/ISSUE_TEMPLATE/question.md @@ -0,0 +1,28 @@ +--- +name: Question +about: Ask a question about using this SDK +title: '[QUESTION] ' +labels: question +assignees: '' +--- + +## Question + + +## Context + + +## Environment + - OS: [e.g. Ubuntu 22.04, macOS 13.0, Windows 11] + - Go version: [e.g. 1.20.4] + - TRON node version: + - SDK version: [e.g. v2.3.0] + +## Code Example + +```go +// Your code here +``` + +## Additional Information + \ No newline at end of file diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 000000000..744244e24 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,42 @@ +## Description + + + + +Fixes # (issue) + +## Type of change + + + +- [ ] Bug fix (non-breaking change which fixes an issue) +- [ ] New feature (non-breaking change which adds functionality) +- [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected) +- [ ] This change requires a documentation update +- [ ] Refactoring (no functional changes, no API changes) +- [ ] Performance improvement + +## Ticket + + +TRON- + +## How Has This Been Tested? + + + + +- [ ] Unit tests +- [ ] Integration tests +- [ ] Manual tests (please describe below) + +## Checklist: + +- [ ] My code follows the style guidelines of this project +- [ ] I have performed a self-review of my code +- [ ] I have commented my code, particularly in hard-to-understand areas +- [ ] I have made corresponding changes to the documentation +- [ ] My changes generate no new warnings +- [ ] I have added tests that prove my fix is effective or that my feature works +- [ ] New and existing unit tests pass locally with my changes +- [ ] Any dependent changes have been merged and published diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml new file mode 100644 index 000000000..94ccbf2a5 --- /dev/null +++ b/.github/workflows/build.yaml @@ -0,0 +1,33 @@ +name: Build + +on: + push: + branches: [ master, develop ] + pull_request: + branches: [ master, develop ] + + +jobs: + build: + name: Build + runs-on: ubuntu-latest + steps: + - name: Check out code + uses: actions/checkout@v6 + + - name: Set up Go + uses: actions/setup-go@v6 + with: + go-version-file: 'go.mod' + cache: true + + - name: Get dependencies + run: go mod download + + - name: Check build + run: go vet ./... + + - name: Build CLI + run: | + make build + make build-windows diff --git a/.github/workflows/format.yaml b/.github/workflows/format.yaml new file mode 100644 index 000000000..1f6edccf0 --- /dev/null +++ b/.github/workflows/format.yaml @@ -0,0 +1,46 @@ +name: Format and Tidy Check + +on: + push: + branches: [ master, develop ] + pull_request: + branches: [ master, develop ] + +jobs: + format-tidy: + name: Format and Tidy Check + runs-on: ubuntu-latest + steps: + - name: Check out code + uses: actions/checkout@v6 + + - name: Set up Go + uses: actions/setup-go@v6 + with: + go-version-file: 'go.mod' + cache: true + + - name: Install goimports + run: go install golang.org/x/tools/cmd/goimports@latest + + - name: Check goimports + run: | + make goimports + if [ -n "$(git status --porcelain)" ]; then + echo "goimports has modified files. Please run 'make goimports' and commit changes." + exit 1 + fi + + - name: Check go mod tidy + run: | + go mod tidy + if [ -n "$(git status --porcelain go.mod go.sum)" ]; then + echo "go.mod or go.sum is not tidy. Please run 'go mod tidy' and commit changes." + exit 1 + fi + + - name: Install golangci-lint + run: go install github.com/golangci/golangci-lint/cmd/golangci-lint@latest + + - name: Check golangci-lint + run: make lint diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml new file mode 100644 index 000000000..ea484876f --- /dev/null +++ b/.github/workflows/release.yaml @@ -0,0 +1,37 @@ +name: Release + +on: + push: + tags: + - "v*" + +jobs: + test: + uses: ./.github/workflows/test.yaml + + release: + name: Release + permissions: + contents: write + needs: test + runs-on: ubuntu-latest + steps: + - name: Check out code + uses: actions/checkout@v6 + with: + fetch-depth: 0 + + - name: Set up Go + uses: actions/setup-go@v6 + with: + go-version-file: 'go.mod' + cache: true + + - name: Run GoReleaser + uses: goreleaser/goreleaser-action@v7 + with: + distribution: goreleaser + version: "~> v2" + args: release --clean + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml new file mode 100644 index 000000000..3ff30a805 --- /dev/null +++ b/.github/workflows/test.yaml @@ -0,0 +1,35 @@ +name: Tests + +on: + push: + branches: [ master, develop ] + pull_request: + branches: [ master, develop ] + workflow_call: + + +jobs: + test: + name: Test + runs-on: ubuntu-latest + steps: + - name: Check out code + uses: actions/checkout@v6 + + - name: Set up Go + uses: actions/setup-go@v6 + with: + go-version-file: 'go.mod' + cache: true + + - name: Get dependencies + run: go mod download + + - name: Run tests + run: go test -race -shuffle=on -coverprofile=coverage.out -covermode=atomic $(go list ./... | grep -v -E '/pkg/proto/|/cmd') + + - name: Upload coverage to Codecov + uses: codecov/codecov-action@v5 + with: + files: coverage.out + fail_ci_if_error: false diff --git a/.gitignore b/.gitignore index 033cd8c76..e92473619 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,25 @@ +# Go binaries +*.exe +*.exe~ +*.dll +*.so tronctl -tonctl-docs -bin -.idea/ +tronctl-docs +bin/ +dist/ +# scripts +scripts/* +# keys +*.pem *.key -scripts/* \ No newline at end of file +# Go test binary, build with `go test -c` +*.out +# dotenv environment variable files +.env +.env.test +# Mac system files +.DS_Store +# VS Code settings +.vscode/ +# JetBrains IDEs +.idea/ diff --git a/.goreleaser.yaml b/.goreleaser.yaml new file mode 100644 index 000000000..aa2e9cb8d --- /dev/null +++ b/.goreleaser.yaml @@ -0,0 +1,47 @@ +version: 2 + +project_name: tronctl + +builds: + - main: ./cmd/ + binary: tronctl + env: + - CGO_ENABLED=0 + ldflags: + - -s -w + - -X main.version=v{{ .Version }} + - -X main.commit={{ .ShortCommit }} + - -X main.builtAt={{ .Date }} + - -X main.builtBy=goreleaser@cryptochain.network + goos: + - linux + - darwin + - windows + goarch: + - amd64 + - arm64 + +archives: + - formats: + - tar.gz + format_overrides: + - goos: windows + formats: + - zip + name_template: >- + {{ .ProjectName }}_{{ .Version }}_{{ .Os }}_{{ .Arch }} + +checksum: + name_template: checksums.txt + +changelog: + filters: + exclude: + - "^docs:" + - "^test:" + - "^chore:" + - "^ci:" + - "^Merge " + +release: + prerelease: auto diff --git a/Makefile b/Makefile index 32c70c94f..f97ab5f03 100644 --- a/Makefile +++ b/Makefile @@ -11,20 +11,53 @@ ldflags += -X main.builtAt=${built_at} -X main.builtBy=${built_by} cli := ./bin/${BUILD_TARGET} uname := $(shell uname) -env := GO111MODULE=on +.PHONY: all build build-windows run debug install clean test lint goimports tidy hooks -DIR := ${CURDIR} -export CGO_LDFLAGS=-L$(DIR)/bin/lib -Wl,-rpath -Wl,${ORIGIN}/lib +all: build -all: +build: $(env) go build -o $(cli) -ldflags="$(ldflags)" cmd/main.go +build-windows: + $(env) GOOS=windows GOARCH=amd64 go build -o $(cli).exe -ldflags="$(ldflags)" cmd/main.go + +run: + $(env) go run -ldflags="$(ldflags)" cmd/main.go + debug: $(env) go build $(flags) -o $(cli) -ldflags="$(ldflags)" cmd/main.go -install:all +install: all cp $(cli) ~/.local/bin clean: @rm -f $(cli) - @rm -rf ./bin \ No newline at end of file + @rm -rf ./bin + +# Test target for CI +test: + $(env) go test -race -shuffle=on -coverprofile=coverage.out -covermode=atomic $$(go list ./... | grep -v -E '/pkg/proto/|/cmd') + +# Lint target for CI +lint: + @golangci-lint run --timeout=5m + +# Format check target (using goimports via golangci-lint) +goimports: + @goimports -w -d $(shell find . -type f -name '*.go' \ + ! -name '*.pb.go' \ + ! -path "./vendor/*") + +# Go mod tidy check +tidy: + $(env) go mod tidy + @if [ -n "$$(git status --porcelain go.mod go.sum)" ]; then \ + echo "go.mod or go.sum is not tidy. Please run 'go mod tidy'"; \ + exit 1; \ + else \ + echo "go.mod and go.sum are tidy."; \ + fi + +# Install git hooks +hooks: + @bash .githooks/install.sh diff --git a/README.md b/README.md index 5921de932..19ac35f3a 100644 --- a/README.md +++ b/README.md @@ -1,77 +1,227 @@ -# TRON's go-sdk +# GoTRON SDK -GoSDK and TRON-CLI tool for TRON's blockchain via GRPC +[![Go Reference](https://pkg.go.dev/badge/github.com/fbsobreira/gotron-sdk.svg)](https://pkg.go.dev/github.com/fbsobreira/gotron-sdk) +[![Go Report Card](https://goreportcard.com/badge/github.com/fbsobreira/gotron-sdk)](https://goreportcard.com/report/github.com/fbsobreira/gotron-sdk) +[![License](https://img.shields.io/github/license/fbsobreira/gotron-sdk)](LICENSE) -# Build +GoTRON SDK is a comprehensive Go SDK and CLI tool for interacting with the TRON blockchain. It provides both a command-line interface (`tronctl`) and Go libraries for TRON blockchain operations. +## Features +- 🔧 **Complete CLI Tool**: Manage accounts, send transactions, interact with smart contracts +- 📚 **Go SDK**: Build TRON applications with a clean, idiomatic Go API +- 🔐 **Secure Key Management**: Hardware wallet support, encrypted keystores +- 🚀 **High Performance**: Native gRPC communication with TRON nodes +- 🛠️ **Developer Friendly**: Comprehensive examples and documentation + +## Quick Start + +### Installation + +#### Install from source +```bash +git clone https://github.com/fbsobreira/gotron-sdk.git +cd gotron-sdk +make install ``` -$ git pull -r origin master -$ make + +#### Install with go get +```bash +go get -u github.com/fbsobreira/gotron-sdk ``` -# Usage & Examples +### Basic Usage -# bash completions +#### CLI Usage +```bash +# Create a new account +tronctl keys add -once built, add `tronctl` to your path and add to your `.bashrc` +# Check balance +tronctl account balance
+# Send TRX +tronctl account send --signer ``` -. <(tronctl completion) + +#### SDK Usage +```go +package main + +import ( + "fmt" + "log" + + "github.com/fbsobreira/gotron-sdk/pkg/address" + "github.com/fbsobreira/gotron-sdk/pkg/client" +) + +func main() { + // Create client + c := client.NewGrpcClient("grpc.trongrid.io:50051") + err := c.Start(client.GRPCInsecure()) + if err != nil { + log.Fatal(err) + } + defer c.Stop() + + // Get account info + addr, _ := address.Base58ToAddress("TUEZSdKsoDHQMeZwihtdoBiN46zxhGWYdH") + account, err := c.GetAccount(addr.String()) + if err != nil { + log.Fatal(err) + } + + fmt.Printf("Balance: %d\n", account.Balance) +} + ``` -## Transfer JSON file format -The JSON file will be a JSON array where each element has the following attributes: +## Documentation + +- [Installation Guide](docs/installation.md) - Detailed installation instructions +- [CLI Usage Guide](docs/cli-usage.md) - Complete CLI command reference +- [SDK Usage Guide](docs/sdk-usage.md) - Go SDK examples and patterns +- [API Reference](docs/api-reference.md) - Detailed API documentation +- [Examples](docs/examples.md) - Common use cases and examples + +## Supported Features + +### Account Management +- Create and import accounts +- Hardware wallet support (Ledger) +- Keystore management +- Multi-signature support + +### Transactions +- TRX transfers +- TRC10 token operations +- TRC20 token operations +- Smart contract interactions +- Transaction signing and broadcasting + +### Smart Contracts +- Contract deployment +- Contract calls and triggers +- ABI encoding/decoding +- Event monitoring + +### Blockchain Queries +- Block information +- Transaction details +- Account resources +- Witness/SR information +- Proposal management + +## Configuration + +### Environment Variables +```bash +# Set custom node +export TRON_NODE="grpc.trongrid.io:50051" -| Key | Value-type | Value-description| -| :------------------:|:----------:| :----------------| -| `from` | string | [**Required**] Sender's one address, must have key in keystore. | -| `to` | string | [**Required**] The receivers one address. | -| `amount` | string | [**Required**] The amount to send in $ONE. | -| `passphrase-file` | string | [*Optional*] The file path to file containing the passphrase in plain text. If none is provided, check for passphrase string. | -| `passphrase-string` | string | [*Optional*] The passphrase as a string in plain text. If none is provided, passphrase is ''. | -| `stop-on-error` | boolean | [*Optional*] If true, stop sending transactions if an error occurred, default is false. | +# Enable TLS +export TRON_NODE_TLS="true" + +# Set Trongrid API key +export TRONGRID_APIKEY="your-api-key" + +# Enable debug mode +export GOTRON_SDK_DEBUG="true" +``` + +### Configuration File +Create `~/.tronctl/config.yaml`: +```yaml +node: grpc.trongrid.io:50051 +network: mainnet +timeout: 60s +tls: true +apiKey: your-api-key +``` -Example of JSON file: +### Transfer JSON Format +For batch transfers, use a JSON file with the following format: +| Key | Value-type | Value-description| +| :------------------:|:----------:| :----------------| +| `from` | string | [**Required**] Sender's address, must have key in keystore | +| `to` | string | [**Required**] Receiver's address | +| `amount` | string | [**Required**] Amount to send in TRX | +| `passphrase-file` | string | [*Optional*] File path containing passphrase | +| `passphrase-string` | string | [*Optional*] Passphrase as string | +| `stop-on-error` | boolean | [*Optional*] Stop on error (default: false) | + +Example: ```json [ { "from": "TUEZSdKsoDHQMeZwihtdoBiN46zxhGWYdH", "to": "TKSXDA8HfE9E1y39RczVQ1ZascUEtaSToF", - "amount": "1", + "amount": "100", "passphrase-string": "", "stop-on-error": true - }, - { - "from": "TUEZSdKsoDHQMeZwihtdoBiN46zxhGWYdH", - "to": "TEvHMZWyfjCAdDJEKYxYVL8rRpigddLC1R", - "amount": "1", - "passphrase-file": "./pw.txt", } ] ``` +## Shell Completion + +Add to your `.bashrc` or `.zshrc`: +```bash +# Bash +source <(tronctl completion bash) + +# Zsh +source <(tronctl completion zsh) +``` -# Debugging +## Development -The gotron-sdk code respects `GOTRON_SDK_DEBUG` as debugging -based environment variables. +### Requirements +- Go 1.18 or higher +- Make (for building) +- Protocol Buffers compiler (for regenerating protos) +### Building ```bash -GOTRON_SDK_DEBUG=true ./tronctl +# Build binary +make build + +# Cross-compile for Windows +make build-windows + +# Run tests +make test + +# Run linter +make lint + +# Generate protobuf files +./gen-proto.sh ``` +## Contributing + +We welcome contributions! Please see our [Contributing Guidelines](CONTRIBUTING.md) for details. + +1. Fork the repository +2. Create your feature branch (`git checkout -b feature/amazing-feature`) +3. Commit your changes (`git commit -m 'Add some amazing feature'`) +4. Push to the branch (`git push origin feature/amazing-feature`) +5. Open a Pull Request + +## Version History -# GRPC TLS +### Note on Versions +The v2.x.x releases were incorrectly tagged without proper Go module versioning. These versions have been retracted. Please use v1.x.x versions or later. -If you node require TLS connection, use parameter `--withTLS` -TLS credentials can also be set persistent in config file: `withTLS: true` +## License -# Trongrid API Key +This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details. -To set trongrid API Key first create you api key at `www.trongrid.io` and use parameter - `--apiKey=25f66928-0b70-48cd-9ac6-da6f8247c663` (replace with your API key) -Trongrid API Key can also be set persistent in config file: `apiKey: 25f66928-0b70-48cd-9ac6-da6f8247c663` (replace with your API key) +## Support -OS environment variable `TRONGRID_APIKEY` will overwrite any prior API key configuration if set. \ No newline at end of file +- 📖 [Documentation](https://github.com/fbsobreira/gotron-sdk/tree/master/docs) +- 🐛 [Issue Tracker](https://github.com/fbsobreira/gotron-sdk/issues) +- 💬 [Discussions](https://github.com/fbsobreira/gotron-sdk/discussions) diff --git a/cmd/README.md b/cmd/README.md index e19713a34..b58bde270 100644 --- a/cmd/README.md +++ b/cmd/README.md @@ -20,8 +20,9 @@ - [ ] update sr - [x] update brokerage - [ ] proposal - - [ ] create + [x] proposal + - [x] create + - [x] withdraw - [x] vote proposal - [x] list proposals diff --git a/cmd/subcommands/account.go b/cmd/subcommands/account.go index 3918055ef..61d310a77 100644 --- a/cmd/subcommands/account.go +++ b/cmd/subcommands/account.go @@ -1,6 +1,7 @@ package cmd import ( + "encoding/hex" "encoding/json" "fmt" "math" @@ -51,8 +52,9 @@ func accountSub() []*cobra.Command { result["address"] = addr.String() result["type"] = acc.GetType() result["balance"] = float64(acc.GetBalance()) / 1000000 - result["allowance"] = float64(acc.GetAllowance()+rewards) / 1000000 - result["rewards"] = float64(acc.GetAllowance()) / 1000000 + result["allowance"] = float64(acc.GetAllowance()) / 1000000 + result["rewards"] = float64(rewards) / 1000000 + asJSON, _ := json.Marshal(result) fmt.Println(common.JSONPrettyFormat(string(asJSON))) return nil @@ -607,7 +609,88 @@ func accountSub() []*cobra.Command { cmdPermission.Flags().StringSliceVar(&permissionList, "allow", []string{}, "TYPE:THRESHOLD:ADDRESS1-WEIGHT+ADDRESS2-WEIGHT") - return []*cobra.Command{cmdBalance, cmdActivate, cmdSend, cmdAddress, cmdInfo, cmdWithdraw, cmdFreeze, cmdVote, cmdPermission} + var useFixedLength bool + var hashMessage bool + + cmdSign := &cobra.Command{ + Use: "sign", + Short: "sign message", + Args: cobra.ExactArgs(1), + RunE: func(cmd *cobra.Command, args []string) error { + if signerAddress.String() == "" { + return fmt.Errorf("no signer specified") + } + + message := []byte(args[0]) + if hashMessage { + message = common.Keccak256([]byte(message)) + } + + var signature []byte + if useLedgerWallet { + // TODO: + // account := keystore.Account{Address: signerAddress.GetAddress()} + } else { + ks, acct, err := store.UnlockedKeystore(signerAddress.String(), passphrase) + if err != nil { + return err + } + signature, err = ks.Wallets()[0].SignText(*acct, []byte(message), useFixedLength) + if err != nil { + return err + } + } + + result := make(map[string]interface{}) + result["Signer"] = signerAddress.String() + result["Message"] = args[0] + result["Signature"] = hex.EncodeToString(signature) + asJSON, _ := json.Marshal(result) + fmt.Println(common.JSONPrettyFormat(string(asJSON))) + return nil + }, + } + cmdSign.Flags().BoolVar(&useFixedLength, "useFixedLength", false, "--useFixedLength=true") + cmdSign.Flags().BoolVar(&hashMessage, "hashMessage", false, "--hashMessage=true") + + cmdVerify := &cobra.Command{ + Use: "verify", + Short: "verify message signature", + Args: cobra.ExactArgs(2), + RunE: func(cmd *cobra.Command, args []string) error { + + message := []byte(args[0]) + if hashMessage { + message = common.Keccak256([]byte(message)) + } + + // compute message hash + hash := keystore.TextHash(message, useFixedLength) + signature, err := hex.DecodeString(args[1]) + if err != nil { + fmt.Println("Invalid signature") + return err + } + + addr, err := keystore.RecoverPubkey(hash, signature) + if err != nil { + return err + } + + result := make(map[string]interface{}) + result["Message"] = args[0] + result["Signature"] = args[1] + result["Signer"] = addr.String() + + asJSON, _ := json.Marshal(result) + fmt.Println(common.JSONPrettyFormat(string(asJSON))) + return nil + }, + } + cmdVerify.Flags().BoolVar(&useFixedLength, "useFixedLength", false, "--useFixedLength=true") + cmdVerify.Flags().BoolVar(&hashMessage, "hashMessage", false, "--hashMessage=true") + + return []*cobra.Command{cmdBalance, cmdActivate, cmdSend, cmdAddress, cmdInfo, cmdWithdraw, cmdFreeze, cmdVote, cmdPermission, cmdSign, cmdVerify} } func init() { @@ -615,8 +698,7 @@ func init() { Use: "account", Short: "Account Actions", RunE: func(cmd *cobra.Command, args []string) error { - cmd.Help() - return nil + return cmd.Help() }, } diff --git a/cmd/subcommands/bc.go b/cmd/subcommands/bc.go index 6a30d98e5..c09e7e707 100644 --- a/cmd/subcommands/bc.go +++ b/cmd/subcommands/bc.go @@ -3,14 +3,15 @@ package cmd import ( "encoding/json" "fmt" + "strings" "time" "github.com/fatih/structs" "github.com/fbsobreira/gotron-sdk/pkg/address" "github.com/fbsobreira/gotron-sdk/pkg/common" "github.com/fbsobreira/gotron-sdk/pkg/proto/core" - "github.com/golang/protobuf/ptypes" "github.com/spf13/cobra" + "google.golang.org/protobuf/reflect/protoreflect" ) var () @@ -107,210 +108,94 @@ func bcSub() []*cobra.Command { result["contractName"] = contract.Type.String() //parse contract + var c interface{} switch contract.Type { case core.Transaction_Contract_AccountCreateContract: - var c core.AccountCreateContract - if err = ptypes.UnmarshalAny(contract.GetParameter(), &c); err != nil { - return fmt.Errorf("Tx inconsistent") - } - result["contract"] = structs.Map(c) + c = &core.AccountCreateContract{} case core.Transaction_Contract_TransferContract: - var c core.TransferContract - if err = ptypes.UnmarshalAny(contract.GetParameter(), &c); err != nil { - return fmt.Errorf("Tx inconsistent") - } - result["contract"] = structs.Map(c) + c = &core.TransferContract{} case core.Transaction_Contract_TransferAssetContract: - var c core.TransferAssetContract - if err = ptypes.UnmarshalAny(contract.GetParameter(), &c); err != nil { - return fmt.Errorf("Tx inconsistent") - } - result["contract"] = structs.Map(c) + c = &core.TransferAssetContract{} case core.Transaction_Contract_VoteWitnessContract: - var c core.VoteWitnessContract - if err = ptypes.UnmarshalAny(contract.GetParameter(), &c); err != nil { - return fmt.Errorf("Tx inconsistent") - } - result["contract"] = structs.Map(c) + c = &core.VoteWitnessContract{} case core.Transaction_Contract_WitnessCreateContract: - var c core.WitnessCreateContract - if err = ptypes.UnmarshalAny(contract.GetParameter(), &c); err != nil { - return fmt.Errorf("Tx inconsistent") - } - result["contract"] = structs.Map(c) + c = &core.WitnessCreateContract{} + case core.Transaction_Contract_WitnessUpdateContract: + c = &core.WitnessUpdateContract{} case core.Transaction_Contract_AssetIssueContract: - var c core.AssetIssueContract - if err = ptypes.UnmarshalAny(contract.GetParameter(), &c); err != nil { - return fmt.Errorf("Tx inconsistent") - } - result["contract"] = structs.Map(c) + c = &core.AssetIssueContract{} case core.Transaction_Contract_ParticipateAssetIssueContract: - var c core.ParticipateAssetIssueContract - if err = ptypes.UnmarshalAny(contract.GetParameter(), &c); err != nil { - return fmt.Errorf("Tx inconsistent") - } - result["contract"] = structs.Map(c) + c = &core.ParticipateAssetIssueContract{} case core.Transaction_Contract_AccountUpdateContract: - var c core.AccountUpdateContract - if err = ptypes.UnmarshalAny(contract.GetParameter(), &c); err != nil { - return fmt.Errorf("Tx inconsistent") - } - result["contract"] = structs.Map(c) + c = &core.AccountUpdateContract{} case core.Transaction_Contract_FreezeBalanceContract: - var c core.FreezeBalanceContract - if err = ptypes.UnmarshalAny(contract.GetParameter(), &c); err != nil { - return fmt.Errorf("Tx inconsistent") - } - result["contract"] = structs.Map(c) + c = &core.FreezeBalanceContract{} case core.Transaction_Contract_UnfreezeBalanceContract: - var c core.UnfreezeBalanceContract - if err = ptypes.UnmarshalAny(contract.GetParameter(), &c); err != nil { - return fmt.Errorf("Tx inconsistent") - } - result["contract"] = structs.Map(c) + c = &core.UnfreezeBalanceContract{} case core.Transaction_Contract_WithdrawBalanceContract: - var c core.WithdrawBalanceContract - if err = ptypes.UnmarshalAny(contract.GetParameter(), &c); err != nil { - return fmt.Errorf("Tx inconsistent") - } - result["contract"] = structs.Map(c) + c = &core.WithdrawBalanceContract{} case core.Transaction_Contract_UnfreezeAssetContract: - var c core.UnfreezeAssetContract - if err = ptypes.UnmarshalAny(contract.GetParameter(), &c); err != nil { - return fmt.Errorf("Tx inconsistent") - } - result["contract"] = structs.Map(c) + c = &core.UnfreezeAssetContract{} case core.Transaction_Contract_UpdateAssetContract: - var c core.UpdateAssetContract - if err = ptypes.UnmarshalAny(contract.GetParameter(), &c); err != nil { - return fmt.Errorf("Tx inconsistent") - } - result["contract"] = structs.Map(c) - + c = &core.UpdateAssetContract{} case core.Transaction_Contract_ProposalCreateContract: - var c core.ProposalCreateContract - if err = ptypes.UnmarshalAny(contract.GetParameter(), &c); err != nil { - return fmt.Errorf("Tx inconsistent") - } - result["contract"] = structs.Map(c) + c = &core.ProposalCreateContract{} case core.Transaction_Contract_ProposalApproveContract: - var c core.ProposalApproveContract - if err = ptypes.UnmarshalAny(contract.GetParameter(), &c); err != nil { - return fmt.Errorf("Tx inconsistent") - } - result["contract"] = structs.Map(c) + c = &core.ProposalApproveContract{} case core.Transaction_Contract_ProposalDeleteContract: - var c core.ProposalDeleteContract - if err = ptypes.UnmarshalAny(contract.GetParameter(), &c); err != nil { - return fmt.Errorf("Tx inconsistent") - } - result["contract"] = structs.Map(c) + c = &core.ProposalDeleteContract{} case core.Transaction_Contract_SetAccountIdContract: - var c core.SetAccountIdContract - if err = ptypes.UnmarshalAny(contract.GetParameter(), &c); err != nil { - return fmt.Errorf("Tx inconsistent") - } - result["contract"] = structs.Map(c) + c = &core.SetAccountIdContract{} case core.Transaction_Contract_CustomContract: - return fmt.Errorf("Tx inconsistent") + return fmt.Errorf("proto unmarshal any: %s", "customContract") case core.Transaction_Contract_CreateSmartContract: - var c core.CreateSmartContract - if err = ptypes.UnmarshalAny(contract.GetParameter(), &c); err != nil { - return fmt.Errorf("Tx inconsistent") - } - result["contract"] = structs.Map(c) + c = &core.CreateSmartContract{} case core.Transaction_Contract_TriggerSmartContract: - var c core.TriggerSmartContract - if err = ptypes.UnmarshalAny(contract.GetParameter(), &c); err != nil { - return fmt.Errorf("Tx inconsistent") - } - result["contract"] = structs.Map(c) + c = &core.TriggerSmartContract{} + case core.Transaction_Contract_GetContract: + return fmt.Errorf("proto unmarshal any: %s", "getContract") case core.Transaction_Contract_UpdateSettingContract: - var c core.UpdateSettingContract - if err = ptypes.UnmarshalAny(contract.GetParameter(), &c); err != nil { - return fmt.Errorf("Tx inconsistent") - } - result["contract"] = structs.Map(c) + c = &core.UpdateSettingContract{} case core.Transaction_Contract_ExchangeCreateContract: - var c core.ExchangeCreateContract - if err = ptypes.UnmarshalAny(contract.GetParameter(), &c); err != nil { - return fmt.Errorf("Tx inconsistent") - } - result["contract"] = structs.Map(c) + c = &core.ExchangeCreateContract{} case core.Transaction_Contract_ExchangeInjectContract: - var c core.ExchangeInjectContract - if err = ptypes.UnmarshalAny(contract.GetParameter(), &c); err != nil { - return fmt.Errorf("Tx inconsistent") - } - result["contract"] = structs.Map(c) + c = &core.ExchangeInjectContract{} case core.Transaction_Contract_ExchangeWithdrawContract: - var c core.ExchangeWithdrawContract - if err = ptypes.UnmarshalAny(contract.GetParameter(), &c); err != nil { - return fmt.Errorf("Tx inconsistent") - } - result["contract"] = structs.Map(c) + c = &core.ExchangeWithdrawContract{} case core.Transaction_Contract_ExchangeTransactionContract: - var c core.ExchangeTransactionContract - if err = ptypes.UnmarshalAny(contract.GetParameter(), &c); err != nil { - return fmt.Errorf("Tx inconsistent") - } - result["contract"] = structs.Map(c) + c = &core.ExchangeTransactionContract{} case core.Transaction_Contract_UpdateEnergyLimitContract: - var c core.UpdateEnergyLimitContract - if err = ptypes.UnmarshalAny(contract.GetParameter(), &c); err != nil { - return fmt.Errorf("Tx inconsistent") - } - result["contract"] = structs.Map(c) + c = &core.UpdateEnergyLimitContract{} case core.Transaction_Contract_AccountPermissionUpdateContract: - var c core.AccountPermissionUpdateContract - if err = ptypes.UnmarshalAny(contract.GetParameter(), &c); err != nil { - return fmt.Errorf("Tx inconsistent") - } - result["contract"] = structs.Map(c) + c = &core.AccountPermissionUpdateContract{} case core.Transaction_Contract_ClearABIContract: - var c core.ClearABIContract - if err = ptypes.UnmarshalAny(contract.GetParameter(), &c); err != nil { - return fmt.Errorf("Tx inconsistent") - } - result["contract"] = structs.Map(c) + c = &core.ClearABIContract{} case core.Transaction_Contract_UpdateBrokerageContract: - var c core.UpdateBrokerageContract - if err = ptypes.UnmarshalAny(contract.GetParameter(), &c); err != nil { - return fmt.Errorf("Tx inconsistent") - } - result["contract"] = structs.Map(c) + c = &core.UpdateBrokerageContract{} case core.Transaction_Contract_ShieldedTransferContract: - var c core.ShieldedTransferContract - if err = ptypes.UnmarshalAny(contract.GetParameter(), &c); err != nil { - return fmt.Errorf("Tx inconsistent") - } - result["contract"] = structs.Map(c) + c = &core.ShieldedTransferContract{} + case core.Transaction_Contract_MarketSellAssetContract: + c = &core.MarketSellAssetContract{} + case core.Transaction_Contract_MarketCancelOrderContract: + c = &core.MarketCancelOrderContract{} + case core.Transaction_Contract_FreezeBalanceV2Contract: + c = &core.FreezeBalanceV2Contract{} + case core.Transaction_Contract_UnfreezeBalanceV2Contract: + c = &core.UnfreezeBalanceV2Contract{} + case core.Transaction_Contract_WithdrawExpireUnfreezeContract: + c = &core.WithdrawExpireUnfreezeContract{} + case core.Transaction_Contract_DelegateResourceContract: + c = &core.DelegateResourceContract{} + case core.Transaction_Contract_UnDelegateResourceContract: + c = &core.UnDelegateResourceContract{} default: - return fmt.Errorf("Tx inconsistent") + return fmt.Errorf("proto unmarshal any: %+w", err) } - c := result["contract"].(map[string]interface{}) - delete(c, "XXX_NoUnkeyedLiteral") - delete(c, "XXX_sizecache") - delete(c, "XXX_unrecognized") - if v, ok := c["OwnerAddress"]; ok && len(v.([]uint8)) > 0 { - c["OwnerAddress"] = address.Address(v.([]uint8)).String() - } - if v, ok := c["ReceiverAddress"]; ok && len(v.([]uint8)) > 0 { - c["ReceiverAddress"] = address.Address(v.([]uint8)).String() - } - if v, ok := c["ToAddress"]; ok && len(v.([]uint8)) > 0 { - c["ToAddress"] = address.Address(v.([]uint8)).String() - } - - if v, ok := c["Votes"]; ok { - votes := make(map[string]int64) - for _, d := range v.([]interface{}) { - dP := d.(map[string]interface{}) - votes[address.Address(dP["VoteAddress"].([]uint8)).String()] = dP["VoteCount"].(int64) - } - c["Votes"] = votes + if err = contract.GetParameter().UnmarshalTo(c.(protoreflect.ProtoMessage)); err != nil { + return fmt.Errorf("proto unmarshal any: %+w", err) } + result["contract"] = parseContractHumanReadable(structs.Map(c)) asJSON, _ := json.Marshal(result) fmt.Println(common.JSONPrettyFormat(string(asJSON))) @@ -321,13 +206,43 @@ func bcSub() []*cobra.Command { return []*cobra.Command{cmdNode, cmdMT, cmdTX} } +func parseContractHumanReadable(ck map[string]interface{}) map[string]interface{} { + // Addresses fields + addresses := map[string]bool{ + "OwnerAddress": true, + "ReceiverAddress": true, + "ToAddress": true, + "ContractAddress": true, + } + for f, d := range ck { + if strings.HasPrefix(f, "XXX_") { + delete(ck, f) + } + + // convert addresses + if addresses[f] { + ck[f] = address.Address(d.([]uint8)).String() + } + } + + if v, ok := ck["Votes"]; ok { + votes := make(map[string]int64) + for _, d := range v.([]interface{}) { + dP := d.(map[string]interface{}) + votes[address.Address(dP["VoteAddress"].([]uint8)).String()] = dP["VoteCount"].(int64) + } + ck["Votes"] = votes + } + + return ck +} + func init() { cmdBC := &cobra.Command{ Use: "bc", Short: "Blockchain Actions", RunE: func(cmd *cobra.Command, args []string) error { - cmd.Help() - return nil + return cmd.Help() }, } diff --git a/cmd/subcommands/completion.go b/cmd/subcommands/completion.go index b3a8d8e34..a3ff9277e 100644 --- a/cmd/subcommands/completion.go +++ b/cmd/subcommands/completion.go @@ -17,8 +17,7 @@ func init() { Add the line to your ~/.bashrc to enable completion for each bash session. `, RunE: func(cmd *cobra.Command, args []string) error { - RootCmd.GenBashCompletion(os.Stdout) - return nil + return RootCmd.GenBashCompletion(os.Stdout) }, } RootCmd.AddCommand(cmdCompletion) diff --git a/cmd/subcommands/config.go b/cmd/subcommands/config.go index 3497b7017..9475b67b5 100644 --- a/cmd/subcommands/config.go +++ b/cmd/subcommands/config.go @@ -3,7 +3,6 @@ package cmd import ( "encoding/json" "fmt" - "io/ioutil" "os" "strconv" "strings" @@ -18,8 +17,7 @@ func init() { Use: "config", Short: "update default config", RunE: func(cmd *cobra.Command, args []string) error { - cmd.Help() - return nil + return cmd.Help() }, } @@ -108,7 +106,9 @@ func initConfig() { config.NoPretty = false config.APIKey = "" config.WithTLS = false - SaveConfig(config) + if err = SaveConfig(config); err != nil { + panic(fmt.Sprintf("Failed to write to config file %s.", DefaultConfigFile)) + } } else { panic(err.Error()) } @@ -117,7 +117,7 @@ func initConfig() { // LoadConfig loads config file in yaml format func LoadConfig() (*Config, error) { - in, err := ioutil.ReadFile(DefaultConfigFile) + in, err := os.ReadFile(DefaultConfigFile) readConfig := &Config{} if err == nil { if err := yaml.Unmarshal(in, readConfig); err != nil { @@ -133,7 +133,7 @@ func SaveConfig(conf *Config) error { if err != nil { return err } - if err := ioutil.WriteFile(DefaultConfigFile, out, 0600); err != nil { + if err := os.WriteFile(DefaultConfigFile, out, 0600); err != nil { panic(fmt.Sprintf("Failed to write to config file %s.", DefaultConfigFile)) } return nil diff --git a/cmd/subcommands/contract.go b/cmd/subcommands/contract.go index 9c14f20c6..1e647c1e8 100644 --- a/cmd/subcommands/contract.go +++ b/cmd/subcommands/contract.go @@ -3,8 +3,8 @@ package cmd import ( "encoding/json" "fmt" - "io/ioutil" "math" + "os" "github.com/fbsobreira/gotron-sdk/pkg/address" "github.com/fbsobreira/gotron-sdk/pkg/client/transaction" @@ -27,6 +27,7 @@ var ( tAmount float64 tTokenID string tTokenAmount float64 + estimate bool ) func contractSub() []*cobra.Command { @@ -38,7 +39,7 @@ func contractSub() []*cobra.Command { if abiSTR == "" { if abiFile != "" { - abiBytes, err := ioutil.ReadFile(abiFile) + abiBytes, err := os.ReadFile(abiFile) if err != nil { return fmt.Errorf("cannot read ABI file: %s %v", abiFile, err) } @@ -54,7 +55,7 @@ func contractSub() []*cobra.Command { if bcSTR == "" { if bcFile != "" { - bcBytes, err := ioutil.ReadFile(bcFile) + bcBytes, err := os.ReadFile(bcFile) if err != nil { return fmt.Errorf("cannot read Bytecode file: %s %v", bcFile, err) } @@ -164,7 +165,7 @@ func contractSub() []*cobra.Command { result := make(map[string]interface{}) //TODO: parse based on contract ABI - result["Result"] = common.ToHex(cResult[0]) + result["Result"] = common.BytesToHexString(cResult[0]) asJSON, _ := json.Marshal(result) fmt.Println(common.JSONPrettyFormat(string(asJSON))) @@ -202,6 +203,38 @@ func contractSub() []*cobra.Command { param = args[2] } + if estimate { + estimate, err := conn.EstimateEnergy( + signerAddress.String(), + addr.String(), + args[1], + param, + valueInt, + tTokenID, + tokenInt, + ) + + if err != nil { + return err + } + + if noPrettyOutput { + fmt.Println(estimate) + return nil + } + + result := make(map[string]interface{}) + result["EnergyRequired"] = estimate.EnergyRequired + result["result"] = map[string]interface{}{ + "code": estimate.Result.Code.String(), + "message": string(estimate.Result.Message), + "result": estimate.Result.Result, + } + + asJSON, _ := json.Marshal(result) + fmt.Println(common.JSONPrettyFormat(string(asJSON))) + } + tx, err := conn.TriggerContract( signerAddress.String(), addr.String(), @@ -265,6 +298,7 @@ func contractSub() []*cobra.Command { cmdTrigger.Flags().Float64Var(&tAmount, "value", 0, "trx amount") cmdTrigger.Flags().StringVar(&tTokenID, "token", "", "token id") cmdTrigger.Flags().Float64Var(&tTokenAmount, "tokenValue", 0, "token amount") + cmdTrigger.Flags().BoolVar(&estimate, "estiamte", false, "estimate energy required") return []*cobra.Command{cmdDeploy, cmdConstant, cmdTrigger} } @@ -274,8 +308,7 @@ func init() { Use: "contract", Short: "SmartContract actions", RunE: func(cmd *cobra.Command, args []string) error { - cmd.Help() - return nil + return cmd.Help() }, } diff --git a/cmd/subcommands/exchange.go b/cmd/subcommands/exchange.go index 6fb76da71..87a47d06a 100644 --- a/cmd/subcommands/exchange.go +++ b/cmd/subcommands/exchange.go @@ -463,8 +463,7 @@ func init() { Use: "exchange", Short: "Bancos Exchange Actions", RunE: func(cmd *cobra.Command, args []string) error { - cmd.Help() - return nil + return cmd.Help() }, } diff --git a/cmd/subcommands/keys.go b/cmd/subcommands/keys.go index 735d7eda9..a76e77916 100644 --- a/cmd/subcommands/keys.go +++ b/cmd/subcommands/keys.go @@ -2,18 +2,22 @@ package cmd import ( "bufio" + "encoding/hex" "fmt" "os" + "github.com/btcsuite/btcd/btcec/v2" "github.com/fatih/color" + "github.com/fbsobreira/go-bip39" "github.com/fbsobreira/gotron-sdk/pkg/account" + "github.com/fbsobreira/gotron-sdk/pkg/address" c "github.com/fbsobreira/gotron-sdk/pkg/common" - + "github.com/fbsobreira/gotron-sdk/pkg/keys" "github.com/fbsobreira/gotron-sdk/pkg/ledger" "github.com/fbsobreira/gotron-sdk/pkg/mnemonic" "github.com/fbsobreira/gotron-sdk/pkg/store" "github.com/spf13/cobra" - "github.com/tyler-smith/go-bip39" + "golang.org/x/term" ) const ( @@ -25,9 +29,6 @@ var ( quietImport bool recoverFromMnemonic bool passphrase string - blsFilePath string - blsShardID uint32 - blsCount uint32 ppPrompt = fmt.Sprintf( "prompt for passphrase, otherwise use default passphrase: \"`%s`\"", c.DefaultPassphrase, ) @@ -228,8 +229,52 @@ func keysSub() []*cobra.Command { }, } + randomPrivateKey := &cobra.Command{ + Use: "random-pk", + Short: "export a random private key", + Args: cobra.NoArgs, + RunE: func(cmd *cobra.Command, args []string) error { + privateKey, err := keys.GenerateKey() + if err != nil { + return err + } + fmt.Println(hex.EncodeToString(privateKey.Serialize())) + return nil + }, + } + + addressFromPrivateKey := &cobra.Command{ + Use: "address-pk", + Short: "export address from private key", + Args: cobra.NoArgs, + RunE: func(cmd *cobra.Command, args []string) error { + fmt.Println("Enter privete key hex format:") + data, err := term.ReadPassword(int(os.Stdin.Fd())) + if err != nil { + return err + } + + // decode hex + privateKeyBytes, err := hex.DecodeString(string(data)) + if err != nil { + return err + } + + if len(privateKeyBytes) != c.Secp256k1PrivateKeyBytesLength { + return c.ErrBadKeyLength + } + + // btcec.PrivKeyFromBytes only returns a secret key and public key + sk, _ := btcec.PrivKeyFromBytes(privateKeyBytes) + + addr := address.PubkeyToAddress(*sk.PubKey().ToECDSA()) + fmt.Println(addr) + return nil + }, + } + return []*cobra.Command{cmdList, cmdLocation, cmdAdd, cmdRemove, cmdMnemonic, cmdRecoverMnemonic, cmdImportKS, cmdImportPK, - cmdExportKS, cmdExportPK} + cmdExportKS, cmdExportPK, randomPrivateKey, addressFromPrivateKey} } func init() { @@ -238,8 +283,7 @@ func init() { Short: "Add or view local private keys", Long: "Manage your local keys", RunE: func(cmd *cobra.Command, args []string) error { - cmd.Help() - return nil + return cmd.Help() }, } diff --git a/cmd/subcommands/proposal.go b/cmd/subcommands/proposal.go index 4d940f4cd..8fb6a3ee8 100644 --- a/cmd/subcommands/proposal.go +++ b/cmd/subcommands/proposal.go @@ -133,6 +133,61 @@ func proposalSub() []*cobra.Command { }, } + cmdProposalWithdraw := &cobra.Command{ + Use: "withdraw", + Short: "Withdraw network proposal", + Args: cobra.ExactArgs(1), + RunE: func(cmd *cobra.Command, args []string) error { + if signerAddress.String() == "" { + return fmt.Errorf("no signer specified") + } + + id, err := strconv.ParseInt(args[0], 10, 64) + if err != nil { + return err + } + + tx, err := conn.ProposalWithdraw(signerAddress.String(), id) + if err != nil { + return err + } + var ctrlr *transaction.Controller + if useLedgerWallet { + account := keystore.Account{Address: signerAddress.GetAddress()} + ctrlr = transaction.NewController(conn, nil, &account, tx.Transaction, opts) + } else { + ks, acct, err := store.UnlockedKeystore(signerAddress.String(), passphrase) + if err != nil { + return err + } + ctrlr = transaction.NewController(conn, ks, acct, tx.Transaction, opts) + } + if err = ctrlr.ExecuteTransaction(); err != nil { + return err + } + + if noPrettyOutput { + fmt.Println(tx, ctrlr.Receipt, ctrlr.Result) + return nil + } + + result := make(map[string]interface{}) + result["from"] = signerAddress.String() + result["txID"] = common.BytesToHexString(tx.GetTxid()) + result["blockNumber"] = ctrlr.Receipt.BlockNumber + result["message"] = string(ctrlr.Result.Message) + result["receipt"] = map[string]interface{}{ + "fee": ctrlr.Receipt.Fee, + "netFee": ctrlr.Receipt.Receipt.NetFee, + "netUsage": ctrlr.Receipt.Receipt.NetUsage, + } + + asJSON, _ := json.Marshal(result) + fmt.Println(common.JSONPrettyFormat(string(asJSON))) + return nil + }, + } + cmdProposalCreate := &cobra.Command{ Use: "create", Short: "Approve network proposal", @@ -208,7 +263,7 @@ func proposalSub() []*cobra.Command { cmdProposalCreate.Flags().StringSliceVar(&proposalList, "params", []string{}, "ID:VALUE,ID:VALUE") - return []*cobra.Command{cmdProposalList, cmdProposalApprove, cmdProposalCreate} + return []*cobra.Command{cmdProposalList, cmdProposalApprove, cmdProposalWithdraw, cmdProposalCreate} } func init() { @@ -216,8 +271,7 @@ func init() { Use: "proposal", Short: "Network upgrade proposal", RunE: func(cmd *cobra.Command, args []string) error { - cmd.Help() - return nil + return cmd.Help() }, } diff --git a/cmd/subcommands/root.go b/cmd/subcommands/root.go index a352d47b9..1e9987fed 100644 --- a/cmd/subcommands/root.go +++ b/cmd/subcommands/root.go @@ -4,24 +4,21 @@ import ( "bytes" "encoding/json" "fmt" - "io/ioutil" "net/http" "os" "path" - "regexp" "strings" "time" color "github.com/fatih/color" "github.com/fbsobreira/gotron-sdk/pkg/client" "github.com/fbsobreira/gotron-sdk/pkg/client/transaction" - "github.com/fbsobreira/gotron-sdk/pkg/common" c "github.com/fbsobreira/gotron-sdk/pkg/common" "github.com/fbsobreira/gotron-sdk/pkg/store" "github.com/pkg/errors" "github.com/spf13/cobra" "github.com/spf13/cobra/doc" - "golang.org/x/crypto/ssh/terminal" + "golang.org/x/term" "google.golang.org/grpc" "google.golang.org/grpc/credentials" ) @@ -39,7 +36,6 @@ var ( passphraseFilePath string defaultKeystoreDir string node string - keyStoreDir string givenFilePath string timeout uint32 withTLS bool @@ -52,7 +48,7 @@ var ( SilenceUsage: true, PersistentPreRunE: func(cmd *cobra.Command, args []string) error { if verbose { - common.EnableAllVerbose() + c.EnableAllVerbose() } switch URLcomponents := strings.Split(node, ":"); len(URLcomponents) { case 1: @@ -65,7 +61,7 @@ var ( if withTLS { opts = append(opts, grpc.WithTransportCredentials(credentials.NewTLS(nil))) } else { - opts = append(opts, grpc.WithInsecure()) + opts = append(opts, client.GRPCInsecure()) } // check for env API Key @@ -73,7 +69,9 @@ var ( apiKey = trongridKey } // set API - conn.SetAPIKey(apiKey) + if err := conn.SetAPIKey(apiKey); err != nil { + return err + } if err := conn.Start(opts...); err != nil { return err @@ -104,8 +102,7 @@ CLI interface to Tron blockchain %s`, g("type 'tronclt --help' for details")), RunE: func(cmd *cobra.Command, args []string) error { - cmd.Help() - return nil + return cmd.Help() }, } ) @@ -140,8 +137,11 @@ func init() { RunE: func(cmd *cobra.Command, args []string) error { cwd, _ := os.Getwd() docDir := path.Join(cwd, tronctlDocsDir) - os.Mkdir(docDir, 0700) - err := doc.GenMarkdownTree(RootCmd, docDir) + err := os.Mkdir(docDir, 0700) + if err != nil && !os.IsExist(err) { + return fmt.Errorf("could not create %s directory: %v", tronctlDocsDir, err) + } + err = doc.GenMarkdownTree(RootCmd, docDir) return err }, }) @@ -152,10 +152,9 @@ var ( VersionWrapDump = "" versionLink = "https://api.github.com/repos/fbsobreira/gotron-sdk/releases/latest" versionTagLink = "https://api.github.com/repos/fbsobreira/gotron-sdk/git/ref/tags/" - versionFormat = regexp.MustCompile("v[0-9]+-[a-z0-9]{7}") ) -//GitHubReleaseAssets json struct +// GitHubReleaseAssets json struct type GitHubReleaseAssets struct { ID json.Number `json:"id"` Name string `json:"name"` @@ -163,7 +162,7 @@ type GitHubReleaseAssets struct { URL string `json:"browser_download_url"` } -//GitHubRelease json struct +// GitHubRelease json struct type GitHubRelease struct { Prerelease bool `json:"prerelease"` TagName string `json:"tag_name"` @@ -172,7 +171,7 @@ type GitHubRelease struct { Assets []GitHubReleaseAssets `json:"assets"` } -//GitHubTag json struct +// GitHubTag json struct type GitHubTag struct { Ref string `json:"ref"` NodeID string `json:"node_id"` @@ -183,12 +182,21 @@ type GitHubTag struct { } func getGitVersion() (string, error) { - resp, _ := http.Get(versionLink) - defer resp.Body.Close() + resp, err := http.Get(versionLink) + if err != nil { + return "", err + } + + if resp != nil { + defer resp.Body.Close() + } // if error, no op if resp != nil && resp.StatusCode == 200 { buf := new(bytes.Buffer) - buf.ReadFrom(resp.Body) + _, err = buf.ReadFrom(resp.Body) + if err != nil { + return "", err + } release := &GitHubRelease{} if err := json.Unmarshal(buf.Bytes(), release); err != nil { return "", err @@ -199,7 +207,10 @@ func getGitVersion() (string, error) { // if error, no op if respTag != nil && respTag.StatusCode == 200 { buf.Reset() - buf.ReadFrom(respTag.Body) + _, err := buf.ReadFrom(respTag.Body) + if err != nil { + return "", err + } releaseTag := &GitHubTag{} if err := json.Unmarshal(buf.Bytes(), releaseTag); err != nil { @@ -209,8 +220,8 @@ func getGitVersion() (string, error) { if releaseTag.DATA.SHA[:8] != commit[1] { warnMsg := fmt.Sprintf("Warning: Using outdated version. Redownload to upgrade to %s\n", release.TagName) - fmt.Fprintf(os.Stderr, color.RedString(warnMsg)) - return release.TagName, fmt.Errorf(warnMsg) + fmt.Fprintf(os.Stderr, "%s", color.RedString(warnMsg)) + return release.TagName, fmt.Errorf("%s", warnMsg) } return release.TagName, nil } @@ -226,7 +237,7 @@ func Execute() { VersionWrapDump += ":" + tag } errMsg := errors.Wrapf(err, "commit: %s, error", VersionWrapDump).Error() - fmt.Fprintf(os.Stderr, errMsg+"\n") + fmt.Fprintf(os.Stderr, "%s\n", errMsg) fmt.Fprintf(os.Stderr, "try adding a `--help` flag\n") os.Exit(1) } @@ -274,7 +285,7 @@ func getPassphrase() (string, error) { if _, err := os.Stat(passphraseFilePath); os.IsNotExist(err) { return "", fmt.Errorf("passphrase file not found at `%s`", passphraseFilePath) } - dat, err := ioutil.ReadFile(passphraseFilePath) + dat, err := os.ReadFile(passphraseFilePath) if err != nil { return "", err } @@ -282,7 +293,7 @@ func getPassphrase() (string, error) { return pw, nil } else if userProvidesPassphrase { fmt.Println("Enter passphrase:") - pass, err := terminal.ReadPassword(int(os.Stdin.Fd())) + pass, err := term.ReadPassword(int(os.Stdin.Fd())) if err != nil { return "", err } @@ -300,7 +311,7 @@ func getPassphraseWithConfirm() (string, error) { if _, err := os.Stat(passphraseFilePath); os.IsNotExist(err) { return "", fmt.Errorf("passphrase file not found at `%s`", passphraseFilePath) } - dat, err := ioutil.ReadFile(passphraseFilePath) + dat, err := os.ReadFile(passphraseFilePath) if err != nil { return "", err } @@ -308,12 +319,12 @@ func getPassphraseWithConfirm() (string, error) { return pw, nil } else if userProvidesPassphrase { fmt.Println("Enter passphrase:") - pass, err := terminal.ReadPassword(int(os.Stdin.Fd())) + pass, err := term.ReadPassword(int(os.Stdin.Fd())) if err != nil { return "", err } fmt.Println("Repeat the passphrase:") - repeatPass, err := terminal.ReadPassword(int(os.Stdin.Fd())) + repeatPass, err := term.ReadPassword(int(os.Stdin.Fd())) if err != nil { return "", err } diff --git a/cmd/subcommands/sr.go b/cmd/subcommands/sr.go index 0b835bca6..a3c2be4b3 100644 --- a/cmd/subcommands/sr.go +++ b/cmd/subcommands/sr.go @@ -197,8 +197,7 @@ func init() { Use: "sr", Short: "SR Actions", RunE: func(cmd *cobra.Command, args []string) error { - cmd.Help() - return nil + return cmd.Help() }, } diff --git a/cmd/subcommands/trc10.go b/cmd/subcommands/trc10.go index 91b662b17..e7e1d7127 100644 --- a/cmd/subcommands/trc10.go +++ b/cmd/subcommands/trc10.go @@ -36,7 +36,7 @@ func trc10Sub() []*cobra.Command { return fmt.Errorf("no signer specified") } - trxNum := int64(1) + var trxNum int64 tokenNum := int64(1) t, err := dateparse.ParseAny(issueStartDate) if err != nil { @@ -450,8 +450,7 @@ func init() { Use: "trc10", Short: "Assets Manager", RunE: func(cmd *cobra.Command, args []string) error { - cmd.Help() - return nil + return cmd.Help() }, } diff --git a/cmd/subcommands/trc20.go b/cmd/subcommands/trc20.go index 891b63be1..8965db9cd 100644 --- a/cmd/subcommands/trc20.go +++ b/cmd/subcommands/trc20.go @@ -145,8 +145,7 @@ func init() { Use: "trc20", Short: "TRC20 Manager", RunE: func(cmd *cobra.Command, args []string) error { - cmd.Help() - return nil + return cmd.Help() }, } diff --git a/cmd/subcommands/utility.go b/cmd/subcommands/utility.go index ff912c947..11009d66a 100644 --- a/cmd/subcommands/utility.go +++ b/cmd/subcommands/utility.go @@ -12,8 +12,7 @@ func init() { Use: "utility", Short: "common tron utilities", RunE: func(cmd *cobra.Command, args []string) error { - cmd.Help() - return nil + return cmd.Help() }, } diff --git a/docs/api-reference.md b/docs/api-reference.md new file mode 100644 index 000000000..de0afefcd --- /dev/null +++ b/docs/api-reference.md @@ -0,0 +1,924 @@ +# API Reference + +Complete API reference for the GoTRON SDK packages. + +## Table of Contents + +- [Client Package](#client-package) +- [Address Package](#address-package) +- [Transaction Package](#transaction-package) +- [Keystore Package](#keystore-package) +- [ABI Package](#abi-package) +- [Common Package](#common-package) +- [Proto Package](#proto-package) + +## Client Package + +### `github.com/fbsobreira/gotron-sdk/pkg/client` + +The client package provides the main interface for interacting with TRON nodes. + +#### GrpcClient + +```go +type GrpcClient struct { + Address string + Conn *grpc.ClientConn + Client api.WalletClient +} +``` + +##### Constructor + +```go +func NewGrpcClient(address string) *GrpcClient +func NewGrpcClientWithTimeout(address string, timeout int) *GrpcClient +``` + +##### Connection Methods + +```go +func (g *GrpcClient) Start(opts ...grpc.DialOption) error +func (g *GrpcClient) Stop() +func (g *GrpcClient) SetAPIKey(apiKey string) error +``` + +##### Account Methods + +```go +// Get account information +func (g *GrpcClient) GetAccount(addr string) (*core.Account, error) + +// Get account resources +func (g *GrpcClient) GetAccountResource(addr string) (*api.AccountResourceMessage, error) + +// Get account net usage +func (g *GrpcClient) GetAccountNet(addr string) (*api.AccountNetMessage, error) +``` + +##### Transaction Methods + +```go +// Create transfer transaction +func (g *GrpcClient) Transfer(from, toAddress string, amount int64) (*api.TransactionExtention, error) + +// Broadcast transaction +func (g *GrpcClient) Broadcast(tx *core.Transaction) (*api.Return, error) + +// Get transaction by ID +func (g *GrpcClient) GetTransactionByID(id string) (*core.Transaction, error) + +// Get transaction info by ID +func (g *GrpcClient) GetTransactionInfoByID(id string) (*core.TransactionInfo, error) +``` + +##### Block Methods + +```go +// Get current block +func (g *GrpcClient) GetNowBlock() (*api.BlockExtention, error) + +// Get block by number +func (g *GrpcClient) GetBlockByNum(num int64) (*api.BlockExtention, error) + +// Get block by ID +func (g *GrpcClient) GetBlockByID(id string) (*core.Block, error) + +// Get block by latest number +func (g *GrpcClient) GetBlockByLatestNum(num int64) (*api.BlockListExtention, error) + +// Get block by limit next +func (g *GrpcClient) GetBlockByLimitNext(start, end int64) (*api.BlockListExtention, error) +``` + +##### Smart Contract Methods + +```go +// Deploy contract +func (g *GrpcClient) DeployContract( + from, contractName string, + abi *core.SmartContract_ABI, + codeStr string, + feeLimit, curPercent, oeLimit int64, +) (*api.TransactionExtention, error) + +// Trigger smart contract +func (g *GrpcClient) TriggerContract( + from, contractAddress, method, jsonString string, + feeLimit, tAmount int64, + tTokenID string, + tTokenAmount int64, +) (*api.TransactionExtention, error) + +// Trigger constant contract (call) +func (g *GrpcClient) TriggerConstantContract( + from, contractAddress, method, jsonString string, +) (*api.TransactionExtention, error) + +// Get contract ABI +func (g *GrpcClient) GetContractABI(contractAddress string) (*core.SmartContract_ABI, error) +``` + +##### Resource Management + +```go +// Freeze balance V2 +func (g *GrpcClient) FreezeBalanceV2( + from string, + resource core.ResourceCode, + frozenBalance int64, +) (*api.TransactionExtention, error) + +// Unfreeze balance V2 +func (g *GrpcClient) UnfreezeBalanceV2( + from string, + resource core.ResourceCode, + unfreezeBalance int64, +) (*api.TransactionExtention, error) + +// Delegate resource +func (g *GrpcClient) DelegateResource( + from, to string, + resource core.ResourceCode, + delegateBalance int64, + lock bool, + lockPeriod int64, +) (*api.TransactionExtention, error) + +// Undelegate resource +func (g *GrpcClient) UnDelegateResource( + owner, receiver string, + resource core.ResourceCode, + delegateBalance int64, +) (*api.TransactionExtention, error) + +// Freeze balance V1 (deprecated, use V2) +func (g *GrpcClient) FreezeBalance( + from, delegateTo string, + resource core.ResourceCode, + frozenBalance int64, +) (*api.TransactionExtention, error) + +// Unfreeze balance V1 (deprecated, use V2) +func (g *GrpcClient) UnfreezeBalance( + from, delegateTo string, + resource core.ResourceCode, +) (*api.TransactionExtention, error) +``` + +##### Witness Methods + +```go +// List witnesses +func (g *GrpcClient) ListWitnesses() (*api.WitnessList, error) + +// Create witness +func (g *GrpcClient) CreateWitness(from, urlStr string) (*api.TransactionExtention, error) + +// Update witness +func (g *GrpcClient) UpdateWitness(from, urlStr string) (*api.TransactionExtention, error) + +// Vote witness +func (g *GrpcClient) VoteWitnessAccount( + from string, + witnessMap map[string]int64, +) (*api.TransactionExtention, error) + +// Get witness brokerage +func (g *GrpcClient) GetWitnessBrokerage(witness string) (float64, error) + +// Update brokerage +func (g *GrpcClient) UpdateBrokerage(from string, commission int32) (*api.TransactionExtention, error) +``` + +##### TRC10 Token Methods + +```go +// Create asset issue +func (g *GrpcClient) AssetIssue( + from, name, description, abbr, urlStr string, + precision int32, + totalSupply, startTime, endTime, FreeAssetNetLimit, PublicFreeAssetNetLimit int64, + trxNum, icoNum, voteScore int32, + frozenSupply map[string]string, +) (*api.TransactionExtention, error) + +// Transfer asset +func (g *GrpcClient) TransferAsset( + from, toAddress, assetName string, + amount int64, +) (*api.TransactionExtention, error) + +// Participate asset issue +func (g *GrpcClient) ParticipateAssetIssue( + from, issuerAddress, tokenID string, + amount int64, +) (*api.TransactionExtention, error) + +// Get asset issue by account +func (g *GrpcClient) GetAssetIssueByAccount(address string) (*api.AssetIssueList, error) + +// Get asset issue by ID +func (g *GrpcClient) GetAssetIssueByID(tokenID string) (*core.AssetIssueContract, error) + +// Get asset issue list +func (g *GrpcClient) GetAssetIssueList(page int64, limit ...int) (*api.AssetIssueList, error) +``` + +##### Proposal Methods + +```go +// List proposals +func (g *GrpcClient) ProposalsList() (*api.ProposalList, error) + +// Create proposal +func (g *GrpcClient) ProposalCreate(from string, parameters map[int64]int64) (*api.TransactionExtention, error) + +// Approve proposal +func (g *GrpcClient) ProposalApprove(from string, id int64, confirm bool) (*api.TransactionExtention, error) + +// Withdraw proposal +func (g *GrpcClient) ProposalWithdraw(from string, id int64) (*api.TransactionExtention, error) +``` + +##### Exchange Methods + +```go +// List exchanges +func (g *GrpcClient) ExchangeList(page int64, limit ...int) (*api.ExchangeList, error) + +// Get exchange by ID +func (g *GrpcClient) ExchangeByID(id int64) (*core.Exchange, error) + +// Create exchange +func (g *GrpcClient) ExchangeCreate( + from string, + tokenID, tokenQuant int64, + secondTokenID, secondTokenQuant int64, +) (*api.TransactionExtention, error) + +// Inject exchange +func (g *GrpcClient) ExchangeInject( + from string, + exchangeID int64, + tokenID, tokenQuant int64, +) (*api.TransactionExtention, error) + +// Withdraw exchange +func (g *GrpcClient) ExchangeWithdraw( + from string, + exchangeID int64, + tokenID, tokenQuant int64, +) (*api.TransactionExtention, error) + +// Trade with exchange +func (g *GrpcClient) ExchangeTrade( + from string, + exchangeID int64, + tokenID, tokenQuant, expected int64, +) (*api.TransactionExtention, error) +``` + +##### TRC20 Token Methods + +```go +// General TRC20 contract call +func (g *GrpcClient) TRC20Call( + from, contractAddress, data string, + constant bool, + feeLimit int64, +) (*api.TransactionExtention, error) + +// Get token name +func (g *GrpcClient) TRC20GetName(contractAddress string) (string, error) + +// Get token symbol +func (g *GrpcClient) TRC20GetSymbol(contractAddress string) (string, error) + +// Get token decimals +func (g *GrpcClient) TRC20GetDecimals(contractAddress string) (*big.Int, error) + +// Parse numeric property from TRC20 response +func (g *GrpcClient) ParseTRC20NumericProperty(data string) (*big.Int, error) + +// Parse string property from TRC20 response +func (g *GrpcClient) ParseTRC20StringProperty(data string) (string, error) + +// Get token balance for address +func (g *GrpcClient) TRC20ContractBalance(addr, contractAddress string) (*big.Int, error) + +// Send TRC20 tokens +func (g *GrpcClient) TRC20Send( + from, to, contract string, + amount *big.Int, + feeLimit int64, +) (*api.TransactionExtention, error) + +// Transfer TRC20 tokens on behalf of owner +func (g *GrpcClient) TRC20TransferFrom( + owner, from, to, contract string, + amount *big.Int, + feeLimit int64, +) (*api.TransactionExtention, error) + +// Approve TRC20 token spending +func (g *GrpcClient) TRC20Approve( + from, to, contract string, + amount *big.Int, + feeLimit int64, +) (*api.TransactionExtention, error) +``` + +##### Network Information Methods + +```go +// List all nodes +func (g *GrpcClient) ListNodes() (*api.NodeList, error) + +// Get next maintenance time +func (g *GrpcClient) GetNextMaintenanceTime() (*api.NumberMessage, error) + +// Get total transaction count +func (g *GrpcClient) TotalTransaction() (*api.NumberMessage, error) + +// Get node information +func (g *GrpcClient) GetNodeInfo() (*core.NodeInfo, error) + +// Get energy prices +func (g *GrpcClient) GetEnergyPrices() (*api.PricesResponseMessage, error) + +// Get bandwidth prices +func (g *GrpcClient) GetBandwidthPrices() (*api.PricesResponseMessage, error) + +// Get memo fee +func (g *GrpcClient) GetMemoFee() (*api.PricesResponseMessage, error) +``` + +##### Additional Account Methods + +```go +// Get rewards information +func (g *GrpcClient) GetRewardsInfo(addr string) (int64, error) + +// Create new account +func (g *GrpcClient) CreateAccount(from, addr string) (*api.TransactionExtention, error) + +// Get detailed account information +func (g *GrpcClient) GetAccountDetailed(addr string) (*account.Account, error) + +// Withdraw balance (claim rewards) +func (g *GrpcClient) WithdrawBalance(from string) (*api.TransactionExtention, error) + +// Update account permissions +func (g *GrpcClient) UpdateAccountPermission( + from string, + owner, witness map[string]interface{}, + actives []map[string]interface{}, +) (*api.TransactionExtention, error) +``` + +##### Additional Asset Methods + +```go +// Get asset issue by name +func (g *GrpcClient) GetAssetIssueByName(name string) (*core.AssetIssueContract, error) + +// Update asset issue +func (g *GrpcClient) UpdateAssetIssue( + from, description, urlStr string, + newLimit, newPublicLimit int64, +) (*api.TransactionExtention, error) + +// Unfreeze asset +func (g *GrpcClient) UnfreezeAsset(from string) (*api.TransactionExtention, error) +``` + +##### Additional Resource Methods + +```go +// Get delegated resources +func (g *GrpcClient) GetDelegatedResources(address string) ([]*api.DelegatedResourceList, error) + +// Get delegated resources V2 +func (g *GrpcClient) GetDelegatedResourcesV2(address string) ([]*api.DelegatedResourceList, error) + +// Get maximum delegatable size +func (g *GrpcClient) GetCanDelegatedMaxSize(address string, resource int32) (*api.CanDelegatedMaxSizeResponseMessage, error) + +// Get available unfreeze count +func (g *GrpcClient) GetAvailableUnfreezeCount(from string) (*api.GetAvailableUnfreezeCountResponseMessage, error) + +// Get withdrawable unfreeze amount +func (g *GrpcClient) GetCanWithdrawUnfreezeAmount(from string, timestamp int64) (*api.CanWithdrawUnfreezeAmountResponseMessage, error) + +// Withdraw expired unfreeze +func (g *GrpcClient) WithdrawExpireUnfreeze(from string, timestamp int64) (*api.TransactionExtention, error) +``` + +##### Additional Contract Methods + +```go +// Update contract energy limit +func (g *GrpcClient) UpdateEnergyLimitContract( + from, contractAddress string, + value int64, +) (*api.TransactionExtention, error) + +// Update contract settings +func (g *GrpcClient) UpdateSettingContract( + from, contractAddress string, + value int64, +) (*api.TransactionExtention, error) + +// Estimate energy for contract call +func (g *GrpcClient) EstimateEnergy( + from, contractAddress, method, jsonString string, + tAmount int64, + tTokenID string, + tTokenAmount int64, +) (*api.EstimateEnergyMessage, error) + +// Update transaction hash +func (g *GrpcClient) UpdateHash(tx *api.TransactionExtention) error +``` + +##### Additional Block Methods + +```go +// Get block information by number +func (g *GrpcClient) GetBlockInfoByNum(num int64) (*api.TransactionInfoList, error) +``` + +##### Transaction Analysis Methods + +```go +// Get transaction sign weight +func (g *GrpcClient) GetTransactionSignWeight(tx *core.Transaction) (*api.TransactionSignWeight, error) +``` + +##### Client Management Methods + +```go +// Set client timeout +func (g *GrpcClient) SetTimeout(timeout time.Duration) + +// Reconnect to a different node +func (g *GrpcClient) Reconnect(url string) error +``` + +## Address Package + +### `github.com/fbsobreira/gotron-sdk/pkg/address` + +The address package handles TRON address encoding and validation. + +#### Types + +```go +type Address [AddressLength]byte +``` + +#### Constants + +```go +const ( + AddressLength = 21 + AddressLengthBase58 = 34 + TronBytePrefix = byte(0x41) +) +``` + +#### Functions + +```go +// Convert public key to address +func PubkeyToAddress(p ecdsa.PublicKey) Address + +// Convert BTCEC public key to address +func BTCECPubkeyToAddress(p *btcec.PublicKey) Address + +// Convert BTCEC private key to address +func BTCECPrivkeyToAddress(p *btcec.PrivateKey) Address + +// Convert base58 string to address +func Base58ToAddress(s string) (Address, error) + +// Convert base64 string to address +func Base64ToAddress(s string) (Address, error) + +// Convert hex string to address +func HexToAddress(s string) Address + +// Convert big.Int to address +func BigToAddress(b *big.Int) Address +``` + +#### Methods + +```go +// Convert to string (base58) +func (a Address) String() string + +// Convert to hex +func (a Address) Hex() string + +// Convert to bytes +func (a Address) Bytes() []byte + +// Check if valid TRON address +func (a Address) IsValid() bool + +// Database driver value interface +func (a Address) Value() (driver.Value, error) +``` + +## Transaction Package + +### `github.com/fbsobreira/gotron-sdk/pkg/client/transaction` + +The transaction package handles transaction signing and management. + +#### Controller + +```go +type Controller struct { + executionError error + resultError error + client *client.GrpcClient + tx *core.Transaction + sender sender + Behavior behavior + Result *api.Return + Receipt *core.TransactionInfo +} +``` + +##### Methods + +```go +// Create controller with options +func NewController( + client *client.GrpcClient, + senderKs *keystore.KeyStore, + senderAcct *keystore.Account, + tx *core.Transaction, + options ...func(*Controller), +) *Controller + +// Execute transaction (sign and broadcast) +func (C *Controller) ExecuteTransaction() error + +// Get transaction hash +func (C *Controller) TransactionHash() (string, error) + +// Get raw data bytes from transaction +func (C *Controller) GetRawData() ([]byte, error) + +// Get result error +func (C *Controller) GetResultError() error +``` + +#### Transaction Signing Functions + +```go +// Sign transaction with BTCEC private key +func SignTransaction(tx *core.Transaction, signer *btcec.PrivateKey) (*core.Transaction, error) + +// Sign transaction with ECDSA private key +func SignTransactionECDSA(tx *core.Transaction, signer *ecdsa.PrivateKey) (*core.Transaction, error) +``` + +## Keystore Package + +### `github.com/fbsobreira/gotron-sdk/pkg/keystore` + +The keystore package provides secure key storage and management. + +#### KeyStore + +```go +type KeyStore struct { + storage Storage + cache *accountCache + scrypt scryptParams + isLocked bool + mu sync.RWMutex +} +``` + +##### Constructor + +```go +func NewKeyStore(keydir string, scryptN, scryptP int) *KeyStore +``` + +##### Methods + +```go +// Create new account +func (ks *KeyStore) NewAccount(passphrase string) (Account, error) + +// Import ECDSA private key +func (ks *KeyStore) ImportECDSA(priv *ecdsa.PrivateKey, passphrase string) (Account, error) + +// Import preexisting keyfile +func (ks *KeyStore) Import(keyJSON []byte, passphrase, newPassphrase string) (Account, error) + +// Export account +func (ks *KeyStore) Export(a Account, passphrase, newPassphrase string) ([]byte, error) + +// Delete account +func (ks *KeyStore) Delete(a Account, passphrase string) error + +// Update account +func (ks *KeyStore) Update(a Account, passphrase, newPassphrase string) error + +// List all accounts +func (ks *KeyStore) Accounts() []Account + +// Check if account exists +func (ks *KeyStore) HasAddress(addr address.Address) bool + +// Unlock account +func (ks *KeyStore) Unlock(a Account, passphrase string) error + +// Lock account +func (ks *KeyStore) Lock(addr address.Address) error + +// Sign transaction +func (ks *KeyStore) SignTx(a Account, tx *core.Transaction) (*core.Transaction, error) + +// Sign transaction with passphrase +func (ks *KeyStore) SignTxWithPassphrase(a Account, passphrase string, tx *core.Transaction) (*core.Transaction, error) + +// Sign hash +func (ks *KeyStore) SignHash(a Account, hash []byte) ([]byte, error) + +// Sign hash with passphrase +func (ks *KeyStore) SignHashWithPassphrase(a Account, passphrase string, hash []byte) ([]byte, error) + +// Get wallets +func (ks *KeyStore) Wallets() []Wallet + +// Subscribe to wallet events +func (ks *KeyStore) Subscribe(sink chan<- WalletEvent) event.Subscription + +// Get decrypted key +func (ks *KeyStore) GetDecryptedKey(a Account, auth string) (Account, *Key, error) + +// Export account to JSON +func (ks *KeyStore) Export(a Account, passphrase, newPassphrase string) (keyJSON []byte, err error) +``` + +#### Account + +```go +type Account struct { + Address address.Address + URL URL +} +``` + +## ABI Package + +### `github.com/fbsobreira/gotron-sdk/pkg/abi` + +The ABI package handles smart contract ABI encoding and decoding. + +#### ABI + +```go +type ABI struct { + Constructor Method + Methods map[string]Method + Events map[string]Event +} +``` + +##### Functions + +```go +// Parse ABI from JSON +func JSON(reader io.Reader) (ABI, error) + +// Pack method call +func (abi ABI) Pack(name string, args ...interface{}) ([]byte, error) + +// Unpack method return +func (abi ABI) Unpack(v interface{}, name string, data []byte) error + +// Unpack event +func (abi ABI) UnpackEvent(v interface{}, name string, data []byte) error +``` + +#### Method + +```go +type Method struct { + Name string + Const bool + Inputs Arguments + Outputs Arguments +} +``` + +#### Event + +```go +type Event struct { + Name string + Anonymous bool + Inputs Arguments +} +``` + +## Common Package + +### `github.com/fbsobreira/gotron-sdk/pkg/common` + +The common package provides utility functions and types. + +#### Functions + +```go +// Bytes to hex string +func BytesToHex(bytes []byte) string + +// Hex string to bytes +func HexToBytes(hex string) []byte + +// Left pad bytes +func LeftPadBytes(slice []byte, l int) []byte + +// Right pad bytes +func RightPadBytes(slice []byte, l int) []byte + +// Convert to hex +func ToHex(b []byte) string + +// From hex +func FromHex(s string) []byte + +// Has hex prefix +func HasHexPrefix(str string) bool + +// Is hex +func IsHex(str string) bool + +// Big int to bytes +func BigToBytes(num *big.Int) []byte + +// Bytes to big int +func BytesToBig(bytes []byte) *big.Int +``` + +#### Hash Functions + +```go +// Keccak256 hash +func Keccak256(data ...[]byte) []byte + +// SHA256 hash +func SHA256(data ...[]byte) []byte + +// Hash message for signing +func HashMessage(message []byte) []byte +``` + +#### Encoding Functions + +```go +// Encode to base58 +func EncodeBase58(input []byte) string + +// Decode from base58 +func DecodeBase58(input string) ([]byte, error) + +// Encode check +func EncodeCheck(input []byte) string + +// Decode check +func DecodeCheck(input string) ([]byte, error) +``` + +## Proto Package + +### `github.com/fbsobreira/gotron-sdk/pkg/proto` + +The proto package contains generated Protocol Buffer definitions for TRON. + +#### Core Types + +- `Account` +- `Transaction` +- `Block` +- `SmartContract` +- `AssetIssueContract` +- `TransferContract` +- `TransferAssetContract` +- `VoteWitnessContract` +- `WitnessCreateContract` +- `WitnessUpdateContract` +- `FreezeBalanceContract` +- `UnfreezeBalanceContract` +- `ProposalCreateContract` +- `ProposalApproveContract` +- `ProposalDeleteContract` +- `ExchangeCreateContract` +- `ExchangeInjectContract` +- `ExchangeWithdrawContract` +- `ExchangeTransactionContract` + +#### API Types + +- `TransactionExtention` +- `Return` +- `NodeInfo` +- `AccountResourceMessage` +- `AccountNetMessage` +- `WitnessList` +- `AssetIssueList` +- `ProposalList` +- `ExchangeList` +- `BlockList` + +## Error Types + +### Common Errors + +```go +var ( + ErrInvalidAddress = errors.New("invalid address") + ErrInvalidPrivateKey = errors.New("invalid private key") + ErrInvalidTransaction = errors.New("invalid transaction") + ErrInsufficientFunds = errors.New("insufficient funds") + ErrContractExecution = errors.New("contract execution failed") +) +``` + +### Transaction Errors + +```go +var ( + ErrBadTransactionParam = errors.New("bad transaction parameter") + ErrTransactionExpired = errors.New("transaction expired") + ErrTransactionFailed = errors.New("transaction failed") +) +``` + +### Keystore Errors + +```go +var ( + ErrLocked = errors.New("account locked") + ErrNoMatch = errors.New("no key for given address or file") + ErrDecrypt = errors.New("could not decrypt key with given passphrase") + ErrAccountAlreadyExists = errors.New("account already exists") +) +``` + +## Constants + +### Resource Types + +```go +const ( + ResourceBandwidth core.ResourceCode = 0 + ResourceEnergy core.ResourceCode = 1 +) +``` + +### Contract Types + +```go +const ( + AccountCreateContract = "AccountCreateContract" + TransferContract = "TransferContract" + TransferAssetContract = "TransferAssetContract" + VoteAssetContract = "VoteAssetContract" + VoteWitnessContract = "VoteWitnessContract" + WitnessCreateContract = "WitnessCreateContract" + AssetIssueContract = "AssetIssueContract" + WitnessUpdateContract = "WitnessUpdateContract" + ParticipateAssetIssueContract = "ParticipateAssetIssueContract" + AccountUpdateContract = "AccountUpdateContract" + FreezeBalanceContract = "FreezeBalanceContract" + UnfreezeBalanceContract = "UnfreezeBalanceContract" + WithdrawBalanceContract = "WithdrawBalanceContract" + UnfreezeAssetContract = "UnfreezeAssetContract" + UpdateAssetContract = "UpdateAssetContract" + ProposalCreateContract = "ProposalCreateContract" + ProposalApproveContract = "ProposalApproveContract" + ProposalDeleteContract = "ProposalDeleteContract" + SetAccountIdContract = "SetAccountIdContract" + CustomContract = "CustomContract" + CreateSmartContract = "CreateSmartContract" + TriggerSmartContract = "TriggerSmartContract" + ExchangeCreateContract = "ExchangeCreateContract" + ExchangeInjectContract = "ExchangeInjectContract" + ExchangeWithdrawContract = "ExchangeWithdrawContract" + ExchangeTransactionContract = "ExchangeTransactionContract" + UpdateEnergyLimitContract = "UpdateEnergyLimitContract" + AccountPermissionUpdateContract = "AccountPermissionUpdateContract" +) +``` \ No newline at end of file diff --git a/docs/cli-usage.md b/docs/cli-usage.md new file mode 100644 index 000000000..3a5ffaca9 --- /dev/null +++ b/docs/cli-usage.md @@ -0,0 +1,657 @@ +# CLI Usage Guide + +This guide provides comprehensive documentation for the `tronctl` command-line interface. + +## Table of Contents + +- [Global Options](#global-options) +- [Account Commands](#account-commands) +- [Key Management](#key-management) +- [Blockchain Commands](#blockchain-commands) +- [Smart Contract Commands](#smart-contract-commands) +- [TRC10 Token Commands](#trc10-token-commands) +- [TRC20 Token Commands](#trc20-token-commands) +- [Super Representative Commands](#super-representative-commands) +- [Proposal Commands](#proposal-commands) +- [Exchange Commands](#exchange-commands) +- [Configuration](#configuration) +- [Utility Commands](#utility-commands) +- [Examples](#examples) + +## Global Options + +These options can be used with any command: + +``` +--node
TRON node address (default: grpc.trongrid.io:50051) +--apiKey Trongrid API key +--withTLS Use TLS connection +--timeout Request timeout (default: 60s) +--verbose Enable verbose output +--config Config file path (default: ~/.tronctl/config.yaml) +``` + +## Account Commands + +### Get Account Balance + +```bash +tronctl account balance
+ +# Example +tronctl account balance TPjGUuQfq6R3FMBmsacd6Z5dvAgrD2rz4n +``` + +### Send TRX + +```bash +tronctl account send + +# Options +--signer Signer account name (required) + +# Example +tronctl account send TN3W4H6rK2ce4vX9YnFQHwKENnHjoxb3m9 100.5 --signer myaccount +``` + +### Freeze Resources + +```bash +tronctl account freeze + +# Parameters +resource: BANDWIDTH or ENERGY + +# Options +--signer Account to freeze from (required) +--days Days to freeze (default: 3) + +# Example +tronctl account freeze 1000 ENERGY --signer myaccount +``` + +### Unfreeze Resources + +```bash +tronctl account unfreeze + +# Example +tronctl account unfreeze BANDWIDTH +``` + +### Vote for Witnesses + +```bash +tronctl account vote + +# Options +--signer Voter account name (required) + +# Example +tronctl account vote TLyqzVGLV1srkB7dToTAEqgDSfPtXRJZYH 100000 --signer myaccount +``` + +### Get Account Resources + +```bash +tronctl account resources
+ +# Example +tronctl account resources TPjGUuQfq6R3FMBmsacd6Z5dvAgrD2rz4n +``` + +### Update Account Permissions + +```bash +tronctl account permission
+ +# Options +--owner Owner permission keys (comma-separated) +--active Active permission keys (comma-separated) +--witness Witness permission key + +# Example +tronctl account permission TPjGUuQfq6R3FMBmsacd6Z5dvAgrD2rz4n --active TN3W4H6rK2ce4vX9YnFQHwKENnHjoxb3m9,TLyqzVGLV1srkB7dToTAEqgDSfPtXRJZYH +``` + +### Sign Message + +```bash +tronctl account sign + +# Options +--signer Signer account name (required) + +# Example +tronctl account sign "Hello TRON" --signer myaccount +``` + +## Key Management + +### Create New Account + +```bash +tronctl keys add + +# Options +--passphrase Use passphrase encryption + +# Example +tronctl keys add myaccount +``` + +### Import Private Key + +```bash +tronctl keys import + +# Options +--passphrase Use passphrase encryption + +# Example +tronctl keys import 8f5c7e1a2b3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f +``` + +### Recover from Mnemonic + +```bash +tronctl keys recover-from-mnemonic + +# Interactive prompt will ask for: +# - Mnemonic phrase +# - Mnemonic password (optional) + +# Example +tronctl keys recover-from-mnemonic myaccount +``` + +### Export Private Key + +```bash +tronctl keys export + +# Example +tronctl keys export myaccount +``` + +### List Accounts + +```bash +tronctl keys list + +# Example output: +# Address Balance Type +# TPjGUuQfq6R3FMBmsacd6Z5dvAgrD2rz4n 1000 TRX Local +# TN3W4H6rK2ce4vX9YnFQHwKENnHjoxb3m9 500 TRX Ledger +``` + +### Remove Account + +```bash +tronctl keys remove + +# Example +tronctl keys remove myaccount +``` + +## Blockchain Commands + +### Get Node Information + +```bash +tronctl bc getnodeinfo + +# Example output shows node version, network, and configuration +``` + +### Get Transaction by ID + +```bash +tronctl bc gettransaction + +# Example +tronctl bc gettransaction 7c2d4206c03a883dd9066d620335dc1be272a8dc733cfa3f6d10308faa37facc +``` + +### Get Block by Number + +```bash +tronctl bc getblock + +# Example +tronctl bc getblock 30000000 +``` + +### Get Latest Block + +```bash +tronctl bc getblockbylatest +``` + +### Get Next Maintenance Time + +```bash +tronctl bc nextmaintenancetime +``` + +## Smart Contract Commands + +### Deploy Contract + +```bash +tronctl contract deploy + +# Options +--signer Deployer account name (required) +--name Contract name +--consume-user-energy User energy consumption percentage +--fee-limit Maximum TRX to spend +--constructor Constructor arguments (JSON format) + +# Example +tronctl contract deploy Token.bin Token.abi --signer myaccount --name "MyToken" --constructor '["My Token", "MTK", 1000000]' +``` + +### Call Contract (Read) + +```bash +tronctl contract call + +# Options +--abi ABI file path + +# Example +tronctl contract call TN3W4H6rK2ce4vX9YnFQHwKENnHjoxb3m9 balanceOf '["TPjGUuQfq6R3FMBmsacd6Z5dvAgrD2rz4n"]' +``` + +### Trigger Contract (Write) + +```bash +tronctl contract trigger + +# Options +--signer Caller account name (required) +--abi ABI file path +--fee-limit Maximum TRX to spend +--call-value TRX to send with call + +# Example +tronctl contract trigger TN3W4H6rK2ce4vX9YnFQHwKENnHjoxb3m9 transfer '["TLyqzVGLV1srkB7dToTAEqgDSfPtXRJZYH", 1000]' --signer myaccount --fee-limit 10 +``` + +### Get Contract Info + +```bash +tronctl contract get + +# Example +tronctl contract get TN3W4H6rK2ce4vX9YnFQHwKENnHjoxb3m9 +``` + +## TRC10 Token Commands + +### Issue Token + +```bash +tronctl trc10 issue + +# Options +--name Token name +--abbr Token abbreviation +--supply Total supply +--decimal Decimal places +--description Token description +--url Project URL +--start-time