@nockchain/rose 0.1.4-nightly.5
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/.github/workflows/artifacts.yml +33 -0
- package/.github/workflows/ci.yml +68 -0
- package/.github/workflows/publish-sdk.yml +35 -0
- package/.nvmrc +1 -0
- package/.prettierignore +5 -0
- package/.prettierrc +8 -0
- package/LICENSE +22 -0
- package/README.md +117 -0
- package/extension/background/index.ts +1500 -0
- package/extension/content/index.ts +59 -0
- package/extension/icons/rose.svg +27 -0
- package/extension/icons/rose128.png +0 -0
- package/extension/icons/rose16.png +0 -0
- package/extension/icons/rose256.png +0 -0
- package/extension/icons/rose32.png +0 -0
- package/extension/icons/rose48.png +0 -0
- package/extension/icons/rose512.png +0 -0
- package/extension/inpage/index.ts +86 -0
- package/extension/manifest.json +48 -0
- package/extension/popup/Popup.tsx +94 -0
- package/extension/popup/Router.tsx +121 -0
- package/extension/popup/assets/arrow-down-icon.svg +3 -0
- package/extension/popup/assets/arrow-left-icon.svg +3 -0
- package/extension/popup/assets/arrow-right-icon.svg +3 -0
- package/extension/popup/assets/arrow-up-icon.svg +3 -0
- package/extension/popup/assets/arrow-up-right-icon.svg +3 -0
- package/extension/popup/assets/checkmark-icon.svg +3 -0
- package/extension/popup/assets/checkmark-pencil-icon.svg +3 -0
- package/extension/popup/assets/checkmark-success-icon.svg +3 -0
- package/extension/popup/assets/clock-icon.svg +3 -0
- package/extension/popup/assets/close-x-icon.svg +3 -0
- package/extension/popup/assets/copy-icon.svg +6 -0
- package/extension/popup/assets/explorer-icon.svg +3 -0
- package/extension/popup/assets/eye-off-icon.svg +3 -0
- package/extension/popup/assets/eye-open-icon.svg +4 -0
- package/extension/popup/assets/feedback-icon.svg +3 -0
- package/extension/popup/assets/green-status-dot.svg +3 -0
- package/extension/popup/assets/info-icon.svg +3 -0
- package/extension/popup/assets/iris-logo-40.svg +27 -0
- package/extension/popup/assets/iris-logo-96.svg +27 -0
- package/extension/popup/assets/iris-logo-blue.svg +27 -0
- package/extension/popup/assets/iris-logo-no-eye.svg +27 -0
- package/extension/popup/assets/iris-logo-orange.svg +27 -0
- package/extension/popup/assets/iris-logo.svg +27 -0
- package/extension/popup/assets/key-icon.svg +3 -0
- package/extension/popup/assets/lock-icon-yellow.svg +3 -0
- package/extension/popup/assets/lock-icon.svg +3 -0
- package/extension/popup/assets/pencil-edit-icon.svg +3 -0
- package/extension/popup/assets/permissions-icon.svg +3 -0
- package/extension/popup/assets/receipt-icon.svg +5 -0
- package/extension/popup/assets/refresh-icon.svg +3 -0
- package/extension/popup/assets/settings-gear-icon.svg +8 -0
- package/extension/popup/assets/settings-icon.svg +3 -0
- package/extension/popup/assets/theme-icon.svg +3 -0
- package/extension/popup/assets/trash-bin-icon.svg +3 -0
- package/extension/popup/assets/trend-down-arrow.svg +5 -0
- package/extension/popup/assets/trend-up-arrow.svg +5 -0
- package/extension/popup/assets/user-account-icon.svg +3 -0
- package/extension/popup/assets/vector-bottom-left.svg +9 -0
- package/extension/popup/assets/vector-left.svg +9 -0
- package/extension/popup/assets/vector-right.svg +9 -0
- package/extension/popup/assets/vector-top-right-rotated.svg +8 -0
- package/extension/popup/assets/vector-top-right.svg +9 -0
- package/extension/popup/assets/wallet-dropdown-arrow.svg +5 -0
- package/extension/popup/assets/wallet-icon-style-1.svg +6 -0
- package/extension/popup/assets/wallet-icon-style-10.svg +8 -0
- package/extension/popup/assets/wallet-icon-style-11.svg +8 -0
- package/extension/popup/assets/wallet-icon-style-12.svg +8 -0
- package/extension/popup/assets/wallet-icon-style-13.svg +8 -0
- package/extension/popup/assets/wallet-icon-style-14.svg +8 -0
- package/extension/popup/assets/wallet-icon-style-15.svg +8 -0
- package/extension/popup/assets/wallet-icon-style-2.svg +8 -0
- package/extension/popup/assets/wallet-icon-style-3.svg +8 -0
- package/extension/popup/assets/wallet-icon-style-4.svg +8 -0
- package/extension/popup/assets/wallet-icon-style-5.svg +8 -0
- package/extension/popup/assets/wallet-icon-style-6.svg +8 -0
- package/extension/popup/assets/wallet-icon-style-7.svg +8 -0
- package/extension/popup/assets/wallet-icon-style-8.svg +8 -0
- package/extension/popup/assets/wallet-icon-style-9.svg +8 -0
- package/extension/popup/components/AccountIcon.tsx +78 -0
- package/extension/popup/components/AccountSelector.tsx +246 -0
- package/extension/popup/components/Alert.tsx +48 -0
- package/extension/popup/components/ConfirmModal.tsx +81 -0
- package/extension/popup/components/PasswordInput.tsx +49 -0
- package/extension/popup/components/ScreenContainer.tsx +17 -0
- package/extension/popup/components/SiteIcon.tsx +60 -0
- package/extension/popup/components/ThemeToggle.tsx +44 -0
- package/extension/popup/components/icons/ArrowDownLeftIcon.tsx +20 -0
- package/extension/popup/components/icons/ArrowUpRightIcon.tsx +20 -0
- package/extension/popup/components/icons/CheckIcon.tsx +20 -0
- package/extension/popup/components/icons/ChevronDownIcon.tsx +15 -0
- package/extension/popup/components/icons/ChevronLeftIcon.tsx +15 -0
- package/extension/popup/components/icons/ChevronRightIcon.tsx +15 -0
- package/extension/popup/components/icons/ChevronUpIcon.tsx +15 -0
- package/extension/popup/components/icons/CloseIcon.tsx +26 -0
- package/extension/popup/components/icons/CopyIcon.tsx +20 -0
- package/extension/popup/components/icons/EditIcon.tsx +20 -0
- package/extension/popup/components/icons/EyeIcon.tsx +13 -0
- package/extension/popup/components/icons/EyeOffIcon.tsx +13 -0
- package/extension/popup/components/icons/InfoIcon.tsx +20 -0
- package/extension/popup/components/icons/LockIcon.tsx +20 -0
- package/extension/popup/components/icons/PlusIcon.tsx +15 -0
- package/extension/popup/components/icons/ReceiveArrowIcon.tsx +14 -0
- package/extension/popup/components/icons/ReceiveCircleIcon.tsx +20 -0
- package/extension/popup/components/icons/SendPaperPlaneIcon.tsx +18 -0
- package/extension/popup/components/icons/SentArrowIcon.tsx +21 -0
- package/extension/popup/components/icons/SettingsIcon.tsx +26 -0
- package/extension/popup/components/icons/ShieldIcon.tsx +20 -0
- package/extension/popup/components/icons/UploadIcon.tsx +20 -0
- package/extension/popup/components/icons/WalletIcon.tsx +20 -0
- package/extension/popup/contexts/ThemeContext.tsx +105 -0
- package/extension/popup/hooks/useApprovalDetection.ts +128 -0
- package/extension/popup/hooks/useAutoFocus.ts +36 -0
- package/extension/popup/hooks/useAutoRejectOnClose.ts +25 -0
- package/extension/popup/hooks/useClickOutside.ts +33 -0
- package/extension/popup/hooks/useCopyToClipboard.ts +33 -0
- package/extension/popup/hooks/useFavicon.ts +64 -0
- package/extension/popup/hooks/useNumericInput.ts +93 -0
- package/extension/popup/index.html +13 -0
- package/extension/popup/index.tsx +24 -0
- package/extension/popup/screens/AboutScreen.tsx +118 -0
- package/extension/popup/screens/HomeScreen.tailwind.css +85 -0
- package/extension/popup/screens/HomeScreen.tsx +902 -0
- package/extension/popup/screens/KeySettingsPasswordScreen.tsx +164 -0
- package/extension/popup/screens/LockTimeScreen.tsx +155 -0
- package/extension/popup/screens/ReceiveScreen.tsx +149 -0
- package/extension/popup/screens/RecoveryPhraseScreen.tsx +183 -0
- package/extension/popup/screens/SendReviewScreen.tsx +308 -0
- package/extension/popup/screens/SendScreen.tsx +825 -0
- package/extension/popup/screens/SendSubmittedScreen.tsx +193 -0
- package/extension/popup/screens/SettingsScreen.tsx +116 -0
- package/extension/popup/screens/ThemeSettingsScreen.tsx +107 -0
- package/extension/popup/screens/TransactionDetailsScreen.tsx +346 -0
- package/extension/popup/screens/ViewSecretPhraseScreen.tsx +212 -0
- package/extension/popup/screens/WalletPermissionsScreen.tsx +123 -0
- package/extension/popup/screens/WalletSettingsScreen.tsx +381 -0
- package/extension/popup/screens/WalletStylingScreen.tsx +306 -0
- package/extension/popup/screens/approvals/ConnectApprovalScreen.tsx +136 -0
- package/extension/popup/screens/approvals/SignMessageScreen.tsx +140 -0
- package/extension/popup/screens/approvals/SignRawTxScreen.tsx +320 -0
- package/extension/popup/screens/approvals/TransactionApprovalScreen.tsx +167 -0
- package/extension/popup/screens/onboarding/BackupScreen.tsx +254 -0
- package/extension/popup/screens/onboarding/CreateScreen.tsx +273 -0
- package/extension/popup/screens/onboarding/ImportScreen.tsx +676 -0
- package/extension/popup/screens/onboarding/ImportScreenV0.tsx +678 -0
- package/extension/popup/screens/onboarding/ImportSuccessScreen.tsx +236 -0
- package/extension/popup/screens/onboarding/ResumeBackupScreen.tsx +166 -0
- package/extension/popup/screens/onboarding/StartScreen.tsx +142 -0
- package/extension/popup/screens/onboarding/SuccessScreen.tsx +193 -0
- package/extension/popup/screens/onboarding/VerifyScreen.tsx +220 -0
- package/extension/popup/screens/system/LockedScreen.tsx +288 -0
- package/extension/popup/screens/transactions/ReceiveScreen.tsx +84 -0
- package/extension/popup/screens/transactions/SentScreen.tsx +138 -0
- package/extension/popup/store.ts +482 -0
- package/extension/popup/styles.css +246 -0
- package/extension/popup/utils/format.ts +58 -0
- package/extension/popup/utils/formatWalletError.ts +36 -0
- package/extension/popup/utils/memo.ts +299 -0
- package/extension/popup/utils/messaging.ts +16 -0
- package/extension/shared/address-encoding.ts +69 -0
- package/extension/shared/balance-query.ts +123 -0
- package/extension/shared/constants.ts +386 -0
- package/extension/shared/currency.ts +128 -0
- package/extension/shared/first-name-derivation.ts +128 -0
- package/extension/shared/keyfile.ts +58 -0
- package/extension/shared/onboarding.ts +78 -0
- package/extension/shared/price-api.ts +79 -0
- package/extension/shared/rpc-client-browser.ts +315 -0
- package/extension/shared/transaction-builder.ts +443 -0
- package/extension/shared/types.ts +450 -0
- package/extension/shared/utxo-diff.ts +212 -0
- package/extension/shared/utxo-store.ts +548 -0
- package/extension/shared/utxo-sync.ts +343 -0
- package/extension/shared/validators.ts +26 -0
- package/extension/shared/vault.ts +1580 -0
- package/extension/shared/wallet-crypto.ts +77 -0
- package/extension/shared/wasm-utils.ts +76 -0
- package/extension/shared/webcrypto.ts +67 -0
- package/extension/types/wasm.d.ts +13 -0
- package/package.json +39 -0
- package/postcss.config.js +6 -0
- package/rose-extension-dist.zip +0 -0
- package/sdk/README.md +88 -0
- package/sdk/examples/app.ts +166 -0
- package/sdk/examples/index.html +51 -0
- package/sdk/examples/tsconfig.json +15 -0
- package/sdk/examples/tx-builder.html +532 -0
- package/sdk/examples/tx-builder.ts +1766 -0
- package/sdk/package-lock.json +424 -0
- package/sdk/package.json +68 -0
- package/sdk/src/constants.ts +28 -0
- package/sdk/src/errors.ts +74 -0
- package/sdk/src/hooks/index.ts +1 -0
- package/sdk/src/hooks/use-rose.ts +94 -0
- package/sdk/src/index.ts +12 -0
- package/sdk/src/provider.ts +396 -0
- package/sdk/src/transaction.ts +163 -0
- package/sdk/src/types/rose-wasm.d.ts +14 -0
- package/sdk/src/types.ts +97 -0
- package/sdk/src/wasm.ts +13 -0
- package/sdk/tsconfig.json +20 -0
- package/sdk/vite.config.examples.ts +32 -0
- package/tailwind.config.ts +38 -0
- package/tsconfig.json +20 -0
- package/vite.config.ts +60 -0
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Wallet cryptographic utilities
|
|
3
|
+
* Integrates Nockchain WASM bindings
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import {
|
|
7
|
+
generateMnemonic as generateMnemonicScure,
|
|
8
|
+
validateMnemonic as validateMnemonicScure,
|
|
9
|
+
} from '@scure/bip39';
|
|
10
|
+
import { wordlist } from '@scure/bip39/wordlists/english.js';
|
|
11
|
+
import { deriveMasterKeyFromMnemonic } from '@nockchain/rose-wasm/rose_wasm.js';
|
|
12
|
+
import { publicKeyToPKH } from './address-encoding';
|
|
13
|
+
import { initIrisSdkOnce } from './wasm-utils';
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Generates a BIP-39 mnemonic (24 words)
|
|
17
|
+
* Uses 256 bits of entropy for maximum security
|
|
18
|
+
*/
|
|
19
|
+
export function generateMnemonic(): string {
|
|
20
|
+
return generateMnemonicScure(wordlist, 256);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Validates a BIP-39 mnemonic
|
|
25
|
+
* @param mnemonic - The mnemonic phrase to validate
|
|
26
|
+
* @returns true if valid, false otherwise
|
|
27
|
+
*/
|
|
28
|
+
export function validateMnemonic(mnemonic: string): boolean {
|
|
29
|
+
return validateMnemonicScure(mnemonic, wordlist);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Derives a Nockchain v1 PKH address from the master key (no child derivation)
|
|
34
|
+
* This matches the CLI wallet behavior
|
|
35
|
+
* @param mnemonic - The BIP-39 mnemonic phrase
|
|
36
|
+
* @returns A Base58-encoded Nockchain v1 PKH address (~60 characters)
|
|
37
|
+
*/
|
|
38
|
+
export async function deriveAddressFromMaster(mnemonic: string): Promise<string> {
|
|
39
|
+
await initIrisSdkOnce();
|
|
40
|
+
|
|
41
|
+
// Derive master key from mnemonic
|
|
42
|
+
const masterKey = deriveMasterKeyFromMnemonic(mnemonic, '');
|
|
43
|
+
|
|
44
|
+
// Use master key public key directly (no child derivation)
|
|
45
|
+
const address = publicKeyToPKH(masterKey.publicKey);
|
|
46
|
+
|
|
47
|
+
// Clean up WASM memory
|
|
48
|
+
masterKey.free();
|
|
49
|
+
|
|
50
|
+
return address;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Derives a Nockchain v1 PKH address from a mnemonic using SLIP-10 child derivation
|
|
55
|
+
* @param mnemonic - The BIP-39 mnemonic phrase
|
|
56
|
+
* @param accountIndex - The account derivation index (default 0)
|
|
57
|
+
* @returns A Base58-encoded Nockchain v1 PKH address (~60 characters)
|
|
58
|
+
*/
|
|
59
|
+
export async function deriveAddress(mnemonic: string, accountIndex: number = 0): Promise<string> {
|
|
60
|
+
await initIrisSdkOnce();
|
|
61
|
+
|
|
62
|
+
// Derive master key from mnemonic
|
|
63
|
+
const masterKey = deriveMasterKeyFromMnemonic(mnemonic, '');
|
|
64
|
+
|
|
65
|
+
// Derive child key at account index
|
|
66
|
+
const childKey = masterKey.deriveChild(accountIndex);
|
|
67
|
+
|
|
68
|
+
// Get the public key hash (PKH) for v1 addresses
|
|
69
|
+
// v1 uses TIP5 hash of the public key, base58 encoded
|
|
70
|
+
const address = publicKeyToPKH(childKey.publicKey);
|
|
71
|
+
|
|
72
|
+
// Clean up WASM memory
|
|
73
|
+
childKey.free();
|
|
74
|
+
masterKey.free();
|
|
75
|
+
|
|
76
|
+
return address;
|
|
77
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WASM Utilities
|
|
3
|
+
* Centralized utilities for loading and initializing WASM modules
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import initWasm from '@nockchain/rose-wasm/rose_wasm.js';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Track if WASM modules have been initialized (per-context)
|
|
10
|
+
*/
|
|
11
|
+
let wasmInitialized = false;
|
|
12
|
+
const IRIS_WASM_INIT_KEY = '__rose_wasm_init_promise__';
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Initialize WASM modules (low-level, no caching).
|
|
16
|
+
*
|
|
17
|
+
* Prefer calling `initIrisSdkOnce()` (or its aliases) instead.
|
|
18
|
+
*/
|
|
19
|
+
async function initWasmModulesRaw(): Promise<void> {
|
|
20
|
+
// Let the wasm-bindgen loader resolve the `.wasm` URL via `import.meta.url`.
|
|
21
|
+
// Vite will rewrite that into a hashed asset (e.g. `dist/assets/rose_wasm_bg-<hash>.wasm`)
|
|
22
|
+
// and the extension can fetch it from its own origin.
|
|
23
|
+
await initWasm();
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Initialize Rose WASM once per context (promise-cached).
|
|
28
|
+
* Concurrent callers share a single init Promise (SDK-style).
|
|
29
|
+
*/
|
|
30
|
+
export async function initIrisSdkOnce(): Promise<void> {
|
|
31
|
+
if (wasmInitialized) return;
|
|
32
|
+
|
|
33
|
+
const g = globalThis as typeof globalThis & Record<string, unknown>;
|
|
34
|
+
const existing = g[IRIS_WASM_INIT_KEY];
|
|
35
|
+
|
|
36
|
+
if (existing && existing instanceof Promise) {
|
|
37
|
+
return existing.catch(err => {
|
|
38
|
+
if (g[IRIS_WASM_INIT_KEY] === existing) {
|
|
39
|
+
delete g[IRIS_WASM_INIT_KEY];
|
|
40
|
+
}
|
|
41
|
+
throw err;
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const p = initWasmModulesRaw()
|
|
46
|
+
.then(() => {
|
|
47
|
+
wasmInitialized = true;
|
|
48
|
+
})
|
|
49
|
+
.catch(err => {
|
|
50
|
+
if (g[IRIS_WASM_INIT_KEY] === p) {
|
|
51
|
+
delete g[IRIS_WASM_INIT_KEY];
|
|
52
|
+
}
|
|
53
|
+
throw err;
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
g[IRIS_WASM_INIT_KEY] = p;
|
|
57
|
+
await p;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Back-compat alias.
|
|
62
|
+
* Historically, the codebase used `ensureWasmInitialized()` to mean "once per context".
|
|
63
|
+
*/
|
|
64
|
+
export async function ensureWasmInitialized(): Promise<void> {
|
|
65
|
+
return initIrisSdkOnce();
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Exported for compatibility with recent call sites.
|
|
70
|
+
*
|
|
71
|
+
* Important: despite the name, this is intentionally **deduped** and safe to call many times.
|
|
72
|
+
* If you truly need a raw init (rare), use a dedicated helper in this module.
|
|
73
|
+
*/
|
|
74
|
+
export async function initWasmModules(): Promise<void> {
|
|
75
|
+
return initIrisSdkOnce();
|
|
76
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WebCrypto utilities for vault encryption/decryption
|
|
3
|
+
* Uses PBKDF2 for key derivation and AES-GCM for encryption
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/** PBKDF2 iteration count (OWASP recommends 600k+ for SHA-256, we use 310k for UX balance) */
|
|
7
|
+
export const PBKDF2_ITERATIONS = 310_000;
|
|
8
|
+
|
|
9
|
+
export function rand(n: number): Uint8Array {
|
|
10
|
+
const u = new Uint8Array(n);
|
|
11
|
+
crypto.getRandomValues(u);
|
|
12
|
+
return u;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export async function deriveKeyPBKDF2(
|
|
16
|
+
password: string,
|
|
17
|
+
salt: Uint8Array,
|
|
18
|
+
iterations: number = PBKDF2_ITERATIONS,
|
|
19
|
+
hash: 'SHA-256' | 'SHA-512' = 'SHA-256'
|
|
20
|
+
): Promise<{ key: CryptoKey; salt: Uint8Array }> {
|
|
21
|
+
const enc = new TextEncoder();
|
|
22
|
+
const baseKey = await crypto.subtle.importKey(
|
|
23
|
+
'raw',
|
|
24
|
+
enc.encode(password),
|
|
25
|
+
{ name: 'PBKDF2' },
|
|
26
|
+
false,
|
|
27
|
+
['deriveKey']
|
|
28
|
+
);
|
|
29
|
+
const key = await crypto.subtle.deriveKey(
|
|
30
|
+
{
|
|
31
|
+
name: 'PBKDF2',
|
|
32
|
+
salt: salt as BufferSource,
|
|
33
|
+
iterations,
|
|
34
|
+
hash,
|
|
35
|
+
},
|
|
36
|
+
baseKey,
|
|
37
|
+
{ name: 'AES-GCM', length: 256 },
|
|
38
|
+
true,
|
|
39
|
+
['encrypt', 'decrypt']
|
|
40
|
+
);
|
|
41
|
+
// Return key and salt as plain object (safe - no mutation of CryptoKey)
|
|
42
|
+
return { key, salt };
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export async function encryptGCM(
|
|
46
|
+
key: CryptoKey,
|
|
47
|
+
data: Uint8Array
|
|
48
|
+
): Promise<{ iv: Uint8Array; ct: Uint8Array }> {
|
|
49
|
+
const iv = rand(12);
|
|
50
|
+
const ct = new Uint8Array(
|
|
51
|
+
await crypto.subtle.encrypt(
|
|
52
|
+
{ name: 'AES-GCM', iv: iv as BufferSource },
|
|
53
|
+
key,
|
|
54
|
+
data as BufferSource
|
|
55
|
+
)
|
|
56
|
+
);
|
|
57
|
+
return { iv, ct };
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export async function decryptGCM(key: CryptoKey, iv: Uint8Array, ct: Uint8Array): Promise<string> {
|
|
61
|
+
const pt = await crypto.subtle.decrypt(
|
|
62
|
+
{ name: 'AES-GCM', iv: iv as BufferSource },
|
|
63
|
+
key,
|
|
64
|
+
ct as BufferSource
|
|
65
|
+
);
|
|
66
|
+
return new TextDecoder().decode(pt);
|
|
67
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Type declarations for WASM modules
|
|
3
|
+
* TypeScript can't find the .d.ts files when importing .js extensions
|
|
4
|
+
* with moduleResolution: "bundler", so we declare them here
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
/// <reference path="@nockchain/rose-wasm/rose_wasm.d.ts" />
|
|
8
|
+
|
|
9
|
+
declare module '@nockchain/rose-wasm/rose_wasm.js' {
|
|
10
|
+
export * from '@nockchain/rose-wasm/rose_wasm';
|
|
11
|
+
import init from '@nockchain/rose-wasm/rose_wasm';
|
|
12
|
+
export default init;
|
|
13
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@nockchain/rose",
|
|
3
|
+
"version": "0.1.4-nightly.5",
|
|
4
|
+
"description": "Rose - Chrome Wallet Extension for Nockchain",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"dev": "vite build --watch",
|
|
8
|
+
"build": "tsc && vite build",
|
|
9
|
+
"preview": "vite preview",
|
|
10
|
+
"format": "prettier --write .",
|
|
11
|
+
"format:check": "prettier --check .",
|
|
12
|
+
"typecheck": "tsc --noEmit"
|
|
13
|
+
},
|
|
14
|
+
"dependencies": {
|
|
15
|
+
"@fontsource/inter": "5.2.8",
|
|
16
|
+
"@fontsource/lora": "5.2.8",
|
|
17
|
+
"@nockchain/sdk": "0.1.4-nightly.5",
|
|
18
|
+
"@scure/base": "2.0.0",
|
|
19
|
+
"@scure/bip39": "2.0.1",
|
|
20
|
+
"react": "19.2.3",
|
|
21
|
+
"react-dom": "19.2.3",
|
|
22
|
+
"zustand": "5.0.9"
|
|
23
|
+
},
|
|
24
|
+
"devDependencies": {
|
|
25
|
+
"@crxjs/vite-plugin": "2.3.0",
|
|
26
|
+
"@tailwindcss/postcss": "4.1.18",
|
|
27
|
+
"@types/chrome": "0.1.32",
|
|
28
|
+
"@types/node": "20.19.6",
|
|
29
|
+
"@types/react": "19.2.7",
|
|
30
|
+
"@types/react-dom": "19.2.3",
|
|
31
|
+
"autoprefixer": "10.4.23",
|
|
32
|
+
"postcss": "8.5.6",
|
|
33
|
+
"prettier": "3.7.4",
|
|
34
|
+
"tailwindcss": "4.1.18",
|
|
35
|
+
"terser": "5.44.1",
|
|
36
|
+
"typescript": "5.9.3",
|
|
37
|
+
"vite": "7.3.0"
|
|
38
|
+
}
|
|
39
|
+
}
|
|
Binary file
|
package/sdk/README.md
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
# `@nockchain/sdk`
|
|
2
|
+
|
|
3
|
+
TypeScript SDK for interacting with the **Rose** browser wallet extension (Nockchain).
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm i @nockchain/sdk
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## What you get
|
|
12
|
+
|
|
13
|
+
- **`NockchainProvider`**: connect to Rose and request signatures / transactions (EIP-1193-ish API).
|
|
14
|
+
- **`TransactionBuilder`**: small fluent helper for constructing the simple “send transaction” payload.
|
|
15
|
+
- **WASM**: use `@nockchain/rose-wasm` directly for `TxBuilder`, `GrpcClient`, etc.
|
|
16
|
+
- **React Hook**: `useIris()` for one-time WASM init + gRPC client + provider wiring.
|
|
17
|
+
|
|
18
|
+
## Basic usage (provider)
|
|
19
|
+
|
|
20
|
+
```ts
|
|
21
|
+
import { NockchainProvider } from '@nockchain/sdk';
|
|
22
|
+
|
|
23
|
+
const provider = new NockchainProvider();
|
|
24
|
+
const { pkh, grpcEndpoint } = await provider.connect();
|
|
25
|
+
|
|
26
|
+
const sig = await provider.signMessage('hello');
|
|
27
|
+
console.log(sig.signature, sig.publicKeyHex);
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Building a simple transaction payload
|
|
31
|
+
|
|
32
|
+
```ts
|
|
33
|
+
import { NockchainProvider, TransactionBuilder } from '@nockchain/sdk';
|
|
34
|
+
|
|
35
|
+
const provider = new NockchainProvider();
|
|
36
|
+
await provider.connect();
|
|
37
|
+
|
|
38
|
+
const tx = new TransactionBuilder().to('...recipient_pkh...').amount(1_000_000).build();
|
|
39
|
+
const txId = await provider.sendTransaction(tx);
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## WASM / raw transaction signing
|
|
43
|
+
|
|
44
|
+
If you’re using `@nockchain/rose-wasm` types like `TxBuilder`, import them directly:
|
|
45
|
+
|
|
46
|
+
```ts
|
|
47
|
+
import {
|
|
48
|
+
TxBuilder,
|
|
49
|
+
Pkh,
|
|
50
|
+
SpendCondition,
|
|
51
|
+
Digest,
|
|
52
|
+
GrpcClient,
|
|
53
|
+
RawTx,
|
|
54
|
+
Note,
|
|
55
|
+
} from '@nockchain/rose-wasm/rose_wasm.js';
|
|
56
|
+
import { NockchainProvider } from '@nockchain/sdk';
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
See `sdk/examples/` for an end-to-end example of building + signing a raw transaction.
|
|
60
|
+
|
|
61
|
+
## React: `useIris` hook
|
|
62
|
+
|
|
63
|
+
```tsx
|
|
64
|
+
import { useIris } from '@nockchain/sdk';
|
|
65
|
+
|
|
66
|
+
export function App() {
|
|
67
|
+
const { provider, rpcClient, status, error, isReady } = useIris({
|
|
68
|
+
rpcUrl: 'https://rpc.nockbox.org',
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
if (status === 'loading') return <div>Loading…</div>;
|
|
72
|
+
if (status === 'error') return <pre>{String(error)}</pre>;
|
|
73
|
+
if (!isReady) return null;
|
|
74
|
+
|
|
75
|
+
return <div>Ready: {String(!!provider && !!rpcClient)}</div>;
|
|
76
|
+
}
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
Notes:
|
|
80
|
+
|
|
81
|
+
- `react` is a **peer dependency** (you bring your own React).
|
|
82
|
+
- The hook initializes WASM once per page load (safe for StrictMode/HMR).
|
|
83
|
+
|
|
84
|
+
## Development notes (this monorepo)
|
|
85
|
+
|
|
86
|
+
This SDK is built with `tsc` and publishes **compiled output** from `sdk/dist/`.
|
|
87
|
+
|
|
88
|
+
If you are iterating on `@nockchain/rose-wasm`, the recommended workflow is to publish it to a **local npm registry** (e.g. Verdaccio), then publish `@nockchain/sdk` against that version, and finally consume Rose using normal semver dependencies (no `file:`).
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
import { NockchainProvider, wasm } from '../src/index';
|
|
2
|
+
|
|
3
|
+
const statusDiv = document.getElementById('status')!;
|
|
4
|
+
const outputPre = document.getElementById('output')!;
|
|
5
|
+
const connectBtn = document.getElementById('connectBtn') as HTMLButtonElement;
|
|
6
|
+
const signRawTxBtn = document.getElementById('signRawTxBtn') as HTMLButtonElement;
|
|
7
|
+
const recipientInput = document.getElementById('recipientInput') as HTMLInputElement;
|
|
8
|
+
|
|
9
|
+
let provider: NockchainProvider;
|
|
10
|
+
let grpcEndpoint: string | null = null;
|
|
11
|
+
let walletPkh: string | null = null;
|
|
12
|
+
|
|
13
|
+
function log(msg: string) {
|
|
14
|
+
outputPre.textContent += msg + '\n';
|
|
15
|
+
console.log(msg);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
async function init() {
|
|
19
|
+
try {
|
|
20
|
+
await wasm.default();
|
|
21
|
+
log('WASM initialized');
|
|
22
|
+
|
|
23
|
+
// Initialize NockchainProvider
|
|
24
|
+
provider = new NockchainProvider();
|
|
25
|
+
log('NockchainProvider initialized');
|
|
26
|
+
} catch (e) {
|
|
27
|
+
log('Failed to init: ' + e);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
connectBtn.onclick = async () => {
|
|
32
|
+
if (!provider) {
|
|
33
|
+
log('Provider not initialized');
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
try {
|
|
37
|
+
// Connect to wallet (returns pkh and grpcEndpoint)
|
|
38
|
+
const info = await provider.connect();
|
|
39
|
+
grpcEndpoint = info.grpcEndpoint;
|
|
40
|
+
walletPkh = info.pkh;
|
|
41
|
+
|
|
42
|
+
statusDiv.textContent = 'Connected: ' + walletPkh;
|
|
43
|
+
signRawTxBtn.disabled = false;
|
|
44
|
+
log('Connected: ' + walletPkh + ' @ ' + grpcEndpoint);
|
|
45
|
+
} catch (e: any) {
|
|
46
|
+
log('Connect failed: ' + e.message);
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
signRawTxBtn.onclick = async () => {
|
|
51
|
+
try {
|
|
52
|
+
log('Building transaction...');
|
|
53
|
+
|
|
54
|
+
// 1. Validate inputs
|
|
55
|
+
if (!grpcEndpoint || !walletPkh) {
|
|
56
|
+
log('Please connect and get wallet info first');
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const recipient = recipientInput.value.trim();
|
|
61
|
+
if (!recipient) {
|
|
62
|
+
log('Please enter a recipient address');
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// 2. Create gRPC client
|
|
67
|
+
log('Creating gRPC client for: ' + grpcEndpoint);
|
|
68
|
+
const grpcClient = new wasm.GrpcClient(grpcEndpoint);
|
|
69
|
+
|
|
70
|
+
// 3. Create spend condition using wallet PKH (single, no timelock)
|
|
71
|
+
log('Creating spend condition for PKH: ' + walletPkh);
|
|
72
|
+
const pkh = wasm.Pkh.single(walletPkh);
|
|
73
|
+
const spendCondition = wasm.SpendCondition.newPkh(pkh);
|
|
74
|
+
|
|
75
|
+
// 4. Get firstName from spend condition
|
|
76
|
+
const firstName = spendCondition.firstName();
|
|
77
|
+
log('First name: ' + firstName.value.substring(0, 20) + '...');
|
|
78
|
+
|
|
79
|
+
// 5. Query notes matching this firstName
|
|
80
|
+
log('Querying notes from gRPC...');
|
|
81
|
+
const balance = await grpcClient.getBalanceByFirstName(firstName.value);
|
|
82
|
+
|
|
83
|
+
if (!balance || !balance.notes || balance.notes.length === 0) {
|
|
84
|
+
log('No notes found - wallet might be empty');
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
log('Found ' + balance.notes.length + ' notes');
|
|
89
|
+
|
|
90
|
+
// Convert notes from protobuf
|
|
91
|
+
const notes = balance.notes.map((n: any) => wasm.Note.fromProtobuf(n.note));
|
|
92
|
+
const note = notes[0];
|
|
93
|
+
const noteAssets = note.assets;
|
|
94
|
+
log('Using note with ' + noteAssets + ' nicks');
|
|
95
|
+
|
|
96
|
+
// 6. Build transaction (send 10 NOCK = 655360 nicks)
|
|
97
|
+
const TEN_NOCK_IN_NICKS = BigInt(10 * 65536);
|
|
98
|
+
const feePerWord = BigInt(32768); // 0.5 NOCK per word
|
|
99
|
+
|
|
100
|
+
log('Building transaction to send 10 NOCK...');
|
|
101
|
+
const builder = new wasm.TxBuilder(feePerWord);
|
|
102
|
+
|
|
103
|
+
// Create recipient digest
|
|
104
|
+
const recipientDigest = new wasm.Digest(recipient);
|
|
105
|
+
|
|
106
|
+
// Create refund digest (same as wallet PKH)
|
|
107
|
+
const refundDigest = new wasm.Digest(walletPkh);
|
|
108
|
+
|
|
109
|
+
// Use simpleSpend (no lockData for lower fees)
|
|
110
|
+
builder.simpleSpend(
|
|
111
|
+
[notes[0]],
|
|
112
|
+
[spendCondition],
|
|
113
|
+
recipientDigest,
|
|
114
|
+
TEN_NOCK_IN_NICKS,
|
|
115
|
+
null, // fee_override (let it auto-calculate)
|
|
116
|
+
refundDigest,
|
|
117
|
+
false // include_lock_data
|
|
118
|
+
);
|
|
119
|
+
|
|
120
|
+
// 7. Build the transaction and get notes/spend conditions
|
|
121
|
+
log('Building raw transaction...');
|
|
122
|
+
const nockchainTx = builder.build();
|
|
123
|
+
const txId = nockchainTx.id;
|
|
124
|
+
log('Transaction ID: ' + txId.value);
|
|
125
|
+
|
|
126
|
+
const rawTxProtobuf = nockchainTx.toRawTx().toProtobuf();
|
|
127
|
+
|
|
128
|
+
// Get notes and spend conditions from builder
|
|
129
|
+
const txNotes = builder.allNotes();
|
|
130
|
+
|
|
131
|
+
log('Notes count: ' + txNotes.notes.length);
|
|
132
|
+
log('Spend conditions count: ' + txNotes.spendConditions.length);
|
|
133
|
+
|
|
134
|
+
// 8. Sign using provider.signRawTx (pass wasm objects directly)
|
|
135
|
+
log('Signing transaction...');
|
|
136
|
+
const signedTxProtobuf = await provider.signRawTx({
|
|
137
|
+
rawTx: rawTxProtobuf, // Pass wasm RawTx directly
|
|
138
|
+
notes: txNotes.notes, // Pass wasm Note objects directly
|
|
139
|
+
spendConditions: txNotes.spendConditions, // Pass wasm SpendCondition objects directly
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
log('Transaction signed successfully!');
|
|
143
|
+
|
|
144
|
+
// Convert to jam string for file download
|
|
145
|
+
const signedTx = wasm.RawTx.fromProtobuf(signedTxProtobuf);
|
|
146
|
+
const jamBytes = signedTx.toJam();
|
|
147
|
+
|
|
148
|
+
// 9. Download to file using transaction ID
|
|
149
|
+
const blob = new Blob([new Uint8Array(jamBytes)], { type: 'application/jam' });
|
|
150
|
+
const url = URL.createObjectURL(blob);
|
|
151
|
+
const a = document.createElement('a');
|
|
152
|
+
a.href = url;
|
|
153
|
+
a.download = `${txId.value}.tx`;
|
|
154
|
+
document.body.appendChild(a);
|
|
155
|
+
a.click();
|
|
156
|
+
document.body.removeChild(a);
|
|
157
|
+
URL.revokeObjectURL(url);
|
|
158
|
+
|
|
159
|
+
log('Downloaded transaction to file: ' + txId.value + '.tx');
|
|
160
|
+
} catch (e: any) {
|
|
161
|
+
log('Error: ' + e.message);
|
|
162
|
+
console.error(e);
|
|
163
|
+
}
|
|
164
|
+
};
|
|
165
|
+
|
|
166
|
+
init();
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
|
+
<title>Nockbox SDK Test</title>
|
|
7
|
+
<style>
|
|
8
|
+
body {
|
|
9
|
+
font-family: sans-serif;
|
|
10
|
+
padding: 20px;
|
|
11
|
+
max-width: 800px;
|
|
12
|
+
margin: 0 auto;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
button {
|
|
16
|
+
margin: 5px;
|
|
17
|
+
padding: 10px;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
#output {
|
|
21
|
+
background: #f5f5f5;
|
|
22
|
+
padding: 10px;
|
|
23
|
+
border: 1px solid #ccc;
|
|
24
|
+
max-height: 400px;
|
|
25
|
+
overflow-y: auto;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
input {
|
|
29
|
+
padding: 8px;
|
|
30
|
+
width: 400px;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
.input-group {
|
|
34
|
+
margin: 10px 0;
|
|
35
|
+
}
|
|
36
|
+
</style>
|
|
37
|
+
</head>
|
|
38
|
+
|
|
39
|
+
<body>
|
|
40
|
+
<h1>Nockbox SDK Test</h1>
|
|
41
|
+
<div id="status">Not connected</div>
|
|
42
|
+
<button id="connectBtn">Connect</button>
|
|
43
|
+
<div class="input-group">
|
|
44
|
+
<label for="recipientInput">Recipient Address:</label><br />
|
|
45
|
+
<input type="text" id="recipientInput" placeholder="Enter recipient PKH address" />
|
|
46
|
+
</div>
|
|
47
|
+
<button id="signRawTxBtn" disabled>Build & Sign Transaction</button>
|
|
48
|
+
<pre id="output"></pre>
|
|
49
|
+
<script type="module" src="./app.ts"></script>
|
|
50
|
+
</body>
|
|
51
|
+
</html>
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2020",
|
|
4
|
+
"module": "ESNext",
|
|
5
|
+
"lib": ["ES2020", "DOM"],
|
|
6
|
+
"moduleResolution": "bundler",
|
|
7
|
+
"strict": true,
|
|
8
|
+
"esModuleInterop": true,
|
|
9
|
+
"skipLibCheck": true,
|
|
10
|
+
"forceConsistentCasingInFileNames": true,
|
|
11
|
+
"resolveJsonModule": true,
|
|
12
|
+
"noEmit": true
|
|
13
|
+
},
|
|
14
|
+
"include": ["**/*"]
|
|
15
|
+
}
|