-
Notifications
You must be signed in to change notification settings - Fork 77
/
wallet.go
214 lines (186 loc) · 4.92 KB
/
wallet.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
package wallet
import (
"encoding/json"
"errors"
"io"
"os"
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
"github.com/nspcc-dev/neo-go/pkg/encoding/address"
"github.com/nspcc-dev/neo-go/pkg/util"
"github.com/nspcc-dev/neo-go/pkg/vm"
)
const (
// The current version of neo-go wallet implementations.
walletVersion = "3.0"
)
// Wallet represents a NEO (NEP-2, NEP-6) compliant wallet.
type Wallet struct {
// Version of the wallet, used for later upgrades.
Version string `json:"version"`
// A list of accounts which describes the details of each account
// in the wallet.
Accounts []*Account `json:"accounts"`
Scrypt keys.ScryptParams `json:"scrypt"`
// Extra metadata can be used for storing arbitrary data.
// This field can be empty.
Extra Extra `json:"extra"`
// Path where the wallet file is located..
path string
// ReadWriter for reading and writing wallet data.
rw io.ReadWriter
}
// Extra stores imported token contracts.
type Extra struct {
// Tokens is a list of imported token contracts.
Tokens []*Token
}
// NewWallet creates a new NEO wallet at the given location.
func NewWallet(location string) (*Wallet, error) {
file, err := os.Create(location)
if err != nil {
return nil, err
}
return newWallet(file), nil
}
// NewWalletFromFile creates a Wallet from the given wallet file path
func NewWalletFromFile(path string) (*Wallet, error) {
file, err := os.OpenFile(path, os.O_RDWR, os.ModeAppend)
if err != nil {
return nil, err
}
wall := &Wallet{
rw: file,
path: file.Name(),
}
if err := json.NewDecoder(file).Decode(wall); err != nil {
return nil, err
}
return wall, nil
}
func newWallet(rw io.ReadWriter) *Wallet {
var path string
if f, ok := rw.(*os.File); ok {
path = f.Name()
}
return &Wallet{
Version: walletVersion,
Accounts: []*Account{},
Scrypt: keys.NEP2ScryptParams(),
rw: rw,
path: path,
}
}
// CreateAccount generates a new account for the end user and encrypts
// the private key with the given passphrase.
func (w *Wallet) CreateAccount(name, passphrase string) error {
acc, err := NewAccount()
if err != nil {
return err
}
acc.Label = name
if err := acc.Encrypt(passphrase); err != nil {
return err
}
w.AddAccount(acc)
return w.Save()
}
// AddAccount adds an existing Account to the wallet.
func (w *Wallet) AddAccount(acc *Account) {
w.Accounts = append(w.Accounts, acc)
}
// RemoveAccount removes an Account with the specified addr
// from the wallet.
func (w *Wallet) RemoveAccount(addr string) error {
for i, acc := range w.Accounts {
if acc.Address == addr {
copy(w.Accounts[i:], w.Accounts[i+1:])
w.Accounts = w.Accounts[:len(w.Accounts)-1]
return nil
}
}
return errors.New("account wasn't found")
}
// AddToken adds new token to a wallet.
func (w *Wallet) AddToken(tok *Token) {
w.Extra.Tokens = append(w.Extra.Tokens, tok)
}
// RemoveToken removes token with the specified hash from the wallet.
func (w *Wallet) RemoveToken(h util.Uint160) error {
for i, tok := range w.Extra.Tokens {
if tok.Hash.Equals(h) {
copy(w.Extra.Tokens[i:], w.Extra.Tokens[i+1:])
w.Extra.Tokens = w.Extra.Tokens[:len(w.Extra.Tokens)-1]
return nil
}
}
return errors.New("token wasn't found")
}
// Path returns the location of the wallet on the filesystem.
func (w *Wallet) Path() string {
return w.path
}
// Save saves the wallet data. It's the internal io.ReadWriter
// that is responsible for saving the data. This can
// be a buffer, file, etc..
func (w *Wallet) Save() error {
if err := w.rewind(); err != nil {
return err
}
return json.NewEncoder(w.rw).Encode(w)
}
// savePretty saves wallet in a beautiful JSON.
func (w *Wallet) savePretty() error {
if err := w.rewind(); err != nil {
return err
}
enc := json.NewEncoder(w.rw)
enc.SetIndent("", " ")
return enc.Encode(w)
}
func (w *Wallet) rewind() error {
if s, ok := w.rw.(io.Seeker); ok {
if _, err := s.Seek(0, 0); err != nil {
return err
}
}
return nil
}
// JSON outputs a pretty JSON representation of the wallet.
func (w *Wallet) JSON() ([]byte, error) {
return json.MarshalIndent(w, " ", " ")
}
// Close closes the internal rw if its an io.ReadCloser.
func (w *Wallet) Close() {
if rc, ok := w.rw.(io.ReadCloser); ok {
rc.Close()
}
}
// GetAccount returns account corresponding to the provided scripthash.
func (w *Wallet) GetAccount(h util.Uint160) *Account {
addr := address.Uint160ToString(h)
for _, acc := range w.Accounts {
if acc.Address == addr {
return acc
}
}
return nil
}
// GetChangeAddress returns the default address to send transaction's change to.
func (w *Wallet) GetChangeAddress() util.Uint160 {
var res util.Uint160
var acc *Account
for i := range w.Accounts {
if acc == nil || w.Accounts[i].Default {
if w.Accounts[i].Contract != nil && vm.IsSignatureContract(w.Accounts[i].Contract.Script) {
acc = w.Accounts[i]
if w.Accounts[i].Default {
break
}
}
}
}
if acc != nil {
res = acc.Contract.ScriptHash()
}
return res
}