@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.
Files changed (205) hide show
  1. package/.github/workflows/artifacts.yml +33 -0
  2. package/.github/workflows/ci.yml +68 -0
  3. package/.github/workflows/publish-sdk.yml +35 -0
  4. package/.nvmrc +1 -0
  5. package/.prettierignore +5 -0
  6. package/.prettierrc +8 -0
  7. package/LICENSE +22 -0
  8. package/README.md +117 -0
  9. package/extension/background/index.ts +1500 -0
  10. package/extension/content/index.ts +59 -0
  11. package/extension/icons/rose.svg +27 -0
  12. package/extension/icons/rose128.png +0 -0
  13. package/extension/icons/rose16.png +0 -0
  14. package/extension/icons/rose256.png +0 -0
  15. package/extension/icons/rose32.png +0 -0
  16. package/extension/icons/rose48.png +0 -0
  17. package/extension/icons/rose512.png +0 -0
  18. package/extension/inpage/index.ts +86 -0
  19. package/extension/manifest.json +48 -0
  20. package/extension/popup/Popup.tsx +94 -0
  21. package/extension/popup/Router.tsx +121 -0
  22. package/extension/popup/assets/arrow-down-icon.svg +3 -0
  23. package/extension/popup/assets/arrow-left-icon.svg +3 -0
  24. package/extension/popup/assets/arrow-right-icon.svg +3 -0
  25. package/extension/popup/assets/arrow-up-icon.svg +3 -0
  26. package/extension/popup/assets/arrow-up-right-icon.svg +3 -0
  27. package/extension/popup/assets/checkmark-icon.svg +3 -0
  28. package/extension/popup/assets/checkmark-pencil-icon.svg +3 -0
  29. package/extension/popup/assets/checkmark-success-icon.svg +3 -0
  30. package/extension/popup/assets/clock-icon.svg +3 -0
  31. package/extension/popup/assets/close-x-icon.svg +3 -0
  32. package/extension/popup/assets/copy-icon.svg +6 -0
  33. package/extension/popup/assets/explorer-icon.svg +3 -0
  34. package/extension/popup/assets/eye-off-icon.svg +3 -0
  35. package/extension/popup/assets/eye-open-icon.svg +4 -0
  36. package/extension/popup/assets/feedback-icon.svg +3 -0
  37. package/extension/popup/assets/green-status-dot.svg +3 -0
  38. package/extension/popup/assets/info-icon.svg +3 -0
  39. package/extension/popup/assets/iris-logo-40.svg +27 -0
  40. package/extension/popup/assets/iris-logo-96.svg +27 -0
  41. package/extension/popup/assets/iris-logo-blue.svg +27 -0
  42. package/extension/popup/assets/iris-logo-no-eye.svg +27 -0
  43. package/extension/popup/assets/iris-logo-orange.svg +27 -0
  44. package/extension/popup/assets/iris-logo.svg +27 -0
  45. package/extension/popup/assets/key-icon.svg +3 -0
  46. package/extension/popup/assets/lock-icon-yellow.svg +3 -0
  47. package/extension/popup/assets/lock-icon.svg +3 -0
  48. package/extension/popup/assets/pencil-edit-icon.svg +3 -0
  49. package/extension/popup/assets/permissions-icon.svg +3 -0
  50. package/extension/popup/assets/receipt-icon.svg +5 -0
  51. package/extension/popup/assets/refresh-icon.svg +3 -0
  52. package/extension/popup/assets/settings-gear-icon.svg +8 -0
  53. package/extension/popup/assets/settings-icon.svg +3 -0
  54. package/extension/popup/assets/theme-icon.svg +3 -0
  55. package/extension/popup/assets/trash-bin-icon.svg +3 -0
  56. package/extension/popup/assets/trend-down-arrow.svg +5 -0
  57. package/extension/popup/assets/trend-up-arrow.svg +5 -0
  58. package/extension/popup/assets/user-account-icon.svg +3 -0
  59. package/extension/popup/assets/vector-bottom-left.svg +9 -0
  60. package/extension/popup/assets/vector-left.svg +9 -0
  61. package/extension/popup/assets/vector-right.svg +9 -0
  62. package/extension/popup/assets/vector-top-right-rotated.svg +8 -0
  63. package/extension/popup/assets/vector-top-right.svg +9 -0
  64. package/extension/popup/assets/wallet-dropdown-arrow.svg +5 -0
  65. package/extension/popup/assets/wallet-icon-style-1.svg +6 -0
  66. package/extension/popup/assets/wallet-icon-style-10.svg +8 -0
  67. package/extension/popup/assets/wallet-icon-style-11.svg +8 -0
  68. package/extension/popup/assets/wallet-icon-style-12.svg +8 -0
  69. package/extension/popup/assets/wallet-icon-style-13.svg +8 -0
  70. package/extension/popup/assets/wallet-icon-style-14.svg +8 -0
  71. package/extension/popup/assets/wallet-icon-style-15.svg +8 -0
  72. package/extension/popup/assets/wallet-icon-style-2.svg +8 -0
  73. package/extension/popup/assets/wallet-icon-style-3.svg +8 -0
  74. package/extension/popup/assets/wallet-icon-style-4.svg +8 -0
  75. package/extension/popup/assets/wallet-icon-style-5.svg +8 -0
  76. package/extension/popup/assets/wallet-icon-style-6.svg +8 -0
  77. package/extension/popup/assets/wallet-icon-style-7.svg +8 -0
  78. package/extension/popup/assets/wallet-icon-style-8.svg +8 -0
  79. package/extension/popup/assets/wallet-icon-style-9.svg +8 -0
  80. package/extension/popup/components/AccountIcon.tsx +78 -0
  81. package/extension/popup/components/AccountSelector.tsx +246 -0
  82. package/extension/popup/components/Alert.tsx +48 -0
  83. package/extension/popup/components/ConfirmModal.tsx +81 -0
  84. package/extension/popup/components/PasswordInput.tsx +49 -0
  85. package/extension/popup/components/ScreenContainer.tsx +17 -0
  86. package/extension/popup/components/SiteIcon.tsx +60 -0
  87. package/extension/popup/components/ThemeToggle.tsx +44 -0
  88. package/extension/popup/components/icons/ArrowDownLeftIcon.tsx +20 -0
  89. package/extension/popup/components/icons/ArrowUpRightIcon.tsx +20 -0
  90. package/extension/popup/components/icons/CheckIcon.tsx +20 -0
  91. package/extension/popup/components/icons/ChevronDownIcon.tsx +15 -0
  92. package/extension/popup/components/icons/ChevronLeftIcon.tsx +15 -0
  93. package/extension/popup/components/icons/ChevronRightIcon.tsx +15 -0
  94. package/extension/popup/components/icons/ChevronUpIcon.tsx +15 -0
  95. package/extension/popup/components/icons/CloseIcon.tsx +26 -0
  96. package/extension/popup/components/icons/CopyIcon.tsx +20 -0
  97. package/extension/popup/components/icons/EditIcon.tsx +20 -0
  98. package/extension/popup/components/icons/EyeIcon.tsx +13 -0
  99. package/extension/popup/components/icons/EyeOffIcon.tsx +13 -0
  100. package/extension/popup/components/icons/InfoIcon.tsx +20 -0
  101. package/extension/popup/components/icons/LockIcon.tsx +20 -0
  102. package/extension/popup/components/icons/PlusIcon.tsx +15 -0
  103. package/extension/popup/components/icons/ReceiveArrowIcon.tsx +14 -0
  104. package/extension/popup/components/icons/ReceiveCircleIcon.tsx +20 -0
  105. package/extension/popup/components/icons/SendPaperPlaneIcon.tsx +18 -0
  106. package/extension/popup/components/icons/SentArrowIcon.tsx +21 -0
  107. package/extension/popup/components/icons/SettingsIcon.tsx +26 -0
  108. package/extension/popup/components/icons/ShieldIcon.tsx +20 -0
  109. package/extension/popup/components/icons/UploadIcon.tsx +20 -0
  110. package/extension/popup/components/icons/WalletIcon.tsx +20 -0
  111. package/extension/popup/contexts/ThemeContext.tsx +105 -0
  112. package/extension/popup/hooks/useApprovalDetection.ts +128 -0
  113. package/extension/popup/hooks/useAutoFocus.ts +36 -0
  114. package/extension/popup/hooks/useAutoRejectOnClose.ts +25 -0
  115. package/extension/popup/hooks/useClickOutside.ts +33 -0
  116. package/extension/popup/hooks/useCopyToClipboard.ts +33 -0
  117. package/extension/popup/hooks/useFavicon.ts +64 -0
  118. package/extension/popup/hooks/useNumericInput.ts +93 -0
  119. package/extension/popup/index.html +13 -0
  120. package/extension/popup/index.tsx +24 -0
  121. package/extension/popup/screens/AboutScreen.tsx +118 -0
  122. package/extension/popup/screens/HomeScreen.tailwind.css +85 -0
  123. package/extension/popup/screens/HomeScreen.tsx +902 -0
  124. package/extension/popup/screens/KeySettingsPasswordScreen.tsx +164 -0
  125. package/extension/popup/screens/LockTimeScreen.tsx +155 -0
  126. package/extension/popup/screens/ReceiveScreen.tsx +149 -0
  127. package/extension/popup/screens/RecoveryPhraseScreen.tsx +183 -0
  128. package/extension/popup/screens/SendReviewScreen.tsx +308 -0
  129. package/extension/popup/screens/SendScreen.tsx +825 -0
  130. package/extension/popup/screens/SendSubmittedScreen.tsx +193 -0
  131. package/extension/popup/screens/SettingsScreen.tsx +116 -0
  132. package/extension/popup/screens/ThemeSettingsScreen.tsx +107 -0
  133. package/extension/popup/screens/TransactionDetailsScreen.tsx +346 -0
  134. package/extension/popup/screens/ViewSecretPhraseScreen.tsx +212 -0
  135. package/extension/popup/screens/WalletPermissionsScreen.tsx +123 -0
  136. package/extension/popup/screens/WalletSettingsScreen.tsx +381 -0
  137. package/extension/popup/screens/WalletStylingScreen.tsx +306 -0
  138. package/extension/popup/screens/approvals/ConnectApprovalScreen.tsx +136 -0
  139. package/extension/popup/screens/approvals/SignMessageScreen.tsx +140 -0
  140. package/extension/popup/screens/approvals/SignRawTxScreen.tsx +320 -0
  141. package/extension/popup/screens/approvals/TransactionApprovalScreen.tsx +167 -0
  142. package/extension/popup/screens/onboarding/BackupScreen.tsx +254 -0
  143. package/extension/popup/screens/onboarding/CreateScreen.tsx +273 -0
  144. package/extension/popup/screens/onboarding/ImportScreen.tsx +676 -0
  145. package/extension/popup/screens/onboarding/ImportScreenV0.tsx +678 -0
  146. package/extension/popup/screens/onboarding/ImportSuccessScreen.tsx +236 -0
  147. package/extension/popup/screens/onboarding/ResumeBackupScreen.tsx +166 -0
  148. package/extension/popup/screens/onboarding/StartScreen.tsx +142 -0
  149. package/extension/popup/screens/onboarding/SuccessScreen.tsx +193 -0
  150. package/extension/popup/screens/onboarding/VerifyScreen.tsx +220 -0
  151. package/extension/popup/screens/system/LockedScreen.tsx +288 -0
  152. package/extension/popup/screens/transactions/ReceiveScreen.tsx +84 -0
  153. package/extension/popup/screens/transactions/SentScreen.tsx +138 -0
  154. package/extension/popup/store.ts +482 -0
  155. package/extension/popup/styles.css +246 -0
  156. package/extension/popup/utils/format.ts +58 -0
  157. package/extension/popup/utils/formatWalletError.ts +36 -0
  158. package/extension/popup/utils/memo.ts +299 -0
  159. package/extension/popup/utils/messaging.ts +16 -0
  160. package/extension/shared/address-encoding.ts +69 -0
  161. package/extension/shared/balance-query.ts +123 -0
  162. package/extension/shared/constants.ts +386 -0
  163. package/extension/shared/currency.ts +128 -0
  164. package/extension/shared/first-name-derivation.ts +128 -0
  165. package/extension/shared/keyfile.ts +58 -0
  166. package/extension/shared/onboarding.ts +78 -0
  167. package/extension/shared/price-api.ts +79 -0
  168. package/extension/shared/rpc-client-browser.ts +315 -0
  169. package/extension/shared/transaction-builder.ts +443 -0
  170. package/extension/shared/types.ts +450 -0
  171. package/extension/shared/utxo-diff.ts +212 -0
  172. package/extension/shared/utxo-store.ts +548 -0
  173. package/extension/shared/utxo-sync.ts +343 -0
  174. package/extension/shared/validators.ts +26 -0
  175. package/extension/shared/vault.ts +1580 -0
  176. package/extension/shared/wallet-crypto.ts +77 -0
  177. package/extension/shared/wasm-utils.ts +76 -0
  178. package/extension/shared/webcrypto.ts +67 -0
  179. package/extension/types/wasm.d.ts +13 -0
  180. package/package.json +39 -0
  181. package/postcss.config.js +6 -0
  182. package/rose-extension-dist.zip +0 -0
  183. package/sdk/README.md +88 -0
  184. package/sdk/examples/app.ts +166 -0
  185. package/sdk/examples/index.html +51 -0
  186. package/sdk/examples/tsconfig.json +15 -0
  187. package/sdk/examples/tx-builder.html +532 -0
  188. package/sdk/examples/tx-builder.ts +1766 -0
  189. package/sdk/package-lock.json +424 -0
  190. package/sdk/package.json +68 -0
  191. package/sdk/src/constants.ts +28 -0
  192. package/sdk/src/errors.ts +74 -0
  193. package/sdk/src/hooks/index.ts +1 -0
  194. package/sdk/src/hooks/use-rose.ts +94 -0
  195. package/sdk/src/index.ts +12 -0
  196. package/sdk/src/provider.ts +396 -0
  197. package/sdk/src/transaction.ts +163 -0
  198. package/sdk/src/types/rose-wasm.d.ts +14 -0
  199. package/sdk/src/types.ts +97 -0
  200. package/sdk/src/wasm.ts +13 -0
  201. package/sdk/tsconfig.json +20 -0
  202. package/sdk/vite.config.examples.ts +32 -0
  203. package/tailwind.config.ts +38 -0
  204. package/tsconfig.json +20 -0
  205. 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
+ }
@@ -0,0 +1,6 @@
1
+ export default {
2
+ plugins: {
3
+ '@tailwindcss/postcss': {},
4
+ autoprefixer: {},
5
+ },
6
+ };
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
+ }