diff --git a/.config/dotnet-tools.json b/.config/dotnet-tools.json
new file mode 100644
index 0000000..ba0cf5f
--- /dev/null
+++ b/.config/dotnet-tools.json
@@ -0,0 +1,13 @@
+{
+ "version": 1,
+ "isRoot": true,
+ "tools": {
+ "nswag.consolecore": {
+ "version": "14.7.1",
+ "commands": [
+ "nswag"
+ ],
+ "rollForward": false
+ }
+ }
+}
\ No newline at end of file
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 0eb3399..0b56f7e 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -38,7 +38,7 @@ jobs:
~/.cargo/registry
~/.cargo/git
.wasm-build
- key: wasm-${{ runner.os }}-${{ hashFiles('scripts/build-wasm.sh', 'rust-toolchain.toml') }}
+ key: wasm-${{ runner.os }}-${{ hashFiles('scripts/build-wasm.sh', 'scripts/fetch-crate.sh', 'scripts/pins.env', 'rust-toolchain.toml') }}
- name: Set up Node.js
uses: actions/setup-node@v4
@@ -136,7 +136,7 @@ jobs:
~/.cargo/registry
~/.cargo/git
.wasm-build
- key: wasm-${{ runner.os }}-${{ hashFiles('scripts/build-wasm.sh', 'rust-toolchain.toml') }}
+ key: wasm-${{ runner.os }}-${{ hashFiles('scripts/build-wasm.sh', 'scripts/fetch-crate.sh', 'scripts/pins.env', 'rust-toolchain.toml') }}
- name: Build wasm core
run: bash scripts/build-wasm.sh
diff --git a/.gitignore b/.gitignore
index 167e576..ae102ad 100644
--- a/.gitignore
+++ b/.gitignore
@@ -20,6 +20,7 @@ coverage*.info
# Wasm core built from the pinned crates.io release (never committed)
wasm/
.wasm-build/
+.node-api-build/
# Node harness build output
node_modules/
diff --git a/Directory.Packages.props b/Directory.Packages.props
index 8bde0e2..85b04a6 100644
--- a/Directory.Packages.props
+++ b/Directory.Packages.props
@@ -8,6 +8,7 @@
+
diff --git a/Makefile b/Makefile
index 53b7a3f..83e74ea 100644
--- a/Makefile
+++ b/Makefile
@@ -1,4 +1,4 @@
-.PHONY: help developer restore build rebuild do-lint do-lint-ci test pack clean wasm node-harness release
+.PHONY: help developer restore build rebuild do-lint do-lint-ci test pack clean wasm node-api node-harness release
# Build configuration (Debug or Release)
CONFIG ?= Release
@@ -65,6 +65,10 @@ pack: build
wasm:
./scripts/build-wasm.sh
+# Regenerate the node REST transport from the pinned OpenAPI spec (committed)
+node-api:
+ ./scripts/generate-node-api.sh
+
$(WASM_DEST):
./scripts/build-wasm.sh
@@ -103,6 +107,7 @@ help:
@echo " make do-lint - Lint code with formatting fixes (C# + harness + spelling)"
@echo " make pack - Produce the NuGet packages into $(ARTIFACTS)/"
@echo " make wasm - Build the P1 wasm core from the pinned crates.io release"
+ @echo " make node-api - Regenerate the node REST transport from the pinned OpenAPI spec"
@echo " make clean - Remove build outputs"
@echo ""
@echo "CI Commands:"
diff --git a/README.md b/README.md
index 7405f00..38178ed 100644
--- a/README.md
+++ b/README.md
@@ -49,12 +49,12 @@ using KeetaNet.Anchor.Crypto;
using var runtime = WasmRuntime.Load();
```
-The runtime exposes one factory per domain — `Accounts`, `Certificates`, `KycCertificates`, `Containers`, `Sharables` — plus `CreateKycClient` and `CreateAssetMovementClient` for the networked clients. Every handle-backed object they create implements `IDisposable`. Wrap them in `using` and dispose them before the runtime.
+The runtime exposes one factory per domain - `Accounts`, `Certificates`, `KycCertificates`, `Containers`, `Sharables` - plus `CreateKycClient` and `CreateAssetMovementClient` for the networked clients. Every handle-backed object they create implements `IDisposable`. Wrap them in `using` and dispose them before the runtime.
In an application using `Microsoft.Extensions.DependencyInjection`, register the runtime once instead of threading it around; the container owns its disposal:
```csharp
-// Program.cs — requires the KeetaNet.Anchor.Extensions.DependencyInjection package.
+// Program.cs - requires the KeetaNet.Anchor.Extensions.DependencyInjection package.
builder.Services.AddKeetaNetAnchor();
// Any service: inject the singleton and create what you need per use.
@@ -107,20 +107,20 @@ Provider results use the pending-or-ready shape: `Ready` carries the value, othe
using KycClient kyc = runtime.CreateKycClient(nodeUrl, root, signer);
string[] countries = { "US" };
-IReadOnlyList providers = await kyc.GetProvidersAsync(countries, cancellationToken);
+IReadOnlyList providers = await kyc.GetProviders(countries, cancellationToken);
KycProvider provider = providers[0];
// Start a verification. The user completes it in a browser at WebUrl.
-VerificationOutcome created = await kyc.StartVerificationAsync(provider, countries, cancellationToken: cancellationToken);
+VerificationOutcome created = await kyc.StartVerification(provider, countries, cancellationToken: cancellationToken);
Verification verification = created.Ready!;
Console.WriteLine(verification.WebUrl);
Console.WriteLine(verification.ExpectedCost.Token);
// Poll the provider's decision.
-StatusOutcome status = await kyc.GetVerificationStatusAsync(provider, verification.Id, cancellationToken);
+StatusOutcome status = await kyc.GetVerificationStatus(provider, verification.Id, cancellationToken);
// Fetch the issued chain once ready; a pending fetch reports RetryAfterMs instead.
-CertificatesOutcome outcome = await kyc.GetCertificatesAsync(provider, verification.Id, cancellationToken);
+CertificatesOutcome outcome = await kyc.GetCertificates(provider, verification.Id, cancellationToken);
Certificates issued = outcome.Ready!;
string leafPem = issued.Results[0].Value;
```
@@ -235,13 +235,13 @@ byte[]? signerKey = opened.GetSigningAccount(); // type-prefixed public key, nul
using AssetMovementClient assets = runtime.CreateAssetMovementClient(nodeUrl, root, signer);
// Discovery: all providers, by id, by signer account, or by transfer shape.
-IReadOnlyList providers = await assets.GetProvidersAsync(cancellationToken);
+IReadOnlyList providers = await assets.GetProviders(cancellationToken);
var search = new AssetProviderSearch(Asset: asset, From: "chain:evm:100", To: "chain:keeta:100");
-IReadOnlyList capable = await assets.GetProvidersForTransferAsync(search, cancellationToken);
+IReadOnlyList capable = await assets.GetProvidersForTransfer(search, cancellationToken);
AssetProvider provider = capable[0];
// Check the signer's readiness before transacting.
-AssetAccountStatus account = await assets.GetAccountStatusAsync(provider, cancellationToken);
+AssetAccountStatus account = await assets.GetAccountStatus(provider, cancellationToken);
// Push transfer: simulate first, then promote the simulation.
var request = new AssetTransferRequest(
@@ -249,17 +249,17 @@ var request = new AssetTransferRequest(
From: new AssetTransferSource("chain:evm:100"),
To: new AssetTransferDestination("chain:keeta:100", recipientAddress),
Value: "100");
-AssetSimulatedTransfer simulated = await assets.SimulateTransferAsync(provider, request, cancellationToken);
-AssetTransfer transfer = await simulated.CreateTransferAsync(cancellationToken: cancellationToken);
-AssetTransferStatus status = await transfer.GetTransferStatusAsync(cancellationToken);
+AssetSimulatedTransfer simulated = await assets.SimulateTransfer(provider, request, cancellationToken);
+AssetTransfer transfer = await simulated.CreateTransfer(cancellationToken: cancellationToken);
+AssetTransferStatus status = await transfer.GetTransferStatus(cancellationToken);
// Pull transfer (fiat rails): initiate, pick an instruction, execute.
-AssetTransfer pull = await assets.InitiateTransferAsync(provider, pullRequest, cancellationToken);
+AssetTransfer pull = await assets.InitiateTransfer(provider, pullRequest, cancellationToken);
var instruction = new AssetPullInstruction("ACH_DEBIT", pull.InstructionChoices[0].GetProperty("pullFrom"));
-AssetTransferStatus executed = await pull.ExecuteTransferAsync(instruction, cancellationToken);
+AssetTransferStatus executed = await pull.ExecuteTransfer(instruction, cancellationToken);
// Share KYC attributes; a pending outcome polls its promise URL inside the core.
-AssetShareKycOutcome shared = await assets.ShareKycAttributesAndWaitAsync(
+AssetShareKycOutcome shared = await assets.ShareKycAttributesAndWait(
provider,
new AssetShareKycRequest(exportedAttributes),
pollInterval: TimeSpan.FromSeconds(1),
@@ -267,7 +267,7 @@ AssetShareKycOutcome shared = await assets.ShareKycAttributesAndWaitAsync(
cancellationToken: cancellationToken);
```
-Persistent forwarding follows the same pattern: `InitiatePersistentForwardingTemplateAsync` / `CreatePersistentForwardingTemplateAsync` / `CreatePersistentForwardingAddressAsync` create, the `List*Async` methods page, and the `Deactivate*Async` methods retire.
+Persistent forwarding follows the same pattern: `InitiatePersistentForwardingTemplate` / `CreatePersistentForwardingTemplate` / `CreatePersistentForwardingAddress` create, the `List*` methods page, and the `Deactivate*` methods retire.
### Errors
@@ -276,7 +276,7 @@ Every failure surfaced from the core throws `KeetaException`: a stable machine-r
```csharp
try
{
- await assets.InitiateTransferAsync(provider, request, cancellationToken);
+ await assets.InitiateTransfer(provider, request, cancellationToken);
}
catch (KeetaException error)
{
@@ -295,7 +295,7 @@ Interop is Wasmtime-hosted WebAssembly, not native P/Invoke. Guest memory is a s
### SDK Rules:
- **Thread-safe by construction.** A `WasmRuntime` owns one Wasmtime `Store`, which cannot be used from more than one thread. The runtime confines it to a dedicated dispatcher thread and serializes every call onto it, so any thread may use the SDK: offline operations dispatch synchronously, networked client operations are `async` and accept a `CancellationToken`.
-- **Deterministic disposal.** Handle-backed types (`Account`, `Certificate`, `KycCertificate`, containers, the clients) implement `IDisposable` and release their wasm handle on `Dispose`. Wrap them in `using`. A finalizer backstop reclaims forgotten handles by enqueueing the free onto the dispatcher, but deterministic disposal remains the contract.
+- **Deterministic disposal.** Handle-backed types (`Account`, `Certificate`, `KycCertificate`, containers, the clients) implement `IDisposable` and release their wasm handle on `Dispose`. Wrap them in `using`.
## License
diff --git a/cspell.yaml b/cspell.yaml
index 561b183..fccfea9 100644
--- a/cspell.yaml
+++ b/cspell.yaml
@@ -56,7 +56,10 @@ words:
- belgrave
- idisp
- lifecycles
+ - renderable
- tlsv
+ - csclient
+ - nswag
- rasn
- rustfmt
- userprofile
@@ -66,3 +69,5 @@ words:
- msys
- maxdepth
- powershell
+ - renderable
+ - solana
diff --git a/scripts/build-wasm.sh b/scripts/build-wasm.sh
index 7f9af07..fba52ba 100755
--- a/scripts/build-wasm.sh
+++ b/scripts/build-wasm.sh
@@ -3,26 +3,17 @@
# the library embeds it.
set -euo pipefail
-CRATE_NAME="keetanetwork-anchor-client-wasi"
-CRATE_VERSION="0.1.1"
-CRATE_SHA256="dd5c19da144a320d23d5b0b1af392ea6c94f6b7cb584e490bcf42155c768d9d6"
-
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
+
+# shellcheck source=scripts/pins.env
+source "${ROOT}/scripts/pins.env"
+# shellcheck source=scripts/fetch-crate.sh
+source "${ROOT}/scripts/fetch-crate.sh"
+
BUILD_DIR="${ROOT}/.wasm-build"
-TARBALL="${BUILD_DIR}/${CRATE_NAME}-${CRATE_VERSION}.crate"
-CRATE_DIR="${BUILD_DIR}/${CRATE_NAME}-${CRATE_VERSION}"
+CRATE_DIR="${BUILD_DIR}/${ANCHOR_WASI_CRATE}-${ANCHOR_WASI_VERSION}"
ARTIFACT="${CRATE_DIR}/target/wasm32-wasip1/release/keetanetwork_anchor_client_wasi.wasm"
DEST="${ROOT}/src/KeetaNet.Anchor/wasm/keetanetwork_anchor_client_wasi.wasm"
-DOWNLOAD_URL="https://crates.io/api/v1/crates/${CRATE_NAME}/${CRATE_VERSION}/download"
-
-checksum() {
- local file="$1"
- if command -v sha256sum >/dev/null 2>&1; then
- sha256sum "${file}" | cut -d' ' -f1
- else
- shasum -a 256 "${file}" | cut -d' ' -f1
- fi
-}
# rasn-compiler probes `$CARGO_HOME/bin/rustfmt` and `$CARGO`-adjacent
# `rustfmt` without the .exe suffix. When both miss it silently emits
@@ -102,31 +93,6 @@ require_wasip1_target() {
fi
}
-fetch_crate() {
- mkdir -p "${BUILD_DIR}"
- if [[ ! -f "${TARBALL}" ]]; then
- echo "build-wasm: downloading ${CRATE_NAME} ${CRATE_VERSION} from crates.io"
- # crates.io rejects requests without a User-Agent.
- curl --fail --silent --show-error --location \
- --proto '=https' --tlsv1.2 \
- --user-agent "anchor-csharp build-wasm (https://github.com/KeetaNetwork/anchor-csharp)" \
- --output "${TARBALL}" "${DOWNLOAD_URL}"
- fi
-
- actual="$(checksum "${TARBALL}")"
- if [[ "${actual}" != "${CRATE_SHA256}" ]]; then
- rm -f "${TARBALL}"
- echo "build-wasm: checksum mismatch for ${TARBALL}" >&2
- echo " expected: ${CRATE_SHA256}" >&2
- echo " actual: ${actual}" >&2
- exit 1
- fi
-
- if [[ ! -d "${CRATE_DIR}" ]]; then
- tar --extract --gzip --file "${TARBALL}" --directory "${BUILD_DIR}"
- fi
-}
-
build_artifact() {
echo "build-wasm: cargo build (wasm32-wasip1, features p1, release)"
cargo build \
@@ -139,7 +105,7 @@ build_artifact() {
shim_rustfmt_for_windows
require_wasip1_target
-fetch_crate
+fetch_crate "${ANCHOR_WASI_CRATE}" "${ANCHOR_WASI_VERSION}" "${ANCHOR_WASI_SHA256}" "${BUILD_DIR}"
purge_stale_asn1_outputs
build_artifact
diff --git a/scripts/fetch-crate.sh b/scripts/fetch-crate.sh
new file mode 100644
index 0000000..a73d737
--- /dev/null
+++ b/scripts/fetch-crate.sh
@@ -0,0 +1,46 @@
+# Shared pinned-crate fetch for the generation scripts. Source this file and
+# call `fetch_crate`.
+
+checksum() {
+ local file="$1"
+ if command -v sha256sum >/dev/null 2>&1; then
+ sha256sum "${file}" | cut -d' ' -f1
+ else
+ shasum -a 256 "${file}" | cut -d' ' -f1
+ fi
+}
+
+# fetch_crate
+#
+# Download the pinned crates.io release into ``, verify its SHA-256,
+# and extract it to `/-` (skipping work already done).
+fetch_crate() {
+ local name="$1" version="$2" expected="$3" build_dir="$4"
+ local tarball="${build_dir}/${name}-${version}.crate"
+ local crate_dir="${build_dir}/${name}-${version}"
+ local url="https://crates.io/api/v1/crates/${name}/${version}/download"
+
+ mkdir -p "${build_dir}"
+ if [[ ! -f "${tarball}" ]]; then
+ echo "fetch-crate: downloading ${name} ${version} from crates.io"
+ # crates.io rejects requests without a User-Agent.
+ curl --fail --silent --show-error --location \
+ --proto '=https' --tlsv1.2 \
+ --user-agent "anchor-csharp fetch-crate (https://github.com/KeetaNetwork/anchor-csharp)" \
+ --output "${tarball}" "${url}"
+ fi
+
+ local actual
+ actual="$(checksum "${tarball}")"
+ if [[ "${actual}" != "${expected}" ]]; then
+ rm -f "${tarball}"
+ echo "fetch-crate: checksum mismatch for ${tarball}" >&2
+ echo " expected: ${expected}" >&2
+ echo " actual: ${actual}" >&2
+ exit 1
+ fi
+
+ if [[ ! -d "${crate_dir}" ]]; then
+ tar --extract --gzip --file "${tarball}" --directory "${build_dir}"
+ fi
+}
diff --git a/scripts/generate-node-api.sh b/scripts/generate-node-api.sh
new file mode 100644
index 0000000..30319fc
--- /dev/null
+++ b/scripts/generate-node-api.sh
@@ -0,0 +1,39 @@
+#!/usr/bin/env bash
+# Generate the node REST transport (models + typed operations) from the
+# canonical OpenAPI spec shipped in the pinned keetanetwork-client crate.
+set -euo pipefail
+
+ROOT="$(cd "$(dirname "$0")/.." && pwd)"
+
+# shellcheck source=scripts/pins.env
+source "${ROOT}/scripts/pins.env"
+# shellcheck source=scripts/fetch-crate.sh
+source "${ROOT}/scripts/fetch-crate.sh"
+
+BUILD_DIR="${ROOT}/.node-api-build"
+CRATE_DIR="${BUILD_DIR}/${NODE_CLIENT_CRATE}-${NODE_CLIENT_VERSION}"
+SPEC="${CRATE_DIR}/openapi/keetanet-node.yaml"
+DEST="${ROOT}/src/KeetaNet.Anchor/Generated/Node/NodeApi.cs"
+
+generate_client() {
+ echo "generate-node-api: nswag openapi2csclient from ${SPEC}"
+ mkdir -p "$(dirname "${DEST}")"
+ (cd "${ROOT}" && dotnet tool restore >/dev/null && dotnet nswag openapi2csclient \
+ "/input:${SPEC}" \
+ /classname:NodeApi \
+ /namespace:KeetaNet.Anchor.Generated.Node \
+ "/output:${DEST}" \
+ /jsonLibrary:SystemTextJson \
+ /injectHttpClient:true \
+ /disposeHttpClient:false \
+ /generateClientInterfaces:false \
+ /generateOptionalParameters:true \
+ /generateNativeRecords:true \
+ /useBaseUrl:true \
+ /generateBaseUrlProperty:true \
+ /exceptionClass:NodeApiException)
+}
+
+fetch_crate "${NODE_CLIENT_CRATE}" "${NODE_CLIENT_VERSION}" "${NODE_CLIENT_SHA256}" "${BUILD_DIR}"
+generate_client
+echo "generate-node-api: node transport at ${DEST}"
diff --git a/scripts/pins.env b/scripts/pins.env
new file mode 100644
index 0000000..42d53cf
--- /dev/null
+++ b/scripts/pins.env
@@ -0,0 +1,12 @@
+# Every pinned upstream crate release, in one place. Sourced by the scripts;
+# bump versions and checksums here only.
+
+# The wasm core (scripts/build-wasm.sh).
+ANCHOR_WASI_CRATE="keetanetwork-anchor-client-wasi"
+ANCHOR_WASI_VERSION="0.2.0"
+ANCHOR_WASI_SHA256="19c51c46f5efa58bcf4a37e324090bb91644ede5fcb0ae916983214e9092ff42"
+
+# The canonical node OpenAPI spec (scripts/generate-node-api.sh).
+NODE_CLIENT_CRATE="keetanetwork-client"
+NODE_CLIENT_VERSION="0.4.0"
+NODE_CLIENT_SHA256="b43d33dc69ca0571499c19046b016271b66a7f4d9b2aa5539a2351b4c3231270"
diff --git a/src/KeetaNet.Anchor.Extensions.DependencyInjection/KeetaNet.Anchor.Extensions.DependencyInjection.csproj b/src/KeetaNet.Anchor.Extensions.DependencyInjection/KeetaNet.Anchor.Extensions.DependencyInjection.csproj
index 7a2895b..629f48e 100644
--- a/src/KeetaNet.Anchor.Extensions.DependencyInjection/KeetaNet.Anchor.Extensions.DependencyInjection.csproj
+++ b/src/KeetaNet.Anchor.Extensions.DependencyInjection/KeetaNet.Anchor.Extensions.DependencyInjection.csproj
@@ -13,6 +13,7 @@
+
diff --git a/src/KeetaNet.Anchor.Extensions.DependencyInjection/KeetaNetAnchorServiceCollectionExtensions.cs b/src/KeetaNet.Anchor.Extensions.DependencyInjection/KeetaNetAnchorServiceCollectionExtensions.cs
index 8254b12..a966076 100644
--- a/src/KeetaNet.Anchor.Extensions.DependencyInjection/KeetaNetAnchorServiceCollectionExtensions.cs
+++ b/src/KeetaNet.Anchor.Extensions.DependencyInjection/KeetaNetAnchorServiceCollectionExtensions.cs
@@ -19,4 +19,20 @@ public static IServiceCollection AddKeetaNetAnchor(this IServiceCollection servi
services.TryAddSingleton(static _ => WasmRuntime.Load());
return services;
}
+
+ ///
+ /// Register a for the node API at
+ /// as a typed HTTP client, so its
+ /// comes from
+ /// IHttpClientFactory (pooled handlers, policy-friendly).
+ ///
+ /// The same collection, for chaining.
+ public static IServiceCollection AddKeetaNetAnchorNodeClient(this IServiceCollection services, string nodeUrl)
+ {
+ services.AddKeetaNetAnchor();
+ services.AddHttpClient(nameof(NodeClient))
+ .AddTypedClient((http, provider) =>
+ provider.GetRequiredService().CreateNodeClient(nodeUrl, http));
+ return services;
+ }
}
diff --git a/src/KeetaNet.Anchor/Crypto/AccountFactory.cs b/src/KeetaNet.Anchor/Crypto/AccountFactory.cs
index 5be11b6..d9c0fb0 100644
--- a/src/KeetaNet.Anchor/Crypto/AccountFactory.cs
+++ b/src/KeetaNet.Anchor/Crypto/AccountFactory.cs
@@ -2,7 +2,7 @@ namespace KeetaNet.Anchor.Crypto;
///
/// Creates objects owned by one runtime. Reached through
-/// ; every account it creates must be
+/// . Every account it creates must be
/// disposed before that runtime.
///
public sealed class AccountFactory
diff --git a/src/KeetaNet.Anchor/Crypto/Certificate.cs b/src/KeetaNet.Anchor/Crypto/Certificate.cs
index 8c76c83..9d5c543 100644
--- a/src/KeetaNet.Anchor/Crypto/Certificate.cs
+++ b/src/KeetaNet.Anchor/Crypto/Certificate.cs
@@ -58,5 +58,12 @@ public DateTimeOffset NotAfter
///
public string SubjectPublicKey => Runtime.CertificateSubjectPublicKey(Handle);
+ ///
+ /// The SHA3-256 of the certificate's DER: the key the ledger stores a
+ /// published certificate under, so it feeds
+ /// .
+ ///
+ public CertificateHash Hash => CertificateHash.Parse(Runtime.CertificateHash(Convert.ToHexString(ToDer())));
+
private protected override void Release(WasmRuntime runtime, int handle) => runtime.CertificateFree(handle);
}
diff --git a/src/KeetaNet.Anchor/Crypto/EncryptedContainerFactory.cs b/src/KeetaNet.Anchor/Crypto/EncryptedContainerFactory.cs
index 73faa53..d919430 100644
--- a/src/KeetaNet.Anchor/Crypto/EncryptedContainerFactory.cs
+++ b/src/KeetaNet.Anchor/Crypto/EncryptedContainerFactory.cs
@@ -12,8 +12,8 @@ public sealed class EncryptedContainerFactory
///
/// Build a plaintext container. A non-empty set
- /// seals it to those accounts; attaches a detached
- /// signature; overrides the default plaintext policy.
+ /// seals it to those accounts. attaches a detached
+ /// signature. overrides the default plaintext policy.
///
public EncryptedContainer FromPlaintext(
byte[] data,
diff --git a/src/KeetaNet.Anchor/Crypto/Hashes.cs b/src/KeetaNet.Anchor/Crypto/Hashes.cs
new file mode 100644
index 0000000..23912bc
--- /dev/null
+++ b/src/KeetaNet.Anchor/Crypto/Hashes.cs
@@ -0,0 +1,111 @@
+namespace KeetaNet.Anchor.Crypto;
+
+///
+/// The hash of a block, as the reference implementations model it (a 32-byte
+/// value type, not a wasm handle). Hex is the transport form: parse with
+/// , emit with .
+///
+public readonly struct BlockHash : IEquatable
+{
+ /// The block hash length in bytes.
+ public const int Length = 32;
+
+ private readonly string _hex;
+
+ private BlockHash(string normalizedHex)
+ {
+ _hex = normalizedHex;
+ }
+
+ /// Parse a 64-character hex block hash. Case is normalized away.
+ public static BlockHash Parse(string hex) => new(HexValue.Normalize(hex, Length));
+
+ /// The raw 32 hash bytes.
+ public byte[] ToBytes() => Convert.FromHexString(_hex);
+
+ /// The lowercase hex transport form.
+ public override string ToString() => _hex ?? "";
+
+ ///
+ public bool Equals(BlockHash other) => string.Equals(_hex, other._hex, StringComparison.Ordinal);
+
+ ///
+ public override bool Equals(object? obj) => obj is BlockHash other && Equals(other);
+
+ ///
+ public override int GetHashCode() => _hex is null ? 0 : _hex.GetHashCode(StringComparison.Ordinal);
+
+ /// Value equality over the hash bytes.
+ public static bool operator ==(BlockHash left, BlockHash right) => left.Equals(right);
+
+ /// Value inequality over the hash bytes.
+ public static bool operator !=(BlockHash left, BlockHash right) => !left.Equals(right);
+}
+
+///
+/// The hash a published certificate is stored under on the ledger: the
+/// SHA3-256 of its DER, as the reference implementations model it.
+///
+public readonly struct CertificateHash : IEquatable
+{
+ /// The SHA3-256 certificate hash length in bytes.
+ public const int Length = 32;
+
+ private readonly string _hex;
+
+ private CertificateHash(string normalizedHex)
+ {
+ _hex = normalizedHex;
+ }
+
+ /// Parse a 64-character hex certificate hash. Case is normalized away.
+ public static CertificateHash Parse(string hex) => new(HexValue.Normalize(hex, Length));
+
+ /// The raw 32 hash bytes.
+ public byte[] ToBytes() => Convert.FromHexString(_hex);
+
+ /// The lowercase hex transport form.
+ public override string ToString() => _hex ?? "";
+
+ ///
+ public bool Equals(CertificateHash other) => string.Equals(_hex, other._hex, StringComparison.Ordinal);
+
+ ///
+ public override bool Equals(object? obj) => obj is CertificateHash other && Equals(other);
+
+ ///
+ public override int GetHashCode() => _hex is null ? 0 : _hex.GetHashCode(StringComparison.Ordinal);
+
+ /// Value equality over the hash bytes.
+ public static bool operator ==(CertificateHash left, CertificateHash right) => left.Equals(right);
+
+ /// Value inequality over the hash bytes.
+ public static bool operator !=(CertificateHash left, CertificateHash right) => !left.Equals(right);
+}
+
+/// Shared validation for the hex-transported hash value types.
+internal static class HexValue
+{
+ ///
+ /// Validate that encodes exactly
+ /// bytes and lowercase it, so equality is
+ /// byte equality regardless of the producer's casing.
+ ///
+ public static string Normalize(string hex, int expectedBytes)
+ {
+ if (hex.Length != expectedBytes * 2)
+ {
+ throw new KeetaException("HASH_LENGTH", $"expected {expectedBytes * 2} hex characters, got {hex.Length}");
+ }
+
+ try
+ {
+ byte[] value = Convert.FromHexString(hex);
+ return Convert.ToHexString(value).ToLowerInvariant();
+ }
+ catch (FormatException error)
+ {
+ throw new KeetaException("HASH_FORMAT", "the hash is not valid hex", error);
+ }
+ }
+}
diff --git a/src/KeetaNet.Anchor/Crypto/KycCertificate.cs b/src/KeetaNet.Anchor/Crypto/KycCertificate.cs
index ef434ed..8493ca4 100644
--- a/src/KeetaNet.Anchor/Crypto/KycCertificate.cs
+++ b/src/KeetaNet.Anchor/Crypto/KycCertificate.cs
@@ -124,7 +124,7 @@ private sealed record AttributeEntry(string Name, bool Sensitive);
///
/// A fluent builder for a KYC leaf certificate: collect a subject, issuer,
/// validity window, and attributes, then the signed
-/// leaf. Sensitive attributes are encrypted to the subject; the issuer
+/// leaf. Sensitive attributes are encrypted to the subject. The issuer
/// signs. The subject and issuer may use different signing algorithms.
///
public sealed class KycCertificateBuilder
@@ -142,7 +142,7 @@ public sealed class KycCertificateBuilder
internal KycCertificateBuilder(WasmRuntime runtime) => _runtime = runtime;
- /// The subject the leaf is issued to; sensitive attributes encrypt to its key.
+ /// The subject the leaf is issued to. Sensitive attributes encrypt to its key.
/// A read-only (public-key) account suffices to issue.
public KycCertificateBuilder Subject(Account subject)
{
diff --git a/src/KeetaNet.Anchor/Generated/Node/NodeApi.cs b/src/KeetaNet.Anchor/Generated/Node/NodeApi.cs
new file mode 100644
index 0000000..12d0896
--- /dev/null
+++ b/src/KeetaNet.Anchor/Generated/Node/NodeApi.cs
@@ -0,0 +1,3977 @@
+//----------------------
+//
+// Generated using the NSwag toolchain v14.7.1.0 (NJsonSchema v11.6.1.0 (Newtonsoft.Json v13.0.0.0)) (http://NSwag.org)
+//
+//----------------------
+
+#pragma warning disable 108 // Disable "CS0108 '{derivedDto}.ToJson()' hides inherited member '{dtoBase}.ToJson()'. Use the new keyword if hiding was intended."
+#pragma warning disable 114 // Disable "CS0114 '{derivedDto}.RaisePropertyChanged(String)' hides inherited member 'dtoBase.RaisePropertyChanged(String)'. To make the current member override that implementation, add the override keyword. Otherwise add the new keyword."
+#pragma warning disable 472 // Disable "CS0472 The result of the expression is always 'false' since a value of type 'Int32' is never equal to 'null' of type 'Int32?'
+#pragma warning disable 612 // Disable "CS0612 '...' is obsolete"
+#pragma warning disable 649 // Disable "CS0649 Field is never assigned to, and will always have its default value null"
+#pragma warning disable 1573 // Disable "CS1573 Parameter '...' has no matching param tag in the XML comment for ...
+#pragma warning disable 1591 // Disable "CS1591 Missing XML comment for publicly visible type or member ..."
+#pragma warning disable 8073 // Disable "CS8073 The result of the expression is always 'false' since a value of type 'T' is never equal to 'null' of type 'T?'"
+#pragma warning disable 3016 // Disable "CS3016 Arrays as attribute arguments is not CLS-compliant"
+#pragma warning disable 8600 // Disable "CS8600 Converting null literal or possible null value to non-nullable type"
+#pragma warning disable 8602 // Disable "CS8602 Dereference of a possibly null reference"
+#pragma warning disable 8603 // Disable "CS8603 Possible null reference return"
+#pragma warning disable 8604 // Disable "CS8604 Possible null reference argument for parameter"
+#pragma warning disable 8625 // Disable "CS8625 Cannot convert null literal to non-nullable reference type"
+#pragma warning disable 8765 // Disable "CS8765 Nullability of type of parameter doesn't match overridden member (possibly because of nullability attributes)."
+
+namespace KeetaNet.Anchor.Generated.Node
+{
+ using System = global::System;
+
+ [System.CodeDom.Compiler.GeneratedCode("NSwag", "14.7.1.0 (NJsonSchema v11.6.1.0 (Newtonsoft.Json v13.0.0.0))")]
+ public partial class NodeApi
+ {
+ #pragma warning disable 8618
+ private string _baseUrl;
+ #pragma warning restore 8618
+
+ private System.Net.Http.HttpClient _httpClient;
+ private static System.Lazy _settings = new System.Lazy(CreateSerializerSettings, true);
+ private System.Text.Json.JsonSerializerOptions _instanceSettings;
+
+ #pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable.
+ public NodeApi(System.Net.Http.HttpClient httpClient)
+ #pragma warning restore CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable.
+ {
+ BaseUrl = "http://localhost:8080/api";
+ _httpClient = httpClient;
+ Initialize();
+ }
+
+ private static System.Text.Json.JsonSerializerOptions CreateSerializerSettings()
+ {
+ var settings = new System.Text.Json.JsonSerializerOptions();
+ UpdateJsonSerializerSettings(settings);
+ return settings;
+ }
+
+ public string BaseUrl
+ {
+ get { return _baseUrl; }
+ set
+ {
+ _baseUrl = value;
+ if (!string.IsNullOrEmpty(_baseUrl) && !_baseUrl.EndsWith("/"))
+ _baseUrl += '/';
+ }
+ }
+
+ protected System.Text.Json.JsonSerializerOptions JsonSerializerSettings { get { return _instanceSettings ?? _settings.Value; } }
+
+ static partial void UpdateJsonSerializerSettings(System.Text.Json.JsonSerializerOptions settings);
+
+ partial void Initialize();
+
+ partial void PrepareRequest(System.Net.Http.HttpClient client, System.Net.Http.HttpRequestMessage request, string url);
+ partial void PrepareRequest(System.Net.Http.HttpClient client, System.Net.Http.HttpRequestMessage request, System.Text.StringBuilder urlBuilder);
+ partial void ProcessResponse(System.Net.Http.HttpClient client, System.Net.Http.HttpResponseMessage response);
+
+ /// A cancellation token that can be used by other objects or threads to receive notice of cancellation.
+ ///
+ /// Create a new vote
+ ///
+ ///
+ /// Submit blocks to a representative and receive a vote for them.
+ ///
+ /// Vote created successfully
+ /// A server side error occurred.
+ public virtual async System.Threading.Tasks.Task CreateVoteAsync(Body body, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken))
+ {
+ if (body == null)
+ throw new System.ArgumentNullException("body");
+
+ var client_ = _httpClient;
+ var disposeClient_ = false;
+ try
+ {
+ using (var request_ = new System.Net.Http.HttpRequestMessage())
+ {
+ var json_ = System.Text.Json.JsonSerializer.SerializeToUtf8Bytes(body, JsonSerializerSettings);
+ var content_ = new System.Net.Http.ByteArrayContent(json_);
+ content_.Headers.ContentType = System.Net.Http.Headers.MediaTypeHeaderValue.Parse("application/json");
+ request_.Content = content_;
+ request_.Method = new System.Net.Http.HttpMethod("POST");
+ request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("application/json"));
+
+ var urlBuilder_ = new System.Text.StringBuilder();
+ if (!string.IsNullOrEmpty(_baseUrl)) urlBuilder_.Append(_baseUrl);
+ // Operation Path: "vote"
+ urlBuilder_.Append("vote");
+
+ PrepareRequest(client_, request_, urlBuilder_);
+
+ var url_ = urlBuilder_.ToString();
+ request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute);
+
+ PrepareRequest(client_, request_, url_);
+
+ var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false);
+ var disposeResponse_ = true;
+ try
+ {
+ var headers_ = new System.Collections.Generic.Dictionary>();
+ foreach (var item_ in response_.Headers)
+ headers_[item_.Key] = item_.Value;
+ if (response_.Content != null && response_.Content.Headers != null)
+ {
+ foreach (var item_ in response_.Content.Headers)
+ headers_[item_.Key] = item_.Value;
+ }
+
+ ProcessResponse(client_, response_);
+
+ var status_ = (int)response_.StatusCode;
+ if (status_ == 200)
+ {
+ var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false);
+ if (objectResponse_.Object == null)
+ {
+ throw new NodeApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null);
+ }
+ return objectResponse_.Object;
+ }
+ else
+ if (status_ == 500)
+ {
+ var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false);
+ if (objectResponse_.Object == null)
+ {
+ throw new NodeApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null);
+ }
+ throw new NodeApiException("Server error", status_, objectResponse_.Text, headers_, objectResponse_.Object, null);
+ }
+ else
+ {
+ var responseData_ = response_.Content == null ? null : await ReadAsStringAsync(response_.Content, cancellationToken).ConfigureAwait(false);
+ throw new NodeApiException("The HTTP status code of the response was not expected (" + status_ + ").", status_, responseData_, headers_, null);
+ }
+ }
+ finally
+ {
+ if (disposeResponse_)
+ response_.Dispose();
+ }
+ }
+ }
+ finally
+ {
+ if (disposeClient_)
+ client_.Dispose();
+ }
+ }
+
+ /// A cancellation token that can be used by other objects or threads to receive notice of cancellation.
+ ///
+ /// Create a vote quote
+ ///
+ ///
+ /// Generate a vote quote for the given blocks.
+ ///
+ /// Quote created successfully
+ /// A server side error occurred.
+ public virtual async System.Threading.Tasks.Task CreateVoteQuoteAsync(Body2 body, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken))
+ {
+ if (body == null)
+ throw new System.ArgumentNullException("body");
+
+ var client_ = _httpClient;
+ var disposeClient_ = false;
+ try
+ {
+ using (var request_ = new System.Net.Http.HttpRequestMessage())
+ {
+ var json_ = System.Text.Json.JsonSerializer.SerializeToUtf8Bytes(body, JsonSerializerSettings);
+ var content_ = new System.Net.Http.ByteArrayContent(json_);
+ content_.Headers.ContentType = System.Net.Http.Headers.MediaTypeHeaderValue.Parse("application/json");
+ request_.Content = content_;
+ request_.Method = new System.Net.Http.HttpMethod("POST");
+ request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("application/json"));
+
+ var urlBuilder_ = new System.Text.StringBuilder();
+ if (!string.IsNullOrEmpty(_baseUrl)) urlBuilder_.Append(_baseUrl);
+ // Operation Path: "vote/quote"
+ urlBuilder_.Append("vote/quote");
+
+ PrepareRequest(client_, request_, urlBuilder_);
+
+ var url_ = urlBuilder_.ToString();
+ request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute);
+
+ PrepareRequest(client_, request_, url_);
+
+ var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false);
+ var disposeResponse_ = true;
+ try
+ {
+ var headers_ = new System.Collections.Generic.Dictionary>();
+ foreach (var item_ in response_.Headers)
+ headers_[item_.Key] = item_.Value;
+ if (response_.Content != null && response_.Content.Headers != null)
+ {
+ foreach (var item_ in response_.Content.Headers)
+ headers_[item_.Key] = item_.Value;
+ }
+
+ ProcessResponse(client_, response_);
+
+ var status_ = (int)response_.StatusCode;
+ if (status_ == 200)
+ {
+ var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false);
+ if (objectResponse_.Object == null)
+ {
+ throw new NodeApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null);
+ }
+ return objectResponse_.Object;
+ }
+ else
+ if (status_ == 500)
+ {
+ var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false);
+ if (objectResponse_.Object == null)
+ {
+ throw new NodeApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null);
+ }
+ throw new NodeApiException("Server error", status_, objectResponse_.Text, headers_, objectResponse_.Object, null);
+ }
+ else
+ {
+ var responseData_ = response_.Content == null ? null : await ReadAsStringAsync(response_.Content, cancellationToken).ConfigureAwait(false);
+ throw new NodeApiException("The HTTP status code of the response was not expected (" + status_ + ").", status_, responseData_, headers_, null);
+ }
+ }
+ finally
+ {
+ if (disposeResponse_)
+ response_.Dispose();
+ }
+ }
+ }
+ finally
+ {
+ if (disposeClient_)
+ client_.Dispose();
+ }
+ }
+
+ /// A cancellation token that can be used by other objects or threads to receive notice of cancellation.
+ ///
+ /// Get votes for a block
+ ///
+ ///
+ /// Retrieve the votes the node holds for a specific block hash.
+ ///
+ /// 64-character hexadecimal block hash
+ /// Which ledger storage to read votes from (defaults to main).
+ /// Votes retrieved successfully
+ /// A server side error occurred.
+ public virtual async System.Threading.Tasks.Task GetBlockVotesAsync(string blockhash, Side? side = null, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken))
+ {
+ if (blockhash == null)
+ throw new System.ArgumentNullException("blockhash");
+
+ var client_ = _httpClient;
+ var disposeClient_ = false;
+ try
+ {
+ using (var request_ = new System.Net.Http.HttpRequestMessage())
+ {
+ request_.Method = new System.Net.Http.HttpMethod("GET");
+ request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("application/json"));
+
+ var urlBuilder_ = new System.Text.StringBuilder();
+ if (!string.IsNullOrEmpty(_baseUrl)) urlBuilder_.Append(_baseUrl);
+ // Operation Path: "vote/{blockhash}"
+ urlBuilder_.Append("vote/");
+ urlBuilder_.Append(System.Uri.EscapeDataString(ConvertToString(blockhash, System.Globalization.CultureInfo.InvariantCulture)));
+ urlBuilder_.Append('?');
+ if (side != null)
+ {
+ urlBuilder_.Append(System.Uri.EscapeDataString("side")).Append('=').Append(System.Uri.EscapeDataString(ConvertToString(side, System.Globalization.CultureInfo.InvariantCulture))).Append('&');
+ }
+ urlBuilder_.Length--;
+
+ PrepareRequest(client_, request_, urlBuilder_);
+
+ var url_ = urlBuilder_.ToString();
+ request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute);
+
+ PrepareRequest(client_, request_, url_);
+
+ var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false);
+ var disposeResponse_ = true;
+ try
+ {
+ var headers_ = new System.Collections.Generic.Dictionary>();
+ foreach (var item_ in response_.Headers)
+ headers_[item_.Key] = item_.Value;
+ if (response_.Content != null && response_.Content.Headers != null)
+ {
+ foreach (var item_ in response_.Content.Headers)
+ headers_[item_.Key] = item_.Value;
+ }
+
+ ProcessResponse(client_, response_);
+
+ var status_ = (int)response_.StatusCode;
+ if (status_ == 200)
+ {
+ var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false);
+ if (objectResponse_.Object == null)
+ {
+ throw new NodeApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null);
+ }
+ return objectResponse_.Object;
+ }
+ else
+ if (status_ == 500)
+ {
+ var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false);
+ if (objectResponse_.Object == null)
+ {
+ throw new NodeApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null);
+ }
+ throw new NodeApiException("Server error", status_, objectResponse_.Text, headers_, objectResponse_.Object, null);
+ }
+ else
+ {
+ var responseData_ = response_.Content == null ? null : await ReadAsStringAsync(response_.Content, cancellationToken).ConfigureAwait(false);
+ throw new NodeApiException("The HTTP status code of the response was not expected (" + status_ + ").", status_, responseData_, headers_, null);
+ }
+ }
+ finally
+ {
+ if (disposeResponse_)
+ response_.Dispose();
+ }
+ }
+ }
+ finally
+ {
+ if (disposeClient_)
+ client_.Dispose();
+ }
+ }
+
+ /// A cancellation token that can be used by other objects or threads to receive notice of cancellation.
+ ///
+ /// Get node version
+ ///
+ ///
+ /// Retrieve the version of this node software.
+ ///
+ /// Version retrieved successfully
+ /// A server side error occurred.
+ public virtual async System.Threading.Tasks.Task GetNodeVersionAsync(System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken))
+ {
+ var client_ = _httpClient;
+ var disposeClient_ = false;
+ try
+ {
+ using (var request_ = new System.Net.Http.HttpRequestMessage())
+ {
+ request_.Method = new System.Net.Http.HttpMethod("GET");
+ request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("application/json"));
+
+ var urlBuilder_ = new System.Text.StringBuilder();
+ if (!string.IsNullOrEmpty(_baseUrl)) urlBuilder_.Append(_baseUrl);
+ // Operation Path: "node/version"
+ urlBuilder_.Append("node/version");
+
+ PrepareRequest(client_, request_, urlBuilder_);
+
+ var url_ = urlBuilder_.ToString();
+ request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute);
+
+ PrepareRequest(client_, request_, url_);
+
+ var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false);
+ var disposeResponse_ = true;
+ try
+ {
+ var headers_ = new System.Collections.Generic.Dictionary>();
+ foreach (var item_ in response_.Headers)
+ headers_[item_.Key] = item_.Value;
+ if (response_.Content != null && response_.Content.Headers != null)
+ {
+ foreach (var item_ in response_.Content.Headers)
+ headers_[item_.Key] = item_.Value;
+ }
+
+ ProcessResponse(client_, response_);
+
+ var status_ = (int)response_.StatusCode;
+ if (status_ == 200)
+ {
+ var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false);
+ if (objectResponse_.Object == null)
+ {
+ throw new NodeApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null);
+ }
+ return objectResponse_.Object;
+ }
+ else
+ if (status_ == 500)
+ {
+ var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false);
+ if (objectResponse_.Object == null)
+ {
+ throw new NodeApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null);
+ }
+ throw new NodeApiException("Server error", status_, objectResponse_.Text, headers_, objectResponse_.Object, null);
+ }
+ else
+ {
+ var responseData_ = response_.Content == null ? null : await ReadAsStringAsync(response_.Content, cancellationToken).ConfigureAwait(false);
+ throw new NodeApiException("The HTTP status code of the response was not expected (" + status_ + ").", status_, responseData_, headers_, null);
+ }
+ }
+ finally
+ {
+ if (disposeResponse_)
+ response_.Dispose();
+ }
+ }
+ }
+ finally
+ {
+ if (disposeClient_)
+ client_.Dispose();
+ }
+ }
+
+ /// A cancellation token that can be used by other objects or threads to receive notice of cancellation.
+ ///
+ /// Get account state
+ ///
+ ///
+ /// Retrieve the head, representative, balances, and info for an account.
+ ///
+ /// Account public key
+ /// Account state retrieved successfully
+ /// A server side error occurred.
+ public virtual async System.Threading.Tasks.Task GetAccountStateAsync(string account, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken))
+ {
+ if (account == null)
+ throw new System.ArgumentNullException("account");
+
+ var client_ = _httpClient;
+ var disposeClient_ = false;
+ try
+ {
+ using (var request_ = new System.Net.Http.HttpRequestMessage())
+ {
+ request_.Method = new System.Net.Http.HttpMethod("GET");
+ request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("application/json"));
+
+ var urlBuilder_ = new System.Text.StringBuilder();
+ if (!string.IsNullOrEmpty(_baseUrl)) urlBuilder_.Append(_baseUrl);
+ // Operation Path: "node/ledger/account/{account}"
+ urlBuilder_.Append("node/ledger/account/");
+ urlBuilder_.Append(System.Uri.EscapeDataString(ConvertToString(account, System.Globalization.CultureInfo.InvariantCulture)));
+
+ PrepareRequest(client_, request_, urlBuilder_);
+
+ var url_ = urlBuilder_.ToString();
+ request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute);
+
+ PrepareRequest(client_, request_, url_);
+
+ var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false);
+ var disposeResponse_ = true;
+ try
+ {
+ var headers_ = new System.Collections.Generic.Dictionary>();
+ foreach (var item_ in response_.Headers)
+ headers_[item_.Key] = item_.Value;
+ if (response_.Content != null && response_.Content.Headers != null)
+ {
+ foreach (var item_ in response_.Content.Headers)
+ headers_[item_.Key] = item_.Value;
+ }
+
+ ProcessResponse(client_, response_);
+
+ var status_ = (int)response_.StatusCode;
+ if (status_ == 200)
+ {
+ var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false);
+ if (objectResponse_.Object == null)
+ {
+ throw new NodeApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null);
+ }
+ return objectResponse_.Object;
+ }
+ else
+ if (status_ == 500)
+ {
+ var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false);
+ if (objectResponse_.Object == null)
+ {
+ throw new NodeApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null);
+ }
+ throw new NodeApiException("Server error", status_, objectResponse_.Text, headers_, objectResponse_.Object, null);
+ }
+ else
+ {
+ var responseData_ = response_.Content == null ? null : await ReadAsStringAsync(response_.Content, cancellationToken).ConfigureAwait(false);
+ throw new NodeApiException("The HTTP status code of the response was not expected (" + status_ + ").", status_, responseData_, headers_, null);
+ }
+ }
+ finally
+ {
+ if (disposeResponse_)
+ response_.Dispose();
+ }
+ }
+ }
+ finally
+ {
+ if (disposeClient_)
+ client_.Dispose();
+ }
+ }
+
+ /// A cancellation token that can be used by other objects or threads to receive notice of cancellation.
+ ///
+ /// Get all account balances
+ ///
+ ///
+ /// Retrieve balances for all tokens held by an account.
+ ///
+ /// Account public key
+ /// Balances retrieved successfully
+ /// A server side error occurred.
+ public virtual async System.Threading.Tasks.Task GetAccountBalancesAsync(string account, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken))
+ {
+ if (account == null)
+ throw new System.ArgumentNullException("account");
+
+ var client_ = _httpClient;
+ var disposeClient_ = false;
+ try
+ {
+ using (var request_ = new System.Net.Http.HttpRequestMessage())
+ {
+ request_.Method = new System.Net.Http.HttpMethod("GET");
+ request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("application/json"));
+
+ var urlBuilder_ = new System.Text.StringBuilder();
+ if (!string.IsNullOrEmpty(_baseUrl)) urlBuilder_.Append(_baseUrl);
+ // Operation Path: "node/ledger/account/{account}/balance"
+ urlBuilder_.Append("node/ledger/account/");
+ urlBuilder_.Append(System.Uri.EscapeDataString(ConvertToString(account, System.Globalization.CultureInfo.InvariantCulture)));
+ urlBuilder_.Append("/balance");
+
+ PrepareRequest(client_, request_, urlBuilder_);
+
+ var url_ = urlBuilder_.ToString();
+ request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute);
+
+ PrepareRequest(client_, request_, url_);
+
+ var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false);
+ var disposeResponse_ = true;
+ try
+ {
+ var headers_ = new System.Collections.Generic.Dictionary>();
+ foreach (var item_ in response_.Headers)
+ headers_[item_.Key] = item_.Value;
+ if (response_.Content != null && response_.Content.Headers != null)
+ {
+ foreach (var item_ in response_.Content.Headers)
+ headers_[item_.Key] = item_.Value;
+ }
+
+ ProcessResponse(client_, response_);
+
+ var status_ = (int)response_.StatusCode;
+ if (status_ == 200)
+ {
+ var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false);
+ if (objectResponse_.Object == null)
+ {
+ throw new NodeApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null);
+ }
+ return objectResponse_.Object;
+ }
+ else
+ if (status_ == 500)
+ {
+ var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false);
+ if (objectResponse_.Object == null)
+ {
+ throw new NodeApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null);
+ }
+ throw new NodeApiException("Server error", status_, objectResponse_.Text, headers_, objectResponse_.Object, null);
+ }
+ else
+ {
+ var responseData_ = response_.Content == null ? null : await ReadAsStringAsync(response_.Content, cancellationToken).ConfigureAwait(false);
+ throw new NodeApiException("The HTTP status code of the response was not expected (" + status_ + ").", status_, responseData_, headers_, null);
+ }
+ }
+ finally
+ {
+ if (disposeResponse_)
+ response_.Dispose();
+ }
+ }
+ }
+ finally
+ {
+ if (disposeClient_)
+ client_.Dispose();
+ }
+ }
+
+ /// A cancellation token that can be used by other objects or threads to receive notice of cancellation.
+ ///
+ /// Get account balance for a specific token
+ ///
+ ///
+ /// Retrieve the balance of a specific token for an account.
+ ///
+ /// Account public key
+ /// Token account address
+ /// Balance retrieved successfully
+ /// A server side error occurred.
+ public virtual async System.Threading.Tasks.Task GetAccountBalanceAsync(string account, string token, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken))
+ {
+ if (account == null)
+ throw new System.ArgumentNullException("account");
+
+ if (token == null)
+ throw new System.ArgumentNullException("token");
+
+ var client_ = _httpClient;
+ var disposeClient_ = false;
+ try
+ {
+ using (var request_ = new System.Net.Http.HttpRequestMessage())
+ {
+ request_.Method = new System.Net.Http.HttpMethod("GET");
+ request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("application/json"));
+
+ var urlBuilder_ = new System.Text.StringBuilder();
+ if (!string.IsNullOrEmpty(_baseUrl)) urlBuilder_.Append(_baseUrl);
+ // Operation Path: "node/ledger/account/{account}/balance/{token}"
+ urlBuilder_.Append("node/ledger/account/");
+ urlBuilder_.Append(System.Uri.EscapeDataString(ConvertToString(account, System.Globalization.CultureInfo.InvariantCulture)));
+ urlBuilder_.Append("/balance/");
+ urlBuilder_.Append(System.Uri.EscapeDataString(ConvertToString(token, System.Globalization.CultureInfo.InvariantCulture)));
+
+ PrepareRequest(client_, request_, urlBuilder_);
+
+ var url_ = urlBuilder_.ToString();
+ request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute);
+
+ PrepareRequest(client_, request_, url_);
+
+ var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false);
+ var disposeResponse_ = true;
+ try
+ {
+ var headers_ = new System.Collections.Generic.Dictionary>();
+ foreach (var item_ in response_.Headers)
+ headers_[item_.Key] = item_.Value;
+ if (response_.Content != null && response_.Content.Headers != null)
+ {
+ foreach (var item_ in response_.Content.Headers)
+ headers_[item_.Key] = item_.Value;
+ }
+
+ ProcessResponse(client_, response_);
+
+ var status_ = (int)response_.StatusCode;
+ if (status_ == 200)
+ {
+ var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false);
+ if (objectResponse_.Object == null)
+ {
+ throw new NodeApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null);
+ }
+ return objectResponse_.Object;
+ }
+ else
+ if (status_ == 500)
+ {
+ var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false);
+ if (objectResponse_.Object == null)
+ {
+ throw new NodeApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null);
+ }
+ throw new NodeApiException("Server error", status_, objectResponse_.Text, headers_, objectResponse_.Object, null);
+ }
+ else
+ {
+ var responseData_ = response_.Content == null ? null : await ReadAsStringAsync(response_.Content, cancellationToken).ConfigureAwait(false);
+ throw new NodeApiException("The HTTP status code of the response was not expected (" + status_ + ").", status_, responseData_, headers_, null);
+ }
+ }
+ finally
+ {
+ if (disposeResponse_)
+ response_.Dispose();
+ }
+ }
+ }
+ finally
+ {
+ if (disposeClient_)
+ client_.Dispose();
+ }
+ }
+
+ /// A cancellation token that can be used by other objects or threads to receive notice of cancellation.
+ ///
+ /// Get account head block
+ ///
+ ///
+ /// Retrieve the head block for an account.
+ ///
+ /// Account public key
+ /// Head block retrieved successfully
+ /// A server side error occurred.
+ public virtual async System.Threading.Tasks.Task GetAccountHeadAsync(string account, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken))
+ {
+ if (account == null)
+ throw new System.ArgumentNullException("account");
+
+ var client_ = _httpClient;
+ var disposeClient_ = false;
+ try
+ {
+ using (var request_ = new System.Net.Http.HttpRequestMessage())
+ {
+ request_.Method = new System.Net.Http.HttpMethod("GET");
+ request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("application/json"));
+
+ var urlBuilder_ = new System.Text.StringBuilder();
+ if (!string.IsNullOrEmpty(_baseUrl)) urlBuilder_.Append(_baseUrl);
+ // Operation Path: "node/ledger/account/{account}/head"
+ urlBuilder_.Append("node/ledger/account/");
+ urlBuilder_.Append(System.Uri.EscapeDataString(ConvertToString(account, System.Globalization.CultureInfo.InvariantCulture)));
+ urlBuilder_.Append("/head");
+
+ PrepareRequest(client_, request_, urlBuilder_);
+
+ var url_ = urlBuilder_.ToString();
+ request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute);
+
+ PrepareRequest(client_, request_, url_);
+
+ var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false);
+ var disposeResponse_ = true;
+ try
+ {
+ var headers_ = new System.Collections.Generic.Dictionary>();
+ foreach (var item_ in response_.Headers)
+ headers_[item_.Key] = item_.Value;
+ if (response_.Content != null && response_.Content.Headers != null)
+ {
+ foreach (var item_ in response_.Content.Headers)
+ headers_[item_.Key] = item_.Value;
+ }
+
+ ProcessResponse(client_, response_);
+
+ var status_ = (int)response_.StatusCode;
+ if (status_ == 200)
+ {
+ var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false);
+ if (objectResponse_.Object == null)
+ {
+ throw new NodeApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null);
+ }
+ return objectResponse_.Object;
+ }
+ else
+ if (status_ == 500)
+ {
+ var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false);
+ if (objectResponse_.Object == null)
+ {
+ throw new NodeApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null);
+ }
+ throw new NodeApiException("Server error", status_, objectResponse_.Text, headers_, objectResponse_.Object, null);
+ }
+ else
+ {
+ var responseData_ = response_.Content == null ? null : await ReadAsStringAsync(response_.Content, cancellationToken).ConfigureAwait(false);
+ throw new NodeApiException("The HTTP status code of the response was not expected (" + status_ + ").", status_, responseData_, headers_, null);
+ }
+ }
+ finally
+ {
+ if (disposeResponse_)
+ response_.Dispose();
+ }
+ }
+ }
+ finally
+ {
+ if (disposeClient_)
+ client_.Dispose();
+ }
+ }
+
+ /// A cancellation token that can be used by other objects or threads to receive notice of cancellation.
+ ///
+ /// Get pending block for account
+ ///
+ ///
+ /// Retrieve the next pending block for an account.
+ ///
+ /// Account public key
+ /// Pending block retrieved successfully
+ /// A server side error occurred.
+ public virtual async System.Threading.Tasks.Task GetPendingBlockAsync(string account, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken))
+ {
+ if (account == null)
+ throw new System.ArgumentNullException("account");
+
+ var client_ = _httpClient;
+ var disposeClient_ = false;
+ try
+ {
+ using (var request_ = new System.Net.Http.HttpRequestMessage())
+ {
+ request_.Method = new System.Net.Http.HttpMethod("GET");
+ request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("application/json"));
+
+ var urlBuilder_ = new System.Text.StringBuilder();
+ if (!string.IsNullOrEmpty(_baseUrl)) urlBuilder_.Append(_baseUrl);
+ // Operation Path: "node/ledger/account/{account}/pending"
+ urlBuilder_.Append("node/ledger/account/");
+ urlBuilder_.Append(System.Uri.EscapeDataString(ConvertToString(account, System.Globalization.CultureInfo.InvariantCulture)));
+ urlBuilder_.Append("/pending");
+
+ PrepareRequest(client_, request_, urlBuilder_);
+
+ var url_ = urlBuilder_.ToString();
+ request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute);
+
+ PrepareRequest(client_, request_, url_);
+
+ var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false);
+ var disposeResponse_ = true;
+ try
+ {
+ var headers_ = new System.Collections.Generic.Dictionary>();
+ foreach (var item_ in response_.Headers)
+ headers_[item_.Key] = item_.Value;
+ if (response_.Content != null && response_.Content.Headers != null)
+ {
+ foreach (var item_ in response_.Content.Headers)
+ headers_[item_.Key] = item_.Value;
+ }
+
+ ProcessResponse(client_, response_);
+
+ var status_ = (int)response_.StatusCode;
+ if (status_ == 200)
+ {
+ var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false);
+ if (objectResponse_.Object == null)
+ {
+ throw new NodeApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null);
+ }
+ return objectResponse_.Object;
+ }
+ else
+ if (status_ == 500)
+ {
+ var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false);
+ if (objectResponse_.Object == null)
+ {
+ throw new NodeApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null);
+ }
+ throw new NodeApiException("Server error", status_, objectResponse_.Text, headers_, objectResponse_.Object, null);
+ }
+ else
+ {
+ var responseData_ = response_.Content == null ? null : await ReadAsStringAsync(response_.Content, cancellationToken).ConfigureAwait(false);
+ throw new NodeApiException("The HTTP status code of the response was not expected (" + status_ + ").", status_, responseData_, headers_, null);
+ }
+ }
+ finally
+ {
+ if (disposeResponse_)
+ response_.Dispose();
+ }
+ }
+ }
+ finally
+ {
+ if (disposeClient_)
+ client_.Dispose();
+ }
+ }
+
+ /// A cancellation token that can be used by other objects or threads to receive notice of cancellation.
+ ///
+ /// Get block by hash
+ ///
+ ///
+ /// Retrieve a specific block by its hash.
+ ///
+ /// 64-character hexadecimal block hash
+ /// Which ledger storage to read from (defaults to main).
+ /// Block retrieved successfully
+ /// A server side error occurred.
+ public virtual async System.Threading.Tasks.Task GetBlockAsync(string blockhash, Side2? side = null, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken))
+ {
+ if (blockhash == null)
+ throw new System.ArgumentNullException("blockhash");
+
+ var client_ = _httpClient;
+ var disposeClient_ = false;
+ try
+ {
+ using (var request_ = new System.Net.Http.HttpRequestMessage())
+ {
+ request_.Method = new System.Net.Http.HttpMethod("GET");
+ request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("application/json"));
+
+ var urlBuilder_ = new System.Text.StringBuilder();
+ if (!string.IsNullOrEmpty(_baseUrl)) urlBuilder_.Append(_baseUrl);
+ // Operation Path: "node/ledger/block/{blockhash}"
+ urlBuilder_.Append("node/ledger/block/");
+ urlBuilder_.Append(System.Uri.EscapeDataString(ConvertToString(blockhash, System.Globalization.CultureInfo.InvariantCulture)));
+ urlBuilder_.Append('?');
+ if (side != null)
+ {
+ urlBuilder_.Append(System.Uri.EscapeDataString("side")).Append('=').Append(System.Uri.EscapeDataString(ConvertToString(side, System.Globalization.CultureInfo.InvariantCulture))).Append('&');
+ }
+ urlBuilder_.Length--;
+
+ PrepareRequest(client_, request_, urlBuilder_);
+
+ var url_ = urlBuilder_.ToString();
+ request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute);
+
+ PrepareRequest(client_, request_, url_);
+
+ var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false);
+ var disposeResponse_ = true;
+ try
+ {
+ var headers_ = new System.Collections.Generic.Dictionary>();
+ foreach (var item_ in response_.Headers)
+ headers_[item_.Key] = item_.Value;
+ if (response_.Content != null && response_.Content.Headers != null)
+ {
+ foreach (var item_ in response_.Content.Headers)
+ headers_[item_.Key] = item_.Value;
+ }
+
+ ProcessResponse(client_, response_);
+
+ var status_ = (int)response_.StatusCode;
+ if (status_ == 200)
+ {
+ var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false);
+ if (objectResponse_.Object == null)
+ {
+ throw new NodeApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null);
+ }
+ return objectResponse_.Object;
+ }
+ else
+ if (status_ == 500)
+ {
+ var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false);
+ if (objectResponse_.Object == null)
+ {
+ throw new NodeApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null);
+ }
+ throw new NodeApiException("Server error", status_, objectResponse_.Text, headers_, objectResponse_.Object, null);
+ }
+ else
+ {
+ var responseData_ = response_.Content == null ? null : await ReadAsStringAsync(response_.Content, cancellationToken).ConfigureAwait(false);
+ throw new NodeApiException("The HTTP status code of the response was not expected (" + status_ + ").", status_, responseData_, headers_, null);
+ }
+ }
+ finally
+ {
+ if (disposeResponse_)
+ response_.Dispose();
+ }
+ }
+ }
+ finally
+ {
+ if (disposeClient_)
+ client_.Dispose();
+ }
+ }
+
+ /// A cancellation token that can be used by other objects or threads to receive notice of cancellation.
+ ///
+ /// Get successor block
+ ///
+ ///
+ /// Retrieve the block that follows the specified block.
+ ///
+ /// 64-character hexadecimal block hash
+ /// Successor block retrieved successfully
+ /// A server side error occurred.
+ public virtual async System.Threading.Tasks.Task GetSuccessorBlockAsync(string blockhash, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken))
+ {
+ if (blockhash == null)
+ throw new System.ArgumentNullException("blockhash");
+
+ var client_ = _httpClient;
+ var disposeClient_ = false;
+ try
+ {
+ using (var request_ = new System.Net.Http.HttpRequestMessage())
+ {
+ request_.Method = new System.Net.Http.HttpMethod("GET");
+ request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("application/json"));
+
+ var urlBuilder_ = new System.Text.StringBuilder();
+ if (!string.IsNullOrEmpty(_baseUrl)) urlBuilder_.Append(_baseUrl);
+ // Operation Path: "node/ledger/block/{blockhash}/successor"
+ urlBuilder_.Append("node/ledger/block/");
+ urlBuilder_.Append(System.Uri.EscapeDataString(ConvertToString(blockhash, System.Globalization.CultureInfo.InvariantCulture)));
+ urlBuilder_.Append("/successor");
+
+ PrepareRequest(client_, request_, urlBuilder_);
+
+ var url_ = urlBuilder_.ToString();
+ request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute);
+
+ PrepareRequest(client_, request_, url_);
+
+ var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false);
+ var disposeResponse_ = true;
+ try
+ {
+ var headers_ = new System.Collections.Generic.Dictionary>();
+ foreach (var item_ in response_.Headers)
+ headers_[item_.Key] = item_.Value;
+ if (response_.Content != null && response_.Content.Headers != null)
+ {
+ foreach (var item_ in response_.Content.Headers)
+ headers_[item_.Key] = item_.Value;
+ }
+
+ ProcessResponse(client_, response_);
+
+ var status_ = (int)response_.StatusCode;
+ if (status_ == 200)
+ {
+ var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false);
+ if (objectResponse_.Object == null)
+ {
+ throw new NodeApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null);
+ }
+ return objectResponse_.Object;
+ }
+ else
+ if (status_ == 500)
+ {
+ var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false);
+ if (objectResponse_.Object == null)
+ {
+ throw new NodeApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null);
+ }
+ throw new NodeApiException("Server error", status_, objectResponse_.Text, headers_, objectResponse_.Object, null);
+ }
+ else
+ {
+ var responseData_ = response_.Content == null ? null : await ReadAsStringAsync(response_.Content, cancellationToken).ConfigureAwait(false);
+ throw new NodeApiException("The HTTP status code of the response was not expected (" + status_ + ").", status_, responseData_, headers_, null);
+ }
+ }
+ finally
+ {
+ if (disposeResponse_)
+ response_.Dispose();
+ }
+ }
+ }
+ finally
+ {
+ if (disposeClient_)
+ client_.Dispose();
+ }
+ }
+
+ /// A cancellation token that can be used by other objects or threads to receive notice of cancellation.
+ ///
+ /// Get node statistics
+ ///
+ /// Statistics
+ /// A server side error occurred.
+ public virtual async System.Threading.Tasks.Task