@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
package/sdk/src/index.ts
ADDED
|
@@ -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
|
+
}
|
package/sdk/src/types.ts
ADDED
|
@@ -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
|
+
}
|
package/sdk/src/wasm.ts
ADDED
|
@@ -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';
|