@parity/product-sdk-contracts 0.0.0-dev.421.0 → 0.0.0-dev.457.0
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 +1 -1
- package/dist/codegen.d.ts +2 -1
- package/dist/index.d.ts +3 -2
- package/dist/index.js.map +1 -1
- package/dist/pvm.d.ts +2 -1
- package/dist/testing.d.ts +2 -1
- package/dist/{types-Dtu4IsSh.d.ts → types-DKim_9fW.d.ts} +6 -5
- package/package.json +9 -8
package/README.md
CHANGED
|
@@ -53,7 +53,7 @@ Synchronous factory. Builds a `ContractRuntime` internally that wires the typed
|
|
|
53
53
|
| `descriptor` | `ChainDefinition` | E.g. `paseo_asset_hub` from `@parity/product-sdk-descriptors` |
|
|
54
54
|
| `options.signerManager?` | `SignerManager` | Resolves signer + origin from the logged-in account |
|
|
55
55
|
| `options.defaultOrigin?` | `SS58String` | Static fallback origin for queries |
|
|
56
|
-
| `options.defaultSigner?` | `
|
|
56
|
+
| `options.defaultSigner?` | `CommonSignerTxCreator` | Static fallback signer for txs, a PAPI 3 transaction creator |
|
|
57
57
|
|
|
58
58
|
### `manager.getContract(library)`
|
|
59
59
|
|
package/dist/codegen.d.ts
CHANGED
package/dist/index.d.ts
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { Result } from '@parity/result';
|
|
2
2
|
import { PolkadotClient, HexString, SS58String } from 'polkadot-api';
|
|
3
|
-
import { C as CdmJson, a as ContractRuntime, b as ContractManagerOptions, c as ContractDefaults, d as ContractRuntimeOptions, L as LiveContractResolutionOptions, e as ContractError, f as Contracts, g as Contract, h as ContractDef, A as AbiEntry, i as ContractOptions } from './types-
|
|
4
|
-
export { j as AbiParam, k as CdmJsonContract, l as CdmJsonDependencyVersion, m as ContractDryRunAt, n as ContractDryRunFailedError, o as ContractInvalidOriginError, p as ContractLiveAddressResolutionError, q as ContractNotFoundError, r as ContractRevertInfo, s as ContractRevertedError, t as ContractSignerMissingError, D as DecodedContractRevert, P as PrepareOptions, Q as QueryOptions, u as QueryResult, R as ReviveDryRunCall, v as ReviveDryRunCallOptions, w as ReviveDryRunResult, x as ReviveTypedApi, T as TxOptions, y as createContractRuntime, z as createContractRuntimeFromClient, B as ensureContractAccountMapped, E as isContractAccountMapped } from './types-
|
|
3
|
+
import { C as CdmJson, a as ContractRuntime, b as ContractManagerOptions, c as ContractDefaults, d as ContractRuntimeOptions, L as LiveContractResolutionOptions, e as ContractError, f as Contracts, g as Contract, h as ContractDef, A as AbiEntry, i as ContractOptions } from './types-DKim_9fW.js';
|
|
4
|
+
export { j as AbiParam, k as CdmJsonContract, l as CdmJsonDependencyVersion, m as ContractDryRunAt, n as ContractDryRunFailedError, o as ContractInvalidOriginError, p as ContractLiveAddressResolutionError, q as ContractNotFoundError, r as ContractRevertInfo, s as ContractRevertedError, t as ContractSignerMissingError, D as DecodedContractRevert, P as PrepareOptions, Q as QueryOptions, u as QueryResult, R as ReviveDryRunCall, v as ReviveDryRunCallOptions, w as ReviveDryRunResult, x as ReviveTypedApi, T as TxOptions, y as createContractRuntime, z as createContractRuntimeFromClient, B as ensureContractAccountMapped, E as isContractAccountMapped } from './types-DKim_9fW.js';
|
|
5
5
|
export { BatchableCall, TxResult } from '@parity/product-sdk-tx';
|
|
6
|
+
import '@polkadot-api/signers-common';
|
|
6
7
|
import '@parity/product-sdk-signer';
|
|
7
8
|
import '@parity/product-sdk-errors';
|
|
8
9
|
|
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":["ok","err"],"mappings":"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","file":"index.js","sourcesContent":["// Copyright 2026 Parity Technologies (UK) Ltd.\n// SPDX-License-Identifier: Apache-2.0\nimport { isValidH160 } from \"@parity/product-sdk-address\";\nimport type { SdkError } from \"@parity/product-sdk-errors\";\nimport type { HexString } from \"polkadot-api\";\n\n/**\n * Base class for all contract errors. Use `instanceof ContractError` to catch any\n * contract-related error. Implements the cross-package {@link SdkError} marker so\n * `isSdkError(e)` also recognizes it.\n */\nexport class ContractError extends Error implements SdkError {\n readonly isSdkError = true as const;\n readonly source = \"contracts\";\n\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/**\n * A dry-run / transaction origin was not a valid SS58 address.\n *\n * Raised *before* the origin reaches PAPI's `AccountId` codec, which would\n * otherwise fail deep inside the encode stack with a bare `Invalid checksum`\n * and no hint that the origin was the wrong format. The most common mistake\n * is passing the account's H160 (`0x…`): pallet-revive derives the H160\n * `msg.sender` *from* the SS58 origin, so the origin itself must be SS58 —\n * the message calls that case out with a `h160ToSs58` pointer.\n *\n * Deliberately a validation error, not an auto-conversion: silently converting\n * an H160 here would hide which account the caller believes is calling.\n */\nexport class ContractInvalidOriginError extends ContractError {\n /** The rejected origin value. */\n readonly origin: string;\n\n constructor(origin: string) {\n const h160Hint = isValidH160(origin)\n ? \" (looks like an H160 — convert it with h160ToSs58 from @parity/product-sdk-address first)\"\n : \"\";\n super(`Contract call origin must be an SS58 address, got \"${origin}\"${h160Hint}`);\n this.name = \"ContractInvalidOriginError\";\n this.origin = origin;\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 ContractInvalidOriginError(\"0xabc\")).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(\"ContractInvalidOriginError\", () => {\n test(\"captures the rejected origin\", () => {\n const err = new ContractInvalidOriginError(\"not-an-address\");\n expect(err.name).toBe(\"ContractInvalidOriginError\");\n expect(err.origin).toBe(\"not-an-address\");\n expect(err.message).toContain(\"SS58\");\n expect(err.message).toContain(\"not-an-address\");\n expect(err.message).not.toContain(\"h160ToSs58\");\n });\n\n test(\"adds the h160ToSs58 hint when the origin looks like an H160\", () => {\n const h160 = \"0x9621dde636de098b43efb0fa9b61facfe328f99d\";\n const err = new ContractInvalidOriginError(h160);\n expect(err.origin).toBe(h160);\n expect(err.message).toContain(\"looks like an H160\");\n expect(err.message).toContain(\"h160ToSs58\");\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 { isValidSs58 } from \"@parity/product-sdk-address\";\nimport { createLogger } from \"@parity/product-sdk-logger\";\nimport { type Result, err, ok, unwrapErr, unwrapOk } from \"@parity/result\";\nimport { submitAndWatch } from \"@parity/product-sdk-tx\";\nimport type {\n BatchableCall,\n SubmittableTransaction,\n TxError,\n TxResult,\n} from \"@parity/product-sdk-tx\";\nimport { ss58Address } from \"@polkadot-labs/hdkd-helpers\";\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\";\n\nimport {\n type ContractError,\n ContractDryRunFailedError,\n ContractInvalidOriginError,\n ContractRevertedError,\n ContractSignerMissingError,\n type ContractRevertInfo,\n} from \"./errors.js\";\nimport type { ContractRuntime } from \"./runtime.js\";\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 the fallback origin for read-only\n * queries when no wallet is connected. The runtime API requires an origin, so\n * we pass this account when there is no connected one.\n *\n * Exported so other products can reuse the exact same read origin instead of\n * re-deriving it: pass it as an explicit `defaultOrigin` / `registryOrigin`,\n * or compare against it. Its SS58 (substrate generic, prefix 42) value is\n * `5EYCAe5ijiYfhaAUBd6H9WGRTsvwFFc7GnhQkiHvBYxdvpbV`.\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\"));\nexport const 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 * Validate a resolved origin *before* it reaches PAPI's `AccountId` codec,\n * which otherwise fails deep inside the encode stack with a bare\n * `Invalid checksum` and no hint that the origin was the wrong format.\n * Returns the typed error (with an H160-specific hint — the recurring\n * consumer mistake is passing the account's `0x…` H160) or `null` when the\n * origin is a well-formed SS58 address. Validation only, no auto-conversion —\n * see {@link ContractInvalidOriginError}.\n */\nfunction validateOrigin(origin: SS58String): ContractInvalidOriginError | null {\n return isValidSs58(origin) ? null : new ContractInvalidOriginError(origin);\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 * Returns a {@link Result}: `ok(SubmittableTransaction)` — what `.tx()` hands to\n * `submitAndWatch` and what `.prepare()` returns as a `BatchableCall` — or\n * `err(ContractError)` if the pre-submit dry-run fails or the call reverts.\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<Result<SubmittableTransaction, ContractError>> {\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 return err(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 return err(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 ok(\n 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/**\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 // QueryResult has no error channel, so an invalid origin\n // is thrown here (the one deliberate asymmetry with\n // .tx()/.prepare(), which return it as err(...)).\n const originError = validateOrigin(origin);\n if (originError) throw originError;\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 (\n ...args: unknown[]\n ): Promise<Result<TxResult, ContractError | TxError>> => {\n const { positionalArgs, overrides } = extractOverrides<TxOptions>(\n argNames,\n args,\n );\n const signer = resolveSigner(defaults, overrides?.signer);\n if (!signer) {\n return err(new ContractSignerMissingError());\n }\n\n const origin =\n resolveOrigin(defaults, overrides?.origin) ??\n (ss58Address(signer.publicKey) as SS58String);\n const originError = validateOrigin(origin);\n if (originError) return err(originError);\n\n const built = await buildReviveCall(\n runtime,\n dest,\n abi,\n methodName,\n positionalArgs,\n origin,\n overrides,\n );\n if (!built.ok) return built;\n\n return submitAndWatch(built.value, signer, {\n waitFor: overrides?.waitFor,\n timeoutMs: overrides?.timeoutMs,\n mortalityPeriod: overrides?.mortalityPeriod,\n onStatus: overrides?.onStatus,\n });\n },\n\n prepare: async (\n ...args: unknown[]\n ): Promise<Result<BatchableCall, ContractError>> => {\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 const originError = validateOrigin(origin);\n if (originError) return err(originError);\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 test(\"query fallback derives pallet-revive's keyless account\", () => {\n // Frozen public value: other products import QUERY_FALLBACK_ORIGIN\n // to reuse this exact read origin. A regression here breaks them.\n expect(QUERY_FALLBACK_ORIGIN).toBe(\"5EYCAe5ijiYfhaAUBd6H9WGRTsvwFFc7GnhQkiHvBYxdvpbV\");\n const defaults: ContractDefaults = {};\n expect(resolveOrigin(defaults, undefined, true)).toBe(QUERY_FALLBACK_ORIGIN);\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 const error = unwrapErr(\n await (\n wrapped as unknown as {\n increment: { tx: () => Promise<Result<unknown, unknown>> };\n }\n ).increment.tx(),\n );\n expect(error).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 const error = unwrapErr(\n await (\n wrapped as unknown as {\n increment: { tx: (opts: unknown) => Promise<Result<unknown, unknown>> };\n }\n ).increment.tx({ gasLimit: { ref_time: 1n, proof_size: 1n } }),\n );\n expect(error).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 prepared = unwrapOk(\n await (\n wrapped as unknown as {\n add: {\n prepare: (\n n: number,\n opts: unknown,\n ) => Promise<Result<{ decodedCall: unknown }, unknown>>;\n };\n }\n ).add.prepare(7, {\n gasLimit: overrideWeight,\n storageDepositLimit: 42n,\n value: 5n,\n }),\n ) as { decodedCall: unknown };\n\n // SubmittableTransaction is a valid BatchableCall —\n // `batchSubmitAndWatch` reads `.decodedCall` off it.\n expect(prepared.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 const prepared = unwrapOk(\n await (\n wrapped as unknown as {\n increment: { prepare: () => Promise<Result<unknown, unknown>> };\n }\n ).increment.prepare(),\n );\n expect(prepared).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 const error = unwrapErr(\n await (\n wrapped as unknown as {\n increment: { prepare: () => Promise<Result<unknown, unknown>> };\n }\n ).increment.prepare(),\n );\n expect(error).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 = unwrapOk(\n await (\n wrapped as unknown as {\n increment: { prepare: () => Promise<Result<BatchableCall, unknown>> };\n }\n ).increment.prepare(),\n );\n const b = unwrapOk(\n await (\n wrapped as unknown as {\n add: { prepare: (n: number) => Promise<Result<BatchableCall, unknown>> };\n }\n ).add.prepare(1),\n );\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 — origin validation\", () => {\n // Regression suite for the \"bare Invalid checksum\" footgun: a\n // non-SS58 origin (typically the account's H160) must be rejected\n // with a typed, self-explanatory error BEFORE any dry-run happens —\n // never forwarded into PAPI's AccountId codec. `.query()` throws\n // (QueryResult has no error channel); `.tx()`/`.prepare()` return\n // err(ContractInvalidOriginError).\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 H160_ORIGIN = \"0x9621dde636de098b43efb0fa9b61facfe328f99d\";\n const fakeSigner = {\n publicKey: new Uint8Array(32),\n } as unknown as PolkadotSigner;\n\n function guardedRuntime(): ContractRuntime {\n return {\n api: {\n tx: {\n Revive: {\n call: () => {\n throw new Error(\n \"Revive.call must NOT be invoked for an invalid origin\",\n );\n },\n },\n },\n } as unknown as ContractRuntime[\"api\"],\n dryRunCall: () => {\n throw new Error(\"dryRunCall must NOT be invoked for an invalid origin\");\n },\n };\n }\n\n test(\"query() throws ContractInvalidOriginError for an H160 origin, with the conversion hint\", async () => {\n const wrapped = wrapContract(guardedRuntime(), ADDRESS, abi, {\n origin: H160_ORIGIN as SS58String,\n });\n\n const call = (\n wrapped as unknown as { getCount: { query: () => Promise<unknown> } }\n ).getCount.query();\n await expect(call).rejects.toBeInstanceOf(ContractInvalidOriginError);\n await expect(call).rejects.toMatchObject({\n name: \"ContractInvalidOriginError\",\n origin: H160_ORIGIN,\n });\n await expect(call).rejects.toThrow(/h160ToSs58/);\n });\n\n test(\"query() throws for a garbage per-call origin override\", async () => {\n const wrapped = wrapContract(guardedRuntime(), ADDRESS, abi, {\n origin: \"5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY\" as SS58String,\n });\n\n await expect(\n (\n wrapped as unknown as {\n getCount: { query: (opts: { origin: string }) => Promise<unknown> };\n }\n ).getCount.query({ origin: \"not-an-address\" }),\n ).rejects.toBeInstanceOf(ContractInvalidOriginError);\n });\n\n test(\"tx() returns err(ContractInvalidOriginError) for an H160 origin\", async () => {\n const wrapped = wrapContract(guardedRuntime(), ADDRESS, abi, {\n signer: fakeSigner,\n origin: H160_ORIGIN as SS58String,\n });\n\n const error = unwrapErr(\n await (\n wrapped as unknown as {\n getCount: { tx: () => Promise<Result<unknown, unknown>> };\n }\n ).getCount.tx(),\n );\n expect(error).toBeInstanceOf(ContractInvalidOriginError);\n expect(error).toMatchObject({ origin: H160_ORIGIN });\n expect((error as ContractInvalidOriginError).message).toContain(\"h160ToSs58\");\n });\n\n test(\"prepare() returns err(ContractInvalidOriginError) for an H160 origin\", async () => {\n const wrapped = wrapContract(guardedRuntime(), ADDRESS, abi, {\n origin: H160_ORIGIN as SS58String,\n });\n\n const error = unwrapErr(\n await (\n wrapped as unknown as {\n getCount: { prepare: () => Promise<Result<unknown, unknown>> };\n }\n ).getCount.prepare(),\n );\n expect(error).toBeInstanceOf(ContractInvalidOriginError);\n expect(error).toMatchObject({ origin: H160_ORIGIN });\n });\n\n test(\"a valid SS58 origin still flows through to the dry-run\", async () => {\n let capturedOrigin: string | undefined;\n const runtime: ContractRuntime = {\n api: {} 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: 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 const alice = \"5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY\" as SS58String;\n const wrapped = wrapContract(runtime, ADDRESS, abi, { origin: alice });\n\n const result = await (\n wrapped as unknown as {\n getCount: { query: () => Promise<{ success: boolean }> };\n }\n ).getCount.query();\n\n expect(result.success).toBe(true);\n expect(capturedOrigin).toBe(alice);\n });\n\n test(\"the query fallback origin passes validation (no-wallet path keeps working)\", async () => {\n let invoked = false;\n const runtime: ContractRuntime = {\n api: {} as unknown as ContractRuntime[\"api\"],\n dryRunCall: async () => {\n invoked = 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: true, value: { flags: 0, data: new Uint8Array(32) } },\n };\n },\n };\n // No origin configured anywhere — resolveOrigin falls back to\n // QUERY_FALLBACK_ORIGIN, which must be considered valid.\n const wrapped = wrapContract(runtime, ADDRESS, abi, {});\n await (\n wrapped as unknown as { getCount: { query: () => Promise<unknown> } }\n ).getCount.query();\n expect(invoked).toBe(true);\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 const error = unwrapErr(\n await (\n wrapped as unknown as {\n transfer: {\n tx: (to: string, amount: bigint) => Promise<Result<unknown, unknown>>;\n };\n }\n ).transfer.tx(\"0x0000000000000000000000000000000000000001\", 1n),\n );\n expect(error).toMatchObject({\n name: \"ContractRevertedError\",\n methodName: \"transfer\",\n data: \"0x556e617574686f72697a6564\",\n reason: \"Unauthorized\",\n });\n });\n\n test(\"prepare() returns err(ContractRevertedError) before constructing the extrinsic\", async () => {\n const wrapped = wrapContract(revertingRuntime(), ADDRESS, abi, {});\n\n const error = unwrapErr(\n await (\n wrapped as unknown as {\n transfer: {\n prepare: (\n to: string,\n amount: bigint,\n ) => Promise<Result<unknown, unknown>>;\n };\n }\n ).transfer.prepare(\"0x0000000000000000000000000000000000000001\", 1n),\n );\n expect(error).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 { ss58ToH160 } from \"@parity/product-sdk-address\";\nimport { type Result, err, ok } from \"@parity/result\";\nimport type { SubmittableTransaction, TxError, TxResult, Weight } from \"@parity/product-sdk-tx\";\nimport { ensureAccountMapped } from \"@parity/product-sdk-tx\";\nimport type { HexString, PolkadotClient, PolkadotSigner, SS58String } from \"polkadot-api\";\n\nimport { ContractError } from \"./errors.js\";\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 A `Result`: `ok(TxResult)` from the mapping extrinsic, `ok(null)` if already mapped,\n * or `err(TxError)` on failure. Delegates to `@parity/product-sdk-tx`'s `ensureAccountMapped`.\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<Result<TxResult | null, TxError>> {\n const checker = {\n addressIsMapped: async (addr: string): Promise<boolean> => {\n const mapped = await isContractAccountMapped(runtime, addr as SS58String);\n // ensureAccountMapped expects a throwing probe; it catches and\n // wraps this in a TxAccountMappingError with the cause attached.\n if (!mapped.ok) throw mapped.error;\n return mapped.value;\n },\n };\n return ensureAccountMapped(address, signer, checker, runtime.api, options);\n}\n\n/**\n * Read-only probe: is `address` mapped on `pallet-revive`?\n *\n * The read half of {@link ensureContractAccountMapped}: checks\n * `Revive.OriginalAccount` for the H160 derived from `address` without\n * needing a signer, so it can never submit a transaction or prompt a wallet.\n * Use it for readiness checks (\"can this account make contract calls right\n * now?\") from paths that must stay side-effect free.\n *\n * @param runtime - The contract runtime (typically `createContractRuntime(...)`).\n * @param address - The SS58 address to check.\n * @returns A {@link Result}: `ok(true)` when the mapping exists, `ok(false)` when it\n * doesn't, or `err(ContractError)` when the address can't be converted or the\n * storage query fails (underlying error attached as `cause`).\n */\nexport async function isContractAccountMapped(\n runtime: ContractRuntime,\n address: SS58String,\n): Promise<Result<boolean, ContractError>> {\n try {\n const h160 = ss58ToH160(address) as HexString;\n const mapped =\n (await runtime.api.query.Revive.OriginalAccount.getValue(h160)) !== undefined;\n return ok(mapped);\n } catch (cause) {\n return err(\n new ContractError(`Failed to check pallet-revive mapping for ${address}`, { cause }),\n );\n }\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 ok(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.ok).toBe(true);\n if (result.ok) expect(result.value).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(\"isContractAccountMapped\", () => {\n const aliceSs58 = \"5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY\" as SS58String;\n\n function makeReadRuntime(getValue: ReturnType<typeof vi.fn>): ContractRuntime {\n return {\n api: {\n query: {\n Revive: {\n OriginalAccount: { getValue },\n },\n },\n } as unknown as ReviveTypedApi,\n dryRunCall: () => {\n throw new Error(\"dryRunCall is unrelated to the mapping read\");\n },\n };\n }\n\n test(\"returns ok(true) when the mapping exists\", async () => {\n const getValue = vi.fn(async () => \"5mappedSs58\" as SS58String);\n const result = await isContractAccountMapped(makeReadRuntime(getValue), aliceSs58);\n\n expect(result.ok).toBe(true);\n if (result.ok) expect(result.value).toBe(true);\n });\n\n test(\"returns ok(false) when no mapping exists\", async () => {\n const getValue = vi.fn(async () => undefined);\n const result = await isContractAccountMapped(makeReadRuntime(getValue), aliceSs58);\n\n expect(result.ok).toBe(true);\n if (result.ok) expect(result.value).toBe(false);\n });\n\n test(\"queries by the derived H160, not the SS58 address\", async () => {\n const getValue = vi.fn(async (_h160: string) => undefined);\n await isContractAccountMapped(makeReadRuntime(getValue), aliceSs58);\n\n expect(getValue).toHaveBeenCalledTimes(1);\n const passedAddress = getValue.mock.calls[0][0];\n expect(passedAddress.startsWith(\"0x\")).toBe(true);\n expect(passedAddress.length).toBe(2 + 40);\n });\n\n test(\"returns err(ContractError) with cause when the storage query fails\", async () => {\n const underlying = new Error(\"RPC connection lost\");\n const getValue = vi.fn(async () => {\n throw underlying;\n });\n const result = await isContractAccountMapped(makeReadRuntime(getValue), aliceSs58);\n\n expect(result.ok).toBe(false);\n if (!result.ok) {\n expect(result.error).toBeInstanceOf(ContractError);\n expect(result.error.cause).toBe(underlying);\n expect(result.error.message).toContain(aliceSs58);\n }\n });\n\n test(\"returns err(ContractError) for an address that is not valid SS58\", async () => {\n const getValue = vi.fn(async () => undefined);\n const result = await isContractAccountMapped(\n makeReadRuntime(getValue),\n \"not-an-address\" as SS58String,\n );\n\n expect(result.ok).toBe(false);\n if (!result.ok) expect(result.error).toBeInstanceOf(ContractError);\n // The failure happens at H160 derivation — the chain is never hit.\n expect(getValue).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 Result, err, ok } from \"@parity/result\";\nimport type { HexString, PolkadotClient, SS58String } from \"polkadot-api\";\n\nimport {\n ContractError,\n ContractLiveAddressResolutionError,\n ContractNotFoundError,\n} from \"./errors.js\";\nimport { wrapContract } from \"./wrap.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 from\n * the registry, the result is `err`. Use `new ContractManager(...)` or\n * `ContractManager.fromClient(...)` directly for snapshot-only behavior.\n *\n * @returns A {@link Result}: `ok(CdmJson)` with live addresses patched in, or\n * `err(ContractError)` — a `ContractLiveAddressResolutionError` (missing\n * contracts, no registry, or a lookup that failed / returned nothing) or a\n * `ContractNotFoundError` (a requested library isn't installed).\n */\nexport async function withLiveContractAddresses(\n cdmJson: CdmJson,\n runtime: ContractRuntime,\n options?: LiveContractResolutionOptions,\n): Promise<Result<CdmJson, ContractError>> {\n try {\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 ok(resolved);\n } catch (cause) {\n // Surface every failure on the err channel, as the signature promises.\n // Internal helpers throw ContractError subclasses; a failed registry\n // `.query()` can also throw a raw RPC/network error from the underlying\n // dry-run — wrap those in the ContractError base so the contract holds.\n if (cause instanceof ContractError) return err(cause);\n const message = cause instanceof Error ? cause.message : String(cause);\n return err(\n new ContractLiveAddressResolutionError(\n `Live contract address resolution failed: ${message}`,\n { cause },\n ),\n );\n }\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<Result<ContractManager, ContractError>> {\n const resolved = await withLiveContractAddresses(cdmJson, runtime, {\n ...options,\n registryOrigin: options?.registryOrigin ?? (options?.defaultOrigin as SS58String),\n });\n if (!resolved.ok) return resolved;\n return ok(new ContractManager(resolved.value, 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<Result<ContractManager, ContractError>> {\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 // A real (Alice) SS58 — the wrap layer validates query origins\n // before dry-running, so placeholder strings are rejected.\n registryOrigin: \"5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY\" as SS58String,\n });\n\n expect(resolved.ok).toBe(true);\n if (!resolved.ok) throw resolved.error;\n expect(resolved.value.contracts?.[\"@w3s/playground-registry\"].address).toBe(\n liveAddress,\n );\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.ok).toBe(true);\n if (!resolved.ok) throw resolved.error;\n expect(resolved.value.contracts?.[\"@w3s/playground-registry\"].address).toBe(\n versionedAddress,\n );\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.ok).toBe(true);\n if (!resolved.ok) throw resolved.error;\n expect(resolved.value.contracts?.[\"@w3s/playground-registry\"].address).toBe(\n versionedAddress,\n );\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 const resolved = await withLiveContractAddresses(flattenedCdm, runtime);\n expect(resolved.ok).toBe(false);\n if (!resolved.ok) {\n expect(resolved.error).toBeInstanceOf(ContractLiveAddressResolutionError);\n expect(resolved.error.message).toMatch(/not registered/);\n }\n });\n\n test(\"returns err (does not throw) when the registry query hits a raw RPC error\", async () => {\n // A failed registry `.query()` can throw a raw network/RPC error from\n // the underlying dry-run — NOT a ContractError. The function must still\n // honor its Result signature and surface it on the err channel.\n const rpcError = new Error(\"WebSocket connection closed\");\n const runtime: ContractRuntime = {\n ...fakeRuntime(),\n dryRunCall: async () => {\n throw rpcError;\n },\n };\n\n const resolved = await withLiveContractAddresses(flattenedCdm, runtime);\n expect(resolved.ok).toBe(false);\n if (!resolved.ok) {\n expect(resolved.error).toBeInstanceOf(ContractLiveAddressResolutionError);\n expect(resolved.error.message).toContain(\"WebSocket connection closed\");\n expect(resolved.error.cause).toBe(rpcError);\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":["ok","err"],"mappings":"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","file":"index.js","sourcesContent":["// Copyright 2026 Parity Technologies (UK) Ltd.\n// SPDX-License-Identifier: Apache-2.0\nimport { isValidH160 } from \"@parity/product-sdk-address\";\nimport type { SdkError } from \"@parity/product-sdk-errors\";\nimport type { HexString } from \"polkadot-api\";\n\n/**\n * Base class for all contract errors. Use `instanceof ContractError` to catch any\n * contract-related error. Implements the cross-package {@link SdkError} marker so\n * `isSdkError(e)` also recognizes it.\n */\nexport class ContractError extends Error implements SdkError {\n readonly isSdkError = true as const;\n readonly source = \"contracts\";\n\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/**\n * A dry-run / transaction origin was not a valid SS58 address.\n *\n * Raised *before* the origin reaches PAPI's `AccountId` codec, which would\n * otherwise fail deep inside the encode stack with a bare `Invalid checksum`\n * and no hint that the origin was the wrong format. The most common mistake\n * is passing the account's H160 (`0x…`): pallet-revive derives the H160\n * `msg.sender` *from* the SS58 origin, so the origin itself must be SS58 —\n * the message calls that case out with a `h160ToSs58` pointer.\n *\n * Deliberately a validation error, not an auto-conversion: silently converting\n * an H160 here would hide which account the caller believes is calling.\n */\nexport class ContractInvalidOriginError extends ContractError {\n /** The rejected origin value. */\n readonly origin: string;\n\n constructor(origin: string) {\n const h160Hint = isValidH160(origin)\n ? \" (looks like an H160 — convert it with h160ToSs58 from @parity/product-sdk-address first)\"\n : \"\";\n super(`Contract call origin must be an SS58 address, got \"${origin}\"${h160Hint}`);\n this.name = \"ContractInvalidOriginError\";\n this.origin = origin;\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 ContractInvalidOriginError(\"0xabc\")).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(\"ContractInvalidOriginError\", () => {\n test(\"captures the rejected origin\", () => {\n const err = new ContractInvalidOriginError(\"not-an-address\");\n expect(err.name).toBe(\"ContractInvalidOriginError\");\n expect(err.origin).toBe(\"not-an-address\");\n expect(err.message).toContain(\"SS58\");\n expect(err.message).toContain(\"not-an-address\");\n expect(err.message).not.toContain(\"h160ToSs58\");\n });\n\n test(\"adds the h160ToSs58 hint when the origin looks like an H160\", () => {\n const h160 = \"0x9621dde636de098b43efb0fa9b61facfe328f99d\";\n const err = new ContractInvalidOriginError(h160);\n expect(err.origin).toBe(h160);\n expect(err.message).toContain(\"looks like an H160\");\n expect(err.message).toContain(\"h160ToSs58\");\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 { CommonSignerTxCreator } from \"@polkadot-api/signers-common\";\nimport { isValidSs58 } from \"@parity/product-sdk-address\";\nimport { createLogger } from \"@parity/product-sdk-logger\";\nimport { type Result, err, ok, unwrapErr, unwrapOk } from \"@parity/result\";\nimport { submitAndWatch } from \"@parity/product-sdk-tx\";\nimport type {\n BatchableCall,\n SubmittableTransaction,\n TxError,\n TxResult,\n} from \"@parity/product-sdk-tx\";\nimport { ss58Address } from \"@polkadot-labs/hdkd-helpers\";\nimport type { HexString, SS58String } from \"polkadot-api\";\nimport {\n bytesToHex,\n decodeErrorResult,\n decodeFunctionResult,\n encodeFunctionData,\n type Abi as ViemAbi,\n} from \"viem\";\n\nimport {\n type ContractError,\n ContractDryRunFailedError,\n ContractInvalidOriginError,\n ContractRevertedError,\n ContractSignerMissingError,\n type ContractRevertInfo,\n} from \"./errors.js\";\nimport type { ContractRuntime } from \"./runtime.js\";\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 the fallback origin for read-only\n * queries when no wallet is connected. The runtime API requires an origin, so\n * we pass this account when there is no connected one.\n *\n * Exported so other products can reuse the exact same read origin instead of\n * re-deriving it: pass it as an explicit `defaultOrigin` / `registryOrigin`,\n * or compare against it. Its SS58 (substrate generic, prefix 42) value is\n * `5EYCAe5ijiYfhaAUBd6H9WGRTsvwFFc7GnhQkiHvBYxdvpbV`.\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\"));\nexport const 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?: CommonSignerTxCreator,\n): CommonSignerTxCreator | undefined {\n return override ?? defaults.signerManager?.getSigner() ?? defaults.signer;\n}\n\n/**\n * Validate a resolved origin *before* it reaches PAPI's `AccountId` codec,\n * which otherwise fails deep inside the encode stack with a bare\n * `Invalid checksum` and no hint that the origin was the wrong format.\n * Returns the typed error (with an H160-specific hint — the recurring\n * consumer mistake is passing the account's `0x…` H160) or `null` when the\n * origin is a well-formed SS58 address. Validation only, no auto-conversion —\n * see {@link ContractInvalidOriginError}.\n */\nfunction validateOrigin(origin: SS58String): ContractInvalidOriginError | null {\n return isValidSs58(origin) ? null : new ContractInvalidOriginError(origin);\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 * Returns a {@link Result}: `ok(SubmittableTransaction)` — what `.tx()` hands to\n * `submitAndWatch` and what `.prepare()` returns as a `BatchableCall` — or\n * `err(ContractError)` if the pre-submit dry-run fails or the call reverts.\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<Result<SubmittableTransaction, ContractError>> {\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 return err(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 return err(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 ok(\n 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/**\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 // QueryResult has no error channel, so an invalid origin\n // is thrown here (the one deliberate asymmetry with\n // .tx()/.prepare(), which return it as err(...)).\n const originError = validateOrigin(origin);\n if (originError) throw originError;\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 (\n ...args: unknown[]\n ): Promise<Result<TxResult, ContractError | TxError>> => {\n const { positionalArgs, overrides } = extractOverrides<TxOptions>(\n argNames,\n args,\n );\n const signer = resolveSigner(defaults, overrides?.signer);\n if (!signer) {\n return err(new ContractSignerMissingError());\n }\n\n const origin =\n resolveOrigin(defaults, overrides?.origin) ??\n (ss58Address(signer.publicKey) as SS58String);\n const originError = validateOrigin(origin);\n if (originError) return err(originError);\n\n const built = await buildReviveCall(\n runtime,\n dest,\n abi,\n methodName,\n positionalArgs,\n origin,\n overrides,\n );\n if (!built.ok) return built;\n\n return submitAndWatch(built.value, signer, {\n waitFor: overrides?.waitFor,\n timeoutMs: overrides?.timeoutMs,\n mortalityPeriod: overrides?.mortalityPeriod,\n onStatus: overrides?.onStatus,\n });\n },\n\n prepare: async (\n ...args: unknown[]\n ): Promise<Result<BatchableCall, ContractError>> => {\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 const originError = validateOrigin(origin);\n if (originError) return err(originError);\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?: CommonSignerTxCreator | 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 test(\"query fallback derives pallet-revive's keyless account\", () => {\n // Frozen public value: other products import QUERY_FALLBACK_ORIGIN\n // to reuse this exact read origin. A regression here breaks them.\n expect(QUERY_FALLBACK_ORIGIN).toBe(\"5EYCAe5ijiYfhaAUBd6H9WGRTsvwFFc7GnhQkiHvBYxdvpbV\");\n const defaults: ContractDefaults = {};\n expect(resolveOrigin(defaults, undefined, true)).toBe(QUERY_FALLBACK_ORIGIN);\n });\n });\n\n describe(\"resolveSigner\", () => {\n const fakeSigner = { id: \"fake\" } as unknown as CommonSignerTxCreator;\n const sourceSigner = { id: \"source\" } as unknown as CommonSignerTxCreator;\n\n test(\"explicit override wins\", () => {\n const defaults: ContractDefaults = {\n signer: { id: \"static\" } as unknown as CommonSignerTxCreator,\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 CommonSignerTxCreator,\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 createSubmitAndWatch: () => ({\n subscribe: (handlers: {\n next: (event: unknown) => void;\n }) => {\n queueMicrotask(() => {\n handlers.next({\n type: \"inBestBlock\",\n txHash: \"0xdeadbeef\",\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 CommonSignerTxCreator;\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 CommonSignerTxCreator;\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 CommonSignerTxCreator;\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 const error = unwrapErr(\n await (\n wrapped as unknown as {\n increment: { tx: () => Promise<Result<unknown, unknown>> };\n }\n ).increment.tx(),\n );\n expect(error).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 createSubmitAndWatch: () => ({\n subscribe: (handlers: {\n next: (event: unknown) => void;\n }) => {\n queueMicrotask(() => {\n handlers.next({\n type: \"inBestBlock\",\n txHash: \"0xdeadbeef\",\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 const error = unwrapErr(\n await (\n wrapped as unknown as {\n increment: { tx: (opts: unknown) => Promise<Result<unknown, unknown>> };\n }\n ).increment.tx({ gasLimit: { ref_time: 1n, proof_size: 1n } }),\n );\n expect(error).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 createSubmitAndWatch: () => {\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 prepared = unwrapOk(\n await (\n wrapped as unknown as {\n add: {\n prepare: (\n n: number,\n opts: unknown,\n ) => Promise<Result<{ decodedCall: unknown }, unknown>>;\n };\n }\n ).add.prepare(7, {\n gasLimit: overrideWeight,\n storageDepositLimit: 42n,\n value: 5n,\n }),\n ) as { decodedCall: unknown };\n\n // SubmittableTransaction is a valid BatchableCall —\n // `batchSubmitAndWatch` reads `.decodedCall` off it.\n expect(prepared.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 const prepared = unwrapOk(\n await (\n wrapped as unknown as {\n increment: { prepare: () => Promise<Result<unknown, unknown>> };\n }\n ).increment.prepare(),\n );\n expect(prepared).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 const error = unwrapErr(\n await (\n wrapped as unknown as {\n increment: { prepare: () => Promise<Result<unknown, unknown>> };\n }\n ).increment.prepare(),\n );\n expect(error).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 = unwrapOk(\n await (\n wrapped as unknown as {\n increment: { prepare: () => Promise<Result<BatchableCall, unknown>> };\n }\n ).increment.prepare(),\n );\n const b = unwrapOk(\n await (\n wrapped as unknown as {\n add: { prepare: (n: number) => Promise<Result<BatchableCall, unknown>> };\n }\n ).add.prepare(1),\n );\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 createSubmitAndWatch: () => ({\n subscribe: (h: {\n next: (event: unknown) => void;\n }) => {\n queueMicrotask(() => {\n h.next({\n type: \"inBestBlock\",\n txHash: \"0xb\",\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 — origin validation\", () => {\n // Regression suite for the \"bare Invalid checksum\" footgun: a\n // non-SS58 origin (typically the account's H160) must be rejected\n // with a typed, self-explanatory error BEFORE any dry-run happens —\n // never forwarded into PAPI's AccountId codec. `.query()` throws\n // (QueryResult has no error channel); `.tx()`/`.prepare()` return\n // err(ContractInvalidOriginError).\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 H160_ORIGIN = \"0x9621dde636de098b43efb0fa9b61facfe328f99d\";\n const fakeSigner = {\n publicKey: new Uint8Array(32),\n } as unknown as CommonSignerTxCreator;\n\n function guardedRuntime(): ContractRuntime {\n return {\n api: {\n tx: {\n Revive: {\n call: () => {\n throw new Error(\n \"Revive.call must NOT be invoked for an invalid origin\",\n );\n },\n },\n },\n } as unknown as ContractRuntime[\"api\"],\n dryRunCall: () => {\n throw new Error(\"dryRunCall must NOT be invoked for an invalid origin\");\n },\n };\n }\n\n test(\"query() throws ContractInvalidOriginError for an H160 origin, with the conversion hint\", async () => {\n const wrapped = wrapContract(guardedRuntime(), ADDRESS, abi, {\n origin: H160_ORIGIN as SS58String,\n });\n\n const call = (\n wrapped as unknown as { getCount: { query: () => Promise<unknown> } }\n ).getCount.query();\n await expect(call).rejects.toBeInstanceOf(ContractInvalidOriginError);\n await expect(call).rejects.toMatchObject({\n name: \"ContractInvalidOriginError\",\n origin: H160_ORIGIN,\n });\n await expect(call).rejects.toThrow(/h160ToSs58/);\n });\n\n test(\"query() throws for a garbage per-call origin override\", async () => {\n const wrapped = wrapContract(guardedRuntime(), ADDRESS, abi, {\n origin: \"5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY\" as SS58String,\n });\n\n await expect(\n (\n wrapped as unknown as {\n getCount: { query: (opts: { origin: string }) => Promise<unknown> };\n }\n ).getCount.query({ origin: \"not-an-address\" }),\n ).rejects.toBeInstanceOf(ContractInvalidOriginError);\n });\n\n test(\"tx() returns err(ContractInvalidOriginError) for an H160 origin\", async () => {\n const wrapped = wrapContract(guardedRuntime(), ADDRESS, abi, {\n signer: fakeSigner,\n origin: H160_ORIGIN as SS58String,\n });\n\n const error = unwrapErr(\n await (\n wrapped as unknown as {\n getCount: { tx: () => Promise<Result<unknown, unknown>> };\n }\n ).getCount.tx(),\n );\n expect(error).toBeInstanceOf(ContractInvalidOriginError);\n expect(error).toMatchObject({ origin: H160_ORIGIN });\n expect((error as ContractInvalidOriginError).message).toContain(\"h160ToSs58\");\n });\n\n test(\"prepare() returns err(ContractInvalidOriginError) for an H160 origin\", async () => {\n const wrapped = wrapContract(guardedRuntime(), ADDRESS, abi, {\n origin: H160_ORIGIN as SS58String,\n });\n\n const error = unwrapErr(\n await (\n wrapped as unknown as {\n getCount: { prepare: () => Promise<Result<unknown, unknown>> };\n }\n ).getCount.prepare(),\n );\n expect(error).toBeInstanceOf(ContractInvalidOriginError);\n expect(error).toMatchObject({ origin: H160_ORIGIN });\n });\n\n test(\"a valid SS58 origin still flows through to the dry-run\", async () => {\n let capturedOrigin: string | undefined;\n const runtime: ContractRuntime = {\n api: {} 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: 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 const alice = \"5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY\" as SS58String;\n const wrapped = wrapContract(runtime, ADDRESS, abi, { origin: alice });\n\n const result = await (\n wrapped as unknown as {\n getCount: { query: () => Promise<{ success: boolean }> };\n }\n ).getCount.query();\n\n expect(result.success).toBe(true);\n expect(capturedOrigin).toBe(alice);\n });\n\n test(\"the query fallback origin passes validation (no-wallet path keeps working)\", async () => {\n let invoked = false;\n const runtime: ContractRuntime = {\n api: {} as unknown as ContractRuntime[\"api\"],\n dryRunCall: async () => {\n invoked = 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: true, value: { flags: 0, data: new Uint8Array(32) } },\n };\n },\n };\n // No origin configured anywhere — resolveOrigin falls back to\n // QUERY_FALLBACK_ORIGIN, which must be considered valid.\n const wrapped = wrapContract(runtime, ADDRESS, abi, {});\n await (\n wrapped as unknown as { getCount: { query: () => Promise<unknown> } }\n ).getCount.query();\n expect(invoked).toBe(true);\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 CommonSignerTxCreator;\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 const error = unwrapErr(\n await (\n wrapped as unknown as {\n transfer: {\n tx: (to: string, amount: bigint) => Promise<Result<unknown, unknown>>;\n };\n }\n ).transfer.tx(\"0x0000000000000000000000000000000000000001\", 1n),\n );\n expect(error).toMatchObject({\n name: \"ContractRevertedError\",\n methodName: \"transfer\",\n data: \"0x556e617574686f72697a6564\",\n reason: \"Unauthorized\",\n });\n });\n\n test(\"prepare() returns err(ContractRevertedError) before constructing the extrinsic\", async () => {\n const wrapped = wrapContract(revertingRuntime(), ADDRESS, abi, {});\n\n const error = unwrapErr(\n await (\n wrapped as unknown as {\n transfer: {\n prepare: (\n to: string,\n amount: bigint,\n ) => Promise<Result<unknown, unknown>>;\n };\n }\n ).transfer.prepare(\"0x0000000000000000000000000000000000000001\", 1n),\n );\n expect(error).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 { CommonSignerTxCreator } from \"@polkadot-api/signers-common\";\nimport { ss58ToH160 } from \"@parity/product-sdk-address\";\nimport { type Result, err, ok } from \"@parity/result\";\nimport type { SubmittableTransaction, TxError, TxResult, Weight } from \"@parity/product-sdk-tx\";\nimport { ensureAccountMapped } from \"@parity/product-sdk-tx\";\nimport type { HexString, PolkadotClient, SS58String } from \"polkadot-api\";\n\nimport { ContractError } from \"./errors.js\";\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 A `Result`: `ok(TxResult)` from the mapping extrinsic, `ok(null)` if already mapped,\n * or `err(TxError)` on failure. Delegates to `@parity/product-sdk-tx`'s `ensureAccountMapped`.\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: CommonSignerTxCreator,\n options?: { timeoutMs?: number; onStatus?: (s: string) => void },\n): Promise<Result<TxResult | null, TxError>> {\n const checker = {\n addressIsMapped: async (addr: string): Promise<boolean> => {\n const mapped = await isContractAccountMapped(runtime, addr as SS58String);\n // ensureAccountMapped expects a throwing probe; it catches and\n // wraps this in a TxAccountMappingError with the cause attached.\n if (!mapped.ok) throw mapped.error;\n return mapped.value;\n },\n };\n return ensureAccountMapped(address, signer, checker, runtime.api, options);\n}\n\n/**\n * Read-only probe: is `address` mapped on `pallet-revive`?\n *\n * The read half of {@link ensureContractAccountMapped}: checks\n * `Revive.OriginalAccount` for the H160 derived from `address` without\n * needing a signer, so it can never submit a transaction or prompt a wallet.\n * Use it for readiness checks (\"can this account make contract calls right\n * now?\") from paths that must stay side-effect free.\n *\n * @param runtime - The contract runtime (typically `createContractRuntime(...)`).\n * @param address - The SS58 address to check.\n * @returns A {@link Result}: `ok(true)` when the mapping exists, `ok(false)` when it\n * doesn't, or `err(ContractError)` when the address can't be converted or the\n * storage query fails (underlying error attached as `cause`).\n */\nexport async function isContractAccountMapped(\n runtime: ContractRuntime,\n address: SS58String,\n): Promise<Result<boolean, ContractError>> {\n try {\n const h160 = ss58ToH160(address) as HexString;\n const mapped =\n (await runtime.api.query.Revive.OriginalAccount.getValue(h160)) !== undefined;\n return ok(mapped);\n } catch (cause) {\n return err(\n new ContractError(`Failed to check pallet-revive mapping for ${address}`, { cause }),\n );\n }\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 CommonSignerTxCreator;\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 ok(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.ok).toBe(true);\n if (result.ok) expect(result.value).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(\"isContractAccountMapped\", () => {\n const aliceSs58 = \"5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY\" as SS58String;\n\n function makeReadRuntime(getValue: ReturnType<typeof vi.fn>): ContractRuntime {\n return {\n api: {\n query: {\n Revive: {\n OriginalAccount: { getValue },\n },\n },\n } as unknown as ReviveTypedApi,\n dryRunCall: () => {\n throw new Error(\"dryRunCall is unrelated to the mapping read\");\n },\n };\n }\n\n test(\"returns ok(true) when the mapping exists\", async () => {\n const getValue = vi.fn(async () => \"5mappedSs58\" as SS58String);\n const result = await isContractAccountMapped(makeReadRuntime(getValue), aliceSs58);\n\n expect(result.ok).toBe(true);\n if (result.ok) expect(result.value).toBe(true);\n });\n\n test(\"returns ok(false) when no mapping exists\", async () => {\n const getValue = vi.fn(async () => undefined);\n const result = await isContractAccountMapped(makeReadRuntime(getValue), aliceSs58);\n\n expect(result.ok).toBe(true);\n if (result.ok) expect(result.value).toBe(false);\n });\n\n test(\"queries by the derived H160, not the SS58 address\", async () => {\n const getValue = vi.fn(async (_h160: string) => undefined);\n await isContractAccountMapped(makeReadRuntime(getValue), aliceSs58);\n\n expect(getValue).toHaveBeenCalledTimes(1);\n const passedAddress = getValue.mock.calls[0][0];\n expect(passedAddress.startsWith(\"0x\")).toBe(true);\n expect(passedAddress.length).toBe(2 + 40);\n });\n\n test(\"returns err(ContractError) with cause when the storage query fails\", async () => {\n const underlying = new Error(\"RPC connection lost\");\n const getValue = vi.fn(async () => {\n throw underlying;\n });\n const result = await isContractAccountMapped(makeReadRuntime(getValue), aliceSs58);\n\n expect(result.ok).toBe(false);\n if (!result.ok) {\n expect(result.error).toBeInstanceOf(ContractError);\n expect(result.error.cause).toBe(underlying);\n expect(result.error.message).toContain(aliceSs58);\n }\n });\n\n test(\"returns err(ContractError) for an address that is not valid SS58\", async () => {\n const getValue = vi.fn(async () => undefined);\n const result = await isContractAccountMapped(\n makeReadRuntime(getValue),\n \"not-an-address\" as SS58String,\n );\n\n expect(result.ok).toBe(false);\n if (!result.ok) expect(result.error).toBeInstanceOf(ContractError);\n // The failure happens at H160 derivation — the chain is never hit.\n expect(getValue).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 Result, err, ok } from \"@parity/result\";\nimport type { HexString, PolkadotClient, SS58String } from \"polkadot-api\";\n\nimport {\n ContractError,\n ContractLiveAddressResolutionError,\n ContractNotFoundError,\n} from \"./errors.js\";\nimport { wrapContract } from \"./wrap.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 from\n * the registry, the result is `err`. Use `new ContractManager(...)` or\n * `ContractManager.fromClient(...)` directly for snapshot-only behavior.\n *\n * @returns A {@link Result}: `ok(CdmJson)` with live addresses patched in, or\n * `err(ContractError)` — a `ContractLiveAddressResolutionError` (missing\n * contracts, no registry, or a lookup that failed / returned nothing) or a\n * `ContractNotFoundError` (a requested library isn't installed).\n */\nexport async function withLiveContractAddresses(\n cdmJson: CdmJson,\n runtime: ContractRuntime,\n options?: LiveContractResolutionOptions,\n): Promise<Result<CdmJson, ContractError>> {\n try {\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 ok(resolved);\n } catch (cause) {\n // Surface every failure on the err channel, as the signature promises.\n // Internal helpers throw ContractError subclasses; a failed registry\n // `.query()` can also throw a raw RPC/network error from the underlying\n // dry-run — wrap those in the ContractError base so the contract holds.\n if (cause instanceof ContractError) return err(cause);\n const message = cause instanceof Error ? cause.message : String(cause);\n return err(\n new ContractLiveAddressResolutionError(\n `Live contract address resolution failed: ${message}`,\n { cause },\n ),\n );\n }\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<Result<ContractManager, ContractError>> {\n const resolved = await withLiveContractAddresses(cdmJson, runtime, {\n ...options,\n registryOrigin: options?.registryOrigin ?? (options?.defaultOrigin as SS58String),\n });\n if (!resolved.ok) return resolved;\n return ok(new ContractManager(resolved.value, 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<Result<ContractManager, ContractError>> {\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 // A real (Alice) SS58 — the wrap layer validates query origins\n // before dry-running, so placeholder strings are rejected.\n registryOrigin: \"5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY\" as SS58String,\n });\n\n expect(resolved.ok).toBe(true);\n if (!resolved.ok) throw resolved.error;\n expect(resolved.value.contracts?.[\"@w3s/playground-registry\"].address).toBe(\n liveAddress,\n );\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.ok).toBe(true);\n if (!resolved.ok) throw resolved.error;\n expect(resolved.value.contracts?.[\"@w3s/playground-registry\"].address).toBe(\n versionedAddress,\n );\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.ok).toBe(true);\n if (!resolved.ok) throw resolved.error;\n expect(resolved.value.contracts?.[\"@w3s/playground-registry\"].address).toBe(\n versionedAddress,\n );\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 const resolved = await withLiveContractAddresses(flattenedCdm, runtime);\n expect(resolved.ok).toBe(false);\n if (!resolved.ok) {\n expect(resolved.error).toBeInstanceOf(ContractLiveAddressResolutionError);\n expect(resolved.error.message).toMatch(/not registered/);\n }\n });\n\n test(\"returns err (does not throw) when the registry query hits a raw RPC error\", async () => {\n // A failed registry `.query()` can throw a raw network/RPC error from\n // the underlying dry-run — NOT a ContractError. The function must still\n // honor its Result signature and surface it on the err channel.\n const rpcError = new Error(\"WebSocket connection closed\");\n const runtime: ContractRuntime = {\n ...fakeRuntime(),\n dryRunCall: async () => {\n throw rpcError;\n },\n };\n\n const resolved = await withLiveContractAddresses(flattenedCdm, runtime);\n expect(resolved.ok).toBe(false);\n if (!resolved.ok) {\n expect(resolved.error).toBeInstanceOf(ContractLiveAddressResolutionError);\n expect(resolved.error.message).toContain(\"WebSocket connection closed\");\n expect(resolved.error.cause).toBe(rpcError);\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
package/dist/testing.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { HexString, SS58String } from 'polkadot-api';
|
|
2
2
|
import { Weight } from '@parity/product-sdk-tx';
|
|
3
|
-
import { A as AbiEntry, a as ContractRuntime, w as ReviveDryRunResult } from './types-
|
|
3
|
+
import { A as AbiEntry, a as ContractRuntime, w as ReviveDryRunResult } from './types-DKim_9fW.js';
|
|
4
|
+
import '@polkadot-api/signers-common';
|
|
4
5
|
import '@parity/result';
|
|
5
6
|
import '@parity/product-sdk-signer';
|
|
6
7
|
import '@parity/product-sdk-errors';
|
|
@@ -1,7 +1,8 @@
|
|
|
1
|
+
import { CommonSignerTxCreator } from '@polkadot-api/signers-common';
|
|
1
2
|
import { Result } from '@parity/result';
|
|
2
3
|
import { SignerManager } from '@parity/product-sdk-signer';
|
|
3
4
|
import { Weight, SubmittableTransaction, TxResult, TxError, SubmitOptions, BatchableCall } from '@parity/product-sdk-tx';
|
|
4
|
-
import { HexString, SS58String, PolkadotClient
|
|
5
|
+
import { HexString, SS58String, PolkadotClient } from 'polkadot-api';
|
|
5
6
|
import { SdkError } from '@parity/product-sdk-errors';
|
|
6
7
|
|
|
7
8
|
/**
|
|
@@ -306,7 +307,7 @@ declare function createContractRuntimeFromClient<TDescriptor>(client: PolkadotCl
|
|
|
306
307
|
* // now safe to call contract.<method>.tx({ signer })
|
|
307
308
|
* ```
|
|
308
309
|
*/
|
|
309
|
-
declare function ensureContractAccountMapped(runtime: ContractRuntime, address: SS58String, signer:
|
|
310
|
+
declare function ensureContractAccountMapped(runtime: ContractRuntime, address: SS58String, signer: CommonSignerTxCreator, options?: {
|
|
310
311
|
timeoutMs?: number;
|
|
311
312
|
onStatus?: (s: string) => void;
|
|
312
313
|
}): Promise<Result<TxResult | null, TxError>>;
|
|
@@ -419,7 +420,7 @@ interface QueryOptions {
|
|
|
419
420
|
}
|
|
420
421
|
/** Options for transaction calls — passed as the last argument after positional args. */
|
|
421
422
|
interface TxOptions extends SubmitOptions {
|
|
422
|
-
signer?:
|
|
423
|
+
signer?: CommonSignerTxCreator;
|
|
423
424
|
origin?: SS58String;
|
|
424
425
|
value?: bigint;
|
|
425
426
|
gasLimit?: Weight;
|
|
@@ -455,7 +456,7 @@ interface PrepareOptions {
|
|
|
455
456
|
/** Mutable defaults shared across all contract handles from a manager. */
|
|
456
457
|
interface ContractDefaults {
|
|
457
458
|
origin?: SS58String;
|
|
458
|
-
signer?:
|
|
459
|
+
signer?: CommonSignerTxCreator;
|
|
459
460
|
signerManager?: SignerManager;
|
|
460
461
|
}
|
|
461
462
|
/**
|
|
@@ -477,7 +478,7 @@ interface ContractOptions {
|
|
|
477
478
|
/** Static fallback caller address for queries. */
|
|
478
479
|
defaultOrigin?: SS58String;
|
|
479
480
|
/** Static fallback signer for transactions. */
|
|
480
|
-
defaultSigner?:
|
|
481
|
+
defaultSigner?: CommonSignerTxCreator;
|
|
481
482
|
}
|
|
482
483
|
/** Options for {@link ContractManager} construction. */
|
|
483
484
|
type ContractManagerOptions = ContractOptions;
|
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.0.0-dev.
|
|
4
|
+
"version": "0.0.0-dev.457.0",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
7
7
|
"types": "./dist/index.d.ts",
|
|
@@ -32,15 +32,16 @@
|
|
|
32
32
|
},
|
|
33
33
|
"dependencies": {
|
|
34
34
|
"@polkadot-labs/hdkd-helpers": "^0.0.30",
|
|
35
|
-
"polkadot-api": "^
|
|
35
|
+
"@polkadot-api/signers-common": "^0.4.0",
|
|
36
|
+
"polkadot-api": "^3.2.1",
|
|
36
37
|
"viem": "^2.52.0",
|
|
37
|
-
"@parity/product-sdk-
|
|
38
|
-
"@parity/product-sdk-
|
|
39
|
-
"@parity/product-sdk-keys": "0.0.0-dev.421.0",
|
|
40
|
-
"@parity/product-sdk-signer": "0.0.0-dev.421.0",
|
|
41
|
-
"@parity/product-sdk-logger": "0.1.1",
|
|
38
|
+
"@parity/product-sdk-logger": "0.0.0-dev.457.0",
|
|
39
|
+
"@parity/product-sdk-address": "0.0.0-dev.457.0",
|
|
42
40
|
"@parity/result": "0.2.0",
|
|
43
|
-
"@parity/product-sdk-
|
|
41
|
+
"@parity/product-sdk-errors": "0.2.0",
|
|
42
|
+
"@parity/product-sdk-signer": "0.0.0-dev.457.0",
|
|
43
|
+
"@parity/product-sdk-keys": "0.0.0-dev.457.0",
|
|
44
|
+
"@parity/product-sdk-tx": "0.0.0-dev.457.0"
|
|
44
45
|
},
|
|
45
46
|
"devDependencies": {
|
|
46
47
|
"tsup": "^8.5.1",
|