@qorechain/wallet-adapter 0.2.0 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -34,7 +34,7 @@ tx = TxRaw{ body, authInfo, [sigC] }
34
34
  The extension type URL is `/qorechain.pqc.v1.PQCHybridSignature` and algorithm
35
35
  `1` = ML-DSA-87. The sign-bytes form `v` is chosen per network — see below.
36
36
 
37
- ## Hybrid sign-bytes: v1 and v2 (chain v3.1.98)
37
+ ## Hybrid sign-bytes: v1 and v2 (chain v3.2.0, testnet v3.1.98)
38
38
 
39
39
  The ML-DSA key signs one of two byte forms (B0 = body without the PQC extension,
40
40
  A = AuthInfo bytes):
@@ -46,23 +46,40 @@ v2: "qorechain-pqc-hybrid-v2" ‖ BE64(len chainId) ‖ chainId ‖ BE3
46
46
 
47
47
  v2 adds a domain tag (a signature the key made in any other context can never be
48
48
  valid transaction sign-bytes) and binds the chain-id. **A network accepts exactly
49
- one form at any height.** The networks that existed before chain v3.1.98
49
+ one form at any height.** The networks that existed before v2
50
50
  (`qorechain-vladi` mainnet, `qorechain-diana` testnet) verify v1 until the
51
- `v3.1.98` upgrade plan is applied on them and v2 from then on; they upgrade at
52
- different heights. Today the testnet verifies v2 and **mainnet stays on v1 until
53
- its own upgrade**. Any other chain verifies v2 from its first block.
51
+ upgrade plan that carries the switch is applied on them and v2 from then on; they
52
+ upgrade at different heights. Today the testnet verifies v2 and **mainnet stays
53
+ on v1 until its own upgrade**. Any other chain verifies v2 from its first block.
54
+
55
+ **The switch ships under two plan names.** The release is `v3.2.0`, but the
56
+ testnet already took the same handler under the earlier name `v3.1.98` and keeps
57
+ that record forever, so the chain registers both (`SIGN_BYTES_V2_UPGRADES =
58
+ ['v3.2.0', 'v3.1.98']`). A client must ask for **every** name and sign v2 if the
59
+ numeric height of **any** of them is > 0. Asking for one name only resolves v1 on
60
+ a network that upgraded under the other, and every hybrid transaction is then
61
+ refused with `pqc` code 21.
54
62
 
55
63
  `signBytesVersion` (on `QoreChainSigner`, per `signHybrid` call, and on
56
64
  `signHybridEth`) takes:
57
65
 
58
66
  - `'auto'` (default) — a non-legacy chain signs v2 with no network call. On
59
67
  `qorechain-vladi` / `qorechain-diana` the adapter asks the network
60
- `GET {rest}/cosmos/upgrade/v1beta1/applied_plan/v3.1.98` and signs v2 iff the
61
- returned height is > 0 (compared numerically: mainnet answers `{"height":"0"}`).
68
+ `GET {rest}/cosmos/upgrade/v1beta1/applied_plan/{name}` for every name in
69
+ `SIGN_BYTES_V2_UPGRADES` (`v3.2.0`, then `v3.1.98`) and signs v2 iff the
70
+ returned height of any of them is > 0 (compared numerically: a network that has
71
+ not taken a plan answers `{"height":"0"}` or `{}`). The names are asked in
72
+ order and the first positive height wins, so a network on the current release
73
+ costs one request and one that upgraded under the earlier name costs two.
62
74
  The answer is cached per (rest, chain-id) for 60 s. **Pass `rest` (the LCD URL)**;
63
- without it, or if the query fails, signing throws instead of guessing.
75
+ without it, or if a query fails, signing throws instead of guessing.
64
76
  - `'v1'` / `'v2'` — used as given, no network call.
65
77
 
78
+ **Upgrading to 0.2.1.** 0.2.0 asked for `v3.1.98` alone, which resolves v1 on a
79
+ mainnet that upgraded as `v3.2.0` and gets every hybrid transaction refused with
80
+ `pqc` code 21. Upgrade before the mainnet upgrade height; nothing else in the
81
+ resolver changed and no call site needs touching.
82
+
66
83
  **Upgrading to 0.2.0.** `rest` is optional in the TypeScript types, so a caller that forgets it compiles cleanly and only fails at runtime on `qorechain-vladi` / `qorechain-diana`. Cover your wiring with a runtime test, not just a type check. In unit tests, pass `signBytesVersion: "v1"` or `"v2"` explicitly (or inject `fetch`): `"auto"` asks the network, so a test that omits it silently depends on a live node.
67
84
 
68
85
  Signed results are the usual `TxRaw` `Uint8Array`, with `.signBytesVersion`
@@ -156,7 +173,7 @@ Keplr / any-signDirect adapter + PQC framing:
156
173
  - `derivePqcKeyFromWallet(wallet, chainId, address)` — deterministic ML-DSA-87 key from a wallet signature.
157
174
  - `hybridSignBytesV1(b0, auth)`, `hybridSignBytesV2(chainId, b0, auth)`, `hybridSignBytes(version, chainId, b0, auth)` — the two sign-bytes forms and a dispatcher (version required; the old implicit `frame()` is removed).
158
175
  - `signBytesVersionFor(chainId, v2AppliedHeight)`, `resolveSignBytesVersion({ chainId, rest?, signBytesVersion?, fetch?, ttlMs?, forceRefresh? })`, `clearSignBytesCache()`, `isHybridSignBytesRejection(errOrResult)`.
159
- - Constants `HYBRID_SIGN_BYTES_V2_DOMAIN`, `SIGN_BYTES_V2_UPGRADE` (`"v3.1.98"`), `LEGACY_SIGN_BYTES_CHAINS`.
176
+ - Constants `HYBRID_SIGN_BYTES_V2_DOMAIN`, `SIGN_BYTES_V2_UPGRADES` (`["v3.2.0", "v3.1.98"]` — every plan name that switches a network to v2), `SIGN_BYTES_V2_UPGRADE` (`"v3.2.0"`, the primary name = `SIGN_BYTES_V2_UPGRADES[0]`), `LEGACY_SIGN_BYTES_CHAINS`.
160
177
  - `encodePqcHybridSignature(algId, sig)` — proto encoder for the extension.
161
178
  - `qoreChainInfo({ chainId?, rpc, rest })` — Keplr chain descriptor; `qoreEvmChainParams(...)` / `addQoreEvmToWallet(provider, opts)` — MetaMask (EIP-3085) EVM descriptor.
162
179
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@qorechain/wallet-adapter",
3
- "version": "0.2.0",
3
+ "version": "0.2.1",
4
4
  "description": "Drop-in adapter to add QoreChain to any Cosmos wallet (Keplr, Leap, Cosmostation, …) and sign its PQC-required transactions. The wallet signs an ordinary SIGN_MODE_DIRECT SignDoc; the adapter layers a standard FIPS-204 ML-DSA-87 hybrid signature into the tx body, so no wallet code changes are needed.",
5
5
  "type": "module",
6
6
  "main": "src/index.js",
package/src/index.d.ts CHANGED
@@ -56,11 +56,14 @@ export function authSignBytes(p: { programId: string; accounts: SvmAccountMeta[]
56
56
  export function buildPhantomSvmEnvelope(p: { wallet: any; programId?: string; accounts: SvmAccountMeta[]; data: Uint8Array; recentBlockhashHex: string }): Promise<any>;
57
57
  export function buildPhantomTransfer(p: { wallet: any; fromSvmAddr: string; toSvmAddr: string; lamports: number | bigint; recentBlockhashHex: string }): Promise<any>;
58
58
  export function registerAuthenticatorMsg(p: { owner: string; phantomPubkey: Uint8Array; permissions?: string[]; expiryUnix: number | bigint; label?: string }): { typeUrl: string; value: any };
59
- // --- Per-network hybrid PQC sign-bytes (v1 legacy / v2, chain v3.1.98) ---
59
+ // --- Per-network hybrid PQC sign-bytes (v1 legacy / v2, chain v3.2.0 / testnet v3.1.98) ---
60
60
  export type SignBytesVersion = 'v1' | 'v2';
61
61
  export type SignBytesVersionOption = SignBytesVersion | 'auto';
62
62
  export const HYBRID_SIGN_BYTES_V2_DOMAIN: 'qorechain-pqc-hybrid-v2';
63
- export const SIGN_BYTES_V2_UPGRADE: 'v3.1.98';
63
+ /** Every upgrade plan name that switches a network to v2, most recent first. */
64
+ export const SIGN_BYTES_V2_UPGRADES: readonly ['v3.2.0', 'v3.1.98'];
65
+ /** The primary (current release) plan name. */
66
+ export const SIGN_BYTES_V2_UPGRADE: 'v3.2.0';
64
67
  export const LEGACY_SIGN_BYTES_CHAINS: readonly string[];
65
68
  /** v1: BE32(len b0) ‖ b0 ‖ BE32(len authInfo) ‖ authInfo. */
66
69
  export function hybridSignBytesV1(b0: Uint8Array, authInfo: Uint8Array): Uint8Array;
@@ -68,7 +71,7 @@ export function hybridSignBytesV1(b0: Uint8Array, authInfo: Uint8Array): Uint8Ar
68
71
  export function hybridSignBytesV2(chainId: string, b0: Uint8Array, authInfo: Uint8Array): Uint8Array;
69
72
  /** Version-dispatching builder; `version` is required. */
70
73
  export function hybridSignBytes(version: SignBytesVersion, chainId: string, b0: Uint8Array, authInfo: Uint8Array): Uint8Array;
71
- /** Mirror of the chain's SignBytesVersionFor; the height is compared numerically ("0" → not applied). */
74
+ /** Mirror of the chain's SignBytesVersionFor; pass the greatest applied height over SIGN_BYTES_V2_UPGRADES. The height is compared numerically ("0" → not applied). */
72
75
  export function signBytesVersionFor(chainId: string, v2AppliedHeight: string | number | bigint | null | undefined): SignBytesVersion;
73
76
  export function resolveSignBytesVersion(opts: {
74
77
  chainId: string;
package/src/index.js CHANGED
@@ -30,7 +30,7 @@ import { hybridSignBytes, resolveSignBytesVersion } from './signbytes.js';
30
30
  export { encodePqcHybridSignature, HYBRID_SIG_TYPE_URL, ALGORITHM_ML_DSA_87 };
31
31
  // Per-network hybrid sign-bytes (v1 legacy / v2) + resolver + rejection detector.
32
32
  export {
33
- HYBRID_SIGN_BYTES_V2_DOMAIN, SIGN_BYTES_V2_UPGRADE, LEGACY_SIGN_BYTES_CHAINS,
33
+ HYBRID_SIGN_BYTES_V2_DOMAIN, SIGN_BYTES_V2_UPGRADE, SIGN_BYTES_V2_UPGRADES, LEGACY_SIGN_BYTES_CHAINS,
34
34
  hybridSignBytesV1, hybridSignBytesV2, hybridSignBytes,
35
35
  signBytesVersionFor, resolveSignBytesVersion, clearSignBytesCache,
36
36
  isHybridSignBytesRejection,
package/src/signbytes.js CHANGED
@@ -10,15 +10,26 @@
10
10
  // B0 = TxBody WITHOUT the PQC extension option, A = AuthInfo bytes verbatim.
11
11
  // Byte-identical to the chain's x/pqc/types.HybridSignBytesLegacy / HybridSignBytes.
12
12
  //
13
- // Which form to sign: a network that existed before chain release v3.1.98
14
- // (qorechain-vladi mainnet, qorechain-diana testnet) verifies v1 until the
15
- // "v3.1.98" upgrade plan is applied on it, and v2 from then on. Any other chain
16
- // verifies v2 from its first block. The two networks upgrade at different
17
- // heights, so a client must ASK the target network (applied_plan) rather than
18
- // hardcode a form. That is what resolveSignBytesVersion does.
13
+ // Which form to sign: a network that existed before the release that introduced
14
+ // v2 (qorechain-vladi mainnet, qorechain-diana testnet) verifies v1 until that
15
+ // upgrade plan is applied on it, and v2 from then on. Any other chain verifies
16
+ // v2 from its first block. The two networks upgrade at different heights, so a
17
+ // client must ASK the target network (applied_plan) rather than hardcode a form.
18
+ // That is what resolveSignBytesVersion does.
19
+ //
20
+ // The switch ships under TWO plan names: the release is "v3.2.0", but the
21
+ // testnet already took the same handler under the earlier name "v3.1.98" and
22
+ // keeps that record forever. The chain registers both (x/pqc/types
23
+ // SignBytesV2Upgrades), so a client must ask for EVERY name and sign v2 if the
24
+ // numeric height of ANY of them is greater than zero. Asking for one name only
25
+ // resolves v1 on a network that upgraded under the other, and every hybrid
26
+ // transaction is then refused with pqc code 21.
19
27
 
20
28
  export const HYBRID_SIGN_BYTES_V2_DOMAIN = 'qorechain-pqc-hybrid-v2';
21
- export const SIGN_BYTES_V2_UPGRADE = 'v3.1.98';
29
+ /** Every upgrade plan name that switches a network to v2 sign-bytes, most recent first. */
30
+ export const SIGN_BYTES_V2_UPGRADES = Object.freeze(['v3.2.0', 'v3.1.98']);
31
+ /** The primary (current release) plan name; see SIGN_BYTES_V2_UPGRADES for all of them. */
32
+ export const SIGN_BYTES_V2_UPGRADE = SIGN_BYTES_V2_UPGRADES[0];
22
33
  export const LEGACY_SIGN_BYTES_CHAINS = Object.freeze(['qorechain-vladi', 'qorechain-diana']);
23
34
 
24
35
  const te = new TextEncoder();
@@ -86,10 +97,11 @@ function toHeight(h) {
86
97
  }
87
98
 
88
99
  /**
89
- * The form a client must sign for `chainId`, given the height at which the
90
- * v3.1.98 plan was applied on it (0 / "0" / missing = not applied). Mirrors the
91
- * chain's SignBytesVersionFor. Heights are compared NUMERICALLY: the node returns
92
- * the height as a string, and "0" is truthy.
100
+ * The form a client must sign for `chainId`, given the height at which a v2
101
+ * sign-bytes upgrade plan was applied on it (0 / "0" / missing = none applied;
102
+ * pass the greatest height over SIGN_BYTES_V2_UPGRADES). Mirrors the chain's
103
+ * SignBytesVersionFor. Heights are compared NUMERICALLY: the node returns the
104
+ * height as a string, and "0" is truthy.
93
105
  */
94
106
  export function signBytesVersionFor(chainId, v2AppliedHeight) {
95
107
  if (toHeight(v2AppliedHeight) > 0n) return 'v2';
@@ -107,10 +119,13 @@ function normRest(rest) { return String(rest).replace(/\/+$/, ''); }
107
119
  * Resolve the hybrid sign-bytes form for a network.
108
120
  * - 'v1' | 'v2' are returned as-is (no network).
109
121
  * - 'auto' (default): a chain that is not a legacy network gets 'v2' with no
110
- * HTTP; a legacy network is asked `GET {rest}/cosmos/upgrade/v1beta1/applied_plan/v3.1.98`
111
- * and signs v2 iff the returned height > 0. Answers are cached per
112
- * (rest, chainId) for `ttlMs`; `forceRefresh` bypasses the cache.
113
- * Throws (never guesses) when a legacy network has no `rest` or the query fails.
122
+ * HTTP; a legacy network is asked `GET {rest}/cosmos/upgrade/v1beta1/applied_plan/{name}`
123
+ * for EVERY name in SIGN_BYTES_V2_UPGRADES and signs v2 iff the numeric
124
+ * height of ANY of them is > 0. The names are asked in order and the first
125
+ * positive height wins, so a network on the current release costs one
126
+ * request and one that upgraded under the earlier name costs two. Answers
127
+ * are cached per (rest, chainId) for `ttlMs`; `forceRefresh` bypasses the cache.
128
+ * Throws (never guesses) when a legacy network has no `rest` or a query fails.
114
129
  */
115
130
  export async function resolveSignBytesVersion({
116
131
  chainId, rest, signBytesVersion = 'auto', fetch = globalThis.fetch, ttlMs = 60_000, forceRefresh = false,
@@ -138,15 +153,23 @@ export async function resolveSignBytesVersion({
138
153
  if (typeof fetch !== 'function') {
139
154
  throw new Error(`Cannot query ${base}: no fetch implementation available; ${hint}.`);
140
155
  }
141
- const url = `${base}/cosmos/upgrade/v1beta1/applied_plan/${SIGN_BYTES_V2_UPGRADE}`;
156
+ const plans = `${base}/cosmos/upgrade/v1beta1/applied_plan/{${SIGN_BYTES_V2_UPGRADES.join(',')}}`;
142
157
  let version;
143
158
  try {
144
- const res = await fetch(url, { headers: { accept: 'application/json' } });
145
- if (!res || !res.ok) throw new Error(`HTTP ${res ? res.status : 'no response'}`);
146
- const body = await res.json();
147
- version = signBytesVersionFor(chainId, body?.height ?? '0');
159
+ // Ask for every plan name; the first positive height decides (v2). Only when
160
+ // ALL of them answer 0 / {} is the network still on v1.
161
+ let applied = 0n;
162
+ for (const name of SIGN_BYTES_V2_UPGRADES) {
163
+ const url = `${base}/cosmos/upgrade/v1beta1/applied_plan/${name}`;
164
+ const res = await fetch(url, { headers: { accept: 'application/json' } });
165
+ if (!res || !res.ok) throw new Error(`HTTP ${res ? res.status : 'no response'} for ${name}`);
166
+ const body = await res.json();
167
+ applied = toHeight(body?.height ?? '0');
168
+ if (applied > 0n) break;
169
+ }
170
+ version = signBytesVersionFor(chainId, applied);
148
171
  } catch (e) {
149
- throw new Error(`Cannot determine the hybrid sign-bytes form for ${chainId} from ${url} (${e && e.message ? e.message : e}); ${hint}.`);
172
+ throw new Error(`Cannot determine the hybrid sign-bytes form for ${chainId} from ${plans} (${e && e.message ? e.message : e}); ${hint}.`);
150
173
  }
151
174
  cache.set(key, { version, at: Date.now() });
152
175
  return version;