From 2a599e962ad7fb83e1d5895d760a117f2d71e96c Mon Sep 17 00:00:00 2001 From: Jakub Dzikowski Date: Thu, 17 Sep 2026 13:50:52 +0200 Subject: [PATCH 1/3] Fix: Keep inbound transaction payloads as JSON objects Fabric constructed a DTO class from parameter metadata before parseValidDTO ran. Pass the parsed JSON through and mark Fabric parameters as type object so the Submit/Evaluate input class is the one that is instantiated. Co-authored-by: Cursor --- chaincode/src/contracts/GalaTransaction.ts | 9 +++++++++ chaincode/src/utils/GalaJSONSerializer.ts | 16 ++++------------ 2 files changed, 13 insertions(+), 12 deletions(-) diff --git a/chaincode/src/contracts/GalaTransaction.ts b/chaincode/src/contracts/GalaTransaction.ts index 5a8841c88a..4029d72288 100644 --- a/chaincode/src/contracts/GalaTransaction.ts +++ b/chaincode/src/contracts/GalaTransaction.ts @@ -356,6 +356,15 @@ function GalaTransaction( // Ensure this is an actual HLF transaction. // If this annotation is missing, you cannot call the chaincode method Transaction(isWrite)(target, propertyKey); + + const transactions = + (Reflect.getMetadata("fabric:transactions", target) as + | { name?: string; parameters?: { schema?: unknown }[] }[] + | undefined) ?? []; + const transaction = transactions.find((entry) => entry.name === propertyKey); + for (const parameter of transaction?.parameters ?? []) { + parameter.schema = { type: "object" }; + } }; } diff --git a/chaincode/src/utils/GalaJSONSerializer.ts b/chaincode/src/utils/GalaJSONSerializer.ts index fda39c6c03..cd60ae4924 100644 --- a/chaincode/src/utils/GalaJSONSerializer.ts +++ b/chaincode/src/utils/GalaJSONSerializer.ts @@ -13,7 +13,6 @@ * limitations under the License. */ import { serialize } from "@gala-chain/api"; -import { plainToInstance } from "class-transformer"; import * as Logger from "fabric-contract-api/lib/logger"; /* @@ -147,17 +146,10 @@ export default class GalaJSONSerializer { jsonForValidation = value; return { value, jsonForValidation }; } else if (schema.type === "object") { - logger.debug(`${loggerPrefix} fromBuffer assuming data as object`); - // so this implies we have some json that should be formed up as an object - // need to get the constructor - const cnstr = fullschema.components.schemas[schema.$id].cnstr; - if (cnstr) { - logger.debug(`${loggerPrefix} fromBuffer handling data as object`); - jsonForValidation = JSON.parse(stringData); - value = plainToInstance(cnstr, jsonForValidation); - return { value, jsonForValidation }; - } - logger.debug(`${loggerPrefix} no known constructor`); + logger.debug(`${loggerPrefix} fromBuffer handling data as JSON object`); + jsonForValidation = JSON.parse(stringData); + value = jsonForValidation; + return { value, jsonForValidation }; } else if (schema.type === "array") { jsonForValidation = JSON.parse(stringData); From bd26b19c78cf88973f20042504c27c4dee3c94b5 Mon Sep 17 00:00:00 2001 From: Jakub Dzikowski Date: Thu, 17 Sep 2026 16:01:58 +0200 Subject: [PATCH 2/3] Fix: Parse object payloads as JSON in the test serializer Keep the chain-test copy in sync so inbound DTOs stay plain objects after Fabric schema type is forced to object. --- chain-test/src/unit/GalaJSONSerializer.ts | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/chain-test/src/unit/GalaJSONSerializer.ts b/chain-test/src/unit/GalaJSONSerializer.ts index 056916dec6..1128a02db6 100644 --- a/chain-test/src/unit/GalaJSONSerializer.ts +++ b/chain-test/src/unit/GalaJSONSerializer.ts @@ -12,7 +12,6 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { plainToInstance } from "class-transformer"; import * as Logger from "fabric-contract-api/lib/logger"; import { serializeValue } from "../serialize"; @@ -150,17 +149,10 @@ export default class GalaJSONSerializer { jsonForValidation = value; return { value, jsonForValidation }; } else if (schema.type === "object") { - logger.debug(`${loggerPrefix} fromBuffer assuming data as object`); - // so this implies we have some json that should be formed up as an object - // need to get the constructor - const cnstr = fullschema.components.schemas[schema.$id].cnstr; - if (cnstr) { - logger.debug(`${loggerPrefix} fromBuffer handling data as object`); - jsonForValidation = JSON.parse(stringData); - value = plainToInstance(cnstr, jsonForValidation); - return { value, jsonForValidation }; - } - logger.debug(`${loggerPrefix} no known constructor`); + logger.debug(`${loggerPrefix} fromBuffer handling data as JSON object`); + jsonForValidation = JSON.parse(stringData); + value = jsonForValidation; + return { value, jsonForValidation }; } else if (schema.type === "array") { jsonForValidation = JSON.parse(stringData); From 7ba736f533e26ebb2a87a6f4606274435bd067cc Mon Sep 17 00:00:00 2001 From: Jakub Dzikowski Date: Thu, 17 Sep 2026 16:17:09 +0200 Subject: [PATCH 3/3] Docs: Explain why Fabric parameter schemas are overwritten Fabric keys transaction metadata by method name, so the same name on two contracts can bind the wrong DTO. parseValidDTO does the real parse. --- chaincode/src/contracts/GalaTransaction.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/chaincode/src/contracts/GalaTransaction.ts b/chaincode/src/contracts/GalaTransaction.ts index 4029d72288..a6629e2c9b 100644 --- a/chaincode/src/contracts/GalaTransaction.ts +++ b/chaincode/src/contracts/GalaTransaction.ts @@ -357,6 +357,10 @@ function GalaTransaction( // If this annotation is missing, you cannot call the chaincode method Transaction(isWrite)(target, propertyKey); + // Overwrite all Reflect metadata types. Fabric stores them by method name, + // so duplicate method names across contracts can get the wrong type. + // Strict serialization is applied in GalaTransaction via parseValidDTO. + // Fabric's generic object schema is sufficient here. const transactions = (Reflect.getMetadata("fabric:transactions", target) as | { name?: string; parameters?: { schema?: unknown }[] }[]