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,28 @@
|
|
|
1
|
+
|
|
2
|
+
export function objectField(input: Record<string, unknown> | null | undefined, key: string): Record<string, unknown> | null {
|
|
3
|
+
if (!input) return null
|
|
4
|
+
const value = input[key]
|
|
5
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) return null
|
|
6
|
+
return value as Record<string, unknown>
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function arrayField(input: Record<string, unknown> | null | undefined, key: string): Array<unknown> | null {
|
|
10
|
+
if (!input) return null
|
|
11
|
+
const value = input[key]
|
|
12
|
+
return Array.isArray(value) ? value : null
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function stringField(input: Record<string, unknown> | null | undefined, key: string): string | undefined {
|
|
16
|
+
const value = input?.[key]
|
|
17
|
+
return typeof value === 'string' && value.length > 0 ? value : undefined
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function numberField(input: Record<string, unknown> | null | undefined, key: string): number | undefined {
|
|
21
|
+
const value = input?.[key]
|
|
22
|
+
return typeof value === 'number' && Number.isSafeInteger(value) && value > 0 ? value : undefined
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function booleanField(input: Record<string, unknown> | null | undefined, key: string): boolean | undefined {
|
|
26
|
+
const value = input?.[key]
|
|
27
|
+
return typeof value === 'boolean' ? value : undefined
|
|
28
|
+
}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { getAddress, keccak256, type Address, type Hex, type PublicClient } from 'viem'
|
|
2
|
+
import { OPERATOR_VAULT_RUNTIME_BYTECODE, OPERATOR_VAULT_RUNTIME_BYTECODE_HASH } from './constants.js'
|
|
3
|
+
|
|
4
|
+
export class OperatorVaultBytecodeMismatchError extends Error {
|
|
5
|
+
readonly vaultAddress: Address
|
|
6
|
+
readonly observedHash: Hex | null
|
|
7
|
+
readonly observedLength: number
|
|
8
|
+
readonly expectedHash: Hex
|
|
9
|
+
readonly expectedLength: number
|
|
10
|
+
readonly txHash?: Hex
|
|
11
|
+
constructor(
|
|
12
|
+
vaultAddress: Address,
|
|
13
|
+
observedHash: Hex | null,
|
|
14
|
+
observedLength: number,
|
|
15
|
+
txHash?: Hex,
|
|
16
|
+
) {
|
|
17
|
+
super(
|
|
18
|
+
'Deployed contract bytecode does not match the expected operator delegation vault. The deploy transaction may have been intercepted.',
|
|
19
|
+
)
|
|
20
|
+
this.name = 'OperatorVaultBytecodeMismatchError'
|
|
21
|
+
this.vaultAddress = vaultAddress
|
|
22
|
+
this.observedHash = observedHash
|
|
23
|
+
this.observedLength = observedLength
|
|
24
|
+
this.expectedHash = OPERATOR_VAULT_RUNTIME_BYTECODE_HASH
|
|
25
|
+
this.expectedLength = (OPERATOR_VAULT_RUNTIME_BYTECODE.length - 2) / 2
|
|
26
|
+
if (txHash) this.txHash = txHash
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export type AssertVaultBytecodeClient = Pick<PublicClient, 'getBytecode'>
|
|
31
|
+
|
|
32
|
+
export const OPERATOR_VAULT_POLL_MAX_ATTEMPTS = 5
|
|
33
|
+
export const OPERATOR_VAULT_POLL_DELAY_MS = 1500
|
|
34
|
+
export function delayMs(ms: number): Promise<void> {
|
|
35
|
+
return new Promise(resolve => setTimeout(resolve, ms))
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
async function readVaultBytecodeWithPoll(
|
|
39
|
+
client: AssertVaultBytecodeClient,
|
|
40
|
+
address: Address,
|
|
41
|
+
): Promise<Hex | undefined> {
|
|
42
|
+
let lastErr: unknown
|
|
43
|
+
let lastCode: Hex | undefined
|
|
44
|
+
for (let attempt = 0; attempt < OPERATOR_VAULT_POLL_MAX_ATTEMPTS; attempt++) {
|
|
45
|
+
if (attempt > 0) await delayMs(OPERATOR_VAULT_POLL_DELAY_MS)
|
|
46
|
+
try {
|
|
47
|
+
const code = await client.getBytecode({ address })
|
|
48
|
+
lastErr = undefined
|
|
49
|
+
lastCode = code
|
|
50
|
+
const isEmpty = !code || code === '0x'
|
|
51
|
+
if (!isEmpty) return code
|
|
52
|
+
} catch (err) {
|
|
53
|
+
lastErr = err
|
|
54
|
+
lastCode = undefined
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
if (lastErr) throw lastErr
|
|
58
|
+
return lastCode
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export async function assertVaultBytecode(
|
|
62
|
+
client: AssertVaultBytecodeClient,
|
|
63
|
+
vaultAddress: Address,
|
|
64
|
+
txHash?: Hex,
|
|
65
|
+
): Promise<void> {
|
|
66
|
+
const address = getAddress(vaultAddress)
|
|
67
|
+
const code = await readVaultBytecodeWithPoll(client, address)
|
|
68
|
+
if (!code || code === '0x') {
|
|
69
|
+
throw new OperatorVaultBytecodeMismatchError(address, null, 0, txHash)
|
|
70
|
+
}
|
|
71
|
+
const observedLength = (code.length - 2) / 2
|
|
72
|
+
const observed = keccak256(code).toLowerCase() as Hex
|
|
73
|
+
const expected = OPERATOR_VAULT_RUNTIME_BYTECODE_HASH.toLowerCase() as Hex
|
|
74
|
+
if (observed !== expected) {
|
|
75
|
+
throw new OperatorVaultBytecodeMismatchError(address, observed, observedLength, txHash)
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function shortHash(hash: Hex): string {
|
|
80
|
+
return `${hash.slice(0, 18)}...${hash.slice(-6)}`
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export function formatOperatorVaultBytecodeMismatchDetail(
|
|
84
|
+
err: OperatorVaultBytecodeMismatchError,
|
|
85
|
+
): string {
|
|
86
|
+
const lines = [
|
|
87
|
+
`Vault address: ${err.vaultAddress}`,
|
|
88
|
+
]
|
|
89
|
+
if (err.txHash) lines.push(`Deploy tx: ${err.txHash}`)
|
|
90
|
+
lines.push(`Expected hash: ${shortHash(err.expectedHash)}`)
|
|
91
|
+
if (err.observedHash) {
|
|
92
|
+
lines.push(`Observed hash: ${shortHash(err.observedHash)}`)
|
|
93
|
+
lines.push(`Observed length: ${err.observedLength} bytes (expected ${err.expectedLength})`)
|
|
94
|
+
} else {
|
|
95
|
+
lines.push(`Observed code: none. Address has no code.`)
|
|
96
|
+
}
|
|
97
|
+
return lines.join('\n')
|
|
98
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { getAddress, keccak256, parseAbi, type Address, type Hex } from 'viem'
|
|
2
|
+
|
|
3
|
+
export const OPERATOR_VAULT_ABI = parseAbi([
|
|
4
|
+
'constructor(address registry, uint256 agentId)',
|
|
5
|
+
'function agentOwner(address registry, uint256 agentId) view returns (address)',
|
|
6
|
+
'function metadataOperators(address registry, uint256 agentId, address operator) view returns (bool)',
|
|
7
|
+
'function heldAgent() view returns (address registry, uint256 agentId, address owner)',
|
|
8
|
+
'function setMetadataOperator(address registry, uint256 agentId, address operator, bool approved)',
|
|
9
|
+
'function unwrap(address registry, uint256 agentId, address recipient)',
|
|
10
|
+
'function rotateAgentURI(address registry, uint256 agentId, string newURI)',
|
|
11
|
+
'event AgentDeposited(address indexed registry, uint256 indexed agentId, address indexed owner)',
|
|
12
|
+
'event AgentUnwrapped(address indexed registry, uint256 indexed agentId, address indexed recipient)',
|
|
13
|
+
'event MetadataOperatorChanged(address indexed registry, uint256 indexed agentId, address indexed operator, bool approved)',
|
|
14
|
+
'event AgentURIRotated(address indexed registry, uint256 indexed agentId, string newURI, address indexed signer)',
|
|
15
|
+
])
|
|
16
|
+
|
|
17
|
+
export const OPERATOR_VAULT_ADDRESSES: Record<number, Address> = {
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function operatorVaultAddressForChain(chainId: number): Address | undefined {
|
|
21
|
+
const address = OPERATOR_VAULT_ADDRESSES[chainId]
|
|
22
|
+
return address ? getAddress(address) : undefined
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function resolveConfiguredOperatorVaultAddress(
|
|
26
|
+
operatorVaults: Readonly<Record<string, string>> | undefined,
|
|
27
|
+
chainId: number,
|
|
28
|
+
): Address | undefined {
|
|
29
|
+
const fromConfig = operatorVaults?.[String(chainId)]
|
|
30
|
+
if (fromConfig) return getAddress(fromConfig)
|
|
31
|
+
return operatorVaultAddressForChain(chainId)
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export const OPERATOR_VAULT_DEPLOY_BYTECODE: Hex = '0x608060405234801561001057600080fd5b5060405161090538038061090583398101604081905261002f91610058565b600080546001600160a01b0319166001600160a01b039390931692909217909155600155610092565b6000806040838503121561006b57600080fd5b82516001600160a01b038116811461008257600080fd5b6020939093015192949293505050565b610864806100a16000396000f3fe608060405234801561001057600080fd5b506004361061007d5760003560e01c80631c3bb2851161005b5780631c3bb2851461011157806324a3e2a1146101265780639a61ce8114610149578063b68f43451461015c57600080fd5b8063029ae47e146100825780631400a7e2146100b2578063150b7a02146100e5575b600080fd5b610095610090366004610602565b61016f565b6040516001600160a01b0390911681526020015b60405180910390f35b600254600354600454604080516001600160a01b03948516815260208101939093529216918101919091526060016100a9565b6100f86100f3366004610675565b61019a565b6040516001600160e01b031990911681526020016100a9565b61012461011f3660046106e4565b610293565b005b61013961013436600461073e565b610397565b60405190151581526020016100a9565b61012461015736600461077a565b6103d0565b61012461016a36600461073e565b6104a2565b600061017b83836105ab565b610186576000610193565b6004546001600160a01b03165b9392505050565b600080546001600160a01b0316331415806101b757506001548414155b156101d557604051639667ffdf60e01b815260040160405180910390fd5b6004546001600160a01b0316156101ff5760405163d5a8211560e01b815260040160405180910390fd5b60028054336001600160a01b0319918216179091556003859055600480549091166001600160a01b0387161790556005805460018101909155600003600019016102495760016005555b6040516001600160a01b03861690859033907f7b19af5fe09a0be4ed70b569401ce6fa0de072757b228cc2088350eccc2f987390600090a450630a85bd0160e11b95945050505050565b600061029f858561016f565b9050336001600160a01b038216148015906102c257506102c0858533610397565b155b156102e05760405163ea8e4eb560e01b815260040160405180910390fd5b604051630af28bd360e01b81526001600160a01b03861690630af28bd390610310908790879087906004016107f7565b600060405180830381600087803b15801561032a57600080fd5b505af115801561033e573d6000803e3d6000fd5b50505050336001600160a01b031684866001600160a01b03167fa455a129bb4260e5274d34dd213bcd11399667f80a0615e6cb375728d2e75a90868660405161038892919061081a565b60405180910390a45050505050565b60006103a384846105ab565b80156103c857506005546001600160a01b038316600090815260066020526040902054145b949350505050565b6103da848461016f565b6001600160a01b0316336001600160a01b03161461040b576040516330cd747160e01b815260040160405180910390fd5b8015610432576005546001600160a01b03831660009081526006602052604090205561044c565b6001600160a01b0382166000908152600660205260408120555b816001600160a01b031683856001600160a01b03167fea7d7802a5cc5520abde4345007961a980fcb27dca3030ca6aede6bdc58bd79e84604051610494911515815260200190565b60405180910390a450505050565b60006104ae848461016f565b9050336001600160a01b038216146104d9576040516330cd747160e01b815260040160405180910390fd5b600280546001600160a01b03199081169091556000600355600480549091168155604051632142170760e11b815230918101919091526001600160a01b038381166024830152604482018590528516906342842e0e90606401600060405180830381600087803b15801561054c57600080fd5b505af1158015610560573d6000803e3d6000fd5b50505050816001600160a01b031683856001600160a01b03167fa433e9204e6763dff9274d8c1febdbc7c60aa7d7e85c106d5b978372658438b860405160405180910390a450505050565b6004546000906001600160a01b0316158015906105d557506002546001600160a01b038481169116145b801561019357505060035414919050565b80356001600160a01b03811681146105fd57600080fd5b919050565b6000806040838503121561061557600080fd5b61061e836105e6565b946020939093013593505050565b60008083601f84011261063e57600080fd5b50813567ffffffffffffffff81111561065657600080fd5b60208301915083602082850101111561066e57600080fd5b9250929050565b60008060008060006080868803121561068d57600080fd5b610696866105e6565b94506106a4602087016105e6565b935060408601359250606086013567ffffffffffffffff8111156106c757600080fd5b6106d38882890161062c565b969995985093965092949392505050565b600080600080606085870312156106fa57600080fd5b610703856105e6565b935060208501359250604085013567ffffffffffffffff81111561072657600080fd5b6107328782880161062c565b95989497509550505050565b60008060006060848603121561075357600080fd5b61075c846105e6565b925060208401359150610771604085016105e6565b90509250925092565b6000806000806080858703121561079057600080fd5b610799856105e6565b9350602085013592506107ae604086016105e6565b9150606085013580151581146107c357600080fd5b939692955090935050565b81835281816020850137506000828201602090810191909152601f909101601f19169091010190565b8381526040602082015260006108116040830184866107ce565b95945050505050565b6020815260006103c86020830184866107ce56fea2646970667358221220dd5405c74ca871baa3fc981f170a85015cbe31e79a0e1034acc7154f51ae011664736f6c63430008180033'
|
|
35
|
+
|
|
36
|
+
export const OPERATOR_VAULT_RUNTIME_BYTECODE: Hex = '0x608060405234801561001057600080fd5b506004361061007d5760003560e01c80631c3bb2851161005b5780631c3bb2851461011157806324a3e2a1146101265780639a61ce8114610149578063b68f43451461015c57600080fd5b8063029ae47e146100825780631400a7e2146100b2578063150b7a02146100e5575b600080fd5b610095610090366004610602565b61016f565b6040516001600160a01b0390911681526020015b60405180910390f35b600254600354600454604080516001600160a01b03948516815260208101939093529216918101919091526060016100a9565b6100f86100f3366004610675565b61019a565b6040516001600160e01b031990911681526020016100a9565b61012461011f3660046106e4565b610293565b005b61013961013436600461073e565b610397565b60405190151581526020016100a9565b61012461015736600461077a565b6103d0565b61012461016a36600461073e565b6104a2565b600061017b83836105ab565b610186576000610193565b6004546001600160a01b03165b9392505050565b600080546001600160a01b0316331415806101b757506001548414155b156101d557604051639667ffdf60e01b815260040160405180910390fd5b6004546001600160a01b0316156101ff5760405163d5a8211560e01b815260040160405180910390fd5b60028054336001600160a01b0319918216179091556003859055600480549091166001600160a01b0387161790556005805460018101909155600003600019016102495760016005555b6040516001600160a01b03861690859033907f7b19af5fe09a0be4ed70b569401ce6fa0de072757b228cc2088350eccc2f987390600090a450630a85bd0160e11b95945050505050565b600061029f858561016f565b9050336001600160a01b038216148015906102c257506102c0858533610397565b155b156102e05760405163ea8e4eb560e01b815260040160405180910390fd5b604051630af28bd360e01b81526001600160a01b03861690630af28bd390610310908790879087906004016107f7565b600060405180830381600087803b15801561032a57600080fd5b505af115801561033e573d6000803e3d6000fd5b50505050336001600160a01b031684866001600160a01b03167fa455a129bb4260e5274d34dd213bcd11399667f80a0615e6cb375728d2e75a90868660405161038892919061081a565b60405180910390a45050505050565b60006103a384846105ab565b80156103c857506005546001600160a01b038316600090815260066020526040902054145b949350505050565b6103da848461016f565b6001600160a01b0316336001600160a01b03161461040b576040516330cd747160e01b815260040160405180910390fd5b8015610432576005546001600160a01b03831660009081526006602052604090205561044c565b6001600160a01b0382166000908152600660205260408120555b816001600160a01b031683856001600160a01b03167fea7d7802a5cc5520abde4345007961a980fcb27dca3030ca6aede6bdc58bd79e84604051610494911515815260200190565b60405180910390a450505050565b60006104ae848461016f565b9050336001600160a01b038216146104d9576040516330cd747160e01b815260040160405180910390fd5b600280546001600160a01b03199081169091556000600355600480549091168155604051632142170760e11b815230918101919091526001600160a01b038381166024830152604482018590528516906342842e0e90606401600060405180830381600087803b15801561054c57600080fd5b505af1158015610560573d6000803e3d6000fd5b50505050816001600160a01b031683856001600160a01b03167fa433e9204e6763dff9274d8c1febdbc7c60aa7d7e85c106d5b978372658438b860405160405180910390a450505050565b6004546000906001600160a01b0316158015906105d557506002546001600160a01b038481169116145b801561019357505060035414919050565b80356001600160a01b03811681146105fd57600080fd5b919050565b6000806040838503121561061557600080fd5b61061e836105e6565b946020939093013593505050565b60008083601f84011261063e57600080fd5b50813567ffffffffffffffff81111561065657600080fd5b60208301915083602082850101111561066e57600080fd5b9250929050565b60008060008060006080868803121561068d57600080fd5b610696866105e6565b94506106a4602087016105e6565b935060408601359250606086013567ffffffffffffffff8111156106c757600080fd5b6106d38882890161062c565b969995985093965092949392505050565b600080600080606085870312156106fa57600080fd5b610703856105e6565b935060208501359250604085013567ffffffffffffffff81111561072657600080fd5b6107328782880161062c565b95989497509550505050565b60008060006060848603121561075357600080fd5b61075c846105e6565b925060208401359150610771604085016105e6565b90509250925092565b6000806000806080858703121561079057600080fd5b610799856105e6565b9350602085013592506107ae604086016105e6565b9150606085013580151581146107c357600080fd5b939692955090935050565b81835281816020850137506000828201602090810191909152601f909101601f19169091010190565b8381526040602082015260006108116040830184866107ce565b95945050505050565b6020815260006103c86020830184866107ce56fea2646970667358221220dd5405c74ca871baa3fc981f170a85015cbe31e79a0e1034acc7154f51ae011664736f6c63430008180033'
|
|
37
|
+
|
|
38
|
+
export const OPERATOR_VAULT_RUNTIME_BYTECODE_HASH: Hex = keccak256(OPERATOR_VAULT_RUNTIME_BYTECODE)
|
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
import { getAddress, parseAbi, parseAbiItem, type Address, type PublicClient } from 'viem'
|
|
2
|
+
import { OPERATOR_VAULT_ABI } from './constants.js'
|
|
3
|
+
import { delayMs, OPERATOR_VAULT_POLL_DELAY_MS, OPERATOR_VAULT_POLL_MAX_ATTEMPTS } from './bytecode.js'
|
|
4
|
+
|
|
5
|
+
const DISCOVER_LOG_WINDOW_MAX_ATTEMPTS = 3
|
|
6
|
+
const DISCOVER_LOG_WINDOW_DELAY_MS = 2000
|
|
7
|
+
|
|
8
|
+
export type OperatorVaultReadClient = Pick<PublicClient, 'readContract'>
|
|
9
|
+
|
|
10
|
+
const ERC721_OWNER_OF_ABI = parseAbi([
|
|
11
|
+
'function ownerOf(uint256 tokenId) view returns (address)',
|
|
12
|
+
])
|
|
13
|
+
|
|
14
|
+
export type DiscoverPriorVaultClient = Pick<PublicClient, 'readContract' | 'getBytecode'>
|
|
15
|
+
|
|
16
|
+
export type DiscoverPriorVaultArgs = {
|
|
17
|
+
client: DiscoverPriorVaultClient
|
|
18
|
+
registry: Address
|
|
19
|
+
agentId: bigint
|
|
20
|
+
expectedOwner: Address
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export async function discoverPriorVaultFromTokenOwner(
|
|
24
|
+
args: DiscoverPriorVaultArgs,
|
|
25
|
+
): Promise<{ found: false } | { found: true; vaultAddress: Address }> {
|
|
26
|
+
const registryAddr = getAddress(args.registry)
|
|
27
|
+
const expected = getAddress(args.expectedOwner).toLowerCase()
|
|
28
|
+
const tokenOwner = await args.client.readContract({
|
|
29
|
+
address: registryAddr,
|
|
30
|
+
abi: ERC721_OWNER_OF_ABI,
|
|
31
|
+
functionName: 'ownerOf',
|
|
32
|
+
args: [args.agentId],
|
|
33
|
+
}) as Address
|
|
34
|
+
if (!tokenOwner || tokenOwner.toLowerCase() === '0x0000000000000000000000000000000000000000') {
|
|
35
|
+
return { found: false }
|
|
36
|
+
}
|
|
37
|
+
if (tokenOwner.toLowerCase() === expected) {
|
|
38
|
+
return { found: false }
|
|
39
|
+
}
|
|
40
|
+
const candidate = getAddress(tokenOwner)
|
|
41
|
+
const code = await args.client.getBytecode({ address: candidate })
|
|
42
|
+
if (!code || code === '0x') return { found: false }
|
|
43
|
+
let vaultLevelOwner: Address
|
|
44
|
+
try {
|
|
45
|
+
vaultLevelOwner = await args.client.readContract({
|
|
46
|
+
address: candidate,
|
|
47
|
+
abi: OPERATOR_VAULT_ABI,
|
|
48
|
+
functionName: 'agentOwner',
|
|
49
|
+
args: [registryAddr, args.agentId],
|
|
50
|
+
}) as Address
|
|
51
|
+
} catch {
|
|
52
|
+
return { found: false }
|
|
53
|
+
}
|
|
54
|
+
if (!vaultLevelOwner || vaultLevelOwner.toLowerCase() !== expected) {
|
|
55
|
+
return { found: false }
|
|
56
|
+
}
|
|
57
|
+
return { found: true, vaultAddress: candidate }
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export type IsAgentInVaultArgs = {
|
|
61
|
+
client: OperatorVaultReadClient
|
|
62
|
+
vaultAddress: Address
|
|
63
|
+
registry: Address
|
|
64
|
+
agentId: bigint
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export async function isAgentInVault(
|
|
68
|
+
args: IsAgentInVaultArgs,
|
|
69
|
+
): Promise<{ inVault: boolean; ownerAddress?: Address }> {
|
|
70
|
+
const owner = await args.client.readContract({
|
|
71
|
+
address: getAddress(args.vaultAddress),
|
|
72
|
+
abi: OPERATOR_VAULT_ABI,
|
|
73
|
+
functionName: 'agentOwner',
|
|
74
|
+
args: [getAddress(args.registry), args.agentId],
|
|
75
|
+
}) as Address
|
|
76
|
+
if (!owner || owner.toLowerCase() === '0x0000000000000000000000000000000000000000') {
|
|
77
|
+
return { inVault: false }
|
|
78
|
+
}
|
|
79
|
+
return { inVault: true, ownerAddress: getAddress(owner) }
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export async function confirmAgentInVault(
|
|
83
|
+
args: IsAgentInVaultArgs,
|
|
84
|
+
): Promise<{ inVault: true; ownerAddress: Address }> {
|
|
85
|
+
let lastErr: unknown
|
|
86
|
+
for (let attempt = 0; attempt < OPERATOR_VAULT_POLL_MAX_ATTEMPTS; attempt++) {
|
|
87
|
+
if (attempt > 0) await delayMs(OPERATOR_VAULT_POLL_DELAY_MS)
|
|
88
|
+
try {
|
|
89
|
+
const status = await isAgentInVault(args)
|
|
90
|
+
lastErr = undefined
|
|
91
|
+
if (status.inVault && status.ownerAddress) {
|
|
92
|
+
return { inVault: true, ownerAddress: status.ownerAddress }
|
|
93
|
+
}
|
|
94
|
+
} catch (err) {
|
|
95
|
+
lastErr = err
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
if (lastErr) throw lastErr
|
|
99
|
+
throw new Error(
|
|
100
|
+
`Operator delegation vault ${getAddress(args.vaultAddress)} does not hold agent token #${args.agentId.toString()} for registry ${getAddress(args.registry)} after the deposit-confirmation budget was exhausted. The deposit transaction may have been re-orged or applied to the wrong vault. Re-run the switch.`,
|
|
101
|
+
)
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export type ConfirmAgentWithdrawnArgs = IsAgentInVaultArgs & {
|
|
105
|
+
recipient: Address
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export async function confirmAgentWithdrawnFromVault(
|
|
109
|
+
args: ConfirmAgentWithdrawnArgs,
|
|
110
|
+
): Promise<{ inVault: false; ownerAddress: Address }> {
|
|
111
|
+
const recipient = getAddress(args.recipient)
|
|
112
|
+
let lastErr: unknown
|
|
113
|
+
let lastObserved: string | undefined
|
|
114
|
+
for (let attempt = 0; attempt < OPERATOR_VAULT_POLL_MAX_ATTEMPTS; attempt++) {
|
|
115
|
+
if (attempt > 0) await delayMs(OPERATOR_VAULT_POLL_DELAY_MS)
|
|
116
|
+
try {
|
|
117
|
+
const status = await isAgentInVault(args)
|
|
118
|
+
const tokenOwner = await args.client.readContract({
|
|
119
|
+
address: getAddress(args.registry),
|
|
120
|
+
abi: ERC721_OWNER_OF_ABI,
|
|
121
|
+
functionName: 'ownerOf',
|
|
122
|
+
args: [args.agentId],
|
|
123
|
+
}) as Address
|
|
124
|
+
const ownerAddress = getAddress(tokenOwner)
|
|
125
|
+
lastErr = undefined
|
|
126
|
+
lastObserved = status.inVault
|
|
127
|
+
? `vault owner ${status.ownerAddress ?? 'unknown'}, token owner ${ownerAddress}`
|
|
128
|
+
: `token owner ${ownerAddress}`
|
|
129
|
+
if (!status.inVault && ownerAddress.toLowerCase() === recipient.toLowerCase()) {
|
|
130
|
+
return { inVault: false, ownerAddress }
|
|
131
|
+
}
|
|
132
|
+
} catch (err) {
|
|
133
|
+
lastErr = err
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
if (lastErr) throw lastErr
|
|
137
|
+
throw new Error(
|
|
138
|
+
`Operator delegation vault ${getAddress(args.vaultAddress)} did not release agent token #${args.agentId.toString()} to ${recipient} after the withdraw-confirmation budget was exhausted. Last observed: ${lastObserved ?? 'unknown'}.`,
|
|
139
|
+
)
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
const AGENT_DEPOSITED_EVENT = parseAbiItem(
|
|
143
|
+
'event AgentDeposited(address indexed registry, uint256 indexed agentId, address indexed owner)',
|
|
144
|
+
)
|
|
145
|
+
|
|
146
|
+
export type DiscoverVaultedTokensArgs = {
|
|
147
|
+
client: PublicClient
|
|
148
|
+
vaultAddress: Address
|
|
149
|
+
registry: Address
|
|
150
|
+
depositorAddress: Address
|
|
151
|
+
fromBlock?: bigint
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
const DISCOVER_VAULTED_TOKENS_BLOCK_WINDOW = 9_000n
|
|
155
|
+
|
|
156
|
+
export async function discoverVaultedTokens(
|
|
157
|
+
args: DiscoverVaultedTokensArgs,
|
|
158
|
+
): Promise<Array<{ registry: Address; agentId: bigint }>> {
|
|
159
|
+
const vaultAddr = getAddress(args.vaultAddress)
|
|
160
|
+
const registryAddr = getAddress(args.registry)
|
|
161
|
+
const depositor = getAddress(args.depositorAddress)
|
|
162
|
+
const fromBlock = args.fromBlock ?? 0n
|
|
163
|
+
const latest = await args.client.getBlockNumber()
|
|
164
|
+
const fetchWindow = async (cursorFrom: bigint, cursorTo: bigint) =>
|
|
165
|
+
args.client.getLogs({
|
|
166
|
+
address: vaultAddr,
|
|
167
|
+
event: AGENT_DEPOSITED_EVENT,
|
|
168
|
+
args: { registry: registryAddr, owner: depositor },
|
|
169
|
+
fromBlock: cursorFrom,
|
|
170
|
+
toBlock: cursorTo,
|
|
171
|
+
})
|
|
172
|
+
type DepositLog = Awaited<ReturnType<typeof fetchWindow>>[number]
|
|
173
|
+
const fetchWindowWithRetry = async (cursorFrom: bigint, cursorTo: bigint): Promise<DepositLog[]> => {
|
|
174
|
+
let lastErr: unknown
|
|
175
|
+
for (let attempt = 0; attempt < DISCOVER_LOG_WINDOW_MAX_ATTEMPTS; attempt++) {
|
|
176
|
+
if (attempt > 0) await delayMs(DISCOVER_LOG_WINDOW_DELAY_MS)
|
|
177
|
+
try {
|
|
178
|
+
return await fetchWindow(cursorFrom, cursorTo)
|
|
179
|
+
} catch (err) {
|
|
180
|
+
lastErr = err
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
throw lastErr ?? new Error(`failed to fetch AgentDeposited logs for window [${cursorFrom},${cursorTo}]`)
|
|
184
|
+
}
|
|
185
|
+
const logs: DepositLog[] = []
|
|
186
|
+
let cursor = fromBlock
|
|
187
|
+
while (cursor <= latest) {
|
|
188
|
+
const windowEnd = cursor + DISCOVER_VAULTED_TOKENS_BLOCK_WINDOW - 1n
|
|
189
|
+
const toBlock = windowEnd < latest ? windowEnd : latest
|
|
190
|
+
const window = await fetchWindowWithRetry(cursor, toBlock)
|
|
191
|
+
logs.push(...window)
|
|
192
|
+
if (toBlock === latest) break
|
|
193
|
+
cursor = toBlock + 1n
|
|
194
|
+
}
|
|
195
|
+
const seen = new Set<string>()
|
|
196
|
+
const candidates: Array<{ registry: Address; agentId: bigint }> = []
|
|
197
|
+
for (const log of logs) {
|
|
198
|
+
const agentId = log.args.agentId
|
|
199
|
+
if (agentId === undefined) continue
|
|
200
|
+
const key = `${registryAddr.toLowerCase()}:${agentId.toString()}`
|
|
201
|
+
if (seen.has(key)) continue
|
|
202
|
+
seen.add(key)
|
|
203
|
+
candidates.push({ registry: registryAddr, agentId })
|
|
204
|
+
}
|
|
205
|
+
const out: Array<{ registry: Address; agentId: bigint }> = []
|
|
206
|
+
for (const candidate of candidates) {
|
|
207
|
+
const status = await isAgentInVault({
|
|
208
|
+
client: args.client,
|
|
209
|
+
vaultAddress: vaultAddr,
|
|
210
|
+
registry: candidate.registry,
|
|
211
|
+
agentId: candidate.agentId,
|
|
212
|
+
})
|
|
213
|
+
if (status.inVault && status.ownerAddress?.toLowerCase() === depositor.toLowerCase()) {
|
|
214
|
+
out.push(candidate)
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
return out
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
export type ReadMetadataOperatorsArgs = {
|
|
221
|
+
client: OperatorVaultReadClient
|
|
222
|
+
vaultAddress: Address
|
|
223
|
+
registry: Address
|
|
224
|
+
agentId: bigint
|
|
225
|
+
candidates: readonly Address[]
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
export async function readMetadataOperators(
|
|
229
|
+
args: ReadMetadataOperatorsArgs,
|
|
230
|
+
): Promise<Record<Address, boolean>> {
|
|
231
|
+
const out: Record<Address, boolean> = {}
|
|
232
|
+
for (const candidate of args.candidates) {
|
|
233
|
+
try {
|
|
234
|
+
const approved = await args.client.readContract({
|
|
235
|
+
address: getAddress(args.vaultAddress),
|
|
236
|
+
abi: OPERATOR_VAULT_ABI,
|
|
237
|
+
functionName: 'metadataOperators',
|
|
238
|
+
args: [getAddress(args.registry), args.agentId, getAddress(candidate)],
|
|
239
|
+
}) as boolean
|
|
240
|
+
out[candidate] = Boolean(approved)
|
|
241
|
+
} catch {
|
|
242
|
+
out[candidate] = false
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
return out
|
|
246
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { encodeFunctionData, getAddress, parseAbi, type Address, type Hex } from 'viem'
|
|
2
|
+
import { OPERATOR_VAULT_ABI } from './constants.js'
|
|
3
|
+
|
|
4
|
+
const ERC721_SAFE_TRANSFER_ABI = parseAbi([
|
|
5
|
+
'function safeTransferFrom(address from, address to, uint256 tokenId)',
|
|
6
|
+
])
|
|
7
|
+
|
|
8
|
+
export type DepositAgentArgs = {
|
|
9
|
+
registry: Address
|
|
10
|
+
agentId: bigint
|
|
11
|
+
walletAddress: Address
|
|
12
|
+
vaultAddress: Address
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function encodeDepositAgent(args: DepositAgentArgs): { to: Address; data: Hex } {
|
|
16
|
+
return {
|
|
17
|
+
to: getAddress(args.registry),
|
|
18
|
+
data: encodeFunctionData({
|
|
19
|
+
abi: ERC721_SAFE_TRANSFER_ABI,
|
|
20
|
+
functionName: 'safeTransferFrom',
|
|
21
|
+
args: [getAddress(args.walletAddress), getAddress(args.vaultAddress), args.agentId],
|
|
22
|
+
}),
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export type SetMetadataOperatorArgs = {
|
|
27
|
+
registry: Address
|
|
28
|
+
agentId: bigint
|
|
29
|
+
operator: Address
|
|
30
|
+
approved: boolean
|
|
31
|
+
vaultAddress: Address
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function encodeSetMetadataOperator(args: SetMetadataOperatorArgs): { to: Address; data: Hex } {
|
|
35
|
+
return {
|
|
36
|
+
to: getAddress(args.vaultAddress),
|
|
37
|
+
data: encodeFunctionData({
|
|
38
|
+
abi: OPERATOR_VAULT_ABI,
|
|
39
|
+
functionName: 'setMetadataOperator',
|
|
40
|
+
args: [getAddress(args.registry), args.agentId, getAddress(args.operator), args.approved],
|
|
41
|
+
}),
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export type RotateAgentURIArgs = {
|
|
46
|
+
registry: Address
|
|
47
|
+
agentId: bigint
|
|
48
|
+
newURI: string
|
|
49
|
+
vaultAddress: Address
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function encodeRotateAgentURI(args: RotateAgentURIArgs): { to: Address; data: Hex } {
|
|
53
|
+
return {
|
|
54
|
+
to: getAddress(args.vaultAddress),
|
|
55
|
+
data: encodeFunctionData({
|
|
56
|
+
abi: OPERATOR_VAULT_ABI,
|
|
57
|
+
functionName: 'rotateAgentURI',
|
|
58
|
+
args: [getAddress(args.registry), args.agentId, args.newURI],
|
|
59
|
+
}),
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export type UnwrapAgentArgs = {
|
|
64
|
+
registry: Address
|
|
65
|
+
agentId: bigint
|
|
66
|
+
recipient: Address
|
|
67
|
+
vaultAddress: Address
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export function encodeUnwrapAgent(
|
|
71
|
+
args: UnwrapAgentArgs,
|
|
72
|
+
): { to: Address; data: Hex } {
|
|
73
|
+
return {
|
|
74
|
+
to: getAddress(args.vaultAddress),
|
|
75
|
+
data: encodeFunctionData({
|
|
76
|
+
abi: OPERATOR_VAULT_ABI,
|
|
77
|
+
functionName: 'unwrap',
|
|
78
|
+
args: [getAddress(args.registry), args.agentId, getAddress(args.recipient)],
|
|
79
|
+
}),
|
|
80
|
+
}
|
|
81
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
export {
|
|
2
|
+
OPERATOR_VAULT_ABI,
|
|
3
|
+
OPERATOR_VAULT_ADDRESSES,
|
|
4
|
+
OPERATOR_VAULT_DEPLOY_BYTECODE,
|
|
5
|
+
OPERATOR_VAULT_RUNTIME_BYTECODE,
|
|
6
|
+
OPERATOR_VAULT_RUNTIME_BYTECODE_HASH,
|
|
7
|
+
operatorVaultAddressForChain,
|
|
8
|
+
resolveConfiguredOperatorVaultAddress,
|
|
9
|
+
} from './operatorVault/constants.js'
|
|
10
|
+
export {
|
|
11
|
+
OperatorVaultBytecodeMismatchError,
|
|
12
|
+
assertVaultBytecode,
|
|
13
|
+
formatOperatorVaultBytecodeMismatchDetail,
|
|
14
|
+
} from './operatorVault/bytecode.js'
|
|
15
|
+
export type { AssertVaultBytecodeClient } from './operatorVault/bytecode.js'
|
|
16
|
+
export {
|
|
17
|
+
encodeDepositAgent,
|
|
18
|
+
encodeRotateAgentURI,
|
|
19
|
+
encodeSetMetadataOperator,
|
|
20
|
+
encodeUnwrapAgent,
|
|
21
|
+
} from './operatorVault/transactions.js'
|
|
22
|
+
export type {
|
|
23
|
+
DepositAgentArgs,
|
|
24
|
+
RotateAgentURIArgs,
|
|
25
|
+
SetMetadataOperatorArgs,
|
|
26
|
+
UnwrapAgentArgs,
|
|
27
|
+
} from './operatorVault/transactions.js'
|
|
28
|
+
export {
|
|
29
|
+
confirmAgentWithdrawnFromVault,
|
|
30
|
+
confirmAgentInVault,
|
|
31
|
+
discoverPriorVaultFromTokenOwner,
|
|
32
|
+
discoverVaultedTokens,
|
|
33
|
+
isAgentInVault,
|
|
34
|
+
readMetadataOperators,
|
|
35
|
+
} from './operatorVault/read.js'
|
|
36
|
+
export type {
|
|
37
|
+
ConfirmAgentWithdrawnArgs,
|
|
38
|
+
DiscoverPriorVaultArgs,
|
|
39
|
+
DiscoverPriorVaultClient,
|
|
40
|
+
DiscoverVaultedTokensArgs,
|
|
41
|
+
IsAgentInVaultArgs,
|
|
42
|
+
OperatorVaultReadClient,
|
|
43
|
+
ReadMetadataOperatorsArgs,
|
|
44
|
+
} from './operatorVault/read.js'
|
|
@@ -1,16 +1,10 @@
|
|
|
1
1
|
export const PINATA_UPLOAD_API_URL = 'https://uploads.pinata.cloud/v3/files'
|
|
2
2
|
export const PINATA_AUTH_TEST_URL = 'https://api.pinata.cloud/data/testAuthentication'
|
|
3
|
-
|
|
3
|
+
const DEFAULT_PINATA_GATEWAY_URL = 'https://gateway.pinata.cloud'
|
|
4
4
|
export const DEFAULT_IPFS_API_URL = process.env.ETHAGENT_IPFS_API_URL?.trim() || PINATA_UPLOAD_API_URL
|
|
5
5
|
|
|
6
6
|
export type FetchLike = (input: string | URL, init?: RequestInit) => Promise<Response>
|
|
7
7
|
|
|
8
|
-
export type IpfsClient = {
|
|
9
|
-
apiUrl: string
|
|
10
|
-
add: (content: string | Uint8Array) => Promise<IpfsAddResult>
|
|
11
|
-
cat: (cid: string) => Promise<Uint8Array>
|
|
12
|
-
}
|
|
13
|
-
|
|
14
8
|
export type IpfsAddResult = {
|
|
15
9
|
cid: string
|
|
16
10
|
pinVerified: boolean
|
|
@@ -21,19 +15,6 @@ type IpfsOptions = {
|
|
|
21
15
|
pinataJwt?: string
|
|
22
16
|
}
|
|
23
17
|
|
|
24
|
-
export function createIpfsClient(apiUrl = DEFAULT_IPFS_API_URL, fetchImpl: FetchLike = fetch, options: IpfsOptions = {}): IpfsClient {
|
|
25
|
-
const base = normalizeApiUrl(apiUrl)
|
|
26
|
-
return {
|
|
27
|
-
apiUrl: base,
|
|
28
|
-
add: content => addToIpfs(base, content, fetchImpl, options),
|
|
29
|
-
cat: cid => catFromIpfs(base, cid, fetchImpl),
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
export function needsPinataJwt(apiUrl = DEFAULT_IPFS_API_URL, options: IpfsOptions = {}): boolean {
|
|
34
|
-
return isPinataUploadUrl(apiUrl) && !pinataJwt(options)
|
|
35
|
-
}
|
|
36
|
-
|
|
37
18
|
export function extractPinataJwt(input: string): string {
|
|
38
19
|
const trimmed = input.trim()
|
|
39
20
|
const matches = trimmed.match(/\b[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\b/g) ?? []
|
|
@@ -111,11 +92,13 @@ export async function catFromIpfs(
|
|
|
111
92
|
apiUrl: string,
|
|
112
93
|
cid: string,
|
|
113
94
|
fetchImpl: FetchLike = fetch,
|
|
95
|
+
options: { signal?: AbortSignal } = {},
|
|
114
96
|
): Promise<Uint8Array> {
|
|
115
|
-
if (isPinataUploadUrl(apiUrl)) return catFromPinata(cid, fetchImpl)
|
|
97
|
+
if (isPinataUploadUrl(apiUrl)) return catFromPinata(cid, fetchImpl, options.signal)
|
|
116
98
|
const arg = encodeURIComponent(cid.trim())
|
|
117
99
|
const response = await fetchImpl(`${normalizeApiUrl(apiUrl)}/api/v0/cat?arg=${arg}`, {
|
|
118
100
|
method: 'POST',
|
|
101
|
+
...(options.signal ? { signal: options.signal } : {}),
|
|
119
102
|
})
|
|
120
103
|
if (!response.ok) throw new Error(`IPFS cat failed: ${response.status} ${response.statusText}`)
|
|
121
104
|
return new Uint8Array(await response.arrayBuffer())
|
|
@@ -163,17 +146,36 @@ async function addFileToPinata(
|
|
|
163
146
|
const data = await response.json() as { data?: { cid?: string }; IpfsHash?: string; Hash?: string; Cid?: string }
|
|
164
147
|
const cid = data.data?.cid ?? data.IpfsHash ?? data.Hash ?? data.Cid
|
|
165
148
|
if (!cid) throw new Error('IPFS upload response did not include a CID')
|
|
166
|
-
|
|
149
|
+
const verified = await verifyCidReachable(cid, fetchImpl)
|
|
150
|
+
return { cid, pinVerified: verified, provider: 'pinata' }
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
async function verifyCidReachable(
|
|
154
|
+
cid: string,
|
|
155
|
+
fetchImpl: FetchLike,
|
|
156
|
+
): Promise<boolean> {
|
|
157
|
+
const gateway = normalizeApiUrl(process.env.PINATA_GATEWAY_URL?.trim() || DEFAULT_PINATA_GATEWAY_URL)
|
|
158
|
+
const path = cid.trim().split('/').map(part => encodeURIComponent(part)).join('/')
|
|
159
|
+
const url = `${gateway}/ipfs/${path}`
|
|
160
|
+
for (const delayMs of [0, 1500]) {
|
|
161
|
+
if (delayMs > 0) await new Promise(resolve => setTimeout(resolve, delayMs))
|
|
162
|
+
try {
|
|
163
|
+
const response = await fetchImpl(url, { method: 'HEAD' })
|
|
164
|
+
if (response.ok) return true
|
|
165
|
+
} catch {
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
return false
|
|
167
169
|
}
|
|
168
170
|
|
|
169
171
|
function pinataJwt(options: IpfsOptions): string | undefined {
|
|
170
172
|
return options.pinataJwt?.trim() || process.env.PINATA_JWT?.trim() || undefined
|
|
171
173
|
}
|
|
172
174
|
|
|
173
|
-
async function catFromPinata(cid: string, fetchImpl: FetchLike): Promise<Uint8Array> {
|
|
175
|
+
async function catFromPinata(cid: string, fetchImpl: FetchLike, signal?: AbortSignal): Promise<Uint8Array> {
|
|
174
176
|
const gateway = normalizeApiUrl(process.env.PINATA_GATEWAY_URL?.trim() || DEFAULT_PINATA_GATEWAY_URL)
|
|
175
177
|
const path = cid.trim().split('/').map(part => encodeURIComponent(part)).join('/')
|
|
176
|
-
const response = await fetchImpl(`${gateway}/ipfs/${path}
|
|
178
|
+
const response = await fetchImpl(`${gateway}/ipfs/${path}`, signal ? { signal } : {})
|
|
177
179
|
if (!response.ok) throw new Error(`IPFS fetch failed: ${response.status} ${response.statusText}`)
|
|
178
180
|
return new Uint8Array(await response.arrayBuffer())
|
|
179
181
|
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { numberToHex } from 'viem'
|
|
2
|
+
import type {
|
|
3
|
+
PreparedGasFee,
|
|
4
|
+
PrepareTransactionGasFeeArgs,
|
|
5
|
+
PrepareTransactionGasFeeClient,
|
|
6
|
+
} from './types.js'
|
|
7
|
+
|
|
8
|
+
const GAS_FEE_PREP_MAX_ATTEMPTS = 5
|
|
9
|
+
const GAS_FEE_PREP_DELAY_MS = 1500
|
|
10
|
+
|
|
11
|
+
function gasFeeDelay(ms: number): Promise<void> {
|
|
12
|
+
return new Promise(resolve => setTimeout(resolve, ms))
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export async function prepareTransactionGasFee(args: PrepareTransactionGasFeeArgs): Promise<PreparedGasFee> {
|
|
16
|
+
let lastErr: unknown
|
|
17
|
+
for (let attempt = 0; attempt < GAS_FEE_PREP_MAX_ATTEMPTS; attempt++) {
|
|
18
|
+
if (attempt > 0) await gasFeeDelay(GAS_FEE_PREP_DELAY_MS)
|
|
19
|
+
try {
|
|
20
|
+
const estimateArgs: Parameters<PrepareTransactionGasFeeClient['estimateGas']>[0] = {
|
|
21
|
+
account: args.account,
|
|
22
|
+
data: args.data,
|
|
23
|
+
...(args.to ? { to: args.to } : {}),
|
|
24
|
+
...(args.value !== undefined ? { value: args.value } : {}),
|
|
25
|
+
}
|
|
26
|
+
const [gas, fees] = await Promise.all([
|
|
27
|
+
args.client.estimateGas(estimateArgs),
|
|
28
|
+
args.client.estimateFeesPerGas(),
|
|
29
|
+
])
|
|
30
|
+
const gasWithBuffer = (gas * 12n) / 10n
|
|
31
|
+
return {
|
|
32
|
+
gas: numberToHex(gasWithBuffer),
|
|
33
|
+
maxFeePerGas: numberToHex(fees.maxFeePerGas),
|
|
34
|
+
maxPriorityFeePerGas: numberToHex(fees.maxPriorityFeePerGas),
|
|
35
|
+
}
|
|
36
|
+
} catch (err) {
|
|
37
|
+
lastErr = err
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
throw lastErr ?? new Error('failed to prepare transaction gas/fee')
|
|
41
|
+
}
|