@qorechain/wallet-adapter 0.1.0 → 0.1.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/package.json +24 -6
- package/src/index.d.ts +11 -0
- package/src/index.js +8 -2
- package/src/phantom.js +159 -0
package/package.json
CHANGED
|
@@ -1,16 +1,34 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@qorechain/wallet-adapter",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.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",
|
|
7
7
|
"types": "src/index.d.ts",
|
|
8
|
-
"exports": {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
"
|
|
8
|
+
"exports": {
|
|
9
|
+
".": "./src/index.js"
|
|
10
|
+
},
|
|
11
|
+
"files": [
|
|
12
|
+
"src",
|
|
13
|
+
"README.md"
|
|
14
|
+
],
|
|
15
|
+
"scripts": {
|
|
16
|
+
"test": "node --test"
|
|
17
|
+
},
|
|
18
|
+
"keywords": [
|
|
19
|
+
"qorechain",
|
|
20
|
+
"keplr",
|
|
21
|
+
"cosmos",
|
|
22
|
+
"wallet",
|
|
23
|
+
"post-quantum",
|
|
24
|
+
"ml-dsa",
|
|
25
|
+
"pqc",
|
|
26
|
+
"dilithium"
|
|
27
|
+
],
|
|
12
28
|
"license": "Apache-2.0",
|
|
13
|
-
"publishConfig": {
|
|
29
|
+
"publishConfig": {
|
|
30
|
+
"access": "public"
|
|
31
|
+
},
|
|
14
32
|
"dependencies": {
|
|
15
33
|
"@qorechain/pqc": "^0.1.0",
|
|
16
34
|
"cosmjs-types": "^0.9.0"
|
package/src/index.d.ts
CHANGED
|
@@ -1,5 +1,16 @@
|
|
|
1
1
|
export const HYBRID_SIG_TYPE_URL: string;
|
|
2
2
|
export const ALGORITHM_ML_DSA_87: number;
|
|
3
|
+
|
|
4
|
+
// --- Phantom / any-ed25519-wallet support ---
|
|
5
|
+
export const SYSTEM_PROGRAM_ID: string;
|
|
6
|
+
export function base58Encode(bytes: Uint8Array): string;
|
|
7
|
+
export function base58Decode(str: string, size?: number): Uint8Array;
|
|
8
|
+
export function systemTransferData(lamports: number | bigint): Uint8Array;
|
|
9
|
+
export interface SvmAccountMeta { pubkey: string; isSigner: boolean; isWritable: boolean; }
|
|
10
|
+
export function authSignBytes(p: { programId: string; accounts: SvmAccountMeta[]; data: Uint8Array; recentBlockhashHex: string }): Promise<Uint8Array>;
|
|
11
|
+
export function buildPhantomSvmEnvelope(p: { wallet: any; programId?: string; accounts: SvmAccountMeta[]; data: Uint8Array; recentBlockhashHex: string }): Promise<any>;
|
|
12
|
+
export function buildPhantomTransfer(p: { wallet: any; fromSvmAddr: string; toSvmAddr: string; lamports: number | bigint; recentBlockhashHex: string }): Promise<any>;
|
|
13
|
+
export function registerAuthenticatorMsg(p: { owner: string; phantomPubkey: Uint8Array; permissions?: string[]; expiryUnix: number | bigint; label?: string }): { typeUrl: string; value: any };
|
|
3
14
|
export function frame(b0: Uint8Array, auth: Uint8Array): Uint8Array;
|
|
4
15
|
export function encodePqcHybridSignature(algorithmId: number, sig: Uint8Array): Uint8Array;
|
|
5
16
|
export function derivePqcKeyFromWallet(wallet: any, chainId: string, address: string, domain?: string): Promise<{ publicKey: Uint8Array; secretKey: Uint8Array }>;
|
package/src/index.js
CHANGED
|
@@ -24,6 +24,11 @@
|
|
|
24
24
|
import { mldsa, shake256 } from '@qorechain/pqc';
|
|
25
25
|
import { frame, encodePqcHybridSignature, HYBRID_SIG_TYPE_URL, ALGORITHM_ML_DSA_87 } from './framing.js';
|
|
26
26
|
export { frame, encodePqcHybridSignature, HYBRID_SIG_TYPE_URL, ALGORITHM_ML_DSA_87 };
|
|
27
|
+
// Phantom / any-ed25519-wallet support: drive the one unified account from Phantom.
|
|
28
|
+
export {
|
|
29
|
+
base58Encode, base58Decode, SYSTEM_PROGRAM_ID, systemTransferData,
|
|
30
|
+
authSignBytes, buildPhantomSvmEnvelope, buildPhantomTransfer, registerAuthenticatorMsg,
|
|
31
|
+
} from './phantom.js';
|
|
27
32
|
import { TxBody, AuthInfo, TxRaw, SignerInfo, ModeInfo, Fee } from 'cosmjs-types/cosmos/tx/v1beta1/tx.js';
|
|
28
33
|
import { SignMode } from 'cosmjs-types/cosmos/tx/signing/v1beta1/signing.js';
|
|
29
34
|
import { SignDoc } from 'cosmjs-types/cosmos/tx/v1beta1/tx.js';
|
|
@@ -106,8 +111,9 @@ export function qoreChainInfo({ chainId = 'qorechain-diana', rpc, rest } = {}) {
|
|
|
106
111
|
currencies: [{ coinDenom: 'QOR', coinMinimalDenom: 'uqor', coinDecimals: 6 }],
|
|
107
112
|
feeCurrencies: [{
|
|
108
113
|
coinDenom: 'QOR', coinMinimalDenom: 'uqor', coinDecimals: 6,
|
|
109
|
-
//
|
|
110
|
-
|
|
114
|
+
// MUST be >= the chain's feemarket min_gas_price (0.1 uqor/gas). A lower
|
|
115
|
+
// value (e.g. 0.001) makes wallets build txs the fee floor rejects.
|
|
116
|
+
gasPriceStep: { low: 0.1, average: 0.15, high: 0.25 },
|
|
111
117
|
}],
|
|
112
118
|
stakeCurrency: { coinDenom: 'QOR', coinMinimalDenom: 'uqor', coinDecimals: 6 },
|
|
113
119
|
features: ['cosmwasm'],
|
package/src/phantom.js
ADDED
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
// @qorechain/wallet-adapter — Phantom (and any ed25519 wallet) support.
|
|
2
|
+
//
|
|
3
|
+
// Lets a user drive their ONE unified QoreChain account (same identity + same
|
|
4
|
+
// balance across Cosmos/EVM/SVM) from Phantom, by:
|
|
5
|
+
// 1) linking their Phantom key once as an on-chain authenticator
|
|
6
|
+
// (MsgRegisterAuthenticator, owner-signed — see registerAuthenticatorMsg), and
|
|
7
|
+
// 2) signing SVM actions with Phantom's `signMessage` over the exact bytes the
|
|
8
|
+
// node reconstructs, then posting them to the Solana-compatible `sendTransaction`.
|
|
9
|
+
//
|
|
10
|
+
// The signed bytes are domain-separated and bound to the exact action + a recent
|
|
11
|
+
// blockhash, so a signature cannot be replayed for a different action/account or
|
|
12
|
+
// (via the blockhash window) indefinitely. This mirrors the on-chain
|
|
13
|
+
// `resolveEnvelopeSigner` / `authSignBytes` in x/svm/rpc.
|
|
14
|
+
|
|
15
|
+
const B58 = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz';
|
|
16
|
+
const B58MAP = (() => { const m = {}; for (let i = 0; i < B58.length; i++) m[B58[i]] = i; return m; })();
|
|
17
|
+
|
|
18
|
+
/** base58Encode encodes bytes to a Base58 string (Bitcoin alphabet). */
|
|
19
|
+
export function base58Encode(bytes) {
|
|
20
|
+
let n = 0n;
|
|
21
|
+
for (const b of bytes) n = (n << 8n) | BigInt(b);
|
|
22
|
+
let out = '';
|
|
23
|
+
while (n > 0n) { const r = Number(n % 58n); n /= 58n; out = B58[r] + out; }
|
|
24
|
+
for (const b of bytes) { if (b === 0) out = B58[0] + out; else break; }
|
|
25
|
+
return out;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/** base58Decode decodes a Base58 string, left-padding to `size` bytes (default 32). */
|
|
29
|
+
export function base58Decode(str, size = 32) {
|
|
30
|
+
let n = 0n;
|
|
31
|
+
for (const c of str) { const v = B58MAP[c]; if (v === undefined) throw new Error('invalid base58 char: ' + c); n = n * 58n + BigInt(v); }
|
|
32
|
+
const bytes = [];
|
|
33
|
+
while (n > 0n) { bytes.unshift(Number(n & 0xffn)); n >>= 8n; }
|
|
34
|
+
for (const c of str) { if (c === '1') bytes.unshift(0); else break; }
|
|
35
|
+
while (bytes.length < size) bytes.unshift(0);
|
|
36
|
+
if (bytes.length > size) throw new Error('decoded value exceeds ' + size + ' bytes');
|
|
37
|
+
return new Uint8Array(bytes);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function toB64(bytes) {
|
|
41
|
+
if (typeof Buffer !== 'undefined') return Buffer.from(bytes).toString('base64');
|
|
42
|
+
let s = ''; for (const b of bytes) s += String.fromCharCode(b); return btoa(s);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
async function sha256(bytes) {
|
|
46
|
+
if (typeof globalThis.crypto?.subtle?.digest === 'function') {
|
|
47
|
+
return new Uint8Array(await globalThis.crypto.subtle.digest('SHA-256', bytes));
|
|
48
|
+
}
|
|
49
|
+
const { createHash } = await import('crypto'); // Node fallback
|
|
50
|
+
return new Uint8Array(createHash('sha256').update(bytes).digest());
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** The System Program id (32 zero bytes) in base58. */
|
|
54
|
+
export const SYSTEM_PROGRAM_ID = base58Encode(new Uint8Array(32));
|
|
55
|
+
|
|
56
|
+
/** systemTransferData encodes a System Program "Transfer" instruction (variant 2). */
|
|
57
|
+
export function systemTransferData(lamports) {
|
|
58
|
+
const d = new Uint8Array(12);
|
|
59
|
+
d[0] = 2; // Transfer
|
|
60
|
+
let v = BigInt(lamports);
|
|
61
|
+
for (let i = 0; i < 8; i++) { d[4 + i] = Number(v & 0xffn); v >>= 8n; }
|
|
62
|
+
return d;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* authSignBytes rebuilds, byte-for-byte, the digest the node signs-checks:
|
|
67
|
+
* sha256( "qorechain-svm-auth-v1" ‖ programId(32) ‖
|
|
68
|
+
* Σ[ addr(32) ‖ flags ] ‖ data ‖ recentBlockhash(32) )
|
|
69
|
+
* where flags = (isSigner?1) | (isWritable?2). All addresses are decoded to
|
|
70
|
+
* their raw 32 bytes; recentBlockhash is the getLatestBlockhash HEX string.
|
|
71
|
+
* Returns a 32-byte Uint8Array (what the wallet actually signs).
|
|
72
|
+
*/
|
|
73
|
+
export async function authSignBytes({ programId, accounts, data, recentBlockhashHex }) {
|
|
74
|
+
const parts = [];
|
|
75
|
+
const enc = new TextEncoder();
|
|
76
|
+
parts.push(enc.encode('qorechain-svm-auth-v1'));
|
|
77
|
+
parts.push(base58Decode(programId));
|
|
78
|
+
for (const a of accounts) {
|
|
79
|
+
parts.push(base58Decode(a.pubkey));
|
|
80
|
+
parts.push(new Uint8Array([(a.isSigner ? 1 : 0) | (a.isWritable ? 2 : 0)]));
|
|
81
|
+
}
|
|
82
|
+
parts.push(data instanceof Uint8Array ? data : new Uint8Array(data));
|
|
83
|
+
parts.push(hexToBytes(recentBlockhashHex));
|
|
84
|
+
let len = 0; for (const p of parts) len += p.length;
|
|
85
|
+
const buf = new Uint8Array(len); let o = 0; for (const p of parts) { buf.set(p, o); o += p.length; }
|
|
86
|
+
return sha256(buf);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function hexToBytes(hex) {
|
|
90
|
+
const out = new Uint8Array(hex.length / 2);
|
|
91
|
+
for (let i = 0; i < out.length; i++) out[i] = parseInt(hex.substr(i * 2, 2), 16);
|
|
92
|
+
return out;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* buildPhantomSvmEnvelope produces the `sendTransaction` envelope for an SVM
|
|
97
|
+
* instruction, authorized by a Phantom-style ed25519 wallet.
|
|
98
|
+
*
|
|
99
|
+
* @param {object} p
|
|
100
|
+
* @param {{publicKey:{toBytes:()=>Uint8Array}, signMessage:(m:Uint8Array)=>Promise<{signature:Uint8Array}>}} p.wallet Phantom provider
|
|
101
|
+
* @param {string} p.programId base58 program id (default: System Program)
|
|
102
|
+
* @param {Array<{pubkey:string,isSigner:boolean,isWritable:boolean}>} p.accounts
|
|
103
|
+
* @param {Uint8Array} p.data raw instruction data
|
|
104
|
+
* @param {string} p.recentBlockhashHex value from getLatestBlockhash (.value.blockhash, hex)
|
|
105
|
+
* @returns {Promise<object>} the JSON envelope to POST to sendTransaction
|
|
106
|
+
*/
|
|
107
|
+
export async function buildPhantomSvmEnvelope({ wallet, programId = SYSTEM_PROGRAM_ID, accounts, data, recentBlockhashHex }) {
|
|
108
|
+
const digest = await authSignBytes({ programId, accounts, data, recentBlockhashHex });
|
|
109
|
+
const { signature } = await wallet.signMessage(digest);
|
|
110
|
+
const pub = wallet.publicKey.toBytes ? wallet.publicKey.toBytes() : new Uint8Array(wallet.publicKey);
|
|
111
|
+
return {
|
|
112
|
+
programId,
|
|
113
|
+
accounts,
|
|
114
|
+
data: toB64(data),
|
|
115
|
+
auth: {
|
|
116
|
+
scheme: 'ed25519',
|
|
117
|
+
pubkey: base58Encode(pub),
|
|
118
|
+
signature: toB64(signature),
|
|
119
|
+
recentBlockhash: base58Encode(hexToBytes(recentBlockhashHex)),
|
|
120
|
+
},
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* buildPhantomTransfer is a convenience wrapper: a native-QOR transfer from the
|
|
126
|
+
* Phantom-controlled account to `toSvmAddr`, authorized by Phantom.
|
|
127
|
+
*/
|
|
128
|
+
export async function buildPhantomTransfer({ wallet, fromSvmAddr, toSvmAddr, lamports, recentBlockhashHex }) {
|
|
129
|
+
return buildPhantomSvmEnvelope({
|
|
130
|
+
wallet,
|
|
131
|
+
programId: SYSTEM_PROGRAM_ID,
|
|
132
|
+
accounts: [
|
|
133
|
+
{ pubkey: fromSvmAddr, isSigner: true, isWritable: true },
|
|
134
|
+
{ pubkey: toSvmAddr, isSigner: false, isWritable: true },
|
|
135
|
+
],
|
|
136
|
+
data: systemTransferData(lamports),
|
|
137
|
+
recentBlockhashHex,
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* registerAuthenticatorMsg builds the owner-signed MsgRegisterAuthenticator that
|
|
143
|
+
* links a Phantom ed25519 key to the owner's own account. Broadcast it with the
|
|
144
|
+
* standard QoreChainSigner (PQC-hybrid) like any other Cosmos message.
|
|
145
|
+
*/
|
|
146
|
+
export function registerAuthenticatorMsg({ owner, phantomPubkey, permissions = ['svm'], expiryUnix, label = 'phantom' }) {
|
|
147
|
+
return {
|
|
148
|
+
typeUrl: '/qorechain.abstractaccount.v1.MsgRegisterAuthenticator',
|
|
149
|
+
value: {
|
|
150
|
+
owner,
|
|
151
|
+
accountAddress: owner, // self-custody: link to the owner's own account
|
|
152
|
+
scheme: 'ed25519',
|
|
153
|
+
pubkey: phantomPubkey, // Uint8Array (32 bytes)
|
|
154
|
+
permissions,
|
|
155
|
+
expiryUnix: BigInt(expiryUnix),
|
|
156
|
+
label,
|
|
157
|
+
},
|
|
158
|
+
};
|
|
159
|
+
}
|