ethagent 2.1.1 → 2.2.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.
Files changed (60) hide show
  1. package/package.json +1 -1
  2. package/src/auth/openaiOAuth/credentials.ts +47 -0
  3. package/src/auth/openaiOAuth/crypto.ts +23 -0
  4. package/src/auth/openaiOAuth/index.ts +238 -0
  5. package/src/auth/openaiOAuth/landingPage.ts +125 -0
  6. package/src/auth/openaiOAuth/listener.ts +151 -0
  7. package/src/auth/openaiOAuth/refresh.ts +70 -0
  8. package/src/auth/openaiOAuth/shared.ts +115 -0
  9. package/src/chat/chatSessionState.ts +2 -1
  10. package/src/chat/commands.ts +2 -1
  11. package/src/identity/ens/agentRecords.ts +5 -19
  12. package/src/identity/ens/ensAutomation/setup.ts +0 -1
  13. package/src/identity/ens/ensAutomation/types.ts +0 -1
  14. package/src/identity/hub/OperationalRoutes.tsx +2 -11
  15. package/src/identity/hub/components/IdentitySummary.tsx +8 -3
  16. package/src/identity/hub/components/MenuScreen.tsx +1 -2
  17. package/src/identity/hub/components/menuFlagsFromReconciliation.ts +1 -3
  18. package/src/identity/hub/effects/ens/transactions.ts +15 -15
  19. package/src/identity/hub/effects/index.ts +0 -1
  20. package/src/identity/hub/effects/profile/profileState.ts +12 -4
  21. package/src/identity/hub/effects/publicProfile/runPublicProfileSave.ts +37 -159
  22. package/src/identity/hub/effects/rebackup/runRebackup.ts +2 -2
  23. package/src/identity/hub/effects/restoreAdmin.ts +2 -61
  24. package/src/identity/hub/effects/shared/sync.ts +3 -44
  25. package/src/identity/hub/flows/custody/CustodyEditFlow.tsx +1 -39
  26. package/src/identity/hub/flows/custody/custodyFlowActions.ts +5 -3
  27. package/src/identity/hub/flows/custody/custodyFlowTypes.ts +1 -1
  28. package/src/identity/hub/flows/ens/EnsEditAdvancedScreens.tsx +80 -175
  29. package/src/identity/hub/flows/ens/EnsEditFlow.tsx +20 -75
  30. package/src/identity/hub/flows/ens/EnsEditMaintenanceScreens.tsx +16 -56
  31. package/src/identity/hub/flows/ens/EnsEditReviewScreens.tsx +0 -18
  32. package/src/identity/hub/flows/ens/EnsEditRunners.tsx +0 -136
  33. package/src/identity/hub/flows/ens/EnsEditShared.tsx +5 -4
  34. package/src/identity/hub/flows/ens/EnsEditSimpleScreens.tsx +56 -205
  35. package/src/identity/hub/flows/ens/IdentityHubEnsFlow.tsx +7 -0
  36. package/src/identity/hub/flows/ens/OperatorWalletsScreen.tsx +0 -31
  37. package/src/identity/hub/flows/ens/ensEditCopy.ts +1 -1
  38. package/src/identity/hub/flows/ens/ensEditTypes.ts +6 -20
  39. package/src/identity/hub/flows/profile/EditProfileFlow.tsx +7 -0
  40. package/src/identity/hub/flows/restore/RestoreFlow.tsx +5 -5
  41. package/src/identity/hub/reconciliation/agentReconciliation/hook.ts +0 -1
  42. package/src/identity/hub/reconciliation/agentReconciliation/run.ts +1 -34
  43. package/src/identity/hub/reconciliation/agentReconciliation/types.ts +0 -4
  44. package/src/identity/hub/reconciliation/index.ts +0 -7
  45. package/src/identity/hub/reconciliation/walletSetup.ts +1 -194
  46. package/src/identity/wallet/browserWallet/types.ts +0 -5
  47. package/src/identity/wallet/page/copy.ts +1 -31
  48. package/src/identity/wallet/walletPurposeCompat.ts +0 -2
  49. package/src/models/ModelPicker.tsx +246 -8
  50. package/src/models/catalog.ts +28 -1
  51. package/src/models/modelPickerOptions.ts +15 -1
  52. package/src/providers/openai-responses-format.ts +156 -0
  53. package/src/providers/openai-responses.ts +276 -0
  54. package/src/providers/registry.ts +85 -8
  55. package/src/runtime/systemPrompt.ts +1 -1
  56. package/src/runtime/turn.ts +0 -1
  57. package/src/storage/secrets.ts +4 -1
  58. package/src/tools/privateContinuityEditTool.ts +6 -0
  59. package/src/utils/openExternal.ts +20 -10
  60. package/src/identity/ens/ensRegistration.ts +0 -199
@@ -1,4 +1,4 @@
1
- import { getAddress, type Address, type Hex } from 'viem'
1
+ import { getAddress, type Address } from 'viem'
2
2
  import type { EthagentIdentity } from '../../../../storage/config.js'
3
3
  import {
4
4
  createWalletRestoreAccessChallenge,
@@ -32,19 +32,13 @@ import { resolveValidatedPinataJwt, savePinataJwt } from '../../../storage/pinat
32
32
  import {
33
33
  openBrowserWalletSession,
34
34
  requestBrowserWalletSignatureAndTransaction,
35
- type BrowserWalletSession,
36
35
  type BrowserWalletSignature,
37
36
  type WalletPurpose,
38
37
  } from '../../../wallet/browserWallet.js'
39
38
  import type { ProfileUpdates, Step } from '../../identityHubReducer.js'
40
- import { reconcileWalletSetup } from '../../reconciliation/index.js'
41
39
  import { acquireTxGuard, releaseTxGuard } from '../../txGuard.js'
42
40
  import type { EffectCallbacks } from '../types.js'
43
41
  import { awaitConfirmedReceipt } from '../receipts.js'
44
- import {
45
- createMainnetEnsPublicClient,
46
- runUpdateEnsRecords,
47
- } from '../ens/transactions.js'
48
42
  import {
49
43
  assertVerifiedPin,
50
44
  deriveAgentName,
@@ -55,7 +49,7 @@ import {
55
49
  appendResolverSyncWarning,
56
50
  markCurrentContinuityFilesPublished,
57
51
  resolverSyncWarningMessage,
58
- syncResolverApprovalsAfterOwnerSave,
52
+ syncVaultOperatorsAfterOwnerSave,
59
53
  } from '../shared/sync.js'
60
54
  import {
61
55
  assertSnapshotSaveSignerAuthorized,
@@ -241,7 +235,7 @@ async function runPublicProfileSigningInner(
241
235
  }
242
236
  await writePublicSkillsFile(nextIdentity, result.prepared.publicSkillsJson)
243
237
  await markCurrentContinuityFilesPublished(nextIdentity)
244
- const resolverSyncWarning = await syncResolverApprovalsAfterOwnerSave({
238
+ const resolverSyncWarning = await syncVaultOperatorsAfterOwnerSave({
245
239
  beforeIdentity: step.identity,
246
240
  afterIdentity: nextIdentity,
247
241
  registry: step.registry,
@@ -263,92 +257,21 @@ async function runOperatorWalletPublicProfileSave(
263
257
  callbacks: EffectCallbacks,
264
258
  ): Promise<void> {
265
259
  if (!step.identity.agentId) throw new Error('Cannot update public profile: identity is missing an agent token ID')
266
- const baseState = (step.identity.state ?? {}) as Record<string, unknown>
267
- const ensName = typeof baseState.ensName === 'string' ? baseState.ensName.trim() : ''
268
- if (!ensName) {
269
- throw new Error('Operator wallet profile updates require an ENS subdomain, connect the owner wallet to set one up first')
260
+ if (!step.vaultAddress) {
261
+ throw new Error('Operator-wallet profile updates require a Vault. Set one up via Advanced Mode, or connect the owner wallet to update the profile.')
270
262
  }
271
263
  const snapshotOwner = ownerAddressForSnapshotSave(step.identity, step.profileUpdates)
272
264
  const walletAccess = walletRestoreAccessContext(step.identity, step.registry, step.profileUpdates, snapshotOwner)
273
265
  if (!walletAccess) throw new Error('Operator-wallet profile updates require wallet restore access context')
274
266
  const challengePurpose: WalletChallengePurpose = 'restore-operator'
275
- if (step.vaultAddress) {
276
- await runOperatorWalletVaultPublicProfileSave({
277
- step,
278
- callbacks,
279
- snapshotOwner,
280
- walletAccess,
281
- challengePurpose,
282
- vaultAddress: step.vaultAddress,
283
- })
284
- return
285
- }
286
-
287
- const reconcileBaseState = (step.identity.state ?? {}) as Record<string, unknown>
288
- const activeOperator = typeof reconcileBaseState.activeOperatorAddress === 'string'
289
- ? reconcileBaseState.activeOperatorAddress.trim()
290
- : ''
291
- if (activeOperator) {
292
- const fixPlan = await reconcileWalletSetup({ identity: step.identity, registry: step.registry })
293
- const stale = fixPlan.items.find(
294
- (item): item is Extract<typeof item, { kind: 'missing-approval' }> =>
295
- item.kind === 'missing-approval'
296
- && item.address.toLowerCase() === activeOperator.toLowerCase(),
297
- )
298
- if (stale) {
299
- throw new Error(
300
- `Operator wallet ${stale.address} is no longer approved as a resolver delegate for this agent's ENS subdomain. Connect the owner wallet and run "Fix Records" to restore the approval before retrying.`,
301
- )
302
- }
303
- }
304
-
305
- const session = await openBrowserWalletSession({ onReady: callbacks.onWalletReady })
306
- try {
307
- const wallet = await session.requestSignature({
308
- chainId: step.registry.chainId,
309
- messageForAccount: account => createWalletRestoreAccessChallenge({
310
- token: walletAccess.token,
311
- ownerAddress: snapshotOwner,
312
- walletAddress: account,
313
- accessEpoch: walletAccess.accessEpoch,
314
- purpose: challengePurpose,
315
- }),
316
- purpose: 'update-profile-operator',
317
- })
318
- assertSnapshotSaveSignerAuthorized(step.identity, step.profileUpdates, wallet.account, snapshotOwner, walletAccess)
319
- const prepared = await prepareOperatorProfileArtifacts({
320
- step,
321
- wallet,
322
- snapshotOwner,
323
- walletAccess,
324
- challengePurpose,
325
- includeMetadata: false,
326
- })
327
- await publishOperatorProfileEnsRecord({
328
- ensName,
329
- signer: wallet.account,
330
- registry: step.registry,
331
- agentCardUri: prepared.agentCardUri,
332
- callbacks,
333
- session,
334
- })
335
-
336
- await writePublicSkillsFile(prepared.nextIdentity, prepared.publicSkillsJson)
337
- await markCurrentContinuityFilesPublished(prepared.nextIdentity).catch(() => null)
338
- await recordPublishedContinuitySnapshot({
339
- identity: prepared.nextIdentity,
340
- label: 'operator-wallet ENS profile update',
341
- }).catch(() => null)
342
-
343
- await callbacks.onIdentityComplete(
344
- prepared.nextIdentity,
345
- 'Profile pointer published to ENS records (org.ethagent.profile). ERC-8004 metadata stays put until owner wallet next signs.',
346
- 'update',
347
- )
348
- } finally {
349
- await session.close().catch(() => null)
350
- callbacks.onWalletReady(null)
351
- }
267
+ await runOperatorWalletVaultPublicProfileSave({
268
+ step,
269
+ callbacks,
270
+ snapshotOwner,
271
+ walletAccess,
272
+ challengePurpose,
273
+ vaultAddress: step.vaultAddress,
274
+ })
352
275
  }
353
276
 
354
277
  type WalletAccessContext = NonNullable<ReturnType<typeof walletRestoreAccessContext>>
@@ -391,11 +314,7 @@ async function runOperatorWalletVaultPublicProfileSave(args: {
391
314
  snapshotOwner,
392
315
  walletAccess,
393
316
  challengePurpose,
394
- includeMetadata: true,
395
317
  })
396
- if (!prepared.agentUri || !prepared.metadataCid) {
397
- throw new Error('Vault profile update did not prepare ERC-8004 metadata')
398
- }
399
318
 
400
319
  const vaultCall = encodeRotateAgentURI({
401
320
  registry: getAddress(step.registry.identityRegistryAddress),
@@ -447,9 +366,8 @@ async function runOperatorWalletVaultPublicProfileSave(args: {
447
366
  type OperatorProfileArtifacts = {
448
367
  nextIdentity: EthagentIdentity
449
368
  publicSkillsJson: string
450
- agentCardUri: string
451
- agentUri?: string
452
- metadataCid?: string
369
+ agentUri: string
370
+ metadataCid: string
453
371
  }
454
372
 
455
373
  async function prepareOperatorProfileArtifacts(args: {
@@ -458,7 +376,6 @@ async function prepareOperatorProfileArtifacts(args: {
458
376
  snapshotOwner: Address
459
377
  walletAccess: WalletAccessContext
460
378
  challengePurpose: WalletChallengePurpose
461
- includeMetadata: boolean
462
379
  }): Promise<OperatorProfileArtifacts> {
463
380
  const { step, wallet, snapshotOwner, walletAccess, challengePurpose } = args
464
381
  const {
@@ -523,80 +440,41 @@ async function prepareOperatorProfileArtifacts(args: {
523
440
  agentId: step.identity.agentId!,
524
441
  }
525
442
 
526
- let metadataCid: string | undefined
527
- let agentUri: string | undefined
528
- if (args.includeMetadata) {
529
- const registration = withEthagentPointers({
530
- type: 'https://eips.ethereum.org/EIPS/eip-8004#registration-v1',
531
- name: nextName ?? deriveAgentName(step.identity),
532
- ...(nextDescription ? { description: nextDescription } : {}),
533
- ...(uploadedImageUri ? { image: uploadedImageUri } : {}),
534
- }, {
535
- backup: { cid: statePin.cid, envelopeVersion: envelope.envelopeVersion, createdAt: envelope.createdAt },
536
- publicDiscovery: { skillsCid: publicSkills.cid, agentCardCid: publicSkills.agentCardCid, updatedAt: publicSkills.updatedAt },
537
- registration: { chainId: step.registry.chainId, identityRegistryAddress: step.registry.identityRegistryAddress, agentId: step.identity.agentId },
538
- ensName: nextEnsName,
539
- operators: operatorsPointerFromState(state, nextEnsName),
540
- ownerAddress: snapshotOwner,
541
- })
542
- const metadataPin = await addToIpfs(DEFAULT_IPFS_API_URL, JSON.stringify(registration, null, 2), fetch, { pinataJwt: step.pinataJwt })
543
- assertVerifiedPin(metadataPin)
544
- metadataCid = metadataPin.cid
545
- agentUri = `ipfs://${metadataCid}`
546
- }
443
+ const registration = withEthagentPointers({
444
+ type: 'https://eips.ethereum.org/EIPS/eip-8004#registration-v1',
445
+ name: nextName ?? deriveAgentName(step.identity),
446
+ ...(nextDescription ? { description: nextDescription } : {}),
447
+ ...(uploadedImageUri ? { image: uploadedImageUri } : {}),
448
+ }, {
449
+ backup: { cid: statePin.cid, envelopeVersion: envelope.envelopeVersion, createdAt: envelope.createdAt },
450
+ publicDiscovery: { skillsCid: publicSkills.cid, agentCardCid: publicSkills.agentCardCid, updatedAt: publicSkills.updatedAt },
451
+ registration: { chainId: step.registry.chainId, identityRegistryAddress: step.registry.identityRegistryAddress, agentId: step.identity.agentId },
452
+ ensName: nextEnsName,
453
+ operators: operatorsPointerFromState(state, nextEnsName),
454
+ ownerAddress: snapshotOwner,
455
+ })
456
+ const metadataPin = await addToIpfs(DEFAULT_IPFS_API_URL, JSON.stringify(registration, null, 2), fetch, { pinataJwt: step.pinataJwt })
457
+ assertVerifiedPin(metadataPin)
458
+ const metadataCid = metadataPin.cid
459
+ const agentUri = `ipfs://${metadataCid}`
547
460
 
548
- const nextBackup = agentUri && metadataCid
549
- ? { ...backup, metadataCid, agentUri }
550
- : backup
551
461
  const nextIdentity: EthagentIdentity = {
552
462
  ...step.identity,
553
463
  state,
554
- backup: nextBackup,
464
+ backup: { ...backup, metadataCid, agentUri },
555
465
  publicSkills,
556
- ...(agentUri ? { agentUri } : {}),
557
- ...(metadataCid ? { metadataCid } : {}),
466
+ agentUri,
467
+ metadataCid,
558
468
  }
559
469
 
560
470
  return {
561
471
  nextIdentity,
562
472
  publicSkillsJson,
563
- agentCardUri: `ipfs://${agentCardPin.cid}`,
564
- ...(agentUri ? { agentUri } : {}),
565
- ...(metadataCid ? { metadataCid } : {}),
473
+ agentUri,
474
+ metadataCid,
566
475
  }
567
476
  }
568
477
 
569
- async function publishOperatorProfileEnsRecord(args: {
570
- ensName: string
571
- signer: Address
572
- registry: Erc8004RegistryConfig
573
- agentCardUri: string
574
- callbacks: EffectCallbacks
575
- session: BrowserWalletSession
576
- flowId?: string
577
- flowStep?: number
578
- }): Promise<void> {
579
- const ensClient = createMainnetEnsPublicClient()
580
- const tx = await runUpdateEnsRecords({
581
- fullName: args.ensName,
582
- ownerAddress: args.signer,
583
- records: { profile: args.agentCardUri },
584
- callbacks: args.callbacks,
585
- purpose: 'update-profile-operator',
586
- tokenChainId: args.registry.chainId,
587
- session: args.session,
588
- publicClient: ensClient,
589
- ...(args.flowId ? { flowId: args.flowId } : {}),
590
- ...(typeof args.flowStep === 'number' ? { flowStep: args.flowStep } : {}),
591
- })
592
- await awaitConfirmedReceipt(
593
- ensClient,
594
- tx.txHash as Hex,
595
- 'Public profile ENS record update',
596
- { kind: 'public-profile', chainId: 1 },
597
- )
598
- }
599
-
600
478
  async function assertVaultSignerCanRotateAgentUri(args: {
601
479
  registry: Erc8004RegistryConfig
602
480
  vaultAddress: Address
@@ -63,7 +63,7 @@ import {
63
63
  appendResolverSyncWarning,
64
64
  markCurrentContinuityFilesPublished,
65
65
  resolverSyncWarningMessage,
66
- syncResolverApprovalsAfterOwnerSave,
66
+ syncVaultOperatorsAfterOwnerSave,
67
67
  } from '../shared/sync.js'
68
68
  import { runOperatorWalletRebackup } from './vault.js'
69
69
 
@@ -360,7 +360,7 @@ async function runRebackupSigningInner(
360
360
  }
361
361
  await recordPublishedContinuitySnapshot({ identity: nextIdentity, label: 'published encrypted snapshot' }).catch(() => null)
362
362
  await markCurrentContinuityFilesPublished(nextIdentity).catch(() => null)
363
- const resolverSyncWarning = await syncResolverApprovalsAfterOwnerSave({
363
+ const resolverSyncWarning = await syncVaultOperatorsAfterOwnerSave({
364
364
  beforeIdentity: step.identity,
365
365
  afterIdentity: nextIdentity,
366
366
  registry: step.registry,
@@ -1,24 +1,9 @@
1
- import { getAddress, type Address } from 'viem'
2
- import type { EthagentConfig, EthagentIdentity } from '../../../storage/config.js'
1
+ import type { EthagentConfig } from '../../../storage/config.js'
3
2
  import { saveConfig } from '../../../storage/config.js'
4
- import {
5
- normalizeErc8004RegistryConfig,
6
- type Erc8004RegistryConfig,
7
- } from '../../registry/erc8004.js'
3
+ import { normalizeErc8004RegistryConfig } from '../../registry/erc8004.js'
8
4
  import { registryConfigFromConfig } from '../../registry/registryConfig.js'
9
- import { readOwnerAddressField } from '../../identityCompat.js'
10
- import {
11
- sendBrowserWalletTransaction,
12
- } from '../../wallet/browserWallet.js'
13
- import {
14
- encodeResolverApprovalChanges,
15
- verifyResolverApprovalsLanded,
16
- type RecordsFixPlan,
17
- } from '../reconciliation/index.js'
18
5
  import type { Step } from '../identityHubReducer.js'
19
6
  import type { EffectCallbacks } from './types.js'
20
- import { awaitOptionalReceipt } from './receipts.js'
21
- import { createMainnetEnsPublicClient } from './ens/transactions.js'
22
7
 
23
8
  export async function runRestoreRegistrySubmit(
24
9
  value: string,
@@ -47,47 +32,3 @@ export async function runRestoreRegistrySubmit(
47
32
  }
48
33
  callbacks.onStep({ kind: 'restore-discovering', ownerHandle: step.ownerHandle, registry, purpose: step.purpose })
49
34
  }
50
-
51
- export async function runFixRecordsSubmit(args: {
52
- identity: EthagentIdentity
53
- registry: Erc8004RegistryConfig
54
- plan: RecordsFixPlan
55
- callbacks: EffectCallbacks
56
- }): Promise<void> {
57
- const baseState = (args.identity.state ?? {}) as Record<string, unknown>
58
- const ensName = args.plan.ensName ?? (typeof baseState.ensName === 'string' ? baseState.ensName.trim() : '')
59
- if (!ensName) throw new Error('Cannot fix records: identity has no ENS subdomain')
60
- const missing: Address[] = []
61
- const stale: Address[] = []
62
- for (const item of args.plan.items) {
63
- if (item.kind === 'missing-approval') missing.push(item.address)
64
- else if (item.kind === 'stale-approval') stale.push(item.address)
65
- }
66
- if (missing.length === 0 && stale.length === 0) return
67
- const encoded = await encodeResolverApprovalChanges({
68
- ensName,
69
- diff: { added: missing, removed: stale },
70
- })
71
- if (!encoded) return
72
- const ownerAddressRaw = readOwnerAddressField(baseState) ?? args.identity.ownerAddress ?? args.identity.address
73
- const ownerAddress = getAddress(ownerAddressRaw)
74
- const tx = await sendBrowserWalletTransaction({
75
- chainId: 1,
76
- expectedAccount: ownerAddress,
77
- to: encoded.resolverAddress,
78
- data: encoded.data,
79
- onReady: args.callbacks.onWalletReady,
80
- purpose: 'reconcile-resolver-approvals',
81
- })
82
- args.callbacks.onWalletReady(null)
83
- const client = createMainnetEnsPublicClient()
84
- await awaitOptionalReceipt(client, tx.txHash, 'Resolver approval reconciliation')
85
- await verifyResolverApprovalsLanded({
86
- ensName,
87
- ownerAddress: ownerAddress,
88
- resolverAddress: encoded.resolverAddress,
89
- added: encoded.added,
90
- removed: encoded.removed,
91
- client,
92
- })
93
- }
@@ -9,14 +9,9 @@ import {
9
9
  encodeSetMetadataOperator,
10
10
  readMetadataOperators,
11
11
  } from '../../../registry/vault.js'
12
- import {
13
- sendBrowserWalletTransaction,
14
- type WalletPurpose,
15
- } from '../../../wallet/browserWallet.js'
12
+ import { sendBrowserWalletTransaction } from '../../../wallet/browserWallet.js'
16
13
  import {
17
14
  computeApprovalDiff,
18
- encodeResolverApprovalChanges,
19
- verifyResolverApprovalsLanded,
20
15
  type ApprovalDiff,
21
16
  } from '../../reconciliation/index.js'
22
17
  import { normalizeApprovedOperatorWallets } from '../../operatorWallets.js'
@@ -24,8 +19,7 @@ import { readOwnerAddressField } from '../../../identityCompat.js'
24
19
  import { localContinuitySnapshotContentHashes } from '../../../continuity/storage.js'
25
20
  import { updatePublishedContinuitySnapshotContentHashes } from '../../../continuity/snapshots.js'
26
21
  import type { EffectCallbacks } from '../types.js'
27
- import { awaitConfirmedReceipt, awaitOptionalReceipt } from '../receipts.js'
28
- import { createMainnetEnsPublicClient } from '../ens/transactions.js'
22
+ import { awaitConfirmedReceipt } from '../receipts.js'
29
23
 
30
24
  export function resolverSyncWarningMessage(err: unknown): string {
31
25
  return err instanceof Error ? err.message : String(err)
@@ -36,7 +30,7 @@ export function appendResolverSyncWarning(message: string, warning: string | nul
36
30
  return `${message}\n\nWarning: ${warning}`
37
31
  }
38
32
 
39
- export async function syncResolverApprovalsAfterOwnerSave(args: {
33
+ export async function syncVaultOperatorsAfterOwnerSave(args: {
40
34
  beforeIdentity: EthagentIdentity
41
35
  afterIdentity: EthagentIdentity
42
36
  registry: Erc8004RegistryConfig
@@ -45,7 +39,6 @@ export async function syncResolverApprovalsAfterOwnerSave(args: {
45
39
  }): Promise<void> {
46
40
  const beforeState = (args.beforeIdentity.state ?? {}) as Record<string, unknown>
47
41
  const afterState = (args.afterIdentity.state ?? {}) as Record<string, unknown>
48
- const ensName = typeof afterState.ensName === 'string' ? afterState.ensName.trim() : ''
49
42
  const before = normalizeApprovedOperatorWallets(beforeState.approvedOperatorWallets)
50
43
  const after = normalizeApprovedOperatorWallets(afterState.approvedOperatorWallets)
51
44
  const diff = computeApprovalDiff(before, after)
@@ -54,40 +47,6 @@ export async function syncResolverApprovalsAfterOwnerSave(args: {
54
47
  const ownerAddressRaw = readOwnerAddressField(afterState) ?? args.afterIdentity.ownerAddress ?? args.afterIdentity.address
55
48
  const ownerAddress = getAddress(ownerAddressRaw)
56
49
 
57
- if (ensName) {
58
- let encoded
59
- try {
60
- encoded = await encodeResolverApprovalChanges({ ensName, diff })
61
- } catch {
62
- encoded = null
63
- }
64
- if (encoded) {
65
- const purpose: WalletPurpose = diff.removed.length > 0 && diff.added.length === 0
66
- ? 'revoke-operator-wallet-resolver'
67
- : 'authorize-operator-wallet-resolver'
68
-
69
- const tx = await sendBrowserWalletTransaction({
70
- chainId: 1,
71
- expectedAccount: ownerAddress,
72
- to: encoded.resolverAddress,
73
- data: encoded.data,
74
- onReady: args.callbacks.onWalletReady,
75
- purpose,
76
- })
77
- args.callbacks.onWalletReady(null)
78
- const client = createMainnetEnsPublicClient()
79
- await awaitOptionalReceipt(client, tx.txHash, 'Resolver delegation sync')
80
- await verifyResolverApprovalsLanded({
81
- ensName,
82
- ownerAddress: ownerAddress,
83
- resolverAddress: encoded.resolverAddress,
84
- added: encoded.added,
85
- removed: encoded.removed,
86
- client,
87
- })
88
- }
89
- }
90
-
91
50
  await syncVaultMetadataOperatorsAfterOwnerSave({
92
51
  afterIdentity: args.afterIdentity,
93
52
  registry: args.registry,
@@ -15,11 +15,7 @@ import { ensValidationReasonText, selectEnsStatus } from '../../model/ens.js'
15
15
  import { shortAddress } from '../../model/format.js'
16
16
  import { lastBackupLabel } from '../../model/identity.js'
17
17
  import {
18
- describeFixPlanItem,
19
- fixPlanRequiresOwnerWallet,
20
- reconcileWalletSetup,
21
18
  type AgentReconciliation,
22
- type RecordsFixPlan,
23
19
  } from '../../reconciliation/index.js'
24
20
 
25
21
  const footerHint = (hint: string) => <Text color={theme.dim}>{hint}</Text>
@@ -37,7 +33,6 @@ interface CustodyEditFlowProps {
37
33
  onReturnToVault: (returnTo: Step, vaultAddress: Address) => void
38
34
  onResumeAdvanced: (returnTo: Step) => void
39
35
  onManageOperatorWallets: () => void
40
- onFixRecords: (plan: RecordsFixPlan) => void
41
36
  onPrepareTransfer: () => void
42
37
  onBack: () => void
43
38
  }
@@ -59,7 +54,6 @@ export const CustodyEditFlow: React.FC<CustodyEditFlowProps> = ({
59
54
  onReturnToVault,
60
55
  onResumeAdvanced,
61
56
  onManageOperatorWallets,
62
- onFixRecords,
63
57
  onPrepareTransfer,
64
58
  onBack,
65
59
  }) => {
@@ -77,19 +71,8 @@ export const CustodyEditFlow: React.FC<CustodyEditFlowProps> = ({
77
71
  const tokenLabel = identity.agentId ? `Token #${identity.agentId}` : 'Token #unknown'
78
72
  const tokenOwner = identity.ownerAddress ?? identity.address
79
73
 
80
- const [fixPlan, setFixPlan] = React.useState<RecordsFixPlan | null>(null)
81
- React.useEffect(() => {
82
- if (step.kind !== 'custody-model') return
83
- if (custodyMode !== 'advanced') return
84
- let cancelled = false
85
- reconcileWalletSetup({ identity, registry })
86
- .then(plan => { if (!cancelled) setFixPlan(plan) })
87
- .catch(() => { if (!cancelled) setFixPlan(null) })
88
- return () => { cancelled = true }
89
- }, [identity, registry, step.kind, custodyMode])
90
-
91
74
  if (step.kind === 'custody-model') {
92
- type Action = 'switch-advanced' | 'switch-simple' | 'resume-advanced' | 'cancel-advanced' | 'withdraw-token' | 'return-to-vault' | 'manage-operator-wallets' | 'fix-records' | 'back'
75
+ type Action = 'switch-advanced' | 'switch-simple' | 'resume-advanced' | 'cancel-advanced' | 'withdraw-token' | 'return-to-vault' | 'manage-operator-wallets' | 'back'
93
76
  const onChainCustody = reconciliation?.custody
94
77
  const midFlow = onChainCustody === 'mid-flow-uri-pending'
95
78
  const isAdvanced = onChainCustody === 'advanced' || midFlow || custodyMode === 'advanced'
@@ -149,15 +132,6 @@ export const CustodyEditFlow: React.FC<CustodyEditFlowProps> = ({
149
132
  hint: 'Add or revoke wallets that can publish updates onchain.',
150
133
  })
151
134
  }
152
- const hasFixablePlan = fixPlan !== null && fixPlanRequiresOwnerWallet(fixPlan)
153
- if (hasFixablePlan) {
154
- options.push({ value: 'fix-records', role: 'section', label: 'Records Out Of Sync' })
155
- options.push({
156
- value: 'fix-records',
157
- label: 'Fix Records (Owner Wallet)',
158
- hint: 'Sync ENS resolver approvals with the operator wallet list.',
159
- })
160
- }
161
135
  options.push({ value: 'back', role: 'section', label: 'Navigation' })
162
136
  options.push({ value: 'back', label: 'Back', hint: 'Return to Identity Hub', role: 'utility' })
163
137
  const notice = step.kind === 'custody-model' ? step.notice : undefined
@@ -201,14 +175,6 @@ export const CustodyEditFlow: React.FC<CustodyEditFlowProps> = ({
201
175
  return <Row label="Last Saved" value={lastBackup} muted={lastBackup === 'never'} />
202
176
  })()}
203
177
  </Box>
204
- {fixPlan && fixPlan.items.length > 0 ? (
205
- <Box marginTop={1} flexDirection="column">
206
- <Text color={theme.accentPeriwinkle} bold>Records out of sync:</Text>
207
- {fixPlan.items.map((item, idx) => (
208
- <Text key={idx} color={theme.dim}>· {describeFixPlanItem(item)}</Text>
209
- ))}
210
- </Box>
211
- ) : null}
212
178
  <Box marginTop={1}>
213
179
  <Select<Action>
214
180
  options={options}
@@ -226,10 +192,6 @@ export const CustodyEditFlow: React.FC<CustodyEditFlowProps> = ({
226
192
  onSetStep({ kind: 'custody-simple-confirm', identity, registry, returnTo })
227
193
  return
228
194
  }
229
- if (choice === 'fix-records') {
230
- if (fixPlan) onFixRecords(fixPlan)
231
- return
232
- }
233
195
  if (choice === 'switch-advanced') {
234
196
  onSetStep({ kind: 'custody-advanced-confirm', identity, registry, returnTo })
235
197
  return
@@ -16,7 +16,7 @@ export function createCustodyFlowActions({
16
16
  }: CustodyFlowDeps): {
17
17
  beginVaultDeposit: (currentStep: Step, returnTo: Step, profileUpdates: ProfileUpdates) => void
18
18
  beginVaultUnwrap: (currentStep: Step, returnTo: Step, profileUpdates: ProfileUpdates) => void
19
- beginWithdrawToken: (currentStep: Step, returnTo: Step) => void
19
+ beginWithdrawToken: (currentStep: Step, returnTo: Step, returnContext?: 'ens' | 'simple-exit') => void
20
20
  beginReturnToVault: (currentStep: Step, returnTo: Step, vaultAddress: Address) => void
21
21
  } {
22
22
  const beginVaultDeposit = (currentStep: Step, returnTo: Step, profileUpdates: ProfileUpdates): void => {
@@ -122,8 +122,8 @@ export function createCustodyFlowActions({
122
122
  })()
123
123
  }
124
124
 
125
- const beginWithdrawToken = (currentStep: Step, returnTo: Step): void => {
126
- if (!isCustodyEditStep(currentStep)) return
125
+ const beginWithdrawToken = (currentStep: Step, returnTo: Step, returnContext?: 'ens' | 'simple-exit'): void => {
126
+ if (!isCustodyEditStep(currentStep) && currentStep.kind !== 'edit-profile-ens') return
127
127
  const vaultAddress = resolveVaultAddress(currentStep.identity, config?.erc8004?.operatorVaults)
128
128
  if (!vaultAddress) {
129
129
  handleStepError(
@@ -147,6 +147,7 @@ export function createCustodyFlowActions({
147
147
  registry: currentStep.registry,
148
148
  vaultAddress,
149
149
  returnTo,
150
+ ...(returnContext ? { returnContext } : {}),
150
151
  })
151
152
  ;(async () => {
152
153
  const client = createErc8004PublicClient(currentStep.registry)
@@ -172,6 +173,7 @@ export function createCustodyFlowActions({
172
173
  vaultAddress,
173
174
  agentId: activeAgentId,
174
175
  returnTo,
176
+ ...(returnContext ? { returnContext } : {}),
175
177
  })
176
178
  return
177
179
  }
@@ -35,7 +35,7 @@ export interface CustodyFlowDeps {
35
35
  export interface CustodyFlow {
36
36
  beginVaultDeposit: (currentStep: Step, returnTo: Step, profileUpdates: ProfileUpdates) => void
37
37
  beginVaultUnwrap: (currentStep: Step, returnTo: Step, profileUpdates: ProfileUpdates) => void
38
- beginWithdrawToken: (currentStep: Step, returnTo: Step) => void
38
+ beginWithdrawToken: (currentStep: Step, returnTo: Step, returnContext?: 'ens' | 'simple-exit') => void
39
39
  beginReturnToVault: (currentStep: Step, returnTo: Step, vaultAddress: Address) => void
40
40
  renderCustodyStep: () => React.ReactElement | null
41
41
  renderRebackupSubtitle: (