@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,136 @@
|
|
|
1
|
+
import { useEffect } from 'react';
|
|
2
|
+
import { useStore } from '../../store';
|
|
3
|
+
import { AccountIcon } from '../../components/AccountIcon';
|
|
4
|
+
import { SiteIcon } from '../../components/SiteIcon';
|
|
5
|
+
import { truncateAddress } from '../../utils/format';
|
|
6
|
+
import { send } from '../../utils/messaging';
|
|
7
|
+
import { INTERNAL_METHODS } from '../../../shared/constants';
|
|
8
|
+
import { useAutoRejectOnClose } from '../../hooks/useAutoRejectOnClose';
|
|
9
|
+
|
|
10
|
+
export function ConnectApprovalScreen() {
|
|
11
|
+
const { navigate, pendingConnectRequest, setPendingConnectRequest, wallet } = useStore();
|
|
12
|
+
|
|
13
|
+
const request = pendingConnectRequest;
|
|
14
|
+
const requestId = request?.id ?? '';
|
|
15
|
+
|
|
16
|
+
// Hooks must be unconditional; if there's no request, no-op.
|
|
17
|
+
useAutoRejectOnClose(requestId, INTERNAL_METHODS.REJECT_CONNECTION);
|
|
18
|
+
|
|
19
|
+
// Avoid calling navigate() during render.
|
|
20
|
+
useEffect(() => {
|
|
21
|
+
if (!request) navigate('home');
|
|
22
|
+
}, [request, navigate]);
|
|
23
|
+
|
|
24
|
+
if (!request) return null;
|
|
25
|
+
|
|
26
|
+
const { id, origin } = request;
|
|
27
|
+
const domain = origin.includes('://') ? new URL(origin).hostname : origin;
|
|
28
|
+
|
|
29
|
+
async function handleReject() {
|
|
30
|
+
await send(INTERNAL_METHODS.REJECT_CONNECTION, [id]);
|
|
31
|
+
setPendingConnectRequest(null);
|
|
32
|
+
window.close();
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
async function handleConnect() {
|
|
36
|
+
await send(INTERNAL_METHODS.APPROVE_CONNECTION, [id]);
|
|
37
|
+
setPendingConnectRequest(null);
|
|
38
|
+
window.close();
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const bg = 'var(--color-bg)';
|
|
42
|
+
const surface = 'var(--color-surface-800)';
|
|
43
|
+
const textPrimary = 'var(--color-text-primary)';
|
|
44
|
+
const textMuted = 'var(--color-text-muted)';
|
|
45
|
+
const divider = 'var(--color-divider)';
|
|
46
|
+
const green = 'var(--color-green)';
|
|
47
|
+
|
|
48
|
+
return (
|
|
49
|
+
<div className="h-screen flex items-center justify-center" style={{ backgroundColor: bg }}>
|
|
50
|
+
<div
|
|
51
|
+
className="w-full h-full flex flex-col"
|
|
52
|
+
style={{ backgroundColor: bg, maxWidth: '357px', maxHeight: '600px' }}
|
|
53
|
+
>
|
|
54
|
+
{/* Header */}
|
|
55
|
+
<div className="flex items-center justify-center px-4 py-4 shrink-0">
|
|
56
|
+
<h2 className="text-xl font-semibold" style={{ color: textPrimary }}>
|
|
57
|
+
Connect Request
|
|
58
|
+
</h2>
|
|
59
|
+
</div>
|
|
60
|
+
|
|
61
|
+
{/* Content */}
|
|
62
|
+
<div className="flex-1 min-h-0 overflow-y-auto">
|
|
63
|
+
<div className="px-4 pb-2">
|
|
64
|
+
{/* Site Icon & Info */}
|
|
65
|
+
<div className="text-center mb-4">
|
|
66
|
+
<div className="mb-3">
|
|
67
|
+
<SiteIcon origin={origin} domain={domain} size="lg" />
|
|
68
|
+
</div>
|
|
69
|
+
<h3 className="text-lg font-semibold mb-1" style={{ color: textPrimary }}>
|
|
70
|
+
{domain}
|
|
71
|
+
</h3>
|
|
72
|
+
<p className="text-xs break-all px-4" style={{ color: textMuted }}>
|
|
73
|
+
{origin}
|
|
74
|
+
</p>
|
|
75
|
+
</div>
|
|
76
|
+
|
|
77
|
+
{/* Permissions */}
|
|
78
|
+
<div className="rounded-lg p-3 mb-3" style={{ backgroundColor: surface }}>
|
|
79
|
+
<p className="text-sm mb-2 font-medium" style={{ color: textPrimary }}>
|
|
80
|
+
Requesting permission to:
|
|
81
|
+
</p>
|
|
82
|
+
<div className="space-y-1.5">
|
|
83
|
+
<div className="flex items-start gap-2 text-sm" style={{ color: textPrimary }}>
|
|
84
|
+
<span style={{ color: green }}>✓</span>
|
|
85
|
+
<span>View your wallet address</span>
|
|
86
|
+
</div>
|
|
87
|
+
<div className="flex items-start gap-2 text-sm" style={{ color: textPrimary }}>
|
|
88
|
+
<span style={{ color: green }}>✓</span>
|
|
89
|
+
<span>Request transaction approvals</span>
|
|
90
|
+
</div>
|
|
91
|
+
</div>
|
|
92
|
+
</div>
|
|
93
|
+
|
|
94
|
+
{/* Account */}
|
|
95
|
+
<div>
|
|
96
|
+
<label className="text-xs block mb-1.5 font-medium" style={{ color: textMuted }}>
|
|
97
|
+
Connecting Account
|
|
98
|
+
</label>
|
|
99
|
+
<div
|
|
100
|
+
className="rounded-lg p-3 flex items-center gap-2.5"
|
|
101
|
+
style={{ backgroundColor: surface }}
|
|
102
|
+
>
|
|
103
|
+
<AccountIcon
|
|
104
|
+
styleId={wallet.currentAccount?.iconStyleId}
|
|
105
|
+
color={wallet.currentAccount?.iconColor}
|
|
106
|
+
className="w-8 h-8 shrink-0"
|
|
107
|
+
/>
|
|
108
|
+
<div className="flex-1 min-w-0">
|
|
109
|
+
<p className="text-sm font-medium" style={{ color: textPrimary }}>
|
|
110
|
+
{wallet.currentAccount?.name || 'Unknown'}
|
|
111
|
+
</p>
|
|
112
|
+
<p className="text-xs font-mono mt-0.5" style={{ color: textMuted }}>
|
|
113
|
+
{truncateAddress(wallet.currentAccount?.address)}
|
|
114
|
+
</p>
|
|
115
|
+
</div>
|
|
116
|
+
</div>
|
|
117
|
+
</div>
|
|
118
|
+
</div>
|
|
119
|
+
</div>
|
|
120
|
+
|
|
121
|
+
{/* Footer Buttons */}
|
|
122
|
+
<div
|
|
123
|
+
className="px-4 py-2.5 shrink-0 flex gap-3"
|
|
124
|
+
style={{ borderTop: `1px solid ${divider}` }}
|
|
125
|
+
>
|
|
126
|
+
<button onClick={handleReject} className="btn-secondary flex-1">
|
|
127
|
+
Cancel
|
|
128
|
+
</button>
|
|
129
|
+
<button onClick={handleConnect} className="btn-primary flex-1">
|
|
130
|
+
Connect
|
|
131
|
+
</button>
|
|
132
|
+
</div>
|
|
133
|
+
</div>
|
|
134
|
+
</div>
|
|
135
|
+
);
|
|
136
|
+
}
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import { useStore } from '../../store';
|
|
2
|
+
import { AccountIcon } from '../../components/AccountIcon';
|
|
3
|
+
import { SiteIcon } from '../../components/SiteIcon';
|
|
4
|
+
import { truncateAddress } from '../../utils/format';
|
|
5
|
+
import { send } from '../../utils/messaging';
|
|
6
|
+
import { INTERNAL_METHODS } from '../../../shared/constants';
|
|
7
|
+
import { useAutoRejectOnClose } from '../../hooks/useAutoRejectOnClose';
|
|
8
|
+
|
|
9
|
+
export function SignMessageScreen() {
|
|
10
|
+
const { navigate, pendingSignRequest, setPendingSignRequest, wallet } = useStore();
|
|
11
|
+
|
|
12
|
+
if (!pendingSignRequest) {
|
|
13
|
+
navigate('home');
|
|
14
|
+
return null;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const { id, origin, message } = pendingSignRequest;
|
|
18
|
+
|
|
19
|
+
useAutoRejectOnClose(id, INTERNAL_METHODS.REJECT_SIGN_MESSAGE);
|
|
20
|
+
|
|
21
|
+
async function handleDecline() {
|
|
22
|
+
await send(INTERNAL_METHODS.REJECT_SIGN_MESSAGE, [id]);
|
|
23
|
+
setPendingSignRequest(null);
|
|
24
|
+
window.close();
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
async function handleSign() {
|
|
28
|
+
await send(INTERNAL_METHODS.APPROVE_SIGN_MESSAGE, [id]);
|
|
29
|
+
setPendingSignRequest(null);
|
|
30
|
+
window.close();
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const bg = 'var(--color-bg)';
|
|
34
|
+
const surface = 'var(--color-surface-800)';
|
|
35
|
+
const textPrimary = 'var(--color-text-primary)';
|
|
36
|
+
const textMuted = 'var(--color-text-muted)';
|
|
37
|
+
const divider = 'var(--color-divider)';
|
|
38
|
+
|
|
39
|
+
return (
|
|
40
|
+
<div className="h-screen flex items-center justify-center" style={{ backgroundColor: bg }}>
|
|
41
|
+
<div
|
|
42
|
+
className="w-full h-full flex flex-col"
|
|
43
|
+
style={{ backgroundColor: bg, maxWidth: '357px', maxHeight: '600px' }}
|
|
44
|
+
>
|
|
45
|
+
{/* Header */}
|
|
46
|
+
<div className="flex items-center justify-center px-4 py-4 shrink-0">
|
|
47
|
+
<h2 className="text-xl font-semibold" style={{ color: textPrimary }}>
|
|
48
|
+
Sign Message
|
|
49
|
+
</h2>
|
|
50
|
+
</div>
|
|
51
|
+
|
|
52
|
+
{/* Content */}
|
|
53
|
+
<div className="flex-1 min-h-0 overflow-y-auto">
|
|
54
|
+
<div className="px-4 pb-2">
|
|
55
|
+
{/* Site Info */}
|
|
56
|
+
<div className="mb-3">
|
|
57
|
+
<label className="text-xs block mb-1.5 font-medium" style={{ color: textMuted }}>
|
|
58
|
+
Requesting Site
|
|
59
|
+
</label>
|
|
60
|
+
<div
|
|
61
|
+
className="rounded-lg p-3 flex items-center gap-3"
|
|
62
|
+
style={{ backgroundColor: surface }}
|
|
63
|
+
>
|
|
64
|
+
<SiteIcon
|
|
65
|
+
origin={origin}
|
|
66
|
+
domain={origin.includes('://') ? new URL(origin).hostname : origin}
|
|
67
|
+
size="md"
|
|
68
|
+
/>
|
|
69
|
+
<div className="flex-1 min-w-0">
|
|
70
|
+
<p className="text-sm font-semibold mb-0.5" style={{ color: textPrimary }}>
|
|
71
|
+
{origin.includes('://') ? new URL(origin).hostname : origin}
|
|
72
|
+
</p>
|
|
73
|
+
<p className="text-xs break-all" style={{ color: textMuted }}>
|
|
74
|
+
{origin}
|
|
75
|
+
</p>
|
|
76
|
+
</div>
|
|
77
|
+
</div>
|
|
78
|
+
</div>
|
|
79
|
+
|
|
80
|
+
{/* Message Content */}
|
|
81
|
+
<div className="mb-3">
|
|
82
|
+
<label className="text-xs block mb-1.5 font-medium" style={{ color: textMuted }}>
|
|
83
|
+
Message
|
|
84
|
+
</label>
|
|
85
|
+
<div
|
|
86
|
+
className="rounded-lg p-3 max-h-48 overflow-y-auto"
|
|
87
|
+
style={{ backgroundColor: surface }}
|
|
88
|
+
>
|
|
89
|
+
<pre
|
|
90
|
+
className="text-sm whitespace-pre-wrap break-words font-mono"
|
|
91
|
+
style={{ color: textPrimary }}
|
|
92
|
+
>
|
|
93
|
+
{message}
|
|
94
|
+
</pre>
|
|
95
|
+
</div>
|
|
96
|
+
</div>
|
|
97
|
+
|
|
98
|
+
{/* Account */}
|
|
99
|
+
<div>
|
|
100
|
+
<label className="text-xs block mb-1.5 font-medium" style={{ color: textMuted }}>
|
|
101
|
+
Signing Account
|
|
102
|
+
</label>
|
|
103
|
+
<div
|
|
104
|
+
className="rounded-lg p-3 flex items-center gap-2.5"
|
|
105
|
+
style={{ backgroundColor: surface }}
|
|
106
|
+
>
|
|
107
|
+
<AccountIcon
|
|
108
|
+
styleId={wallet.currentAccount?.iconStyleId}
|
|
109
|
+
color={wallet.currentAccount?.iconColor}
|
|
110
|
+
className="w-8 h-8 shrink-0"
|
|
111
|
+
/>
|
|
112
|
+
<div className="flex-1 min-w-0">
|
|
113
|
+
<p className="text-sm font-medium" style={{ color: textPrimary }}>
|
|
114
|
+
{wallet.currentAccount?.name || 'Unknown'}
|
|
115
|
+
</p>
|
|
116
|
+
<p className="text-xs font-mono mt-0.5" style={{ color: textMuted }}>
|
|
117
|
+
{truncateAddress(wallet.currentAccount?.address)}
|
|
118
|
+
</p>
|
|
119
|
+
</div>
|
|
120
|
+
</div>
|
|
121
|
+
</div>
|
|
122
|
+
</div>
|
|
123
|
+
</div>
|
|
124
|
+
|
|
125
|
+
{/* Footer Buttons */}
|
|
126
|
+
<div
|
|
127
|
+
className="px-4 py-2.5 shrink-0 flex gap-3"
|
|
128
|
+
style={{ borderTop: `1px solid ${divider}` }}
|
|
129
|
+
>
|
|
130
|
+
<button onClick={handleDecline} className="btn-secondary flex-1">
|
|
131
|
+
Decline
|
|
132
|
+
</button>
|
|
133
|
+
<button onClick={handleSign} className="btn-primary flex-1">
|
|
134
|
+
Sign
|
|
135
|
+
</button>
|
|
136
|
+
</div>
|
|
137
|
+
</div>
|
|
138
|
+
</div>
|
|
139
|
+
);
|
|
140
|
+
}
|
|
@@ -0,0 +1,320 @@
|
|
|
1
|
+
import { useEffect, useState } from 'react';
|
|
2
|
+
import { useStore } from '../../store';
|
|
3
|
+
import { INTERNAL_METHODS, APPROVAL_CONSTANTS } from '../../../shared/constants';
|
|
4
|
+
import { send } from '../../utils/messaging';
|
|
5
|
+
import { SignRawTxRequest } from '../../../shared/types';
|
|
6
|
+
import { useAutoRejectOnClose } from '../../hooks/useAutoRejectOnClose';
|
|
7
|
+
import { AccountIcon } from '../../components/AccountIcon';
|
|
8
|
+
import { SiteIcon } from '../../components/SiteIcon';
|
|
9
|
+
import { truncateAddress } from '../../utils/format';
|
|
10
|
+
import { nickToNock, formatNock } from '../../../shared/currency';
|
|
11
|
+
import { extractMemo } from '../../utils/memo';
|
|
12
|
+
|
|
13
|
+
interface NoteItemProps {
|
|
14
|
+
note: any;
|
|
15
|
+
type: 'to' | 'from';
|
|
16
|
+
textPrimary: string;
|
|
17
|
+
textMuted: string;
|
|
18
|
+
surface: string;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function NoteItem({ note, type, textPrimary, textMuted, surface }: NoteItemProps) {
|
|
22
|
+
const [copied, setCopied] = useState(false);
|
|
23
|
+
|
|
24
|
+
// Extract data from the complex JSON structure
|
|
25
|
+
// Structure: [{"note_version":{"V1":{...}}}] or similar
|
|
26
|
+
// We need to handle potential variations if the structure isn't exactly as expected, but assuming the provided JSON is representative.
|
|
27
|
+
|
|
28
|
+
// The note object passed here is likely one item from the array, e.g. {"note_version":{"V1":{...}}}
|
|
29
|
+
|
|
30
|
+
let versionData: any = null;
|
|
31
|
+
|
|
32
|
+
if (note.note_version?.V1) {
|
|
33
|
+
versionData = note.note_version.V1;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
if (!versionData) {
|
|
37
|
+
return (
|
|
38
|
+
<div className="rounded-lg p-3 mb-2" style={{ backgroundColor: surface }}>
|
|
39
|
+
<p className="text-sm text-red-500">Unknown note format</p>
|
|
40
|
+
<pre className="text-xs break-all">{JSON.stringify(note)}</pre>
|
|
41
|
+
</div>
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const assetsValue = versionData.assets?.value || '0';
|
|
46
|
+
const nicks = parseInt(assetsValue, 10);
|
|
47
|
+
const nocks = nickToNock(nicks);
|
|
48
|
+
const formattedNocks = formatNock(nocks);
|
|
49
|
+
|
|
50
|
+
const firstName = versionData.name?.first || '';
|
|
51
|
+
const lastName = versionData.name?.last || '';
|
|
52
|
+
const fullName = `[ ${firstName} ${lastName} ]`;
|
|
53
|
+
|
|
54
|
+
// Truncate name: first 4 chars of first name ... last 4 chars of last name
|
|
55
|
+
const truncatedName = `[ ${firstName.slice(0, 4)}...${lastName.slice(-4)} ]`;
|
|
56
|
+
|
|
57
|
+
const handleCopy = () => {
|
|
58
|
+
navigator.clipboard.writeText(fullName);
|
|
59
|
+
setCopied(true);
|
|
60
|
+
setTimeout(() => setCopied(false), 2000);
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
return (
|
|
64
|
+
<div className="rounded-lg p-3 mb-2" style={{ backgroundColor: surface }}>
|
|
65
|
+
<div className="flex flex-row items-center gap-1 text-sm font-medium">
|
|
66
|
+
<span style={{ color: textPrimary }}>{formattedNocks} NOCK</span>
|
|
67
|
+
<span style={{ color: textMuted }}>{type}</span>
|
|
68
|
+
<span
|
|
69
|
+
className="font-mono cursor-pointer hover:opacity-80 transition-opacity relative group"
|
|
70
|
+
style={{ color: textMuted }}
|
|
71
|
+
onClick={handleCopy}
|
|
72
|
+
title={fullName}
|
|
73
|
+
>
|
|
74
|
+
{truncatedName}
|
|
75
|
+
{copied && (
|
|
76
|
+
<span className="absolute right-0 top-0 z-50 whitespace-nowrap bg-green-500 text-white text-[10px] px-1 rounded transform -translate-y-full">
|
|
77
|
+
Copied!
|
|
78
|
+
</span>
|
|
79
|
+
)}
|
|
80
|
+
</span>
|
|
81
|
+
</div>
|
|
82
|
+
</div>
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export function SignRawTxScreen() {
|
|
87
|
+
const { pendingSignRawTxRequest, setPendingSignRawTxRequest, navigate, wallet } = useStore();
|
|
88
|
+
const [memo, setMemo] = useState<string | null>(null);
|
|
89
|
+
|
|
90
|
+
const request = pendingSignRawTxRequest;
|
|
91
|
+
const requestId = request?.id ?? '';
|
|
92
|
+
|
|
93
|
+
// Hooks must be unconditional; if there's no request, no-op.
|
|
94
|
+
useAutoRejectOnClose(requestId, INTERNAL_METHODS.REJECT_SIGN_RAW_TX);
|
|
95
|
+
|
|
96
|
+
// Avoid calling navigate() during render (it triggers "setState while rendering").
|
|
97
|
+
useEffect(() => {
|
|
98
|
+
if (!request) navigate('home');
|
|
99
|
+
}, [request, navigate]);
|
|
100
|
+
|
|
101
|
+
// Memo decoding must be unconditional too, otherwise when the request is cleared
|
|
102
|
+
// (e.g. after approval/reject), this component would render fewer hooks.
|
|
103
|
+
useEffect(() => {
|
|
104
|
+
let cancelled = false;
|
|
105
|
+
|
|
106
|
+
if (!request) {
|
|
107
|
+
setMemo(null);
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
(async () => {
|
|
112
|
+
try {
|
|
113
|
+
const { ensureWasmInitialized } = await import('../../../shared/wasm-utils');
|
|
114
|
+
await ensureWasmInitialized();
|
|
115
|
+
if (cancelled) return;
|
|
116
|
+
setMemo(extractMemo({ rawTx: request.rawTx, outputs: request.outputs }));
|
|
117
|
+
} catch (err) {
|
|
118
|
+
console.error('[SignRawTx] Failed to decode memo:', err);
|
|
119
|
+
if (!cancelled) setMemo(null);
|
|
120
|
+
}
|
|
121
|
+
})();
|
|
122
|
+
|
|
123
|
+
return () => {
|
|
124
|
+
cancelled = true;
|
|
125
|
+
};
|
|
126
|
+
}, [request]);
|
|
127
|
+
|
|
128
|
+
if (!request) return null;
|
|
129
|
+
|
|
130
|
+
const { id, origin, rawTx, notes, outputs } = request;
|
|
131
|
+
|
|
132
|
+
async function handleDecline() {
|
|
133
|
+
await send(INTERNAL_METHODS.REJECT_SIGN_RAW_TX, [id]);
|
|
134
|
+
setPendingSignRawTxRequest(null);
|
|
135
|
+
window.close();
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
async function handleSign() {
|
|
139
|
+
await send(INTERNAL_METHODS.APPROVE_SIGN_RAW_TX, [id]);
|
|
140
|
+
setPendingSignRawTxRequest(null);
|
|
141
|
+
window.close();
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// Calculate network fee
|
|
145
|
+
let totalFeeNicks = 0;
|
|
146
|
+
try {
|
|
147
|
+
if (rawTx && rawTx.spends && Array.isArray(rawTx.spends)) {
|
|
148
|
+
totalFeeNicks = rawTx.spends.reduce((sum: number, spend: any) => {
|
|
149
|
+
const feeValue = spend?.spend?.spend_kind?.Witness?.fee?.value;
|
|
150
|
+
const fee = feeValue ? parseInt(feeValue, 10) : 0;
|
|
151
|
+
return sum + (isNaN(fee) ? 0 : fee);
|
|
152
|
+
}, 0);
|
|
153
|
+
}
|
|
154
|
+
} catch (err) {
|
|
155
|
+
console.error('Error calculating fee:', err);
|
|
156
|
+
// Default to 0 if error
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
const totalFeeNocks = nickToNock(totalFeeNicks);
|
|
160
|
+
const formattedFee = formatNock(totalFeeNocks);
|
|
161
|
+
|
|
162
|
+
const bg = 'var(--color-bg)';
|
|
163
|
+
const surface = 'var(--color-surface-800)';
|
|
164
|
+
const textPrimary = 'var(--color-text-primary)';
|
|
165
|
+
const textMuted = 'var(--color-text-muted)';
|
|
166
|
+
const divider = 'var(--color-divider)';
|
|
167
|
+
|
|
168
|
+
return (
|
|
169
|
+
<div className="h-screen flex items-center justify-center" style={{ backgroundColor: bg }}>
|
|
170
|
+
<div
|
|
171
|
+
className="w-full h-full flex flex-col"
|
|
172
|
+
style={{ backgroundColor: bg, maxWidth: '357px', maxHeight: '600px' }}
|
|
173
|
+
>
|
|
174
|
+
{/* Header */}
|
|
175
|
+
<div className="flex items-center justify-center px-4 py-4 shrink-0">
|
|
176
|
+
<h2 className="text-xl font-semibold" style={{ color: textPrimary }}>
|
|
177
|
+
Sign Raw Transaction
|
|
178
|
+
</h2>
|
|
179
|
+
</div>
|
|
180
|
+
|
|
181
|
+
{/* Content */}
|
|
182
|
+
<div className="flex-1 min-h-0 overflow-y-auto">
|
|
183
|
+
<div className="px-4 pb-2">
|
|
184
|
+
{/* Site Info */}
|
|
185
|
+
<div className="mb-3">
|
|
186
|
+
<label className="text-xs block mb-1.5 font-medium" style={{ color: textMuted }}>
|
|
187
|
+
Requesting Site
|
|
188
|
+
</label>
|
|
189
|
+
<div
|
|
190
|
+
className="rounded-lg p-3 flex items-center gap-3"
|
|
191
|
+
style={{ backgroundColor: surface }}
|
|
192
|
+
>
|
|
193
|
+
<SiteIcon
|
|
194
|
+
origin={origin}
|
|
195
|
+
domain={origin.includes('://') ? new URL(origin).hostname : origin}
|
|
196
|
+
size="md"
|
|
197
|
+
/>
|
|
198
|
+
<div className="flex-1 min-w-0">
|
|
199
|
+
<p className="text-sm font-semibold mb-0.5" style={{ color: textPrimary }}>
|
|
200
|
+
{origin.includes('://') ? new URL(origin).hostname : origin}
|
|
201
|
+
</p>
|
|
202
|
+
<p className="text-xs break-all" style={{ color: textMuted }}>
|
|
203
|
+
{origin}
|
|
204
|
+
</p>
|
|
205
|
+
</div>
|
|
206
|
+
</div>
|
|
207
|
+
</div>
|
|
208
|
+
|
|
209
|
+
{/* Raw Transaction Content */}
|
|
210
|
+
<div className="mb-3">
|
|
211
|
+
<label className="text-xs block mb-1.5 font-medium" style={{ color: textMuted }}>
|
|
212
|
+
Inputs ({notes.length})
|
|
213
|
+
</label>
|
|
214
|
+
<div className="max-h-48 overflow-y-auto">
|
|
215
|
+
{notes.map((note: any, index: number) => (
|
|
216
|
+
<NoteItem
|
|
217
|
+
key={`input-${index}`}
|
|
218
|
+
note={note}
|
|
219
|
+
type="from"
|
|
220
|
+
textPrimary={textPrimary}
|
|
221
|
+
textMuted={textMuted}
|
|
222
|
+
surface={surface}
|
|
223
|
+
/>
|
|
224
|
+
))}
|
|
225
|
+
</div>
|
|
226
|
+
</div>
|
|
227
|
+
|
|
228
|
+
{/* Raw Transaction Outputs */}
|
|
229
|
+
{outputs && outputs.length > 0 && (
|
|
230
|
+
<div className="mb-3">
|
|
231
|
+
<label className="text-xs block mb-1.5 font-medium" style={{ color: textMuted }}>
|
|
232
|
+
Outputs ({outputs.length})
|
|
233
|
+
</label>
|
|
234
|
+
<div className="max-h-48 overflow-y-auto">
|
|
235
|
+
{outputs.map((output: any, index: number) => (
|
|
236
|
+
<NoteItem
|
|
237
|
+
key={`output-${index}`}
|
|
238
|
+
note={output}
|
|
239
|
+
type="to"
|
|
240
|
+
textPrimary={textPrimary}
|
|
241
|
+
textMuted={textMuted}
|
|
242
|
+
surface={surface}
|
|
243
|
+
/>
|
|
244
|
+
))}
|
|
245
|
+
</div>
|
|
246
|
+
</div>
|
|
247
|
+
)}
|
|
248
|
+
|
|
249
|
+
{/* Memo (optional) */}
|
|
250
|
+
{memo && (
|
|
251
|
+
<div className="mb-3">
|
|
252
|
+
<label className="text-xs block mb-1.5 font-medium" style={{ color: textMuted }}>
|
|
253
|
+
Memo
|
|
254
|
+
</label>
|
|
255
|
+
<div className="rounded-lg p-3" style={{ backgroundColor: surface }}>
|
|
256
|
+
<p
|
|
257
|
+
className="text-sm whitespace-pre-wrap break-words"
|
|
258
|
+
style={{ color: textPrimary }}
|
|
259
|
+
>
|
|
260
|
+
{memo}
|
|
261
|
+
</p>
|
|
262
|
+
</div>
|
|
263
|
+
</div>
|
|
264
|
+
)}
|
|
265
|
+
|
|
266
|
+
{/* Network Fee */}
|
|
267
|
+
<div className="mb-3">
|
|
268
|
+
<label className="text-xs block mb-1.5 font-medium" style={{ color: textMuted }}>
|
|
269
|
+
Network Fee
|
|
270
|
+
</label>
|
|
271
|
+
<div className="rounded-lg p-3" style={{ backgroundColor: surface }}>
|
|
272
|
+
<p className="text-sm font-semibold" style={{ color: textPrimary }}>
|
|
273
|
+
{formattedFee} NOCK
|
|
274
|
+
</p>
|
|
275
|
+
</div>
|
|
276
|
+
</div>
|
|
277
|
+
|
|
278
|
+
{/* Account */}
|
|
279
|
+
<div>
|
|
280
|
+
<label className="text-xs block mb-1.5 font-medium" style={{ color: textMuted }}>
|
|
281
|
+
Signing Account
|
|
282
|
+
</label>
|
|
283
|
+
<div
|
|
284
|
+
className="rounded-lg p-3 flex items-center gap-2.5"
|
|
285
|
+
style={{ backgroundColor: surface }}
|
|
286
|
+
>
|
|
287
|
+
<AccountIcon
|
|
288
|
+
styleId={wallet.currentAccount?.iconStyleId}
|
|
289
|
+
color={wallet.currentAccount?.iconColor}
|
|
290
|
+
className="w-8 h-8 shrink-0"
|
|
291
|
+
/>
|
|
292
|
+
<div className="flex-1 min-w-0">
|
|
293
|
+
<p className="text-sm font-medium" style={{ color: textPrimary }}>
|
|
294
|
+
{wallet.currentAccount?.name || 'Unknown'}
|
|
295
|
+
</p>
|
|
296
|
+
<p className="text-xs font-mono mt-0.5" style={{ color: textMuted }}>
|
|
297
|
+
{truncateAddress(wallet.currentAccount?.address)}
|
|
298
|
+
</p>
|
|
299
|
+
</div>
|
|
300
|
+
</div>
|
|
301
|
+
</div>
|
|
302
|
+
</div>
|
|
303
|
+
</div>
|
|
304
|
+
|
|
305
|
+
{/* Footer Buttons */}
|
|
306
|
+
<div
|
|
307
|
+
className="px-4 py-2.5 shrink-0 flex gap-3"
|
|
308
|
+
style={{ borderTop: `1px solid ${divider}` }}
|
|
309
|
+
>
|
|
310
|
+
<button onClick={handleDecline} className="btn-secondary flex-1">
|
|
311
|
+
Decline
|
|
312
|
+
</button>
|
|
313
|
+
<button onClick={handleSign} className="btn-primary flex-1">
|
|
314
|
+
Sign
|
|
315
|
+
</button>
|
|
316
|
+
</div>
|
|
317
|
+
</div>
|
|
318
|
+
</div>
|
|
319
|
+
);
|
|
320
|
+
}
|