AssetFactory.create sends malformed calldata: deploy routine args are not spread when encoded
Summary
AssetFactory.create accepts deploy-routine arguments as rest params (...calldata), but when it hands them off to ManifestCoder.encodeArguments, it passes the entire calldata array in a single argument slot instead of spreading. ManifestCoder.encodeArguments(routine, ...args) then reads args[field.slot] per declared field — slot 0 gets the whole array, every other slot gets undefined. The resulting calldata is malformed: arg 0 gets serialized as garbage and the remaining args are silently truncated.
Every MASX deploy whose Init routine accepts at least one argument reverts on-chain with:
builtin.CallFailure: invalid inputs: malformed data for '<first_arg>': data does not decode to an identifier: incompatible wire: mismatched data length for byte array
(<first_arg> is whatever the first declared field is — typically treasury_addr, owner, etc.) This affects the canonical AssetFactory.create example in the docs verbatim.
Version
Reproduced on every published 0.7.0 release candidate: rc9, rc10, rc11, rc13, rc14, rc15. The line has not changed across these.
Location
Source — js-moi-asset/src.ts/asset-factory.ts (around line 55):
payload.logic_payload.calldata = manifestCoder.encodeArguments(callsite, calldata) as Hex; // ← missing spread
Build output — js-moi-asset/lib.cjs/asset-factory.js:40 and lib.esm/asset-factory.js:37:
if (argsLen > 0) {
const manifestCoder = new ManifestCoder(manifest);
payload.logic_payload.calldata = manifestCoder.encodeArguments(callsite, calldata); // ← missing spread
}
For contrast, AssetDriver (used by MASX invokes) gets this right:
// js-moi-asset/lib.cjs/asset-driver.js
payload.calldata = this.manifestCoder.encodeArguments(ixObject.routine.name, ...ixObject.arguments);
— which is why MASX invokes work and only MASX deploys with args are broken.
Root cause
ManifestCoder.encodeArguments uses rest params:
encodeArguments(routine, ...args) {
const element = this.elementDescriptor.getRoutineElement(routine).data;
const schema = this.schema.parseFields(element.accepts ?? []);
const calldata = Object.values(element.accepts).reduce((acc, field) => {
acc[field.label] = this.parseCalldata(schema.fields[field.label], args[field.slot]);
return acc;
}, {});
...
}
When AssetFactory.create calls encodeArguments(callsite, calldata) with the whole array, args becomes [calldata]. So args[0] is the entire [arg1, arg2, ...] array, and args[1] is undefined. The schema then tries to encode that array as a bytes (or whatever the first declared type is) field and produces a malformed blob; the runtime can't decode it back into the declared type.
Reproduction
A minimal cocolang asset whose Init takes any argument is enough. Using a (Identifier, U64) Init:
import 'dotenv/config'
import { VoyageProvider, Wallet, AssetFactory } from 'js-moi-sdk'
import manifest from './taxtoken.json' with { type: 'json' }
const provider = new VoyageProvider('devnet')
const wallet = await Wallet.fromMnemonic(process.env.MOI_MNEMONIC, "m/44'/6174'/7020'/0/0")
wallet.connect(provider)
const identifier = await wallet.getIdentifier()
const ctx = AssetFactory.create(
wallet, 'TAXED', 1_000_000, identifier.toString(), true, manifest, 'Init',
identifier.toBytes(), 500,
)
const response = await ctx.send()
const receipt = await response.wait()
console.log(receipt.ix_operations[0].data.error)
// → "builtin.CallFailure invalid inputs: malformed data for 'treasury_addr':
// data does not decode to an identifier: incompatible wire:
// mismatched data length for byte array"
Fix
Add the spread on src.ts/asset-factory.ts:55 (and matching build outputs):
payload.logic_payload.calldata = manifestCoder.encodeArguments(callsite, ...calldata) as Hex;
That is the entire patch — three characters. Workaround for downstream users until release: patch-package the spread into lib.cjs/asset-factory.js and lib.esm/asset-factory.js.
AssetFactory.createsends malformed calldata: deploy routine args are not spread when encodedSummary
AssetFactory.createaccepts deploy-routine arguments as rest params (...calldata), but when it hands them off toManifestCoder.encodeArguments, it passes the entirecalldataarray in a single argument slot instead of spreading.ManifestCoder.encodeArguments(routine, ...args)then readsargs[field.slot]per declared field — slot 0 gets the whole array, every other slot getsundefined. The resulting calldata is malformed: arg 0 gets serialized as garbage and the remaining args are silently truncated.Every MASX deploy whose Init routine accepts at least one argument reverts on-chain with:
(
<first_arg>is whatever the first declared field is — typicallytreasury_addr,owner, etc.) This affects the canonicalAssetFactory.createexample in the docs verbatim.Version
Reproduced on every published
0.7.0release candidate:rc9,rc10,rc11,rc13,rc14,rc15. The line has not changed across these.Location
Source —
js-moi-asset/src.ts/asset-factory.ts(around line 55):Build output —
js-moi-asset/lib.cjs/asset-factory.js:40andlib.esm/asset-factory.js:37:For contrast,
AssetDriver(used by MASX invokes) gets this right:— which is why MASX invokes work and only MASX deploys with args are broken.
Root cause
ManifestCoder.encodeArgumentsuses rest params:When
AssetFactory.createcallsencodeArguments(callsite, calldata)with the whole array,argsbecomes[calldata]. Soargs[0]is the entire[arg1, arg2, ...]array, andargs[1]isundefined. The schema then tries to encode that array as abytes(or whatever the first declared type is) field and produces a malformed blob; the runtime can't decode it back into the declared type.Reproduction
A minimal cocolang asset whose Init takes any argument is enough. Using a
(Identifier, U64)Init:Fix
Add the spread on
src.ts/asset-factory.ts:55(and matching build outputs):That is the entire patch — three characters. Workaround for downstream users until release:
patch-packagethe spread intolib.cjs/asset-factory.jsandlib.esm/asset-factory.js.