Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
35299cc
fix: resolve some compatibility issues
neilcampbell Nov 8, 2025
1512e8a
chore: add lmsig encode/decode support
neilcampbell Nov 8, 2025
f663f61
fix: more algod refinements
neilcampbell Nov 10, 2025
f7ef62c
chore: fixes
neilcampbell Nov 12, 2025
3d5ac32
chore: adding ledger state delta models and cleaning up block models
neilcampbell Nov 13, 2025
4174540
feat: added algod tests
lempira Nov 13, 2025
ad2c19a
chore: adjusting the block type to align with go-algorand and js-algosdk
neilcampbell Nov 14, 2025
920368c
Merge remote-tracking branch 'origin/decoupling-algod-compat' into fe…
lempira Nov 14, 2025
73db727
feat(tests): add no-parameter endpoint tests with external snapshots
lempira Nov 14, 2025
b6510a1
feat(tests): add address-based endpoint tests with external snapshots
lempira Nov 14, 2025
ed67d75
feat(tests): add transaction endpoint tests with external snapshots
lempira Nov 14, 2025
978de1a
feat(tests): add application endpoint test with external snapshots
lempira Nov 14, 2025
3923f67
feat(tests): add ledger endpoint tests with external snapshots
lempira Nov 14, 2025
7447a3f
feat(tests): add round-based endpoint tests with external snapshots
lempira Nov 14, 2025
dea903d
feat(tests): add devmode and sync round tests with localnet config
lempira Nov 19, 2025
2f0a546
test(algod-client): replace snapshots with zod validation
lempira Nov 21, 2025
73298f6
test(algod-client): added dummy data and TODOs for tests with missing…
lempira Nov 21, 2025
29f8e88
chore: deleted tests for not used algod endpoints
lempira Nov 24, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -66,3 +66,6 @@ __pycache__/
env/
venv/
ENV/

# Polytest (temporary files until polytest branch merged to main)
.polytest_algokit-polytest/
18 changes: 18 additions & 0 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"type": "node",
"request": "launch",
"name": "Debug Vitest Test",
"runtimeExecutable": "npm",
"runtimeArgs": ["test", "--", "--run", "--no-coverage", "${file}"],
"cwd": "${workspaceFolder}/packages/algod_client",
"console": "integratedTerminal",
"internalConsoleOptions": "neverOpen"
}
]
}
45 changes: 37 additions & 8 deletions algokit-configs/openapi-converter/specs/algod.oas3.json
Original file line number Diff line number Diff line change
Expand Up @@ -4054,9 +4054,7 @@
"description": "base64 encoded program bytes"
},
"sourcemap": {
"type": "object",
"properties": {},
"description": "JSON of the source map"
"$ref": "#/components/schemas/SourceMap"
}
}
}
Expand Down Expand Up @@ -4695,8 +4693,7 @@
"id",
"network",
"proto",
"rwd",
"timestamp"
"rwd"
],
"type": "object",
"properties": {
Expand Down Expand Up @@ -4994,6 +4991,7 @@
"type": "integer",
"description": "unique asset identifier",
"x-go-type": "basics.AssetIndex",
"x-algokit-field-rename": "id",
"x-algokit-bigint": true
},
"params": {
Expand Down Expand Up @@ -6551,6 +6549,39 @@
}
},
"description": "Proof of transaction in a block."
},
"SourceMap": {
"type": "object",
"required": [
"version",
"sources",
"names",
"mappings"
],
"properties": {
"version": {
"type": "integer"
},
"sources": {
"description": "A list of original sources used by the \"mappings\" entry.",
"type": "array",
"items": {
"type": "string"
}
},
"names": {
"description": "A list of symbol names used by the \"mappings\" entry.",
"type": "array",
"items": {
"type": "string"
}
},
"mappings": {
"description": "A string with the encoded mapping data.",
"type": "string"
}
},
"description": "Source map for the program"
}
},
"responses": {
Expand Down Expand Up @@ -7333,9 +7364,7 @@
"description": "base64 encoded program bytes"
},
"sourcemap": {
"type": "object",
"properties": {},
"description": "JSON of the source map"
"$ref": "#/components/schemas/SourceMap"
}
}
}
Expand Down
1 change: 1 addition & 0 deletions algokit-configs/openapi-converter/specs/indexer.oas3.json
Original file line number Diff line number Diff line change
Expand Up @@ -3587,6 +3587,7 @@
"index": {
"type": "integer",
"description": "unique asset identifier",
"x-algokit-field-rename": "id",
"x-algokit-bigint": true
},
"deleted": {
Expand Down
2 changes: 2 additions & 0 deletions oas-generator/src/oas_generator/generator/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,8 @@ class FieldDescriptor:
is_signed_txn: bool
is_optional: bool
is_nullable: bool
inline_object_schema: dict | None = None
inline_meta_name: str | None = None


@dataclass
Expand Down
88 changes: 32 additions & 56 deletions oas-generator/src/oas_generator/generator/template_engine.py
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,8 @@ def _build_model_descriptor(self, name: str, schema: Schema, all_schemas: Schema
signed_txn = False
bytes_flag = False
bigint_flag = False
inline_object_schema = None

if is_array and isinstance(items, dict):
if "$ref" in items:
ref_model = ts_pascal_case(items["$ref"].split("/")[-1])
Expand All @@ -209,15 +211,31 @@ def _build_model_descriptor(self, name: str, schema: Schema, all_schemas: Schema
else:
if "$ref" in (prop_schema or {}):
ref_model = ts_pascal_case(prop_schema["$ref"].split("/")[-1])
fmt = prop_schema.get(constants.SchemaKey.FORMAT)
bytes_flag = fmt == "byte" or prop_schema.get(constants.X_ALGOKIT_BYTES_BASE64) is True
bigint_flag = bool(prop_schema.get(constants.X_ALGOKIT_BIGINT) is True)
signed_txn = bool(prop_schema.get(constants.X_ALGOKIT_SIGNED_TXN) is True)
# Check for special codec flags first
elif bool(prop_schema.get(constants.X_ALGOKIT_SIGNED_TXN) is True):
signed_txn = True
# For inline nested objects, store the schema for inline metadata generation
elif (prop_schema.get(constants.SchemaKey.TYPE) == "object" and
"properties" in prop_schema and
"$ref" not in prop_schema and
prop_schema.get(constants.X_ALGOKIT_SIGNED_TXN) is not True):
# Store the inline object schema for metadata generation
inline_object_schema = prop_schema
else:
fmt = prop_schema.get(constants.SchemaKey.FORMAT)
bytes_flag = fmt == "byte" or prop_schema.get(constants.X_ALGOKIT_BYTES_BASE64) is True
bigint_flag = bool(prop_schema.get(constants.X_ALGOKIT_BIGINT) is True)
signed_txn = bool(prop_schema.get(constants.X_ALGOKIT_SIGNED_TXN) is True)

is_optional = prop_name not in required_fields
# Nullable per OpenAPI
is_nullable = bool(prop_schema.get(constants.SchemaKey.NULLABLE) is True)

# Generate inline metadata name for nested objects
inline_meta_name = None
if inline_object_schema:
inline_meta_name = f"{model_name}{ts_pascal_case(canonical)}Meta"

fields.append(
FieldDescriptor(
name=name_camel,
Expand All @@ -230,6 +248,8 @@ def _build_model_descriptor(self, name: str, schema: Schema, all_schemas: Schema
is_signed_txn=signed_txn,
is_optional=is_optional,
is_nullable=is_nullable,
inline_object_schema=inline_object_schema,
inline_meta_name=inline_meta_name,
)
)

Expand Down Expand Up @@ -855,56 +875,27 @@ def generate(
if ts_pascal_case(name) in all_used_types}



# Generate components (only used schemas)
files.update(self.schema_processor.generate_models(output_dir, used_schemas))

if service_class == "AlgodApi":
models_dir = output_dir / constants.DirectoryName.SRC / constants.DirectoryName.MODELS

# Add SuggestedParams custom model
# Generate the custom typed models
files[models_dir / "suggested-params.ts"] = self.renderer.render(
"models/transaction-params/suggested-params.ts.j2",
{"spec": spec},
)

# Custom typed block models
# Block-specific models (prefixed to avoid shape collisions)
files[models_dir / "block-eval-delta.ts"] = self.renderer.render(
"models/block/block-eval-delta.ts.j2",
{"spec": spec},
)
files[models_dir / "block-state-delta.ts"] = self.renderer.render(
"models/block/block-state-delta.ts.j2",
{"spec": spec},
)
files[models_dir / "block-account-state-delta.ts"] = self.renderer.render(
"models/block/block-account-state-delta.ts.j2",
{"spec": spec},
)
# BlockAppEvalDelta is implemented by repurposing application-eval-delta.ts.j2 to new name
files[models_dir / "block-app-eval-delta.ts"] = self.renderer.render(
"models/block/application-eval-delta.ts.j2",
{"spec": spec},
)
files[models_dir / "block_state_proof_tracking_data.ts"] = self.renderer.render(
"models/block/block-state-proof-tracking-data.ts.j2",
{"spec": spec},
)
files[models_dir / "block_state_proof_tracking.ts"] = self.renderer.render(
"models/block/block-state-proof-tracking.ts.j2",
{"spec": spec},
)
files[models_dir / "signed-txn-in-block.ts"] = self.renderer.render(
"models/block/signed-txn-in-block.ts.j2",
"models/custom/suggested-params.ts.j2",
{"spec": spec},
)
files[models_dir / "block.ts"] = self.renderer.render(
"models/block/block.ts.j2",
"models/custom/block.ts.j2",
{"spec": spec},
)
files[models_dir / "get-block.ts"] = self.renderer.render(
"models/block/get-block.ts.j2",
"models/custom/get-block.ts.j2",
{"spec": spec},
)
files[models_dir / "ledger-state-delta.ts"] = self.renderer.render(
"models/custom/ledger-state-delta.ts.j2",
{"spec": spec},
)

Expand All @@ -914,22 +905,8 @@ def generate(
extras = (
"\n"
"export type { SuggestedParams, SuggestedParamsMeta } from './suggested-params';\n"
"export type { BlockEvalDelta } from './block-eval-delta';\n"
"export { BlockEvalDeltaMeta } from './block-eval-delta';\n"
"export type { BlockStateDelta } from './block-state-delta';\n"
"export { BlockStateDeltaMeta } from './block-state-delta';\n"
"export type { BlockAccountStateDelta } from './block-account-state-delta';\n"
"export { BlockAccountStateDeltaMeta } from './block-account-state-delta';\n"
"export type { BlockAppEvalDelta } from './block-app-eval-delta';\n"
"export { BlockAppEvalDeltaMeta } from './block-app-eval-delta';\n"
"export type { BlockStateProofTrackingData } from './block_state_proof_tracking_data';\n"
"export { BlockStateProofTrackingDataMeta } from './block_state_proof_tracking_data';\n"
"export type { BlockStateProofTracking } from './block_state_proof_tracking';\n"
"export { BlockStateProofTrackingMeta } from './block_state_proof_tracking';\n"
"export type { Block } from './block';\n"
"export { BlockMeta } from './block';\n"
"export type { SignedTxnInBlock } from './signed-txn-in-block';\n"
"export { SignedTxnInBlockMeta } from './signed-txn-in-block';\n"
)
files[index_path] = base_index + extras
files.update(self._generate_client_files(output_dir, client_class, service_class))
Expand Down Expand Up @@ -962,7 +939,6 @@ def _generate_runtime(
core_dir / "fetch-http-request.ts": ("base/src/core/fetch-http-request.ts.j2", context),
core_dir / "api-error.ts": ("base/src/core/api-error.ts.j2", context),
core_dir / "request.ts": ("base/src/core/request.ts.j2", context),
core_dir / "serialization.ts": ("base/src/core/serialization.ts.j2", context),
core_dir / "codecs.ts": ("base/src/core/codecs.ts.j2", context),
core_dir / "model-runtime.ts": ("base/src/core/model-runtime.ts.j2", context),
# Project files
Expand Down
32 changes: 20 additions & 12 deletions oas-generator/src/oas_generator/templates/apis/service.ts.j2
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import { {% for t in sorted %}{{ t }}Meta{% if not loop.last %}, {% endif %}{% e

{% macro field_type_meta(type_name) -%}
{%- if type_name in import_types -%}
({ kind: 'model', meta: () => {{ type_name }}Meta } as const)
({ kind: 'model', meta: {{ type_name }}Meta } as const)
{%- elif type_name == 'SignedTransaction' -%}
({ kind: 'codec', codecKey: 'SignedTransaction' } as const)
{%- elif type_name == 'Uint8Array' -%}
Expand Down Expand Up @@ -105,8 +105,8 @@ export class {{ service_class_name }} {
{%- endif %}
): Promise<{{ op.responseTsType }}> {
const headers: Record<string, string> = {};
{% set supports_msgpack = op.returnsMsgpack or (op.requestBody and op.requestBody.supportsMsgpack) %}
const responseFormat: BodyFormat = {% if op.forceMsgpackQuery %}'msgpack'{% elif supports_msgpack %}'json'{% else %}'json'{% endif %};
{% set body_format = 'msgpack' if op.forceMsgpackQuery else 'json' %}
const responseFormat: BodyFormat = '{{ body_format }}'
headers['Accept'] = {{ service_class_name }}.acceptFor(responseFormat);

{% if op.requestBody and op.method.upper() not in ['GET', 'HEAD'] %}
Expand All @@ -118,9 +118,11 @@ export class {{ service_class_name }} {
const bodyMeta = {{ meta_expr(op.requestBody.tsType) }};
const mediaType = bodyMeta ? {{ service_class_name }}.mediaFor(responseFormat) : undefined;
if (mediaType) headers['Content-Type'] = mediaType;
const serializedBody = bodyMeta && body !== undefined
? AlgorandSerializer.encode(body, bodyMeta, responseFormat)
: body;
{% if op.requestBody and not meta_expr(op.requestBody.tsType) == 'undefined' %}
const serializedBody = body ? AlgorandSerializer.encode(body, bodyMeta, responseFormat) : undefined;
{% else %}
const serializedBody = body;
{% endif %}
{% endif %}
{% endif %}

Expand All @@ -132,7 +134,11 @@ export class {{ service_class_name }} {
}
{% endif %}

const payload = await this.httpRequest.request<unknown>({
{% if op.responseTsType == 'void' %}
await this.httpRequest.request<void>({
{% else %}
const payload = await this.httpRequest.request<{{'Uint8Array' if body_format == 'msgpack' else 'string'}}>({
{% endif %}
method: '{{ op.method }}',
url: '{{ op.path }}',
path: {
Expand All @@ -158,11 +164,13 @@ export class {{ service_class_name }} {
{% endif %}
});

const responseMeta = {{ meta_expr(op.responseTsType) }};
if (responseMeta) {
return AlgorandSerializer.decode(payload, responseMeta, responseFormat);
}
return payload as {{ op.responseTsType }};
{% if op.responseTsType != 'void' %}
{% if meta_expr(op.responseTsType) == 'undefined' %}
return payload;
{% else %}
return AlgorandSerializer.decode(payload, {{ meta_expr(op.responseTsType) }}, responseFormat);
{% endif %}
{% endif %}
}

{% endfor %}
Expand Down
Loading