diff --git a/CHANGELOG.md b/CHANGELOG.md
index 8f192db4..516c220e 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,4 +1,6 @@
## Unreleased changes
+- Added
+ - New GRPC-endpoints: `GetBlocks`, `GetFinalizedBlocks`, `GetBranches`, `GetAncestors`, `GetBlockPendingUpdates`
## 4.1.0
- Bugfix
diff --git a/ConcordiumNetSdk.sln b/ConcordiumNetSdk.sln
index 57522607..b291f93c 100644
--- a/ConcordiumNetSdk.sln
+++ b/ConcordiumNetSdk.sln
@@ -67,6 +67,16 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GetModuleSource", "examples
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GetInstanceInfo", "examples\GetInstanceInfo\GetInstanceInfo.csproj", "{60BB9915-06F0-48FC-ABCD-271B6A361A01}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GetAncestors", "examples\GetAncestors\GetAncestors.csproj", "{F2A3BBF5-5ACE-443A-BB1C-6A9BDF7CB22A}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GetBlockPendingUpdates", "examples\GetBlockPendingUpdates\GetBlockPendingUpdates.csproj", "{C5BEBE65-EA24-431D-BB53-DA3673EF7E60}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GetBlocks", "examples\GetBlocks\GetBlocks.csproj", "{79E97788-D084-487E-8F34-0BA1911C452A}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GetBranches", "examples\GetBranches\GetBranches.csproj", "{26417CD7-2897-47BA-BA9B-C4475187331A}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GetFinalizedBlocks", "examples\GetFinalizedBlocks\GetFinalizedBlocks.csproj", "{E2CC6AD7-98CE-41F5-8C66-AE8781F29C77}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -197,6 +207,26 @@ Global
{60BB9915-06F0-48FC-ABCD-271B6A361A01}.Debug|Any CPU.Build.0 = Debug|Any CPU
{60BB9915-06F0-48FC-ABCD-271B6A361A01}.Release|Any CPU.ActiveCfg = Release|Any CPU
{60BB9915-06F0-48FC-ABCD-271B6A361A01}.Release|Any CPU.Build.0 = Release|Any CPU
+ {F2A3BBF5-5ACE-443A-BB1C-6A9BDF7CB22A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {F2A3BBF5-5ACE-443A-BB1C-6A9BDF7CB22A}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {F2A3BBF5-5ACE-443A-BB1C-6A9BDF7CB22A}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {F2A3BBF5-5ACE-443A-BB1C-6A9BDF7CB22A}.Release|Any CPU.Build.0 = Release|Any CPU
+ {C5BEBE65-EA24-431D-BB53-DA3673EF7E60}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {C5BEBE65-EA24-431D-BB53-DA3673EF7E60}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {C5BEBE65-EA24-431D-BB53-DA3673EF7E60}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {C5BEBE65-EA24-431D-BB53-DA3673EF7E60}.Release|Any CPU.Build.0 = Release|Any CPU
+ {79E97788-D084-487E-8F34-0BA1911C452A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {79E97788-D084-487E-8F34-0BA1911C452A}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {79E97788-D084-487E-8F34-0BA1911C452A}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {79E97788-D084-487E-8F34-0BA1911C452A}.Release|Any CPU.Build.0 = Release|Any CPU
+ {26417CD7-2897-47BA-BA9B-C4475187331A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {26417CD7-2897-47BA-BA9B-C4475187331A}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {26417CD7-2897-47BA-BA9B-C4475187331A}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {26417CD7-2897-47BA-BA9B-C4475187331A}.Release|Any CPU.Build.0 = Release|Any CPU
+ {E2CC6AD7-98CE-41F5-8C66-AE8781F29C77}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {E2CC6AD7-98CE-41F5-8C66-AE8781F29C77}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {E2CC6AD7-98CE-41F5-8C66-AE8781F29C77}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {E2CC6AD7-98CE-41F5-8C66-AE8781F29C77}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -234,5 +264,10 @@ Global
{E6530630-82A0-4AB2-A600-A86DC619CB88} = {FD2CDD9F-4650-4705-9CA2-98CC81F8891D}
{FFF88CB0-DC68-4B00-8632-770A270946F5} = {FD2CDD9F-4650-4705-9CA2-98CC81F8891D}
{60BB9915-06F0-48FC-ABCD-271B6A361A01} = {FD2CDD9F-4650-4705-9CA2-98CC81F8891D}
+ {F2A3BBF5-5ACE-443A-BB1C-6A9BDF7CB22A} = {FD2CDD9F-4650-4705-9CA2-98CC81F8891D}
+ {C5BEBE65-EA24-431D-BB53-DA3673EF7E60} = {FD2CDD9F-4650-4705-9CA2-98CC81F8891D}
+ {79E97788-D084-487E-8F34-0BA1911C452A} = {FD2CDD9F-4650-4705-9CA2-98CC81F8891D}
+ {26417CD7-2897-47BA-BA9B-C4475187331A} = {FD2CDD9F-4650-4705-9CA2-98CC81F8891D}
+ {E2CC6AD7-98CE-41F5-8C66-AE8781F29C77} = {FD2CDD9F-4650-4705-9CA2-98CC81F8891D}
EndGlobalSection
EndGlobal
diff --git a/examples/GetAncestors/GetAncestors.csproj b/examples/GetAncestors/GetAncestors.csproj
new file mode 100644
index 00000000..f9d1b111
--- /dev/null
+++ b/examples/GetAncestors/GetAncestors.csproj
@@ -0,0 +1,18 @@
+
+
+
+ Exe
+ net6.0
+ enable
+ enable
+
+
+
+
+
+
+
+
+
+
+
diff --git a/examples/GetAncestors/Program.cs b/examples/GetAncestors/Program.cs
new file mode 100644
index 00000000..5a2b48ea
--- /dev/null
+++ b/examples/GetAncestors/Program.cs
@@ -0,0 +1,54 @@
+using CommandLine;
+using Concordium.Sdk.Client;
+using Concordium.Sdk.Types;
+
+// We disable these warnings since CommandLine needs to set properties in options
+// but we don't want to give default values.
+#pragma warning disable CS8618
+
+namespace GetAncestors;
+
+internal sealed class GetAncestorsOptions
+{
+ [Option(HelpText = "URL representing the endpoint where the gRPC V2 API is served.",
+ Default = "http://node.testnet.concordium.com:20000/")]
+ public string Endpoint { get; set; }
+ [Option(
+ 'm',
+ "max-ancestors",
+ HelpText = "The maximum number of ancestors returned.",
+ Required = true
+ )]
+ public ulong MaxAncestors { get; set; }
+ [Option(
+ 'b',
+ "block-hash",
+ HelpText = "Block hash of the block. Defaults to LastFinal."
+ )]
+ public string BlockHash { get; set; }
+}
+
+public static class Program
+{
+ ///
+ /// Example how to use
+ ///
+ public static async Task Main(string[] args) =>
+ await Parser.Default
+ .ParseArguments(args)
+ .WithParsedAsync(Run);
+
+ private static async Task Run(GetAncestorsOptions o)
+ {
+ using var client = new ConcordiumClient(new Uri(o.Endpoint), new ConcordiumClientOptions());
+
+ IBlockHashInput bi = o.BlockHash != null ? new Given(BlockHash.From(o.BlockHash)) : new LastFinal();
+
+ var ancestors = await client.GetAncestors(bi, o.MaxAncestors);
+
+ await foreach (var ancestor in ancestors.Response)
+ {
+ Console.WriteLine($"Ancestor: {ancestor}");
+ }
+ }
+}
diff --git a/examples/GetBlockPendingUpdates/GetBlockPendingUpdates.csproj b/examples/GetBlockPendingUpdates/GetBlockPendingUpdates.csproj
new file mode 100644
index 00000000..f9d1b111
--- /dev/null
+++ b/examples/GetBlockPendingUpdates/GetBlockPendingUpdates.csproj
@@ -0,0 +1,18 @@
+
+
+
+ Exe
+ net6.0
+ enable
+ enable
+
+
+
+
+
+
+
+
+
+
+
diff --git a/examples/GetBlockPendingUpdates/Program.cs b/examples/GetBlockPendingUpdates/Program.cs
new file mode 100644
index 00000000..53e11786
--- /dev/null
+++ b/examples/GetBlockPendingUpdates/Program.cs
@@ -0,0 +1,49 @@
+using CommandLine;
+using Concordium.Sdk.Client;
+using Concordium.Sdk.Types;
+
+// We disable these warnings since CommandLine needs to set properties in options
+// but we don't want to give default values.
+#pragma warning disable CS8618
+
+namespace GetBlockPendingUpdates;
+
+internal sealed class GetBlockPendingUpdatesOptions
+{
+ [Option(HelpText = "URL representing the endpoint where the gRPC V2 API is served.",
+ Default = "http://node.testnet.concordium.com:20000/")]
+ public string Endpoint { get; set; }
+ [Option(
+ 'b',
+ "block-hash",
+ HelpText = "Block hash of the block. Defaults to LastFinal."
+ )]
+ public string BlockHash { get; set; }
+}
+
+public static class Program
+{
+ ///
+ /// Example how to use
+ ///
+ public static async Task Main(string[] args) =>
+ await Parser.Default
+ .ParseArguments(args)
+ .WithParsedAsync(Run);
+
+ private static async Task Run(GetBlockPendingUpdatesOptions o)
+ {
+ using var client = new ConcordiumClient(new Uri(o.Endpoint), new ConcordiumClientOptions());
+
+ IBlockHashInput bi = o.BlockHash != null ? new Given(BlockHash.From(o.BlockHash)) : new LastFinal();
+
+ var updates = await client.GetBlockPendingUpdates(bi);
+
+ Console.WriteLine($"Updates:");
+ await foreach (var update in updates.Response)
+ {
+ Console.WriteLine($"Pending update: {update}");
+
+ }
+ }
+}
diff --git a/examples/GetBlocks/GetBlocks.csproj b/examples/GetBlocks/GetBlocks.csproj
new file mode 100644
index 00000000..f9d1b111
--- /dev/null
+++ b/examples/GetBlocks/GetBlocks.csproj
@@ -0,0 +1,18 @@
+
+
+
+ Exe
+ net6.0
+ enable
+ enable
+
+
+
+
+
+
+
+
+
+
+
diff --git a/examples/GetBlocks/Program.cs b/examples/GetBlocks/Program.cs
new file mode 100644
index 00000000..ed840f1a
--- /dev/null
+++ b/examples/GetBlocks/Program.cs
@@ -0,0 +1,40 @@
+using CommandLine;
+using Concordium.Sdk.Client;
+
+// We disable these warnings since CommandLine needs to set properties in options
+// but we don't want to give default values.
+#pragma warning disable CS8618
+
+namespace GetBlocks;
+
+internal sealed class GetBlocksOptions
+{
+ [Option(HelpText = "URL representing the endpoint where the gRPC V2 API is served.",
+ Default = "http://node.testnet.concordium.com:20000/")]
+ public string Endpoint { get; set; }
+}
+
+public static class Program
+{
+ ///
+ /// Example how to use
+ ///
+ public static async Task Main(string[] args) =>
+ await Parser.Default
+ .ParseArguments(args)
+ .WithParsedAsync(Run);
+
+ private static async Task Run(GetBlocksOptions options)
+ {
+ using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(10));
+
+ using var client = new ConcordiumClient(new Uri(options.Endpoint), new ConcordiumClientOptions());
+
+ var blocks = client.GetBlocks();
+
+ await foreach (var block in blocks)
+ {
+ Console.WriteLine($"Block arrived: {block}");
+ }
+ }
+}
diff --git a/examples/GetBranches/GetBranches.csproj b/examples/GetBranches/GetBranches.csproj
new file mode 100644
index 00000000..ea5fc94a
--- /dev/null
+++ b/examples/GetBranches/GetBranches.csproj
@@ -0,0 +1,19 @@
+
+
+
+ Exe
+ net6.0
+ enable
+ enable
+ GetNodeInfo
+
+
+
+
+
+
+
+
+
+
+
diff --git a/examples/GetBranches/Program.cs b/examples/GetBranches/Program.cs
new file mode 100644
index 00000000..e1fbd81a
--- /dev/null
+++ b/examples/GetBranches/Program.cs
@@ -0,0 +1,47 @@
+using CommandLine;
+using Concordium.Sdk.Client;
+using Branch = Concordium.Sdk.Types.Branch;
+
+// We disable these warnings since CommandLine needs to set properties in options
+// but we don't want to give default values.
+#pragma warning disable CS8618
+
+namespace GetBranches;
+
+internal sealed class GetBranchesOptions
+{
+ [Option(HelpText = "URL representing the endpoint where the gRPC V2 API is served.",
+ Default = "http://node.testnet.concordium.com:20000/")]
+ public string Endpoint { get; set; }
+}
+
+
+public static class Program
+{
+ ///
+ /// Example how to use
+ /// s
+ public static async Task Main(string[] args) =>
+ await Parser.Default
+ .ParseArguments(args)
+ .WithParsedAsync(Run);
+
+ private static async Task Run(GetBranchesOptions options)
+ {
+ using var client = new ConcordiumClient(new Uri(options.Endpoint), new ConcordiumClientOptions());
+
+ var branch = await client.GetBranchesAsync();
+
+ PrintBranchesAsTree(0, branch);
+ }
+
+ private static void PrintBranchesAsTree(uint depth, Branch branch)
+ {
+ for (var i = 0; i < depth; i++)
+ {
+ Console.Write("--");
+ }
+ Console.WriteLine(branch.BlockHash);
+ branch.Children.ForEach(x => PrintBranchesAsTree(depth + 1, x));
+ }
+}
diff --git a/examples/GetFinalizedBlocks/GetFinalizedBlocks.csproj b/examples/GetFinalizedBlocks/GetFinalizedBlocks.csproj
new file mode 100644
index 00000000..f9d1b111
--- /dev/null
+++ b/examples/GetFinalizedBlocks/GetFinalizedBlocks.csproj
@@ -0,0 +1,18 @@
+
+
+
+ Exe
+ net6.0
+ enable
+ enable
+
+
+
+
+
+
+
+
+
+
+
diff --git a/examples/GetFinalizedBlocks/Program.cs b/examples/GetFinalizedBlocks/Program.cs
new file mode 100644
index 00000000..2808d64b
--- /dev/null
+++ b/examples/GetFinalizedBlocks/Program.cs
@@ -0,0 +1,40 @@
+using CommandLine;
+using Concordium.Sdk.Client;
+
+// We disable these warnings since CommandLine needs to set properties in options
+// but we don't want to give default values.
+#pragma warning disable CS8618
+
+namespace GetFinalizedBlocks;
+
+internal sealed class GetFinalizedBlocksOptions
+{
+ [Option(HelpText = "URL representing the endpoint where the gRPC V2 API is served.",
+ Default = "http://node.testnet.concordium.com:20000/")]
+ public string Endpoint { get; set; }
+}
+
+public static class Program
+{
+ ///
+ /// Example how to use
+ ///
+ public static async Task Main(string[] args) =>
+ await Parser.Default
+ .ParseArguments(args)
+ .WithParsedAsync(Run);
+
+ private static async Task Run(GetFinalizedBlocksOptions options)
+ {
+ using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(10));
+
+ using var client = new ConcordiumClient(new Uri(options.Endpoint), new ConcordiumClientOptions());
+
+ var blocks = client.GetFinalizedBlocks();
+
+ await foreach (var block in blocks)
+ {
+ Console.WriteLine($"Finalized block arrived: {block}");
+ }
+ }
+}
diff --git a/src/Client/ConcordiumClient.cs b/src/Client/ConcordiumClient.cs
index 0aa4e2d8..82915bbe 100644
--- a/src/Client/ConcordiumClient.cs
+++ b/src/Client/ConcordiumClient.cs
@@ -5,15 +5,19 @@
using Grpc.Net.Client;
using AccountAddress = Concordium.Sdk.Types.AccountAddress;
using AccountInfo = Concordium.Sdk.Types.AccountInfo;
+using ArrivedBlockInfo = Concordium.Sdk.Types.ArrivedBlockInfo;
using BakerId = Concordium.Sdk.Types.BakerId;
using BlockHash = Concordium.Sdk.Types.BlockHash;
using BlockInfo = Concordium.Sdk.Types.BlockInfo;
using BlockItemSummary = Concordium.Sdk.Types.BlockItemSummary;
+using Branch = Concordium.Sdk.Types.Branch;
using ConsensusInfo = Concordium.Sdk.Types.ConsensusInfo;
using ContractAddress = Concordium.Sdk.Types.ContractAddress;
using FinalizationSummary = Concordium.Sdk.Types.FinalizationSummary;
+using FinalizedBlockInfo = Concordium.Sdk.Types.FinalizedBlockInfo;
using IpInfo = Concordium.Sdk.Types.IpInfo;
using NodeInfo = Concordium.Sdk.Types.NodeInfo;
+using PendingUpdate = Concordium.Sdk.Types.PendingUpdate;
using TransactionHash = Concordium.Sdk.Types.TransactionHash;
using VersionedModuleSource = Concordium.Sdk.Types.VersionedModuleSource;
@@ -545,6 +549,112 @@ await Task.WhenAll(response.ResponseHeadersAsync, response.ResponseAsync)
.ConfigureAwait(false);
}
+ ///
+ /// Return a stream of blocks that arrive from the time the query is made onward.
+ /// This can be used to listen for incoming blocks.
+ ///
+ /// Cancellation token
+ /// A stream of blocks that arrive from the time the query is made onward.
+ ///
+ /// RPC error occurred, access for more information.
+ /// indicates that this endpoint is disabled in the node.
+ ///
+ public IAsyncEnumerable GetBlocks(CancellationToken token = default)
+ {
+ var response = this.Raw.GetBlocks(token);
+ return response.ResponseStream.ReadAllAsync(token).Select(ArrivedBlockInfo.From);
+ }
+
+ ///
+ /// Return a stream of blocks that are finalized from the time the query is
+ /// made onward. This can be used to listen for newly finalized blocks. Note
+ /// that there is no guarantee that blocks will not be skipped if the client is
+ /// too slow in processing the stream, however blocks will always be sent by
+ /// increasing block height.
+ ///
+ /// Cancellation token
+ /// A stream of finalized blocks that arrive from the time the query is made onward.
+ ///
+ /// RPC error occurred, access for more information.
+ /// indicates that this endpoint is disabled in the node.
+ ///
+ public IAsyncEnumerable GetFinalizedBlocks(CancellationToken token = default)
+ {
+ var response = this.Raw.GetFinalizedBlocks(token);
+ return response.ResponseStream.ReadAllAsync(token).Select(FinalizedBlockInfo.From);
+ }
+
+ ///
+ /// Get the current branches of blocks starting from and including the last finalized block.
+ ///
+ /// Cancellation token
+ /// The current branches of blocks.
+ ///
+ /// RPC error occurred, access for more information.
+ /// indicates that this endpoint is disabled in the node.
+ ///
+ public async Task GetBranchesAsync(CancellationToken token = default)
+ {
+ var response = await this.Raw.GetBranchesAsync(token).ConfigureAwait(false);
+ return Branch.From(response);
+ }
+
+ ///
+ /// Get the current branches of blocks starting from and including the last finalized block.
+ ///
+ /// Cancellation token
+ /// The current branches of blocks.
+ ///
+ /// RPC error occurred, access for more information.
+ /// indicates that this endpoint is disabled in the node.
+ ///
+ public Branch GetBranches(CancellationToken token = default)
+ {
+ var response = this.Raw.GetBranches(token);
+ return Branch.From(response);
+ }
+
+ ///
+ /// Get the pending updates to chain parameters at the end of a given block.
+ /// The stream will end when all the pending updates for a given block have been returned.
+ ///
+ /// The block to get ancestors of
+ /// Cancellation token
+ /// The pending updates.
+ ///
+ /// RPC error occurred, access for more information.
+ /// indicates that this endpoint is disabled in the node.
+ ///
+ public Task>> GetBlockPendingUpdates(IBlockHashInput blockHash, CancellationToken token = default)
+ {
+ var responses = this.Raw.GetBlockPendingUpdates(blockHash.Into(), token);
+ return QueryResponse>.From(responses, PendingUpdate.From, token);
+ }
+
+ ///
+ /// Get a stream of ancestors for the provided block.
+ /// Starting with the provided block itself, moving backwards until no more
+ /// ancestors or the requested number of ancestors has been returned.
+ ///
+ /// The block to get ancestors of
+ /// The maximum number of ancestors returned
+ /// Cancellation token
+ /// A stream of ancestors.
+ ///
+ /// RPC error occurred, access for more information.
+ /// indicates that this endpoint is disabled in the node.
+ ///
+ public Task>> GetAncestors(IBlockHashInput blockHash, ulong limit, CancellationToken token = default)
+ {
+ var req = new AncestorsRequest()
+ {
+ BlockHash = blockHash.Into(),
+ Amount = limit
+ };
+ var response = this.Raw.GetAncestors(req, token);
+ return QueryResponse>.From(response, BlockHash.From, token);
+ }
+
///
/// Get all smart contract modules that exist at the end of a given block.
///
diff --git a/src/Types/ArrivedBlockInfo.cs b/src/Types/ArrivedBlockInfo.cs
new file mode 100644
index 00000000..dabdfa36
--- /dev/null
+++ b/src/Types/ArrivedBlockInfo.cs
@@ -0,0 +1,15 @@
+namespace Concordium.Sdk.Types;
+
+///
+/// Information about an arrived block that is part of the streaming response.
+///
+/// Hash of the block.
+/// Absolute height of the block, height 0 is the genesis block.
+public sealed record ArrivedBlockInfo(BlockHash BlockHash, AbsoluteHeight BlockHeight)
+{
+ internal static ArrivedBlockInfo From(Grpc.V2.ArrivedBlockInfo info) =>
+ new(
+ BlockHash.From(info.Hash),
+ AbsoluteHeight.From(info.Height)
+ );
+}
diff --git a/src/Types/BlockHeight.cs b/src/Types/BlockHeight.cs
index 9eac8335..7cdf6d07 100644
--- a/src/Types/BlockHeight.cs
+++ b/src/Types/BlockHeight.cs
@@ -16,6 +16,8 @@ public interface IBlockHeight
/// Height from the beginning of the chain.
public sealed record AbsoluteHeight(ulong Height) : IBlockHeight
{
+ internal static AbsoluteHeight From(AbsoluteBlockHeight blockHeight) => new(blockHeight.Value);
+
public BlocksAtHeightRequest Into() =>
new()
{
diff --git a/src/Types/Branch.cs b/src/Types/Branch.cs
new file mode 100644
index 00000000..1fd1ceec
--- /dev/null
+++ b/src/Types/Branch.cs
@@ -0,0 +1,15 @@
+namespace Concordium.Sdk.Types;
+
+///
+/// Response type for GetBranches.
+///
+/// The hash of the block.
+/// Further blocks branching of this block.
+public sealed record Branch(BlockHash BlockHash, List Children)
+{
+ internal static Branch From(Grpc.V2.Branch info) =>
+ new(
+ BlockHash.From(info.BlockHash),
+ info.Children.Select(From).ToList()
+ );
+}
diff --git a/src/Types/ElectionDifficulty.cs b/src/Types/ElectionDifficulty.cs
new file mode 100644
index 00000000..7a9669d3
--- /dev/null
+++ b/src/Types/ElectionDifficulty.cs
@@ -0,0 +1,9 @@
+namespace Concordium.Sdk.Types;
+
+/// Election difficulty parameter.
+/// The election difficulty.
+public sealed record ElectionDifficulty(AmountFraction Difficulty)
+{
+ internal static ElectionDifficulty From(Grpc.V2.ElectionDifficulty electionDifficulty) =>
+ new(AmountFraction.From(electionDifficulty.Value));
+}
diff --git a/src/Types/FinalizedBlockInfo.cs b/src/Types/FinalizedBlockInfo.cs
new file mode 100644
index 00000000..e22588a1
--- /dev/null
+++ b/src/Types/FinalizedBlockInfo.cs
@@ -0,0 +1,15 @@
+namespace Concordium.Sdk.Types;
+
+///
+/// Information about a finalized block that is part of the streaming response.
+///
+/// Hash of the block.
+/// Absolute height of the block, height 0 is the genesis block.
+public sealed record FinalizedBlockInfo(BlockHash BlockHash, AbsoluteHeight BlockHeight)
+{
+ internal static FinalizedBlockInfo From(Grpc.V2.FinalizedBlockInfo info) =>
+ new(
+ BlockHash.From(info.Hash),
+ AbsoluteHeight.From(info.Height)
+ );
+}
diff --git a/src/Types/PendingUpdate.cs b/src/Types/PendingUpdate.cs
new file mode 100644
index 00000000..8af2a1c3
--- /dev/null
+++ b/src/Types/PendingUpdate.cs
@@ -0,0 +1,106 @@
+using Concordium.Sdk.Exceptions;
+using GrpcEffect = Concordium.Grpc.V2.PendingUpdate.EffectOneofCase;
+
+namespace Concordium.Sdk.Types;
+///
+/// Minimum stake needed to become a baker. This only applies to protocol version 1-3.
+///
+/// Minimum threshold required for registering as a baker.
+public record BakerStakeThreshold(CcdAmount MinimumThresholdForBaking)
+{
+ internal static BakerStakeThreshold From(Grpc.V2.BakerStakeThreshold bakerStakeThreshold) => new(CcdAmount.From(bakerStakeThreshold.BakerStakeThreshold_));
+};
+
+///
+/// A pending update.
+///
+/// The effective time of the update.
+/// The effect of the update.
+public sealed record PendingUpdate(TransactionTime EffectiveTime, IEffect Effect)
+{
+ internal static PendingUpdate From(Grpc.V2.PendingUpdate pendingUpdate) =>
+ new(
+ TransactionTime.From(pendingUpdate.EffectiveTime),
+ pendingUpdate.EffectCase switch
+ {
+ GrpcEffect.RootKeys => new EffectRootKeys(RootKeys.From(pendingUpdate.RootKeys)),
+ GrpcEffect.Level1Keys => new EffectLevel1Keys(Level1Keys.From(pendingUpdate.Level1Keys)),
+ GrpcEffect.Level2KeysCpv0 => new EffectLevel2KeysCpv0(AuthorizationsV0.From(pendingUpdate.Level2KeysCpv0)),
+ GrpcEffect.Level2KeysCpv1 => new EffectLevel2KeysCpv1(AuthorizationsV1.From(pendingUpdate.Level2KeysCpv1)),
+ GrpcEffect.Protocol => new EffectProtocol(ProtocolUpdate.From(pendingUpdate.Protocol)),
+ GrpcEffect.ElectionDifficulty => new EffectElectionDifficulty(ElectionDifficulty.From(pendingUpdate.ElectionDifficulty)),
+ GrpcEffect.EuroPerEnergy => new EffectEuroPerEnergy(ExchangeRate.From(pendingUpdate.EuroPerEnergy)),
+ GrpcEffect.MicroCcdPerEuro => new EffectMicroCcdPerEnergy(ExchangeRate.From(pendingUpdate.MicroCcdPerEuro)),
+ GrpcEffect.FoundationAccount => new EffectFoundationAccount(AccountAddress.From(pendingUpdate.FoundationAccount)),
+ GrpcEffect.MintDistributionCpv0 => new EffectMintDistributionCpv0(MintDistributionCpv0.From(pendingUpdate.MintDistributionCpv0)),
+ GrpcEffect.MintDistributionCpv1 => new EffectMintDistributionCpv1(MintDistributionCpv1.From(pendingUpdate.MintDistributionCpv1)),
+ GrpcEffect.TransactionFeeDistribution => new EffectTransactionFeeDistribution(TransactionFeeDistribution.From(pendingUpdate.TransactionFeeDistribution)),
+ GrpcEffect.GasRewards => new EffectGasRewards(GasRewards.From(pendingUpdate.GasRewards)),
+ GrpcEffect.PoolParametersCpv0 => new EffectPoolParametersCpv0(BakerStakeThreshold.From(pendingUpdate.PoolParametersCpv0)),
+ GrpcEffect.PoolParametersCpv1 => new EffectPoolParametersCpv1(PoolParameters.From(pendingUpdate.PoolParametersCpv1)),
+ GrpcEffect.AddAnonymityRevoker => new EffectAddAnonymityRevoker(ArInfo.From(pendingUpdate.AddAnonymityRevoker)),
+ GrpcEffect.AddIdentityProvider => new EffectAddIdentityProvider(IpInfo.From(pendingUpdate.AddIdentityProvider)),
+ GrpcEffect.CooldownParameters => new EffectCooldownParameters(CooldownParameters.From(pendingUpdate.CooldownParameters)),
+ GrpcEffect.TimeParameters => new EffectTimeParameters(TimeParameters.From(pendingUpdate.TimeParameters)),
+ GrpcEffect.GasRewardsCpv2 => new EffectGasRewardsCpv2(GasRewardsCpv2.From(pendingUpdate.GasRewardsCpv2)),
+ GrpcEffect.TimeoutParameters => new EffectTimeoutParameters(TimeoutParameters.From(pendingUpdate.TimeoutParameters)),
+ GrpcEffect.MinBlockTime => new EffectMinBlockTime(TimeSpan.FromMilliseconds(pendingUpdate.MinBlockTime.Value)),
+ GrpcEffect.BlockEnergyLimit => new EffectBlockEnergyLimit(EnergyAmount.From(pendingUpdate.BlockEnergyLimit)),
+ GrpcEffect.FinalizationCommitteeParameters => new EffectFinalizationCommitteeParameters(FinalizationCommitteeParameters.From(pendingUpdate.FinalizationCommitteeParameters)),
+ GrpcEffect.None => throw new NotImplementedException(),
+ _ => throw new MissingEnumException(pendingUpdate.EffectCase),
+ }
+ );
+}
+
+/// The effect of the update.
+public interface IEffect { };
+
+/// Updates to the root keys.
+public sealed record EffectRootKeys(RootKeys RootKeys) : IEffect;
+/// Updates to the level 1 keys.
+public sealed record EffectLevel1Keys(Level1Keys Level1Keys) : IEffect;
+/// Updates to the level 2 keys.
+public sealed record EffectLevel2KeysCpv0(AuthorizationsV0 Level2KeysUpdateV0) : IEffect;
+/// Updates to the level 2 keys.
+public sealed record EffectLevel2KeysCpv1(AuthorizationsV1 Level2KeysUpdateV1) : IEffect;
+/// Protocol updates.
+public sealed record EffectProtocol(ProtocolUpdate ProtocolUpdate) : IEffect;
+/// Updates to the election difficulty parameter.
+public sealed record EffectElectionDifficulty(ElectionDifficulty ElectionDifficulty) : IEffect;
+/// Updates to the euro:energy exchange rate.
+public sealed record EffectEuroPerEnergy(ExchangeRate EuroPerEnergy) : IEffect;
+/// Updates to the CCD:EUR exchange rate.
+public sealed record EffectMicroCcdPerEnergy(ExchangeRate MicroCcdPerEnergy) : IEffect;
+/// Updates to the foundation account.
+public sealed record EffectFoundationAccount(AccountAddress FoundationAccount) : IEffect;
+/// Updates to the mint distribution. Is only relevant prior to protocol version 4.
+public sealed record EffectMintDistributionCpv0(MintDistributionCpv0 MintDistributionCpv0) : IEffect;
+/// The mint distribution was updated. Introduced in protocol version 4.
+public sealed record EffectMintDistributionCpv1(MintDistributionCpv1 MintDistributionCpv1) : IEffect;
+/// Updates to the transaction fee distribution.
+public sealed record EffectTransactionFeeDistribution(TransactionFeeDistribution TransactionFeeDistribution) : IEffect;
+/// Updates to the GAS rewards.
+public sealed record EffectGasRewards(GasRewards GasRewards) : IEffect;
+/// Updates baker stake threshold. Is only relevant prior to protocol version 4.
+public sealed record EffectPoolParametersCpv0(BakerStakeThreshold BakerParameters) : IEffect;
+/// Updates pool parameters. Introduced in protocol version 4.
+public sealed record EffectPoolParametersCpv1(PoolParameters PoolParameters) : IEffect;
+/// Adds a new anonymity revoker.
+public sealed record EffectAddAnonymityRevoker(ArInfo AddAnonymityRevoker) : IEffect;
+/// Adds a new identity provider.
+public sealed record EffectAddIdentityProvider(IpInfo AddIdentityProvider) : IEffect;
+/// Updates to cooldown parameters for chain parameters version 1 introduced in protocol version 4.
+public sealed record EffectCooldownParameters(CooldownParameters CooldownParameters) : IEffect;
+/// Updates to time parameters for chain parameters version 1 introduced in protocol version 4.
+public sealed record EffectTimeParameters(TimeParameters TimeParameters) : IEffect;
+/// Updates to the GAS rewards effective from protocol version 6 (chain parameters version 2).
+public sealed record EffectGasRewardsCpv2(GasRewardsCpv2 GasRewardsCpv2) : IEffect;
+/// Updates to the consensus timeouts for chain parameters version 2.
+public sealed record EffectTimeoutParameters(TimeoutParameters TimeoutParameters) : IEffect;
+/// Updates to the the minimum time between blocks for chain parameters version 2.
+public sealed record EffectMinBlockTime(TimeSpan MinBlockTime) : IEffect;
+/// Updates to the block energy limit for chain parameters version 2.
+public sealed record EffectBlockEnergyLimit(EnergyAmount BlockEnergyLimit) : IEffect;
+/// Updates to the finalization committee for for chain parameters version 2.
+public sealed record EffectFinalizationCommitteeParameters(FinalizationCommitteeParameters FinalizationCommitteeParameters) : IEffect;
diff --git a/src/Types/TransactionTime.cs b/src/Types/TransactionTime.cs
new file mode 100644
index 00000000..e3e516c2
--- /dev/null
+++ b/src/Types/TransactionTime.cs
@@ -0,0 +1,24 @@
+namespace Concordium.Sdk.Types;
+
+/// Transaction time specified as seconds since unix epoch.
+/// Seconds since the unix epoch.
+public sealed record TransactionTime(ulong SecondsSinceUnixEpoch)
+{
+ internal static TransactionTime From(Grpc.V2.TransactionTime transactionTime) =>
+ new(transactionTime.Value);
+
+ /// Convert the TransactionTime to a DateTimeOffset.
+ public DateTimeOffset ToDateTimeOffset()
+ {
+ if (this.SecondsSinceUnixEpoch > long.MaxValue)
+ {
+ throw new ArgumentOutOfRangeException(
+ $"The timestamp has a value of {this.SecondsSinceUnixEpoch} which exceeds the maximum value of supported by DateTimeOffset."
+ );
+ }
+ else
+ {
+ return DateTimeOffset.FromUnixTimeSeconds((long)this.SecondsSinceUnixEpoch);
+ }
+ }
+}