@parity/product-sdk-contracts 0.4.0 → 0.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/codegen.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { A as AbiEntry } from './types-DJDMz91q.js';
1
+ import { A as AbiEntry } from './types-BdHp-xWt.js';
2
2
  import 'polkadot-api';
3
3
  import '@parity/product-sdk-tx';
4
4
  import '@parity/product-sdk-signer';
package/dist/index.d.ts CHANGED
@@ -1,8 +1,8 @@
1
1
  import { HexString, SS58String, PolkadotClient, PolkadotSigner } from 'polkadot-api';
2
2
  import { Weight, SubmittableTransaction, TxResult } from '@parity/product-sdk-tx';
3
3
  export { BatchableCall, TxResult } from '@parity/product-sdk-tx';
4
- import { C as CdmJson, a as ContractManagerOptions, b as ContractDefaults, c as Contracts, d as Contract, e as ContractDef, A as AbiEntry, f as ContractOptions } from './types-DJDMz91q.js';
5
- export { g as AbiParam, h as CdmJsonContract, i as CdmJsonTarget, P as PrepareOptions, Q as QueryOptions, j as QueryResult, T as TxOptions } from './types-DJDMz91q.js';
4
+ import { C as CdmJson, a as ContractManagerOptions, b as ContractDefaults, c as Contracts, d as Contract, e as ContractDef, A as AbiEntry, f as ContractOptions } from './types-BdHp-xWt.js';
5
+ export { g as AbiParam, h as CdmJsonContract, i as CdmJsonTarget, P as PrepareOptions, Q as QueryOptions, j as QueryResult, T as TxOptions } from './types-BdHp-xWt.js';
6
6
  import '@parity/product-sdk-signer';
7
7
 
8
8
  /**
@@ -101,7 +101,7 @@ type ReviveDryRunCall = (origin: SS58String, dest: HexString, value: bigint, gas
101
101
  *
102
102
  * const client = await createChainClient({
103
103
  * chains: { assetHub: paseo_asset_hub },
104
- * rpcs: { assetHub: ["wss://sys.ibp.network/asset-hub-paseo"] },
104
+ * rpcs: { assetHub: ["wss://paseo-asset-hub-next-rpc.polkadot.io"] },
105
105
  * });
106
106
  * const runtime = createContractRuntimeFromClient(client.raw.assetHub, paseo_asset_hub);
107
107
  * ```
package/dist/index.js CHANGED
@@ -180,7 +180,7 @@ function wrapContract(runtime, address, abi, defaults) {
180
180
  if (!dryRun.result.success) {
181
181
  return {
182
182
  success: false,
183
- value: void 0,
183
+ value: dryRun.result.value,
184
184
  gasRequired: dryRun.weight_required
185
185
  };
186
186
  }
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/errors.ts","../src/wrap.ts","../src/runtime.ts","../src/manager.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["/** Base class for all contract errors. Use `instanceof ContractError` to catch any contract-related error. */\nexport class ContractError extends Error {\n constructor(message: string, options?: ErrorOptions) {\n super(message, options);\n this.name = \"ContractError\";\n }\n}\n\n/** No signer was available for a transaction call. */\nexport class ContractSignerMissingError extends ContractError {\n constructor() {\n super(\n \"No signer available. Pass { signer } in call options, \" +\n \"set defaultSigner, or provide a signerManager.\",\n );\n this.name = \"ContractSignerMissingError\";\n }\n}\n\n/** A contract was not found in the cdm.json manifest. */\nexport class ContractNotFoundError extends ContractError {\n readonly library: string;\n readonly targetHash: string;\n\n constructor(library: string, targetHash: string) {\n super(`Contract \"${library}\" not found in cdm.json for target ${targetHash}`);\n this.name = \"ContractNotFoundError\";\n this.library = library;\n this.targetHash = targetHash;\n }\n}\n\n/**\n * A pre-flight `ReviveApi.call` dry-run reported failure. Thrown from the `.tx()`\n * path before the extrinsic is built — prevents callers from paying gas on a\n * transaction the chain already told us would revert.\n *\n * `dispatchError` carries the chain's encoded error (typically `ModuleError`,\n * `ContractReverted`, `OutOfGas`, or `AccountNotMapped` — see the `Revive`\n * pallet error variants).\n */\nexport class ContractDryRunFailedError extends ContractError {\n readonly methodName: string;\n readonly dispatchError: unknown;\n\n constructor(methodName: string, dispatchError: unknown) {\n super(\n `Dry-run failed for \"${methodName}\": ${\n typeof dispatchError === \"string\" ? dispatchError : JSON.stringify(dispatchError)\n }. The transaction was not submitted.`,\n );\n this.name = \"ContractDryRunFailedError\";\n this.methodName = methodName;\n this.dispatchError = dispatchError;\n }\n}\n\nif (import.meta.vitest) {\n const { test, expect, describe } = import.meta.vitest;\n\n describe(\"ContractError\", () => {\n test(\"base error has correct name\", () => {\n const err = new ContractError(\"test\");\n expect(err.name).toBe(\"ContractError\");\n expect(err).toBeInstanceOf(Error);\n });\n\n test(\"instanceof catches all contract errors\", () => {\n expect(new ContractSignerMissingError()).toBeInstanceOf(ContractError);\n expect(new ContractNotFoundError(\"@a/b\", \"abc\")).toBeInstanceOf(ContractError);\n expect(new ContractDryRunFailedError(\"foo\", \"x\")).toBeInstanceOf(ContractError);\n });\n });\n\n describe(\"ContractSignerMissingError\", () => {\n test(\"message mentions signer options\", () => {\n const err = new ContractSignerMissingError();\n expect(err.message).toContain(\"signer\");\n expect(err.message).toContain(\"signerManager\");\n expect(err.name).toBe(\"ContractSignerMissingError\");\n });\n });\n\n describe(\"ContractNotFoundError\", () => {\n test(\"includes library and target\", () => {\n const err = new ContractNotFoundError(\"@test/foo\", \"abc123\");\n expect(err.library).toBe(\"@test/foo\");\n expect(err.targetHash).toBe(\"abc123\");\n expect(err.message).toContain(\"@test/foo\");\n expect(err.message).toContain(\"abc123\");\n });\n });\n\n describe(\"ContractDryRunFailedError\", () => {\n test(\"captures method name and dispatch error\", () => {\n const dispatchError = { type: \"Module\", value: { type: \"Revive\" } };\n const err = new ContractDryRunFailedError(\"transfer\", dispatchError);\n expect(err.methodName).toBe(\"transfer\");\n expect(err.dispatchError).toBe(dispatchError);\n expect(err.message).toContain(\"transfer\");\n expect(err.message).toContain(\"not submitted\");\n expect(err.name).toBe(\"ContractDryRunFailedError\");\n });\n\n test(\"handles string dispatch error without JSON-stringifying\", () => {\n const err = new ContractDryRunFailedError(\"foo\", \"ContractReverted\");\n expect(err.message).toContain(\"ContractReverted\");\n expect(err.message).not.toContain('\"ContractReverted\"');\n });\n });\n}\n","import type { HexString, PolkadotSigner, SS58String } from \"polkadot-api\";\nimport { encodeFunctionData, decodeFunctionResult, type Abi as ViemAbi } from \"viem\";\nimport { submitAndWatch } from \"@parity/product-sdk-tx\";\nimport { seedToAccount } from \"@parity/product-sdk-keys\";\nimport { createLogger } from \"@parity/product-sdk-logger\";\nimport { DEV_PHRASE, ss58Address } from \"@polkadot-labs/hdkd-helpers\";\nimport { ContractSignerMissingError, ContractDryRunFailedError } from \"./errors.js\";\nimport type { ContractRuntime } from \"./runtime.js\";\nimport type { BatchableCall, SubmittableTransaction } from \"@parity/product-sdk-tx\";\nimport type {\n AbiEntry,\n Contract,\n ContractDef,\n ContractDefaults,\n PrepareOptions,\n QueryOptions,\n QueryResult,\n TxOptions,\n} from \"./types.js\";\n\nconst log = createLogger(\"contracts\");\n\n/** Map of method name → ordered ABI parameter names. */\nfunction buildMethodArgMap(abi: AbiEntry[]): Record<string, string[]> {\n const map: Record<string, string[]> = {};\n for (const entry of abi) {\n if (entry.type === \"function\" && entry.name) {\n map[entry.name] = entry.inputs.map((p) => p.name);\n }\n }\n return map;\n}\n\n/**\n * If the caller passed more arguments than the ABI expects and the last\n * argument is a plain object, treat it as an options override.\n */\nfunction extractOverrides<T>(\n argNames: string[],\n args: unknown[],\n): { positionalArgs: unknown[]; overrides?: T } {\n if (args.length > argNames.length && args.length > 0) {\n const last = args[args.length - 1];\n if (last && typeof last === \"object\" && !Array.isArray(last)) {\n return { positionalArgs: args.slice(0, -1), overrides: last as T };\n }\n }\n return { positionalArgs: args };\n}\n\n/**\n * Dev address (Alice) used as fallback origin for read-only queries when no\n * wallet is connected. Queries are dry-run simulations — the origin only\n * affects gas estimation and is safe to stub.\n */\nconst QUERY_FALLBACK_ORIGIN = seedToAccount(DEV_PHRASE, \"//Alice\").ss58Address as SS58String;\n\nfunction resolveOrigin(\n defaults: ContractDefaults,\n override?: SS58String,\n forQuery?: boolean,\n): SS58String | undefined {\n if (override) return override;\n const sourceAddr = defaults.signerManager?.getState().selectedAccount?.address;\n if (sourceAddr) return sourceAddr as SS58String;\n if (defaults.origin) return defaults.origin;\n if (forQuery) {\n log.warn(\"No origin configured — using dev fallback (Alice) for query dry-run\");\n return QUERY_FALLBACK_ORIGIN;\n }\n return undefined;\n}\n\nfunction resolveSigner(\n defaults: ContractDefaults,\n override?: PolkadotSigner,\n): PolkadotSigner | undefined {\n return override ?? defaults.signerManager?.getSigner() ?? defaults.signer;\n}\n\n/**\n * Normalise a contract address to a `0x`-prefixed 20-byte hex string —\n * the shape PAPI ≥2.0 codecs and compat checks accept for `[u8; 20]` args.\n * Accepts the prefix being absent and re-adds it.\n */\nfunction normalizeContractAddress(address: string): HexString {\n const hex = address.startsWith(\"0x\") ? address.slice(2) : address;\n if (hex.length !== 40) {\n throw new Error(`Expected 20-byte H160 contract address, got ${hex.length / 2} bytes`);\n }\n return `0x${hex.toLowerCase()}` as HexString;\n}\n\n/** Convert a `0x`-prefixed hex string to a `Uint8Array`. */\nfunction hexToBytes(hex: HexString): Uint8Array {\n const stripped = hex.slice(2);\n const out = new Uint8Array(stripped.length / 2);\n for (let i = 0; i < out.length; i++) {\n out[i] = Number.parseInt(stripped.slice(i * 2, i * 2 + 2), 16);\n }\n return out;\n}\n\n/**\n * Encode the calldata for a contract method using the Solidity ABI codec.\n * Returns `selector ‖ head ‖ tail` as a `0x`-prefixed hex string.\n */\nfunction encodeCalldata(abi: AbiEntry[], methodName: string, args: unknown[]): `0x${string}` {\n return encodeFunctionData({\n abi: abi as unknown as ViemAbi,\n functionName: methodName,\n args,\n });\n}\n\n/**\n * Decode a successful query's return data via the Solidity ABI codec.\n * Returns `undefined` for void methods.\n *\n * Shape note: viem hands back the raw value for single-output methods and a\n * positional array for multi-output ones. The codegen pairs in\n * `generateMethodResponseType` surface multi-output returns as a named\n * object (`{name1: T1; name2: T2}`), so we assemble that object here from\n * viem's array. Single-output and Solidity-tuple outputs (which viem\n * already returns as a named object) pass through untouched.\n */\nfunction decodeReturn(abi: AbiEntry[], methodName: string, returnData: Uint8Array): unknown {\n if (returnData.byteLength === 0) return undefined;\n let hex = \"0x\";\n for (let i = 0; i < returnData.byteLength; i++) {\n hex += returnData[i].toString(16).padStart(2, \"0\");\n }\n const decoded = decodeFunctionResult({\n abi: abi as unknown as ViemAbi,\n functionName: methodName,\n data: hex as `0x${string}`,\n });\n\n const entry = abi.find((e) => e.type === \"function\" && e.name === methodName);\n const outputs = entry?.outputs ?? [];\n if (outputs.length <= 1 || !Array.isArray(decoded)) return decoded;\n // Fall back to positional `_0`, `_1`, … when outputs are unnamed —\n // matches generateMethodResponseType's naming policy.\n const obj: Record<string, unknown> = {};\n for (let i = 0; i < outputs.length; i++) {\n obj[outputs[i].name || `_${i}`] = decoded[i];\n }\n return obj;\n}\n\n/**\n * Shared pre-submit pipeline for `.tx()` and `.prepare()`:\n *\n * 1. Encode the calldata via viem.\n * 2. If either gas/storage override is missing, dry-run via\n * `runtime.dryRunCall` to size the missing limit(s) and fail fast on\n * revert / OOG / `AccountNotMapped`. Skipped when both are provided.\n * 3. Build the `Revive.call` extrinsic via the typed API.\n *\n * Returned `SubmittableTransaction` is what `.tx()` hands to `submitAndWatch`\n * and what `.prepare()` returns as a `BatchableCall`.\n */\nasync function buildReviveCall(\n runtime: ContractRuntime,\n dest: HexString,\n abi: AbiEntry[],\n methodName: string,\n positionalArgs: unknown[],\n origin: SS58String,\n overrides: PrepareOptions | TxOptions | undefined,\n): Promise<SubmittableTransaction> {\n const value = overrides?.value ?? 0n;\n const calldata = hexToBytes(encodeCalldata(abi, methodName, positionalArgs));\n\n let weightLimit = overrides?.gasLimit;\n let storageDepositLimit = overrides?.storageDepositLimit;\n if (weightLimit === undefined || storageDepositLimit === undefined) {\n const dryRun = await runtime.dryRunCall(\n origin,\n dest,\n value,\n undefined,\n undefined,\n calldata,\n );\n if (!dryRun.result.success) {\n throw new ContractDryRunFailedError(methodName, dryRun.result.value);\n }\n weightLimit = weightLimit ?? dryRun.weight_required;\n if (storageDepositLimit === undefined) {\n storageDepositLimit =\n dryRun.storage_deposit.type === \"Charge\" ? dryRun.storage_deposit.value : 0n;\n }\n }\n\n return runtime.api.tx.Revive.call({\n dest,\n value,\n weight_limit: weightLimit,\n storage_deposit_limit: storageDepositLimit,\n data: calldata,\n });\n}\n\n/**\n * Build a typed contract handle backed by direct `Revive` extrinsic +\n * `ReviveApi` runtime API calls. The Solidity ABI codec runs through `viem`.\n *\n * @param runtime - A `ContractRuntime` (returned by `createContractRuntime`).\n * @param address - The H160 address of the deployed contract.\n * @param abi - The Solidity ABI for the contract.\n * @param defaults - Origin / signer fallbacks shared across all method calls.\n */\nexport function wrapContract(\n runtime: ContractRuntime,\n address: string,\n abi: AbiEntry[],\n defaults: ContractDefaults,\n): Contract<ContractDef> {\n const methodArgs = buildMethodArgMap(abi);\n const dest = normalizeContractAddress(address);\n\n return new Proxy({} as Record<string, unknown>, {\n get(_, methodName: string) {\n if (typeof methodName !== \"string\") return undefined;\n const argNames = methodArgs[methodName];\n if (!argNames) return undefined;\n\n return {\n query: async (...args: unknown[]): Promise<QueryResult<unknown>> => {\n const { positionalArgs, overrides } = extractOverrides<QueryOptions>(\n argNames,\n args,\n );\n const origin = resolveOrigin(defaults, overrides?.origin, true)!;\n const value = overrides?.value ?? 0n;\n\n const calldata = hexToBytes(encodeCalldata(abi, methodName, positionalArgs));\n\n const dryRun = await runtime.dryRunCall(\n origin,\n dest,\n value,\n undefined,\n undefined,\n calldata,\n );\n\n if (!dryRun.result.success) {\n return {\n success: false,\n value: undefined,\n gasRequired: dryRun.weight_required,\n };\n }\n\n const decoded = decodeReturn(abi, methodName, dryRun.result.value.data);\n return {\n success: true,\n value: decoded,\n gasRequired: dryRun.weight_required,\n };\n },\n\n tx: async (...args: unknown[]) => {\n const { positionalArgs, overrides } = extractOverrides<TxOptions>(\n argNames,\n args,\n );\n const signer = resolveSigner(defaults, overrides?.signer);\n if (!signer) {\n throw new ContractSignerMissingError();\n }\n\n const origin =\n resolveOrigin(defaults, overrides?.origin) ??\n (ss58Address(signer.publicKey) as SS58String);\n\n const tx = await buildReviveCall(\n runtime,\n dest,\n abi,\n methodName,\n positionalArgs,\n origin,\n overrides,\n );\n\n return submitAndWatch(tx, signer, {\n waitFor: overrides?.waitFor,\n timeoutMs: overrides?.timeoutMs,\n mortalityPeriod: overrides?.mortalityPeriod,\n onStatus: overrides?.onStatus,\n });\n },\n\n prepare: async (...args: unknown[]): Promise<BatchableCall> => {\n // `.prepare()` builds the same `Revive.call` extrinsic as\n // `.tx()` but stops before submission — the returned\n // SubmittableTransaction is a BatchableCall consumable\n // by `batchSubmitAndWatch`. Origin defaults to the dev\n // address (Alice) for dry-run gas estimation since no\n // signer is required at prepare time; the batch's signer\n // replaces the dispatched origin at submission.\n const { positionalArgs, overrides } = extractOverrides<PrepareOptions>(\n argNames,\n args,\n );\n const origin = resolveOrigin(defaults, overrides?.origin, true)!;\n return buildReviveCall(\n runtime,\n dest,\n abi,\n methodName,\n positionalArgs,\n origin,\n overrides,\n );\n },\n };\n },\n }) as Contract<ContractDef>;\n}\n\nif (import.meta.vitest) {\n const { test, expect, describe } = import.meta.vitest;\n\n describe(\"buildMethodArgMap\", () => {\n test(\"extracts function parameter names from ABI\", () => {\n const abi: AbiEntry[] = [\n { type: \"constructor\", inputs: [], stateMutability: \"nonpayable\" },\n {\n type: \"function\",\n name: \"transfer\",\n inputs: [\n { name: \"to\", type: \"address\" },\n { name: \"amount\", type: \"uint256\" },\n ],\n outputs: [{ name: \"\", type: \"bool\" }],\n },\n {\n type: \"function\",\n name: \"balanceOf\",\n inputs: [{ name: \"owner\", type: \"address\" }],\n outputs: [{ name: \"\", type: \"uint256\" }],\n },\n { type: \"event\", name: \"Transfer\", inputs: [] },\n ];\n expect(buildMethodArgMap(abi)).toEqual({\n transfer: [\"to\", \"amount\"],\n balanceOf: [\"owner\"],\n });\n });\n\n test(\"returns empty map for ABI with no functions\", () => {\n const abi: AbiEntry[] = [\n { type: \"constructor\", inputs: [] },\n { type: \"event\", name: \"Evt\", inputs: [] },\n ];\n expect(buildMethodArgMap(abi)).toEqual({});\n });\n });\n\n describe(\"extractOverrides\", () => {\n test(\"returns overrides when extra object arg is present\", () => {\n const result = extractOverrides<{ origin: string }>([\"a\"], [42, { origin: \"0x1\" }]);\n expect(result.positionalArgs).toEqual([42]);\n expect(result.overrides).toEqual({ origin: \"0x1\" });\n });\n\n test(\"returns no overrides when arg count matches\", () => {\n const result = extractOverrides([\"a\", \"b\"], [1, 2]);\n expect(result.positionalArgs).toEqual([1, 2]);\n expect(result.overrides).toBeUndefined();\n });\n\n test(\"does not treat array as overrides\", () => {\n const result = extractOverrides([\"a\"], [1, [2, 3]]);\n expect(result.positionalArgs).toEqual([1, [2, 3]]);\n expect(result.overrides).toBeUndefined();\n });\n\n test(\"does not treat primitive as overrides\", () => {\n const result = extractOverrides([\"a\"], [1, \"extra\"]);\n expect(result.positionalArgs).toEqual([1, \"extra\"]);\n expect(result.overrides).toBeUndefined();\n });\n });\n\n describe(\"normalizeContractAddress\", () => {\n test(\"accepts 0x-prefixed H160\", () => {\n expect(normalizeContractAddress(\"0x1234567890abcdef1234567890ABCDEF12345678\")).toBe(\n \"0x1234567890abcdef1234567890abcdef12345678\",\n );\n });\n\n test(\"accepts unprefixed hex and re-adds the 0x prefix\", () => {\n expect(normalizeContractAddress(\"aabbccddeeff00112233445566778899aabbccdd\")).toBe(\n \"0xaabbccddeeff00112233445566778899aabbccdd\",\n );\n });\n\n test(\"rejects wrong length\", () => {\n expect(() => normalizeContractAddress(\"0x1234\")).toThrow(/20-byte/);\n });\n });\n\n describe(\"hexToBytes\", () => {\n test(\"decodes 0x-prefixed hex to bytes\", () => {\n expect(Array.from(hexToBytes(\"0xdeadbeef\"))).toEqual([0xde, 0xad, 0xbe, 0xef]);\n });\n\n test(\"returns an empty array for the empty hex literal\", () => {\n expect(hexToBytes(\"0x\").byteLength).toBe(0);\n });\n });\n\n describe(\"encodeCalldata / decodeReturn (viem round-trip)\", () => {\n const abi: AbiEntry[] = [\n {\n type: \"function\",\n name: \"add\",\n inputs: [\n { name: \"a\", type: \"uint32\" },\n { name: \"b\", type: \"uint32\" },\n ],\n outputs: [{ name: \"\", type: \"uint32\" }],\n stateMutability: \"view\",\n },\n {\n type: \"function\",\n name: \"name\",\n inputs: [],\n outputs: [{ name: \"\", type: \"string\" }],\n stateMutability: \"view\",\n },\n ];\n\n test(\"encodes selector + args\", () => {\n const data = encodeCalldata(abi, \"add\", [1, 2]);\n expect(data.slice(0, 2)).toBe(\"0x\");\n // 4-byte selector + 2 * 32-byte args = 68 bytes = 136 hex chars + \"0x\"\n expect(data.length).toBe(2 + 4 * 2 + 2 * 32 * 2);\n });\n\n test(\"decodes single uint32 return\", () => {\n const buf = new Uint8Array(32);\n buf[31] = 7;\n expect(decodeReturn(abi, \"add\", buf)).toBe(7);\n });\n\n test(\"decodes string return\", () => {\n const hex =\n \"0000000000000000000000000000000000000000000000000000000000000020\" +\n \"0000000000000000000000000000000000000000000000000000000000000002\" +\n \"6869000000000000000000000000000000000000000000000000000000000000\";\n const buf = new Uint8Array(hex.length / 2);\n for (let i = 0; i < buf.length; i++) {\n buf[i] = Number.parseInt(hex.slice(i * 2, i * 2 + 2), 16);\n }\n expect(decodeReturn(abi, \"name\", buf)).toBe(\"hi\");\n });\n\n test(\"returns undefined for empty data\", () => {\n expect(decodeReturn(abi, \"add\", new Uint8Array(0))).toBeUndefined();\n });\n\n test(\"multi-output method: assembles named object from viem's positional array\", () => {\n // viem hands back `[balance, nonce]` for multi-output methods,\n // but `generateMethodResponseType` surfaces this as\n // `{ balance: bigint; nonce: bigint }`. decodeReturn should\n // bridge the two so the runtime shape matches the codegen type.\n const multiAbi: AbiEntry[] = [\n {\n type: \"function\",\n name: \"info\",\n inputs: [],\n outputs: [\n { name: \"balance\", type: \"uint256\" },\n { name: \"nonce\", type: \"uint256\" },\n ],\n stateMutability: \"view\",\n },\n ];\n const buf = new Uint8Array(64);\n buf[31] = 7;\n buf[63] = 11;\n expect(decodeReturn(multiAbi, \"info\", buf)).toEqual({ balance: 7n, nonce: 11n });\n });\n\n test(\"multi-output method with unnamed outputs: falls back to _0, _1, …\", () => {\n // Mirrors generateMethodResponseType's `_${i}` policy when an\n // output has no name in the ABI.\n const unnamedAbi: AbiEntry[] = [\n {\n type: \"function\",\n name: \"stats\",\n inputs: [],\n outputs: [\n { name: \"\", type: \"uint256\" },\n { name: \"\", type: \"uint256\" },\n ],\n stateMutability: \"view\",\n },\n ];\n const buf = new Uint8Array(64);\n buf[31] = 1;\n buf[63] = 2;\n expect(decodeReturn(unnamedAbi, \"stats\", buf)).toEqual({ _0: 1n, _1: 2n });\n });\n\n test(\"Solidity tuple output: viem already returns a named object — pass through\", () => {\n // Single tuple-type output: viem builds the named object itself\n // from the component names, so decodeReturn must not double-wrap.\n const tupleAbi: AbiEntry[] = [\n {\n type: \"function\",\n name: \"info\",\n inputs: [],\n outputs: [\n {\n name: \"result\",\n type: \"tuple\",\n components: [\n { name: \"balance\", type: \"uint256\" },\n { name: \"nonce\", type: \"uint256\" },\n ],\n },\n ],\n stateMutability: \"view\",\n },\n ];\n const buf = new Uint8Array(64);\n buf[31] = 7;\n buf[63] = 11;\n expect(decodeReturn(tupleAbi, \"info\", buf)).toEqual({ balance: 7n, nonce: 11n });\n });\n });\n\n /** Minimal SignerManager mock for resolve* helpers. */\n function mockSigner(opts: {\n address?: string | null;\n signer?: PolkadotSigner | null;\n }): import(\"@parity/product-sdk-signer\").SignerManager {\n return {\n getSigner: () => opts.signer ?? null,\n getState: () => ({\n selectedAccount: opts.address ? ({ address: opts.address } as never) : null,\n }),\n } as unknown as import(\"@parity/product-sdk-signer\").SignerManager;\n }\n\n describe(\"resolveOrigin\", () => {\n test(\"explicit override wins\", () => {\n const defaults: ContractDefaults = {\n origin: \"5Static\" as SS58String,\n signerManager: mockSigner({ address: \"5Source\" }),\n };\n expect(resolveOrigin(defaults, \"5Override\" as SS58String)).toBe(\"5Override\");\n });\n\n test(\"signerManager wins over static default\", () => {\n const defaults: ContractDefaults = {\n origin: \"5Static\" as SS58String,\n signerManager: mockSigner({ address: \"5Source\" }),\n };\n expect(resolveOrigin(defaults)).toBe(\"5Source\");\n });\n\n test(\"falls back to static default\", () => {\n const defaults: ContractDefaults = { origin: \"5Static\" as SS58String };\n expect(resolveOrigin(defaults)).toBe(\"5Static\");\n });\n\n test(\"returns undefined when nothing available\", () => {\n expect(resolveOrigin({})).toBeUndefined();\n });\n });\n\n describe(\"resolveSigner\", () => {\n const fakeSigner = { id: \"fake\" } as unknown as PolkadotSigner;\n const sourceSigner = { id: \"source\" } as unknown as PolkadotSigner;\n\n test(\"explicit override wins\", () => {\n const defaults: ContractDefaults = {\n signer: { id: \"static\" } as unknown as PolkadotSigner,\n signerManager: mockSigner({ signer: sourceSigner }),\n };\n expect(resolveSigner(defaults, fakeSigner)).toBe(fakeSigner);\n });\n\n test(\"signerManager wins over static default\", () => {\n const defaults: ContractDefaults = {\n signer: { id: \"static\" } as unknown as PolkadotSigner,\n signerManager: mockSigner({ signer: sourceSigner }),\n };\n expect(resolveSigner(defaults)).toBe(sourceSigner);\n });\n\n test(\"falls back to static default\", () => {\n const defaults: ContractDefaults = { signer: fakeSigner };\n expect(resolveSigner(defaults)).toBe(fakeSigner);\n });\n\n test(\"returns undefined when nothing available\", () => {\n expect(resolveSigner({})).toBeUndefined();\n });\n });\n\n describe(\"wrapContract — PAPI 2.x boundary (HexString / Uint8Array contract)\", () => {\n // The codegen now emits `HexString` for `bytes` and `SizedHex<N>` for\n // `bytesN`. These tests pin the runtime side: when a caller passes a\n // hex string for those args, the SDK must hand PAPI a `0x…` `dest`\n // and a `Uint8Array` `data` — anything else trips PAPI 2.x's\n // `isCompatible` check or its codecs. We capture the arguments PAPI\n // receives and assert on their concrete shapes.\n const ADDRESS_INPUT = \"0x0102030405060708090a0b0c0d0e0f1011121314\";\n\n type Captured = {\n dryRun: Parameters<ContractRuntime[\"dryRunCall\"]> | null;\n tx: { dest: unknown; data: unknown } | null;\n };\n\n function mockRuntime(captured: Captured): ContractRuntime {\n const successfulDryRun: ContractRuntime[\"dryRunCall\"] = async (...args) => {\n captured.dryRun = args;\n return {\n weight_consumed: { ref_time: 0n, proof_size: 0n },\n weight_required: { ref_time: 1n, proof_size: 1n },\n storage_deposit: { type: \"Charge\", value: 7n },\n max_storage_deposit: { type: \"Charge\", value: 7n },\n gas_consumed: 0n,\n result: { success: true, value: { flags: 0, data: new Uint8Array(0) } },\n };\n };\n return {\n api: {\n tx: {\n Revive: {\n call: (args: { dest: unknown; data: unknown }) => {\n captured.tx = { dest: args.dest, data: args.data };\n return {\n signSubmitAndWatch: () => ({\n subscribe: (handlers: {\n next: (event: unknown) => void;\n }) => {\n queueMicrotask(() => {\n handlers.next({\n type: \"txBestBlocksState\",\n txHash: \"0xdeadbeef\",\n found: true,\n ok: true,\n events: [],\n block: {\n hash: \"0xblock\",\n number: 1,\n index: 0,\n },\n });\n });\n return { unsubscribe: () => {} };\n },\n }),\n };\n },\n },\n },\n } as unknown as ContractRuntime[\"api\"],\n dryRunCall: successfulDryRun,\n };\n }\n\n const fakeSigner = {\n publicKey: new Uint8Array(32),\n } as unknown as PolkadotSigner;\n const origin = \"5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY\" as SS58String;\n\n test(\"`bytesN` argument: hex string is forwarded as 0x-string dest and Uint8Array calldata\", async () => {\n // Solidity: function setHash(bytes32 hash) — exercises the\n // `bytesN` codegen branch (now `SizedHex<N>`). The argument here\n // is what a user following the generated types would pass.\n const abi: AbiEntry[] = [\n {\n type: \"function\",\n name: \"setHash\",\n inputs: [{ name: \"hash\", type: \"bytes32\" }],\n outputs: [],\n stateMutability: \"nonpayable\",\n },\n ];\n\n const captured: Captured = { dryRun: null, tx: null };\n const wrapped = wrapContract(mockRuntime(captured), ADDRESS_INPUT, abi, {\n signer: fakeSigner,\n origin,\n });\n\n const hash = \"0x1111111111111111111111111111111111111111111111111111111111111111\";\n await (\n wrapped as unknown as { setHash: { tx: (h: string) => Promise<unknown> } }\n ).setHash.tx(hash);\n\n // PAPI's compat check rejects anything that isn't a `0x…` string\n // for an H160 dest. The class-based `FixedSizeBinary` would fail.\n expect(captured.dryRun?.[1]).toBe(ADDRESS_INPUT);\n expect(typeof captured.dryRun?.[1]).toBe(\"string\");\n\n // Variable-length calldata must arrive as a `Uint8Array`. The\n // ABI selector for `setHash(bytes32)` is `0xa61eb053`, followed\n // by the 32-byte argument right-padded into a 32-byte word.\n const calldata = captured.dryRun?.[5] as Uint8Array;\n expect(calldata).toBeInstanceOf(Uint8Array);\n expect(calldata.byteLength).toBe(4 + 32);\n expect(Array.from(calldata.slice(4, 36))).toEqual(Array(32).fill(0x11));\n\n // The same pair flows into the typed extrinsic — a class instance\n // here would silently mis-encode under PAPI 2.x.\n expect(captured.tx?.dest).toBe(ADDRESS_INPUT);\n expect(captured.tx?.data).toBeInstanceOf(Uint8Array);\n });\n\n test(\"variable `bytes` argument: hex string round-trips through viem to Uint8Array calldata\", async () => {\n // Solidity: function store(bytes data) — exercises the `bytes`\n // codegen branch (now `HexString`).\n const abi: AbiEntry[] = [\n {\n type: \"function\",\n name: \"store\",\n inputs: [{ name: \"data\", type: \"bytes\" }],\n outputs: [],\n stateMutability: \"nonpayable\",\n },\n ];\n\n const captured: Captured = { dryRun: null, tx: null };\n const wrapped = wrapContract(mockRuntime(captured), ADDRESS_INPUT, abi, {\n signer: fakeSigner,\n origin,\n });\n\n await (\n wrapped as unknown as { store: { tx: (b: string) => Promise<unknown> } }\n ).store.tx(\"0xdeadbeef\");\n\n const calldata = captured.dryRun?.[5] as Uint8Array;\n expect(calldata).toBeInstanceOf(Uint8Array);\n // Selector + length-32-word + offset-32-word + padded 4-byte payload (32-byte word).\n expect(calldata.byteLength).toBe(4 + 32 * 3);\n // 0xdeadbeef sits at the start of the third 32-byte word.\n const payloadStart = 4 + 32 * 2;\n expect(Array.from(calldata.slice(payloadStart, payloadStart + 4))).toEqual([\n 0xde, 0xad, 0xbe, 0xef,\n ]);\n });\n\n test(\"query() decodes a `bytesN` return value back to the original hex string\", async () => {\n // Solidity: function getHash() returns (bytes32). The dry-run\n // result's `data` is a raw `Uint8Array` under PAPI 2.x — wrap\n // must hand it to viem's decoder unwrapped.\n const abi: AbiEntry[] = [\n {\n type: \"function\",\n name: \"getHash\",\n inputs: [],\n outputs: [{ name: \"\", type: \"bytes32\" }],\n stateMutability: \"view\",\n },\n ];\n\n // 32-byte word filled with 0x22 — what the chain returns for a\n // hypothetical `bytes32` reading.\n const responseBytes = new Uint8Array(32).fill(0x22);\n const runtime: ContractRuntime = {\n api: {} as unknown as ContractRuntime[\"api\"],\n dryRunCall: async () => ({\n weight_consumed: { ref_time: 0n, proof_size: 0n },\n weight_required: { ref_time: 0n, proof_size: 0n },\n storage_deposit: { type: \"Refund\", value: 0n },\n max_storage_deposit: { type: \"Refund\", value: 0n },\n gas_consumed: 0n,\n result: { success: true, value: { flags: 0, data: responseBytes } },\n }),\n };\n\n const wrapped = wrapContract(runtime, ADDRESS_INPUT, abi, { origin });\n const result = await (\n wrapped as unknown as {\n getHash: { query: () => Promise<{ success: boolean; value: unknown }> };\n }\n ).getHash.query();\n\n expect(result.success).toBe(true);\n // viem decodes `bytes32` as a `0x…` hex string.\n expect(result.value).toBe(\n \"0x2222222222222222222222222222222222222222222222222222222222222222\",\n );\n });\n });\n\n describe(\"wrapContract — tx dry-run failure\", () => {\n const abi: AbiEntry[] = [\n {\n type: \"function\",\n name: \"increment\",\n inputs: [],\n outputs: [],\n stateMutability: \"nonpayable\",\n },\n ];\n const ADDRESS = \"0x0102030405060708090a0b0c0d0e0f1011121314\";\n const fakeSigner = {\n publicKey: new Uint8Array(32),\n } as unknown as PolkadotSigner;\n\n test(\"throws ContractDryRunFailedError when ReviveApi.call reports failure\", async () => {\n const dispatchError = { type: \"Module\", value: { type: \"ContractReverted\" } };\n const failingDryRun: ContractRuntime[\"dryRunCall\"] = async () => ({\n weight_consumed: { ref_time: 0n, proof_size: 0n },\n weight_required: { ref_time: 0n, proof_size: 0n },\n storage_deposit: { type: \"Refund\", value: 0n },\n max_storage_deposit: { type: \"Refund\", value: 0n },\n gas_consumed: 0n,\n result: { success: false, value: dispatchError },\n });\n const runtime: ContractRuntime = {\n api: {\n apis: {\n ReviveApi: {\n call: () => {\n throw new Error(\n \"typed ReviveApi.call must NOT be invoked — runtime.dryRunCall owns the dry-run path\",\n );\n },\n },\n },\n tx: {\n Revive: {\n call: () => {\n throw new Error(\n \"Revive.call must NOT be invoked on dry-run failure\",\n );\n },\n },\n },\n } as unknown as ContractRuntime[\"api\"],\n dryRunCall: failingDryRun,\n };\n\n const wrapped = wrapContract(runtime, ADDRESS, abi, {\n signer: fakeSigner,\n origin: \"5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY\" as SS58String,\n });\n\n await expect(\n (\n wrapped as unknown as { increment: { tx: () => Promise<unknown> } }\n ).increment.tx(),\n ).rejects.toMatchObject({\n name: \"ContractDryRunFailedError\",\n methodName: \"increment\",\n dispatchError,\n });\n });\n\n test(\"skips dry-run entirely when both gasLimit and storageDepositLimit overrides are passed\", async () => {\n // When the caller supplies both weight and storage-deposit\n // overrides, `.tx()` should go straight to the extrinsic builder\n // — no RPC round-trip, no revert pre-check. We assert this by\n // wiring both `dryRunCall` and `apis.ReviveApi.call` to throw if\n // invoked, and checking the tx still lands.\n let txArgs: { weight_limit: unknown; storage_deposit_limit: unknown } | null = null;\n const runtime: ContractRuntime = {\n api: {\n apis: {\n ReviveApi: {\n call: () => {\n throw new Error(\n \"ReviveApi.call must NOT be invoked when both overrides are passed\",\n );\n },\n },\n },\n tx: {\n Revive: {\n call: (args: {\n weight_limit: unknown;\n storage_deposit_limit: unknown;\n }) => {\n txArgs = {\n weight_limit: args.weight_limit,\n storage_deposit_limit: args.storage_deposit_limit,\n };\n return {\n signSubmitAndWatch: () => ({\n subscribe: (handlers: {\n next: (event: unknown) => void;\n }) => {\n queueMicrotask(() => {\n handlers.next({\n type: \"txBestBlocksState\",\n txHash: \"0xdeadbeef\",\n found: true,\n ok: true,\n events: [],\n block: {\n hash: \"0xblock\",\n number: 1,\n index: 0,\n },\n });\n });\n return { unsubscribe: () => {} };\n },\n }),\n };\n },\n },\n },\n } as unknown as ContractRuntime[\"api\"],\n dryRunCall: () => {\n throw new Error(\n \"dryRunCall must NOT be invoked when both overrides are passed\",\n );\n },\n };\n\n const wrapped = wrapContract(runtime, ADDRESS, abi, {\n signer: fakeSigner,\n origin: \"5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY\" as SS58String,\n });\n\n const overrideWeight = { ref_time: 1234n, proof_size: 56n };\n const overrideDeposit = 7890n;\n await (\n wrapped as unknown as {\n increment: { tx: (opts: unknown) => Promise<unknown> };\n }\n ).increment.tx({\n gasLimit: overrideWeight,\n storageDepositLimit: overrideDeposit,\n });\n\n expect(txArgs).toEqual({\n weight_limit: overrideWeight,\n storage_deposit_limit: overrideDeposit,\n });\n });\n\n test(\"missing storageDepositLimit override still triggers the dry-run\", async () => {\n // Half-overrides don't bypass: if the caller passes `gasLimit`\n // but not `storageDepositLimit`, the SDK must still dry-run to\n // size the deposit AND to fail fast on revert. The previous\n // `!weightLimit` check was correct here; the tightening to\n // `=== undefined` keeps this branch intact for any future\n // refactor that touches the guard.\n let dryRunInvoked = false;\n const runtime: ContractRuntime = {\n api: {\n tx: {\n Revive: {\n call: () => {\n throw new Error(\"Revive.call must not run — dry-run failed\");\n },\n },\n },\n } as unknown as ContractRuntime[\"api\"],\n dryRunCall: async () => {\n dryRunInvoked = true;\n return {\n weight_consumed: { ref_time: 0n, proof_size: 0n },\n weight_required: { ref_time: 0n, proof_size: 0n },\n storage_deposit: { type: \"Refund\", value: 0n },\n max_storage_deposit: { type: \"Refund\", value: 0n },\n gas_consumed: 0n,\n result: { success: false, value: { type: \"ContractReverted\" } },\n };\n },\n };\n\n const wrapped = wrapContract(runtime, ADDRESS, abi, {\n signer: fakeSigner,\n origin: \"5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY\" as SS58String,\n });\n\n await expect(\n (\n wrapped as unknown as {\n increment: { tx: (opts: unknown) => Promise<unknown> };\n }\n ).increment.tx({ gasLimit: { ref_time: 1n, proof_size: 1n } }),\n ).rejects.toMatchObject({ name: \"ContractDryRunFailedError\" });\n expect(dryRunInvoked).toBe(true);\n });\n });\n\n describe(\"wrapContract — prepare (batch composition)\", () => {\n // `.prepare()` is the revive-runtime port of the polkadot-apps\n // batching helper. These tests pin the contract the rest of the\n // SDK relies on:\n //\n // - returns a `SubmittableTransaction` that doubles as a\n // `BatchableCall` (has `.decodedCall` / forwards through\n // `batchSubmitAndWatch`'s `resolveDecodedCall`),\n // - never invokes `submitAndWatch`,\n // - sizes weight + storage via the dry-run unless the caller\n // supplies both overrides,\n // - bubbles a dry-run failure as `ContractDryRunFailedError`\n // before the extrinsic is built,\n // - requires no signer (the batch's signer dispatches).\n const abi: AbiEntry[] = [\n {\n type: \"function\",\n name: \"increment\",\n inputs: [],\n outputs: [],\n stateMutability: \"nonpayable\",\n },\n {\n type: \"function\",\n name: \"add\",\n inputs: [{ name: \"n\", type: \"uint32\" }],\n outputs: [],\n stateMutability: \"nonpayable\",\n },\n ];\n const ADDRESS = \"0x0102030405060708090a0b0c0d0e0f1011121314\";\n\n test(\"builds a Revive.call submittable without signing or dry-running when both overrides given\", async () => {\n let txArgs: {\n dest: unknown;\n value: unknown;\n weight_limit: unknown;\n storage_deposit_limit: unknown;\n data: unknown;\n } | null = null;\n const captured: { dryRun: boolean } = { dryRun: false };\n const sentinelDecodedCall = { pallet: \"Revive\", call: \"call\" };\n const runtime: ContractRuntime = {\n api: {\n tx: {\n Revive: {\n call: (args: typeof txArgs) => {\n txArgs = args;\n // Real PAPI returns a SubmittableTransaction\n // with `.decodedCall`; the field is what\n // `batchSubmitAndWatch` reads to assemble\n // the `Utility.batch_all` payload.\n return {\n decodedCall: sentinelDecodedCall,\n signSubmitAndWatch: () => {\n throw new Error(\n \"prepare must NOT sign or submit — caller batches the result\",\n );\n },\n };\n },\n },\n },\n } as unknown as ContractRuntime[\"api\"],\n dryRunCall: async () => {\n captured.dryRun = true;\n throw new Error(\"prepare must NOT dry-run when both overrides are given\");\n },\n };\n const wrapped = wrapContract(runtime, ADDRESS, abi, {});\n\n const overrideWeight = { ref_time: 99n, proof_size: 11n };\n const result = await (\n wrapped as unknown as {\n add: {\n prepare: (n: number, opts: unknown) => Promise<{ decodedCall: unknown }>;\n };\n }\n ).add.prepare(7, {\n gasLimit: overrideWeight,\n storageDepositLimit: 42n,\n value: 5n,\n });\n\n // SubmittableTransaction is a valid BatchableCall —\n // `batchSubmitAndWatch` reads `.decodedCall` off it.\n expect(result.decodedCall).toBe(sentinelDecodedCall);\n\n // Override values flowed straight through to the extrinsic.\n expect(txArgs).toEqual({\n dest: ADDRESS,\n value: 5n,\n weight_limit: overrideWeight,\n storage_deposit_limit: 42n,\n // viem-encoded `add(uint32)` calldata: 4-byte selector +\n // 32-byte argument. We don't assert the byte-level layout\n // here (covered in the bytesN boundary tests).\n data: expect.any(Uint8Array),\n });\n expect(captured.dryRun).toBe(false);\n });\n\n test(\"dry-runs to fill the missing limits when overrides are partial\", async () => {\n let dryRunCalls = 0;\n const txArgs: { weight_limit: unknown; storage_deposit_limit: unknown }[] = [];\n const runtime: ContractRuntime = {\n api: {\n tx: {\n Revive: {\n call: (args: {\n weight_limit: unknown;\n storage_deposit_limit: unknown;\n }) => {\n txArgs.push(args);\n return { decodedCall: { sentinel: true } };\n },\n },\n },\n } as unknown as ContractRuntime[\"api\"],\n dryRunCall: async () => {\n dryRunCalls += 1;\n return {\n weight_consumed: { ref_time: 0n, proof_size: 0n },\n weight_required: { ref_time: 123n, proof_size: 7n },\n storage_deposit: { type: \"Charge\", value: 99n },\n max_storage_deposit: { type: \"Charge\", value: 99n },\n gas_consumed: 0n,\n result: { success: true, value: { flags: 0, data: new Uint8Array(0) } },\n };\n },\n };\n const wrapped = wrapContract(runtime, ADDRESS, abi, {});\n\n // Only gasLimit override → dry-run still fires to size storage.\n await (\n wrapped as unknown as {\n increment: { prepare: (opts?: unknown) => Promise<unknown> };\n }\n ).increment.prepare({ gasLimit: { ref_time: 10n, proof_size: 1n } });\n\n // Only storageDepositLimit → dry-run still fires to size weight.\n await (\n wrapped as unknown as {\n increment: { prepare: (opts?: unknown) => Promise<unknown> };\n }\n ).increment.prepare({ storageDepositLimit: 0n });\n\n // Nothing → dry-run fills both.\n await (\n wrapped as unknown as {\n increment: { prepare: (opts?: unknown) => Promise<unknown> };\n }\n ).increment.prepare();\n\n expect(dryRunCalls).toBe(3);\n // First call kept the gasLimit override; second kept the\n // storageDepositLimit override; third filled both from the\n // dry-run result.\n expect(txArgs[0]?.weight_limit).toEqual({ ref_time: 10n, proof_size: 1n });\n expect(txArgs[0]?.storage_deposit_limit).toBe(99n);\n expect(txArgs[1]?.weight_limit).toEqual({ ref_time: 123n, proof_size: 7n });\n expect(txArgs[1]?.storage_deposit_limit).toBe(0n);\n expect(txArgs[2]?.weight_limit).toEqual({ ref_time: 123n, proof_size: 7n });\n expect(txArgs[2]?.storage_deposit_limit).toBe(99n);\n });\n\n test(\"does not require a signer; falls back to dev origin for the dry-run\", async () => {\n let capturedOrigin: string | undefined;\n const runtime: ContractRuntime = {\n api: {\n tx: {\n Revive: {\n call: () => ({ decodedCall: { sentinel: true } }),\n },\n },\n } as unknown as ContractRuntime[\"api\"],\n dryRunCall: async (origin) => {\n capturedOrigin = origin;\n return {\n weight_consumed: { ref_time: 0n, proof_size: 0n },\n weight_required: { ref_time: 1n, proof_size: 1n },\n storage_deposit: { type: \"Refund\", value: 0n },\n max_storage_deposit: { type: \"Refund\", value: 0n },\n gas_consumed: 0n,\n result: { success: true, value: { flags: 0, data: new Uint8Array(0) } },\n };\n },\n };\n // No signer / signerManager / defaultOrigin set.\n const wrapped = wrapContract(runtime, ADDRESS, abi, {});\n\n await expect(\n (\n wrapped as unknown as {\n increment: { prepare: () => Promise<unknown> };\n }\n ).increment.prepare(),\n ).resolves.toMatchObject({ decodedCall: { sentinel: true } });\n\n // Origin must have been resolved without throwing — falls\n // back to the dev address (Alice) for dry-run gas estimation.\n expect(capturedOrigin).toBe(QUERY_FALLBACK_ORIGIN);\n });\n\n test(\"throws ContractDryRunFailedError before constructing the extrinsic on revert\", async () => {\n const dispatchError = { type: \"Module\", value: { type: \"ContractReverted\" } };\n const runtime: ContractRuntime = {\n api: {\n tx: {\n Revive: {\n call: () => {\n throw new Error(\n \"Revive.call must NOT be invoked on a failing dry-run\",\n );\n },\n },\n },\n } as unknown as ContractRuntime[\"api\"],\n dryRunCall: async () => ({\n weight_consumed: { ref_time: 0n, proof_size: 0n },\n weight_required: { ref_time: 0n, proof_size: 0n },\n storage_deposit: { type: \"Refund\", value: 0n },\n max_storage_deposit: { type: \"Refund\", value: 0n },\n gas_consumed: 0n,\n result: { success: false, value: dispatchError },\n }),\n };\n const wrapped = wrapContract(runtime, ADDRESS, abi, {});\n\n await expect(\n (\n wrapped as unknown as { increment: { prepare: () => Promise<unknown> } }\n ).increment.prepare(),\n ).rejects.toMatchObject({\n name: \"ContractDryRunFailedError\",\n methodName: \"increment\",\n dispatchError,\n });\n });\n\n test(\"prepared calls flow through batchSubmitAndWatch end-to-end\", async () => {\n // Asserts the integration contract: two prepared calls\n // resolve into a `Utility.batch_all({ calls: [...] })`\n // payload of their `.decodedCall` values.\n const { batchSubmitAndWatch } = await import(\"@parity/product-sdk-tx\");\n const decodedCalls = [\n { pallet: \"Revive\", method: \"call\", value: \"one\" },\n { pallet: \"Revive\", method: \"call\", value: \"two\" },\n ];\n let nextCall = 0;\n const runtime: ContractRuntime = {\n api: {\n tx: {\n Revive: {\n call: () => ({ decodedCall: decodedCalls[nextCall++] }),\n },\n },\n } as unknown as ContractRuntime[\"api\"],\n dryRunCall: async () => ({\n weight_consumed: { ref_time: 0n, proof_size: 0n },\n weight_required: { ref_time: 1n, proof_size: 1n },\n storage_deposit: { type: \"Refund\", value: 0n },\n max_storage_deposit: { type: \"Refund\", value: 0n },\n gas_consumed: 0n,\n result: { success: true, value: { flags: 0, data: new Uint8Array(0) } },\n }),\n };\n const wrapped = wrapContract(runtime, ADDRESS, abi, {});\n\n const a = await (\n wrapped as unknown as { increment: { prepare: () => Promise<BatchableCall> } }\n ).increment.prepare();\n const b = await (\n wrapped as unknown as {\n add: { prepare: (n: number) => Promise<BatchableCall> };\n }\n ).add.prepare(1);\n\n let batchCalls: unknown[] | null = null;\n const fakeApi = {\n tx: {\n Utility: {\n batch_all: (args: { calls: unknown[] }) => {\n batchCalls = args.calls;\n return {\n signSubmitAndWatch: () => ({\n subscribe: (h: {\n next: (event: unknown) => void;\n }) => {\n queueMicrotask(() => {\n h.next({\n type: \"txBestBlocksState\",\n txHash: \"0xb\",\n found: true,\n ok: true,\n events: [],\n block: {\n hash: \"0xblk\",\n number: 1,\n index: 0,\n },\n });\n });\n return { unsubscribe: () => {} };\n },\n }),\n };\n },\n },\n },\n } as unknown as Parameters<typeof batchSubmitAndWatch>[1];\n\n const result = await batchSubmitAndWatch([a, b], fakeApi, {\n publicKey: new Uint8Array(32),\n } as unknown as Parameters<typeof batchSubmitAndWatch>[2]);\n\n expect(result.ok).toBe(true);\n expect(batchCalls).toEqual(decodedCalls);\n });\n });\n}\n","import type { HexString, PolkadotClient, PolkadotSigner, SS58String } from \"polkadot-api\";\nimport type { SubmittableTransaction, Weight, TxResult } from \"@parity/product-sdk-tx\";\nimport { ensureAccountMapped } from \"@parity/product-sdk-tx\";\nimport { ss58ToH160 } from \"@parity/product-sdk-address\";\n\n/**\n * Result of a `Revive.call` extrinsic — present on the typed API as\n * `api.tx.Revive.call(args)`. Returned object is a PAPI submittable that\n * `submitAndWatch` consumes natively.\n *\n * `dest` is an H160 hex string and `data` is a raw `Uint8Array`: this matches\n * what `polkadot-api` ≥2.0 codecs accept and produce. The class-based\n * `Binary` / `FixedSizeBinary` wrappers from `@polkadot-api/substrate-bindings`\n * 0.12 are *not* accepted by PAPI 2.x's compatibility check.\n */\nexport type ReviveCallTx = (args: {\n dest: HexString;\n value: bigint;\n weight_limit: Weight;\n storage_deposit_limit: bigint;\n data: Uint8Array;\n}) => SubmittableTransaction;\n\n/**\n * Dry-run result returned by `ReviveApi.call`. Mirrors the shape exposed by\n * descriptors (`paseo-asset-hub`, `polkadot-asset-hub`, `kusama-asset-hub`).\n *\n * `data` is a raw `Uint8Array` because PAPI ≥2.0 dropped the `Binary` class\n * wrapper for `Vec<u8>` codecs.\n */\nexport interface ReviveDryRunResult {\n weight_consumed: Weight;\n weight_required: Weight;\n storage_deposit: { type: \"Refund\" | \"Charge\"; value: bigint };\n max_storage_deposit: { type: \"Refund\" | \"Charge\"; value: bigint };\n gas_consumed: bigint;\n /**\n * `success: true` carries `{ flags, data }`; `success: false` carries the\n * dispatch error as the chain encoded it.\n */\n result:\n | { success: true; value: { flags: number; data: Uint8Array } }\n | { success: false; value: unknown };\n}\n\n/** Structural shape consumed by `ContractManager` / `createContract`. */\nexport interface ReviveTypedApi {\n tx: {\n Revive: {\n call: ReviveCallTx;\n map_account(): SubmittableTransaction;\n };\n };\n query: {\n Revive: {\n OriginalAccount: {\n getValue(address: HexString): Promise<SS58String | undefined>;\n };\n };\n };\n apis: {\n ReviveApi: {\n call(\n origin: SS58String,\n dest: HexString,\n value: bigint,\n gas_limit: Weight | undefined,\n storage_deposit_limit: bigint | undefined,\n input_data: Uint8Array,\n ): Promise<ReviveDryRunResult>;\n };\n };\n}\n\n/**\n * Signature of a `ReviveApi.call` dry-run, used by the wrapped contract layer\n * to estimate weight + storage deposit and surface revert / OOG /\n * `AccountNotMapped` failures before a tx is signed.\n *\n * Identical to `ReviveTypedApi.apis.ReviveApi.call`, but extracted so the\n * runtime can route this single hot call through PAPI's *unsafe* API\n * (skipping compatibility-token checks) on production runtimes whose\n * descriptors lag a chain upgrade — every other surface still uses the\n * compat-checked typed API.\n */\nexport type ReviveDryRunCall = (\n origin: SS58String,\n dest: HexString,\n value: bigint,\n gas_limit: Weight | undefined,\n storage_deposit_limit: bigint | undefined,\n input_data: Uint8Array,\n) => Promise<ReviveDryRunResult>;\n\n/**\n * Runtime handle that drives queries and transactions against a\n * pallet-revive-capable chain.\n *\n * @example\n * ```ts\n * import { createChainClient } from \"@parity/product-sdk-chain-client\";\n * import { paseo_asset_hub } from \"@parity/product-sdk-descriptors/paseo-asset-hub\";\n * import { createContractRuntimeFromClient } from \"@parity/product-sdk-contracts\";\n *\n * const client = await createChainClient({\n * chains: { assetHub: paseo_asset_hub },\n * rpcs: { assetHub: [\"wss://sys.ibp.network/asset-hub-paseo\"] },\n * });\n * const runtime = createContractRuntimeFromClient(client.raw.assetHub, paseo_asset_hub);\n * ```\n */\nexport interface ContractRuntime {\n readonly api: ReviveTypedApi;\n /**\n * Dry-run entry point. Production factories route this through the\n * *unsafe* API to avoid compatibility-token failures when the descriptor\n * trails a runtime upgrade. The {@link createContractRuntime} test factory\n * delegates to `api.apis.ReviveApi.call`.\n */\n readonly dryRunCall: ReviveDryRunCall;\n}\n\n/**\n * Wrap a typed PAPI API as a `ContractRuntime`. Intended for tests and\n * advanced setups where the caller already holds a typed API. Routes the\n * dry-run through the typed (compatibility-token-checked) `ReviveApi.call`\n * — fine for mocks but susceptible to `Incompatible runtime entry` errors\n * on a live chain whose descriptor lags. Prefer\n * {@link createContractRuntimeFromClient} for production use.\n */\nexport function createContractRuntime(api: ReviveTypedApi): ContractRuntime {\n return {\n api,\n dryRunCall: (origin, dest, value, gas, deposit, data) =>\n api.apis.ReviveApi.call(origin, dest, value, gas, deposit, data),\n };\n}\n\n/**\n * Build a `ContractRuntime` from a raw `PolkadotClient` plus its descriptor.\n *\n * The typed API powers `tx.Revive.call`, `tx.Revive.map_account`, and\n * `query.Revive.OriginalAccount` (extrinsics + storage are tolerant of\n * descriptor drift). The runtime-API dry-run, which is *not* tolerant of\n * descriptor drift on PAPI's compat-token path, is routed through\n * `client.getUnsafeApi()` — bypassing the compat check while preserving\n * argument and return shapes.\n *\n * Use this on every production code path that calls a contract's `.tx()` or\n * `.query()` against a live chain.\n *\n * @example\n * ```ts\n * import { paseo_asset_hub } from \"@parity/product-sdk-descriptors/paseo-asset-hub\";\n * import { createContractRuntimeFromClient } from \"@parity/product-sdk-contracts\";\n *\n * const runtime = createContractRuntimeFromClient(rawClient, paseo_asset_hub);\n * ```\n */\nexport function createContractRuntimeFromClient<TDescriptor>(\n client: PolkadotClient,\n descriptor: TDescriptor,\n): ContractRuntime {\n const typed = client.getTypedApi(\n descriptor as Parameters<PolkadotClient[\"getTypedApi\"]>[0],\n ) as unknown as ReviveTypedApi;\n const unsafe = client.getUnsafeApi() as unknown as {\n apis: { ReviveApi: { call: ReviveDryRunCall } };\n };\n return {\n api: typed,\n dryRunCall: (origin, dest, value, gas, deposit, data) =>\n unsafe.apis.ReviveApi.call(origin, dest, value, gas, deposit, data),\n };\n}\n\n/**\n * Ensure the SS58 account is mapped to its derived H160 on `pallet-revive`.\n *\n * `pallet-revive` requires every signing account to have a registered\n * `OriginalAccount` mapping before the runtime accepts its `Revive.call`\n * extrinsics. The mapping is one-time and cheap. This helper:\n *\n * 1. Reads `Revive.OriginalAccount` for the H160 derived from `address`.\n * 2. Returns `null` if already mapped (idempotent fast-path).\n * 3. Otherwise submits `Revive.map_account()` and waits for inclusion.\n *\n * Call this once per signing account at app startup — after that, every\n * subsequent `contract.<method>.tx({ signer })` against the same chain will\n * succeed without further mapping work.\n *\n * @param runtime - The contract runtime (typically `createContractRuntime(...)`).\n * @param address - The SS58 address of the account to map.\n * @param signer - A signer matching `address`.\n * @param options - Optional timeout / status callback (forwarded to the underlying tx).\n * @returns The `TxResult` from the mapping extrinsic, or `null` if already mapped.\n *\n * @example\n * ```ts\n * import { createContractRuntime, ensureContractAccountMapped } from \"@parity/product-sdk-contracts\";\n *\n * const runtime = createContractRuntime(client.getTypedApi(paseo_asset_hub));\n * await ensureContractAccountMapped(runtime, signerManager.getState().selectedAccount!.address, signer);\n * // now safe to call contract.<method>.tx({ signer })\n * ```\n */\nexport async function ensureContractAccountMapped(\n runtime: ContractRuntime,\n address: SS58String,\n signer: PolkadotSigner,\n options?: { timeoutMs?: number; onStatus?: (s: string) => void },\n): Promise<TxResult | null> {\n const checker = {\n addressIsMapped: async (addr: string): Promise<boolean> => {\n const h160 = ss58ToH160(addr) as HexString;\n return (await runtime.api.query.Revive.OriginalAccount.getValue(h160)) !== undefined;\n },\n };\n return ensureAccountMapped(address, signer, checker, runtime.api, options);\n}\n\nif (import.meta.vitest) {\n const { test, expect, describe, vi } = import.meta.vitest;\n\n describe(\"ensureContractAccountMapped\", () => {\n // Pin the wiring: storage hit ⇒ short-circuit to null without\n // submitting; H160 (not SS58) is what reaches the storage query.\n const aliceSs58 = \"5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY\" as SS58String;\n const fakeSigner = { publicKey: new Uint8Array(32) } as unknown as PolkadotSigner;\n\n function makeRuntime(opts: {\n mapped: boolean;\n mapAccount?: () => SubmittableTransaction;\n }): {\n runtime: ContractRuntime;\n getValue: ReturnType<typeof vi.fn>;\n mapAccount: ReturnType<typeof vi.fn>;\n } {\n const getValue = vi.fn(async () =>\n opts.mapped ? (\"5mappedSs58\" as SS58String) : undefined,\n );\n const mapAccount = vi.fn(() => {\n if (opts.mapAccount) return opts.mapAccount();\n throw new Error(\"map_account must NOT be invoked when address is already mapped\");\n });\n const runtime: ContractRuntime = {\n api: {\n tx: {\n Revive: {\n call: () => {\n throw new Error(\"Revive.call is unrelated to mapping\");\n },\n map_account: mapAccount,\n },\n },\n query: {\n Revive: {\n OriginalAccount: { getValue },\n },\n },\n apis: {\n ReviveApi: {\n call: () => {\n throw new Error(\"ReviveApi.call is unrelated to mapping\");\n },\n },\n },\n } as unknown as ReviveTypedApi,\n dryRunCall: () => {\n throw new Error(\"dryRunCall is unrelated to mapping\");\n },\n };\n return { runtime, getValue, mapAccount };\n }\n\n test(\"returns null without submitting when storage already has the mapping\", async () => {\n const { runtime, getValue, mapAccount } = makeRuntime({ mapped: true });\n const result = await ensureContractAccountMapped(runtime, aliceSs58, fakeSigner);\n\n expect(result).toBeNull();\n // The H160 derivation hands a `0x…` hex string to the storage\n // query — not the SS58 address. If the wiring ever forwards the\n // SS58 by accident, this assertion catches it.\n expect(getValue).toHaveBeenCalledTimes(1);\n const passedAddress = getValue.mock.calls[0][0] as string;\n expect(passedAddress.startsWith(\"0x\")).toBe(true);\n expect(passedAddress.length).toBe(2 + 40);\n expect(mapAccount).not.toHaveBeenCalled();\n });\n });\n}\n","import type { HexString, PolkadotClient } from \"polkadot-api\";\nimport { wrapContract } from \"./wrap.js\";\nimport { ContractNotFoundError } from \"./errors.js\";\nimport type { ContractRuntime } from \"./runtime.js\";\nimport { createContractRuntimeFromClient } from \"./runtime.js\";\nimport type {\n AbiEntry,\n CdmJson,\n CdmJsonContract,\n Contract,\n ContractDef,\n ContractDefaults,\n ContractManagerOptions,\n ContractOptions,\n Contracts,\n} from \"./types.js\";\n\n/**\n * Manages typed contract interactions backed by a `cdm.json` manifest.\n *\n * Pass a `signerManager` (e.g. a `SignerManager` from `@parity/product-sdk-signer`)\n * so the currently logged-in account is used automatically — no manual\n * signer/origin wiring needed.\n *\n * @example\n * ```ts\n * import { createChainClient } from \"@parity/product-sdk-chain-client\";\n * import { paseo_asset_hub } from \"@parity/product-sdk-descriptors/paseo-asset-hub\";\n * import { ContractManager, createContractRuntime } from \"@parity/product-sdk-contracts\";\n * import cdmJson from \"./cdm.json\";\n *\n * const client = await createChainClient({\n * chains: { assetHub: paseo_asset_hub },\n * rpcs: { assetHub: [\"wss://paseo-asset-hub-next-rpc.polkadot.io\"] },\n * });\n * const runtime = createContractRuntime(client.assetHub);\n * const manager = new ContractManager(cdmJson, runtime, {\n * signerManager,\n * });\n *\n * const counter = manager.getContract(\"@example/counter\");\n * const { value } = await counter.getCount.query();\n * await counter.increment.tx();\n * ```\n */\nexport class ContractManager {\n private cdmJson: CdmJson;\n private targetHash: string;\n private runtime: ContractRuntime;\n private defaults: ContractDefaults;\n\n constructor(cdmJson: CdmJson, runtime: ContractRuntime, options?: ContractManagerOptions) {\n this.cdmJson = cdmJson;\n this.runtime = runtime;\n\n if (options?.targetHash) {\n this.targetHash = options.targetHash;\n } else {\n const targets = Object.keys(cdmJson.targets);\n if (targets.length === 0) throw new Error(\"No targets found in cdm.json\");\n this.targetHash = targets[0];\n }\n\n this.defaults = {\n signerManager: options?.signerManager,\n origin: options?.defaultOrigin,\n signer: options?.defaultSigner,\n };\n }\n\n /** Update the default origin, signer, or signerManager used by all contract handles. */\n setDefaults(defaults: ContractDefaults): void {\n if (defaults.signerManager !== undefined)\n this.defaults.signerManager = defaults.signerManager;\n if (defaults.origin !== undefined) this.defaults.origin = defaults.origin;\n if (defaults.signer !== undefined) this.defaults.signer = defaults.signer;\n }\n\n /**\n * Create a `ContractManager` from a raw `PolkadotClient`.\n *\n * Convenience factory: builds a `ContractRuntime` internally from the\n * client's typed API. Requires that the chain's typed API exposes the\n * `Revive` pallet and `ReviveApi` runtime API (Asset Hub Paseo /\n * Polkadot / Kusama).\n *\n * @param cdmJson - The CDM manifest.\n * @param client - A `PolkadotClient` for the chain where contracts are deployed.\n * @param descriptor - The chain descriptor used to derive the typed API.\n * @param options - Optional configuration (signerManager, defaults).\n */\n static fromClient<TDescriptor>(\n cdmJson: CdmJson,\n client: PolkadotClient,\n descriptor: TDescriptor,\n options?: ContractManagerOptions,\n ): ContractManager {\n return new ContractManager(\n cdmJson,\n createContractRuntimeFromClient(client, descriptor),\n options,\n );\n }\n\n private getContractData(library: string): CdmJsonContract {\n const contractsForTarget = this.cdmJson.contracts?.[this.targetHash];\n if (!contractsForTarget || !(library in contractsForTarget)) {\n throw new ContractNotFoundError(library, this.targetHash);\n }\n return contractsForTarget[library];\n }\n\n /**\n * Get a typed contract handle.\n *\n * Each method on the returned object has `.query()` for read-only calls\n * and `.tx()` for signed transactions. When codegen augments\n * {@link Contracts}, passing a known library name returns a fully-typed\n * handle. Without codegen the generic overload still works — methods are\n * accessible but untyped.\n */\n getContract<K extends string & keyof Contracts>(library: K): Contract<Contracts[K]>;\n getContract(library: string): Contract<ContractDef>;\n getContract(library: string): Contract<ContractDef> {\n const data = this.getContractData(library);\n return wrapContract(this.runtime, data.address, data.abi, this.defaults);\n }\n\n /** Get the on-chain address of an installed contract. */\n getAddress(library: string): HexString {\n return this.getContractData(library).address;\n }\n\n /**\n * Get the underlying {@link ContractRuntime} backing this manager.\n *\n * Useful when a consumer needs to call helpers that take a runtime\n * directly — most commonly {@link ensureContractAccountMapped} at app\n * boot. Avoids the alternative of building a second runtime against the\n * same client and descriptor.\n */\n getRuntime(): ContractRuntime {\n return this.runtime;\n }\n}\n\n/**\n * Create a contract handle from a raw H160 address and ABI — no `cdm.json` needed.\n *\n * @example\n * ```ts\n * import { createContractRuntime, createContract } from \"@parity/product-sdk-contracts\";\n *\n * const runtime = createContractRuntime(client.assetHub);\n * const counter = createContract(runtime, \"0xC472...\", abi, { signerManager });\n * await counter.getCount.query();\n * await counter.increment.tx();\n * ```\n */\nexport function createContract(\n runtime: ContractRuntime,\n address: HexString,\n abi: AbiEntry[],\n options?: ContractOptions,\n): Contract<ContractDef> {\n const defaults: ContractDefaults = {\n signerManager: options?.signerManager,\n origin: options?.defaultOrigin,\n signer: options?.defaultSigner,\n };\n return wrapContract(runtime, address, abi, defaults);\n}\n\n/**\n * Create a contract handle from a raw `PolkadotClient`, descriptor, address, and ABI.\n *\n * Convenience wrapper that builds the `ContractRuntime` from the client's\n * typed API. The chain must expose `Revive` + `ReviveApi`.\n *\n * @example\n * ```ts\n * const counter = createContractFromClient(client, paseo_asset_hub, \"0xC472...\", abi);\n * const { value } = await counter.getCount.query();\n * ```\n */\nexport function createContractFromClient<TDescriptor>(\n client: PolkadotClient,\n descriptor: TDescriptor,\n address: HexString,\n abi: AbiEntry[],\n options?: ContractOptions,\n): Contract<ContractDef> {\n return createContract(\n createContractRuntimeFromClient(client, descriptor),\n address,\n abi,\n options,\n );\n}\n\nif (import.meta.vitest) {\n const { describe, test, expect } = import.meta.vitest;\n\n /**\n * Real-world cdm.json structure as it appears in\n * `paritytech/playground-cli/cdm.json`. Used here as the reproducer\n * for the cdm-resolution flow: if `getContract()` works against\n * this manifest shape, it works against any consumer's manifest.\n *\n * Notable shape differences from the generated examples:\n * - `metadataCid` is absent (made optional in 0.2.1)\n * - target hash is a single 16-char hex string\n * - `dependencies` uses `\"latest\"` for version\n * - contract addresses are 20-byte EVM-shaped (Polkadot Asset Hub\n * uses Solidity-compatible addresses for Revive contracts)\n */\n const playgroundCdm: CdmJson = {\n targets: {\n acc2c3b5e912b762: {\n \"asset-hub\": \"wss://paseo-asset-hub-next-rpc.polkadot.io\",\n bulletin: \"https://paseo-bulletin-next-ipfs.polkadot.io\",\n },\n },\n dependencies: {\n acc2c3b5e912b762: {\n \"@w3s/playground-registry\": \"latest\",\n },\n },\n contracts: {\n acc2c3b5e912b762: {\n \"@w3s/playground-registry\": {\n version: 6,\n address: \"0x4A37B123b0BA2A894cA5953f472264921d44e298\",\n abi: [\n { type: \"constructor\", inputs: [], stateMutability: \"nonpayable\" },\n {\n type: \"function\",\n name: \"publish\",\n inputs: [\n { name: \"domain\", type: \"string\" },\n { name: \"metadata_uri\", type: \"string\" },\n { name: \"visibility\", type: \"uint8\" },\n ],\n outputs: [],\n stateMutability: \"nonpayable\",\n },\n {\n type: \"function\",\n name: \"unpublish\",\n inputs: [{ name: \"domain\", type: \"string\" }],\n outputs: [],\n stateMutability: \"nonpayable\",\n },\n ],\n },\n },\n },\n };\n\n /**\n * Minimal `ContractRuntime` stub — `ContractManager` only forwards the\n * runtime through to `wrapContract`'s proxy, which doesn't invoke any\n * runtime member at construction time. The fields below stay\n * shape-only; any test that actually wants to call `.query()` / `.tx()`\n * builds its own runtime with real captures.\n */\n function fakeRuntime(): ContractRuntime {\n return {\n api: {\n tx: { Revive: { call: () => null, map_account: () => null } },\n query: { Revive: { OriginalAccount: { getValue: async () => undefined } } },\n apis: { ReviveApi: { call: async () => null } },\n },\n dryRunCall: async () => null,\n } as unknown as ContractRuntime;\n }\n\n describe(\"ContractManager — cdm.json resolution\", () => {\n test(\"constructs from a real-world cdm.json without errors\", () => {\n const manager = new ContractManager(playgroundCdm, fakeRuntime());\n expect(manager.getAddress(\"@w3s/playground-registry\")).toBe(\n \"0x4A37B123b0BA2A894cA5953f472264921d44e298\",\n );\n });\n\n test(\"getContract returns a typed handle for a library in the manifest\", () => {\n const manager = new ContractManager(playgroundCdm, fakeRuntime());\n const registry = manager.getContract(\"@w3s/playground-registry\") as unknown as Record<\n string,\n { query: unknown; tx: unknown }\n >;\n\n expect(typeof registry.publish.query).toBe(\"function\");\n expect(typeof registry.publish.tx).toBe(\"function\");\n expect(typeof registry.unpublish.query).toBe(\"function\");\n });\n\n test(\"getContract throws ContractNotFoundError for an unknown library\", () => {\n const manager = new ContractManager(playgroundCdm, fakeRuntime());\n expect(() => manager.getContract(\"@nonexistent/contract\")).toThrow(\n /not found in cdm\\.json/,\n );\n });\n\n test(\"auto-selects the first target when no targetHash is provided\", () => {\n const manager = new ContractManager(playgroundCdm, fakeRuntime());\n // Single-target manifest — should resolve cleanly without\n // requiring an explicit targetHash.\n expect(() => manager.getContract(\"@w3s/playground-registry\")).not.toThrow();\n });\n\n test(\"getAddress returns the manifest's recorded H160 for a library\", () => {\n // Replaces the prior \"passes the right address to inkSdk\" test —\n // the new runtime doesn't take the address at construction time\n // (wrapContract receives it directly), so we assert the\n // manifest-side projection instead.\n const manager = new ContractManager(playgroundCdm, fakeRuntime());\n expect(manager.getAddress(\"@w3s/playground-registry\")).toBe(\n \"0x4A37B123b0BA2A894cA5953f472264921d44e298\",\n );\n });\n\n test(\"explicit targetHash option selects the right contracts subtree\", () => {\n // Multi-target manifest: we should be able to pin to a specific\n // target hash and have getContract resolve against it.\n const multiTargetCdm: CdmJson = {\n targets: {\n target_a: { \"asset-hub\": \"wss://a\", bulletin: \"https://a\" },\n target_b: { \"asset-hub\": \"wss://b\", bulletin: \"https://b\" },\n },\n dependencies: {\n target_a: { \"@org/foo\": \"1.0\" },\n target_b: { \"@org/foo\": \"2.0\" },\n },\n contracts: {\n target_a: {\n \"@org/foo\": {\n version: 1,\n address: \"0x1111111111111111111111111111111111111111\",\n abi: [],\n },\n },\n target_b: {\n \"@org/foo\": {\n version: 2,\n address: \"0x2222222222222222222222222222222222222222\",\n abi: [],\n },\n },\n },\n };\n\n const aManager = new ContractManager(multiTargetCdm, fakeRuntime(), {\n targetHash: \"target_a\",\n });\n const bManager = new ContractManager(multiTargetCdm, fakeRuntime(), {\n targetHash: \"target_b\",\n });\n\n expect(aManager.getAddress(\"@org/foo\")).toBe(\n \"0x1111111111111111111111111111111111111111\",\n );\n expect(bManager.getAddress(\"@org/foo\")).toBe(\n \"0x2222222222222222222222222222222222222222\",\n );\n });\n\n test(\"constructor throws when cdm.json has no targets\", () => {\n const emptyCdm: CdmJson = { targets: {}, dependencies: {} };\n expect(() => new ContractManager(emptyCdm, fakeRuntime())).toThrow(/No targets found/);\n });\n });\n\n describe(\"ContractManager defaults\", () => {\n test(\"setDefaults updates origin / signer / signerManager mid-flight\", () => {\n const manager = new ContractManager(playgroundCdm, fakeRuntime(), {\n defaultOrigin: \"5OldOrigin\" as HexString,\n });\n // This is a behavioral check via private-ish field — we don't\n // expose `defaults` directly, but `setDefaults` returning\n // without error is the contract.\n expect(() => manager.setDefaults({ origin: \"5NewOrigin\" as HexString })).not.toThrow();\n });\n });\n}\n"]}
1
+ {"version":3,"sources":["../src/errors.ts","../src/wrap.ts","../src/runtime.ts","../src/manager.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["/** Base class for all contract errors. Use `instanceof ContractError` to catch any contract-related error. */\nexport class ContractError extends Error {\n constructor(message: string, options?: ErrorOptions) {\n super(message, options);\n this.name = \"ContractError\";\n }\n}\n\n/** No signer was available for a transaction call. */\nexport class ContractSignerMissingError extends ContractError {\n constructor() {\n super(\n \"No signer available. Pass { signer } in call options, \" +\n \"set defaultSigner, or provide a signerManager.\",\n );\n this.name = \"ContractSignerMissingError\";\n }\n}\n\n/** A contract was not found in the cdm.json manifest. */\nexport class ContractNotFoundError extends ContractError {\n readonly library: string;\n readonly targetHash: string;\n\n constructor(library: string, targetHash: string) {\n super(`Contract \"${library}\" not found in cdm.json for target ${targetHash}`);\n this.name = \"ContractNotFoundError\";\n this.library = library;\n this.targetHash = targetHash;\n }\n}\n\n/**\n * A pre-flight `ReviveApi.call` dry-run reported failure. Thrown from the `.tx()`\n * path before the extrinsic is built — prevents callers from paying gas on a\n * transaction the chain already told us would revert.\n *\n * `dispatchError` carries the chain's encoded error (typically `ModuleError`,\n * `ContractReverted`, `OutOfGas`, or `AccountNotMapped` — see the `Revive`\n * pallet error variants).\n */\nexport class ContractDryRunFailedError extends ContractError {\n readonly methodName: string;\n readonly dispatchError: unknown;\n\n constructor(methodName: string, dispatchError: unknown) {\n super(\n `Dry-run failed for \"${methodName}\": ${\n typeof dispatchError === \"string\" ? dispatchError : JSON.stringify(dispatchError)\n }. The transaction was not submitted.`,\n );\n this.name = \"ContractDryRunFailedError\";\n this.methodName = methodName;\n this.dispatchError = dispatchError;\n }\n}\n\nif (import.meta.vitest) {\n const { test, expect, describe } = import.meta.vitest;\n\n describe(\"ContractError\", () => {\n test(\"base error has correct name\", () => {\n const err = new ContractError(\"test\");\n expect(err.name).toBe(\"ContractError\");\n expect(err).toBeInstanceOf(Error);\n });\n\n test(\"instanceof catches all contract errors\", () => {\n expect(new ContractSignerMissingError()).toBeInstanceOf(ContractError);\n expect(new ContractNotFoundError(\"@a/b\", \"abc\")).toBeInstanceOf(ContractError);\n expect(new ContractDryRunFailedError(\"foo\", \"x\")).toBeInstanceOf(ContractError);\n });\n });\n\n describe(\"ContractSignerMissingError\", () => {\n test(\"message mentions signer options\", () => {\n const err = new ContractSignerMissingError();\n expect(err.message).toContain(\"signer\");\n expect(err.message).toContain(\"signerManager\");\n expect(err.name).toBe(\"ContractSignerMissingError\");\n });\n });\n\n describe(\"ContractNotFoundError\", () => {\n test(\"includes library and target\", () => {\n const err = new ContractNotFoundError(\"@test/foo\", \"abc123\");\n expect(err.library).toBe(\"@test/foo\");\n expect(err.targetHash).toBe(\"abc123\");\n expect(err.message).toContain(\"@test/foo\");\n expect(err.message).toContain(\"abc123\");\n });\n });\n\n describe(\"ContractDryRunFailedError\", () => {\n test(\"captures method name and dispatch error\", () => {\n const dispatchError = { type: \"Module\", value: { type: \"Revive\" } };\n const err = new ContractDryRunFailedError(\"transfer\", dispatchError);\n expect(err.methodName).toBe(\"transfer\");\n expect(err.dispatchError).toBe(dispatchError);\n expect(err.message).toContain(\"transfer\");\n expect(err.message).toContain(\"not submitted\");\n expect(err.name).toBe(\"ContractDryRunFailedError\");\n });\n\n test(\"handles string dispatch error without JSON-stringifying\", () => {\n const err = new ContractDryRunFailedError(\"foo\", \"ContractReverted\");\n expect(err.message).toContain(\"ContractReverted\");\n expect(err.message).not.toContain('\"ContractReverted\"');\n });\n });\n}\n","import type { HexString, PolkadotSigner, SS58String } from \"polkadot-api\";\nimport { encodeFunctionData, decodeFunctionResult, type Abi as ViemAbi } from \"viem\";\nimport { submitAndWatch } from \"@parity/product-sdk-tx\";\nimport { seedToAccount } from \"@parity/product-sdk-keys\";\nimport { createLogger } from \"@parity/product-sdk-logger\";\nimport { DEV_PHRASE, ss58Address } from \"@polkadot-labs/hdkd-helpers\";\nimport { ContractSignerMissingError, ContractDryRunFailedError } from \"./errors.js\";\nimport type { ContractRuntime } from \"./runtime.js\";\nimport type { BatchableCall, SubmittableTransaction } from \"@parity/product-sdk-tx\";\nimport type {\n AbiEntry,\n Contract,\n ContractDef,\n ContractDefaults,\n PrepareOptions,\n QueryOptions,\n QueryResult,\n TxOptions,\n} from \"./types.js\";\n\nconst log = createLogger(\"contracts\");\n\n/** Map of method name → ordered ABI parameter names. */\nfunction buildMethodArgMap(abi: AbiEntry[]): Record<string, string[]> {\n const map: Record<string, string[]> = {};\n for (const entry of abi) {\n if (entry.type === \"function\" && entry.name) {\n map[entry.name] = entry.inputs.map((p) => p.name);\n }\n }\n return map;\n}\n\n/**\n * If the caller passed more arguments than the ABI expects and the last\n * argument is a plain object, treat it as an options override.\n */\nfunction extractOverrides<T>(\n argNames: string[],\n args: unknown[],\n): { positionalArgs: unknown[]; overrides?: T } {\n if (args.length > argNames.length && args.length > 0) {\n const last = args[args.length - 1];\n if (last && typeof last === \"object\" && !Array.isArray(last)) {\n return { positionalArgs: args.slice(0, -1), overrides: last as T };\n }\n }\n return { positionalArgs: args };\n}\n\n/**\n * Dev address (Alice) used as fallback origin for read-only queries when no\n * wallet is connected. Queries are dry-run simulations — the origin only\n * affects gas estimation and is safe to stub.\n */\nconst QUERY_FALLBACK_ORIGIN = seedToAccount(DEV_PHRASE, \"//Alice\").ss58Address as SS58String;\n\nfunction resolveOrigin(\n defaults: ContractDefaults,\n override?: SS58String,\n forQuery?: boolean,\n): SS58String | undefined {\n if (override) return override;\n const sourceAddr = defaults.signerManager?.getState().selectedAccount?.address;\n if (sourceAddr) return sourceAddr as SS58String;\n if (defaults.origin) return defaults.origin;\n if (forQuery) {\n log.warn(\"No origin configured — using dev fallback (Alice) for query dry-run\");\n return QUERY_FALLBACK_ORIGIN;\n }\n return undefined;\n}\n\nfunction resolveSigner(\n defaults: ContractDefaults,\n override?: PolkadotSigner,\n): PolkadotSigner | undefined {\n return override ?? defaults.signerManager?.getSigner() ?? defaults.signer;\n}\n\n/**\n * Normalise a contract address to a `0x`-prefixed 20-byte hex string —\n * the shape PAPI ≥2.0 codecs and compat checks accept for `[u8; 20]` args.\n * Accepts the prefix being absent and re-adds it.\n */\nfunction normalizeContractAddress(address: string): HexString {\n const hex = address.startsWith(\"0x\") ? address.slice(2) : address;\n if (hex.length !== 40) {\n throw new Error(`Expected 20-byte H160 contract address, got ${hex.length / 2} bytes`);\n }\n return `0x${hex.toLowerCase()}` as HexString;\n}\n\n/** Convert a `0x`-prefixed hex string to a `Uint8Array`. */\nfunction hexToBytes(hex: HexString): Uint8Array {\n const stripped = hex.slice(2);\n const out = new Uint8Array(stripped.length / 2);\n for (let i = 0; i < out.length; i++) {\n out[i] = Number.parseInt(stripped.slice(i * 2, i * 2 + 2), 16);\n }\n return out;\n}\n\n/**\n * Encode the calldata for a contract method using the Solidity ABI codec.\n * Returns `selector ‖ head ‖ tail` as a `0x`-prefixed hex string.\n */\nfunction encodeCalldata(abi: AbiEntry[], methodName: string, args: unknown[]): `0x${string}` {\n return encodeFunctionData({\n abi: abi as unknown as ViemAbi,\n functionName: methodName,\n args,\n });\n}\n\n/**\n * Decode a successful query's return data via the Solidity ABI codec.\n * Returns `undefined` for void methods.\n *\n * Shape note: viem hands back the raw value for single-output methods and a\n * positional array for multi-output ones. The codegen pairs in\n * `generateMethodResponseType` surface multi-output returns as a named\n * object (`{name1: T1; name2: T2}`), so we assemble that object here from\n * viem's array. Single-output and Solidity-tuple outputs (which viem\n * already returns as a named object) pass through untouched.\n */\nfunction decodeReturn(abi: AbiEntry[], methodName: string, returnData: Uint8Array): unknown {\n if (returnData.byteLength === 0) return undefined;\n let hex = \"0x\";\n for (let i = 0; i < returnData.byteLength; i++) {\n hex += returnData[i].toString(16).padStart(2, \"0\");\n }\n const decoded = decodeFunctionResult({\n abi: abi as unknown as ViemAbi,\n functionName: methodName,\n data: hex as `0x${string}`,\n });\n\n const entry = abi.find((e) => e.type === \"function\" && e.name === methodName);\n const outputs = entry?.outputs ?? [];\n if (outputs.length <= 1 || !Array.isArray(decoded)) return decoded;\n // Fall back to positional `_0`, `_1`, … when outputs are unnamed —\n // matches generateMethodResponseType's naming policy.\n const obj: Record<string, unknown> = {};\n for (let i = 0; i < outputs.length; i++) {\n obj[outputs[i].name || `_${i}`] = decoded[i];\n }\n return obj;\n}\n\n/**\n * Shared pre-submit pipeline for `.tx()` and `.prepare()`:\n *\n * 1. Encode the calldata via viem.\n * 2. If either gas/storage override is missing, dry-run via\n * `runtime.dryRunCall` to size the missing limit(s) and fail fast on\n * revert / OOG / `AccountNotMapped`. Skipped when both are provided.\n * 3. Build the `Revive.call` extrinsic via the typed API.\n *\n * Returned `SubmittableTransaction` is what `.tx()` hands to `submitAndWatch`\n * and what `.prepare()` returns as a `BatchableCall`.\n */\nasync function buildReviveCall(\n runtime: ContractRuntime,\n dest: HexString,\n abi: AbiEntry[],\n methodName: string,\n positionalArgs: unknown[],\n origin: SS58String,\n overrides: PrepareOptions | TxOptions | undefined,\n): Promise<SubmittableTransaction> {\n const value = overrides?.value ?? 0n;\n const calldata = hexToBytes(encodeCalldata(abi, methodName, positionalArgs));\n\n let weightLimit = overrides?.gasLimit;\n let storageDepositLimit = overrides?.storageDepositLimit;\n if (weightLimit === undefined || storageDepositLimit === undefined) {\n const dryRun = await runtime.dryRunCall(\n origin,\n dest,\n value,\n undefined,\n undefined,\n calldata,\n );\n if (!dryRun.result.success) {\n throw new ContractDryRunFailedError(methodName, dryRun.result.value);\n }\n weightLimit = weightLimit ?? dryRun.weight_required;\n if (storageDepositLimit === undefined) {\n storageDepositLimit =\n dryRun.storage_deposit.type === \"Charge\" ? dryRun.storage_deposit.value : 0n;\n }\n }\n\n return runtime.api.tx.Revive.call({\n dest,\n value,\n weight_limit: weightLimit,\n storage_deposit_limit: storageDepositLimit,\n data: calldata,\n });\n}\n\n/**\n * Build a typed contract handle backed by direct `Revive` extrinsic +\n * `ReviveApi` runtime API calls. The Solidity ABI codec runs through `viem`.\n *\n * @param runtime - A `ContractRuntime` (returned by `createContractRuntime`).\n * @param address - The H160 address of the deployed contract.\n * @param abi - The Solidity ABI for the contract.\n * @param defaults - Origin / signer fallbacks shared across all method calls.\n */\nexport function wrapContract(\n runtime: ContractRuntime,\n address: string,\n abi: AbiEntry[],\n defaults: ContractDefaults,\n): Contract<ContractDef> {\n const methodArgs = buildMethodArgMap(abi);\n const dest = normalizeContractAddress(address);\n\n return new Proxy({} as Record<string, unknown>, {\n get(_, methodName: string) {\n if (typeof methodName !== \"string\") return undefined;\n const argNames = methodArgs[methodName];\n if (!argNames) return undefined;\n\n return {\n query: async (...args: unknown[]): Promise<QueryResult<unknown>> => {\n const { positionalArgs, overrides } = extractOverrides<QueryOptions>(\n argNames,\n args,\n );\n const origin = resolveOrigin(defaults, overrides?.origin, true)!;\n const value = overrides?.value ?? 0n;\n\n const calldata = hexToBytes(encodeCalldata(abi, methodName, positionalArgs));\n\n const dryRun = await runtime.dryRunCall(\n origin,\n dest,\n value,\n undefined,\n undefined,\n calldata,\n );\n\n if (!dryRun.result.success) {\n // Pass the dispatch-error payload through. `value`\n // typically narrows as a tagged enum (e.g.\n // `{ type: \"Module\", value: ... }`,\n // `{ type: \"ContractReverted\" }`,\n // `{ type: \"AccountNotMapped\" }`) — callers inspect\n // its shape to learn why the call failed instead of\n // receiving a bare `undefined` with no signal.\n return {\n success: false,\n value: dryRun.result.value,\n gasRequired: dryRun.weight_required,\n };\n }\n\n const decoded = decodeReturn(abi, methodName, dryRun.result.value.data);\n return {\n success: true,\n value: decoded,\n gasRequired: dryRun.weight_required,\n };\n },\n\n tx: async (...args: unknown[]) => {\n const { positionalArgs, overrides } = extractOverrides<TxOptions>(\n argNames,\n args,\n );\n const signer = resolveSigner(defaults, overrides?.signer);\n if (!signer) {\n throw new ContractSignerMissingError();\n }\n\n const origin =\n resolveOrigin(defaults, overrides?.origin) ??\n (ss58Address(signer.publicKey) as SS58String);\n\n const tx = await buildReviveCall(\n runtime,\n dest,\n abi,\n methodName,\n positionalArgs,\n origin,\n overrides,\n );\n\n return submitAndWatch(tx, signer, {\n waitFor: overrides?.waitFor,\n timeoutMs: overrides?.timeoutMs,\n mortalityPeriod: overrides?.mortalityPeriod,\n onStatus: overrides?.onStatus,\n });\n },\n\n prepare: async (...args: unknown[]): Promise<BatchableCall> => {\n // `.prepare()` builds the same `Revive.call` extrinsic as\n // `.tx()` but stops before submission — the returned\n // SubmittableTransaction is a BatchableCall consumable\n // by `batchSubmitAndWatch`. Origin defaults to the dev\n // address (Alice) for dry-run gas estimation since no\n // signer is required at prepare time; the batch's signer\n // replaces the dispatched origin at submission.\n const { positionalArgs, overrides } = extractOverrides<PrepareOptions>(\n argNames,\n args,\n );\n const origin = resolveOrigin(defaults, overrides?.origin, true)!;\n return buildReviveCall(\n runtime,\n dest,\n abi,\n methodName,\n positionalArgs,\n origin,\n overrides,\n );\n },\n };\n },\n }) as Contract<ContractDef>;\n}\n\nif (import.meta.vitest) {\n const { test, expect, describe } = import.meta.vitest;\n\n describe(\"buildMethodArgMap\", () => {\n test(\"extracts function parameter names from ABI\", () => {\n const abi: AbiEntry[] = [\n { type: \"constructor\", inputs: [], stateMutability: \"nonpayable\" },\n {\n type: \"function\",\n name: \"transfer\",\n inputs: [\n { name: \"to\", type: \"address\" },\n { name: \"amount\", type: \"uint256\" },\n ],\n outputs: [{ name: \"\", type: \"bool\" }],\n },\n {\n type: \"function\",\n name: \"balanceOf\",\n inputs: [{ name: \"owner\", type: \"address\" }],\n outputs: [{ name: \"\", type: \"uint256\" }],\n },\n { type: \"event\", name: \"Transfer\", inputs: [] },\n ];\n expect(buildMethodArgMap(abi)).toEqual({\n transfer: [\"to\", \"amount\"],\n balanceOf: [\"owner\"],\n });\n });\n\n test(\"returns empty map for ABI with no functions\", () => {\n const abi: AbiEntry[] = [\n { type: \"constructor\", inputs: [] },\n { type: \"event\", name: \"Evt\", inputs: [] },\n ];\n expect(buildMethodArgMap(abi)).toEqual({});\n });\n });\n\n describe(\"extractOverrides\", () => {\n test(\"returns overrides when extra object arg is present\", () => {\n const result = extractOverrides<{ origin: string }>([\"a\"], [42, { origin: \"0x1\" }]);\n expect(result.positionalArgs).toEqual([42]);\n expect(result.overrides).toEqual({ origin: \"0x1\" });\n });\n\n test(\"returns no overrides when arg count matches\", () => {\n const result = extractOverrides([\"a\", \"b\"], [1, 2]);\n expect(result.positionalArgs).toEqual([1, 2]);\n expect(result.overrides).toBeUndefined();\n });\n\n test(\"does not treat array as overrides\", () => {\n const result = extractOverrides([\"a\"], [1, [2, 3]]);\n expect(result.positionalArgs).toEqual([1, [2, 3]]);\n expect(result.overrides).toBeUndefined();\n });\n\n test(\"does not treat primitive as overrides\", () => {\n const result = extractOverrides([\"a\"], [1, \"extra\"]);\n expect(result.positionalArgs).toEqual([1, \"extra\"]);\n expect(result.overrides).toBeUndefined();\n });\n });\n\n describe(\"normalizeContractAddress\", () => {\n test(\"accepts 0x-prefixed H160\", () => {\n expect(normalizeContractAddress(\"0x1234567890abcdef1234567890ABCDEF12345678\")).toBe(\n \"0x1234567890abcdef1234567890abcdef12345678\",\n );\n });\n\n test(\"accepts unprefixed hex and re-adds the 0x prefix\", () => {\n expect(normalizeContractAddress(\"aabbccddeeff00112233445566778899aabbccdd\")).toBe(\n \"0xaabbccddeeff00112233445566778899aabbccdd\",\n );\n });\n\n test(\"rejects wrong length\", () => {\n expect(() => normalizeContractAddress(\"0x1234\")).toThrow(/20-byte/);\n });\n });\n\n describe(\"hexToBytes\", () => {\n test(\"decodes 0x-prefixed hex to bytes\", () => {\n expect(Array.from(hexToBytes(\"0xdeadbeef\"))).toEqual([0xde, 0xad, 0xbe, 0xef]);\n });\n\n test(\"returns an empty array for the empty hex literal\", () => {\n expect(hexToBytes(\"0x\").byteLength).toBe(0);\n });\n });\n\n describe(\"encodeCalldata / decodeReturn (viem round-trip)\", () => {\n const abi: AbiEntry[] = [\n {\n type: \"function\",\n name: \"add\",\n inputs: [\n { name: \"a\", type: \"uint32\" },\n { name: \"b\", type: \"uint32\" },\n ],\n outputs: [{ name: \"\", type: \"uint32\" }],\n stateMutability: \"view\",\n },\n {\n type: \"function\",\n name: \"name\",\n inputs: [],\n outputs: [{ name: \"\", type: \"string\" }],\n stateMutability: \"view\",\n },\n ];\n\n test(\"encodes selector + args\", () => {\n const data = encodeCalldata(abi, \"add\", [1, 2]);\n expect(data.slice(0, 2)).toBe(\"0x\");\n // 4-byte selector + 2 * 32-byte args = 68 bytes = 136 hex chars + \"0x\"\n expect(data.length).toBe(2 + 4 * 2 + 2 * 32 * 2);\n });\n\n test(\"decodes single uint32 return\", () => {\n const buf = new Uint8Array(32);\n buf[31] = 7;\n expect(decodeReturn(abi, \"add\", buf)).toBe(7);\n });\n\n test(\"decodes string return\", () => {\n const hex =\n \"0000000000000000000000000000000000000000000000000000000000000020\" +\n \"0000000000000000000000000000000000000000000000000000000000000002\" +\n \"6869000000000000000000000000000000000000000000000000000000000000\";\n const buf = new Uint8Array(hex.length / 2);\n for (let i = 0; i < buf.length; i++) {\n buf[i] = Number.parseInt(hex.slice(i * 2, i * 2 + 2), 16);\n }\n expect(decodeReturn(abi, \"name\", buf)).toBe(\"hi\");\n });\n\n test(\"returns undefined for empty data\", () => {\n expect(decodeReturn(abi, \"add\", new Uint8Array(0))).toBeUndefined();\n });\n\n test(\"multi-output method: assembles named object from viem's positional array\", () => {\n // viem hands back `[balance, nonce]` for multi-output methods,\n // but `generateMethodResponseType` surfaces this as\n // `{ balance: bigint; nonce: bigint }`. decodeReturn should\n // bridge the two so the runtime shape matches the codegen type.\n const multiAbi: AbiEntry[] = [\n {\n type: \"function\",\n name: \"info\",\n inputs: [],\n outputs: [\n { name: \"balance\", type: \"uint256\" },\n { name: \"nonce\", type: \"uint256\" },\n ],\n stateMutability: \"view\",\n },\n ];\n const buf = new Uint8Array(64);\n buf[31] = 7;\n buf[63] = 11;\n expect(decodeReturn(multiAbi, \"info\", buf)).toEqual({ balance: 7n, nonce: 11n });\n });\n\n test(\"multi-output method with unnamed outputs: falls back to _0, _1, …\", () => {\n // Mirrors generateMethodResponseType's `_${i}` policy when an\n // output has no name in the ABI.\n const unnamedAbi: AbiEntry[] = [\n {\n type: \"function\",\n name: \"stats\",\n inputs: [],\n outputs: [\n { name: \"\", type: \"uint256\" },\n { name: \"\", type: \"uint256\" },\n ],\n stateMutability: \"view\",\n },\n ];\n const buf = new Uint8Array(64);\n buf[31] = 1;\n buf[63] = 2;\n expect(decodeReturn(unnamedAbi, \"stats\", buf)).toEqual({ _0: 1n, _1: 2n });\n });\n\n test(\"Solidity tuple output: viem already returns a named object — pass through\", () => {\n // Single tuple-type output: viem builds the named object itself\n // from the component names, so decodeReturn must not double-wrap.\n const tupleAbi: AbiEntry[] = [\n {\n type: \"function\",\n name: \"info\",\n inputs: [],\n outputs: [\n {\n name: \"result\",\n type: \"tuple\",\n components: [\n { name: \"balance\", type: \"uint256\" },\n { name: \"nonce\", type: \"uint256\" },\n ],\n },\n ],\n stateMutability: \"view\",\n },\n ];\n const buf = new Uint8Array(64);\n buf[31] = 7;\n buf[63] = 11;\n expect(decodeReturn(tupleAbi, \"info\", buf)).toEqual({ balance: 7n, nonce: 11n });\n });\n });\n\n /** Minimal SignerManager mock for resolve* helpers. */\n function mockSigner(opts: {\n address?: string | null;\n signer?: PolkadotSigner | null;\n }): import(\"@parity/product-sdk-signer\").SignerManager {\n return {\n getSigner: () => opts.signer ?? null,\n getState: () => ({\n selectedAccount: opts.address ? ({ address: opts.address } as never) : null,\n }),\n } as unknown as import(\"@parity/product-sdk-signer\").SignerManager;\n }\n\n describe(\"resolveOrigin\", () => {\n test(\"explicit override wins\", () => {\n const defaults: ContractDefaults = {\n origin: \"5Static\" as SS58String,\n signerManager: mockSigner({ address: \"5Source\" }),\n };\n expect(resolveOrigin(defaults, \"5Override\" as SS58String)).toBe(\"5Override\");\n });\n\n test(\"signerManager wins over static default\", () => {\n const defaults: ContractDefaults = {\n origin: \"5Static\" as SS58String,\n signerManager: mockSigner({ address: \"5Source\" }),\n };\n expect(resolveOrigin(defaults)).toBe(\"5Source\");\n });\n\n test(\"falls back to static default\", () => {\n const defaults: ContractDefaults = { origin: \"5Static\" as SS58String };\n expect(resolveOrigin(defaults)).toBe(\"5Static\");\n });\n\n test(\"returns undefined when nothing available\", () => {\n expect(resolveOrigin({})).toBeUndefined();\n });\n });\n\n describe(\"resolveSigner\", () => {\n const fakeSigner = { id: \"fake\" } as unknown as PolkadotSigner;\n const sourceSigner = { id: \"source\" } as unknown as PolkadotSigner;\n\n test(\"explicit override wins\", () => {\n const defaults: ContractDefaults = {\n signer: { id: \"static\" } as unknown as PolkadotSigner,\n signerManager: mockSigner({ signer: sourceSigner }),\n };\n expect(resolveSigner(defaults, fakeSigner)).toBe(fakeSigner);\n });\n\n test(\"signerManager wins over static default\", () => {\n const defaults: ContractDefaults = {\n signer: { id: \"static\" } as unknown as PolkadotSigner,\n signerManager: mockSigner({ signer: sourceSigner }),\n };\n expect(resolveSigner(defaults)).toBe(sourceSigner);\n });\n\n test(\"falls back to static default\", () => {\n const defaults: ContractDefaults = { signer: fakeSigner };\n expect(resolveSigner(defaults)).toBe(fakeSigner);\n });\n\n test(\"returns undefined when nothing available\", () => {\n expect(resolveSigner({})).toBeUndefined();\n });\n });\n\n describe(\"wrapContract — PAPI 2.x boundary (HexString / Uint8Array contract)\", () => {\n // The codegen now emits `HexString` for `bytes` and `SizedHex<N>` for\n // `bytesN`. These tests pin the runtime side: when a caller passes a\n // hex string for those args, the SDK must hand PAPI a `0x…` `dest`\n // and a `Uint8Array` `data` — anything else trips PAPI 2.x's\n // `isCompatible` check or its codecs. We capture the arguments PAPI\n // receives and assert on their concrete shapes.\n const ADDRESS_INPUT = \"0x0102030405060708090a0b0c0d0e0f1011121314\";\n\n type Captured = {\n dryRun: Parameters<ContractRuntime[\"dryRunCall\"]> | null;\n tx: { dest: unknown; data: unknown } | null;\n };\n\n function mockRuntime(captured: Captured): ContractRuntime {\n const successfulDryRun: ContractRuntime[\"dryRunCall\"] = async (...args) => {\n captured.dryRun = args;\n return {\n weight_consumed: { ref_time: 0n, proof_size: 0n },\n weight_required: { ref_time: 1n, proof_size: 1n },\n storage_deposit: { type: \"Charge\", value: 7n },\n max_storage_deposit: { type: \"Charge\", value: 7n },\n gas_consumed: 0n,\n result: { success: true, value: { flags: 0, data: new Uint8Array(0) } },\n };\n };\n return {\n api: {\n tx: {\n Revive: {\n call: (args: { dest: unknown; data: unknown }) => {\n captured.tx = { dest: args.dest, data: args.data };\n return {\n signSubmitAndWatch: () => ({\n subscribe: (handlers: {\n next: (event: unknown) => void;\n }) => {\n queueMicrotask(() => {\n handlers.next({\n type: \"txBestBlocksState\",\n txHash: \"0xdeadbeef\",\n found: true,\n ok: true,\n events: [],\n block: {\n hash: \"0xblock\",\n number: 1,\n index: 0,\n },\n });\n });\n return { unsubscribe: () => {} };\n },\n }),\n };\n },\n },\n },\n } as unknown as ContractRuntime[\"api\"],\n dryRunCall: successfulDryRun,\n };\n }\n\n const fakeSigner = {\n publicKey: new Uint8Array(32),\n } as unknown as PolkadotSigner;\n const origin = \"5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY\" as SS58String;\n\n test(\"`bytesN` argument: hex string is forwarded as 0x-string dest and Uint8Array calldata\", async () => {\n // Solidity: function setHash(bytes32 hash) — exercises the\n // `bytesN` codegen branch (now `SizedHex<N>`). The argument here\n // is what a user following the generated types would pass.\n const abi: AbiEntry[] = [\n {\n type: \"function\",\n name: \"setHash\",\n inputs: [{ name: \"hash\", type: \"bytes32\" }],\n outputs: [],\n stateMutability: \"nonpayable\",\n },\n ];\n\n const captured: Captured = { dryRun: null, tx: null };\n const wrapped = wrapContract(mockRuntime(captured), ADDRESS_INPUT, abi, {\n signer: fakeSigner,\n origin,\n });\n\n const hash = \"0x1111111111111111111111111111111111111111111111111111111111111111\";\n await (\n wrapped as unknown as { setHash: { tx: (h: string) => Promise<unknown> } }\n ).setHash.tx(hash);\n\n // PAPI's compat check rejects anything that isn't a `0x…` string\n // for an H160 dest. The class-based `FixedSizeBinary` would fail.\n expect(captured.dryRun?.[1]).toBe(ADDRESS_INPUT);\n expect(typeof captured.dryRun?.[1]).toBe(\"string\");\n\n // Variable-length calldata must arrive as a `Uint8Array`. The\n // ABI selector for `setHash(bytes32)` is `0xa61eb053`, followed\n // by the 32-byte argument right-padded into a 32-byte word.\n const calldata = captured.dryRun?.[5] as Uint8Array;\n expect(calldata).toBeInstanceOf(Uint8Array);\n expect(calldata.byteLength).toBe(4 + 32);\n expect(Array.from(calldata.slice(4, 36))).toEqual(Array(32).fill(0x11));\n\n // The same pair flows into the typed extrinsic — a class instance\n // here would silently mis-encode under PAPI 2.x.\n expect(captured.tx?.dest).toBe(ADDRESS_INPUT);\n expect(captured.tx?.data).toBeInstanceOf(Uint8Array);\n });\n\n test(\"variable `bytes` argument: hex string round-trips through viem to Uint8Array calldata\", async () => {\n // Solidity: function store(bytes data) — exercises the `bytes`\n // codegen branch (now `HexString`).\n const abi: AbiEntry[] = [\n {\n type: \"function\",\n name: \"store\",\n inputs: [{ name: \"data\", type: \"bytes\" }],\n outputs: [],\n stateMutability: \"nonpayable\",\n },\n ];\n\n const captured: Captured = { dryRun: null, tx: null };\n const wrapped = wrapContract(mockRuntime(captured), ADDRESS_INPUT, abi, {\n signer: fakeSigner,\n origin,\n });\n\n await (\n wrapped as unknown as { store: { tx: (b: string) => Promise<unknown> } }\n ).store.tx(\"0xdeadbeef\");\n\n const calldata = captured.dryRun?.[5] as Uint8Array;\n expect(calldata).toBeInstanceOf(Uint8Array);\n // Selector + length-32-word + offset-32-word + padded 4-byte payload (32-byte word).\n expect(calldata.byteLength).toBe(4 + 32 * 3);\n // 0xdeadbeef sits at the start of the third 32-byte word.\n const payloadStart = 4 + 32 * 2;\n expect(Array.from(calldata.slice(payloadStart, payloadStart + 4))).toEqual([\n 0xde, 0xad, 0xbe, 0xef,\n ]);\n });\n\n test(\"query() decodes a `bytesN` return value back to the original hex string\", async () => {\n // Solidity: function getHash() returns (bytes32). The dry-run\n // result's `data` is a raw `Uint8Array` under PAPI 2.x — wrap\n // must hand it to viem's decoder unwrapped.\n const abi: AbiEntry[] = [\n {\n type: \"function\",\n name: \"getHash\",\n inputs: [],\n outputs: [{ name: \"\", type: \"bytes32\" }],\n stateMutability: \"view\",\n },\n ];\n\n // 32-byte word filled with 0x22 — what the chain returns for a\n // hypothetical `bytes32` reading.\n const responseBytes = new Uint8Array(32).fill(0x22);\n const runtime: ContractRuntime = {\n api: {} as unknown as ContractRuntime[\"api\"],\n dryRunCall: async () => ({\n weight_consumed: { ref_time: 0n, proof_size: 0n },\n weight_required: { ref_time: 0n, proof_size: 0n },\n storage_deposit: { type: \"Refund\", value: 0n },\n max_storage_deposit: { type: \"Refund\", value: 0n },\n gas_consumed: 0n,\n result: { success: true, value: { flags: 0, data: responseBytes } },\n }),\n };\n\n const wrapped = wrapContract(runtime, ADDRESS_INPUT, abi, { origin });\n const result = await (\n wrapped as unknown as {\n getHash: { query: () => Promise<{ success: boolean; value: unknown }> };\n }\n ).getHash.query();\n\n expect(result.success).toBe(true);\n // viem decodes `bytes32` as a `0x…` hex string.\n expect(result.value).toBe(\n \"0x2222222222222222222222222222222222222222222222222222222222222222\",\n );\n });\n });\n\n describe(\"wrapContract — tx dry-run failure\", () => {\n const abi: AbiEntry[] = [\n {\n type: \"function\",\n name: \"increment\",\n inputs: [],\n outputs: [],\n stateMutability: \"nonpayable\",\n },\n ];\n const ADDRESS = \"0x0102030405060708090a0b0c0d0e0f1011121314\";\n const fakeSigner = {\n publicKey: new Uint8Array(32),\n } as unknown as PolkadotSigner;\n\n test(\"throws ContractDryRunFailedError when ReviveApi.call reports failure\", async () => {\n const dispatchError = { type: \"Module\", value: { type: \"ContractReverted\" } };\n const failingDryRun: ContractRuntime[\"dryRunCall\"] = async () => ({\n weight_consumed: { ref_time: 0n, proof_size: 0n },\n weight_required: { ref_time: 0n, proof_size: 0n },\n storage_deposit: { type: \"Refund\", value: 0n },\n max_storage_deposit: { type: \"Refund\", value: 0n },\n gas_consumed: 0n,\n result: { success: false, value: dispatchError },\n });\n const runtime: ContractRuntime = {\n api: {\n apis: {\n ReviveApi: {\n call: () => {\n throw new Error(\n \"typed ReviveApi.call must NOT be invoked — runtime.dryRunCall owns the dry-run path\",\n );\n },\n },\n },\n tx: {\n Revive: {\n call: () => {\n throw new Error(\n \"Revive.call must NOT be invoked on dry-run failure\",\n );\n },\n },\n },\n } as unknown as ContractRuntime[\"api\"],\n dryRunCall: failingDryRun,\n };\n\n const wrapped = wrapContract(runtime, ADDRESS, abi, {\n signer: fakeSigner,\n origin: \"5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY\" as SS58String,\n });\n\n await expect(\n (\n wrapped as unknown as { increment: { tx: () => Promise<unknown> } }\n ).increment.tx(),\n ).rejects.toMatchObject({\n name: \"ContractDryRunFailedError\",\n methodName: \"increment\",\n dispatchError,\n });\n });\n\n test(\"skips dry-run entirely when both gasLimit and storageDepositLimit overrides are passed\", async () => {\n // When the caller supplies both weight and storage-deposit\n // overrides, `.tx()` should go straight to the extrinsic builder\n // — no RPC round-trip, no revert pre-check. We assert this by\n // wiring both `dryRunCall` and `apis.ReviveApi.call` to throw if\n // invoked, and checking the tx still lands.\n let txArgs: { weight_limit: unknown; storage_deposit_limit: unknown } | null = null;\n const runtime: ContractRuntime = {\n api: {\n apis: {\n ReviveApi: {\n call: () => {\n throw new Error(\n \"ReviveApi.call must NOT be invoked when both overrides are passed\",\n );\n },\n },\n },\n tx: {\n Revive: {\n call: (args: {\n weight_limit: unknown;\n storage_deposit_limit: unknown;\n }) => {\n txArgs = {\n weight_limit: args.weight_limit,\n storage_deposit_limit: args.storage_deposit_limit,\n };\n return {\n signSubmitAndWatch: () => ({\n subscribe: (handlers: {\n next: (event: unknown) => void;\n }) => {\n queueMicrotask(() => {\n handlers.next({\n type: \"txBestBlocksState\",\n txHash: \"0xdeadbeef\",\n found: true,\n ok: true,\n events: [],\n block: {\n hash: \"0xblock\",\n number: 1,\n index: 0,\n },\n });\n });\n return { unsubscribe: () => {} };\n },\n }),\n };\n },\n },\n },\n } as unknown as ContractRuntime[\"api\"],\n dryRunCall: () => {\n throw new Error(\n \"dryRunCall must NOT be invoked when both overrides are passed\",\n );\n },\n };\n\n const wrapped = wrapContract(runtime, ADDRESS, abi, {\n signer: fakeSigner,\n origin: \"5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY\" as SS58String,\n });\n\n const overrideWeight = { ref_time: 1234n, proof_size: 56n };\n const overrideDeposit = 7890n;\n await (\n wrapped as unknown as {\n increment: { tx: (opts: unknown) => Promise<unknown> };\n }\n ).increment.tx({\n gasLimit: overrideWeight,\n storageDepositLimit: overrideDeposit,\n });\n\n expect(txArgs).toEqual({\n weight_limit: overrideWeight,\n storage_deposit_limit: overrideDeposit,\n });\n });\n\n test(\"missing storageDepositLimit override still triggers the dry-run\", async () => {\n // Half-overrides don't bypass: if the caller passes `gasLimit`\n // but not `storageDepositLimit`, the SDK must still dry-run to\n // size the deposit AND to fail fast on revert. The previous\n // `!weightLimit` check was correct here; the tightening to\n // `=== undefined` keeps this branch intact for any future\n // refactor that touches the guard.\n let dryRunInvoked = false;\n const runtime: ContractRuntime = {\n api: {\n tx: {\n Revive: {\n call: () => {\n throw new Error(\"Revive.call must not run — dry-run failed\");\n },\n },\n },\n } as unknown as ContractRuntime[\"api\"],\n dryRunCall: async () => {\n dryRunInvoked = true;\n return {\n weight_consumed: { ref_time: 0n, proof_size: 0n },\n weight_required: { ref_time: 0n, proof_size: 0n },\n storage_deposit: { type: \"Refund\", value: 0n },\n max_storage_deposit: { type: \"Refund\", value: 0n },\n gas_consumed: 0n,\n result: { success: false, value: { type: \"ContractReverted\" } },\n };\n },\n };\n\n const wrapped = wrapContract(runtime, ADDRESS, abi, {\n signer: fakeSigner,\n origin: \"5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY\" as SS58String,\n });\n\n await expect(\n (\n wrapped as unknown as {\n increment: { tx: (opts: unknown) => Promise<unknown> };\n }\n ).increment.tx({ gasLimit: { ref_time: 1n, proof_size: 1n } }),\n ).rejects.toMatchObject({ name: \"ContractDryRunFailedError\" });\n expect(dryRunInvoked).toBe(true);\n });\n });\n\n describe(\"wrapContract — prepare (batch composition)\", () => {\n // `.prepare()` is the revive-runtime port of the polkadot-apps\n // batching helper. These tests pin the contract the rest of the\n // SDK relies on:\n //\n // - returns a `SubmittableTransaction` that doubles as a\n // `BatchableCall` (has `.decodedCall` / forwards through\n // `batchSubmitAndWatch`'s `resolveDecodedCall`),\n // - never invokes `submitAndWatch`,\n // - sizes weight + storage via the dry-run unless the caller\n // supplies both overrides,\n // - bubbles a dry-run failure as `ContractDryRunFailedError`\n // before the extrinsic is built,\n // - requires no signer (the batch's signer dispatches).\n const abi: AbiEntry[] = [\n {\n type: \"function\",\n name: \"increment\",\n inputs: [],\n outputs: [],\n stateMutability: \"nonpayable\",\n },\n {\n type: \"function\",\n name: \"add\",\n inputs: [{ name: \"n\", type: \"uint32\" }],\n outputs: [],\n stateMutability: \"nonpayable\",\n },\n ];\n const ADDRESS = \"0x0102030405060708090a0b0c0d0e0f1011121314\";\n\n test(\"builds a Revive.call submittable without signing or dry-running when both overrides given\", async () => {\n let txArgs: {\n dest: unknown;\n value: unknown;\n weight_limit: unknown;\n storage_deposit_limit: unknown;\n data: unknown;\n } | null = null;\n const captured: { dryRun: boolean } = { dryRun: false };\n const sentinelDecodedCall = { pallet: \"Revive\", call: \"call\" };\n const runtime: ContractRuntime = {\n api: {\n tx: {\n Revive: {\n call: (args: typeof txArgs) => {\n txArgs = args;\n // Real PAPI returns a SubmittableTransaction\n // with `.decodedCall`; the field is what\n // `batchSubmitAndWatch` reads to assemble\n // the `Utility.batch_all` payload.\n return {\n decodedCall: sentinelDecodedCall,\n signSubmitAndWatch: () => {\n throw new Error(\n \"prepare must NOT sign or submit — caller batches the result\",\n );\n },\n };\n },\n },\n },\n } as unknown as ContractRuntime[\"api\"],\n dryRunCall: async () => {\n captured.dryRun = true;\n throw new Error(\"prepare must NOT dry-run when both overrides are given\");\n },\n };\n const wrapped = wrapContract(runtime, ADDRESS, abi, {});\n\n const overrideWeight = { ref_time: 99n, proof_size: 11n };\n const result = await (\n wrapped as unknown as {\n add: {\n prepare: (n: number, opts: unknown) => Promise<{ decodedCall: unknown }>;\n };\n }\n ).add.prepare(7, {\n gasLimit: overrideWeight,\n storageDepositLimit: 42n,\n value: 5n,\n });\n\n // SubmittableTransaction is a valid BatchableCall —\n // `batchSubmitAndWatch` reads `.decodedCall` off it.\n expect(result.decodedCall).toBe(sentinelDecodedCall);\n\n // Override values flowed straight through to the extrinsic.\n expect(txArgs).toEqual({\n dest: ADDRESS,\n value: 5n,\n weight_limit: overrideWeight,\n storage_deposit_limit: 42n,\n // viem-encoded `add(uint32)` calldata: 4-byte selector +\n // 32-byte argument. We don't assert the byte-level layout\n // here (covered in the bytesN boundary tests).\n data: expect.any(Uint8Array),\n });\n expect(captured.dryRun).toBe(false);\n });\n\n test(\"dry-runs to fill the missing limits when overrides are partial\", async () => {\n let dryRunCalls = 0;\n const txArgs: { weight_limit: unknown; storage_deposit_limit: unknown }[] = [];\n const runtime: ContractRuntime = {\n api: {\n tx: {\n Revive: {\n call: (args: {\n weight_limit: unknown;\n storage_deposit_limit: unknown;\n }) => {\n txArgs.push(args);\n return { decodedCall: { sentinel: true } };\n },\n },\n },\n } as unknown as ContractRuntime[\"api\"],\n dryRunCall: async () => {\n dryRunCalls += 1;\n return {\n weight_consumed: { ref_time: 0n, proof_size: 0n },\n weight_required: { ref_time: 123n, proof_size: 7n },\n storage_deposit: { type: \"Charge\", value: 99n },\n max_storage_deposit: { type: \"Charge\", value: 99n },\n gas_consumed: 0n,\n result: { success: true, value: { flags: 0, data: new Uint8Array(0) } },\n };\n },\n };\n const wrapped = wrapContract(runtime, ADDRESS, abi, {});\n\n // Only gasLimit override → dry-run still fires to size storage.\n await (\n wrapped as unknown as {\n increment: { prepare: (opts?: unknown) => Promise<unknown> };\n }\n ).increment.prepare({ gasLimit: { ref_time: 10n, proof_size: 1n } });\n\n // Only storageDepositLimit → dry-run still fires to size weight.\n await (\n wrapped as unknown as {\n increment: { prepare: (opts?: unknown) => Promise<unknown> };\n }\n ).increment.prepare({ storageDepositLimit: 0n });\n\n // Nothing → dry-run fills both.\n await (\n wrapped as unknown as {\n increment: { prepare: (opts?: unknown) => Promise<unknown> };\n }\n ).increment.prepare();\n\n expect(dryRunCalls).toBe(3);\n // First call kept the gasLimit override; second kept the\n // storageDepositLimit override; third filled both from the\n // dry-run result.\n expect(txArgs[0]?.weight_limit).toEqual({ ref_time: 10n, proof_size: 1n });\n expect(txArgs[0]?.storage_deposit_limit).toBe(99n);\n expect(txArgs[1]?.weight_limit).toEqual({ ref_time: 123n, proof_size: 7n });\n expect(txArgs[1]?.storage_deposit_limit).toBe(0n);\n expect(txArgs[2]?.weight_limit).toEqual({ ref_time: 123n, proof_size: 7n });\n expect(txArgs[2]?.storage_deposit_limit).toBe(99n);\n });\n\n test(\"does not require a signer; falls back to dev origin for the dry-run\", async () => {\n let capturedOrigin: string | undefined;\n const runtime: ContractRuntime = {\n api: {\n tx: {\n Revive: {\n call: () => ({ decodedCall: { sentinel: true } }),\n },\n },\n } as unknown as ContractRuntime[\"api\"],\n dryRunCall: async (origin) => {\n capturedOrigin = origin;\n return {\n weight_consumed: { ref_time: 0n, proof_size: 0n },\n weight_required: { ref_time: 1n, proof_size: 1n },\n storage_deposit: { type: \"Refund\", value: 0n },\n max_storage_deposit: { type: \"Refund\", value: 0n },\n gas_consumed: 0n,\n result: { success: true, value: { flags: 0, data: new Uint8Array(0) } },\n };\n },\n };\n // No signer / signerManager / defaultOrigin set.\n const wrapped = wrapContract(runtime, ADDRESS, abi, {});\n\n await expect(\n (\n wrapped as unknown as {\n increment: { prepare: () => Promise<unknown> };\n }\n ).increment.prepare(),\n ).resolves.toMatchObject({ decodedCall: { sentinel: true } });\n\n // Origin must have been resolved without throwing — falls\n // back to the dev address (Alice) for dry-run gas estimation.\n expect(capturedOrigin).toBe(QUERY_FALLBACK_ORIGIN);\n });\n\n test(\"throws ContractDryRunFailedError before constructing the extrinsic on revert\", async () => {\n const dispatchError = { type: \"Module\", value: { type: \"ContractReverted\" } };\n const runtime: ContractRuntime = {\n api: {\n tx: {\n Revive: {\n call: () => {\n throw new Error(\n \"Revive.call must NOT be invoked on a failing dry-run\",\n );\n },\n },\n },\n } as unknown as ContractRuntime[\"api\"],\n dryRunCall: async () => ({\n weight_consumed: { ref_time: 0n, proof_size: 0n },\n weight_required: { ref_time: 0n, proof_size: 0n },\n storage_deposit: { type: \"Refund\", value: 0n },\n max_storage_deposit: { type: \"Refund\", value: 0n },\n gas_consumed: 0n,\n result: { success: false, value: dispatchError },\n }),\n };\n const wrapped = wrapContract(runtime, ADDRESS, abi, {});\n\n await expect(\n (\n wrapped as unknown as { increment: { prepare: () => Promise<unknown> } }\n ).increment.prepare(),\n ).rejects.toMatchObject({\n name: \"ContractDryRunFailedError\",\n methodName: \"increment\",\n dispatchError,\n });\n });\n\n test(\"prepared calls flow through batchSubmitAndWatch end-to-end\", async () => {\n // Asserts the integration contract: two prepared calls\n // resolve into a `Utility.batch_all({ calls: [...] })`\n // payload of their `.decodedCall` values.\n const { batchSubmitAndWatch } = await import(\"@parity/product-sdk-tx\");\n const decodedCalls = [\n { pallet: \"Revive\", method: \"call\", value: \"one\" },\n { pallet: \"Revive\", method: \"call\", value: \"two\" },\n ];\n let nextCall = 0;\n const runtime: ContractRuntime = {\n api: {\n tx: {\n Revive: {\n call: () => ({ decodedCall: decodedCalls[nextCall++] }),\n },\n },\n } as unknown as ContractRuntime[\"api\"],\n dryRunCall: async () => ({\n weight_consumed: { ref_time: 0n, proof_size: 0n },\n weight_required: { ref_time: 1n, proof_size: 1n },\n storage_deposit: { type: \"Refund\", value: 0n },\n max_storage_deposit: { type: \"Refund\", value: 0n },\n gas_consumed: 0n,\n result: { success: true, value: { flags: 0, data: new Uint8Array(0) } },\n }),\n };\n const wrapped = wrapContract(runtime, ADDRESS, abi, {});\n\n const a = await (\n wrapped as unknown as { increment: { prepare: () => Promise<BatchableCall> } }\n ).increment.prepare();\n const b = await (\n wrapped as unknown as {\n add: { prepare: (n: number) => Promise<BatchableCall> };\n }\n ).add.prepare(1);\n\n let batchCalls: unknown[] | null = null;\n const fakeApi = {\n tx: {\n Utility: {\n batch_all: (args: { calls: unknown[] }) => {\n batchCalls = args.calls;\n return {\n signSubmitAndWatch: () => ({\n subscribe: (h: {\n next: (event: unknown) => void;\n }) => {\n queueMicrotask(() => {\n h.next({\n type: \"txBestBlocksState\",\n txHash: \"0xb\",\n found: true,\n ok: true,\n events: [],\n block: {\n hash: \"0xblk\",\n number: 1,\n index: 0,\n },\n });\n });\n return { unsubscribe: () => {} };\n },\n }),\n };\n },\n },\n },\n } as unknown as Parameters<typeof batchSubmitAndWatch>[1];\n\n const result = await batchSubmitAndWatch([a, b], fakeApi, {\n publicKey: new Uint8Array(32),\n } as unknown as Parameters<typeof batchSubmitAndWatch>[2]);\n\n expect(result.ok).toBe(true);\n expect(batchCalls).toEqual(decodedCalls);\n });\n });\n}\n","import type { HexString, PolkadotClient, PolkadotSigner, SS58String } from \"polkadot-api\";\nimport type { SubmittableTransaction, Weight, TxResult } from \"@parity/product-sdk-tx\";\nimport { ensureAccountMapped } from \"@parity/product-sdk-tx\";\nimport { ss58ToH160 } from \"@parity/product-sdk-address\";\n\n/**\n * Result of a `Revive.call` extrinsic — present on the typed API as\n * `api.tx.Revive.call(args)`. Returned object is a PAPI submittable that\n * `submitAndWatch` consumes natively.\n *\n * `dest` is an H160 hex string and `data` is a raw `Uint8Array`: this matches\n * what `polkadot-api` ≥2.0 codecs accept and produce. The class-based\n * `Binary` / `FixedSizeBinary` wrappers from `@polkadot-api/substrate-bindings`\n * 0.12 are *not* accepted by PAPI 2.x's compatibility check.\n */\nexport type ReviveCallTx = (args: {\n dest: HexString;\n value: bigint;\n weight_limit: Weight;\n storage_deposit_limit: bigint;\n data: Uint8Array;\n}) => SubmittableTransaction;\n\n/**\n * Dry-run result returned by `ReviveApi.call`. Mirrors the shape exposed by\n * descriptors (`paseo-asset-hub`, `polkadot-asset-hub`, `kusama-asset-hub`).\n *\n * `data` is a raw `Uint8Array` because PAPI ≥2.0 dropped the `Binary` class\n * wrapper for `Vec<u8>` codecs.\n */\nexport interface ReviveDryRunResult {\n weight_consumed: Weight;\n weight_required: Weight;\n storage_deposit: { type: \"Refund\" | \"Charge\"; value: bigint };\n max_storage_deposit: { type: \"Refund\" | \"Charge\"; value: bigint };\n gas_consumed: bigint;\n /**\n * `success: true` carries `{ flags, data }`; `success: false` carries the\n * dispatch error as the chain encoded it.\n */\n result:\n | { success: true; value: { flags: number; data: Uint8Array } }\n | { success: false; value: unknown };\n}\n\n/** Structural shape consumed by `ContractManager` / `createContract`. */\nexport interface ReviveTypedApi {\n tx: {\n Revive: {\n call: ReviveCallTx;\n map_account(): SubmittableTransaction;\n };\n };\n query: {\n Revive: {\n OriginalAccount: {\n getValue(address: HexString): Promise<SS58String | undefined>;\n };\n };\n };\n apis: {\n ReviveApi: {\n call(\n origin: SS58String,\n dest: HexString,\n value: bigint,\n gas_limit: Weight | undefined,\n storage_deposit_limit: bigint | undefined,\n input_data: Uint8Array,\n ): Promise<ReviveDryRunResult>;\n };\n };\n}\n\n/**\n * Signature of a `ReviveApi.call` dry-run, used by the wrapped contract layer\n * to estimate weight + storage deposit and surface revert / OOG /\n * `AccountNotMapped` failures before a tx is signed.\n *\n * Identical to `ReviveTypedApi.apis.ReviveApi.call`, but extracted so the\n * runtime can route this single hot call through PAPI's *unsafe* API\n * (skipping compatibility-token checks) on production runtimes whose\n * descriptors lag a chain upgrade — every other surface still uses the\n * compat-checked typed API.\n */\nexport type ReviveDryRunCall = (\n origin: SS58String,\n dest: HexString,\n value: bigint,\n gas_limit: Weight | undefined,\n storage_deposit_limit: bigint | undefined,\n input_data: Uint8Array,\n) => Promise<ReviveDryRunResult>;\n\n/**\n * Runtime handle that drives queries and transactions against a\n * pallet-revive-capable chain.\n *\n * @example\n * ```ts\n * import { createChainClient } from \"@parity/product-sdk-chain-client\";\n * import { paseo_asset_hub } from \"@parity/product-sdk-descriptors/paseo-asset-hub\";\n * import { createContractRuntimeFromClient } from \"@parity/product-sdk-contracts\";\n *\n * const client = await createChainClient({\n * chains: { assetHub: paseo_asset_hub },\n * rpcs: { assetHub: [\"wss://paseo-asset-hub-next-rpc.polkadot.io\"] },\n * });\n * const runtime = createContractRuntimeFromClient(client.raw.assetHub, paseo_asset_hub);\n * ```\n */\nexport interface ContractRuntime {\n readonly api: ReviveTypedApi;\n /**\n * Dry-run entry point. Production factories route this through the\n * *unsafe* API to avoid compatibility-token failures when the descriptor\n * trails a runtime upgrade. The {@link createContractRuntime} test factory\n * delegates to `api.apis.ReviveApi.call`.\n */\n readonly dryRunCall: ReviveDryRunCall;\n}\n\n/**\n * Wrap a typed PAPI API as a `ContractRuntime`. Intended for tests and\n * advanced setups where the caller already holds a typed API. Routes the\n * dry-run through the typed (compatibility-token-checked) `ReviveApi.call`\n * — fine for mocks but susceptible to `Incompatible runtime entry` errors\n * on a live chain whose descriptor lags. Prefer\n * {@link createContractRuntimeFromClient} for production use.\n */\nexport function createContractRuntime(api: ReviveTypedApi): ContractRuntime {\n return {\n api,\n dryRunCall: (origin, dest, value, gas, deposit, data) =>\n api.apis.ReviveApi.call(origin, dest, value, gas, deposit, data),\n };\n}\n\n/**\n * Build a `ContractRuntime` from a raw `PolkadotClient` plus its descriptor.\n *\n * The typed API powers `tx.Revive.call`, `tx.Revive.map_account`, and\n * `query.Revive.OriginalAccount` (extrinsics + storage are tolerant of\n * descriptor drift). The runtime-API dry-run, which is *not* tolerant of\n * descriptor drift on PAPI's compat-token path, is routed through\n * `client.getUnsafeApi()` — bypassing the compat check while preserving\n * argument and return shapes.\n *\n * Use this on every production code path that calls a contract's `.tx()` or\n * `.query()` against a live chain.\n *\n * @example\n * ```ts\n * import { paseo_asset_hub } from \"@parity/product-sdk-descriptors/paseo-asset-hub\";\n * import { createContractRuntimeFromClient } from \"@parity/product-sdk-contracts\";\n *\n * const runtime = createContractRuntimeFromClient(rawClient, paseo_asset_hub);\n * ```\n */\nexport function createContractRuntimeFromClient<TDescriptor>(\n client: PolkadotClient,\n descriptor: TDescriptor,\n): ContractRuntime {\n const typed = client.getTypedApi(\n descriptor as Parameters<PolkadotClient[\"getTypedApi\"]>[0],\n ) as unknown as ReviveTypedApi;\n const unsafe = client.getUnsafeApi() as unknown as {\n apis: { ReviveApi: { call: ReviveDryRunCall } };\n };\n return {\n api: typed,\n dryRunCall: (origin, dest, value, gas, deposit, data) =>\n unsafe.apis.ReviveApi.call(origin, dest, value, gas, deposit, data),\n };\n}\n\n/**\n * Ensure the SS58 account is mapped to its derived H160 on `pallet-revive`.\n *\n * `pallet-revive` requires every signing account to have a registered\n * `OriginalAccount` mapping before the runtime accepts its `Revive.call`\n * extrinsics. The mapping is one-time and cheap. This helper:\n *\n * 1. Reads `Revive.OriginalAccount` for the H160 derived from `address`.\n * 2. Returns `null` if already mapped (idempotent fast-path).\n * 3. Otherwise submits `Revive.map_account()` and waits for inclusion.\n *\n * Call this once per signing account at app startup — after that, every\n * subsequent `contract.<method>.tx({ signer })` against the same chain will\n * succeed without further mapping work.\n *\n * @param runtime - The contract runtime (typically `createContractRuntime(...)`).\n * @param address - The SS58 address of the account to map.\n * @param signer - A signer matching `address`.\n * @param options - Optional timeout / status callback (forwarded to the underlying tx).\n * @returns The `TxResult` from the mapping extrinsic, or `null` if already mapped.\n *\n * @example\n * ```ts\n * import { createContractRuntime, ensureContractAccountMapped } from \"@parity/product-sdk-contracts\";\n *\n * const runtime = createContractRuntime(client.getTypedApi(paseo_asset_hub));\n * await ensureContractAccountMapped(runtime, signerManager.getState().selectedAccount!.address, signer);\n * // now safe to call contract.<method>.tx({ signer })\n * ```\n */\nexport async function ensureContractAccountMapped(\n runtime: ContractRuntime,\n address: SS58String,\n signer: PolkadotSigner,\n options?: { timeoutMs?: number; onStatus?: (s: string) => void },\n): Promise<TxResult | null> {\n const checker = {\n addressIsMapped: async (addr: string): Promise<boolean> => {\n const h160 = ss58ToH160(addr) as HexString;\n return (await runtime.api.query.Revive.OriginalAccount.getValue(h160)) !== undefined;\n },\n };\n return ensureAccountMapped(address, signer, checker, runtime.api, options);\n}\n\nif (import.meta.vitest) {\n const { test, expect, describe, vi } = import.meta.vitest;\n\n describe(\"ensureContractAccountMapped\", () => {\n // Pin the wiring: storage hit ⇒ short-circuit to null without\n // submitting; H160 (not SS58) is what reaches the storage query.\n const aliceSs58 = \"5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY\" as SS58String;\n const fakeSigner = { publicKey: new Uint8Array(32) } as unknown as PolkadotSigner;\n\n function makeRuntime(opts: {\n mapped: boolean;\n mapAccount?: () => SubmittableTransaction;\n }): {\n runtime: ContractRuntime;\n getValue: ReturnType<typeof vi.fn>;\n mapAccount: ReturnType<typeof vi.fn>;\n } {\n const getValue = vi.fn(async () =>\n opts.mapped ? (\"5mappedSs58\" as SS58String) : undefined,\n );\n const mapAccount = vi.fn(() => {\n if (opts.mapAccount) return opts.mapAccount();\n throw new Error(\"map_account must NOT be invoked when address is already mapped\");\n });\n const runtime: ContractRuntime = {\n api: {\n tx: {\n Revive: {\n call: () => {\n throw new Error(\"Revive.call is unrelated to mapping\");\n },\n map_account: mapAccount,\n },\n },\n query: {\n Revive: {\n OriginalAccount: { getValue },\n },\n },\n apis: {\n ReviveApi: {\n call: () => {\n throw new Error(\"ReviveApi.call is unrelated to mapping\");\n },\n },\n },\n } as unknown as ReviveTypedApi,\n dryRunCall: () => {\n throw new Error(\"dryRunCall is unrelated to mapping\");\n },\n };\n return { runtime, getValue, mapAccount };\n }\n\n test(\"returns null without submitting when storage already has the mapping\", async () => {\n const { runtime, getValue, mapAccount } = makeRuntime({ mapped: true });\n const result = await ensureContractAccountMapped(runtime, aliceSs58, fakeSigner);\n\n expect(result).toBeNull();\n // The H160 derivation hands a `0x…` hex string to the storage\n // query — not the SS58 address. If the wiring ever forwards the\n // SS58 by accident, this assertion catches it.\n expect(getValue).toHaveBeenCalledTimes(1);\n const passedAddress = getValue.mock.calls[0][0] as string;\n expect(passedAddress.startsWith(\"0x\")).toBe(true);\n expect(passedAddress.length).toBe(2 + 40);\n expect(mapAccount).not.toHaveBeenCalled();\n });\n });\n}\n","import type { HexString, PolkadotClient } from \"polkadot-api\";\nimport { wrapContract } from \"./wrap.js\";\nimport { ContractNotFoundError } from \"./errors.js\";\nimport type { ContractRuntime } from \"./runtime.js\";\nimport { createContractRuntimeFromClient } from \"./runtime.js\";\nimport type {\n AbiEntry,\n CdmJson,\n CdmJsonContract,\n Contract,\n ContractDef,\n ContractDefaults,\n ContractManagerOptions,\n ContractOptions,\n Contracts,\n} from \"./types.js\";\n\n/**\n * Manages typed contract interactions backed by a `cdm.json` manifest.\n *\n * Pass a `signerManager` (e.g. a `SignerManager` from `@parity/product-sdk-signer`)\n * so the currently logged-in account is used automatically — no manual\n * signer/origin wiring needed.\n *\n * @example\n * ```ts\n * import { createChainClient } from \"@parity/product-sdk-chain-client\";\n * import { paseo_asset_hub } from \"@parity/product-sdk-descriptors/paseo-asset-hub\";\n * import { ContractManager, createContractRuntime } from \"@parity/product-sdk-contracts\";\n * import cdmJson from \"./cdm.json\";\n *\n * const client = await createChainClient({\n * chains: { assetHub: paseo_asset_hub },\n * rpcs: { assetHub: [\"wss://paseo-asset-hub-next-rpc.polkadot.io\"] },\n * });\n * const runtime = createContractRuntime(client.assetHub);\n * const manager = new ContractManager(cdmJson, runtime, {\n * signerManager,\n * });\n *\n * const counter = manager.getContract(\"@example/counter\");\n * const { value } = await counter.getCount.query();\n * await counter.increment.tx();\n * ```\n */\nexport class ContractManager {\n private cdmJson: CdmJson;\n private targetHash: string;\n private runtime: ContractRuntime;\n private defaults: ContractDefaults;\n\n constructor(cdmJson: CdmJson, runtime: ContractRuntime, options?: ContractManagerOptions) {\n this.cdmJson = cdmJson;\n this.runtime = runtime;\n\n if (options?.targetHash) {\n this.targetHash = options.targetHash;\n } else {\n const targets = Object.keys(cdmJson.targets);\n if (targets.length === 0) throw new Error(\"No targets found in cdm.json\");\n this.targetHash = targets[0];\n }\n\n this.defaults = {\n signerManager: options?.signerManager,\n origin: options?.defaultOrigin,\n signer: options?.defaultSigner,\n };\n }\n\n /** Update the default origin, signer, or signerManager used by all contract handles. */\n setDefaults(defaults: ContractDefaults): void {\n if (defaults.signerManager !== undefined)\n this.defaults.signerManager = defaults.signerManager;\n if (defaults.origin !== undefined) this.defaults.origin = defaults.origin;\n if (defaults.signer !== undefined) this.defaults.signer = defaults.signer;\n }\n\n /**\n * Create a `ContractManager` from a raw `PolkadotClient`.\n *\n * Convenience factory: builds a `ContractRuntime` internally from the\n * client's typed API. Requires that the chain's typed API exposes the\n * `Revive` pallet and `ReviveApi` runtime API (Asset Hub Paseo /\n * Polkadot / Kusama).\n *\n * @param cdmJson - The CDM manifest.\n * @param client - A `PolkadotClient` for the chain where contracts are deployed.\n * @param descriptor - The chain descriptor used to derive the typed API.\n * @param options - Optional configuration (signerManager, defaults).\n */\n static fromClient<TDescriptor>(\n cdmJson: CdmJson,\n client: PolkadotClient,\n descriptor: TDescriptor,\n options?: ContractManagerOptions,\n ): ContractManager {\n return new ContractManager(\n cdmJson,\n createContractRuntimeFromClient(client, descriptor),\n options,\n );\n }\n\n private getContractData(library: string): CdmJsonContract {\n const contractsForTarget = this.cdmJson.contracts?.[this.targetHash];\n if (!contractsForTarget || !(library in contractsForTarget)) {\n throw new ContractNotFoundError(library, this.targetHash);\n }\n return contractsForTarget[library];\n }\n\n /**\n * Get a typed contract handle.\n *\n * Each method on the returned object has `.query()` for read-only calls\n * and `.tx()` for signed transactions. When codegen augments\n * {@link Contracts}, passing a known library name returns a fully-typed\n * handle. Without codegen the generic overload still works — methods are\n * accessible but untyped.\n */\n getContract<K extends string & keyof Contracts>(library: K): Contract<Contracts[K]>;\n getContract(library: string): Contract<ContractDef>;\n getContract(library: string): Contract<ContractDef> {\n const data = this.getContractData(library);\n return wrapContract(this.runtime, data.address, data.abi, this.defaults);\n }\n\n /** Get the on-chain address of an installed contract. */\n getAddress(library: string): HexString {\n return this.getContractData(library).address;\n }\n\n /**\n * Get the underlying {@link ContractRuntime} backing this manager.\n *\n * Useful when a consumer needs to call helpers that take a runtime\n * directly — most commonly {@link ensureContractAccountMapped} at app\n * boot. Avoids the alternative of building a second runtime against the\n * same client and descriptor.\n */\n getRuntime(): ContractRuntime {\n return this.runtime;\n }\n}\n\n/**\n * Create a contract handle from a raw H160 address and ABI — no `cdm.json` needed.\n *\n * @example\n * ```ts\n * import { createContractRuntime, createContract } from \"@parity/product-sdk-contracts\";\n *\n * const runtime = createContractRuntime(client.assetHub);\n * const counter = createContract(runtime, \"0xC472...\", abi, { signerManager });\n * await counter.getCount.query();\n * await counter.increment.tx();\n * ```\n */\nexport function createContract(\n runtime: ContractRuntime,\n address: HexString,\n abi: AbiEntry[],\n options?: ContractOptions,\n): Contract<ContractDef> {\n const defaults: ContractDefaults = {\n signerManager: options?.signerManager,\n origin: options?.defaultOrigin,\n signer: options?.defaultSigner,\n };\n return wrapContract(runtime, address, abi, defaults);\n}\n\n/**\n * Create a contract handle from a raw `PolkadotClient`, descriptor, address, and ABI.\n *\n * Convenience wrapper that builds the `ContractRuntime` from the client's\n * typed API. The chain must expose `Revive` + `ReviveApi`.\n *\n * @example\n * ```ts\n * const counter = createContractFromClient(client, paseo_asset_hub, \"0xC472...\", abi);\n * const { value } = await counter.getCount.query();\n * ```\n */\nexport function createContractFromClient<TDescriptor>(\n client: PolkadotClient,\n descriptor: TDescriptor,\n address: HexString,\n abi: AbiEntry[],\n options?: ContractOptions,\n): Contract<ContractDef> {\n return createContract(\n createContractRuntimeFromClient(client, descriptor),\n address,\n abi,\n options,\n );\n}\n\nif (import.meta.vitest) {\n const { describe, test, expect } = import.meta.vitest;\n\n /**\n * Real-world cdm.json structure as it appears in\n * `paritytech/playground-cli/cdm.json`. Used here as the reproducer\n * for the cdm-resolution flow: if `getContract()` works against\n * this manifest shape, it works against any consumer's manifest.\n *\n * Notable shape differences from the generated examples:\n * - `metadataCid` is absent (made optional in 0.2.1)\n * - target hash is a single 16-char hex string\n * - `dependencies` uses `\"latest\"` for version\n * - contract addresses are 20-byte EVM-shaped (Polkadot Asset Hub\n * uses Solidity-compatible addresses for Revive contracts)\n */\n const playgroundCdm: CdmJson = {\n targets: {\n acc2c3b5e912b762: {\n \"asset-hub\": \"wss://paseo-asset-hub-next-rpc.polkadot.io\",\n bulletin: \"https://paseo-bulletin-next-ipfs.polkadot.io\",\n },\n },\n dependencies: {\n acc2c3b5e912b762: {\n \"@w3s/playground-registry\": \"latest\",\n },\n },\n contracts: {\n acc2c3b5e912b762: {\n \"@w3s/playground-registry\": {\n version: 6,\n address: \"0x4A37B123b0BA2A894cA5953f472264921d44e298\",\n abi: [\n { type: \"constructor\", inputs: [], stateMutability: \"nonpayable\" },\n {\n type: \"function\",\n name: \"publish\",\n inputs: [\n { name: \"domain\", type: \"string\" },\n { name: \"metadata_uri\", type: \"string\" },\n { name: \"visibility\", type: \"uint8\" },\n ],\n outputs: [],\n stateMutability: \"nonpayable\",\n },\n {\n type: \"function\",\n name: \"unpublish\",\n inputs: [{ name: \"domain\", type: \"string\" }],\n outputs: [],\n stateMutability: \"nonpayable\",\n },\n ],\n },\n },\n },\n };\n\n /**\n * Minimal `ContractRuntime` stub — `ContractManager` only forwards the\n * runtime through to `wrapContract`'s proxy, which doesn't invoke any\n * runtime member at construction time. The fields below stay\n * shape-only; any test that actually wants to call `.query()` / `.tx()`\n * builds its own runtime with real captures.\n */\n function fakeRuntime(): ContractRuntime {\n return {\n api: {\n tx: { Revive: { call: () => null, map_account: () => null } },\n query: { Revive: { OriginalAccount: { getValue: async () => undefined } } },\n apis: { ReviveApi: { call: async () => null } },\n },\n dryRunCall: async () => null,\n } as unknown as ContractRuntime;\n }\n\n describe(\"ContractManager — cdm.json resolution\", () => {\n test(\"constructs from a real-world cdm.json without errors\", () => {\n const manager = new ContractManager(playgroundCdm, fakeRuntime());\n expect(manager.getAddress(\"@w3s/playground-registry\")).toBe(\n \"0x4A37B123b0BA2A894cA5953f472264921d44e298\",\n );\n });\n\n test(\"getContract returns a typed handle for a library in the manifest\", () => {\n const manager = new ContractManager(playgroundCdm, fakeRuntime());\n const registry = manager.getContract(\"@w3s/playground-registry\") as unknown as Record<\n string,\n { query: unknown; tx: unknown }\n >;\n\n expect(typeof registry.publish.query).toBe(\"function\");\n expect(typeof registry.publish.tx).toBe(\"function\");\n expect(typeof registry.unpublish.query).toBe(\"function\");\n });\n\n test(\"getContract throws ContractNotFoundError for an unknown library\", () => {\n const manager = new ContractManager(playgroundCdm, fakeRuntime());\n expect(() => manager.getContract(\"@nonexistent/contract\")).toThrow(\n /not found in cdm\\.json/,\n );\n });\n\n test(\"auto-selects the first target when no targetHash is provided\", () => {\n const manager = new ContractManager(playgroundCdm, fakeRuntime());\n // Single-target manifest — should resolve cleanly without\n // requiring an explicit targetHash.\n expect(() => manager.getContract(\"@w3s/playground-registry\")).not.toThrow();\n });\n\n test(\"getAddress returns the manifest's recorded H160 for a library\", () => {\n // Replaces the prior \"passes the right address to inkSdk\" test —\n // the new runtime doesn't take the address at construction time\n // (wrapContract receives it directly), so we assert the\n // manifest-side projection instead.\n const manager = new ContractManager(playgroundCdm, fakeRuntime());\n expect(manager.getAddress(\"@w3s/playground-registry\")).toBe(\n \"0x4A37B123b0BA2A894cA5953f472264921d44e298\",\n );\n });\n\n test(\"explicit targetHash option selects the right contracts subtree\", () => {\n // Multi-target manifest: we should be able to pin to a specific\n // target hash and have getContract resolve against it.\n const multiTargetCdm: CdmJson = {\n targets: {\n target_a: { \"asset-hub\": \"wss://a\", bulletin: \"https://a\" },\n target_b: { \"asset-hub\": \"wss://b\", bulletin: \"https://b\" },\n },\n dependencies: {\n target_a: { \"@org/foo\": \"1.0\" },\n target_b: { \"@org/foo\": \"2.0\" },\n },\n contracts: {\n target_a: {\n \"@org/foo\": {\n version: 1,\n address: \"0x1111111111111111111111111111111111111111\",\n abi: [],\n },\n },\n target_b: {\n \"@org/foo\": {\n version: 2,\n address: \"0x2222222222222222222222222222222222222222\",\n abi: [],\n },\n },\n },\n };\n\n const aManager = new ContractManager(multiTargetCdm, fakeRuntime(), {\n targetHash: \"target_a\",\n });\n const bManager = new ContractManager(multiTargetCdm, fakeRuntime(), {\n targetHash: \"target_b\",\n });\n\n expect(aManager.getAddress(\"@org/foo\")).toBe(\n \"0x1111111111111111111111111111111111111111\",\n );\n expect(bManager.getAddress(\"@org/foo\")).toBe(\n \"0x2222222222222222222222222222222222222222\",\n );\n });\n\n test(\"constructor throws when cdm.json has no targets\", () => {\n const emptyCdm: CdmJson = { targets: {}, dependencies: {} };\n expect(() => new ContractManager(emptyCdm, fakeRuntime())).toThrow(/No targets found/);\n });\n });\n\n describe(\"ContractManager defaults\", () => {\n test(\"setDefaults updates origin / signer / signerManager mid-flight\", () => {\n const manager = new ContractManager(playgroundCdm, fakeRuntime(), {\n defaultOrigin: \"5OldOrigin\" as HexString,\n });\n // This is a behavioral check via private-ish field — we don't\n // expose `defaults` directly, but `setDefaults` returning\n // without error is the contract.\n expect(() => manager.setDefaults({ origin: \"5NewOrigin\" as HexString })).not.toThrow();\n });\n });\n}\n"]}
package/dist/pvm.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { A as AbiEntry } from './types-DJDMz91q.js';
1
+ import { A as AbiEntry } from './types-BdHp-xWt.js';
2
2
  import 'polkadot-api';
3
3
  import '@parity/product-sdk-tx';
4
4
  import '@parity/product-sdk-signer';
@@ -56,16 +56,33 @@ interface ContractDef {
56
56
  */
57
57
  interface Contracts {
58
58
  }
59
- /** Result from a read-only contract query. */
60
- interface QueryResult<T> {
61
- success: boolean;
59
+ /**
60
+ * Result from a read-only contract query.
61
+ *
62
+ * On success, `value` is the decoded response and `gasRequired` is the
63
+ * `Weight` consumed by `ReviveApi.call`'s dry-run — consumable directly by
64
+ * `Revive.call`'s `weight_limit` parameter.
65
+ *
66
+ * On failure, `value` carries the raw dispatch-error payload the runtime
67
+ * returned (typically a tagged enum like `{ type: "Module", value: ... }`,
68
+ * `{ type: "ContractReverted" }`, or `{ type: "AccountNotMapped" }` — see the
69
+ * `Revive` pallet error variants). Surfacing it lets callers narrow on shape
70
+ * to diagnose silent failures instead of seeing `undefined` and being unable
71
+ * to tell whether the contract reverted, gas estimation failed, or the
72
+ * dry-run never ran. `gasRequired` is still populated when the runtime
73
+ * reported a weight even though the call ultimately failed (e.g. a revert
74
+ * after partial execution); it's optional because some failure modes don't
75
+ * carry one.
76
+ */
77
+ type QueryResult<T> = {
78
+ success: true;
62
79
  value: T;
63
- /**
64
- * Weight required to execute this call as a transaction. Returned by
65
- * `ReviveApi.call` and consumed by `Revive.call`'s `weight_limit`.
66
- */
80
+ gasRequired: Weight;
81
+ } | {
82
+ success: false;
83
+ value: unknown;
67
84
  gasRequired?: Weight;
68
- }
85
+ };
69
86
  /** Options for query calls — passed as the last argument after positional args. */
70
87
  interface QueryOptions {
71
88
  origin?: SS58String;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@parity/product-sdk-contracts",
3
3
  "description": "Typed contract interactions on Polkadot Asset Hub",
4
- "version": "0.4.0",
4
+ "version": "0.5.1",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
7
7
  "types": "./dist/index.d.ts",
@@ -31,10 +31,10 @@
31
31
  "polkadot-api": "^2.1.2",
32
32
  "viem": "^2.46.2",
33
33
  "@parity/product-sdk-address": "0.1.1",
34
- "@parity/product-sdk-keys": "0.2.2",
34
+ "@parity/product-sdk-keys": "0.3.0",
35
35
  "@parity/product-sdk-logger": "0.1.1",
36
- "@parity/product-sdk-signer": "0.2.3",
37
- "@parity/product-sdk-tx": "0.2.2"
36
+ "@parity/product-sdk-signer": "0.3.0",
37
+ "@parity/product-sdk-tx": "0.2.4"
38
38
  },
39
39
  "devDependencies": {
40
40
  "tsup": "^8.4.0",