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
|
@@ -1,393 +1,30 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
data: Hex
|
|
32
|
-
value?: Hex
|
|
33
|
-
timeoutMs?: number
|
|
34
|
-
onReady?: ReadyHandler
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
type SignAndTransactionRequest<TPrepared> = {
|
|
38
|
-
chainId: number
|
|
39
|
-
expectedAccount?: Address
|
|
40
|
-
message?: string
|
|
41
|
-
messageForAccount?: (account: Address) => string
|
|
42
|
-
timeoutMs?: number
|
|
43
|
-
onReady?: ReadyHandler
|
|
44
|
-
prepareTransaction: (wallet: BrowserWalletSignature) => Promise<{
|
|
45
|
-
to: Address
|
|
46
|
-
data: Hex
|
|
47
|
-
value?: Hex
|
|
48
|
-
prepared: TPrepared
|
|
49
|
-
}>
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
type AccountRequest = {
|
|
53
|
-
timeoutMs?: number
|
|
54
|
-
onReady?: ReadyHandler
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
export type BrowserWalletSignature = {
|
|
58
|
-
account: Address
|
|
59
|
-
message: string
|
|
60
|
-
signature: Hex
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
export type BrowserWalletTransaction = {
|
|
64
|
-
account: Address
|
|
65
|
-
txHash: Hex
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
export type BrowserWalletSignAndTransaction<TPrepared> = BrowserWalletSignature & {
|
|
69
|
-
txHash: Hex
|
|
70
|
-
prepared: TPrepared
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
export type BrowserWalletAccount = {
|
|
74
|
-
account: Address
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
export async function requestBrowserWalletAccount(args: AccountRequest = {}): Promise<BrowserWalletAccount> {
|
|
78
|
-
return await startBrowserWalletServer<BrowserWalletAccount>({
|
|
79
|
-
title: 'ethagent wallet connection',
|
|
80
|
-
timeoutMs: args.timeoutMs,
|
|
81
|
-
onReady: args.onReady,
|
|
82
|
-
payload: {
|
|
83
|
-
kind: 'account',
|
|
84
|
-
},
|
|
85
|
-
complete: body => {
|
|
86
|
-
const account = parseAccount(body.account)
|
|
87
|
-
return { account }
|
|
88
|
-
},
|
|
89
|
-
})
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
export async function requestBrowserWalletSignature(args: SignatureRequest): Promise<BrowserWalletSignature> {
|
|
93
|
-
if (!args.message && !args.messageForAccount) throw new Error('wallet signature request needs a message')
|
|
94
|
-
return await startBrowserWalletServer<BrowserWalletSignature>({
|
|
95
|
-
title: 'ethagent wallet signature',
|
|
96
|
-
timeoutMs: args.timeoutMs,
|
|
97
|
-
onReady: args.onReady,
|
|
98
|
-
payload: {
|
|
99
|
-
kind: 'sign',
|
|
100
|
-
chainIdHex: chainIdHex(args.chainId),
|
|
101
|
-
message: args.message,
|
|
102
|
-
},
|
|
103
|
-
prepare: body => {
|
|
104
|
-
const account = parseAccount(body.account)
|
|
105
|
-
if (args.expectedAccount && account.toLowerCase() !== args.expectedAccount.toLowerCase()) {
|
|
106
|
-
throw new Error(`connected wallet ${account} does not match owner ${args.expectedAccount}`)
|
|
107
|
-
}
|
|
108
|
-
const message = args.messageForAccount ? args.messageForAccount(account) : args.message!
|
|
109
|
-
return { message }
|
|
110
|
-
},
|
|
111
|
-
complete: body => {
|
|
112
|
-
const account = parseAccount(body.account)
|
|
113
|
-
const message = typeof body.message === 'string' ? body.message : ''
|
|
114
|
-
const signature = parseHex(body.signature, 'wallet signature')
|
|
115
|
-
if (args.expectedAccount && account.toLowerCase() !== args.expectedAccount.toLowerCase()) {
|
|
116
|
-
throw new Error(`connected wallet ${account} does not match owner ${args.expectedAccount}`)
|
|
117
|
-
}
|
|
118
|
-
const recovered = recoverAddressFromSignature(message, signature)
|
|
119
|
-
if (recovered.toLowerCase() !== account.toLowerCase()) {
|
|
120
|
-
throw new Error('wallet signature does not match connected account')
|
|
121
|
-
}
|
|
122
|
-
return { account, message, signature }
|
|
123
|
-
},
|
|
124
|
-
})
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
export async function sendBrowserWalletTransaction(args: TransactionRequest): Promise<BrowserWalletTransaction> {
|
|
128
|
-
return await startBrowserWalletServer<BrowserWalletTransaction>({
|
|
129
|
-
title: 'ethagent wallet transaction',
|
|
130
|
-
timeoutMs: args.timeoutMs,
|
|
131
|
-
onReady: args.onReady,
|
|
132
|
-
payload: {
|
|
133
|
-
kind: 'transaction',
|
|
134
|
-
chainIdHex: chainIdHex(args.chainId),
|
|
135
|
-
expectedAccount: args.expectedAccount,
|
|
136
|
-
tx: {
|
|
137
|
-
to: args.to,
|
|
138
|
-
data: args.data,
|
|
139
|
-
...(args.value ? { value: args.value } : {}),
|
|
140
|
-
},
|
|
141
|
-
},
|
|
142
|
-
complete: body => {
|
|
143
|
-
const account = parseAccount(body.account)
|
|
144
|
-
if (account.toLowerCase() !== args.expectedAccount.toLowerCase()) {
|
|
145
|
-
throw new Error(`connected wallet ${account} does not match owner ${args.expectedAccount}`)
|
|
146
|
-
}
|
|
147
|
-
return { account, txHash: parseHex(body.txHash, 'transaction hash') }
|
|
148
|
-
},
|
|
149
|
-
})
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
export async function requestBrowserWalletSignatureAndTransaction<TPrepared>(
|
|
153
|
-
args: SignAndTransactionRequest<TPrepared>,
|
|
154
|
-
): Promise<BrowserWalletSignAndTransaction<TPrepared>> {
|
|
155
|
-
if (!args.message && !args.messageForAccount) throw new Error('wallet signature request needs a message')
|
|
156
|
-
|
|
157
|
-
let prepared:
|
|
158
|
-
| {
|
|
159
|
-
account: Address
|
|
160
|
-
message: string
|
|
161
|
-
signature: Hex
|
|
162
|
-
tx: { to: Address; data: Hex; value?: Hex }
|
|
163
|
-
prepared: TPrepared
|
|
164
|
-
}
|
|
165
|
-
| null = null
|
|
166
|
-
|
|
167
|
-
return await startBrowserWalletServer<BrowserWalletSignAndTransaction<TPrepared>>({
|
|
168
|
-
title: 'ethagent wallet approval',
|
|
169
|
-
timeoutMs: args.timeoutMs,
|
|
170
|
-
onReady: args.onReady,
|
|
171
|
-
payload: {
|
|
172
|
-
kind: 'sign-transaction',
|
|
173
|
-
chainIdHex: chainIdHex(args.chainId),
|
|
174
|
-
message: args.message,
|
|
175
|
-
},
|
|
176
|
-
prepare: body => {
|
|
177
|
-
const account = parseAccount(body.account)
|
|
178
|
-
if (args.expectedAccount && account.toLowerCase() !== args.expectedAccount.toLowerCase()) {
|
|
179
|
-
throw new Error(`connected wallet ${account} does not match owner ${args.expectedAccount}`)
|
|
180
|
-
}
|
|
181
|
-
const message = args.messageForAccount ? args.messageForAccount(account) : args.message!
|
|
182
|
-
return { message }
|
|
183
|
-
},
|
|
184
|
-
prepareTransaction: async body => {
|
|
185
|
-
const account = parseAccount(body.account)
|
|
186
|
-
const message = typeof body.message === 'string' ? body.message : ''
|
|
187
|
-
const signature = parseHex(body.signature, 'wallet signature')
|
|
188
|
-
if (args.expectedAccount && account.toLowerCase() !== args.expectedAccount.toLowerCase()) {
|
|
189
|
-
throw new Error(`connected wallet ${account} does not match owner ${args.expectedAccount}`)
|
|
190
|
-
}
|
|
191
|
-
const recovered = recoverAddressFromSignature(message, signature)
|
|
192
|
-
if (recovered.toLowerCase() !== account.toLowerCase()) {
|
|
193
|
-
throw new Error('wallet signature does not match connected account')
|
|
194
|
-
}
|
|
195
|
-
const next = await args.prepareTransaction({ account, message, signature })
|
|
196
|
-
prepared = {
|
|
197
|
-
account,
|
|
198
|
-
message,
|
|
199
|
-
signature,
|
|
200
|
-
tx: {
|
|
201
|
-
to: next.to,
|
|
202
|
-
data: next.data,
|
|
203
|
-
...(next.value ? { value: next.value } : {}),
|
|
204
|
-
},
|
|
205
|
-
prepared: next.prepared,
|
|
206
|
-
}
|
|
207
|
-
return {
|
|
208
|
-
tx: prepared.tx,
|
|
209
|
-
}
|
|
210
|
-
},
|
|
211
|
-
complete: body => {
|
|
212
|
-
if (!prepared) throw new Error('wallet transaction was not prepared')
|
|
213
|
-
const account = parseAccount(body.account)
|
|
214
|
-
if (account.toLowerCase() !== prepared.account.toLowerCase()) {
|
|
215
|
-
throw new Error(`connected wallet ${account} does not match owner ${prepared.account}`)
|
|
216
|
-
}
|
|
217
|
-
return {
|
|
218
|
-
account,
|
|
219
|
-
message: prepared.message,
|
|
220
|
-
signature: prepared.signature,
|
|
221
|
-
txHash: parseHex(body.txHash, 'transaction hash'),
|
|
222
|
-
prepared: prepared.prepared,
|
|
223
|
-
}
|
|
224
|
-
},
|
|
225
|
-
})
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
function startBrowserWalletServer<T>(args: {
|
|
229
|
-
title: string
|
|
230
|
-
payload: Record<string, unknown>
|
|
231
|
-
timeoutMs?: number
|
|
232
|
-
onReady?: ReadyHandler
|
|
233
|
-
prepare?: (body: Record<string, unknown>) => Record<string, unknown>
|
|
234
|
-
prepareTransaction?: (body: Record<string, unknown>) => Promise<Record<string, unknown>>
|
|
235
|
-
complete: (body: Record<string, unknown>) => T
|
|
236
|
-
}): Promise<T> {
|
|
237
|
-
const sessionToken = randomUUID()
|
|
238
|
-
const timeoutMs = args.timeoutMs ?? 5 * 60_000
|
|
239
|
-
|
|
240
|
-
return new Promise<T>((resolve, reject) => {
|
|
241
|
-
let settled = false
|
|
242
|
-
const finish = (fn: () => void): void => {
|
|
243
|
-
if (settled) return
|
|
244
|
-
settled = true
|
|
245
|
-
clearTimeout(timer)
|
|
246
|
-
server.close()
|
|
247
|
-
fn()
|
|
248
|
-
}
|
|
249
|
-
const fail = (err: unknown): void => finish(() => reject(err instanceof Error ? err : new Error(String(err))))
|
|
250
|
-
|
|
251
|
-
const server = http.createServer((req, res) => {
|
|
252
|
-
void handleRequest(req, res).catch(err => {
|
|
253
|
-
respondJson(res, 500, { ok: false, error: (err as Error).message })
|
|
254
|
-
})
|
|
255
|
-
})
|
|
256
|
-
|
|
257
|
-
const timer = setTimeout(() => {
|
|
258
|
-
fail(new Error('browser wallet request timed out'))
|
|
259
|
-
}, timeoutMs)
|
|
260
|
-
|
|
261
|
-
const handleRequest = async (req: http.IncomingMessage, res: http.ServerResponse): Promise<void> => {
|
|
262
|
-
const url = new URL(req.url ?? '/', 'http://127.0.0.1')
|
|
263
|
-
if (req.method === 'GET' && (url.pathname === '/' || url.pathname === '/ethagent')) {
|
|
264
|
-
respondHtml(res, walletPage(args.title, sessionToken, args.payload))
|
|
265
|
-
return
|
|
266
|
-
}
|
|
267
|
-
if (req.method === 'POST' && (url.pathname === '/prepare' || url.pathname === '/ethagent/prepare')) {
|
|
268
|
-
const body = await readJson(req)
|
|
269
|
-
assertSessionToken(body, sessionToken)
|
|
270
|
-
if (!args.prepare) {
|
|
271
|
-
respondJson(res, 400, { ok: false, error: 'this wallet request does not have a prepare step' })
|
|
272
|
-
return
|
|
273
|
-
}
|
|
274
|
-
respondJson(res, 200, { ok: true, ...args.prepare(body) })
|
|
275
|
-
return
|
|
276
|
-
}
|
|
277
|
-
if (req.method === 'POST' && (url.pathname === '/prepare-transaction' || url.pathname === '/ethagent/prepare-transaction')) {
|
|
278
|
-
const body = await readJson(req)
|
|
279
|
-
assertSessionToken(body, sessionToken)
|
|
280
|
-
if (!args.prepareTransaction) {
|
|
281
|
-
respondJson(res, 400, { ok: false, error: 'this wallet request does not prepare transactions' })
|
|
282
|
-
return
|
|
283
|
-
}
|
|
284
|
-
respondJson(res, 200, { ok: true, ...(await args.prepareTransaction(body)) })
|
|
285
|
-
return
|
|
286
|
-
}
|
|
287
|
-
if (req.method === 'POST' && (url.pathname === '/complete' || url.pathname === '/ethagent/complete')) {
|
|
288
|
-
const body = await readJson(req)
|
|
289
|
-
assertSessionToken(body, sessionToken)
|
|
290
|
-
const result = args.complete(body)
|
|
291
|
-
respondJson(res, 200, { ok: true })
|
|
292
|
-
finish(() => resolve(result))
|
|
293
|
-
return
|
|
294
|
-
}
|
|
295
|
-
if (req.method === 'POST' && (url.pathname === '/cancel' || url.pathname === '/ethagent/cancel')) {
|
|
296
|
-
const body = await readJson(req)
|
|
297
|
-
assertSessionToken(body, sessionToken)
|
|
298
|
-
respondJson(res, 200, { ok: true })
|
|
299
|
-
fail(new Error('browser wallet request was cancelled'))
|
|
300
|
-
return
|
|
301
|
-
}
|
|
302
|
-
respondJson(res, 404, { ok: false, error: 'wallet session not found' })
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
server.once('error', fail)
|
|
306
|
-
server.listen(0, '127.0.0.1', () => {
|
|
307
|
-
const address = server.address()
|
|
308
|
-
if (!address || typeof address === 'string') {
|
|
309
|
-
fail(new Error('could not start browser wallet server'))
|
|
310
|
-
return
|
|
311
|
-
}
|
|
312
|
-
const url = `http://localhost:${address.port}/`
|
|
313
|
-
args.onReady?.({ url })
|
|
314
|
-
})
|
|
315
|
-
})
|
|
316
|
-
}
|
|
317
|
-
|
|
318
|
-
async function readJson(req: http.IncomingMessage): Promise<Record<string, unknown>> {
|
|
319
|
-
const chunks: Buffer[] = []
|
|
320
|
-
for await (const chunk of req) {
|
|
321
|
-
chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk))
|
|
322
|
-
}
|
|
323
|
-
const raw = Buffer.concat(chunks).toString('utf8')
|
|
324
|
-
const parsed = raw ? JSON.parse(raw) as unknown : {}
|
|
325
|
-
if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) throw new Error('request body must be a JSON object')
|
|
326
|
-
return parsed as Record<string, unknown>
|
|
327
|
-
}
|
|
328
|
-
|
|
329
|
-
function respondHtml(res: http.ServerResponse, body: string): void {
|
|
330
|
-
res.writeHead(200, {
|
|
331
|
-
'content-type': 'text/html; charset=utf-8',
|
|
332
|
-
'cache-control': 'no-store',
|
|
333
|
-
})
|
|
334
|
-
res.end(body)
|
|
335
|
-
}
|
|
336
|
-
|
|
337
|
-
function respondJson(res: http.ServerResponse, status: number, body: Record<string, unknown>): void {
|
|
338
|
-
res.writeHead(status, {
|
|
339
|
-
'content-type': 'application/json; charset=utf-8',
|
|
340
|
-
'cache-control': 'no-store',
|
|
341
|
-
})
|
|
342
|
-
res.end(JSON.stringify(body))
|
|
343
|
-
}
|
|
344
|
-
|
|
345
|
-
function parseAccount(value: unknown): Address {
|
|
346
|
-
if (typeof value !== 'string') throw new Error('wallet account is missing')
|
|
347
|
-
return getAddress(value)
|
|
348
|
-
}
|
|
349
|
-
|
|
350
|
-
function parseHex(value: unknown, label: string): Hex {
|
|
351
|
-
if (typeof value !== 'string' || !/^0x[0-9a-fA-F]+$/.test(value)) throw new Error(`${label} is invalid`)
|
|
352
|
-
return value as Hex
|
|
353
|
-
}
|
|
354
|
-
|
|
355
|
-
function assertSessionToken(body: Record<string, unknown>, sessionToken: string): void {
|
|
356
|
-
if (body.sessionToken !== sessionToken) throw new Error('wallet session token is invalid')
|
|
357
|
-
}
|
|
358
|
-
|
|
359
|
-
function chainIdHex(chainId: number): Hex {
|
|
360
|
-
return `0x${chainId.toString(16)}` as Hex
|
|
361
|
-
}
|
|
362
|
-
|
|
363
|
-
function loadWalletHtml(): string {
|
|
364
|
-
try {
|
|
365
|
-
return readFileSync(join(WALLET_PAGE_DIR, 'wallet.html'), 'utf8')
|
|
366
|
-
} catch (err) {
|
|
367
|
-
const sourcePath = join(dirname(fileURLToPath(import.meta.url)), '..', '..', '..', '..', 'src', 'identity', 'wallet', 'wallet-page', 'wallet.html')
|
|
368
|
-
try {
|
|
369
|
-
return readFileSync(sourcePath, 'utf8')
|
|
370
|
-
} catch {
|
|
371
|
-
throw err
|
|
372
|
-
}
|
|
373
|
-
}
|
|
374
|
-
}
|
|
375
|
-
|
|
376
|
-
export function __testWalletPage(title: string, sessionToken: string, payload: Record<string, unknown>): string {
|
|
377
|
-
return walletPage(title, sessionToken, payload)
|
|
378
|
-
}
|
|
379
|
-
|
|
380
|
-
function walletPage(title: string, sessionToken: string, payload: Record<string, unknown>): string {
|
|
381
|
-
const config = JSON.stringify({ sessionToken, ...payload }).replaceAll('<', '\\u003c')
|
|
382
|
-
const injection = `<script>window.__WALLET_CONFIG__ = ${config};</script>`
|
|
383
|
-
return WALLET_HTML
|
|
384
|
-
.replace(/<title>.*?<\/title>/, `<title>${escapeHtml(title)}</title>`)
|
|
385
|
-
.replace('<head>', `<head>\n ${injection}`)
|
|
386
|
-
}
|
|
387
|
-
function escapeHtml(value: string): string {
|
|
388
|
-
return value
|
|
389
|
-
.replaceAll('&', '&')
|
|
390
|
-
.replaceAll('<', '<')
|
|
391
|
-
.replaceAll('>', '>')
|
|
392
|
-
.replaceAll('"', '"')
|
|
393
|
-
}
|
|
1
|
+
export {
|
|
2
|
+
BrowserWalletError,
|
|
3
|
+
} from './browserWallet/types.js'
|
|
4
|
+
export type {
|
|
5
|
+
AccountRequest,
|
|
6
|
+
BrowserWalletAccount,
|
|
7
|
+
BrowserWalletErrorPayload,
|
|
8
|
+
BrowserWalletReady,
|
|
9
|
+
BrowserWalletSession,
|
|
10
|
+
BrowserWalletSignAndTransaction,
|
|
11
|
+
BrowserWalletSignature,
|
|
12
|
+
BrowserWalletTransaction,
|
|
13
|
+
PreparedGasFee,
|
|
14
|
+
PrepareTransactionGasFeeArgs,
|
|
15
|
+
PrepareTransactionGasFeeClient,
|
|
16
|
+
ReadyHandler,
|
|
17
|
+
SignAndTransactionRequest,
|
|
18
|
+
SignatureRequest,
|
|
19
|
+
TransactionRequest,
|
|
20
|
+
WalletPurpose,
|
|
21
|
+
} from './browserWallet/types.js'
|
|
22
|
+
export { prepareTransactionGasFee } from './browserWallet/gas.js'
|
|
23
|
+
export {
|
|
24
|
+
requestBrowserWalletAccount,
|
|
25
|
+
requestBrowserWalletSignature,
|
|
26
|
+
requestBrowserWalletSignatureAndTransaction,
|
|
27
|
+
sendBrowserWalletTransaction,
|
|
28
|
+
} from './browserWallet/requests.js'
|
|
29
|
+
export { openBrowserWalletSession } from './browserWallet/session.js'
|
|
30
|
+
export { __testWalletPage } from './browserWallet/html.js'
|
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
import { config } from './state.js'
|
|
2
|
+
import type { WalletConfig, WalletTx } from './types.js'
|
|
3
|
+
import { CANCEL_CLOSE_DELAY_MS, CLOSE_DELAY_MS, TX_CLOSE_DELAY_MS } from './constants.js'
|
|
4
|
+
import { shortAddr } from './copy.js'
|
|
5
|
+
import {
|
|
6
|
+
applyFlowChrome,
|
|
7
|
+
approve,
|
|
8
|
+
cancel,
|
|
9
|
+
clearLastWalletError,
|
|
10
|
+
currentState,
|
|
11
|
+
errorSlot,
|
|
12
|
+
getLastWalletError,
|
|
13
|
+
initializeViewElements,
|
|
14
|
+
serializeWalletError,
|
|
15
|
+
setState,
|
|
16
|
+
showPreparedMessage,
|
|
17
|
+
statusHint,
|
|
18
|
+
statusText,
|
|
19
|
+
} from './view.js'
|
|
20
|
+
import {
|
|
21
|
+
buildTxParams,
|
|
22
|
+
clearCurrentWalletMethod,
|
|
23
|
+
currentWalletMethod,
|
|
24
|
+
setActiveEthereum,
|
|
25
|
+
waitForEthereumProvider,
|
|
26
|
+
walletRequest,
|
|
27
|
+
} from './walletProvider.js'
|
|
28
|
+
|
|
29
|
+
let closeCountdown: ReturnType<typeof setInterval> | null = null
|
|
30
|
+
let handlersAttached = false
|
|
31
|
+
|
|
32
|
+
async function post(path: string, body: Record<string, unknown>): Promise<any> {
|
|
33
|
+
const r = await fetch(path, {
|
|
34
|
+
method: "POST",
|
|
35
|
+
headers: { "content-type": "application/json" },
|
|
36
|
+
body: JSON.stringify({ ...(body || {}), sessionToken: config.sessionToken }),
|
|
37
|
+
});
|
|
38
|
+
const d = await r.json();
|
|
39
|
+
if (!r.ok || !d.ok) throw new Error(d.error || "Wallet request failed.");
|
|
40
|
+
return d;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function showCloseCountdown(delayMs: number): void {
|
|
44
|
+
if (closeCountdown) clearInterval(closeCountdown);
|
|
45
|
+
const deadline = Date.now() + delayMs;
|
|
46
|
+
const update = () => {
|
|
47
|
+
const seconds = Math.max(1, Math.ceil((deadline - Date.now()) / 1000));
|
|
48
|
+
statusHint.textContent = "Return to your terminal. Closing in " + seconds + "s.";
|
|
49
|
+
};
|
|
50
|
+
update();
|
|
51
|
+
closeCountdown = setInterval(update, 250);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
let __sessionMode = false;
|
|
55
|
+
function closeSoon(delayMs?: number): void {
|
|
56
|
+
if (__sessionMode) {
|
|
57
|
+
if (closeCountdown) clearInterval(closeCountdown);
|
|
58
|
+
statusHint.textContent = "Waiting for next request from terminal...";
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
const ms = delayMs == null ? CLOSE_DELAY_MS : delayMs;
|
|
62
|
+
showCloseCountdown(ms);
|
|
63
|
+
setTimeout(() => {
|
|
64
|
+
if (closeCountdown) clearInterval(closeCountdown);
|
|
65
|
+
try { if (window.opener && !window.opener.closed) window.opener.focus(); } catch (_) { }
|
|
66
|
+
window.close();
|
|
67
|
+
window.open("", "_self");
|
|
68
|
+
window.close();
|
|
69
|
+
}, ms);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
async function ensureWallet(): Promise<string> {
|
|
73
|
+
setState("connecting");
|
|
74
|
+
const provider = await waitForEthereumProvider();
|
|
75
|
+
setActiveEthereum(provider);
|
|
76
|
+
const accounts = await provider.request({ method: "eth_requestAccounts" });
|
|
77
|
+
const account = accounts && accounts[0];
|
|
78
|
+
if (!account) throw new Error("No wallet account was selected.");
|
|
79
|
+
if (config.expectedAccount && account.toLowerCase() !== String(config.expectedAccount).toLowerCase()) {
|
|
80
|
+
throw new Error(
|
|
81
|
+
"Switch to " + shortAddr(config.expectedAccount) + " in your wallet, then press Enter to retry."
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
if (!config.chainIdHex) return account;
|
|
85
|
+
try {
|
|
86
|
+
await provider.request({ method: "wallet_switchEthereumChain", params: [{ chainId: config.chainIdHex }] });
|
|
87
|
+
} catch (err) {
|
|
88
|
+
const cur = await provider.request({ method: "eth_chainId" });
|
|
89
|
+
if (String(cur).toLowerCase() !== String(config.chainIdHex).toLowerCase()) throw err;
|
|
90
|
+
}
|
|
91
|
+
return account;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export async function runWalletFlow(): Promise<void> {
|
|
95
|
+
approve.disabled = true;
|
|
96
|
+
approve.hidden = true;
|
|
97
|
+
cancel.disabled = false;
|
|
98
|
+
errorSlot.innerHTML = "";
|
|
99
|
+
clearLastWalletError();
|
|
100
|
+
try {
|
|
101
|
+
const account = await ensureWallet();
|
|
102
|
+
if (config.kind === "account") {
|
|
103
|
+
setState("submitting");
|
|
104
|
+
await post("/complete", { account });
|
|
105
|
+
setState("done");
|
|
106
|
+
closeSoon();
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
if (config.kind === "sign") {
|
|
110
|
+
const prepared = config.message ? { message: config.message } : await post("/prepare", { account });
|
|
111
|
+
showPreparedMessage(prepared.message);
|
|
112
|
+
setState("approve");
|
|
113
|
+
const signature = await walletRequest("personal_sign", [prepared.message, account]);
|
|
114
|
+
setState("submitting");
|
|
115
|
+
await post("/complete", { account, message: prepared.message, signature });
|
|
116
|
+
setState("done");
|
|
117
|
+
closeSoon();
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
if (config.kind === "sign-transaction") {
|
|
121
|
+
const prepared = config.message ? { message: config.message } : await post("/prepare", { account });
|
|
122
|
+
showPreparedMessage(prepared.message);
|
|
123
|
+
setState("approve-sign", { account });
|
|
124
|
+
const signature = await walletRequest("personal_sign", [prepared.message, account]);
|
|
125
|
+
setState("preparing-transaction", { account });
|
|
126
|
+
const txPayload = await post("/prepare-transaction", { account, message: prepared.message, signature });
|
|
127
|
+
setState("approve-transaction", { account, tx: txPayload.tx });
|
|
128
|
+
const tx = txPayload.tx || {};
|
|
129
|
+
const txHash = await walletRequest("eth_sendTransaction", [buildTxParams(account, tx)]);
|
|
130
|
+
setState("submitting", { account, tx, txHash });
|
|
131
|
+
await post("/complete", { account, txHash });
|
|
132
|
+
setState("done", { account, tx, txHash });
|
|
133
|
+
closeSoon(TX_CLOSE_DELAY_MS);
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
if (config.kind === "transaction") {
|
|
137
|
+
setState("approve", { account, tx: config.tx });
|
|
138
|
+
const txHash = await walletRequest("eth_sendTransaction", [buildTxParams(account, config.tx as WalletTx)]);
|
|
139
|
+
setState("submitting", { account, tx: config.tx, txHash });
|
|
140
|
+
await post("/complete", { account, txHash });
|
|
141
|
+
setState("done", { account, tx: config.tx, txHash });
|
|
142
|
+
closeSoon(TX_CLOSE_DELAY_MS);
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
throw new Error("Unknown wallet request type.");
|
|
146
|
+
} catch (err) {
|
|
147
|
+
approve.disabled = false;
|
|
148
|
+
approve.hidden = false;
|
|
149
|
+
cancel.disabled = false;
|
|
150
|
+
const serialized = serializeWalletError(err, currentWalletMethod);
|
|
151
|
+
clearCurrentWalletMethod();
|
|
152
|
+
setState("error", serialized as Record<string, unknown>);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
export async function cancelFlow(): Promise<void> {
|
|
157
|
+
approve.disabled = true;
|
|
158
|
+
cancel.disabled = true;
|
|
159
|
+
const lastWalletError = getLastWalletError();
|
|
160
|
+
if (lastWalletError) {
|
|
161
|
+
await post("/error", lastWalletError as Record<string, unknown>).catch(() => { });
|
|
162
|
+
} else {
|
|
163
|
+
await post("/cancel", {}).catch(() => { });
|
|
164
|
+
}
|
|
165
|
+
setState("done");
|
|
166
|
+
statusText.textContent = lastWalletError ? "Aborted · returning" : "Cancelled · returning";
|
|
167
|
+
closeSoon(CANCEL_CLOSE_DELAY_MS);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
function escapeAllowed(): boolean {
|
|
171
|
+
return currentState !== "submitting" && currentState !== "done";
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
export function startSessionMode(): void {
|
|
175
|
+
__sessionMode = true;
|
|
176
|
+
setState("connecting");
|
|
177
|
+
statusText.textContent = "Waiting for terminal...";
|
|
178
|
+
const reconnectHint = document.getElementById("reconnect-hint") as HTMLElement | null;
|
|
179
|
+
const showReconnecting = (visible: boolean) => {
|
|
180
|
+
if (!reconnectHint) return;
|
|
181
|
+
reconnectHint.hidden = !visible;
|
|
182
|
+
};
|
|
183
|
+
const events = new EventSource("/events");
|
|
184
|
+
const clearReconnect = () => showReconnecting(false);
|
|
185
|
+
events.addEventListener("prompt", (ev: MessageEvent) => {
|
|
186
|
+
clearReconnect();
|
|
187
|
+
try {
|
|
188
|
+
const next = JSON.parse(ev.data);
|
|
189
|
+
for (const k of Object.keys(config)) delete (config as any)[k];
|
|
190
|
+
Object.assign(config, next);
|
|
191
|
+
applyFlowChrome();
|
|
192
|
+
setTimeout(runWalletFlow, 50);
|
|
193
|
+
} catch (err) {
|
|
194
|
+
setState("error", { message: (err && (err as Error).message) || String(err) });
|
|
195
|
+
}
|
|
196
|
+
});
|
|
197
|
+
events.addEventListener("done", () => {
|
|
198
|
+
clearReconnect();
|
|
199
|
+
events.close();
|
|
200
|
+
__sessionMode = false;
|
|
201
|
+
setState("done");
|
|
202
|
+
statusText.textContent = "All set · Returning";
|
|
203
|
+
closeSoon();
|
|
204
|
+
});
|
|
205
|
+
events.onerror = () => {
|
|
206
|
+
if (events.readyState === EventSource.CLOSED) {
|
|
207
|
+
clearReconnect();
|
|
208
|
+
return;
|
|
209
|
+
}
|
|
210
|
+
showReconnecting(true);
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
function attachWalletHandlers(): void {
|
|
215
|
+
if (handlersAttached) return
|
|
216
|
+
handlersAttached = true
|
|
217
|
+
approve.onclick = runWalletFlow
|
|
218
|
+
cancel.onclick = cancelFlow
|
|
219
|
+
window.addEventListener('keydown', (e) => {
|
|
220
|
+
if (e.key === 'Escape') {
|
|
221
|
+
if (!escapeAllowed()) { e.preventDefault(); return }
|
|
222
|
+
e.preventDefault()
|
|
223
|
+
cancelFlow()
|
|
224
|
+
} else if (e.key === 'Enter') {
|
|
225
|
+
if (!approve.hidden && !approve.disabled) { e.preventDefault(); runWalletFlow(); return }
|
|
226
|
+
}
|
|
227
|
+
})
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
export function bootWallet(): void {
|
|
231
|
+
initializeViewElements()
|
|
232
|
+
attachWalletHandlers()
|
|
233
|
+
applyFlowChrome()
|
|
234
|
+
if (!window.__WALLET_PREVIEW__) {
|
|
235
|
+
if (document.readyState === 'loading') {
|
|
236
|
+
document.addEventListener('DOMContentLoaded', bootWallet, { once: true })
|
|
237
|
+
} else {
|
|
238
|
+
if (config && config.kind === 'session-wait') {
|
|
239
|
+
startSessionMode()
|
|
240
|
+
} else {
|
|
241
|
+
runWalletFlow()
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
} else {
|
|
245
|
+
window.__walletPreview = {
|
|
246
|
+
setState,
|
|
247
|
+
setConfig: (c: Partial<WalletConfig>) => { Object.assign(config, c); applyFlowChrome() },
|
|
248
|
+
}
|
|
249
|
+
runWalletFlow()
|
|
250
|
+
}
|
|
251
|
+
}
|