ethagent 1.1.2 → 2.0.1
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 +124 -32
- package/package.json +8 -3
- 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,335 @@
|
|
|
1
|
+
import { getAddress, isAddress, type Address } from 'viem'
|
|
2
|
+
import type { TransferSnapshotMetadata } from '../../../storage/config.js'
|
|
3
|
+
import type { WalletContinuityRestoreAccessKey } from '../../continuity/envelope.js'
|
|
4
|
+
import { readOwnerAddressField } from '../../identityCompat.js'
|
|
5
|
+
import { arrayField, numberField, objectField, stringField } from '../fieldParsers.js'
|
|
6
|
+
import type {
|
|
7
|
+
ApprovedOperatorWalletRecord,
|
|
8
|
+
EthagentBackupPointer,
|
|
9
|
+
EthagentOperatorsPointer,
|
|
10
|
+
EthagentPublicDiscoveryPointer,
|
|
11
|
+
EthagentRegistrationPointer,
|
|
12
|
+
} from './types.js'
|
|
13
|
+
|
|
14
|
+
export function parseEthagentBackupPointer(registration: Record<string, unknown> | null): EthagentBackupPointer | null {
|
|
15
|
+
if (!registration) return null
|
|
16
|
+
const ext = objectField(registration, 'x-ethagent') ?? objectField(registration, 'ethagent')
|
|
17
|
+
const backup = ext ? objectField(ext, 'backup') : null
|
|
18
|
+
const cid = backup ? stringField(backup, 'cid') : undefined
|
|
19
|
+
if (!cid) return null
|
|
20
|
+
const agentAddress = stringField(ext, 'agentAddress')
|
|
21
|
+
const transferSnapshot = backup ? parseTransferSnapshotMetadata(objectField(backup, 'transferSnapshot')) : undefined
|
|
22
|
+
const pastBackupsArray = arrayField(backup ?? {}, 'pastBackups')
|
|
23
|
+
const pastBackups = pastBackupsArray?.flatMap(item => {
|
|
24
|
+
if (!item || typeof item !== 'object') return []
|
|
25
|
+
const obj = item as Record<string, unknown>
|
|
26
|
+
const itemCid = stringField(obj, 'cid')
|
|
27
|
+
if (!itemCid) return []
|
|
28
|
+
return [{
|
|
29
|
+
cid: itemCid,
|
|
30
|
+
...(stringField(obj, 'createdAt') ? { createdAt: stringField(obj, 'createdAt') } : {}),
|
|
31
|
+
}]
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
return {
|
|
35
|
+
cid,
|
|
36
|
+
envelopeVersion: backup ? stringField(backup, 'envelopeVersion') : undefined,
|
|
37
|
+
createdAt: backup ? stringField(backup, 'createdAt') : undefined,
|
|
38
|
+
...(agentAddress && isAddress(agentAddress) ? { agentAddress: getAddress(agentAddress) } : {}),
|
|
39
|
+
...(transferSnapshot ? { transferSnapshot } : {}),
|
|
40
|
+
...(pastBackups && pastBackups.length > 0 ? { pastBackups } : {}),
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function parseTransferSnapshotMetadata(input: Record<string, unknown> | null): TransferSnapshotMetadata | undefined {
|
|
45
|
+
if (!input) return undefined
|
|
46
|
+
const kind = stringField(input, 'kind')
|
|
47
|
+
const senderRaw = stringField(input, 'senderAddress')
|
|
48
|
+
const receiverRaw = stringField(input, 'receiverAddress')
|
|
49
|
+
const slotCount = numberField(input, 'slotCount')
|
|
50
|
+
if (kind !== 'dual-wallet' || !senderRaw || !receiverRaw || !slotCount) return undefined
|
|
51
|
+
if (!isAddress(senderRaw, { strict: false }) || !isAddress(receiverRaw, { strict: false })) return undefined
|
|
52
|
+
return {
|
|
53
|
+
kind: 'dual-wallet',
|
|
54
|
+
senderAddress: getAddress(senderRaw),
|
|
55
|
+
receiverAddress: getAddress(receiverRaw),
|
|
56
|
+
...(stringField(input, 'receiverHandle') ? { receiverHandle: stringField(input, 'receiverHandle') } : {}),
|
|
57
|
+
slotCount,
|
|
58
|
+
...(stringField(input, 'createdAt') ? { createdAt: stringField(input, 'createdAt') } : {}),
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export function parseEthagentPublicDiscoveryPointer(registration: Record<string, unknown> | null): EthagentPublicDiscoveryPointer | null {
|
|
63
|
+
if (!registration) return null
|
|
64
|
+
const ext = objectField(registration, 'x-ethagent') ?? objectField(registration, 'ethagent')
|
|
65
|
+
const publicSkills = ext ? objectField(ext, 'publicSkills') : null
|
|
66
|
+
const agentCard = ext ? objectField(ext, 'agentCard') : null
|
|
67
|
+
const skillsCid = publicSkills ? stringField(publicSkills, 'cid') : undefined
|
|
68
|
+
const agentCardCid = agentCard ? stringField(agentCard, 'cid') : undefined
|
|
69
|
+
const updatedAt = (publicSkills ? stringField(publicSkills, 'updatedAt') : undefined)
|
|
70
|
+
?? (agentCard ? stringField(agentCard, 'updatedAt') : undefined)
|
|
71
|
+
if (!skillsCid && !agentCardCid) return null
|
|
72
|
+
return {
|
|
73
|
+
...(skillsCid ? { skillsCid } : {}),
|
|
74
|
+
...(agentCardCid ? { agentCardCid } : {}),
|
|
75
|
+
...(updatedAt ? { updatedAt } : {}),
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export function parseEthagentOperatorsPointer(registration: Record<string, unknown> | null): EthagentOperatorsPointer | null {
|
|
80
|
+
if (!registration) return null
|
|
81
|
+
const ext = objectField(registration, 'x-ethagent') ?? objectField(registration, 'ethagent')
|
|
82
|
+
const operators = ext ? objectField(ext, 'operators') : null
|
|
83
|
+
if (!operators) return null
|
|
84
|
+
const approvedOperatorWallets = parseOperatorRecords(arrayField(operators, 'approvedOperatorWallets') ?? [])
|
|
85
|
+
const activeRaw = stringField(operators, 'activeOperatorAddress')
|
|
86
|
+
const ownerRaw = readOwnerAddressField(operators)
|
|
87
|
+
const ensName = stringField(operators, 'ensName')
|
|
88
|
+
const restoreAccessEpoch = numberField(operators, 'restoreAccessEpoch')
|
|
89
|
+
const ownerRestoreAccessKey = parseRestoreAccessKey(objectField(operators, 'ownerRestoreAccessKey'))
|
|
90
|
+
const activeOperatorAddress = activeRaw && isAddress(activeRaw, { strict: false }) ? getAddress(activeRaw) : undefined
|
|
91
|
+
const ownerAddress = ownerRaw && isAddress(ownerRaw, { strict: false }) ? getAddress(ownerRaw) : undefined
|
|
92
|
+
if (approvedOperatorWallets.length === 0 && !activeOperatorAddress && !ownerAddress && !ensName && !ownerRestoreAccessKey && !restoreAccessEpoch) return null
|
|
93
|
+
return {
|
|
94
|
+
approvedOperatorWallets,
|
|
95
|
+
...(activeOperatorAddress ? { activeOperatorAddress } : {}),
|
|
96
|
+
...(ownerAddress ? { ownerAddress } : {}),
|
|
97
|
+
...(ensName ? { ensName } : {}),
|
|
98
|
+
...(restoreAccessEpoch && Number.isSafeInteger(restoreAccessEpoch) && restoreAccessEpoch > 0 ? { restoreAccessEpoch } : {}),
|
|
99
|
+
...(ownerRestoreAccessKey ? { ownerRestoreAccessKey } : {}),
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function parseOperatorRecords(items: unknown[]): ApprovedOperatorWalletRecord[] {
|
|
104
|
+
const out: ApprovedOperatorWalletRecord[] = []
|
|
105
|
+
const seen = new Set<string>()
|
|
106
|
+
for (const item of items) {
|
|
107
|
+
const record = parseOperatorRecord(item)
|
|
108
|
+
if (!record) continue
|
|
109
|
+
const key = record.address.toLowerCase()
|
|
110
|
+
if (seen.has(key)) continue
|
|
111
|
+
seen.add(key)
|
|
112
|
+
out.push(record)
|
|
113
|
+
}
|
|
114
|
+
return out
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function parseOperatorRecord(item: unknown): ApprovedOperatorWalletRecord | null {
|
|
118
|
+
if (typeof item === 'string') {
|
|
119
|
+
const trimmed = item.trim()
|
|
120
|
+
if (!isAddress(trimmed, { strict: false })) return null
|
|
121
|
+
return { address: getAddress(trimmed) }
|
|
122
|
+
}
|
|
123
|
+
if (!item || typeof item !== 'object' || Array.isArray(item)) return null
|
|
124
|
+
const obj = item as Record<string, unknown>
|
|
125
|
+
const rawAddress = stringField(obj, 'address')
|
|
126
|
+
if (!rawAddress || !isAddress(rawAddress, { strict: false })) return null
|
|
127
|
+
const challenge = stringField(obj, 'challenge')
|
|
128
|
+
const verifiedAt = stringField(obj, 'verifiedAt')
|
|
129
|
+
const restoreAccessKey = parseRestoreAccessKey(objectField(obj, 'restoreAccessKey'))
|
|
130
|
+
return {
|
|
131
|
+
address: getAddress(rawAddress),
|
|
132
|
+
...(challenge ? { challenge } : {}),
|
|
133
|
+
...(verifiedAt ? { verifiedAt } : {}),
|
|
134
|
+
...(restoreAccessKey ? { restoreAccessKey } : {}),
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function parseRestoreAccessKey(obj: Record<string, unknown> | null): WalletContinuityRestoreAccessKey | undefined {
|
|
139
|
+
if (!obj) return undefined
|
|
140
|
+
const address = stringField(obj, 'address')
|
|
141
|
+
const challenge = stringField(obj, 'challenge')
|
|
142
|
+
const salt = stringField(obj, 'salt')
|
|
143
|
+
const kemPublicKey = stringField(obj, 'kemPublicKey')
|
|
144
|
+
const createdAt = stringField(obj, 'createdAt')
|
|
145
|
+
if (!address || !isAddress(address, { strict: false }) || !challenge || !salt || !kemPublicKey) return undefined
|
|
146
|
+
return {
|
|
147
|
+
address: getAddress(address),
|
|
148
|
+
challenge,
|
|
149
|
+
salt,
|
|
150
|
+
kemPublicKey,
|
|
151
|
+
...(createdAt ? { createdAt } : {}),
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function serializeRestoreAccessKey(key: WalletContinuityRestoreAccessKey): Record<string, unknown> {
|
|
156
|
+
return {
|
|
157
|
+
address: getAddress(key.address),
|
|
158
|
+
challenge: key.challenge,
|
|
159
|
+
salt: key.salt,
|
|
160
|
+
kemPublicKey: key.kemPublicKey,
|
|
161
|
+
...(key.createdAt ? { createdAt: key.createdAt } : {}),
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
function serializeOperatorsPointer(pointer: EthagentOperatorsPointer): Record<string, unknown> {
|
|
166
|
+
return {
|
|
167
|
+
approvedOperatorWallets: pointer.approvedOperatorWallets.map(record => ({
|
|
168
|
+
address: getAddress(record.address),
|
|
169
|
+
...(record.challenge ? { challenge: record.challenge } : {}),
|
|
170
|
+
...(record.verifiedAt ? { verifiedAt: record.verifiedAt } : {}),
|
|
171
|
+
...(record.restoreAccessKey ? { restoreAccessKey: serializeRestoreAccessKey(record.restoreAccessKey) } : {}),
|
|
172
|
+
})),
|
|
173
|
+
...(pointer.activeOperatorAddress ? { activeOperatorAddress: getAddress(pointer.activeOperatorAddress) } : {}),
|
|
174
|
+
...(pointer.ownerAddress ? { ownerAddress: getAddress(pointer.ownerAddress) } : {}),
|
|
175
|
+
...(pointer.ensName ? { ensName: pointer.ensName } : {}),
|
|
176
|
+
...(pointer.restoreAccessEpoch ? { restoreAccessEpoch: pointer.restoreAccessEpoch } : {}),
|
|
177
|
+
...(pointer.ownerRestoreAccessKey ? { ownerRestoreAccessKey: serializeRestoreAccessKey(pointer.ownerRestoreAccessKey) } : {}),
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
export function withEthagentBackupPointer(
|
|
182
|
+
registration: Record<string, unknown> | null,
|
|
183
|
+
backup: EthagentBackupPointer,
|
|
184
|
+
publicDiscovery?: EthagentPublicDiscoveryPointer,
|
|
185
|
+
registrationPointer?: EthagentRegistrationPointer,
|
|
186
|
+
ownerAddress?: Address,
|
|
187
|
+
): Record<string, unknown> {
|
|
188
|
+
const inferredOwnerAddress = ownerAddress ?? backup.agentAddress
|
|
189
|
+
return withEthagentPointers(registration, {
|
|
190
|
+
backup,
|
|
191
|
+
publicDiscovery,
|
|
192
|
+
registration: registrationPointer,
|
|
193
|
+
...(inferredOwnerAddress ? { ownerAddress: inferredOwnerAddress } : {}),
|
|
194
|
+
})
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
export function withEthagentPointers(
|
|
198
|
+
registration: Record<string, unknown> | null,
|
|
199
|
+
pointers: {
|
|
200
|
+
backup?: EthagentBackupPointer
|
|
201
|
+
publicDiscovery?: EthagentPublicDiscoveryPointer
|
|
202
|
+
registration?: EthagentRegistrationPointer
|
|
203
|
+
ensName?: string
|
|
204
|
+
operators?: EthagentOperatorsPointer
|
|
205
|
+
ownerAddress?: Address
|
|
206
|
+
},
|
|
207
|
+
): Record<string, unknown> {
|
|
208
|
+
const next: Record<string, unknown> = registration ? { ...registration } : {}
|
|
209
|
+
const prior = objectField(next, 'x-ethagent') ?? {}
|
|
210
|
+
const { backup, publicDiscovery, registration: registrationPointer, operators } = pointers
|
|
211
|
+
const updatedAt = publicDiscovery?.updatedAt ?? backup?.createdAt
|
|
212
|
+
const ownerAddress = pointers.ownerAddress
|
|
213
|
+
? getAddress(pointers.ownerAddress)
|
|
214
|
+
: backup?.agentAddress
|
|
215
|
+
? getAddress(backup.agentAddress)
|
|
216
|
+
: undefined
|
|
217
|
+
const priorX402 = objectField(prior, 'x402') ?? {}
|
|
218
|
+
const ext: Record<string, unknown> = {
|
|
219
|
+
...prior,
|
|
220
|
+
version: 1,
|
|
221
|
+
...(ownerAddress ? {
|
|
222
|
+
agentAddress: ownerAddress,
|
|
223
|
+
x402: {
|
|
224
|
+
...priorX402,
|
|
225
|
+
walletAddress: ownerAddress,
|
|
226
|
+
},
|
|
227
|
+
} : {}),
|
|
228
|
+
...(backup ? {
|
|
229
|
+
backup: {
|
|
230
|
+
cid: backup.cid,
|
|
231
|
+
...(backup.envelopeVersion ? { envelopeVersion: backup.envelopeVersion } : {}),
|
|
232
|
+
...(backup.createdAt ? { createdAt: backup.createdAt } : {}),
|
|
233
|
+
...(backup.transferSnapshot ? { transferSnapshot: serializeTransferSnapshotMetadata(backup.transferSnapshot) } : {}),
|
|
234
|
+
},
|
|
235
|
+
} : {}),
|
|
236
|
+
...(publicDiscovery?.skillsCid ? {
|
|
237
|
+
publicSkills: {
|
|
238
|
+
cid: publicDiscovery.skillsCid,
|
|
239
|
+
format: 'application/json',
|
|
240
|
+
...(updatedAt ? { updatedAt } : {}),
|
|
241
|
+
},
|
|
242
|
+
} : {}),
|
|
243
|
+
...(publicDiscovery?.agentCardCid ? {
|
|
244
|
+
agentCard: {
|
|
245
|
+
cid: publicDiscovery.agentCardCid,
|
|
246
|
+
format: 'application/json',
|
|
247
|
+
...(updatedAt ? { updatedAt } : {}),
|
|
248
|
+
},
|
|
249
|
+
} : {}),
|
|
250
|
+
...(operators ? { operators: serializeOperatorsPointer(operators) } : {}),
|
|
251
|
+
}
|
|
252
|
+
delete ext.transfer
|
|
253
|
+
delete ext.handoff
|
|
254
|
+
next['x-ethagent'] = ext
|
|
255
|
+
if (publicDiscovery) {
|
|
256
|
+
next.services = withPublicDiscoveryServices(next.services, publicDiscovery, pointers.ensName)
|
|
257
|
+
}
|
|
258
|
+
if (registrationPointer && registrationPointer.agentId !== undefined) {
|
|
259
|
+
next.registrations = withRegistrationsArray(next.registrations, registrationPointer)
|
|
260
|
+
}
|
|
261
|
+
return next
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
function serializeTransferSnapshotMetadata(metadata: TransferSnapshotMetadata): Record<string, unknown> {
|
|
265
|
+
return {
|
|
266
|
+
kind: 'dual-wallet',
|
|
267
|
+
senderAddress: getAddress(metadata.senderAddress),
|
|
268
|
+
receiverAddress: getAddress(metadata.receiverAddress),
|
|
269
|
+
...(metadata.receiverHandle ? { receiverHandle: metadata.receiverHandle } : {}),
|
|
270
|
+
slotCount: metadata.slotCount,
|
|
271
|
+
...(metadata.createdAt ? { createdAt: metadata.createdAt } : {}),
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
function withPublicDiscoveryServices(input: unknown, publicDiscovery: EthagentPublicDiscoveryPointer, ensName?: string): unknown[] {
|
|
276
|
+
const prior = Array.isArray(input) ? input.filter(item => item && typeof item === 'object') : []
|
|
277
|
+
const services = prior.filter(item => !isEthagentManagedService(item)) as unknown[]
|
|
278
|
+
if (publicDiscovery.agentCardCid) {
|
|
279
|
+
const endpoint = `ipfs://${publicDiscovery.agentCardCid}`
|
|
280
|
+
pushUniqueService(services, {
|
|
281
|
+
type: 'a2a',
|
|
282
|
+
name: 'agent-card',
|
|
283
|
+
endpoint,
|
|
284
|
+
url: endpoint,
|
|
285
|
+
})
|
|
286
|
+
}
|
|
287
|
+
if (publicDiscovery.skillsCid) {
|
|
288
|
+
const endpoint = `ipfs://${publicDiscovery.skillsCid}`
|
|
289
|
+
pushUniqueService(services, {
|
|
290
|
+
type: 'A2A-skills',
|
|
291
|
+
name: 'public-skills',
|
|
292
|
+
endpoint,
|
|
293
|
+
url: endpoint,
|
|
294
|
+
})
|
|
295
|
+
}
|
|
296
|
+
if (ensName) {
|
|
297
|
+
services.push({ name: 'ENS', endpoint: ensName, version: 'v1' })
|
|
298
|
+
}
|
|
299
|
+
return services
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
function pushUniqueService(services: unknown[], service: Record<string, string>): void {
|
|
303
|
+
const duplicate = services.some(item => {
|
|
304
|
+
if (!item || typeof item !== 'object' || Array.isArray(item)) return false
|
|
305
|
+
const obj = item as Record<string, unknown>
|
|
306
|
+
return obj.type === service.type && obj.endpoint === service.endpoint
|
|
307
|
+
})
|
|
308
|
+
if (!duplicate) services.push(service)
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
function isEthagentManagedService(item: unknown): boolean {
|
|
312
|
+
if (!item || typeof item !== 'object' || Array.isArray(item)) return false
|
|
313
|
+
const obj = item as Record<string, unknown>
|
|
314
|
+
const type = obj.type
|
|
315
|
+
const name = obj.name
|
|
316
|
+
if (name === 'ENS') return true
|
|
317
|
+
if (type === 'a2a' && (name === undefined || name === 'agent-card')) return true
|
|
318
|
+
return (type === 'A2A-skills' || type === 'ipfs') && name === 'public-skills'
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
function withRegistrationsArray(_input: unknown, registration: EthagentRegistrationPointer): unknown[] {
|
|
322
|
+
return [{
|
|
323
|
+
agentId: registrationAgentIdValue(registration.agentId),
|
|
324
|
+
agentRegistry: `eip155:${registration.chainId}:${registration.identityRegistryAddress}`,
|
|
325
|
+
}]
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
function registrationAgentIdValue(agentId: string | number | undefined): string | number | undefined {
|
|
329
|
+
if (typeof agentId === 'number') return agentId
|
|
330
|
+
if (typeof agentId !== 'string') return agentId
|
|
331
|
+
const trimmed = agentId.trim()
|
|
332
|
+
if (!/^(0|[1-9]\d*)$/.test(trimmed)) return agentId
|
|
333
|
+
const numeric = Number(trimmed)
|
|
334
|
+
return Number.isSafeInteger(numeric) ? numeric : agentId
|
|
335
|
+
}
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import { getAddress, type Address, type PublicClient } from 'viem'
|
|
2
|
+
import { OPERATOR_VAULT_ABI } from '../operatorVault.js'
|
|
3
|
+
import { ERC8004_ABI } from './abi.js'
|
|
4
|
+
import { createErc8004PublicClient } from './client.js'
|
|
5
|
+
import type { Erc8004RegistryConfig } from './types.js'
|
|
6
|
+
|
|
7
|
+
type TokenOwnerReadClient = Pick<PublicClient, 'readContract'>
|
|
8
|
+
|
|
9
|
+
const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000'
|
|
10
|
+
|
|
11
|
+
async function readErc8004TokenOwner(args: Erc8004RegistryConfig & {
|
|
12
|
+
agentId: bigint
|
|
13
|
+
publicClient?: TokenOwnerReadClient
|
|
14
|
+
}): Promise<Address> {
|
|
15
|
+
const publicClient = args.publicClient ?? createErc8004PublicClient(args)
|
|
16
|
+
const owner = await publicClient.readContract({
|
|
17
|
+
address: args.identityRegistryAddress,
|
|
18
|
+
abi: ERC8004_ABI,
|
|
19
|
+
functionName: 'ownerOf',
|
|
20
|
+
args: [args.agentId],
|
|
21
|
+
}) as Address
|
|
22
|
+
return getAddress(owner)
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export type Erc8004TokenOwnerValidation =
|
|
26
|
+
| { ok: true; ownerAddress: Address }
|
|
27
|
+
| { ok: false; reason: 'token-owner-mismatch' | 'token-owner-lookup-failed'; ownerAddress?: Address; detail: string }
|
|
28
|
+
|
|
29
|
+
export async function validateErc8004TokenOwner(args: Erc8004RegistryConfig & {
|
|
30
|
+
agentId: bigint
|
|
31
|
+
expectedOwner: Address
|
|
32
|
+
publicClient?: TokenOwnerReadClient
|
|
33
|
+
operatorVaults?: Readonly<Record<string, string>>
|
|
34
|
+
}): Promise<Erc8004TokenOwnerValidation> {
|
|
35
|
+
let owner: Address
|
|
36
|
+
try {
|
|
37
|
+
owner = await readErc8004TokenOwner(args)
|
|
38
|
+
} catch (err: unknown) {
|
|
39
|
+
return {
|
|
40
|
+
ok: false,
|
|
41
|
+
reason: 'token-owner-lookup-failed',
|
|
42
|
+
detail: err instanceof Error ? err.message : String(err),
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
const expectedOwner = getAddress(args.expectedOwner)
|
|
46
|
+
if (owner.toLowerCase() === expectedOwner.toLowerCase()) {
|
|
47
|
+
return { ok: true, ownerAddress: owner }
|
|
48
|
+
}
|
|
49
|
+
const vaultOwnerResult = await readVaultLevelOwner({
|
|
50
|
+
...args,
|
|
51
|
+
vaultAddress: owner,
|
|
52
|
+
})
|
|
53
|
+
if (vaultOwnerResult.kind === 'empty') {
|
|
54
|
+
return {
|
|
55
|
+
ok: false,
|
|
56
|
+
reason: 'token-owner-lookup-failed',
|
|
57
|
+
detail: `ERC-8004 token #${args.agentId.toString()} is still reported at the operator delegation vault, but that vault record is empty. Ownership is still settling; retry shortly.`,
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
if (vaultOwnerResult.kind === 'error') {
|
|
61
|
+
return {
|
|
62
|
+
ok: false,
|
|
63
|
+
reason: 'token-owner-lookup-failed',
|
|
64
|
+
detail: vaultOwnerResult.error instanceof Error ? vaultOwnerResult.error.message : String(vaultOwnerResult.error),
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
if (vaultOwnerResult.kind === 'ok') {
|
|
68
|
+
const vaultOwner = vaultOwnerResult.ownerAddress
|
|
69
|
+
if (vaultOwner.toLowerCase() === expectedOwner.toLowerCase()) {
|
|
70
|
+
return { ok: true, ownerAddress: vaultOwner }
|
|
71
|
+
}
|
|
72
|
+
return {
|
|
73
|
+
ok: false,
|
|
74
|
+
reason: 'token-owner-mismatch',
|
|
75
|
+
ownerAddress: vaultOwner,
|
|
76
|
+
detail: `ERC-8004 token #${args.agentId.toString()} is held by the operator delegation vault for ${vaultOwner}`,
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
return {
|
|
80
|
+
ok: false,
|
|
81
|
+
reason: 'token-owner-mismatch',
|
|
82
|
+
ownerAddress: owner,
|
|
83
|
+
detail: `ERC-8004 token #${args.agentId.toString()} is owned by ${shortHex(owner)}`,
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
async function readVaultLevelOwner(args: Erc8004RegistryConfig & {
|
|
88
|
+
agentId: bigint
|
|
89
|
+
vaultAddress: Address
|
|
90
|
+
publicClient?: TokenOwnerReadClient
|
|
91
|
+
}): Promise<
|
|
92
|
+
| { kind: 'ok'; ownerAddress: Address }
|
|
93
|
+
| { kind: 'empty' }
|
|
94
|
+
| { kind: 'not-vault' }
|
|
95
|
+
| { kind: 'error'; error: unknown }
|
|
96
|
+
> {
|
|
97
|
+
try {
|
|
98
|
+
const client = args.publicClient ?? createErc8004PublicClient(args)
|
|
99
|
+
const vaultOwner = await client.readContract({
|
|
100
|
+
address: args.vaultAddress,
|
|
101
|
+
abi: OPERATOR_VAULT_ABI,
|
|
102
|
+
functionName: 'agentOwner',
|
|
103
|
+
args: [args.identityRegistryAddress, args.agentId],
|
|
104
|
+
}) as Address
|
|
105
|
+
const normalizedVaultOwner = getAddress(vaultOwner)
|
|
106
|
+
if (normalizedVaultOwner.toLowerCase() === ZERO_ADDRESS) return { kind: 'empty' }
|
|
107
|
+
return { kind: 'ok', ownerAddress: normalizedVaultOwner }
|
|
108
|
+
} catch (err: unknown) {
|
|
109
|
+
return looksLikeTransientVaultReadFailure(err) ? { kind: 'error', error: err } : { kind: 'not-vault' }
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function looksLikeTransientVaultReadFailure(err: unknown): boolean {
|
|
114
|
+
const message = err instanceof Error ? err.message : String(err)
|
|
115
|
+
return /timeout|network|header not found|rate|server|rpc/i.test(message)
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function shortHex(value: string): string {
|
|
119
|
+
if (value.length <= 14) return value
|
|
120
|
+
return `${value.slice(0, 6)}...${value.slice(-4)}`
|
|
121
|
+
}
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import type { Address, Hex } from 'viem'
|
|
2
|
+
import type { Erc8004RegistryConfig } from './types.js'
|
|
3
|
+
import { createErc8004PublicClient } from './client.js'
|
|
4
|
+
import { encodeRegisterAgent, encodeSetAgentUri } from './transactions.js'
|
|
5
|
+
import { cleanRpcError, formatEthAmount } from './utils.js'
|
|
6
|
+
|
|
7
|
+
export type RegisterAgentPreflight = {
|
|
8
|
+
gas: bigint
|
|
9
|
+
gasPrice: bigint
|
|
10
|
+
estimatedCostWei: bigint
|
|
11
|
+
requiredBalanceWei: bigint
|
|
12
|
+
balanceWei: bigint
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export type RegisterAgentPreflightErrorCode = 'insufficient-funds' | 'simulation-failed'
|
|
16
|
+
|
|
17
|
+
export class RegisterAgentPreflightError extends Error {
|
|
18
|
+
code: RegisterAgentPreflightErrorCode
|
|
19
|
+
title: string
|
|
20
|
+
detail: string
|
|
21
|
+
hint: string
|
|
22
|
+
requiredBalanceWei?: bigint
|
|
23
|
+
balanceWei?: bigint
|
|
24
|
+
|
|
25
|
+
constructor(args: {
|
|
26
|
+
code: RegisterAgentPreflightErrorCode
|
|
27
|
+
title: string
|
|
28
|
+
detail: string
|
|
29
|
+
hint: string
|
|
30
|
+
requiredBalanceWei?: bigint
|
|
31
|
+
balanceWei?: bigint
|
|
32
|
+
}) {
|
|
33
|
+
super(args.detail ? `${args.title}: ${args.detail}` : args.title)
|
|
34
|
+
this.name = 'RegisterAgentPreflightError'
|
|
35
|
+
this.code = args.code
|
|
36
|
+
this.title = args.title
|
|
37
|
+
this.detail = args.detail
|
|
38
|
+
this.hint = args.hint
|
|
39
|
+
this.requiredBalanceWei = args.requiredBalanceWei
|
|
40
|
+
this.balanceWei = args.balanceWei
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
type RegisterAgentPreflightClient = {
|
|
45
|
+
estimateGas: (args: { account: Address; to: Address; data: Hex }) => Promise<bigint>
|
|
46
|
+
getGasPrice: () => Promise<bigint>
|
|
47
|
+
getBalance: (args: { address: Address }) => Promise<bigint>
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export async function preflightRegisterAgent(args: Erc8004RegistryConfig & {
|
|
51
|
+
ownerAddress: Address
|
|
52
|
+
agentURI: string
|
|
53
|
+
publicClient?: RegisterAgentPreflightClient
|
|
54
|
+
}): Promise<RegisterAgentPreflight> {
|
|
55
|
+
const publicClient = args.publicClient ?? createErc8004PublicClient(args) as RegisterAgentPreflightClient
|
|
56
|
+
const data = encodeRegisterAgent({ agentURI: args.agentURI })
|
|
57
|
+
let gas: bigint
|
|
58
|
+
try {
|
|
59
|
+
gas = await publicClient.estimateGas({
|
|
60
|
+
account: args.ownerAddress,
|
|
61
|
+
to: args.identityRegistryAddress,
|
|
62
|
+
data,
|
|
63
|
+
})
|
|
64
|
+
} catch (err: unknown) {
|
|
65
|
+
throw new RegisterAgentPreflightError({
|
|
66
|
+
code: 'simulation-failed',
|
|
67
|
+
title: 'Registration Blocked',
|
|
68
|
+
detail: cleanRpcError(err),
|
|
69
|
+
hint: 'No transaction was sent.',
|
|
70
|
+
})
|
|
71
|
+
}
|
|
72
|
+
const [gasPrice, balance] = await Promise.all([
|
|
73
|
+
publicClient.getGasPrice(),
|
|
74
|
+
publicClient.getBalance({ address: args.ownerAddress }),
|
|
75
|
+
])
|
|
76
|
+
const estimatedCost = gas * gasPrice
|
|
77
|
+
const requiredBalance = estimatedCost + estimatedCost / 5n
|
|
78
|
+
if (balance < requiredBalance) {
|
|
79
|
+
throw new RegisterAgentPreflightError({
|
|
80
|
+
code: 'insufficient-funds',
|
|
81
|
+
title: 'Not Enough ETH',
|
|
82
|
+
detail: `Need ~${formatEthAmount(requiredBalance)} ETH. Wallet has ${formatEthAmount(balance)} ETH.`,
|
|
83
|
+
hint: 'Add ETH to this wallet, then try again.',
|
|
84
|
+
requiredBalanceWei: requiredBalance,
|
|
85
|
+
balanceWei: balance,
|
|
86
|
+
})
|
|
87
|
+
}
|
|
88
|
+
return {
|
|
89
|
+
gas,
|
|
90
|
+
gasPrice,
|
|
91
|
+
estimatedCostWei: estimatedCost,
|
|
92
|
+
requiredBalanceWei: requiredBalance,
|
|
93
|
+
balanceWei: balance,
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export async function preflightSetAgentUri(args: Erc8004RegistryConfig & {
|
|
98
|
+
account: Address
|
|
99
|
+
agentId: bigint
|
|
100
|
+
newUri: string
|
|
101
|
+
publicClient?: RegisterAgentPreflightClient
|
|
102
|
+
}): Promise<void> {
|
|
103
|
+
const publicClient = args.publicClient ?? createErc8004PublicClient(args) as RegisterAgentPreflightClient
|
|
104
|
+
const data = encodeSetAgentUri({ agentId: args.agentId, newUri: args.newUri })
|
|
105
|
+
try {
|
|
106
|
+
await publicClient.estimateGas({
|
|
107
|
+
account: args.account,
|
|
108
|
+
to: args.identityRegistryAddress,
|
|
109
|
+
data,
|
|
110
|
+
})
|
|
111
|
+
} catch (err: unknown) {
|
|
112
|
+
const detail = cleanRpcError(err)
|
|
113
|
+
const looksLikeOwnershipRevert = /not.*owner|owner.*only|unauthor|forbidden|caller/i.test(detail)
|
|
114
|
+
throw new RegisterAgentPreflightError({
|
|
115
|
+
code: 'simulation-failed',
|
|
116
|
+
title: 'Backup Update Blocked',
|
|
117
|
+
detail,
|
|
118
|
+
hint: looksLikeOwnershipRevert
|
|
119
|
+
? `Connect the wallet that owns this agent (${args.account}) and try again.`
|
|
120
|
+
: 'No transaction was sent.',
|
|
121
|
+
})
|
|
122
|
+
}
|
|
123
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { decodeEventLog, encodeFunctionData, getAddress, type Address, type Hex } from 'viem'
|
|
2
|
+
import { ERC8004_ABI, REGISTERED_EVENT, TRANSFER_EVENT } from './abi.js'
|
|
3
|
+
|
|
4
|
+
type ReceiptLog = { address?: Address; topics: readonly Hex[]; data: Hex }
|
|
5
|
+
|
|
6
|
+
export function encodeRegisterAgent(args: {
|
|
7
|
+
agentURI: string
|
|
8
|
+
}): Hex {
|
|
9
|
+
return encodeFunctionData({
|
|
10
|
+
abi: ERC8004_ABI,
|
|
11
|
+
functionName: 'register',
|
|
12
|
+
args: [args.agentURI],
|
|
13
|
+
})
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function encodeSetAgentUri(args: {
|
|
17
|
+
agentId: bigint
|
|
18
|
+
newUri: string
|
|
19
|
+
}): Hex {
|
|
20
|
+
return encodeFunctionData({
|
|
21
|
+
abi: ERC8004_ABI,
|
|
22
|
+
functionName: 'setAgentURI',
|
|
23
|
+
args: [args.agentId, args.newUri],
|
|
24
|
+
})
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function registeredAgentFromReceipt(args: {
|
|
28
|
+
logs: ReceiptLog[]
|
|
29
|
+
identityRegistryAddress: Address
|
|
30
|
+
ownerAddress?: Address
|
|
31
|
+
fallbackAgentURI?: string
|
|
32
|
+
}): { agentId: bigint; agentURI: string; owner: Address } {
|
|
33
|
+
for (const log of args.logs) {
|
|
34
|
+
if (log.address && log.address.toLowerCase() !== args.identityRegistryAddress.toLowerCase()) continue
|
|
35
|
+
try {
|
|
36
|
+
const decoded = decodeEventLog({
|
|
37
|
+
abi: [REGISTERED_EVENT],
|
|
38
|
+
topics: log.topics as [Hex, ...Hex[]],
|
|
39
|
+
data: log.data,
|
|
40
|
+
})
|
|
41
|
+
if (decoded.eventName !== 'Registered') continue
|
|
42
|
+
const eventArgs = decoded.args as { agentId?: bigint; agentURI?: string; owner?: Address }
|
|
43
|
+
if (eventArgs.agentId === undefined || !eventArgs.agentURI || !eventArgs.owner) continue
|
|
44
|
+
if (args.ownerAddress && eventArgs.owner.toLowerCase() !== args.ownerAddress.toLowerCase()) continue
|
|
45
|
+
return {
|
|
46
|
+
agentId: eventArgs.agentId,
|
|
47
|
+
agentURI: eventArgs.agentURI,
|
|
48
|
+
owner: getAddress(eventArgs.owner),
|
|
49
|
+
}
|
|
50
|
+
} catch {
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000'
|
|
54
|
+
for (const log of args.logs) {
|
|
55
|
+
if (log.address && log.address.toLowerCase() !== args.identityRegistryAddress.toLowerCase()) continue
|
|
56
|
+
try {
|
|
57
|
+
const decoded = decodeEventLog({
|
|
58
|
+
abi: [TRANSFER_EVENT],
|
|
59
|
+
topics: log.topics as [Hex, ...Hex[]],
|
|
60
|
+
data: log.data,
|
|
61
|
+
})
|
|
62
|
+
if (decoded.eventName !== 'Transfer') continue
|
|
63
|
+
const eventArgs = decoded.args as { from?: Address; to?: Address; tokenId?: bigint }
|
|
64
|
+
if (!eventArgs.from || !eventArgs.to || eventArgs.tokenId === undefined) continue
|
|
65
|
+
if (eventArgs.from.toLowerCase() !== ZERO_ADDRESS) continue
|
|
66
|
+
if (args.ownerAddress && eventArgs.to.toLowerCase() !== args.ownerAddress.toLowerCase()) continue
|
|
67
|
+
const agentURI = args.fallbackAgentURI ?? ''
|
|
68
|
+
return {
|
|
69
|
+
agentId: eventArgs.tokenId,
|
|
70
|
+
agentURI,
|
|
71
|
+
owner: getAddress(eventArgs.to),
|
|
72
|
+
}
|
|
73
|
+
} catch {
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
throw new Error('ERC-8004 registration event was not found in transaction receipt')
|
|
77
|
+
}
|