@nockchain/rose 0.1.4-nightly.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/workflows/artifacts.yml +33 -0
- package/.github/workflows/ci.yml +68 -0
- package/.github/workflows/publish-sdk.yml +35 -0
- package/.nvmrc +1 -0
- package/.prettierignore +5 -0
- package/.prettierrc +8 -0
- package/LICENSE +22 -0
- package/README.md +117 -0
- package/extension/background/index.ts +1500 -0
- package/extension/content/index.ts +59 -0
- package/extension/icons/rose.svg +27 -0
- package/extension/icons/rose128.png +0 -0
- package/extension/icons/rose16.png +0 -0
- package/extension/icons/rose256.png +0 -0
- package/extension/icons/rose32.png +0 -0
- package/extension/icons/rose48.png +0 -0
- package/extension/icons/rose512.png +0 -0
- package/extension/inpage/index.ts +86 -0
- package/extension/manifest.json +48 -0
- package/extension/popup/Popup.tsx +94 -0
- package/extension/popup/Router.tsx +121 -0
- package/extension/popup/assets/arrow-down-icon.svg +3 -0
- package/extension/popup/assets/arrow-left-icon.svg +3 -0
- package/extension/popup/assets/arrow-right-icon.svg +3 -0
- package/extension/popup/assets/arrow-up-icon.svg +3 -0
- package/extension/popup/assets/arrow-up-right-icon.svg +3 -0
- package/extension/popup/assets/checkmark-icon.svg +3 -0
- package/extension/popup/assets/checkmark-pencil-icon.svg +3 -0
- package/extension/popup/assets/checkmark-success-icon.svg +3 -0
- package/extension/popup/assets/clock-icon.svg +3 -0
- package/extension/popup/assets/close-x-icon.svg +3 -0
- package/extension/popup/assets/copy-icon.svg +6 -0
- package/extension/popup/assets/explorer-icon.svg +3 -0
- package/extension/popup/assets/eye-off-icon.svg +3 -0
- package/extension/popup/assets/eye-open-icon.svg +4 -0
- package/extension/popup/assets/feedback-icon.svg +3 -0
- package/extension/popup/assets/green-status-dot.svg +3 -0
- package/extension/popup/assets/info-icon.svg +3 -0
- package/extension/popup/assets/iris-logo-40.svg +27 -0
- package/extension/popup/assets/iris-logo-96.svg +27 -0
- package/extension/popup/assets/iris-logo-blue.svg +27 -0
- package/extension/popup/assets/iris-logo-no-eye.svg +27 -0
- package/extension/popup/assets/iris-logo-orange.svg +27 -0
- package/extension/popup/assets/iris-logo.svg +27 -0
- package/extension/popup/assets/key-icon.svg +3 -0
- package/extension/popup/assets/lock-icon-yellow.svg +3 -0
- package/extension/popup/assets/lock-icon.svg +3 -0
- package/extension/popup/assets/pencil-edit-icon.svg +3 -0
- package/extension/popup/assets/permissions-icon.svg +3 -0
- package/extension/popup/assets/receipt-icon.svg +5 -0
- package/extension/popup/assets/refresh-icon.svg +3 -0
- package/extension/popup/assets/settings-gear-icon.svg +8 -0
- package/extension/popup/assets/settings-icon.svg +3 -0
- package/extension/popup/assets/theme-icon.svg +3 -0
- package/extension/popup/assets/trash-bin-icon.svg +3 -0
- package/extension/popup/assets/trend-down-arrow.svg +5 -0
- package/extension/popup/assets/trend-up-arrow.svg +5 -0
- package/extension/popup/assets/user-account-icon.svg +3 -0
- package/extension/popup/assets/vector-bottom-left.svg +9 -0
- package/extension/popup/assets/vector-left.svg +9 -0
- package/extension/popup/assets/vector-right.svg +9 -0
- package/extension/popup/assets/vector-top-right-rotated.svg +8 -0
- package/extension/popup/assets/vector-top-right.svg +9 -0
- package/extension/popup/assets/wallet-dropdown-arrow.svg +5 -0
- package/extension/popup/assets/wallet-icon-style-1.svg +6 -0
- package/extension/popup/assets/wallet-icon-style-10.svg +8 -0
- package/extension/popup/assets/wallet-icon-style-11.svg +8 -0
- package/extension/popup/assets/wallet-icon-style-12.svg +8 -0
- package/extension/popup/assets/wallet-icon-style-13.svg +8 -0
- package/extension/popup/assets/wallet-icon-style-14.svg +8 -0
- package/extension/popup/assets/wallet-icon-style-15.svg +8 -0
- package/extension/popup/assets/wallet-icon-style-2.svg +8 -0
- package/extension/popup/assets/wallet-icon-style-3.svg +8 -0
- package/extension/popup/assets/wallet-icon-style-4.svg +8 -0
- package/extension/popup/assets/wallet-icon-style-5.svg +8 -0
- package/extension/popup/assets/wallet-icon-style-6.svg +8 -0
- package/extension/popup/assets/wallet-icon-style-7.svg +8 -0
- package/extension/popup/assets/wallet-icon-style-8.svg +8 -0
- package/extension/popup/assets/wallet-icon-style-9.svg +8 -0
- package/extension/popup/components/AccountIcon.tsx +78 -0
- package/extension/popup/components/AccountSelector.tsx +246 -0
- package/extension/popup/components/Alert.tsx +48 -0
- package/extension/popup/components/ConfirmModal.tsx +81 -0
- package/extension/popup/components/PasswordInput.tsx +49 -0
- package/extension/popup/components/ScreenContainer.tsx +17 -0
- package/extension/popup/components/SiteIcon.tsx +60 -0
- package/extension/popup/components/ThemeToggle.tsx +44 -0
- package/extension/popup/components/icons/ArrowDownLeftIcon.tsx +20 -0
- package/extension/popup/components/icons/ArrowUpRightIcon.tsx +20 -0
- package/extension/popup/components/icons/CheckIcon.tsx +20 -0
- package/extension/popup/components/icons/ChevronDownIcon.tsx +15 -0
- package/extension/popup/components/icons/ChevronLeftIcon.tsx +15 -0
- package/extension/popup/components/icons/ChevronRightIcon.tsx +15 -0
- package/extension/popup/components/icons/ChevronUpIcon.tsx +15 -0
- package/extension/popup/components/icons/CloseIcon.tsx +26 -0
- package/extension/popup/components/icons/CopyIcon.tsx +20 -0
- package/extension/popup/components/icons/EditIcon.tsx +20 -0
- package/extension/popup/components/icons/EyeIcon.tsx +13 -0
- package/extension/popup/components/icons/EyeOffIcon.tsx +13 -0
- package/extension/popup/components/icons/InfoIcon.tsx +20 -0
- package/extension/popup/components/icons/LockIcon.tsx +20 -0
- package/extension/popup/components/icons/PlusIcon.tsx +15 -0
- package/extension/popup/components/icons/ReceiveArrowIcon.tsx +14 -0
- package/extension/popup/components/icons/ReceiveCircleIcon.tsx +20 -0
- package/extension/popup/components/icons/SendPaperPlaneIcon.tsx +18 -0
- package/extension/popup/components/icons/SentArrowIcon.tsx +21 -0
- package/extension/popup/components/icons/SettingsIcon.tsx +26 -0
- package/extension/popup/components/icons/ShieldIcon.tsx +20 -0
- package/extension/popup/components/icons/UploadIcon.tsx +20 -0
- package/extension/popup/components/icons/WalletIcon.tsx +20 -0
- package/extension/popup/contexts/ThemeContext.tsx +105 -0
- package/extension/popup/hooks/useApprovalDetection.ts +128 -0
- package/extension/popup/hooks/useAutoFocus.ts +36 -0
- package/extension/popup/hooks/useAutoRejectOnClose.ts +25 -0
- package/extension/popup/hooks/useClickOutside.ts +33 -0
- package/extension/popup/hooks/useCopyToClipboard.ts +33 -0
- package/extension/popup/hooks/useFavicon.ts +64 -0
- package/extension/popup/hooks/useNumericInput.ts +93 -0
- package/extension/popup/index.html +13 -0
- package/extension/popup/index.tsx +24 -0
- package/extension/popup/screens/AboutScreen.tsx +118 -0
- package/extension/popup/screens/HomeScreen.tailwind.css +85 -0
- package/extension/popup/screens/HomeScreen.tsx +902 -0
- package/extension/popup/screens/KeySettingsPasswordScreen.tsx +164 -0
- package/extension/popup/screens/LockTimeScreen.tsx +155 -0
- package/extension/popup/screens/ReceiveScreen.tsx +149 -0
- package/extension/popup/screens/RecoveryPhraseScreen.tsx +183 -0
- package/extension/popup/screens/SendReviewScreen.tsx +308 -0
- package/extension/popup/screens/SendScreen.tsx +825 -0
- package/extension/popup/screens/SendSubmittedScreen.tsx +193 -0
- package/extension/popup/screens/SettingsScreen.tsx +116 -0
- package/extension/popup/screens/ThemeSettingsScreen.tsx +107 -0
- package/extension/popup/screens/TransactionDetailsScreen.tsx +346 -0
- package/extension/popup/screens/ViewSecretPhraseScreen.tsx +212 -0
- package/extension/popup/screens/WalletPermissionsScreen.tsx +123 -0
- package/extension/popup/screens/WalletSettingsScreen.tsx +381 -0
- package/extension/popup/screens/WalletStylingScreen.tsx +306 -0
- package/extension/popup/screens/approvals/ConnectApprovalScreen.tsx +136 -0
- package/extension/popup/screens/approvals/SignMessageScreen.tsx +140 -0
- package/extension/popup/screens/approvals/SignRawTxScreen.tsx +320 -0
- package/extension/popup/screens/approvals/TransactionApprovalScreen.tsx +167 -0
- package/extension/popup/screens/onboarding/BackupScreen.tsx +254 -0
- package/extension/popup/screens/onboarding/CreateScreen.tsx +273 -0
- package/extension/popup/screens/onboarding/ImportScreen.tsx +676 -0
- package/extension/popup/screens/onboarding/ImportScreenV0.tsx +678 -0
- package/extension/popup/screens/onboarding/ImportSuccessScreen.tsx +236 -0
- package/extension/popup/screens/onboarding/ResumeBackupScreen.tsx +166 -0
- package/extension/popup/screens/onboarding/StartScreen.tsx +142 -0
- package/extension/popup/screens/onboarding/SuccessScreen.tsx +193 -0
- package/extension/popup/screens/onboarding/VerifyScreen.tsx +220 -0
- package/extension/popup/screens/system/LockedScreen.tsx +288 -0
- package/extension/popup/screens/transactions/ReceiveScreen.tsx +84 -0
- package/extension/popup/screens/transactions/SentScreen.tsx +138 -0
- package/extension/popup/store.ts +482 -0
- package/extension/popup/styles.css +246 -0
- package/extension/popup/utils/format.ts +58 -0
- package/extension/popup/utils/formatWalletError.ts +36 -0
- package/extension/popup/utils/memo.ts +299 -0
- package/extension/popup/utils/messaging.ts +16 -0
- package/extension/shared/address-encoding.ts +69 -0
- package/extension/shared/balance-query.ts +123 -0
- package/extension/shared/constants.ts +386 -0
- package/extension/shared/currency.ts +128 -0
- package/extension/shared/first-name-derivation.ts +128 -0
- package/extension/shared/keyfile.ts +58 -0
- package/extension/shared/onboarding.ts +78 -0
- package/extension/shared/price-api.ts +79 -0
- package/extension/shared/rpc-client-browser.ts +315 -0
- package/extension/shared/transaction-builder.ts +443 -0
- package/extension/shared/types.ts +450 -0
- package/extension/shared/utxo-diff.ts +212 -0
- package/extension/shared/utxo-store.ts +548 -0
- package/extension/shared/utxo-sync.ts +343 -0
- package/extension/shared/validators.ts +26 -0
- package/extension/shared/vault.ts +1580 -0
- package/extension/shared/wallet-crypto.ts +77 -0
- package/extension/shared/wasm-utils.ts +76 -0
- package/extension/shared/webcrypto.ts +67 -0
- package/extension/types/wasm.d.ts +13 -0
- package/package.json +39 -0
- package/postcss.config.js +6 -0
- package/rose-extension-dist.zip +0 -0
- package/sdk/README.md +88 -0
- package/sdk/examples/app.ts +166 -0
- package/sdk/examples/index.html +51 -0
- package/sdk/examples/tsconfig.json +15 -0
- package/sdk/examples/tx-builder.html +532 -0
- package/sdk/examples/tx-builder.ts +1766 -0
- package/sdk/package-lock.json +424 -0
- package/sdk/package.json +68 -0
- package/sdk/src/constants.ts +28 -0
- package/sdk/src/errors.ts +74 -0
- package/sdk/src/hooks/index.ts +1 -0
- package/sdk/src/hooks/use-rose.ts +94 -0
- package/sdk/src/index.ts +12 -0
- package/sdk/src/provider.ts +396 -0
- package/sdk/src/transaction.ts +163 -0
- package/sdk/src/types/rose-wasm.d.ts +14 -0
- package/sdk/src/types.ts +97 -0
- package/sdk/src/wasm.ts +13 -0
- package/sdk/tsconfig.json +20 -0
- package/sdk/vite.config.examples.ts +32 -0
- package/tailwind.config.ts +38 -0
- package/tsconfig.json +20 -0
- package/vite.config.ts +60 -0
|
@@ -0,0 +1,450 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared TypeScript types and interfaces
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { PROVIDER_METHODS, INTERNAL_METHODS, RPC_METHODS, ERROR_CODES } from './constants';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Account information for multi-account wallet
|
|
9
|
+
* Each account is derived from the same mnemonic using BIP-44 derivation paths
|
|
10
|
+
*/
|
|
11
|
+
export interface Account {
|
|
12
|
+
/** User-defined account name (editable) */
|
|
13
|
+
name: string;
|
|
14
|
+
/** Nockchain V1 PKH address (40 bytes base58-encoded, ~54-55 chars) */
|
|
15
|
+
address: string;
|
|
16
|
+
/** BIP-44 derivation index (0, 1, 2, ...) */
|
|
17
|
+
index: number;
|
|
18
|
+
/** Icon style ID (1-15, defaults to index % 3 + 1 for variety) */
|
|
19
|
+
iconStyleId?: number;
|
|
20
|
+
/** Icon color (hex string, defaults to #5968fb) */
|
|
21
|
+
iconColor?: string;
|
|
22
|
+
/** Whether this account is hidden from the UI */
|
|
23
|
+
hidden?: boolean;
|
|
24
|
+
/** Timestamp when the account was created (milliseconds since epoch) */
|
|
25
|
+
createdAt?: number;
|
|
26
|
+
/** Derivation path: 'master' (master key) or 'derived' (child key at index) */
|
|
27
|
+
derivation?: 'master' | 'slip10';
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Type for all valid RPC method names
|
|
32
|
+
*/
|
|
33
|
+
export type RPCMethod = (typeof RPC_METHODS)[keyof typeof RPC_METHODS];
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Type for provider method names only
|
|
37
|
+
*/
|
|
38
|
+
export type ProviderMethod = (typeof PROVIDER_METHODS)[keyof typeof PROVIDER_METHODS];
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Type for internal method names only
|
|
42
|
+
*/
|
|
43
|
+
export type InternalMethod = (typeof INTERNAL_METHODS)[keyof typeof INTERNAL_METHODS];
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Type for all valid error codes
|
|
47
|
+
*/
|
|
48
|
+
export type ErrorCode = (typeof ERROR_CODES)[keyof typeof ERROR_CODES];
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Transaction information
|
|
52
|
+
* Represents a blockchain transaction (sent or received)
|
|
53
|
+
*/
|
|
54
|
+
export interface Transaction {
|
|
55
|
+
/** Unique transaction ID */
|
|
56
|
+
id: string;
|
|
57
|
+
/** Transaction type */
|
|
58
|
+
type: 'sent' | 'received';
|
|
59
|
+
/** Amount in NOCK */
|
|
60
|
+
amount: number;
|
|
61
|
+
/** Counterparty address (sender for received, recipient for sent) */
|
|
62
|
+
address: string;
|
|
63
|
+
/** Transaction timestamp */
|
|
64
|
+
timestamp: Date;
|
|
65
|
+
/** Transaction status */
|
|
66
|
+
status: 'confirmed' | 'pending' | 'failed';
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Transaction details for confirmation screen
|
|
71
|
+
* Used to display transaction details immediately after sending
|
|
72
|
+
*/
|
|
73
|
+
export interface TransactionDetails {
|
|
74
|
+
/** Transaction ID */
|
|
75
|
+
txid: string;
|
|
76
|
+
/** Amount in NOCK */
|
|
77
|
+
amount: number;
|
|
78
|
+
/** Transaction fee in NOCK */
|
|
79
|
+
fee: number;
|
|
80
|
+
/** Recipient address */
|
|
81
|
+
to?: string;
|
|
82
|
+
/** Sender address */
|
|
83
|
+
from?: string;
|
|
84
|
+
/** Protobuf transaction object for debugging/export (dev feature) */
|
|
85
|
+
protobufTx?: any;
|
|
86
|
+
/** Whether this is a sweep transaction (all UTXOs sent to recipient) */
|
|
87
|
+
sendMax?: boolean;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Structured balance information for an account
|
|
92
|
+
* Separates confirmed (on-chain) balance from pending transaction effects
|
|
93
|
+
*/
|
|
94
|
+
export interface AccountBalance {
|
|
95
|
+
/** Balance from confirmed UTXOs on-chain */
|
|
96
|
+
confirmed: number;
|
|
97
|
+
/** Sum of (amount + fee) for pending outbound transactions */
|
|
98
|
+
pendingOut: number;
|
|
99
|
+
/** Sum of amounts for pending inbound transactions */
|
|
100
|
+
pendingIn: number;
|
|
101
|
+
/** Spendable balance: confirmed - pendingOut */
|
|
102
|
+
available: number;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Pending connection request from a dApp
|
|
107
|
+
*/
|
|
108
|
+
export interface ConnectRequest {
|
|
109
|
+
/** Unique request ID */
|
|
110
|
+
id: string;
|
|
111
|
+
/** Origin of the requesting site (e.g., "https://app.example.com") */
|
|
112
|
+
origin: string;
|
|
113
|
+
/** Request timestamp */
|
|
114
|
+
timestamp: number;
|
|
115
|
+
|
|
116
|
+
/** Optional signing context */
|
|
117
|
+
signWith?: 'v1' | 'v0';
|
|
118
|
+
|
|
119
|
+
/** v0 derivation label when signWith='v0' */
|
|
120
|
+
v0Derivation?: 'master' | 'child0' | 'hard0';
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Pending sign message request from a dApp
|
|
125
|
+
*/
|
|
126
|
+
export interface SignRequest {
|
|
127
|
+
/** Unique request ID */
|
|
128
|
+
id: string;
|
|
129
|
+
/** Origin of the requesting site (e.g., "https://app.example.com") */
|
|
130
|
+
origin: string;
|
|
131
|
+
/** Message to be signed */
|
|
132
|
+
message: string;
|
|
133
|
+
/** Request timestamp */
|
|
134
|
+
timestamp: number;
|
|
135
|
+
|
|
136
|
+
/** Optional signing context */
|
|
137
|
+
signWith?: 'v1' | 'v0';
|
|
138
|
+
|
|
139
|
+
/** v0 derivation label when signWith='v0' */
|
|
140
|
+
v0Derivation?: 'master' | 'child0' | 'hard0';
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Pending transaction approval request from a dApp
|
|
145
|
+
*/
|
|
146
|
+
export interface TransactionRequest {
|
|
147
|
+
/** Unique request ID */
|
|
148
|
+
id: string;
|
|
149
|
+
/** Origin of the requesting site (e.g., "https://app.example.com") */
|
|
150
|
+
origin: string;
|
|
151
|
+
/** Recipient address */
|
|
152
|
+
to: string;
|
|
153
|
+
/** Amount in nicks */
|
|
154
|
+
amount: number;
|
|
155
|
+
/** Transaction fee in nicks */
|
|
156
|
+
fee: number;
|
|
157
|
+
/** Request timestamp */
|
|
158
|
+
timestamp: number;
|
|
159
|
+
|
|
160
|
+
/** Optional signing context */
|
|
161
|
+
signWith?: 'v1' | 'v0';
|
|
162
|
+
|
|
163
|
+
/** v0 derivation label when signWith='v0' */
|
|
164
|
+
v0Derivation?: 'master' | 'child0' | 'hard0';
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Pending raw transaction signing request from a dApp
|
|
169
|
+
*/
|
|
170
|
+
export interface SignRawTxRequest {
|
|
171
|
+
/** Unique request ID */
|
|
172
|
+
id: string;
|
|
173
|
+
/** Origin of the requesting site */
|
|
174
|
+
origin: string;
|
|
175
|
+
/** Raw transaction protobuf */
|
|
176
|
+
rawTx: any;
|
|
177
|
+
/** Input notes (protobuf) */
|
|
178
|
+
notes: any[];
|
|
179
|
+
/** Spend conditions (protobuf) */
|
|
180
|
+
spendConditions: any[];
|
|
181
|
+
/** Output notes (protobuf). Ignored from dApp side */
|
|
182
|
+
outputs?: any[];
|
|
183
|
+
/** Request timestamp */
|
|
184
|
+
timestamp: number;
|
|
185
|
+
|
|
186
|
+
/** Optional signing context */
|
|
187
|
+
signWith?: 'v1' | 'v0';
|
|
188
|
+
|
|
189
|
+
/** v0 derivation label when signWith='v0' */
|
|
190
|
+
v0Derivation?: 'master' | 'child0' | 'hard0';
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* Nockchain note (UTXO) structure
|
|
195
|
+
* Represents an unspent transaction output on the blockchain
|
|
196
|
+
*/
|
|
197
|
+
export interface Note {
|
|
198
|
+
/** Note version (0 for legacy, 1 for v1) */
|
|
199
|
+
version: number;
|
|
200
|
+
/** Origin page (block height where note was created) */
|
|
201
|
+
originPage: bigint;
|
|
202
|
+
/** Minimum timelock (optional, for coinbase/timelocked notes) */
|
|
203
|
+
timelockMin?: bigint;
|
|
204
|
+
/** Maximum timelock (optional, for coinbase/timelocked notes) */
|
|
205
|
+
timelockMax?: bigint;
|
|
206
|
+
/** First 40 bytes of note name (80-byte identifier) */
|
|
207
|
+
nameFirst: Uint8Array;
|
|
208
|
+
/** Last 40 bytes of note name (80-byte identifier) */
|
|
209
|
+
nameLast: Uint8Array;
|
|
210
|
+
/** First name as base58 string (optional, from WASM gRPC client) */
|
|
211
|
+
nameFirstBase58?: string;
|
|
212
|
+
/** Last name as base58 string (optional, from WASM gRPC client) */
|
|
213
|
+
nameLastBase58?: string;
|
|
214
|
+
/** Note data hash as base58 string (optional, from WASM gRPC client) */
|
|
215
|
+
noteDataHashBase58?: string;
|
|
216
|
+
/** Public keys for lock (legacy format) */
|
|
217
|
+
lockPubkeys: Uint8Array[];
|
|
218
|
+
/** Number of keys required to unlock (legacy format) */
|
|
219
|
+
lockKeysRequired: bigint;
|
|
220
|
+
/** Source transaction hash */
|
|
221
|
+
sourceHash: Uint8Array;
|
|
222
|
+
/** Whether this note is from a coinbase (mining reward) transaction */
|
|
223
|
+
sourceIsCoinbase: boolean;
|
|
224
|
+
/** Amount in nicks (1 NOCK = 65,536 nicks) */
|
|
225
|
+
assets: number;
|
|
226
|
+
/** Raw protobuf note object from RPC (for Note.fromProtobuf) */
|
|
227
|
+
protoNote?: any;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// ============================================================================
|
|
231
|
+
// UTXO Store Types - For tracking note lifecycle and enabling successive sends
|
|
232
|
+
// ============================================================================
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* Note state in the UTXO store
|
|
236
|
+
* - available: Can be used in new transactions
|
|
237
|
+
* - in_flight: Reserved by a pending transaction (locked until confirmed or expired)
|
|
238
|
+
* - spent: Transaction confirmed, note is consumed
|
|
239
|
+
*/
|
|
240
|
+
export type NoteState = 'available' | 'in_flight' | 'spent';
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* Stored note (UTXO) with state tracking
|
|
244
|
+
* This is the core type for the local UTXO store
|
|
245
|
+
*/
|
|
246
|
+
export interface StoredNote {
|
|
247
|
+
/** Unique identifier: nameFirst:nameLast in base58 */
|
|
248
|
+
noteId: string;
|
|
249
|
+
|
|
250
|
+
/** Account that owns this note */
|
|
251
|
+
accountAddress: string;
|
|
252
|
+
|
|
253
|
+
// Chain identity
|
|
254
|
+
/** Source transaction hash (base58) that created this note */
|
|
255
|
+
sourceHash: string;
|
|
256
|
+
/** Block height where this note was created */
|
|
257
|
+
originPage: number;
|
|
258
|
+
|
|
259
|
+
// Value
|
|
260
|
+
/** Amount in nicks (1 NOCK = 65,536 nicks) */
|
|
261
|
+
assets: number;
|
|
262
|
+
|
|
263
|
+
// For WASM transaction building
|
|
264
|
+
/** First 40 bytes of note name as base58 */
|
|
265
|
+
nameFirst: string;
|
|
266
|
+
/** Last 40 bytes of note name as base58 */
|
|
267
|
+
nameLast: string;
|
|
268
|
+
/** Note data hash as base58 (for WASM) */
|
|
269
|
+
noteDataHashBase58: string;
|
|
270
|
+
/** Raw protobuf note for WasmNote.fromProtobuf() */
|
|
271
|
+
protoNote: any;
|
|
272
|
+
|
|
273
|
+
// State tracking
|
|
274
|
+
/** Current state of this note */
|
|
275
|
+
state: NoteState;
|
|
276
|
+
/** If in_flight, which wallet transaction is using this note */
|
|
277
|
+
pendingTxId?: string;
|
|
278
|
+
/** Was this note change from one of our own transactions? */
|
|
279
|
+
isChange?: boolean;
|
|
280
|
+
|
|
281
|
+
// Metadata
|
|
282
|
+
/** Timestamp when this note was first discovered (ms since epoch) */
|
|
283
|
+
discoveredAt: number;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
/**
|
|
287
|
+
* UTXO store structure - per account
|
|
288
|
+
*/
|
|
289
|
+
export interface UTXOStore {
|
|
290
|
+
[accountAddress: string]: {
|
|
291
|
+
notes: StoredNote[];
|
|
292
|
+
/** Version for optimistic locking / conflict detection */
|
|
293
|
+
version: number;
|
|
294
|
+
};
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
// ============================================================================
|
|
298
|
+
// Wallet Transaction Types - Separate from UTXO lifecycle
|
|
299
|
+
// ============================================================================
|
|
300
|
+
|
|
301
|
+
/**
|
|
302
|
+
* Wallet transaction status
|
|
303
|
+
* - created: Transaction object created but not yet broadcasted
|
|
304
|
+
* - broadcast_pending: Attempting to broadcast
|
|
305
|
+
* - broadcasted_unconfirmed: In mempool, waiting for confirmation
|
|
306
|
+
* - confirmed: Included in a block
|
|
307
|
+
* - failed: Broadcast or confirmation failed
|
|
308
|
+
* - expired: Timed out waiting for confirmation
|
|
309
|
+
*/
|
|
310
|
+
export type WalletTxStatus =
|
|
311
|
+
| 'created'
|
|
312
|
+
| 'broadcast_pending'
|
|
313
|
+
| 'broadcasted_unconfirmed'
|
|
314
|
+
| 'confirmed'
|
|
315
|
+
| 'failed'
|
|
316
|
+
| 'expired';
|
|
317
|
+
|
|
318
|
+
/**
|
|
319
|
+
* Wallet transaction record
|
|
320
|
+
* Links to StoredNotes via noteIds
|
|
321
|
+
*/
|
|
322
|
+
export interface WalletTransaction {
|
|
323
|
+
/** Internal unique ID (UUID) */
|
|
324
|
+
id: string;
|
|
325
|
+
|
|
326
|
+
/** On-chain transaction hash (known after broadcast) */
|
|
327
|
+
txHash?: string;
|
|
328
|
+
|
|
329
|
+
/** Account that initiated/received this transaction */
|
|
330
|
+
accountAddress: string;
|
|
331
|
+
|
|
332
|
+
/** Transaction direction relative to this account */
|
|
333
|
+
direction: 'outgoing' | 'incoming' | 'self';
|
|
334
|
+
|
|
335
|
+
// Timestamps
|
|
336
|
+
/** When the transaction was created (ms since epoch) */
|
|
337
|
+
createdAt: number;
|
|
338
|
+
/** When the transaction was last updated (ms since epoch) */
|
|
339
|
+
updatedAt: number;
|
|
340
|
+
|
|
341
|
+
/** USD price per NOCK at the time of transaction */
|
|
342
|
+
priceUsdAtTime?: number;
|
|
343
|
+
|
|
344
|
+
/** Current status */
|
|
345
|
+
status: WalletTxStatus;
|
|
346
|
+
|
|
347
|
+
// For outgoing transactions
|
|
348
|
+
/** Note IDs used as inputs (spent) */
|
|
349
|
+
inputNoteIds?: string[];
|
|
350
|
+
/** Expected change note IDs (predicted before confirmation) */
|
|
351
|
+
expectedChangeNoteIds?: string[];
|
|
352
|
+
/** Expected change amount in nicks (sum of inputs - amount - fee) */
|
|
353
|
+
expectedChange?: number;
|
|
354
|
+
/** Recipient address */
|
|
355
|
+
recipient?: string;
|
|
356
|
+
/** Amount sent in nicks */
|
|
357
|
+
amount?: number;
|
|
358
|
+
/** Fee paid in nicks */
|
|
359
|
+
fee?: number;
|
|
360
|
+
|
|
361
|
+
// For incoming transactions
|
|
362
|
+
/** Note IDs received */
|
|
363
|
+
receivedNoteIds?: string[];
|
|
364
|
+
/** Sender address (if known) */
|
|
365
|
+
sender?: string;
|
|
366
|
+
|
|
367
|
+
// Confirmation tracking
|
|
368
|
+
/** Block height when confirmed */
|
|
369
|
+
confirmedAtBlock?: number;
|
|
370
|
+
/** Number of confirmations */
|
|
371
|
+
confirmations?: number;
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
/**
|
|
375
|
+
* Wallet transaction store - per account
|
|
376
|
+
*/
|
|
377
|
+
export interface WalletTxStore {
|
|
378
|
+
[accountAddress: string]: WalletTransaction[];
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
/**
|
|
382
|
+
* Per-account sync state for incremental UTXO syncing
|
|
383
|
+
*/
|
|
384
|
+
export interface AccountSyncState {
|
|
385
|
+
accountAddress: string;
|
|
386
|
+
/** Last block height that was fully synced (inclusive) */
|
|
387
|
+
lastSyncedHeight: number;
|
|
388
|
+
/** Timestamp of last successful sync */
|
|
389
|
+
lastSyncedAt: number;
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
/**
|
|
393
|
+
* Sync state store - per account
|
|
394
|
+
*/
|
|
395
|
+
export interface SyncStateStore {
|
|
396
|
+
[accountAddress: string]: AccountSyncState;
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
/**
|
|
400
|
+
* Expected change output - tracked before broadcast to identify change vs incoming
|
|
401
|
+
*/
|
|
402
|
+
export interface ExpectedChange {
|
|
403
|
+
/** Wallet transaction ID that will produce this change */
|
|
404
|
+
walletTxId: string;
|
|
405
|
+
/** Predicted note ID (if deterministic) */
|
|
406
|
+
expectedNoteId?: string;
|
|
407
|
+
/** Expected transaction hash (if known before broadcast) */
|
|
408
|
+
expectedTxHash?: string;
|
|
409
|
+
/** Account address */
|
|
410
|
+
accountAddress: string;
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
// ============================================================================
|
|
414
|
+
// UTXO Diff Types - For pure diff computation in utxo-sync
|
|
415
|
+
// ============================================================================
|
|
416
|
+
|
|
417
|
+
/**
|
|
418
|
+
* Fetched UTXO from chain (before being stored)
|
|
419
|
+
*/
|
|
420
|
+
export interface FetchedUTXO {
|
|
421
|
+
noteId: string;
|
|
422
|
+
sourceHash: string;
|
|
423
|
+
originPage: number;
|
|
424
|
+
assets: number;
|
|
425
|
+
nameFirst: string;
|
|
426
|
+
nameLast: string;
|
|
427
|
+
noteDataHashBase58: string;
|
|
428
|
+
protoNote: any;
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
/**
|
|
432
|
+
* Result of diffing local store against chain state
|
|
433
|
+
*/
|
|
434
|
+
export interface UTXODiff {
|
|
435
|
+
/** UTXOs on chain that are not in our local store (new) */
|
|
436
|
+
newUTXOs: FetchedUTXO[];
|
|
437
|
+
/** UTXOs that exist both locally and on chain (unchanged) */
|
|
438
|
+
stillUnspent: StoredNote[];
|
|
439
|
+
/** UTXOs in our local store that are no longer on chain (spent) */
|
|
440
|
+
nowSpent: StoredNote[];
|
|
441
|
+
/** Map of noteId -> walletTxId for notes that are change from our own txs */
|
|
442
|
+
isChangeMap: Map<string, string>;
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
/**
|
|
446
|
+
* Last sync timestamps per account address
|
|
447
|
+
*/
|
|
448
|
+
export interface LastSyncTimestamps {
|
|
449
|
+
[accountAddress: string]: number;
|
|
450
|
+
}
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* UTXO Diff - Pure functions for computing UTXO state changes
|
|
3
|
+
*
|
|
4
|
+
* This module contains NO storage operations or chrome APIs.
|
|
5
|
+
* It's purely functional, making it easy to test.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type { StoredNote, FetchedUTXO, UTXODiff, WalletTransaction } from './types';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Compute the diff between local UTXO store and fetched chain state
|
|
12
|
+
*
|
|
13
|
+
* @param localNotes - Notes currently in our local store
|
|
14
|
+
* @param fetchedUTXOs - UTXOs fetched from the chain
|
|
15
|
+
* @param pendingOutgoingTxs - Our pending outgoing transactions (for confirmation detection)
|
|
16
|
+
* @param allOutgoingTxs - All outgoing transactions including confirmed (for change detection)
|
|
17
|
+
* @returns Diff result with new, unchanged, and spent notes
|
|
18
|
+
*/
|
|
19
|
+
export function computeUTXODiff(
|
|
20
|
+
localNotes: StoredNote[],
|
|
21
|
+
fetchedUTXOs: FetchedUTXO[],
|
|
22
|
+
pendingOutgoingTxs: WalletTransaction[],
|
|
23
|
+
allOutgoingTxs?: WalletTransaction[]
|
|
24
|
+
): UTXODiff {
|
|
25
|
+
// Build lookup maps
|
|
26
|
+
const localMap = new Map<string, StoredNote>();
|
|
27
|
+
for (const note of localNotes) {
|
|
28
|
+
localMap.set(note.noteId, note);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const fetchedMap = new Map<string, FetchedUTXO>();
|
|
32
|
+
for (const utxo of fetchedUTXOs) {
|
|
33
|
+
fetchedMap.set(utxo.noteId, utxo);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Build a map of expectedChange amounts to tx IDs for change detection
|
|
37
|
+
// We match new UTXOs by their amount against expected change from our outgoing transactions
|
|
38
|
+
const txsForChangeDetection = allOutgoingTxs || pendingOutgoingTxs;
|
|
39
|
+
const expectedChangeToTxId = new Map<number, string>();
|
|
40
|
+
for (const tx of txsForChangeDetection) {
|
|
41
|
+
if (tx.expectedChange && tx.expectedChange > 0) {
|
|
42
|
+
expectedChangeToTxId.set(tx.expectedChange, tx.id);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Compute diff
|
|
47
|
+
const newUTXOs: FetchedUTXO[] = [];
|
|
48
|
+
const stillUnspent: StoredNote[] = [];
|
|
49
|
+
const nowSpent: StoredNote[] = [];
|
|
50
|
+
const isChangeMap = new Map<string, string>();
|
|
51
|
+
|
|
52
|
+
// Find new UTXOs (on chain but not in local store)
|
|
53
|
+
for (const [noteId, fetched] of fetchedMap) {
|
|
54
|
+
const local = localMap.get(noteId);
|
|
55
|
+
|
|
56
|
+
if (!local) {
|
|
57
|
+
// This is a new UTXO we haven't seen before
|
|
58
|
+
newUTXOs.push(fetched);
|
|
59
|
+
|
|
60
|
+
// Check if this is change from one of our outgoing transactions
|
|
61
|
+
// Match by expectedChange amount (RPC doesn't provide usable sourceHash)
|
|
62
|
+
const matchedTxId = expectedChangeToTxId.get(fetched.assets);
|
|
63
|
+
if (matchedTxId) {
|
|
64
|
+
isChangeMap.set(noteId, matchedTxId);
|
|
65
|
+
// Remove from map to prevent double-matching
|
|
66
|
+
expectedChangeToTxId.delete(fetched.assets);
|
|
67
|
+
}
|
|
68
|
+
} else if (local.state !== 'spent') {
|
|
69
|
+
// Still exists on chain and in our store
|
|
70
|
+
stillUnspent.push(local);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Find spent notes (in local store but not on chain)
|
|
75
|
+
for (const [noteId, local] of localMap) {
|
|
76
|
+
// Skip already-spent notes (we're tracking state, not presence)
|
|
77
|
+
if (local.state === 'spent') {
|
|
78
|
+
continue;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
if (!fetchedMap.has(noteId)) {
|
|
82
|
+
// Note was in our store but is no longer on chain = spent
|
|
83
|
+
nowSpent.push(local);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
return {
|
|
88
|
+
newUTXOs,
|
|
89
|
+
stillUnspent,
|
|
90
|
+
nowSpent,
|
|
91
|
+
isChangeMap,
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Classify a new UTXO as incoming payment vs change
|
|
97
|
+
*
|
|
98
|
+
* @param newUTXO - The new UTXO to classify
|
|
99
|
+
* @param isChangeMap - Map from computeUTXODiff identifying change UTXOs
|
|
100
|
+
* @returns { isChange: boolean, walletTxId?: string }
|
|
101
|
+
*/
|
|
102
|
+
export function classifyNewUTXO(
|
|
103
|
+
newUTXO: FetchedUTXO,
|
|
104
|
+
isChangeMap: Map<string, string>
|
|
105
|
+
): { isChange: boolean; walletTxId?: string } {
|
|
106
|
+
const walletTxId = isChangeMap.get(newUTXO.noteId);
|
|
107
|
+
if (walletTxId) {
|
|
108
|
+
return { isChange: true, walletTxId };
|
|
109
|
+
}
|
|
110
|
+
return { isChange: false };
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Filter notes by state
|
|
115
|
+
*/
|
|
116
|
+
export function filterNotesByState(notes: StoredNote[], state: StoredNote['state']): StoredNote[] {
|
|
117
|
+
return notes.filter(n => n.state === state);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Get notes that are locked by a specific transaction
|
|
122
|
+
*/
|
|
123
|
+
export function getNotesForTransaction(notes: StoredNote[], walletTxId: string): StoredNote[] {
|
|
124
|
+
return notes.filter(n => n.pendingTxId === walletTxId);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Check if any notes are expired (in_flight for too long)
|
|
129
|
+
*
|
|
130
|
+
* @param notes - Notes to check
|
|
131
|
+
* @param maxAgeMs - Maximum age in milliseconds before considered expired
|
|
132
|
+
* @returns Notes that have been pending too long
|
|
133
|
+
*/
|
|
134
|
+
export function findExpiredNotes(notes: StoredNote[], maxAgeMs: number): StoredNote[] {
|
|
135
|
+
const now = Date.now();
|
|
136
|
+
return notes.filter(note => {
|
|
137
|
+
if (note.state !== 'in_flight') {
|
|
138
|
+
return false;
|
|
139
|
+
}
|
|
140
|
+
// Use discoveredAt as proxy for when the note was locked
|
|
141
|
+
// In practice, we'd want a separate lockedAt timestamp
|
|
142
|
+
return now - note.discoveredAt > maxAgeMs;
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Find transactions that should be marked as expired
|
|
148
|
+
*
|
|
149
|
+
* @param transactions - Wallet transactions to check
|
|
150
|
+
* @param maxAgeMs - Maximum age before expiry
|
|
151
|
+
* @returns Transactions that have exceeded the timeout
|
|
152
|
+
*/
|
|
153
|
+
export function findExpiredTransactions(
|
|
154
|
+
transactions: WalletTransaction[],
|
|
155
|
+
maxAgeMs: number
|
|
156
|
+
): WalletTransaction[] {
|
|
157
|
+
const now = Date.now();
|
|
158
|
+
return transactions.filter(tx => {
|
|
159
|
+
// Only check pending states
|
|
160
|
+
if (
|
|
161
|
+
tx.status !== 'created' &&
|
|
162
|
+
tx.status !== 'broadcast_pending' &&
|
|
163
|
+
tx.status !== 'broadcasted_unconfirmed'
|
|
164
|
+
) {
|
|
165
|
+
return false;
|
|
166
|
+
}
|
|
167
|
+
return now - tx.createdAt > maxAgeMs;
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Check if a transaction's inputs have been spent
|
|
173
|
+
* Used to detect confirmation when we see input notes disappear from chain
|
|
174
|
+
*
|
|
175
|
+
* @param tx - The transaction to check
|
|
176
|
+
* @param nowSpent - Notes that are no longer on chain
|
|
177
|
+
* @returns true if all inputs are spent
|
|
178
|
+
*/
|
|
179
|
+
export function areTransactionInputsSpent(tx: WalletTransaction, nowSpent: StoredNote[]): boolean {
|
|
180
|
+
if (!tx.inputNoteIds || tx.inputNoteIds.length === 0) {
|
|
181
|
+
return false;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
const spentNoteIds = new Set(nowSpent.map(n => n.noteId));
|
|
185
|
+
return tx.inputNoteIds.every(noteId => spentNoteIds.has(noteId));
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Match expected change to actual new UTXOs
|
|
190
|
+
* This is called after a transaction confirms to link change outputs
|
|
191
|
+
*
|
|
192
|
+
* @param tx - The confirmed transaction
|
|
193
|
+
* @param newUTXOs - New UTXOs discovered in this sync
|
|
194
|
+
* @param isChangeMap - Map identifying which new UTXOs are change
|
|
195
|
+
* @returns Note IDs that are confirmed change for this transaction
|
|
196
|
+
*/
|
|
197
|
+
export function matchChangeOutputs(
|
|
198
|
+
tx: WalletTransaction,
|
|
199
|
+
newUTXOs: FetchedUTXO[],
|
|
200
|
+
isChangeMap: Map<string, string>
|
|
201
|
+
): string[] {
|
|
202
|
+
const changeNoteIds: string[] = [];
|
|
203
|
+
|
|
204
|
+
for (const utxo of newUTXOs) {
|
|
205
|
+
const matchedTxId = isChangeMap.get(utxo.noteId);
|
|
206
|
+
if (matchedTxId === tx.id) {
|
|
207
|
+
changeNoteIds.push(utxo.noteId);
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
return changeNoteIds;
|
|
212
|
+
}
|