ethagent 1.1.2 → 2.0.0
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/LICENSE +21 -21
- package/README.md +126 -30
- package/package.json +7 -2
- package/src/app/FirstRun.tsx +190 -146
- package/src/app/FirstRunTimeline.tsx +47 -0
- package/src/app/input/AppInputProvider.tsx +1 -1
- package/src/app/keybindings/KeybindingProvider.tsx +1 -1
- package/src/chat/ChatBottomPane.tsx +0 -1
- package/src/chat/ChatInput.tsx +6 -6
- package/src/chat/ChatScreen.tsx +35 -15
- package/src/chat/ContextLimitView.tsx +4 -4
- package/src/chat/ContinuityEditReviewView.tsx +10 -22
- package/src/chat/CopyPicker.tsx +0 -1
- package/src/chat/MessageList.tsx +62 -45
- package/src/chat/PermissionPrompt.tsx +13 -9
- package/src/chat/PlanApprovalView.tsx +3 -3
- package/src/chat/ResumeView.tsx +1 -4
- package/src/chat/RewindView.tsx +2 -2
- package/src/chat/chatInputState.ts +1 -1
- package/src/chat/chatScreenUtils.ts +22 -11
- package/src/chat/chatSessionState.ts +2 -2
- package/src/chat/chatTurnOrchestrator.ts +16 -81
- package/src/chat/commands.ts +1 -1
- package/src/chat/textCursor.ts +1 -1
- package/src/chat/transcriptViewport.ts +2 -7
- package/src/cli/ResetConfirmView.tsx +1 -1
- package/src/cli/main.tsx +9 -3
- package/src/cli/preview.tsx +0 -5
- package/src/cli/updateNotice.ts +4 -2
- package/src/identity/continuity/editor.ts +7 -107
- package/src/identity/continuity/envelope.ts +1048 -40
- package/src/identity/continuity/history.ts +4 -4
- package/src/identity/continuity/localBackup.ts +249 -0
- package/src/identity/continuity/privateEdit/apply.ts +170 -0
- package/src/identity/continuity/privateEdit/diff.ts +82 -0
- package/src/identity/continuity/privateEdit/files.ts +23 -0
- package/src/identity/continuity/privateEdit/types.ts +28 -0
- package/src/identity/continuity/privateEdit.ts +10 -298
- package/src/identity/continuity/publicSkills.ts +8 -9
- package/src/identity/continuity/snapshots.ts +17 -6
- package/src/identity/continuity/storage/defaults.ts +111 -0
- package/src/identity/continuity/storage/files.ts +72 -0
- package/src/identity/continuity/storage/markdown.ts +81 -0
- package/src/identity/continuity/storage/paths.ts +24 -0
- package/src/identity/continuity/storage/scaffold.ts +124 -0
- package/src/identity/continuity/storage/status.ts +86 -0
- package/src/identity/continuity/storage/types.ts +27 -0
- package/src/identity/continuity/storage.ts +32 -507
- package/src/identity/continuity/zipWriter.ts +95 -0
- package/src/identity/crypto/backupEnvelope.ts +14 -247
- package/src/identity/crypto/eth.ts +7 -7
- package/src/identity/ens/agentRecords.ts +96 -0
- package/src/identity/ens/ensAutomation/contracts.ts +38 -0
- package/src/identity/ens/ensAutomation/delete.ts +80 -0
- package/src/identity/ens/ensAutomation/names.ts +14 -0
- package/src/identity/ens/ensAutomation/operators.ts +29 -0
- package/src/identity/ens/ensAutomation/read.ts +114 -0
- package/src/identity/ens/ensAutomation/root.ts +63 -0
- package/src/identity/ens/ensAutomation/setup.ts +284 -0
- package/src/identity/ens/ensAutomation/transactions.ts +107 -0
- package/src/identity/ens/ensAutomation/types.ts +126 -0
- package/src/identity/ens/ensAutomation.ts +29 -0
- package/src/identity/ens/ensLookup/client.ts +43 -0
- package/src/identity/ens/ensLookup/constants.ts +26 -0
- package/src/identity/ens/ensLookup/discovery.ts +70 -0
- package/src/identity/ens/ensLookup/names.ts +34 -0
- package/src/identity/ens/ensLookup/records.ts +45 -0
- package/src/identity/ens/ensLookup/resolve.ts +75 -0
- package/src/identity/ens/ensLookup/tokenReference.ts +17 -0
- package/src/identity/ens/ensLookup/types.ts +38 -0
- package/src/identity/ens/ensLookup/validation.ts +72 -0
- package/src/identity/ens/ensLookup.ts +19 -0
- package/src/identity/ens/ensRegistration.ts +199 -0
- package/src/identity/ens/resolverDelegation.ts +48 -0
- package/src/identity/hub/IdentityHub.tsx +13 -817
- package/src/identity/hub/OperationalRoutes.tsx +370 -0
- package/src/identity/hub/Routes.tsx +361 -0
- package/src/identity/hub/advancedEnsValidation.ts +45 -0
- package/src/identity/hub/{screens → components}/DetailsScreen.tsx +14 -8
- package/src/identity/hub/{screens → components}/ErrorScreen.tsx +15 -5
- package/src/identity/hub/components/FlowTimeline.tsx +27 -0
- package/src/identity/hub/components/IdentitySummary.tsx +190 -0
- package/src/identity/hub/components/MenuScreen.tsx +237 -0
- package/src/identity/hub/{screens → components}/NetworkScreen.tsx +3 -3
- package/src/identity/hub/{screens/RebackupStorageScreen.tsx → components/PinataJwtInput.tsx} +21 -18
- package/src/identity/hub/components/UnlinkedIdentityScreen.tsx +76 -0
- package/src/identity/hub/{screens → components}/WalletApprovalScreen.tsx +9 -8
- package/src/identity/hub/components/menuFlagsFromReconciliation.ts +68 -0
- package/src/identity/hub/effects/create.ts +310 -0
- package/src/identity/hub/effects/ens/flows.ts +218 -0
- package/src/identity/hub/effects/ens/index.ts +11 -0
- package/src/identity/hub/effects/ens/transactions.ts +239 -0
- package/src/identity/hub/effects/index.ts +74 -0
- package/src/identity/hub/effects/profile/profileState.ts +173 -0
- package/src/identity/hub/effects/publicProfile/index.ts +5 -0
- package/src/identity/hub/effects/publicProfile/runPublicProfileSave.ts +646 -0
- package/src/identity/hub/effects/rebackup/index.ts +7 -0
- package/src/identity/hub/effects/rebackup/operatorVault.ts +378 -0
- package/src/identity/hub/effects/rebackup/runRebackup.ts +451 -0
- package/src/identity/hub/effects/receipts.ts +46 -0
- package/src/identity/hub/effects/restore/apply.ts +112 -0
- package/src/identity/hub/effects/restore/auth.ts +159 -0
- package/src/identity/hub/effects/restore/discover.ts +86 -0
- package/src/identity/hub/effects/restore/envelopes.ts +21 -0
- package/src/identity/hub/effects/restore/fetch.ts +25 -0
- package/src/identity/hub/effects/restore/index.ts +22 -0
- package/src/identity/hub/effects/restore/recovery.ts +135 -0
- package/src/identity/hub/effects/restore/resolve.ts +102 -0
- package/src/identity/hub/effects/restore/restoreEffects.ts +22 -0
- package/src/identity/hub/effects/restore/shared.ts +91 -0
- package/src/identity/hub/effects/restoreAdmin.ts +93 -0
- package/src/identity/hub/effects/shared/profilePrep.ts +139 -0
- package/src/identity/hub/effects/shared/snapshot.ts +336 -0
- package/src/identity/hub/effects/shared/sync.ts +190 -0
- package/src/identity/hub/effects/token-transfer/index.ts +6 -0
- package/src/identity/hub/effects/token-transfer/progress.ts +59 -0
- package/src/identity/hub/effects/token-transfer/runTokenTransfer.ts +299 -0
- package/src/identity/hub/effects/types.ts +53 -0
- package/src/identity/hub/effects/vault/preflight.ts +50 -0
- package/src/identity/hub/flows/continuity/ContinuityDashboardScreen.tsx +170 -0
- package/src/identity/hub/flows/continuity/RebackupStorageScreen.tsx +28 -0
- package/src/identity/hub/{screens → flows/continuity}/RecoveryConfirmScreen.tsx +28 -19
- package/src/identity/hub/flows/continuity/SavePromptScreen.tsx +49 -0
- package/src/identity/hub/{screens → flows/create}/CreateFlow.tsx +61 -62
- package/src/identity/hub/flows/custody/CustodyEditFlow.tsx +347 -0
- package/src/identity/hub/flows/custody/custodyEffects.ts +321 -0
- package/src/identity/hub/flows/custody/custodyFlowActions.ts +236 -0
- package/src/identity/hub/flows/custody/custodyFlowEffects.ts +163 -0
- package/src/identity/hub/flows/custody/custodyFlowHelpers.ts +25 -0
- package/src/identity/hub/flows/custody/custodyFlowRoutes.tsx +239 -0
- package/src/identity/hub/flows/custody/custodyFlowTypes.ts +45 -0
- package/src/identity/hub/flows/custody/useCustodyFlow.tsx +25 -0
- package/src/identity/hub/flows/ens/EnsEditAdvancedScreens.tsx +336 -0
- package/src/identity/hub/flows/ens/EnsEditFlow.tsx +397 -0
- package/src/identity/hub/flows/ens/EnsEditMaintenanceScreens.tsx +332 -0
- package/src/identity/hub/flows/ens/EnsEditReviewScreens.tsx +471 -0
- package/src/identity/hub/flows/ens/EnsEditRunners.tsx +198 -0
- package/src/identity/hub/flows/ens/EnsEditShared.tsx +162 -0
- package/src/identity/hub/flows/ens/EnsEditSimpleScreens.tsx +518 -0
- package/src/identity/hub/flows/ens/IdentityHubEnsFlow.tsx +299 -0
- package/src/identity/hub/flows/ens/OperatorWalletsScreen.tsx +398 -0
- package/src/identity/hub/flows/ens/ensEditCopy.ts +117 -0
- package/src/identity/hub/flows/ens/ensEditTypes.ts +91 -0
- package/src/identity/hub/flows/profile/EditProfileFlow.tsx +271 -0
- package/src/identity/hub/flows/restore/RestoreFlow.tsx +324 -0
- package/src/identity/hub/flows/restore/useRestoreFlowEffects.ts +77 -0
- package/src/identity/hub/{screens → flows/settings}/StorageCredentialScreen.tsx +23 -44
- package/src/identity/hub/flows/token-transfer/IdentityHubTokenTransferFlow.tsx +162 -0
- package/src/identity/hub/flows/token-transfer/TokenTransferScreens.tsx +256 -0
- package/src/identity/hub/identityHubReducer.ts +164 -99
- package/src/identity/hub/model/continuity.ts +94 -0
- package/src/identity/hub/model/copy.ts +35 -0
- package/src/identity/hub/model/custody.ts +54 -0
- package/src/identity/hub/model/ens.ts +49 -0
- package/src/identity/hub/model/errors.ts +140 -0
- package/src/identity/hub/model/format.ts +15 -0
- package/src/identity/hub/model/identity.ts +94 -0
- package/src/identity/hub/model/network.ts +32 -0
- package/src/identity/hub/model/transfer.ts +57 -0
- package/src/identity/hub/operatorWallets.ts +131 -0
- package/src/identity/hub/reconciliation/agentReconciliation/hook.ts +46 -0
- package/src/identity/hub/reconciliation/agentReconciliation/ownership.ts +129 -0
- package/src/identity/hub/reconciliation/agentReconciliation/run.ts +302 -0
- package/src/identity/hub/reconciliation/agentReconciliation/types.ts +17 -0
- package/src/identity/hub/reconciliation/index.ts +21 -0
- package/src/identity/hub/reconciliation/useAgentReconciliation.ts +10 -0
- package/src/identity/hub/reconciliation/walletSetup.ts +220 -0
- package/src/identity/hub/txGuard.ts +51 -0
- package/src/identity/hub/types.ts +17 -0
- package/src/identity/hub/useIdentityHubContinuity.ts +136 -0
- package/src/identity/hub/useIdentityHubController.ts +396 -0
- package/src/identity/hub/useIdentityHubSideEffects.ts +309 -0
- package/src/identity/hub/utils.ts +79 -0
- package/src/identity/identityCompat.ts +34 -0
- package/src/identity/profile/agentIcon.ts +61 -0
- package/src/identity/profile/imagePicker.ts +12 -12
- package/src/identity/registry/erc8004/abi.ts +14 -0
- package/src/identity/registry/erc8004/chains.ts +150 -0
- package/src/identity/registry/erc8004/client.ts +11 -0
- package/src/identity/registry/erc8004/discovery.ts +511 -0
- package/src/identity/registry/erc8004/metadata.ts +335 -0
- package/src/identity/registry/erc8004/ownership.ts +121 -0
- package/src/identity/registry/erc8004/preflight.ts +123 -0
- package/src/identity/registry/erc8004/transactions.ts +77 -0
- package/src/identity/registry/erc8004/types.ts +88 -0
- package/src/identity/registry/erc8004/uri.ts +59 -0
- package/src/identity/registry/erc8004/utils.ts +58 -0
- package/src/identity/registry/erc8004.ts +53 -1106
- package/src/identity/registry/fieldParsers.ts +28 -0
- package/src/identity/registry/operatorVault/bytecode.ts +98 -0
- package/src/identity/registry/operatorVault/constants.ts +38 -0
- package/src/identity/registry/operatorVault/read.ts +246 -0
- package/src/identity/registry/operatorVault/transactions.ts +81 -0
- package/src/identity/registry/operatorVault.ts +44 -0
- package/src/identity/storage/ipfs.ts +26 -24
- package/src/identity/wallet/browserWallet/gas.ts +41 -0
- package/src/identity/wallet/browserWallet/html.ts +106 -0
- package/src/identity/wallet/browserWallet/http.ts +28 -0
- package/src/identity/wallet/browserWallet/requestServer.ts +106 -0
- package/src/identity/wallet/browserWallet/requests.ts +191 -0
- package/src/identity/wallet/browserWallet/session.ts +325 -0
- package/src/identity/wallet/browserWallet/types.ts +192 -0
- package/src/identity/wallet/browserWallet/validation.ts +74 -0
- package/src/identity/wallet/browserWallet.ts +30 -393
- package/src/identity/wallet/page/constants.ts +5 -0
- package/src/identity/wallet/page/controller.ts +251 -0
- package/src/identity/wallet/page/copy.ts +340 -0
- package/src/identity/wallet/page/grainient.ts +278 -0
- package/src/identity/wallet/page/html.ts +28 -0
- package/src/identity/wallet/page/markup.ts +50 -0
- package/src/identity/wallet/page/state.ts +9 -0
- package/src/identity/wallet/page/styles/base.ts +259 -0
- package/src/identity/wallet/page/styles/components.ts +262 -0
- package/src/identity/wallet/page/styles/index.ts +5 -0
- package/src/identity/wallet/page/styles/responsive.ts +247 -0
- package/src/identity/wallet/page/types.ts +47 -0
- package/src/identity/wallet/page/view.ts +535 -0
- package/src/identity/wallet/page/walletProvider.ts +70 -0
- package/src/identity/wallet/page.tsx +38 -0
- package/src/identity/wallet/walletPurposeCompat.ts +27 -0
- package/src/mcp/manager.ts +0 -1
- package/src/models/ModelPicker.tsx +36 -30
- package/src/models/catalog.ts +5 -2
- package/src/models/huggingface.ts +9 -9
- package/src/models/llamacpp.ts +13 -13
- package/src/models/modelDisplay.ts +75 -0
- package/src/models/modelPickerOptions.ts +16 -3
- package/src/models/modelRecommendation.ts +0 -1
- package/src/providers/errors.ts +16 -0
- package/src/providers/gemini.ts +252 -39
- package/src/providers/registry.ts +2 -2
- package/src/providers/retry.ts +1 -1
- package/src/runtime/sessionMode.ts +1 -1
- package/src/runtime/systemPrompt.ts +2 -0
- package/src/runtime/toolExecution.ts +18 -22
- package/src/runtime/toolIntent.ts +0 -20
- package/src/runtime/turn.ts +0 -92
- package/src/storage/atomicWrite.ts +4 -1
- package/src/storage/config.ts +181 -5
- package/src/storage/identity.ts +9 -3
- package/src/storage/secrets.ts +2 -2
- package/src/tools/bashSafety.ts +8 -0
- package/src/tools/changeDirectoryTool.ts +1 -1
- package/src/tools/deleteFileTool.ts +4 -4
- package/src/tools/editTool.ts +4 -4
- package/src/tools/editUtils.ts +5 -5
- package/src/tools/privateContinuityEditTool.ts +4 -5
- package/src/tools/privateContinuityReadTool.ts +1 -2
- package/src/tools/registry.ts +30 -0
- package/src/tools/writeFileTool.ts +5 -5
- package/src/ui/BrandSplash.tsx +20 -85
- package/src/ui/ProgressBar.tsx +3 -5
- package/src/ui/Select.tsx +20 -8
- package/src/ui/Spinner.tsx +38 -3
- package/src/ui/Surface.tsx +2 -2
- package/src/ui/TextInput.tsx +63 -20
- package/src/ui/theme.ts +7 -34
- package/src/utils/openExternal.ts +21 -0
- package/src/utils/withRetry.ts +47 -3
- package/src/identity/hub/identityHubEffects.ts +0 -937
- package/src/identity/hub/identityHubModel.ts +0 -371
- package/src/identity/hub/screens/ContinuityDashboardScreen.tsx +0 -156
- package/src/identity/hub/screens/EditProfileFlow.tsx +0 -146
- package/src/identity/hub/screens/IdentitySummary.tsx +0 -106
- package/src/identity/hub/screens/MenuScreen.tsx +0 -117
- package/src/identity/hub/screens/RestoreFlow.tsx +0 -206
- package/src/identity/wallet/wallet-page/wallet.html +0 -1202
- /package/src/identity/hub/{screens → components}/BusyScreen.tsx +0 -0
|
@@ -0,0 +1,325 @@
|
|
|
1
|
+
import http from 'node:http'
|
|
2
|
+
import { randomUUID } from 'node:crypto'
|
|
3
|
+
import { recoverAddressFromSignature } from '../../crypto/eth.js'
|
|
4
|
+
import { normalizeWalletPayloadPurpose } from '../walletPurposeCompat.js'
|
|
5
|
+
import { walletPage } from './html.js'
|
|
6
|
+
import { readJson, respondHtml, respondJson } from './http.js'
|
|
7
|
+
import {
|
|
8
|
+
BrowserWalletError,
|
|
9
|
+
type BrowserWalletSession,
|
|
10
|
+
type BrowserWalletSignAndTransaction,
|
|
11
|
+
type BrowserWalletSignature,
|
|
12
|
+
type BrowserWalletTransaction,
|
|
13
|
+
type PendingPrompt,
|
|
14
|
+
type ReadyHandler,
|
|
15
|
+
type SignAndTransactionRequest,
|
|
16
|
+
} from './types.js'
|
|
17
|
+
import {
|
|
18
|
+
accountMismatchError,
|
|
19
|
+
chainIdHex,
|
|
20
|
+
parseAccount,
|
|
21
|
+
parseBrowserWalletErrorBody,
|
|
22
|
+
parseHex,
|
|
23
|
+
} from './validation.js'
|
|
24
|
+
|
|
25
|
+
export async function openBrowserWalletSession(args: {
|
|
26
|
+
title?: string
|
|
27
|
+
onReady?: ReadyHandler
|
|
28
|
+
} = {}): Promise<BrowserWalletSession> {
|
|
29
|
+
const title = args.title ?? 'ethagent wallet session'
|
|
30
|
+
const sseClients: Set<http.ServerResponse> = new Set()
|
|
31
|
+
let pending: PendingPrompt | null = null
|
|
32
|
+
let closed = false
|
|
33
|
+
|
|
34
|
+
const pushEvent = (kind: string, data: Record<string, unknown>): void => {
|
|
35
|
+
const payload = `event: ${kind}\ndata: ${JSON.stringify(data)}\n\n`
|
|
36
|
+
for (const res of sseClients) {
|
|
37
|
+
try { res.write(payload) } catch { }
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const failPending = (err: unknown): void => {
|
|
42
|
+
if (!pending) return
|
|
43
|
+
clearTimeout(pending.timeout)
|
|
44
|
+
pending.reject(err instanceof Error ? err : new Error(String(err)))
|
|
45
|
+
pending = null
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const handleRequest = async (req: http.IncomingMessage, res: http.ServerResponse): Promise<void> => {
|
|
49
|
+
const url = new URL(req.url ?? '/', 'http://127.0.0.1')
|
|
50
|
+
if (req.method === 'GET' && (url.pathname === '/' || url.pathname === '/ethagent')) {
|
|
51
|
+
respondHtml(res, walletPage(title, '', { kind: 'session-wait' }))
|
|
52
|
+
return
|
|
53
|
+
}
|
|
54
|
+
if (req.method === 'GET' && url.pathname === '/events') {
|
|
55
|
+
res.writeHead(200, {
|
|
56
|
+
'content-type': 'text/event-stream',
|
|
57
|
+
'cache-control': 'no-store',
|
|
58
|
+
'connection': 'keep-alive',
|
|
59
|
+
})
|
|
60
|
+
res.write(`: connected\n\n`)
|
|
61
|
+
sseClients.add(res)
|
|
62
|
+
req.on('close', () => sseClients.delete(res))
|
|
63
|
+
if (pending) {
|
|
64
|
+
res.write(`event: prompt\ndata: ${JSON.stringify({ sessionToken: pending.sessionToken, ...pending.payload })}\n\n`)
|
|
65
|
+
}
|
|
66
|
+
return
|
|
67
|
+
}
|
|
68
|
+
if (req.method === 'POST' && url.pathname === '/prepare') {
|
|
69
|
+
const body = await readJson(req)
|
|
70
|
+
if (!pending || body.sessionToken !== pending.sessionToken) {
|
|
71
|
+
respondJson(res, 409, { ok: false, error: 'no active prompt' })
|
|
72
|
+
return
|
|
73
|
+
}
|
|
74
|
+
if (!pending.prepare) {
|
|
75
|
+
respondJson(res, 400, { ok: false, error: 'this prompt does not have a prepare step' })
|
|
76
|
+
return
|
|
77
|
+
}
|
|
78
|
+
respondJson(res, 200, { ok: true, ...pending.prepare(body) })
|
|
79
|
+
return
|
|
80
|
+
}
|
|
81
|
+
if (req.method === 'POST' && url.pathname === '/prepare-transaction') {
|
|
82
|
+
const body = await readJson(req)
|
|
83
|
+
if (!pending || body.sessionToken !== pending.sessionToken) {
|
|
84
|
+
respondJson(res, 409, { ok: false, error: 'no active prompt' })
|
|
85
|
+
return
|
|
86
|
+
}
|
|
87
|
+
if (!pending.prepareTransaction) {
|
|
88
|
+
respondJson(res, 400, { ok: false, error: 'this prompt does not prepare transactions' })
|
|
89
|
+
return
|
|
90
|
+
}
|
|
91
|
+
respondJson(res, 200, { ok: true, ...(await pending.prepareTransaction(body)) })
|
|
92
|
+
return
|
|
93
|
+
}
|
|
94
|
+
if (req.method === 'POST' && url.pathname === '/complete') {
|
|
95
|
+
const body = await readJson(req)
|
|
96
|
+
if (!pending || body.sessionToken !== pending.sessionToken) {
|
|
97
|
+
respondJson(res, 409, { ok: false, error: 'no active prompt' })
|
|
98
|
+
return
|
|
99
|
+
}
|
|
100
|
+
const finished = pending
|
|
101
|
+
pending = null
|
|
102
|
+
respondJson(res, 200, { ok: true })
|
|
103
|
+
clearTimeout(finished.timeout)
|
|
104
|
+
finished.resolve(body)
|
|
105
|
+
return
|
|
106
|
+
}
|
|
107
|
+
if (req.method === 'POST' && url.pathname === '/cancel') {
|
|
108
|
+
const body = await readJson(req)
|
|
109
|
+
if (pending && body.sessionToken === pending.sessionToken) {
|
|
110
|
+
respondJson(res, 200, { ok: true })
|
|
111
|
+
failPending(new Error('wallet request was cancelled'))
|
|
112
|
+
return
|
|
113
|
+
}
|
|
114
|
+
respondJson(res, 409, { ok: false, error: 'no active prompt' })
|
|
115
|
+
return
|
|
116
|
+
}
|
|
117
|
+
if (req.method === 'POST' && url.pathname === '/error') {
|
|
118
|
+
const body = await readJson(req)
|
|
119
|
+
if (pending && body.sessionToken === pending.sessionToken) {
|
|
120
|
+
respondJson(res, 200, { ok: true })
|
|
121
|
+
failPending(new BrowserWalletError(parseBrowserWalletErrorBody(body)))
|
|
122
|
+
return
|
|
123
|
+
}
|
|
124
|
+
respondJson(res, 409, { ok: false, error: 'no active prompt' })
|
|
125
|
+
return
|
|
126
|
+
}
|
|
127
|
+
respondJson(res, 404, { ok: false, error: 'wallet session not found' })
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
const server = http.createServer((req, res) => {
|
|
131
|
+
void handleRequest(req, res).catch(err => {
|
|
132
|
+
respondJson(res, 500, { ok: false, error: (err as Error).message })
|
|
133
|
+
})
|
|
134
|
+
})
|
|
135
|
+
|
|
136
|
+
const url = await new Promise<string>((resolve, reject) => {
|
|
137
|
+
server.once('error', reject)
|
|
138
|
+
server.listen(0, '127.0.0.1', () => {
|
|
139
|
+
const addr = server.address()
|
|
140
|
+
if (!addr || typeof addr === 'string') {
|
|
141
|
+
reject(new Error('could not start wallet server'))
|
|
142
|
+
return
|
|
143
|
+
}
|
|
144
|
+
resolve(`http://localhost:${addr.port}/`)
|
|
145
|
+
})
|
|
146
|
+
})
|
|
147
|
+
args.onReady?.({ url })
|
|
148
|
+
|
|
149
|
+
const dispatch = <T>(opts: {
|
|
150
|
+
payload: Record<string, unknown>
|
|
151
|
+
prepare?: (body: Record<string, unknown>) => Record<string, unknown>
|
|
152
|
+
prepareTransaction?: (body: Record<string, unknown>) => Promise<Record<string, unknown>>
|
|
153
|
+
complete: (body: Record<string, unknown>) => T
|
|
154
|
+
timeoutMs?: number
|
|
155
|
+
}): Promise<T> => {
|
|
156
|
+
if (closed) return Promise.reject(new Error('wallet session is closed'))
|
|
157
|
+
if (pending) return Promise.reject(new Error('wallet session has another prompt in flight'))
|
|
158
|
+
const sessionToken = randomUUID()
|
|
159
|
+
const payload = normalizeWalletPayloadPurpose(opts.payload)
|
|
160
|
+
return new Promise<T>((resolve, reject) => {
|
|
161
|
+
const timeout = setTimeout(() => {
|
|
162
|
+
failPending(new Error('Wallet Request Timed Out'))
|
|
163
|
+
}, opts.timeoutMs ?? 5 * 60_000)
|
|
164
|
+
pending = {
|
|
165
|
+
sessionToken,
|
|
166
|
+
payload,
|
|
167
|
+
...(opts.prepare ? { prepare: opts.prepare } : {}),
|
|
168
|
+
...(opts.prepareTransaction ? { prepareTransaction: opts.prepareTransaction } : {}),
|
|
169
|
+
resolve: body => {
|
|
170
|
+
try { resolve(opts.complete(body)) } catch (err) { reject(err) }
|
|
171
|
+
},
|
|
172
|
+
reject,
|
|
173
|
+
timeout,
|
|
174
|
+
}
|
|
175
|
+
pushEvent('prompt', { sessionToken, ...payload })
|
|
176
|
+
})
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
return {
|
|
180
|
+
url,
|
|
181
|
+
requestSignature: async (req): Promise<BrowserWalletSignature> => {
|
|
182
|
+
if (!req.message && !req.messageForAccount) throw new Error('Wallet signature request needs a message')
|
|
183
|
+
return dispatch<BrowserWalletSignature>({
|
|
184
|
+
payload: {
|
|
185
|
+
kind: 'sign',
|
|
186
|
+
chainIdHex: chainIdHex(req.chainId),
|
|
187
|
+
message: req.message,
|
|
188
|
+
...(req.expectedAccount ? { expectedAccount: req.expectedAccount } : {}),
|
|
189
|
+
...(req.purpose ? { purpose: req.purpose } : {}),
|
|
190
|
+
...(req.flowId ? { flowId: req.flowId } : {}),
|
|
191
|
+
...(typeof req.flowStep === 'number' ? { flowStep: req.flowStep } : {}),
|
|
192
|
+
...(req.tokenChainName ? { tokenChainName: req.tokenChainName } : {}),
|
|
193
|
+
},
|
|
194
|
+
...(req.timeoutMs !== undefined ? { timeoutMs: req.timeoutMs } : {}),
|
|
195
|
+
prepare: body => {
|
|
196
|
+
const account = parseAccount(body.account)
|
|
197
|
+
if (req.expectedAccount && account.toLowerCase() !== req.expectedAccount.toLowerCase()) {
|
|
198
|
+
throw accountMismatchError(account, req.expectedAccount, req.purpose)
|
|
199
|
+
}
|
|
200
|
+
const message = req.messageForAccount ? req.messageForAccount(account) : req.message!
|
|
201
|
+
return { message }
|
|
202
|
+
},
|
|
203
|
+
complete: body => {
|
|
204
|
+
const account = parseAccount(body.account)
|
|
205
|
+
const message = typeof body.message === 'string' ? body.message : ''
|
|
206
|
+
const signature = parseHex(body.signature, 'wallet signature')
|
|
207
|
+
if (req.expectedAccount && account.toLowerCase() !== req.expectedAccount.toLowerCase()) {
|
|
208
|
+
throw accountMismatchError(account, req.expectedAccount, req.purpose)
|
|
209
|
+
}
|
|
210
|
+
const recovered = recoverAddressFromSignature(message, signature)
|
|
211
|
+
if (recovered.toLowerCase() !== account.toLowerCase()) {
|
|
212
|
+
throw new Error('Wallet signature does not match connected account')
|
|
213
|
+
}
|
|
214
|
+
return { account, message, signature }
|
|
215
|
+
},
|
|
216
|
+
})
|
|
217
|
+
},
|
|
218
|
+
sendTransaction: async (req): Promise<BrowserWalletTransaction> => dispatch<BrowserWalletTransaction>({
|
|
219
|
+
payload: {
|
|
220
|
+
kind: 'transaction',
|
|
221
|
+
chainIdHex: chainIdHex(req.chainId),
|
|
222
|
+
expectedAccount: req.expectedAccount,
|
|
223
|
+
tx: { to: req.to, data: req.data, ...(req.value ? { value: req.value } : {}) },
|
|
224
|
+
...(req.purpose ? { purpose: req.purpose } : {}),
|
|
225
|
+
...(req.flowId ? { flowId: req.flowId } : {}),
|
|
226
|
+
...(typeof req.flowStep === 'number' ? { flowStep: req.flowStep } : {}),
|
|
227
|
+
...(req.tokenChainName ? { tokenChainName: req.tokenChainName } : {}),
|
|
228
|
+
},
|
|
229
|
+
...(req.timeoutMs !== undefined ? { timeoutMs: req.timeoutMs } : {}),
|
|
230
|
+
complete: body => {
|
|
231
|
+
const account = parseAccount(body.account)
|
|
232
|
+
if (account.toLowerCase() !== req.expectedAccount.toLowerCase()) {
|
|
233
|
+
throw accountMismatchError(account, req.expectedAccount, req.purpose)
|
|
234
|
+
}
|
|
235
|
+
return { account, txHash: parseHex(body.txHash, 'transaction hash') }
|
|
236
|
+
},
|
|
237
|
+
}),
|
|
238
|
+
requestSignatureAndTransaction: async <TPrepared>(
|
|
239
|
+
req: SignAndTransactionRequest<TPrepared>,
|
|
240
|
+
): Promise<BrowserWalletSignAndTransaction<TPrepared>> => {
|
|
241
|
+
if (!req.message && !req.messageForAccount) throw new Error('Wallet signature request needs a message')
|
|
242
|
+
let prepared:
|
|
243
|
+
| {
|
|
244
|
+
account: `0x${string}`
|
|
245
|
+
message: string
|
|
246
|
+
signature: `0x${string}`
|
|
247
|
+
tx: { to: `0x${string}`; data: `0x${string}`; value?: `0x${string}` }
|
|
248
|
+
prepared: TPrepared
|
|
249
|
+
}
|
|
250
|
+
| null = null
|
|
251
|
+
return dispatch<BrowserWalletSignAndTransaction<TPrepared>>({
|
|
252
|
+
payload: {
|
|
253
|
+
kind: 'sign-transaction',
|
|
254
|
+
chainIdHex: chainIdHex(req.chainId),
|
|
255
|
+
message: req.message,
|
|
256
|
+
...(req.expectedAccount ? { expectedAccount: req.expectedAccount } : {}),
|
|
257
|
+
...(req.purpose ? { purpose: req.purpose } : {}),
|
|
258
|
+
...(req.flowId ? { flowId: req.flowId } : {}),
|
|
259
|
+
...(typeof req.flowStep === 'number' ? { flowStep: req.flowStep } : {}),
|
|
260
|
+
...(req.tokenChainName ? { tokenChainName: req.tokenChainName } : {}),
|
|
261
|
+
},
|
|
262
|
+
...(req.timeoutMs !== undefined ? { timeoutMs: req.timeoutMs } : {}),
|
|
263
|
+
prepare: body => {
|
|
264
|
+
const account = parseAccount(body.account)
|
|
265
|
+
if (req.expectedAccount && account.toLowerCase() !== req.expectedAccount.toLowerCase()) {
|
|
266
|
+
throw accountMismatchError(account, req.expectedAccount, req.purpose)
|
|
267
|
+
}
|
|
268
|
+
const message = req.messageForAccount ? req.messageForAccount(account) : req.message!
|
|
269
|
+
return { message }
|
|
270
|
+
},
|
|
271
|
+
prepareTransaction: async body => {
|
|
272
|
+
const account = parseAccount(body.account)
|
|
273
|
+
const message = typeof body.message === 'string' ? body.message : ''
|
|
274
|
+
const signature = parseHex(body.signature, 'wallet signature')
|
|
275
|
+
if (req.expectedAccount && account.toLowerCase() !== req.expectedAccount.toLowerCase()) {
|
|
276
|
+
throw accountMismatchError(account, req.expectedAccount, req.purpose)
|
|
277
|
+
}
|
|
278
|
+
const recovered = recoverAddressFromSignature(message, signature)
|
|
279
|
+
if (recovered.toLowerCase() !== account.toLowerCase()) {
|
|
280
|
+
throw new Error('Wallet signature does not match connected account')
|
|
281
|
+
}
|
|
282
|
+
const next = await req.prepareTransaction({ account, message, signature })
|
|
283
|
+
prepared = {
|
|
284
|
+
account,
|
|
285
|
+
message,
|
|
286
|
+
signature,
|
|
287
|
+
tx: {
|
|
288
|
+
to: next.to,
|
|
289
|
+
data: next.data,
|
|
290
|
+
...(next.value ? { value: next.value } : {}),
|
|
291
|
+
},
|
|
292
|
+
prepared: next.prepared,
|
|
293
|
+
}
|
|
294
|
+
return { tx: prepared.tx }
|
|
295
|
+
},
|
|
296
|
+
complete: body => {
|
|
297
|
+
if (!prepared) throw new Error('Wallet transaction was not prepared')
|
|
298
|
+
const account = parseAccount(body.account)
|
|
299
|
+
if (account.toLowerCase() !== prepared.account.toLowerCase()) {
|
|
300
|
+
throw accountMismatchError(account, prepared.account, req.purpose)
|
|
301
|
+
}
|
|
302
|
+
return {
|
|
303
|
+
account,
|
|
304
|
+
message: prepared.message,
|
|
305
|
+
signature: prepared.signature,
|
|
306
|
+
txHash: parseHex(body.txHash, 'transaction hash'),
|
|
307
|
+
prepared: prepared.prepared,
|
|
308
|
+
}
|
|
309
|
+
},
|
|
310
|
+
})
|
|
311
|
+
},
|
|
312
|
+
close: async (): Promise<void> => {
|
|
313
|
+
if (closed) return
|
|
314
|
+
closed = true
|
|
315
|
+
if (pending) failPending(new Error('wallet session closed before request completed'))
|
|
316
|
+
pushEvent('done', {})
|
|
317
|
+
await new Promise(resolve => setTimeout(resolve, 250))
|
|
318
|
+
for (const res of sseClients) {
|
|
319
|
+
try { res.end() } catch { }
|
|
320
|
+
}
|
|
321
|
+
sseClients.clear()
|
|
322
|
+
await new Promise<void>(resolve => server.close(() => resolve()))
|
|
323
|
+
},
|
|
324
|
+
}
|
|
325
|
+
}
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
import type { Address, Hex, PublicClient } from 'viem'
|
|
2
|
+
|
|
3
|
+
export type ReadyHandler = (session: BrowserWalletReady) => void
|
|
4
|
+
|
|
5
|
+
export type BrowserWalletReady = {
|
|
6
|
+
url: string
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export type WalletPurpose =
|
|
10
|
+
| 'connect-operator-wallet'
|
|
11
|
+
| 'create-agent'
|
|
12
|
+
| 'restore-owner-wallet'
|
|
13
|
+
| 'restore-operator-wallet'
|
|
14
|
+
| 'update-snapshot-owner'
|
|
15
|
+
| 'update-snapshot-operator'
|
|
16
|
+
| 'update-snapshot-connected'
|
|
17
|
+
| 'update-ens'
|
|
18
|
+
| 'clear-ens'
|
|
19
|
+
| 'update-profile-owner'
|
|
20
|
+
| 'update-profile-operator'
|
|
21
|
+
| 'update-profile-connected'
|
|
22
|
+
| 'update-ens-records'
|
|
23
|
+
| 'clear-ens-records'
|
|
24
|
+
| 'create-simple-ens-subdomain'
|
|
25
|
+
| 'set-simple-ens-records'
|
|
26
|
+
| 'create-agent-ens-subdomain'
|
|
27
|
+
| 'set-agent-ens-records'
|
|
28
|
+
| 'update-operators'
|
|
29
|
+
| 'operator-proof'
|
|
30
|
+
| 'authorize-operator-wallet-resolver'
|
|
31
|
+
| 'revoke-operator-wallet-resolver'
|
|
32
|
+
| 'reconcile-resolver-approvals'
|
|
33
|
+
| 'sync-operator-vault'
|
|
34
|
+
| 'refetch-snapshot'
|
|
35
|
+
| 'prepare-transfer-sender'
|
|
36
|
+
| 'prepare-transfer-target'
|
|
37
|
+
| 'publish-transfer-snapshot'
|
|
38
|
+
| 'deploy-agent-vault'
|
|
39
|
+
| 'deposit-agent-vault'
|
|
40
|
+
| 'unwrap-agent-vault'
|
|
41
|
+
| 'rotate-agent-uri-vault-owner'
|
|
42
|
+
| 'rotate-agent-uri-vault-operator'
|
|
43
|
+
| 'withdraw-vault'
|
|
44
|
+
| 'register-root-commit'
|
|
45
|
+
| 'register-root-tx'
|
|
46
|
+
| 'delete-ens-subdomain'
|
|
47
|
+
|
|
48
|
+
export type SignatureRequest = {
|
|
49
|
+
chainId: number
|
|
50
|
+
expectedAccount?: Address
|
|
51
|
+
message?: string
|
|
52
|
+
messageForAccount?: (account: Address) => string
|
|
53
|
+
timeoutMs?: number
|
|
54
|
+
onReady?: ReadyHandler
|
|
55
|
+
purpose?: WalletPurpose
|
|
56
|
+
flowId?: string
|
|
57
|
+
flowStep?: number
|
|
58
|
+
tokenChainName?: string
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export type TransactionRequest = {
|
|
62
|
+
chainId: number
|
|
63
|
+
expectedAccount: Address
|
|
64
|
+
to?: Address
|
|
65
|
+
data: Hex
|
|
66
|
+
value?: Hex
|
|
67
|
+
gas?: Hex
|
|
68
|
+
maxFeePerGas?: Hex
|
|
69
|
+
maxPriorityFeePerGas?: Hex
|
|
70
|
+
timeoutMs?: number
|
|
71
|
+
onReady?: ReadyHandler
|
|
72
|
+
purpose?: WalletPurpose
|
|
73
|
+
flowId?: string
|
|
74
|
+
flowStep?: number
|
|
75
|
+
tokenChainName?: string
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export type SignAndTransactionRequest<TPrepared> = {
|
|
79
|
+
chainId: number
|
|
80
|
+
expectedAccount?: Address
|
|
81
|
+
message?: string
|
|
82
|
+
messageForAccount?: (account: Address) => string
|
|
83
|
+
timeoutMs?: number
|
|
84
|
+
onReady?: ReadyHandler
|
|
85
|
+
purpose?: WalletPurpose
|
|
86
|
+
flowId?: string
|
|
87
|
+
flowStep?: number
|
|
88
|
+
tokenChainName?: string
|
|
89
|
+
prepareTransaction: (wallet: BrowserWalletSignature) => Promise<{
|
|
90
|
+
to: Address
|
|
91
|
+
data: Hex
|
|
92
|
+
value?: Hex
|
|
93
|
+
prepared: TPrepared
|
|
94
|
+
}>
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export type AccountRequest = {
|
|
98
|
+
timeoutMs?: number
|
|
99
|
+
onReady?: ReadyHandler
|
|
100
|
+
purpose?: WalletPurpose
|
|
101
|
+
flowId?: string
|
|
102
|
+
flowStep?: number
|
|
103
|
+
tokenChainName?: string
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export type BrowserWalletErrorPayload = {
|
|
107
|
+
message: string
|
|
108
|
+
code?: string
|
|
109
|
+
data?: string
|
|
110
|
+
causes?: readonly string[]
|
|
111
|
+
method?: string
|
|
112
|
+
purpose?: string
|
|
113
|
+
chainIdHex?: string
|
|
114
|
+
title?: string
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export class BrowserWalletError extends Error {
|
|
118
|
+
readonly code?: string
|
|
119
|
+
readonly data?: string
|
|
120
|
+
readonly causes: readonly string[]
|
|
121
|
+
readonly method?: string
|
|
122
|
+
readonly purpose?: string
|
|
123
|
+
readonly chainIdHex?: string
|
|
124
|
+
readonly title?: string
|
|
125
|
+
constructor(payload: BrowserWalletErrorPayload) {
|
|
126
|
+
super(payload.message || 'Wallet request failed')
|
|
127
|
+
this.name = 'BrowserWalletError'
|
|
128
|
+
if (payload.code !== undefined) this.code = payload.code
|
|
129
|
+
if (payload.data !== undefined) this.data = payload.data
|
|
130
|
+
this.causes = payload.causes ?? []
|
|
131
|
+
if (payload.method !== undefined) this.method = payload.method
|
|
132
|
+
if (payload.purpose !== undefined) this.purpose = payload.purpose
|
|
133
|
+
if (payload.chainIdHex !== undefined) this.chainIdHex = payload.chainIdHex
|
|
134
|
+
if (payload.title !== undefined) this.title = payload.title
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
export type PrepareTransactionGasFeeClient = Pick<PublicClient, 'estimateGas' | 'estimateFeesPerGas'>
|
|
139
|
+
|
|
140
|
+
export type PrepareTransactionGasFeeArgs = {
|
|
141
|
+
client: PrepareTransactionGasFeeClient
|
|
142
|
+
account: Address
|
|
143
|
+
to?: Address
|
|
144
|
+
data: Hex
|
|
145
|
+
value?: bigint
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
export type PreparedGasFee = {
|
|
149
|
+
gas: Hex
|
|
150
|
+
maxFeePerGas: Hex
|
|
151
|
+
maxPriorityFeePerGas: Hex
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
export type BrowserWalletSignature = {
|
|
155
|
+
account: Address
|
|
156
|
+
message: string
|
|
157
|
+
signature: Hex
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
export type BrowserWalletTransaction = {
|
|
161
|
+
account: Address
|
|
162
|
+
txHash: Hex
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
export type BrowserWalletSignAndTransaction<TPrepared> = BrowserWalletSignature & {
|
|
166
|
+
txHash: Hex
|
|
167
|
+
prepared: TPrepared
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
export type BrowserWalletAccount = {
|
|
171
|
+
account: Address
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
export type BrowserWalletSession = {
|
|
175
|
+
url: string
|
|
176
|
+
requestSignature: (req: SignatureRequest) => Promise<BrowserWalletSignature>
|
|
177
|
+
sendTransaction: (req: TransactionRequest) => Promise<BrowserWalletTransaction>
|
|
178
|
+
requestSignatureAndTransaction: <TPrepared>(
|
|
179
|
+
req: SignAndTransactionRequest<TPrepared>,
|
|
180
|
+
) => Promise<BrowserWalletSignAndTransaction<TPrepared>>
|
|
181
|
+
close: () => Promise<void>
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
export type PendingPrompt = {
|
|
185
|
+
sessionToken: string
|
|
186
|
+
payload: Record<string, unknown>
|
|
187
|
+
prepare?: (body: Record<string, unknown>) => Record<string, unknown>
|
|
188
|
+
prepareTransaction?: (body: Record<string, unknown>) => Promise<Record<string, unknown>>
|
|
189
|
+
resolve: (body: Record<string, unknown>) => void
|
|
190
|
+
reject: (err: unknown) => void
|
|
191
|
+
timeout: NodeJS.Timeout
|
|
192
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { getAddress, type Address, type Hex } from 'viem'
|
|
2
|
+
import { normalizeWalletPurposeValue } from '../walletPurposeCompat.js'
|
|
3
|
+
import type { BrowserWalletErrorPayload, WalletPurpose } from './types.js'
|
|
4
|
+
|
|
5
|
+
export function parseBrowserWalletErrorBody(body: Record<string, unknown>): BrowserWalletErrorPayload {
|
|
6
|
+
const message = typeof body.message === 'string' && body.message.trim() ? body.message.trim() : 'Wallet request failed'
|
|
7
|
+
const code = typeof body.code === 'string' && body.code.trim() ? body.code.trim() : undefined
|
|
8
|
+
const data = typeof body.data === 'string' && body.data.trim() ? body.data.trim() : undefined
|
|
9
|
+
const rawCauses = Array.isArray(body.causes) ? body.causes : []
|
|
10
|
+
const causes = rawCauses.filter((c): c is string => typeof c === 'string' && c.trim().length > 0).slice(0, 5)
|
|
11
|
+
const method = typeof body.method === 'string' && body.method.trim() ? body.method.trim() : undefined
|
|
12
|
+
const purpose = normalizeWalletPurposeValue(body.purpose)
|
|
13
|
+
const chainIdHex = typeof body.chainIdHex === 'string' && body.chainIdHex.trim() ? body.chainIdHex.trim() : undefined
|
|
14
|
+
const title = typeof body.title === 'string' && body.title.trim() ? body.title.trim() : undefined
|
|
15
|
+
return { message, code, data, causes, method, purpose, chainIdHex, title }
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function parseAccount(value: unknown): Address {
|
|
19
|
+
if (typeof value !== 'string') throw new Error('Wallet account is missing')
|
|
20
|
+
return getAddress(value)
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function parseHex(value: unknown, label: string): Hex {
|
|
24
|
+
if (typeof value !== 'string' || !/^0x[0-9a-fA-F]+$/.test(value)) throw new Error(`${label} is invalid`)
|
|
25
|
+
return value as Hex
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function accountMismatchError(account: Address, expectedAccount: Address, purpose?: WalletPurpose): Error {
|
|
29
|
+
if (isOwnerWalletPurpose(purpose)) {
|
|
30
|
+
return new Error(`Owner Wallet Required: connected wallet ${account} does not match owner wallet ${expectedAccount}`)
|
|
31
|
+
}
|
|
32
|
+
if (isOperatorWalletPurpose(purpose)) {
|
|
33
|
+
return new Error(`Operator Wallet Required: connected wallet ${account} does not match operator wallet ${expectedAccount}`)
|
|
34
|
+
}
|
|
35
|
+
if (purpose === 'prepare-transfer-sender' || purpose === 'publish-transfer-snapshot') {
|
|
36
|
+
return new Error(`Sender Wallet Required: connected wallet ${account} does not match sender wallet ${expectedAccount}`)
|
|
37
|
+
}
|
|
38
|
+
if (purpose === 'prepare-transfer-target') {
|
|
39
|
+
return new Error(`Receiver Wallet Required: connected wallet ${account} does not match receiver wallet ${expectedAccount}. Switch accounts in your wallet to the receiver address and retry.`)
|
|
40
|
+
}
|
|
41
|
+
return new Error(`Connected wallet ${account} does not match expected wallet ${expectedAccount}`)
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function assertSessionToken(body: Record<string, unknown>, sessionToken: string): void {
|
|
45
|
+
if (body.sessionToken !== sessionToken) throw new Error('Wallet session token is invalid')
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function chainIdHex(chainId: number): Hex {
|
|
49
|
+
return `0x${chainId.toString(16)}` as Hex
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function isOwnerWalletPurpose(purpose: WalletPurpose | undefined): boolean {
|
|
53
|
+
return purpose === 'restore-owner-wallet'
|
|
54
|
+
|| purpose === 'create-agent'
|
|
55
|
+
|| purpose === 'create-agent-ens-subdomain'
|
|
56
|
+
|| purpose === 'set-agent-ens-records'
|
|
57
|
+
|| purpose === 'update-operators'
|
|
58
|
+
|| purpose === 'update-snapshot-owner'
|
|
59
|
+
|| purpose === 'update-profile-owner'
|
|
60
|
+
|| purpose === 'update-ens'
|
|
61
|
+
|| purpose === 'clear-ens'
|
|
62
|
+
|| purpose === 'deploy-agent-vault'
|
|
63
|
+
|| purpose === 'sync-operator-vault'
|
|
64
|
+
|| purpose === 'rotate-agent-uri-vault-owner'
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function isOperatorWalletPurpose(purpose: WalletPurpose | undefined): boolean {
|
|
68
|
+
return purpose === 'restore-operator-wallet'
|
|
69
|
+
|| purpose === 'operator-proof'
|
|
70
|
+
|| purpose === 'connect-operator-wallet'
|
|
71
|
+
|| purpose === 'update-snapshot-operator'
|
|
72
|
+
|| purpose === 'update-profile-operator'
|
|
73
|
+
|| purpose === 'rotate-agent-uri-vault-operator'
|
|
74
|
+
}
|