@parity/product-sdk-contracts 0.7.0 → 0.7.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/README.md +0 -1
- package/dist/codegen.d.ts +1 -1
- package/dist/index.d.ts +2 -3
- package/dist/index.js.map +1 -1
- package/dist/pvm.d.ts +1 -1
- package/dist/{types-BJl1dir8.d.ts → types-pkS1R9Bk.d.ts} +0 -1
- package/package.json +5 -5
package/README.md
CHANGED
package/dist/codegen.d.ts
CHANGED
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { PolkadotClient, HexString } from 'polkadot-api';
|
|
2
|
-
import { C as CdmJson, a as ContractRuntime, b as ContractManagerOptions, c as ContractDefaults, d as ContractRuntimeOptions, L as LiveContractResolutionOptions, e as Contracts, f as Contract, g as ContractDef, A as AbiEntry, h as ContractOptions } from './types-
|
|
3
|
-
export { i as AbiParam, j as CdmJsonContract, k as CdmJsonDependencyVersion, l as ContractDryRunAt, P as PrepareOptions, Q as QueryOptions, m as QueryResult, R as ReviveDryRunCall, n as ReviveDryRunCallOptions, o as ReviveDryRunResult, p as ReviveTypedApi, T as TxOptions, q as createContractRuntime, r as createContractRuntimeFromClient, s as ensureContractAccountMapped } from './types-
|
|
2
|
+
import { C as CdmJson, a as ContractRuntime, b as ContractManagerOptions, c as ContractDefaults, d as ContractRuntimeOptions, L as LiveContractResolutionOptions, e as Contracts, f as Contract, g as ContractDef, A as AbiEntry, h as ContractOptions } from './types-pkS1R9Bk.js';
|
|
3
|
+
export { i as AbiParam, j as CdmJsonContract, k as CdmJsonDependencyVersion, l as ContractDryRunAt, P as PrepareOptions, Q as QueryOptions, m as QueryResult, R as ReviveDryRunCall, n as ReviveDryRunCallOptions, o as ReviveDryRunResult, p as ReviveTypedApi, T as TxOptions, q as createContractRuntime, r as createContractRuntimeFromClient, s as ensureContractAccountMapped } from './types-pkS1R9Bk.js';
|
|
4
4
|
export { BatchableCall, TxResult } from '@parity/product-sdk-tx';
|
|
5
5
|
import '@parity/product-sdk-signer';
|
|
6
6
|
|
|
@@ -29,7 +29,6 @@ declare function withLiveContractAddresses(cdmJson: CdmJson, runtime: ContractRu
|
|
|
29
29
|
*
|
|
30
30
|
* const client = await createChainClient({
|
|
31
31
|
* chains: { assetHub: paseo_asset_hub },
|
|
32
|
-
* rpcs: { assetHub: ["wss://paseo-asset-hub-next-rpc.polkadot.io"] },
|
|
33
32
|
* });
|
|
34
33
|
* const runtime = createContractRuntime(client.assetHub);
|
|
35
34
|
* const manager = new ContractManager(cdmJson, runtime, {
|
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":["// Copyright 2026 Parity Technologies (UK) Ltd.\n// SPDX-License-Identifier: Apache-2.0\nimport type { HexString } from \"polkadot-api\";\n\n/** 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\n constructor(library: string) {\n super(`Contract \"${library}\" not found in cdm.json`);\n this.name = \"ContractNotFoundError\";\n this.library = library;\n }\n}\n\n/** Live CDM registry address resolution failed. */\nexport class ContractLiveAddressResolutionError extends ContractError {\n readonly library: string | undefined;\n readonly detail: unknown;\n\n constructor(\n message: string,\n options?: { library?: string; detail?: unknown; cause?: unknown },\n ) {\n super(message, options?.cause !== undefined ? { cause: options.cause } : undefined);\n this.name = \"ContractLiveAddressResolutionError\";\n this.library = options?.library;\n this.detail = options?.detail;\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\n/** viem-decoded standard or ABI-defined contract error. */\nexport interface DecodedContractRevert {\n errorName: string;\n args: readonly unknown[] | undefined;\n}\n\n/**\n * Tagged-enum value surfaced on `QueryResult.value` when a contract reverts\n * via the REVERT flag. The discriminant is intentionally distinct from\n * `pallet-revive`'s bare `{ type: \"ContractReverted\" }` dispatch-error variant,\n * which is the other path that can populate `QueryResult.value` on failure.\n */\nexport interface ContractRevertInfo {\n type: \"ContractRevertedWithPayload\";\n data: HexString;\n reason?: string;\n decoded?: DecodedContractRevert;\n}\n\n// Top-level bigints stringify unquoted (`42`); bigints inside an object or\n// array stringify as JSON strings (`\"42\"`) because that's the only way a\n// JSON replacer can emit them. Tolerated since this string is only ever\n// read by humans in an error message, not parsed.\nfunction stringifyArg(arg: unknown): string {\n if (typeof arg === \"bigint\") return arg.toString();\n return JSON.stringify(arg, (_, v) => (typeof v === \"bigint\" ? v.toString() : v));\n}\n\n/** A contract call returned with the `REVERT` flag set on a dispatched-OK call. */\nexport class ContractRevertedError extends ContractError {\n readonly methodName: string;\n readonly data: HexString;\n readonly reason?: string;\n readonly decoded?: DecodedContractRevert;\n\n constructor(\n methodName: string,\n data: HexString,\n info?: { reason?: string; decoded?: DecodedContractRevert },\n ) {\n // `reason` already carries the human-readable message for Error and Panic,\n // so only fall back to `errorName(args...)` for ABI-defined custom errors.\n const suffix =\n info?.reason ??\n (info?.decoded\n ? `${info.decoded.errorName}(${(info.decoded.args ?? []).map(stringifyArg).join(\", \")})`\n : data);\n super(\n `Contract reverted in \"${methodName}\": ${suffix}. The transaction was not submitted.`,\n );\n this.name = \"ContractRevertedError\";\n this.methodName = methodName;\n this.data = data;\n this.reason = info?.reason;\n this.decoded = info?.decoded;\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\")).toBeInstanceOf(ContractError);\n expect(new ContractLiveAddressResolutionError(\"test\")).toBeInstanceOf(ContractError);\n expect(new ContractDryRunFailedError(\"foo\", \"x\")).toBeInstanceOf(ContractError);\n expect(new ContractRevertedError(\"foo\", \"0x\" as HexString)).toBeInstanceOf(\n ContractError,\n );\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\", () => {\n const err = new ContractNotFoundError(\"@test/foo\");\n expect(err.library).toBe(\"@test/foo\");\n expect(err.message).toBe('Contract \"@test/foo\" not found in cdm.json');\n });\n });\n\n describe(\"ContractLiveAddressResolutionError\", () => {\n test(\"captures library and detail\", () => {\n const detail = { success: false };\n const err = new ContractLiveAddressResolutionError(\"failed\", {\n library: \"@test/foo\",\n detail,\n });\n expect(err.name).toBe(\"ContractLiveAddressResolutionError\");\n expect(err.library).toBe(\"@test/foo\");\n expect(err.detail).toBe(detail);\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 describe(\"ContractRevertedError\", () => {\n test(\"captures method, raw data, reason, and decoded payload\", () => {\n const data = \"0x556e617574686f72697a6564\" as HexString;\n const err = new ContractRevertedError(\"transfer\", data, {\n reason: \"Unauthorized\",\n decoded: { errorName: \"Error\", args: [\"Unauthorized\"] },\n });\n expect(err.name).toBe(\"ContractRevertedError\");\n expect(err.methodName).toBe(\"transfer\");\n expect(err.data).toBe(data);\n expect(err.reason).toBe(\"Unauthorized\");\n expect(err.decoded?.errorName).toBe(\"Error\");\n // Error(string) prefers the bare reason over `Error(\"...\")` form.\n expect(err.message).toBe(\n 'Contract reverted in \"transfer\": Unauthorized. The transaction was not submitted.',\n );\n });\n\n test(\"falls back to reason then to raw data when decoded is absent\", () => {\n const data = \"0xdeadbeef\" as HexString;\n const withReason = new ContractRevertedError(\"foo\", data, { reason: \"Whoops\" });\n expect(withReason.message).toContain(\"Whoops\");\n\n const noInfo = new ContractRevertedError(\"foo\", data);\n expect(noInfo.message).toContain(\"0xdeadbeef\");\n expect(noInfo.reason).toBeUndefined();\n expect(noInfo.decoded).toBeUndefined();\n });\n\n test(\"stringifies top-level bigint args without throwing\", () => {\n const err = new ContractRevertedError(\"bar\", \"0x\" as HexString, {\n decoded: { errorName: \"InsufficientBalance\", args: [42n, 100n] },\n });\n expect(err.message).toContain(\"InsufficientBalance(42, 100)\");\n });\n\n test(\"stringifies nested bigint args inside a struct without throwing\", () => {\n // viem returns `[{ owed: 42n }]` for struct args; the default\n // JSON.stringify replacer would throw on the nested bigint.\n const err = new ContractRevertedError(\"redeem\", \"0x\" as HexString, {\n decoded: {\n errorName: \"Bad\",\n args: [{ owed: 42n, nested: { deeper: [7n, 9n] } }],\n },\n });\n expect(err.message).toContain('\"owed\":\"42\"');\n expect(err.message).toContain('\"deeper\":[\"7\",\"9\"]');\n });\n\n test(\"uses the Panic reason instead of Panic(<code>) when decodeRevert provided one\", () => {\n // Regression: Panic reason used to be clobbered by the errorName(args) form.\n const err = new ContractRevertedError(\"withdraw\", \"0x\" as HexString, {\n reason: \"Panic: arithmetic overflow\",\n decoded: { errorName: \"Panic\", args: [17n] },\n });\n expect(err.message).toContain(\"Panic: arithmetic overflow\");\n expect(err.message).not.toContain(\"Panic(17)\");\n });\n });\n}\n","// Copyright 2026 Parity Technologies (UK) Ltd.\n// SPDX-License-Identifier: Apache-2.0\nimport type { HexString, PolkadotSigner, SS58String } from \"polkadot-api\";\nimport {\n bytesToHex,\n decodeErrorResult,\n decodeFunctionResult,\n encodeFunctionData,\n type Abi as ViemAbi,\n} from \"viem\";\nimport { submitAndWatch } from \"@parity/product-sdk-tx\";\nimport { createLogger } from \"@parity/product-sdk-logger\";\nimport { ss58Address } from \"@polkadot-labs/hdkd-helpers\";\nimport {\n ContractDryRunFailedError,\n ContractRevertedError,\n ContractSignerMissingError,\n type ContractRevertInfo,\n} 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 * pallet-revive's own account, used as fallback origin for read-only queries\n * when no wallet is connected. The runtime API requires an origin, so we pass\n * this account when there is no connected one.\n *\n * This mirrors `Pallet::<T>::account_id()` in pallet-revive, which is\n * `PalletId(*b\"py/reviv\").into_account_truncating()`. The 32-byte AccountId is\n * the PalletId `TYPE_ID` (`b\"modl\"`) followed by the id (`b\"py/reviv\"`), zero\n * padded, i.e. `\"modlpy/reviv\"` + 20 trailing zero bytes.\n */\nconst REVIVE_PALLET_ACCOUNT = new Uint8Array(32);\nREVIVE_PALLET_ACCOUNT.set(new TextEncoder().encode(\"modlpy/reviv\"));\nconst QUERY_FALLBACK_ORIGIN = ss58Address(REVIVE_PALLET_ACCOUNT) 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 pallet-revive account fallback 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// Bit 0 of `pallet-revive`'s `ReturnFlags`. Other bits are reserved, so we\n// mask explicitly rather than checking `flags !== 0`.\nconst REVERT_FLAG = 1;\n\n// Solidity panic codes that `Panic(uint256)` can carry. Stable across compiler\n// versions; mapping them to a human-readable name beats showing the raw hex.\nconst PANIC_REASONS: Record<string, string> = {\n \"0x01\": \"assertion failed\",\n \"0x11\": \"arithmetic overflow\",\n \"0x12\": \"division by zero\",\n \"0x21\": \"invalid enum value\",\n \"0x22\": \"improperly encoded storage byte array\",\n \"0x31\": \"pop on empty array\",\n \"0x32\": \"array index out of bounds\",\n \"0x41\": \"memory allocation overflow\",\n \"0x51\": \"call to uninitialized internal function\",\n};\n\n/**\n * Adapt viem's `decodeErrorResult` output into our tagged `ContractRevertInfo`.\n * The core decode is viem's; this wrapper layers in a UTF-8 fallback for raw\n * `revert(bytes)` payloads and the panic-code-to-string mapping. viem's own\n * `panicReasons` and `ContractFunctionRevertedError` are unsuitable here:\n * `panicReasons` sits at `viem/constants/solidity` and isn't surfaced by\n * viem's `exports` map, and `ContractFunctionRevertedError` is shaped for\n * viem's call pipeline and would need adapting back into this shape anyway.\n */\nfunction decodeRevert(abi: AbiEntry[], data: Uint8Array): ContractRevertInfo {\n const hex = bytesToHex(data);\n const info: ContractRevertInfo = {\n type: \"ContractRevertedWithPayload\",\n data: hex as HexString,\n };\n if (data.byteLength === 0) return info;\n try {\n // The ABI path wins even if a raw `revert(bytes)` payload happens to\n // start with `0x08c379a0` / `0x4e487b71` - astronomically rare on real\n // chains, but worth flagging for anyone debugging an oddly-decoded revert.\n const decoded = decodeErrorResult({\n abi: abi as unknown as ViemAbi,\n data: hex,\n });\n info.decoded = {\n errorName: decoded.errorName,\n args: decoded.args as readonly unknown[] | undefined,\n };\n if (decoded.errorName === \"Error\" && typeof decoded.args?.[0] === \"string\") {\n info.reason = decoded.args[0];\n } else if (decoded.errorName === \"Panic\" && typeof decoded.args?.[0] === \"bigint\") {\n const code = `0x${decoded.args[0].toString(16).padStart(2, \"0\")}`;\n info.reason = PANIC_REASONS[code] ? `Panic: ${PANIC_REASONS[code]}` : `Panic(${code})`;\n }\n return info;\n } catch {\n try {\n info.reason = new TextDecoder(\"utf-8\", { fatal: true }).decode(data);\n } catch {\n // Opaque bytes; expose only the raw hex.\n }\n return info;\n }\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 const decoded = decodeFunctionResult({\n abi: abi as unknown as ViemAbi,\n functionName: methodName,\n data: bytesToHex(returnData),\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 // Passing both overrides skips the dry-run entirely - including the REVERT\n // pre-check. Callers who supply both are accepting that a reverting tx\n // would still be submitted and gas paid.\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 overrides?.at !== undefined ? { at: overrides.at } : undefined,\n );\n if (!dryRun.result.success) {\n throw new ContractDryRunFailedError(methodName, dryRun.result.value);\n }\n if ((dryRun.result.value.flags & REVERT_FLAG) !== 0) {\n // Fail fast so callers don't pay gas on a call the chain already told us would revert.\n const { data, reason, decoded } = decodeRevert(abi, dryRun.result.value.data);\n log.debug(\"Contract reverted\", { methodName, reason, errorName: decoded?.errorName });\n throw new ContractRevertedError(methodName, data, { reason, decoded });\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 overrides?.at !== undefined ? { at: overrides.at } : undefined,\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 if ((dryRun.result.value.flags & REVERT_FLAG) !== 0) {\n // Surface as a tagged value; decoding revert bytes as a normal return would throw.\n const info = decodeRevert(abi, dryRun.result.value.data);\n log.debug(\"Contract reverted\", {\n methodName,\n reason: info.reason,\n errorName: info.decoded?.errorName,\n });\n return {\n success: false,\n value: info,\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\n // pallet-revive account for the dry-run since no signer is\n // required at prepare time; the batch's signer replaces the\n // 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 — query .at option routing\", () => {\n // The `.query()` per-call `at` override is the user-facing hook for\n // pinning a dry-run to a different block than the runtime default\n // (see issue #95). These tests pin the wiring: the `at` value\n // arrives at `runtime.dryRunCall` as its trailing options arg,\n // exactly when the caller passes it — and is absent otherwise so\n // the runtime default applies.\n const abi: AbiEntry[] = [\n {\n type: \"function\",\n name: \"getCount\",\n inputs: [],\n outputs: [{ name: \"\", type: \"uint32\" }],\n stateMutability: \"view\",\n },\n ];\n const ADDRESS = \"0x0102030405060708090a0b0c0d0e0f1011121314\";\n const origin = \"5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY\" as SS58String;\n\n function makeCapturingRuntime(): {\n runtime: ContractRuntime;\n calls: Array<Parameters<ContractRuntime[\"dryRunCall\"]>>;\n } {\n const calls: Array<Parameters<ContractRuntime[\"dryRunCall\"]>> = [];\n const runtime: ContractRuntime = {\n api: {} as unknown as ContractRuntime[\"api\"],\n dryRunCall: (...args) => {\n calls.push(args);\n return Promise.resolve({\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: new Uint8Array(32) } },\n });\n },\n };\n return { runtime, calls };\n }\n\n test(\"forwards `at: finalized` to dryRunCall when passed per call\", async () => {\n const { runtime, calls } = makeCapturingRuntime();\n const wrapped = wrapContract(runtime, ADDRESS, abi, { origin });\n await (\n wrapped as unknown as {\n getCount: { query: (opts: { at: string }) => Promise<unknown> };\n }\n ).getCount.query({ at: \"finalized\" });\n expect(calls[0]?.[6]).toEqual({ at: \"finalized\" });\n });\n\n test(\"forwards a block hash `at` value to dryRunCall\", async () => {\n const { runtime, calls } = makeCapturingRuntime();\n const wrapped = wrapContract(runtime, ADDRESS, abi, { origin });\n const blockHash = `0x${\"cd\".repeat(32)}` as `0x${string}`;\n await (\n wrapped as unknown as {\n getCount: { query: (opts: { at: string }) => Promise<unknown> };\n }\n ).getCount.query({ at: blockHash });\n expect(calls[0]?.[6]).toEqual({ at: blockHash });\n });\n\n test(\"omits the options argument when no `at` override is passed\", async () => {\n // No per-call override ⇒ the wrap layer must not synthesise an\n // options object; the runtime applies its own default.\n const { runtime, calls } = makeCapturingRuntime();\n const wrapped = wrapContract(runtime, ADDRESS, abi, { origin });\n await (\n wrapped as unknown as { getCount: { query: () => Promise<unknown> } }\n ).getCount.query();\n expect(calls[0]?.[6]).toBeUndefined();\n });\n\n test(\"omits options when only unrelated overrides are passed\", async () => {\n const { runtime, calls } = makeCapturingRuntime();\n const wrapped = wrapContract(runtime, ADDRESS, abi, { origin });\n await (\n wrapped as unknown as {\n getCount: { query: (opts: { value: bigint }) => Promise<unknown> };\n }\n ).getCount.query({ value: 1n });\n expect(calls[0]?.[6]).toBeUndefined();\n });\n });\n\n describe(\"wrapContract — tx/prepare .at option routing\", () => {\n // The `.tx()` and `.prepare()` per-call `at` override pins the\n // sizing dry-run to a specific block. Same wiring as `.query()` —\n // these tests assert the value arrives at `runtime.dryRunCall` as\n // its trailing options arg, and is absent otherwise so the\n // runtime default applies.\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 origin = \"5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY\" as SS58String;\n const fakeSigner = {\n publicKey: new Uint8Array(32),\n } as unknown as PolkadotSigner;\n\n function makeCapturingRuntime(): {\n runtime: ContractRuntime;\n calls: Array<Parameters<ContractRuntime[\"dryRunCall\"]>>;\n } {\n const calls: Array<Parameters<ContractRuntime[\"dryRunCall\"]>> = [];\n const runtime: ContractRuntime = {\n api: {\n tx: {\n Revive: {\n call: () =>\n ({}) as unknown as Awaited<\n ReturnType<ContractRuntime[\"api\"][\"tx\"][\"Revive\"][\"call\"]>\n >,\n },\n },\n } as unknown as ContractRuntime[\"api\"],\n dryRunCall: (...args) => {\n calls.push(args);\n return Promise.resolve({\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: new Uint8Array(0) } },\n });\n },\n };\n return { runtime, calls };\n }\n\n test(\".tx() forwards `at: finalized` to dryRunCall when passed per call\", async () => {\n const { runtime, calls } = makeCapturingRuntime();\n const wrapped = wrapContract(runtime, ADDRESS, abi, {\n origin,\n signer: fakeSigner,\n });\n await (\n wrapped as unknown as {\n increment: { tx: (opts: { at: string }) => Promise<unknown> };\n }\n ).increment\n .tx({ at: \"finalized\" })\n .catch(() => {\n // submit downstream of dryRunCall is irrelevant — only\n // care that the dry-run captured the `at` value.\n });\n expect(calls[0]?.[6]).toEqual({ at: \"finalized\" });\n });\n\n test(\".tx() forwards a block hash `at` value to dryRunCall\", async () => {\n const { runtime, calls } = makeCapturingRuntime();\n const wrapped = wrapContract(runtime, ADDRESS, abi, {\n origin,\n signer: fakeSigner,\n });\n const blockHash = `0x${\"cd\".repeat(32)}` as `0x${string}`;\n await (\n wrapped as unknown as {\n increment: { tx: (opts: { at: string }) => Promise<unknown> };\n }\n ).increment\n .tx({ at: blockHash })\n .catch(() => {});\n expect(calls[0]?.[6]).toEqual({ at: blockHash });\n });\n\n test(\".tx() omits the options argument when no `at` override is passed\", async () => {\n const { runtime, calls } = makeCapturingRuntime();\n const wrapped = wrapContract(runtime, ADDRESS, abi, {\n origin,\n signer: fakeSigner,\n });\n await (wrapped as unknown as { increment: { tx: () => Promise<unknown> } }).increment\n .tx()\n .catch(() => {});\n expect(calls[0]?.[6]).toBeUndefined();\n });\n\n test(\".tx() omits options when only unrelated overrides are passed\", async () => {\n const { runtime, calls } = makeCapturingRuntime();\n const wrapped = wrapContract(runtime, ADDRESS, abi, {\n origin,\n signer: fakeSigner,\n });\n await (\n wrapped as unknown as {\n increment: { tx: (opts: { value: bigint }) => Promise<unknown> };\n }\n ).increment\n .tx({ value: 1n })\n .catch(() => {});\n expect(calls[0]?.[6]).toBeUndefined();\n });\n\n test(\".prepare() forwards `at: finalized` to dryRunCall\", async () => {\n // `.prepare()` shares `buildReviveCall` with `.tx()`; one\n // smoke test pins that `PrepareOptions.at` is wired too.\n const { runtime, calls } = makeCapturingRuntime();\n const wrapped = wrapContract(runtime, ADDRESS, abi, { origin });\n await (\n wrapped as unknown as {\n increment: { prepare: (opts: { at: string }) => Promise<unknown> };\n }\n ).increment\n .prepare({ at: \"finalized\" })\n .catch(() => {});\n expect(calls[0]?.[6]).toEqual({ at: \"finalized\" });\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 pallet-revive account for the dry-run.\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 describe(\"decodeRevert\", () => {\n const abi: AbiEntry[] = [\n {\n type: \"error\",\n name: \"InsufficientBalance\",\n inputs: [\n { name: \"needed\", type: \"uint256\" },\n { name: \"available\", type: \"uint256\" },\n ],\n },\n ];\n\n test(\"decodes a standard Error(string) revert and lifts the reason\", () => {\n // 0x08c379a0 selector + ABI-encoded \"Whoops\".\n const hex =\n \"0x08c379a0\" +\n \"0000000000000000000000000000000000000000000000000000000000000020\" +\n \"0000000000000000000000000000000000000000000000000000000000000006\" +\n \"57686f6f70730000000000000000000000000000000000000000000000000000\";\n const bytes = hexToBytes(hex as HexString);\n const info = decodeRevert([], bytes);\n expect(info.type).toBe(\"ContractRevertedWithPayload\");\n expect(info.reason).toBe(\"Whoops\");\n expect(info.decoded?.errorName).toBe(\"Error\");\n });\n\n test(\"decodes Panic(uint256) and names well-known codes\", () => {\n // 0x4e487b71 selector + panic code 0x11 (arithmetic overflow).\n const hex = `0x4e487b71${\"00\".repeat(31)}11`;\n const info = decodeRevert([], hexToBytes(hex as HexString));\n expect(info.decoded?.errorName).toBe(\"Panic\");\n expect(info.reason).toBe(\"Panic: arithmetic overflow\");\n });\n\n test(\"decodes Panic(uint256) for unknown codes by falling back to the hex code\", () => {\n // 0x4e487b71 selector + panic code 0xff (not a Solidity-defined code).\n const hex = `0x4e487b71${\"00\".repeat(31)}ff`;\n const info = decodeRevert([], hexToBytes(hex as HexString));\n expect(info.decoded?.errorName).toBe(\"Panic\");\n expect(info.reason).toBe(\"Panic(0xff)\");\n });\n\n test(\"decodes an ABI-defined custom error\", () => {\n // InsufficientBalance(uint256,uint256) selector + (1, 2).\n const hex =\n \"0xcf479181\" +\n \"0000000000000000000000000000000000000000000000000000000000000001\" +\n \"0000000000000000000000000000000000000000000000000000000000000002\";\n const info = decodeRevert(abi, hexToBytes(hex as HexString));\n expect(info.decoded?.errorName).toBe(\"InsufficientBalance\");\n expect(info.decoded?.args).toEqual([1n, 2n]);\n expect(info.reason).toBeUndefined();\n });\n\n test(\"falls back to a UTF-8 reason on raw revert(bytes) payloads\", () => {\n const bytes = hexToBytes(\"0x556e617574686f72697a6564\" as HexString);\n const info = decodeRevert([], bytes);\n expect(info.decoded).toBeUndefined();\n expect(info.reason).toBe(\"Unauthorized\");\n expect(info.data).toBe(\"0x556e617574686f72697a6564\");\n });\n\n test(\"leaves reason undefined for opaque non-UTF8 bytes\", () => {\n const info = decodeRevert([], new Uint8Array([0xff, 0xfe, 0xfd]));\n expect(info.decoded).toBeUndefined();\n expect(info.reason).toBeUndefined();\n expect(info.data).toBe(\"0xfffefd\");\n });\n\n test(\"empty payload produces a bare ContractRevertedWithPayload with no extras\", () => {\n const info = decodeRevert([], new Uint8Array(0));\n expect(info).toEqual({ type: \"ContractRevertedWithPayload\", data: \"0x\" });\n });\n });\n\n describe(\"wrapContract — REVERT flag handling\", () => {\n const abi: AbiEntry[] = [\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 stateMutability: \"nonpayable\",\n },\n {\n type: \"function\",\n name: \"ping\",\n inputs: [],\n outputs: [],\n stateMutability: \"nonpayable\",\n },\n ];\n const ADDRESS = \"0x0102030405060708090a0b0c0d0e0f1011121314\";\n const ORIGIN = \"5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY\" as SS58String;\n const fakeSigner = {\n publicKey: new Uint8Array(32),\n } as unknown as PolkadotSigner;\n\n const REVERT_PAYLOAD = hexToBytes(\"0x556e617574686f72697a6564\" as HexString);\n\n function revertingRuntime(data: Uint8Array = REVERT_PAYLOAD): ContractRuntime {\n return {\n api: {\n tx: {\n Revive: {\n call: () => {\n throw new Error(\n \"Revive.call must NOT be invoked when the dry-run reverts\",\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: 5n, proof_size: 5n },\n storage_deposit: { type: \"Refund\", value: 0n },\n max_storage_deposit: { type: \"Refund\", value: 0n },\n gas_consumed: 0n,\n result: {\n success: true,\n value: { flags: 1, data },\n },\n }),\n };\n }\n\n test(\"query() returns success:false with a ContractReverted value when REVERT bit is set\", async () => {\n const wrapped = wrapContract(revertingRuntime(), ADDRESS, abi, {\n signer: fakeSigner,\n origin: ORIGIN,\n });\n\n const result = await (\n wrapped as unknown as {\n transfer: {\n query: (\n to: string,\n amount: bigint,\n ) => Promise<{ success: boolean; value: unknown; gasRequired: unknown }>;\n };\n }\n ).transfer.query(\"0x0000000000000000000000000000000000000001\", 1n);\n\n expect(result.success).toBe(false);\n expect(result.value).toEqual({\n type: \"ContractRevertedWithPayload\",\n data: \"0x556e617574686f72697a6564\",\n reason: \"Unauthorized\",\n });\n expect(result.gasRequired).toEqual({ ref_time: 5n, proof_size: 5n });\n });\n\n test(\"query() handles an empty revert payload end-to-end (revert with no message)\", async () => {\n const wrapped = wrapContract(revertingRuntime(new Uint8Array(0)), ADDRESS, abi, {\n signer: fakeSigner,\n origin: ORIGIN,\n });\n const result = await (\n wrapped as unknown as {\n ping: {\n query: () => Promise<{ success: boolean; value: unknown }>;\n };\n }\n ).ping.query();\n\n expect(result.success).toBe(false);\n expect(result.value).toEqual({ type: \"ContractRevertedWithPayload\", data: \"0x\" });\n });\n\n test(\"tx() throws ContractRevertedError before signing when the dry-run reverts\", async () => {\n const wrapped = wrapContract(revertingRuntime(), ADDRESS, abi, {\n signer: fakeSigner,\n origin: ORIGIN,\n });\n\n await expect(\n (\n wrapped as unknown as {\n transfer: {\n tx: (to: string, amount: bigint) => Promise<unknown>;\n };\n }\n ).transfer.tx(\"0x0000000000000000000000000000000000000001\", 1n),\n ).rejects.toMatchObject({\n name: \"ContractRevertedError\",\n methodName: \"transfer\",\n data: \"0x556e617574686f72697a6564\",\n reason: \"Unauthorized\",\n });\n });\n\n test(\"prepare() throws ContractRevertedError before constructing the extrinsic\", async () => {\n const wrapped = wrapContract(revertingRuntime(), ADDRESS, abi, {});\n\n await expect(\n (\n wrapped as unknown as {\n transfer: {\n prepare: (to: string, amount: bigint) => Promise<unknown>;\n };\n }\n ).transfer.prepare(\"0x0000000000000000000000000000000000000001\", 1n),\n ).rejects.toMatchObject({\n name: \"ContractRevertedError\",\n methodName: \"transfer\",\n reason: \"Unauthorized\",\n });\n });\n\n test(\"revert with a standard Error(string) payload lifts the reason\", async () => {\n // Solidity `require(false, \"nope\")`.\n const errorBytes = hexToBytes(\n (\"0x08c379a0\" +\n \"0000000000000000000000000000000000000000000000000000000000000020\" +\n \"0000000000000000000000000000000000000000000000000000000000000004\" +\n \"6e6f706500000000000000000000000000000000000000000000000000000000\") as HexString,\n );\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: 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: 1, data: errorBytes } },\n }),\n };\n const wrapped = wrapContract(runtime, ADDRESS, abi, {\n signer: fakeSigner,\n origin: ORIGIN,\n });\n const result = await (\n wrapped as unknown as {\n transfer: {\n query: (\n to: string,\n amount: bigint,\n ) => Promise<{ success: boolean; value: { reason?: string } }>;\n };\n }\n ).transfer.query(\"0x0000000000000000000000000000000000000001\", 1n);\n expect(result.success).toBe(false);\n expect(result.value.reason).toBe(\"nope\");\n });\n\n test(\"REVERT bit cleared stays on the happy path - normal return value is decoded\", async () => {\n const returnBytes = new Uint8Array(32);\n returnBytes[31] = 1; // bool true\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: 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: returnBytes } },\n }),\n };\n const wrapped = wrapContract(runtime, ADDRESS, abi, { origin: ORIGIN });\n const result = await (\n wrapped as unknown as {\n transfer: {\n query: (\n to: string,\n amount: bigint,\n ) => Promise<{ success: boolean; value: unknown }>;\n };\n }\n ).transfer.query(\"0x0000000000000000000000000000000000000001\", 1n);\n expect(result.success).toBe(true);\n expect(result.value).toBe(true);\n });\n });\n}\n","// Copyright 2026 Parity Technologies (UK) Ltd.\n// SPDX-License-Identifier: Apache-2.0\nimport 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/**\n * Block reference used to target `ReviveApi.call` dry-runs. Matches PAPI's\n * runtime-call `at` option: `\"best\"`, `\"finalized\"`, or a block hash.\n */\nexport type ContractDryRunAt = \"best\" | \"finalized\" | HexString;\n\n/**\n * Per-call options accepted by {@link ReviveDryRunCall}.\n *\n * Note on the trailing `options` arg: pallet-revive's Rust runtime API\n * `ReviveApi::call` only takes the 6 positional args (origin, dest, value,\n * gas_limit, storage_deposit_limit, input_data). This 7th `options` object\n * is **injected by PAPI** on every `api.apis.X.Y` runtime-API call via its\n * `WithCallOptions` type wrapper (see\n * `polkadot-api/packages/client/src/viewFns.ts:9-11` upstream — defined\n * as `WithCallOptions$2` in the bundled `.d.ts` due to TS bundler suffixing).\n * It never reaches the Rust side — PAPI's `viewFns.ts:38-41` consumes it in\n * JS, reads `options.at`, resolves it to a concrete block hash via the\n * chain-head's runtime context, and uses that hash on the JSON-RPC\n * `state_call` invocation. The 6-arg payload sent over the wire is\n * unchanged.\n */\nexport interface ReviveDryRunCallOptions {\n at?: ContractDryRunAt;\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 options?: ReviveDryRunCallOptions,\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 options?: ReviveDryRunCallOptions,\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 * The runtime default `at` (set via {@link ContractRuntimeOptions.at} on\n * the factory) is applied when the caller does not pass an explicit\n * `options.at`. `.query()` per-call overrides flow through this argument.\n */\n readonly dryRunCall: ReviveDryRunCall;\n}\n\n/** Options for {@link createContractRuntime} / {@link createContractRuntimeFromClient}. */\nexport interface ContractRuntimeOptions {\n /**\n * Block to target for `ReviveApi.call` dry-runs. Defaults to `\"best\"`\n * so contract `.query()` reads observe the same state as transactions\n * resolved at best-block (the product-sdk `.tx()` default). Set to\n * `\"finalized\"` to read the canonical, lagged state, or to a specific\n * block hash to pin reads to a historical block. Can be overridden per\n * call via `QueryOptions.at`.\n */\n at?: ContractDryRunAt;\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(\n api: ReviveTypedApi,\n options?: ContractRuntimeOptions,\n): ContractRuntime {\n const defaultAt: ContractDryRunAt = options?.at ?? \"best\";\n return {\n api,\n dryRunCall: (origin, dest, value, gas, deposit, data, callOpts) =>\n api.apis.ReviveApi.call(origin, dest, value, gas, deposit, data, {\n at: callOpts?.at ?? defaultAt,\n }),\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 options?: ContractRuntimeOptions,\n): ContractRuntime {\n const defaultAt: ContractDryRunAt = options?.at ?? \"best\";\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, callOpts) =>\n unsafe.apis.ReviveApi.call(origin, dest, value, gas, deposit, data, {\n at: callOpts?.at ?? defaultAt,\n }),\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 describe(\"createContractRuntime — at-block routing\", () => {\n // The contract is: a `.query()` against the runtime resolves at\n // best-block by default so it observes the same state as `.tx()` /\n // `batchSubmitAndWatch()` (which resolve at best-block). Callers can\n // override per call. These tests pin both behaviours by capturing the\n // options that arrive at the underlying `ReviveApi.call`.\n const origin = \"5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY\" as SS58String;\n const dest = \"0x0102030405060708090a0b0c0d0e0f1011121314\" as HexString;\n\n function makeApi(): {\n api: ReviveTypedApi;\n calls: Array<unknown[]>;\n } {\n const calls: Array<unknown[]> = [];\n const api = {\n apis: {\n ReviveApi: {\n call: (...args: unknown[]) => {\n calls.push(args);\n return Promise.resolve({\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: {\n success: true,\n value: { flags: 0, data: new Uint8Array(0) },\n },\n });\n },\n },\n },\n } as unknown as ReviveTypedApi;\n return { api, calls };\n }\n\n test(\"defaults to `at: best` when no option is passed to the factory\", async () => {\n const { api, calls } = makeApi();\n const runtime = createContractRuntime(api);\n await runtime.dryRunCall(origin, dest, 0n, undefined, undefined, new Uint8Array(0));\n expect(calls[0]?.[6]).toEqual({ at: \"best\" });\n });\n\n test(\"respects an explicit factory default\", async () => {\n const { api, calls } = makeApi();\n const runtime = createContractRuntime(api, { at: \"finalized\" });\n await runtime.dryRunCall(origin, dest, 0n, undefined, undefined, new Uint8Array(0));\n expect(calls[0]?.[6]).toEqual({ at: \"finalized\" });\n });\n\n test(\"per-call `at` option overrides the factory default\", async () => {\n const { api, calls } = makeApi();\n const runtime = createContractRuntime(api, { at: \"best\" });\n const blockHash = `0x${\"ab\".repeat(32)}` as HexString;\n await runtime.dryRunCall(origin, dest, 0n, undefined, undefined, new Uint8Array(0), {\n at: blockHash,\n });\n expect(calls[0]?.[6]).toEqual({ at: blockHash });\n });\n\n test(\"explicit per-call `at: best` is forwarded even when factory default is `finalized`\", async () => {\n const { api, calls } = makeApi();\n const runtime = createContractRuntime(api, { at: \"finalized\" });\n await runtime.dryRunCall(origin, dest, 0n, undefined, undefined, new Uint8Array(0), {\n at: \"best\",\n });\n expect(calls[0]?.[6]).toEqual({ at: \"best\" });\n });\n });\n\n describe(\"createContractRuntimeFromClient — at-block routing\", () => {\n // The production factory has its own (parallel) `at` plumbing\n // through `getUnsafeApi()`. A regression in this branch would slip\n // past the typed-API tests above, so we mock the client and pin the\n // same contract: factory default applied, per-call override wins.\n const origin = \"5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY\" as SS58String;\n const dest = \"0x0102030405060708090a0b0c0d0e0f1011121314\" as HexString;\n\n function makeClient(): {\n client: PolkadotClient;\n calls: Array<unknown[]>;\n } {\n const calls: Array<unknown[]> = [];\n const unsafe = {\n apis: {\n ReviveApi: {\n call: (...args: unknown[]) => {\n calls.push(args);\n return Promise.resolve({\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: {\n success: true,\n value: { flags: 0, data: new Uint8Array(0) },\n },\n });\n },\n },\n },\n };\n const client = {\n getTypedApi: () => ({}) as never,\n getUnsafeApi: () => unsafe,\n } as unknown as PolkadotClient;\n return { client, calls };\n }\n\n test(\"defaults to `at: best` when no factory option is set\", async () => {\n const { client, calls } = makeClient();\n const runtime = createContractRuntimeFromClient(client, {});\n await runtime.dryRunCall(origin, dest, 0n, undefined, undefined, new Uint8Array(0));\n expect(calls[0]?.[6]).toEqual({ at: \"best\" });\n });\n\n test(\"respects an explicit factory default\", async () => {\n const { client, calls } = makeClient();\n const runtime = createContractRuntimeFromClient(client, {}, { at: \"finalized\" });\n await runtime.dryRunCall(origin, dest, 0n, undefined, undefined, new Uint8Array(0));\n expect(calls[0]?.[6]).toEqual({ at: \"finalized\" });\n });\n\n test(\"per-call `at` overrides factory default through the unsafe-API path\", async () => {\n const { client, calls } = makeClient();\n const runtime = createContractRuntimeFromClient(client, {}, { at: \"best\" });\n await runtime.dryRunCall(origin, dest, 0n, undefined, undefined, new Uint8Array(0), {\n at: \"finalized\",\n });\n expect(calls[0]?.[6]).toEqual({ at: \"finalized\" });\n });\n\n test(\"explicit per-call `at: best` is forwarded even when factory default is `finalized`\", async () => {\n const { client, calls } = makeClient();\n const runtime = createContractRuntimeFromClient(client, {}, { at: \"finalized\" });\n await runtime.dryRunCall(origin, dest, 0n, undefined, undefined, new Uint8Array(0), {\n at: \"best\",\n });\n expect(calls[0]?.[6]).toEqual({ at: \"best\" });\n });\n });\n}\n","// Copyright 2026 Parity Technologies (UK) Ltd.\n// SPDX-License-Identifier: Apache-2.0\nimport type { HexString, PolkadotClient, SS58String } from \"polkadot-api\";\nimport { wrapContract } from \"./wrap.js\";\nimport { ContractLiveAddressResolutionError, ContractNotFoundError } from \"./errors.js\";\nimport type { ContractRuntime, ContractRuntimeOptions } 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 LiveContractResolutionOptions,\n} from \"./types.js\";\n\ntype ContractMap = Record<string, CdmJsonContract>;\ntype OptionAddress = { isSome: boolean; value: HexString };\ntype LiveVersionSpec = number | \"latest\";\n\nconst CDM_REGISTRY_ABI: AbiEntry[] = [\n {\n type: \"function\",\n name: \"getAddress\",\n inputs: [{ name: \"contract_name\", type: \"string\" }],\n outputs: [\n {\n name: \"\",\n type: \"tuple\",\n components: [\n { name: \"isSome\", type: \"bool\" },\n { name: \"value\", type: \"address\" },\n ],\n },\n ],\n stateMutability: \"view\",\n },\n {\n type: \"function\",\n name: \"getAddressAtVersion\",\n inputs: [\n { name: \"contract_name\", type: \"string\" },\n { name: \"version\", type: \"uint32\" },\n ],\n outputs: [\n {\n name: \"\",\n type: \"tuple\",\n components: [\n { name: \"isSome\", type: \"bool\" },\n { name: \"value\", type: \"address\" },\n ],\n },\n ],\n stateMutability: \"view\",\n },\n];\n\nfunction cloneCdmJson(cdmJson: CdmJson): CdmJson {\n const cloneContractMap = (contracts: ContractMap): ContractMap =>\n Object.fromEntries(\n Object.entries(contracts).map(([library, contract]) => [library, { ...contract }]),\n );\n\n return {\n ...cdmJson,\n dependencies: { ...cdmJson.dependencies },\n contracts: cdmJson.contracts ? cloneContractMap(cdmJson.contracts) : undefined,\n };\n}\n\nfunction resolveRegistryAddress(cdmJson: CdmJson, override?: HexString): HexString {\n if (override) return override;\n if (cdmJson.registry) return cdmJson.registry;\n throw new ContractLiveAddressResolutionError(\n \"CDM registry address is required for live contract address resolution. Pass registryAddress or set cdm.json registry.\",\n );\n}\n\nfunction patchContractAddress(cdmJson: CdmJson, library: string, address: HexString): void {\n const contract = cdmJson.contracts?.[library];\n if (!contract) {\n throw new ContractNotFoundError(library);\n }\n contract.address = address;\n}\n\nfunction resolveLiveVersionSpec(\n cdmJson: CdmJson,\n library: string,\n contract: CdmJsonContract,\n): LiveVersionSpec {\n const requested = cdmJson.dependencies[library];\n if (typeof requested === \"number\" && Number.isInteger(requested) && requested >= 0) {\n return requested;\n }\n if (typeof requested === \"string\") {\n if (requested === \"latest\") return \"latest\";\n const parsed = Number(requested);\n if (Number.isInteger(parsed) && parsed >= 0) return parsed;\n }\n return contract.version;\n}\n\nasync function queryLiveAddress(\n registry: Contract<ContractDef>,\n library: string,\n version: LiveVersionSpec,\n): Promise<HexString> {\n const result =\n version === \"latest\"\n ? await registry.getAddress.query(library)\n : await registry.getAddressAtVersion.query(library, version);\n if (!result.success) {\n throw new ContractLiveAddressResolutionError(\n version === \"latest\"\n ? `Failed to resolve live address for \"${library}\" from the CDM registry`\n : `Failed to resolve live address for \"${library}\" version ${version} from the CDM registry`,\n { library, detail: result.value },\n );\n }\n\n const value = result.value as OptionAddress | undefined;\n if (!value?.isSome) {\n throw new ContractLiveAddressResolutionError(\n version === \"latest\"\n ? `Contract \"${library}\" is not registered in the CDM registry`\n : `Contract \"${library}\" version ${version} is not registered in the CDM registry`,\n { library, detail: result.value },\n );\n }\n\n return value.value;\n}\n\n/**\n * Return a cloned manifest whose installed contract addresses have been\n * replaced by live addresses from the CDM registry.\n *\n * This is intentionally strict: if a requested library cannot be resolved\n * from the registry, the promise rejects. Use `new ContractManager(...)` or\n * `ContractManager.fromClient(...)` directly for snapshot-only behavior.\n */\nexport async function withLiveContractAddresses(\n cdmJson: CdmJson,\n runtime: ContractRuntime,\n options?: LiveContractResolutionOptions,\n): Promise<CdmJson> {\n const contracts = cdmJson.contracts;\n if (!contracts || Object.keys(contracts).length === 0) {\n throw new ContractLiveAddressResolutionError(\n \"No installed contracts found in cdm.json for live address resolution.\",\n );\n }\n\n const libraries = options?.libraries ?? Object.keys(contracts);\n for (const library of libraries) {\n if (!(library in contracts)) {\n throw new ContractNotFoundError(library);\n }\n }\n\n const registryAddress = resolveRegistryAddress(cdmJson, options?.registryAddress);\n const registry = createContract(runtime, registryAddress, CDM_REGISTRY_ABI, {\n defaultOrigin: options?.registryOrigin,\n });\n const liveAddresses = await Promise.all(\n libraries.map(async (library): Promise<readonly [string, HexString]> => {\n const version = resolveLiveVersionSpec(cdmJson, library, contracts[library]);\n return [library, await queryLiveAddress(registry, library, version)];\n }),\n );\n\n const resolved = cloneCdmJson(cdmJson);\n for (const [library, address] of liveAddresses) {\n patchContractAddress(resolved, library, address);\n }\n return resolved;\n}\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 contracts: ContractMap | undefined;\n private runtime: ContractRuntime;\n private defaults: ContractDefaults;\n\n constructor(cdmJson: CdmJson, runtime: ContractRuntime, options?: ContractManagerOptions) {\n this.runtime = runtime;\n this.contracts = cdmJson.contracts;\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 & ContractRuntimeOptions,\n ): ContractManager {\n return new ContractManager(\n cdmJson,\n createContractRuntimeFromClient(client, descriptor, options),\n options,\n );\n }\n\n /**\n * Create a manager after strictly resolving installed contract addresses\n * from the live CDM registry. ABIs still come from the installed manifest.\n */\n static async fromLive(\n cdmJson: CdmJson,\n runtime: ContractRuntime,\n options?: ContractManagerOptions & LiveContractResolutionOptions,\n ): Promise<ContractManager> {\n const resolved = await withLiveContractAddresses(cdmJson, runtime, {\n ...options,\n registryOrigin: options?.registryOrigin ?? (options?.defaultOrigin as SS58String),\n });\n return new ContractManager(resolved, runtime, options);\n }\n\n /**\n * Convenience factory for {@link fromLive} when the caller has a raw\n * `PolkadotClient` and descriptor.\n */\n static async fromLiveClient<TDescriptor>(\n cdmJson: CdmJson,\n client: PolkadotClient,\n descriptor: TDescriptor,\n options?: ContractManagerOptions & ContractRuntimeOptions & LiveContractResolutionOptions,\n ): Promise<ContractManager> {\n const runtime = createContractRuntimeFromClient(client, descriptor, options);\n return ContractManager.fromLive(cdmJson, runtime, options);\n }\n\n private getContractData(library: string): CdmJsonContract {\n if (!this.contracts || !(library in this.contracts)) {\n throw new ContractNotFoundError(library);\n }\n return this.contracts[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 & ContractRuntimeOptions,\n): Contract<ContractDef> {\n return createContract(\n createContractRuntimeFromClient(client, descriptor, options),\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 * current CDM installs. 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 * - `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 dependencies: {\n \"@w3s/playground-registry\": \"latest\",\n },\n contracts: {\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 const flattenedCdm: CdmJson = {\n registry: \"0x9999999999999999999999999999999999999999\",\n dependencies: {\n \"@w3s/playground-registry\": \"latest\",\n },\n contracts: {\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 },\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 async function registryRuntimeFor(\n value:\n | OptionAddress\n | ((call: { functionName: string; args: readonly unknown[] }) => OptionAddress),\n ): Promise<{ runtime: ContractRuntime; calls: { functionName: string; args: unknown[] }[] }> {\n const { bytesToHex, decodeFunctionData, encodeFunctionResult, hexToBytes } = await import(\n \"viem\"\n );\n const calls: { functionName: string; args: unknown[] }[] = [];\n\n const runtime = {\n ...fakeRuntime(),\n dryRunCall: async (\n _origin: unknown,\n _dest: unknown,\n _value: unknown,\n _gasLimit: unknown,\n _storageDepositLimit: unknown,\n calldata: Uint8Array,\n ) => {\n const decoded = decodeFunctionData({\n abi: CDM_REGISTRY_ABI as any,\n data: bytesToHex(calldata),\n });\n const call = {\n functionName: decoded.functionName,\n args: [...(decoded.args ?? [])],\n };\n calls.push(call);\n const resultValue = typeof value === \"function\" ? value(call) : value;\n const data = hexToBytes(\n encodeFunctionResult({\n abi: CDM_REGISTRY_ABI as any,\n functionName: decoded.functionName,\n result: resultValue,\n }) as `0x${string}`,\n );\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\" as const, value: 0n },\n max_storage_deposit: { type: \"Refund\" as const, value: 0n },\n gas_consumed: 0n,\n result: { success: true, value: { flags: 0, data } },\n };\n },\n };\n return { runtime, calls };\n }\n\n describe(\"ContractManager — cdm.json resolution\", () => {\n test(\"constructs from a flat cdm.json\", () => {\n const manager = new ContractManager(flattenedCdm, fakeRuntime());\n expect(manager.getAddress(\"@w3s/playground-registry\")).toBe(\n \"0x4A37B123b0BA2A894cA5953f472264921d44e298\",\n );\n });\n\n test(\"getContract returns a typed handle from a flattened cdm.json\", () => {\n const manager = new ContractManager(flattenedCdm, 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 });\n\n test(\"getContract throws without target wording for a flattened cdm.json miss\", () => {\n const manager = new ContractManager(flattenedCdm, fakeRuntime());\n expect(() => manager.getContract(\"@nonexistent/contract\")).toThrow(\n 'Contract \"@nonexistent/contract\" not found in cdm.json',\n );\n });\n\n test(\"strictly patches flattened manifests with live registry addresses\", async () => {\n const liveAddress = \"0x7777777777777777777777777777777777777777\";\n const { runtime, calls } = await registryRuntimeFor({\n isSome: true,\n value: liveAddress,\n });\n\n const resolved = await withLiveContractAddresses(flattenedCdm, runtime, {\n registryOrigin: \"5LiveOrigin\" as SS58String,\n });\n\n expect(resolved.contracts?.[\"@w3s/playground-registry\"].address).toBe(liveAddress);\n expect(calls[0]).toMatchObject({\n functionName: \"getAddress\",\n args: [\"@w3s/playground-registry\"],\n });\n expect(flattenedCdm.contracts?.[\"@w3s/playground-registry\"].address).toBe(\n \"0x4A37B123b0BA2A894cA5953f472264921d44e298\",\n );\n });\n\n test(\"uses versioned registry lookup for pinned dependencies\", async () => {\n const latestAddress = \"0x7777777777777777777777777777777777777777\";\n const versionedAddress = \"0x6666666666666666666666666666666666666666\";\n const { runtime, calls } = await registryRuntimeFor(({ functionName }) => ({\n isSome: true,\n value: functionName === \"getAddressAtVersion\" ? versionedAddress : latestAddress,\n }));\n const pinnedCdm: CdmJson = {\n ...flattenedCdm,\n dependencies: {\n \"@w3s/playground-registry\": 6,\n },\n };\n\n const resolved = await withLiveContractAddresses(pinnedCdm, runtime);\n\n expect(resolved.contracts?.[\"@w3s/playground-registry\"].address).toBe(versionedAddress);\n expect(calls[0]).toMatchObject({\n functionName: \"getAddressAtVersion\",\n args: [\"@w3s/playground-registry\", 6],\n });\n });\n\n test(\"falls back to installed contract version when dependency entry is absent\", async () => {\n const versionedAddress = \"0x5555555555555555555555555555555555555555\";\n const { runtime, calls } = await registryRuntimeFor({\n isSome: true,\n value: versionedAddress,\n });\n const missingDependencyCdm: CdmJson = {\n ...flattenedCdm,\n dependencies: {},\n };\n\n const resolved = await withLiveContractAddresses(missingDependencyCdm, runtime);\n\n expect(resolved.contracts?.[\"@w3s/playground-registry\"].address).toBe(versionedAddress);\n expect(calls[0]).toMatchObject({\n functionName: \"getAddressAtVersion\",\n args: [\"@w3s/playground-registry\", 6],\n });\n });\n\n test(\"live registry resolution fails instead of falling back to the snapshot\", async () => {\n const { runtime } = await registryRuntimeFor({\n isSome: false,\n value: \"0x0000000000000000000000000000000000000000\",\n });\n\n await expect(withLiveContractAddresses(flattenedCdm, runtime)).rejects.toThrow(\n /not registered/,\n );\n });\n\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(\"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\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":["// Copyright 2026 Parity Technologies (UK) Ltd.\n// SPDX-License-Identifier: Apache-2.0\nimport type { HexString } from \"polkadot-api\";\n\n/** 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\n constructor(library: string) {\n super(`Contract \"${library}\" not found in cdm.json`);\n this.name = \"ContractNotFoundError\";\n this.library = library;\n }\n}\n\n/** Live CDM registry address resolution failed. */\nexport class ContractLiveAddressResolutionError extends ContractError {\n readonly library: string | undefined;\n readonly detail: unknown;\n\n constructor(\n message: string,\n options?: { library?: string; detail?: unknown; cause?: unknown },\n ) {\n super(message, options?.cause !== undefined ? { cause: options.cause } : undefined);\n this.name = \"ContractLiveAddressResolutionError\";\n this.library = options?.library;\n this.detail = options?.detail;\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\n/** viem-decoded standard or ABI-defined contract error. */\nexport interface DecodedContractRevert {\n errorName: string;\n args: readonly unknown[] | undefined;\n}\n\n/**\n * Tagged-enum value surfaced on `QueryResult.value` when a contract reverts\n * via the REVERT flag. The discriminant is intentionally distinct from\n * `pallet-revive`'s bare `{ type: \"ContractReverted\" }` dispatch-error variant,\n * which is the other path that can populate `QueryResult.value` on failure.\n */\nexport interface ContractRevertInfo {\n type: \"ContractRevertedWithPayload\";\n data: HexString;\n reason?: string;\n decoded?: DecodedContractRevert;\n}\n\n// Top-level bigints stringify unquoted (`42`); bigints inside an object or\n// array stringify as JSON strings (`\"42\"`) because that's the only way a\n// JSON replacer can emit them. Tolerated since this string is only ever\n// read by humans in an error message, not parsed.\nfunction stringifyArg(arg: unknown): string {\n if (typeof arg === \"bigint\") return arg.toString();\n return JSON.stringify(arg, (_, v) => (typeof v === \"bigint\" ? v.toString() : v));\n}\n\n/** A contract call returned with the `REVERT` flag set on a dispatched-OK call. */\nexport class ContractRevertedError extends ContractError {\n readonly methodName: string;\n readonly data: HexString;\n readonly reason?: string;\n readonly decoded?: DecodedContractRevert;\n\n constructor(\n methodName: string,\n data: HexString,\n info?: { reason?: string; decoded?: DecodedContractRevert },\n ) {\n // `reason` already carries the human-readable message for Error and Panic,\n // so only fall back to `errorName(args...)` for ABI-defined custom errors.\n const suffix =\n info?.reason ??\n (info?.decoded\n ? `${info.decoded.errorName}(${(info.decoded.args ?? []).map(stringifyArg).join(\", \")})`\n : data);\n super(\n `Contract reverted in \"${methodName}\": ${suffix}. The transaction was not submitted.`,\n );\n this.name = \"ContractRevertedError\";\n this.methodName = methodName;\n this.data = data;\n this.reason = info?.reason;\n this.decoded = info?.decoded;\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\")).toBeInstanceOf(ContractError);\n expect(new ContractLiveAddressResolutionError(\"test\")).toBeInstanceOf(ContractError);\n expect(new ContractDryRunFailedError(\"foo\", \"x\")).toBeInstanceOf(ContractError);\n expect(new ContractRevertedError(\"foo\", \"0x\" as HexString)).toBeInstanceOf(\n ContractError,\n );\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\", () => {\n const err = new ContractNotFoundError(\"@test/foo\");\n expect(err.library).toBe(\"@test/foo\");\n expect(err.message).toBe('Contract \"@test/foo\" not found in cdm.json');\n });\n });\n\n describe(\"ContractLiveAddressResolutionError\", () => {\n test(\"captures library and detail\", () => {\n const detail = { success: false };\n const err = new ContractLiveAddressResolutionError(\"failed\", {\n library: \"@test/foo\",\n detail,\n });\n expect(err.name).toBe(\"ContractLiveAddressResolutionError\");\n expect(err.library).toBe(\"@test/foo\");\n expect(err.detail).toBe(detail);\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 describe(\"ContractRevertedError\", () => {\n test(\"captures method, raw data, reason, and decoded payload\", () => {\n const data = \"0x556e617574686f72697a6564\" as HexString;\n const err = new ContractRevertedError(\"transfer\", data, {\n reason: \"Unauthorized\",\n decoded: { errorName: \"Error\", args: [\"Unauthorized\"] },\n });\n expect(err.name).toBe(\"ContractRevertedError\");\n expect(err.methodName).toBe(\"transfer\");\n expect(err.data).toBe(data);\n expect(err.reason).toBe(\"Unauthorized\");\n expect(err.decoded?.errorName).toBe(\"Error\");\n // Error(string) prefers the bare reason over `Error(\"...\")` form.\n expect(err.message).toBe(\n 'Contract reverted in \"transfer\": Unauthorized. The transaction was not submitted.',\n );\n });\n\n test(\"falls back to reason then to raw data when decoded is absent\", () => {\n const data = \"0xdeadbeef\" as HexString;\n const withReason = new ContractRevertedError(\"foo\", data, { reason: \"Whoops\" });\n expect(withReason.message).toContain(\"Whoops\");\n\n const noInfo = new ContractRevertedError(\"foo\", data);\n expect(noInfo.message).toContain(\"0xdeadbeef\");\n expect(noInfo.reason).toBeUndefined();\n expect(noInfo.decoded).toBeUndefined();\n });\n\n test(\"stringifies top-level bigint args without throwing\", () => {\n const err = new ContractRevertedError(\"bar\", \"0x\" as HexString, {\n decoded: { errorName: \"InsufficientBalance\", args: [42n, 100n] },\n });\n expect(err.message).toContain(\"InsufficientBalance(42, 100)\");\n });\n\n test(\"stringifies nested bigint args inside a struct without throwing\", () => {\n // viem returns `[{ owed: 42n }]` for struct args; the default\n // JSON.stringify replacer would throw on the nested bigint.\n const err = new ContractRevertedError(\"redeem\", \"0x\" as HexString, {\n decoded: {\n errorName: \"Bad\",\n args: [{ owed: 42n, nested: { deeper: [7n, 9n] } }],\n },\n });\n expect(err.message).toContain('\"owed\":\"42\"');\n expect(err.message).toContain('\"deeper\":[\"7\",\"9\"]');\n });\n\n test(\"uses the Panic reason instead of Panic(<code>) when decodeRevert provided one\", () => {\n // Regression: Panic reason used to be clobbered by the errorName(args) form.\n const err = new ContractRevertedError(\"withdraw\", \"0x\" as HexString, {\n reason: \"Panic: arithmetic overflow\",\n decoded: { errorName: \"Panic\", args: [17n] },\n });\n expect(err.message).toContain(\"Panic: arithmetic overflow\");\n expect(err.message).not.toContain(\"Panic(17)\");\n });\n });\n}\n","// Copyright 2026 Parity Technologies (UK) Ltd.\n// SPDX-License-Identifier: Apache-2.0\nimport type { HexString, PolkadotSigner, SS58String } from \"polkadot-api\";\nimport {\n bytesToHex,\n decodeErrorResult,\n decodeFunctionResult,\n encodeFunctionData,\n type Abi as ViemAbi,\n} from \"viem\";\nimport { submitAndWatch } from \"@parity/product-sdk-tx\";\nimport { createLogger } from \"@parity/product-sdk-logger\";\nimport { ss58Address } from \"@polkadot-labs/hdkd-helpers\";\nimport {\n ContractDryRunFailedError,\n ContractRevertedError,\n ContractSignerMissingError,\n type ContractRevertInfo,\n} 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 * pallet-revive's own account, used as fallback origin for read-only queries\n * when no wallet is connected. The runtime API requires an origin, so we pass\n * this account when there is no connected one.\n *\n * This mirrors `Pallet::<T>::account_id()` in pallet-revive, which is\n * `PalletId(*b\"py/reviv\").into_account_truncating()`. The 32-byte AccountId is\n * the PalletId `TYPE_ID` (`b\"modl\"`) followed by the id (`b\"py/reviv\"`), zero\n * padded, i.e. `\"modlpy/reviv\"` + 20 trailing zero bytes.\n */\nconst REVIVE_PALLET_ACCOUNT = new Uint8Array(32);\nREVIVE_PALLET_ACCOUNT.set(new TextEncoder().encode(\"modlpy/reviv\"));\nconst QUERY_FALLBACK_ORIGIN = ss58Address(REVIVE_PALLET_ACCOUNT) 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 pallet-revive account fallback 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// Bit 0 of `pallet-revive`'s `ReturnFlags`. Other bits are reserved, so we\n// mask explicitly rather than checking `flags !== 0`.\nconst REVERT_FLAG = 1;\n\n// Solidity panic codes that `Panic(uint256)` can carry. Stable across compiler\n// versions; mapping them to a human-readable name beats showing the raw hex.\nconst PANIC_REASONS: Record<string, string> = {\n \"0x01\": \"assertion failed\",\n \"0x11\": \"arithmetic overflow\",\n \"0x12\": \"division by zero\",\n \"0x21\": \"invalid enum value\",\n \"0x22\": \"improperly encoded storage byte array\",\n \"0x31\": \"pop on empty array\",\n \"0x32\": \"array index out of bounds\",\n \"0x41\": \"memory allocation overflow\",\n \"0x51\": \"call to uninitialized internal function\",\n};\n\n/**\n * Adapt viem's `decodeErrorResult` output into our tagged `ContractRevertInfo`.\n * The core decode is viem's; this wrapper layers in a UTF-8 fallback for raw\n * `revert(bytes)` payloads and the panic-code-to-string mapping. viem's own\n * `panicReasons` and `ContractFunctionRevertedError` are unsuitable here:\n * `panicReasons` sits at `viem/constants/solidity` and isn't surfaced by\n * viem's `exports` map, and `ContractFunctionRevertedError` is shaped for\n * viem's call pipeline and would need adapting back into this shape anyway.\n */\nfunction decodeRevert(abi: AbiEntry[], data: Uint8Array): ContractRevertInfo {\n const hex = bytesToHex(data);\n const info: ContractRevertInfo = {\n type: \"ContractRevertedWithPayload\",\n data: hex as HexString,\n };\n if (data.byteLength === 0) return info;\n try {\n // The ABI path wins even if a raw `revert(bytes)` payload happens to\n // start with `0x08c379a0` / `0x4e487b71` - astronomically rare on real\n // chains, but worth flagging for anyone debugging an oddly-decoded revert.\n const decoded = decodeErrorResult({\n abi: abi as unknown as ViemAbi,\n data: hex,\n });\n info.decoded = {\n errorName: decoded.errorName,\n args: decoded.args as readonly unknown[] | undefined,\n };\n if (decoded.errorName === \"Error\" && typeof decoded.args?.[0] === \"string\") {\n info.reason = decoded.args[0];\n } else if (decoded.errorName === \"Panic\" && typeof decoded.args?.[0] === \"bigint\") {\n const code = `0x${decoded.args[0].toString(16).padStart(2, \"0\")}`;\n info.reason = PANIC_REASONS[code] ? `Panic: ${PANIC_REASONS[code]}` : `Panic(${code})`;\n }\n return info;\n } catch {\n try {\n info.reason = new TextDecoder(\"utf-8\", { fatal: true }).decode(data);\n } catch {\n // Opaque bytes; expose only the raw hex.\n }\n return info;\n }\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 const decoded = decodeFunctionResult({\n abi: abi as unknown as ViemAbi,\n functionName: methodName,\n data: bytesToHex(returnData),\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 // Passing both overrides skips the dry-run entirely - including the REVERT\n // pre-check. Callers who supply both are accepting that a reverting tx\n // would still be submitted and gas paid.\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 overrides?.at !== undefined ? { at: overrides.at } : undefined,\n );\n if (!dryRun.result.success) {\n throw new ContractDryRunFailedError(methodName, dryRun.result.value);\n }\n if ((dryRun.result.value.flags & REVERT_FLAG) !== 0) {\n // Fail fast so callers don't pay gas on a call the chain already told us would revert.\n const { data, reason, decoded } = decodeRevert(abi, dryRun.result.value.data);\n log.debug(\"Contract reverted\", { methodName, reason, errorName: decoded?.errorName });\n throw new ContractRevertedError(methodName, data, { reason, decoded });\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 overrides?.at !== undefined ? { at: overrides.at } : undefined,\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 if ((dryRun.result.value.flags & REVERT_FLAG) !== 0) {\n // Surface as a tagged value; decoding revert bytes as a normal return would throw.\n const info = decodeRevert(abi, dryRun.result.value.data);\n log.debug(\"Contract reverted\", {\n methodName,\n reason: info.reason,\n errorName: info.decoded?.errorName,\n });\n return {\n success: false,\n value: info,\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\n // pallet-revive account for the dry-run since no signer is\n // required at prepare time; the batch's signer replaces the\n // 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 — query .at option routing\", () => {\n // The `.query()` per-call `at` override is the user-facing hook for\n // pinning a dry-run to a different block than the runtime default\n // (see issue #95). These tests pin the wiring: the `at` value\n // arrives at `runtime.dryRunCall` as its trailing options arg,\n // exactly when the caller passes it — and is absent otherwise so\n // the runtime default applies.\n const abi: AbiEntry[] = [\n {\n type: \"function\",\n name: \"getCount\",\n inputs: [],\n outputs: [{ name: \"\", type: \"uint32\" }],\n stateMutability: \"view\",\n },\n ];\n const ADDRESS = \"0x0102030405060708090a0b0c0d0e0f1011121314\";\n const origin = \"5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY\" as SS58String;\n\n function makeCapturingRuntime(): {\n runtime: ContractRuntime;\n calls: Array<Parameters<ContractRuntime[\"dryRunCall\"]>>;\n } {\n const calls: Array<Parameters<ContractRuntime[\"dryRunCall\"]>> = [];\n const runtime: ContractRuntime = {\n api: {} as unknown as ContractRuntime[\"api\"],\n dryRunCall: (...args) => {\n calls.push(args);\n return Promise.resolve({\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: new Uint8Array(32) } },\n });\n },\n };\n return { runtime, calls };\n }\n\n test(\"forwards `at: finalized` to dryRunCall when passed per call\", async () => {\n const { runtime, calls } = makeCapturingRuntime();\n const wrapped = wrapContract(runtime, ADDRESS, abi, { origin });\n await (\n wrapped as unknown as {\n getCount: { query: (opts: { at: string }) => Promise<unknown> };\n }\n ).getCount.query({ at: \"finalized\" });\n expect(calls[0]?.[6]).toEqual({ at: \"finalized\" });\n });\n\n test(\"forwards a block hash `at` value to dryRunCall\", async () => {\n const { runtime, calls } = makeCapturingRuntime();\n const wrapped = wrapContract(runtime, ADDRESS, abi, { origin });\n const blockHash = `0x${\"cd\".repeat(32)}` as `0x${string}`;\n await (\n wrapped as unknown as {\n getCount: { query: (opts: { at: string }) => Promise<unknown> };\n }\n ).getCount.query({ at: blockHash });\n expect(calls[0]?.[6]).toEqual({ at: blockHash });\n });\n\n test(\"omits the options argument when no `at` override is passed\", async () => {\n // No per-call override ⇒ the wrap layer must not synthesise an\n // options object; the runtime applies its own default.\n const { runtime, calls } = makeCapturingRuntime();\n const wrapped = wrapContract(runtime, ADDRESS, abi, { origin });\n await (\n wrapped as unknown as { getCount: { query: () => Promise<unknown> } }\n ).getCount.query();\n expect(calls[0]?.[6]).toBeUndefined();\n });\n\n test(\"omits options when only unrelated overrides are passed\", async () => {\n const { runtime, calls } = makeCapturingRuntime();\n const wrapped = wrapContract(runtime, ADDRESS, abi, { origin });\n await (\n wrapped as unknown as {\n getCount: { query: (opts: { value: bigint }) => Promise<unknown> };\n }\n ).getCount.query({ value: 1n });\n expect(calls[0]?.[6]).toBeUndefined();\n });\n });\n\n describe(\"wrapContract — tx/prepare .at option routing\", () => {\n // The `.tx()` and `.prepare()` per-call `at` override pins the\n // sizing dry-run to a specific block. Same wiring as `.query()` —\n // these tests assert the value arrives at `runtime.dryRunCall` as\n // its trailing options arg, and is absent otherwise so the\n // runtime default applies.\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 origin = \"5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY\" as SS58String;\n const fakeSigner = {\n publicKey: new Uint8Array(32),\n } as unknown as PolkadotSigner;\n\n function makeCapturingRuntime(): {\n runtime: ContractRuntime;\n calls: Array<Parameters<ContractRuntime[\"dryRunCall\"]>>;\n } {\n const calls: Array<Parameters<ContractRuntime[\"dryRunCall\"]>> = [];\n const runtime: ContractRuntime = {\n api: {\n tx: {\n Revive: {\n call: () =>\n ({}) as unknown as Awaited<\n ReturnType<ContractRuntime[\"api\"][\"tx\"][\"Revive\"][\"call\"]>\n >,\n },\n },\n } as unknown as ContractRuntime[\"api\"],\n dryRunCall: (...args) => {\n calls.push(args);\n return Promise.resolve({\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: new Uint8Array(0) } },\n });\n },\n };\n return { runtime, calls };\n }\n\n test(\".tx() forwards `at: finalized` to dryRunCall when passed per call\", async () => {\n const { runtime, calls } = makeCapturingRuntime();\n const wrapped = wrapContract(runtime, ADDRESS, abi, {\n origin,\n signer: fakeSigner,\n });\n await (\n wrapped as unknown as {\n increment: { tx: (opts: { at: string }) => Promise<unknown> };\n }\n ).increment\n .tx({ at: \"finalized\" })\n .catch(() => {\n // submit downstream of dryRunCall is irrelevant — only\n // care that the dry-run captured the `at` value.\n });\n expect(calls[0]?.[6]).toEqual({ at: \"finalized\" });\n });\n\n test(\".tx() forwards a block hash `at` value to dryRunCall\", async () => {\n const { runtime, calls } = makeCapturingRuntime();\n const wrapped = wrapContract(runtime, ADDRESS, abi, {\n origin,\n signer: fakeSigner,\n });\n const blockHash = `0x${\"cd\".repeat(32)}` as `0x${string}`;\n await (\n wrapped as unknown as {\n increment: { tx: (opts: { at: string }) => Promise<unknown> };\n }\n ).increment\n .tx({ at: blockHash })\n .catch(() => {});\n expect(calls[0]?.[6]).toEqual({ at: blockHash });\n });\n\n test(\".tx() omits the options argument when no `at` override is passed\", async () => {\n const { runtime, calls } = makeCapturingRuntime();\n const wrapped = wrapContract(runtime, ADDRESS, abi, {\n origin,\n signer: fakeSigner,\n });\n await (wrapped as unknown as { increment: { tx: () => Promise<unknown> } }).increment\n .tx()\n .catch(() => {});\n expect(calls[0]?.[6]).toBeUndefined();\n });\n\n test(\".tx() omits options when only unrelated overrides are passed\", async () => {\n const { runtime, calls } = makeCapturingRuntime();\n const wrapped = wrapContract(runtime, ADDRESS, abi, {\n origin,\n signer: fakeSigner,\n });\n await (\n wrapped as unknown as {\n increment: { tx: (opts: { value: bigint }) => Promise<unknown> };\n }\n ).increment\n .tx({ value: 1n })\n .catch(() => {});\n expect(calls[0]?.[6]).toBeUndefined();\n });\n\n test(\".prepare() forwards `at: finalized` to dryRunCall\", async () => {\n // `.prepare()` shares `buildReviveCall` with `.tx()`; one\n // smoke test pins that `PrepareOptions.at` is wired too.\n const { runtime, calls } = makeCapturingRuntime();\n const wrapped = wrapContract(runtime, ADDRESS, abi, { origin });\n await (\n wrapped as unknown as {\n increment: { prepare: (opts: { at: string }) => Promise<unknown> };\n }\n ).increment\n .prepare({ at: \"finalized\" })\n .catch(() => {});\n expect(calls[0]?.[6]).toEqual({ at: \"finalized\" });\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 pallet-revive account for the dry-run.\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 describe(\"decodeRevert\", () => {\n const abi: AbiEntry[] = [\n {\n type: \"error\",\n name: \"InsufficientBalance\",\n inputs: [\n { name: \"needed\", type: \"uint256\" },\n { name: \"available\", type: \"uint256\" },\n ],\n },\n ];\n\n test(\"decodes a standard Error(string) revert and lifts the reason\", () => {\n // 0x08c379a0 selector + ABI-encoded \"Whoops\".\n const hex =\n \"0x08c379a0\" +\n \"0000000000000000000000000000000000000000000000000000000000000020\" +\n \"0000000000000000000000000000000000000000000000000000000000000006\" +\n \"57686f6f70730000000000000000000000000000000000000000000000000000\";\n const bytes = hexToBytes(hex as HexString);\n const info = decodeRevert([], bytes);\n expect(info.type).toBe(\"ContractRevertedWithPayload\");\n expect(info.reason).toBe(\"Whoops\");\n expect(info.decoded?.errorName).toBe(\"Error\");\n });\n\n test(\"decodes Panic(uint256) and names well-known codes\", () => {\n // 0x4e487b71 selector + panic code 0x11 (arithmetic overflow).\n const hex = `0x4e487b71${\"00\".repeat(31)}11`;\n const info = decodeRevert([], hexToBytes(hex as HexString));\n expect(info.decoded?.errorName).toBe(\"Panic\");\n expect(info.reason).toBe(\"Panic: arithmetic overflow\");\n });\n\n test(\"decodes Panic(uint256) for unknown codes by falling back to the hex code\", () => {\n // 0x4e487b71 selector + panic code 0xff (not a Solidity-defined code).\n const hex = `0x4e487b71${\"00\".repeat(31)}ff`;\n const info = decodeRevert([], hexToBytes(hex as HexString));\n expect(info.decoded?.errorName).toBe(\"Panic\");\n expect(info.reason).toBe(\"Panic(0xff)\");\n });\n\n test(\"decodes an ABI-defined custom error\", () => {\n // InsufficientBalance(uint256,uint256) selector + (1, 2).\n const hex =\n \"0xcf479181\" +\n \"0000000000000000000000000000000000000000000000000000000000000001\" +\n \"0000000000000000000000000000000000000000000000000000000000000002\";\n const info = decodeRevert(abi, hexToBytes(hex as HexString));\n expect(info.decoded?.errorName).toBe(\"InsufficientBalance\");\n expect(info.decoded?.args).toEqual([1n, 2n]);\n expect(info.reason).toBeUndefined();\n });\n\n test(\"falls back to a UTF-8 reason on raw revert(bytes) payloads\", () => {\n const bytes = hexToBytes(\"0x556e617574686f72697a6564\" as HexString);\n const info = decodeRevert([], bytes);\n expect(info.decoded).toBeUndefined();\n expect(info.reason).toBe(\"Unauthorized\");\n expect(info.data).toBe(\"0x556e617574686f72697a6564\");\n });\n\n test(\"leaves reason undefined for opaque non-UTF8 bytes\", () => {\n const info = decodeRevert([], new Uint8Array([0xff, 0xfe, 0xfd]));\n expect(info.decoded).toBeUndefined();\n expect(info.reason).toBeUndefined();\n expect(info.data).toBe(\"0xfffefd\");\n });\n\n test(\"empty payload produces a bare ContractRevertedWithPayload with no extras\", () => {\n const info = decodeRevert([], new Uint8Array(0));\n expect(info).toEqual({ type: \"ContractRevertedWithPayload\", data: \"0x\" });\n });\n });\n\n describe(\"wrapContract — REVERT flag handling\", () => {\n const abi: AbiEntry[] = [\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 stateMutability: \"nonpayable\",\n },\n {\n type: \"function\",\n name: \"ping\",\n inputs: [],\n outputs: [],\n stateMutability: \"nonpayable\",\n },\n ];\n const ADDRESS = \"0x0102030405060708090a0b0c0d0e0f1011121314\";\n const ORIGIN = \"5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY\" as SS58String;\n const fakeSigner = {\n publicKey: new Uint8Array(32),\n } as unknown as PolkadotSigner;\n\n const REVERT_PAYLOAD = hexToBytes(\"0x556e617574686f72697a6564\" as HexString);\n\n function revertingRuntime(data: Uint8Array = REVERT_PAYLOAD): ContractRuntime {\n return {\n api: {\n tx: {\n Revive: {\n call: () => {\n throw new Error(\n \"Revive.call must NOT be invoked when the dry-run reverts\",\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: 5n, proof_size: 5n },\n storage_deposit: { type: \"Refund\", value: 0n },\n max_storage_deposit: { type: \"Refund\", value: 0n },\n gas_consumed: 0n,\n result: {\n success: true,\n value: { flags: 1, data },\n },\n }),\n };\n }\n\n test(\"query() returns success:false with a ContractReverted value when REVERT bit is set\", async () => {\n const wrapped = wrapContract(revertingRuntime(), ADDRESS, abi, {\n signer: fakeSigner,\n origin: ORIGIN,\n });\n\n const result = await (\n wrapped as unknown as {\n transfer: {\n query: (\n to: string,\n amount: bigint,\n ) => Promise<{ success: boolean; value: unknown; gasRequired: unknown }>;\n };\n }\n ).transfer.query(\"0x0000000000000000000000000000000000000001\", 1n);\n\n expect(result.success).toBe(false);\n expect(result.value).toEqual({\n type: \"ContractRevertedWithPayload\",\n data: \"0x556e617574686f72697a6564\",\n reason: \"Unauthorized\",\n });\n expect(result.gasRequired).toEqual({ ref_time: 5n, proof_size: 5n });\n });\n\n test(\"query() handles an empty revert payload end-to-end (revert with no message)\", async () => {\n const wrapped = wrapContract(revertingRuntime(new Uint8Array(0)), ADDRESS, abi, {\n signer: fakeSigner,\n origin: ORIGIN,\n });\n const result = await (\n wrapped as unknown as {\n ping: {\n query: () => Promise<{ success: boolean; value: unknown }>;\n };\n }\n ).ping.query();\n\n expect(result.success).toBe(false);\n expect(result.value).toEqual({ type: \"ContractRevertedWithPayload\", data: \"0x\" });\n });\n\n test(\"tx() throws ContractRevertedError before signing when the dry-run reverts\", async () => {\n const wrapped = wrapContract(revertingRuntime(), ADDRESS, abi, {\n signer: fakeSigner,\n origin: ORIGIN,\n });\n\n await expect(\n (\n wrapped as unknown as {\n transfer: {\n tx: (to: string, amount: bigint) => Promise<unknown>;\n };\n }\n ).transfer.tx(\"0x0000000000000000000000000000000000000001\", 1n),\n ).rejects.toMatchObject({\n name: \"ContractRevertedError\",\n methodName: \"transfer\",\n data: \"0x556e617574686f72697a6564\",\n reason: \"Unauthorized\",\n });\n });\n\n test(\"prepare() throws ContractRevertedError before constructing the extrinsic\", async () => {\n const wrapped = wrapContract(revertingRuntime(), ADDRESS, abi, {});\n\n await expect(\n (\n wrapped as unknown as {\n transfer: {\n prepare: (to: string, amount: bigint) => Promise<unknown>;\n };\n }\n ).transfer.prepare(\"0x0000000000000000000000000000000000000001\", 1n),\n ).rejects.toMatchObject({\n name: \"ContractRevertedError\",\n methodName: \"transfer\",\n reason: \"Unauthorized\",\n });\n });\n\n test(\"revert with a standard Error(string) payload lifts the reason\", async () => {\n // Solidity `require(false, \"nope\")`.\n const errorBytes = hexToBytes(\n (\"0x08c379a0\" +\n \"0000000000000000000000000000000000000000000000000000000000000020\" +\n \"0000000000000000000000000000000000000000000000000000000000000004\" +\n \"6e6f706500000000000000000000000000000000000000000000000000000000\") as HexString,\n );\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: 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: 1, data: errorBytes } },\n }),\n };\n const wrapped = wrapContract(runtime, ADDRESS, abi, {\n signer: fakeSigner,\n origin: ORIGIN,\n });\n const result = await (\n wrapped as unknown as {\n transfer: {\n query: (\n to: string,\n amount: bigint,\n ) => Promise<{ success: boolean; value: { reason?: string } }>;\n };\n }\n ).transfer.query(\"0x0000000000000000000000000000000000000001\", 1n);\n expect(result.success).toBe(false);\n expect(result.value.reason).toBe(\"nope\");\n });\n\n test(\"REVERT bit cleared stays on the happy path - normal return value is decoded\", async () => {\n const returnBytes = new Uint8Array(32);\n returnBytes[31] = 1; // bool true\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: 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: returnBytes } },\n }),\n };\n const wrapped = wrapContract(runtime, ADDRESS, abi, { origin: ORIGIN });\n const result = await (\n wrapped as unknown as {\n transfer: {\n query: (\n to: string,\n amount: bigint,\n ) => Promise<{ success: boolean; value: unknown }>;\n };\n }\n ).transfer.query(\"0x0000000000000000000000000000000000000001\", 1n);\n expect(result.success).toBe(true);\n expect(result.value).toBe(true);\n });\n });\n}\n","// Copyright 2026 Parity Technologies (UK) Ltd.\n// SPDX-License-Identifier: Apache-2.0\nimport 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/**\n * Block reference used to target `ReviveApi.call` dry-runs. Matches PAPI's\n * runtime-call `at` option: `\"best\"`, `\"finalized\"`, or a block hash.\n */\nexport type ContractDryRunAt = \"best\" | \"finalized\" | HexString;\n\n/**\n * Per-call options accepted by {@link ReviveDryRunCall}.\n *\n * Note on the trailing `options` arg: pallet-revive's Rust runtime API\n * `ReviveApi::call` only takes the 6 positional args (origin, dest, value,\n * gas_limit, storage_deposit_limit, input_data). This 7th `options` object\n * is **injected by PAPI** on every `api.apis.X.Y` runtime-API call via its\n * `WithCallOptions` type wrapper (see\n * `polkadot-api/packages/client/src/viewFns.ts:9-11` upstream — defined\n * as `WithCallOptions$2` in the bundled `.d.ts` due to TS bundler suffixing).\n * It never reaches the Rust side — PAPI's `viewFns.ts:38-41` consumes it in\n * JS, reads `options.at`, resolves it to a concrete block hash via the\n * chain-head's runtime context, and uses that hash on the JSON-RPC\n * `state_call` invocation. The 6-arg payload sent over the wire is\n * unchanged.\n */\nexport interface ReviveDryRunCallOptions {\n at?: ContractDryRunAt;\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 options?: ReviveDryRunCallOptions,\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 options?: ReviveDryRunCallOptions,\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 * });\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 * The runtime default `at` (set via {@link ContractRuntimeOptions.at} on\n * the factory) is applied when the caller does not pass an explicit\n * `options.at`. `.query()` per-call overrides flow through this argument.\n */\n readonly dryRunCall: ReviveDryRunCall;\n}\n\n/** Options for {@link createContractRuntime} / {@link createContractRuntimeFromClient}. */\nexport interface ContractRuntimeOptions {\n /**\n * Block to target for `ReviveApi.call` dry-runs. Defaults to `\"best\"`\n * so contract `.query()` reads observe the same state as transactions\n * resolved at best-block (the product-sdk `.tx()` default). Set to\n * `\"finalized\"` to read the canonical, lagged state, or to a specific\n * block hash to pin reads to a historical block. Can be overridden per\n * call via `QueryOptions.at`.\n */\n at?: ContractDryRunAt;\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(\n api: ReviveTypedApi,\n options?: ContractRuntimeOptions,\n): ContractRuntime {\n const defaultAt: ContractDryRunAt = options?.at ?? \"best\";\n return {\n api,\n dryRunCall: (origin, dest, value, gas, deposit, data, callOpts) =>\n api.apis.ReviveApi.call(origin, dest, value, gas, deposit, data, {\n at: callOpts?.at ?? defaultAt,\n }),\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 options?: ContractRuntimeOptions,\n): ContractRuntime {\n const defaultAt: ContractDryRunAt = options?.at ?? \"best\";\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, callOpts) =>\n unsafe.apis.ReviveApi.call(origin, dest, value, gas, deposit, data, {\n at: callOpts?.at ?? defaultAt,\n }),\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 describe(\"createContractRuntime — at-block routing\", () => {\n // The contract is: a `.query()` against the runtime resolves at\n // best-block by default so it observes the same state as `.tx()` /\n // `batchSubmitAndWatch()` (which resolve at best-block). Callers can\n // override per call. These tests pin both behaviours by capturing the\n // options that arrive at the underlying `ReviveApi.call`.\n const origin = \"5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY\" as SS58String;\n const dest = \"0x0102030405060708090a0b0c0d0e0f1011121314\" as HexString;\n\n function makeApi(): {\n api: ReviveTypedApi;\n calls: Array<unknown[]>;\n } {\n const calls: Array<unknown[]> = [];\n const api = {\n apis: {\n ReviveApi: {\n call: (...args: unknown[]) => {\n calls.push(args);\n return Promise.resolve({\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: {\n success: true,\n value: { flags: 0, data: new Uint8Array(0) },\n },\n });\n },\n },\n },\n } as unknown as ReviveTypedApi;\n return { api, calls };\n }\n\n test(\"defaults to `at: best` when no option is passed to the factory\", async () => {\n const { api, calls } = makeApi();\n const runtime = createContractRuntime(api);\n await runtime.dryRunCall(origin, dest, 0n, undefined, undefined, new Uint8Array(0));\n expect(calls[0]?.[6]).toEqual({ at: \"best\" });\n });\n\n test(\"respects an explicit factory default\", async () => {\n const { api, calls } = makeApi();\n const runtime = createContractRuntime(api, { at: \"finalized\" });\n await runtime.dryRunCall(origin, dest, 0n, undefined, undefined, new Uint8Array(0));\n expect(calls[0]?.[6]).toEqual({ at: \"finalized\" });\n });\n\n test(\"per-call `at` option overrides the factory default\", async () => {\n const { api, calls } = makeApi();\n const runtime = createContractRuntime(api, { at: \"best\" });\n const blockHash = `0x${\"ab\".repeat(32)}` as HexString;\n await runtime.dryRunCall(origin, dest, 0n, undefined, undefined, new Uint8Array(0), {\n at: blockHash,\n });\n expect(calls[0]?.[6]).toEqual({ at: blockHash });\n });\n\n test(\"explicit per-call `at: best` is forwarded even when factory default is `finalized`\", async () => {\n const { api, calls } = makeApi();\n const runtime = createContractRuntime(api, { at: \"finalized\" });\n await runtime.dryRunCall(origin, dest, 0n, undefined, undefined, new Uint8Array(0), {\n at: \"best\",\n });\n expect(calls[0]?.[6]).toEqual({ at: \"best\" });\n });\n });\n\n describe(\"createContractRuntimeFromClient — at-block routing\", () => {\n // The production factory has its own (parallel) `at` plumbing\n // through `getUnsafeApi()`. A regression in this branch would slip\n // past the typed-API tests above, so we mock the client and pin the\n // same contract: factory default applied, per-call override wins.\n const origin = \"5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY\" as SS58String;\n const dest = \"0x0102030405060708090a0b0c0d0e0f1011121314\" as HexString;\n\n function makeClient(): {\n client: PolkadotClient;\n calls: Array<unknown[]>;\n } {\n const calls: Array<unknown[]> = [];\n const unsafe = {\n apis: {\n ReviveApi: {\n call: (...args: unknown[]) => {\n calls.push(args);\n return Promise.resolve({\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: {\n success: true,\n value: { flags: 0, data: new Uint8Array(0) },\n },\n });\n },\n },\n },\n };\n const client = {\n getTypedApi: () => ({}) as never,\n getUnsafeApi: () => unsafe,\n } as unknown as PolkadotClient;\n return { client, calls };\n }\n\n test(\"defaults to `at: best` when no factory option is set\", async () => {\n const { client, calls } = makeClient();\n const runtime = createContractRuntimeFromClient(client, {});\n await runtime.dryRunCall(origin, dest, 0n, undefined, undefined, new Uint8Array(0));\n expect(calls[0]?.[6]).toEqual({ at: \"best\" });\n });\n\n test(\"respects an explicit factory default\", async () => {\n const { client, calls } = makeClient();\n const runtime = createContractRuntimeFromClient(client, {}, { at: \"finalized\" });\n await runtime.dryRunCall(origin, dest, 0n, undefined, undefined, new Uint8Array(0));\n expect(calls[0]?.[6]).toEqual({ at: \"finalized\" });\n });\n\n test(\"per-call `at` overrides factory default through the unsafe-API path\", async () => {\n const { client, calls } = makeClient();\n const runtime = createContractRuntimeFromClient(client, {}, { at: \"best\" });\n await runtime.dryRunCall(origin, dest, 0n, undefined, undefined, new Uint8Array(0), {\n at: \"finalized\",\n });\n expect(calls[0]?.[6]).toEqual({ at: \"finalized\" });\n });\n\n test(\"explicit per-call `at: best` is forwarded even when factory default is `finalized`\", async () => {\n const { client, calls } = makeClient();\n const runtime = createContractRuntimeFromClient(client, {}, { at: \"finalized\" });\n await runtime.dryRunCall(origin, dest, 0n, undefined, undefined, new Uint8Array(0), {\n at: \"best\",\n });\n expect(calls[0]?.[6]).toEqual({ at: \"best\" });\n });\n });\n}\n","// Copyright 2026 Parity Technologies (UK) Ltd.\n// SPDX-License-Identifier: Apache-2.0\nimport type { HexString, PolkadotClient, SS58String } from \"polkadot-api\";\nimport { wrapContract } from \"./wrap.js\";\nimport { ContractLiveAddressResolutionError, ContractNotFoundError } from \"./errors.js\";\nimport type { ContractRuntime, ContractRuntimeOptions } 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 LiveContractResolutionOptions,\n} from \"./types.js\";\n\ntype ContractMap = Record<string, CdmJsonContract>;\ntype OptionAddress = { isSome: boolean; value: HexString };\ntype LiveVersionSpec = number | \"latest\";\n\nconst CDM_REGISTRY_ABI: AbiEntry[] = [\n {\n type: \"function\",\n name: \"getAddress\",\n inputs: [{ name: \"contract_name\", type: \"string\" }],\n outputs: [\n {\n name: \"\",\n type: \"tuple\",\n components: [\n { name: \"isSome\", type: \"bool\" },\n { name: \"value\", type: \"address\" },\n ],\n },\n ],\n stateMutability: \"view\",\n },\n {\n type: \"function\",\n name: \"getAddressAtVersion\",\n inputs: [\n { name: \"contract_name\", type: \"string\" },\n { name: \"version\", type: \"uint32\" },\n ],\n outputs: [\n {\n name: \"\",\n type: \"tuple\",\n components: [\n { name: \"isSome\", type: \"bool\" },\n { name: \"value\", type: \"address\" },\n ],\n },\n ],\n stateMutability: \"view\",\n },\n];\n\nfunction cloneCdmJson(cdmJson: CdmJson): CdmJson {\n const cloneContractMap = (contracts: ContractMap): ContractMap =>\n Object.fromEntries(\n Object.entries(contracts).map(([library, contract]) => [library, { ...contract }]),\n );\n\n return {\n ...cdmJson,\n dependencies: { ...cdmJson.dependencies },\n contracts: cdmJson.contracts ? cloneContractMap(cdmJson.contracts) : undefined,\n };\n}\n\nfunction resolveRegistryAddress(cdmJson: CdmJson, override?: HexString): HexString {\n if (override) return override;\n if (cdmJson.registry) return cdmJson.registry;\n throw new ContractLiveAddressResolutionError(\n \"CDM registry address is required for live contract address resolution. Pass registryAddress or set cdm.json registry.\",\n );\n}\n\nfunction patchContractAddress(cdmJson: CdmJson, library: string, address: HexString): void {\n const contract = cdmJson.contracts?.[library];\n if (!contract) {\n throw new ContractNotFoundError(library);\n }\n contract.address = address;\n}\n\nfunction resolveLiveVersionSpec(\n cdmJson: CdmJson,\n library: string,\n contract: CdmJsonContract,\n): LiveVersionSpec {\n const requested = cdmJson.dependencies[library];\n if (typeof requested === \"number\" && Number.isInteger(requested) && requested >= 0) {\n return requested;\n }\n if (typeof requested === \"string\") {\n if (requested === \"latest\") return \"latest\";\n const parsed = Number(requested);\n if (Number.isInteger(parsed) && parsed >= 0) return parsed;\n }\n return contract.version;\n}\n\nasync function queryLiveAddress(\n registry: Contract<ContractDef>,\n library: string,\n version: LiveVersionSpec,\n): Promise<HexString> {\n const result =\n version === \"latest\"\n ? await registry.getAddress.query(library)\n : await registry.getAddressAtVersion.query(library, version);\n if (!result.success) {\n throw new ContractLiveAddressResolutionError(\n version === \"latest\"\n ? `Failed to resolve live address for \"${library}\" from the CDM registry`\n : `Failed to resolve live address for \"${library}\" version ${version} from the CDM registry`,\n { library, detail: result.value },\n );\n }\n\n const value = result.value as OptionAddress | undefined;\n if (!value?.isSome) {\n throw new ContractLiveAddressResolutionError(\n version === \"latest\"\n ? `Contract \"${library}\" is not registered in the CDM registry`\n : `Contract \"${library}\" version ${version} is not registered in the CDM registry`,\n { library, detail: result.value },\n );\n }\n\n return value.value;\n}\n\n/**\n * Return a cloned manifest whose installed contract addresses have been\n * replaced by live addresses from the CDM registry.\n *\n * This is intentionally strict: if a requested library cannot be resolved\n * from the registry, the promise rejects. Use `new ContractManager(...)` or\n * `ContractManager.fromClient(...)` directly for snapshot-only behavior.\n */\nexport async function withLiveContractAddresses(\n cdmJson: CdmJson,\n runtime: ContractRuntime,\n options?: LiveContractResolutionOptions,\n): Promise<CdmJson> {\n const contracts = cdmJson.contracts;\n if (!contracts || Object.keys(contracts).length === 0) {\n throw new ContractLiveAddressResolutionError(\n \"No installed contracts found in cdm.json for live address resolution.\",\n );\n }\n\n const libraries = options?.libraries ?? Object.keys(contracts);\n for (const library of libraries) {\n if (!(library in contracts)) {\n throw new ContractNotFoundError(library);\n }\n }\n\n const registryAddress = resolveRegistryAddress(cdmJson, options?.registryAddress);\n const registry = createContract(runtime, registryAddress, CDM_REGISTRY_ABI, {\n defaultOrigin: options?.registryOrigin,\n });\n const liveAddresses = await Promise.all(\n libraries.map(async (library): Promise<readonly [string, HexString]> => {\n const version = resolveLiveVersionSpec(cdmJson, library, contracts[library]);\n return [library, await queryLiveAddress(registry, library, version)];\n }),\n );\n\n const resolved = cloneCdmJson(cdmJson);\n for (const [library, address] of liveAddresses) {\n patchContractAddress(resolved, library, address);\n }\n return resolved;\n}\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 * });\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 contracts: ContractMap | undefined;\n private runtime: ContractRuntime;\n private defaults: ContractDefaults;\n\n constructor(cdmJson: CdmJson, runtime: ContractRuntime, options?: ContractManagerOptions) {\n this.runtime = runtime;\n this.contracts = cdmJson.contracts;\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 & ContractRuntimeOptions,\n ): ContractManager {\n return new ContractManager(\n cdmJson,\n createContractRuntimeFromClient(client, descriptor, options),\n options,\n );\n }\n\n /**\n * Create a manager after strictly resolving installed contract addresses\n * from the live CDM registry. ABIs still come from the installed manifest.\n */\n static async fromLive(\n cdmJson: CdmJson,\n runtime: ContractRuntime,\n options?: ContractManagerOptions & LiveContractResolutionOptions,\n ): Promise<ContractManager> {\n const resolved = await withLiveContractAddresses(cdmJson, runtime, {\n ...options,\n registryOrigin: options?.registryOrigin ?? (options?.defaultOrigin as SS58String),\n });\n return new ContractManager(resolved, runtime, options);\n }\n\n /**\n * Convenience factory for {@link fromLive} when the caller has a raw\n * `PolkadotClient` and descriptor.\n */\n static async fromLiveClient<TDescriptor>(\n cdmJson: CdmJson,\n client: PolkadotClient,\n descriptor: TDescriptor,\n options?: ContractManagerOptions & ContractRuntimeOptions & LiveContractResolutionOptions,\n ): Promise<ContractManager> {\n const runtime = createContractRuntimeFromClient(client, descriptor, options);\n return ContractManager.fromLive(cdmJson, runtime, options);\n }\n\n private getContractData(library: string): CdmJsonContract {\n if (!this.contracts || !(library in this.contracts)) {\n throw new ContractNotFoundError(library);\n }\n return this.contracts[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 & ContractRuntimeOptions,\n): Contract<ContractDef> {\n return createContract(\n createContractRuntimeFromClient(client, descriptor, options),\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 * current CDM installs. 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 * - `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 dependencies: {\n \"@w3s/playground-registry\": \"latest\",\n },\n contracts: {\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 const flattenedCdm: CdmJson = {\n registry: \"0x9999999999999999999999999999999999999999\",\n dependencies: {\n \"@w3s/playground-registry\": \"latest\",\n },\n contracts: {\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 },\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 async function registryRuntimeFor(\n value:\n | OptionAddress\n | ((call: { functionName: string; args: readonly unknown[] }) => OptionAddress),\n ): Promise<{ runtime: ContractRuntime; calls: { functionName: string; args: unknown[] }[] }> {\n const { bytesToHex, decodeFunctionData, encodeFunctionResult, hexToBytes } = await import(\n \"viem\"\n );\n const calls: { functionName: string; args: unknown[] }[] = [];\n\n const runtime = {\n ...fakeRuntime(),\n dryRunCall: async (\n _origin: unknown,\n _dest: unknown,\n _value: unknown,\n _gasLimit: unknown,\n _storageDepositLimit: unknown,\n calldata: Uint8Array,\n ) => {\n const decoded = decodeFunctionData({\n abi: CDM_REGISTRY_ABI as any,\n data: bytesToHex(calldata),\n });\n const call = {\n functionName: decoded.functionName,\n args: [...(decoded.args ?? [])],\n };\n calls.push(call);\n const resultValue = typeof value === \"function\" ? value(call) : value;\n const data = hexToBytes(\n encodeFunctionResult({\n abi: CDM_REGISTRY_ABI as any,\n functionName: decoded.functionName,\n result: resultValue,\n }) as `0x${string}`,\n );\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\" as const, value: 0n },\n max_storage_deposit: { type: \"Refund\" as const, value: 0n },\n gas_consumed: 0n,\n result: { success: true, value: { flags: 0, data } },\n };\n },\n };\n return { runtime, calls };\n }\n\n describe(\"ContractManager — cdm.json resolution\", () => {\n test(\"constructs from a flat cdm.json\", () => {\n const manager = new ContractManager(flattenedCdm, fakeRuntime());\n expect(manager.getAddress(\"@w3s/playground-registry\")).toBe(\n \"0x4A37B123b0BA2A894cA5953f472264921d44e298\",\n );\n });\n\n test(\"getContract returns a typed handle from a flattened cdm.json\", () => {\n const manager = new ContractManager(flattenedCdm, 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 });\n\n test(\"getContract throws without target wording for a flattened cdm.json miss\", () => {\n const manager = new ContractManager(flattenedCdm, fakeRuntime());\n expect(() => manager.getContract(\"@nonexistent/contract\")).toThrow(\n 'Contract \"@nonexistent/contract\" not found in cdm.json',\n );\n });\n\n test(\"strictly patches flattened manifests with live registry addresses\", async () => {\n const liveAddress = \"0x7777777777777777777777777777777777777777\";\n const { runtime, calls } = await registryRuntimeFor({\n isSome: true,\n value: liveAddress,\n });\n\n const resolved = await withLiveContractAddresses(flattenedCdm, runtime, {\n registryOrigin: \"5LiveOrigin\" as SS58String,\n });\n\n expect(resolved.contracts?.[\"@w3s/playground-registry\"].address).toBe(liveAddress);\n expect(calls[0]).toMatchObject({\n functionName: \"getAddress\",\n args: [\"@w3s/playground-registry\"],\n });\n expect(flattenedCdm.contracts?.[\"@w3s/playground-registry\"].address).toBe(\n \"0x4A37B123b0BA2A894cA5953f472264921d44e298\",\n );\n });\n\n test(\"uses versioned registry lookup for pinned dependencies\", async () => {\n const latestAddress = \"0x7777777777777777777777777777777777777777\";\n const versionedAddress = \"0x6666666666666666666666666666666666666666\";\n const { runtime, calls } = await registryRuntimeFor(({ functionName }) => ({\n isSome: true,\n value: functionName === \"getAddressAtVersion\" ? versionedAddress : latestAddress,\n }));\n const pinnedCdm: CdmJson = {\n ...flattenedCdm,\n dependencies: {\n \"@w3s/playground-registry\": 6,\n },\n };\n\n const resolved = await withLiveContractAddresses(pinnedCdm, runtime);\n\n expect(resolved.contracts?.[\"@w3s/playground-registry\"].address).toBe(versionedAddress);\n expect(calls[0]).toMatchObject({\n functionName: \"getAddressAtVersion\",\n args: [\"@w3s/playground-registry\", 6],\n });\n });\n\n test(\"falls back to installed contract version when dependency entry is absent\", async () => {\n const versionedAddress = \"0x5555555555555555555555555555555555555555\";\n const { runtime, calls } = await registryRuntimeFor({\n isSome: true,\n value: versionedAddress,\n });\n const missingDependencyCdm: CdmJson = {\n ...flattenedCdm,\n dependencies: {},\n };\n\n const resolved = await withLiveContractAddresses(missingDependencyCdm, runtime);\n\n expect(resolved.contracts?.[\"@w3s/playground-registry\"].address).toBe(versionedAddress);\n expect(calls[0]).toMatchObject({\n functionName: \"getAddressAtVersion\",\n args: [\"@w3s/playground-registry\", 6],\n });\n });\n\n test(\"live registry resolution fails instead of falling back to the snapshot\", async () => {\n const { runtime } = await registryRuntimeFor({\n isSome: false,\n value: \"0x0000000000000000000000000000000000000000\",\n });\n\n await expect(withLiveContractAddresses(flattenedCdm, runtime)).rejects.toThrow(\n /not registered/,\n );\n });\n\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(\"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\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
|
@@ -122,7 +122,6 @@ type ReviveDryRunCall = (origin: SS58String, dest: HexString, value: bigint, gas
|
|
|
122
122
|
*
|
|
123
123
|
* const client = await createChainClient({
|
|
124
124
|
* chains: { assetHub: paseo_asset_hub },
|
|
125
|
-
* rpcs: { assetHub: ["wss://paseo-asset-hub-next-rpc.polkadot.io"] },
|
|
126
125
|
* });
|
|
127
126
|
* const runtime = createContractRuntimeFromClient(client.raw.assetHub, paseo_asset_hub);
|
|
128
127
|
* ```
|
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.7.
|
|
4
|
+
"version": "0.7.1",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
7
7
|
"types": "./dist/index.d.ts",
|
|
@@ -30,11 +30,11 @@
|
|
|
30
30
|
"@polkadot-labs/hdkd-helpers": "^0.0.30",
|
|
31
31
|
"polkadot-api": "^2.1.5",
|
|
32
32
|
"viem": "^2.52.0",
|
|
33
|
-
"@parity/product-sdk-keys": "0.3.3",
|
|
34
|
-
"@parity/product-sdk-logger": "0.1.1",
|
|
35
|
-
"@parity/product-sdk-signer": "0.6.0",
|
|
36
33
|
"@parity/product-sdk-address": "0.1.1",
|
|
37
|
-
"@parity/product-sdk-tx": "0.2.
|
|
34
|
+
"@parity/product-sdk-tx": "0.2.8",
|
|
35
|
+
"@parity/product-sdk-signer": "0.6.1",
|
|
36
|
+
"@parity/product-sdk-keys": "0.3.4",
|
|
37
|
+
"@parity/product-sdk-logger": "0.1.1"
|
|
38
38
|
},
|
|
39
39
|
"devDependencies": {
|
|
40
40
|
"tsup": "^8.5.1",
|