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,511 @@
|
|
|
1
|
+
import { getAddress, isAddress, type Address, type PublicClient } from 'viem'
|
|
2
|
+
import { mainnet } from 'viem/chains'
|
|
3
|
+
import { DEFAULT_IPFS_API_URL } from '../../storage/ipfs.js'
|
|
4
|
+
import { isAgentInVault } from '../operatorVault.js'
|
|
5
|
+
import { ERC8004_ABI, TRANSFER_EVENT } from './abi.js'
|
|
6
|
+
import {
|
|
7
|
+
SUPPORTED_ERC8004_CHAINS,
|
|
8
|
+
erc8004ConfigForSupportedChain,
|
|
9
|
+
supportedErc8004ChainForId,
|
|
10
|
+
} from './chains.js'
|
|
11
|
+
import { createErc8004PublicClient } from './client.js'
|
|
12
|
+
import { parseEthagentBackupPointer, parseEthagentOperatorsPointer, parseEthagentPublicDiscoveryPointer } from './metadata.js'
|
|
13
|
+
import type { DiscoverOwnedAgentsAcrossSupportedNetworksArgs, DiscoverOwnedAgentsArgs, Erc8004AgentCandidate, Erc8004RegistryConfig, EthagentOperatorsPointer } from './types.js'
|
|
14
|
+
import { loadAgentRegistrationWithRetry } from './uri.js'
|
|
15
|
+
import { cleanRpcError, mapWithConcurrency } from './utils.js'
|
|
16
|
+
import { stringField } from '../fieldParsers.js'
|
|
17
|
+
|
|
18
|
+
const DISCOVERY_CONCURRENCY = 2
|
|
19
|
+
|
|
20
|
+
type TransferLog = { args: { tokenId?: bigint } }
|
|
21
|
+
|
|
22
|
+
export class AgentTokenIdRequiredError extends Error {
|
|
23
|
+
ownerAddress: Address
|
|
24
|
+
registry: Erc8004RegistryConfig
|
|
25
|
+
balance: bigint
|
|
26
|
+
detail?: string
|
|
27
|
+
|
|
28
|
+
constructor(args: {
|
|
29
|
+
ownerAddress: Address
|
|
30
|
+
registry: Erc8004RegistryConfig
|
|
31
|
+
balance: bigint
|
|
32
|
+
detail?: string
|
|
33
|
+
}) {
|
|
34
|
+
const chain = supportedErc8004ChainForId(args.registry.chainId)
|
|
35
|
+
const label = chain?.network ?? chain?.name ?? `chain ${args.registry.chainId}`
|
|
36
|
+
super(`Automatic ${label} token ownership lookup could not enumerate this wallet's ERC-8004 token IDs.`)
|
|
37
|
+
this.name = 'AgentTokenIdRequiredError'
|
|
38
|
+
this.ownerAddress = args.ownerAddress
|
|
39
|
+
this.registry = args.registry
|
|
40
|
+
this.balance = args.balance
|
|
41
|
+
if (args.detail) this.detail = cleanRpcError(args.detail)
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
async function resolveOwnerHandle(
|
|
46
|
+
ownerHandle: string,
|
|
47
|
+
args: Pick<Erc8004RegistryConfig, 'chainId' | 'rpcUrl'> & { publicClient?: PublicClient },
|
|
48
|
+
): Promise<Address> {
|
|
49
|
+
const trimmed = ownerHandle.trim()
|
|
50
|
+
if (isAddress(trimmed)) return getAddress(trimmed)
|
|
51
|
+
if (!trimmed.includes('.')) throw new Error('Enter an Ethereum address or ENS name')
|
|
52
|
+
|
|
53
|
+
const publicClient = args.publicClient ?? createErc8004PublicClient(args)
|
|
54
|
+
const resolved = await publicClient.getEnsAddress({ name: trimmed })
|
|
55
|
+
if (!resolved) throw new Error(`ENS name did not resolve: ${trimmed}`)
|
|
56
|
+
return getAddress(resolved)
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export async function discoverOwnedAgentBackups(args: DiscoverOwnedAgentsArgs): Promise<Erc8004AgentCandidate[]> {
|
|
60
|
+
if (args.signal?.aborted) throw new DOMException('discovery cancelled', 'AbortError')
|
|
61
|
+
const publicClient = args.publicClient ?? createErc8004PublicClient(args)
|
|
62
|
+
const ownerAddress = await resolveOwnerHandle(args.ownerHandle, args)
|
|
63
|
+
const fromBlock = args.fromBlock ?? supportedErc8004ChainForId(args.chainId)?.fromBlock ?? 0n
|
|
64
|
+
const tokenIds = await findCandidateTokenIds({
|
|
65
|
+
publicClient,
|
|
66
|
+
registry: args,
|
|
67
|
+
ownerAddress,
|
|
68
|
+
fromBlock,
|
|
69
|
+
})
|
|
70
|
+
const out: Erc8004AgentCandidate[] = []
|
|
71
|
+
for (const tokenId of tokenIds) {
|
|
72
|
+
if (args.signal?.aborted) throw new DOMException('discovery cancelled', 'AbortError')
|
|
73
|
+
const candidate = await loadOwnedAgentCandidate({
|
|
74
|
+
...args,
|
|
75
|
+
publicClient,
|
|
76
|
+
ownerAddress,
|
|
77
|
+
tokenId,
|
|
78
|
+
}).catch(err => {
|
|
79
|
+
if (err instanceof TokenOwnerMismatchError) return null
|
|
80
|
+
throw err
|
|
81
|
+
})
|
|
82
|
+
if (candidate) out.push(candidate)
|
|
83
|
+
}
|
|
84
|
+
return out.sort((a, b) => Number(b.agentId - a.agentId))
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export async function discoverOwnedAgentBackupByTokenId(args: DiscoverOwnedAgentsArgs & {
|
|
88
|
+
tokenId: bigint
|
|
89
|
+
}): Promise<Erc8004AgentCandidate> {
|
|
90
|
+
const publicClient = args.publicClient ?? createErc8004PublicClient(args)
|
|
91
|
+
const ownerAddress = await resolveOwnerHandle(args.ownerHandle, args)
|
|
92
|
+
return loadOwnedAgentCandidate({
|
|
93
|
+
...args,
|
|
94
|
+
publicClient,
|
|
95
|
+
ownerAddress,
|
|
96
|
+
tokenId: args.tokenId,
|
|
97
|
+
})
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export async function discoverOwnedAgentBackupsAcrossSupportedNetworks(
|
|
101
|
+
args: DiscoverOwnedAgentsAcrossSupportedNetworksArgs,
|
|
102
|
+
): Promise<Erc8004AgentCandidate[]> {
|
|
103
|
+
const ownerAddress = await resolveOwnerAddressForSupportedLookup(args)
|
|
104
|
+
const configs = SUPPORTED_ERC8004_CHAINS.map(chain => {
|
|
105
|
+
const override = args.registryOverrides?.find(item => item.chainId === chain.chainId)
|
|
106
|
+
return override ?? erc8004ConfigForSupportedChain(chain.chainId)
|
|
107
|
+
})
|
|
108
|
+
const results = await mapWithConcurrency(configs, DISCOVERY_CONCURRENCY, async config => {
|
|
109
|
+
try {
|
|
110
|
+
return {
|
|
111
|
+
ok: true as const,
|
|
112
|
+
candidates: await discoverOwnedAgentBackups({
|
|
113
|
+
...config,
|
|
114
|
+
ownerHandle: ownerAddress,
|
|
115
|
+
ipfsApiUrl: args.ipfsApiUrl,
|
|
116
|
+
publicClient: args.publicClients?.[config.chainId],
|
|
117
|
+
fetchImpl: args.fetchImpl,
|
|
118
|
+
...(args.signal ? { signal: args.signal } : {}),
|
|
119
|
+
}),
|
|
120
|
+
}
|
|
121
|
+
} catch (err: unknown) {
|
|
122
|
+
return { ok: false as const, error: err }
|
|
123
|
+
}
|
|
124
|
+
})
|
|
125
|
+
|
|
126
|
+
const candidates = results.flatMap(result => result.ok ? result.candidates : [])
|
|
127
|
+
if (candidates.length > 0) {
|
|
128
|
+
return candidates.sort(compareCandidatesByNetworkThenNewest)
|
|
129
|
+
}
|
|
130
|
+
const failures = results.filter(result => !result.ok)
|
|
131
|
+
if (failures.length === results.length && failures.length > 0) {
|
|
132
|
+
throw new Error(`lookup failed on all supported networks: ${cleanRpcError(failures[0]!.error)}`)
|
|
133
|
+
}
|
|
134
|
+
const tokenIdRequired = failures
|
|
135
|
+
.map(result => result.error)
|
|
136
|
+
.find((err): err is AgentTokenIdRequiredError => err instanceof AgentTokenIdRequiredError)
|
|
137
|
+
if (tokenIdRequired) throw tokenIdRequired
|
|
138
|
+
return []
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
async function findCandidateTokenIds(args: {
|
|
142
|
+
publicClient: PublicClient
|
|
143
|
+
registry: Erc8004RegistryConfig
|
|
144
|
+
ownerAddress: Address
|
|
145
|
+
fromBlock: bigint
|
|
146
|
+
}): Promise<bigint[]> {
|
|
147
|
+
const tokenIds = new Set<bigint>()
|
|
148
|
+
let balance: bigint | undefined
|
|
149
|
+
let attempt = 0
|
|
150
|
+
while (true) {
|
|
151
|
+
try {
|
|
152
|
+
balance = await args.publicClient.readContract({
|
|
153
|
+
address: args.registry.identityRegistryAddress,
|
|
154
|
+
abi: ERC8004_ABI,
|
|
155
|
+
functionName: 'balanceOf',
|
|
156
|
+
args: [args.ownerAddress],
|
|
157
|
+
}) as bigint
|
|
158
|
+
break
|
|
159
|
+
} catch (err: unknown) {
|
|
160
|
+
if (++attempt > 3) {
|
|
161
|
+
throw new AgentTokenIdRequiredError({
|
|
162
|
+
ownerAddress: args.ownerAddress,
|
|
163
|
+
registry: args.registry,
|
|
164
|
+
balance: 0n,
|
|
165
|
+
detail: cleanRpcError(err),
|
|
166
|
+
})
|
|
167
|
+
}
|
|
168
|
+
await new Promise(r => setTimeout(r, attempt * 1000))
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
if (balance === 0n) return []
|
|
172
|
+
|
|
173
|
+
const enumerableTokenIds = await findEnumerableTokenIds({
|
|
174
|
+
publicClient: args.publicClient,
|
|
175
|
+
registry: args.registry,
|
|
176
|
+
ownerAddress: args.ownerAddress,
|
|
177
|
+
balance,
|
|
178
|
+
})
|
|
179
|
+
if (enumerableTokenIds) return enumerableTokenIds
|
|
180
|
+
|
|
181
|
+
try {
|
|
182
|
+
for await (const logs of getTransferLogChunksBackwards({
|
|
183
|
+
publicClient: args.publicClient,
|
|
184
|
+
registry: args.registry,
|
|
185
|
+
ownerAddress: args.ownerAddress,
|
|
186
|
+
fromBlock: args.fromBlock,
|
|
187
|
+
})) {
|
|
188
|
+
for (const log of logs) {
|
|
189
|
+
const tokenId = log.args.tokenId
|
|
190
|
+
if (tokenId === undefined || tokenIds.has(tokenId)) continue
|
|
191
|
+
if (await isCurrentTokenOwner(args.publicClient, args.registry.identityRegistryAddress, tokenId, args.ownerAddress)) {
|
|
192
|
+
tokenIds.add(tokenId)
|
|
193
|
+
if (BigInt(tokenIds.size) >= balance) return [...tokenIds]
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
} catch (err: unknown) {
|
|
198
|
+
throw new AgentTokenIdRequiredError({
|
|
199
|
+
ownerAddress: args.ownerAddress,
|
|
200
|
+
registry: args.registry,
|
|
201
|
+
balance,
|
|
202
|
+
detail: cleanRpcError(err),
|
|
203
|
+
})
|
|
204
|
+
}
|
|
205
|
+
if (BigInt(tokenIds.size) < balance) {
|
|
206
|
+
throw new AgentTokenIdRequiredError({
|
|
207
|
+
ownerAddress: args.ownerAddress,
|
|
208
|
+
registry: args.registry,
|
|
209
|
+
balance,
|
|
210
|
+
detail: 'Owned token ids were not found in logs',
|
|
211
|
+
})
|
|
212
|
+
}
|
|
213
|
+
return [...tokenIds]
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
async function* getTransferLogChunksBackwards(args: {
|
|
217
|
+
publicClient: PublicClient
|
|
218
|
+
registry: Erc8004RegistryConfig
|
|
219
|
+
ownerAddress: Address
|
|
220
|
+
fromBlock: bigint
|
|
221
|
+
}): AsyncGenerator<TransferLog[]> {
|
|
222
|
+
const latest = await args.publicClient.getBlockNumber()
|
|
223
|
+
if (args.fromBlock > latest) return
|
|
224
|
+
|
|
225
|
+
const ranges = blockRangesBackwards(args.fromBlock, latest, logBlockRangeForChain(args.registry.chainId))
|
|
226
|
+
const CONCURRENCY = 5
|
|
227
|
+
|
|
228
|
+
for (let i = 0; i < ranges.length; i += CONCURRENCY) {
|
|
229
|
+
const batch = ranges.slice(i, i + CONCURRENCY)
|
|
230
|
+
const logsArrays = await Promise.all(batch.map(async range => {
|
|
231
|
+
try {
|
|
232
|
+
return await getTransferLogsAdaptive({
|
|
233
|
+
...args,
|
|
234
|
+
fromBlock: range.fromBlock,
|
|
235
|
+
toBlock: range.toBlock,
|
|
236
|
+
minBlockRange: minLogBlockRangeForChain(args.registry.chainId),
|
|
237
|
+
})
|
|
238
|
+
} catch {
|
|
239
|
+
return [] as TransferLog[]
|
|
240
|
+
}
|
|
241
|
+
}))
|
|
242
|
+
for (const logs of logsArrays) {
|
|
243
|
+
if (logs.length > 0) yield logs
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
async function findEnumerableTokenIds(args: {
|
|
249
|
+
publicClient: PublicClient
|
|
250
|
+
registry: Erc8004RegistryConfig
|
|
251
|
+
ownerAddress: Address
|
|
252
|
+
balance: bigint
|
|
253
|
+
}): Promise<bigint[] | null> {
|
|
254
|
+
const tokenIds: bigint[] = []
|
|
255
|
+
try {
|
|
256
|
+
for (let index = 0n; index < args.balance; index += 1n) {
|
|
257
|
+
const tokenId = await args.publicClient.readContract({
|
|
258
|
+
address: args.registry.identityRegistryAddress,
|
|
259
|
+
abi: ERC8004_ABI,
|
|
260
|
+
functionName: 'tokenOfOwnerByIndex',
|
|
261
|
+
args: [args.ownerAddress, index],
|
|
262
|
+
}) as bigint
|
|
263
|
+
tokenIds.push(tokenId)
|
|
264
|
+
}
|
|
265
|
+
return tokenIds
|
|
266
|
+
} catch {
|
|
267
|
+
return null
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
async function getTransferLogsAdaptive(args: {
|
|
272
|
+
publicClient: PublicClient
|
|
273
|
+
registry: Erc8004RegistryConfig
|
|
274
|
+
ownerAddress: Address
|
|
275
|
+
fromBlock: bigint
|
|
276
|
+
toBlock: bigint
|
|
277
|
+
minBlockRange: bigint
|
|
278
|
+
}): Promise<TransferLog[]> {
|
|
279
|
+
const size = args.toBlock - args.fromBlock + 1n
|
|
280
|
+
let attempt = 0
|
|
281
|
+
while (true) {
|
|
282
|
+
try {
|
|
283
|
+
const logs = await args.publicClient.getLogs({
|
|
284
|
+
address: args.registry.identityRegistryAddress,
|
|
285
|
+
event: TRANSFER_EVENT,
|
|
286
|
+
args: { to: args.ownerAddress },
|
|
287
|
+
fromBlock: args.fromBlock,
|
|
288
|
+
toBlock: args.toBlock,
|
|
289
|
+
})
|
|
290
|
+
return logs as TransferLog[]
|
|
291
|
+
} catch (err: unknown) {
|
|
292
|
+
attempt++
|
|
293
|
+
const msg = err instanceof Error ? err.message.toLowerCase() : String(err).toLowerCase()
|
|
294
|
+
const isSizeLimit = msg.includes('limit') || msg.includes('range') || msg.includes('too many') || msg.includes('exceeds') || msg.includes('block count')
|
|
295
|
+
|
|
296
|
+
if (!isSizeLimit && attempt <= 3) {
|
|
297
|
+
await new Promise(r => setTimeout(r, attempt * 1000))
|
|
298
|
+
continue
|
|
299
|
+
}
|
|
300
|
+
if (size <= args.minBlockRange) {
|
|
301
|
+
if (attempt <= 3) {
|
|
302
|
+
await new Promise(r => setTimeout(r, attempt * 1000))
|
|
303
|
+
continue
|
|
304
|
+
}
|
|
305
|
+
throw err
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
const mid = args.fromBlock + size / 2n - 1n
|
|
309
|
+
const [newer, older] = await Promise.all([
|
|
310
|
+
getTransferLogsAdaptive({ ...args, fromBlock: mid + 1n, toBlock: args.toBlock }),
|
|
311
|
+
getTransferLogsAdaptive({ ...args, fromBlock: args.fromBlock, toBlock: mid })
|
|
312
|
+
])
|
|
313
|
+
return [...newer, ...older]
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
class TokenOwnerMismatchError extends Error {
|
|
319
|
+
constructor() {
|
|
320
|
+
super('Wallet is not the token owner or an operator wallet')
|
|
321
|
+
this.name = 'TokenOwnerMismatchError'
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
class MetadataFetchError extends Error {
|
|
326
|
+
readonly tokenId: bigint
|
|
327
|
+
readonly agentUri: string
|
|
328
|
+
override readonly cause: unknown
|
|
329
|
+
constructor(tokenId: bigint, agentUri: string, cause: unknown) {
|
|
330
|
+
super(`failed to fetch agent metadata for token #${tokenId.toString()} at ${agentUri}: ${cause instanceof Error ? cause.message : String(cause)}`)
|
|
331
|
+
this.name = 'MetadataFetchError'
|
|
332
|
+
this.tokenId = tokenId
|
|
333
|
+
this.agentUri = agentUri
|
|
334
|
+
this.cause = cause
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
function isAuthorizedAgentLookupAddress(args: {
|
|
339
|
+
requesterAddress: Address
|
|
340
|
+
tokenOwnerAddress: Address
|
|
341
|
+
operators: EthagentOperatorsPointer | null
|
|
342
|
+
}): boolean {
|
|
343
|
+
const requester = args.requesterAddress.toLowerCase()
|
|
344
|
+
if (args.tokenOwnerAddress.toLowerCase() === requester) return true
|
|
345
|
+
if (args.operators?.activeOperatorAddress?.toLowerCase() === requester) return true
|
|
346
|
+
return args.operators?.approvedOperatorWallets.some(record => record.address.toLowerCase() === requester) ?? false
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
async function loadOwnedAgentCandidate(args: DiscoverOwnedAgentsArgs & {
|
|
350
|
+
publicClient: PublicClient
|
|
351
|
+
ownerAddress: Address
|
|
352
|
+
tokenId: bigint
|
|
353
|
+
}): Promise<Erc8004AgentCandidate> {
|
|
354
|
+
let attempt = 0
|
|
355
|
+
let currentOwner: Address | undefined
|
|
356
|
+
let agentUri: string | undefined
|
|
357
|
+
while (true) {
|
|
358
|
+
try {
|
|
359
|
+
if (!currentOwner) {
|
|
360
|
+
currentOwner = await args.publicClient.readContract({
|
|
361
|
+
address: args.identityRegistryAddress,
|
|
362
|
+
abi: ERC8004_ABI,
|
|
363
|
+
functionName: 'ownerOf',
|
|
364
|
+
args: [args.tokenId],
|
|
365
|
+
}) as Address
|
|
366
|
+
}
|
|
367
|
+
if (!agentUri) {
|
|
368
|
+
agentUri = await args.publicClient.readContract({
|
|
369
|
+
address: args.identityRegistryAddress,
|
|
370
|
+
abi: ERC8004_ABI,
|
|
371
|
+
functionName: 'tokenURI',
|
|
372
|
+
args: [args.tokenId],
|
|
373
|
+
}) as string
|
|
374
|
+
}
|
|
375
|
+
break
|
|
376
|
+
} catch (err: unknown) {
|
|
377
|
+
if (++attempt > 3) throw err
|
|
378
|
+
await new Promise(r => setTimeout(r, attempt * 1000))
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
const tokenOwnerAddress = getAddress(currentOwner)
|
|
383
|
+
let loaded: { metadataCid?: string; registration: Record<string, unknown> }
|
|
384
|
+
try {
|
|
385
|
+
loaded = await loadAgentRegistrationWithRetry(agentUri, {
|
|
386
|
+
ipfsApiUrl: args.ipfsApiUrl ?? DEFAULT_IPFS_API_URL,
|
|
387
|
+
fetchImpl: args.fetchImpl,
|
|
388
|
+
...(args.signal ? { signal: args.signal } : {}),
|
|
389
|
+
})
|
|
390
|
+
} catch (err: unknown) {
|
|
391
|
+
if (args.signal?.aborted || (err instanceof Error && err.name === 'AbortError')) throw err
|
|
392
|
+
throw new MetadataFetchError(args.tokenId, agentUri, err)
|
|
393
|
+
}
|
|
394
|
+
const parsed = parseEthagentBackupPointer(loaded.registration)
|
|
395
|
+
const publicDiscovery = parseEthagentPublicDiscoveryPointer(loaded.registration)
|
|
396
|
+
const operators = parseEthagentOperatorsPointer(loaded.registration)
|
|
397
|
+
let vaultLevelOwner: Address | undefined
|
|
398
|
+
try {
|
|
399
|
+
const status = await isAgentInVault({
|
|
400
|
+
client: args.publicClient,
|
|
401
|
+
vaultAddress: tokenOwnerAddress,
|
|
402
|
+
registry: args.identityRegistryAddress,
|
|
403
|
+
agentId: args.tokenId,
|
|
404
|
+
})
|
|
405
|
+
if (status.inVault && status.ownerAddress) {
|
|
406
|
+
vaultLevelOwner = status.ownerAddress
|
|
407
|
+
}
|
|
408
|
+
} catch {
|
|
409
|
+
vaultLevelOwner = undefined
|
|
410
|
+
}
|
|
411
|
+
if (!isAuthorizedAgentLookupAddress({
|
|
412
|
+
requesterAddress: args.ownerAddress,
|
|
413
|
+
tokenOwnerAddress,
|
|
414
|
+
operators,
|
|
415
|
+
})) {
|
|
416
|
+
if (!vaultLevelOwner || vaultLevelOwner.toLowerCase() !== args.ownerAddress.toLowerCase()) {
|
|
417
|
+
throw new TokenOwnerMismatchError()
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
const ownerAddress = vaultLevelOwner ?? tokenOwnerAddress
|
|
421
|
+
return {
|
|
422
|
+
tokenOwnerAddress,
|
|
423
|
+
ownerAddress,
|
|
424
|
+
chainId: args.chainId,
|
|
425
|
+
rpcUrl: args.rpcUrl,
|
|
426
|
+
identityRegistryAddress: args.identityRegistryAddress,
|
|
427
|
+
agentId: args.tokenId,
|
|
428
|
+
agentUri,
|
|
429
|
+
metadataCid: loaded.metadataCid,
|
|
430
|
+
name: stringField(loaded.registration, 'name'),
|
|
431
|
+
description: stringField(loaded.registration, 'description'),
|
|
432
|
+
imageUrl: stringField(loaded.registration, 'image'),
|
|
433
|
+
backup: parsed ?? undefined,
|
|
434
|
+
publicDiscovery: publicDiscovery ?? undefined,
|
|
435
|
+
operators: operators ?? undefined,
|
|
436
|
+
registration: loaded.registration,
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
async function isCurrentTokenOwner(
|
|
441
|
+
publicClient: PublicClient,
|
|
442
|
+
registry: Address,
|
|
443
|
+
tokenId: bigint,
|
|
444
|
+
ownerAddress: Address,
|
|
445
|
+
): Promise<boolean> {
|
|
446
|
+
let attempt = 0
|
|
447
|
+
while (true) {
|
|
448
|
+
try {
|
|
449
|
+
const currentOwner = await publicClient.readContract({
|
|
450
|
+
address: registry,
|
|
451
|
+
abi: ERC8004_ABI,
|
|
452
|
+
functionName: 'ownerOf',
|
|
453
|
+
args: [tokenId],
|
|
454
|
+
}) as Address
|
|
455
|
+
return currentOwner.toLowerCase() === ownerAddress.toLowerCase()
|
|
456
|
+
} catch (err: unknown) {
|
|
457
|
+
if (++attempt > 3) throw err
|
|
458
|
+
await new Promise(r => setTimeout(r, attempt * 1000))
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
async function resolveOwnerAddressForSupportedLookup(
|
|
464
|
+
args: DiscoverOwnedAgentsAcrossSupportedNetworksArgs,
|
|
465
|
+
): Promise<Address> {
|
|
466
|
+
const trimmed = args.ownerHandle.trim()
|
|
467
|
+
if (isAddress(trimmed)) return getAddress(trimmed)
|
|
468
|
+
const mainnetConfig = erc8004ConfigForSupportedChain(mainnet.id)
|
|
469
|
+
return resolveOwnerHandle(trimmed, {
|
|
470
|
+
...mainnetConfig,
|
|
471
|
+
publicClient: args.publicClients?.[mainnet.id],
|
|
472
|
+
})
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
function compareCandidatesByNetworkThenNewest(a: Erc8004AgentCandidate, b: Erc8004AgentCandidate): number {
|
|
476
|
+
const networkOrder = chainSortIndex(a.chainId) - chainSortIndex(b.chainId)
|
|
477
|
+
if (networkOrder !== 0) return networkOrder
|
|
478
|
+
return Number(b.agentId - a.agentId)
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
function blockRangesBackwards(
|
|
482
|
+
fromBlock: bigint,
|
|
483
|
+
latest: bigint,
|
|
484
|
+
blockRange: bigint,
|
|
485
|
+
): Array<{ fromBlock: bigint; toBlock: bigint }> {
|
|
486
|
+
const ranges: Array<{ fromBlock: bigint; toBlock: bigint }> = []
|
|
487
|
+
for (let end = latest; end >= fromBlock;) {
|
|
488
|
+
const start = end - blockRange + 1n > fromBlock ? end - blockRange + 1n : fromBlock
|
|
489
|
+
ranges.push({ fromBlock: start, toBlock: end })
|
|
490
|
+
if (start === fromBlock) break
|
|
491
|
+
end = start - 1n
|
|
492
|
+
}
|
|
493
|
+
return ranges
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
function logBlockRangeForChain(chainId: number): bigint {
|
|
497
|
+
const chain = supportedErc8004ChainForId(chainId)
|
|
498
|
+
if (!chain) return 10_000n
|
|
499
|
+
return chain.logBlockRange
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
function minLogBlockRangeForChain(chainId: number): bigint {
|
|
503
|
+
const chain = supportedErc8004ChainForId(chainId)
|
|
504
|
+
if (!chain) return 2_000n
|
|
505
|
+
return chain.kind === 'l2' ? chain.logBlockRange : chain.logBlockRange / 2n || 1n
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
function chainSortIndex(chainId: number): number {
|
|
509
|
+
const index = SUPPORTED_ERC8004_CHAINS.findIndex(chain => chain.chainId === chainId)
|
|
510
|
+
return index === -1 ? Number.MAX_SAFE_INTEGER : index
|
|
511
|
+
}
|