1import logger from '../logger' 2import * as TeamBuildingGen from '../actions/team-building-gen' 3import * as Constants from '../constants/wallets' 4import * as Container from '../util/container' 5import * as Types from '../constants/types/wallets' 6import * as WalletsGen from '../actions/wallets-gen' 7import HiddenString from '../util/hidden-string' 8import {editTeambuildingDraft} from './team-building' 9import {teamBuilderReducerCreator} from '../team-building/reducer-helper' 10import shallowEqual from 'shallowequal' 11import {mapEqual} from '../util/map' 12 13const initialState: Types.State = Constants.makeState() 14 15const updateAssetMap = ( 16 assetMap: Map<Types.AssetID, Types.AssetDescription>, 17 assets: Array<Types.AssetDescription> 18) => 19 assets.forEach(asset => { 20 const key = Types.assetDescriptionToAssetID(asset) 21 const oldAsset = assetMap.get(key) 22 if (!shallowEqual(asset, oldAsset)) { 23 assetMap.set(key, asset) 24 } 25 }) 26 27type Actions = WalletsGen.Actions | TeamBuildingGen.Actions 28export default Container.makeReducer<Actions, Types.State>(initialState, { 29 [WalletsGen.resetStore]: draftState => { 30 return {...initialState, staticConfig: draftState.staticConfig} as Types.State 31 }, 32 [WalletsGen.didSetAccountAsDefault]: (draftState, action) => { 33 draftState.accountMap = new Map(action.payload.accounts.map(account => [account.accountID, account])) 34 }, 35 [WalletsGen.accountsReceived]: (draftState, action) => { 36 draftState.accountMap = new Map(action.payload.accounts.map(account => [account.accountID, account])) 37 }, 38 [WalletsGen.changedAccountName]: (draftState, action) => { 39 const {account} = action.payload 40 // accept the updated account if we've loaded it already 41 // this is because we get the sort order from the full accounts load, 42 // and can't figure it out from these notifications alone. 43 if (account) { 44 // } && state.accountMap.get(account.accountID)) { 45 const {accountID} = account 46 const old = draftState.accountMap.get(accountID) 47 if (old) { 48 draftState.accountMap.set(accountID, {...old, ...account}) 49 } 50 } 51 }, 52 [WalletsGen.accountUpdateReceived]: (draftState, action) => { 53 const {account} = action.payload 54 // accept the updated account if we've loaded it already 55 // this is because we get the sort order from the full accounts load, 56 // and can't figure it out from these notifications alone. 57 if (account) { 58 const {accountID} = account 59 const old = draftState.accountMap.get(accountID) 60 if (old) { 61 draftState.accountMap.set(accountID, {...old, ...account}) 62 } 63 } 64 }, 65 [WalletsGen.assetsReceived]: (draftState, action) => { 66 draftState.assetsMap.set(action.payload.accountID, action.payload.assets) 67 }, 68 [WalletsGen.buildPayment]: draftState => { 69 draftState.buildCounter++ 70 }, 71 [WalletsGen.builtPaymentReceived]: (draftState, action) => { 72 if (action.payload.forBuildCounter === draftState.buildCounter) { 73 draftState.builtPayment = { 74 ...draftState.builtPayment, 75 ...Constants.makeBuiltPayment(action.payload.build), 76 } 77 } 78 }, 79 [WalletsGen.builtRequestReceived]: (draftState, action) => { 80 if (action.payload.forBuildCounter === draftState.buildCounter) { 81 draftState.builtRequest = { 82 ...draftState.builtRequest, 83 ...Constants.makeBuiltRequest(action.payload.build), 84 } 85 } 86 }, 87 [WalletsGen.openSendRequestForm]: (draftState, action) => { 88 if (!draftState.acceptedDisclaimer) { 89 return 90 } 91 draftState.building = { 92 ...Constants.makeBuilding(), 93 amount: action.payload.amount || '', 94 currency: 95 action.payload.currency || // explicitly set 96 (draftState.lastSentXLM && 'XLM') || // lastSentXLM override 97 (action.payload.from && 98 Constants.getDisplayCurrencyInner(draftState as Types.State, action.payload.from).code) || // display currency of explicitly set 'from' account 99 Constants.getDefaultDisplayCurrencyInner(draftState as Types.State).code || // display currency of default account 100 '', // Empty string -> not loaded 101 from: action.payload.from || Types.noAccountID, 102 isRequest: !!action.payload.isRequest, 103 publicMemo: action.payload.publicMemo || new HiddenString(''), 104 recipientType: action.payload.recipientType || 'keybaseUser', 105 secretNote: action.payload.secretNote || new HiddenString(''), 106 to: action.payload.to || '', 107 } 108 draftState.builtPayment = Constants.makeBuiltPayment() 109 draftState.builtRequest = Constants.makeBuiltRequest() 110 draftState.sentPaymentError = '' 111 }, 112 [WalletsGen.abandonPayment]: draftState => { 113 draftState.building = Constants.makeBuilding() 114 }, 115 [WalletsGen.clearBuilding]: draftState => { 116 draftState.building = Constants.makeBuilding() 117 }, 118 [WalletsGen.clearBuiltPayment]: draftState => { 119 draftState.builtPayment = Constants.makeBuiltPayment() 120 }, 121 [WalletsGen.clearBuiltRequest]: draftState => { 122 draftState.builtRequest = Constants.makeBuiltRequest() 123 }, 124 [WalletsGen.externalPartnersReceived]: (draftState, action) => { 125 draftState.externalPartners = action.payload.externalPartners 126 }, 127 [WalletsGen.paymentDetailReceived]: (draftState, action) => { 128 const emptyMap: Map<Types.PaymentID, Types.Payment> = new Map() 129 const map = draftState.paymentsMap.get(action.payload.accountID) ?? emptyMap 130 131 const paymentDetail = action.payload.payment 132 map.set(paymentDetail.id, { 133 ...(map.get(paymentDetail.id) ?? Constants.makePayment()), 134 ...action.payload.payment, 135 }) 136 137 draftState.paymentsMap.set(action.payload.accountID, map) 138 }, 139 [WalletsGen.paymentsReceived]: (draftState, action) => { 140 const emptyMap: Map<Types.PaymentID, Types.Payment> = new Map() 141 const map = draftState.paymentsMap.get(action.payload.accountID) ?? emptyMap 142 143 const paymentResults = [...action.payload.payments, ...action.payload.pending] 144 paymentResults.forEach(paymentResult => { 145 map.set(paymentResult.id, { 146 ...(map.get(paymentResult.id) ?? Constants.makePayment()), 147 ...paymentResult, 148 }) 149 }) 150 draftState.loadPaymentsError = action.payload.error 151 draftState.paymentsMap.set(action.payload.accountID, map) 152 draftState.paymentCursorMap.set(action.payload.accountID, action.payload.paymentCursor) 153 draftState.paymentLoadingMoreMap.set(action.payload.accountID, false) 154 // allowClearOldestUnread dictates whether this action is allowed to delete the value of oldestUnread. 155 // GetPaymentsLocal can erroneously return an empty oldestUnread value when a non-latest page is requested 156 // and oldestUnread points into the latest page. 157 if ( 158 action.payload.allowClearOldestUnread || 159 (action.payload.oldestUnread || Types.noPaymentID) !== Types.noPaymentID 160 ) { 161 draftState.paymentOldestUnreadMap.set(action.payload.accountID, action.payload.oldestUnread) 162 } 163 }, 164 [WalletsGen.pendingPaymentsReceived]: (draftState, action) => { 165 const newPending = action.payload.pending.map(p => [p.id, Constants.makePayment(p)] as const) 166 const emptyMap: Map<Types.PaymentID, Types.Payment> = new Map() 167 const oldFiltered = [ 168 ...(draftState.paymentsMap.get(action.payload.accountID) ?? emptyMap).entries(), 169 ].filter(([_k, v]) => v.section !== 'pending') 170 const val = new Map([...oldFiltered, ...newPending]) 171 draftState.paymentsMap.set(action.payload.accountID, val) 172 }, 173 [WalletsGen.recentPaymentsReceived]: (draftState, action) => { 174 const newPayments = action.payload.payments.map(p => [p.id, Constants.makePayment(p)] as const) 175 const emptyMap: Map<Types.PaymentID, Types.Payment> = new Map() 176 const old = (draftState.paymentsMap.get(action.payload.accountID) ?? emptyMap).entries() 177 178 draftState.paymentsMap.set(action.payload.accountID, new Map([...old, ...newPayments])) 179 draftState.paymentCursorMap.set( 180 action.payload.accountID, 181 draftState.paymentCursorMap.get(action.payload.accountID) || action.payload.paymentCursor 182 ) 183 draftState.paymentOldestUnreadMap.set(action.payload.accountID, action.payload.oldestUnread) 184 }, 185 [WalletsGen.displayCurrenciesReceived]: (draftState, action) => { 186 draftState.currencies = action.payload.currencies 187 }, 188 [WalletsGen.displayCurrencyReceived]: (draftState, action) => { 189 const account = Constants.getAccountInner( 190 draftState as Types.State, 191 action.payload.accountID || Types.noAccountID 192 ) 193 if (account.accountID === Types.noAccountID) { 194 return 195 } 196 draftState.accountMap.set(account.accountID, {...account, displayCurrency: action.payload.currency}) 197 }, 198 [WalletsGen.reviewPayment]: draftState => { 199 draftState.builtPayment.reviewBanners = [] 200 draftState.reviewCounter++ 201 draftState.reviewLastSeqno = undefined 202 }, 203 [WalletsGen.reviewedPaymentReceived]: (draftState, action) => { 204 // paymentReviewed notifications can arrive out of order, so check their freshness. 205 const {bid, reviewID, seqno, banners, nextButton} = action.payload 206 const useable = 207 draftState.building.bid === bid && 208 draftState.reviewCounter === reviewID && 209 (draftState.reviewLastSeqno || 0) <= seqno 210 if (!useable) { 211 logger.info(`ignored stale reviewPaymentReceived`) 212 return 213 } 214 215 draftState.builtPayment.readyToSend = nextButton 216 draftState.builtPayment.reviewBanners = banners ?? null 217 draftState.reviewLastSeqno = seqno 218 }, 219 [WalletsGen.secretKeyReceived]: (draftState, action) => { 220 draftState.exportedSecretKey = action.payload.secretKey 221 draftState.exportedSecretKeyAccountID = draftState.selectedAccount 222 }, 223 [WalletsGen.secretKeySeen]: draftState => { 224 draftState.exportedSecretKey = new HiddenString('') 225 draftState.exportedSecretKeyAccountID = Types.noAccountID 226 }, 227 [WalletsGen.selectAccount]: (draftState, action) => { 228 if (!action.payload.accountID) { 229 logger.error('Selecting empty account ID') 230 } 231 draftState.exportedSecretKey = new HiddenString('') 232 const old = draftState.selectedAccount 233 draftState.selectedAccount = action.payload.accountID 234 // we clear the old selected payments and cursors 235 if (!old) { 236 return 237 } 238 239 draftState.paymentCursorMap.delete(old) 240 draftState.paymentsMap.delete(old) 241 }, 242 [WalletsGen.setBuildingAmount]: (draftState, action) => { 243 draftState.building.amount = action.payload.amount 244 draftState.builtPayment.amountErrMsg = '' 245 draftState.builtPayment.worthDescription = '' 246 draftState.builtPayment.worthInfo = '' 247 draftState.builtRequest.amountErrMsg = '' 248 draftState.builtRequest.worthDescription = '' 249 draftState.builtRequest.worthInfo = '' 250 }, 251 [WalletsGen.setBuildingCurrency]: (draftState, action) => { 252 draftState.building.currency = action.payload.currency 253 draftState.builtPayment = Constants.makeBuiltPayment() 254 }, 255 [WalletsGen.setBuildingFrom]: (draftState, action) => { 256 draftState.building.from = action.payload.from 257 draftState.builtPayment = Constants.makeBuiltPayment() 258 }, 259 [WalletsGen.setBuildingIsRequest]: (draftState, action) => { 260 draftState.building.isRequest = action.payload.isRequest 261 draftState.builtPayment = Constants.makeBuiltPayment() 262 draftState.builtRequest = Constants.makeBuiltRequest() 263 }, 264 [WalletsGen.setBuildingPublicMemo]: (draftState, action) => { 265 draftState.building.publicMemo = action.payload.publicMemo 266 draftState.builtPayment.publicMemoErrMsg = new HiddenString('') 267 }, 268 [WalletsGen.setBuildingRecipientType]: (draftState, action) => { 269 draftState.building.recipientType = action.payload.recipientType 270 draftState.builtPayment = Constants.makeBuiltPayment() 271 }, 272 [WalletsGen.setBuildingSecretNote]: (draftState, action) => { 273 draftState.building.secretNote = action.payload.secretNote 274 draftState.builtPayment.secretNoteErrMsg = new HiddenString('') 275 draftState.builtRequest.secretNoteErrMsg = new HiddenString('') 276 }, 277 [WalletsGen.setBuildingTo]: (draftState, action) => { 278 draftState.building.to = action.payload.to 279 draftState.builtPayment.toErrMsg = '' 280 draftState.builtRequest.toErrMsg = '' 281 }, 282 [WalletsGen.clearBuildingAdvanced]: draftState => { 283 draftState.buildingAdvanced = Constants.emptyBuildingAdvanced 284 draftState.builtPaymentAdvanced = Constants.emptyBuiltPaymentAdvanced 285 }, 286 [WalletsGen.setBuildingAdvancedRecipient]: (draftState, action) => { 287 draftState.buildingAdvanced.recipient = action.payload.recipient 288 }, 289 [WalletsGen.setBuildingAdvancedRecipientAmount]: (draftState, action) => { 290 draftState.buildingAdvanced.recipientAmount = action.payload.recipientAmount 291 draftState.builtPaymentAdvanced = Constants.emptyBuiltPaymentAdvanced 292 }, 293 [WalletsGen.setBuildingAdvancedRecipientAsset]: (draftState, action) => { 294 draftState.buildingAdvanced.recipientAsset = action.payload.recipientAsset 295 draftState.builtPaymentAdvanced = Constants.emptyBuiltPaymentAdvanced 296 }, 297 [WalletsGen.setBuildingAdvancedRecipientType]: (draftState, action) => { 298 draftState.buildingAdvanced.recipientType = action.payload.recipientType 299 }, 300 [WalletsGen.setBuildingAdvancedPublicMemo]: (draftState, action) => { 301 draftState.buildingAdvanced.publicMemo = action.payload.publicMemo 302 // TODO PICNIC-142 clear error when we have that 303 }, 304 [WalletsGen.setBuildingAdvancedSenderAccountID]: (draftState, action) => { 305 draftState.buildingAdvanced.senderAccountID = action.payload.senderAccountID 306 }, 307 [WalletsGen.setBuildingAdvancedSenderAsset]: (draftState, action) => { 308 draftState.buildingAdvanced.senderAsset = action.payload.senderAsset 309 draftState.builtPaymentAdvanced = Constants.emptyBuiltPaymentAdvanced 310 }, 311 [WalletsGen.setBuildingAdvancedSecretNote]: (draftState, action) => { 312 draftState.buildingAdvanced.secretNote = action.payload.secretNote 313 // TODO PICNIC-142 clear error when we have that 314 }, 315 [WalletsGen.sendAssetChoicesReceived]: (draftState, action) => { 316 const {sendAssetChoices} = action.payload 317 draftState.building.sendAssetChoices = sendAssetChoices 318 }, 319 [WalletsGen.buildingPaymentIDReceived]: (draftState, action) => { 320 const {bid} = action.payload 321 draftState.building.bid = bid 322 }, 323 [WalletsGen.setLastSentXLM]: (draftState, action) => { 324 draftState.lastSentXLM = action.payload.lastSentXLM 325 }, 326 [WalletsGen.setReadyToReview]: (draftState, action) => { 327 draftState.builtPayment.readyToReview = action.payload.readyToReview 328 }, 329 [WalletsGen.validateAccountName]: (draftState, action) => { 330 draftState.accountName = action.payload.name 331 draftState.accountNameValidationState = 'waiting' 332 }, 333 [WalletsGen.validatedAccountName]: (draftState, action) => { 334 if (action.payload.name !== draftState.accountName) { 335 // this wasn't from the most recent call 336 return 337 } 338 draftState.accountName = '' 339 draftState.accountNameError = action.payload.error ? action.payload.error : '' 340 draftState.accountNameValidationState = action.payload.error ? 'error' : 'valid' 341 }, 342 [WalletsGen.validateSecretKey]: (draftState, action) => { 343 draftState.secretKey = action.payload.secretKey 344 draftState.secretKeyValidationState = 'waiting' 345 }, 346 [WalletsGen.validatedSecretKey]: (draftState, action) => { 347 if (action.payload.secretKey.stringValue() !== draftState.secretKey.stringValue()) { 348 // this wasn't from the most recent call 349 return 350 } 351 draftState.secretKey = new HiddenString('') 352 draftState.secretKeyError = action.payload.error ? action.payload.error : '' 353 draftState.secretKeyValidationState = action.payload.error ? 'error' : 'valid' 354 }, 355 [WalletsGen.changedTrustline]: (draftState, action) => { 356 draftState.changeTrustlineError = action.payload.error || '' 357 }, 358 [WalletsGen.clearErrors]: draftState => { 359 draftState.accountName = '' 360 draftState.accountNameError = '' 361 draftState.accountNameValidationState = 'none' 362 draftState.builtPayment.readyToSend = 'spinning' 363 draftState.changeTrustlineError = '' 364 draftState.createNewAccountError = '' 365 draftState.linkExistingAccountError = '' 366 draftState.secretKey = new HiddenString('') 367 draftState.secretKeyError = '' 368 draftState.secretKeyValidationState = 'none' 369 draftState.sentPaymentError = '' 370 }, 371 [WalletsGen.createdNewAccount]: (draftState, action) => { 372 if (action.payload.error) { 373 draftState.createNewAccountError = action.payload.error ?? '' 374 } else { 375 draftState.accountName = '' 376 draftState.accountNameError = '' 377 draftState.accountNameValidationState = 'none' 378 draftState.changeTrustlineError = '' 379 draftState.createNewAccountError = '' 380 draftState.linkExistingAccountError = '' 381 draftState.secretKey = new HiddenString('') 382 draftState.secretKeyError = '' 383 draftState.secretKeyValidationState = 'none' 384 draftState.selectedAccount = action.payload.accountID 385 } 386 }, 387 [WalletsGen.linkedExistingAccount]: (draftState, action) => { 388 if (action.payload.error) { 389 draftState.linkExistingAccountError = action.payload.error ?? '' 390 } else { 391 draftState.accountName = '' 392 draftState.accountNameError = '' 393 draftState.accountNameValidationState = 'none' 394 draftState.createNewAccountError = '' 395 draftState.linkExistingAccountError = '' 396 draftState.secretKey = new HiddenString('') 397 draftState.secretKeyError = '' 398 draftState.secretKeyValidationState = 'none' 399 draftState.selectedAccount = action.payload.accountID 400 } 401 }, 402 [WalletsGen.sentPaymentError]: (draftState, action) => { 403 draftState.sentPaymentError = action.payload.error 404 }, 405 [WalletsGen.loadMorePayments]: (draftState, action) => { 406 if (draftState.paymentCursorMap.get(action.payload.accountID)) { 407 draftState.paymentLoadingMoreMap.set(action.payload.accountID, true) 408 } 409 }, 410 [WalletsGen.badgesUpdated]: (draftState, action) => { 411 action.payload.accounts.forEach(({accountID, numUnread}) => 412 draftState.unreadPaymentsMap.set(accountID, numUnread) 413 ) 414 }, 415 [WalletsGen.walletDisclaimerReceived]: (draftState, action) => { 416 draftState.acceptedDisclaimer = action.payload.accepted 417 }, 418 [WalletsGen.acceptDisclaimer]: draftState => { 419 draftState.acceptingDisclaimerDelay = true 420 }, 421 [WalletsGen.resetAcceptingDisclaimer]: draftState => { 422 draftState.acceptingDisclaimerDelay = false 423 }, 424 [WalletsGen.loadedMobileOnlyMode]: (draftState, action) => { 425 draftState.mobileOnlyMap.set(action.payload.accountID, action.payload.enabled) 426 }, 427 [WalletsGen.validateSEP7Link]: draftState => { 428 // Clear out old state just in [ 429 draftState.sep7ConfirmError = '' 430 draftState.sep7ConfirmInfo = undefined 431 draftState.sep7ConfirmPath = Constants.emptyBuiltPaymentAdvanced 432 draftState.sep7ConfirmURI = '' 433 draftState.sep7SendError = '' 434 }, 435 [WalletsGen.setSEP7SendError]: (draftState, action) => { 436 draftState.sep7SendError = action.payload.error 437 }, 438 [WalletsGen.validateSEP7LinkError]: (draftState, action) => { 439 draftState.sep7ConfirmError = action.payload.error 440 }, 441 [WalletsGen.setSEP7Tx]: (draftState, action) => { 442 draftState.sep7ConfirmInfo = action.payload.tx 443 draftState.sep7ConfirmFromQR = action.payload.fromQR 444 draftState.sep7ConfirmURI = action.payload.confirmURI 445 }, 446 [WalletsGen.setTrustlineExpanded]: (draftState, action) => { 447 if (action.payload.expanded) { 448 draftState.trustline.expandedAssets.add(action.payload.assetID) 449 } else { 450 draftState.trustline.expandedAssets.delete(action.payload.assetID) 451 } 452 }, 453 [WalletsGen.setTrustlineAcceptedAssets]: (draftState, action) => { 454 const {accountID, limits} = action.payload 455 const accountAcceptedAssets = draftState.trustline.acceptedAssets.get(accountID) 456 if (!accountAcceptedAssets || !mapEqual(limits, accountAcceptedAssets)) { 457 draftState.trustline.acceptedAssets.set(accountID, limits) 458 } 459 updateAssetMap(draftState.trustline.assetMap, action.payload.assets) 460 }, 461 [WalletsGen.setTrustlineAcceptedAssetsByUsername]: (draftState, action) => { 462 const {username, limits, assets} = action.payload 463 const accountAcceptedAssets = draftState.trustline.acceptedAssetsByUsername.get(username) 464 if (!accountAcceptedAssets || !mapEqual(limits, accountAcceptedAssets)) { 465 draftState.trustline.acceptedAssetsByUsername.set(username, limits) 466 } 467 updateAssetMap(draftState.trustline.assetMap, assets) 468 }, 469 [WalletsGen.setTrustlinePopularAssets]: (draftState, action) => { 470 draftState.trustline.popularAssets = action.payload.assets.map(asset => 471 Types.assetDescriptionToAssetID(asset) 472 ) 473 updateAssetMap(draftState.trustline.assetMap, action.payload.assets) 474 draftState.trustline.totalAssetsCount = action.payload.totalCount 475 draftState.trustline.loaded = true 476 }, 477 [WalletsGen.setTrustlineSearchText]: (draftState, action) => { 478 if (!action.payload.text) { 479 draftState.trustline.searchingAssets = [] 480 } 481 }, 482 [WalletsGen.setTrustlineSearchResults]: (draftState, action) => { 483 draftState.trustline.searchingAssets = action.payload.assets.map(asset => 484 Types.assetDescriptionToAssetID(asset) 485 ) 486 updateAssetMap(draftState.trustline.assetMap, action.payload.assets) 487 }, 488 [WalletsGen.clearTrustlineSearchResults]: draftState => { 489 draftState.trustline.searchingAssets = undefined 490 }, 491 [WalletsGen.setBuiltPaymentAdvanced]: (draftState, action) => { 492 if (action.payload.forSEP7) { 493 draftState.sep7ConfirmPath = action.payload.builtPaymentAdvanced 494 } else { 495 draftState.builtPaymentAdvanced = action.payload.builtPaymentAdvanced 496 } 497 }, 498 [WalletsGen.staticConfigLoaded]: (draftState, action) => { 499 draftState.staticConfig = action.payload.staticConfig 500 }, 501 [WalletsGen.assetDeposit]: draftState => { 502 draftState.sep6Error = false 503 draftState.sep6Message = '' 504 }, 505 [WalletsGen.assetWithdraw]: draftState => { 506 draftState.sep6Error = false 507 draftState.sep6Message = '' 508 }, 509 [WalletsGen.setSEP6Message]: (draftState, action) => { 510 draftState.sep6Error = action.payload.error 511 draftState.sep6Message = action.payload.message 512 }, 513 ...teamBuilderReducerCreator<Types.State>( 514 (draftState: Container.Draft<Types.State>, action: TeamBuildingGen.Actions) => { 515 const val = editTeambuildingDraft('wallets', draftState.teamBuilding, action) 516 if (val !== undefined) { 517 draftState.teamBuilding = val 518 } 519 } 520 ), 521}) 522