@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,12 @@
1
+ /**
2
+ * Rose Wallet SDK
3
+ * TypeScript SDK for interacting with Rose wallet extension
4
+ */
5
+
6
+ export * from './types.js';
7
+ export * from './provider.js';
8
+ export * from './transaction.js';
9
+ export * from './errors.js';
10
+ export * from './constants.js';
11
+ export * from './hooks/index.js';
12
+ export * as wasm from './wasm.js';
@@ -0,0 +1,396 @@
1
+ /**
2
+ * NockchainProvider - Main SDK class for interacting with Rose wallet
3
+ */
4
+
5
+ import type { Transaction, NockchainEvent, EventListener, InjectedNockchain } from './types.js';
6
+ import { TransactionBuilder } from './transaction.js';
7
+ import { WalletNotInstalledError, UserRejectedError, RpcError, NoAccountError } from './errors.js';
8
+ import { PROVIDER_METHODS } from './constants.js';
9
+
10
+ /**
11
+ * NockchainProvider class - Main interface for dApps to interact with Rose wallet
12
+ *
13
+ * @example
14
+ * ```typescript
15
+ * const nockchain = new NockchainProvider();
16
+ *
17
+ * // Connect wallet
18
+ * const accounts = await nockchain.requestAccounts();
19
+ *
20
+ * // Build and send transaction
21
+ * const tx = nockchain.transaction()
22
+ * .to('recipient_address')
23
+ * .amount(1_000_000)
24
+ * .build();
25
+ *
26
+ * const txId = await nockchain.sendTransaction(tx);
27
+ * ```
28
+ */
29
+ export class NockchainProvider {
30
+ private injected: InjectedNockchain;
31
+ private eventListeners: Map<NockchainEvent, Set<EventListener>>;
32
+ private _accounts: string[] = [];
33
+ private _chainId: string | null = null;
34
+ private _messageHandler?: (event: MessageEvent) => void;
35
+
36
+ /**
37
+ * Create a new NockchainProvider instance
38
+ * @throws {WalletNotInstalledError} If the Rose extension is not installed
39
+ */
40
+ constructor() {
41
+ if (typeof window === 'undefined') {
42
+ throw new Error('NockchainProvider can only be used in a browser environment');
43
+ }
44
+
45
+ // Verify Rose extension is installed and authentic
46
+ if (!NockchainProvider.isInstalled()) {
47
+ throw new WalletNotInstalledError();
48
+ }
49
+
50
+ const injected = window.nockchain;
51
+
52
+ // TODO: remove this duplicate check
53
+ if (!injected) {
54
+ throw new WalletNotInstalledError();
55
+ }
56
+
57
+ this.injected = injected;
58
+ this.eventListeners = new Map();
59
+
60
+ // Initialize event listeners for wallet events
61
+ this.setupEventListeners();
62
+ }
63
+
64
+ /**
65
+ * Connect to the wallet and request access
66
+ * This will prompt the user to approve the connection
67
+ * @returns Promise resolving to wallet info with PKH and gRPC endpoint
68
+ * @throws {UserRejectedError} If the user rejects the request
69
+ * @throws {RpcError} If the RPC call fails
70
+ */
71
+ async connect(): Promise<{ pkh: string; grpcEndpoint: string }> {
72
+ const info = await this.request<{ pkh: string; grpcEndpoint: string }>({
73
+ method: PROVIDER_METHODS.CONNECT,
74
+ });
75
+
76
+ // Store the PKH as the connected account
77
+ this._accounts = [info.pkh];
78
+ return info;
79
+ }
80
+
81
+ /**
82
+ * Get the currently connected accounts (if any)
83
+ * @returns Array of connected account addresses (PKH)
84
+ */
85
+ get accounts(): string[] {
86
+ return [...this._accounts];
87
+ }
88
+
89
+ /**
90
+ * Get the current chain ID
91
+ * @returns The current chain ID or null if not connected
92
+ */
93
+ get chainId(): string | null {
94
+ return this._chainId;
95
+ }
96
+
97
+ /**
98
+ * Check if the wallet is connected
99
+ * @returns true if wallet is connected
100
+ */
101
+ get isConnected(): boolean {
102
+ return this._accounts.length > 0;
103
+ }
104
+
105
+ /**
106
+ * Send a transaction
107
+ * @param transaction - The transaction object to send
108
+ * @returns Promise resolving to the transaction ID
109
+ * @throws {NoAccountError} If no account is connected
110
+ * @throws {UserRejectedError} If the user rejects the transaction
111
+ * @throws {RpcError} If the RPC call fails
112
+ */
113
+ async sendTransaction(transaction: Transaction): Promise<string> {
114
+ if (!this.isConnected) {
115
+ throw new NoAccountError();
116
+ }
117
+
118
+ return this.request<string>({
119
+ method: PROVIDER_METHODS.SEND_TRANSACTION,
120
+ params: [transaction],
121
+ });
122
+ }
123
+
124
+ /**
125
+ * Sign an arbitrary message with the current account
126
+ * @param message - The message to sign
127
+ * @returns Promise resolving to the signature and public key hex (for verification)
128
+ * @throws {NoAccountError} If no account is connected
129
+ * @throws {UserRejectedError} If the user rejects the signing request
130
+ * @throws {RpcError} If the RPC call fails
131
+ */
132
+ async signMessage(message: string): Promise<{ signature: string; publicKeyHex: string }> {
133
+ if (!this.isConnected) {
134
+ throw new NoAccountError();
135
+ }
136
+
137
+ return this.request<{ signature: string; publicKeyHex: string }>({
138
+ method: PROVIDER_METHODS.SIGN_MESSAGE,
139
+ params: [message],
140
+ });
141
+ }
142
+
143
+ /**
144
+ * Sign a raw transaction
145
+ * Accepts either wasm objects (with toProtobuf() method) or protobuf JS objects
146
+ * @param params - The transaction parameters (rawTx, notes, spendConditions)
147
+ * @returns Promise resolving to the signed raw transaction as protobuf Uint8Array
148
+ * @throws {NoAccountError} If no account is connected
149
+ * @throws {UserRejectedError} If the user rejects the signing request
150
+ * @throws {RpcError} If the RPC call fails
151
+ *
152
+ * @example
153
+ * ```typescript
154
+ * // Option 1: Pass wasm objects directly (auto-converts to protobuf)
155
+ * const rawTx = builder.build();
156
+ * const txNotes = builder.allNotes();
157
+ *
158
+ * const signedTx = await provider.signRawTx({
159
+ * rawTx: rawTx, // wasm RawTx object
160
+ * notes: txNotes.notes, // array of wasm Note objects
161
+ * spendConditions: txNotes.spendConditions // array of wasm SpendCondition objects
162
+ * });
163
+ *
164
+ * // Option 2: Pass protobuf JS objects directly
165
+ * const signedTx = await provider.signRawTx({
166
+ * rawTx: rawTxProtobufObject, // protobuf JS object
167
+ * notes: noteProtobufObjects, // array of protobuf JS objects
168
+ * spendConditions: spendCondProtobufObjects // array of protobuf JS objects
169
+ * });
170
+ * ```
171
+ */
172
+ async signRawTx(params: {
173
+ rawTx: any;
174
+ notes: any[];
175
+ spendConditions: any[];
176
+ }): Promise<Uint8Array> {
177
+ if (!this.isConnected) {
178
+ throw new NoAccountError();
179
+ }
180
+
181
+ // Helper to convert to protobuf if it's a wasm object
182
+ const toProtobuf = (obj: any): any => {
183
+ // If object has toProtobuf method, it's a wasm object - convert it
184
+ if (obj && typeof obj.toProtobuf === 'function') {
185
+ return obj.toProtobuf();
186
+ }
187
+ // Otherwise assume it's already a protobuf JS object
188
+ return obj;
189
+ };
190
+
191
+ // Convert wasm objects to protobuf (if needed)
192
+ const protobufParams = {
193
+ rawTx: toProtobuf(params.rawTx),
194
+ notes: params.notes.map(toProtobuf),
195
+ spendConditions: params.spendConditions.map(toProtobuf),
196
+ };
197
+
198
+ return this.request<Uint8Array>({
199
+ method: PROVIDER_METHODS.SIGN_RAW_TX,
200
+ params: [protobufParams],
201
+ });
202
+ }
203
+
204
+ /**
205
+ * Create a new transaction builder
206
+ * @returns A new TransactionBuilder instance
207
+ *
208
+ * @example
209
+ * ```typescript
210
+ * const tx = provider.transaction()
211
+ * .to('recipient_address')
212
+ * .amount(1_000_000)
213
+ * .fee(50_000)
214
+ * .build();
215
+ * ```
216
+ */
217
+ transaction(): TransactionBuilder {
218
+ return new TransactionBuilder();
219
+ }
220
+
221
+ /**
222
+ * Add an event listener for wallet events
223
+ * @param event - The event to listen for
224
+ * @param listener - The callback function to invoke when the event occurs
225
+ *
226
+ * @example
227
+ * ```typescript
228
+ * provider.on('accountsChanged', (accounts) => {
229
+ * console.log('Accounts changed:', accounts);
230
+ * });
231
+ * ```
232
+ */
233
+ on<T = unknown>(event: NockchainEvent, listener: EventListener<T>): void {
234
+ if (!this.eventListeners.has(event)) {
235
+ this.eventListeners.set(event, new Set());
236
+ }
237
+ this.eventListeners.get(event)!.add(listener as EventListener);
238
+ }
239
+
240
+ /**
241
+ * Remove an event listener
242
+ * @param event - The event to stop listening for
243
+ * @param listener - The callback function to remove
244
+ */
245
+ off<T = unknown>(event: NockchainEvent, listener: EventListener<T>): void {
246
+ const listeners = this.eventListeners.get(event);
247
+ if (listeners) {
248
+ listeners.delete(listener as EventListener);
249
+ }
250
+ }
251
+
252
+ /**
253
+ * Remove all event listeners for a specific event or all events
254
+ * @param event - Optional event to remove listeners for (removes all if not specified)
255
+ */
256
+ removeAllListeners(event?: NockchainEvent): void {
257
+ if (event) {
258
+ this.eventListeners.delete(event);
259
+ } else {
260
+ this.eventListeners.clear();
261
+ }
262
+ }
263
+
264
+ /**
265
+ * Make a raw RPC request to the wallet extension (EIP-1193 compatible)
266
+ * @param args - The RPC request arguments
267
+ * @returns Promise resolving to the result
268
+ * @throws {UserRejectedError} If the user rejects the request
269
+ * @throws {RpcError} If the RPC call fails
270
+ */
271
+ public async request<T = unknown>(args: { method: string; params?: unknown[] }): Promise<T> {
272
+ try {
273
+ const result = await this.injected.request<T>(args);
274
+ return result;
275
+ } catch (error) {
276
+ // Handle RPC errors and map known error codes
277
+ if (error && typeof error === 'object' && 'code' in error && 'message' in error) {
278
+ const { code, message, data } = error as { code: number; message: string; data?: unknown };
279
+ const rpcError = new RpcError(code, message, data);
280
+
281
+ // Map EIP-1193 error codes to typed errors
282
+ if (this.isUserRejected(rpcError)) {
283
+ throw new UserRejectedError(message);
284
+ }
285
+
286
+ throw rpcError;
287
+ }
288
+ // Re-throw other errors as-is
289
+ throw error;
290
+ }
291
+ }
292
+
293
+ /**
294
+ * Check if an error represents user rejection
295
+ * Uses EIP-1193 standard error code 4001
296
+ */
297
+ private isUserRejected(error: RpcError | unknown): boolean {
298
+ // EIP-1193 standard: 4001 = User Rejected Request
299
+ const USER_REJECTED_CODES = new Set([4001]);
300
+
301
+ if (error instanceof RpcError) {
302
+ return USER_REJECTED_CODES.has(error.code);
303
+ }
304
+
305
+ // Fallback: check message for common rejection phrases
306
+ if (error instanceof Error) {
307
+ return /reject|denied|cancel/i.test(error.message);
308
+ }
309
+
310
+ return false;
311
+ }
312
+
313
+ /**
314
+ * Set up event listeners for wallet events
315
+ * This listens for events from the extension and forwards them to registered listeners
316
+ */
317
+ private setupEventListeners(): void {
318
+ if (typeof window === 'undefined') return;
319
+
320
+ this._messageHandler = (event: MessageEvent) => {
321
+ // Only accept messages from the same window
322
+ if (event.source !== window) return;
323
+
324
+ const payload = event.data;
325
+
326
+ // SECURITY: Verify the message is from Rose extension
327
+ // This prevents malicious scripts from forging wallet events
328
+ if (!payload || payload.__rose !== true) return;
329
+
330
+ // Check if this is a valid wallet event
331
+ if (typeof payload.type !== 'string' || !payload.type.startsWith('nockchain_')) return;
332
+
333
+ const eventType = payload.type.replace('nockchain_', '') as NockchainEvent;
334
+ const eventData = payload.data;
335
+
336
+ // Update internal state based on event type
337
+ if (eventType === 'accountsChanged' && Array.isArray(eventData)) {
338
+ this._accounts = eventData;
339
+ } else if (eventType === 'chainChanged' && typeof eventData === 'string') {
340
+ this._chainId = eventData;
341
+ } else if (eventType === 'disconnect') {
342
+ // Clear state on disconnect
343
+ this._accounts = [];
344
+ this._chainId = null;
345
+ }
346
+
347
+ // Emit to registered listeners
348
+ this.emit(eventType, eventData);
349
+ };
350
+
351
+ window.addEventListener('message', this._messageHandler);
352
+ }
353
+
354
+ /**
355
+ * Clean up event listeners and resources
356
+ * Call this when the provider is no longer needed (e.g., on component unmount)
357
+ */
358
+ public dispose(): void {
359
+ if (this._messageHandler && typeof window !== 'undefined') {
360
+ window.removeEventListener('message', this._messageHandler);
361
+ this._messageHandler = undefined;
362
+ }
363
+ this.removeAllListeners();
364
+ }
365
+
366
+ /**
367
+ * Emit an event to all registered listeners
368
+ * @param event - The event to emit
369
+ * @param data - The data to pass to listeners
370
+ */
371
+ private emit<T = unknown>(event: NockchainEvent, data: T): void {
372
+ const listeners = this.eventListeners.get(event);
373
+ if (listeners) {
374
+ listeners.forEach(listener => {
375
+ try {
376
+ listener(data);
377
+ } catch (error) {
378
+ console.error(`Error in ${event} listener:`, error);
379
+ }
380
+ });
381
+ }
382
+ }
383
+
384
+ /**
385
+ * Check if the Rose extension is installed and authentic
386
+ * @returns true if the extension is installed
387
+ */
388
+ static isInstalled(): boolean {
389
+ if (typeof window === 'undefined' || !window.nockchain?.providers) {
390
+ return false;
391
+ }
392
+ return window.nockchain.providers.some(
393
+ (provider: { name?: string }) => provider && provider.name === 'rose'
394
+ );
395
+ }
396
+ }
@@ -0,0 +1,163 @@
1
+ /**
2
+ * Transaction builder with fluent API for constructing Nockchain transactions
3
+ */
4
+
5
+ import { base58 } from '@scure/base';
6
+ import type { Transaction } from './types.js';
7
+ import { InvalidAddressError, InvalidTransactionError } from './errors.js';
8
+
9
+ /**
10
+ * Conversion rate: 1 NOCK = 65,536 nicks (2^16)
11
+ */
12
+ export const NOCK_TO_NICKS = 65_536;
13
+
14
+ /**
15
+ * Default transaction fee in nicks (32,768 nicks = 0.5 NOCK)
16
+ */
17
+ export const DEFAULT_FEE = 32_768;
18
+
19
+ /**
20
+ * Minimum amount in nicks (must be positive)
21
+ */
22
+ export const MIN_AMOUNT = 1;
23
+
24
+ /**
25
+ * TransactionBuilder class implementing the builder pattern for type-safe transaction construction
26
+ *
27
+ * @example
28
+ * ```typescript
29
+ * const tx = new TransactionBuilder()
30
+ * .to('nock1recipient_address')
31
+ * .amount(1_000_000)
32
+ * .fee(50_000)
33
+ * .build();
34
+ * ```
35
+ */
36
+ export class TransactionBuilder {
37
+ private _to?: string;
38
+ private _amount?: number;
39
+ private _fee?: number;
40
+
41
+ /**
42
+ * Set the recipient address for the transaction
43
+ * @param address - Base58-encoded Nockchain V1 PKH address (40 bytes, ~54-55 chars)
44
+ * @returns A new TransactionBuilder instance with the address set
45
+ * @throws {InvalidAddressError} If the address format is invalid
46
+ */
47
+ to(address: string): TransactionBuilder {
48
+ if (!this.isValidAddress(address)) {
49
+ throw new InvalidAddressError(address);
50
+ }
51
+
52
+ const builder = new TransactionBuilder();
53
+ builder._to = address;
54
+ builder._amount = this._amount;
55
+ builder._fee = this._fee;
56
+ return builder;
57
+ }
58
+
59
+ /**
60
+ * Set the amount to send in nicks (1 NOCK = 65,536 nicks)
61
+ * @param nicks - Amount in nicks (must be a positive integer)
62
+ * @returns A new TransactionBuilder instance with the amount set
63
+ * @throws {InvalidTransactionError} If the amount is invalid
64
+ */
65
+ amount(nicks: number): TransactionBuilder {
66
+ if (!Number.isInteger(nicks)) {
67
+ throw new InvalidTransactionError('Amount must be an integer');
68
+ }
69
+ if (nicks < MIN_AMOUNT) {
70
+ throw new InvalidTransactionError(`Amount must be at least ${MIN_AMOUNT} nick`);
71
+ }
72
+ // Note: MIN_AMOUNT = 1, so the above check already covers negative amounts
73
+
74
+ const builder = new TransactionBuilder();
75
+ builder._to = this._to;
76
+ builder._amount = nicks;
77
+ builder._fee = this._fee;
78
+ return builder;
79
+ }
80
+
81
+ /**
82
+ * Set the transaction fee in nicks (optional, defaults to 32,768 nicks)
83
+ * @param nicks - Fee amount in nicks (must be a positive integer)
84
+ * @returns A new TransactionBuilder instance with the fee set
85
+ * @throws {InvalidTransactionError} If the fee is invalid
86
+ */
87
+ fee(nicks: number): TransactionBuilder {
88
+ if (!Number.isInteger(nicks)) {
89
+ throw new InvalidTransactionError('Fee must be an integer');
90
+ }
91
+ if (nicks < 0) {
92
+ throw new InvalidTransactionError('Fee must be non-negative');
93
+ }
94
+
95
+ const builder = new TransactionBuilder();
96
+ builder._to = this._to;
97
+ builder._amount = this._amount;
98
+ builder._fee = nicks;
99
+ return builder;
100
+ }
101
+
102
+ /**
103
+ * Build and validate the transaction
104
+ * @returns The constructed Transaction object
105
+ * @throws {InvalidTransactionError} If required fields are missing
106
+ */
107
+ build(): Transaction {
108
+ if (!this._to) {
109
+ throw new InvalidTransactionError('Missing required field: to (recipient address)');
110
+ }
111
+ if (this._amount === undefined || this._amount === null) {
112
+ throw new InvalidTransactionError('Missing required field: amount');
113
+ }
114
+
115
+ return {
116
+ to: this._to,
117
+ amount: this._amount,
118
+ fee: this._fee ?? DEFAULT_FEE,
119
+ };
120
+ }
121
+
122
+ /**
123
+ * Validate a Nockchain V1 PKH address format
124
+ * V1 PKH addresses are TIP5 hash (40 bytes) of public key, base58-encoded
125
+ *
126
+ * Validates by decoding the base58 string and checking for exactly 40 bytes
127
+ * rather than relying on character count which can vary
128
+ *
129
+ * @param address - The address to validate
130
+ * @returns true if valid, false otherwise
131
+ */
132
+ private isValidAddress(address: string): boolean {
133
+ try {
134
+ const trimmed = (address || '').trim();
135
+ if (trimmed.length === 0) return false;
136
+
137
+ const bytes = base58.decode(trimmed);
138
+ return bytes.length === 40;
139
+ } catch {
140
+ // Invalid base58 encoding
141
+ return false;
142
+ }
143
+ }
144
+
145
+ /**
146
+ * Create a transaction builder from an existing transaction object
147
+ * Useful for modifying existing transactions
148
+ * @param tx - The transaction to create a builder from
149
+ * @returns A new TransactionBuilder instance with values from the transaction
150
+ * @throws {InvalidAddressError} If the transaction address is invalid
151
+ * @throws {InvalidTransactionError} If the transaction amount or fee is invalid
152
+ */
153
+ static fromTransaction(tx: Transaction): TransactionBuilder {
154
+ // Use setters to ensure validation is applied
155
+ let builder = new TransactionBuilder().to(tx.to).amount(tx.amount);
156
+
157
+ if (typeof tx.fee === 'number') {
158
+ builder = builder.fee(tx.fee);
159
+ }
160
+
161
+ return builder;
162
+ }
163
+ }
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Type declarations for Rose WASM JS entrypoint.
3
+ *
4
+ * With `moduleResolution: "bundler"`, TypeScript can fail to associate the
5
+ * package-provided `.d.ts` with the `.js` entrypoint path, so we bridge it here.
6
+ */
7
+
8
+ /// <reference path="@nockchain/rose-wasm/rose_wasm.d.ts" />
9
+
10
+ declare module '@nockchain/rose-wasm/rose_wasm.js' {
11
+ export * from '@nockchain/rose-wasm/rose_wasm';
12
+ import init from '@nockchain/rose-wasm/rose_wasm';
13
+ export default init;
14
+ }
@@ -0,0 +1,97 @@
1
+ /**
2
+ * TypeScript type definitions for Rose SDK
3
+ */
4
+
5
+ /**
6
+ * Transaction object representing a Nockchain transaction
7
+ */
8
+ export interface Transaction {
9
+ /** Recipient address (base58-encoded public key hash / PKH) */
10
+ to: string;
11
+ /** Amount to send in nicks (1 NOCK = 65,536 nicks) */
12
+ amount: number;
13
+ /** Transaction fee in nicks (optional, defaults to 32,768 = 0.5 NOCK) */
14
+ fee?: number;
15
+ }
16
+
17
+ /**
18
+ * RPC request object for communicating with the extension
19
+ */
20
+ export interface RpcRequest {
21
+ /** The RPC method to call */
22
+ method: string;
23
+ /** Optional parameters for the method */
24
+ params?: unknown[];
25
+ /** Optional timeout for the request */
26
+ timeout?: number;
27
+ }
28
+
29
+ /**
30
+ * RPC response object from the extension
31
+ */
32
+ export interface RpcResponse<T = unknown> {
33
+ /** The result of the RPC call */
34
+ result?: T;
35
+ /** Error information if the call failed */
36
+ error?: {
37
+ code: number;
38
+ message: string;
39
+ data?: unknown;
40
+ };
41
+ }
42
+
43
+ /**
44
+ * Event types that the provider can emit
45
+ */
46
+ export type NockchainEvent = 'accountsChanged' | 'chainChanged' | 'connect' | 'disconnect';
47
+
48
+ /**
49
+ * Event listener callback function
50
+ */
51
+ export type EventListener<T = unknown> = (data: T) => void;
52
+
53
+ /**
54
+ * Interface for the injected window.nockchain object
55
+ */
56
+ export interface InjectedNockchain {
57
+ /**
58
+ * Make an RPC request to the wallet extension
59
+ * @param request - The RPC request object
60
+ * @returns Promise resolving to the result
61
+ */
62
+ request<T = unknown>(request: RpcRequest): Promise<T>;
63
+
64
+ /**
65
+ * Provider names and versions (e.g., 'rose', 'iris')
66
+ */
67
+ providers?: { name: string; version: string }[];
68
+ }
69
+
70
+ /**
71
+ * Extended Window interface with nockchain property
72
+ */
73
+ declare global {
74
+ interface Window {
75
+ nockchain?: InjectedNockchain;
76
+ }
77
+ }
78
+
79
+ /**
80
+ * Chain information
81
+ */
82
+ export interface ChainInfo {
83
+ /** Chain ID (e.g., 'mainnet', 'testnet') */
84
+ chainId: string;
85
+ /** Network name */
86
+ name: string;
87
+ }
88
+
89
+ /**
90
+ * Account information
91
+ */
92
+ export interface AccountInfo {
93
+ /** Account address */
94
+ address: string;
95
+ /** Account balance in nicks (optional, may not be available) */
96
+ balance?: number;
97
+ }
@@ -0,0 +1,13 @@
1
+ /**
2
+ * `@nockchain/sdk/wasm`
3
+ *
4
+ * Convenience re-export of the Rose WASM package so consumers can import:
5
+ * - `@nockchain/sdk/wasm`
6
+ * instead of:
7
+ * - `@nockchain/rose-wasm/rose_wasm.js`
8
+ *
9
+ * This mirrors the "SDK owns WASM surface" pattern used elsewhere in the repo.
10
+ */
11
+
12
+ export * from '@nockchain/rose-wasm/rose_wasm.js';
13
+ export { default } from '@nockchain/rose-wasm/rose_wasm.js';