Skip to content

Commit 4629093

Browse files
authored
Merge pull request #6 from KeetaNetwork/feat/add-missing-kyc-endpoints
Feature: Add Lightweight Node Client
2 parents b38cc06 + ed4fdb8 commit 4629093

54 files changed

Lines changed: 6428 additions & 238 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.config/dotnet-tools.json

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
{
2+
"version": 1,
3+
"isRoot": true,
4+
"tools": {
5+
"nswag.consolecore": {
6+
"version": "14.7.1",
7+
"commands": [
8+
"nswag"
9+
],
10+
"rollForward": false
11+
}
12+
}
13+
}

.github/workflows/ci.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ jobs:
3838
~/.cargo/registry
3939
~/.cargo/git
4040
.wasm-build
41-
key: wasm-${{ runner.os }}-${{ hashFiles('scripts/build-wasm.sh', 'rust-toolchain.toml') }}
41+
key: wasm-${{ runner.os }}-${{ hashFiles('scripts/build-wasm.sh', 'scripts/fetch-crate.sh', 'scripts/pins.env', 'rust-toolchain.toml') }}
4242

4343
- name: Set up Node.js
4444
uses: actions/setup-node@v4
@@ -136,7 +136,7 @@ jobs:
136136
~/.cargo/registry
137137
~/.cargo/git
138138
.wasm-build
139-
key: wasm-${{ runner.os }}-${{ hashFiles('scripts/build-wasm.sh', 'rust-toolchain.toml') }}
139+
key: wasm-${{ runner.os }}-${{ hashFiles('scripts/build-wasm.sh', 'scripts/fetch-crate.sh', 'scripts/pins.env', 'rust-toolchain.toml') }}
140140

141141
- name: Build wasm core
142142
run: bash scripts/build-wasm.sh

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ coverage*.info
2020
# Wasm core built from the pinned crates.io release (never committed)
2121
wasm/
2222
.wasm-build/
23+
.node-api-build/
2324

2425
# Node harness build output
2526
node_modules/

Directory.Packages.props

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
<ItemGroup>
99
<PackageVersion Include="Wasmtime" Version="44.0.0" />
1010
<PackageVersion Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="10.0.2" />
11+
<PackageVersion Include="Microsoft.Extensions.Http" Version="10.0.2" />
1112
</ItemGroup>
1213

1314
<ItemGroup Label="Test">

Makefile

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
.PHONY: help developer restore build rebuild do-lint do-lint-ci test pack clean wasm node-harness release
1+
.PHONY: help developer restore build rebuild do-lint do-lint-ci test pack clean wasm node-api node-harness release
22

33
# Build configuration (Debug or Release)
44
CONFIG ?= Release
@@ -65,6 +65,10 @@ pack: build
6565
wasm:
6666
./scripts/build-wasm.sh
6767

68+
# Regenerate the node REST transport from the pinned OpenAPI spec (committed)
69+
node-api:
70+
./scripts/generate-node-api.sh
71+
6872
$(WASM_DEST):
6973
./scripts/build-wasm.sh
7074

@@ -103,6 +107,7 @@ help:
103107
@echo " make do-lint - Lint code with formatting fixes (C# + harness + spelling)"
104108
@echo " make pack - Produce the NuGet packages into $(ARTIFACTS)/"
105109
@echo " make wasm - Build the P1 wasm core from the pinned crates.io release"
110+
@echo " make node-api - Regenerate the node REST transport from the pinned OpenAPI spec"
106111
@echo " make clean - Remove build outputs"
107112
@echo ""
108113
@echo "CI Commands:"

README.md

Lines changed: 18 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -49,12 +49,12 @@ using KeetaNet.Anchor.Crypto;
4949
using var runtime = WasmRuntime.Load();
5050
```
5151

52-
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.
52+
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.
5353

5454
In an application using `Microsoft.Extensions.DependencyInjection`, register the runtime once instead of threading it around; the container owns its disposal:
5555

5656
```csharp
57-
// Program.cs requires the KeetaNet.Anchor.Extensions.DependencyInjection package.
57+
// Program.cs - requires the KeetaNet.Anchor.Extensions.DependencyInjection package.
5858
builder.Services.AddKeetaNetAnchor();
5959

6060
// 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
107107
using KycClient kyc = runtime.CreateKycClient(nodeUrl, root, signer);
108108

109109
string[] countries = { "US" };
110-
IReadOnlyList<KycProvider> providers = await kyc.GetProvidersAsync(countries, cancellationToken);
110+
IReadOnlyList<KycProvider> providers = await kyc.GetProviders(countries, cancellationToken);
111111
KycProvider provider = providers[0];
112112

113113
// Start a verification. The user completes it in a browser at WebUrl.
114-
VerificationOutcome created = await kyc.StartVerificationAsync(provider, countries, cancellationToken: cancellationToken);
114+
VerificationOutcome created = await kyc.StartVerification(provider, countries, cancellationToken: cancellationToken);
115115
Verification verification = created.Ready!;
116116
Console.WriteLine(verification.WebUrl);
117117
Console.WriteLine(verification.ExpectedCost.Token);
118118

119119
// Poll the provider's decision.
120-
StatusOutcome status = await kyc.GetVerificationStatusAsync(provider, verification.Id, cancellationToken);
120+
StatusOutcome status = await kyc.GetVerificationStatus(provider, verification.Id, cancellationToken);
121121

122122
// Fetch the issued chain once ready; a pending fetch reports RetryAfterMs instead.
123-
CertificatesOutcome outcome = await kyc.GetCertificatesAsync(provider, verification.Id, cancellationToken);
123+
CertificatesOutcome outcome = await kyc.GetCertificates(provider, verification.Id, cancellationToken);
124124
Certificates issued = outcome.Ready!;
125125
string leafPem = issued.Results[0].Value;
126126
```
@@ -235,39 +235,39 @@ byte[]? signerKey = opened.GetSigningAccount(); // type-prefixed public key, nul
235235
using AssetMovementClient assets = runtime.CreateAssetMovementClient(nodeUrl, root, signer);
236236

237237
// Discovery: all providers, by id, by signer account, or by transfer shape.
238-
IReadOnlyList<AssetProvider> providers = await assets.GetProvidersAsync(cancellationToken);
238+
IReadOnlyList<AssetProvider> providers = await assets.GetProviders(cancellationToken);
239239
var search = new AssetProviderSearch(Asset: asset, From: "chain:evm:100", To: "chain:keeta:100");
240-
IReadOnlyList<AssetProvider> capable = await assets.GetProvidersForTransferAsync(search, cancellationToken);
240+
IReadOnlyList<AssetProvider> capable = await assets.GetProvidersForTransfer(search, cancellationToken);
241241
AssetProvider provider = capable[0];
242242

243243
// Check the signer's readiness before transacting.
244-
AssetAccountStatus account = await assets.GetAccountStatusAsync(provider, cancellationToken);
244+
AssetAccountStatus account = await assets.GetAccountStatus(provider, cancellationToken);
245245

246246
// Push transfer: simulate first, then promote the simulation.
247247
var request = new AssetTransferRequest(
248248
Asset: asset,
249249
From: new AssetTransferSource("chain:evm:100"),
250250
To: new AssetTransferDestination("chain:keeta:100", recipientAddress),
251251
Value: "100");
252-
AssetSimulatedTransfer simulated = await assets.SimulateTransferAsync(provider, request, cancellationToken);
253-
AssetTransfer transfer = await simulated.CreateTransferAsync(cancellationToken: cancellationToken);
254-
AssetTransferStatus status = await transfer.GetTransferStatusAsync(cancellationToken);
252+
AssetSimulatedTransfer simulated = await assets.SimulateTransfer(provider, request, cancellationToken);
253+
AssetTransfer transfer = await simulated.CreateTransfer(cancellationToken: cancellationToken);
254+
AssetTransferStatus status = await transfer.GetTransferStatus(cancellationToken);
255255

256256
// Pull transfer (fiat rails): initiate, pick an instruction, execute.
257-
AssetTransfer pull = await assets.InitiateTransferAsync(provider, pullRequest, cancellationToken);
257+
AssetTransfer pull = await assets.InitiateTransfer(provider, pullRequest, cancellationToken);
258258
var instruction = new AssetPullInstruction("ACH_DEBIT", pull.InstructionChoices[0].GetProperty("pullFrom"));
259-
AssetTransferStatus executed = await pull.ExecuteTransferAsync(instruction, cancellationToken);
259+
AssetTransferStatus executed = await pull.ExecuteTransfer(instruction, cancellationToken);
260260

261261
// Share KYC attributes; a pending outcome polls its promise URL inside the core.
262-
AssetShareKycOutcome shared = await assets.ShareKycAttributesAndWaitAsync(
262+
AssetShareKycOutcome shared = await assets.ShareKycAttributesAndWait(
263263
provider,
264264
new AssetShareKycRequest(exportedAttributes),
265265
pollInterval: TimeSpan.FromSeconds(1),
266266
timeout: TimeSpan.FromMinutes(2),
267267
cancellationToken: cancellationToken);
268268
```
269269

270-
Persistent forwarding follows the same pattern: `InitiatePersistentForwardingTemplateAsync` / `CreatePersistentForwardingTemplateAsync` / `CreatePersistentForwardingAddressAsync` create, the `List*Async` methods page, and the `Deactivate*Async` methods retire.
270+
Persistent forwarding follows the same pattern: `InitiatePersistentForwardingTemplate` / `CreatePersistentForwardingTemplate` / `CreatePersistentForwardingAddress` create, the `List*` methods page, and the `Deactivate*` methods retire.
271271

272272
### Errors
273273

@@ -276,7 +276,7 @@ Every failure surfaced from the core throws `KeetaException`: a stable machine-r
276276
```csharp
277277
try
278278
{
279-
await assets.InitiateTransferAsync(provider, request, cancellationToken);
279+
await assets.InitiateTransfer(provider, request, cancellationToken);
280280
}
281281
catch (KeetaException error)
282282
{
@@ -295,7 +295,7 @@ Interop is Wasmtime-hosted WebAssembly, not native P/Invoke. Guest memory is a s
295295
### SDK Rules:
296296

297297
- **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`.
298-
- **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.
298+
- **Deterministic disposal.** Handle-backed types (`Account`, `Certificate`, `KycCertificate`, containers, the clients) implement `IDisposable` and release their wasm handle on `Dispose`. Wrap them in `using`.
299299

300300
## License
301301

cspell.yaml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,10 @@ words:
5656
- belgrave
5757
- idisp
5858
- lifecycles
59+
- renderable
5960
- tlsv
61+
- csclient
62+
- nswag
6063
- rasn
6164
- rustfmt
6265
- userprofile
@@ -66,3 +69,5 @@ words:
6669
- msys
6770
- maxdepth
6871
- powershell
72+
- renderable
73+
- solana

scripts/build-wasm.sh

Lines changed: 8 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -3,26 +3,17 @@
33
# the library embeds it.
44
set -euo pipefail
55

6-
CRATE_NAME="keetanetwork-anchor-client-wasi"
7-
CRATE_VERSION="0.1.1"
8-
CRATE_SHA256="dd5c19da144a320d23d5b0b1af392ea6c94f6b7cb584e490bcf42155c768d9d6"
9-
106
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
7+
8+
# shellcheck source=scripts/pins.env
9+
source "${ROOT}/scripts/pins.env"
10+
# shellcheck source=scripts/fetch-crate.sh
11+
source "${ROOT}/scripts/fetch-crate.sh"
12+
1113
BUILD_DIR="${ROOT}/.wasm-build"
12-
TARBALL="${BUILD_DIR}/${CRATE_NAME}-${CRATE_VERSION}.crate"
13-
CRATE_DIR="${BUILD_DIR}/${CRATE_NAME}-${CRATE_VERSION}"
14+
CRATE_DIR="${BUILD_DIR}/${ANCHOR_WASI_CRATE}-${ANCHOR_WASI_VERSION}"
1415
ARTIFACT="${CRATE_DIR}/target/wasm32-wasip1/release/keetanetwork_anchor_client_wasi.wasm"
1516
DEST="${ROOT}/src/KeetaNet.Anchor/wasm/keetanetwork_anchor_client_wasi.wasm"
16-
DOWNLOAD_URL="https://crates.io/api/v1/crates/${CRATE_NAME}/${CRATE_VERSION}/download"
17-
18-
checksum() {
19-
local file="$1"
20-
if command -v sha256sum >/dev/null 2>&1; then
21-
sha256sum "${file}" | cut -d' ' -f1
22-
else
23-
shasum -a 256 "${file}" | cut -d' ' -f1
24-
fi
25-
}
2617

2718
# rasn-compiler probes `$CARGO_HOME/bin/rustfmt` and `$CARGO`-adjacent
2819
# `rustfmt` without the .exe suffix. When both miss it silently emits
@@ -102,31 +93,6 @@ require_wasip1_target() {
10293
fi
10394
}
10495

105-
fetch_crate() {
106-
mkdir -p "${BUILD_DIR}"
107-
if [[ ! -f "${TARBALL}" ]]; then
108-
echo "build-wasm: downloading ${CRATE_NAME} ${CRATE_VERSION} from crates.io"
109-
# crates.io rejects requests without a User-Agent.
110-
curl --fail --silent --show-error --location \
111-
--proto '=https' --tlsv1.2 \
112-
--user-agent "anchor-csharp build-wasm (https://github.com/KeetaNetwork/anchor-csharp)" \
113-
--output "${TARBALL}" "${DOWNLOAD_URL}"
114-
fi
115-
116-
actual="$(checksum "${TARBALL}")"
117-
if [[ "${actual}" != "${CRATE_SHA256}" ]]; then
118-
rm -f "${TARBALL}"
119-
echo "build-wasm: checksum mismatch for ${TARBALL}" >&2
120-
echo " expected: ${CRATE_SHA256}" >&2
121-
echo " actual: ${actual}" >&2
122-
exit 1
123-
fi
124-
125-
if [[ ! -d "${CRATE_DIR}" ]]; then
126-
tar --extract --gzip --file "${TARBALL}" --directory "${BUILD_DIR}"
127-
fi
128-
}
129-
13096
build_artifact() {
13197
echo "build-wasm: cargo build (wasm32-wasip1, features p1, release)"
13298
cargo build \
@@ -139,7 +105,7 @@ build_artifact() {
139105

140106
shim_rustfmt_for_windows
141107
require_wasip1_target
142-
fetch_crate
108+
fetch_crate "${ANCHOR_WASI_CRATE}" "${ANCHOR_WASI_VERSION}" "${ANCHOR_WASI_SHA256}" "${BUILD_DIR}"
143109
purge_stale_asn1_outputs
144110
build_artifact
145111

scripts/fetch-crate.sh

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
# Shared pinned-crate fetch for the generation scripts. Source this file and
2+
# call `fetch_crate`.
3+
4+
checksum() {
5+
local file="$1"
6+
if command -v sha256sum >/dev/null 2>&1; then
7+
sha256sum "${file}" | cut -d' ' -f1
8+
else
9+
shasum -a 256 "${file}" | cut -d' ' -f1
10+
fi
11+
}
12+
13+
# fetch_crate <name> <version> <sha256> <build_dir>
14+
#
15+
# Download the pinned crates.io release into `<build_dir>`, verify its SHA-256,
16+
# and extract it to `<build_dir>/<name>-<version>` (skipping work already done).
17+
fetch_crate() {
18+
local name="$1" version="$2" expected="$3" build_dir="$4"
19+
local tarball="${build_dir}/${name}-${version}.crate"
20+
local crate_dir="${build_dir}/${name}-${version}"
21+
local url="https://crates.io/api/v1/crates/${name}/${version}/download"
22+
23+
mkdir -p "${build_dir}"
24+
if [[ ! -f "${tarball}" ]]; then
25+
echo "fetch-crate: downloading ${name} ${version} from crates.io"
26+
# crates.io rejects requests without a User-Agent.
27+
curl --fail --silent --show-error --location \
28+
--proto '=https' --tlsv1.2 \
29+
--user-agent "anchor-csharp fetch-crate (https://github.com/KeetaNetwork/anchor-csharp)" \
30+
--output "${tarball}" "${url}"
31+
fi
32+
33+
local actual
34+
actual="$(checksum "${tarball}")"
35+
if [[ "${actual}" != "${expected}" ]]; then
36+
rm -f "${tarball}"
37+
echo "fetch-crate: checksum mismatch for ${tarball}" >&2
38+
echo " expected: ${expected}" >&2
39+
echo " actual: ${actual}" >&2
40+
exit 1
41+
fi
42+
43+
if [[ ! -d "${crate_dir}" ]]; then
44+
tar --extract --gzip --file "${tarball}" --directory "${build_dir}"
45+
fi
46+
}

scripts/generate-node-api.sh

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
#!/usr/bin/env bash
2+
# Generate the node REST transport (models + typed operations) from the
3+
# canonical OpenAPI spec shipped in the pinned keetanetwork-client crate.
4+
set -euo pipefail
5+
6+
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
7+
8+
# shellcheck source=scripts/pins.env
9+
source "${ROOT}/scripts/pins.env"
10+
# shellcheck source=scripts/fetch-crate.sh
11+
source "${ROOT}/scripts/fetch-crate.sh"
12+
13+
BUILD_DIR="${ROOT}/.node-api-build"
14+
CRATE_DIR="${BUILD_DIR}/${NODE_CLIENT_CRATE}-${NODE_CLIENT_VERSION}"
15+
SPEC="${CRATE_DIR}/openapi/keetanet-node.yaml"
16+
DEST="${ROOT}/src/KeetaNet.Anchor/Generated/Node/NodeApi.cs"
17+
18+
generate_client() {
19+
echo "generate-node-api: nswag openapi2csclient from ${SPEC}"
20+
mkdir -p "$(dirname "${DEST}")"
21+
(cd "${ROOT}" && dotnet tool restore >/dev/null && dotnet nswag openapi2csclient \
22+
"/input:${SPEC}" \
23+
/classname:NodeApi \
24+
/namespace:KeetaNet.Anchor.Generated.Node \
25+
"/output:${DEST}" \
26+
/jsonLibrary:SystemTextJson \
27+
/injectHttpClient:true \
28+
/disposeHttpClient:false \
29+
/generateClientInterfaces:false \
30+
/generateOptionalParameters:true \
31+
/generateNativeRecords:true \
32+
/useBaseUrl:true \
33+
/generateBaseUrlProperty:true \
34+
/exceptionClass:NodeApiException)
35+
}
36+
37+
fetch_crate "${NODE_CLIENT_CRATE}" "${NODE_CLIENT_VERSION}" "${NODE_CLIENT_SHA256}" "${BUILD_DIR}"
38+
generate_client
39+
echo "generate-node-api: node transport at ${DEST}"

0 commit comments

Comments
 (0)