-
Notifications
You must be signed in to change notification settings - Fork 3
/
algorand.go
346 lines (286 loc) · 11 KB
/
algorand.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
package algorand
import (
"log"
"github.com/pkg/errors"
utils "github.com/Varunram/essentials/utils"
"github.com/algorand/go-algorand-sdk/client/algod"
"github.com/algorand/go-algorand-sdk/client/algod/models"
"github.com/algorand/go-algorand-sdk/client/kmd"
"github.com/algorand/go-algorand-sdk/mnemonic"
"github.com/algorand/go-algorand-sdk/transaction"
"github.com/algorand/go-algorand-sdk/types"
)
// Algorand's model is similar to that of ethereum and stellar (account based model)
// these accounts have a name ("blah") and each one of them can have multiple addresses
// associated with them. Each address should have a minimum balance of 0.1 Algo (100,000 microAlgos)
// Algorand doesn't have a horizone API (PHEW!) and needs one to directly interact with the blockchain
// or create a private test network
// These constants represent the algod REST endpoint and the corresponding
// API token. You can retrieve these from the `algod.net` and `algod.token`
// files in the algod data directory.
// AlgodClient is a package-level gloabal variable
var AlgodClient algod.Client
// KmdClient is a package-level gloabal variable
var KmdClient kmd.Client
// InitAlgodClient initializes a new algorand daemon client
func InitAlgodClient() (algod.Client, error) {
var err error
AlgodClient, err = algod.MakeClient(AlgodAddress, AlgodToken)
if err != nil {
log.Printf("failed to make algod client: %s\n", err)
return AlgodClient, nil
}
return AlgodClient, nil
}
// InitKmdClient initializes a new key management daemon client
func InitKmdClient() (kmd.Client, error) {
return kmd.MakeClient(KmdAddress, KmdToken)
}
// Init initializes the algod client and the kmd client
func Init() error {
var err error
AlgodClient, err = InitAlgodClient()
if err != nil {
return err
}
KmdClient, err = InitKmdClient()
return err
}
// GetLatestBlock getst the latest block from the blockchain
func GetLatestBlock(status models.NodeStatus) (models.Block, error) {
return AlgodClient.Block(status.LastRound)
}
// GetBlock gets the details ofa given block from the algorand blockchain
func GetBlock(blockNumber uint64) (models.Block, error) {
return AlgodClient.Block(blockNumber)
}
// GetStatus gets the status of a given algod client
func GetStatus(Client algod.Client) (models.NodeStatus, error) {
var status models.NodeStatus
status, err := AlgodClient.Status()
if err != nil {
log.Printf("error getting algod status: %s\n", err)
return status, err
}
return status, nil
}
// CreateNewWallet creates a new wallet
func CreateNewWallet(name string, password string) (string, error) {
response, err := KmdClient.CreateWallet(name, password, kmd.DefaultWalletDriver, types.MasterDerivationKey{})
if err != nil {
return "", errors.Wrap(err, "error creating wallet")
}
walletID := response.Wallet.ID
return walletID, nil
}
// generateWalletToken creates a wallet handle and is used for things like signing transactions
// and creating accounts. Wallet handles do expire, but they can be renewed
func generateWalletToken(walletID string, password string) (string, error) {
// Get a wallet handle. The wallet handle is used for things like signing transactions
// and creating accounts. Wallet handles do expire, but they can be renewed
initResponse, err := KmdClient.InitWalletHandle(walletID, password)
if err != nil {
log.Printf("Error initializing wallet handle: %s\n", err)
return "", err
}
walletHandleToken := initResponse.WalletHandleToken
return walletHandleToken, nil
}
// generateAddress generates an address from a given wallet handle
func generateAddress(walletHandleToken string) (string, error) {
// Generate a new address from the wallet handle
genResponse, err := KmdClient.GenerateKey(walletHandleToken)
if err != nil {
log.Printf("Error generating key: %s\n", err)
return "", err
}
log.Printf("Generated address %s\n", genResponse.Address)
return genResponse.Address, nil
}
func sendTx(fromAddr string, toAddr string, amount uint64, note []byte,
walletHandleToken string, password string) (string, error) {
// Get the suggested transaction parameters
txParams, err := AlgodClient.SuggestedParams()
if err != nil {
return "", errors.Wrap(err, "error getting suggested tx params")
}
// Make transaction
genID := txParams.GenesisID
genHash := txParams.GenesisHash
fee := txParams.Fee
lastRound := txParams.LastRound
// (from, to string, fee, amount, firstRound, lastRound uint64, note []byte,
// closeRemainderTo, genesisID string, genesisHash []byte) (types.Transaction, error)
tx, err := transaction.MakePaymentTxn(fromAddr, toAddr, fee, amount, lastRound-50, lastRound+50, nil, "", genID, genHash)
if err != nil {
return "", errors.Wrap(err, "error creating transaction")
}
// Sign the transaction
signResponse, err := KmdClient.SignTransaction(walletHandleToken, password, tx)
if err != nil {
return "", errors.Wrap(err, "failed to sign transaction with kmd")
}
// Broadcast the transaction to the network
// Note that this transaction will get rejected because the accounts do not have any tokens
sendResponse, err := AlgodClient.SendRawTransaction(signResponse.SignedTransaction)
if err != nil {
return "", errors.Wrap(err, "failed to send transaction")
}
log.Println("Transaction ID: ", sendResponse.TxID)
return sendResponse.TxID, nil
}
// GetAlgo seeds a given wallet with a specific number of algos similar to what friendbot
// does on stellar
func GetAlgo(walletName string, password string) (string, error) {
ourWalletId, err := getWalletId(walletName)
if err != nil {
return "", errors.Wrap(err, "couldn't get wallet id of wallet, quitting")
}
// get a wallet handle token to sign the transaction with
walletHandleToken, err := generateWalletToken(ourWalletId, password)
if err != nil {
return "", errors.Wrap(err, "error initializing wallet handle")
}
// Generate a new address from the wallet handle
toAddr, err := generateAddress(walletHandleToken)
if err != nil {
return "", errors.Wrap(err, "error generating key")
}
log.Println("Generated to address: ", toAddr)
fromAddr := "YXU3MTTKV74UAGED6ROTHVVPEY5646WI3N5FLLQZWFV66AFKVQ5PMMYDZE"
amount := uint64(150000) // 100000 is the minimum balance an account can hold
note := []byte("cool")
txid, err := sendTx(fromAddr, toAddr, amount, note, walletHandleToken, password)
if err != nil {
return "", errors.Wrap(err, "couldn't send tx")
}
log.Println("txid: ", txid)
return txid, nil
}
// SendAlgoToSelf sends algos to another address owned by the same user
func SendAlgoToSelf(walletName string, password string, fromAddr string, amount uint64) (string, error) {
// Get the list of wallets
ourWalletId, err := getWalletId(walletName)
if err != nil {
return "", errors.Wrap(err, "couldn't get wallet id of wallet, quitting")
}
// get a wallet handle token to sign the transaction with
walletHandleToken, err := generateWalletToken(ourWalletId, password)
if err != nil {
return "", errors.Wrap(err, "error initializing wallet handle")
}
// Generate a new address from the wallet handle
toAddr, err := generateAddress(walletHandleToken)
if err != nil {
return "", errors.Wrap(err, "error generating key")
}
log.Println("Generated to address: ", toAddr)
note := []byte("cool")
txid, err := sendTx(fromAddr, toAddr, amount, note, walletHandleToken, password)
if err != nil {
return "", errors.Wrap(err, "couldn't send tx")
}
log.Println("txid: ", txid)
return txid, nil
}
func getWalletId(walletName string) (string, error) {
listResponse, err := KmdClient.ListWallets()
if err != nil {
return "", errors.Wrap(err, "error listing wallets")
}
// Find our walletID in the list
var ourWalletId string
for _, wallet := range listResponse.Wallets {
if wallet.Name == walletName {
log.Printf("found wallet '%s' with ID: %s\n", wallet.Name, wallet.ID)
ourWalletId = wallet.ID
}
}
return ourWalletId, nil
}
// SendAlgo sends algos to another address from a source account
func SendAlgo(walletName string, password string, amount uint64, fromAddr string, toAddr string) (string, error) {
// Get the list of wallets
ourWalletId, err := getWalletId(walletName)
if err != nil {
return "", errors.Wrap(err, "couldn't get wallet id of wallet, quitting")
}
// get a wallet handle token to sign the transaction with
walletHandleToken, err := generateWalletToken(ourWalletId, password)
if err != nil {
return "", errors.Wrap(err, "error initializing wallet handle")
}
note := []byte("cool")
txid, err := sendTx(fromAddr, toAddr, amount, note, walletHandleToken, password)
if err != nil {
return "", errors.Wrap(err, "couldn't send tx")
}
log.Println("txid: ", txid)
return txid, nil
}
// CreateNewWalletAndAddress creates a new wallet and an address
func CreateNewWalletAndAddress(name string, password string) (string, error) {
var err error
walletID, err := CreateNewWallet(name, password)
if err != nil {
return "", errors.Wrap(err, "couldn't create new wallet id, quitting")
}
return GenerateNewAddress(walletID, name, password)
}
// GenerateNewAddress generates a new address associated with the given wallet
func GenerateNewAddress(walletID string, name string, password string) (string, error) {
var err error
walletHandleToken, err := generateWalletToken(walletID, password)
if err != nil {
return "", errors.Wrap(err, "failed to create new wallet handler")
}
// Generate a new address from the wallet handle
address, err := generateAddress(walletHandleToken)
if err != nil {
return "", errors.Wrap(err, "failed to gneerate new address")
}
return address, nil
}
// GenerateBackup gets the seedphrase from the walletName for backup
func GenerateBackup(walletName string, password string) (string, error) {
// Get the list of wallets
ourWalletId, err := getWalletId(walletName)
if err != nil {
return "", errors.Wrap(err, "couldn't get wallet id of wallet, quitting")
}
// get a wallet handle token to sign the transaction with
walletHandleToken, err := generateWalletToken(ourWalletId, password)
if err != nil {
return "", errors.Wrap(err, "error initializing wallet handle")
}
// Get the backup phrase
resp, err := KmdClient.ExportMasterDerivationKey(walletHandleToken, password)
if err != nil {
return "", errors.Wrap(err, "error exporting backup phrase")
}
// This string should be kept in a safe place and not shared
backupPhrase, err := mnemonic.FromKey(resp.MasterDerivationKey[:])
if err != nil {
return "", errors.Wrap(err, "error getting backup phrase")
}
return backupPhrase, nil
}
// Wallet defines the algorand wallet strcuture
type Wallet struct {
WalletName string
WalletID string
}
// GenNewWallet generates a new algorand wallet
func GenNewWallet(walletName string, password string) (Wallet, error) {
var x Wallet
var err error
if len(walletName) > 16 {
return x, errors.New("wallet name too long, quitting")
}
x.WalletName = "algowl" + utils.GetRandomString(16-len(walletName))
x.WalletID, err = CreateNewWallet(x.WalletName, password)
if err != nil {
return x, errors.Wrap(err, "couldn't create new wallet id, quitting")
}
return x, nil
}