diff --git a/algokit-configs/openapi-converter/specs/algod.oas3.json b/algokit-configs/openapi-converter/specs/algod.oas3.json index 629768e8..652da26e 100644 --- a/algokit-configs/openapi-converter/specs/algod.oas3.json +++ b/algokit-configs/openapi-converter/specs/algod.oas3.json @@ -4054,9 +4054,7 @@ "description": "base64 encoded program bytes" }, "sourcemap": { - "type": "object", - "properties": {}, - "description": "JSON of the source map" + "$ref": "#/components/schemas/SourceMap" } } } @@ -4695,8 +4693,7 @@ "id", "network", "proto", - "rwd", - "timestamp" + "rwd" ], "type": "object", "properties": { @@ -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": { @@ -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": { @@ -7333,9 +7364,7 @@ "description": "base64 encoded program bytes" }, "sourcemap": { - "type": "object", - "properties": {}, - "description": "JSON of the source map" + "$ref": "#/components/schemas/SourceMap" } } } diff --git a/algokit-configs/openapi-converter/specs/indexer.oas3.json b/algokit-configs/openapi-converter/specs/indexer.oas3.json index 9573accc..da9b30cb 100644 --- a/algokit-configs/openapi-converter/specs/indexer.oas3.json +++ b/algokit-configs/openapi-converter/specs/indexer.oas3.json @@ -3587,6 +3587,7 @@ "index": { "type": "integer", "description": "unique asset identifier", + "x-algokit-field-rename": "id", "x-algokit-bigint": true }, "deleted": { diff --git a/oas-generator/src/oas_generator/generator/models.py b/oas-generator/src/oas_generator/generator/models.py index c12d28db..78f9752b 100644 --- a/oas-generator/src/oas_generator/generator/models.py +++ b/oas-generator/src/oas_generator/generator/models.py @@ -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 diff --git a/oas-generator/src/oas_generator/generator/template_engine.py b/oas-generator/src/oas_generator/generator/template_engine.py index 5fdc0a0d..5866d69d 100644 --- a/oas-generator/src/oas_generator/generator/template_engine.py +++ b/oas-generator/src/oas_generator/generator/template_engine.py @@ -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]) @@ -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, @@ -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, ) ) @@ -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}, ) @@ -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)) @@ -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 diff --git a/oas-generator/src/oas_generator/templates/apis/service.ts.j2 b/oas-generator/src/oas_generator/templates/apis/service.ts.j2 index a262320f..3bd6fee0 100644 --- a/oas-generator/src/oas_generator/templates/apis/service.ts.j2 +++ b/oas-generator/src/oas_generator/templates/apis/service.ts.j2 @@ -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' -%} @@ -105,8 +105,8 @@ export class {{ service_class_name }} { {%- endif %} ): Promise<{{ op.responseTsType }}> { const headers: Record = {}; - {% 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'] %} @@ -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 %} @@ -132,7 +134,11 @@ export class {{ service_class_name }} { } {% endif %} - const payload = await this.httpRequest.request({ + {% if op.responseTsType == 'void' %} + await this.httpRequest.request({ + {% else %} + const payload = await this.httpRequest.request<{{'Uint8Array' if body_format == 'msgpack' else 'string'}}>({ + {% endif %} method: '{{ op.method }}', url: '{{ op.path }}', path: { @@ -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 %} diff --git a/oas-generator/src/oas_generator/templates/base/src/core/codecs.ts.j2 b/oas-generator/src/oas_generator/templates/base/src/core/codecs.ts.j2 index 35549be3..d500c6b6 100644 --- a/oas-generator/src/oas_generator/templates/base/src/core/codecs.ts.j2 +++ b/oas-generator/src/oas_generator/templates/base/src/core/codecs.ts.j2 @@ -1,42 +1,28 @@ import { decode as msgpackDecode, encode as msgpackEncode } from 'algorand-msgpack' -export function encodeMsgPack(data: T): Uint8Array { +export function encodeMsgPack(data: ApiData): Uint8Array { return new Uint8Array(msgpackEncode(data, { sortKeys: true, ignoreUndefined: true })); } -export function decodeMsgPack(buffer: Uint8Array): T { - const map = msgpackDecode(buffer, { useMap: true }) as unknown; - return mapToObject(map) as T; +type MsgPackDecodeOptions = { + useMap: boolean; + rawBinaryStringKeys: boolean; + rawBinaryStringValues: boolean; } -/** - * Converts a Map structure from msgpack decoding to a plain object structure. - * Maps are converted to objects recursively, except for the special case - * where the field name is "r" which remains as a Map. - */ -function mapToObject(value: unknown, fieldName?: string): unknown { - // Preserve Uint8Array as-is - if (value instanceof Uint8Array) { - return value; - } else if (value instanceof Map) { - // Special case: keep "r" field as Map - if (fieldName === 'r') { - const newMap = new Map(); - for (const [k, v] of value.entries()) { - newMap.set(k, mapToObject(v)); - } - return newMap; - } - - // Convert Map to object - const obj: Record = {}; - for (const [k, v] of value.entries()) { - obj[k] = mapToObject(v, k); - } - return obj; - } else if (Array.isArray(value)) { - return value.map((item) => mapToObject(item)); - } - - return value; +export function decodeMsgPack( + buffer: Uint8Array, + options: MsgPackDecodeOptions = { useMap: true, rawBinaryStringKeys: true, rawBinaryStringValues: true }, +): Map { + return msgpackDecode(buffer, options) as Map; } +export type ApiData = + | null + | undefined + | string + | number + | bigint + | boolean + | Uint8Array + | object + | Map // TODO: NC - Do we ever have a string key? diff --git a/oas-generator/src/oas_generator/templates/base/src/core/model-runtime.ts.j2 b/oas-generator/src/oas_generator/templates/base/src/core/model-runtime.ts.j2 index dde27e9d..649ff070 100644 --- a/oas-generator/src/oas_generator/templates/base/src/core/model-runtime.ts.j2 +++ b/oas-generator/src/oas_generator/templates/base/src/core/model-runtime.ts.j2 @@ -1,19 +1,24 @@ import { - encodeSignedTransaction as transactEncodeSignedTransaction, - decodeSignedTransaction as transactDecodeSignedTransaction, + addressFromPublicKey, + decodedTransactionMapToObject, + fromSignedTransactionDto, + toSignedTransactionDto, type SignedTransaction, } from '@algorandfoundation/algokit-transact'; -import { encodeMsgPack, decodeMsgPack } from './codecs'; -import { toBase64, fromBase64 } from './serialization'; +import { Buffer } from 'buffer'; +import { ApiData, decodeMsgPack, encodeMsgPack } from './codecs'; export type BodyFormat = 'json' | 'msgpack' | 'map'; export interface ScalarFieldType { readonly kind: 'scalar'; + // TODO: NC - Make this a type field readonly isBytes?: boolean; readonly isBigint?: boolean; + readonly isAddress?: boolean; } +// TODO: NC - Needs to be renamed export interface CodecFieldType { readonly kind: 'codec'; readonly codecKey: string; @@ -34,7 +39,13 @@ export interface RecordFieldType { readonly value: FieldType; } -export type FieldType = ScalarFieldType | CodecFieldType | ModelFieldType | ArrayFieldType | RecordFieldType; +export interface MapFieldType { + readonly kind: 'map'; + readonly keyType: 'number' | 'bigint' | 'bytes'; + readonly value: FieldType; +} + +export type FieldType = ScalarFieldType | CodecFieldType | ModelFieldType | ArrayFieldType | RecordFieldType | MapFieldType; export interface FieldMetadata { readonly name: string; @@ -61,39 +72,22 @@ export interface ModelMetadata { readonly passThrough?: FieldType; } -// Registry for model metadata to avoid direct circular imports between model files -const modelMetaRegistry = new Map(); - -export function registerModelMeta(name: string, meta: ModelMetadata): void { - modelMetaRegistry.set(name, meta); -} - -export function getModelMeta(name: string): ModelMetadata { - const meta = modelMetaRegistry.get(name); - if (!meta) throw new Error(`Model metadata not registered: ${name}`); - return meta; -} - -export interface TypeCodec { - encode(value: TValue, format: BodyFormat): unknown; - decode(value: unknown, format: BodyFormat): TValue; +export interface EncodeableTypeConverter> { + beforeEncoding(value: T, format: BodyFormat): Record; + afterDecoding(decoded: Record | Map, format: BodyFormat): T; } -const codecRegistry = new Map>(); - -export function registerCodec(key: string, codec: TypeCodec): void { - codecRegistry.set(key, codec as TypeCodec); -} +const encodeableTypeConverterRegistry = new Map>>(); -export function getCodec(key: string): TypeCodec | undefined { - return codecRegistry.get(key) as TypeCodec | undefined; +export function registerEncodeableTypeConverter(key: string, codec: EncodeableTypeConverter>): void { + encodeableTypeConverterRegistry.set(key, codec); } export class AlgorandSerializer { - static encode(value: unknown, meta: ModelMetadata, format: 'map'): Map - static encode(value: unknown, meta: ModelMetadata, format: 'json'): string - static encode(value: unknown, meta: ModelMetadata, format?: 'msgpack'): Uint8Array - static encode(value: unknown, meta: ModelMetadata, format: BodyFormat = 'msgpack'): Uint8Array | string | Map { + static encode(value: Record, meta: ModelMetadata, format: 'map'): Map + static encode(value: Record, meta: ModelMetadata, format: 'json'): string + static encode(value: Record, meta: ModelMetadata, format?: 'msgpack'): Uint8Array + static encode(value: Record, meta: ModelMetadata, format: BodyFormat = 'msgpack'): Uint8Array | string | Map { if (format === 'map') { // For map format, use msgpack transformation to preserve types like bigint, then convert to nested Maps const wire = this.transform(value, meta, { direction: 'encode', format: 'msgpack' }); @@ -107,25 +101,25 @@ export class AlgorandSerializer { return typeof wire === 'string' ? wire : JSON.stringify(wire); } - static decode(payload: unknown, meta: ModelMetadata, format: BodyFormat = 'msgpack'): T { - let wire: unknown = payload; + static decode(value: Uint8Array | string, meta: ModelMetadata, format: BodyFormat = 'msgpack'): T { + let wire: ApiData = value; if (format === 'msgpack') { - if (payload instanceof Uint8Array) { - wire = decodeMsgPack(payload); + if (value instanceof Uint8Array) { + wire = decodeMsgPack(value); } - } else if (typeof payload === 'string') { - wire = JSON.parse(payload); + } else if (typeof value === 'string') { + wire = JSON.parse(value); } return this.transform(wire, meta, { direction: 'decode', format }) as T; } - private static transform(value: unknown, meta: ModelMetadata, ctx: TransformContext): unknown { + private static transform(value: ApiData, meta: ModelMetadata, ctx: TransformContext): ApiData { if (value === undefined || value === null) { return value; } if (meta.codecKey) { - return this.applyCodec(value, meta.codecKey, ctx); + return this.applyEncodeableTypeConversion(value, meta.codecKey, ctx); } switch (meta.kind) { @@ -139,22 +133,20 @@ export class AlgorandSerializer { } } - private static transformObject(value: unknown, meta: ModelMetadata, ctx: TransformContext): unknown { + private static transformObject(value: ApiData, meta: ModelMetadata, ctx: TransformContext): ApiData { const fields = meta.fields ?? []; - const hasFlattenedSignedTxn = fields.some( - (f) => f.flattened && f.type.kind === 'codec' && f.type.codecKey === 'SignedTransaction', - ); + const hasFlattenedField = fields.some((f) => f.flattened); if (ctx.direction === 'encode') { - const src = value as Record; - const out: Record = {}; + const src = value as Record; + const out: Record = {}; for (const field of fields) { const fieldValue = src[field.name]; if (fieldValue === undefined) continue; const encoded = this.transformType(fieldValue, field.type, ctx); if (encoded === undefined && fieldValue === undefined) continue; - if (field.flattened && field.type.kind === 'codec' && field.type.codecKey === 'SignedTransaction') { - // Merge signed transaction map into parent - const mapValue = encoded as Record; + if (field.flattened) { + // Merge flattened field into parent + const mapValue = encoded as Record; for (const [k, v] of Object.entries(mapValue ?? {})) out[k] = v; continue; } @@ -169,66 +161,206 @@ export class AlgorandSerializer { return out; } - const src = value as Record; - const out: Record = {}; + // Decoding + const out: Record = {}; const fieldByWire = new Map(fields.filter((f) => !!f.wireKey).map((field) => [field.wireKey as string, field])); - for (const [wireKey, wireValue] of Object.entries(src)) { - const field = fieldByWire.get(wireKey); + // Build a map of wire keys for each flattened field + const flattenedFieldWireKeys = new Map>(); + if (hasFlattenedField) { + for (const field of fields) { + if (field.flattened && field.type.kind === 'model') { + const modelMeta = typeof field.type.meta === 'function' ? field.type.meta() : field.type.meta; + const wireKeys = this.collectWireKeys(modelMeta); + flattenedFieldWireKeys.set(field, wireKeys); + } + } + } + + const entries = value instanceof Map ? Array.from(value.entries()) : Object.entries(value as Record); + const unmatchedEntries = new Map(); + + for (const [key, wireValue] of entries) { + const wireKey = key instanceof Uint8Array ? Buffer.from(key).toString('utf-8') : key; + const isStringKey = typeof wireKey === 'string'; + const field = isStringKey ? fieldByWire.get(wireKey) : undefined; + if (field) { const decoded = this.transformType(wireValue, field.type, ctx); - out[field.name] = decoded; + out[field.name] = decoded === null && !field.nullable ? undefined : decoded; continue; } - if (meta.additionalProperties) { + + if (isStringKey && meta.additionalProperties) { out[wireKey] = this.transformType(wireValue, meta.additionalProperties, ctx); continue; } - // If we have a flattened SignedTransaction, skip unknown keys (e.g., 'sig', 'txn') - if (!hasFlattenedSignedTxn) { - out[wireKey] = wireValue; + + // Store unmatched entries for potential flattened field reconstruction + if (isStringKey) { + unmatchedEntries.set(wireKey, wireValue) } } - // If there are flattened fields, attempt to reconstruct them from remaining keys by decoding - for (const field of fields) { - if (out[field.name] !== undefined) continue; - if (field.flattened && field.type.kind === 'codec' && field.type.codecKey === 'SignedTransaction') { - // Reconstruct from entire object map - out[field.name] = this.applyCodec(src, 'SignedTransaction', ctx); + // Reconstruct flattened fields from unmatched entries + if (hasFlattenedField) { + for (const field of fields) { + if (out[field.name] !== undefined) continue; + if (field.flattened) { + if (field.type.kind === 'codec') { + // Reconstruct codec from entire object map + out[field.name] = this.applyEncodeableTypeConversion(value, field.type.codecKey, ctx); + } else if (field.type.kind === 'model') { + const modelMeta = typeof field.type.meta === 'function' ? field.type.meta() : field.type.meta; + + // Check if this flattened model contains nested flattened codecs + const hasNestedCodec = this.hasNestedFlattenedCodec(modelMeta); + + let decoded: ApiData; + if (hasNestedCodec) { + // If the model has nested flattened codecs, we need to pass the original value + // so the nested model can reconstruct its flattened codec fields + decoded = this.transform(value, modelMeta, ctx); + } else { + // Filter the wire data to only include keys belonging to this flattened model + const modelWireKeys = flattenedFieldWireKeys.get(field); + if (modelWireKeys) { + const filteredData: Record = {}; + for (const [k, v] of unmatchedEntries.entries()) { + if (modelWireKeys.has(k)) { + filteredData[k] = v; + } + } + // Also check if the original value is a Map and filter it + if (value instanceof Map) { + const filteredMap = new Map(); + for (const [k, v] of value.entries()) { + const keyStr = k instanceof Uint8Array ? Buffer.from(k).toString('utf-8') : String(k); + if (typeof keyStr === 'string' && modelWireKeys.has(keyStr)) { + filteredMap.set(k as string | Uint8Array, v); + } + } + decoded = this.transform(filteredMap, modelMeta, ctx); + } else { + decoded = this.transform(filteredData, modelMeta, ctx); + } + } else { + decoded = undefined; + } + } + + // If the field is optional and the decoded object is empty, set it to undefined + if (field.optional && decoded !== undefined && this.isEmptyObject(decoded)) { + out[field.name] = undefined; + } else { + out[field.name] = decoded; + } + } + } + } + } + + // Add any remaining unmatched entries if there are no flattened fields + if (!hasFlattenedField) { + for (const [k, v] of unmatchedEntries.entries()) { + out[k] = v; } } return out; } - private static transformType(value: unknown, type: FieldType, ctx: TransformContext): unknown { + private static collectWireKeys(meta: ModelMetadata): Set { + const wireKeys = new Set(); + if (meta.kind !== 'object' || !meta.fields) return wireKeys; + + for (const field of meta.fields) { + if (field.wireKey) { + wireKeys.add(field.wireKey); + } + if (field.flattened && field.type.kind === 'model') { + const childMeta = typeof field.type.meta === 'function' ? field.type.meta() : field.type.meta; + const childKeys = this.collectWireKeys(childMeta); + for (const key of childKeys) { + wireKeys.add(key); + } + } + // Note: flattened codec fields don't have predictable wire keys, + // so they need to be handled differently during reconstruction + } + + return wireKeys; + } + + private static hasNestedFlattenedCodec(meta: ModelMetadata): boolean { + if (meta.kind !== 'object' || !meta.fields) return false; + + for (const field of meta.fields) { + if (field.flattened) { + if (field.type.kind === 'codec') { + return true; + } + if (field.type.kind === 'model') { + const childMeta = typeof field.type.meta === 'function' ? field.type.meta() : field.type.meta; + if (this.hasNestedFlattenedCodec(childMeta)) { + return true; + } + } + } + } + + return false; + } + + private static isEmptyObject(value: ApiData): boolean { + if (value === null || value === undefined) return true; + if (typeof value !== 'object') return false; + if (Array.isArray(value)) return false; + if (value instanceof Uint8Array) return false; + if (value instanceof Map) return value.size === 0; + + // Check if it's a plain object with no own properties (excluding undefined values) + const keys = Object.keys(value); + if (keys.length === 0) return true; + + // Check if all properties are undefined + return keys.every((key) => (value as Record)[key] === undefined); + } + + private static transformType(value: ApiData, type: FieldType, ctx: TransformContext): ApiData { if (value === undefined || value === null) return value; switch (type.kind) { case 'scalar': return this.transformScalar(value, type, ctx); case 'codec': - return this.applyCodec(value, type.codecKey, ctx); + return this.applyEncodeableTypeConversion(value, type.codecKey, ctx); case 'model': return this.transform(value, typeof type.meta === 'function' ? type.meta() : type.meta, ctx); case 'array': if (!Array.isArray(value)) return value; return value.map((item) => this.transformType(item, type.item, ctx)); - case 'record': - if (typeof value !== 'object' || value === null) return value; + case 'record': { + if ((!(value instanceof Map) && typeof value !== 'object') || value === null) return value + const entries = value instanceof Map ? Array.from(value.entries()) : Object.entries(value as Record); return Object.fromEntries( - Object.entries(value as Record).map(([k, v]) => [k, this.transformType(v, type.value, ctx)]), + entries.map(([k, v]) => { + const key = k instanceof Uint8Array ? Buffer.from(k).toString('utf-8') : k; + return [key, this.transformType(v, type.value, ctx)]; + }), ); + } + case 'map': + return this.transformMap(value, type, ctx); default: return value; } } - private static transformScalar(value: unknown, meta: ScalarFieldType, ctx: TransformContext): unknown { + private static transformScalar(value: ApiData, meta: ScalarFieldType, ctx: TransformContext): ApiData { if (ctx.direction === 'encode') { if (meta.isBytes && ctx.format === 'json') { - if (value instanceof Uint8Array) return toBase64(value); + if (value instanceof Uint8Array) return Buffer.from(value).toString('base64'); } if (meta.isBigint && ctx.format === 'json') { if (typeof value === 'bigint') return value.toString(); @@ -239,7 +371,17 @@ export class AlgorandSerializer { } if (meta.isBytes && ctx.format === 'json' && typeof value === 'string') { - return fromBase64(value); + return new Uint8Array(Buffer.from(value, 'base64')); + } + + if (value instanceof Uint8Array) { + if (meta.isAddress) { + // TODO: NC - Fix all the address models to have this on it. + return addressFromPublicKey(value); + } else if (!meta.isBytes) { + return Buffer.from(value).toString('utf-8'); + } + return value; } if (meta.isBigint) { @@ -255,20 +397,61 @@ export class AlgorandSerializer { } } + if (value instanceof Map) { + const out: Record = {}; + for (const [k, v] of value.entries()) { + const key = k instanceof Uint8Array ? Buffer.from(k).toString('utf-8') : k.toString(); + out[key] = this.transformType(v, { kind: 'scalar', isBytes: v instanceof Uint8Array }, ctx); + } + return out; + } + + if (Array.isArray(value)) { + return value.map((item) => this.transformType(item, { kind: 'scalar', isBytes: item instanceof Uint8Array }, ctx)); + } + return value; } - private static applyCodec(value: unknown, codecKey: string, ctx: TransformContext): unknown { - const codec = codecRegistry.get(codecKey); + private static applyEncodeableTypeConversion(value: ApiData, typeKey: string, ctx: TransformContext): ApiData { + const codec = encodeableTypeConverterRegistry.get(typeKey); if (!codec) { - throw new Error(`Codec for "${codecKey}" is not registered`); + throw new Error(`Type converter for "${typeKey}" is not registered`); } - return ctx.direction === 'encode' - ? codec.encode(value, ctx.format) - : codec.decode(value, ctx.format); + + // TODO: NC - Need to properly guard against these conditions + if (ctx.direction === 'encode') { + if (value instanceof Map) { + throw new Error(`Cannot encode Map with type converter "${typeKey}"`); + } + return codec.beforeEncoding(value as Parameters[0], ctx.format); + } + + return codec.afterDecoding(value as Parameters[0], ctx.format); } - private static convertToNestedMaps(value: unknown): Map | unknown[] | unknown { + private static transformMap(value: ApiData, meta: MapFieldType, ctx: TransformContext): ApiData { + if (ctx.direction === 'encode') { + if (!(value instanceof Map)) return value; + const result = new Map() + for (const [k, v] of value.entries()) { + const transformedValue = this.transformType(v, meta.value, ctx); + result.set(k, transformedValue) + } + return result; + } + // Decoding + if ((!(value instanceof Map) && typeof value !== 'object') || value === null) return value; + const entries = value instanceof Map ? Array.from(value.entries()) : Object.entries(value as Record); + const result = new Map(); + for (const [k, v] of entries) { + const transformedValue = this.transformType(v, meta.value, ctx); + result.set(k, transformedValue); + } + return result; + } + + private static convertToNestedMaps(value: ApiData): Map | ApiData[] | ApiData { if (value === null || value === undefined) { return value; } @@ -286,8 +469,8 @@ export class AlgorandSerializer { } if (typeof value === 'object' && value !== null && !(value instanceof Uint8Array)) { - const map = new Map(); - Object.entries(value as Record).forEach(([key, val]) => { + const map = new Map(); + Object.entries(value as Record).forEach(([key, val]) => { map.set(key, this.convertToNestedMaps(val)); }); return map; @@ -305,42 +488,23 @@ interface TransformContext { readonly format: BodyFormat; } -const encodeSignedTransactionImpl = (value: unknown): Uint8Array => - transactEncodeSignedTransaction(value as SignedTransaction); -const decodeSignedTransactionImpl = (value: Uint8Array): SignedTransaction => - transactDecodeSignedTransaction(value); - -class SignedTransactionCodec implements TypeCodec { - encode(value: unknown, format: BodyFormat): unknown { - if (value == null) return value; +class SignedTransactionConverter implements EncodeableTypeConverter { + beforeEncoding(value: SignedTransaction, format: BodyFormat): Record { if (format === 'json') { - if (value instanceof Uint8Array) return toBase64(value); - return toBase64(encodeSignedTransactionImpl(value)); + throw new Error('JSON format not supported for SignedTransaction encoding'); } - if (value instanceof Uint8Array) { - // Already canonical bytes; decode to structured map so parent encoding keeps map semantics - return decodeMsgPack(value); - } - // Convert signed transaction object into canonical map representation - return decodeMsgPack(encodeSignedTransactionImpl(value)); + return toSignedTransactionDto(value); } - - decode(value: unknown, format: BodyFormat): unknown { - if (value == null) return value; - if (format === 'json') { - if (typeof value === 'string') return decodeSignedTransactionImpl(fromBase64(value)); - if (value instanceof Uint8Array) return decodeSignedTransactionImpl(value); - return value; + afterDecoding(value: Record | Map, format: BodyFormat): SignedTransaction { + if (format === 'json' || !(value instanceof Map)) { + throw new Error('JSON format not supported for SignedTransaction decoding'); } - if (value instanceof Uint8Array) return decodeSignedTransactionImpl(value); - // Value is a decoded map; re-encode to bytes before handing to transact decoder - try { - return decodeSignedTransactionImpl(encodeMsgPack(value)); - } catch { - return value; + if (!(value instanceof Map)) { + throw new Error('Invalid decoded msgpack format for SignedTransaction'); } + const stxnDto = decodedTransactionMapToObject(value) as Parameters[0]; + return fromSignedTransactionDto(stxnDto); } } -registerCodec('SignedTransaction', new SignedTransactionCodec()); - +registerEncodeableTypeConverter('SignedTransaction', new SignedTransactionConverter()); diff --git a/oas-generator/src/oas_generator/templates/base/src/core/request.ts.j2 b/oas-generator/src/oas_generator/templates/base/src/core/request.ts.j2 index 88aef710..a13e481d 100644 --- a/oas-generator/src/oas_generator/templates/base/src/core/request.ts.j2 +++ b/oas-generator/src/oas_generator/templates/base/src/core/request.ts.j2 @@ -88,7 +88,11 @@ export async function request(config: ClientConfig, options: { try { const ct = response.headers.get('content-type') ?? ''; if (ct.includes('application/msgpack')) { - errorBody = decodeMsgPack(new Uint8Array(await response.arrayBuffer())); + errorBody = decodeMsgPack(new Uint8Array(await response.arrayBuffer()), { + useMap: false, + rawBinaryStringKeys: false, + rawBinaryStringValues: false, + }); } else if (ct.includes('application/json')) { errorBody = JSON.parse(await response.text()); } else { diff --git a/oas-generator/src/oas_generator/templates/base/src/core/serialization.ts.j2 b/oas-generator/src/oas_generator/templates/base/src/core/serialization.ts.j2 deleted file mode 100644 index 411a8bb6..00000000 --- a/oas-generator/src/oas_generator/templates/base/src/core/serialization.ts.j2 +++ /dev/null @@ -1,26 +0,0 @@ -export function toBase64(bytes: Uint8Array): string { - if (typeof Buffer !== 'undefined') { - return Buffer.from(bytes).toString('base64'); - } - const globalRef: Record = globalThis as unknown as Record; - const btoaFn = globalRef.btoa as ((value: string) => string) | undefined; - if (typeof btoaFn === 'function') { - return btoaFn(String.fromCharCode(...bytes)); - } - throw new Error('Base64 encoding not supported in this environment'); -} - -export function fromBase64(s: string): Uint8Array { - if (typeof Buffer !== 'undefined') { - return new Uint8Array(Buffer.from(s, 'base64')); - } - const globalRef: Record = globalThis as unknown as Record; - const atobFn = globalRef.atob as ((value: string) => string) | undefined; - if (typeof atobFn === 'function') { - const bin = atobFn(s); - const out = new Uint8Array(bin.length); - for (let i = 0; i < bin.length; i += 1) out[i] = bin.charCodeAt(i); - return out; - } - throw new Error('Base64 decoding not supported in this environment'); -} diff --git a/oas-generator/src/oas_generator/templates/base/src/index.ts.j2 b/oas-generator/src/oas_generator/templates/base/src/index.ts.j2 index 6eb10e70..1dd63e72 100644 --- a/oas-generator/src/oas_generator/templates/base/src/index.ts.j2 +++ b/oas-generator/src/oas_generator/templates/base/src/index.ts.j2 @@ -2,7 +2,6 @@ export * from './core/client-config'; export * from './core/base-http-request'; export * from './core/fetch-http-request'; export * from './core/api-error'; -export * from './core/serialization'; export * from './core/codecs'; export * from './core/model-runtime'; diff --git a/oas-generator/src/oas_generator/templates/models/block/application-eval-delta.ts.j2 b/oas-generator/src/oas_generator/templates/models/block/application-eval-delta.ts.j2 deleted file mode 100644 index 849ec0f2..00000000 --- a/oas-generator/src/oas_generator/templates/models/block/application-eval-delta.ts.j2 +++ /dev/null @@ -1,37 +0,0 @@ -import type { ModelMetadata } from '../core/model-runtime'; -import { getModelMeta, registerModelMeta } from '../core/model-runtime'; -import type { SignedTxnInBlock } from './signed-txn-in-block'; -import type { BlockStateDelta } from './block-state-delta'; -import { BlockStateDeltaMeta } from './block-state-delta'; - -/** - * State changes from application execution, including inner transactions and logs. - */ -export interface BlockAppEvalDelta { - /** [gd] Global state delta for the application. */ - globalDelta?: BlockStateDelta; - /** [ld] Local state deltas keyed by address index. */ - localDeltas?: Record; - /** [itx] Inner transactions produced by this application execution. */ - innerTxns?: SignedTxnInBlock[]; - /** [sa] Shared accounts referenced by local deltas. */ - sharedAccounts?: Uint8Array[]; - /** [lg] Application log outputs. */ - logs?: Uint8Array[]; -} - -export const BlockAppEvalDeltaMeta: ModelMetadata = { - name: 'BlockAppEvalDelta', - kind: 'object', - fields: [ - { name: 'globalDelta', wireKey: 'gd', optional: true, nullable: false, type: { kind: 'model', meta: () => BlockStateDeltaMeta } }, - { name: 'localDeltas', wireKey: 'ld', optional: true, nullable: false, type: { kind: 'record', value: { kind: 'model', meta: () => BlockStateDeltaMeta } } }, - { name: 'innerTxns', wireKey: 'itx', optional: true, nullable: false, type: { kind: 'array', item: { kind: 'model', meta: () => getModelMeta('SignedTxnInBlock') } } }, - { name: 'sharedAccounts', wireKey: 'sa', optional: true, nullable: false, type: { kind: 'array', item: { kind: 'scalar', isBytes: true } } }, - { name: 'logs', wireKey: 'lg', optional: true, nullable: false, type: { kind: 'array', item: { kind: 'scalar', isBytes: true } } }, - ], -}; - -registerModelMeta('BlockAppEvalDelta', BlockAppEvalDeltaMeta); - - diff --git a/oas-generator/src/oas_generator/templates/models/block/block-account-state-delta.ts.j2 b/oas-generator/src/oas_generator/templates/models/block/block-account-state-delta.ts.j2 deleted file mode 100644 index d49bb97a..00000000 --- a/oas-generator/src/oas_generator/templates/models/block/block-account-state-delta.ts.j2 +++ /dev/null @@ -1,22 +0,0 @@ -import type { ModelMetadata } from '../core/model-runtime'; -import { registerModelMeta } from '../core/model-runtime'; -import { BlockStateDeltaMeta } from './block-state-delta'; - -/** BlockAccountStateDelta pairs an address with a BlockStateDelta map. */ -export interface BlockAccountStateDelta { - address: string; - delta: import('./block-state-delta').BlockStateDelta; -} - -export const BlockAccountStateDeltaMeta: ModelMetadata = { - name: 'BlockAccountStateDelta', - kind: 'object', - fields: [ - { name: 'address', wireKey: 'address', optional: false, nullable: false, type: { kind: 'scalar' } }, - { name: 'delta', wireKey: 'delta', optional: false, nullable: false, type: { kind: 'model', meta: () => BlockStateDeltaMeta } }, - ], -}; - -registerModelMeta('BlockAccountStateDelta', BlockAccountStateDeltaMeta); - - diff --git a/oas-generator/src/oas_generator/templates/models/block/block-eval-delta.ts.j2 b/oas-generator/src/oas_generator/templates/models/block/block-eval-delta.ts.j2 deleted file mode 100644 index 93d9eb02..00000000 --- a/oas-generator/src/oas_generator/templates/models/block/block-eval-delta.ts.j2 +++ /dev/null @@ -1,26 +0,0 @@ -import type { ModelMetadata } from '../core/model-runtime'; -import { registerModelMeta } from '../core/model-runtime'; - -/** BlockEvalDelta represents a TEAL value delta (block/msgpack wire keys). */ -export interface BlockEvalDelta { - /** [at] delta action. */ - action: number; - /** [bs] bytes value. */ - bytes?: string; - /** [ui] uint value. */ - uint?: bigint; -} - -export const BlockEvalDeltaMeta: ModelMetadata = { - name: 'BlockEvalDelta', - kind: 'object', - fields: [ - { name: 'action', wireKey: 'at', optional: false, nullable: false, type: { kind: 'scalar' } }, - { name: 'bytes', wireKey: 'bs', optional: true, nullable: false, type: { kind: 'scalar' } }, - { name: 'uint', wireKey: 'ui', optional: true, nullable: false, type: { kind: 'scalar', isBigint: true } }, - ], -}; - -registerModelMeta('BlockEvalDelta', BlockEvalDeltaMeta); - - diff --git a/oas-generator/src/oas_generator/templates/models/block/block-state-delta.ts.j2 b/oas-generator/src/oas_generator/templates/models/block/block-state-delta.ts.j2 deleted file mode 100644 index 945a2f89..00000000 --- a/oas-generator/src/oas_generator/templates/models/block/block-state-delta.ts.j2 +++ /dev/null @@ -1,16 +0,0 @@ -import type { ModelMetadata } from '../core/model-runtime'; -import { registerModelMeta } from '../core/model-runtime'; -import { BlockEvalDeltaMeta } from './block-eval-delta'; - -/** BlockStateDelta is a map keyed by state key to BlockEvalDelta. */ -export type BlockStateDelta = Record; - -export const BlockStateDeltaMeta: ModelMetadata = { - name: 'BlockStateDelta', - kind: 'object', - additionalProperties: { kind: 'model', meta: () => BlockEvalDeltaMeta }, -}; - -registerModelMeta('BlockStateDelta', BlockStateDeltaMeta); - - diff --git a/oas-generator/src/oas_generator/templates/models/block/block-state-proof-tracking-data.ts.j2 b/oas-generator/src/oas_generator/templates/models/block/block-state-proof-tracking-data.ts.j2 deleted file mode 100644 index 6306e924..00000000 --- a/oas-generator/src/oas_generator/templates/models/block/block-state-proof-tracking-data.ts.j2 +++ /dev/null @@ -1,26 +0,0 @@ -import type { ModelMetadata } from '../core/model-runtime'; -import { registerModelMeta } from '../core/model-runtime'; - -/** Tracking metadata for a specific StateProofType. */ -export interface BlockStateProofTrackingData { - /** [v] Vector commitment root of state proof voters. */ - stateProofVotersCommitment?: Uint8Array; - /** [t] Online total weight during state proof round. */ - stateProofOnlineTotalWeight?: bigint; - /** [n] Next round for which state proofs are accepted. */ - stateProofNextRound?: bigint; -} - -export const BlockStateProofTrackingDataMeta: ModelMetadata = { - name: 'BlockStateProofTrackingData', - kind: 'object', - fields: [ - { name: 'stateProofVotersCommitment', wireKey: 'v', optional: true, nullable: false, type: { kind: 'scalar', isBytes: true } }, - { name: 'stateProofOnlineTotalWeight', wireKey: 't', optional: true, nullable: false, type: { kind: 'scalar', isBigint: true } }, - { name: 'stateProofNextRound', wireKey: 'n', optional: true, nullable: false, type: { kind: 'scalar', isBigint: true } }, - ], -}; - -registerModelMeta('BlockStateProofTrackingData', BlockStateProofTrackingDataMeta); - - diff --git a/oas-generator/src/oas_generator/templates/models/block/block-state-proof-tracking.ts.j2 b/oas-generator/src/oas_generator/templates/models/block/block-state-proof-tracking.ts.j2 deleted file mode 100644 index c2ccaef9..00000000 --- a/oas-generator/src/oas_generator/templates/models/block/block-state-proof-tracking.ts.j2 +++ /dev/null @@ -1,17 +0,0 @@ -import type { ModelMetadata } from '../core/model-runtime'; -import { registerModelMeta } from '../core/model-runtime'; -import type { BlockStateProofTrackingData } from './block_state_proof_tracking_data'; -import { BlockStateProofTrackingDataMeta } from './block_state_proof_tracking_data'; - -/** Tracks state proof metadata by state proof type. */ -export type BlockStateProofTracking = Record; - -export const BlockStateProofTrackingMeta: ModelMetadata = { - name: 'BlockStateProofTracking', - kind: 'object', - additionalProperties: { kind: 'model', meta: () => BlockStateProofTrackingDataMeta }, -}; - -registerModelMeta('BlockStateProofTracking', BlockStateProofTrackingMeta); - - diff --git a/oas-generator/src/oas_generator/templates/models/block/block.ts.j2 b/oas-generator/src/oas_generator/templates/models/block/block.ts.j2 deleted file mode 100644 index 465ebd0d..00000000 --- a/oas-generator/src/oas_generator/templates/models/block/block.ts.j2 +++ /dev/null @@ -1,120 +0,0 @@ -import type { ModelMetadata } from '../core/model-runtime'; -import type { SignedTxnInBlock } from './signed-txn-in-block'; -import { SignedTxnInBlockMeta } from './signed-txn-in-block'; -import type { BlockStateProofTracking } from './block_state_proof_tracking'; -import { BlockStateProofTrackingMeta } from './block_state_proof_tracking'; - -/** - * Block contains the BlockHeader and the list of transactions (Payset). - */ -export interface Block { - /** [rnd] Round number. */ - round?: bigint; - /** [prev] Previous block hash. */ - previousBlockHash?: Uint8Array; - /** [prev512] Previous block hash using SHA-512. */ - previousBlockHash512?: Uint8Array; - /** [seed] Sortition seed. */ - seed?: Uint8Array; - /** [txn] Root of transaction merkle tree using SHA512_256. */ - transactionsRoot?: Uint8Array; - /** [txn256] Root of transaction vector commitment using SHA256. */ - transactionsRootSha256?: Uint8Array; - /** [txn512] Root of transaction vector commitment using SHA512. */ - transactionsRootSha512?: Uint8Array; - /** [ts] Block timestamp in seconds since epoch. */ - timestamp?: bigint; - /** [gen] Genesis ID. */ - genesisId?: string; - /** [gh] Genesis hash. */ - genesisHash?: Uint8Array; - /** [prp] Proposer address. */ - proposer?: Uint8Array; - /** [fc] Fees collected in this block. */ - feesCollected?: bigint; - /** [bi] Bonus incentive for block proposal. */ - bonus?: bigint; - /** [pp] Proposer payout. */ - proposerPayout?: bigint; - /** [fees] FeeSink address. */ - feeSink?: Uint8Array; - /** [rwd] RewardsPool address. */ - rewardsPool?: Uint8Array; - /** [earn] Rewards level. */ - rewardsLevel?: bigint; - /** [rate] Rewards rate. */ - rewardsRate?: bigint; - /** [frac] Rewards residue. */ - rewardsResidue?: bigint; - /** [rwcalr] Rewards recalculation round. */ - rewardsRecalculationRound?: bigint; - /** [proto] Current consensus protocol. */ - currentProtocol?: string; - /** [nextproto] Next proposed protocol. */ - nextProtocol?: string; - /** [nextyes] Next protocol approvals. */ - nextProtocolApprovals?: bigint; - /** [nextbefore] Next protocol vote deadline. */ - nextProtocolVoteBefore?: bigint; - /** [nextswitch] Next protocol switch round. */ - nextProtocolSwitchOn?: bigint; - /** [upgradeprop] Upgrade proposal. */ - upgradePropose?: string; - /** [upgradedelay] Upgrade delay in rounds. */ - upgradeDelay?: bigint; - /** [upgradeyes] Upgrade approval flag. */ - upgradeApprove?: boolean; - /** [tc] Transaction counter. */ - txnCounter?: bigint; - /** [spt] State proof tracking data keyed by state proof type. */ - stateProofTracking?: BlockStateProofTracking; - /** [partupdrmv] Expired participation accounts. */ - expiredParticipationAccounts?: Uint8Array[]; - /** [partupdabs] Absent participation accounts. */ - absentParticipationAccounts?: Uint8Array[]; - /** [txns] Block transactions (Payset). */ - transactions?: SignedTxnInBlock[]; -} - -export const BlockMeta: ModelMetadata = { - name: 'Block', - kind: 'object', - fields: [ - { name: 'round', wireKey: 'rnd', optional: true, nullable: false, type: { kind: 'scalar', isBigint: true } }, - { name: 'previousBlockHash', wireKey: 'prev', optional: true, nullable: false, type: { kind: 'scalar', isBytes: true } }, - { name: 'previousBlockHash512', wireKey: 'prev512', optional: true, nullable: false, type: { kind: 'scalar', isBytes: true } }, - { name: 'seed', wireKey: 'seed', optional: true, nullable: false, type: { kind: 'scalar', isBytes: true } }, - { name: 'transactionsRoot', wireKey: 'txn', optional: true, nullable: false, type: { kind: 'scalar', isBytes: true } }, - { name: 'transactionsRootSha256', wireKey: 'txn256', optional: true, nullable: false, type: { kind: 'scalar', isBytes: true } }, - { name: 'transactionsRootSha512', wireKey: 'txn512', optional: true, nullable: false, type: { kind: 'scalar', isBytes: true } }, - { name: 'timestamp', wireKey: 'ts', optional: true, nullable: false, type: { kind: 'scalar', isBigint: true } }, - { name: 'genesisId', wireKey: 'gen', optional: true, nullable: false, type: { kind: 'scalar' } }, - { name: 'genesisHash', wireKey: 'gh', optional: true, nullable: false, type: { kind: 'scalar', isBytes: true } }, - { name: 'proposer', wireKey: 'prp', optional: true, nullable: false, type: { kind: 'scalar', isBytes: true } }, - { name: 'feesCollected', wireKey: 'fc', optional: true, nullable: false, type: { kind: 'scalar', isBigint: true } }, - { name: 'bonus', wireKey: 'bi', optional: true, nullable: false, type: { kind: 'scalar', isBigint: true } }, - { name: 'proposerPayout', wireKey: 'pp', optional: true, nullable: false, type: { kind: 'scalar', isBigint: true } }, - { name: 'feeSink', wireKey: 'fees', optional: true, nullable: false, type: { kind: 'scalar', isBytes: true } }, - { name: 'rewardsPool', wireKey: 'rwd', optional: true, nullable: false, type: { kind: 'scalar', isBytes: true } }, - { name: 'rewardsLevel', wireKey: 'earn', optional: true, nullable: false, type: { kind: 'scalar', isBigint: true } }, - { name: 'rewardsRate', wireKey: 'rate', optional: true, nullable: false, type: { kind: 'scalar', isBigint: true } }, - { name: 'rewardsResidue', wireKey: 'frac', optional: true, nullable: false, type: { kind: 'scalar', isBigint: true } }, - { name: 'rewardsRecalculationRound', wireKey: 'rwcalr', optional: true, nullable: false, type: { kind: 'scalar', isBigint: true } }, - { name: 'currentProtocol', wireKey: 'proto', optional: true, nullable: false, type: { kind: 'scalar' } }, - { name: 'nextProtocol', wireKey: 'nextproto', optional: true, nullable: false, type: { kind: 'scalar' } }, - { name: 'nextProtocolApprovals', wireKey: 'nextyes', optional: true, nullable: false, type: { kind: 'scalar', isBigint: true } }, - { name: 'nextProtocolVoteBefore', wireKey: 'nextbefore', optional: true, nullable: false, type: { kind: 'scalar', isBigint: true } }, - { name: 'nextProtocolSwitchOn', wireKey: 'nextswitch', optional: true, nullable: false, type: { kind: 'scalar', isBigint: true } }, - { name: 'upgradePropose', wireKey: 'upgradeprop', optional: true, nullable: false, type: { kind: 'scalar' } }, - { name: 'upgradeDelay', wireKey: 'upgradedelay', optional: true, nullable: false, type: { kind: 'scalar', isBigint: true } }, - { name: 'upgradeApprove', wireKey: 'upgradeyes', optional: true, nullable: false, type: { kind: 'scalar' } }, - { name: 'txnCounter', wireKey: 'tc', optional: true, nullable: false, type: { kind: 'scalar', isBigint: true } }, - { name: 'stateProofTracking', wireKey: 'spt', optional: true, nullable: false, type: { kind: 'model', meta: () => BlockStateProofTrackingMeta } }, - { name: 'expiredParticipationAccounts', wireKey: 'partupdrmv', optional: true, nullable: false, type: { kind: 'array', item: { kind: 'scalar', isBytes: true } } }, - { name: 'absentParticipationAccounts', wireKey: 'partupdabs', optional: true, nullable: false, type: { kind: 'array', item: { kind: 'scalar', isBytes: true } } }, - { name: 'transactions', wireKey: 'txns', optional: true, nullable: false, type: { kind: 'array', item: { kind: 'model', meta: () => SignedTxnInBlockMeta } } }, - ], -}; - - - diff --git a/oas-generator/src/oas_generator/templates/models/block/signed-txn-in-block.ts.j2 b/oas-generator/src/oas_generator/templates/models/block/signed-txn-in-block.ts.j2 deleted file mode 100644 index cabf849d..00000000 --- a/oas-generator/src/oas_generator/templates/models/block/signed-txn-in-block.ts.j2 +++ /dev/null @@ -1,63 +0,0 @@ -/* - * {{ spec.info.title }} - * - * {{ spec.info.description or "API client generated from OpenAPI specification" }} - * - * The version of the OpenAPI document: {{ spec.info.version }} - {% if spec.info.contact and spec.info.contact.email %} * Contact: {{ spec.info.contact.email }} - {% endif %} * Generated by: Rust OpenAPI Generator - */ - -import type { ModelMetadata } from '../core/model-runtime'; -import type { SignedTransaction } from '@algorandfoundation/algokit-transact'; -import type { BlockAppEvalDelta } from './block-app-eval-delta'; -import { getModelMeta, registerModelMeta } from '../core/model-runtime'; - -/** - * SignedTxnInBlock is a SignedTransaction with additional ApplyData and block-specific metadata. - */ -export interface SignedTxnInBlock { - signedTransaction: SignedTransaction; - logicSignature?: Record; - closingAmount?: bigint; - assetClosingAmount?: bigint; - senderRewards?: bigint; - receiverRewards?: bigint; - closeRewards?: bigint; - evalDelta?: BlockAppEvalDelta; - configAsset?: bigint; - applicationId?: bigint; - hasGenesisId?: boolean; - hasGenesisHash?: boolean; -} - -export const SignedTxnInBlockMeta: ModelMetadata = { - name: 'SignedTxnInBlock', - kind: 'object', - fields: [ - { - name: 'signedTransaction', - // flatten signed transaction fields into parent - flattened: true, - optional: false, - nullable: false, - type: { kind: 'codec', codecKey: 'SignedTransaction' }, - }, - { name: 'logicSignature', wireKey: 'lsig', optional: true, nullable: false, type: { kind: 'scalar' } }, - { name: 'closingAmount', wireKey: 'ca', optional: true, nullable: false, type: { kind: 'scalar', isBigint: true } }, - { name: 'assetClosingAmount', wireKey: 'aca', optional: true, nullable: false, type: { kind: 'scalar', isBigint: true } }, - { name: 'senderRewards', wireKey: 'rs', optional: true, nullable: false, type: { kind: 'scalar', isBigint: true } }, - { name: 'receiverRewards', wireKey: 'rr', optional: true, nullable: false, type: { kind: 'scalar', isBigint: true } }, - { name: 'closeRewards', wireKey: 'rc', optional: true, nullable: false, type: { kind: 'scalar', isBigint: true } }, - { name: 'evalDelta', wireKey: 'dt', optional: true, nullable: false, type: { kind: 'model', meta: () => getModelMeta('BlockAppEvalDelta') } }, - { name: 'configAsset', wireKey: 'caid', optional: true, nullable: false, type: { kind: 'scalar', isBigint: true } }, - { name: 'applicationId', wireKey: 'apid', optional: true, nullable: false, type: { kind: 'scalar', isBigint: true } }, - { name: 'hasGenesisId', wireKey: 'hgi', optional: true, nullable: false, type: { kind: 'scalar' } }, - { name: 'hasGenesisHash', wireKey: 'hgh', optional: true, nullable: false, type: { kind: 'scalar' } }, - ], -}; - -registerModelMeta('SignedTxnInBlock', SignedTxnInBlockMeta); - - - diff --git a/oas-generator/src/oas_generator/templates/models/custom/block.ts.j2 b/oas-generator/src/oas_generator/templates/models/custom/block.ts.j2 new file mode 100644 index 00000000..1e59f5e9 --- /dev/null +++ b/oas-generator/src/oas_generator/templates/models/custom/block.ts.j2 @@ -0,0 +1,349 @@ +import { SignedTransaction } from '@algorandfoundation/algokit-transact' +import type { ModelMetadata } from '../core/model-runtime' + +/** BlockEvalDelta represents a TEAL value delta (block/msgpack wire keys). */ +export type BlockEvalDelta = { + /** [at] delta action. */ + action: number + /** [bs] bytes value. */ + bytes?: string + /** [ui] uint value. */ + uint?: bigint +} + +export const BlockEvalDeltaMeta: ModelMetadata = { + name: 'BlockEvalDelta', + kind: 'object', + fields: [ + { name: 'action', wireKey: 'at', optional: false, nullable: false, type: { kind: 'scalar' } }, + { name: 'bytes', wireKey: 'bs', optional: true, nullable: false, type: { kind: 'scalar' } }, + { name: 'uint', wireKey: 'ui', optional: true, nullable: false, type: { kind: 'scalar', isBigint: true } }, + ], +} + +/** + * State changes from application execution, including inner transactions and logs. + */ +export type BlockAppEvalDelta = { + /** [gd] Global state delta for the application. */ + globalDelta?: Map + /** [ld] Local state deltas keyed by address index. */ + localDeltas?: Map> + /** [itx] Inner transactions produced by this application execution. */ + innerTxns?: SignedTxnInBlock[] + /** [sa] Shared accounts referenced by local deltas. */ + sharedAccounts?: string[] + /** [lg] Application log outputs. */ + logs?: Uint8Array[] +} + +export const BlockAppEvalDeltaMeta: ModelMetadata = { + name: 'BlockAppEvalDelta', + kind: 'object', + fields: [ + { + name: 'globalDelta', + wireKey: 'gd', + optional: true, + nullable: false, + type: { kind: 'map', keyType: 'bytes', value: { kind: 'model', meta: BlockEvalDeltaMeta } }, + }, + { + name: 'localDeltas', + wireKey: 'ld', + optional: true, + nullable: false, + type: { + kind: 'map', + keyType: 'number', + value: { kind: 'map', keyType: 'bytes', value: { kind: 'model', meta: BlockEvalDeltaMeta } }, + }, + }, + { + name: 'innerTxns', + wireKey: 'itx', + optional: true, + nullable: false, + type: { kind: 'array', item: { kind: 'model', meta: () => SignedTxnInBlockMeta } }, + }, + { + name: 'sharedAccounts', + wireKey: 'sa', + optional: true, + nullable: false, + type: { kind: 'array', item: { kind: 'scalar', isAddress: true } }, + }, + { name: 'logs', wireKey: 'lg', optional: true, nullable: false, type: { kind: 'array', item: { kind: 'scalar', isBytes: true } } }, + ], +} + +/** Tracking metadata for a specific StateProofType. */ +export type BlockStateProofTrackingData = { + /** [v] Vector commitment root of state proof voters. */ + stateProofVotersCommitment?: Uint8Array + /** [t] Online total weight during state proof round. */ + stateProofOnlineTotalWeight?: bigint + /** [n] Next round for which state proofs are accepted. */ + stateProofNextRound?: bigint +} + +export const BlockStateProofTrackingDataMeta: ModelMetadata = { + name: 'BlockStateProofTrackingData', + kind: 'object', + fields: [ + { name: 'stateProofVotersCommitment', wireKey: 'v', optional: true, nullable: false, type: { kind: 'scalar', isBytes: true } }, + { name: 'stateProofOnlineTotalWeight', wireKey: 't', optional: true, nullable: false, type: { kind: 'scalar', isBigint: true } }, + { name: 'stateProofNextRound', wireKey: 'n', optional: true, nullable: false, type: { kind: 'scalar', isBigint: true } }, + ], +} + +export type ApplyData = { + closingAmount?: bigint + assetClosingAmount?: bigint + senderRewards?: bigint + receiverRewards?: bigint + closeRewards?: bigint + evalDelta?: BlockAppEvalDelta + configAsset?: bigint + applicationId?: bigint +} + +export const ApplyDataMeta: ModelMetadata = { + name: 'SignedTxnInBlock', + kind: 'object', + fields: [ + { name: 'closingAmount', wireKey: 'ca', optional: true, nullable: false, type: { kind: 'scalar', isBigint: true } }, + { name: 'assetClosingAmount', wireKey: 'aca', optional: true, nullable: false, type: { kind: 'scalar', isBigint: true } }, + { name: 'senderRewards', wireKey: 'rs', optional: true, nullable: false, type: { kind: 'scalar', isBigint: true } }, + { name: 'receiverRewards', wireKey: 'rr', optional: true, nullable: false, type: { kind: 'scalar', isBigint: true } }, + { name: 'closeRewards', wireKey: 'rc', optional: true, nullable: false, type: { kind: 'scalar', isBigint: true } }, + { name: 'evalDelta', wireKey: 'dt', optional: true, nullable: false, type: { kind: 'model', meta: BlockAppEvalDeltaMeta } }, + { name: 'configAsset', wireKey: 'caid', optional: true, nullable: false, type: { kind: 'scalar', isBigint: true } }, + { name: 'applicationId', wireKey: 'apid', optional: true, nullable: false, type: { kind: 'scalar', isBigint: true } }, + ], +} + +/** + * SignedTxnWithAD is a SignedTransaction with additional ApplyData. + */ +export type SignedTxnWithAD = { + /** The signed transaction. */ + signedTxn: SignedTransaction + /** Apply data containing transaction execution information. */ + applyData: ApplyData +} + +export const SignedTxnWithADMeta: ModelMetadata = { + name: 'SignedTxnWithAD', + kind: 'object', + fields: [ + { + name: 'signedTransaction', + flattened: true, + optional: false, + nullable: false, + type: { kind: 'codec', codecKey: 'SignedTransaction' }, + }, + { + name: 'applyData', + flattened: true, + optional: true, + nullable: false, + type: { kind: 'model', meta: ApplyDataMeta }, + }, + ], +} + +/** + * SignedTxnInBlock is a SignedTransaction with additional ApplyData and block-specific metadata. + */ +export type SignedTxnInBlock = { + signedTransaction: SignedTxnWithAD + hasGenesisId?: boolean + hasGenesisHash?: boolean +} + +export const SignedTxnInBlockMeta: ModelMetadata = { + name: 'SignedTxnInBlock', + kind: 'object', + fields: [ + { + name: 'signedTransaction', + flattened: true, + optional: false, + nullable: false, + type: { kind: 'model', meta: SignedTxnWithADMeta }, + }, + { name: 'hasGenesisId', wireKey: 'hgi', optional: true, nullable: false, type: { kind: 'scalar' } }, + { name: 'hasGenesisHash', wireKey: 'hgh', optional: true, nullable: false, type: { kind: 'scalar' } }, + ], +} + +export type ParticipationUpdates = { + /** [partupdrmv] Expired participation accounts. */ + expiredParticipationAccounts?: string[] + /** [partupdabs] Absent participation accounts. */ + absentParticipationAccounts?: string[] +} + +export const ParticipationUpdatesMeta: ModelMetadata = { + name: 'ParticipationUpdates', + kind: 'object', + fields: [ + { + name: 'expiredParticipationAccounts', + wireKey: 'partupdrmv', + optional: true, + nullable: false, + type: { kind: 'array', item: { kind: 'scalar', isAddress: true } }, + }, + { + name: 'absentParticipationAccounts', + wireKey: 'partupdabs', + optional: true, + nullable: false, + type: { kind: 'array', item: { kind: 'scalar', isAddress: true } }, + }, + ], +} + +export type BlockHeader = { + /** [rnd] Round number. */ + round?: bigint + /** [prev] Previous block hash. */ + previousBlockHash?: Uint8Array + /** [prev512] Previous block hash using SHA-512. */ + previousBlockHash512?: Uint8Array + /** [seed] Sortition seed. */ + seed?: Uint8Array + /** [txn] Root of transaction merkle tree using SHA512_256. */ + transactionsRoot?: Uint8Array + /** [txn256] Root of transaction vector commitment using SHA256. */ + transactionsRootSha256?: Uint8Array + /** [txn512] Root of transaction vector commitment using SHA512. */ + transactionsRootSha512?: Uint8Array + /** [ts] Block timestamp in seconds since epoch. */ + timestamp?: bigint + /** [gen] Genesis ID. */ + genesisId?: string + /** [gh] Genesis hash. */ + genesisHash?: Uint8Array + /** [prp] Proposer address. */ + proposer?: string + /** [fc] Fees collected in this block. */ + feesCollected?: bigint + /** [bi] Bonus incentive for block proposal. */ + bonus?: bigint + /** [pp] Proposer payout. */ + proposerPayout?: bigint + /** [fees] FeeSink address. */ + feeSink?: string + /** [rwd] RewardsPool address. */ + rewardsPool?: string + /** [earn] Rewards level. */ + rewardsLevel?: bigint + /** [rate] Rewards rate. */ + rewardsRate?: bigint + /** [frac] Rewards residue. */ + rewardsResidue?: bigint + /** [rwcalr] Rewards recalculation round. */ + rewardsRecalculationRound?: bigint + /** [proto] Current consensus protocol. */ + currentProtocol?: string + /** [nextproto] Next proposed protocol. */ + nextProtocol?: string + /** [nextyes] Next protocol approvals. */ + nextProtocolApprovals?: bigint + /** [nextbefore] Next protocol vote deadline. */ + nextProtocolVoteBefore?: bigint + /** [nextswitch] Next protocol switch round. */ + nextProtocolSwitchOn?: bigint + /** [upgradeprop] Upgrade proposal. */ + upgradePropose?: string + /** [upgradedelay] Upgrade delay in rounds. */ + upgradeDelay?: bigint + /** [upgradeyes] Upgrade approval flag. */ + upgradeApprove?: boolean + /** [tc] Transaction counter. */ + txnCounter?: bigint + /** [spt] State proof tracking data keyed by state proof type. */ + stateProofTracking?: Map + /** Represents participation account data that needs to be checked/acted on by the network */ + participationUpdates?: ParticipationUpdates +} + +export const BlockHeaderMeta: ModelMetadata = { + name: 'BlockHeader', + kind: 'object', + fields: [ + { name: 'round', wireKey: 'rnd', optional: true, nullable: false, type: { kind: 'scalar', isBigint: true } }, + { name: 'previousBlockHash', wireKey: 'prev', optional: true, nullable: false, type: { kind: 'scalar', isBytes: true } }, + { name: 'previousBlockHash512', wireKey: 'prev512', optional: true, nullable: false, type: { kind: 'scalar', isBytes: true } }, + { name: 'seed', wireKey: 'seed', optional: true, nullable: false, type: { kind: 'scalar', isBytes: true } }, + { name: 'transactionsRoot', wireKey: 'txn', optional: false, nullable: false, type: { kind: 'scalar', isBytes: true } }, + { name: 'transactionsRootSha256', wireKey: 'txn256', optional: true, nullable: false, type: { kind: 'scalar', isBytes: true } }, + { name: 'transactionsRootSha512', wireKey: 'txn512', optional: true, nullable: false, type: { kind: 'scalar', isBytes: true } }, + { name: 'timestamp', wireKey: 'ts', optional: true, nullable: false, type: { kind: 'scalar', isBigint: true } }, + { name: 'genesisId', wireKey: 'gen', optional: true, nullable: false, type: { kind: 'scalar' } }, + { name: 'genesisHash', wireKey: 'gh', optional: true, nullable: false, type: { kind: 'scalar', isBytes: true } }, + { name: 'proposer', wireKey: 'prp', optional: true, nullable: false, type: { kind: 'scalar', isAddress: true } }, + { name: 'feesCollected', wireKey: 'fc', optional: true, nullable: false, type: { kind: 'scalar', isBigint: true } }, + { name: 'bonus', wireKey: 'bi', optional: true, nullable: false, type: { kind: 'scalar', isBigint: true } }, + { name: 'proposerPayout', wireKey: 'pp', optional: true, nullable: false, type: { kind: 'scalar', isBigint: true } }, + { name: 'feeSink', wireKey: 'fees', optional: true, nullable: false, type: { kind: 'scalar', isAddress: true } }, + { name: 'rewardsPool', wireKey: 'rwd', optional: true, nullable: false, type: { kind: 'scalar', isAddress: true } }, + { name: 'rewardsLevel', wireKey: 'earn', optional: true, nullable: false, type: { kind: 'scalar', isBigint: true } }, + { name: 'rewardsRate', wireKey: 'rate', optional: true, nullable: false, type: { kind: 'scalar', isBigint: true } }, + { name: 'rewardsResidue', wireKey: 'frac', optional: true, nullable: false, type: { kind: 'scalar', isBigint: true } }, + { name: 'rewardsRecalculationRound', wireKey: 'rwcalr', optional: true, nullable: false, type: { kind: 'scalar', isBigint: true } }, + { name: 'currentProtocol', wireKey: 'proto', optional: true, nullable: false, type: { kind: 'scalar' } }, + { name: 'nextProtocol', wireKey: 'nextproto', optional: true, nullable: false, type: { kind: 'scalar' } }, + { name: 'nextProtocolApprovals', wireKey: 'nextyes', optional: true, nullable: false, type: { kind: 'scalar', isBigint: true } }, + { name: 'nextProtocolVoteBefore', wireKey: 'nextbefore', optional: true, nullable: false, type: { kind: 'scalar', isBigint: true } }, + { name: 'nextProtocolSwitchOn', wireKey: 'nextswitch', optional: true, nullable: false, type: { kind: 'scalar', isBigint: true } }, + { name: 'upgradePropose', wireKey: 'upgradeprop', optional: true, nullable: false, type: { kind: 'scalar' } }, + { name: 'upgradeDelay', wireKey: 'upgradedelay', optional: true, nullable: false, type: { kind: 'scalar', isBigint: true } }, + { name: 'upgradeApprove', wireKey: 'upgradeyes', optional: true, nullable: false, type: { kind: 'scalar' } }, + { name: 'txnCounter', wireKey: 'tc', optional: true, nullable: false, type: { kind: 'scalar', isBigint: true } }, + { + name: 'stateProofTracking', + wireKey: 'spt', + optional: true, + nullable: false, + type: { kind: 'map', keyType: 'number', value: { kind: 'model', meta: BlockStateProofTrackingDataMeta } }, + }, + { + name: 'participationUpdates', + flattened: true, + optional: true, + nullable: false, + type: { kind: 'model', meta: ParticipationUpdatesMeta }, + }, + ], +} + +/** + * Block contains the BlockHeader and the list of transactions (Payset). + */ +export type Block = { + /** The block information (Header) */ + header: BlockHeader + + /** [txns] Block transactions (Payset). */ + payset?: SignedTxnInBlock[] +} + +export const BlockMeta: ModelMetadata = { + name: 'Block', + kind: 'object', + fields: [ + { name: 'header', flattened: true, optional: false, nullable: false, type: { kind: 'model', meta: BlockHeaderMeta } }, + { + name: 'payset', + wireKey: 'txns', + optional: true, + nullable: false, + type: { kind: 'array', item: { kind: 'model', meta: SignedTxnInBlockMeta } }, + }, + ], +} diff --git a/oas-generator/src/oas_generator/templates/models/block/get-block.ts.j2 b/oas-generator/src/oas_generator/templates/models/custom/get-block.ts.j2 similarity index 83% rename from oas-generator/src/oas_generator/templates/models/block/get-block.ts.j2 rename to oas-generator/src/oas_generator/templates/models/custom/get-block.ts.j2 index 04816e65..f0cb9d5f 100644 --- a/oas-generator/src/oas_generator/templates/models/block/get-block.ts.j2 +++ b/oas-generator/src/oas_generator/templates/models/custom/get-block.ts.j2 @@ -5,7 +5,7 @@ import { BlockMeta } from './block'; export type GetBlock = { /** Block data including header and transactions. */ block: Block; - /** Block certificate (msgpack only). */ + /** Block certificate. */ cert?: Record; }; @@ -13,10 +13,7 @@ export const GetBlockMeta: ModelMetadata = { name: 'GetBlock', kind: 'object', fields: [ - { name: 'block', wireKey: 'block', optional: false, nullable: false, type: { kind: 'model', meta: () => BlockMeta } }, + { name: 'block', wireKey: 'block', optional: false, nullable: false, type: { kind: 'model', meta: BlockMeta } }, { name: 'cert', wireKey: 'cert', optional: true, nullable: false, type: { kind: 'scalar' } }, ], }; - - - diff --git a/oas-generator/src/oas_generator/templates/models/custom/ledger-state-delta.ts.j2 b/oas-generator/src/oas_generator/templates/models/custom/ledger-state-delta.ts.j2 new file mode 100644 index 00000000..9bb0e6da --- /dev/null +++ b/oas-generator/src/oas_generator/templates/models/custom/ledger-state-delta.ts.j2 @@ -0,0 +1,696 @@ +import type { ModelMetadata } from '../core/model-runtime' +import type { Block } from './block' +import { BlockMeta } from './block' + +/** + * Contains type information and a value, representing a value in a TEAL program. + */ +export type LedgerTealValue = { + /** + * Type determines the type of the value. + * * 1 represents the type of a byte slice in a TEAL program + * * 2 represents the type of an unsigned integer in a TEAL program + */ + type: number + /** bytes value. */ + bytes?: Uint8Array + /** uint value. */ + uint?: bigint +} + +export const LedgerTealValueMeta: ModelMetadata = { + name: 'LedgerTealValue', + kind: 'object', + fields: [ + { name: 'type', wireKey: 'tt', optional: false, nullable: false, type: { kind: 'scalar' } }, + { name: 'bytes', wireKey: 'tb', optional: true, nullable: false, type: { kind: 'scalar', isBytes: true } }, + { name: 'uint', wireKey: 'ui', optional: true, nullable: false, type: { kind: 'scalar', isBigint: true } }, + ], +} + +/** + * Sets maximums on the number of each type that may be stored. + */ +export type LedgerStateSchema = { + /** Number of uints in state. */ + numUints?: bigint + /** Number of byte slices in state. */ + numByteSlices?: bigint +} + +export const LedgerStateSchemaMeta: ModelMetadata = { + name: 'LedgerStateSchema', + kind: 'object', + fields: [ + { name: 'numUints', wireKey: 'nui', optional: true, nullable: false, type: { kind: 'scalar', isBigint: true } }, + { name: 'numByteSlices', wireKey: 'nbs', optional: true, nullable: false, type: { kind: 'scalar', isBigint: true } }, + ], +} + +/** + * Stores the global information associated with an application. + */ +export type LedgerAppParams = { + approvalProgram: Uint8Array + clearStateProgram: Uint8Array + localStateSchema: LedgerStateSchema + globalStateSchema: LedgerStateSchema + extraProgramPages: number + version?: number + sizeSponsor?: string + globalState?: Map +} + +export const LedgerAppParamsMeta: ModelMetadata = { + name: 'LedgerAppParams', + kind: 'object', + fields: [ + { name: 'approvalProgram', wireKey: 'approv', optional: false, nullable: false, type: { kind: 'scalar', isBytes: true } }, + { name: 'clearStateProgram', wireKey: 'clearp', optional: false, nullable: false, type: { kind: 'scalar', isBytes: true } }, + { name: 'localStateSchema', wireKey: 'lsch', optional: false, nullable: false, type: { kind: 'model', meta: LedgerStateSchemaMeta } }, + { name: 'globalStateSchema', wireKey: 'gsch', optional: false, nullable: false, type: { kind: 'model', meta: LedgerStateSchemaMeta } }, + { name: 'extraProgramPages', wireKey: 'epp', optional: false, nullable: false, type: { kind: 'scalar' } }, + { name: 'version', wireKey: 'v', optional: true, nullable: false, type: { kind: 'scalar' } }, + { name: 'sizeSponsor', wireKey: 'ss', optional: true, nullable: false, type: { kind: 'scalar', isAddress: true } }, + { + name: 'globalState', + wireKey: 'gs', + optional: true, + nullable: false, + type: { kind: 'map', keyType: 'bytes', value: { kind: 'model', meta: LedgerTealValueMeta } }, + }, + ], +} + +/** + * Stores the LocalState associated with an application. + */ +export type LedgerAppLocalState = { + schema: LedgerStateSchema + keyValue?: Map +} + +export const LedgerAppLocalStateMeta: ModelMetadata = { + name: 'LedgerAppLocalState', + kind: 'object', + fields: [ + { name: 'schema', wireKey: 'hsch', optional: false, nullable: false, type: { kind: 'model', meta: LedgerStateSchemaMeta } }, + { + name: 'keyValue', + wireKey: 'tkv', + optional: true, + nullable: false, + type: { kind: 'map', keyType: 'bytes', value: { kind: 'model', meta: LedgerTealValueMeta } }, + }, + ], +} + +/** + * Tracks a changed AppLocalState, and whether it was deleted. + */ +export type LedgerAppLocalStateDelta = { + deleted: boolean + localState?: LedgerAppLocalState +} + +export const LedgerAppLocalStateDeltaMeta: ModelMetadata = { + name: 'LedgerAppLocalStateDelta', + kind: 'object', + fields: [ + { name: 'deleted', wireKey: 'Deleted', optional: false, nullable: false, type: { kind: 'scalar' } }, + { name: 'localState', wireKey: 'LocalState', optional: true, nullable: false, type: { kind: 'model', meta: LedgerAppLocalStateMeta } }, + ], +} + +/** + * Tracks a changed AppParams, and whether it was deleted. + */ +export type LedgerAppParamsDelta = { + deleted: boolean + params?: LedgerAppParams +} + +export const LedgerAppParamsDeltaMeta: ModelMetadata = { + name: 'LedgerAppParamsDelta', + kind: 'object', + fields: [ + { name: 'deleted', wireKey: 'Deleted', optional: false, nullable: false, type: { kind: 'scalar' } }, + { name: 'params', wireKey: 'Params', optional: true, nullable: false, type: { kind: 'model', meta: LedgerAppParamsMeta } }, + ], +} + +/** + * Represents AppParams and AppLocalState in deltas. + */ +export type LedgerAppResourceRecord = { + appId: bigint + address: string + params: LedgerAppParamsDelta + state: LedgerAppLocalStateDelta +} + +export const LedgerAppResourceRecordMeta: ModelMetadata = { + name: 'LedgerAppResourceRecord', + kind: 'object', + fields: [ + { name: 'appId', wireKey: 'Aidx', optional: false, nullable: false, type: { kind: 'scalar', isBigint: true } }, + { name: 'address', wireKey: 'Addr', optional: false, nullable: false, type: { kind: 'scalar', isAddress: true } }, + { name: 'params', wireKey: 'Params', optional: false, nullable: false, type: { kind: 'model', meta: LedgerAppParamsDeltaMeta } }, + { name: 'state', wireKey: 'State', optional: false, nullable: false, type: { kind: 'model', meta: LedgerAppLocalStateDeltaMeta } }, + ], +} + +/** + * Describes an asset held by an account. + */ +export type LedgerAssetHolding = { + amount: bigint + frozen: boolean +} + +export const LedgerAssetHoldingMeta: ModelMetadata = { + name: 'LedgerAssetHolding', + kind: 'object', + fields: [ + { name: 'amount', wireKey: 'a', optional: false, nullable: false, type: { kind: 'scalar', isBigint: true } }, + { name: 'frozen', wireKey: 'f', optional: false, nullable: false, type: { kind: 'scalar' } }, + ], +} + +/** + * Records a changed AssetHolding, and whether it was deleted. + */ +export type LedgerAssetHoldingDelta = { + deleted: boolean + holding?: LedgerAssetHolding +} + +export const LedgerAssetHoldingDeltaMeta: ModelMetadata = { + name: 'LedgerAssetHoldingDelta', + kind: 'object', + fields: [ + { name: 'deleted', wireKey: 'Deleted', optional: false, nullable: false, type: { kind: 'scalar' } }, + { name: 'holding', wireKey: 'Holding', optional: true, nullable: false, type: { kind: 'model', meta: LedgerAssetHoldingMeta } }, + ], +} + +/** + * Describes the parameters of an asset. + */ +export type LedgerAssetParams = { + /** + * Specifies the total number of units of this asset created. + */ + total: bigint + /** + * Specifies the number of digits to display after the decimal place when displaying this asset. + * A value of 0 represents an asset that is not divisible, a value of 1 represents an asset divisible into tenths, and so on. + * This value must be between 0 and 19 (inclusive). + */ + decimals: number + /** + * Specifies whether slots for this asset in user accounts are frozen by default or not. + */ + defaultFrozen: boolean + /** + * Specifies a hint for the name of a unit of this asset. + */ + unitName?: string + /** + * Specifies a hint for the name of the asset. + */ + assetName?: string + /** + * Specifies a URL where more information about the asset can be retrieved. + */ + url?: string + /** + * Specifies a commitment to some unspecified asset metadata. The format of this + * metadata is up to the application. + */ + metadataHash?: Uint8Array + /** + * Manager specifies an account that is allowed to change the non-zero addresses in this AssetParams. + */ + manager?: string + /** + * Specifies an account whose holdings of this asset should be reported as "not minted". + */ + reserve?: string + /** + * Specifies an account that is allowed to change the frozen state of holdings of this asset. + */ + freeze?: string + /** + * Specifies an account that is allowed to take units of this asset from any account. + */ + clawback?: string +} + +export const LedgerAssetParamsMeta: ModelMetadata = { + name: 'LedgerAssetParams', + kind: 'object', + fields: [ + { name: 'total', wireKey: 't', optional: false, nullable: false, type: { kind: 'scalar', isBigint: true } }, + { name: 'decimals', wireKey: 'dc', optional: false, nullable: false, type: { kind: 'scalar' } }, + { name: 'defaultFrozen', wireKey: 'df', optional: false, nullable: false, type: { kind: 'scalar' } }, + { name: 'unitName', wireKey: 'un', optional: true, nullable: false, type: { kind: 'scalar' } }, + { name: 'assetName', wireKey: 'an', optional: true, nullable: false, type: { kind: 'scalar' } }, + { name: 'url', wireKey: 'au', optional: true, nullable: false, type: { kind: 'scalar' } }, + { name: 'metadataHash', wireKey: 'am', optional: true, nullable: false, type: { kind: 'scalar', isBytes: true } }, + { name: 'manager', wireKey: 'm', optional: true, nullable: false, type: { kind: 'scalar', isAddress: true } }, + { name: 'reserve', wireKey: 'r', optional: true, nullable: false, type: { kind: 'scalar', isAddress: true } }, + { name: 'freeze', wireKey: 'f', optional: true, nullable: false, type: { kind: 'scalar', isAddress: true } }, + { name: 'clawback', wireKey: 'c', optional: true, nullable: false, type: { kind: 'scalar', isAddress: true } }, + ], +} + +/** + * Tracks a changed asset params, and whether it was deleted. + */ +export type LedgerAssetParamsDelta = { + deleted: boolean + params?: LedgerAssetParams +} + +export const LedgerAssetParamsDeltaMeta: ModelMetadata = { + name: 'LedgerAssetParamsDelta', + kind: 'object', + fields: [ + { name: 'deleted', wireKey: 'Deleted', optional: false, nullable: false, type: { kind: 'scalar' } }, + { name: 'params', wireKey: 'Params', optional: true, nullable: false, type: { kind: 'model', meta: LedgerAssetParamsMeta } }, + ], +} + +/** + * Represents asset params and asset holding in deltas. + */ +export type LedgerAssetResourceRecord = { + assetId: bigint + address: string + params: LedgerAssetParamsDelta + holding: LedgerAssetHoldingDelta +} + +export const LedgerAssetResourceRecordMeta: ModelMetadata = { + name: 'LedgerAssetResourceRecord', + kind: 'object', + fields: [ + { name: 'assetId', wireKey: 'Aidx', optional: false, nullable: false, type: { kind: 'scalar', isBigint: true } }, + { name: 'address', wireKey: 'Addr', optional: false, nullable: false, type: { kind: 'scalar', isAddress: true } }, + { name: 'params', wireKey: 'Params', optional: false, nullable: false, type: { kind: 'model', meta: LedgerAssetParamsDeltaMeta } }, + { name: 'holding', wireKey: 'Holding', optional: false, nullable: false, type: { kind: 'model', meta: LedgerAssetHoldingDeltaMeta } }, + ], +} + +/** + * Holds participation information. + */ +export type LedgerVotingData = { + voteId: Uint8Array + selectionId: Uint8Array + stateProofId: Uint8Array + voteFirstValid: bigint + voteLastValid: bigint + voteKeyDilution: bigint +} + +export const LedgerVotingDataMeta: ModelMetadata = { + name: 'LedgerVotingData', + kind: 'object', + fields: [ + { name: 'voteId', wireKey: 'VoteID', optional: false, nullable: false, type: { kind: 'scalar', isBytes: true } }, + { name: 'selectionId', wireKey: 'SelectionID', optional: false, nullable: false, type: { kind: 'scalar', isBytes: true } }, + { name: 'stateProofId', wireKey: 'StateProofID', optional: false, nullable: false, type: { kind: 'scalar', isBytes: true } }, + { name: 'voteFirstValid', wireKey: 'VoteFirstValid', optional: false, nullable: false, type: { kind: 'scalar', isBigint: true } }, + { name: 'voteLastValid', wireKey: 'VoteLastValid', optional: false, nullable: false, type: { kind: 'scalar', isBigint: true } }, + { name: 'voteKeyDilution', wireKey: 'VoteKeyDilution', optional: false, nullable: false, type: { kind: 'scalar', isBigint: true } }, + ], +} + +/** + * Contains base account info like balance, status and total number of resources. + */ +export type LedgerAccountBaseData = { + /** + * Account status. Values are: + * * 0: Offline + * * 1: Online + * * 2: NotParticipating + */ + status: number + microAlgos: bigint + rewardsBase: bigint + rewardedMicroAlgos: bigint + authAddress: string + incentiveEligible: boolean + /** + * Totals across created globals, and opted in locals. + */ + totalAppSchema: LedgerStateSchema + /** + * Total number of extra pages across all created apps. + */ + totalExtraAppPages: number + /** + * Total number of apps this account has created. + */ + totalAppParams: number + /** + * Total number of apps this account is opted into. + */ + totalAppLocalStates: number + /** + * Total number of assets created by this account. + */ + totalAssetParams: number + /** + * Total of asset creations and optins (i.e. number of holdings). + */ + totalAssets: number + /** + * Total number of boxes associated to this account. + */ + totalBoxes: bigint + /** + * Total bytes for this account's boxes. keys and values count. + */ + totalBoxBytes: bigint + /** + * The last round that this account proposed the winning block. + */ + lastProposed: bigint + /** + * The last round that this account sent a heartbeat to show it was online. + */ + lastHeartbeat: bigint +} + +export const LedgerAccountBaseDataMeta: ModelMetadata = { + name: 'LedgerAccountBaseData', + kind: 'object', + fields: [ + { name: 'status', wireKey: 'Status', optional: false, nullable: false, type: { kind: 'scalar' } }, + { name: 'microAlgos', wireKey: 'MicroAlgos', optional: false, nullable: false, type: { kind: 'scalar', isBigint: true } }, + { name: 'rewardsBase', wireKey: 'RewardsBase', optional: false, nullable: false, type: { kind: 'scalar', isBigint: true } }, + { + name: 'rewardedMicroAlgos', + wireKey: 'RewardedMicroAlgos', + optional: false, + nullable: false, + type: { kind: 'scalar', isBigint: true }, + }, + { name: 'authAddress', wireKey: 'AuthAddr', optional: false, nullable: false, type: { kind: 'scalar', isAddress: true } }, + { name: 'incentiveEligible', wireKey: 'IncentiveEligible', optional: false, nullable: false, type: { kind: 'scalar' } }, + { + name: 'totalAppSchema', + wireKey: 'TotalAppSchema', + optional: false, + nullable: false, + type: { kind: 'model', meta: LedgerStateSchemaMeta }, + }, + { name: 'totalExtraAppPages', wireKey: 'TotalExtraAppPages', optional: false, nullable: false, type: { kind: 'scalar' } }, + { name: 'totalAppParams', wireKey: 'TotalAppParams', optional: false, nullable: false, type: { kind: 'scalar' } }, + { name: 'totalAppLocalStates', wireKey: 'TotalAppLocalStates', optional: false, nullable: false, type: { kind: 'scalar' } }, + { name: 'totalAssetParams', wireKey: 'TotalAssetParams', optional: false, nullable: false, type: { kind: 'scalar' } }, + { name: 'totalAssets', wireKey: 'TotalAssets', optional: false, nullable: false, type: { kind: 'scalar' } }, + { name: 'totalBoxes', wireKey: 'TotalBoxes', optional: false, nullable: false, type: { kind: 'scalar', isBigint: true } }, + { name: 'totalBoxBytes', wireKey: 'TotalBoxBytes', optional: false, nullable: false, type: { kind: 'scalar', isBigint: true } }, + { name: 'lastProposed', wireKey: 'LastProposed', optional: false, nullable: false, type: { kind: 'scalar', isBigint: true } }, + { name: 'lastHeartbeat', wireKey: 'LastHeartbeat', optional: false, nullable: false, type: { kind: 'scalar', isBigint: true } }, + ], +} + +/** + * Provides per-account data. + */ +export type LedgerAccountData = { + accountBaseData: LedgerAccountBaseData + votingData: LedgerVotingData +} + +export const LedgerAccountDataMeta: ModelMetadata = { + name: 'LedgerAccountData', + kind: 'object', + fields: [ + { + name: 'accountBaseData', + flattened: true, + optional: false, + nullable: false, + type: { kind: 'model', meta: LedgerAccountBaseDataMeta }, + }, + { name: 'votingData', flattened: true, optional: false, nullable: false, type: { kind: 'model', meta: LedgerVotingDataMeta } }, + ], +} + +export type LedgerBalanceRecord = { + address: string + accountData: LedgerAccountData +} + +export const LedgerBalanceRecordMeta: ModelMetadata = { + name: 'LedgerBalanceRecord', + kind: 'object', + fields: [ + { name: 'address', wireKey: 'Addr', optional: false, nullable: false, type: { kind: 'scalar', isAddress: true } }, + { name: 'accountData', flattened: true, optional: false, nullable: false, type: { kind: 'model', meta: LedgerAccountDataMeta } }, + ], +} + +export type LedgerAccountDeltas = { + accounts?: LedgerBalanceRecord[] + appResources?: LedgerAppResourceRecord[] + assetResources?: LedgerAssetResourceRecord[] +} + +export const LedgerAccountDeltasMeta: ModelMetadata = { + name: 'LedgerAccountDeltas', + kind: 'object', + fields: [ + { + name: 'accounts', + wireKey: 'Accts', + optional: true, + nullable: false, + type: { kind: 'array', item: { kind: 'model', meta: LedgerBalanceRecordMeta } }, + }, + { + name: 'appResources', + wireKey: 'AppResources', + optional: true, + nullable: false, + type: { kind: 'array', item: { kind: 'model', meta: LedgerAppResourceRecordMeta } }, + }, + { + name: 'assetResources', + wireKey: 'AssetResources', + optional: true, + nullable: false, + type: { kind: 'array', item: { kind: 'model', meta: LedgerAssetResourceRecordMeta } }, + }, + ], +} + +/** + * Shows how the data associated with a key in the kvstore has changed. + */ +export type LedgerKvValueDelta = { + /** + * Stores the most recent value (undefined means deleted). + */ + data?: Uint8Array + /** + * Stores the previous value (undefined means didn't exist). + */ + oldData?: Uint8Array +} + +export const LedgerKvValueDeltaMeta: ModelMetadata = { + name: 'LedgerKvValueDelta', + kind: 'object', + fields: [ + { name: 'data', wireKey: 'Data', optional: true, nullable: false, type: { kind: 'scalar', isBytes: true } }, + { name: 'oldData', wireKey: 'OldData', optional: true, nullable: false, type: { kind: 'scalar', isBytes: true } }, + ], +} + +/** + * Defines the transactions included in a block, their index and last valid round. + */ +export type LedgerIncludedTransactions = { + lastValid: bigint + /** + * The index of the transaction in the block. + */ + intra: number +} + +export const LedgerIncludedTransactionsMeta: ModelMetadata = { + name: 'LedgerIncludedTransactions', + kind: 'object', + fields: [ + { name: 'lastValid', wireKey: 'LastValid', optional: false, nullable: false, type: { kind: 'scalar', isBigint: true } }, + { name: 'intra', wireKey: 'Intra', optional: false, nullable: false, type: { kind: 'scalar' } }, + ], +} + +/** + * Represents a change to a single creatable state. + */ +export type LedgerModifiedCreatable = { + /** + * Type of the creatable. The values are: + * * 0: Asset + * * 1: Application + */ + creatableType: number + /** + * Created if true, deleted if false. + */ + created: boolean + /** + * Creator of the app/asset. + */ + creator: string + /** + * Keeps track of how many times this app/asset appears in accountUpdates.creatableDeltas. + */ + nDeltas: number +} + +export const LedgerModifiedCreatableMeta: ModelMetadata = { + name: 'LedgerModifiedCreatable', + kind: 'object', + fields: [ + { name: 'creatableType', wireKey: 'Ctype', optional: false, nullable: false, type: { kind: 'scalar' } }, + { name: 'created', wireKey: 'Created', optional: false, nullable: false, type: { kind: 'scalar' } }, + { name: 'creator', wireKey: 'Creator', optional: false, nullable: false, type: { kind: 'scalar', isAddress: true } }, + { name: 'ndeltas', wireKey: 'Ndeltas', optional: false, nullable: false, type: { kind: 'scalar' } }, + ], +} + +/** + * Represents a total of algos of a certain class of accounts (split up by their Status value). + */ +export type LedgerAlgoCount = { + /** + * Sum of algos of all accounts in this scope. + */ + money: bigint + /** + * Total number of whole reward units in accounts. + */ + rewardUnits: bigint +} + +export const LedgerAlgoCountMeta: ModelMetadata = { + name: 'LedgerAlgoCount', + kind: 'object', + fields: [ + { name: 'money', wireKey: 'mon', optional: false, nullable: false, type: { kind: 'scalar', isBigint: true } }, + { name: 'rewardUnits', wireKey: 'rwd', optional: false, nullable: false, type: { kind: 'scalar', isBigint: true } }, + ], +} + +/** + * Represents the totals of algos in the system grouped by different account status values. + */ +export type LedgerAccountTotals = { + online: LedgerAlgoCount + offline: LedgerAlgoCount + notParticipating: LedgerAlgoCount + /** + * Total number of algos received per reward unit since genesis. + */ + rewardsLevel: bigint +} + +export const LedgerAccountTotalsMeta: ModelMetadata = { + name: 'LedgerAccountTotals', + kind: 'object', + fields: [ + { name: 'online', wireKey: 'online', optional: false, nullable: false, type: { kind: 'model', meta: LedgerAlgoCountMeta } }, + { name: 'offline', wireKey: 'offline', optional: false, nullable: false, type: { kind: 'model', meta: LedgerAlgoCountMeta } }, + { name: 'notParticipating', wireKey: 'notpart', optional: false, nullable: false, type: { kind: 'model', meta: LedgerAlgoCountMeta } }, + { name: 'rewardsLevel', wireKey: 'rwdlvl', optional: false, nullable: false, type: { kind: 'scalar', isBigint: true } }, + ], +} + +/** + * Describes the delta between a given round to the previous round. + */ +export type LedgerStateDelta = { + /** + * Modified new accounts. + */ + accounts: LedgerAccountDeltas + /** + * Block header. + */ + block: Block + /** + * Represents modification on StateProofNextRound field in the block header. If the block contains + * a valid state proof transaction, this field will contain the next round for state proof. + * otherwise it will be set to 0. + */ + stateProofNext: bigint + /** + * Previous block timestamp + */ + prevTimestamp: bigint + /** + * The account totals reflecting the changes in this StateDelta object. + */ + totals: LedgerAccountTotals + /** + * Modified kv pairs. + */ + kvMods?: Map + /** + * New Txids for the txtail and TxnCounter, mapped to txn.LastValid. + */ + txIds?: Map + /** + * New txleases for the txtail mapped to expiration. + */ + txLeases?: Record + /** + * New creatables creator lookup table. + */ + creatables?: Map +} + +export const LedgerStateDeltaMeta: ModelMetadata = { + name: 'LedgerStateDelta', + kind: 'object', + fields: [ + { name: 'accounts', wireKey: 'Accts', optional: false, nullable: false, type: { kind: 'model', meta: LedgerAccountDeltasMeta } }, + { name: 'block', wireKey: 'Hdr', optional: false, nullable: false, type: { kind: 'model', meta: BlockMeta } }, + { name: 'stateProofNext', wireKey: 'StateProofNext', optional: false, nullable: false, type: { kind: 'scalar', isBigint: true } }, + { name: 'prevTimestamp', wireKey: 'PrevTimestamp', optional: false, nullable: false, type: { kind: 'scalar', isBigint: true } }, + { name: 'totals', wireKey: 'Totals', optional: false, nullable: false, type: { kind: 'model', meta: LedgerAccountTotalsMeta } }, + { + name: 'kvMods', + wireKey: 'KvMods', + optional: true, + nullable: false, + type: { kind: 'map', keyType: 'bytes', value: { kind: 'model', meta: LedgerKvValueDeltaMeta } }, + }, + { + name: 'txIds', + wireKey: 'Txids', + optional: true, + nullable: false, + type: { kind: 'map', keyType: 'bytes', value: { kind: 'model', meta: LedgerIncludedTransactionsMeta } }, + }, + { name: 'txLeases', wireKey: 'Txleases', optional: true, nullable: false, type: { kind: 'scalar' } }, + { + name: 'creatables', + wireKey: 'Creatables', + optional: true, + nullable: false, + type: { kind: 'map', keyType: 'number', value: { kind: 'model', meta: LedgerModifiedCreatableMeta } }, + }, + ], +} diff --git a/oas-generator/src/oas_generator/templates/models/transaction-params/suggested-params.ts.j2 b/oas-generator/src/oas_generator/templates/models/custom/suggested-params.ts.j2 similarity index 100% rename from oas-generator/src/oas_generator/templates/models/transaction-params/suggested-params.ts.j2 rename to oas-generator/src/oas_generator/templates/models/custom/suggested-params.ts.j2 diff --git a/oas-generator/src/oas_generator/templates/models/model.ts.j2 b/oas-generator/src/oas_generator/templates/models/model.ts.j2 index f6680f2a..eee3fbb1 100644 --- a/oas-generator/src/oas_generator/templates/models/model.ts.j2 +++ b/oas-generator/src/oas_generator/templates/models/model.ts.j2 @@ -12,11 +12,13 @@ { kind: 'scalar'{% if is_bytes %}, isBytes: true{% endif %}{% if is_bigint %}, isBigint: true{% endif %} } {%- endmacro %} -{% macro base_type(ref_model, is_signed_txn, is_bytes, is_bigint) -%} +{% macro base_type(ref_model, is_signed_txn, is_bytes, is_bigint, inline_meta_name) -%} {%- if is_signed_txn -%} { kind: 'codec', codecKey: 'SignedTransaction' } {%- elif ref_model -%} -{ kind: 'model', meta: () => {{ ref_model }}Meta } +{ kind: 'model', meta: {% if modelName == ref_model %}() => {% endif %}{{ ref_model }}Meta } +{%- elif inline_meta_name -%} +{ kind: 'model', meta: {{ inline_meta_name }} } {%- else -%} {{ scalar_meta(is_bytes, is_bigint) }} {%- endif -%} @@ -24,9 +26,9 @@ {% macro field_type(field) -%} {%- if field.is_array -%} -{ kind: 'array', item: {{ base_type(field.ref_model, field.is_signed_txn, field.is_bytes, field.is_bigint) }} } +{ kind: 'array', item: {{ base_type(field.ref_model, field.is_signed_txn, field.is_bytes, field.is_bigint, none) }} } {%- else -%} -{{ base_type(field.ref_model, field.is_signed_txn, field.is_bytes, field.is_bigint) }} +{{ base_type(field.ref_model, field.is_signed_txn, field.is_bytes, field.is_bigint, field.inline_meta_name) }} {%- endif -%} {%- endmacro %} @@ -58,6 +60,29 @@ import { {{ r }}Meta } from './{{ r | ts_kebab_case }}'; {% endif %} {% endfor %} +{% macro inline_object_meta(inline_schema, required_fields, meta_name) -%} +{ name: '{{ meta_name }}', kind: 'object', fields: [ +{%- for prop_name, prop_schema in inline_schema.get('properties', {}).items() %} + { + name: '{{ prop_name | ts_camel_case }}', + wireKey: '{{ prop_name }}', + optional: {{ 'false' if prop_name in required_fields else 'true' }}, + nullable: {{ 'true' if prop_schema.get('nullable') else 'false' }}, + type: {{ scalar_meta(prop_schema.get('format') == 'byte' or prop_schema.get('x-algokit-bytes-base64'), prop_schema.get('x-algokit-bigint')) }}, + }, +{%- endfor %} + ] } +{%- endmacro %} + +{% if descriptor.is_object %} +{% for f in descriptor.fields %} +{% if f.inline_object_schema %} +const {{ f.inline_meta_name }}: ModelMetadata = {{ inline_object_meta(f.inline_object_schema, f.inline_object_schema.get('required', []), f.inline_meta_name) }}; + +{% endif %} +{% endfor %} +{% endif %} + {{ schema.description | ts_doc_comment }} {% if isObject and schema.get('allOf') is not defined and schema.get('oneOf') is not defined and schema.get('anyOf') is not defined %} export interface {{ modelName }} { diff --git a/packages/algod_client/package.json b/packages/algod_client/package.json index de108cfd..5d3ce75f 100644 --- a/packages/algod_client/package.json +++ b/packages/algod_client/package.json @@ -26,4 +26,4 @@ "dependencies": {}, "peerDependencies": {}, "devDependencies": {} -} +} \ No newline at end of file diff --git a/packages/algod_client/src/apis/api.service.ts b/packages/algod_client/src/apis/api.service.ts index 21678eac..de104837 100644 --- a/packages/algod_client/src/apis/api.service.ts +++ b/packages/algod_client/src/apis/api.service.ts @@ -94,7 +94,7 @@ export class AlgodApi { const responseFormat: BodyFormat = 'json' headers['Accept'] = AlgodApi.acceptFor(responseFormat) - const payload = await this.httpRequest.request({ + const payload = await this.httpRequest.request({ method: 'GET', url: '/v2/accounts/{address}/applications/{application-id}', path: { address: address, 'application-id': typeof applicationId === 'bigint' ? applicationId.toString() : applicationId }, @@ -104,11 +104,7 @@ export class AlgodApi { mediaType: undefined, }) - const responseMeta = AccountApplicationInformationMeta - if (responseMeta) { - return AlgorandSerializer.decode(payload, responseMeta, responseFormat) - } - return payload as AccountApplicationInformation + return AlgorandSerializer.decode(payload, AccountApplicationInformationMeta, responseFormat) } /** @@ -119,7 +115,7 @@ export class AlgodApi { const responseFormat: BodyFormat = 'json' headers['Accept'] = AlgodApi.acceptFor(responseFormat) - const payload = await this.httpRequest.request({ + const payload = await this.httpRequest.request({ method: 'GET', url: '/v2/accounts/{address}/assets/{asset-id}', path: { address: address, 'asset-id': typeof assetId === 'bigint' ? assetId.toString() : assetId }, @@ -129,11 +125,7 @@ export class AlgodApi { mediaType: undefined, }) - const responseMeta = AccountAssetInformationMeta - if (responseMeta) { - return AlgorandSerializer.decode(payload, responseMeta, responseFormat) - } - return payload as AccountAssetInformation + return AlgorandSerializer.decode(payload, AccountAssetInformationMeta, responseFormat) } /** @@ -144,7 +136,7 @@ export class AlgodApi { const responseFormat: BodyFormat = 'json' headers['Accept'] = AlgodApi.acceptFor(responseFormat) - const payload = await this.httpRequest.request({ + const payload = await this.httpRequest.request({ method: 'GET', url: '/v2/accounts/{address}', path: { address: address }, @@ -154,11 +146,7 @@ export class AlgodApi { mediaType: undefined, }) - const responseMeta = AccountMeta - if (responseMeta) { - return AlgorandSerializer.decode(payload, responseMeta, responseFormat) - } - return payload as Account + return AlgorandSerializer.decode(payload, AccountMeta, responseFormat) } /** @@ -169,7 +157,7 @@ export class AlgodApi { const responseFormat: BodyFormat = 'json' headers['Accept'] = AlgodApi.acceptFor(responseFormat) - const payload = await this.httpRequest.request({ + const payload = await this.httpRequest.request({ method: 'GET', url: '/v2/applications/{application-id}/box', path: { 'application-id': typeof applicationId === 'bigint' ? applicationId.toString() : applicationId }, @@ -179,11 +167,7 @@ export class AlgodApi { mediaType: undefined, }) - const responseMeta = BoxMeta - if (responseMeta) { - return AlgorandSerializer.decode(payload, responseMeta, responseFormat) - } - return payload as Box + return AlgorandSerializer.decode(payload, BoxMeta, responseFormat) } /** @@ -194,7 +178,7 @@ export class AlgodApi { const responseFormat: BodyFormat = 'json' headers['Accept'] = AlgodApi.acceptFor(responseFormat) - const payload = await this.httpRequest.request({ + const payload = await this.httpRequest.request({ method: 'GET', url: '/v2/applications/{application-id}/boxes', path: { 'application-id': typeof applicationId === 'bigint' ? applicationId.toString() : applicationId }, @@ -204,11 +188,7 @@ export class AlgodApi { mediaType: undefined, }) - const responseMeta = GetApplicationBoxesMeta - if (responseMeta) { - return AlgorandSerializer.decode(payload, responseMeta, responseFormat) - } - return payload as GetApplicationBoxes + return AlgorandSerializer.decode(payload, GetApplicationBoxesMeta, responseFormat) } /** @@ -219,7 +199,7 @@ export class AlgodApi { const responseFormat: BodyFormat = 'json' headers['Accept'] = AlgodApi.acceptFor(responseFormat) - const payload = await this.httpRequest.request({ + const payload = await this.httpRequest.request({ method: 'GET', url: '/v2/applications/{application-id}', path: { 'application-id': typeof applicationId === 'bigint' ? applicationId.toString() : applicationId }, @@ -229,11 +209,7 @@ export class AlgodApi { mediaType: undefined, }) - const responseMeta = ApplicationMeta - if (responseMeta) { - return AlgorandSerializer.decode(payload, responseMeta, responseFormat) - } - return payload as Application + return AlgorandSerializer.decode(payload, ApplicationMeta, responseFormat) } /** @@ -244,7 +220,7 @@ export class AlgodApi { const responseFormat: BodyFormat = 'json' headers['Accept'] = AlgodApi.acceptFor(responseFormat) - const payload = await this.httpRequest.request({ + const payload = await this.httpRequest.request({ method: 'GET', url: '/v2/assets/{asset-id}', path: { 'asset-id': typeof assetId === 'bigint' ? assetId.toString() : assetId }, @@ -254,11 +230,7 @@ export class AlgodApi { mediaType: undefined, }) - const responseMeta = AssetMeta - if (responseMeta) { - return AlgorandSerializer.decode(payload, responseMeta, responseFormat) - } - return payload as Asset + return AlgorandSerializer.decode(payload, AssetMeta, responseFormat) } async getBlock(round: number | bigint, params?: { headerOnly?: boolean }): Promise { @@ -266,7 +238,7 @@ export class AlgodApi { const responseFormat: BodyFormat = 'msgpack' headers['Accept'] = AlgodApi.acceptFor(responseFormat) - const payload = await this.httpRequest.request({ + const payload = await this.httpRequest.request({ method: 'GET', url: '/v2/blocks/{round}', path: { round: typeof round === 'bigint' ? round.toString() : round }, @@ -276,11 +248,7 @@ export class AlgodApi { mediaType: undefined, }) - const responseMeta = GetBlockMeta - if (responseMeta) { - return AlgorandSerializer.decode(payload, responseMeta, responseFormat) - } - return payload as GetBlock + return AlgorandSerializer.decode(payload, GetBlockMeta, responseFormat) } async getBlockHash(round: number | bigint): Promise { @@ -288,7 +256,7 @@ export class AlgodApi { const responseFormat: BodyFormat = 'json' headers['Accept'] = AlgodApi.acceptFor(responseFormat) - const payload = await this.httpRequest.request({ + const payload = await this.httpRequest.request({ method: 'GET', url: '/v2/blocks/{round}/hash', path: { round: typeof round === 'bigint' ? round.toString() : round }, @@ -298,11 +266,7 @@ export class AlgodApi { mediaType: undefined, }) - const responseMeta = GetBlockHashMeta - if (responseMeta) { - return AlgorandSerializer.decode(payload, responseMeta, responseFormat) - } - return payload as GetBlockHash + return AlgorandSerializer.decode(payload, GetBlockHashMeta, responseFormat) } /** @@ -313,7 +277,7 @@ export class AlgodApi { const responseFormat: BodyFormat = 'json' headers['Accept'] = AlgodApi.acceptFor(responseFormat) - const payload = await this.httpRequest.request({ + const payload = await this.httpRequest.request({ method: 'GET', url: '/v2/devmode/blocks/offset', path: {}, @@ -323,11 +287,7 @@ export class AlgodApi { mediaType: undefined, }) - const responseMeta = GetBlockTimeStampOffsetMeta - if (responseMeta) { - return AlgorandSerializer.decode(payload, responseMeta, responseFormat) - } - return payload as GetBlockTimeStampOffset + return AlgorandSerializer.decode(payload, GetBlockTimeStampOffsetMeta, responseFormat) } async getBlockTxIds(round: number | bigint): Promise { @@ -335,7 +295,7 @@ export class AlgodApi { const responseFormat: BodyFormat = 'json' headers['Accept'] = AlgodApi.acceptFor(responseFormat) - const payload = await this.httpRequest.request({ + const payload = await this.httpRequest.request({ method: 'GET', url: '/v2/blocks/{round}/txids', path: { round: typeof round === 'bigint' ? round.toString() : round }, @@ -345,11 +305,7 @@ export class AlgodApi { mediaType: undefined, }) - const responseMeta = GetBlockTxIdsMeta - if (responseMeta) { - return AlgorandSerializer.decode(payload, responseMeta, responseFormat) - } - return payload as GetBlockTxIds + return AlgorandSerializer.decode(payload, GetBlockTxIdsMeta, responseFormat) } /** @@ -360,7 +316,7 @@ export class AlgodApi { const responseFormat: BodyFormat = 'json' headers['Accept'] = AlgodApi.acceptFor(responseFormat) - const payload = await this.httpRequest.request({ + const payload = await this.httpRequest.request({ method: 'GET', url: '/genesis', path: {}, @@ -370,11 +326,7 @@ export class AlgodApi { mediaType: undefined, }) - const responseMeta = GenesisMeta - if (responseMeta) { - return AlgorandSerializer.decode(payload, responseMeta, responseFormat) - } - return payload as Genesis + return AlgorandSerializer.decode(payload, GenesisMeta, responseFormat) } /** @@ -385,7 +337,7 @@ export class AlgodApi { const responseFormat: BodyFormat = 'msgpack' headers['Accept'] = AlgodApi.acceptFor(responseFormat) - const payload = await this.httpRequest.request({ + const payload = await this.httpRequest.request({ method: 'GET', url: '/v2/deltas/{round}', path: { round: typeof round === 'bigint' ? round.toString() : round }, @@ -395,11 +347,7 @@ export class AlgodApi { mediaType: undefined, }) - const responseMeta = LedgerStateDeltaMeta - if (responseMeta) { - return AlgorandSerializer.decode(payload, responseMeta, responseFormat) - } - return payload as LedgerStateDelta + return AlgorandSerializer.decode(payload, LedgerStateDeltaMeta, responseFormat) } /** @@ -410,7 +358,7 @@ export class AlgodApi { const responseFormat: BodyFormat = 'msgpack' headers['Accept'] = AlgodApi.acceptFor(responseFormat) - const payload = await this.httpRequest.request({ + const payload = await this.httpRequest.request({ method: 'GET', url: '/v2/deltas/txn/group/{id}', path: { id: id }, @@ -420,11 +368,7 @@ export class AlgodApi { mediaType: undefined, }) - const responseMeta = LedgerStateDeltaMeta - if (responseMeta) { - return AlgorandSerializer.decode(payload, responseMeta, responseFormat) - } - return payload as LedgerStateDelta + return AlgorandSerializer.decode(payload, LedgerStateDeltaMeta, responseFormat) } async getLightBlockHeaderProof(round: number | bigint): Promise { @@ -432,7 +376,7 @@ export class AlgodApi { const responseFormat: BodyFormat = 'json' headers['Accept'] = AlgodApi.acceptFor(responseFormat) - const payload = await this.httpRequest.request({ + const payload = await this.httpRequest.request({ method: 'GET', url: '/v2/blocks/{round}/lightheader/proof', path: { round: typeof round === 'bigint' ? round.toString() : round }, @@ -442,11 +386,7 @@ export class AlgodApi { mediaType: undefined, }) - const responseMeta = LightBlockHeaderProofMeta - if (responseMeta) { - return AlgorandSerializer.decode(payload, responseMeta, responseFormat) - } - return payload as LightBlockHeaderProof + return AlgorandSerializer.decode(payload, LightBlockHeaderProofMeta, responseFormat) } /** @@ -457,7 +397,7 @@ export class AlgodApi { const responseFormat: BodyFormat = 'msgpack' headers['Accept'] = AlgodApi.acceptFor(responseFormat) - const payload = await this.httpRequest.request({ + const payload = await this.httpRequest.request({ method: 'GET', url: '/v2/transactions/pending', path: {}, @@ -467,11 +407,7 @@ export class AlgodApi { mediaType: undefined, }) - const responseMeta = GetPendingTransactionsMeta - if (responseMeta) { - return AlgorandSerializer.decode(payload, responseMeta, responseFormat) - } - return payload as GetPendingTransactions + return AlgorandSerializer.decode(payload, GetPendingTransactionsMeta, responseFormat) } /** @@ -482,7 +418,7 @@ export class AlgodApi { const responseFormat: BodyFormat = 'msgpack' headers['Accept'] = AlgodApi.acceptFor(responseFormat) - const payload = await this.httpRequest.request({ + const payload = await this.httpRequest.request({ method: 'GET', url: '/v2/accounts/{address}/transactions/pending', path: { address: address }, @@ -492,11 +428,7 @@ export class AlgodApi { mediaType: undefined, }) - const responseMeta = GetPendingTransactionsByAddressMeta - if (responseMeta) { - return AlgorandSerializer.decode(payload, responseMeta, responseFormat) - } - return payload as GetPendingTransactionsByAddress + return AlgorandSerializer.decode(payload, GetPendingTransactionsByAddressMeta, responseFormat) } async getReady(): Promise { @@ -504,7 +436,7 @@ export class AlgodApi { const responseFormat: BodyFormat = 'json' headers['Accept'] = AlgodApi.acceptFor(responseFormat) - const payload = await this.httpRequest.request({ + await this.httpRequest.request({ method: 'GET', url: '/ready', path: {}, @@ -513,12 +445,6 @@ export class AlgodApi { body: undefined, mediaType: undefined, }) - - const responseMeta = undefined - if (responseMeta) { - return AlgorandSerializer.decode(payload, responseMeta, responseFormat) - } - return payload as void } async getStateProof(round: number | bigint): Promise { @@ -526,7 +452,7 @@ export class AlgodApi { const responseFormat: BodyFormat = 'json' headers['Accept'] = AlgodApi.acceptFor(responseFormat) - const payload = await this.httpRequest.request({ + const payload = await this.httpRequest.request({ method: 'GET', url: '/v2/stateproofs/{round}', path: { round: typeof round === 'bigint' ? round.toString() : round }, @@ -536,11 +462,7 @@ export class AlgodApi { mediaType: undefined, }) - const responseMeta = StateProofMeta - if (responseMeta) { - return AlgorandSerializer.decode(payload, responseMeta, responseFormat) - } - return payload as StateProof + return AlgorandSerializer.decode(payload, StateProofMeta, responseFormat) } async getStatus(): Promise { @@ -548,7 +470,7 @@ export class AlgodApi { const responseFormat: BodyFormat = 'json' headers['Accept'] = AlgodApi.acceptFor(responseFormat) - const payload = await this.httpRequest.request({ + const payload = await this.httpRequest.request({ method: 'GET', url: '/v2/status', path: {}, @@ -558,11 +480,7 @@ export class AlgodApi { mediaType: undefined, }) - const responseMeta = GetStatusMeta - if (responseMeta) { - return AlgorandSerializer.decode(payload, responseMeta, responseFormat) - } - return payload as GetStatus + return AlgorandSerializer.decode(payload, GetStatusMeta, responseFormat) } async getSupply(): Promise { @@ -570,7 +488,7 @@ export class AlgodApi { const responseFormat: BodyFormat = 'json' headers['Accept'] = AlgodApi.acceptFor(responseFormat) - const payload = await this.httpRequest.request({ + const payload = await this.httpRequest.request({ method: 'GET', url: '/v2/ledger/supply', path: {}, @@ -580,11 +498,7 @@ export class AlgodApi { mediaType: undefined, }) - const responseMeta = GetSupplyMeta - if (responseMeta) { - return AlgorandSerializer.decode(payload, responseMeta, responseFormat) - } - return payload as GetSupply + return AlgorandSerializer.decode(payload, GetSupplyMeta, responseFormat) } /** @@ -595,7 +509,7 @@ export class AlgodApi { const responseFormat: BodyFormat = 'json' headers['Accept'] = AlgodApi.acceptFor(responseFormat) - const payload = await this.httpRequest.request({ + const payload = await this.httpRequest.request({ method: 'GET', url: '/v2/ledger/sync', path: {}, @@ -605,11 +519,7 @@ export class AlgodApi { mediaType: undefined, }) - const responseMeta = GetSyncRoundMeta - if (responseMeta) { - return AlgorandSerializer.decode(payload, responseMeta, responseFormat) - } - return payload as GetSyncRound + return AlgorandSerializer.decode(payload, GetSyncRoundMeta, responseFormat) } /** @@ -620,7 +530,7 @@ export class AlgodApi { const responseFormat: BodyFormat = 'msgpack' headers['Accept'] = AlgodApi.acceptFor(responseFormat) - const payload = await this.httpRequest.request({ + const payload = await this.httpRequest.request({ method: 'GET', url: '/v2/deltas/{round}/txn/group', path: { round: typeof round === 'bigint' ? round.toString() : round }, @@ -630,11 +540,7 @@ export class AlgodApi { mediaType: undefined, }) - const responseMeta = GetTransactionGroupLedgerStateDeltasForRoundMeta - if (responseMeta) { - return AlgorandSerializer.decode(payload, responseMeta, responseFormat) - } - return payload as GetTransactionGroupLedgerStateDeltasForRound + return AlgorandSerializer.decode(payload, GetTransactionGroupLedgerStateDeltasForRoundMeta, responseFormat) } async getTransactionProof( @@ -646,7 +552,7 @@ export class AlgodApi { const responseFormat: BodyFormat = 'json' headers['Accept'] = AlgodApi.acceptFor(responseFormat) - const payload = await this.httpRequest.request({ + const payload = await this.httpRequest.request({ method: 'GET', url: '/v2/blocks/{round}/transactions/{txid}/proof', path: { round: typeof round === 'bigint' ? round.toString() : round, txid: txid }, @@ -656,11 +562,7 @@ export class AlgodApi { mediaType: undefined, }) - const responseMeta = TransactionProofMeta - if (responseMeta) { - return AlgorandSerializer.decode(payload, responseMeta, responseFormat) - } - return payload as TransactionProof + return AlgorandSerializer.decode(payload, TransactionProofMeta, responseFormat) } /** @@ -671,7 +573,7 @@ export class AlgodApi { const responseFormat: BodyFormat = 'json' headers['Accept'] = AlgodApi.acceptFor(responseFormat) - const payload = await this.httpRequest.request({ + const payload = await this.httpRequest.request({ method: 'GET', url: '/versions', path: {}, @@ -681,11 +583,7 @@ export class AlgodApi { mediaType: undefined, }) - const responseMeta = VersionMeta - if (responseMeta) { - return AlgorandSerializer.decode(payload, responseMeta, responseFormat) - } - return payload as Version + return AlgorandSerializer.decode(payload, VersionMeta, responseFormat) } async healthCheck(): Promise { @@ -693,7 +591,7 @@ export class AlgodApi { const responseFormat: BodyFormat = 'json' headers['Accept'] = AlgodApi.acceptFor(responseFormat) - const payload = await this.httpRequest.request({ + await this.httpRequest.request({ method: 'GET', url: '/health', path: {}, @@ -702,12 +600,6 @@ export class AlgodApi { body: undefined, mediaType: undefined, }) - - const responseMeta = undefined - if (responseMeta) { - return AlgorandSerializer.decode(payload, responseMeta, responseFormat) - } - return payload as void } /** @@ -722,7 +614,7 @@ export class AlgodApi { const responseFormat: BodyFormat = 'msgpack' headers['Accept'] = AlgodApi.acceptFor(responseFormat) - const payload = await this.httpRequest.request({ + const payload = await this.httpRequest.request({ method: 'GET', url: '/v2/transactions/pending/{txid}', path: { txid: txid }, @@ -732,11 +624,7 @@ export class AlgodApi { mediaType: undefined, }) - const responseMeta = PendingTransactionResponseMeta - if (responseMeta) { - return AlgorandSerializer.decode(payload, responseMeta, responseFormat) - } - return payload as PendingTransactionResponse + return AlgorandSerializer.decode(payload, PendingTransactionResponseMeta, responseFormat) } private async _rawTransaction(body: Uint8Array): Promise { @@ -748,7 +636,7 @@ export class AlgodApi { const mediaType = 'application/msgpack' headers['Content-Type'] = mediaType - const payload = await this.httpRequest.request({ + const payload = await this.httpRequest.request({ method: 'POST', url: '/v2/transactions', path: {}, @@ -758,11 +646,7 @@ export class AlgodApi { mediaType: mediaType, }) - const responseMeta = RawTransactionMeta - if (responseMeta) { - return AlgorandSerializer.decode(payload, responseMeta, responseFormat) - } - return payload as RawTransaction + return AlgorandSerializer.decode(payload, RawTransactionMeta, responseFormat) } /** @@ -773,7 +657,7 @@ export class AlgodApi { const responseFormat: BodyFormat = 'json' headers['Accept'] = AlgodApi.acceptFor(responseFormat) - const payload = await this.httpRequest.request({ + await this.httpRequest.request({ method: 'POST', url: '/v2/devmode/blocks/offset/{offset}', path: { offset: offset }, @@ -782,12 +666,6 @@ export class AlgodApi { body: undefined, mediaType: undefined, }) - - const responseMeta = undefined - if (responseMeta) { - return AlgorandSerializer.decode(payload, responseMeta, responseFormat) - } - return payload as void } /** @@ -798,7 +676,7 @@ export class AlgodApi { const responseFormat: BodyFormat = 'json' headers['Accept'] = AlgodApi.acceptFor(responseFormat) - const payload = await this.httpRequest.request({ + await this.httpRequest.request({ method: 'POST', url: '/v2/ledger/sync/{round}', path: { round: typeof round === 'bigint' ? round.toString() : round }, @@ -807,12 +685,6 @@ export class AlgodApi { body: undefined, mediaType: undefined, }) - - const responseMeta = undefined - if (responseMeta) { - return AlgorandSerializer.decode(payload, responseMeta, responseFormat) - } - return payload as void } async simulateTransaction(body: SimulateRequest): Promise { @@ -823,9 +695,9 @@ export class AlgodApi { const bodyMeta = SimulateRequestMeta const mediaType = bodyMeta ? AlgodApi.mediaFor(responseFormat) : undefined if (mediaType) headers['Content-Type'] = mediaType - const serializedBody = bodyMeta && body !== undefined ? AlgorandSerializer.encode(body, bodyMeta, responseFormat) : body + const serializedBody = body ? AlgorandSerializer.encode(body, bodyMeta, responseFormat) : undefined - const payload = await this.httpRequest.request({ + const payload = await this.httpRequest.request({ method: 'POST', url: '/v2/transactions/simulate', path: {}, @@ -835,11 +707,7 @@ export class AlgodApi { mediaType: mediaType, }) - const responseMeta = SimulateTransactionMeta - if (responseMeta) { - return AlgorandSerializer.decode(payload, responseMeta, responseFormat) - } - return payload as SimulateTransaction + return AlgorandSerializer.decode(payload, SimulateTransactionMeta, responseFormat) } /** @@ -853,9 +721,9 @@ export class AlgodApi { const bodyMeta = undefined const mediaType = bodyMeta ? AlgodApi.mediaFor(responseFormat) : undefined if (mediaType) headers['Content-Type'] = mediaType - const serializedBody = bodyMeta && body !== undefined ? AlgorandSerializer.encode(body, bodyMeta, responseFormat) : body + const serializedBody = body - const payload = await this.httpRequest.request({ + const payload = await this.httpRequest.request({ method: 'POST', url: '/v2/teal/compile', path: {}, @@ -865,11 +733,7 @@ export class AlgodApi { mediaType: mediaType, }) - const responseMeta = TealCompileMeta - if (responseMeta) { - return AlgorandSerializer.decode(payload, responseMeta, responseFormat) - } - return payload as TealCompile + return AlgorandSerializer.decode(payload, TealCompileMeta, responseFormat) } /** @@ -884,7 +748,7 @@ export class AlgodApi { const mediaType = 'application/msgpack' headers['Content-Type'] = mediaType - const payload = await this.httpRequest.request({ + const payload = await this.httpRequest.request({ method: 'POST', url: '/v2/teal/disassemble', path: {}, @@ -894,11 +758,7 @@ export class AlgodApi { mediaType: mediaType, }) - const responseMeta = TealDisassembleMeta - if (responseMeta) { - return AlgorandSerializer.decode(payload, responseMeta, responseFormat) - } - return payload as TealDisassemble + return AlgorandSerializer.decode(payload, TealDisassembleMeta, responseFormat) } /** @@ -912,9 +772,9 @@ export class AlgodApi { const bodyMeta = DryrunRequestMeta const mediaType = bodyMeta ? AlgodApi.mediaFor(responseFormat) : undefined if (mediaType) headers['Content-Type'] = mediaType - const serializedBody = bodyMeta && body !== undefined ? AlgorandSerializer.encode(body, bodyMeta, responseFormat) : body + const serializedBody = body ? AlgorandSerializer.encode(body, bodyMeta, responseFormat) : undefined - const payload = await this.httpRequest.request({ + const payload = await this.httpRequest.request({ method: 'POST', url: '/v2/teal/dryrun', path: {}, @@ -924,11 +784,7 @@ export class AlgodApi { mediaType: mediaType, }) - const responseMeta = TealDryrunMeta - if (responseMeta) { - return AlgorandSerializer.decode(payload, responseMeta, responseFormat) - } - return payload as TealDryrun + return AlgorandSerializer.decode(payload, TealDryrunMeta, responseFormat) } private async _transactionParams(): Promise { @@ -936,7 +792,7 @@ export class AlgodApi { const responseFormat: BodyFormat = 'json' headers['Accept'] = AlgodApi.acceptFor(responseFormat) - const payload = await this.httpRequest.request({ + const payload = await this.httpRequest.request({ method: 'GET', url: '/v2/transactions/params', path: {}, @@ -946,11 +802,7 @@ export class AlgodApi { mediaType: undefined, }) - const responseMeta = TransactionParamsMeta - if (responseMeta) { - return AlgorandSerializer.decode(payload, responseMeta, responseFormat) - } - return payload as TransactionParams + return AlgorandSerializer.decode(payload, TransactionParamsMeta, responseFormat) } /** @@ -961,7 +813,7 @@ export class AlgodApi { const responseFormat: BodyFormat = 'json' headers['Accept'] = AlgodApi.acceptFor(responseFormat) - const payload = await this.httpRequest.request({ + await this.httpRequest.request({ method: 'DELETE', url: '/v2/ledger/sync', path: {}, @@ -970,12 +822,6 @@ export class AlgodApi { body: undefined, mediaType: undefined, }) - - const responseMeta = undefined - if (responseMeta) { - return AlgorandSerializer.decode(payload, responseMeta, responseFormat) - } - return payload as void } /** @@ -986,7 +832,7 @@ export class AlgodApi { const responseFormat: BodyFormat = 'json' headers['Accept'] = AlgodApi.acceptFor(responseFormat) - const payload = await this.httpRequest.request({ + const payload = await this.httpRequest.request({ method: 'GET', url: '/v2/status/wait-for-block-after/{round}', path: { round: typeof round === 'bigint' ? round.toString() : round }, @@ -996,11 +842,7 @@ export class AlgodApi { mediaType: undefined, }) - const responseMeta = WaitForBlockMeta - if (responseMeta) { - return AlgorandSerializer.decode(payload, responseMeta, responseFormat) - } - return payload as WaitForBlock + return AlgorandSerializer.decode(payload, WaitForBlockMeta, responseFormat) } /** diff --git a/packages/algod_client/src/core/codecs.ts b/packages/algod_client/src/core/codecs.ts index 829dcd7b..214a543d 100644 --- a/packages/algod_client/src/core/codecs.ts +++ b/packages/algod_client/src/core/codecs.ts @@ -1,42 +1,28 @@ import { decode as msgpackDecode, encode as msgpackEncode } from 'algorand-msgpack' -export function encodeMsgPack(data: T): Uint8Array { +export function encodeMsgPack(data: ApiData): Uint8Array { return new Uint8Array(msgpackEncode(data, { sortKeys: true, ignoreUndefined: true })) } -export function decodeMsgPack(buffer: Uint8Array): T { - const map = msgpackDecode(buffer, { useMap: true }) as unknown - return mapToObject(map) as T +type MsgPackDecodeOptions = { + useMap: boolean + rawBinaryStringKeys: boolean + rawBinaryStringValues: boolean } -/** - * Converts a Map structure from msgpack decoding to a plain object structure. - * Maps are converted to objects recursively, except for the special case - * where the field name is "r" which remains as a Map. - */ -function mapToObject(value: unknown, fieldName?: string): unknown { - // Preserve Uint8Array as-is - if (value instanceof Uint8Array) { - return value - } else if (value instanceof Map) { - // Special case: keep "r" field as Map - if (fieldName === 'r') { - const newMap = new Map() - for (const [k, v] of value.entries()) { - newMap.set(k, mapToObject(v)) - } - return newMap - } - - // Convert Map to object - const obj: Record = {} - for (const [k, v] of value.entries()) { - obj[k] = mapToObject(v, k) - } - return obj - } else if (Array.isArray(value)) { - return value.map((item) => mapToObject(item)) - } - - return value +export function decodeMsgPack( + buffer: Uint8Array, + options: MsgPackDecodeOptions = { useMap: true, rawBinaryStringKeys: true, rawBinaryStringValues: true }, +): Map { + return msgpackDecode(buffer, options) as Map } +export type ApiData = + | null + | undefined + | string + | number + | bigint + | boolean + | Uint8Array + | object + | Map // TODO: NC - Do we ever have a string key? diff --git a/packages/algod_client/src/core/model-runtime.ts b/packages/algod_client/src/core/model-runtime.ts index ed41c188..07f6d681 100644 --- a/packages/algod_client/src/core/model-runtime.ts +++ b/packages/algod_client/src/core/model-runtime.ts @@ -1,19 +1,24 @@ import { - encodeSignedTransaction as transactEncodeSignedTransaction, - decodeSignedTransaction as transactDecodeSignedTransaction, + addressFromPublicKey, + decodedTransactionMapToObject, + fromSignedTransactionDto, + toSignedTransactionDto, type SignedTransaction, } from '@algorandfoundation/algokit-transact' -import { encodeMsgPack, decodeMsgPack } from './codecs' -import { toBase64, fromBase64 } from './serialization' +import { Buffer } from 'buffer' +import { ApiData, decodeMsgPack, encodeMsgPack } from './codecs' export type BodyFormat = 'json' | 'msgpack' | 'map' export interface ScalarFieldType { readonly kind: 'scalar' + // TODO: NC - Make this a type field readonly isBytes?: boolean readonly isBigint?: boolean + readonly isAddress?: boolean } +// TODO: NC - Needs to be renamed export interface CodecFieldType { readonly kind: 'codec' readonly codecKey: string @@ -34,7 +39,13 @@ export interface RecordFieldType { readonly value: FieldType } -export type FieldType = ScalarFieldType | CodecFieldType | ModelFieldType | ArrayFieldType | RecordFieldType +export interface MapFieldType { + readonly kind: 'map' + readonly keyType: 'number' | 'bigint' | 'bytes' + readonly value: FieldType +} + +export type FieldType = ScalarFieldType | CodecFieldType | ModelFieldType | ArrayFieldType | RecordFieldType | MapFieldType export interface FieldMetadata { readonly name: string @@ -61,39 +72,26 @@ export interface ModelMetadata { readonly passThrough?: FieldType } -// Registry for model metadata to avoid direct circular imports between model files -const modelMetaRegistry = new Map() - -export function registerModelMeta(name: string, meta: ModelMetadata): void { - modelMetaRegistry.set(name, meta) -} - -export function getModelMeta(name: string): ModelMetadata { - const meta = modelMetaRegistry.get(name) - if (!meta) throw new Error(`Model metadata not registered: ${name}`) - return meta -} - -export interface TypeCodec { - encode(value: TValue, format: BodyFormat): unknown - decode(value: unknown, format: BodyFormat): TValue +export interface EncodeableTypeConverter> { + beforeEncoding(value: T, format: BodyFormat): Record + afterDecoding(decoded: Record | Map, format: BodyFormat): T } -const codecRegistry = new Map>() - -export function registerCodec(key: string, codec: TypeCodec): void { - codecRegistry.set(key, codec as TypeCodec) -} +const encodeableTypeConverterRegistry = new Map>>() -export function getCodec(key: string): TypeCodec | undefined { - return codecRegistry.get(key) as TypeCodec | undefined +export function registerEncodeableTypeConverter(key: string, codec: EncodeableTypeConverter>): void { + encodeableTypeConverterRegistry.set(key, codec) } export class AlgorandSerializer { - static encode(value: unknown, meta: ModelMetadata, format: 'map'): Map - static encode(value: unknown, meta: ModelMetadata, format: 'json'): string - static encode(value: unknown, meta: ModelMetadata, format?: 'msgpack'): Uint8Array - static encode(value: unknown, meta: ModelMetadata, format: BodyFormat = 'msgpack'): Uint8Array | string | Map { + static encode(value: Record, meta: ModelMetadata, format: 'map'): Map + static encode(value: Record, meta: ModelMetadata, format: 'json'): string + static encode(value: Record, meta: ModelMetadata, format?: 'msgpack'): Uint8Array + static encode( + value: Record, + meta: ModelMetadata, + format: BodyFormat = 'msgpack', + ): Uint8Array | string | Map { if (format === 'map') { // For map format, use msgpack transformation to preserve types like bigint, then convert to nested Maps const wire = this.transform(value, meta, { direction: 'encode', format: 'msgpack' }) @@ -107,25 +105,25 @@ export class AlgorandSerializer { return typeof wire === 'string' ? wire : JSON.stringify(wire) } - static decode(payload: unknown, meta: ModelMetadata, format: BodyFormat = 'msgpack'): T { - let wire: unknown = payload + static decode(value: Uint8Array | string, meta: ModelMetadata, format: BodyFormat = 'msgpack'): T { + let wire: ApiData = value if (format === 'msgpack') { - if (payload instanceof Uint8Array) { - wire = decodeMsgPack(payload) + if (value instanceof Uint8Array) { + wire = decodeMsgPack(value) } - } else if (typeof payload === 'string') { - wire = JSON.parse(payload) + } else if (typeof value === 'string') { + wire = JSON.parse(value) } return this.transform(wire, meta, { direction: 'decode', format }) as T } - private static transform(value: unknown, meta: ModelMetadata, ctx: TransformContext): unknown { + private static transform(value: ApiData, meta: ModelMetadata, ctx: TransformContext): ApiData { if (value === undefined || value === null) { return value } if (meta.codecKey) { - return this.applyCodec(value, meta.codecKey, ctx) + return this.applyEncodeableTypeConversion(value, meta.codecKey, ctx) } switch (meta.kind) { @@ -139,20 +137,20 @@ export class AlgorandSerializer { } } - private static transformObject(value: unknown, meta: ModelMetadata, ctx: TransformContext): unknown { + private static transformObject(value: ApiData, meta: ModelMetadata, ctx: TransformContext): ApiData { const fields = meta.fields ?? [] - const hasFlattenedSignedTxn = fields.some((f) => f.flattened && f.type.kind === 'codec' && f.type.codecKey === 'SignedTransaction') + const hasFlattenedField = fields.some((f) => f.flattened) if (ctx.direction === 'encode') { - const src = value as Record - const out: Record = {} + const src = value as Record + const out: Record = {} for (const field of fields) { const fieldValue = src[field.name] if (fieldValue === undefined) continue const encoded = this.transformType(fieldValue, field.type, ctx) if (encoded === undefined && fieldValue === undefined) continue - if (field.flattened && field.type.kind === 'codec' && field.type.codecKey === 'SignedTransaction') { - // Merge signed transaction map into parent - const mapValue = encoded as Record + if (field.flattened) { + // Merge flattened field into parent + const mapValue = encoded as Record for (const [k, v] of Object.entries(mapValue ?? {})) out[k] = v continue } @@ -167,66 +165,206 @@ export class AlgorandSerializer { return out } - const src = value as Record - const out: Record = {} + // Decoding + const out: Record = {} const fieldByWire = new Map(fields.filter((f) => !!f.wireKey).map((field) => [field.wireKey as string, field])) - for (const [wireKey, wireValue] of Object.entries(src)) { - const field = fieldByWire.get(wireKey) + // Build a map of wire keys for each flattened field + const flattenedFieldWireKeys = new Map>() + if (hasFlattenedField) { + for (const field of fields) { + if (field.flattened && field.type.kind === 'model') { + const modelMeta = typeof field.type.meta === 'function' ? field.type.meta() : field.type.meta + const wireKeys = this.collectWireKeys(modelMeta) + flattenedFieldWireKeys.set(field, wireKeys) + } + } + } + + const entries = value instanceof Map ? Array.from(value.entries()) : Object.entries(value as Record) + const unmatchedEntries = new Map() + + for (const [key, wireValue] of entries) { + const wireKey = key instanceof Uint8Array ? Buffer.from(key).toString('utf-8') : key + const isStringKey = typeof wireKey === 'string' + const field = isStringKey ? fieldByWire.get(wireKey) : undefined + if (field) { const decoded = this.transformType(wireValue, field.type, ctx) - out[field.name] = decoded + out[field.name] = decoded === null && !field.nullable ? undefined : decoded continue } - if (meta.additionalProperties) { + + if (isStringKey && meta.additionalProperties) { out[wireKey] = this.transformType(wireValue, meta.additionalProperties, ctx) continue } - // If we have a flattened SignedTransaction, skip unknown keys (e.g., 'sig', 'txn') - if (!hasFlattenedSignedTxn) { - out[wireKey] = wireValue + + // Store unmatched entries for potential flattened field reconstruction + if (isStringKey) { + unmatchedEntries.set(wireKey, wireValue) + } + } + + // Reconstruct flattened fields from unmatched entries + if (hasFlattenedField) { + for (const field of fields) { + if (out[field.name] !== undefined) continue + if (field.flattened) { + if (field.type.kind === 'codec') { + // Reconstruct codec from entire object map + out[field.name] = this.applyEncodeableTypeConversion(value, field.type.codecKey, ctx) + } else if (field.type.kind === 'model') { + const modelMeta = typeof field.type.meta === 'function' ? field.type.meta() : field.type.meta + + // Check if this flattened model contains nested flattened codecs + const hasNestedCodec = this.hasNestedFlattenedCodec(modelMeta) + + let decoded: ApiData + if (hasNestedCodec) { + // If the model has nested flattened codecs, we need to pass the original value + // so the nested model can reconstruct its flattened codec fields + decoded = this.transform(value, modelMeta, ctx) + } else { + // Filter the wire data to only include keys belonging to this flattened model + const modelWireKeys = flattenedFieldWireKeys.get(field) + if (modelWireKeys) { + const filteredData: Record = {} + for (const [k, v] of unmatchedEntries.entries()) { + if (modelWireKeys.has(k)) { + filteredData[k] = v + } + } + // Also check if the original value is a Map and filter it + if (value instanceof Map) { + const filteredMap = new Map() + for (const [k, v] of value.entries()) { + const keyStr = k instanceof Uint8Array ? Buffer.from(k).toString('utf-8') : String(k) + if (typeof keyStr === 'string' && modelWireKeys.has(keyStr)) { + filteredMap.set(k as string | Uint8Array, v) + } + } + decoded = this.transform(filteredMap, modelMeta, ctx) + } else { + decoded = this.transform(filteredData, modelMeta, ctx) + } + } else { + decoded = undefined + } + } + + // If the field is optional and the decoded object is empty, set it to undefined + if (field.optional && decoded !== undefined && this.isEmptyObject(decoded)) { + out[field.name] = undefined + } else { + out[field.name] = decoded + } + } + } } } - // If there are flattened fields, attempt to reconstruct them from remaining keys by decoding - for (const field of fields) { - if (out[field.name] !== undefined) continue - if (field.flattened && field.type.kind === 'codec' && field.type.codecKey === 'SignedTransaction') { - // Reconstruct from entire object map - out[field.name] = this.applyCodec(src, 'SignedTransaction', ctx) + // Add any remaining unmatched entries if there are no flattened fields + if (!hasFlattenedField) { + for (const [k, v] of unmatchedEntries.entries()) { + out[k] = v } } return out } - private static transformType(value: unknown, type: FieldType, ctx: TransformContext): unknown { + private static collectWireKeys(meta: ModelMetadata): Set { + const wireKeys = new Set() + if (meta.kind !== 'object' || !meta.fields) return wireKeys + + for (const field of meta.fields) { + if (field.wireKey) { + wireKeys.add(field.wireKey) + } + if (field.flattened && field.type.kind === 'model') { + const childMeta = typeof field.type.meta === 'function' ? field.type.meta() : field.type.meta + const childKeys = this.collectWireKeys(childMeta) + for (const key of childKeys) { + wireKeys.add(key) + } + } + // Note: flattened codec fields don't have predictable wire keys, + // so they need to be handled differently during reconstruction + } + + return wireKeys + } + + private static hasNestedFlattenedCodec(meta: ModelMetadata): boolean { + if (meta.kind !== 'object' || !meta.fields) return false + + for (const field of meta.fields) { + if (field.flattened) { + if (field.type.kind === 'codec') { + return true + } + if (field.type.kind === 'model') { + const childMeta = typeof field.type.meta === 'function' ? field.type.meta() : field.type.meta + if (this.hasNestedFlattenedCodec(childMeta)) { + return true + } + } + } + } + + return false + } + + private static isEmptyObject(value: ApiData): boolean { + if (value === null || value === undefined) return true + if (typeof value !== 'object') return false + if (Array.isArray(value)) return false + if (value instanceof Uint8Array) return false + if (value instanceof Map) return value.size === 0 + + // Check if it's a plain object with no own properties (excluding undefined values) + const keys = Object.keys(value) + if (keys.length === 0) return true + + // Check if all properties are undefined + return keys.every((key) => (value as Record)[key] === undefined) + } + + private static transformType(value: ApiData, type: FieldType, ctx: TransformContext): ApiData { if (value === undefined || value === null) return value switch (type.kind) { case 'scalar': return this.transformScalar(value, type, ctx) case 'codec': - return this.applyCodec(value, type.codecKey, ctx) + return this.applyEncodeableTypeConversion(value, type.codecKey, ctx) case 'model': return this.transform(value, typeof type.meta === 'function' ? type.meta() : type.meta, ctx) case 'array': if (!Array.isArray(value)) return value return value.map((item) => this.transformType(item, type.item, ctx)) - case 'record': - if (typeof value !== 'object' || value === null) return value + case 'record': { + if ((!(value instanceof Map) && typeof value !== 'object') || value === null) return value + const entries = value instanceof Map ? Array.from(value.entries()) : Object.entries(value as Record) return Object.fromEntries( - Object.entries(value as Record).map(([k, v]) => [k, this.transformType(v, type.value, ctx)]), + entries.map(([k, v]) => { + const key = k instanceof Uint8Array ? Buffer.from(k).toString('utf-8') : k + return [key, this.transformType(v, type.value, ctx)] + }), ) + } + case 'map': + return this.transformMap(value, type, ctx) default: return value } } - private static transformScalar(value: unknown, meta: ScalarFieldType, ctx: TransformContext): unknown { + private static transformScalar(value: ApiData, meta: ScalarFieldType, ctx: TransformContext): ApiData { if (ctx.direction === 'encode') { if (meta.isBytes && ctx.format === 'json') { - if (value instanceof Uint8Array) return toBase64(value) + if (value instanceof Uint8Array) return Buffer.from(value).toString('base64') } if (meta.isBigint && ctx.format === 'json') { if (typeof value === 'bigint') return value.toString() @@ -237,7 +375,17 @@ export class AlgorandSerializer { } if (meta.isBytes && ctx.format === 'json' && typeof value === 'string') { - return fromBase64(value) + return new Uint8Array(Buffer.from(value, 'base64')) + } + + if (value instanceof Uint8Array) { + if (meta.isAddress) { + // TODO: NC - Fix all the address models to have this on it. + return addressFromPublicKey(value) + } else if (!meta.isBytes) { + return Buffer.from(value).toString('utf-8') + } + return value } if (meta.isBigint) { @@ -253,18 +401,61 @@ export class AlgorandSerializer { } } + if (value instanceof Map) { + const out: Record = {} + for (const [k, v] of value.entries()) { + const key = k instanceof Uint8Array ? Buffer.from(k).toString('utf-8') : k.toString() + out[key] = this.transformType(v, { kind: 'scalar', isBytes: v instanceof Uint8Array }, ctx) + } + return out + } + + if (Array.isArray(value)) { + return value.map((item) => this.transformType(item, { kind: 'scalar', isBytes: item instanceof Uint8Array }, ctx)) + } + return value } - private static applyCodec(value: unknown, codecKey: string, ctx: TransformContext): unknown { - const codec = codecRegistry.get(codecKey) + private static applyEncodeableTypeConversion(value: ApiData, typeKey: string, ctx: TransformContext): ApiData { + const codec = encodeableTypeConverterRegistry.get(typeKey) if (!codec) { - throw new Error(`Codec for "${codecKey}" is not registered`) + throw new Error(`Type converter for "${typeKey}" is not registered`) + } + + // TODO: NC - Need to properly guard against these conditions + if (ctx.direction === 'encode') { + if (value instanceof Map) { + throw new Error(`Cannot encode Map with type converter "${typeKey}"`) + } + return codec.beforeEncoding(value as Parameters[0], ctx.format) } - return ctx.direction === 'encode' ? codec.encode(value, ctx.format) : codec.decode(value, ctx.format) + + return codec.afterDecoding(value as Parameters[0], ctx.format) } - private static convertToNestedMaps(value: unknown): Map | unknown[] | unknown { + private static transformMap(value: ApiData, meta: MapFieldType, ctx: TransformContext): ApiData { + if (ctx.direction === 'encode') { + if (!(value instanceof Map)) return value + const result = new Map() + for (const [k, v] of value.entries()) { + const transformedValue = this.transformType(v, meta.value, ctx) + result.set(k, transformedValue) + } + return result + } + // Decoding + if ((!(value instanceof Map) && typeof value !== 'object') || value === null) return value + const entries = value instanceof Map ? Array.from(value.entries()) : Object.entries(value as Record) + const result = new Map() + for (const [k, v] of entries) { + const transformedValue = this.transformType(v, meta.value, ctx) + result.set(k, transformedValue) + } + return result + } + + private static convertToNestedMaps(value: ApiData): Map | ApiData[] | ApiData { if (value === null || value === undefined) { return value } @@ -282,8 +473,8 @@ export class AlgorandSerializer { } if (typeof value === 'object' && value !== null && !(value instanceof Uint8Array)) { - const map = new Map() - Object.entries(value as Record).forEach(([key, val]) => { + const map = new Map() + Object.entries(value as Record).forEach(([key, val]) => { map.set(key, this.convertToNestedMaps(val)) }) return map @@ -301,39 +492,23 @@ interface TransformContext { readonly format: BodyFormat } -const encodeSignedTransactionImpl = (value: unknown): Uint8Array => transactEncodeSignedTransaction(value as SignedTransaction) -const decodeSignedTransactionImpl = (value: Uint8Array): SignedTransaction => transactDecodeSignedTransaction(value) - -class SignedTransactionCodec implements TypeCodec { - encode(value: unknown, format: BodyFormat): unknown { - if (value == null) return value +class SignedTransactionConverter implements EncodeableTypeConverter { + beforeEncoding(value: SignedTransaction, format: BodyFormat): Record { if (format === 'json') { - if (value instanceof Uint8Array) return toBase64(value) - return toBase64(encodeSignedTransactionImpl(value)) - } - if (value instanceof Uint8Array) { - // Already canonical bytes; decode to structured map so parent encoding keeps map semantics - return decodeMsgPack(value) + throw new Error('JSON format not supported for SignedTransaction encoding') } - // Convert signed transaction object into canonical map representation - return decodeMsgPack(encodeSignedTransactionImpl(value)) + return toSignedTransactionDto(value) } - - decode(value: unknown, format: BodyFormat): unknown { - if (value == null) return value - if (format === 'json') { - if (typeof value === 'string') return decodeSignedTransactionImpl(fromBase64(value)) - if (value instanceof Uint8Array) return decodeSignedTransactionImpl(value) - return value + afterDecoding(value: Record | Map, format: BodyFormat): SignedTransaction { + if (format === 'json' || !(value instanceof Map)) { + throw new Error('JSON format not supported for SignedTransaction decoding') } - if (value instanceof Uint8Array) return decodeSignedTransactionImpl(value) - // Value is a decoded map; re-encode to bytes before handing to transact decoder - try { - return decodeSignedTransactionImpl(encodeMsgPack(value)) - } catch { - return value + if (!(value instanceof Map)) { + throw new Error('Invalid decoded msgpack format for SignedTransaction') } + const stxnDto = decodedTransactionMapToObject(value) as Parameters[0] + return fromSignedTransactionDto(stxnDto) } } -registerCodec('SignedTransaction', new SignedTransactionCodec()) +registerEncodeableTypeConverter('SignedTransaction', new SignedTransactionConverter()) diff --git a/packages/algod_client/src/core/request.ts b/packages/algod_client/src/core/request.ts index 112098a7..2f8f4d27 100644 --- a/packages/algod_client/src/core/request.ts +++ b/packages/algod_client/src/core/request.ts @@ -85,7 +85,11 @@ export async function request( try { const ct = response.headers.get('content-type') ?? '' if (ct.includes('application/msgpack')) { - errorBody = decodeMsgPack(new Uint8Array(await response.arrayBuffer())) + errorBody = decodeMsgPack(new Uint8Array(await response.arrayBuffer()), { + useMap: false, + rawBinaryStringKeys: false, + rawBinaryStringValues: false, + }) } else if (ct.includes('application/json')) { errorBody = JSON.parse(await response.text()) } else { diff --git a/packages/algod_client/src/core/serialization.ts b/packages/algod_client/src/core/serialization.ts deleted file mode 100644 index 6be05428..00000000 --- a/packages/algod_client/src/core/serialization.ts +++ /dev/null @@ -1,26 +0,0 @@ -export function toBase64(bytes: Uint8Array): string { - if (typeof Buffer !== 'undefined') { - return Buffer.from(bytes).toString('base64') - } - const globalRef: Record = globalThis as unknown as Record - const btoaFn = globalRef.btoa as ((value: string) => string) | undefined - if (typeof btoaFn === 'function') { - return btoaFn(String.fromCharCode(...bytes)) - } - throw new Error('Base64 encoding not supported in this environment') -} - -export function fromBase64(s: string): Uint8Array { - if (typeof Buffer !== 'undefined') { - return new Uint8Array(Buffer.from(s, 'base64')) - } - const globalRef: Record = globalThis as unknown as Record - const atobFn = globalRef.atob as ((value: string) => string) | undefined - if (typeof atobFn === 'function') { - const bin = atobFn(s) - const out = new Uint8Array(bin.length) - for (let i = 0; i < bin.length; i += 1) out[i] = bin.charCodeAt(i) - return out - } - throw new Error('Base64 decoding not supported in this environment') -} diff --git a/packages/algod_client/src/index.ts b/packages/algod_client/src/index.ts index 915506d5..58a6412d 100644 --- a/packages/algod_client/src/index.ts +++ b/packages/algod_client/src/index.ts @@ -2,7 +2,6 @@ export * from './core/client-config' export * from './core/base-http-request' export * from './core/fetch-http-request' export * from './core/api-error' -export * from './core/serialization' export * from './core/codecs' export * from './core/model-runtime' diff --git a/packages/algod_client/src/models/account-application-information.ts b/packages/algod_client/src/models/account-application-information.ts index 0042fb89..d2adbf6e 100644 --- a/packages/algod_client/src/models/account-application-information.ts +++ b/packages/algod_client/src/models/account-application-information.ts @@ -29,14 +29,14 @@ export const AccountApplicationInformationMeta: ModelMetadata = { wireKey: 'app-local-state', optional: true, nullable: false, - type: { kind: 'model', meta: () => ApplicationLocalStateMeta }, + type: { kind: 'model', meta: ApplicationLocalStateMeta }, }, { name: 'createdApp', wireKey: 'created-app', optional: true, nullable: false, - type: { kind: 'model', meta: () => ApplicationParamsMeta }, + type: { kind: 'model', meta: ApplicationParamsMeta }, }, ], } diff --git a/packages/algod_client/src/models/account-asset-information.ts b/packages/algod_client/src/models/account-asset-information.ts index 32b3e36b..09122a02 100644 --- a/packages/algod_client/src/models/account-asset-information.ts +++ b/packages/algod_client/src/models/account-asset-information.ts @@ -29,14 +29,14 @@ export const AccountAssetInformationMeta: ModelMetadata = { wireKey: 'asset-holding', optional: true, nullable: false, - type: { kind: 'model', meta: () => AssetHoldingMeta }, + type: { kind: 'model', meta: AssetHoldingMeta }, }, { name: 'createdAsset', wireKey: 'created-asset', optional: true, nullable: false, - type: { kind: 'model', meta: () => AssetParamsMeta }, + type: { kind: 'model', meta: AssetParamsMeta }, }, ], } diff --git a/packages/algod_client/src/models/account-state-delta.ts b/packages/algod_client/src/models/account-state-delta.ts index ad93395f..d7ddbc6a 100644 --- a/packages/algod_client/src/models/account-state-delta.ts +++ b/packages/algod_client/src/models/account-state-delta.ts @@ -26,7 +26,7 @@ export const AccountStateDeltaMeta: ModelMetadata = { wireKey: 'delta', optional: false, nullable: false, - type: { kind: 'model', meta: () => StateDeltaMeta }, + type: { kind: 'model', meta: StateDeltaMeta }, }, ], } diff --git a/packages/algod_client/src/models/account.ts b/packages/algod_client/src/models/account.ts index 7824c468..c5a45366 100644 --- a/packages/algod_client/src/models/account.ts +++ b/packages/algod_client/src/models/account.ts @@ -200,7 +200,7 @@ export const AccountMeta: ModelMetadata = { wireKey: 'apps-local-state', optional: true, nullable: false, - type: { kind: 'array', item: { kind: 'model', meta: () => ApplicationLocalStateMeta } }, + type: { kind: 'array', item: { kind: 'model', meta: ApplicationLocalStateMeta } }, }, { name: 'totalAppsOptedIn', @@ -214,7 +214,7 @@ export const AccountMeta: ModelMetadata = { wireKey: 'apps-total-schema', optional: true, nullable: false, - type: { kind: 'model', meta: () => ApplicationStateSchemaMeta }, + type: { kind: 'model', meta: ApplicationStateSchemaMeta }, }, { name: 'appsTotalExtraPages', @@ -228,7 +228,7 @@ export const AccountMeta: ModelMetadata = { wireKey: 'assets', optional: true, nullable: false, - type: { kind: 'array', item: { kind: 'model', meta: () => AssetHoldingMeta } }, + type: { kind: 'array', item: { kind: 'model', meta: AssetHoldingMeta } }, }, { name: 'totalAssetsOptedIn', @@ -242,7 +242,7 @@ export const AccountMeta: ModelMetadata = { wireKey: 'created-apps', optional: true, nullable: false, - type: { kind: 'array', item: { kind: 'model', meta: () => ApplicationMeta } }, + type: { kind: 'array', item: { kind: 'model', meta: ApplicationMeta } }, }, { name: 'totalCreatedApps', @@ -256,7 +256,7 @@ export const AccountMeta: ModelMetadata = { wireKey: 'created-assets', optional: true, nullable: false, - type: { kind: 'array', item: { kind: 'model', meta: () => AssetMeta } }, + type: { kind: 'array', item: { kind: 'model', meta: AssetMeta } }, }, { name: 'totalCreatedAssets', @@ -284,7 +284,7 @@ export const AccountMeta: ModelMetadata = { wireKey: 'participation', optional: true, nullable: false, - type: { kind: 'model', meta: () => AccountParticipationMeta }, + type: { kind: 'model', meta: AccountParticipationMeta }, }, { name: 'incentiveEligible', diff --git a/packages/algod_client/src/models/application-initial-states.ts b/packages/algod_client/src/models/application-initial-states.ts index 22acf411..e9592880 100644 --- a/packages/algod_client/src/models/application-initial-states.ts +++ b/packages/algod_client/src/models/application-initial-states.ts @@ -35,21 +35,21 @@ export const ApplicationInitialStatesMeta: ModelMetadata = { wireKey: 'app-locals', optional: true, nullable: false, - type: { kind: 'array', item: { kind: 'model', meta: () => ApplicationKvStorageMeta } }, + type: { kind: 'array', item: { kind: 'model', meta: ApplicationKvStorageMeta } }, }, { name: 'appGlobals', wireKey: 'app-globals', optional: true, nullable: false, - type: { kind: 'model', meta: () => ApplicationKvStorageMeta }, + type: { kind: 'model', meta: ApplicationKvStorageMeta }, }, { name: 'appBoxes', wireKey: 'app-boxes', optional: true, nullable: false, - type: { kind: 'model', meta: () => ApplicationKvStorageMeta }, + type: { kind: 'model', meta: ApplicationKvStorageMeta }, }, ], } diff --git a/packages/algod_client/src/models/application-kv-storage.ts b/packages/algod_client/src/models/application-kv-storage.ts index c69a95d5..2221bbaf 100644 --- a/packages/algod_client/src/models/application-kv-storage.ts +++ b/packages/algod_client/src/models/application-kv-storage.ts @@ -26,7 +26,7 @@ export const ApplicationKvStorageMeta: ModelMetadata = { wireKey: 'kvs', optional: false, nullable: false, - type: { kind: 'array', item: { kind: 'model', meta: () => AvmKeyValueMeta } }, + type: { kind: 'array', item: { kind: 'model', meta: AvmKeyValueMeta } }, }, { name: 'account', diff --git a/packages/algod_client/src/models/application-local-state.ts b/packages/algod_client/src/models/application-local-state.ts index a04e8a24..9f89eee8 100644 --- a/packages/algod_client/src/models/application-local-state.ts +++ b/packages/algod_client/src/models/application-local-state.ts @@ -32,14 +32,14 @@ export const ApplicationLocalStateMeta: ModelMetadata = { wireKey: 'schema', optional: false, nullable: false, - type: { kind: 'model', meta: () => ApplicationStateSchemaMeta }, + type: { kind: 'model', meta: ApplicationStateSchemaMeta }, }, { name: 'keyValue', wireKey: 'key-value', optional: true, nullable: false, - type: { kind: 'model', meta: () => TealKeyValueStoreMeta }, + type: { kind: 'model', meta: TealKeyValueStoreMeta }, }, ], } diff --git a/packages/algod_client/src/models/application-params.ts b/packages/algod_client/src/models/application-params.ts index 2a045128..8b51a6bf 100644 --- a/packages/algod_client/src/models/application-params.ts +++ b/packages/algod_client/src/models/application-params.ts @@ -74,21 +74,21 @@ export const ApplicationParamsMeta: ModelMetadata = { wireKey: 'local-state-schema', optional: true, nullable: false, - type: { kind: 'model', meta: () => ApplicationStateSchemaMeta }, + type: { kind: 'model', meta: ApplicationStateSchemaMeta }, }, { name: 'globalStateSchema', wireKey: 'global-state-schema', optional: true, nullable: false, - type: { kind: 'model', meta: () => ApplicationStateSchemaMeta }, + type: { kind: 'model', meta: ApplicationStateSchemaMeta }, }, { name: 'globalState', wireKey: 'global-state', optional: true, nullable: false, - type: { kind: 'model', meta: () => TealKeyValueStoreMeta }, + type: { kind: 'model', meta: TealKeyValueStoreMeta }, }, { name: 'version', diff --git a/packages/algod_client/src/models/application-state-operation.ts b/packages/algod_client/src/models/application-state-operation.ts index 00020f19..993fbc44 100644 --- a/packages/algod_client/src/models/application-state-operation.ts +++ b/packages/algod_client/src/models/application-state-operation.ts @@ -58,7 +58,7 @@ export const ApplicationStateOperationMeta: ModelMetadata = { wireKey: 'new-value', optional: true, nullable: false, - type: { kind: 'model', meta: () => AvmValueMeta }, + type: { kind: 'model', meta: AvmValueMeta }, }, { name: 'account', diff --git a/packages/algod_client/src/models/application.ts b/packages/algod_client/src/models/application.ts index c2ae6148..88e19da5 100644 --- a/packages/algod_client/src/models/application.ts +++ b/packages/algod_client/src/models/application.ts @@ -29,7 +29,7 @@ export const ApplicationMeta: ModelMetadata = { wireKey: 'params', optional: false, nullable: false, - type: { kind: 'model', meta: () => ApplicationParamsMeta }, + type: { kind: 'model', meta: ApplicationParamsMeta }, }, ], } diff --git a/packages/algod_client/src/models/asset.ts b/packages/algod_client/src/models/asset.ts index 3c12777b..904c9b99 100644 --- a/packages/algod_client/src/models/asset.ts +++ b/packages/algod_client/src/models/asset.ts @@ -9,7 +9,7 @@ export type Asset = { /** * unique asset identifier */ - index: bigint + id: bigint params: AssetParams } @@ -18,7 +18,7 @@ export const AssetMeta: ModelMetadata = { kind: 'object', fields: [ { - name: 'index', + name: 'id', wireKey: 'index', optional: false, nullable: false, @@ -29,7 +29,7 @@ export const AssetMeta: ModelMetadata = { wireKey: 'params', optional: false, nullable: false, - type: { kind: 'model', meta: () => AssetParamsMeta }, + type: { kind: 'model', meta: AssetParamsMeta }, }, ], } diff --git a/packages/algod_client/src/models/avm-key-value.ts b/packages/algod_client/src/models/avm-key-value.ts index 5d0ef4ce..8385c46a 100644 --- a/packages/algod_client/src/models/avm-key-value.ts +++ b/packages/algod_client/src/models/avm-key-value.ts @@ -26,7 +26,7 @@ export const AvmKeyValueMeta: ModelMetadata = { wireKey: 'value', optional: false, nullable: false, - type: { kind: 'model', meta: () => AvmValueMeta }, + type: { kind: 'model', meta: AvmValueMeta }, }, ], } diff --git a/packages/algod_client/src/models/block-account-state-delta.ts b/packages/algod_client/src/models/block-account-state-delta.ts deleted file mode 100644 index f40192ca..00000000 --- a/packages/algod_client/src/models/block-account-state-delta.ts +++ /dev/null @@ -1,20 +0,0 @@ -import type { ModelMetadata } from '../core/model-runtime' -import { registerModelMeta } from '../core/model-runtime' -import { BlockStateDeltaMeta } from './block-state-delta' - -/** BlockAccountStateDelta pairs an address with a BlockStateDelta map. */ -export interface BlockAccountStateDelta { - address: string - delta: import('./block-state-delta').BlockStateDelta -} - -export const BlockAccountStateDeltaMeta: ModelMetadata = { - name: 'BlockAccountStateDelta', - kind: 'object', - fields: [ - { name: 'address', wireKey: 'address', optional: false, nullable: false, type: { kind: 'scalar' } }, - { name: 'delta', wireKey: 'delta', optional: false, nullable: false, type: { kind: 'model', meta: () => BlockStateDeltaMeta } }, - ], -} - -registerModelMeta('BlockAccountStateDelta', BlockAccountStateDeltaMeta) diff --git a/packages/algod_client/src/models/block-app-eval-delta.ts b/packages/algod_client/src/models/block-app-eval-delta.ts deleted file mode 100644 index bc3b0d80..00000000 --- a/packages/algod_client/src/models/block-app-eval-delta.ts +++ /dev/null @@ -1,53 +0,0 @@ -import type { ModelMetadata } from '../core/model-runtime' -import { getModelMeta, registerModelMeta } from '../core/model-runtime' -import type { SignedTxnInBlock } from './signed-txn-in-block' -import type { BlockStateDelta } from './block-state-delta' -import { BlockStateDeltaMeta } from './block-state-delta' - -/** - * State changes from application execution, including inner transactions and logs. - */ -export interface BlockAppEvalDelta { - /** [gd] Global state delta for the application. */ - globalDelta?: BlockStateDelta - /** [ld] Local state deltas keyed by address index. */ - localDeltas?: Record - /** [itx] Inner transactions produced by this application execution. */ - innerTxns?: SignedTxnInBlock[] - /** [sa] Shared accounts referenced by local deltas. */ - sharedAccounts?: Uint8Array[] - /** [lg] Application log outputs. */ - logs?: Uint8Array[] -} - -export const BlockAppEvalDeltaMeta: ModelMetadata = { - name: 'BlockAppEvalDelta', - kind: 'object', - fields: [ - { name: 'globalDelta', wireKey: 'gd', optional: true, nullable: false, type: { kind: 'model', meta: () => BlockStateDeltaMeta } }, - { - name: 'localDeltas', - wireKey: 'ld', - optional: true, - nullable: false, - type: { kind: 'record', value: { kind: 'model', meta: () => BlockStateDeltaMeta } }, - }, - { - name: 'innerTxns', - wireKey: 'itx', - optional: true, - nullable: false, - type: { kind: 'array', item: { kind: 'model', meta: () => getModelMeta('SignedTxnInBlock') } }, - }, - { - name: 'sharedAccounts', - wireKey: 'sa', - optional: true, - nullable: false, - type: { kind: 'array', item: { kind: 'scalar', isBytes: true } }, - }, - { name: 'logs', wireKey: 'lg', optional: true, nullable: false, type: { kind: 'array', item: { kind: 'scalar', isBytes: true } } }, - ], -} - -registerModelMeta('BlockAppEvalDelta', BlockAppEvalDeltaMeta) diff --git a/packages/algod_client/src/models/block-eval-delta.ts b/packages/algod_client/src/models/block-eval-delta.ts deleted file mode 100644 index faab05dd..00000000 --- a/packages/algod_client/src/models/block-eval-delta.ts +++ /dev/null @@ -1,24 +0,0 @@ -import type { ModelMetadata } from '../core/model-runtime' -import { registerModelMeta } from '../core/model-runtime' - -/** BlockEvalDelta represents a TEAL value delta (block/msgpack wire keys). */ -export interface BlockEvalDelta { - /** [at] delta action. */ - action: number - /** [bs] bytes value. */ - bytes?: string - /** [ui] uint value. */ - uint?: bigint -} - -export const BlockEvalDeltaMeta: ModelMetadata = { - name: 'BlockEvalDelta', - kind: 'object', - fields: [ - { name: 'action', wireKey: 'at', optional: false, nullable: false, type: { kind: 'scalar' } }, - { name: 'bytes', wireKey: 'bs', optional: true, nullable: false, type: { kind: 'scalar' } }, - { name: 'uint', wireKey: 'ui', optional: true, nullable: false, type: { kind: 'scalar', isBigint: true } }, - ], -} - -registerModelMeta('BlockEvalDelta', BlockEvalDeltaMeta) diff --git a/packages/algod_client/src/models/block-state-delta.ts b/packages/algod_client/src/models/block-state-delta.ts deleted file mode 100644 index b3ebdc36..00000000 --- a/packages/algod_client/src/models/block-state-delta.ts +++ /dev/null @@ -1,14 +0,0 @@ -import type { ModelMetadata } from '../core/model-runtime' -import { registerModelMeta } from '../core/model-runtime' -import { BlockEvalDeltaMeta } from './block-eval-delta' - -/** BlockStateDelta is a map keyed by state key to BlockEvalDelta. */ -export type BlockStateDelta = Record - -export const BlockStateDeltaMeta: ModelMetadata = { - name: 'BlockStateDelta', - kind: 'object', - additionalProperties: { kind: 'model', meta: () => BlockEvalDeltaMeta }, -} - -registerModelMeta('BlockStateDelta', BlockStateDeltaMeta) diff --git a/packages/algod_client/src/models/block.ts b/packages/algod_client/src/models/block.ts index e64b8a25..1e59f5e9 100644 --- a/packages/algod_client/src/models/block.ts +++ b/packages/algod_client/src/models/block.ts @@ -1,13 +1,213 @@ +import { SignedTransaction } from '@algorandfoundation/algokit-transact' import type { ModelMetadata } from '../core/model-runtime' -import type { SignedTxnInBlock } from './signed-txn-in-block' -import { SignedTxnInBlockMeta } from './signed-txn-in-block' -import type { BlockStateProofTracking } from './block_state_proof_tracking' -import { BlockStateProofTrackingMeta } from './block_state_proof_tracking' + +/** BlockEvalDelta represents a TEAL value delta (block/msgpack wire keys). */ +export type BlockEvalDelta = { + /** [at] delta action. */ + action: number + /** [bs] bytes value. */ + bytes?: string + /** [ui] uint value. */ + uint?: bigint +} + +export const BlockEvalDeltaMeta: ModelMetadata = { + name: 'BlockEvalDelta', + kind: 'object', + fields: [ + { name: 'action', wireKey: 'at', optional: false, nullable: false, type: { kind: 'scalar' } }, + { name: 'bytes', wireKey: 'bs', optional: true, nullable: false, type: { kind: 'scalar' } }, + { name: 'uint', wireKey: 'ui', optional: true, nullable: false, type: { kind: 'scalar', isBigint: true } }, + ], +} /** - * Block contains the BlockHeader and the list of transactions (Payset). + * State changes from application execution, including inner transactions and logs. + */ +export type BlockAppEvalDelta = { + /** [gd] Global state delta for the application. */ + globalDelta?: Map + /** [ld] Local state deltas keyed by address index. */ + localDeltas?: Map> + /** [itx] Inner transactions produced by this application execution. */ + innerTxns?: SignedTxnInBlock[] + /** [sa] Shared accounts referenced by local deltas. */ + sharedAccounts?: string[] + /** [lg] Application log outputs. */ + logs?: Uint8Array[] +} + +export const BlockAppEvalDeltaMeta: ModelMetadata = { + name: 'BlockAppEvalDelta', + kind: 'object', + fields: [ + { + name: 'globalDelta', + wireKey: 'gd', + optional: true, + nullable: false, + type: { kind: 'map', keyType: 'bytes', value: { kind: 'model', meta: BlockEvalDeltaMeta } }, + }, + { + name: 'localDeltas', + wireKey: 'ld', + optional: true, + nullable: false, + type: { + kind: 'map', + keyType: 'number', + value: { kind: 'map', keyType: 'bytes', value: { kind: 'model', meta: BlockEvalDeltaMeta } }, + }, + }, + { + name: 'innerTxns', + wireKey: 'itx', + optional: true, + nullable: false, + type: { kind: 'array', item: { kind: 'model', meta: () => SignedTxnInBlockMeta } }, + }, + { + name: 'sharedAccounts', + wireKey: 'sa', + optional: true, + nullable: false, + type: { kind: 'array', item: { kind: 'scalar', isAddress: true } }, + }, + { name: 'logs', wireKey: 'lg', optional: true, nullable: false, type: { kind: 'array', item: { kind: 'scalar', isBytes: true } } }, + ], +} + +/** Tracking metadata for a specific StateProofType. */ +export type BlockStateProofTrackingData = { + /** [v] Vector commitment root of state proof voters. */ + stateProofVotersCommitment?: Uint8Array + /** [t] Online total weight during state proof round. */ + stateProofOnlineTotalWeight?: bigint + /** [n] Next round for which state proofs are accepted. */ + stateProofNextRound?: bigint +} + +export const BlockStateProofTrackingDataMeta: ModelMetadata = { + name: 'BlockStateProofTrackingData', + kind: 'object', + fields: [ + { name: 'stateProofVotersCommitment', wireKey: 'v', optional: true, nullable: false, type: { kind: 'scalar', isBytes: true } }, + { name: 'stateProofOnlineTotalWeight', wireKey: 't', optional: true, nullable: false, type: { kind: 'scalar', isBigint: true } }, + { name: 'stateProofNextRound', wireKey: 'n', optional: true, nullable: false, type: { kind: 'scalar', isBigint: true } }, + ], +} + +export type ApplyData = { + closingAmount?: bigint + assetClosingAmount?: bigint + senderRewards?: bigint + receiverRewards?: bigint + closeRewards?: bigint + evalDelta?: BlockAppEvalDelta + configAsset?: bigint + applicationId?: bigint +} + +export const ApplyDataMeta: ModelMetadata = { + name: 'SignedTxnInBlock', + kind: 'object', + fields: [ + { name: 'closingAmount', wireKey: 'ca', optional: true, nullable: false, type: { kind: 'scalar', isBigint: true } }, + { name: 'assetClosingAmount', wireKey: 'aca', optional: true, nullable: false, type: { kind: 'scalar', isBigint: true } }, + { name: 'senderRewards', wireKey: 'rs', optional: true, nullable: false, type: { kind: 'scalar', isBigint: true } }, + { name: 'receiverRewards', wireKey: 'rr', optional: true, nullable: false, type: { kind: 'scalar', isBigint: true } }, + { name: 'closeRewards', wireKey: 'rc', optional: true, nullable: false, type: { kind: 'scalar', isBigint: true } }, + { name: 'evalDelta', wireKey: 'dt', optional: true, nullable: false, type: { kind: 'model', meta: BlockAppEvalDeltaMeta } }, + { name: 'configAsset', wireKey: 'caid', optional: true, nullable: false, type: { kind: 'scalar', isBigint: true } }, + { name: 'applicationId', wireKey: 'apid', optional: true, nullable: false, type: { kind: 'scalar', isBigint: true } }, + ], +} + +/** + * SignedTxnWithAD is a SignedTransaction with additional ApplyData. */ -export interface Block { +export type SignedTxnWithAD = { + /** The signed transaction. */ + signedTxn: SignedTransaction + /** Apply data containing transaction execution information. */ + applyData: ApplyData +} + +export const SignedTxnWithADMeta: ModelMetadata = { + name: 'SignedTxnWithAD', + kind: 'object', + fields: [ + { + name: 'signedTransaction', + flattened: true, + optional: false, + nullable: false, + type: { kind: 'codec', codecKey: 'SignedTransaction' }, + }, + { + name: 'applyData', + flattened: true, + optional: true, + nullable: false, + type: { kind: 'model', meta: ApplyDataMeta }, + }, + ], +} + +/** + * SignedTxnInBlock is a SignedTransaction with additional ApplyData and block-specific metadata. + */ +export type SignedTxnInBlock = { + signedTransaction: SignedTxnWithAD + hasGenesisId?: boolean + hasGenesisHash?: boolean +} + +export const SignedTxnInBlockMeta: ModelMetadata = { + name: 'SignedTxnInBlock', + kind: 'object', + fields: [ + { + name: 'signedTransaction', + flattened: true, + optional: false, + nullable: false, + type: { kind: 'model', meta: SignedTxnWithADMeta }, + }, + { name: 'hasGenesisId', wireKey: 'hgi', optional: true, nullable: false, type: { kind: 'scalar' } }, + { name: 'hasGenesisHash', wireKey: 'hgh', optional: true, nullable: false, type: { kind: 'scalar' } }, + ], +} + +export type ParticipationUpdates = { + /** [partupdrmv] Expired participation accounts. */ + expiredParticipationAccounts?: string[] + /** [partupdabs] Absent participation accounts. */ + absentParticipationAccounts?: string[] +} + +export const ParticipationUpdatesMeta: ModelMetadata = { + name: 'ParticipationUpdates', + kind: 'object', + fields: [ + { + name: 'expiredParticipationAccounts', + wireKey: 'partupdrmv', + optional: true, + nullable: false, + type: { kind: 'array', item: { kind: 'scalar', isAddress: true } }, + }, + { + name: 'absentParticipationAccounts', + wireKey: 'partupdabs', + optional: true, + nullable: false, + type: { kind: 'array', item: { kind: 'scalar', isAddress: true } }, + }, + ], +} + +export type BlockHeader = { /** [rnd] Round number. */ round?: bigint /** [prev] Previous block hash. */ @@ -29,7 +229,7 @@ export interface Block { /** [gh] Genesis hash. */ genesisHash?: Uint8Array /** [prp] Proposer address. */ - proposer?: Uint8Array + proposer?: string /** [fc] Fees collected in this block. */ feesCollected?: bigint /** [bi] Bonus incentive for block proposal. */ @@ -37,9 +237,9 @@ export interface Block { /** [pp] Proposer payout. */ proposerPayout?: bigint /** [fees] FeeSink address. */ - feeSink?: Uint8Array + feeSink?: string /** [rwd] RewardsPool address. */ - rewardsPool?: Uint8Array + rewardsPool?: string /** [earn] Rewards level. */ rewardsLevel?: bigint /** [rate] Rewards rate. */ @@ -67,35 +267,31 @@ export interface Block { /** [tc] Transaction counter. */ txnCounter?: bigint /** [spt] State proof tracking data keyed by state proof type. */ - stateProofTracking?: BlockStateProofTracking - /** [partupdrmv] Expired participation accounts. */ - expiredParticipationAccounts?: Uint8Array[] - /** [partupdabs] Absent participation accounts. */ - absentParticipationAccounts?: Uint8Array[] - /** [txns] Block transactions (Payset). */ - transactions?: SignedTxnInBlock[] + stateProofTracking?: Map + /** Represents participation account data that needs to be checked/acted on by the network */ + participationUpdates?: ParticipationUpdates } -export const BlockMeta: ModelMetadata = { - name: 'Block', +export const BlockHeaderMeta: ModelMetadata = { + name: 'BlockHeader', kind: 'object', fields: [ { name: 'round', wireKey: 'rnd', optional: true, nullable: false, type: { kind: 'scalar', isBigint: true } }, { name: 'previousBlockHash', wireKey: 'prev', optional: true, nullable: false, type: { kind: 'scalar', isBytes: true } }, { name: 'previousBlockHash512', wireKey: 'prev512', optional: true, nullable: false, type: { kind: 'scalar', isBytes: true } }, { name: 'seed', wireKey: 'seed', optional: true, nullable: false, type: { kind: 'scalar', isBytes: true } }, - { name: 'transactionsRoot', wireKey: 'txn', optional: true, nullable: false, type: { kind: 'scalar', isBytes: true } }, + { name: 'transactionsRoot', wireKey: 'txn', optional: false, nullable: false, type: { kind: 'scalar', isBytes: true } }, { name: 'transactionsRootSha256', wireKey: 'txn256', optional: true, nullable: false, type: { kind: 'scalar', isBytes: true } }, { name: 'transactionsRootSha512', wireKey: 'txn512', optional: true, nullable: false, type: { kind: 'scalar', isBytes: true } }, { name: 'timestamp', wireKey: 'ts', optional: true, nullable: false, type: { kind: 'scalar', isBigint: true } }, { name: 'genesisId', wireKey: 'gen', optional: true, nullable: false, type: { kind: 'scalar' } }, { name: 'genesisHash', wireKey: 'gh', optional: true, nullable: false, type: { kind: 'scalar', isBytes: true } }, - { name: 'proposer', wireKey: 'prp', optional: true, nullable: false, type: { kind: 'scalar', isBytes: true } }, + { name: 'proposer', wireKey: 'prp', optional: true, nullable: false, type: { kind: 'scalar', isAddress: true } }, { name: 'feesCollected', wireKey: 'fc', optional: true, nullable: false, type: { kind: 'scalar', isBigint: true } }, { name: 'bonus', wireKey: 'bi', optional: true, nullable: false, type: { kind: 'scalar', isBigint: true } }, { name: 'proposerPayout', wireKey: 'pp', optional: true, nullable: false, type: { kind: 'scalar', isBigint: true } }, - { name: 'feeSink', wireKey: 'fees', optional: true, nullable: false, type: { kind: 'scalar', isBytes: true } }, - { name: 'rewardsPool', wireKey: 'rwd', optional: true, nullable: false, type: { kind: 'scalar', isBytes: true } }, + { name: 'feeSink', wireKey: 'fees', optional: true, nullable: false, type: { kind: 'scalar', isAddress: true } }, + { name: 'rewardsPool', wireKey: 'rwd', optional: true, nullable: false, type: { kind: 'scalar', isAddress: true } }, { name: 'rewardsLevel', wireKey: 'earn', optional: true, nullable: false, type: { kind: 'scalar', isBigint: true } }, { name: 'rewardsRate', wireKey: 'rate', optional: true, nullable: false, type: { kind: 'scalar', isBigint: true } }, { name: 'rewardsResidue', wireKey: 'frac', optional: true, nullable: false, type: { kind: 'scalar', isBigint: true } }, @@ -114,28 +310,40 @@ export const BlockMeta: ModelMetadata = { wireKey: 'spt', optional: true, nullable: false, - type: { kind: 'model', meta: () => BlockStateProofTrackingMeta }, + type: { kind: 'map', keyType: 'number', value: { kind: 'model', meta: BlockStateProofTrackingDataMeta } }, }, { - name: 'expiredParticipationAccounts', - wireKey: 'partupdrmv', - optional: true, - nullable: false, - type: { kind: 'array', item: { kind: 'scalar', isBytes: true } }, - }, - { - name: 'absentParticipationAccounts', - wireKey: 'partupdabs', + name: 'participationUpdates', + flattened: true, optional: true, nullable: false, - type: { kind: 'array', item: { kind: 'scalar', isBytes: true } }, + type: { kind: 'model', meta: ParticipationUpdatesMeta }, }, + ], +} + +/** + * Block contains the BlockHeader and the list of transactions (Payset). + */ +export type Block = { + /** The block information (Header) */ + header: BlockHeader + + /** [txns] Block transactions (Payset). */ + payset?: SignedTxnInBlock[] +} + +export const BlockMeta: ModelMetadata = { + name: 'Block', + kind: 'object', + fields: [ + { name: 'header', flattened: true, optional: false, nullable: false, type: { kind: 'model', meta: BlockHeaderMeta } }, { - name: 'transactions', + name: 'payset', wireKey: 'txns', optional: true, nullable: false, - type: { kind: 'array', item: { kind: 'model', meta: () => SignedTxnInBlockMeta } }, + type: { kind: 'array', item: { kind: 'model', meta: SignedTxnInBlockMeta } }, }, ], } diff --git a/packages/algod_client/src/models/block_state_proof_tracking.ts b/packages/algod_client/src/models/block_state_proof_tracking.ts deleted file mode 100644 index 8fba4555..00000000 --- a/packages/algod_client/src/models/block_state_proof_tracking.ts +++ /dev/null @@ -1,15 +0,0 @@ -import type { ModelMetadata } from '../core/model-runtime' -import { registerModelMeta } from '../core/model-runtime' -import type { BlockStateProofTrackingData } from './block_state_proof_tracking_data' -import { BlockStateProofTrackingDataMeta } from './block_state_proof_tracking_data' - -/** Tracks state proof metadata by state proof type. */ -export type BlockStateProofTracking = Record - -export const BlockStateProofTrackingMeta: ModelMetadata = { - name: 'BlockStateProofTracking', - kind: 'object', - additionalProperties: { kind: 'model', meta: () => BlockStateProofTrackingDataMeta }, -} - -registerModelMeta('BlockStateProofTracking', BlockStateProofTrackingMeta) diff --git a/packages/algod_client/src/models/block_state_proof_tracking_data.ts b/packages/algod_client/src/models/block_state_proof_tracking_data.ts deleted file mode 100644 index db995ca2..00000000 --- a/packages/algod_client/src/models/block_state_proof_tracking_data.ts +++ /dev/null @@ -1,24 +0,0 @@ -import type { ModelMetadata } from '../core/model-runtime' -import { registerModelMeta } from '../core/model-runtime' - -/** Tracking metadata for a specific StateProofType. */ -export interface BlockStateProofTrackingData { - /** [v] Vector commitment root of state proof voters. */ - stateProofVotersCommitment?: Uint8Array - /** [t] Online total weight during state proof round. */ - stateProofOnlineTotalWeight?: bigint - /** [n] Next round for which state proofs are accepted. */ - stateProofNextRound?: bigint -} - -export const BlockStateProofTrackingDataMeta: ModelMetadata = { - name: 'BlockStateProofTrackingData', - kind: 'object', - fields: [ - { name: 'stateProofVotersCommitment', wireKey: 'v', optional: true, nullable: false, type: { kind: 'scalar', isBytes: true } }, - { name: 'stateProofOnlineTotalWeight', wireKey: 't', optional: true, nullable: false, type: { kind: 'scalar', isBigint: true } }, - { name: 'stateProofNextRound', wireKey: 'n', optional: true, nullable: false, type: { kind: 'scalar', isBigint: true } }, - ], -} - -registerModelMeta('BlockStateProofTrackingData', BlockStateProofTrackingDataMeta) diff --git a/packages/algod_client/src/models/dryrun-request.ts b/packages/algod_client/src/models/dryrun-request.ts index 4786220b..3a23c69c 100644 --- a/packages/algod_client/src/models/dryrun-request.ts +++ b/packages/algod_client/src/models/dryrun-request.ts @@ -48,14 +48,14 @@ export const DryrunRequestMeta: ModelMetadata = { wireKey: 'accounts', optional: false, nullable: false, - type: { kind: 'array', item: { kind: 'model', meta: () => AccountMeta } }, + type: { kind: 'array', item: { kind: 'model', meta: AccountMeta } }, }, { name: 'apps', wireKey: 'apps', optional: false, nullable: false, - type: { kind: 'array', item: { kind: 'model', meta: () => ApplicationMeta } }, + type: { kind: 'array', item: { kind: 'model', meta: ApplicationMeta } }, }, { name: 'protocolVersion', @@ -83,7 +83,7 @@ export const DryrunRequestMeta: ModelMetadata = { wireKey: 'sources', optional: false, nullable: false, - type: { kind: 'array', item: { kind: 'model', meta: () => DryrunSourceMeta } }, + type: { kind: 'array', item: { kind: 'model', meta: DryrunSourceMeta } }, }, ], } diff --git a/packages/algod_client/src/models/dryrun-state.ts b/packages/algod_client/src/models/dryrun-state.ts index 4f10c829..68d9a8d0 100644 --- a/packages/algod_client/src/models/dryrun-state.ts +++ b/packages/algod_client/src/models/dryrun-state.ts @@ -47,14 +47,14 @@ export const DryrunStateMeta: ModelMetadata = { wireKey: 'stack', optional: false, nullable: false, - type: { kind: 'array', item: { kind: 'model', meta: () => TealValueMeta } }, + type: { kind: 'array', item: { kind: 'model', meta: TealValueMeta } }, }, { name: 'scratch', wireKey: 'scratch', optional: true, nullable: false, - type: { kind: 'array', item: { kind: 'model', meta: () => TealValueMeta } }, + type: { kind: 'array', item: { kind: 'model', meta: TealValueMeta } }, }, { name: 'error', diff --git a/packages/algod_client/src/models/dryrun-txn-result.ts b/packages/algod_client/src/models/dryrun-txn-result.ts index c87cdf57..cc0b867c 100644 --- a/packages/algod_client/src/models/dryrun-txn-result.ts +++ b/packages/algod_client/src/models/dryrun-txn-result.ts @@ -61,7 +61,7 @@ export const DryrunTxnResultMeta: ModelMetadata = { wireKey: 'logic-sig-trace', optional: true, nullable: false, - type: { kind: 'array', item: { kind: 'model', meta: () => DryrunStateMeta } }, + type: { kind: 'array', item: { kind: 'model', meta: DryrunStateMeta } }, }, { name: 'logicSigMessages', @@ -75,7 +75,7 @@ export const DryrunTxnResultMeta: ModelMetadata = { wireKey: 'app-call-trace', optional: true, nullable: false, - type: { kind: 'array', item: { kind: 'model', meta: () => DryrunStateMeta } }, + type: { kind: 'array', item: { kind: 'model', meta: DryrunStateMeta } }, }, { name: 'appCallMessages', @@ -89,14 +89,14 @@ export const DryrunTxnResultMeta: ModelMetadata = { wireKey: 'global-delta', optional: true, nullable: false, - type: { kind: 'model', meta: () => StateDeltaMeta }, + type: { kind: 'model', meta: StateDeltaMeta }, }, { name: 'localDeltas', wireKey: 'local-deltas', optional: true, nullable: false, - type: { kind: 'array', item: { kind: 'model', meta: () => AccountStateDeltaMeta } }, + type: { kind: 'array', item: { kind: 'model', meta: AccountStateDeltaMeta } }, }, { name: 'logs', diff --git a/packages/algod_client/src/models/eval-delta-key-value.ts b/packages/algod_client/src/models/eval-delta-key-value.ts index 31a0689a..897ff226 100644 --- a/packages/algod_client/src/models/eval-delta-key-value.ts +++ b/packages/algod_client/src/models/eval-delta-key-value.ts @@ -26,7 +26,7 @@ export const EvalDeltaKeyValueMeta: ModelMetadata = { wireKey: 'value', optional: false, nullable: false, - type: { kind: 'model', meta: () => EvalDeltaMeta }, + type: { kind: 'model', meta: EvalDeltaMeta }, }, ], } diff --git a/packages/algod_client/src/models/genesis-allocation.ts b/packages/algod_client/src/models/genesis-allocation.ts index 46feb634..3da033dc 100644 --- a/packages/algod_client/src/models/genesis-allocation.ts +++ b/packages/algod_client/src/models/genesis-allocation.ts @@ -1,5 +1,68 @@ import type { ModelMetadata } from '../core/model-runtime' +const GenesisAllocationStateMeta: ModelMetadata = { + name: 'GenesisAllocationStateMeta', + kind: 'object', + fields: [ + { + name: 'algo', + wireKey: 'algo', + optional: false, + nullable: false, + type: { kind: 'scalar', isBigint: true }, + }, + { + name: 'onl', + wireKey: 'onl', + optional: false, + nullable: false, + type: { kind: 'scalar' }, + }, + { + name: 'sel', + wireKey: 'sel', + optional: true, + nullable: false, + type: { kind: 'scalar' }, + }, + { + name: 'stprf', + wireKey: 'stprf', + optional: true, + nullable: false, + type: { kind: 'scalar' }, + }, + { + name: 'vote', + wireKey: 'vote', + optional: true, + nullable: false, + type: { kind: 'scalar' }, + }, + { + name: 'voteKd', + wireKey: 'voteKD', + optional: true, + nullable: false, + type: { kind: 'scalar', isBigint: true }, + }, + { + name: 'voteFst', + wireKey: 'voteFst', + optional: true, + nullable: false, + type: { kind: 'scalar', isBigint: true }, + }, + { + name: 'voteLst', + wireKey: 'voteLst', + optional: true, + nullable: false, + type: { kind: 'scalar', isBigint: true }, + }, + ], +} + export type GenesisAllocation = { addr: string comment: string @@ -38,7 +101,7 @@ export const GenesisAllocationMeta: ModelMetadata = { wireKey: 'state', optional: false, nullable: false, - type: { kind: 'scalar' }, + type: { kind: 'model', meta: GenesisAllocationStateMeta }, }, ], } diff --git a/packages/algod_client/src/models/genesis.ts b/packages/algod_client/src/models/genesis.ts index ee3d1318..6b8620af 100644 --- a/packages/algod_client/src/models/genesis.ts +++ b/packages/algod_client/src/models/genesis.ts @@ -11,7 +11,7 @@ export type Genesis = { network: string proto: string rwd: string - timestamp: number + timestamp?: number } export const GenesisMeta: ModelMetadata = { @@ -23,7 +23,7 @@ export const GenesisMeta: ModelMetadata = { wireKey: 'alloc', optional: false, nullable: false, - type: { kind: 'array', item: { kind: 'model', meta: () => GenesisAllocationMeta } }, + type: { kind: 'array', item: { kind: 'model', meta: GenesisAllocationMeta } }, }, { name: 'comment', @@ -77,7 +77,7 @@ export const GenesisMeta: ModelMetadata = { { name: 'timestamp', wireKey: 'timestamp', - optional: false, + optional: true, nullable: false, type: { kind: 'scalar' }, }, diff --git a/packages/algod_client/src/models/get-application-boxes.ts b/packages/algod_client/src/models/get-application-boxes.ts index fbb23b78..f812ea95 100644 --- a/packages/algod_client/src/models/get-application-boxes.ts +++ b/packages/algod_client/src/models/get-application-boxes.ts @@ -15,7 +15,7 @@ export const GetApplicationBoxesMeta: ModelMetadata = { wireKey: 'boxes', optional: false, nullable: false, - type: { kind: 'array', item: { kind: 'model', meta: () => BoxDescriptorMeta } }, + type: { kind: 'array', item: { kind: 'model', meta: BoxDescriptorMeta } }, }, ], } diff --git a/packages/algod_client/src/models/get-block.ts b/packages/algod_client/src/models/get-block.ts index cf45466a..72d6ccf7 100644 --- a/packages/algod_client/src/models/get-block.ts +++ b/packages/algod_client/src/models/get-block.ts @@ -5,7 +5,7 @@ import { BlockMeta } from './block' export type GetBlock = { /** Block data including header and transactions. */ block: Block - /** Block certificate (msgpack only). */ + /** Block certificate. */ cert?: Record } @@ -13,7 +13,7 @@ export const GetBlockMeta: ModelMetadata = { name: 'GetBlock', kind: 'object', fields: [ - { name: 'block', wireKey: 'block', optional: false, nullable: false, type: { kind: 'model', meta: () => BlockMeta } }, + { name: 'block', wireKey: 'block', optional: false, nullable: false, type: { kind: 'model', meta: BlockMeta } }, { name: 'cert', wireKey: 'cert', optional: true, nullable: false, type: { kind: 'scalar' } }, ], } diff --git a/packages/algod_client/src/models/get-transaction-group-ledger-state-deltas-for-round.ts b/packages/algod_client/src/models/get-transaction-group-ledger-state-deltas-for-round.ts index e661cf7a..180e601d 100644 --- a/packages/algod_client/src/models/get-transaction-group-ledger-state-deltas-for-round.ts +++ b/packages/algod_client/src/models/get-transaction-group-ledger-state-deltas-for-round.ts @@ -15,7 +15,7 @@ export const GetTransactionGroupLedgerStateDeltasForRoundMeta: ModelMetadata = { wireKey: 'Deltas', optional: false, nullable: false, - type: { kind: 'array', item: { kind: 'model', meta: () => LedgerStateDeltaForTransactionGroupMeta } }, + type: { kind: 'array', item: { kind: 'model', meta: LedgerStateDeltaForTransactionGroupMeta } }, }, ], } diff --git a/packages/algod_client/src/models/index.ts b/packages/algod_client/src/models/index.ts index ee5b69ed..ace6f90d 100644 --- a/packages/algod_client/src/models/index.ts +++ b/packages/algod_client/src/models/index.ts @@ -102,6 +102,8 @@ export type { SimulateInitialStates } from './simulate-initial-states' export { SimulateInitialStatesMeta } from './simulate-initial-states' export type { TransactionProof } from './transaction-proof' export { TransactionProofMeta } from './transaction-proof' +export type { SourceMap } from './source-map' +export { SourceMapMeta } from './source-map' export type { AccountAssetInformation } from './account-asset-information' export { AccountAssetInformationMeta } from './account-asset-information' export type { AccountApplicationInformation } from './account-application-information' @@ -144,19 +146,5 @@ export type { GetBlockTimeStampOffset } from './get-block-time-stamp-offset' export { GetBlockTimeStampOffsetMeta } from './get-block-time-stamp-offset' export type { SuggestedParams, SuggestedParamsMeta } from './suggested-params' -export type { BlockEvalDelta } from './block-eval-delta' -export { BlockEvalDeltaMeta } from './block-eval-delta' -export type { BlockStateDelta } from './block-state-delta' -export { BlockStateDeltaMeta } from './block-state-delta' -export type { BlockAccountStateDelta } from './block-account-state-delta' -export { BlockAccountStateDeltaMeta } from './block-account-state-delta' -export type { BlockAppEvalDelta } from './block-app-eval-delta' -export { BlockAppEvalDeltaMeta } from './block-app-eval-delta' -export type { BlockStateProofTrackingData } from './block_state_proof_tracking_data' -export { BlockStateProofTrackingDataMeta } from './block_state_proof_tracking_data' -export type { BlockStateProofTracking } from './block_state_proof_tracking' -export { BlockStateProofTrackingMeta } from './block_state_proof_tracking' export type { Block } from './block' export { BlockMeta } from './block' -export type { SignedTxnInBlock } from './signed-txn-in-block' -export { SignedTxnInBlockMeta } from './signed-txn-in-block' diff --git a/packages/algod_client/src/models/ledger-state-delta-for-transaction-group.ts b/packages/algod_client/src/models/ledger-state-delta-for-transaction-group.ts index a1693cfa..f27b4aa8 100644 --- a/packages/algod_client/src/models/ledger-state-delta-for-transaction-group.ts +++ b/packages/algod_client/src/models/ledger-state-delta-for-transaction-group.ts @@ -19,7 +19,7 @@ export const LedgerStateDeltaForTransactionGroupMeta: ModelMetadata = { wireKey: 'Delta', optional: false, nullable: false, - type: { kind: 'model', meta: () => LedgerStateDeltaMeta }, + type: { kind: 'model', meta: LedgerStateDeltaMeta }, }, { name: 'ids', diff --git a/packages/algod_client/src/models/ledger-state-delta.ts b/packages/algod_client/src/models/ledger-state-delta.ts index e182e624..9bb0e6da 100644 --- a/packages/algod_client/src/models/ledger-state-delta.ts +++ b/packages/algod_client/src/models/ledger-state-delta.ts @@ -1,12 +1,696 @@ import type { ModelMetadata } from '../core/model-runtime' +import type { Block } from './block' +import { BlockMeta } from './block' /** - * Ledger StateDelta object + * Contains type information and a value, representing a value in a TEAL program. */ -export type LedgerStateDelta = Record +export type LedgerTealValue = { + /** + * Type determines the type of the value. + * * 1 represents the type of a byte slice in a TEAL program + * * 2 represents the type of an unsigned integer in a TEAL program + */ + type: number + /** bytes value. */ + bytes?: Uint8Array + /** uint value. */ + uint?: bigint +} + +export const LedgerTealValueMeta: ModelMetadata = { + name: 'LedgerTealValue', + kind: 'object', + fields: [ + { name: 'type', wireKey: 'tt', optional: false, nullable: false, type: { kind: 'scalar' } }, + { name: 'bytes', wireKey: 'tb', optional: true, nullable: false, type: { kind: 'scalar', isBytes: true } }, + { name: 'uint', wireKey: 'ui', optional: true, nullable: false, type: { kind: 'scalar', isBigint: true } }, + ], +} + +/** + * Sets maximums on the number of each type that may be stored. + */ +export type LedgerStateSchema = { + /** Number of uints in state. */ + numUints?: bigint + /** Number of byte slices in state. */ + numByteSlices?: bigint +} + +export const LedgerStateSchemaMeta: ModelMetadata = { + name: 'LedgerStateSchema', + kind: 'object', + fields: [ + { name: 'numUints', wireKey: 'nui', optional: true, nullable: false, type: { kind: 'scalar', isBigint: true } }, + { name: 'numByteSlices', wireKey: 'nbs', optional: true, nullable: false, type: { kind: 'scalar', isBigint: true } }, + ], +} + +/** + * Stores the global information associated with an application. + */ +export type LedgerAppParams = { + approvalProgram: Uint8Array + clearStateProgram: Uint8Array + localStateSchema: LedgerStateSchema + globalStateSchema: LedgerStateSchema + extraProgramPages: number + version?: number + sizeSponsor?: string + globalState?: Map +} + +export const LedgerAppParamsMeta: ModelMetadata = { + name: 'LedgerAppParams', + kind: 'object', + fields: [ + { name: 'approvalProgram', wireKey: 'approv', optional: false, nullable: false, type: { kind: 'scalar', isBytes: true } }, + { name: 'clearStateProgram', wireKey: 'clearp', optional: false, nullable: false, type: { kind: 'scalar', isBytes: true } }, + { name: 'localStateSchema', wireKey: 'lsch', optional: false, nullable: false, type: { kind: 'model', meta: LedgerStateSchemaMeta } }, + { name: 'globalStateSchema', wireKey: 'gsch', optional: false, nullable: false, type: { kind: 'model', meta: LedgerStateSchemaMeta } }, + { name: 'extraProgramPages', wireKey: 'epp', optional: false, nullable: false, type: { kind: 'scalar' } }, + { name: 'version', wireKey: 'v', optional: true, nullable: false, type: { kind: 'scalar' } }, + { name: 'sizeSponsor', wireKey: 'ss', optional: true, nullable: false, type: { kind: 'scalar', isAddress: true } }, + { + name: 'globalState', + wireKey: 'gs', + optional: true, + nullable: false, + type: { kind: 'map', keyType: 'bytes', value: { kind: 'model', meta: LedgerTealValueMeta } }, + }, + ], +} + +/** + * Stores the LocalState associated with an application. + */ +export type LedgerAppLocalState = { + schema: LedgerStateSchema + keyValue?: Map +} + +export const LedgerAppLocalStateMeta: ModelMetadata = { + name: 'LedgerAppLocalState', + kind: 'object', + fields: [ + { name: 'schema', wireKey: 'hsch', optional: false, nullable: false, type: { kind: 'model', meta: LedgerStateSchemaMeta } }, + { + name: 'keyValue', + wireKey: 'tkv', + optional: true, + nullable: false, + type: { kind: 'map', keyType: 'bytes', value: { kind: 'model', meta: LedgerTealValueMeta } }, + }, + ], +} + +/** + * Tracks a changed AppLocalState, and whether it was deleted. + */ +export type LedgerAppLocalStateDelta = { + deleted: boolean + localState?: LedgerAppLocalState +} + +export const LedgerAppLocalStateDeltaMeta: ModelMetadata = { + name: 'LedgerAppLocalStateDelta', + kind: 'object', + fields: [ + { name: 'deleted', wireKey: 'Deleted', optional: false, nullable: false, type: { kind: 'scalar' } }, + { name: 'localState', wireKey: 'LocalState', optional: true, nullable: false, type: { kind: 'model', meta: LedgerAppLocalStateMeta } }, + ], +} + +/** + * Tracks a changed AppParams, and whether it was deleted. + */ +export type LedgerAppParamsDelta = { + deleted: boolean + params?: LedgerAppParams +} + +export const LedgerAppParamsDeltaMeta: ModelMetadata = { + name: 'LedgerAppParamsDelta', + kind: 'object', + fields: [ + { name: 'deleted', wireKey: 'Deleted', optional: false, nullable: false, type: { kind: 'scalar' } }, + { name: 'params', wireKey: 'Params', optional: true, nullable: false, type: { kind: 'model', meta: LedgerAppParamsMeta } }, + ], +} + +/** + * Represents AppParams and AppLocalState in deltas. + */ +export type LedgerAppResourceRecord = { + appId: bigint + address: string + params: LedgerAppParamsDelta + state: LedgerAppLocalStateDelta +} + +export const LedgerAppResourceRecordMeta: ModelMetadata = { + name: 'LedgerAppResourceRecord', + kind: 'object', + fields: [ + { name: 'appId', wireKey: 'Aidx', optional: false, nullable: false, type: { kind: 'scalar', isBigint: true } }, + { name: 'address', wireKey: 'Addr', optional: false, nullable: false, type: { kind: 'scalar', isAddress: true } }, + { name: 'params', wireKey: 'Params', optional: false, nullable: false, type: { kind: 'model', meta: LedgerAppParamsDeltaMeta } }, + { name: 'state', wireKey: 'State', optional: false, nullable: false, type: { kind: 'model', meta: LedgerAppLocalStateDeltaMeta } }, + ], +} + +/** + * Describes an asset held by an account. + */ +export type LedgerAssetHolding = { + amount: bigint + frozen: boolean +} + +export const LedgerAssetHoldingMeta: ModelMetadata = { + name: 'LedgerAssetHolding', + kind: 'object', + fields: [ + { name: 'amount', wireKey: 'a', optional: false, nullable: false, type: { kind: 'scalar', isBigint: true } }, + { name: 'frozen', wireKey: 'f', optional: false, nullable: false, type: { kind: 'scalar' } }, + ], +} + +/** + * Records a changed AssetHolding, and whether it was deleted. + */ +export type LedgerAssetHoldingDelta = { + deleted: boolean + holding?: LedgerAssetHolding +} + +export const LedgerAssetHoldingDeltaMeta: ModelMetadata = { + name: 'LedgerAssetHoldingDelta', + kind: 'object', + fields: [ + { name: 'deleted', wireKey: 'Deleted', optional: false, nullable: false, type: { kind: 'scalar' } }, + { name: 'holding', wireKey: 'Holding', optional: true, nullable: false, type: { kind: 'model', meta: LedgerAssetHoldingMeta } }, + ], +} + +/** + * Describes the parameters of an asset. + */ +export type LedgerAssetParams = { + /** + * Specifies the total number of units of this asset created. + */ + total: bigint + /** + * Specifies the number of digits to display after the decimal place when displaying this asset. + * A value of 0 represents an asset that is not divisible, a value of 1 represents an asset divisible into tenths, and so on. + * This value must be between 0 and 19 (inclusive). + */ + decimals: number + /** + * Specifies whether slots for this asset in user accounts are frozen by default or not. + */ + defaultFrozen: boolean + /** + * Specifies a hint for the name of a unit of this asset. + */ + unitName?: string + /** + * Specifies a hint for the name of the asset. + */ + assetName?: string + /** + * Specifies a URL where more information about the asset can be retrieved. + */ + url?: string + /** + * Specifies a commitment to some unspecified asset metadata. The format of this + * metadata is up to the application. + */ + metadataHash?: Uint8Array + /** + * Manager specifies an account that is allowed to change the non-zero addresses in this AssetParams. + */ + manager?: string + /** + * Specifies an account whose holdings of this asset should be reported as "not minted". + */ + reserve?: string + /** + * Specifies an account that is allowed to change the frozen state of holdings of this asset. + */ + freeze?: string + /** + * Specifies an account that is allowed to take units of this asset from any account. + */ + clawback?: string +} + +export const LedgerAssetParamsMeta: ModelMetadata = { + name: 'LedgerAssetParams', + kind: 'object', + fields: [ + { name: 'total', wireKey: 't', optional: false, nullable: false, type: { kind: 'scalar', isBigint: true } }, + { name: 'decimals', wireKey: 'dc', optional: false, nullable: false, type: { kind: 'scalar' } }, + { name: 'defaultFrozen', wireKey: 'df', optional: false, nullable: false, type: { kind: 'scalar' } }, + { name: 'unitName', wireKey: 'un', optional: true, nullable: false, type: { kind: 'scalar' } }, + { name: 'assetName', wireKey: 'an', optional: true, nullable: false, type: { kind: 'scalar' } }, + { name: 'url', wireKey: 'au', optional: true, nullable: false, type: { kind: 'scalar' } }, + { name: 'metadataHash', wireKey: 'am', optional: true, nullable: false, type: { kind: 'scalar', isBytes: true } }, + { name: 'manager', wireKey: 'm', optional: true, nullable: false, type: { kind: 'scalar', isAddress: true } }, + { name: 'reserve', wireKey: 'r', optional: true, nullable: false, type: { kind: 'scalar', isAddress: true } }, + { name: 'freeze', wireKey: 'f', optional: true, nullable: false, type: { kind: 'scalar', isAddress: true } }, + { name: 'clawback', wireKey: 'c', optional: true, nullable: false, type: { kind: 'scalar', isAddress: true } }, + ], +} + +/** + * Tracks a changed asset params, and whether it was deleted. + */ +export type LedgerAssetParamsDelta = { + deleted: boolean + params?: LedgerAssetParams +} + +export const LedgerAssetParamsDeltaMeta: ModelMetadata = { + name: 'LedgerAssetParamsDelta', + kind: 'object', + fields: [ + { name: 'deleted', wireKey: 'Deleted', optional: false, nullable: false, type: { kind: 'scalar' } }, + { name: 'params', wireKey: 'Params', optional: true, nullable: false, type: { kind: 'model', meta: LedgerAssetParamsMeta } }, + ], +} + +/** + * Represents asset params and asset holding in deltas. + */ +export type LedgerAssetResourceRecord = { + assetId: bigint + address: string + params: LedgerAssetParamsDelta + holding: LedgerAssetHoldingDelta +} + +export const LedgerAssetResourceRecordMeta: ModelMetadata = { + name: 'LedgerAssetResourceRecord', + kind: 'object', + fields: [ + { name: 'assetId', wireKey: 'Aidx', optional: false, nullable: false, type: { kind: 'scalar', isBigint: true } }, + { name: 'address', wireKey: 'Addr', optional: false, nullable: false, type: { kind: 'scalar', isAddress: true } }, + { name: 'params', wireKey: 'Params', optional: false, nullable: false, type: { kind: 'model', meta: LedgerAssetParamsDeltaMeta } }, + { name: 'holding', wireKey: 'Holding', optional: false, nullable: false, type: { kind: 'model', meta: LedgerAssetHoldingDeltaMeta } }, + ], +} + +/** + * Holds participation information. + */ +export type LedgerVotingData = { + voteId: Uint8Array + selectionId: Uint8Array + stateProofId: Uint8Array + voteFirstValid: bigint + voteLastValid: bigint + voteKeyDilution: bigint +} + +export const LedgerVotingDataMeta: ModelMetadata = { + name: 'LedgerVotingData', + kind: 'object', + fields: [ + { name: 'voteId', wireKey: 'VoteID', optional: false, nullable: false, type: { kind: 'scalar', isBytes: true } }, + { name: 'selectionId', wireKey: 'SelectionID', optional: false, nullable: false, type: { kind: 'scalar', isBytes: true } }, + { name: 'stateProofId', wireKey: 'StateProofID', optional: false, nullable: false, type: { kind: 'scalar', isBytes: true } }, + { name: 'voteFirstValid', wireKey: 'VoteFirstValid', optional: false, nullable: false, type: { kind: 'scalar', isBigint: true } }, + { name: 'voteLastValid', wireKey: 'VoteLastValid', optional: false, nullable: false, type: { kind: 'scalar', isBigint: true } }, + { name: 'voteKeyDilution', wireKey: 'VoteKeyDilution', optional: false, nullable: false, type: { kind: 'scalar', isBigint: true } }, + ], +} + +/** + * Contains base account info like balance, status and total number of resources. + */ +export type LedgerAccountBaseData = { + /** + * Account status. Values are: + * * 0: Offline + * * 1: Online + * * 2: NotParticipating + */ + status: number + microAlgos: bigint + rewardsBase: bigint + rewardedMicroAlgos: bigint + authAddress: string + incentiveEligible: boolean + /** + * Totals across created globals, and opted in locals. + */ + totalAppSchema: LedgerStateSchema + /** + * Total number of extra pages across all created apps. + */ + totalExtraAppPages: number + /** + * Total number of apps this account has created. + */ + totalAppParams: number + /** + * Total number of apps this account is opted into. + */ + totalAppLocalStates: number + /** + * Total number of assets created by this account. + */ + totalAssetParams: number + /** + * Total of asset creations and optins (i.e. number of holdings). + */ + totalAssets: number + /** + * Total number of boxes associated to this account. + */ + totalBoxes: bigint + /** + * Total bytes for this account's boxes. keys and values count. + */ + totalBoxBytes: bigint + /** + * The last round that this account proposed the winning block. + */ + lastProposed: bigint + /** + * The last round that this account sent a heartbeat to show it was online. + */ + lastHeartbeat: bigint +} + +export const LedgerAccountBaseDataMeta: ModelMetadata = { + name: 'LedgerAccountBaseData', + kind: 'object', + fields: [ + { name: 'status', wireKey: 'Status', optional: false, nullable: false, type: { kind: 'scalar' } }, + { name: 'microAlgos', wireKey: 'MicroAlgos', optional: false, nullable: false, type: { kind: 'scalar', isBigint: true } }, + { name: 'rewardsBase', wireKey: 'RewardsBase', optional: false, nullable: false, type: { kind: 'scalar', isBigint: true } }, + { + name: 'rewardedMicroAlgos', + wireKey: 'RewardedMicroAlgos', + optional: false, + nullable: false, + type: { kind: 'scalar', isBigint: true }, + }, + { name: 'authAddress', wireKey: 'AuthAddr', optional: false, nullable: false, type: { kind: 'scalar', isAddress: true } }, + { name: 'incentiveEligible', wireKey: 'IncentiveEligible', optional: false, nullable: false, type: { kind: 'scalar' } }, + { + name: 'totalAppSchema', + wireKey: 'TotalAppSchema', + optional: false, + nullable: false, + type: { kind: 'model', meta: LedgerStateSchemaMeta }, + }, + { name: 'totalExtraAppPages', wireKey: 'TotalExtraAppPages', optional: false, nullable: false, type: { kind: 'scalar' } }, + { name: 'totalAppParams', wireKey: 'TotalAppParams', optional: false, nullable: false, type: { kind: 'scalar' } }, + { name: 'totalAppLocalStates', wireKey: 'TotalAppLocalStates', optional: false, nullable: false, type: { kind: 'scalar' } }, + { name: 'totalAssetParams', wireKey: 'TotalAssetParams', optional: false, nullable: false, type: { kind: 'scalar' } }, + { name: 'totalAssets', wireKey: 'TotalAssets', optional: false, nullable: false, type: { kind: 'scalar' } }, + { name: 'totalBoxes', wireKey: 'TotalBoxes', optional: false, nullable: false, type: { kind: 'scalar', isBigint: true } }, + { name: 'totalBoxBytes', wireKey: 'TotalBoxBytes', optional: false, nullable: false, type: { kind: 'scalar', isBigint: true } }, + { name: 'lastProposed', wireKey: 'LastProposed', optional: false, nullable: false, type: { kind: 'scalar', isBigint: true } }, + { name: 'lastHeartbeat', wireKey: 'LastHeartbeat', optional: false, nullable: false, type: { kind: 'scalar', isBigint: true } }, + ], +} + +/** + * Provides per-account data. + */ +export type LedgerAccountData = { + accountBaseData: LedgerAccountBaseData + votingData: LedgerVotingData +} + +export const LedgerAccountDataMeta: ModelMetadata = { + name: 'LedgerAccountData', + kind: 'object', + fields: [ + { + name: 'accountBaseData', + flattened: true, + optional: false, + nullable: false, + type: { kind: 'model', meta: LedgerAccountBaseDataMeta }, + }, + { name: 'votingData', flattened: true, optional: false, nullable: false, type: { kind: 'model', meta: LedgerVotingDataMeta } }, + ], +} + +export type LedgerBalanceRecord = { + address: string + accountData: LedgerAccountData +} + +export const LedgerBalanceRecordMeta: ModelMetadata = { + name: 'LedgerBalanceRecord', + kind: 'object', + fields: [ + { name: 'address', wireKey: 'Addr', optional: false, nullable: false, type: { kind: 'scalar', isAddress: true } }, + { name: 'accountData', flattened: true, optional: false, nullable: false, type: { kind: 'model', meta: LedgerAccountDataMeta } }, + ], +} + +export type LedgerAccountDeltas = { + accounts?: LedgerBalanceRecord[] + appResources?: LedgerAppResourceRecord[] + assetResources?: LedgerAssetResourceRecord[] +} + +export const LedgerAccountDeltasMeta: ModelMetadata = { + name: 'LedgerAccountDeltas', + kind: 'object', + fields: [ + { + name: 'accounts', + wireKey: 'Accts', + optional: true, + nullable: false, + type: { kind: 'array', item: { kind: 'model', meta: LedgerBalanceRecordMeta } }, + }, + { + name: 'appResources', + wireKey: 'AppResources', + optional: true, + nullable: false, + type: { kind: 'array', item: { kind: 'model', meta: LedgerAppResourceRecordMeta } }, + }, + { + name: 'assetResources', + wireKey: 'AssetResources', + optional: true, + nullable: false, + type: { kind: 'array', item: { kind: 'model', meta: LedgerAssetResourceRecordMeta } }, + }, + ], +} + +/** + * Shows how the data associated with a key in the kvstore has changed. + */ +export type LedgerKvValueDelta = { + /** + * Stores the most recent value (undefined means deleted). + */ + data?: Uint8Array + /** + * Stores the previous value (undefined means didn't exist). + */ + oldData?: Uint8Array +} + +export const LedgerKvValueDeltaMeta: ModelMetadata = { + name: 'LedgerKvValueDelta', + kind: 'object', + fields: [ + { name: 'data', wireKey: 'Data', optional: true, nullable: false, type: { kind: 'scalar', isBytes: true } }, + { name: 'oldData', wireKey: 'OldData', optional: true, nullable: false, type: { kind: 'scalar', isBytes: true } }, + ], +} + +/** + * Defines the transactions included in a block, their index and last valid round. + */ +export type LedgerIncludedTransactions = { + lastValid: bigint + /** + * The index of the transaction in the block. + */ + intra: number +} + +export const LedgerIncludedTransactionsMeta: ModelMetadata = { + name: 'LedgerIncludedTransactions', + kind: 'object', + fields: [ + { name: 'lastValid', wireKey: 'LastValid', optional: false, nullable: false, type: { kind: 'scalar', isBigint: true } }, + { name: 'intra', wireKey: 'Intra', optional: false, nullable: false, type: { kind: 'scalar' } }, + ], +} + +/** + * Represents a change to a single creatable state. + */ +export type LedgerModifiedCreatable = { + /** + * Type of the creatable. The values are: + * * 0: Asset + * * 1: Application + */ + creatableType: number + /** + * Created if true, deleted if false. + */ + created: boolean + /** + * Creator of the app/asset. + */ + creator: string + /** + * Keeps track of how many times this app/asset appears in accountUpdates.creatableDeltas. + */ + nDeltas: number +} + +export const LedgerModifiedCreatableMeta: ModelMetadata = { + name: 'LedgerModifiedCreatable', + kind: 'object', + fields: [ + { name: 'creatableType', wireKey: 'Ctype', optional: false, nullable: false, type: { kind: 'scalar' } }, + { name: 'created', wireKey: 'Created', optional: false, nullable: false, type: { kind: 'scalar' } }, + { name: 'creator', wireKey: 'Creator', optional: false, nullable: false, type: { kind: 'scalar', isAddress: true } }, + { name: 'ndeltas', wireKey: 'Ndeltas', optional: false, nullable: false, type: { kind: 'scalar' } }, + ], +} + +/** + * Represents a total of algos of a certain class of accounts (split up by their Status value). + */ +export type LedgerAlgoCount = { + /** + * Sum of algos of all accounts in this scope. + */ + money: bigint + /** + * Total number of whole reward units in accounts. + */ + rewardUnits: bigint +} + +export const LedgerAlgoCountMeta: ModelMetadata = { + name: 'LedgerAlgoCount', + kind: 'object', + fields: [ + { name: 'money', wireKey: 'mon', optional: false, nullable: false, type: { kind: 'scalar', isBigint: true } }, + { name: 'rewardUnits', wireKey: 'rwd', optional: false, nullable: false, type: { kind: 'scalar', isBigint: true } }, + ], +} + +/** + * Represents the totals of algos in the system grouped by different account status values. + */ +export type LedgerAccountTotals = { + online: LedgerAlgoCount + offline: LedgerAlgoCount + notParticipating: LedgerAlgoCount + /** + * Total number of algos received per reward unit since genesis. + */ + rewardsLevel: bigint +} + +export const LedgerAccountTotalsMeta: ModelMetadata = { + name: 'LedgerAccountTotals', + kind: 'object', + fields: [ + { name: 'online', wireKey: 'online', optional: false, nullable: false, type: { kind: 'model', meta: LedgerAlgoCountMeta } }, + { name: 'offline', wireKey: 'offline', optional: false, nullable: false, type: { kind: 'model', meta: LedgerAlgoCountMeta } }, + { name: 'notParticipating', wireKey: 'notpart', optional: false, nullable: false, type: { kind: 'model', meta: LedgerAlgoCountMeta } }, + { name: 'rewardsLevel', wireKey: 'rwdlvl', optional: false, nullable: false, type: { kind: 'scalar', isBigint: true } }, + ], +} + +/** + * Describes the delta between a given round to the previous round. + */ +export type LedgerStateDelta = { + /** + * Modified new accounts. + */ + accounts: LedgerAccountDeltas + /** + * Block header. + */ + block: Block + /** + * Represents modification on StateProofNextRound field in the block header. If the block contains + * a valid state proof transaction, this field will contain the next round for state proof. + * otherwise it will be set to 0. + */ + stateProofNext: bigint + /** + * Previous block timestamp + */ + prevTimestamp: bigint + /** + * The account totals reflecting the changes in this StateDelta object. + */ + totals: LedgerAccountTotals + /** + * Modified kv pairs. + */ + kvMods?: Map + /** + * New Txids for the txtail and TxnCounter, mapped to txn.LastValid. + */ + txIds?: Map + /** + * New txleases for the txtail mapped to expiration. + */ + txLeases?: Record + /** + * New creatables creator lookup table. + */ + creatables?: Map +} export const LedgerStateDeltaMeta: ModelMetadata = { name: 'LedgerStateDelta', kind: 'object', - fields: [], + fields: [ + { name: 'accounts', wireKey: 'Accts', optional: false, nullable: false, type: { kind: 'model', meta: LedgerAccountDeltasMeta } }, + { name: 'block', wireKey: 'Hdr', optional: false, nullable: false, type: { kind: 'model', meta: BlockMeta } }, + { name: 'stateProofNext', wireKey: 'StateProofNext', optional: false, nullable: false, type: { kind: 'scalar', isBigint: true } }, + { name: 'prevTimestamp', wireKey: 'PrevTimestamp', optional: false, nullable: false, type: { kind: 'scalar', isBigint: true } }, + { name: 'totals', wireKey: 'Totals', optional: false, nullable: false, type: { kind: 'model', meta: LedgerAccountTotalsMeta } }, + { + name: 'kvMods', + wireKey: 'KvMods', + optional: true, + nullable: false, + type: { kind: 'map', keyType: 'bytes', value: { kind: 'model', meta: LedgerKvValueDeltaMeta } }, + }, + { + name: 'txIds', + wireKey: 'Txids', + optional: true, + nullable: false, + type: { kind: 'map', keyType: 'bytes', value: { kind: 'model', meta: LedgerIncludedTransactionsMeta } }, + }, + { name: 'txLeases', wireKey: 'Txleases', optional: true, nullable: false, type: { kind: 'scalar' } }, + { + name: 'creatables', + wireKey: 'Creatables', + optional: true, + nullable: false, + type: { kind: 'map', keyType: 'number', value: { kind: 'model', meta: LedgerModifiedCreatableMeta } }, + }, + ], } diff --git a/packages/algod_client/src/models/pending-transaction-response.ts b/packages/algod_client/src/models/pending-transaction-response.ts index d968f953..7605f700 100644 --- a/packages/algod_client/src/models/pending-transaction-response.ts +++ b/packages/algod_client/src/models/pending-transaction-response.ts @@ -148,14 +148,14 @@ export const PendingTransactionResponseMeta: ModelMetadata = { wireKey: 'local-state-delta', optional: true, nullable: false, - type: { kind: 'array', item: { kind: 'model', meta: () => AccountStateDeltaMeta } }, + type: { kind: 'array', item: { kind: 'model', meta: AccountStateDeltaMeta } }, }, { name: 'globalStateDelta', wireKey: 'global-state-delta', optional: true, nullable: false, - type: { kind: 'model', meta: () => StateDeltaMeta }, + type: { kind: 'model', meta: StateDeltaMeta }, }, { name: 'logs', diff --git a/packages/algod_client/src/models/scratch-change.ts b/packages/algod_client/src/models/scratch-change.ts index b0c906f3..48b979f9 100644 --- a/packages/algod_client/src/models/scratch-change.ts +++ b/packages/algod_client/src/models/scratch-change.ts @@ -29,7 +29,7 @@ export const ScratchChangeMeta: ModelMetadata = { wireKey: 'new-value', optional: false, nullable: false, - type: { kind: 'model', meta: () => AvmValueMeta }, + type: { kind: 'model', meta: AvmValueMeta }, }, ], } diff --git a/packages/algod_client/src/models/signed-txn-in-block.ts b/packages/algod_client/src/models/signed-txn-in-block.ts deleted file mode 100644 index 2ac4b30e..00000000 --- a/packages/algod_client/src/models/signed-txn-in-block.ts +++ /dev/null @@ -1,66 +0,0 @@ -/* - * Algod REST API. - * - * API endpoint for algod operations. - * - * The version of the OpenAPI document: 0.0.1 - * Contact: contact@algorand.com - * Generated by: Rust OpenAPI Generator - */ - -import type { ModelMetadata } from '../core/model-runtime' -import type { SignedTransaction } from '@algorandfoundation/algokit-transact' -import type { BlockAppEvalDelta } from './block-app-eval-delta' -import { getModelMeta, registerModelMeta } from '../core/model-runtime' - -/** - * SignedTxnInBlock is a SignedTransaction with additional ApplyData and block-specific metadata. - */ -export interface SignedTxnInBlock { - signedTransaction: SignedTransaction - logicSignature?: Record - closingAmount?: bigint - assetClosingAmount?: bigint - senderRewards?: bigint - receiverRewards?: bigint - closeRewards?: bigint - evalDelta?: BlockAppEvalDelta - configAsset?: bigint - applicationId?: bigint - hasGenesisId?: boolean - hasGenesisHash?: boolean -} - -export const SignedTxnInBlockMeta: ModelMetadata = { - name: 'SignedTxnInBlock', - kind: 'object', - fields: [ - { - name: 'signedTransaction', - // flatten signed transaction fields into parent - flattened: true, - optional: false, - nullable: false, - type: { kind: 'codec', codecKey: 'SignedTransaction' }, - }, - { name: 'logicSignature', wireKey: 'lsig', optional: true, nullable: false, type: { kind: 'scalar' } }, - { name: 'closingAmount', wireKey: 'ca', optional: true, nullable: false, type: { kind: 'scalar', isBigint: true } }, - { name: 'assetClosingAmount', wireKey: 'aca', optional: true, nullable: false, type: { kind: 'scalar', isBigint: true } }, - { name: 'senderRewards', wireKey: 'rs', optional: true, nullable: false, type: { kind: 'scalar', isBigint: true } }, - { name: 'receiverRewards', wireKey: 'rr', optional: true, nullable: false, type: { kind: 'scalar', isBigint: true } }, - { name: 'closeRewards', wireKey: 'rc', optional: true, nullable: false, type: { kind: 'scalar', isBigint: true } }, - { - name: 'evalDelta', - wireKey: 'dt', - optional: true, - nullable: false, - type: { kind: 'model', meta: () => getModelMeta('BlockAppEvalDelta') }, - }, - { name: 'configAsset', wireKey: 'caid', optional: true, nullable: false, type: { kind: 'scalar', isBigint: true } }, - { name: 'applicationId', wireKey: 'apid', optional: true, nullable: false, type: { kind: 'scalar', isBigint: true } }, - { name: 'hasGenesisId', wireKey: 'hgi', optional: true, nullable: false, type: { kind: 'scalar' } }, - { name: 'hasGenesisHash', wireKey: 'hgh', optional: true, nullable: false, type: { kind: 'scalar' } }, - ], -} - -registerModelMeta('SignedTxnInBlock', SignedTxnInBlockMeta) diff --git a/packages/algod_client/src/models/simulate-initial-states.ts b/packages/algod_client/src/models/simulate-initial-states.ts index 21c948cd..d78284b0 100644 --- a/packages/algod_client/src/models/simulate-initial-states.ts +++ b/packages/algod_client/src/models/simulate-initial-states.ts @@ -21,7 +21,7 @@ export const SimulateInitialStatesMeta: ModelMetadata = { wireKey: 'app-initial-states', optional: true, nullable: false, - type: { kind: 'array', item: { kind: 'model', meta: () => ApplicationInitialStatesMeta } }, + type: { kind: 'array', item: { kind: 'model', meta: ApplicationInitialStatesMeta } }, }, ], } diff --git a/packages/algod_client/src/models/simulate-request.ts b/packages/algod_client/src/models/simulate-request.ts index 01043297..577362eb 100644 --- a/packages/algod_client/src/models/simulate-request.ts +++ b/packages/algod_client/src/models/simulate-request.ts @@ -54,7 +54,7 @@ export const SimulateRequestMeta: ModelMetadata = { wireKey: 'txn-groups', optional: false, nullable: false, - type: { kind: 'array', item: { kind: 'model', meta: () => SimulateRequestTransactionGroupMeta } }, + type: { kind: 'array', item: { kind: 'model', meta: SimulateRequestTransactionGroupMeta } }, }, { name: 'round', @@ -96,7 +96,7 @@ export const SimulateRequestMeta: ModelMetadata = { wireKey: 'exec-trace-config', optional: true, nullable: false, - type: { kind: 'model', meta: () => SimulateTraceConfigMeta }, + type: { kind: 'model', meta: SimulateTraceConfigMeta }, }, { name: 'fixSigners', diff --git a/packages/algod_client/src/models/simulate-transaction-group-result.ts b/packages/algod_client/src/models/simulate-transaction-group-result.ts index b091dd10..72fcb214 100644 --- a/packages/algod_client/src/models/simulate-transaction-group-result.ts +++ b/packages/algod_client/src/models/simulate-transaction-group-result.ts @@ -44,7 +44,7 @@ export const SimulateTransactionGroupResultMeta: ModelMetadata = { wireKey: 'txn-results', optional: false, nullable: false, - type: { kind: 'array', item: { kind: 'model', meta: () => SimulateTransactionResultMeta } }, + type: { kind: 'array', item: { kind: 'model', meta: SimulateTransactionResultMeta } }, }, { name: 'failureMessage', @@ -79,7 +79,7 @@ export const SimulateTransactionGroupResultMeta: ModelMetadata = { wireKey: 'unnamed-resources-accessed', optional: true, nullable: false, - type: { kind: 'model', meta: () => SimulateUnnamedResourcesAccessedMeta }, + type: { kind: 'model', meta: SimulateUnnamedResourcesAccessedMeta }, }, ], } diff --git a/packages/algod_client/src/models/simulate-transaction-result.ts b/packages/algod_client/src/models/simulate-transaction-result.ts index 7d9e550e..d9015e32 100644 --- a/packages/algod_client/src/models/simulate-transaction-result.ts +++ b/packages/algod_client/src/models/simulate-transaction-result.ts @@ -39,7 +39,7 @@ export const SimulateTransactionResultMeta: ModelMetadata = { wireKey: 'txn-result', optional: false, nullable: false, - type: { kind: 'model', meta: () => PendingTransactionResponseMeta }, + type: { kind: 'model', meta: PendingTransactionResponseMeta }, }, { name: 'appBudgetConsumed', @@ -60,14 +60,14 @@ export const SimulateTransactionResultMeta: ModelMetadata = { wireKey: 'exec-trace', optional: true, nullable: false, - type: { kind: 'model', meta: () => SimulationTransactionExecTraceMeta }, + type: { kind: 'model', meta: SimulationTransactionExecTraceMeta }, }, { name: 'unnamedResourcesAccessed', wireKey: 'unnamed-resources-accessed', optional: true, nullable: false, - type: { kind: 'model', meta: () => SimulateUnnamedResourcesAccessedMeta }, + type: { kind: 'model', meta: SimulateUnnamedResourcesAccessedMeta }, }, { name: 'fixedSigner', diff --git a/packages/algod_client/src/models/simulate-transaction.ts b/packages/algod_client/src/models/simulate-transaction.ts index b8845dad..a0419f61 100644 --- a/packages/algod_client/src/models/simulate-transaction.ts +++ b/packages/algod_client/src/models/simulate-transaction.ts @@ -51,28 +51,28 @@ export const SimulateTransactionMeta: ModelMetadata = { wireKey: 'txn-groups', optional: false, nullable: false, - type: { kind: 'array', item: { kind: 'model', meta: () => SimulateTransactionGroupResultMeta } }, + type: { kind: 'array', item: { kind: 'model', meta: SimulateTransactionGroupResultMeta } }, }, { name: 'evalOverrides', wireKey: 'eval-overrides', optional: true, nullable: false, - type: { kind: 'model', meta: () => SimulationEvalOverridesMeta }, + type: { kind: 'model', meta: SimulationEvalOverridesMeta }, }, { name: 'execTraceConfig', wireKey: 'exec-trace-config', optional: true, nullable: false, - type: { kind: 'model', meta: () => SimulateTraceConfigMeta }, + type: { kind: 'model', meta: SimulateTraceConfigMeta }, }, { name: 'initialStates', wireKey: 'initial-states', optional: true, nullable: false, - type: { kind: 'model', meta: () => SimulateInitialStatesMeta }, + type: { kind: 'model', meta: SimulateInitialStatesMeta }, }, ], } diff --git a/packages/algod_client/src/models/simulate-unnamed-resources-accessed.ts b/packages/algod_client/src/models/simulate-unnamed-resources-accessed.ts index cd515a0c..fd4e31ec 100644 --- a/packages/algod_client/src/models/simulate-unnamed-resources-accessed.ts +++ b/packages/algod_client/src/models/simulate-unnamed-resources-accessed.ts @@ -76,7 +76,7 @@ export const SimulateUnnamedResourcesAccessedMeta: ModelMetadata = { wireKey: 'boxes', optional: true, nullable: false, - type: { kind: 'array', item: { kind: 'model', meta: () => BoxReferenceMeta } }, + type: { kind: 'array', item: { kind: 'model', meta: BoxReferenceMeta } }, }, { name: 'extraBoxRefs', @@ -90,14 +90,14 @@ export const SimulateUnnamedResourcesAccessedMeta: ModelMetadata = { wireKey: 'asset-holdings', optional: true, nullable: false, - type: { kind: 'array', item: { kind: 'model', meta: () => AssetHoldingReferenceMeta } }, + type: { kind: 'array', item: { kind: 'model', meta: AssetHoldingReferenceMeta } }, }, { name: 'appLocals', wireKey: 'app-locals', optional: true, nullable: false, - type: { kind: 'array', item: { kind: 'model', meta: () => ApplicationLocalReferenceMeta } }, + type: { kind: 'array', item: { kind: 'model', meta: ApplicationLocalReferenceMeta } }, }, ], } diff --git a/packages/algod_client/src/models/simulation-opcode-trace-unit.ts b/packages/algod_client/src/models/simulation-opcode-trace-unit.ts index 4900df9b..7962f271 100644 --- a/packages/algod_client/src/models/simulation-opcode-trace-unit.ts +++ b/packages/algod_client/src/models/simulation-opcode-trace-unit.ts @@ -57,14 +57,14 @@ export const SimulationOpcodeTraceUnitMeta: ModelMetadata = { wireKey: 'scratch-changes', optional: true, nullable: false, - type: { kind: 'array', item: { kind: 'model', meta: () => ScratchChangeMeta } }, + type: { kind: 'array', item: { kind: 'model', meta: ScratchChangeMeta } }, }, { name: 'stateChanges', wireKey: 'state-changes', optional: true, nullable: false, - type: { kind: 'array', item: { kind: 'model', meta: () => ApplicationStateOperationMeta } }, + type: { kind: 'array', item: { kind: 'model', meta: ApplicationStateOperationMeta } }, }, { name: 'spawnedInners', @@ -85,7 +85,7 @@ export const SimulationOpcodeTraceUnitMeta: ModelMetadata = { wireKey: 'stack-additions', optional: true, nullable: false, - type: { kind: 'array', item: { kind: 'model', meta: () => AvmValueMeta } }, + type: { kind: 'array', item: { kind: 'model', meta: AvmValueMeta } }, }, ], } diff --git a/packages/algod_client/src/models/simulation-transaction-exec-trace.ts b/packages/algod_client/src/models/simulation-transaction-exec-trace.ts index b499f9b8..06cdbb97 100644 --- a/packages/algod_client/src/models/simulation-transaction-exec-trace.ts +++ b/packages/algod_client/src/models/simulation-transaction-exec-trace.ts @@ -61,7 +61,7 @@ export const SimulationTransactionExecTraceMeta: ModelMetadata = { wireKey: 'approval-program-trace', optional: true, nullable: false, - type: { kind: 'array', item: { kind: 'model', meta: () => SimulationOpcodeTraceUnitMeta } }, + type: { kind: 'array', item: { kind: 'model', meta: SimulationOpcodeTraceUnitMeta } }, }, { name: 'approvalProgramHash', @@ -75,7 +75,7 @@ export const SimulationTransactionExecTraceMeta: ModelMetadata = { wireKey: 'clear-state-program-trace', optional: true, nullable: false, - type: { kind: 'array', item: { kind: 'model', meta: () => SimulationOpcodeTraceUnitMeta } }, + type: { kind: 'array', item: { kind: 'model', meta: SimulationOpcodeTraceUnitMeta } }, }, { name: 'clearStateProgramHash', @@ -103,7 +103,7 @@ export const SimulationTransactionExecTraceMeta: ModelMetadata = { wireKey: 'logic-sig-trace', optional: true, nullable: false, - type: { kind: 'array', item: { kind: 'model', meta: () => SimulationOpcodeTraceUnitMeta } }, + type: { kind: 'array', item: { kind: 'model', meta: SimulationOpcodeTraceUnitMeta } }, }, { name: 'logicSigHash', diff --git a/packages/algod_client/src/models/source-map.ts b/packages/algod_client/src/models/source-map.ts new file mode 100644 index 00000000..63e23d2f --- /dev/null +++ b/packages/algod_client/src/models/source-map.ts @@ -0,0 +1,58 @@ +import type { ModelMetadata } from '../core/model-runtime' + +/** + * Source map for the program + */ +export type SourceMap = { + version: number + + /** + * A list of original sources used by the "mappings" entry. + */ + sources: string[] + + /** + * A list of symbol names used by the "mappings" entry. + */ + names: string[] + + /** + * A string with the encoded mapping data. + */ + mappings: string +} + +export const SourceMapMeta: ModelMetadata = { + name: 'SourceMap', + kind: 'object', + fields: [ + { + name: 'version', + wireKey: 'version', + optional: false, + nullable: false, + type: { kind: 'scalar' }, + }, + { + name: 'sources', + wireKey: 'sources', + optional: false, + nullable: false, + type: { kind: 'array', item: { kind: 'scalar' } }, + }, + { + name: 'names', + wireKey: 'names', + optional: false, + nullable: false, + type: { kind: 'array', item: { kind: 'scalar' } }, + }, + { + name: 'mappings', + wireKey: 'mappings', + optional: false, + nullable: false, + type: { kind: 'scalar' }, + }, + ], +} diff --git a/packages/algod_client/src/models/state-delta.ts b/packages/algod_client/src/models/state-delta.ts index ef811cfe..4563bbf8 100644 --- a/packages/algod_client/src/models/state-delta.ts +++ b/packages/algod_client/src/models/state-delta.ts @@ -10,5 +10,5 @@ export type StateDelta = EvalDeltaKeyValue[] export const StateDeltaMeta: ModelMetadata = { name: 'StateDelta', kind: 'array', - arrayItems: { kind: 'model', meta: () => EvalDeltaKeyValueMeta }, + arrayItems: { kind: 'model', meta: EvalDeltaKeyValueMeta }, } diff --git a/packages/algod_client/src/models/state-proof.ts b/packages/algod_client/src/models/state-proof.ts index 96dd3bcd..44737b16 100644 --- a/packages/algod_client/src/models/state-proof.ts +++ b/packages/algod_client/src/models/state-proof.ts @@ -23,7 +23,7 @@ export const StateProofMeta: ModelMetadata = { wireKey: 'Message', optional: false, nullable: false, - type: { kind: 'model', meta: () => StateProofMessageMeta }, + type: { kind: 'model', meta: StateProofMessageMeta }, }, { name: 'stateProof', diff --git a/packages/algod_client/src/models/teal-compile.ts b/packages/algod_client/src/models/teal-compile.ts index 97aacb1f..fac98c7f 100644 --- a/packages/algod_client/src/models/teal-compile.ts +++ b/packages/algod_client/src/models/teal-compile.ts @@ -1,4 +1,6 @@ import type { ModelMetadata } from '../core/model-runtime' +import type { SourceMap } from './source-map' +import { SourceMapMeta } from './source-map' export type TealCompile = { /** @@ -10,11 +12,7 @@ export type TealCompile = { * base64 encoded program bytes */ result: string - - /** - * JSON of the source map - */ - sourcemap?: Record + sourcemap?: SourceMap } export const TealCompileMeta: ModelMetadata = { @@ -40,7 +38,7 @@ export const TealCompileMeta: ModelMetadata = { wireKey: 'sourcemap', optional: true, nullable: false, - type: { kind: 'scalar' }, + type: { kind: 'model', meta: SourceMapMeta }, }, ], } diff --git a/packages/algod_client/src/models/teal-dryrun.ts b/packages/algod_client/src/models/teal-dryrun.ts index 7a7899da..4686cbb4 100644 --- a/packages/algod_client/src/models/teal-dryrun.ts +++ b/packages/algod_client/src/models/teal-dryrun.ts @@ -21,7 +21,7 @@ export const TealDryrunMeta: ModelMetadata = { wireKey: 'txns', optional: false, nullable: false, - type: { kind: 'array', item: { kind: 'model', meta: () => DryrunTxnResultMeta } }, + type: { kind: 'array', item: { kind: 'model', meta: DryrunTxnResultMeta } }, }, { name: 'error', diff --git a/packages/algod_client/src/models/teal-key-value-store.ts b/packages/algod_client/src/models/teal-key-value-store.ts index d0ad997b..99dd316d 100644 --- a/packages/algod_client/src/models/teal-key-value-store.ts +++ b/packages/algod_client/src/models/teal-key-value-store.ts @@ -10,5 +10,5 @@ export type TealKeyValueStore = TealKeyValue[] export const TealKeyValueStoreMeta: ModelMetadata = { name: 'TealKeyValueStore', kind: 'array', - arrayItems: { kind: 'model', meta: () => TealKeyValueMeta }, + arrayItems: { kind: 'model', meta: TealKeyValueMeta }, } diff --git a/packages/algod_client/src/models/teal-key-value.ts b/packages/algod_client/src/models/teal-key-value.ts index 1905fe41..2a71a2b2 100644 --- a/packages/algod_client/src/models/teal-key-value.ts +++ b/packages/algod_client/src/models/teal-key-value.ts @@ -26,7 +26,7 @@ export const TealKeyValueMeta: ModelMetadata = { wireKey: 'value', optional: false, nullable: false, - type: { kind: 'model', meta: () => TealValueMeta }, + type: { kind: 'model', meta: TealValueMeta }, }, ], } diff --git a/packages/algod_client/src/models/version.ts b/packages/algod_client/src/models/version.ts index a930fa80..a0904971 100644 --- a/packages/algod_client/src/models/version.ts +++ b/packages/algod_client/src/models/version.ts @@ -21,7 +21,7 @@ export const VersionMeta: ModelMetadata = { wireKey: 'build', optional: false, nullable: false, - type: { kind: 'model', meta: () => BuildVersionMeta }, + type: { kind: 'model', meta: BuildVersionMeta }, }, { name: 'genesisHashB64', diff --git a/packages/indexer_client/src/apis/api.service.ts b/packages/indexer_client/src/apis/api.service.ts index a287be65..e87a2244 100644 --- a/packages/indexer_client/src/apis/api.service.ts +++ b/packages/indexer_client/src/apis/api.service.ts @@ -70,7 +70,7 @@ export class IndexerApi { const responseFormat: BodyFormat = 'json' headers['Accept'] = IndexerApi.acceptFor(responseFormat) - const payload = await this.httpRequest.request({ + const payload = await this.httpRequest.request({ method: 'GET', url: '/v2/accounts/{account-id}/apps-local-state', path: { 'account-id': accountId }, @@ -85,11 +85,7 @@ export class IndexerApi { mediaType: undefined, }) - const responseMeta = LookupAccountAppLocalStatesMeta - if (responseMeta) { - return AlgorandSerializer.decode(payload, responseMeta, responseFormat) - } - return payload as LookupAccountAppLocalStates + return AlgorandSerializer.decode(payload, LookupAccountAppLocalStatesMeta, responseFormat) } /** @@ -103,7 +99,7 @@ export class IndexerApi { const responseFormat: BodyFormat = 'json' headers['Accept'] = IndexerApi.acceptFor(responseFormat) - const payload = await this.httpRequest.request({ + const payload = await this.httpRequest.request({ method: 'GET', url: '/v2/accounts/{account-id}/assets', path: { 'account-id': accountId }, @@ -118,11 +114,7 @@ export class IndexerApi { mediaType: undefined, }) - const responseMeta = LookupAccountAssetsMeta - if (responseMeta) { - return AlgorandSerializer.decode(payload, responseMeta, responseFormat) - } - return payload as LookupAccountAssets + return AlgorandSerializer.decode(payload, LookupAccountAssetsMeta, responseFormat) } /** @@ -140,7 +132,7 @@ export class IndexerApi { const responseFormat: BodyFormat = 'json' headers['Accept'] = IndexerApi.acceptFor(responseFormat) - const payload = await this.httpRequest.request({ + const payload = await this.httpRequest.request({ method: 'GET', url: '/v2/accounts/{account-id}', path: { 'account-id': accountId }, @@ -154,11 +146,7 @@ export class IndexerApi { mediaType: undefined, }) - const responseMeta = LookupAccountByIdMeta - if (responseMeta) { - return AlgorandSerializer.decode(payload, responseMeta, responseFormat) - } - return payload as LookupAccountById + return AlgorandSerializer.decode(payload, LookupAccountByIdMeta, responseFormat) } /** @@ -172,7 +160,7 @@ export class IndexerApi { const responseFormat: BodyFormat = 'json' headers['Accept'] = IndexerApi.acceptFor(responseFormat) - const payload = await this.httpRequest.request({ + const payload = await this.httpRequest.request({ method: 'GET', url: '/v2/accounts/{account-id}/created-applications', path: { 'account-id': accountId }, @@ -187,11 +175,7 @@ export class IndexerApi { mediaType: undefined, }) - const responseMeta = LookupAccountCreatedApplicationsMeta - if (responseMeta) { - return AlgorandSerializer.decode(payload, responseMeta, responseFormat) - } - return payload as LookupAccountCreatedApplications + return AlgorandSerializer.decode(payload, LookupAccountCreatedApplicationsMeta, responseFormat) } /** @@ -205,7 +189,7 @@ export class IndexerApi { const responseFormat: BodyFormat = 'json' headers['Accept'] = IndexerApi.acceptFor(responseFormat) - const payload = await this.httpRequest.request({ + const payload = await this.httpRequest.request({ method: 'GET', url: '/v2/accounts/{account-id}/created-assets', path: { 'account-id': accountId }, @@ -220,11 +204,7 @@ export class IndexerApi { mediaType: undefined, }) - const responseMeta = LookupAccountCreatedAssetsMeta - if (responseMeta) { - return AlgorandSerializer.decode(payload, responseMeta, responseFormat) - } - return payload as LookupAccountCreatedAssets + return AlgorandSerializer.decode(payload, LookupAccountCreatedAssetsMeta, responseFormat) } /** @@ -254,7 +234,7 @@ export class IndexerApi { const responseFormat: BodyFormat = 'json' headers['Accept'] = IndexerApi.acceptFor(responseFormat) - const payload = await this.httpRequest.request({ + const payload = await this.httpRequest.request({ method: 'GET', url: '/v2/accounts/{account-id}/transactions', path: { 'account-id': accountId }, @@ -284,11 +264,7 @@ export class IndexerApi { mediaType: undefined, }) - const responseMeta = LookupAccountTransactionsMeta - if (responseMeta) { - return AlgorandSerializer.decode(payload, responseMeta, responseFormat) - } - return payload as LookupAccountTransactions + return AlgorandSerializer.decode(payload, LookupAccountTransactionsMeta, responseFormat) } /** @@ -299,7 +275,7 @@ export class IndexerApi { const responseFormat: BodyFormat = 'json' headers['Accept'] = IndexerApi.acceptFor(responseFormat) - const payload = await this.httpRequest.request({ + const payload = await this.httpRequest.request({ method: 'GET', url: '/v2/applications/{application-id}/box', path: { 'application-id': typeof applicationId === 'bigint' ? applicationId.toString() : applicationId }, @@ -309,11 +285,7 @@ export class IndexerApi { mediaType: undefined, }) - const responseMeta = BoxMeta - if (responseMeta) { - return AlgorandSerializer.decode(payload, responseMeta, responseFormat) - } - return payload as Box + return AlgorandSerializer.decode(payload, BoxMeta, responseFormat) } /** @@ -324,7 +296,7 @@ export class IndexerApi { const responseFormat: BodyFormat = 'json' headers['Accept'] = IndexerApi.acceptFor(responseFormat) - const payload = await this.httpRequest.request({ + const payload = await this.httpRequest.request({ method: 'GET', url: '/v2/applications/{application-id}', path: { 'application-id': typeof applicationId === 'bigint' ? applicationId.toString() : applicationId }, @@ -334,11 +306,7 @@ export class IndexerApi { mediaType: undefined, }) - const responseMeta = LookupApplicationByIdMeta - if (responseMeta) { - return AlgorandSerializer.decode(payload, responseMeta, responseFormat) - } - return payload as LookupApplicationById + return AlgorandSerializer.decode(payload, LookupApplicationByIdMeta, responseFormat) } /** @@ -359,7 +327,7 @@ export class IndexerApi { const responseFormat: BodyFormat = 'json' headers['Accept'] = IndexerApi.acceptFor(responseFormat) - const payload = await this.httpRequest.request({ + const payload = await this.httpRequest.request({ method: 'GET', url: '/v2/applications/{application-id}/logs', path: { 'application-id': typeof applicationId === 'bigint' ? applicationId.toString() : applicationId }, @@ -376,11 +344,7 @@ export class IndexerApi { mediaType: undefined, }) - const responseMeta = LookupApplicationLogsByIdMeta - if (responseMeta) { - return AlgorandSerializer.decode(payload, responseMeta, responseFormat) - } - return payload as LookupApplicationLogsById + return AlgorandSerializer.decode(payload, LookupApplicationLogsByIdMeta, responseFormat) } /** @@ -400,7 +364,7 @@ export class IndexerApi { const responseFormat: BodyFormat = 'json' headers['Accept'] = IndexerApi.acceptFor(responseFormat) - const payload = await this.httpRequest.request({ + const payload = await this.httpRequest.request({ method: 'GET', url: '/v2/assets/{asset-id}/balances', path: { 'asset-id': typeof assetId === 'bigint' ? assetId.toString() : assetId }, @@ -420,11 +384,7 @@ export class IndexerApi { mediaType: undefined, }) - const responseMeta = LookupAssetBalancesMeta - if (responseMeta) { - return AlgorandSerializer.decode(payload, responseMeta, responseFormat) - } - return payload as LookupAssetBalances + return AlgorandSerializer.decode(payload, LookupAssetBalancesMeta, responseFormat) } /** @@ -435,7 +395,7 @@ export class IndexerApi { const responseFormat: BodyFormat = 'json' headers['Accept'] = IndexerApi.acceptFor(responseFormat) - const payload = await this.httpRequest.request({ + const payload = await this.httpRequest.request({ method: 'GET', url: '/v2/assets/{asset-id}', path: { 'asset-id': typeof assetId === 'bigint' ? assetId.toString() : assetId }, @@ -445,11 +405,7 @@ export class IndexerApi { mediaType: undefined, }) - const responseMeta = LookupAssetByIdMeta - if (responseMeta) { - return AlgorandSerializer.decode(payload, responseMeta, responseFormat) - } - return payload as LookupAssetById + return AlgorandSerializer.decode(payload, LookupAssetByIdMeta, responseFormat) } /** @@ -481,7 +437,7 @@ export class IndexerApi { const responseFormat: BodyFormat = 'json' headers['Accept'] = IndexerApi.acceptFor(responseFormat) - const payload = await this.httpRequest.request({ + const payload = await this.httpRequest.request({ method: 'GET', url: '/v2/assets/{asset-id}/transactions', path: { 'asset-id': typeof assetId === 'bigint' ? assetId.toString() : assetId }, @@ -513,11 +469,7 @@ export class IndexerApi { mediaType: undefined, }) - const responseMeta = LookupAssetTransactionsMeta - if (responseMeta) { - return AlgorandSerializer.decode(payload, responseMeta, responseFormat) - } - return payload as LookupAssetTransactions + return AlgorandSerializer.decode(payload, LookupAssetTransactionsMeta, responseFormat) } /** @@ -528,7 +480,7 @@ export class IndexerApi { const responseFormat: BodyFormat = 'json' headers['Accept'] = IndexerApi.acceptFor(responseFormat) - const payload = await this.httpRequest.request({ + const payload = await this.httpRequest.request({ method: 'GET', url: '/v2/blocks/{round-number}', path: { 'round-number': typeof roundNumber === 'bigint' ? roundNumber.toString() : roundNumber }, @@ -538,11 +490,7 @@ export class IndexerApi { mediaType: undefined, }) - const responseMeta = BlockMeta - if (responseMeta) { - return AlgorandSerializer.decode(payload, responseMeta, responseFormat) - } - return payload as Block + return AlgorandSerializer.decode(payload, BlockMeta, responseFormat) } /** @@ -553,7 +501,7 @@ export class IndexerApi { const responseFormat: BodyFormat = 'json' headers['Accept'] = IndexerApi.acceptFor(responseFormat) - const payload = await this.httpRequest.request({ + const payload = await this.httpRequest.request({ method: 'GET', url: '/v2/transactions/{txid}', path: { txid: txid }, @@ -563,11 +511,7 @@ export class IndexerApi { mediaType: undefined, }) - const responseMeta = LookupTransactionMeta - if (responseMeta) { - return AlgorandSerializer.decode(payload, responseMeta, responseFormat) - } - return payload as LookupTransaction + return AlgorandSerializer.decode(payload, LookupTransactionMeta, responseFormat) } async makeHealthCheck(): Promise { @@ -575,7 +519,7 @@ export class IndexerApi { const responseFormat: BodyFormat = 'json' headers['Accept'] = IndexerApi.acceptFor(responseFormat) - const payload = await this.httpRequest.request({ + const payload = await this.httpRequest.request({ method: 'GET', url: '/health', path: {}, @@ -585,11 +529,7 @@ export class IndexerApi { mediaType: undefined, }) - const responseMeta = HealthCheckMeta - if (responseMeta) { - return AlgorandSerializer.decode(payload, responseMeta, responseFormat) - } - return payload as HealthCheck + return AlgorandSerializer.decode(payload, HealthCheckMeta, responseFormat) } /** @@ -612,7 +552,7 @@ export class IndexerApi { const responseFormat: BodyFormat = 'json' headers['Accept'] = IndexerApi.acceptFor(responseFormat) - const payload = await this.httpRequest.request({ + const payload = await this.httpRequest.request({ method: 'GET', url: '/v2/accounts', path: {}, @@ -638,11 +578,7 @@ export class IndexerApi { mediaType: undefined, }) - const responseMeta = SearchForAccountsMeta - if (responseMeta) { - return AlgorandSerializer.decode(payload, responseMeta, responseFormat) - } - return payload as SearchForAccounts + return AlgorandSerializer.decode(payload, SearchForAccountsMeta, responseFormat) } /** @@ -656,7 +592,7 @@ export class IndexerApi { const responseFormat: BodyFormat = 'json' headers['Accept'] = IndexerApi.acceptFor(responseFormat) - const payload = await this.httpRequest.request({ + const payload = await this.httpRequest.request({ method: 'GET', url: '/v2/applications/{application-id}/boxes', path: { 'application-id': typeof applicationId === 'bigint' ? applicationId.toString() : applicationId }, @@ -666,11 +602,7 @@ export class IndexerApi { mediaType: undefined, }) - const responseMeta = SearchForApplicationBoxesMeta - if (responseMeta) { - return AlgorandSerializer.decode(payload, responseMeta, responseFormat) - } - return payload as SearchForApplicationBoxes + return AlgorandSerializer.decode(payload, SearchForApplicationBoxesMeta, responseFormat) } /** @@ -687,7 +619,7 @@ export class IndexerApi { const responseFormat: BodyFormat = 'json' headers['Accept'] = IndexerApi.acceptFor(responseFormat) - const payload = await this.httpRequest.request({ + const payload = await this.httpRequest.request({ method: 'GET', url: '/v2/applications', path: {}, @@ -703,11 +635,7 @@ export class IndexerApi { mediaType: undefined, }) - const responseMeta = SearchForApplicationsMeta - if (responseMeta) { - return AlgorandSerializer.decode(payload, responseMeta, responseFormat) - } - return payload as SearchForApplications + return AlgorandSerializer.decode(payload, SearchForApplicationsMeta, responseFormat) } /** @@ -726,7 +654,7 @@ export class IndexerApi { const responseFormat: BodyFormat = 'json' headers['Accept'] = IndexerApi.acceptFor(responseFormat) - const payload = await this.httpRequest.request({ + const payload = await this.httpRequest.request({ method: 'GET', url: '/v2/assets', path: {}, @@ -744,11 +672,7 @@ export class IndexerApi { mediaType: undefined, }) - const responseMeta = SearchForAssetsMeta - if (responseMeta) { - return AlgorandSerializer.decode(payload, responseMeta, responseFormat) - } - return payload as SearchForAssets + return AlgorandSerializer.decode(payload, SearchForAssetsMeta, responseFormat) } /** @@ -769,7 +693,7 @@ export class IndexerApi { const responseFormat: BodyFormat = 'json' headers['Accept'] = IndexerApi.acceptFor(responseFormat) - const payload = await this.httpRequest.request({ + const payload = await this.httpRequest.request({ method: 'GET', url: '/v2/block-headers', path: {}, @@ -789,11 +713,7 @@ export class IndexerApi { mediaType: undefined, }) - const responseMeta = SearchForBlockHeadersMeta - if (responseMeta) { - return AlgorandSerializer.decode(payload, responseMeta, responseFormat) - } - return payload as SearchForBlockHeaders + return AlgorandSerializer.decode(payload, SearchForBlockHeadersMeta, responseFormat) } /** @@ -825,7 +745,7 @@ export class IndexerApi { const responseFormat: BodyFormat = 'json' headers['Accept'] = IndexerApi.acceptFor(responseFormat) - const payload = await this.httpRequest.request({ + const payload = await this.httpRequest.request({ method: 'GET', url: '/v2/transactions', path: {}, @@ -860,10 +780,6 @@ export class IndexerApi { mediaType: undefined, }) - const responseMeta = SearchForTransactionsMeta - if (responseMeta) { - return AlgorandSerializer.decode(payload, responseMeta, responseFormat) - } - return payload as SearchForTransactions + return AlgorandSerializer.decode(payload, SearchForTransactionsMeta, responseFormat) } } diff --git a/packages/indexer_client/src/core/codecs.ts b/packages/indexer_client/src/core/codecs.ts index 829dcd7b..214a543d 100644 --- a/packages/indexer_client/src/core/codecs.ts +++ b/packages/indexer_client/src/core/codecs.ts @@ -1,42 +1,28 @@ import { decode as msgpackDecode, encode as msgpackEncode } from 'algorand-msgpack' -export function encodeMsgPack(data: T): Uint8Array { +export function encodeMsgPack(data: ApiData): Uint8Array { return new Uint8Array(msgpackEncode(data, { sortKeys: true, ignoreUndefined: true })) } -export function decodeMsgPack(buffer: Uint8Array): T { - const map = msgpackDecode(buffer, { useMap: true }) as unknown - return mapToObject(map) as T +type MsgPackDecodeOptions = { + useMap: boolean + rawBinaryStringKeys: boolean + rawBinaryStringValues: boolean } -/** - * Converts a Map structure from msgpack decoding to a plain object structure. - * Maps are converted to objects recursively, except for the special case - * where the field name is "r" which remains as a Map. - */ -function mapToObject(value: unknown, fieldName?: string): unknown { - // Preserve Uint8Array as-is - if (value instanceof Uint8Array) { - return value - } else if (value instanceof Map) { - // Special case: keep "r" field as Map - if (fieldName === 'r') { - const newMap = new Map() - for (const [k, v] of value.entries()) { - newMap.set(k, mapToObject(v)) - } - return newMap - } - - // Convert Map to object - const obj: Record = {} - for (const [k, v] of value.entries()) { - obj[k] = mapToObject(v, k) - } - return obj - } else if (Array.isArray(value)) { - return value.map((item) => mapToObject(item)) - } - - return value +export function decodeMsgPack( + buffer: Uint8Array, + options: MsgPackDecodeOptions = { useMap: true, rawBinaryStringKeys: true, rawBinaryStringValues: true }, +): Map { + return msgpackDecode(buffer, options) as Map } +export type ApiData = + | null + | undefined + | string + | number + | bigint + | boolean + | Uint8Array + | object + | Map // TODO: NC - Do we ever have a string key? diff --git a/packages/indexer_client/src/core/model-runtime.ts b/packages/indexer_client/src/core/model-runtime.ts index ed41c188..07f6d681 100644 --- a/packages/indexer_client/src/core/model-runtime.ts +++ b/packages/indexer_client/src/core/model-runtime.ts @@ -1,19 +1,24 @@ import { - encodeSignedTransaction as transactEncodeSignedTransaction, - decodeSignedTransaction as transactDecodeSignedTransaction, + addressFromPublicKey, + decodedTransactionMapToObject, + fromSignedTransactionDto, + toSignedTransactionDto, type SignedTransaction, } from '@algorandfoundation/algokit-transact' -import { encodeMsgPack, decodeMsgPack } from './codecs' -import { toBase64, fromBase64 } from './serialization' +import { Buffer } from 'buffer' +import { ApiData, decodeMsgPack, encodeMsgPack } from './codecs' export type BodyFormat = 'json' | 'msgpack' | 'map' export interface ScalarFieldType { readonly kind: 'scalar' + // TODO: NC - Make this a type field readonly isBytes?: boolean readonly isBigint?: boolean + readonly isAddress?: boolean } +// TODO: NC - Needs to be renamed export interface CodecFieldType { readonly kind: 'codec' readonly codecKey: string @@ -34,7 +39,13 @@ export interface RecordFieldType { readonly value: FieldType } -export type FieldType = ScalarFieldType | CodecFieldType | ModelFieldType | ArrayFieldType | RecordFieldType +export interface MapFieldType { + readonly kind: 'map' + readonly keyType: 'number' | 'bigint' | 'bytes' + readonly value: FieldType +} + +export type FieldType = ScalarFieldType | CodecFieldType | ModelFieldType | ArrayFieldType | RecordFieldType | MapFieldType export interface FieldMetadata { readonly name: string @@ -61,39 +72,26 @@ export interface ModelMetadata { readonly passThrough?: FieldType } -// Registry for model metadata to avoid direct circular imports between model files -const modelMetaRegistry = new Map() - -export function registerModelMeta(name: string, meta: ModelMetadata): void { - modelMetaRegistry.set(name, meta) -} - -export function getModelMeta(name: string): ModelMetadata { - const meta = modelMetaRegistry.get(name) - if (!meta) throw new Error(`Model metadata not registered: ${name}`) - return meta -} - -export interface TypeCodec { - encode(value: TValue, format: BodyFormat): unknown - decode(value: unknown, format: BodyFormat): TValue +export interface EncodeableTypeConverter> { + beforeEncoding(value: T, format: BodyFormat): Record + afterDecoding(decoded: Record | Map, format: BodyFormat): T } -const codecRegistry = new Map>() - -export function registerCodec(key: string, codec: TypeCodec): void { - codecRegistry.set(key, codec as TypeCodec) -} +const encodeableTypeConverterRegistry = new Map>>() -export function getCodec(key: string): TypeCodec | undefined { - return codecRegistry.get(key) as TypeCodec | undefined +export function registerEncodeableTypeConverter(key: string, codec: EncodeableTypeConverter>): void { + encodeableTypeConverterRegistry.set(key, codec) } export class AlgorandSerializer { - static encode(value: unknown, meta: ModelMetadata, format: 'map'): Map - static encode(value: unknown, meta: ModelMetadata, format: 'json'): string - static encode(value: unknown, meta: ModelMetadata, format?: 'msgpack'): Uint8Array - static encode(value: unknown, meta: ModelMetadata, format: BodyFormat = 'msgpack'): Uint8Array | string | Map { + static encode(value: Record, meta: ModelMetadata, format: 'map'): Map + static encode(value: Record, meta: ModelMetadata, format: 'json'): string + static encode(value: Record, meta: ModelMetadata, format?: 'msgpack'): Uint8Array + static encode( + value: Record, + meta: ModelMetadata, + format: BodyFormat = 'msgpack', + ): Uint8Array | string | Map { if (format === 'map') { // For map format, use msgpack transformation to preserve types like bigint, then convert to nested Maps const wire = this.transform(value, meta, { direction: 'encode', format: 'msgpack' }) @@ -107,25 +105,25 @@ export class AlgorandSerializer { return typeof wire === 'string' ? wire : JSON.stringify(wire) } - static decode(payload: unknown, meta: ModelMetadata, format: BodyFormat = 'msgpack'): T { - let wire: unknown = payload + static decode(value: Uint8Array | string, meta: ModelMetadata, format: BodyFormat = 'msgpack'): T { + let wire: ApiData = value if (format === 'msgpack') { - if (payload instanceof Uint8Array) { - wire = decodeMsgPack(payload) + if (value instanceof Uint8Array) { + wire = decodeMsgPack(value) } - } else if (typeof payload === 'string') { - wire = JSON.parse(payload) + } else if (typeof value === 'string') { + wire = JSON.parse(value) } return this.transform(wire, meta, { direction: 'decode', format }) as T } - private static transform(value: unknown, meta: ModelMetadata, ctx: TransformContext): unknown { + private static transform(value: ApiData, meta: ModelMetadata, ctx: TransformContext): ApiData { if (value === undefined || value === null) { return value } if (meta.codecKey) { - return this.applyCodec(value, meta.codecKey, ctx) + return this.applyEncodeableTypeConversion(value, meta.codecKey, ctx) } switch (meta.kind) { @@ -139,20 +137,20 @@ export class AlgorandSerializer { } } - private static transformObject(value: unknown, meta: ModelMetadata, ctx: TransformContext): unknown { + private static transformObject(value: ApiData, meta: ModelMetadata, ctx: TransformContext): ApiData { const fields = meta.fields ?? [] - const hasFlattenedSignedTxn = fields.some((f) => f.flattened && f.type.kind === 'codec' && f.type.codecKey === 'SignedTransaction') + const hasFlattenedField = fields.some((f) => f.flattened) if (ctx.direction === 'encode') { - const src = value as Record - const out: Record = {} + const src = value as Record + const out: Record = {} for (const field of fields) { const fieldValue = src[field.name] if (fieldValue === undefined) continue const encoded = this.transformType(fieldValue, field.type, ctx) if (encoded === undefined && fieldValue === undefined) continue - if (field.flattened && field.type.kind === 'codec' && field.type.codecKey === 'SignedTransaction') { - // Merge signed transaction map into parent - const mapValue = encoded as Record + if (field.flattened) { + // Merge flattened field into parent + const mapValue = encoded as Record for (const [k, v] of Object.entries(mapValue ?? {})) out[k] = v continue } @@ -167,66 +165,206 @@ export class AlgorandSerializer { return out } - const src = value as Record - const out: Record = {} + // Decoding + const out: Record = {} const fieldByWire = new Map(fields.filter((f) => !!f.wireKey).map((field) => [field.wireKey as string, field])) - for (const [wireKey, wireValue] of Object.entries(src)) { - const field = fieldByWire.get(wireKey) + // Build a map of wire keys for each flattened field + const flattenedFieldWireKeys = new Map>() + if (hasFlattenedField) { + for (const field of fields) { + if (field.flattened && field.type.kind === 'model') { + const modelMeta = typeof field.type.meta === 'function' ? field.type.meta() : field.type.meta + const wireKeys = this.collectWireKeys(modelMeta) + flattenedFieldWireKeys.set(field, wireKeys) + } + } + } + + const entries = value instanceof Map ? Array.from(value.entries()) : Object.entries(value as Record) + const unmatchedEntries = new Map() + + for (const [key, wireValue] of entries) { + const wireKey = key instanceof Uint8Array ? Buffer.from(key).toString('utf-8') : key + const isStringKey = typeof wireKey === 'string' + const field = isStringKey ? fieldByWire.get(wireKey) : undefined + if (field) { const decoded = this.transformType(wireValue, field.type, ctx) - out[field.name] = decoded + out[field.name] = decoded === null && !field.nullable ? undefined : decoded continue } - if (meta.additionalProperties) { + + if (isStringKey && meta.additionalProperties) { out[wireKey] = this.transformType(wireValue, meta.additionalProperties, ctx) continue } - // If we have a flattened SignedTransaction, skip unknown keys (e.g., 'sig', 'txn') - if (!hasFlattenedSignedTxn) { - out[wireKey] = wireValue + + // Store unmatched entries for potential flattened field reconstruction + if (isStringKey) { + unmatchedEntries.set(wireKey, wireValue) + } + } + + // Reconstruct flattened fields from unmatched entries + if (hasFlattenedField) { + for (const field of fields) { + if (out[field.name] !== undefined) continue + if (field.flattened) { + if (field.type.kind === 'codec') { + // Reconstruct codec from entire object map + out[field.name] = this.applyEncodeableTypeConversion(value, field.type.codecKey, ctx) + } else if (field.type.kind === 'model') { + const modelMeta = typeof field.type.meta === 'function' ? field.type.meta() : field.type.meta + + // Check if this flattened model contains nested flattened codecs + const hasNestedCodec = this.hasNestedFlattenedCodec(modelMeta) + + let decoded: ApiData + if (hasNestedCodec) { + // If the model has nested flattened codecs, we need to pass the original value + // so the nested model can reconstruct its flattened codec fields + decoded = this.transform(value, modelMeta, ctx) + } else { + // Filter the wire data to only include keys belonging to this flattened model + const modelWireKeys = flattenedFieldWireKeys.get(field) + if (modelWireKeys) { + const filteredData: Record = {} + for (const [k, v] of unmatchedEntries.entries()) { + if (modelWireKeys.has(k)) { + filteredData[k] = v + } + } + // Also check if the original value is a Map and filter it + if (value instanceof Map) { + const filteredMap = new Map() + for (const [k, v] of value.entries()) { + const keyStr = k instanceof Uint8Array ? Buffer.from(k).toString('utf-8') : String(k) + if (typeof keyStr === 'string' && modelWireKeys.has(keyStr)) { + filteredMap.set(k as string | Uint8Array, v) + } + } + decoded = this.transform(filteredMap, modelMeta, ctx) + } else { + decoded = this.transform(filteredData, modelMeta, ctx) + } + } else { + decoded = undefined + } + } + + // If the field is optional and the decoded object is empty, set it to undefined + if (field.optional && decoded !== undefined && this.isEmptyObject(decoded)) { + out[field.name] = undefined + } else { + out[field.name] = decoded + } + } + } } } - // If there are flattened fields, attempt to reconstruct them from remaining keys by decoding - for (const field of fields) { - if (out[field.name] !== undefined) continue - if (field.flattened && field.type.kind === 'codec' && field.type.codecKey === 'SignedTransaction') { - // Reconstruct from entire object map - out[field.name] = this.applyCodec(src, 'SignedTransaction', ctx) + // Add any remaining unmatched entries if there are no flattened fields + if (!hasFlattenedField) { + for (const [k, v] of unmatchedEntries.entries()) { + out[k] = v } } return out } - private static transformType(value: unknown, type: FieldType, ctx: TransformContext): unknown { + private static collectWireKeys(meta: ModelMetadata): Set { + const wireKeys = new Set() + if (meta.kind !== 'object' || !meta.fields) return wireKeys + + for (const field of meta.fields) { + if (field.wireKey) { + wireKeys.add(field.wireKey) + } + if (field.flattened && field.type.kind === 'model') { + const childMeta = typeof field.type.meta === 'function' ? field.type.meta() : field.type.meta + const childKeys = this.collectWireKeys(childMeta) + for (const key of childKeys) { + wireKeys.add(key) + } + } + // Note: flattened codec fields don't have predictable wire keys, + // so they need to be handled differently during reconstruction + } + + return wireKeys + } + + private static hasNestedFlattenedCodec(meta: ModelMetadata): boolean { + if (meta.kind !== 'object' || !meta.fields) return false + + for (const field of meta.fields) { + if (field.flattened) { + if (field.type.kind === 'codec') { + return true + } + if (field.type.kind === 'model') { + const childMeta = typeof field.type.meta === 'function' ? field.type.meta() : field.type.meta + if (this.hasNestedFlattenedCodec(childMeta)) { + return true + } + } + } + } + + return false + } + + private static isEmptyObject(value: ApiData): boolean { + if (value === null || value === undefined) return true + if (typeof value !== 'object') return false + if (Array.isArray(value)) return false + if (value instanceof Uint8Array) return false + if (value instanceof Map) return value.size === 0 + + // Check if it's a plain object with no own properties (excluding undefined values) + const keys = Object.keys(value) + if (keys.length === 0) return true + + // Check if all properties are undefined + return keys.every((key) => (value as Record)[key] === undefined) + } + + private static transformType(value: ApiData, type: FieldType, ctx: TransformContext): ApiData { if (value === undefined || value === null) return value switch (type.kind) { case 'scalar': return this.transformScalar(value, type, ctx) case 'codec': - return this.applyCodec(value, type.codecKey, ctx) + return this.applyEncodeableTypeConversion(value, type.codecKey, ctx) case 'model': return this.transform(value, typeof type.meta === 'function' ? type.meta() : type.meta, ctx) case 'array': if (!Array.isArray(value)) return value return value.map((item) => this.transformType(item, type.item, ctx)) - case 'record': - if (typeof value !== 'object' || value === null) return value + case 'record': { + if ((!(value instanceof Map) && typeof value !== 'object') || value === null) return value + const entries = value instanceof Map ? Array.from(value.entries()) : Object.entries(value as Record) return Object.fromEntries( - Object.entries(value as Record).map(([k, v]) => [k, this.transformType(v, type.value, ctx)]), + entries.map(([k, v]) => { + const key = k instanceof Uint8Array ? Buffer.from(k).toString('utf-8') : k + return [key, this.transformType(v, type.value, ctx)] + }), ) + } + case 'map': + return this.transformMap(value, type, ctx) default: return value } } - private static transformScalar(value: unknown, meta: ScalarFieldType, ctx: TransformContext): unknown { + private static transformScalar(value: ApiData, meta: ScalarFieldType, ctx: TransformContext): ApiData { if (ctx.direction === 'encode') { if (meta.isBytes && ctx.format === 'json') { - if (value instanceof Uint8Array) return toBase64(value) + if (value instanceof Uint8Array) return Buffer.from(value).toString('base64') } if (meta.isBigint && ctx.format === 'json') { if (typeof value === 'bigint') return value.toString() @@ -237,7 +375,17 @@ export class AlgorandSerializer { } if (meta.isBytes && ctx.format === 'json' && typeof value === 'string') { - return fromBase64(value) + return new Uint8Array(Buffer.from(value, 'base64')) + } + + if (value instanceof Uint8Array) { + if (meta.isAddress) { + // TODO: NC - Fix all the address models to have this on it. + return addressFromPublicKey(value) + } else if (!meta.isBytes) { + return Buffer.from(value).toString('utf-8') + } + return value } if (meta.isBigint) { @@ -253,18 +401,61 @@ export class AlgorandSerializer { } } + if (value instanceof Map) { + const out: Record = {} + for (const [k, v] of value.entries()) { + const key = k instanceof Uint8Array ? Buffer.from(k).toString('utf-8') : k.toString() + out[key] = this.transformType(v, { kind: 'scalar', isBytes: v instanceof Uint8Array }, ctx) + } + return out + } + + if (Array.isArray(value)) { + return value.map((item) => this.transformType(item, { kind: 'scalar', isBytes: item instanceof Uint8Array }, ctx)) + } + return value } - private static applyCodec(value: unknown, codecKey: string, ctx: TransformContext): unknown { - const codec = codecRegistry.get(codecKey) + private static applyEncodeableTypeConversion(value: ApiData, typeKey: string, ctx: TransformContext): ApiData { + const codec = encodeableTypeConverterRegistry.get(typeKey) if (!codec) { - throw new Error(`Codec for "${codecKey}" is not registered`) + throw new Error(`Type converter for "${typeKey}" is not registered`) + } + + // TODO: NC - Need to properly guard against these conditions + if (ctx.direction === 'encode') { + if (value instanceof Map) { + throw new Error(`Cannot encode Map with type converter "${typeKey}"`) + } + return codec.beforeEncoding(value as Parameters[0], ctx.format) } - return ctx.direction === 'encode' ? codec.encode(value, ctx.format) : codec.decode(value, ctx.format) + + return codec.afterDecoding(value as Parameters[0], ctx.format) } - private static convertToNestedMaps(value: unknown): Map | unknown[] | unknown { + private static transformMap(value: ApiData, meta: MapFieldType, ctx: TransformContext): ApiData { + if (ctx.direction === 'encode') { + if (!(value instanceof Map)) return value + const result = new Map() + for (const [k, v] of value.entries()) { + const transformedValue = this.transformType(v, meta.value, ctx) + result.set(k, transformedValue) + } + return result + } + // Decoding + if ((!(value instanceof Map) && typeof value !== 'object') || value === null) return value + const entries = value instanceof Map ? Array.from(value.entries()) : Object.entries(value as Record) + const result = new Map() + for (const [k, v] of entries) { + const transformedValue = this.transformType(v, meta.value, ctx) + result.set(k, transformedValue) + } + return result + } + + private static convertToNestedMaps(value: ApiData): Map | ApiData[] | ApiData { if (value === null || value === undefined) { return value } @@ -282,8 +473,8 @@ export class AlgorandSerializer { } if (typeof value === 'object' && value !== null && !(value instanceof Uint8Array)) { - const map = new Map() - Object.entries(value as Record).forEach(([key, val]) => { + const map = new Map() + Object.entries(value as Record).forEach(([key, val]) => { map.set(key, this.convertToNestedMaps(val)) }) return map @@ -301,39 +492,23 @@ interface TransformContext { readonly format: BodyFormat } -const encodeSignedTransactionImpl = (value: unknown): Uint8Array => transactEncodeSignedTransaction(value as SignedTransaction) -const decodeSignedTransactionImpl = (value: Uint8Array): SignedTransaction => transactDecodeSignedTransaction(value) - -class SignedTransactionCodec implements TypeCodec { - encode(value: unknown, format: BodyFormat): unknown { - if (value == null) return value +class SignedTransactionConverter implements EncodeableTypeConverter { + beforeEncoding(value: SignedTransaction, format: BodyFormat): Record { if (format === 'json') { - if (value instanceof Uint8Array) return toBase64(value) - return toBase64(encodeSignedTransactionImpl(value)) - } - if (value instanceof Uint8Array) { - // Already canonical bytes; decode to structured map so parent encoding keeps map semantics - return decodeMsgPack(value) + throw new Error('JSON format not supported for SignedTransaction encoding') } - // Convert signed transaction object into canonical map representation - return decodeMsgPack(encodeSignedTransactionImpl(value)) + return toSignedTransactionDto(value) } - - decode(value: unknown, format: BodyFormat): unknown { - if (value == null) return value - if (format === 'json') { - if (typeof value === 'string') return decodeSignedTransactionImpl(fromBase64(value)) - if (value instanceof Uint8Array) return decodeSignedTransactionImpl(value) - return value + afterDecoding(value: Record | Map, format: BodyFormat): SignedTransaction { + if (format === 'json' || !(value instanceof Map)) { + throw new Error('JSON format not supported for SignedTransaction decoding') } - if (value instanceof Uint8Array) return decodeSignedTransactionImpl(value) - // Value is a decoded map; re-encode to bytes before handing to transact decoder - try { - return decodeSignedTransactionImpl(encodeMsgPack(value)) - } catch { - return value + if (!(value instanceof Map)) { + throw new Error('Invalid decoded msgpack format for SignedTransaction') } + const stxnDto = decodedTransactionMapToObject(value) as Parameters[0] + return fromSignedTransactionDto(stxnDto) } } -registerCodec('SignedTransaction', new SignedTransactionCodec()) +registerEncodeableTypeConverter('SignedTransaction', new SignedTransactionConverter()) diff --git a/packages/indexer_client/src/core/request.ts b/packages/indexer_client/src/core/request.ts index a8a88eff..9b43c0ac 100644 --- a/packages/indexer_client/src/core/request.ts +++ b/packages/indexer_client/src/core/request.ts @@ -85,7 +85,11 @@ export async function request( try { const ct = response.headers.get('content-type') ?? '' if (ct.includes('application/msgpack')) { - errorBody = decodeMsgPack(new Uint8Array(await response.arrayBuffer())) + errorBody = decodeMsgPack(new Uint8Array(await response.arrayBuffer()), { + useMap: false, + rawBinaryStringKeys: false, + rawBinaryStringValues: false, + }) } else if (ct.includes('application/json')) { errorBody = JSON.parse(await response.text()) } else { diff --git a/packages/indexer_client/src/core/serialization.ts b/packages/indexer_client/src/core/serialization.ts deleted file mode 100644 index 6be05428..00000000 --- a/packages/indexer_client/src/core/serialization.ts +++ /dev/null @@ -1,26 +0,0 @@ -export function toBase64(bytes: Uint8Array): string { - if (typeof Buffer !== 'undefined') { - return Buffer.from(bytes).toString('base64') - } - const globalRef: Record = globalThis as unknown as Record - const btoaFn = globalRef.btoa as ((value: string) => string) | undefined - if (typeof btoaFn === 'function') { - return btoaFn(String.fromCharCode(...bytes)) - } - throw new Error('Base64 encoding not supported in this environment') -} - -export function fromBase64(s: string): Uint8Array { - if (typeof Buffer !== 'undefined') { - return new Uint8Array(Buffer.from(s, 'base64')) - } - const globalRef: Record = globalThis as unknown as Record - const atobFn = globalRef.atob as ((value: string) => string) | undefined - if (typeof atobFn === 'function') { - const bin = atobFn(s) - const out = new Uint8Array(bin.length) - for (let i = 0; i < bin.length; i += 1) out[i] = bin.charCodeAt(i) - return out - } - throw new Error('Base64 decoding not supported in this environment') -} diff --git a/packages/indexer_client/src/index.ts b/packages/indexer_client/src/index.ts index 915506d5..58a6412d 100644 --- a/packages/indexer_client/src/index.ts +++ b/packages/indexer_client/src/index.ts @@ -2,7 +2,6 @@ export * from './core/client-config' export * from './core/base-http-request' export * from './core/fetch-http-request' export * from './core/api-error' -export * from './core/serialization' export * from './core/codecs' export * from './core/model-runtime' diff --git a/packages/indexer_client/src/models/account-state-delta.ts b/packages/indexer_client/src/models/account-state-delta.ts index ad93395f..d7ddbc6a 100644 --- a/packages/indexer_client/src/models/account-state-delta.ts +++ b/packages/indexer_client/src/models/account-state-delta.ts @@ -26,7 +26,7 @@ export const AccountStateDeltaMeta: ModelMetadata = { wireKey: 'delta', optional: false, nullable: false, - type: { kind: 'model', meta: () => StateDeltaMeta }, + type: { kind: 'model', meta: StateDeltaMeta }, }, ], } diff --git a/packages/indexer_client/src/models/account.ts b/packages/indexer_client/src/models/account.ts index 3323de01..b22108b8 100644 --- a/packages/indexer_client/src/models/account.ts +++ b/packages/indexer_client/src/models/account.ts @@ -216,14 +216,14 @@ export const AccountMeta: ModelMetadata = { wireKey: 'apps-local-state', optional: true, nullable: false, - type: { kind: 'array', item: { kind: 'model', meta: () => ApplicationLocalStateMeta } }, + type: { kind: 'array', item: { kind: 'model', meta: ApplicationLocalStateMeta } }, }, { name: 'appsTotalSchema', wireKey: 'apps-total-schema', optional: true, nullable: false, - type: { kind: 'model', meta: () => ApplicationStateSchemaMeta }, + type: { kind: 'model', meta: ApplicationStateSchemaMeta }, }, { name: 'appsTotalExtraPages', @@ -237,28 +237,28 @@ export const AccountMeta: ModelMetadata = { wireKey: 'assets', optional: true, nullable: false, - type: { kind: 'array', item: { kind: 'model', meta: () => AssetHoldingMeta } }, + type: { kind: 'array', item: { kind: 'model', meta: AssetHoldingMeta } }, }, { name: 'createdApps', wireKey: 'created-apps', optional: true, nullable: false, - type: { kind: 'array', item: { kind: 'model', meta: () => ApplicationMeta } }, + type: { kind: 'array', item: { kind: 'model', meta: ApplicationMeta } }, }, { name: 'createdAssets', wireKey: 'created-assets', optional: true, nullable: false, - type: { kind: 'array', item: { kind: 'model', meta: () => AssetMeta } }, + type: { kind: 'array', item: { kind: 'model', meta: AssetMeta } }, }, { name: 'participation', wireKey: 'participation', optional: true, nullable: false, - type: { kind: 'model', meta: () => AccountParticipationMeta }, + type: { kind: 'model', meta: AccountParticipationMeta }, }, { name: 'incentiveEligible', diff --git a/packages/indexer_client/src/models/application-local-state.ts b/packages/indexer_client/src/models/application-local-state.ts index 5de31364..a1f72cc4 100644 --- a/packages/indexer_client/src/models/application-local-state.ts +++ b/packages/indexer_client/src/models/application-local-state.ts @@ -68,14 +68,14 @@ export const ApplicationLocalStateMeta: ModelMetadata = { wireKey: 'schema', optional: false, nullable: false, - type: { kind: 'model', meta: () => ApplicationStateSchemaMeta }, + type: { kind: 'model', meta: ApplicationStateSchemaMeta }, }, { name: 'keyValue', wireKey: 'key-value', optional: true, nullable: false, - type: { kind: 'model', meta: () => TealKeyValueStoreMeta }, + type: { kind: 'model', meta: TealKeyValueStoreMeta }, }, ], } diff --git a/packages/indexer_client/src/models/application-params.ts b/packages/indexer_client/src/models/application-params.ts index 093fdb71..8fa1d959 100644 --- a/packages/indexer_client/src/models/application-params.ts +++ b/packages/indexer_client/src/models/application-params.ts @@ -74,21 +74,21 @@ export const ApplicationParamsMeta: ModelMetadata = { wireKey: 'local-state-schema', optional: true, nullable: false, - type: { kind: 'model', meta: () => ApplicationStateSchemaMeta }, + type: { kind: 'model', meta: ApplicationStateSchemaMeta }, }, { name: 'globalStateSchema', wireKey: 'global-state-schema', optional: true, nullable: false, - type: { kind: 'model', meta: () => ApplicationStateSchemaMeta }, + type: { kind: 'model', meta: ApplicationStateSchemaMeta }, }, { name: 'globalState', wireKey: 'global-state', optional: true, nullable: false, - type: { kind: 'model', meta: () => TealKeyValueStoreMeta }, + type: { kind: 'model', meta: TealKeyValueStoreMeta }, }, { name: 'version', diff --git a/packages/indexer_client/src/models/application.ts b/packages/indexer_client/src/models/application.ts index afc04d8f..3bd1fc93 100644 --- a/packages/indexer_client/src/models/application.ts +++ b/packages/indexer_client/src/models/application.ts @@ -65,7 +65,7 @@ export const ApplicationMeta: ModelMetadata = { wireKey: 'params', optional: false, nullable: false, - type: { kind: 'model', meta: () => ApplicationParamsMeta }, + type: { kind: 'model', meta: ApplicationParamsMeta }, }, ], } diff --git a/packages/indexer_client/src/models/asset.ts b/packages/indexer_client/src/models/asset.ts index e5984148..06f49960 100644 --- a/packages/indexer_client/src/models/asset.ts +++ b/packages/indexer_client/src/models/asset.ts @@ -9,7 +9,7 @@ export type Asset = { /** * unique asset identifier */ - index: bigint + id: bigint /** * Whether or not this asset is currently deleted. @@ -33,7 +33,7 @@ export const AssetMeta: ModelMetadata = { kind: 'object', fields: [ { - name: 'index', + name: 'id', wireKey: 'index', optional: false, nullable: false, @@ -65,7 +65,7 @@ export const AssetMeta: ModelMetadata = { wireKey: 'params', optional: false, nullable: false, - type: { kind: 'model', meta: () => AssetParamsMeta }, + type: { kind: 'model', meta: AssetParamsMeta }, }, ], } diff --git a/packages/indexer_client/src/models/block.ts b/packages/indexer_client/src/models/block.ts index aadd3b22..235d9661 100644 --- a/packages/indexer_client/src/models/block.ts +++ b/packages/indexer_client/src/models/block.ts @@ -176,7 +176,7 @@ export const BlockMeta: ModelMetadata = { wireKey: 'rewards', optional: true, nullable: false, - type: { kind: 'model', meta: () => BlockRewardsMeta }, + type: { kind: 'model', meta: BlockRewardsMeta }, }, { name: 'round', @@ -197,7 +197,7 @@ export const BlockMeta: ModelMetadata = { wireKey: 'state-proof-tracking', optional: true, nullable: false, - type: { kind: 'array', item: { kind: 'model', meta: () => StateProofTrackingMeta } }, + type: { kind: 'array', item: { kind: 'model', meta: StateProofTrackingMeta } }, }, { name: 'timestamp', @@ -211,7 +211,7 @@ export const BlockMeta: ModelMetadata = { wireKey: 'transactions', optional: true, nullable: false, - type: { kind: 'array', item: { kind: 'model', meta: () => TransactionMeta } }, + type: { kind: 'array', item: { kind: 'model', meta: TransactionMeta } }, }, { name: 'transactionsRoot', @@ -246,21 +246,21 @@ export const BlockMeta: ModelMetadata = { wireKey: 'upgrade-state', optional: true, nullable: false, - type: { kind: 'model', meta: () => BlockUpgradeStateMeta }, + type: { kind: 'model', meta: BlockUpgradeStateMeta }, }, { name: 'upgradeVote', wireKey: 'upgrade-vote', optional: true, nullable: false, - type: { kind: 'model', meta: () => BlockUpgradeVoteMeta }, + type: { kind: 'model', meta: BlockUpgradeVoteMeta }, }, { name: 'participationUpdates', wireKey: 'participation-updates', optional: true, nullable: false, - type: { kind: 'model', meta: () => ParticipationUpdatesMeta }, + type: { kind: 'model', meta: ParticipationUpdatesMeta }, }, ], } diff --git a/packages/indexer_client/src/models/eval-delta-key-value.ts b/packages/indexer_client/src/models/eval-delta-key-value.ts index 80097c50..59b8f609 100644 --- a/packages/indexer_client/src/models/eval-delta-key-value.ts +++ b/packages/indexer_client/src/models/eval-delta-key-value.ts @@ -26,7 +26,7 @@ export const EvalDeltaKeyValueMeta: ModelMetadata = { wireKey: 'value', optional: false, nullable: false, - type: { kind: 'model', meta: () => EvalDeltaMeta }, + type: { kind: 'model', meta: EvalDeltaMeta }, }, ], } diff --git a/packages/indexer_client/src/models/health-check.ts b/packages/indexer_client/src/models/health-check.ts index 8b4d632c..34695a47 100644 --- a/packages/indexer_client/src/models/health-check.ts +++ b/packages/indexer_client/src/models/health-check.ts @@ -1,5 +1,7 @@ import type { ModelMetadata } from '../core/model-runtime' +const HealthCheckDataMeta: ModelMetadata = { name: 'HealthCheckDataMeta', kind: 'object', fields: [] } + /** * A health check response. */ @@ -32,7 +34,7 @@ export const HealthCheckMeta: ModelMetadata = { wireKey: 'data', optional: true, nullable: false, - type: { kind: 'scalar' }, + type: { kind: 'model', meta: HealthCheckDataMeta }, }, { name: 'round', diff --git a/packages/indexer_client/src/models/lookup-account-app-local-states.ts b/packages/indexer_client/src/models/lookup-account-app-local-states.ts index d5ce57fb..0c6c288c 100644 --- a/packages/indexer_client/src/models/lookup-account-app-local-states.ts +++ b/packages/indexer_client/src/models/lookup-account-app-local-states.ts @@ -25,7 +25,7 @@ export const LookupAccountAppLocalStatesMeta: ModelMetadata = { wireKey: 'apps-local-states', optional: false, nullable: false, - type: { kind: 'array', item: { kind: 'model', meta: () => ApplicationLocalStateMeta } }, + type: { kind: 'array', item: { kind: 'model', meta: ApplicationLocalStateMeta } }, }, { name: 'currentRound', diff --git a/packages/indexer_client/src/models/lookup-account-assets.ts b/packages/indexer_client/src/models/lookup-account-assets.ts index 6c59fccd..dc8f9740 100644 --- a/packages/indexer_client/src/models/lookup-account-assets.ts +++ b/packages/indexer_client/src/models/lookup-account-assets.ts @@ -38,7 +38,7 @@ export const LookupAccountAssetsMeta: ModelMetadata = { wireKey: 'assets', optional: false, nullable: false, - type: { kind: 'array', item: { kind: 'model', meta: () => AssetHoldingMeta } }, + type: { kind: 'array', item: { kind: 'model', meta: AssetHoldingMeta } }, }, ], } diff --git a/packages/indexer_client/src/models/lookup-account-by-id.ts b/packages/indexer_client/src/models/lookup-account-by-id.ts index 5e0e7e0a..caaf10e8 100644 --- a/packages/indexer_client/src/models/lookup-account-by-id.ts +++ b/packages/indexer_client/src/models/lookup-account-by-id.ts @@ -20,7 +20,7 @@ export const LookupAccountByIdMeta: ModelMetadata = { wireKey: 'account', optional: false, nullable: false, - type: { kind: 'model', meta: () => AccountMeta }, + type: { kind: 'model', meta: AccountMeta }, }, { name: 'currentRound', diff --git a/packages/indexer_client/src/models/lookup-account-created-applications.ts b/packages/indexer_client/src/models/lookup-account-created-applications.ts index e7b81b9a..87ee351b 100644 --- a/packages/indexer_client/src/models/lookup-account-created-applications.ts +++ b/packages/indexer_client/src/models/lookup-account-created-applications.ts @@ -25,7 +25,7 @@ export const LookupAccountCreatedApplicationsMeta: ModelMetadata = { wireKey: 'applications', optional: false, nullable: false, - type: { kind: 'array', item: { kind: 'model', meta: () => ApplicationMeta } }, + type: { kind: 'array', item: { kind: 'model', meta: ApplicationMeta } }, }, { name: 'currentRound', diff --git a/packages/indexer_client/src/models/lookup-account-created-assets.ts b/packages/indexer_client/src/models/lookup-account-created-assets.ts index 533c848b..6bc9c7f9 100644 --- a/packages/indexer_client/src/models/lookup-account-created-assets.ts +++ b/packages/indexer_client/src/models/lookup-account-created-assets.ts @@ -25,7 +25,7 @@ export const LookupAccountCreatedAssetsMeta: ModelMetadata = { wireKey: 'assets', optional: false, nullable: false, - type: { kind: 'array', item: { kind: 'model', meta: () => AssetMeta } }, + type: { kind: 'array', item: { kind: 'model', meta: AssetMeta } }, }, { name: 'currentRound', diff --git a/packages/indexer_client/src/models/lookup-account-transactions.ts b/packages/indexer_client/src/models/lookup-account-transactions.ts index dde018d4..0ebdcde6 100644 --- a/packages/indexer_client/src/models/lookup-account-transactions.ts +++ b/packages/indexer_client/src/models/lookup-account-transactions.ts @@ -38,7 +38,7 @@ export const LookupAccountTransactionsMeta: ModelMetadata = { wireKey: 'transactions', optional: false, nullable: false, - type: { kind: 'array', item: { kind: 'model', meta: () => TransactionMeta } }, + type: { kind: 'array', item: { kind: 'model', meta: TransactionMeta } }, }, ], } diff --git a/packages/indexer_client/src/models/lookup-application-by-id.ts b/packages/indexer_client/src/models/lookup-application-by-id.ts index a1ad544c..4c95fda4 100644 --- a/packages/indexer_client/src/models/lookup-application-by-id.ts +++ b/packages/indexer_client/src/models/lookup-application-by-id.ts @@ -20,7 +20,7 @@ export const LookupApplicationByIdMeta: ModelMetadata = { wireKey: 'application', optional: true, nullable: false, - type: { kind: 'model', meta: () => ApplicationMeta }, + type: { kind: 'model', meta: ApplicationMeta }, }, { name: 'currentRound', diff --git a/packages/indexer_client/src/models/lookup-application-logs-by-id.ts b/packages/indexer_client/src/models/lookup-application-logs-by-id.ts index f50e3e30..c80f393c 100644 --- a/packages/indexer_client/src/models/lookup-application-logs-by-id.ts +++ b/packages/indexer_client/src/models/lookup-application-logs-by-id.ts @@ -50,7 +50,7 @@ export const LookupApplicationLogsByIdMeta: ModelMetadata = { wireKey: 'log-data', optional: true, nullable: false, - type: { kind: 'array', item: { kind: 'model', meta: () => ApplicationLogDataMeta } }, + type: { kind: 'array', item: { kind: 'model', meta: ApplicationLogDataMeta } }, }, ], } diff --git a/packages/indexer_client/src/models/lookup-asset-balances.ts b/packages/indexer_client/src/models/lookup-asset-balances.ts index d1821577..c8e679d3 100644 --- a/packages/indexer_client/src/models/lookup-asset-balances.ts +++ b/packages/indexer_client/src/models/lookup-asset-balances.ts @@ -25,7 +25,7 @@ export const LookupAssetBalancesMeta: ModelMetadata = { wireKey: 'balances', optional: false, nullable: false, - type: { kind: 'array', item: { kind: 'model', meta: () => MiniAssetHoldingMeta } }, + type: { kind: 'array', item: { kind: 'model', meta: MiniAssetHoldingMeta } }, }, { name: 'currentRound', diff --git a/packages/indexer_client/src/models/lookup-asset-by-id.ts b/packages/indexer_client/src/models/lookup-asset-by-id.ts index ea8bd0fb..9e8e5922 100644 --- a/packages/indexer_client/src/models/lookup-asset-by-id.ts +++ b/packages/indexer_client/src/models/lookup-asset-by-id.ts @@ -20,7 +20,7 @@ export const LookupAssetByIdMeta: ModelMetadata = { wireKey: 'asset', optional: false, nullable: false, - type: { kind: 'model', meta: () => AssetMeta }, + type: { kind: 'model', meta: AssetMeta }, }, { name: 'currentRound', diff --git a/packages/indexer_client/src/models/lookup-asset-transactions.ts b/packages/indexer_client/src/models/lookup-asset-transactions.ts index a5890f37..2b167843 100644 --- a/packages/indexer_client/src/models/lookup-asset-transactions.ts +++ b/packages/indexer_client/src/models/lookup-asset-transactions.ts @@ -38,7 +38,7 @@ export const LookupAssetTransactionsMeta: ModelMetadata = { wireKey: 'transactions', optional: false, nullable: false, - type: { kind: 'array', item: { kind: 'model', meta: () => TransactionMeta } }, + type: { kind: 'array', item: { kind: 'model', meta: TransactionMeta } }, }, ], } diff --git a/packages/indexer_client/src/models/lookup-transaction.ts b/packages/indexer_client/src/models/lookup-transaction.ts index 8cab3ce4..23475dec 100644 --- a/packages/indexer_client/src/models/lookup-transaction.ts +++ b/packages/indexer_client/src/models/lookup-transaction.ts @@ -20,7 +20,7 @@ export const LookupTransactionMeta: ModelMetadata = { wireKey: 'transaction', optional: false, nullable: false, - type: { kind: 'model', meta: () => TransactionMeta }, + type: { kind: 'model', meta: TransactionMeta }, }, { name: 'currentRound', diff --git a/packages/indexer_client/src/models/merkle-array-proof.ts b/packages/indexer_client/src/models/merkle-array-proof.ts index e94c01f7..59df1474 100644 --- a/packages/indexer_client/src/models/merkle-array-proof.ts +++ b/packages/indexer_client/src/models/merkle-array-proof.ts @@ -31,7 +31,7 @@ export const MerkleArrayProofMeta: ModelMetadata = { wireKey: 'hash-factory', optional: true, nullable: false, - type: { kind: 'model', meta: () => HashFactoryMeta }, + type: { kind: 'model', meta: HashFactoryMeta }, }, { name: 'treeDepth', diff --git a/packages/indexer_client/src/models/resource-ref.ts b/packages/indexer_client/src/models/resource-ref.ts index 928a64bb..92a82fd5 100644 --- a/packages/indexer_client/src/models/resource-ref.ts +++ b/packages/indexer_client/src/models/resource-ref.ts @@ -61,21 +61,21 @@ export const ResourceRefMeta: ModelMetadata = { wireKey: 'box', optional: true, nullable: false, - type: { kind: 'model', meta: () => BoxReferenceMeta }, + type: { kind: 'model', meta: BoxReferenceMeta }, }, { name: 'holding', wireKey: 'holding', optional: true, nullable: false, - type: { kind: 'model', meta: () => HoldingRefMeta }, + type: { kind: 'model', meta: HoldingRefMeta }, }, { name: 'local', wireKey: 'local', optional: true, nullable: false, - type: { kind: 'model', meta: () => LocalsRefMeta }, + type: { kind: 'model', meta: LocalsRefMeta }, }, ], } diff --git a/packages/indexer_client/src/models/search-for-accounts.ts b/packages/indexer_client/src/models/search-for-accounts.ts index 7f38113e..7b6a37d9 100644 --- a/packages/indexer_client/src/models/search-for-accounts.ts +++ b/packages/indexer_client/src/models/search-for-accounts.ts @@ -25,7 +25,7 @@ export const SearchForAccountsMeta: ModelMetadata = { wireKey: 'accounts', optional: false, nullable: false, - type: { kind: 'array', item: { kind: 'model', meta: () => AccountMeta } }, + type: { kind: 'array', item: { kind: 'model', meta: AccountMeta } }, }, { name: 'currentRound', diff --git a/packages/indexer_client/src/models/search-for-application-boxes.ts b/packages/indexer_client/src/models/search-for-application-boxes.ts index 7035587c..32b5355c 100644 --- a/packages/indexer_client/src/models/search-for-application-boxes.ts +++ b/packages/indexer_client/src/models/search-for-application-boxes.ts @@ -31,7 +31,7 @@ export const SearchForApplicationBoxesMeta: ModelMetadata = { wireKey: 'boxes', optional: false, nullable: false, - type: { kind: 'array', item: { kind: 'model', meta: () => BoxDescriptorMeta } }, + type: { kind: 'array', item: { kind: 'model', meta: BoxDescriptorMeta } }, }, { name: 'nextToken', diff --git a/packages/indexer_client/src/models/search-for-applications.ts b/packages/indexer_client/src/models/search-for-applications.ts index 5a4e05b5..ceb6f76d 100644 --- a/packages/indexer_client/src/models/search-for-applications.ts +++ b/packages/indexer_client/src/models/search-for-applications.ts @@ -25,7 +25,7 @@ export const SearchForApplicationsMeta: ModelMetadata = { wireKey: 'applications', optional: false, nullable: false, - type: { kind: 'array', item: { kind: 'model', meta: () => ApplicationMeta } }, + type: { kind: 'array', item: { kind: 'model', meta: ApplicationMeta } }, }, { name: 'currentRound', diff --git a/packages/indexer_client/src/models/search-for-assets.ts b/packages/indexer_client/src/models/search-for-assets.ts index 77d6d592..b6a17a18 100644 --- a/packages/indexer_client/src/models/search-for-assets.ts +++ b/packages/indexer_client/src/models/search-for-assets.ts @@ -25,7 +25,7 @@ export const SearchForAssetsMeta: ModelMetadata = { wireKey: 'assets', optional: false, nullable: false, - type: { kind: 'array', item: { kind: 'model', meta: () => AssetMeta } }, + type: { kind: 'array', item: { kind: 'model', meta: AssetMeta } }, }, { name: 'currentRound', diff --git a/packages/indexer_client/src/models/search-for-block-headers.ts b/packages/indexer_client/src/models/search-for-block-headers.ts index 33e09c5b..cf2c6f19 100644 --- a/packages/indexer_client/src/models/search-for-block-headers.ts +++ b/packages/indexer_client/src/models/search-for-block-headers.ts @@ -38,7 +38,7 @@ export const SearchForBlockHeadersMeta: ModelMetadata = { wireKey: 'blocks', optional: false, nullable: false, - type: { kind: 'array', item: { kind: 'model', meta: () => BlockMeta } }, + type: { kind: 'array', item: { kind: 'model', meta: BlockMeta } }, }, ], } diff --git a/packages/indexer_client/src/models/search-for-transactions.ts b/packages/indexer_client/src/models/search-for-transactions.ts index 9af103f2..f87c8440 100644 --- a/packages/indexer_client/src/models/search-for-transactions.ts +++ b/packages/indexer_client/src/models/search-for-transactions.ts @@ -38,7 +38,7 @@ export const SearchForTransactionsMeta: ModelMetadata = { wireKey: 'transactions', optional: false, nullable: false, - type: { kind: 'array', item: { kind: 'model', meta: () => TransactionMeta } }, + type: { kind: 'array', item: { kind: 'model', meta: TransactionMeta } }, }, ], } diff --git a/packages/indexer_client/src/models/state-delta.ts b/packages/indexer_client/src/models/state-delta.ts index ef811cfe..4563bbf8 100644 --- a/packages/indexer_client/src/models/state-delta.ts +++ b/packages/indexer_client/src/models/state-delta.ts @@ -10,5 +10,5 @@ export type StateDelta = EvalDeltaKeyValue[] export const StateDeltaMeta: ModelMetadata = { name: 'StateDelta', kind: 'array', - arrayItems: { kind: 'model', meta: () => EvalDeltaKeyValueMeta }, + arrayItems: { kind: 'model', meta: EvalDeltaKeyValueMeta }, } diff --git a/packages/indexer_client/src/models/state-proof-fields.ts b/packages/indexer_client/src/models/state-proof-fields.ts index e19175ee..ff52a035 100644 --- a/packages/indexer_client/src/models/state-proof-fields.ts +++ b/packages/indexer_client/src/models/state-proof-fields.ts @@ -62,14 +62,14 @@ export const StateProofFieldsMeta: ModelMetadata = { wireKey: 'sig-proofs', optional: true, nullable: false, - type: { kind: 'model', meta: () => MerkleArrayProofMeta }, + type: { kind: 'model', meta: MerkleArrayProofMeta }, }, { name: 'partProofs', wireKey: 'part-proofs', optional: true, nullable: false, - type: { kind: 'model', meta: () => MerkleArrayProofMeta }, + type: { kind: 'model', meta: MerkleArrayProofMeta }, }, { name: 'saltVersion', @@ -83,7 +83,7 @@ export const StateProofFieldsMeta: ModelMetadata = { wireKey: 'reveals', optional: true, nullable: false, - type: { kind: 'array', item: { kind: 'model', meta: () => StateProofRevealMeta } }, + type: { kind: 'array', item: { kind: 'model', meta: StateProofRevealMeta } }, }, { name: 'positionsToReveal', diff --git a/packages/indexer_client/src/models/state-proof-participant.ts b/packages/indexer_client/src/models/state-proof-participant.ts index f1db65ce..651bd043 100644 --- a/packages/indexer_client/src/models/state-proof-participant.ts +++ b/packages/indexer_client/src/models/state-proof-participant.ts @@ -20,7 +20,7 @@ export const StateProofParticipantMeta: ModelMetadata = { wireKey: 'verifier', optional: true, nullable: false, - type: { kind: 'model', meta: () => StateProofVerifierMeta }, + type: { kind: 'model', meta: StateProofVerifierMeta }, }, { name: 'weight', diff --git a/packages/indexer_client/src/models/state-proof-reveal.ts b/packages/indexer_client/src/models/state-proof-reveal.ts index 12a1f117..52b121be 100644 --- a/packages/indexer_client/src/models/state-proof-reveal.ts +++ b/packages/indexer_client/src/models/state-proof-reveal.ts @@ -29,14 +29,14 @@ export const StateProofRevealMeta: ModelMetadata = { wireKey: 'sig-slot', optional: true, nullable: false, - type: { kind: 'model', meta: () => StateProofSigSlotMeta }, + type: { kind: 'model', meta: StateProofSigSlotMeta }, }, { name: 'participant', wireKey: 'participant', optional: true, nullable: false, - type: { kind: 'model', meta: () => StateProofParticipantMeta }, + type: { kind: 'model', meta: StateProofParticipantMeta }, }, ], } diff --git a/packages/indexer_client/src/models/state-proof-sig-slot.ts b/packages/indexer_client/src/models/state-proof-sig-slot.ts index b69d3281..79c45d26 100644 --- a/packages/indexer_client/src/models/state-proof-sig-slot.ts +++ b/packages/indexer_client/src/models/state-proof-sig-slot.ts @@ -20,7 +20,7 @@ export const StateProofSigSlotMeta: ModelMetadata = { wireKey: 'signature', optional: true, nullable: false, - type: { kind: 'model', meta: () => StateProofSignatureMeta }, + type: { kind: 'model', meta: StateProofSignatureMeta }, }, { name: 'lowerSigWeight', diff --git a/packages/indexer_client/src/models/state-proof-signature.ts b/packages/indexer_client/src/models/state-proof-signature.ts index 3f03e57c..ce761001 100644 --- a/packages/indexer_client/src/models/state-proof-signature.ts +++ b/packages/indexer_client/src/models/state-proof-signature.ts @@ -36,7 +36,7 @@ export const StateProofSignatureMeta: ModelMetadata = { wireKey: 'proof', optional: true, nullable: false, - type: { kind: 'model', meta: () => MerkleArrayProofMeta }, + type: { kind: 'model', meta: MerkleArrayProofMeta }, }, { name: 'verifyingKey', diff --git a/packages/indexer_client/src/models/teal-key-value-store.ts b/packages/indexer_client/src/models/teal-key-value-store.ts index d0ad997b..99dd316d 100644 --- a/packages/indexer_client/src/models/teal-key-value-store.ts +++ b/packages/indexer_client/src/models/teal-key-value-store.ts @@ -10,5 +10,5 @@ export type TealKeyValueStore = TealKeyValue[] export const TealKeyValueStoreMeta: ModelMetadata = { name: 'TealKeyValueStore', kind: 'array', - arrayItems: { kind: 'model', meta: () => TealKeyValueMeta }, + arrayItems: { kind: 'model', meta: TealKeyValueMeta }, } diff --git a/packages/indexer_client/src/models/teal-key-value.ts b/packages/indexer_client/src/models/teal-key-value.ts index 73575a78..085f5551 100644 --- a/packages/indexer_client/src/models/teal-key-value.ts +++ b/packages/indexer_client/src/models/teal-key-value.ts @@ -26,7 +26,7 @@ export const TealKeyValueMeta: ModelMetadata = { wireKey: 'value', optional: false, nullable: false, - type: { kind: 'model', meta: () => TealValueMeta }, + type: { kind: 'model', meta: TealValueMeta }, }, ], } diff --git a/packages/indexer_client/src/models/transaction-application.ts b/packages/indexer_client/src/models/transaction-application.ts index 69c8fe82..91de10ea 100644 --- a/packages/indexer_client/src/models/transaction-application.ts +++ b/packages/indexer_client/src/models/transaction-application.ts @@ -90,7 +90,7 @@ export const TransactionApplicationMeta: ModelMetadata = { wireKey: 'on-completion', optional: false, nullable: false, - type: { kind: 'model', meta: () => OnCompletionMeta }, + type: { kind: 'model', meta: OnCompletionMeta }, }, { name: 'applicationArgs', @@ -104,7 +104,7 @@ export const TransactionApplicationMeta: ModelMetadata = { wireKey: 'access', optional: true, nullable: false, - type: { kind: 'array', item: { kind: 'model', meta: () => ResourceRefMeta } }, + type: { kind: 'array', item: { kind: 'model', meta: ResourceRefMeta } }, }, { name: 'accounts', @@ -118,7 +118,7 @@ export const TransactionApplicationMeta: ModelMetadata = { wireKey: 'box-references', optional: true, nullable: false, - type: { kind: 'array', item: { kind: 'model', meta: () => BoxReferenceMeta } }, + type: { kind: 'array', item: { kind: 'model', meta: BoxReferenceMeta } }, }, { name: 'foreignApps', @@ -139,14 +139,14 @@ export const TransactionApplicationMeta: ModelMetadata = { wireKey: 'local-state-schema', optional: true, nullable: false, - type: { kind: 'model', meta: () => StateSchemaMeta }, + type: { kind: 'model', meta: StateSchemaMeta }, }, { name: 'globalStateSchema', wireKey: 'global-state-schema', optional: true, nullable: false, - type: { kind: 'model', meta: () => StateSchemaMeta }, + type: { kind: 'model', meta: StateSchemaMeta }, }, { name: 'approvalProgram', diff --git a/packages/indexer_client/src/models/transaction-asset-config.ts b/packages/indexer_client/src/models/transaction-asset-config.ts index 6de8eb53..413943b9 100644 --- a/packages/indexer_client/src/models/transaction-asset-config.ts +++ b/packages/indexer_client/src/models/transaction-asset-config.ts @@ -36,7 +36,7 @@ export const TransactionAssetConfigMeta: ModelMetadata = { wireKey: 'params', optional: true, nullable: false, - type: { kind: 'model', meta: () => AssetParamsMeta }, + type: { kind: 'model', meta: AssetParamsMeta }, }, ], } diff --git a/packages/indexer_client/src/models/transaction-heartbeat.ts b/packages/indexer_client/src/models/transaction-heartbeat.ts index b0c665ea..8098610f 100644 --- a/packages/indexer_client/src/models/transaction-heartbeat.ts +++ b/packages/indexer_client/src/models/transaction-heartbeat.ts @@ -47,7 +47,7 @@ export const TransactionHeartbeatMeta: ModelMetadata = { wireKey: 'hb-proof', optional: false, nullable: false, - type: { kind: 'model', meta: () => HbProofFieldsMeta }, + type: { kind: 'model', meta: HbProofFieldsMeta }, }, { name: 'hbSeed', diff --git a/packages/indexer_client/src/models/transaction-signature-logicsig.ts b/packages/indexer_client/src/models/transaction-signature-logicsig.ts index d5f43cab..5cd658cf 100644 --- a/packages/indexer_client/src/models/transaction-signature-logicsig.ts +++ b/packages/indexer_client/src/models/transaction-signature-logicsig.ts @@ -50,14 +50,14 @@ export const TransactionSignatureLogicsigMeta: ModelMetadata = { wireKey: 'multisig-signature', optional: true, nullable: false, - type: { kind: 'model', meta: () => TransactionSignatureMultisigMeta }, + type: { kind: 'model', meta: TransactionSignatureMultisigMeta }, }, { name: 'logicMultisigSignature', wireKey: 'logic-multisig-signature', optional: true, nullable: false, - type: { kind: 'model', meta: () => TransactionSignatureMultisigMeta }, + type: { kind: 'model', meta: TransactionSignatureMultisigMeta }, }, { name: 'signature', diff --git a/packages/indexer_client/src/models/transaction-signature-multisig.ts b/packages/indexer_client/src/models/transaction-signature-multisig.ts index 0ba916dd..33a494c9 100644 --- a/packages/indexer_client/src/models/transaction-signature-multisig.ts +++ b/packages/indexer_client/src/models/transaction-signature-multisig.ts @@ -34,7 +34,7 @@ export const TransactionSignatureMultisigMeta: ModelMetadata = { wireKey: 'subsignature', optional: true, nullable: false, - type: { kind: 'array', item: { kind: 'model', meta: () => TransactionSignatureMultisigSubsignatureMeta } }, + type: { kind: 'array', item: { kind: 'model', meta: TransactionSignatureMultisigSubsignatureMeta } }, }, { name: 'threshold', diff --git a/packages/indexer_client/src/models/transaction-signature.ts b/packages/indexer_client/src/models/transaction-signature.ts index 679d8f70..bddfc1e3 100644 --- a/packages/indexer_client/src/models/transaction-signature.ts +++ b/packages/indexer_client/src/models/transaction-signature.ts @@ -26,14 +26,14 @@ export const TransactionSignatureMeta: ModelMetadata = { wireKey: 'logicsig', optional: true, nullable: false, - type: { kind: 'model', meta: () => TransactionSignatureLogicsigMeta }, + type: { kind: 'model', meta: TransactionSignatureLogicsigMeta }, }, { name: 'multisig', wireKey: 'multisig', optional: true, nullable: false, - type: { kind: 'model', meta: () => TransactionSignatureMultisigMeta }, + type: { kind: 'model', meta: TransactionSignatureMultisigMeta }, }, { name: 'sig', diff --git a/packages/indexer_client/src/models/transaction-state-proof.ts b/packages/indexer_client/src/models/transaction-state-proof.ts index a254402f..514da0e8 100644 --- a/packages/indexer_client/src/models/transaction-state-proof.ts +++ b/packages/indexer_client/src/models/transaction-state-proof.ts @@ -35,14 +35,14 @@ export const TransactionStateProofMeta: ModelMetadata = { wireKey: 'state-proof', optional: true, nullable: false, - type: { kind: 'model', meta: () => StateProofFieldsMeta }, + type: { kind: 'model', meta: StateProofFieldsMeta }, }, { name: 'message', wireKey: 'message', optional: true, nullable: false, - type: { kind: 'model', meta: () => IndexerStateProofMessageMeta }, + type: { kind: 'model', meta: IndexerStateProofMessageMeta }, }, ], } diff --git a/packages/indexer_client/src/models/transaction.ts b/packages/indexer_client/src/models/transaction.ts index 86f2222b..03930f9b 100644 --- a/packages/indexer_client/src/models/transaction.ts +++ b/packages/indexer_client/src/models/transaction.ts @@ -186,42 +186,42 @@ export const TransactionMeta: ModelMetadata = { wireKey: 'application-transaction', optional: true, nullable: false, - type: { kind: 'model', meta: () => TransactionApplicationMeta }, + type: { kind: 'model', meta: TransactionApplicationMeta }, }, { name: 'assetConfigTransaction', wireKey: 'asset-config-transaction', optional: true, nullable: false, - type: { kind: 'model', meta: () => TransactionAssetConfigMeta }, + type: { kind: 'model', meta: TransactionAssetConfigMeta }, }, { name: 'assetFreezeTransaction', wireKey: 'asset-freeze-transaction', optional: true, nullable: false, - type: { kind: 'model', meta: () => TransactionAssetFreezeMeta }, + type: { kind: 'model', meta: TransactionAssetFreezeMeta }, }, { name: 'assetTransferTransaction', wireKey: 'asset-transfer-transaction', optional: true, nullable: false, - type: { kind: 'model', meta: () => TransactionAssetTransferMeta }, + type: { kind: 'model', meta: TransactionAssetTransferMeta }, }, { name: 'stateProofTransaction', wireKey: 'state-proof-transaction', optional: true, nullable: false, - type: { kind: 'model', meta: () => TransactionStateProofMeta }, + type: { kind: 'model', meta: TransactionStateProofMeta }, }, { name: 'heartbeatTransaction', wireKey: 'heartbeat-transaction', optional: true, nullable: false, - type: { kind: 'model', meta: () => TransactionHeartbeatMeta }, + type: { kind: 'model', meta: TransactionHeartbeatMeta }, }, { name: 'authAddr', @@ -319,7 +319,7 @@ export const TransactionMeta: ModelMetadata = { wireKey: 'keyreg-transaction', optional: true, nullable: false, - type: { kind: 'model', meta: () => TransactionKeyregMeta }, + type: { kind: 'model', meta: TransactionKeyregMeta }, }, { name: 'lastValid', @@ -347,7 +347,7 @@ export const TransactionMeta: ModelMetadata = { wireKey: 'payment-transaction', optional: true, nullable: false, - type: { kind: 'model', meta: () => TransactionPaymentMeta }, + type: { kind: 'model', meta: TransactionPaymentMeta }, }, { name: 'receiverRewards', @@ -389,7 +389,7 @@ export const TransactionMeta: ModelMetadata = { wireKey: 'signature', optional: true, nullable: false, - type: { kind: 'model', meta: () => TransactionSignatureMeta }, + type: { kind: 'model', meta: TransactionSignatureMeta }, }, { name: 'txType', @@ -403,14 +403,14 @@ export const TransactionMeta: ModelMetadata = { wireKey: 'local-state-delta', optional: true, nullable: false, - type: { kind: 'array', item: { kind: 'model', meta: () => AccountStateDeltaMeta } }, + type: { kind: 'array', item: { kind: 'model', meta: AccountStateDeltaMeta } }, }, { name: 'globalStateDelta', wireKey: 'global-state-delta', optional: true, nullable: false, - type: { kind: 'model', meta: () => StateDeltaMeta }, + type: { kind: 'model', meta: StateDeltaMeta }, }, { name: 'logs', diff --git a/packages/kmd_client/src/apis/api.service.ts b/packages/kmd_client/src/apis/api.service.ts index a1858bd8..73895498 100644 --- a/packages/kmd_client/src/apis/api.service.ts +++ b/packages/kmd_client/src/apis/api.service.ts @@ -112,9 +112,9 @@ export class KmdApi { const bodyMeta = CreateWalletRequestMeta const mediaType = bodyMeta ? KmdApi.mediaFor(responseFormat) : undefined if (mediaType) headers['Content-Type'] = mediaType - const serializedBody = bodyMeta && body !== undefined ? AlgorandSerializer.encode(body, bodyMeta, responseFormat) : body + const serializedBody = body ? AlgorandSerializer.encode(body, bodyMeta, responseFormat) : undefined - const payload = await this.httpRequest.request({ + const payload = await this.httpRequest.request({ method: 'POST', url: '/v1/wallet', path: {}, @@ -124,11 +124,7 @@ export class KmdApi { mediaType: mediaType, }) - const responseMeta = PostWalletResponseMeta - if (responseMeta) { - return AlgorandSerializer.decode(payload, responseMeta, responseFormat) - } - return payload as PostWalletResponse + return AlgorandSerializer.decode(payload, PostWalletResponseMeta, responseFormat) } /** @@ -142,9 +138,9 @@ export class KmdApi { const bodyMeta = DeleteKeyRequestMeta const mediaType = bodyMeta ? KmdApi.mediaFor(responseFormat) : undefined if (mediaType) headers['Content-Type'] = mediaType - const serializedBody = bodyMeta && body !== undefined ? AlgorandSerializer.encode(body, bodyMeta, responseFormat) : body + const serializedBody = body ? AlgorandSerializer.encode(body, bodyMeta, responseFormat) : undefined - const payload = await this.httpRequest.request({ + const payload = await this.httpRequest.request({ method: 'DELETE', url: '/v1/key', path: {}, @@ -154,11 +150,7 @@ export class KmdApi { mediaType: mediaType, }) - const responseMeta = DeleteKeyResponseMeta - if (responseMeta) { - return AlgorandSerializer.decode(payload, responseMeta, responseFormat) - } - return payload as DeleteKeyResponse + return AlgorandSerializer.decode(payload, DeleteKeyResponseMeta, responseFormat) } /** @@ -172,9 +164,9 @@ export class KmdApi { const bodyMeta = DeleteMultisigRequestMeta const mediaType = bodyMeta ? KmdApi.mediaFor(responseFormat) : undefined if (mediaType) headers['Content-Type'] = mediaType - const serializedBody = bodyMeta && body !== undefined ? AlgorandSerializer.encode(body, bodyMeta, responseFormat) : body + const serializedBody = body ? AlgorandSerializer.encode(body, bodyMeta, responseFormat) : undefined - const payload = await this.httpRequest.request({ + const payload = await this.httpRequest.request({ method: 'DELETE', url: '/v1/multisig', path: {}, @@ -184,11 +176,7 @@ export class KmdApi { mediaType: mediaType, }) - const responseMeta = DeleteMultisigResponseMeta - if (responseMeta) { - return AlgorandSerializer.decode(payload, responseMeta, responseFormat) - } - return payload as DeleteMultisigResponse + return AlgorandSerializer.decode(payload, DeleteMultisigResponseMeta, responseFormat) } /** @@ -202,9 +190,9 @@ export class KmdApi { const bodyMeta = ExportKeyRequestMeta const mediaType = bodyMeta ? KmdApi.mediaFor(responseFormat) : undefined if (mediaType) headers['Content-Type'] = mediaType - const serializedBody = bodyMeta && body !== undefined ? AlgorandSerializer.encode(body, bodyMeta, responseFormat) : body + const serializedBody = body ? AlgorandSerializer.encode(body, bodyMeta, responseFormat) : undefined - const payload = await this.httpRequest.request({ + const payload = await this.httpRequest.request({ method: 'POST', url: '/v1/key/export', path: {}, @@ -214,11 +202,7 @@ export class KmdApi { mediaType: mediaType, }) - const responseMeta = PostKeyExportResponseMeta - if (responseMeta) { - return AlgorandSerializer.decode(payload, responseMeta, responseFormat) - } - return payload as PostKeyExportResponse + return AlgorandSerializer.decode(payload, PostKeyExportResponseMeta, responseFormat) } /** @@ -232,9 +216,9 @@ export class KmdApi { const bodyMeta = ExportMasterKeyRequestMeta const mediaType = bodyMeta ? KmdApi.mediaFor(responseFormat) : undefined if (mediaType) headers['Content-Type'] = mediaType - const serializedBody = bodyMeta && body !== undefined ? AlgorandSerializer.encode(body, bodyMeta, responseFormat) : body + const serializedBody = body ? AlgorandSerializer.encode(body, bodyMeta, responseFormat) : undefined - const payload = await this.httpRequest.request({ + const payload = await this.httpRequest.request({ method: 'POST', url: '/v1/master-key/export', path: {}, @@ -244,11 +228,7 @@ export class KmdApi { mediaType: mediaType, }) - const responseMeta = PostMasterKeyExportResponseMeta - if (responseMeta) { - return AlgorandSerializer.decode(payload, responseMeta, responseFormat) - } - return payload as PostMasterKeyExportResponse + return AlgorandSerializer.decode(payload, PostMasterKeyExportResponseMeta, responseFormat) } /** @@ -262,9 +242,9 @@ export class KmdApi { const bodyMeta = ExportMultisigRequestMeta const mediaType = bodyMeta ? KmdApi.mediaFor(responseFormat) : undefined if (mediaType) headers['Content-Type'] = mediaType - const serializedBody = bodyMeta && body !== undefined ? AlgorandSerializer.encode(body, bodyMeta, responseFormat) : body + const serializedBody = body ? AlgorandSerializer.encode(body, bodyMeta, responseFormat) : undefined - const payload = await this.httpRequest.request({ + const payload = await this.httpRequest.request({ method: 'POST', url: '/v1/multisig/export', path: {}, @@ -274,11 +254,7 @@ export class KmdApi { mediaType: mediaType, }) - const responseMeta = PostMultisigExportResponseMeta - if (responseMeta) { - return AlgorandSerializer.decode(payload, responseMeta, responseFormat) - } - return payload as PostMultisigExportResponse + return AlgorandSerializer.decode(payload, PostMultisigExportResponseMeta, responseFormat) } /** @@ -292,9 +268,9 @@ export class KmdApi { const bodyMeta = GenerateKeyRequestMeta const mediaType = bodyMeta ? KmdApi.mediaFor(responseFormat) : undefined if (mediaType) headers['Content-Type'] = mediaType - const serializedBody = bodyMeta && body !== undefined ? AlgorandSerializer.encode(body, bodyMeta, responseFormat) : body + const serializedBody = body ? AlgorandSerializer.encode(body, bodyMeta, responseFormat) : undefined - const payload = await this.httpRequest.request({ + const payload = await this.httpRequest.request({ method: 'POST', url: '/v1/key', path: {}, @@ -304,11 +280,7 @@ export class KmdApi { mediaType: mediaType, }) - const responseMeta = PostKeyResponseMeta - if (responseMeta) { - return AlgorandSerializer.decode(payload, responseMeta, responseFormat) - } - return payload as PostKeyResponse + return AlgorandSerializer.decode(payload, PostKeyResponseMeta, responseFormat) } async getVersion(): Promise { @@ -316,7 +288,7 @@ export class KmdApi { const responseFormat: BodyFormat = 'json' headers['Accept'] = KmdApi.acceptFor(responseFormat) - const payload = await this.httpRequest.request({ + const payload = await this.httpRequest.request({ method: 'GET', url: '/versions', path: {}, @@ -326,11 +298,7 @@ export class KmdApi { mediaType: undefined, }) - const responseMeta = VersionsResponseMeta - if (responseMeta) { - return AlgorandSerializer.decode(payload, responseMeta, responseFormat) - } - return payload as VersionsResponse + return AlgorandSerializer.decode(payload, VersionsResponseMeta, responseFormat) } /** @@ -344,9 +312,9 @@ export class KmdApi { const bodyMeta = WalletInfoRequestMeta const mediaType = bodyMeta ? KmdApi.mediaFor(responseFormat) : undefined if (mediaType) headers['Content-Type'] = mediaType - const serializedBody = bodyMeta && body !== undefined ? AlgorandSerializer.encode(body, bodyMeta, responseFormat) : body + const serializedBody = body ? AlgorandSerializer.encode(body, bodyMeta, responseFormat) : undefined - const payload = await this.httpRequest.request({ + const payload = await this.httpRequest.request({ method: 'POST', url: '/v1/wallet/info', path: {}, @@ -356,11 +324,7 @@ export class KmdApi { mediaType: mediaType, }) - const responseMeta = PostWalletInfoResponseMeta - if (responseMeta) { - return AlgorandSerializer.decode(payload, responseMeta, responseFormat) - } - return payload as PostWalletInfoResponse + return AlgorandSerializer.decode(payload, PostWalletInfoResponseMeta, responseFormat) } /** @@ -374,9 +338,9 @@ export class KmdApi { const bodyMeta = ImportKeyRequestMeta const mediaType = bodyMeta ? KmdApi.mediaFor(responseFormat) : undefined if (mediaType) headers['Content-Type'] = mediaType - const serializedBody = bodyMeta && body !== undefined ? AlgorandSerializer.encode(body, bodyMeta, responseFormat) : body + const serializedBody = body ? AlgorandSerializer.encode(body, bodyMeta, responseFormat) : undefined - const payload = await this.httpRequest.request({ + const payload = await this.httpRequest.request({ method: 'POST', url: '/v1/key/import', path: {}, @@ -386,11 +350,7 @@ export class KmdApi { mediaType: mediaType, }) - const responseMeta = PostKeyImportResponseMeta - if (responseMeta) { - return AlgorandSerializer.decode(payload, responseMeta, responseFormat) - } - return payload as PostKeyImportResponse + return AlgorandSerializer.decode(payload, PostKeyImportResponseMeta, responseFormat) } /** @@ -404,9 +364,9 @@ export class KmdApi { const bodyMeta = ImportMultisigRequestMeta const mediaType = bodyMeta ? KmdApi.mediaFor(responseFormat) : undefined if (mediaType) headers['Content-Type'] = mediaType - const serializedBody = bodyMeta && body !== undefined ? AlgorandSerializer.encode(body, bodyMeta, responseFormat) : body + const serializedBody = body ? AlgorandSerializer.encode(body, bodyMeta, responseFormat) : undefined - const payload = await this.httpRequest.request({ + const payload = await this.httpRequest.request({ method: 'POST', url: '/v1/multisig/import', path: {}, @@ -416,11 +376,7 @@ export class KmdApi { mediaType: mediaType, }) - const responseMeta = PostMultisigImportResponseMeta - if (responseMeta) { - return AlgorandSerializer.decode(payload, responseMeta, responseFormat) - } - return payload as PostMultisigImportResponse + return AlgorandSerializer.decode(payload, PostMultisigImportResponseMeta, responseFormat) } /** @@ -434,9 +390,9 @@ export class KmdApi { const bodyMeta = InitWalletHandleTokenRequestMeta const mediaType = bodyMeta ? KmdApi.mediaFor(responseFormat) : undefined if (mediaType) headers['Content-Type'] = mediaType - const serializedBody = bodyMeta && body !== undefined ? AlgorandSerializer.encode(body, bodyMeta, responseFormat) : body + const serializedBody = body ? AlgorandSerializer.encode(body, bodyMeta, responseFormat) : undefined - const payload = await this.httpRequest.request({ + const payload = await this.httpRequest.request({ method: 'POST', url: '/v1/wallet/init', path: {}, @@ -446,11 +402,7 @@ export class KmdApi { mediaType: mediaType, }) - const responseMeta = PostWalletInitResponseMeta - if (responseMeta) { - return AlgorandSerializer.decode(payload, responseMeta, responseFormat) - } - return payload as PostWalletInitResponse + return AlgorandSerializer.decode(payload, PostWalletInitResponseMeta, responseFormat) } /** @@ -464,9 +416,9 @@ export class KmdApi { const bodyMeta = ListKeysRequestMeta const mediaType = bodyMeta ? KmdApi.mediaFor(responseFormat) : undefined if (mediaType) headers['Content-Type'] = mediaType - const serializedBody = bodyMeta && body !== undefined ? AlgorandSerializer.encode(body, bodyMeta, responseFormat) : body + const serializedBody = body ? AlgorandSerializer.encode(body, bodyMeta, responseFormat) : undefined - const payload = await this.httpRequest.request({ + const payload = await this.httpRequest.request({ method: 'POST', url: '/v1/key/list', path: {}, @@ -476,11 +428,7 @@ export class KmdApi { mediaType: mediaType, }) - const responseMeta = PostKeyListResponseMeta - if (responseMeta) { - return AlgorandSerializer.decode(payload, responseMeta, responseFormat) - } - return payload as PostKeyListResponse + return AlgorandSerializer.decode(payload, PostKeyListResponseMeta, responseFormat) } /** @@ -494,9 +442,9 @@ export class KmdApi { const bodyMeta = ListMultisigRequestMeta const mediaType = bodyMeta ? KmdApi.mediaFor(responseFormat) : undefined if (mediaType) headers['Content-Type'] = mediaType - const serializedBody = bodyMeta && body !== undefined ? AlgorandSerializer.encode(body, bodyMeta, responseFormat) : body + const serializedBody = body ? AlgorandSerializer.encode(body, bodyMeta, responseFormat) : undefined - const payload = await this.httpRequest.request({ + const payload = await this.httpRequest.request({ method: 'POST', url: '/v1/multisig/list', path: {}, @@ -506,11 +454,7 @@ export class KmdApi { mediaType: mediaType, }) - const responseMeta = PostMultisigListResponseMeta - if (responseMeta) { - return AlgorandSerializer.decode(payload, responseMeta, responseFormat) - } - return payload as PostMultisigListResponse + return AlgorandSerializer.decode(payload, PostMultisigListResponseMeta, responseFormat) } /** @@ -521,7 +465,7 @@ export class KmdApi { const responseFormat: BodyFormat = 'json' headers['Accept'] = KmdApi.acceptFor(responseFormat) - const payload = await this.httpRequest.request({ + const payload = await this.httpRequest.request({ method: 'GET', url: '/v1/wallets', path: {}, @@ -531,11 +475,7 @@ export class KmdApi { mediaType: undefined, }) - const responseMeta = GetWalletsResponseMeta - if (responseMeta) { - return AlgorandSerializer.decode(payload, responseMeta, responseFormat) - } - return payload as GetWalletsResponse + return AlgorandSerializer.decode(payload, GetWalletsResponseMeta, responseFormat) } /** @@ -549,9 +489,9 @@ export class KmdApi { const bodyMeta = ReleaseWalletHandleTokenRequestMeta const mediaType = bodyMeta ? KmdApi.mediaFor(responseFormat) : undefined if (mediaType) headers['Content-Type'] = mediaType - const serializedBody = bodyMeta && body !== undefined ? AlgorandSerializer.encode(body, bodyMeta, responseFormat) : body + const serializedBody = body ? AlgorandSerializer.encode(body, bodyMeta, responseFormat) : undefined - const payload = await this.httpRequest.request({ + const payload = await this.httpRequest.request({ method: 'POST', url: '/v1/wallet/release', path: {}, @@ -561,11 +501,7 @@ export class KmdApi { mediaType: mediaType, }) - const responseMeta = PostWalletReleaseResponseMeta - if (responseMeta) { - return AlgorandSerializer.decode(payload, responseMeta, responseFormat) - } - return payload as PostWalletReleaseResponse + return AlgorandSerializer.decode(payload, PostWalletReleaseResponseMeta, responseFormat) } /** @@ -579,9 +515,9 @@ export class KmdApi { const bodyMeta = RenameWalletRequestMeta const mediaType = bodyMeta ? KmdApi.mediaFor(responseFormat) : undefined if (mediaType) headers['Content-Type'] = mediaType - const serializedBody = bodyMeta && body !== undefined ? AlgorandSerializer.encode(body, bodyMeta, responseFormat) : body + const serializedBody = body ? AlgorandSerializer.encode(body, bodyMeta, responseFormat) : undefined - const payload = await this.httpRequest.request({ + const payload = await this.httpRequest.request({ method: 'POST', url: '/v1/wallet/rename', path: {}, @@ -591,11 +527,7 @@ export class KmdApi { mediaType: mediaType, }) - const responseMeta = PostWalletRenameResponseMeta - if (responseMeta) { - return AlgorandSerializer.decode(payload, responseMeta, responseFormat) - } - return payload as PostWalletRenameResponse + return AlgorandSerializer.decode(payload, PostWalletRenameResponseMeta, responseFormat) } /** @@ -609,9 +541,9 @@ export class KmdApi { const bodyMeta = RenewWalletHandleTokenRequestMeta const mediaType = bodyMeta ? KmdApi.mediaFor(responseFormat) : undefined if (mediaType) headers['Content-Type'] = mediaType - const serializedBody = bodyMeta && body !== undefined ? AlgorandSerializer.encode(body, bodyMeta, responseFormat) : body + const serializedBody = body ? AlgorandSerializer.encode(body, bodyMeta, responseFormat) : undefined - const payload = await this.httpRequest.request({ + const payload = await this.httpRequest.request({ method: 'POST', url: '/v1/wallet/renew', path: {}, @@ -621,11 +553,7 @@ export class KmdApi { mediaType: mediaType, }) - const responseMeta = PostWalletRenewResponseMeta - if (responseMeta) { - return AlgorandSerializer.decode(payload, responseMeta, responseFormat) - } - return payload as PostWalletRenewResponse + return AlgorandSerializer.decode(payload, PostWalletRenewResponseMeta, responseFormat) } /** @@ -639,9 +567,9 @@ export class KmdApi { const bodyMeta = SignProgramMultisigRequestMeta const mediaType = bodyMeta ? KmdApi.mediaFor(responseFormat) : undefined if (mediaType) headers['Content-Type'] = mediaType - const serializedBody = bodyMeta && body !== undefined ? AlgorandSerializer.encode(body, bodyMeta, responseFormat) : body + const serializedBody = body ? AlgorandSerializer.encode(body, bodyMeta, responseFormat) : undefined - const payload = await this.httpRequest.request({ + const payload = await this.httpRequest.request({ method: 'POST', url: '/v1/multisig/signprogram', path: {}, @@ -651,11 +579,7 @@ export class KmdApi { mediaType: mediaType, }) - const responseMeta = PostMultisigProgramSignResponseMeta - if (responseMeta) { - return AlgorandSerializer.decode(payload, responseMeta, responseFormat) - } - return payload as PostMultisigProgramSignResponse + return AlgorandSerializer.decode(payload, PostMultisigProgramSignResponseMeta, responseFormat) } /** @@ -669,9 +593,9 @@ export class KmdApi { const bodyMeta = SignMultisigRequestMeta const mediaType = bodyMeta ? KmdApi.mediaFor(responseFormat) : undefined if (mediaType) headers['Content-Type'] = mediaType - const serializedBody = bodyMeta && body !== undefined ? AlgorandSerializer.encode(body, bodyMeta, responseFormat) : body + const serializedBody = body ? AlgorandSerializer.encode(body, bodyMeta, responseFormat) : undefined - const payload = await this.httpRequest.request({ + const payload = await this.httpRequest.request({ method: 'POST', url: '/v1/multisig/sign', path: {}, @@ -681,11 +605,7 @@ export class KmdApi { mediaType: mediaType, }) - const responseMeta = PostMultisigTransactionSignResponseMeta - if (responseMeta) { - return AlgorandSerializer.decode(payload, responseMeta, responseFormat) - } - return payload as PostMultisigTransactionSignResponse + return AlgorandSerializer.decode(payload, PostMultisigTransactionSignResponseMeta, responseFormat) } /** @@ -699,9 +619,9 @@ export class KmdApi { const bodyMeta = SignProgramRequestMeta const mediaType = bodyMeta ? KmdApi.mediaFor(responseFormat) : undefined if (mediaType) headers['Content-Type'] = mediaType - const serializedBody = bodyMeta && body !== undefined ? AlgorandSerializer.encode(body, bodyMeta, responseFormat) : body + const serializedBody = body ? AlgorandSerializer.encode(body, bodyMeta, responseFormat) : undefined - const payload = await this.httpRequest.request({ + const payload = await this.httpRequest.request({ method: 'POST', url: '/v1/program/sign', path: {}, @@ -711,11 +631,7 @@ export class KmdApi { mediaType: mediaType, }) - const responseMeta = PostProgramSignResponseMeta - if (responseMeta) { - return AlgorandSerializer.decode(payload, responseMeta, responseFormat) - } - return payload as PostProgramSignResponse + return AlgorandSerializer.decode(payload, PostProgramSignResponseMeta, responseFormat) } /** @@ -729,9 +645,9 @@ export class KmdApi { const bodyMeta = SignTransactionRequestMeta const mediaType = bodyMeta ? KmdApi.mediaFor(responseFormat) : undefined if (mediaType) headers['Content-Type'] = mediaType - const serializedBody = bodyMeta && body !== undefined ? AlgorandSerializer.encode(body, bodyMeta, responseFormat) : body + const serializedBody = body ? AlgorandSerializer.encode(body, bodyMeta, responseFormat) : undefined - const payload = await this.httpRequest.request({ + const payload = await this.httpRequest.request({ method: 'POST', url: '/v1/transaction/sign', path: {}, @@ -741,11 +657,7 @@ export class KmdApi { mediaType: mediaType, }) - const responseMeta = PostTransactionSignResponseMeta - if (responseMeta) { - return AlgorandSerializer.decode(payload, responseMeta, responseFormat) - } - return payload as PostTransactionSignResponse + return AlgorandSerializer.decode(payload, PostTransactionSignResponseMeta, responseFormat) } /** @@ -756,7 +668,7 @@ export class KmdApi { const responseFormat: BodyFormat = 'json' headers['Accept'] = KmdApi.acceptFor(responseFormat) - const payload = await this.httpRequest.request({ + const payload = await this.httpRequest.request({ method: 'GET', url: '/swagger.json', path: {}, @@ -766,10 +678,6 @@ export class KmdApi { mediaType: undefined, }) - const responseMeta = undefined - if (responseMeta) { - return AlgorandSerializer.decode(payload, responseMeta, responseFormat) - } - return payload as string + return payload } } diff --git a/packages/kmd_client/src/core/codecs.ts b/packages/kmd_client/src/core/codecs.ts index 829dcd7b..214a543d 100644 --- a/packages/kmd_client/src/core/codecs.ts +++ b/packages/kmd_client/src/core/codecs.ts @@ -1,42 +1,28 @@ import { decode as msgpackDecode, encode as msgpackEncode } from 'algorand-msgpack' -export function encodeMsgPack(data: T): Uint8Array { +export function encodeMsgPack(data: ApiData): Uint8Array { return new Uint8Array(msgpackEncode(data, { sortKeys: true, ignoreUndefined: true })) } -export function decodeMsgPack(buffer: Uint8Array): T { - const map = msgpackDecode(buffer, { useMap: true }) as unknown - return mapToObject(map) as T +type MsgPackDecodeOptions = { + useMap: boolean + rawBinaryStringKeys: boolean + rawBinaryStringValues: boolean } -/** - * Converts a Map structure from msgpack decoding to a plain object structure. - * Maps are converted to objects recursively, except for the special case - * where the field name is "r" which remains as a Map. - */ -function mapToObject(value: unknown, fieldName?: string): unknown { - // Preserve Uint8Array as-is - if (value instanceof Uint8Array) { - return value - } else if (value instanceof Map) { - // Special case: keep "r" field as Map - if (fieldName === 'r') { - const newMap = new Map() - for (const [k, v] of value.entries()) { - newMap.set(k, mapToObject(v)) - } - return newMap - } - - // Convert Map to object - const obj: Record = {} - for (const [k, v] of value.entries()) { - obj[k] = mapToObject(v, k) - } - return obj - } else if (Array.isArray(value)) { - return value.map((item) => mapToObject(item)) - } - - return value +export function decodeMsgPack( + buffer: Uint8Array, + options: MsgPackDecodeOptions = { useMap: true, rawBinaryStringKeys: true, rawBinaryStringValues: true }, +): Map { + return msgpackDecode(buffer, options) as Map } +export type ApiData = + | null + | undefined + | string + | number + | bigint + | boolean + | Uint8Array + | object + | Map // TODO: NC - Do we ever have a string key? diff --git a/packages/kmd_client/src/core/model-runtime.ts b/packages/kmd_client/src/core/model-runtime.ts index ed41c188..07f6d681 100644 --- a/packages/kmd_client/src/core/model-runtime.ts +++ b/packages/kmd_client/src/core/model-runtime.ts @@ -1,19 +1,24 @@ import { - encodeSignedTransaction as transactEncodeSignedTransaction, - decodeSignedTransaction as transactDecodeSignedTransaction, + addressFromPublicKey, + decodedTransactionMapToObject, + fromSignedTransactionDto, + toSignedTransactionDto, type SignedTransaction, } from '@algorandfoundation/algokit-transact' -import { encodeMsgPack, decodeMsgPack } from './codecs' -import { toBase64, fromBase64 } from './serialization' +import { Buffer } from 'buffer' +import { ApiData, decodeMsgPack, encodeMsgPack } from './codecs' export type BodyFormat = 'json' | 'msgpack' | 'map' export interface ScalarFieldType { readonly kind: 'scalar' + // TODO: NC - Make this a type field readonly isBytes?: boolean readonly isBigint?: boolean + readonly isAddress?: boolean } +// TODO: NC - Needs to be renamed export interface CodecFieldType { readonly kind: 'codec' readonly codecKey: string @@ -34,7 +39,13 @@ export interface RecordFieldType { readonly value: FieldType } -export type FieldType = ScalarFieldType | CodecFieldType | ModelFieldType | ArrayFieldType | RecordFieldType +export interface MapFieldType { + readonly kind: 'map' + readonly keyType: 'number' | 'bigint' | 'bytes' + readonly value: FieldType +} + +export type FieldType = ScalarFieldType | CodecFieldType | ModelFieldType | ArrayFieldType | RecordFieldType | MapFieldType export interface FieldMetadata { readonly name: string @@ -61,39 +72,26 @@ export interface ModelMetadata { readonly passThrough?: FieldType } -// Registry for model metadata to avoid direct circular imports between model files -const modelMetaRegistry = new Map() - -export function registerModelMeta(name: string, meta: ModelMetadata): void { - modelMetaRegistry.set(name, meta) -} - -export function getModelMeta(name: string): ModelMetadata { - const meta = modelMetaRegistry.get(name) - if (!meta) throw new Error(`Model metadata not registered: ${name}`) - return meta -} - -export interface TypeCodec { - encode(value: TValue, format: BodyFormat): unknown - decode(value: unknown, format: BodyFormat): TValue +export interface EncodeableTypeConverter> { + beforeEncoding(value: T, format: BodyFormat): Record + afterDecoding(decoded: Record | Map, format: BodyFormat): T } -const codecRegistry = new Map>() - -export function registerCodec(key: string, codec: TypeCodec): void { - codecRegistry.set(key, codec as TypeCodec) -} +const encodeableTypeConverterRegistry = new Map>>() -export function getCodec(key: string): TypeCodec | undefined { - return codecRegistry.get(key) as TypeCodec | undefined +export function registerEncodeableTypeConverter(key: string, codec: EncodeableTypeConverter>): void { + encodeableTypeConverterRegistry.set(key, codec) } export class AlgorandSerializer { - static encode(value: unknown, meta: ModelMetadata, format: 'map'): Map - static encode(value: unknown, meta: ModelMetadata, format: 'json'): string - static encode(value: unknown, meta: ModelMetadata, format?: 'msgpack'): Uint8Array - static encode(value: unknown, meta: ModelMetadata, format: BodyFormat = 'msgpack'): Uint8Array | string | Map { + static encode(value: Record, meta: ModelMetadata, format: 'map'): Map + static encode(value: Record, meta: ModelMetadata, format: 'json'): string + static encode(value: Record, meta: ModelMetadata, format?: 'msgpack'): Uint8Array + static encode( + value: Record, + meta: ModelMetadata, + format: BodyFormat = 'msgpack', + ): Uint8Array | string | Map { if (format === 'map') { // For map format, use msgpack transformation to preserve types like bigint, then convert to nested Maps const wire = this.transform(value, meta, { direction: 'encode', format: 'msgpack' }) @@ -107,25 +105,25 @@ export class AlgorandSerializer { return typeof wire === 'string' ? wire : JSON.stringify(wire) } - static decode(payload: unknown, meta: ModelMetadata, format: BodyFormat = 'msgpack'): T { - let wire: unknown = payload + static decode(value: Uint8Array | string, meta: ModelMetadata, format: BodyFormat = 'msgpack'): T { + let wire: ApiData = value if (format === 'msgpack') { - if (payload instanceof Uint8Array) { - wire = decodeMsgPack(payload) + if (value instanceof Uint8Array) { + wire = decodeMsgPack(value) } - } else if (typeof payload === 'string') { - wire = JSON.parse(payload) + } else if (typeof value === 'string') { + wire = JSON.parse(value) } return this.transform(wire, meta, { direction: 'decode', format }) as T } - private static transform(value: unknown, meta: ModelMetadata, ctx: TransformContext): unknown { + private static transform(value: ApiData, meta: ModelMetadata, ctx: TransformContext): ApiData { if (value === undefined || value === null) { return value } if (meta.codecKey) { - return this.applyCodec(value, meta.codecKey, ctx) + return this.applyEncodeableTypeConversion(value, meta.codecKey, ctx) } switch (meta.kind) { @@ -139,20 +137,20 @@ export class AlgorandSerializer { } } - private static transformObject(value: unknown, meta: ModelMetadata, ctx: TransformContext): unknown { + private static transformObject(value: ApiData, meta: ModelMetadata, ctx: TransformContext): ApiData { const fields = meta.fields ?? [] - const hasFlattenedSignedTxn = fields.some((f) => f.flattened && f.type.kind === 'codec' && f.type.codecKey === 'SignedTransaction') + const hasFlattenedField = fields.some((f) => f.flattened) if (ctx.direction === 'encode') { - const src = value as Record - const out: Record = {} + const src = value as Record + const out: Record = {} for (const field of fields) { const fieldValue = src[field.name] if (fieldValue === undefined) continue const encoded = this.transformType(fieldValue, field.type, ctx) if (encoded === undefined && fieldValue === undefined) continue - if (field.flattened && field.type.kind === 'codec' && field.type.codecKey === 'SignedTransaction') { - // Merge signed transaction map into parent - const mapValue = encoded as Record + if (field.flattened) { + // Merge flattened field into parent + const mapValue = encoded as Record for (const [k, v] of Object.entries(mapValue ?? {})) out[k] = v continue } @@ -167,66 +165,206 @@ export class AlgorandSerializer { return out } - const src = value as Record - const out: Record = {} + // Decoding + const out: Record = {} const fieldByWire = new Map(fields.filter((f) => !!f.wireKey).map((field) => [field.wireKey as string, field])) - for (const [wireKey, wireValue] of Object.entries(src)) { - const field = fieldByWire.get(wireKey) + // Build a map of wire keys for each flattened field + const flattenedFieldWireKeys = new Map>() + if (hasFlattenedField) { + for (const field of fields) { + if (field.flattened && field.type.kind === 'model') { + const modelMeta = typeof field.type.meta === 'function' ? field.type.meta() : field.type.meta + const wireKeys = this.collectWireKeys(modelMeta) + flattenedFieldWireKeys.set(field, wireKeys) + } + } + } + + const entries = value instanceof Map ? Array.from(value.entries()) : Object.entries(value as Record) + const unmatchedEntries = new Map() + + for (const [key, wireValue] of entries) { + const wireKey = key instanceof Uint8Array ? Buffer.from(key).toString('utf-8') : key + const isStringKey = typeof wireKey === 'string' + const field = isStringKey ? fieldByWire.get(wireKey) : undefined + if (field) { const decoded = this.transformType(wireValue, field.type, ctx) - out[field.name] = decoded + out[field.name] = decoded === null && !field.nullable ? undefined : decoded continue } - if (meta.additionalProperties) { + + if (isStringKey && meta.additionalProperties) { out[wireKey] = this.transformType(wireValue, meta.additionalProperties, ctx) continue } - // If we have a flattened SignedTransaction, skip unknown keys (e.g., 'sig', 'txn') - if (!hasFlattenedSignedTxn) { - out[wireKey] = wireValue + + // Store unmatched entries for potential flattened field reconstruction + if (isStringKey) { + unmatchedEntries.set(wireKey, wireValue) + } + } + + // Reconstruct flattened fields from unmatched entries + if (hasFlattenedField) { + for (const field of fields) { + if (out[field.name] !== undefined) continue + if (field.flattened) { + if (field.type.kind === 'codec') { + // Reconstruct codec from entire object map + out[field.name] = this.applyEncodeableTypeConversion(value, field.type.codecKey, ctx) + } else if (field.type.kind === 'model') { + const modelMeta = typeof field.type.meta === 'function' ? field.type.meta() : field.type.meta + + // Check if this flattened model contains nested flattened codecs + const hasNestedCodec = this.hasNestedFlattenedCodec(modelMeta) + + let decoded: ApiData + if (hasNestedCodec) { + // If the model has nested flattened codecs, we need to pass the original value + // so the nested model can reconstruct its flattened codec fields + decoded = this.transform(value, modelMeta, ctx) + } else { + // Filter the wire data to only include keys belonging to this flattened model + const modelWireKeys = flattenedFieldWireKeys.get(field) + if (modelWireKeys) { + const filteredData: Record = {} + for (const [k, v] of unmatchedEntries.entries()) { + if (modelWireKeys.has(k)) { + filteredData[k] = v + } + } + // Also check if the original value is a Map and filter it + if (value instanceof Map) { + const filteredMap = new Map() + for (const [k, v] of value.entries()) { + const keyStr = k instanceof Uint8Array ? Buffer.from(k).toString('utf-8') : String(k) + if (typeof keyStr === 'string' && modelWireKeys.has(keyStr)) { + filteredMap.set(k as string | Uint8Array, v) + } + } + decoded = this.transform(filteredMap, modelMeta, ctx) + } else { + decoded = this.transform(filteredData, modelMeta, ctx) + } + } else { + decoded = undefined + } + } + + // If the field is optional and the decoded object is empty, set it to undefined + if (field.optional && decoded !== undefined && this.isEmptyObject(decoded)) { + out[field.name] = undefined + } else { + out[field.name] = decoded + } + } + } } } - // If there are flattened fields, attempt to reconstruct them from remaining keys by decoding - for (const field of fields) { - if (out[field.name] !== undefined) continue - if (field.flattened && field.type.kind === 'codec' && field.type.codecKey === 'SignedTransaction') { - // Reconstruct from entire object map - out[field.name] = this.applyCodec(src, 'SignedTransaction', ctx) + // Add any remaining unmatched entries if there are no flattened fields + if (!hasFlattenedField) { + for (const [k, v] of unmatchedEntries.entries()) { + out[k] = v } } return out } - private static transformType(value: unknown, type: FieldType, ctx: TransformContext): unknown { + private static collectWireKeys(meta: ModelMetadata): Set { + const wireKeys = new Set() + if (meta.kind !== 'object' || !meta.fields) return wireKeys + + for (const field of meta.fields) { + if (field.wireKey) { + wireKeys.add(field.wireKey) + } + if (field.flattened && field.type.kind === 'model') { + const childMeta = typeof field.type.meta === 'function' ? field.type.meta() : field.type.meta + const childKeys = this.collectWireKeys(childMeta) + for (const key of childKeys) { + wireKeys.add(key) + } + } + // Note: flattened codec fields don't have predictable wire keys, + // so they need to be handled differently during reconstruction + } + + return wireKeys + } + + private static hasNestedFlattenedCodec(meta: ModelMetadata): boolean { + if (meta.kind !== 'object' || !meta.fields) return false + + for (const field of meta.fields) { + if (field.flattened) { + if (field.type.kind === 'codec') { + return true + } + if (field.type.kind === 'model') { + const childMeta = typeof field.type.meta === 'function' ? field.type.meta() : field.type.meta + if (this.hasNestedFlattenedCodec(childMeta)) { + return true + } + } + } + } + + return false + } + + private static isEmptyObject(value: ApiData): boolean { + if (value === null || value === undefined) return true + if (typeof value !== 'object') return false + if (Array.isArray(value)) return false + if (value instanceof Uint8Array) return false + if (value instanceof Map) return value.size === 0 + + // Check if it's a plain object with no own properties (excluding undefined values) + const keys = Object.keys(value) + if (keys.length === 0) return true + + // Check if all properties are undefined + return keys.every((key) => (value as Record)[key] === undefined) + } + + private static transformType(value: ApiData, type: FieldType, ctx: TransformContext): ApiData { if (value === undefined || value === null) return value switch (type.kind) { case 'scalar': return this.transformScalar(value, type, ctx) case 'codec': - return this.applyCodec(value, type.codecKey, ctx) + return this.applyEncodeableTypeConversion(value, type.codecKey, ctx) case 'model': return this.transform(value, typeof type.meta === 'function' ? type.meta() : type.meta, ctx) case 'array': if (!Array.isArray(value)) return value return value.map((item) => this.transformType(item, type.item, ctx)) - case 'record': - if (typeof value !== 'object' || value === null) return value + case 'record': { + if ((!(value instanceof Map) && typeof value !== 'object') || value === null) return value + const entries = value instanceof Map ? Array.from(value.entries()) : Object.entries(value as Record) return Object.fromEntries( - Object.entries(value as Record).map(([k, v]) => [k, this.transformType(v, type.value, ctx)]), + entries.map(([k, v]) => { + const key = k instanceof Uint8Array ? Buffer.from(k).toString('utf-8') : k + return [key, this.transformType(v, type.value, ctx)] + }), ) + } + case 'map': + return this.transformMap(value, type, ctx) default: return value } } - private static transformScalar(value: unknown, meta: ScalarFieldType, ctx: TransformContext): unknown { + private static transformScalar(value: ApiData, meta: ScalarFieldType, ctx: TransformContext): ApiData { if (ctx.direction === 'encode') { if (meta.isBytes && ctx.format === 'json') { - if (value instanceof Uint8Array) return toBase64(value) + if (value instanceof Uint8Array) return Buffer.from(value).toString('base64') } if (meta.isBigint && ctx.format === 'json') { if (typeof value === 'bigint') return value.toString() @@ -237,7 +375,17 @@ export class AlgorandSerializer { } if (meta.isBytes && ctx.format === 'json' && typeof value === 'string') { - return fromBase64(value) + return new Uint8Array(Buffer.from(value, 'base64')) + } + + if (value instanceof Uint8Array) { + if (meta.isAddress) { + // TODO: NC - Fix all the address models to have this on it. + return addressFromPublicKey(value) + } else if (!meta.isBytes) { + return Buffer.from(value).toString('utf-8') + } + return value } if (meta.isBigint) { @@ -253,18 +401,61 @@ export class AlgorandSerializer { } } + if (value instanceof Map) { + const out: Record = {} + for (const [k, v] of value.entries()) { + const key = k instanceof Uint8Array ? Buffer.from(k).toString('utf-8') : k.toString() + out[key] = this.transformType(v, { kind: 'scalar', isBytes: v instanceof Uint8Array }, ctx) + } + return out + } + + if (Array.isArray(value)) { + return value.map((item) => this.transformType(item, { kind: 'scalar', isBytes: item instanceof Uint8Array }, ctx)) + } + return value } - private static applyCodec(value: unknown, codecKey: string, ctx: TransformContext): unknown { - const codec = codecRegistry.get(codecKey) + private static applyEncodeableTypeConversion(value: ApiData, typeKey: string, ctx: TransformContext): ApiData { + const codec = encodeableTypeConverterRegistry.get(typeKey) if (!codec) { - throw new Error(`Codec for "${codecKey}" is not registered`) + throw new Error(`Type converter for "${typeKey}" is not registered`) + } + + // TODO: NC - Need to properly guard against these conditions + if (ctx.direction === 'encode') { + if (value instanceof Map) { + throw new Error(`Cannot encode Map with type converter "${typeKey}"`) + } + return codec.beforeEncoding(value as Parameters[0], ctx.format) } - return ctx.direction === 'encode' ? codec.encode(value, ctx.format) : codec.decode(value, ctx.format) + + return codec.afterDecoding(value as Parameters[0], ctx.format) } - private static convertToNestedMaps(value: unknown): Map | unknown[] | unknown { + private static transformMap(value: ApiData, meta: MapFieldType, ctx: TransformContext): ApiData { + if (ctx.direction === 'encode') { + if (!(value instanceof Map)) return value + const result = new Map() + for (const [k, v] of value.entries()) { + const transformedValue = this.transformType(v, meta.value, ctx) + result.set(k, transformedValue) + } + return result + } + // Decoding + if ((!(value instanceof Map) && typeof value !== 'object') || value === null) return value + const entries = value instanceof Map ? Array.from(value.entries()) : Object.entries(value as Record) + const result = new Map() + for (const [k, v] of entries) { + const transformedValue = this.transformType(v, meta.value, ctx) + result.set(k, transformedValue) + } + return result + } + + private static convertToNestedMaps(value: ApiData): Map | ApiData[] | ApiData { if (value === null || value === undefined) { return value } @@ -282,8 +473,8 @@ export class AlgorandSerializer { } if (typeof value === 'object' && value !== null && !(value instanceof Uint8Array)) { - const map = new Map() - Object.entries(value as Record).forEach(([key, val]) => { + const map = new Map() + Object.entries(value as Record).forEach(([key, val]) => { map.set(key, this.convertToNestedMaps(val)) }) return map @@ -301,39 +492,23 @@ interface TransformContext { readonly format: BodyFormat } -const encodeSignedTransactionImpl = (value: unknown): Uint8Array => transactEncodeSignedTransaction(value as SignedTransaction) -const decodeSignedTransactionImpl = (value: Uint8Array): SignedTransaction => transactDecodeSignedTransaction(value) - -class SignedTransactionCodec implements TypeCodec { - encode(value: unknown, format: BodyFormat): unknown { - if (value == null) return value +class SignedTransactionConverter implements EncodeableTypeConverter { + beforeEncoding(value: SignedTransaction, format: BodyFormat): Record { if (format === 'json') { - if (value instanceof Uint8Array) return toBase64(value) - return toBase64(encodeSignedTransactionImpl(value)) - } - if (value instanceof Uint8Array) { - // Already canonical bytes; decode to structured map so parent encoding keeps map semantics - return decodeMsgPack(value) + throw new Error('JSON format not supported for SignedTransaction encoding') } - // Convert signed transaction object into canonical map representation - return decodeMsgPack(encodeSignedTransactionImpl(value)) + return toSignedTransactionDto(value) } - - decode(value: unknown, format: BodyFormat): unknown { - if (value == null) return value - if (format === 'json') { - if (typeof value === 'string') return decodeSignedTransactionImpl(fromBase64(value)) - if (value instanceof Uint8Array) return decodeSignedTransactionImpl(value) - return value + afterDecoding(value: Record | Map, format: BodyFormat): SignedTransaction { + if (format === 'json' || !(value instanceof Map)) { + throw new Error('JSON format not supported for SignedTransaction decoding') } - if (value instanceof Uint8Array) return decodeSignedTransactionImpl(value) - // Value is a decoded map; re-encode to bytes before handing to transact decoder - try { - return decodeSignedTransactionImpl(encodeMsgPack(value)) - } catch { - return value + if (!(value instanceof Map)) { + throw new Error('Invalid decoded msgpack format for SignedTransaction') } + const stxnDto = decodedTransactionMapToObject(value) as Parameters[0] + return fromSignedTransactionDto(stxnDto) } } -registerCodec('SignedTransaction', new SignedTransactionCodec()) +registerEncodeableTypeConverter('SignedTransaction', new SignedTransactionConverter()) diff --git a/packages/kmd_client/src/core/request.ts b/packages/kmd_client/src/core/request.ts index 706e60f7..0fedd516 100644 --- a/packages/kmd_client/src/core/request.ts +++ b/packages/kmd_client/src/core/request.ts @@ -85,7 +85,11 @@ export async function request( try { const ct = response.headers.get('content-type') ?? '' if (ct.includes('application/msgpack')) { - errorBody = decodeMsgPack(new Uint8Array(await response.arrayBuffer())) + errorBody = decodeMsgPack(new Uint8Array(await response.arrayBuffer()), { + useMap: false, + rawBinaryStringKeys: false, + rawBinaryStringValues: false, + }) } else if (ct.includes('application/json')) { errorBody = JSON.parse(await response.text()) } else { diff --git a/packages/kmd_client/src/core/serialization.ts b/packages/kmd_client/src/core/serialization.ts deleted file mode 100644 index 6be05428..00000000 --- a/packages/kmd_client/src/core/serialization.ts +++ /dev/null @@ -1,26 +0,0 @@ -export function toBase64(bytes: Uint8Array): string { - if (typeof Buffer !== 'undefined') { - return Buffer.from(bytes).toString('base64') - } - const globalRef: Record = globalThis as unknown as Record - const btoaFn = globalRef.btoa as ((value: string) => string) | undefined - if (typeof btoaFn === 'function') { - return btoaFn(String.fromCharCode(...bytes)) - } - throw new Error('Base64 encoding not supported in this environment') -} - -export function fromBase64(s: string): Uint8Array { - if (typeof Buffer !== 'undefined') { - return new Uint8Array(Buffer.from(s, 'base64')) - } - const globalRef: Record = globalThis as unknown as Record - const atobFn = globalRef.atob as ((value: string) => string) | undefined - if (typeof atobFn === 'function') { - const bin = atobFn(s) - const out = new Uint8Array(bin.length) - for (let i = 0; i < bin.length; i += 1) out[i] = bin.charCodeAt(i) - return out - } - throw new Error('Base64 decoding not supported in this environment') -} diff --git a/packages/kmd_client/src/index.ts b/packages/kmd_client/src/index.ts index 915506d5..58a6412d 100644 --- a/packages/kmd_client/src/index.ts +++ b/packages/kmd_client/src/index.ts @@ -2,7 +2,6 @@ export * from './core/client-config' export * from './core/base-http-request' export * from './core/fetch-http-request' export * from './core/api-error' -export * from './core/serialization' export * from './core/codecs' export * from './core/model-runtime' diff --git a/packages/kmd_client/src/models/create-wallet-request.ts b/packages/kmd_client/src/models/create-wallet-request.ts index 364921bf..d9f1adc8 100644 --- a/packages/kmd_client/src/models/create-wallet-request.ts +++ b/packages/kmd_client/src/models/create-wallet-request.ts @@ -21,7 +21,7 @@ export const CreateWalletRequestMeta: ModelMetadata = { wireKey: 'master_derivation_key', optional: true, nullable: false, - type: { kind: 'model', meta: () => MasterDerivationKeyMeta }, + type: { kind: 'model', meta: MasterDerivationKeyMeta }, }, { name: 'walletDriverName', diff --git a/packages/kmd_client/src/models/get-wallets-response.ts b/packages/kmd_client/src/models/get-wallets-response.ts index b25dcb26..0f99759b 100644 --- a/packages/kmd_client/src/models/get-wallets-response.ts +++ b/packages/kmd_client/src/models/get-wallets-response.ts @@ -35,7 +35,7 @@ export const GetWalletsResponseMeta: ModelMetadata = { wireKey: 'wallets', optional: true, nullable: false, - type: { kind: 'array', item: { kind: 'model', meta: () => WalletMeta } }, + type: { kind: 'array', item: { kind: 'model', meta: WalletMeta } }, }, ], } diff --git a/packages/kmd_client/src/models/import-multisig-request.ts b/packages/kmd_client/src/models/import-multisig-request.ts index 0b174d49..595d0777 100644 --- a/packages/kmd_client/src/models/import-multisig-request.ts +++ b/packages/kmd_client/src/models/import-multisig-request.ts @@ -28,7 +28,7 @@ export const ImportMultisigRequestMeta: ModelMetadata = { wireKey: 'pks', optional: true, nullable: false, - type: { kind: 'array', item: { kind: 'model', meta: () => PublicKeyMeta } }, + type: { kind: 'array', item: { kind: 'model', meta: PublicKeyMeta } }, }, { name: 'threshold', diff --git a/packages/kmd_client/src/models/multisig-sig.ts b/packages/kmd_client/src/models/multisig-sig.ts index fed03784..721ee287 100644 --- a/packages/kmd_client/src/models/multisig-sig.ts +++ b/packages/kmd_client/src/models/multisig-sig.ts @@ -20,7 +20,7 @@ export const MultisigSigMeta: ModelMetadata = { wireKey: 'Subsigs', optional: true, nullable: false, - type: { kind: 'array', item: { kind: 'model', meta: () => MultisigSubsigMeta } }, + type: { kind: 'array', item: { kind: 'model', meta: MultisigSubsigMeta } }, }, { name: 'threshold', diff --git a/packages/kmd_client/src/models/multisig-subsig.ts b/packages/kmd_client/src/models/multisig-subsig.ts index 2f425082..8b44c4c7 100644 --- a/packages/kmd_client/src/models/multisig-subsig.ts +++ b/packages/kmd_client/src/models/multisig-subsig.ts @@ -22,14 +22,14 @@ export const MultisigSubsigMeta: ModelMetadata = { wireKey: 'Key', optional: true, nullable: false, - type: { kind: 'model', meta: () => PublicKeyMeta }, + type: { kind: 'model', meta: PublicKeyMeta }, }, { name: 'sig', wireKey: 'Sig', optional: true, nullable: false, - type: { kind: 'model', meta: () => SignatureMeta }, + type: { kind: 'model', meta: SignatureMeta }, }, ], } diff --git a/packages/kmd_client/src/models/post-master-key-export-response.ts b/packages/kmd_client/src/models/post-master-key-export-response.ts index dbf36727..0df944a0 100644 --- a/packages/kmd_client/src/models/post-master-key-export-response.ts +++ b/packages/kmd_client/src/models/post-master-key-export-response.ts @@ -28,7 +28,7 @@ export const PostMasterKeyExportResponseMeta: ModelMetadata = { wireKey: 'master_derivation_key', optional: true, nullable: false, - type: { kind: 'model', meta: () => MasterDerivationKeyMeta }, + type: { kind: 'model', meta: MasterDerivationKeyMeta }, }, { name: 'message', diff --git a/packages/kmd_client/src/models/post-multisig-export-response.ts b/packages/kmd_client/src/models/post-multisig-export-response.ts index fd9243db..da3fceee 100644 --- a/packages/kmd_client/src/models/post-multisig-export-response.ts +++ b/packages/kmd_client/src/models/post-multisig-export-response.ts @@ -44,7 +44,7 @@ export const PostMultisigExportResponseMeta: ModelMetadata = { wireKey: 'pks', optional: true, nullable: false, - type: { kind: 'array', item: { kind: 'model', meta: () => PublicKeyMeta } }, + type: { kind: 'array', item: { kind: 'model', meta: PublicKeyMeta } }, }, { name: 'threshold', diff --git a/packages/kmd_client/src/models/post-wallet-info-response.ts b/packages/kmd_client/src/models/post-wallet-info-response.ts index 6c30f05a..99477d8c 100644 --- a/packages/kmd_client/src/models/post-wallet-info-response.ts +++ b/packages/kmd_client/src/models/post-wallet-info-response.ts @@ -35,7 +35,7 @@ export const PostWalletInfoResponseMeta: ModelMetadata = { wireKey: 'wallet_handle', optional: true, nullable: false, - type: { kind: 'model', meta: () => WalletHandleMeta }, + type: { kind: 'model', meta: WalletHandleMeta }, }, ], } diff --git a/packages/kmd_client/src/models/post-wallet-rename-response.ts b/packages/kmd_client/src/models/post-wallet-rename-response.ts index 43ab2bf7..53875dbc 100644 --- a/packages/kmd_client/src/models/post-wallet-rename-response.ts +++ b/packages/kmd_client/src/models/post-wallet-rename-response.ts @@ -35,7 +35,7 @@ export const PostWalletRenameResponseMeta: ModelMetadata = { wireKey: 'wallet', optional: true, nullable: false, - type: { kind: 'model', meta: () => WalletMeta }, + type: { kind: 'model', meta: WalletMeta }, }, ], } diff --git a/packages/kmd_client/src/models/post-wallet-renew-response.ts b/packages/kmd_client/src/models/post-wallet-renew-response.ts index 078061e4..bf9e5026 100644 --- a/packages/kmd_client/src/models/post-wallet-renew-response.ts +++ b/packages/kmd_client/src/models/post-wallet-renew-response.ts @@ -35,7 +35,7 @@ export const PostWalletRenewResponseMeta: ModelMetadata = { wireKey: 'wallet_handle', optional: true, nullable: false, - type: { kind: 'model', meta: () => WalletHandleMeta }, + type: { kind: 'model', meta: WalletHandleMeta }, }, ], } diff --git a/packages/kmd_client/src/models/post-wallet-response.ts b/packages/kmd_client/src/models/post-wallet-response.ts index cb91a883..a471bddb 100644 --- a/packages/kmd_client/src/models/post-wallet-response.ts +++ b/packages/kmd_client/src/models/post-wallet-response.ts @@ -35,7 +35,7 @@ export const PostWalletResponseMeta: ModelMetadata = { wireKey: 'wallet', optional: true, nullable: false, - type: { kind: 'model', meta: () => WalletMeta }, + type: { kind: 'model', meta: WalletMeta }, }, ], } diff --git a/packages/kmd_client/src/models/sign-multisig-request.ts b/packages/kmd_client/src/models/sign-multisig-request.ts index 2f798e25..1e3bcb4f 100644 --- a/packages/kmd_client/src/models/sign-multisig-request.ts +++ b/packages/kmd_client/src/models/sign-multisig-request.ts @@ -27,21 +27,21 @@ export const SignMultisigRequestMeta: ModelMetadata = { wireKey: 'partial_multisig', optional: true, nullable: false, - type: { kind: 'model', meta: () => MultisigSigMeta }, + type: { kind: 'model', meta: MultisigSigMeta }, }, { name: 'publicKey', wireKey: 'public_key', optional: true, nullable: false, - type: { kind: 'model', meta: () => PublicKeyMeta }, + type: { kind: 'model', meta: PublicKeyMeta }, }, { name: 'signer', wireKey: 'signer', optional: true, nullable: false, - type: { kind: 'model', meta: () => DigestMeta }, + type: { kind: 'model', meta: DigestMeta }, }, { name: 'transaction', diff --git a/packages/kmd_client/src/models/sign-program-multisig-request.ts b/packages/kmd_client/src/models/sign-program-multisig-request.ts index f92243e2..ed404d5c 100644 --- a/packages/kmd_client/src/models/sign-program-multisig-request.ts +++ b/packages/kmd_client/src/models/sign-program-multisig-request.ts @@ -40,14 +40,14 @@ export const SignProgramMultisigRequestMeta: ModelMetadata = { wireKey: 'partial_multisig', optional: true, nullable: false, - type: { kind: 'model', meta: () => MultisigSigMeta }, + type: { kind: 'model', meta: MultisigSigMeta }, }, { name: 'publicKey', wireKey: 'public_key', optional: true, nullable: false, - type: { kind: 'model', meta: () => PublicKeyMeta }, + type: { kind: 'model', meta: PublicKeyMeta }, }, { name: 'useLegacyMsig', diff --git a/packages/kmd_client/src/models/sign-transaction-request.ts b/packages/kmd_client/src/models/sign-transaction-request.ts index 14d4cf48..e0b6b727 100644 --- a/packages/kmd_client/src/models/sign-transaction-request.ts +++ b/packages/kmd_client/src/models/sign-transaction-request.ts @@ -28,7 +28,7 @@ export const SignTransactionRequestMeta: ModelMetadata = { wireKey: 'public_key', optional: true, nullable: false, - type: { kind: 'model', meta: () => PublicKeyMeta }, + type: { kind: 'model', meta: PublicKeyMeta }, }, { name: 'transaction', diff --git a/packages/kmd_client/src/models/wallet-handle.ts b/packages/kmd_client/src/models/wallet-handle.ts index 650c2b2c..6e170673 100644 --- a/packages/kmd_client/src/models/wallet-handle.ts +++ b/packages/kmd_client/src/models/wallet-handle.ts @@ -27,7 +27,7 @@ export const WalletHandleMeta: ModelMetadata = { wireKey: 'wallet', optional: true, nullable: false, - type: { kind: 'model', meta: () => WalletMeta }, + type: { kind: 'model', meta: WalletMeta }, }, ], } diff --git a/packages/kmd_client/src/models/wallet.ts b/packages/kmd_client/src/models/wallet.ts index f558a0e2..b96229af 100644 --- a/packages/kmd_client/src/models/wallet.ts +++ b/packages/kmd_client/src/models/wallet.ts @@ -58,7 +58,7 @@ export const WalletMeta: ModelMetadata = { wireKey: 'supported_txs', optional: true, nullable: false, - type: { kind: 'array', item: { kind: 'model', meta: () => TxTypeMeta } }, + type: { kind: 'array', item: { kind: 'model', meta: TxTypeMeta } }, }, ], } diff --git a/packages/transact/src/encoding/codecs.ts b/packages/transact/src/encoding/codecs.ts index e9bc4c14..7dbcbbc9 100644 --- a/packages/transact/src/encoding/codecs.ts +++ b/packages/transact/src/encoding/codecs.ts @@ -101,6 +101,20 @@ class BytesCodec extends Codec { } } +class FixedBytesCodec extends Codec { + constructor(private length: number) { + super() + } + + public defaultValue(): Uint8Array { + return new Uint8Array(this.length) + } + + protected isDefaultValue(value: Uint8Array): boolean { + return value.byteLength === this.length && value.every((byte) => byte === 0) + } +} + class BooleanCodec extends Codec { public defaultValue(): boolean { return false @@ -150,6 +164,9 @@ export const bigIntCodec = new BigIntCodec() export const stringCodec = new StringCodec() export const addressCodec = new AddressCodec() export const bytesCodec = new BytesCodec() +export const fixedBytes32Codec = new FixedBytesCodec(32) +export const fixedBytes64Codec = new FixedBytesCodec(64) +export const fixedBytes1793Codec = new FixedBytesCodec(0x701) export const booleanCodec = new BooleanCodec() export const bytesArrayCodec = new ArrayCodec(bytesCodec) export const addressArrayCodec = new ArrayCodec(addressCodec) diff --git a/packages/transact/src/encoding/msgpack.ts b/packages/transact/src/encoding/msgpack.ts index d47d8a7b..815419c3 100644 --- a/packages/transact/src/encoding/msgpack.ts +++ b/packages/transact/src/encoding/msgpack.ts @@ -1,31 +1,38 @@ -import { encode as msgpackEncode, decode as msgpackDecode } from 'algorand-msgpack' +import { decode as msgpackDecode, encode as msgpackEncode } from 'algorand-msgpack' +import { SignedTransactionDto } from './signed-transaction-dto' +import { TransactionDto } from './transaction-dto' -export function encodeMsgpack(data: T): Uint8Array { +export function encodeMsgpack(data: T): Uint8Array { return new Uint8Array(msgpackEncode(data, { sortKeys: true, ignoreUndefined: true })) } -export function decodeMsgpack(encoded: Uint8Array): T { - // The message pack needs to be decoded into map first to support Maps with bigint as key +export function decodeMsgpack(encoded: Uint8Array): T { + // The message pack needs to be decoded into map, so we support number, bigint and Uint8Array keys. + // Additionally we need to use rawBinaryStrings for both keys and values to avoid incorrect utf-8 decoding. // After that, the map is converted to the targeted object - const map = msgpackDecode(encoded, { useMap: true }) as unknown - return mapToObject(map) as T + const map = msgpackDecode(encoded, { useMap: true, rawBinaryStringKeys: true, rawBinaryStringValues: true }) as unknown + return decodedMapToObject(map) as T } /** - * Converts a Map structure from msgpack decoding to a plain object structure. + * Converts a decoded msgpack Map structure to a plain object structure. * Maps are converted to objects recursively, except for the special case * where the field name is "r" which remains as a Map. */ -export function mapToObject(value: unknown, fieldName?: string): unknown { - // Preserve Uint8Array as-is +export function decodedMapToObject(value: unknown, fieldName?: string): unknown { + // Convert Uint8Array to string for specific fields, otherwise preserve as-is if (value instanceof Uint8Array) { + if (fieldName && ['type', 'gen', 'un', 'an', 'au'].includes(fieldName)) { + return Buffer.from(value).toString('utf-8') + } return value } else if (value instanceof Map) { // Special case: keep "r" field as Map if (fieldName === 'r') { const newMap = new Map() for (const [k, v] of value.entries()) { - newMap.set(k, mapToObject(v)) + const normalisedKey = k instanceof Uint8Array ? Buffer.from(k).toString('utf-8') : k + newMap.set(normalisedKey, decodedMapToObject(v, normalisedKey)) } return newMap } @@ -33,11 +40,12 @@ export function mapToObject(value: unknown, fieldName?: string): unknown { // Convert Map to object const obj: Record = {} for (const [k, v] of value.entries()) { - obj[k] = mapToObject(v, k) + const normalisedKey = k instanceof Uint8Array ? Buffer.from(k).toString('utf-8') : k + obj[normalisedKey] = decodedMapToObject(v, normalisedKey) } return obj } else if (Array.isArray(value)) { - return value.map((item) => mapToObject(item)) + return value.map((item) => decodedMapToObject(item, fieldName)) } return value diff --git a/packages/transact/src/encoding/signed-transaction-dto.ts b/packages/transact/src/encoding/signed-transaction-dto.ts index 7cac0328..894110a0 100644 --- a/packages/transact/src/encoding/signed-transaction-dto.ts +++ b/packages/transact/src/encoding/signed-transaction-dto.ts @@ -59,6 +59,9 @@ export type LogicSignatureDto = { /** Signature for delegated logic sig (optional) */ sig?: Uint8Array - /** Multisig for delegated logic sig (optional) */ + /** Legacy multisig for delegated logic sig (optional) */ msig?: MultisigSignatureDto + + /** Multisig for delegated logic sig (optional) */ + lmsig?: MultisigSignatureDto } diff --git a/packages/transact/src/index.ts b/packages/transact/src/index.ts index b7bb5f29..53d94fc8 100644 --- a/packages/transact/src/index.ts +++ b/packages/transact/src/index.ts @@ -20,6 +20,8 @@ export { decodeSignedTransactions, encodeSignedTransaction, encodeSignedTransactions, + fromSignedTransactionDto, + toSignedTransactionDto, type LogicSignature, type MultisigSignature, type MultisigSubsignature, @@ -42,3 +44,5 @@ export { newMultisigSignature, participantsFromMultisigSignature, } from './multisig' + +export { decodedMapToObject as decodedTransactionMapToObject } from './encoding/msgpack' diff --git a/packages/transact/src/transactions/signed-transaction.ts b/packages/transact/src/transactions/signed-transaction.ts index 1de7c85e..1b7845da 100644 --- a/packages/transact/src/transactions/signed-transaction.ts +++ b/packages/transact/src/transactions/signed-transaction.ts @@ -1,4 +1,4 @@ -import { addressCodec, bytesCodec, numberCodec, OmitEmptyObjectCodec } from '../encoding/codecs' +import { addressCodec, bytesCodec, fixedBytes64Codec, numberCodec, OmitEmptyObjectCodec } from '../encoding/codecs' import { decodeMsgpack, encodeMsgpack } from '../encoding/msgpack' import { LogicSignatureDto, MultisigSignatureDto, SignedTransactionDto } from '../encoding/signed-transaction-dto' import { fromTransactionDto, toTransactionDto, Transaction, validateTransaction } from './transaction' @@ -94,9 +94,14 @@ export type LogicSignature = { signature?: Uint8Array /** - * Multisig for delegated logic sig + * Legacy multisig for delegated logic sig */ multiSignature?: MultisigSignature + + /** + * Multisig for delegated logic sig + */ + logicMultiSignature?: MultisigSignature } /** @@ -156,9 +161,6 @@ function validateSignedTransaction(signedTransaction: SignedTransaction): void { const sigTypes = [signedTransaction.signature, signedTransaction.multiSignature, signedTransaction.logicSignature] const setSigCount = sigTypes.filter((sig) => sig !== undefined).length - if (setSigCount === 0) { - throw new Error('At least one signature type must be set') - } if (setSigCount > 1) { throw new Error(`Only one signature type can be set, found ${setSigCount}`) } @@ -179,18 +181,18 @@ function toMultisigSignatureDto(multisigSignature: MultisigSignature): MultisigS thr: numberCodec.encode(multisigSignature.threshold), subsig: multisigSignature.subsignatures.map((subsig) => ({ pk: addressCodec.encode(subsig.address), - s: bytesCodec.encode(subsig.signature), + s: fixedBytes64Codec.encode(subsig.signature), })), }) } -function toSignedTransactionDto(signedTransaction: SignedTransaction): SignedTransactionDto { +export function toSignedTransactionDto(signedTransaction: SignedTransaction): SignedTransactionDto { const stx_dto: SignedTransactionDto = { txn: toTransactionDto(signedTransaction.txn), } if (signedTransaction.signature) { - stx_dto.sig = bytesCodec.encode(signedTransaction.signature) + stx_dto.sig = fixedBytes64Codec.encode(signedTransaction.signature) } if (signedTransaction.multiSignature) { @@ -201,10 +203,13 @@ function toSignedTransactionDto(signedTransaction: SignedTransaction): SignedTra stx_dto.lsig = logicSignatureDtoCodec.encode({ l: bytesCodec.encode(signedTransaction.logicSignature.logic), arg: signedTransaction.logicSignature.args?.map((arg) => bytesCodec.encode(arg) ?? bytesCodec.defaultValue()), - sig: bytesCodec.encode(signedTransaction.logicSignature.signature), - ...(signedTransaction.logicSignature.multiSignature - ? { msig: toMultisigSignatureDto(signedTransaction.logicSignature.multiSignature) } - : undefined), + sig: fixedBytes64Codec.encode(signedTransaction.logicSignature.signature), + ...(signedTransaction.logicSignature.multiSignature && { + msig: toMultisigSignatureDto(signedTransaction.logicSignature.multiSignature), + }), + ...(signedTransaction.logicSignature.logicMultiSignature && { + lmsig: toMultisigSignatureDto(signedTransaction.logicSignature.logicMultiSignature), + }), }) } @@ -223,36 +228,48 @@ function fromMultisigSignatureDto(msigDto: MultisigSignatureDto): MultisigSignat msigDto.subsig?.map((subsigData) => { return { address: addressCodec.decode(subsigData.pk), - signature: bytesCodec.decodeOptional(subsigData.s), + signature: fixedBytes64Codec.decodeOptional(subsigData.s), } satisfies MultisigSubsignature }) ?? [], }) } -function fromSignedTransactionDto(signedTransactionDto: SignedTransactionDto): SignedTransaction { +export function fromSignedTransactionDto(signedTransactionDto: SignedTransactionDto): SignedTransaction { const stx: SignedTransaction = { txn: fromTransactionDto(signedTransactionDto.txn), } - if (signedTransactionDto.sig) { - stx.signature = bytesCodec.decodeOptional(signedTransactionDto.sig) + const signature = signedTransactionDto.sig && fixedBytes64Codec.decodeOptional(signedTransactionDto.sig) + if (signature) { + stx.signature = signature } - if (signedTransactionDto.msig) { - stx.multiSignature = fromMultisigSignatureDto(signedTransactionDto.msig) + const multiSignature = signedTransactionDto.msig && fromMultisigSignatureDto(signedTransactionDto.msig) + if (multiSignature) { + stx.multiSignature = multiSignature } if (signedTransactionDto.lsig) { - stx.logicSignature = logicSignatureCodec.decodeOptional({ + const args = signedTransactionDto.lsig.arg?.map((arg) => bytesCodec.decode(arg)) + const signature = fixedBytes64Codec.decodeOptional(signedTransactionDto.lsig.sig) + const multiSignature = signedTransactionDto.lsig.msig && fromMultisigSignatureDto(signedTransactionDto.lsig.msig) + const logicMultiSignature = signedTransactionDto.lsig.lmsig && fromMultisigSignatureDto(signedTransactionDto.lsig.lmsig) + + const logicSignature = logicSignatureCodec.decodeOptional({ logic: bytesCodec.decode(signedTransactionDto.lsig.l), - args: signedTransactionDto.lsig.arg?.map((arg) => bytesCodec.decode(arg)), - signature: bytesCodec.decodeOptional(signedTransactionDto.lsig.sig), - ...(signedTransactionDto.lsig.msig ? { multiSignature: fromMultisigSignatureDto(signedTransactionDto.lsig.msig) } : undefined), + ...(args && { args }), + ...(signature && { signature }), + ...(multiSignature && { multiSignature }), + ...(logicMultiSignature && { logicMultiSignature }), }) + if (logicSignature) { + stx.logicSignature = logicSignature + } } - if (signedTransactionDto.sgnr) { - stx.authAddress = addressCodec.decodeOptional(signedTransactionDto.sgnr) + const authAddress = signedTransactionDto.sgnr && addressCodec.decodeOptional(signedTransactionDto.sgnr) + if (authAddress) { + stx.authAddress = authAddress } return stx diff --git a/packages/transact/src/transactions/transaction.ts b/packages/transact/src/transactions/transaction.ts index df08ccea..4ba14971 100644 --- a/packages/transact/src/transactions/transaction.ts +++ b/packages/transact/src/transactions/transaction.ts @@ -18,6 +18,9 @@ import { booleanCodec, bytesArrayCodec, bytesCodec, + fixedBytes1793Codec, + fixedBytes32Codec, + fixedBytes64Codec, numberCodec, stringCodec, } from '../encoding/codecs' @@ -308,8 +311,8 @@ export function validateTransaction(transaction: Transaction): void { */ export function encodeTransactionRaw(transaction: Transaction): Uint8Array { validateTransaction(transaction) - const encodingData = toTransactionDto(transaction) - return encodeMsgpack(encodingData) + const transactionDto = toTransactionDto(transaction) + return encodeMsgpack(transactionDto) } /** @@ -558,12 +561,12 @@ export function toTransactionDto(transaction: Transaction): TransactionDto { lv: bigIntCodec.encode(transaction.lastValid), snd: addressCodec.encode(transaction.sender), gen: stringCodec.encode(transaction.genesisId), - gh: bytesCodec.encode(transaction.genesisHash), + gh: fixedBytes32Codec.encode(transaction.genesisHash), fee: bigIntCodec.encode(transaction.fee), note: bytesCodec.encode(transaction.note), - lx: bytesCodec.encode(transaction.lease), + lx: fixedBytes32Codec.encode(transaction.lease), rekey: addressCodec.encode(transaction.rekeyTo), - grp: bytesCodec.encode(transaction.group), + grp: fixedBytes32Codec.encode(transaction.group), } // Add transaction type specific fields @@ -591,7 +594,7 @@ export function toTransactionDto(transaction: Transaction): TransactionDto { un: stringCodec.encode(transaction.assetConfig.unitName), an: stringCodec.encode(transaction.assetConfig.assetName), au: stringCodec.encode(transaction.assetConfig.url), - am: bytesCodec.encode(transaction.assetConfig.metadataHash), + am: fixedBytes32Codec.encode(transaction.assetConfig.metadataHash), m: addressCodec.encode(transaction.assetConfig.manager), f: addressCodec.encode(transaction.assetConfig.freeze), c: addressCodec.encode(transaction.assetConfig.clawback), @@ -748,12 +751,12 @@ export function toTransactionDto(transaction: Transaction): TransactionDto { } if (transaction.keyRegistration) { - txDto.votekey = bytesCodec.encode(transaction.keyRegistration.voteKey) - txDto.selkey = bytesCodec.encode(transaction.keyRegistration.selectionKey) + txDto.votekey = fixedBytes32Codec.encode(transaction.keyRegistration.voteKey) + txDto.selkey = fixedBytes32Codec.encode(transaction.keyRegistration.selectionKey) txDto.votefst = bigIntCodec.encode(transaction.keyRegistration.voteFirst) txDto.votelst = bigIntCodec.encode(transaction.keyRegistration.voteLast) txDto.votekd = bigIntCodec.encode(transaction.keyRegistration.voteKeyDilution) - txDto.sprfkey = bytesCodec.encode(transaction.keyRegistration.stateProofKey) + txDto.sprfkey = fixedBytes64Codec.encode(transaction.keyRegistration.stateProofKey) txDto.nonpart = booleanCodec.encode(transaction.keyRegistration.nonParticipation) } @@ -761,14 +764,14 @@ export function toTransactionDto(transaction: Transaction): TransactionDto { txDto.hb = heartbeatParamsDtoCodec.encode({ a: addressCodec.encode(transaction.heartbeat.address), prf: heartbeatProofDtoCodec.encode({ - s: bytesCodec.encode(transaction.heartbeat.proof.sig), - p: bytesCodec.encode(transaction.heartbeat.proof.pk), - p2: bytesCodec.encode(transaction.heartbeat.proof.pk2), - p1s: bytesCodec.encode(transaction.heartbeat.proof.pk1Sig), - p2s: bytesCodec.encode(transaction.heartbeat.proof.pk2Sig), + s: fixedBytes64Codec.encode(transaction.heartbeat.proof.sig), + p: fixedBytes32Codec.encode(transaction.heartbeat.proof.pk), + p2: fixedBytes32Codec.encode(transaction.heartbeat.proof.pk2), + p1s: fixedBytes64Codec.encode(transaction.heartbeat.proof.pk1Sig), + p2s: fixedBytes64Codec.encode(transaction.heartbeat.proof.pk2Sig), }), sd: bytesCodec.encode(transaction.heartbeat.seed), - vid: bytesCodec.encode(transaction.heartbeat.voteId), + vid: fixedBytes32Codec.encode(transaction.heartbeat.voteId), kd: bigIntCodec.encode(transaction.heartbeat.keyDilution), }) } @@ -795,14 +798,14 @@ export function toTransactionDto(transaction: Transaction): TransactionDto { idx: bigIntCodec.encode(reveal.sigslot.sig.vectorCommitmentIndex), prf: toMerkleArrayProofDto(reveal.sigslot.sig.proof), vkey: { - k: bytesCodec.encode(reveal.sigslot.sig.verifyingKey.publicKey), + k: fixedBytes1793Codec.encode(reveal.sigslot.sig.verifyingKey.publicKey), }, }, l: bigIntCodec.encode(reveal.sigslot.lowerSigWeight), }, p: { p: { - cmt: bytesCodec.encode(reveal.participant.verifier.commitment), + cmt: fixedBytes64Codec.encode(reveal.participant.verifier.commitment), lf: bigIntCodec.encode(reveal.participant.verifier.keyLifetime), }, w: bigIntCodec.encode(reveal.participant.weight), @@ -831,170 +834,206 @@ export function toTransactionDto(transaction: Transaction): TransactionDto { export function fromTransactionDto(transactionDto: TransactionDto): Transaction { const transactionType = fromTransactionTypeDto(transactionDto.type) + const fee = bigIntCodec.decodeOptional(transactionDto.fee) + const genesisId = stringCodec.decodeOptional(transactionDto.gen) + const genesisHash = fixedBytes32Codec.decodeOptional(transactionDto.gh) + const note = bytesCodec.decodeOptional(transactionDto.note) + const lease = fixedBytes32Codec.decodeOptional(transactionDto.lx) + const rekeyTo = addressCodec.decodeOptional(transactionDto.rekey) + const group = fixedBytes32Codec.decodeOptional(transactionDto.grp) + const tx: Transaction = { type: transactionType, sender: addressCodec.decode(transactionDto.snd), firstValid: bigIntCodec.decode(transactionDto.fv), lastValid: bigIntCodec.decode(transactionDto.lv), - fee: bigIntCodec.decodeOptional(transactionDto.fee), - genesisId: stringCodec.decodeOptional(transactionDto.gen), - genesisHash: bytesCodec.decodeOptional(transactionDto.gh), - note: bytesCodec.decodeOptional(transactionDto.note), - lease: bytesCodec.decodeOptional(transactionDto.lx), - rekeyTo: addressCodec.decodeOptional(transactionDto.rekey), - group: bytesCodec.decodeOptional(transactionDto.grp), + ...(fee && { fee }), + ...(genesisId && { genesisId }), + ...(genesisHash && { genesisHash }), + ...(note && { note }), + ...(lease && { lease }), + ...(rekeyTo && { rekeyTo }), + ...(group && { group }), } // Add transaction type specific fields switch (transactionType) { - case TransactionType.Payment: + case TransactionType.Payment: { + const paymentCloseRemainderTo = addressCodec.decodeOptional(transactionDto.close) tx.payment = { amount: bigIntCodec.decode(transactionDto.amt), receiver: addressCodec.decode(transactionDto.rcv), - closeRemainderTo: addressCodec.decodeOptional(transactionDto.close), + ...(paymentCloseRemainderTo && { closeRemainderTo: paymentCloseRemainderTo }), } break - case TransactionType.AssetTransfer: + } + case TransactionType.AssetTransfer: { + const assetTransferCloseRemainderTo = addressCodec.decodeOptional(transactionDto.aclose) + const assetSender = addressCodec.decodeOptional(transactionDto.asnd) tx.assetTransfer = { assetId: bigIntCodec.decode(transactionDto.xaid), amount: bigIntCodec.decode(transactionDto.aamt), receiver: addressCodec.decode(transactionDto.arcv), - closeRemainderTo: addressCodec.decodeOptional(transactionDto.aclose), - assetSender: addressCodec.decodeOptional(transactionDto.asnd), + ...(assetTransferCloseRemainderTo && { closeRemainderTo: assetTransferCloseRemainderTo }), + ...(assetSender && { assetSender }), } break - case TransactionType.AssetConfig: + } + case TransactionType.AssetConfig: { + const assetParams = transactionDto.apar + let assetConfigParams: Omit | undefined = undefined + + if (assetParams) { + const total = bigIntCodec.decodeOptional(assetParams.t) + const decimals = numberCodec.decodeOptional(assetParams.dc) + const defaultFrozen = booleanCodec.decodeOptional(assetParams.df) + const unitName = stringCodec.decodeOptional(assetParams.un) + const assetName = stringCodec.decodeOptional(assetParams.an) + const url = stringCodec.decodeOptional(assetParams.au) + const metadataHash = fixedBytes32Codec.decodeOptional(assetParams.am) + const manager = addressCodec.decodeOptional(assetParams.m) + const reserve = addressCodec.decodeOptional(assetParams.r) + const freeze = addressCodec.decodeOptional(assetParams.f) + const clawback = addressCodec.decodeOptional(assetParams.c) + + assetConfigParams = { + ...(total !== undefined && { total }), + ...(decimals !== undefined && { decimals }), + ...(defaultFrozen !== undefined && { defaultFrozen }), + ...(unitName && { unitName }), + ...(assetName && { assetName }), + ...(url && { url }), + ...(metadataHash && { metadataHash }), + ...(manager && { manager }), + ...(reserve && { reserve }), + ...(freeze && { freeze }), + ...(clawback && { clawback }), + } + } + tx.assetConfig = { assetId: bigIntCodec.decode(transactionDto.caid), - ...(transactionDto.apar !== undefined - ? { - total: bigIntCodec.decodeOptional(transactionDto.apar.t), - decimals: numberCodec.decodeOptional(transactionDto.apar.dc), - defaultFrozen: booleanCodec.decodeOptional(transactionDto.apar.df), - unitName: stringCodec.decodeOptional(transactionDto.apar.un), - assetName: stringCodec.decodeOptional(transactionDto.apar.an), - url: stringCodec.decodeOptional(transactionDto.apar.au), - metadataHash: bytesCodec.decodeOptional(transactionDto.apar.am), - manager: addressCodec.decodeOptional(transactionDto.apar.m), - reserve: addressCodec.decodeOptional(transactionDto.apar.r), - freeze: addressCodec.decodeOptional(transactionDto.apar.f), - clawback: addressCodec.decodeOptional(transactionDto.apar.c), - } - : undefined), + ...(assetConfigParams !== undefined && Object.keys(assetConfigParams).length > 0 && assetConfigParams), } break - case TransactionType.AssetFreeze: + } + case TransactionType.AssetFreeze: { tx.assetFreeze = { assetId: bigIntCodec.decode(transactionDto.faid), freezeTarget: addressCodec.decode(transactionDto.fadd), frozen: booleanCodec.decode(transactionDto.afrz), } break - case TransactionType.AppCall: - tx.appCall = { - appId: bigIntCodec.decode(transactionDto.apid), - onComplete: fromOnApplicationCompleteDto(transactionDto.apan), - approvalProgram: bytesCodec.decodeOptional(transactionDto.apap), - clearStateProgram: bytesCodec.decodeOptional(transactionDto.apsu), - args: transactionDto.apaa?.map((arg) => bytesCodec.decode(arg)), - accountReferences: transactionDto.apat?.map((addr) => addressCodec.decode(addr)), - appReferences: transactionDto.apfa?.map((id) => bigIntCodec.decode(id)), - assetReferences: transactionDto.apas?.map((id) => bigIntCodec.decode(id)), - boxReferences: transactionDto.apbx?.map((box) => { - const index = typeof box.i === 'bigint' ? Number(box.i) : (box.i ?? 0) - let appId: bigint - if (index === 0) { - // 0 means current app - appId = 0n - } else { - // 1-based index into foreignApps array - const foreignAppId = transactionDto.apfa?.[index - 1] - if (foreignAppId === undefined) { - throw new Error(`Failed to find the app reference at index ${index - 1}`) + } + case TransactionType.AppCall: { + const approvalProgram = bytesCodec.decodeOptional(transactionDto.apap) + const clearStateProgram = bytesCodec.decodeOptional(transactionDto.apsu) + const args = transactionDto.apaa?.map((arg) => bytesCodec.decode(arg)) + const accountReferences = transactionDto.apat?.map((addr) => addressCodec.decode(addr)) + const appReferences = transactionDto.apfa?.map((id) => bigIntCodec.decode(id)) + const assetReferences = transactionDto.apas?.map((id) => bigIntCodec.decode(id)) + const extraProgramPages = numberCodec.decodeOptional(transactionDto.apep) + const boxReferences = transactionDto.apbx?.map((box) => { + const index = typeof box.i === 'bigint' ? Number(box.i) : (box.i ?? 0) + let appId: bigint + if (index === 0) { + // 0 means current app + appId = 0n + } else { + // 1-based index into foreignApps array + const foreignAppId = transactionDto.apfa?.[index - 1] + if (foreignAppId === undefined) { + throw new Error(`Failed to find the app reference at index ${index - 1}`) + } + appId = bigIntCodec.decode(foreignAppId) + } + return { + appId: appId, + name: bytesCodec.decode(box.n), + } + }) + const accessReferences: AccessReference[] = [] + if (transactionDto.al) { + for (const ref of transactionDto.al) { + if (ref.d) { + accessReferences.push({ address: addressCodec.decode(ref.d) }) + continue + } + if (ref.s !== undefined) { + accessReferences.push({ assetId: bigIntCodec.decode(ref.s) }) + continue + } + if (ref.p !== undefined) { + accessReferences.push({ appId: bigIntCodec.decode(ref.p) }) + continue + } + if (ref.h) { + const addrIdx = ref.h.d ?? 0 + const assetIdx = ref.h.s + + if (assetIdx === undefined) { + throw new Error(`Holding missing asset index: ${JSON.stringify(ref.h)}`) } - appId = bigIntCodec.decode(foreignAppId) + + const holdingAddress = addrIdx === 0 ? ZERO_ADDRESS : transactionDto.al[addrIdx - 1].d! + const holdingAssetId = transactionDto.al[assetIdx - 1].s! + + accessReferences.push({ + holding: { + address: typeof holdingAddress === 'string' ? holdingAddress : addressCodec.decode(holdingAddress), + assetId: bigIntCodec.decode(holdingAssetId), + }, + }) + continue } - return { - appId: appId, - name: bytesCodec.decode(box.n), + + if (ref.l) { + const addrIdx = ref.l.d ?? 0 + const appIdx = ref.l.p ?? 0 + + const localsAddress = addrIdx === 0 ? ZERO_ADDRESS : transactionDto.al[addrIdx - 1].d! + const localsAppId = appIdx === 0 ? BigInt(0) : transactionDto.al[appIdx - 1].p! + + accessReferences.push({ + locals: { + address: typeof localsAddress === 'string' ? localsAddress : addressCodec.decode(localsAddress), + appId: bigIntCodec.decode(localsAppId), + }, + }) + continue } - }), - accessReferences: transactionDto.al - ? (() => { - const accessList = transactionDto.al! - const result: AccessReference[] = [] - - for (const ref of accessList) { - if (ref.d) { - result.push({ address: addressCodec.decode(ref.d) }) - continue - } - if (ref.s !== undefined) { - result.push({ assetId: bigIntCodec.decode(ref.s) }) - continue - } - if (ref.p !== undefined) { - result.push({ appId: bigIntCodec.decode(ref.p) }) - continue - } - if (ref.h) { - const addrIdx = ref.h.d ?? 0 - const assetIdx = ref.h.s - - if (assetIdx === undefined) { - throw new Error(`Holding missing asset index: ${JSON.stringify(ref.h)}`) - } - - const holdingAddress = addrIdx === 0 ? ZERO_ADDRESS : accessList[addrIdx - 1].d! - const holdingAssetId = accessList[assetIdx - 1].s! - - result.push({ - holding: { - address: typeof holdingAddress === 'string' ? holdingAddress : addressCodec.decode(holdingAddress), - assetId: bigIntCodec.decode(holdingAssetId), - }, - }) - continue - } + if (ref.b) { + const boxAppIdx = ref.b.i ?? 0 + const name = ref.b.n - if (ref.l) { - const addrIdx = ref.l.d ?? 0 - const appIdx = ref.l.p ?? 0 + if (!name) { + throw new Error(`Box missing name: ${JSON.stringify(ref.b)}`) + } - const localsAddress = addrIdx === 0 ? ZERO_ADDRESS : accessList[addrIdx - 1].d! - const localsAppId = appIdx === 0 ? BigInt(0) : accessList[appIdx - 1].p! + const boxAppId = boxAppIdx === 0 ? BigInt(0) : transactionDto.al[boxAppIdx - 1].p! - result.push({ - locals: { - address: typeof localsAddress === 'string' ? localsAddress : addressCodec.decode(localsAddress), - appId: bigIntCodec.decode(localsAppId), - }, - }) - continue - } - if (ref.b) { - const boxAppIdx = ref.b.i ?? 0 - const name = ref.b.n - - if (!name) { - throw new Error(`Box missing name: ${JSON.stringify(ref.b)}`) - } - - const boxAppId = boxAppIdx === 0 ? BigInt(0) : accessList[boxAppIdx - 1].p! - - result.push({ - box: { - appId: bigIntCodec.decode(boxAppId), - name: bytesCodec.decode(name), - }, - }) - } - } + accessReferences.push({ + box: { + appId: bigIntCodec.decode(boxAppId), + name: bytesCodec.decode(name), + }, + }) + } + } + } - return result - })() - : undefined, - extraProgramPages: numberCodec.decodeOptional(transactionDto.apep), + tx.appCall = { + appId: bigIntCodec.decode(transactionDto.apid), + onComplete: fromOnApplicationCompleteDto(transactionDto.apan), + ...(approvalProgram && { approvalProgram }), + ...(clearStateProgram && { clearStateProgram }), + ...(args && { args }), + ...(accountReferences && { accountReferences }), + ...(appReferences && { appReferences }), + ...(assetReferences && { assetReferences }), + ...(extraProgramPages !== undefined && { extraProgramPages }), + ...(boxReferences && boxReferences.length > 0 && { boxReferences }), + ...(accessReferences && accessReferences.length > 0 && { accessReferences }), ...(transactionDto.apgs !== undefined ? { globalStateSchema: stateSchemaCodec.decodeOptional({ @@ -1013,35 +1052,46 @@ export function fromTransactionDto(transactionDto: TransactionDto): Transaction : undefined), } break - case TransactionType.KeyRegistration: + } + case TransactionType.KeyRegistration: { + const voteKey = fixedBytes32Codec.decodeOptional(transactionDto.votekey) + const selectionKey = fixedBytes32Codec.decodeOptional(transactionDto.selkey) + const voteFirst = bigIntCodec.decodeOptional(transactionDto.votefst) + const voteLast = bigIntCodec.decodeOptional(transactionDto.votelst) + const voteKeyDilution = bigIntCodec.decodeOptional(transactionDto.votekd) + const stateProofKey = fixedBytes64Codec.decodeOptional(transactionDto.sprfkey) + const nonParticipation = booleanCodec.decodeOptional(transactionDto.nonpart) + tx.keyRegistration = { - voteKey: bytesCodec.decodeOptional(transactionDto.votekey), - selectionKey: bytesCodec.decodeOptional(transactionDto.selkey), - voteFirst: bigIntCodec.decodeOptional(transactionDto.votefst), - voteLast: bigIntCodec.decodeOptional(transactionDto.votelst), - voteKeyDilution: bigIntCodec.decodeOptional(transactionDto.votekd), - stateProofKey: bytesCodec.decodeOptional(transactionDto.sprfkey), - nonParticipation: booleanCodec.decodeOptional(transactionDto.nonpart), + ...(voteKey && { voteKey }), + ...(selectionKey && { selectionKey }), + ...(voteFirst !== undefined && { voteFirst }), + ...(voteLast !== undefined && { voteLast }), + ...(voteKeyDilution !== undefined && { voteKeyDilution }), + ...(stateProofKey && { stateProofKey }), + ...(nonParticipation !== undefined && { nonParticipation }), } break - case TransactionType.Heartbeat: + } + case TransactionType.Heartbeat: { if (transactionDto.hb) { tx.heartbeat = { address: addressCodec.decode(transactionDto.hb.a), proof: { - sig: bytesCodec.decode(transactionDto.hb.prf?.s), - pk: bytesCodec.decode(transactionDto.hb.prf?.p), - pk2: bytesCodec.decode(transactionDto.hb.prf?.p2), - pk1Sig: bytesCodec.decode(transactionDto.hb.prf?.p1s), - pk2Sig: bytesCodec.decode(transactionDto.hb.prf?.p2s), + sig: fixedBytes64Codec.decode(transactionDto.hb.prf?.s), + pk: fixedBytes32Codec.decode(transactionDto.hb.prf?.p), + pk2: fixedBytes32Codec.decode(transactionDto.hb.prf?.p2), + pk1Sig: fixedBytes64Codec.decode(transactionDto.hb.prf?.p1s), + pk2Sig: fixedBytes64Codec.decode(transactionDto.hb.prf?.p2s), }, seed: bytesCodec.decode(transactionDto.hb.sd), - voteId: bytesCodec.decode(transactionDto.hb.vid), + voteId: fixedBytes32Codec.decode(transactionDto.hb.vid), keyDilution: bigIntCodec.decode(transactionDto.hb.kd), } } break - case TransactionType.StateProof: + } + case TransactionType.StateProof: { tx.stateProof = { stateProofType: transactionDto.sptype ?? 0, stateProof: transactionDto.sp @@ -1061,14 +1111,14 @@ export function fromTransactionDto(transactionDto: TransactionDto): Transaction vectorCommitmentIndex: bigIntCodec.decode(reveal.s?.s?.idx), proof: fromMerkleArrayProofDto(reveal.s?.s?.prf), verifyingKey: { - publicKey: bytesCodec.decode(reveal.s?.s?.vkey?.k), + publicKey: fixedBytes1793Codec.decode(reveal.s?.s?.vkey?.k), }, }, lowerSigWeight: bigIntCodec.decode(reveal.s?.l), }, participant: { verifier: { - commitment: bytesCodec.decode(reveal.p?.p?.cmt), + commitment: fixedBytes64Codec.decode(reveal.p?.p?.cmt), keyLifetime: bigIntCodec.decode(reveal.p?.p?.lf), }, weight: bigIntCodec.decode(reveal.p?.w), @@ -1089,6 +1139,7 @@ export function fromTransactionDto(transactionDto: TransactionDto): Transaction : undefined, } break + } } return tx diff --git a/src/transaction/transaction.ts b/src/transaction/transaction.ts index c824c628..0f8b972a 100644 --- a/src/transaction/transaction.ts +++ b/src/transaction/transaction.ts @@ -947,7 +947,7 @@ export const sendAtomicTransactionComposer = async function (atcSend: AtomicTran if (simulate && simulate.txnGroups[0].failedAt) { for (const txn of simulate.txnGroups[0].txnResults) { err.traces.push({ - trace: AlgorandSerializer.encode(txn.execTrace, SimulationTransactionExecTraceMeta, 'map'), + trace: txn.execTrace ? AlgorandSerializer.encode(txn.execTrace, SimulationTransactionExecTraceMeta, 'map') : undefined, appBudget: txn.appBudgetConsumed, logicSigBudget: txn.logicSigBudgetConsumed, logs: txn.txnResult.logs, diff --git a/src/types/asset-manager.ts b/src/types/asset-manager.ts index 5146fc32..070e8e02 100644 --- a/src/types/asset-manager.ts +++ b/src/types/asset-manager.ts @@ -169,7 +169,7 @@ export class AssetManager { const asset = await this._algod.getAssetById(assetId) return { - assetId: BigInt(asset.index), + assetId: BigInt(asset.id), total: BigInt(asset.params.total), decimals: Number(asset.params.decimals), assetName: asset.params.name, diff --git a/tsconfig.base.json b/tsconfig.base.json index e90dc13b..6048b823 100644 --- a/tsconfig.base.json +++ b/tsconfig.base.json @@ -12,6 +12,7 @@ "resolveJsonModule": true, "skipLibCheck": true, "declarationMap": true, + "strict": true, "baseUrl": ".", "paths": { "@algorandfoundation/algokit-abi": ["packages/abi/src"],