/// <summary> /// Equality implementation. /// </summary> /// <param name="other"> /// The other <code>Signatory</code> object to compare. /// </param> /// <returns> /// True if public key layout and requirements are equivalent to the /// other <code>Signatory</code> object. /// </returns> public bool Equals(Signatory?other) { if (other is null) { return(false); } if (_type != other._type) { return(false); } switch (_type) { case Type.Ed25519: return(((Ed25519PrivateKeyParameters)_data).GetEncoded().SequenceEqual(((Ed25519PrivateKeyParameters)other._data).GetEncoded())); case Type.RSA3072: // Will need more work case Type.ECDSA384: // Will need more work return(Equals(_data, other._data)); case Type.List: var thisList = (Signatory[])_data; var otherList = (Signatory[])other._data; if (thisList.Length == otherList.Length) { for (int i = 0; i < thisList.Length; i++) { if (!thisList[i].Equals(otherList[i])) { return(false); } } return(true); } break; case Type.Callback: return(ReferenceEquals(_data, other._data)); case Type.Pending: var thisPending = (PendingParams)_data; var otherPending = (PendingParams)other._data; return(thisPending.Equals(otherPending)); } return(false); }
/// <summary> /// Internal implementation for Transfer Crypto. /// Returns either a receipt or record or throws /// an exception. /// </summary> private async Task <TResult> TransferImplementationAsync <TResult>(Address fromAddress, Address toAddress, long amount, Signatory?signatory, Action <IContext>?configure) where TResult : new() { fromAddress = RequireInputParameter.FromAddress(fromAddress); toAddress = RequireInputParameter.ToAddress(toAddress); amount = RequireInputParameter.Amount(amount); var transferList = new[] { (fromAddress, -amount), (toAddress, amount) };
/// <summary> /// Internal helper function implementing the file delete functionality. /// </summary> public async Task <TResult> SystemRestoreFileImplementationAsync <TResult>(Address fileToRestore, Signatory?signatory, Action <IContext>?configure = null) where TResult : new() { fileToRestore = RequireInputParameter.FileToRestore(fileToRestore); await using var context = CreateChildContext(configure); RequireInContext.Gateway(context); var payer = RequireInContext.Payer(context); var signatories = Transactions.GatherSignatories(context, signatory); var transactionId = Transactions.GetOrCreateTransactionID(context); var transactionBody = new TransactionBody(context, transactionId); transactionBody.SystemUndelete = new SystemUndeleteTransactionBody { FileID = new FileID(fileToRestore) }; var receipt = await transactionBody.SignAndExecuteWithRetryAsync(signatories, context); if (receipt.Status != ResponseCodeEnum.Success) { throw new TransactionException($"Unable to delete file, status: {receipt.Status}", transactionId.ToTxId(), (ResponseCode)receipt.Status); } var result = new TResult(); if (result is TransactionRecord rec) { var record = await GetTransactionRecordAsync(context, transactionId); record.FillProperties(rec); } else if (result is TransactionReceipt rcpt) { receipt.FillProperties(transactionId, rcpt); } return(result); }
/// <summary> /// Adds a signature to a pending transaction record. The Scheduled Transaction executes /// this signature completes the list of required signatures for execution. /// </summary> /// <param name="pending"> /// The identifier (Address/Schedule ID) of the pending transaction to sign. /// </param> /// <param name="signatory"> /// The signatory containing any additional private keys or callbacks /// to meet the key signing requirements for participants. /// </param> /// <param name="configure"> /// Optional callback method providing an opportunity to modify /// the execution configuration for just this method call. /// It is executed prior to submitting the request to the network. /// </param> /// <returns> /// A Record for the transaction indicating success. /// </returns> /// <exception cref="ArgumentOutOfRangeException">If required arguments are missing.</exception> /// <exception cref="InvalidOperationException">If required context configuration is missing.</exception> /// <exception cref="PrecheckException">If the gateway node create rejected the request upon submission.</exception> /// <exception cref="ConsensusException">If the network was unable to come to consensus before the duration of the transaction expired.</exception> /// <exception cref="TransactionException">If the network rejected the create request as invalid or had missing data.</exception> public async Task <TransactionRecord> SignPendingTransactionWithRecordAsync(Address pending, Signatory?signatory = null, Action <IContext>?configure = null) { return(new TransactionRecord(await ExecuteTransactionAsync(new ScheduleSignTransactionBody(pending), configure, true, signatory).ConfigureAwait(false))); }
/// <summary> /// Sends an HCS message of arbitrary size /// to the network by breaking the message /// into segments, submitting each segment /// in sequence. Manages the segment metadata /// internally, returning an array of receipts /// representing the transactions required to /// upload the entier message. /// </summary> /// <param name="client"> /// A Hashgraph Client instance. /// </param> /// <param name="topic"> /// The address of the topic for the message. /// </param> /// <param name="message"> /// The value of the message, may exceed the /// network limit size. /// </param> /// <param name="segmentSize"> /// The maximum size of each segment. Must /// be under the current allowed size for /// transactions currently supported by the /// network. The method will break the /// message into as many segments as necessary /// to fulfill uploading the entire message. /// </param> /// <param name="signatory"> /// The signatory containing any additional private keys or callbacks /// to meet the key signing requirements for participants. /// </param> /// <param name="configure"> /// Optional callback method providing an opportunity to modify /// the execution configuration for just this method call. /// It is executed prior to submitting the request to the network. /// </param> /// <returns> /// An array of Submit Message Receipts indicating success, one for each /// segment uploaded. The Transaction ID of the first receipt matches /// the correlation transaction ID for the series of message segments /// as a whole. /// </returns> /// <exception cref="ArgumentOutOfRangeException">If required arguments are missing.</exception> /// <exception cref="InvalidOperationException">If required context configuration is missing.</exception> /// <exception cref="PrecheckException">If the gateway node create rejected the request upon submission.</exception> /// <exception cref="ConsensusException">If the network was unable to come to consensus before the duration of the transaction expired.</exception> /// <exception cref="TransactionException">If the network rejected the create request as invalid or had missing data.</exception> public static async Task <SubmitMessageReceipt[]> SubmitLargeMessageAsync(this Client client, Address topic, ReadOnlyMemory <byte> message, int segmentSize, Signatory?signatory = null, Action <IContext>?configure = null) { await using var configuredClient = client.Clone(configure); var segmentCount = (message.Length - 1) / segmentSize + 1; var receipts = new SubmitMessageReceipt[segmentCount]; receipts[0] = await configuredClient.SubmitMessageAsync(new SubmitMessageParams { Topic = topic, Segment = segmentCount > 1 ? message.Slice(0, segmentSize) : message, Index = 1, TotalSegmentCount = segmentCount, Signatory = signatory }).ConfigureAwait(false); var parentTx = receipts[0].Id; for (int i = 1; i < segmentCount - 1; i++) { receipts[i] = await configuredClient.SubmitMessageAsync(new SubmitMessageParams { Topic = topic, Segment = message.Slice(segmentSize *i, segmentSize), ParentTxId = parentTx, Index = i + 1, TotalSegmentCount = segmentCount, Signatory = signatory }).ConfigureAwait(false); } if (segmentCount > 1) { receipts[segmentCount - 1] = await configuredClient.SubmitMessageAsync(new SubmitMessageParams { Topic = topic, Segment = message.Slice(segmentSize * (segmentCount - 1)), ParentTxId = parentTx, Index = segmentCount, TotalSegmentCount = segmentCount, Signatory = signatory }).ConfigureAwait(false); } return(receipts); }
/// <summary> /// Internal implementation of delete token method. /// </summary> private async Task <TResult> SuspendTokenImplementationAsync <TResult>(Address token, Address address, Signatory?signatory, Action <IContext>?configure) where TResult : new() { token = RequireInputParameter.Token(token); address = RequireInputParameter.Address(address); await using var context = CreateChildContext(configure); RequireInContext.Gateway(context); var payer = RequireInContext.Payer(context); var signatories = Transactions.GatherSignatories(context, signatory); var transactionId = Transactions.GetOrCreateTransactionID(context); var transactionBody = new TransactionBody(context, transactionId); transactionBody.TokenFreeze = new TokenFreezeAccountTransactionBody { Token = new TokenID(token), Account = new AccountID(address) }; var receipt = await transactionBody.SignAndExecuteWithRetryAsync(signatories, context); if (receipt.Status != ResponseCodeEnum.Success) { throw new TransactionException($"Unable to Suspend Token, status: {receipt.Status}", transactionId.ToTxId(), (ResponseCode)receipt.Status); } var result = new TResult(); if (result is TransactionRecord rec) { var record = await GetTransactionRecordAsync(context, transactionId); record.FillProperties(rec); } else if (result is TransactionReceipt rcpt) { receipt.FillProperties(transactionId, rcpt); } return(result); }
/// <summary> /// Internal implementation of the submit message call. /// </summary> private async Task <NetworkResult> SubmitMessageImplementationAsync(Address topic, ReadOnlyMemory <byte> message, bool isSegment, TxId?parentTx, int segmentIndex, int segmentTotalCount, Signatory?signatory, Action <IContext>?configure, bool includeRecord) { var transaction = new ConsensusSubmitMessageTransactionBody(topic, message, isSegment, parentTx, segmentIndex, segmentTotalCount); if (isSegment && segmentIndex == 1) { // Smelly Workaround due to necesity to embed the // same transaction ID in the middle of the message // as the envelope for the case of the first segment // of a segmented message. // // First We need to apply the confgure command, to // create the correct context await using var configuredClient = Clone(configure); var initialChunkTransactionId = configuredClient._context.GetOrCreateTransactionID(); if (configuredClient._context.GatherSignatories(signatory).GetSchedule() is null) { // The easy path, this is not a scheduled transaction. transaction.ChunkInfo !.InitialTransactionID = initialChunkTransactionId; } else { // Even more smell, we need to check to see if this is a // scheduled transaction. If this is, the initial chunk // transaction should have the "scheduled" flag set. var scheduledChunkTransactionId = new TransactionID(initialChunkTransactionId); scheduledChunkTransactionId.Scheduled = true; transaction.ChunkInfo !.InitialTransactionID = scheduledChunkTransactionId; } // We use our configured client, however we need to override the // configuration with one additional configuration rule that will // peg the transaction to our pre-computed value. var result = await configuredClient.ExecuteTransactionAsync(transaction, ctx => ctx.Transaction = initialChunkTransactionId.AsTxId(), false, signatory).ConfigureAwait(false); if (includeRecord) { // Note: we use the original context here, because we // don't want to re-use the transaction ID that was pinned // to the subContext, would cause the paying TX to fail as a duplicate. result.Record = await configuredClient.GetTransactionRecordAsync(configuredClient._context, result.TransactionID).ConfigureAwait(false); } return(result); } else { return(await ExecuteTransactionAsync(transaction, configure, includeRecord, signatory).ConfigureAwait(false)); } }
/// <summary> /// Adds a signature to a pending transaction record. The Scheduled Transaction executes /// this signature completes the list of required signatures for execution. /// </summary> /// <param name="pending"> /// The identifier (Address/Schedule ID) of the pending transaction to sign. /// </param> /// <param name="signatory"> /// The signatory containing any additional private keys or callbacks /// to meet the key signing requirements for participants. /// </param> /// <param name="configure"> /// Optional callback method providing an opportunity to modify /// the execution configuration for just this method call. /// It is executed prior to submitting the request to the network. /// </param> /// <returns> /// A Record for the transaction indicating success. /// </returns> /// <exception cref="ArgumentOutOfRangeException">If required arguments are missing.</exception> /// <exception cref="InvalidOperationException">If required context configuration is missing.</exception> /// <exception cref="PrecheckException">If the gateway node create rejected the request upon submission.</exception> /// <exception cref="ConsensusException">If the network was unable to come to consensus before the duration of the transaction expired.</exception> /// <exception cref="TransactionException">If the network rejected the create request as invalid or had missing data.</exception> public async Task <TransactionRecord> SignPendingTransactionWithRecordAsync(Address pending, Signatory?signatory = null, Action <IContext>?configure = null) { // Note: we may return a specialized record if the transaction executes and returns additional metadata. return((await ExecuteTransactionAsync(new ScheduleSignTransactionBody(pending), configure, true, signatory).ConfigureAwait(false)).ToRecord()); }
/// <summary> /// Internal implementation of the contract call method. /// </summary> private async Task <TResult> SubmitMessageImplementationAsync <TResult>(Address topic, ReadOnlyMemory <byte> message, Signatory?signatory, Action <IContext>?configure) where TResult : new() { topic = RequireInputParameter.Topic(topic); message = RequireInputParameter.Message(message); await using var context = CreateChildContext(configure); var gateway = RequireInContext.Gateway(context); var payer = RequireInContext.Payer(context); var signatories = Transactions.GatherSignatories(context, signatory); var transactionId = Transactions.GetOrCreateTransactionID(context); var transactionBody = Transactions.CreateTransactionBody(context, transactionId); transactionBody.ConsensusSubmitMessage = new ConsensusSubmitMessageTransactionBody { TopicID = Protobuf.ToTopicID(topic), Message = ByteString.CopyFrom(message.Span) }; var request = await Transactions.SignTransactionAsync(transactionBody, signatories); var precheck = await Transactions.ExecuteSignedRequestWithRetryAsync(context, request, getRequestMethod, getResponseCode); ValidateResult.PreCheck(transactionId, precheck); var receipt = await GetReceiptAsync(context, transactionId); if (receipt.Status != ResponseCodeEnum.Success) { throw new TransactionException($"Submit Message failed, status: {receipt.Status}", Protobuf.FromTransactionId(transactionId), (ResponseCode)receipt.Status); } var result = new TResult(); if (result is SubmitMessageRecord rec) { var record = await GetTransactionRecordAsync(context, transactionId); Protobuf.FillRecordProperties(record, rec); rec.RunningHash = receipt.TopicRunningHash?.ToByteArray(); rec.SequenceNumber = receipt.TopicSequenceNumber; } else if (result is SubmitMessageReceipt rcpt) { Protobuf.FillReceiptProperties(transactionId, receipt, rcpt); rcpt.RunningHash = receipt.TopicRunningHash?.ToByteArray(); rcpt.SequenceNumber = receipt.TopicSequenceNumber; } return(result);
/// <summary> /// Internal implementation of delete topic method. /// </summary> private async Task <TransactionReceipt> DeleteTopicImplementationAsync(Address topicToDelete, Signatory?signatory, Action <IContext>?configure) { topicToDelete = RequireInputParameter.AddressToDelete(topicToDelete); await using var context = CreateChildContext(configure); RequireInContext.Gateway(context); var payer = RequireInContext.Payer(context); var signatories = Transactions.GatherSignatories(context, signatory); var transactionId = Transactions.GetOrCreateTransactionID(context); var transactionBody = new TransactionBody(context, transactionId); transactionBody.ConsensusDeleteTopic = new ConsensusDeleteTopicTransactionBody { TopicID = new TopicID(topicToDelete) }; var receipt = await transactionBody.SignAndExecuteWithRetryAsync(signatories, context); if (receipt.Status != ResponseCodeEnum.Success) { throw new TransactionException($"Unable to Delete Topic, status: {receipt.Status}", transactionId.ToTxId(), (ResponseCode)receipt.Status); } return(receipt.FillProperties(transactionId, new TransactionReceipt())); }
/// <summary> /// Internal helper function implementing the file delete functionality. /// </summary> public async Task <TResult> DeleteFileImplementationAsync <TResult>(Address fileToDelete, Signatory?signatory, Action <IContext>?configure = null) where TResult : new() { fileToDelete = RequireInputParameter.FileToDelete(fileToDelete); await using var context = CreateChildContext(configure); RequireInContext.Gateway(context); var payer = RequireInContext.Payer(context); var signatories = Transactions.GatherSignatories(context, signatory); var transactionId = Transactions.GetOrCreateTransactionID(context); var transactionBody = Transactions.CreateTransactionBody(context, transactionId); transactionBody.FileDelete = new FileDeleteTransactionBody { FileID = Protobuf.ToFileId(fileToDelete) }; var request = await Transactions.SignTransactionAsync(transactionBody, signatories); var precheck = await Transactions.ExecuteSignedRequestWithRetryAsync(context, request, getRequestMethod, getResponseCode); ValidateResult.PreCheck(transactionId, precheck); var receipt = await GetReceiptAsync(context, transactionId); if (receipt.Status != ResponseCodeEnum.Success) { throw new TransactionException($"Unable to delete file, status: {receipt.Status}", Protobuf.FromTransactionId(transactionId), (ResponseCode)receipt.Status); } var result = new TResult(); if (result is TransactionRecord rec) { var record = await GetTransactionRecordAsync(context, transactionId); Protobuf.FillRecordProperties(record, rec); } else if (result is TransactionReceipt rcpt) { Protobuf.FillReceiptProperties(transactionId, receipt, rcpt); } return(result);
/// <summary> /// Internal implementation for Single Transfer Crypto /// Returns either a receipt or record or throws /// an exception. /// </summary> private async Task <TResult> TransferTokenImplementationAsync <TResult>(Address token, Address fromAddress, Address toAddress, long amount, Signatory?signatory, Action <IContext>?configure) where TResult : new() { amount = RequireInputParameter.Amount(amount); var transactions = new List <TokenTransferList>(1); var xferList = new TokenTransferList { Token = new TokenID(RequireInputParameter.Token(token)) }; xferList.Transfers.Add(new AccountAmount { AccountID = new AccountID(RequireInputParameter.FromAddress(fromAddress)), Amount = -amount }); xferList.Transfers.Add(new AccountAmount { AccountID = new AccountID(RequireInputParameter.ToAddress(toAddress)), Amount = amount }); transactions.Add(xferList); return(await TransferImplementationAsync <TResult>(null, transactions, signatory, configure)); }
/// <summary> /// Internal implementation of delete topic method. /// </summary> private async Task <TransactionReceipt> DeleteTopicImplementationAsync(Address topicToDelete, Signatory?signatory, Action <IContext>?configure) { topicToDelete = RequireInputParameter.AddressToDelete(topicToDelete); await using var context = CreateChildContext(configure); RequireInContext.Gateway(context); var payer = RequireInContext.Payer(context); var signatories = Transactions.GatherSignatories(context, signatory); var transactionId = Transactions.GetOrCreateTransactionID(context); var transactionBody = Transactions.CreateTransactionBody(context, transactionId); transactionBody.ConsensusDeleteTopic = new ConsensusDeleteTopicTransactionBody { TopicID = Protobuf.ToTopicID(topicToDelete) }; var request = await Transactions.SignTransactionAsync(transactionBody, signatories); var precheck = await Transactions.ExecuteSignedRequestWithRetryAsync(context, request, getRequestMethod, getResponseCode); ValidateResult.PreCheck(transactionId, precheck); var receipt = await GetReceiptAsync(context, transactionId); if (receipt.Status != ResponseCodeEnum.Success) { throw new TransactionException($"Unable to Delete Topic, status: {receipt.Status}", Protobuf.FromTransactionId(transactionId), (ResponseCode)receipt.Status); } var result = new TransactionReceipt(); Protobuf.FillReceiptProperties(transactionId, receipt, result); return(result);
/// <summary> /// Internal implementation for Multi Account Transfer Crypto and Tokens. /// Returns either a receipt or record or throws an exception. /// </summary> private async Task <TResult> TransferImplementationAsync <TResult>(TransferList?cryptoTransfers, IEnumerable <TokenTransferList>?tokenTransfers, Signatory?signatory, Action <IContext>?configure) where TResult : new() { await using var context = CreateChildContext(configure); RequireInContext.Gateway(context); var signatories = Transactions.GatherSignatories(context, signatory); var transactionId = Transactions.GetOrCreateTransactionID(context); var transactionBody = new TransactionBody(context, transactionId); transactionBody.CryptoTransfer = new CryptoTransferTransactionBody(); if (cryptoTransfers != null) { transactionBody.CryptoTransfer.Transfers = cryptoTransfers; } if (tokenTransfers != null) { transactionBody.CryptoTransfer.TokenTransfers.AddRange(tokenTransfers); } var receipt = await transactionBody.SignAndExecuteWithRetryAsync(signatories, context); if (receipt.Status != ResponseCodeEnum.Success) { throw new TransactionException($"Unable to execute transfers, status: {receipt.Status}", transactionId.ToTxId(), (ResponseCode)receipt.Status); } var result = new TResult(); if (result is TransactionRecord rec) { var record = await GetTransactionRecordAsync(context, transactionId); record.FillProperties(rec); } else if (result is TransactionReceipt rcpt) { receipt.FillProperties(transactionId, rcpt); } return(result); }
/// <summary> /// Internal implementation for Transfer Crypto. /// Returns either a receipt or record or throws /// an exception. /// </summary> private async Task <TResult> TransferImplementationAsync <TResult>(Address fromAddress, Address toAddress, long amount, Signatory?signatory, Action <IContext>?configure) where TResult : new() { fromAddress = RequireInputParameter.FromAddress(fromAddress); toAddress = RequireInputParameter.ToAddress(toAddress); amount = RequireInputParameter.Amount(amount); var cryptoTransfers = RequireInputParameter.CryptoTransferList(new[] { KeyValuePair.Create(fromAddress, -amount), KeyValuePair.Create(toAddress, amount) }); return(await TransferImplementationAsync <TResult>(cryptoTransfers, null, signatory, configure)); }
/// <summary> /// Internal implementation of the submit message call. /// </summary> private async Task <TResult> SubmitMessageImplementationAsync <TResult>(Address topic, ReadOnlyMemory <byte> message, bool isSegment, TxId?parentTx, int segmentIndex, int segmentTotalCount, Signatory?signatory, Action <IContext>?configure) where TResult : new() { topic = RequireInputParameter.Topic(topic); message = RequireInputParameter.Message(message); await using var context = CreateChildContext(configure); var gateway = RequireInContext.Gateway(context); var payer = RequireInContext.Payer(context); var signatories = Transactions.GatherSignatories(context, signatory); var transactionId = Transactions.GetOrCreateTransactionID(context); var transactionBody = new TransactionBody(context, transactionId); transactionBody.ConsensusSubmitMessage = new ConsensusSubmitMessageTransactionBody { TopicID = new TopicID(topic), Message = ByteString.CopyFrom(message.Span), ChunkInfo = isSegment ? createChunkInfo(transactionId, parentTx, segmentIndex, segmentTotalCount) : null }; var receipt = await transactionBody.SignAndExecuteWithRetryAsync(signatories, context); if (receipt.Status != ResponseCodeEnum.Success) { throw new TransactionException($"Submit Message failed, status: {receipt.Status}", transactionId.ToTxId(), (ResponseCode)receipt.Status); } var result = new TResult(); if (result is SubmitMessageRecord rec) { var record = await GetTransactionRecordAsync(context, transactionId); record.FillProperties(rec); } else if (result is SubmitMessageReceipt rcpt) { receipt.FillProperties(transactionId, rcpt); } return(result);