-
Notifications
You must be signed in to change notification settings - Fork 45
/
Copy pathsaga.ts
185 lines (159 loc) · 5.76 KB
/
saga.ts
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
import { hdkey } from '@oasisprotocol/client'
import { PayloadAction } from '@reduxjs/toolkit'
import { hex2uint, parseRpcBalance, publicKeyToAddress, shortPublicKey, uint2hex } from 'app/lib/helpers'
import { push } from 'connected-react-router'
import nacl from 'tweetnacl'
import { call, fork, put, select, take, takeEvery, takeLatest } from 'typed-redux-saga'
import { walletActions as actions, walletActions } from '.'
import { LedgerAccount } from '../ledger/types'
import { getOasisNic } from '../network/saga'
import { transactionActions } from '../transaction'
import { selectActiveWallet, selectAddress, selectWallets } from './selectors'
import { AddWalletPayload, Wallet, WalletType } from './types'
// Ensure a unique walletId per opened wallet
// Maybe we should switch to something like uuid later
let walletId = 0
/**
* Opened wallet saga
* Will later be used to sign arbitrary messagegs
*/
export function* walletSaga() {}
export function* rootWalletSaga() {
// Wait for an openWallet action (Mnemonic, Private Key, Ledger) and add them if requested
yield* takeEvery(walletActions.openWalletFromPrivateKey, openWalletFromPrivateKey)
yield* takeEvery(walletActions.openWalletFromMnemonic, openWalletFromMnemonic)
yield* takeEvery(walletActions.openWalletsFromLedger, openWalletsFromLedger)
yield* takeEvery(walletActions.addWallet, addWallet)
// Reload balance of matching wallets when a transaction occurs
yield* fork(reloadBalanceOnTransaction)
yield* takeEvery(walletActions.fetchWallet, loadWallet)
// Allow switching between wallets
yield* takeLatest(walletActions.selectWallet, selectWallet)
// Start the wallet saga in parallel
yield* fork(walletSaga)
// Listen to closeWallet
yield* takeEvery(actions.closeWallet, closeWallet)
}
export function* getBalance(publicKey: Uint8Array) {
const nic = yield* call(getOasisNic)
const short = yield* call(shortPublicKey, publicKey)
const account = yield* call([nic, nic.stakingAccount], {
height: 0,
owner: short,
})
return parseRpcBalance(account)
}
function* getWalletByAddress(address: string) {
const wallets = yield* select(selectWallets)
const wallet = Object.values(wallets).find(w => w.address === address)
return wallet ? wallet : undefined
}
/**
* Take multiple ledger accounts that we want to open
*/
export function* openWalletsFromLedger({ payload: accounts }: PayloadAction<LedgerAccount[]>) {
for (const [index, account] of accounts.entries()) {
yield* put(
actions.addWallet({
id: walletId++,
address: account.address,
publicKey: account.publicKey,
type: WalletType.Ledger,
balance: account.balance,
path: account.path,
selectImmediately: index === 0,
}),
)
}
}
export function* openWalletFromPrivateKey({ payload: privateKey }: PayloadAction<string>) {
const type = WalletType.PrivateKey
const publicKeyBytes = nacl.sign.keyPair.fromSecretKey(hex2uint(privateKey)).publicKey
const walletAddress = yield* call(publicKeyToAddress, publicKeyBytes)
const publicKey = uint2hex(publicKeyBytes)
const balance = yield* call(getBalance, publicKeyBytes)
yield* put(
actions.addWallet({
id: walletId++,
address: walletAddress,
publicKey,
privateKey,
type: type!,
balance,
selectImmediately: true,
}),
)
}
export function* openWalletFromMnemonic({ payload: mnemonic }: PayloadAction<string>) {
const signer = yield* call(hdkey.HDKey.getAccountSigner, mnemonic)
const privateKey = uint2hex(signer.secretKey)
const type = WalletType.Mnemonic
const publicKeyBytes = signer.publicKey
const publicKey = uint2hex(publicKeyBytes)
const walletAddress = yield* call(publicKeyToAddress, publicKeyBytes!)
const balance = yield* call(getBalance, publicKeyBytes)
yield* put(
actions.addWallet({
id: walletId++,
address: walletAddress,
publicKey,
privateKey,
type: type!,
balance,
selectImmediately: true,
}),
)
}
/**
* Adds a wallet to the existing wallets
* If the wallet exists already, do nothingg
* If it has "selectImmediately", we select it immediately
*/
export function* addWallet({ payload: newWallet }: PayloadAction<AddWalletPayload>) {
const existingWallet = yield* call(getWalletByAddress, newWallet.address)
if (!existingWallet) {
yield* put(actions.walletOpened(newWallet))
}
const walletId = existingWallet ? existingWallet.id : newWallet.id
if (newWallet.selectImmediately) {
yield* put(walletActions.selectWallet(walletId))
}
}
export function* closeWallet() {
yield* put(actions.walletClosed())
}
export function* selectWallet({ payload: index }: PayloadAction<number>) {
yield* put(walletActions.walletSelected(index))
const newWallet = yield* select(selectActiveWallet)
yield* put(push(`/account/${newWallet?.address}`))
}
function* loadWallet(action: PayloadAction<Wallet>) {
const wallet = action.payload
const balance = yield* call(getBalance, hex2uint(wallet.publicKey))
yield* put(
walletActions.updateBalance({
walletId: wallet.id,
balance,
}),
)
}
/**
* When a transaction is done, and it is related to the account we currently have in state
* refresh the data.
*/
function* reloadBalanceOnTransaction() {
while (true) {
const { payload } = yield* take(transactionActions.transactionSent)
if (payload.type !== 'transfer') {
// @TODO: This should be done for other types of transactions too
return
}
const from = yield* select(selectAddress)
const to = payload.to
const wallets = yield* select(selectWallets)
const matchingWallets = Object.values(wallets).filter(w => w.address === to || w.address === from)
for (const wallet of matchingWallets) {
yield* put(walletActions.fetchWallet(wallet))
}
}
}