private async Task SaveCommandActionAsync() { try { var nextAddressResponse = await App.Current.Synchronizer.WalletRpcClient.NextExternalAddressAsync(SelectedVotingAccount.Account); var pubKeyAddress = nextAddressResponse.Item2; var bestApiVersion = PoolApiClient.BestSupportedApiVersion(SelectedStakePool.SupportedApiVersions); var client = new PoolApiClient(bestApiVersion, SelectedStakePool.Uri, SelectedPoolApiKey, _httpClient); // Sumbit the pubkey. In the current stakepool api version only a single pubkey can // be submitted per user, so if one was already submitted, simply ignore the error. try { await client.CreateVotingAddressAsync(pubKeyAddress); } catch (PoolApiResponseException ex) when(ex.Code == StatusCode.AlreadyExists) { } var poolInfo = await client.GetPurchaseInfoAsync(); var configEntry = new StakePoolUserConfig.Entry { ApiKey = SelectedPoolApiKey, Host = SelectedStakePool.Uri.Host, MultisigVoteScript = poolInfo.RedeemScriptHex, VotingAccount = SelectedVotingAccount.Account.AccountNumber, }; _configuredPools[configEntry.Host] = configEntry; var config = new StakePoolUserConfig { Entries = _configuredPools.Values.ToArray() }; await WriteConfig(config, _configPath); SelectedConfiguredPool = configEntry; _saveCommand.Executable = false; NeedsSaving = false; } catch (Exception ex) { MessageBox.Show(ex.Message, "Error communicating with stake pool"); } }
private Task UpdateStakepoolVotePreferences() { var voteBits = CalculateVoteBits(); var updateTasks = ConfiguredStakePools.OfType <StakePoolSelection>() .Select(sp => { var bestApiVersion = PoolApiClient.BestSupportedApiVersion(sp.PoolInfo.SupportedApiVersions); return(TupleValue.Create(sp, bestApiVersion)); }) .Where(t => t.Item2 >= 2) .Select(t => { var sp = t.Item1; var bestApiVersion = t.Item2; var client = new PoolApiClient(bestApiVersion, sp.PoolInfo.Uri, sp.ApiToken, _httpClient); return(client.SetVoteBitsAsync(voteBits)); }); return(Task.WhenAll(updateTasks)); }
private async Task <bool> PurchaseTicketsWithPassphrase(string passphrase) { var synchronizer = App.Current.Synchronizer; var walletClient = synchronizer.WalletRpcClient; var account = SelectedSourceAccount.Account; var spendLimit = synchronizer.TicketPrice; int requiredConfirms = 2; // TODO allow user to set uint expiryHeight = _expiry + (uint)synchronizer.SyncedBlockHeight; Amount splitFeeLocal = _splitFee; Amount ticketFeeLocal = _ticketFee; Address votingAddress; Address poolFeeAddress; decimal poolFees; if (SelectedStakePool is StakePoolSelection) { var selection = (StakePoolSelection)SelectedStakePool; var bestApiVersion = PoolApiClient.BestSupportedApiVersion(selection.PoolInfo.SupportedApiVersions); var client = new PoolApiClient(bestApiVersion, selection.PoolInfo.Uri, selection.ApiToken, _httpClient); var purchaseInfo = await client.GetPurchaseInfoAsync(); // Import the 1-of-2 multisig vote script. This has to be done here rather than from // the pool management dialog since importing requires an unlocked wallet and we are // unable to open nested dialog windows to prompt for a passphrase. // // This does not need to re-import the script every time ticktes are purchased using // a pool, but for code simplicity it is done this way. Also, in future versions of the // API when it may be possible to generate a new reward address for each ticket, we will // need to import these scripts ever time. await walletClient.ImportScriptAsync(selection.MultisigVoteScript, false, 0, passphrase); votingAddress = Address.Decode(purchaseInfo.VotingAddress); poolFeeAddress = Address.Decode(purchaseInfo.FeeAddress); poolFees = purchaseInfo.Fee; } else { votingAddress = _votingAddress; poolFeeAddress = _poolFeeAddress; poolFees = _poolFees / 100m; } List <Blake256Hash> purchaseResponse; try { purchaseResponse = await walletClient.PurchaseTicketsAsync(account, spendLimit, requiredConfirms, votingAddress, _ticketsToPurchase, poolFeeAddress, poolFees, expiryHeight, _splitFee, _ticketFee, passphrase); } catch (Grpc.Core.RpcException ex) { MessageBox.Show(ex.Status.Detail, "Unexpected error"); return(false); } ResponseString = "Success! Ticket hashes:\n" + string.Join("\n", purchaseResponse); return(true); }
private async Task <bool> PurchaseTicketsWithPassphrase(string passphrase) { var synchronizer = App.Current.Synchronizer; var walletClient = synchronizer.WalletRpcClient; var account = SelectedSourceAccount.Account; var spendLimit = synchronizer.TicketPrice; int requiredConfirms = 2; // TODO allow user to set uint expiryHeight = _expiry + (uint)synchronizer.SyncedBlockHeight; Address votingAddress; Address poolFeeAddress; decimal poolFees; if (SelectedStakePool is StakePoolSelection) { var selection = (StakePoolSelection)SelectedStakePool; var bestApiVersion = PoolApiClient.BestSupportedApiVersion(selection.PoolInfo.SupportedApiVersions); var client = new PoolApiClient(bestApiVersion, selection.PoolInfo.Uri, selection.ApiToken, _httpClient); var purchaseInfo = await client.GetPurchaseInfoAsync(); // Import the 1-of-2 multisig vote script. This has to be done here rather than from // the pool management dialog since importing requires an unlocked wallet and we are // unable to open nested dialog windows to prompt for a passphrase. // // This does not need to re-import the script every time ticktes are purchased using // a pool, but for code simplicity it is done this way. Also, in future versions of the // API when it may be possible to generate a new reward address for each ticket, we will // need to import these scripts ever time. string fraudReason = null; try { var importResp = await walletClient.ImportScriptAsync(selection.MultisigVoteScript, false, 0, passphrase, requireRedeemable : true); if (importResp.Item1 != purchaseInfo.VotingAddress) { fraudReason = "The stakepool voting address is not the P2SH address of the voting redeem script."; } } catch (RpcException ex) when(ex.Status.StatusCode == Grpc.Core.StatusCode.FailedPrecondition) { fraudReason = "The stakepool 1-of-2 voting script is not redeemable by your wallet."; } if (fraudReason != null) { MessageBox.Show(fraudReason + "\n\n" + $"Please report this to the stakepool administrator and the {BlockChain.CurrencyName} developers.", "Warning"); return(false); } votingAddress = Address.Decode(purchaseInfo.VotingAddress); poolFeeAddress = Address.Decode(purchaseInfo.FeeAddress); poolFees = purchaseInfo.Fee; } else { votingAddress = _votingAddress; poolFeeAddress = _poolFeeAddress; poolFees = _poolFees / 100m; } List <Blake256Hash> purchaseResponse; try { purchaseResponse = await walletClient.PurchaseTicketsAsync(account, spendLimit, requiredConfirms, votingAddress, _ticketsToPurchase, poolFeeAddress, poolFees, expiryHeight, TransactionFees.DefaultFeePerKb, _ticketFee, passphrase); } catch (Grpc.Core.RpcException ex) { MessageBox.Show(ex.Status.Detail, "Unexpected error"); return(false); } ResponseString = "Success! Ticket hashes:\n" + string.Join("\n", purchaseResponse); return(true); }