From 6394d3b091feff7fdff1480702f8695ebea47960 Mon Sep 17 00:00:00 2001 From: Roman Useinov Date: Fri, 10 Jul 2026 12:57:19 +0200 Subject: [PATCH] fix: facilitate extrinsic decoding --- .gitignore | 2 + ...polkadot-types-npm-16.5.6-6fe2703ed8.patch | 136 ++++++++++++++++++ README.md | 15 ++ package.json | 2 +- yarn.lock | 18 ++- 5 files changed, 171 insertions(+), 2 deletions(-) create mode 100644 .yarn/patches/@polkadot-types-npm-16.5.6-6fe2703ed8.patch diff --git a/.gitignore b/.gitignore index c5cd3087ae18..67140fe7c9bf 100644 --- a/.gitignore +++ b/.gitignore @@ -10,6 +10,8 @@ tmp/ .env.production.local .npmrc .yarn/* +!.yarn/patches +!.yarn/patches/** !.yarn/releases !.yarn/plugins .pnp.* diff --git a/.yarn/patches/@polkadot-types-npm-16.5.6-6fe2703ed8.patch b/.yarn/patches/@polkadot-types-npm-16.5.6-6fe2703ed8.patch new file mode 100644 index 000000000000..7a892db22cbb --- /dev/null +++ b/.yarn/patches/@polkadot-types-npm-16.5.6-6fe2703ed8.patch @@ -0,0 +1,136 @@ +diff --git a/cjs/extrinsic/v4/ExtrinsicSignature.js b/cjs/extrinsic/v4/ExtrinsicSignature.js +index d2809f1b7a761c2140ea82c132e22bc883ed3dbb..2d020950a8aa88f15541aa5d89cee849043bbb91 100644 +--- a/cjs/extrinsic/v4/ExtrinsicSignature.js ++++ b/cjs/extrinsic/v4/ExtrinsicSignature.js +@@ -15,12 +15,16 @@ function toAddress(registry, address) { + * A container for the [[Signature]] associated with a specific [[Extrinsic]] + */ + class GenericExtrinsicSignatureV4 extends types_codec_1.Struct { ++ #isSigned; + #signKeys; + constructor(registry, value, { isSigned } = {}) { + const signTypes = registry.getSignedExtensionTypes(); + super(registry, (0, util_1.objectSpread)( + // eslint-disable-next-line sort-keys + { signer: 'Address', signature: 'ExtrinsicSignature' }, signTypes), GenericExtrinsicSignatureV4.decodeExtrinsicSignature(value, isSigned)); ++ this.#isSigned = isSigned === undefined ++ ? !this.signature.isEmpty ++ : isSigned; + this.#signKeys = Object.keys(signTypes); + (0, util_1.objectProperties)(this, this.#signKeys, (k) => this.get(k)); + } +@@ -48,7 +52,7 @@ class GenericExtrinsicSignatureV4 extends types_codec_1.Struct { + * @description `true` if the signature is valid + */ + get isSigned() { +- return !this.signature.isEmpty; ++ return this.#isSigned; + } + /** + * @description The [[ExtrinsicEra]] (mortal or immortal) this signature applies to +@@ -117,6 +121,7 @@ class GenericExtrinsicSignatureV4 extends types_codec_1.Struct { + // additional fields (exposed in struct itself) + this.set('signer', signer); + this.set('signature', signature); ++ this.#isSigned = true; + return this; + } + /** +diff --git a/cjs/extrinsic/v5/ExtrinsicSignature.js b/cjs/extrinsic/v5/ExtrinsicSignature.js +index 10e370372d98a496aa8af1584d89c540b8db5ddf..2db1afa1ecfeabcbd3e419c27ad0114755b5217a 100644 +--- a/cjs/extrinsic/v5/ExtrinsicSignature.js ++++ b/cjs/extrinsic/v5/ExtrinsicSignature.js +@@ -11,12 +11,16 @@ const ExtrinsicPayload_js_1 = require("./ExtrinsicPayload.js"); + * A container for the [[Signature]] associated with a specific [[Extrinsic]] + */ + class GenericExtrinsicSignatureV5 extends types_codec_1.Struct { ++ #isSigned; + #signKeys; + constructor(registry, value, { isSigned } = {}) { + const signTypes = registry.getSignedExtensionTypes(); + super(registry, (0, util_1.objectSpread)( + // eslint-disable-next-line sort-keys + { signer: 'Address', signature: 'ExtrinsicSignature', transactionExtensionVersion: 'u8' }, signTypes), GenericExtrinsicSignatureV5.decodeExtrinsicSignature(value, isSigned)); ++ this.#isSigned = isSigned === undefined ++ ? !this.signature.isEmpty ++ : isSigned; + this.#signKeys = Object.keys(signTypes); + (0, util_1.objectProperties)(this, this.#signKeys, (k) => this.get(k)); + } +@@ -44,7 +48,7 @@ class GenericExtrinsicSignatureV5 extends types_codec_1.Struct { + * @description `true` if the signature is valid + */ + get isSigned() { +- return !this.signature.isEmpty; ++ return this.#isSigned; + } + /** + * @description The [[ExtrinsicEra]] (mortal or immortal) this signature applies to +diff --git a/extrinsic/v4/ExtrinsicSignature.js b/extrinsic/v4/ExtrinsicSignature.js +index ba67bdb4e530d0df4efce7615ef70f50026248e9..9fa3bf9e02dfe49e147e496b5a3d50a8ac81feec 100644 +--- a/extrinsic/v4/ExtrinsicSignature.js ++++ b/extrinsic/v4/ExtrinsicSignature.js +@@ -12,12 +12,16 @@ function toAddress(registry, address) { + * A container for the [[Signature]] associated with a specific [[Extrinsic]] + */ + export class GenericExtrinsicSignatureV4 extends Struct { ++ #isSigned; + #signKeys; + constructor(registry, value, { isSigned } = {}) { + const signTypes = registry.getSignedExtensionTypes(); + super(registry, objectSpread( + // eslint-disable-next-line sort-keys + { signer: 'Address', signature: 'ExtrinsicSignature' }, signTypes), GenericExtrinsicSignatureV4.decodeExtrinsicSignature(value, isSigned)); ++ this.#isSigned = isSigned === undefined ++ ? !this.signature.isEmpty ++ : isSigned; + this.#signKeys = Object.keys(signTypes); + objectProperties(this, this.#signKeys, (k) => this.get(k)); + } +@@ -45,7 +49,7 @@ export class GenericExtrinsicSignatureV4 extends Struct { + * @description `true` if the signature is valid + */ + get isSigned() { +- return !this.signature.isEmpty; ++ return this.#isSigned; + } + /** + * @description The [[ExtrinsicEra]] (mortal or immortal) this signature applies to +@@ -114,6 +118,7 @@ export class GenericExtrinsicSignatureV4 extends Struct { + // additional fields (exposed in struct itself) + this.set('signer', signer); + this.set('signature', signature); ++ this.#isSigned = true; + return this; + } + /** +diff --git a/extrinsic/v5/ExtrinsicSignature.js b/extrinsic/v5/ExtrinsicSignature.js +index 845a2f828b837f7d2340daaad6238b9ed12164a3..e0af841efab043f809835cf86b8e126c567ceffc 100644 +--- a/extrinsic/v5/ExtrinsicSignature.js ++++ b/extrinsic/v5/ExtrinsicSignature.js +@@ -8,12 +8,16 @@ import { GenericExtrinsicPayloadV5 } from './ExtrinsicPayload.js'; + * A container for the [[Signature]] associated with a specific [[Extrinsic]] + */ + export class GenericExtrinsicSignatureV5 extends Struct { ++ #isSigned; + #signKeys; + constructor(registry, value, { isSigned } = {}) { + const signTypes = registry.getSignedExtensionTypes(); + super(registry, objectSpread( + // eslint-disable-next-line sort-keys + { signer: 'Address', signature: 'ExtrinsicSignature', transactionExtensionVersion: 'u8' }, signTypes), GenericExtrinsicSignatureV5.decodeExtrinsicSignature(value, isSigned)); ++ this.#isSigned = isSigned === undefined ++ ? !this.signature.isEmpty ++ : isSigned; + this.#signKeys = Object.keys(signTypes); + objectProperties(this, this.#signKeys, (k) => this.get(k)); + } +@@ -41,7 +45,7 @@ export class GenericExtrinsicSignatureV5 extends Struct { + * @description `true` if the signature is valid + */ + get isSigned() { +- return !this.signature.isEmpty; ++ return this.#isSigned; + } + /** + * @description The [[ExtrinsicEra]] (mortal or immortal) this signature applies to diff --git a/README.md b/README.md index 0362f2ba5b2a..2a32b86c36f7 100644 --- a/README.md +++ b/README.md @@ -29,6 +29,21 @@ To get started - 5. Ready! Now you can launch the UI (assuming you have a local Polkadot Node running), via `yarn run start` 6. Access the UI via [http://localhost:3000](http://localhost:3000) +### Quip extrinsic decoding + +This fork carries a Yarn patch for `@polkadot/types` so Apps can decode Quip +blocks that contain V5 bare inherents and V4 signed transactions. Quip uses a +custom hybrid transaction signature envelope instead of the standard +`MultiSignature`. The upstream decoder previously inferred signed-ness from +whether the decoded signature object looked empty; that heuristic breaks for a +struct-like custom signature because a default struct can look non-empty even +when the extrinsic wire byte marks the item as bare/unsigned. + +The patch makes the decoder trust the explicit `isSigned` flag derived from the +extrinsic version/type byte. This preserves compatibility with valid SDK-format +blocks such as `0x05` bare V5 inherents followed by `0x84` signed V4 +transactions. + ## Docker diff --git a/package.json b/package.json index 3a20515868db..02adbeb7e6a3 100644 --- a/package.json +++ b/package.json @@ -106,7 +106,7 @@ "@polkadot/rpc-core": "^16.5.6", "@polkadot/rpc-provider": "^16.5.6", "@polkadot/typegen": "^16.5.6", - "@polkadot/types": "^16.5.6", + "@polkadot/types": "patch:@polkadot/types@npm%3A16.5.6#~/.yarn/patches/@polkadot-types-npm-16.5.6-6fe2703ed8.patch", "@polkadot/types-augment": "^16.5.6", "@polkadot/types-codec": "^16.5.6", "@polkadot/types-create": "^16.5.6", diff --git a/yarn.lock b/yarn.lock index ce46209abcda..86d0db902929 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2776,7 +2776,7 @@ __metadata: languageName: node linkType: hard -"@polkadot/types@npm:^16.5.6": +"@polkadot/types@npm:16.5.6": version: 16.5.6 resolution: "@polkadot/types@npm:16.5.6" dependencies: @@ -2792,6 +2792,22 @@ __metadata: languageName: node linkType: hard +"@polkadot/types@patch:@polkadot/types@npm%3A16.5.6#~/.yarn/patches/@polkadot-types-npm-16.5.6-6fe2703ed8.patch": + version: 16.5.6 + resolution: "@polkadot/types@patch:@polkadot/types@npm%3A16.5.6#~/.yarn/patches/@polkadot-types-npm-16.5.6-6fe2703ed8.patch::version=16.5.6&hash=9b49a6" + dependencies: + "@polkadot/keyring": "npm:^14.0.3" + "@polkadot/types-augment": "npm:16.5.6" + "@polkadot/types-codec": "npm:16.5.6" + "@polkadot/types-create": "npm:16.5.6" + "@polkadot/util": "npm:^14.0.3" + "@polkadot/util-crypto": "npm:^14.0.3" + rxjs: "npm:^7.8.1" + tslib: "npm:^2.8.1" + checksum: 10/364794496a194162575e2e4d3e008905419e71de7195733311a4da1d057d3653cf3a1f918743c7e2a95cadc44e8fed9e1cc806e5374bd9f88ccc3cfd19a48760 + languageName: node + linkType: hard + "@polkadot/ui-keyring@npm:^3.16.7": version: 3.16.7 resolution: "@polkadot/ui-keyring@npm:3.16.7"