public async Task<List<Blake256Hash>> PurchaseTicketsAsync(Account account, Amount spendLimit, int reqConfs, Address ticketAddress, uint number, Address poolAddress, double poolFees, uint expiry, Amount txFee, Amount ticketFee, string passphrase) { var ticketAddressStr = ""; if (ticketAddress != null) { ticketAddressStr = ticketAddress.ToString(); } var poolAddressStr = ""; if (poolAddress != null) { poolAddressStr = poolAddress.ToString(); } if (poolAddressStr == "") { poolFees = 0.0; } var client = new WalletService.WalletServiceClient(_channel); var request = new PurchaseTicketsRequest { Passphrase = ByteString.CopyFromUtf8(passphrase), Account = account.AccountNumber, SpendLimit = spendLimit, RequiredConfirmations = (uint)reqConfs, TicketAddress = ticketAddressStr, NumTickets = number, PoolAddress = poolAddressStr, PoolFees = poolFees, Expiry = expiry, TxFee = txFee, TicketFee = ticketFee, }; var response = await client.PurchaseTicketsAsync(request, cancellationToken: _tokenSource.Token); return response.TicketHashes.Select(h => new Blake256Hash(h.ToByteArray())).ToList(); }
public async Task<StakeDifficultyProperties> StakeDifficultyAsync() { var client = new WalletService.WalletServiceClient(_channel); var request = new TicketPriceRequest { }; var response = await client.TicketPriceAsync(request, cancellationToken: _tokenSource.Token); var properties = new StakeDifficultyProperties { HeightForTicketPrice = response.Height, NextTicketPrice = response.TicketPrice, }; return properties; }
public async Task<Tuple<List<UnspentOutput>, Amount>> FundTransactionAsync( Account account, Amount targetAmount, int requiredConfirmations) { var client = new WalletService.WalletServiceClient(_channel); var request = new FundTransactionRequest { Account = account.AccountNumber, TargetAmount = targetAmount, RequiredConfirmations = requiredConfirmations, IncludeImmatureCoinbases = false, IncludeChangeScript = false, }; var response = await client.FundTransactionAsync(request, cancellationToken: _tokenSource.Token); var outputs = response.SelectedOutputs.Select(MarshalUnspentOutput).ToList(); var total = (Amount)response.TotalAmount; return Tuple.Create(outputs, total); }
public async Task<Tuple<Transaction, bool>> SignTransactionAsync(string passphrase, Transaction tx) { var client = new WalletService.WalletServiceClient(_channel); var request = new SignTransactionRequest { Passphrase = ByteString.CopyFromUtf8(passphrase), SerializedTransaction = ByteString.CopyFrom(tx.Serialize()), }; var response = await client.SignTransactionAsync(request, cancellationToken: _tokenSource.Token); var signedTransaction = Transaction.Deserialize(response.Transaction.ToByteArray()); var complete = response.UnsignedInputIndexes.Count == 0; return Tuple.Create(signedTransaction, complete); }
public async Task<NetworkResponse> NetworkAsync() { var client = new WalletService.WalletServiceClient(_channel); return await client.NetworkAsync(new NetworkRequest(), cancellationToken: _tokenSource.Token); }
public async Task<Blake256Hash> PublishTransactionAsync(byte[] signedTransaction) { if (signedTransaction == null) throw new ArgumentNullException(nameof(signedTransaction)); var client = new WalletService.WalletServiceClient(_channel); var request = new PublishTransactionRequest { SignedTransaction = ByteString.CopyFrom(signedTransaction), }; var response = await client.PublishTransactionAsync(request, cancellationToken: _tokenSource.Token); return new Blake256Hash(response.TransactionHash.ToByteArray()); }
public async Task ImportPrivateKeyAsync(Account account, string privateKeyWif, bool rescan, string passphrase) { if (privateKeyWif == null) throw new ArgumentNullException(nameof(privateKeyWif)); if (passphrase == null) throw new ArgumentNullException(nameof(passphrase)); var client = new WalletService.WalletServiceClient(_channel); var request = new ImportPrivateKeyRequest { Account = account.AccountNumber, PrivateKeyWif = privateKeyWif, Rescan = rescan, Passphrase = ByteString.CopyFromUtf8(passphrase), // Poorly named: this outputs UTF8 from a UTF16 System.String }; await client.ImportPrivateKeyAsync(request, cancellationToken: _tokenSource.Token); }
public async Task RescanFromBlockHeightAsync(int beginHeight, Action<int> progressCallback = null) { var client = new WalletService.WalletServiceClient(_channel); var request = new RescanRequest { BeginHeight = beginHeight }; var responseServer = client.Rescan(request, cancellationToken: _tokenSource.Token); while (await responseServer.ResponseStream.MoveNext()) { progressCallback?.Invoke(responseServer.ResponseStream.Current.RescannedThrough); } }
public async Task<string> NextInternalAddressAsync(Account account) { var client = new WalletService.WalletServiceClient(_channel); var request = new NextAddressRequest { Account = account.AccountNumber, Kind = NextAddressRequest.Types.Kind.Bip0044Internal, }; var resp = await client.NextAddressAsync(request, cancellationToken: _tokenSource.Token); return resp.Address; }
public async Task<Account> NextAccountAsync(string passphrase, string accountName) { if (passphrase == null) throw new ArgumentNullException(nameof(passphrase)); if (accountName == null) throw new ArgumentNullException(nameof(accountName)); var client = new WalletService.WalletServiceClient(_channel); var request = new NextAccountRequest { Passphrase = ByteString.CopyFromUtf8(passphrase), AccountName = accountName, }; var resp = await client.NextAccountAsync(request, cancellationToken: _tokenSource.Token); return new Account(resp.AccountNumber); }
/// <summary> /// Queries the RPC server for the next external BIP0044 address for an account /// </summary> /// <param name="account">Account to create address for</param> /// <returns>Tuple containing the address and pubkey address strings</returns> public async Task<TupleValue<string, string>> NextExternalAddressAsync(Account account) { var client = new WalletService.WalletServiceClient(_channel); var request = new NextAddressRequest { Account = account.AccountNumber, Kind = NextAddressRequest.Types.Kind.Bip0044External, }; var resp = await client.NextAddressAsync(request, cancellationToken: _tokenSource.Token); return TupleValue.Create(resp.Address, resp.PublicKey); }
public async Task<TransactionSet> GetTransactionsAsync(int minRecentTransactions, int minRecentBlocks) { var client = new WalletService.WalletServiceClient(_channel); var request = new GetTransactionsRequest { // TODO: include these. With these uncommented, all transactions are loaded. //StartingBlockHeight = -minRecentBlocks, //MinimumRecentTransactions = minRecentTransactions }; var responseStream = client.GetTransactions(request, cancellationToken: _tokenSource.Token).ResponseStream; var minedTransactions = new List<Block>(); Dictionary<Blake256Hash, WalletTransaction> unminedTransactions = null; while (await responseStream.MoveNext()) { var msg = responseStream.Current; if (msg.MinedTransactions != null) minedTransactions.Add(MarshalBlock(msg.MinedTransactions)); else unminedTransactions = msg.UnminedTransactions.Select(MarshalWalletTransaction).ToDictionary(tx => tx.Hash); } return new TransactionSet(minedTransactions, unminedTransactions ?? new Dictionary<Blake256Hash, WalletTransaction>()); }
public async Task<AccountsResponse> AccountsAsync() { var client = new WalletService.WalletServiceClient(_channel); return await client.AccountsAsync(new AccountsRequest(), cancellationToken: _tokenSource.Token); }
public async Task<StakeInfoProperties> StakeInfoAsync() { var client = new WalletService.WalletServiceClient(_channel); var request = new StakeInfoRequest { }; var response = await client.StakeInfoAsync(request, cancellationToken: _tokenSource.Token); var properties = new StakeInfoProperties { PoolSize = response.PoolSize, AllMempoolTickets = response.AllMempoolTix, OwnMempoolTickets = response.OwnMempoolTix, Immature = response.Immature, Live = response.Live, Voted = response.Voted, Missed = response.Missed, Revoked = response.Revoked, Expired = response.Expired, TotalSubsidy = response.TotalSubsidy, }; return properties; }
public async Task ImportScriptAsync(byte[] scriptBytes, bool rescan, int scanFrom, string passphrase) { if (scriptBytes == null) throw new ArgumentNullException(nameof(scriptBytes)); if (passphrase == null) throw new ArgumentNullException(nameof(passphrase)); var client = new WalletService.WalletServiceClient(_channel); var request = new ImportScriptRequest { Script = ByteString.CopyFrom(scriptBytes), Rescan = rescan, Passphrase = ByteString.CopyFromUtf8(passphrase), // Poorly named: this outputs UTF8 from a UTF16 System.String ScanFrom = scanFrom, }; await client.ImportScriptAsync(request, cancellationToken: _tokenSource.Token); }
public async Task LoadActiveDataFiltersAsync() { var client = new WalletService.WalletServiceClient(_channel); var request = new LoadActiveDataFiltersRequest(); await client.LoadActiveDataFiltersAsync(request, cancellationToken: _tokenSource.Token); }
public async Task RenameAccountAsync(Account account, string newAccountName) { if (newAccountName == null) throw new ArgumentNullException(nameof(newAccountName)); var client = new WalletService.WalletServiceClient(_channel); var request = new RenameAccountRequest { AccountNumber = account.AccountNumber, NewName = newAccountName, }; await client.RenameAccountAsync(request, cancellationToken: _tokenSource.Token); }
/// <summary> /// Begins synchronization of the client with the remote wallet process. /// A delegate must be passed to be connected to the wallet's ChangesProcessed event to avoid /// a race where additional notifications are processed in the sync task before the caller /// can connect the event. The caller is responsible for disconnecting the delegate from the /// event handler when finished. /// </summary> /// <param name="walletEventHandler">Event handler for changes to wallet as new transactions are processed.</param> /// <returns>The synced Wallet and the Task that is keeping the wallet in sync.</returns> public async Task<Tuple<Mutex<Wallet>, Task>> Synchronize(EventHandler<Wallet.ChangesProcessedEventArgs> walletEventHandler) { if (walletEventHandler == null) throw new ArgumentNullException(nameof(walletEventHandler)); TransactionNotifications notifications; Task notificationsTask; // TODO: Initialization requests need timeouts. // Loop until synchronization did not race on a reorg. while (true) { // Begin receiving notifications for new and removed wallet transactions before // old transactions are downloaded. Any received notifications are saved to // a buffer and are processed after GetAllTransactionsAsync is awaited. notifications = new TransactionNotifications(_channel, _tokenSource.Token); notificationsTask = notifications.ListenAndBuffer(); var networkTask = NetworkAsync(); var accountsTask = AccountsAsync(); var networkResp = await networkTask; var activeBlockChain = BlockChainIdentity.FromNetworkBits(networkResp.ActiveNetwork); var txSetTask = GetTransactionsAsync(Wallet.MinRecentTransactions, Wallet.NumRecentBlocks(activeBlockChain)); var txSet = await txSetTask; var rpcAccounts = await accountsTask; var lastAccountBlockHeight = rpcAccounts.CurrentBlockHeight; var lastAccountBlockHash = new Blake256Hash(rpcAccounts.CurrentBlockHash.ToByteArray()); var lastTxBlock = txSet.MinedTransactions.LastOrDefault(); if (lastTxBlock != null) { var lastTxBlockHeight = lastTxBlock.Height; var lastTxBlockHash = lastTxBlock.Hash; if (lastTxBlockHeight > lastAccountBlockHeight || (lastTxBlockHeight == lastAccountBlockHeight && !lastTxBlockHash.Equals(lastAccountBlockHash))) { _tokenSource.Cancel(); continue; } } // Read all received notifications thus far and determine if synchronization raced // on a chain reorganize. Try again if so. IList<WalletChanges> transactionNotifications; if (notifications.Buffer.TryReceiveAll(out transactionNotifications)) { if (transactionNotifications.Any(r => r.DetachedBlocks.Count != 0)) { _tokenSource.Cancel(); continue; } // Skip all attached block notifications that are in blocks lower than the // block accounts notification. If blocks exist at or past that height, // the first's hash should equal that from the accounts notification. // // This ensures that both notifications contain data that is valid at this // block. var remainingNotifications = transactionNotifications .SelectMany(r => r.AttachedBlocks) .SkipWhile(b => b.Height < lastAccountBlockHeight) .ToList(); if (remainingNotifications.Count != 0) { if (!remainingNotifications[0].Hash.Equals(lastAccountBlockHash)) { _tokenSource.Cancel(); continue; } } // TODO: Merge remaining notifications with the transaction set. // For now, be lazy and start the whole sync over. if (remainingNotifications.Count > 1) { _tokenSource.Cancel(); continue; } } var accounts = rpcAccounts.Accounts.ToDictionary( a => new Account(a.AccountNumber), a => new AccountProperties { AccountName = a.AccountName, TotalBalance = a.TotalBalance, // TODO: uncomment when added to protospec and implemented by wallet. //ImmatureCoinbaseReward = a.ImmatureBalance, ExternalKeyCount = a.ExternalKeyCount, InternalKeyCount = a.InternalKeyCount, ImportedKeyCount = a.ImportedKeyCount, }); Func<AccountsResponse.Types.Account, AccountProperties> createProperties = a => new AccountProperties { AccountName = a.AccountName, TotalBalance = a.TotalBalance, // TODO: uncomment when added to protospec and implemented by wallet. //ImmatureCoinbaseReward = a.ImmatureBalance, ExternalKeyCount = a.ExternalKeyCount, InternalKeyCount = a.InternalKeyCount, ImportedKeyCount = a.ImportedKeyCount, }; // This assumes that all but the last account listed in the RPC response are // BIP0032 accounts, with the same account number as their List index. var bip0032Accounts = rpcAccounts.Accounts.Take(rpcAccounts.Accounts.Count - 1).Select(createProperties).ToList(); var importedAccount = createProperties(rpcAccounts.Accounts.Last()); var chainTip = new BlockIdentity(lastAccountBlockHash, lastAccountBlockHeight); var wallet = new Wallet(activeBlockChain, txSet, bip0032Accounts, importedAccount, chainTip); wallet.ChangesProcessed += walletEventHandler; var walletMutex = new Mutex<Wallet>(wallet); var syncTask = Task.Run(async () => { var client = new WalletService.WalletServiceClient(_channel); var accountsStream = client.AccountNotifications(new AccountNotificationsRequest(), cancellationToken: _tokenSource.Token); var accountChangesTask = accountsStream.ResponseStream.MoveNext(); var txChangesTask = notifications.Buffer.OutputAvailableAsync(); while (true) { var completedTask = await Task.WhenAny(accountChangesTask, txChangesTask); if (!await completedTask) { break; } using (var walletGuard = await walletMutex.LockAsync()) { var w = walletGuard.Instance; if (completedTask == accountChangesTask) { var accountProperties = accountsStream.ResponseStream.Current; var account = new Account(accountProperties.AccountNumber); w.UpdateAccountProperties(account, accountProperties.AccountName, accountProperties.ExternalKeyCount, accountProperties.InternalKeyCount, accountProperties.ImportedKeyCount); accountChangesTask = accountsStream.ResponseStream.MoveNext(); } else if (completedTask == txChangesTask) { var changes = notifications.Buffer.Receive(); w.ApplyTransactionChanges(changes); txChangesTask = notifications.Buffer.OutputAvailableAsync(); } } } await notificationsTask; }); return Tuple.Create(walletMutex, syncTask); } }
public TransactionNotifications(Channel channel, CancellationToken cancelToken) { _client = new WalletService.WalletServiceClient(channel); _buffer = new BufferBlock<WalletChanges>(); _cancelToken = cancelToken; }