-
Notifications
You must be signed in to change notification settings - Fork 5
/
Copy pathmain.go
149 lines (134 loc) · 4.3 KB
/
main.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
package main
import (
"context"
"flag"
"fmt"
"log"
"net/url"
"os"
"path/filepath"
"strings"
"github.com/algorand/go-algorand-sdk/client/v2/common"
"github.com/algorand/go-algorand-sdk/client/v2/indexer"
"github.com/algorand/go-algorand-sdk/types"
"github.com/pbennett/algo-export/exporter"
)
type accountList []types.Address
func (al *accountList) String() string {
return fmt.Sprint(*al)
}
func (al *accountList) Set(value string) error {
*al = accountList{}
for _, val := range strings.Split(value, ",") {
address, err := types.DecodeAddress(val)
if err != nil {
return fmt.Errorf("address:%v not valid: %w", address, err)
}
*al = append(*al, address)
}
return nil
}
func main() {
var (
accounts accountList
formatFlag = flag.String("f", exporter.Formats()[0], fmt.Sprintf("Format to export: [%s]", strings.Join(exporter.Formats(), ", ")))
hostAddrFlag = flag.String("s", "localhost:8980", "Index server to connect to")
apiKey = flag.String("api", "", "Optional API Key for local indexer, or for PureStake")
pureStakeApiFlag = flag.Bool("p", false, "Use PureStake API - ignoring -s argument")
outDirFlag = flag.String("o", "", "output directory path for exported files")
)
flag.Var(&accounts, "a", "Account or list of comma delimited accounts to export")
flag.Parse()
if len(accounts) == 0 {
fmt.Println("One or more account addresses to export must be specified.")
flag.Usage()
os.Exit(1)
}
var export = exporter.GetFormatter(*formatFlag)
if export == nil {
fmt.Println("Unable to find formatter for:", *formatFlag)
fmt.Println("Valid formats are:\n", strings.Join(exporter.Formats(), "\n "))
os.Exit(1)
}
client, err := getClient(*hostAddrFlag, *apiKey, *pureStakeApiFlag)
if err != nil {
fmt.Println(err)
os.Exit(1)
}
if !fileExist(*outDirFlag) {
if err = os.MkdirAll(*outDirFlag, 0755); err != nil {
fmt.Println(err)
os.Exit(1)
}
}
if err := exportAccounts(client, export, accounts, *outDirFlag); err != nil {
fmt.Println(err)
os.Exit(1)
}
}
func getClient(serverFlag string, apiKey string, usePureStake bool) (*indexer.Client, error) {
var (
client *indexer.Client
serverAddr *url.URL
err error
)
if !usePureStake {
serverAddr, err = url.Parse(fmt.Sprintf("http://%s", serverFlag))
if err != nil {
return nil, fmt.Errorf("error in server address: %w", err)
}
client, err = indexer.MakeClient(serverAddr.String(), apiKey)
if err != nil {
return nil, fmt.Errorf("error creating indexer client: %w", err)
}
} else {
commonClient, err := common.MakeClientWithHeaders("https://mainnet-algorand.api.purestake.io/idx2", "X-API-Key", apiKey, []*common.Header{})
if err != nil {
return nil, fmt.Errorf("error creating indexer client to purestake: %w", err)
}
client = (*indexer.Client)(commonClient)
}
return client, err
}
func exportAccounts(client *indexer.Client, export exporter.Interface, accounts accountList, outDir string) error {
state := LoadConfig()
fmt.Println("Exporting accounts:")
for _, accountAddress := range accounts {
// accountAddress contains the non-checksummed internal version - String() provides the
// version users know - the base32 pubkey w/ checksum
account := accountAddress.String()
startRound := state.ForAccount(export.Name(), account).LastRound + 1
fmt.Println(account, "starting at:", startRound)
lookupTx := client.LookupAccountTransactions(account)
lookupTx.MinRound(startRound)
transactions, err := lookupTx.Do(context.TODO())
if err != nil {
return fmt.Errorf("error looking up transactions: %w", err)
}
endRound := transactions.CurrentRound
state.ForAccount(export.Name(), account).LastRound = endRound
fmt.Printf(" %v transactions\n", len(transactions.Transactions))
if len(transactions.Transactions) == 0 {
continue
}
outCsv, err := os.Create(filepath.Join(outDir, fmt.Sprintf("%s-%s-%d-%d.csv", export.Name(), account, startRound, endRound)))
export.WriteHeader(outCsv)
for _, tx := range transactions.Transactions {
for _, record := range exporter.FilterTransaction(tx, account) {
export.WriteRecord(outCsv, record)
}
}
}
state.SaveConfig()
return nil
}
func fileExist(file string) bool {
_, err := os.Stat(file)
if err != nil {
if os.IsNotExist(err) {
return false
}
log.Fatalln(err)
}
return true
}