Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ tmp/
.env.production.local
.npmrc
.yarn/*
!.yarn/patches
!.yarn/patches/**
!.yarn/releases
!.yarn/plugins
.pnp.*
Expand Down
3 changes: 3 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[submodule "quip-protocol-rs"]
path = quip-protocol-rs
url = git@gitlab.com:quip.network/quip-protocol-rs.git
136 changes: 136 additions & 0 deletions .yarn/patches/@polkadot-types-npm-16.5.6-6fe2703ed8.patch
Original file line number Diff line number Diff line change
@@ -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
43 changes: 43 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# Quip hybrid-signature integration for the polkadot-js apps fork.
#
# The Quip transaction signer (sr25519 + ML-DSA-44 hybrid) lives in the
# `quip-protocol-rs` git submodule, pinned to a specific commit. Its browser
# WASM is a generated, git-ignored artifact, so it must be built locally before
# the dev signer (packages/apps/src/initQuipSigner.ts) can load it.
#
# Usage:
# make quip-signer # init submodule + (re)build the hybrid-signer WASM
# make start # build WASM if missing, then run the dev server with
# # the Quip hybrid signer enabled
#
# Requires `wasm-pack` (cargo install wasm-pack) and the Rust toolchain.

QUIP_SUBMODULE := quip-protocol-rs
WASM_OUT := $(QUIP_SUBMODULE)/js/quip-transaction-crypto-wasm/quip_transaction_crypto_wasm_bg.wasm

.PHONY: all quip-signer quip-submodule start

# Default target builds everything needed for hybrid-sig support.
all: quip-signer

# Check out the submodule at its pinned commit (idempotent).
quip-submodule:
git submodule update --init $(QUIP_SUBMODULE)

# Build the git-ignored hybrid-signer WASM inside the submodule. The submodule's
# own `wasm-signer` target runs wasm-pack and writes the artifacts into
# quip-protocol-rs/js/quip-transaction-crypto-wasm/, which is exactly where
# initQuipSigner.ts imports them from. Always rebuilds.
quip-signer: quip-submodule
$(MAKE) -C $(QUIP_SUBMODULE) wasm-signer
@echo "Hybrid signer WASM ready. Enable it in the apps with QUIP_DEV_SIGNER=1 (or ?quipSigner)."

# Build the WASM only when it is missing (so `make start` doesn't recompile the
# crate every run). Run `make quip-signer` explicitly to force a rebuild.
$(WASM_OUT):
$(MAKE) quip-signer

# Start the apps dev server (webpack-serve on :3000) with the Quip hybrid signer
# injected. Builds the signer WASM first if it isn't present yet.
start: $(WASM_OUT)
QUIP_DEV_SIGNER=1 yarn start
15 changes: 15 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
8 changes: 8 additions & 0 deletions docker/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,14 @@ RUN npm install yarn -g
WORKDIR /apps
COPY . .

# Quip hybrid dev signer, baked into the static bundle by default. webpack's
# DefinePlugin reads this at build time (the same `process.env.QUIP_DEV_SIGNER`
# initQuipSigner.ts checks), so it must be set before `yarn build:www` — exactly
# like `QUIP_DEV_SIGNER=1 yarn start` locally. Override with
# `--build-arg QUIP_DEV_SIGNER=` to build an image without the signer.
ARG QUIP_DEV_SIGNER=1
ENV QUIP_DEV_SIGNER=$QUIP_DEV_SIGNER

RUN yarn && NODE_ENV=production yarn build:www

# ===========================================================
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
13 changes: 10 additions & 3 deletions packages/apps/src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import '@polkadot/api-augment/substrate';
import React from 'react';
import { createRoot } from 'react-dom/client';

import { initQuipSigner } from './initQuipSigner.js';
import Root from './Root.js';

const rootId = 'root';
Expand All @@ -20,6 +21,12 @@ if (!rootElement) {
throw new Error(`Unable to find element with id '${rootId}'`);
}

createRoot(rootElement).render(
<Root isElectron={false} />
);
void initQuipSigner()
.catch((error): void => {
console.error('Quip dev signer initialization failed', error);
})
.finally((): void => {
createRoot(rootElement).render(
<Root isElectron={false} />
);
});
Loading