ethagent 3.2.0 → 3.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +15 -16
- package/package.json +1 -1
- package/src/identity/continuity/history.ts +8 -8
- package/src/identity/continuity/publicSkills.ts +2 -79
- package/src/identity/continuity/skills/publicSkillsSync.ts +8 -7
- package/src/identity/continuity/snapshots.ts +3 -8
- package/src/identity/continuity/storage/defaults.ts +3 -3
- package/src/identity/continuity/storage/paths.ts +1 -1
- package/src/identity/continuity/storage/scaffold.ts +37 -25
- package/src/identity/continuity/storage/status.ts +11 -11
- package/src/identity/continuity/storage/types.ts +4 -4
- package/src/identity/continuity/storage.ts +4 -4
- package/src/identity/ens/agentRecords.ts +61 -45
- package/src/identity/ens/ensAutomation/read.ts +7 -10
- package/src/identity/ens/ensAutomation/setup.ts +10 -16
- package/src/identity/ens/ensAutomation/types.ts +0 -1
- package/src/identity/ens/ensAutomation.ts +1 -0
- package/src/identity/ens/ensLookup/records.ts +1 -1
- package/src/identity/ens/ensLookup.ts +1 -1
- package/src/identity/ens/erc7930.ts +48 -0
- package/src/identity/hub/OperationalRoutes.tsx +4 -2
- package/src/identity/hub/Routes.tsx +1 -1
- package/src/identity/hub/continuity/effects.ts +17 -39
- package/src/identity/hub/continuity/skills/NewSkillVisibilityScreen.tsx +2 -2
- package/src/identity/hub/continuity/skills/SkillActionsScreen.tsx +2 -2
- package/src/identity/hub/continuity/state.ts +1 -1
- package/src/identity/hub/continuity/vault.ts +16 -50
- package/src/identity/hub/create/effects.ts +12 -16
- package/src/identity/hub/ens/EnsEditAdvancedScreens.tsx +0 -76
- package/src/identity/hub/ens/EnsEditFlow.tsx +28 -8
- package/src/identity/hub/ens/EnsEditMaintenanceScreens.tsx +3 -7
- package/src/identity/hub/ens/EnsEditReviewScreens.tsx +14 -18
- package/src/identity/hub/ens/EnsEditShared.tsx +4 -6
- package/src/identity/hub/ens/EnsEditSimpleScreens.tsx +2 -2
- package/src/identity/hub/ens/EnsFlow.tsx +0 -3
- package/src/identity/hub/ens/editCopy.ts +7 -15
- package/src/identity/hub/ens/transactions.ts +67 -18
- package/src/identity/hub/ens/types.ts +0 -2
- package/src/identity/hub/profile/EditProfileFlow.tsx +0 -3
- package/src/identity/hub/profile/effects.ts +15 -30
- package/src/identity/hub/profile/identity.ts +2 -4
- package/src/identity/hub/profile/operatorSave.ts +10 -30
- package/src/identity/hub/restore/RestoreFlow.tsx +9 -9
- package/src/identity/hub/restore/apply.ts +7 -8
- package/src/identity/hub/restore/helpers.ts +3 -3
- package/src/identity/hub/restore/recovery.ts +9 -10
- package/src/identity/hub/restore/resolve.ts +11 -9
- package/src/identity/hub/shared/components/MenuScreen.tsx +3 -3
- package/src/identity/hub/shared/components/UnlinkedIdentityScreen.tsx +2 -2
- package/src/identity/hub/shared/effects/sync.ts +1 -1
- package/src/identity/hub/transfer/TokenTransferScreens.tsx +1 -1
- package/src/identity/hub/transfer/effects.ts +10 -31
- package/src/identity/hub/useIdentityHubContinuity.ts +12 -12
- package/src/identity/hub/useIdentityHubController.ts +12 -3
- package/src/identity/registry/erc8004/metadata.ts +10 -27
- package/src/identity/registry/erc8004/types.ts +0 -1
- package/src/storage/config.ts +1 -2
- package/src/storage/identity.ts +3 -3
- package/src/storage/rewind.ts +1 -1
- package/src/tools/privateContinuityEditTool.ts +4 -4
- package/src/utils/withRetry.ts +2 -2
|
@@ -3,6 +3,12 @@ import type { AgentEnsRecords, AgentRecordDiff } from '../../ens/agentRecords.js
|
|
|
3
3
|
import type { EnsRegistryAction, EnsSetupBlockedPlan } from '../../ens/ensAutomation.js'
|
|
4
4
|
import type { CustodyMode } from '../custody/state.js'
|
|
5
5
|
|
|
6
|
+
export function abbreviateHexBlobs(input: string): string {
|
|
7
|
+
return input.replace(/0x([0-9a-fA-F]{20,})/g, (_match, hex) => {
|
|
8
|
+
return `0x${hex.slice(0, 8)}...${hex.slice(-8)}`
|
|
9
|
+
})
|
|
10
|
+
}
|
|
11
|
+
|
|
6
12
|
export type EnsLinkOptions = {
|
|
7
13
|
mode: 'simple' | 'advanced'
|
|
8
14
|
ownerAddress?: Address
|
|
@@ -18,7 +24,7 @@ export function recordsHaveCurrentValues(recordsDiff: AgentRecordDiff[]): boolea
|
|
|
18
24
|
}
|
|
19
25
|
|
|
20
26
|
export function emptyAgentEnsRecords(): AgentEnsRecords {
|
|
21
|
-
return {
|
|
27
|
+
return {}
|
|
22
28
|
}
|
|
23
29
|
|
|
24
30
|
export function unlinkEnsLinkOptions(savedCustodyMode: CustodyMode | undefined, savedOwnerAddress: string): EnsLinkOptions {
|
|
@@ -46,18 +52,6 @@ export function modeSwitchHeading(
|
|
|
46
52
|
return 'Automation'
|
|
47
53
|
}
|
|
48
54
|
|
|
49
|
-
export function setupSwitchNotice(
|
|
50
|
-
currentEnsName: string,
|
|
51
|
-
currentMode: CustodyMode | undefined,
|
|
52
|
-
nextEnsName: string,
|
|
53
|
-
nextMode: 'simple' | 'advanced',
|
|
54
|
-
): string | null {
|
|
55
|
-
if (!currentEnsName && !currentMode) return null
|
|
56
|
-
const currentTopology = currentMode === 'advanced' ? 'advanced' : currentMode === 'simple' ? 'simple' : undefined
|
|
57
|
-
if (currentEnsName === nextEnsName && currentTopology === nextMode) return null
|
|
58
|
-
return 'This replaces the saved ENS setup directly. Reset is only for clearing the current link.'
|
|
59
|
-
}
|
|
60
|
-
|
|
61
55
|
export function advancedSubdomainStatusText(action: EnsRegistryAction): string {
|
|
62
56
|
switch (action) {
|
|
63
57
|
case 'create-subdomain':
|
|
@@ -84,8 +78,6 @@ export function manualReasonTitle(reason: EnsSetupBlockedPlan['reason']): string
|
|
|
84
78
|
case 'wrapped-parent':
|
|
85
79
|
case 'subdomain-wrapped':
|
|
86
80
|
return 'ENS NameWrapper ownership could not be verified'
|
|
87
|
-
case 'token-record-collision':
|
|
88
|
-
return 'Subdomain already points to another token'
|
|
89
81
|
case 'token-owner-mismatch':
|
|
90
82
|
return 'Owner wallet does not own this ERC-8004 token'
|
|
91
83
|
case 'token-owner-lookup-failed':
|
|
@@ -5,10 +5,11 @@ import {
|
|
|
5
5
|
createErc8004PublicClient,
|
|
6
6
|
supportedErc8004ChainForId,
|
|
7
7
|
} from '../../registry/erc8004.js'
|
|
8
|
-
import {
|
|
9
|
-
import { encodeEnsRecordsTransaction, encodeEnsRegistryTransaction, type EnsSetupPlan } from '../../ens/ensAutomation.js'
|
|
10
|
-
import type { AgentEnsRecordState, AgentEnsRecords } from '../../ens/agentRecords.js'
|
|
11
|
-
import { changedRecords } from '../../ens/agentRecords.js'
|
|
8
|
+
import { encodeSetEnsip25TextRecord, readEthagentTextRecords } from '../../ens/ensLookup.js'
|
|
9
|
+
import { encodeEnsRecordsTransaction, encodeEnsRegistryTransaction, readAddressRecord, type EnsSetupPlan } from '../../ens/ensAutomation.js'
|
|
10
|
+
import type { AgentEnsRecordState, AgentEnsRecords, AgentRecordDiff } from '../../ens/agentRecords.js'
|
|
11
|
+
import { changedRecords, clearedRecords, diffRecords } from '../../ens/agentRecords.js'
|
|
12
|
+
import { namehash, getAddress } from 'viem'
|
|
12
13
|
import { sendBrowserWalletTransaction, type BrowserWalletSession, type WalletPurpose } from '../../wallet/browserWallet.js'
|
|
13
14
|
import type { EffectCallbacks } from '../shared/effects/types.js'
|
|
14
15
|
function chainLabel(chainId: number): string {
|
|
@@ -32,17 +33,29 @@ export async function runUpdateEnsRecords(args: {
|
|
|
32
33
|
session?: BrowserWalletSession
|
|
33
34
|
flowId?: string
|
|
34
35
|
flowStep?: number
|
|
35
|
-
}): Promise<{ txHash: string }> {
|
|
36
|
+
}): Promise<{ txHash: string } | { skipped: true }> {
|
|
37
|
+
const publicClient = args.publicClient ?? createMainnetEnsPublicClient()
|
|
38
|
+
const queryKeys = Array.from(new Set([
|
|
39
|
+
...Object.keys(args.records ?? {}),
|
|
40
|
+
...Object.keys(args.currentRecords ?? {}),
|
|
41
|
+
]))
|
|
42
|
+
let freshCurrent: AgentEnsRecordState = args.currentRecords ?? {}
|
|
43
|
+
if (queryKeys.length > 0) {
|
|
44
|
+
try {
|
|
45
|
+
freshCurrent = await readEthagentTextRecords(args.fullName, queryKeys, { publicClient })
|
|
46
|
+
} catch {
|
|
47
|
+
freshCurrent = args.currentRecords ?? {}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
36
50
|
const next = ensRecordWritesForUpdate({
|
|
37
51
|
records: args.records,
|
|
38
|
-
currentRecords:
|
|
52
|
+
currentRecords: freshCurrent,
|
|
39
53
|
clearRecords: args.clearRecords,
|
|
40
54
|
})
|
|
41
55
|
if (Object.keys(next).length === 0) {
|
|
42
|
-
|
|
56
|
+
return { skipped: true }
|
|
43
57
|
}
|
|
44
|
-
const
|
|
45
|
-
const encoded = await encodeSetEthagentTextRecords(args.fullName, next, { publicClient })
|
|
58
|
+
const encoded = await encodeSetEnsip25TextRecord(args.fullName, next, { publicClient })
|
|
46
59
|
await preflightEnsRecordTransaction({
|
|
47
60
|
fullName: args.fullName,
|
|
48
61
|
account: args.ownerAddress,
|
|
@@ -83,10 +96,11 @@ export function ensRecordWritesForUpdate(args: {
|
|
|
83
96
|
currentRecords?: AgentEnsRecordState
|
|
84
97
|
clearRecords?: boolean
|
|
85
98
|
}): Record<string, string> {
|
|
99
|
+
const current = args.currentRecords ?? {}
|
|
86
100
|
if (args.clearRecords) {
|
|
87
|
-
return changedRecords(
|
|
101
|
+
return changedRecords(current, clearedRecords(current))
|
|
88
102
|
}
|
|
89
|
-
return changedRecords(
|
|
103
|
+
return changedRecords(current, args.records)
|
|
90
104
|
}
|
|
91
105
|
|
|
92
106
|
export async function runEnsSetupRegistryTransaction(args: {
|
|
@@ -148,13 +162,14 @@ export async function runEnsSetupRecordsTransaction(args: {
|
|
|
148
162
|
flowId?: string
|
|
149
163
|
flowStep?: number
|
|
150
164
|
}): Promise<{ txHash: string } | null> {
|
|
151
|
-
const encoded = encodeEnsRecordsTransaction(args.setup)
|
|
152
|
-
if (!encoded) return null
|
|
153
165
|
const publicClient = args.publicClient ?? createMainnetEnsPublicClient()
|
|
154
|
-
const
|
|
166
|
+
const freshSetup = await refreshEnsSetupAgainstChain(args.setup, publicClient)
|
|
167
|
+
const encoded = encodeEnsRecordsTransaction(freshSetup)
|
|
168
|
+
if (!encoded) return null
|
|
169
|
+
const purpose: WalletPurpose = freshSetup.mode === 'simple' ? 'set-simple-ens-records' : 'set-agent-ens-records'
|
|
155
170
|
await preflightEnsRecordTransaction({
|
|
156
|
-
fullName:
|
|
157
|
-
account:
|
|
171
|
+
fullName: freshSetup.fullName,
|
|
172
|
+
account: freshSetup.ownerAddress,
|
|
158
173
|
to: encoded.to,
|
|
159
174
|
data: encoded.data,
|
|
160
175
|
publicClient,
|
|
@@ -164,7 +179,7 @@ export async function runEnsSetupRecordsTransaction(args: {
|
|
|
164
179
|
if (args.session) {
|
|
165
180
|
const result = await args.session.sendTransaction({
|
|
166
181
|
chainId: 1,
|
|
167
|
-
expectedAccount:
|
|
182
|
+
expectedAccount: freshSetup.ownerAddress,
|
|
168
183
|
to: encoded.to,
|
|
169
184
|
data: encoded.data,
|
|
170
185
|
purpose,
|
|
@@ -176,7 +191,7 @@ export async function runEnsSetupRecordsTransaction(args: {
|
|
|
176
191
|
}
|
|
177
192
|
const result = await sendBrowserWalletTransaction({
|
|
178
193
|
chainId: 1,
|
|
179
|
-
expectedAccount:
|
|
194
|
+
expectedAccount: freshSetup.ownerAddress,
|
|
180
195
|
to: encoded.to,
|
|
181
196
|
data: encoded.data,
|
|
182
197
|
purpose,
|
|
@@ -187,6 +202,40 @@ export async function runEnsSetupRecordsTransaction(args: {
|
|
|
187
202
|
return { txHash: result.txHash }
|
|
188
203
|
}
|
|
189
204
|
|
|
205
|
+
async function refreshEnsSetupAgainstChain(setup: EnsSetupPlan, publicClient: PublicClient): Promise<EnsSetupPlan> {
|
|
206
|
+
if (setup.registryAction !== 'none' && !setup.recordDiffs.length && !setup.addressRecord.changed) {
|
|
207
|
+
return setup
|
|
208
|
+
}
|
|
209
|
+
const node = namehash(setup.fullName)
|
|
210
|
+
const keys = Array.from(new Set(setup.recordDiffs.map(diff => diff.key)))
|
|
211
|
+
let freshCurrent: AgentEnsRecordState = setup.currentRecords
|
|
212
|
+
if (keys.length > 0) {
|
|
213
|
+
try {
|
|
214
|
+
freshCurrent = await readEthagentTextRecords(setup.fullName, keys, { publicClient })
|
|
215
|
+
} catch {
|
|
216
|
+
freshCurrent = setup.currentRecords
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
let freshAddress = setup.addressRecord.current
|
|
220
|
+
try {
|
|
221
|
+
freshAddress = await readAddressRecord(publicClient, setup.resolverAddress, node)
|
|
222
|
+
} catch {
|
|
223
|
+
freshAddress = setup.addressRecord.current
|
|
224
|
+
}
|
|
225
|
+
const addressChanged = !freshAddress || getAddress(freshAddress).toLowerCase() !== getAddress(setup.addressRecord.next).toLowerCase()
|
|
226
|
+
const recordDiffs: AgentRecordDiff[] = diffRecords(freshCurrent, setup.nextRecords)
|
|
227
|
+
return {
|
|
228
|
+
...setup,
|
|
229
|
+
currentRecords: freshCurrent,
|
|
230
|
+
recordDiffs,
|
|
231
|
+
addressRecord: {
|
|
232
|
+
current: freshAddress,
|
|
233
|
+
next: setup.addressRecord.next,
|
|
234
|
+
changed: addressChanged,
|
|
235
|
+
},
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
190
239
|
export function createMainnetEnsPublicClient(): PublicClient {
|
|
191
240
|
return createErc8004PublicClient({
|
|
192
241
|
chainId: 1,
|
|
@@ -31,7 +31,6 @@ export type SimpleEnsPhase =
|
|
|
31
31
|
| { kind: 'review'; fullName: string; validation: EnsValidation; recordsDiff: AgentRecordDiff[]; currentRecords: AgentEnsRecordState; nextRecords: AgentEnsRecords; mode: 'simple' | 'advanced'; ownerAddress?: Address; operatorWallet?: Address }
|
|
32
32
|
|
|
33
33
|
export type AdvancedEnsPhase =
|
|
34
|
-
| { kind: 'advanced-transfer-check' }
|
|
35
34
|
| { kind: 'advanced-root-check'; rootName: string }
|
|
36
35
|
| { kind: 'advanced-subdomain'; rootName: string; label?: string; error?: string }
|
|
37
36
|
| { kind: 'advanced-subdomain-check'; rootName: string; label: string }
|
|
@@ -71,7 +70,6 @@ export type EnsEditProps = {
|
|
|
71
70
|
onEnsRecordsUpdate: (fullName: string, records: AgentEnsRecords, options: EnsLinkOptions, clearRecords?: boolean, currentRecords?: AgentEnsRecordState) => void
|
|
72
71
|
onEnsSetup: (setup: EnsSetupPlan) => void
|
|
73
72
|
onManageOperatorWalletAccess: () => void
|
|
74
|
-
onWithdrawToken: () => void
|
|
75
73
|
initialView?: 'advanced'
|
|
76
74
|
onBack: () => void
|
|
77
75
|
}
|
|
@@ -26,7 +26,6 @@ type EditProfileFlowProps = {
|
|
|
26
26
|
onEnsRecordsUpdate: (fullName: string, records: AgentEnsRecords, options: EnsLinkOptions, clearRecords?: boolean, currentRecords?: AgentEnsRecordState) => void
|
|
27
27
|
onEnsSetup: (setup: EnsSetupPlan) => void
|
|
28
28
|
onManageOperatorWalletAccess: () => void
|
|
29
|
-
onWithdrawToken: () => void
|
|
30
29
|
onBack: () => void
|
|
31
30
|
onMenu: () => void
|
|
32
31
|
}
|
|
@@ -49,7 +48,6 @@ export const EditProfileFlow: React.FC<EditProfileFlowProps> = ({
|
|
|
49
48
|
onEnsRecordsUpdate,
|
|
50
49
|
onEnsSetup,
|
|
51
50
|
onManageOperatorWalletAccess,
|
|
52
|
-
onWithdrawToken,
|
|
53
51
|
onBack,
|
|
54
52
|
onMenu,
|
|
55
53
|
}) => {
|
|
@@ -101,7 +99,6 @@ export const EditProfileFlow: React.FC<EditProfileFlowProps> = ({
|
|
|
101
99
|
onEnsRecordsUpdate={onEnsRecordsUpdate}
|
|
102
100
|
onEnsSetup={onEnsSetup}
|
|
103
101
|
onManageOperatorWalletAccess={onManageOperatorWalletAccess}
|
|
104
|
-
onWithdrawToken={onWithdrawToken}
|
|
105
102
|
initialView={step.initialView}
|
|
106
103
|
onBack={onBack}
|
|
107
104
|
/>
|
|
@@ -6,15 +6,9 @@ import {
|
|
|
6
6
|
type WalletChallengePurpose,
|
|
7
7
|
} from '../../continuity/envelope.js'
|
|
8
8
|
import {
|
|
9
|
-
|
|
10
|
-
writePublicSkillsFile,
|
|
9
|
+
writeAgentCardFile,
|
|
11
10
|
} from '../../continuity/storage.js'
|
|
12
|
-
import {
|
|
13
|
-
createAgentCard,
|
|
14
|
-
defaultPublicSkillsProfile,
|
|
15
|
-
serializeAgentCard,
|
|
16
|
-
} from '../../continuity/publicSkills.js'
|
|
17
|
-
import { syncPublicSkillsManifest } from '../../continuity/skills/publicSkillsSync.js'
|
|
11
|
+
import { syncAgentCardManifest } from '../../continuity/skills/publicSkillsSync.js'
|
|
18
12
|
import { recordPublishedContinuitySnapshot } from '../../continuity/snapshots.js'
|
|
19
13
|
import { addToIpfs, DEFAULT_IPFS_API_URL, isPinataUploadUrl } from '../../storage/ipfs.js'
|
|
20
14
|
import {
|
|
@@ -63,15 +57,15 @@ import {
|
|
|
63
57
|
prepareOperatorProfileArtifacts,
|
|
64
58
|
} from './operatorSave.js'
|
|
65
59
|
|
|
66
|
-
type
|
|
60
|
+
type AgentCardMetadata = NonNullable<EthagentIdentity['agentCard']>
|
|
67
61
|
|
|
68
62
|
type PublicProfilePreparedTransaction = {
|
|
69
63
|
ownerAddress: Address
|
|
70
64
|
agentUri: string
|
|
71
65
|
metadataCid: string
|
|
72
|
-
|
|
66
|
+
agentCard: AgentCardMetadata
|
|
73
67
|
identity: EthagentIdentity
|
|
74
|
-
|
|
68
|
+
agentCardJson: string
|
|
75
69
|
}
|
|
76
70
|
|
|
77
71
|
export async function runPublicProfilePreflight(
|
|
@@ -151,20 +145,12 @@ async function runPublicProfileSigningInner(
|
|
|
151
145
|
includeLastBackedUpAt: false,
|
|
152
146
|
})
|
|
153
147
|
const nextIdentityForFiles: EthagentIdentity = { ...step.identity, state }
|
|
154
|
-
const
|
|
155
|
-
const
|
|
156
|
-
assertVerifiedPin(publicSkillsPin)
|
|
157
|
-
const agentCardPin = await addToIpfs(
|
|
158
|
-
DEFAULT_IPFS_API_URL,
|
|
159
|
-
serializeAgentCard(createAgentCard(defaultPublicSkillsProfile(nextIdentityForFiles))),
|
|
160
|
-
fetch,
|
|
161
|
-
{ pinataJwt: step.pinataJwt },
|
|
162
|
-
)
|
|
148
|
+
const agentCardJson = await syncAgentCardManifest(nextIdentityForFiles)
|
|
149
|
+
const agentCardPin = await addToIpfs(DEFAULT_IPFS_API_URL, agentCardJson, fetch, { pinataJwt: step.pinataJwt })
|
|
163
150
|
assertVerifiedPin(agentCardPin)
|
|
164
151
|
const updatedAt = new Date().toISOString()
|
|
165
|
-
const
|
|
166
|
-
cid:
|
|
167
|
-
agentCardCid: agentCardPin.cid,
|
|
152
|
+
const agentCard: AgentCardMetadata = {
|
|
153
|
+
cid: agentCardPin.cid,
|
|
168
154
|
updatedAt,
|
|
169
155
|
status: 'pinned',
|
|
170
156
|
}
|
|
@@ -181,8 +167,7 @@ async function runPublicProfileSigningInner(
|
|
|
181
167
|
}, {
|
|
182
168
|
backup: existingBackup,
|
|
183
169
|
publicDiscovery: {
|
|
184
|
-
|
|
185
|
-
agentCardCid: publicSkills.agentCardCid,
|
|
170
|
+
agentCardCid: agentCard.cid,
|
|
186
171
|
updatedAt,
|
|
187
172
|
},
|
|
188
173
|
registration: {
|
|
@@ -212,9 +197,9 @@ async function runPublicProfileSigningInner(
|
|
|
212
197
|
ownerAddress: snapshotOwner,
|
|
213
198
|
agentUri,
|
|
214
199
|
metadataCid,
|
|
215
|
-
|
|
200
|
+
agentCard,
|
|
216
201
|
identity: { ...step.identity, state },
|
|
217
|
-
|
|
202
|
+
agentCardJson,
|
|
218
203
|
},
|
|
219
204
|
}
|
|
220
205
|
},
|
|
@@ -231,9 +216,9 @@ async function runPublicProfileSigningInner(
|
|
|
231
216
|
identityRegistryAddress: step.registry.identityRegistryAddress,
|
|
232
217
|
agentUri: result.prepared.agentUri,
|
|
233
218
|
metadataCid: result.prepared.metadataCid,
|
|
234
|
-
|
|
219
|
+
agentCard: result.prepared.agentCard,
|
|
235
220
|
}
|
|
236
|
-
await
|
|
221
|
+
await writeAgentCardFile(nextIdentity, result.prepared.agentCardJson)
|
|
237
222
|
await markCurrentContinuityFilesPublished(nextIdentity)
|
|
238
223
|
const resolverSyncWarning = await syncVaultOperatorsAfterOwnerSave({
|
|
239
224
|
beforeIdentity: step.identity,
|
|
@@ -345,7 +330,7 @@ async function runOperatorWalletVaultPublicProfileSave(args: {
|
|
|
345
330
|
? { ...prepared.nextIdentity.backup, txHash: vaultTx.txHash }
|
|
346
331
|
: prepared.nextIdentity.backup,
|
|
347
332
|
}
|
|
348
|
-
await
|
|
333
|
+
await writeAgentCardFile(nextIdentity, prepared.agentCardJson)
|
|
349
334
|
await markCurrentContinuityFilesPublished(nextIdentity).catch(() => null)
|
|
350
335
|
await recordPublishedContinuitySnapshot({
|
|
351
336
|
identity: nextIdentity,
|
|
@@ -74,16 +74,14 @@ export function identitySummaryRows(
|
|
|
74
74
|
const tokenValue = identity?.agentId ? `#${identity.agentId}` : 'not created'
|
|
75
75
|
const chain = chainSummaryRow(config, identity)
|
|
76
76
|
const stateValue = backup?.cid ? shortCid(backup.cid) : 'not saved yet'
|
|
77
|
-
const
|
|
78
|
-
const cardValue = identity?.publicSkills?.agentCardCid ? shortCid(identity.publicSkills.agentCardCid) : 'not saved'
|
|
77
|
+
const cardValue = identity?.agentCard?.cid ? shortCid(identity.agentCard.cid) : 'not saved'
|
|
79
78
|
const iconValue = typeof identity?.state?.imageUrl === 'string' && identity.state.imageUrl.trim() ? 'attached' : 'not attached'
|
|
80
79
|
return [
|
|
81
80
|
{ label: 'owner wallet', value: ownerValue, tone: identity ? 'ok' : 'dim' },
|
|
82
81
|
{ label: 'token', value: tokenValue, tone: identity?.agentId ? 'ok' : 'dim' },
|
|
83
82
|
{ label: 'network', value: chain.value, tone: chain.tone },
|
|
84
83
|
{ label: 'state', value: stateValue, tone: backup ? 'ok' : 'dim' },
|
|
85
|
-
{ label: '
|
|
86
|
-
{ label: 'card', value: cardValue, tone: identity?.publicSkills?.agentCardCid ? 'ok' : 'dim' },
|
|
84
|
+
{ label: 'card', value: cardValue, tone: identity?.agentCard?.cid ? 'ok' : 'dim' },
|
|
87
85
|
{ label: 'icon', value: iconValue, tone: iconValue === 'attached' ? 'ok' : 'dim' },
|
|
88
86
|
]
|
|
89
87
|
}
|
|
@@ -10,14 +10,7 @@ import {
|
|
|
10
10
|
readContinuityFiles,
|
|
11
11
|
} from '../../continuity/storage.js'
|
|
12
12
|
import {
|
|
13
|
-
|
|
14
|
-
createAgentCard,
|
|
15
|
-
defaultPublicSkillsProfile,
|
|
16
|
-
serializeAgentCard,
|
|
17
|
-
} from '../../continuity/publicSkills.js'
|
|
18
|
-
import {
|
|
19
|
-
derivePublicSkillEntries,
|
|
20
|
-
syncPublicSkillsManifest,
|
|
13
|
+
syncAgentCardManifest,
|
|
21
14
|
} from '../../continuity/skills/publicSkillsSync.js'
|
|
22
15
|
import { addToIpfs, DEFAULT_IPFS_API_URL } from '../../storage/ipfs.js'
|
|
23
16
|
import {
|
|
@@ -40,12 +33,12 @@ import {
|
|
|
40
33
|
} from '../continuity/snapshot.js'
|
|
41
34
|
|
|
42
35
|
type BackupMetadata = NonNullable<EthagentIdentity['backup']>
|
|
43
|
-
type
|
|
36
|
+
type AgentCardMetadata = NonNullable<EthagentIdentity['agentCard']>
|
|
44
37
|
type WalletAccessContext = NonNullable<ReturnType<typeof walletRestoreAccessContext>>
|
|
45
38
|
|
|
46
39
|
export type OperatorProfileArtifacts = {
|
|
47
40
|
nextIdentity: EthagentIdentity
|
|
48
|
-
|
|
41
|
+
agentCardJson: string
|
|
49
42
|
agentUri: string
|
|
50
43
|
metadataCid: string
|
|
51
44
|
}
|
|
@@ -75,20 +68,8 @@ export async function prepareOperatorProfileArtifacts(args: {
|
|
|
75
68
|
})
|
|
76
69
|
const nextIdentityForFiles: EthagentIdentity = { ...step.identity, state }
|
|
77
70
|
|
|
78
|
-
const
|
|
79
|
-
const
|
|
80
|
-
assertVerifiedPin(publicSkillsPin)
|
|
81
|
-
const publicSkillEntries = await derivePublicSkillEntries(nextIdentityForFiles)
|
|
82
|
-
const augmentedPublicProfile = appendPublicSkillEntries(
|
|
83
|
-
defaultPublicSkillsProfile(nextIdentityForFiles),
|
|
84
|
-
publicSkillEntries,
|
|
85
|
-
)
|
|
86
|
-
const agentCardPin = await addToIpfs(
|
|
87
|
-
DEFAULT_IPFS_API_URL,
|
|
88
|
-
serializeAgentCard(createAgentCard(augmentedPublicProfile)),
|
|
89
|
-
fetch,
|
|
90
|
-
{ pinataJwt: step.pinataJwt },
|
|
91
|
-
)
|
|
71
|
+
const agentCardJson = await syncAgentCardManifest(nextIdentityForFiles)
|
|
72
|
+
const agentCardPin = await addToIpfs(DEFAULT_IPFS_API_URL, agentCardJson, fetch, { pinataJwt: step.pinataJwt })
|
|
92
73
|
assertVerifiedPin(agentCardPin)
|
|
93
74
|
|
|
94
75
|
const continuityFiles = await readContinuityFiles(nextIdentityForFiles)
|
|
@@ -108,9 +89,8 @@ export async function prepareOperatorProfileArtifacts(args: {
|
|
|
108
89
|
const statePin = await addToIpfs(DEFAULT_IPFS_API_URL, serializeContinuitySnapshotEnvelope(envelope), fetch, { pinataJwt: step.pinataJwt })
|
|
109
90
|
assertVerifiedPin(statePin)
|
|
110
91
|
|
|
111
|
-
const
|
|
112
|
-
cid:
|
|
113
|
-
agentCardCid: agentCardPin.cid,
|
|
92
|
+
const agentCard: AgentCardMetadata = {
|
|
93
|
+
cid: agentCardPin.cid,
|
|
114
94
|
updatedAt: envelope.createdAt,
|
|
115
95
|
status: 'pinned',
|
|
116
96
|
}
|
|
@@ -134,7 +114,7 @@ export async function prepareOperatorProfileArtifacts(args: {
|
|
|
134
114
|
...(uploadedImageUri ? { image: uploadedImageUri } : {}),
|
|
135
115
|
}, {
|
|
136
116
|
backup: { cid: statePin.cid, envelopeVersion: envelope.envelopeVersion, createdAt: envelope.createdAt },
|
|
137
|
-
publicDiscovery: {
|
|
117
|
+
publicDiscovery: { agentCardCid: agentCard.cid, updatedAt: agentCard.updatedAt },
|
|
138
118
|
registration: { chainId: step.registry.chainId, identityRegistryAddress: step.registry.identityRegistryAddress, agentId: step.identity.agentId },
|
|
139
119
|
ensName: nextEnsName,
|
|
140
120
|
operators: operatorsPointerFromState(state, nextEnsName),
|
|
@@ -149,14 +129,14 @@ export async function prepareOperatorProfileArtifacts(args: {
|
|
|
149
129
|
...step.identity,
|
|
150
130
|
state,
|
|
151
131
|
backup: { ...backup, metadataCid, agentUri },
|
|
152
|
-
|
|
132
|
+
agentCard,
|
|
153
133
|
agentUri,
|
|
154
134
|
metadataCid,
|
|
155
135
|
}
|
|
156
136
|
|
|
157
137
|
return {
|
|
158
138
|
nextIdentity,
|
|
159
|
-
|
|
139
|
+
agentCardJson,
|
|
160
140
|
agentUri,
|
|
161
141
|
metadataCid,
|
|
162
142
|
}
|
|
@@ -101,7 +101,7 @@ export const RestoreFlow: React.FC<RestoreFlowProps> = ({
|
|
|
101
101
|
if (step.kind === 'restore-recovery-input') {
|
|
102
102
|
return (
|
|
103
103
|
<Surface
|
|
104
|
-
title={isSwitch ? '
|
|
104
|
+
title={isSwitch ? 'Switch Agent' : 'Restore Agent'}
|
|
105
105
|
subtitle="The connected wallet doesn't directly own an agent token on this network."
|
|
106
106
|
footer={footerHint('enter select · esc back')}
|
|
107
107
|
>
|
|
@@ -126,7 +126,7 @@ export const RestoreFlow: React.FC<RestoreFlowProps> = ({
|
|
|
126
126
|
if (step.busy) {
|
|
127
127
|
return (
|
|
128
128
|
<Surface
|
|
129
|
-
title={isSwitch ? '
|
|
129
|
+
title={isSwitch ? 'Switch Agent' : 'Restore Agent'}
|
|
130
130
|
subtitle="Looking up the agent onchain."
|
|
131
131
|
footer={footerHint('esc cancels')}
|
|
132
132
|
>
|
|
@@ -138,7 +138,7 @@ export const RestoreFlow: React.FC<RestoreFlowProps> = ({
|
|
|
138
138
|
}
|
|
139
139
|
return (
|
|
140
140
|
<Surface
|
|
141
|
-
title={isSwitch ? '
|
|
141
|
+
title={isSwitch ? 'Switch Agent' : 'Restore Agent'}
|
|
142
142
|
subtitle="Enter the agent's ENS name to decrypt with an authorized operator wallet."
|
|
143
143
|
footer={footerHint('enter continue · esc back')}
|
|
144
144
|
>
|
|
@@ -162,7 +162,7 @@ export const RestoreFlow: React.FC<RestoreFlowProps> = ({
|
|
|
162
162
|
if (step.busy) {
|
|
163
163
|
return (
|
|
164
164
|
<Surface
|
|
165
|
-
title={isSwitch ? '
|
|
165
|
+
title={isSwitch ? 'Switch Agent' : 'Restore Agent'}
|
|
166
166
|
subtitle="Looking up the agent onchain."
|
|
167
167
|
footer={footerHint('esc cancels')}
|
|
168
168
|
>
|
|
@@ -174,7 +174,7 @@ export const RestoreFlow: React.FC<RestoreFlowProps> = ({
|
|
|
174
174
|
}
|
|
175
175
|
return (
|
|
176
176
|
<Surface
|
|
177
|
-
title={isSwitch ? '
|
|
177
|
+
title={isSwitch ? 'Switch Agent' : 'Restore Agent'}
|
|
178
178
|
subtitle={`Enter the ERC-8004 token ID on ${networkLabelForRegistry(step.registry)}.`}
|
|
179
179
|
footer={footerHint('enter continue · esc back')}
|
|
180
180
|
>
|
|
@@ -223,7 +223,7 @@ export const RestoreFlow: React.FC<RestoreFlowProps> = ({
|
|
|
223
223
|
if (step.kind === 'restore-select-token') {
|
|
224
224
|
return (
|
|
225
225
|
<Surface
|
|
226
|
-
title={isSwitch ? '
|
|
226
|
+
title={isSwitch ? 'Switch Agent' : 'Choose Your Agent'}
|
|
227
227
|
subtitle={step.ownerHandle}
|
|
228
228
|
footer={footerHint('enter select · esc back')}
|
|
229
229
|
>
|
|
@@ -260,7 +260,7 @@ export const RestoreFlow: React.FC<RestoreFlowProps> = ({
|
|
|
260
260
|
if (step.kind === 'restore-fetching') {
|
|
261
261
|
return (
|
|
262
262
|
<BusyScreen
|
|
263
|
-
title={isSwitch ? '
|
|
263
|
+
title={isSwitch ? 'Switching Agent' : 'Restoring Your Agent'}
|
|
264
264
|
subtitle="IPFS"
|
|
265
265
|
label="opening encrypted state from IPFS..."
|
|
266
266
|
onCancel={onBack}
|
|
@@ -348,7 +348,7 @@ function restoreAuthorizationView(
|
|
|
348
348
|
title: 'Operator Wallet Required',
|
|
349
349
|
subtitle: `Sign with the operator wallet ${shortAddress(requester)} to decrypt this snapshot.`,
|
|
350
350
|
label: 'waiting for operator wallet signature...',
|
|
351
|
-
progressTitle: isSwitch ? '
|
|
351
|
+
progressTitle: isSwitch ? 'Switching Agent' : 'Restoring Your Agent',
|
|
352
352
|
}
|
|
353
353
|
}
|
|
354
354
|
|
|
@@ -356,6 +356,6 @@ function restoreAuthorizationView(
|
|
|
356
356
|
title: 'Owner Wallet Required',
|
|
357
357
|
subtitle: `This encrypted snapshot requires the owner wallet ${shortAddress(owner)}.`,
|
|
358
358
|
label: 'waiting for owner wallet signature...',
|
|
359
|
-
progressTitle: isSwitch ? '
|
|
359
|
+
progressTitle: isSwitch ? 'Switching Agent' : 'Restoring Your Agent',
|
|
360
360
|
}
|
|
361
361
|
}
|
|
@@ -10,7 +10,7 @@ import {
|
|
|
10
10
|
restoreSkillsTree,
|
|
11
11
|
writeContinuityFiles,
|
|
12
12
|
} from '../../continuity/storage.js'
|
|
13
|
-
import {
|
|
13
|
+
import { syncAgentCardManifest } from '../../continuity/skills/publicSkillsSync.js'
|
|
14
14
|
import { recordPublishedContinuitySnapshot } from '../../continuity/snapshots.js'
|
|
15
15
|
import { requestBrowserWalletSignature } from '../../wallet/browserWallet.js'
|
|
16
16
|
import { setVaultAddressField } from '../../identityCompat.js'
|
|
@@ -18,7 +18,7 @@ import type { Step } from '../identityHubReducer.js'
|
|
|
18
18
|
import type { EffectCallbacks } from '../shared/effects/types.js'
|
|
19
19
|
import { isContinuitySnapshotEnvelope } from './envelopes.js'
|
|
20
20
|
import { restoreSignatureRequestForStep } from './auth.js'
|
|
21
|
-
import { type BackupMetadata, operatorStateFromCandidate,
|
|
21
|
+
import { type BackupMetadata, operatorStateFromCandidate, restorePublishedAgentCard } from './helpers.js'
|
|
22
22
|
|
|
23
23
|
export async function runRestoreAuthorize(
|
|
24
24
|
step: Extract<Step, { kind: 'restore-authorizing' }>,
|
|
@@ -99,10 +99,9 @@ export async function runRestoreAuthorize(
|
|
|
99
99
|
metadataCid: step.candidate.metadataCid,
|
|
100
100
|
state: restoredState,
|
|
101
101
|
backup,
|
|
102
|
-
...(step.candidate.publicDiscovery ? {
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
...(step.candidate.publicDiscovery.agentCardCid ? { agentCardCid: step.candidate.publicDiscovery.agentCardCid } : {}),
|
|
102
|
+
...(step.candidate.publicDiscovery?.agentCardCid ? {
|
|
103
|
+
agentCard: {
|
|
104
|
+
cid: step.candidate.publicDiscovery.agentCardCid,
|
|
106
105
|
...(step.candidate.publicDiscovery.updatedAt ? { updatedAt: step.candidate.publicDiscovery.updatedAt } : {}),
|
|
107
106
|
status: 'pinned',
|
|
108
107
|
},
|
|
@@ -115,9 +114,9 @@ export async function runRestoreAuthorize(
|
|
|
115
114
|
await restoreSkillsTree(nextIdentity, continuitySkills)
|
|
116
115
|
}
|
|
117
116
|
callbacks.onRestoreProgress?.({ phase: 'finishing', label: 'finalizing restored identity...' })
|
|
118
|
-
await
|
|
117
|
+
await restorePublishedAgentCard(nextIdentity, step.apiUrl, step.candidate.publicDiscovery?.agentCardCid)
|
|
119
118
|
await ensureIdentityMarkdownScaffold(nextIdentity)
|
|
120
|
-
await
|
|
119
|
+
await syncAgentCardManifest(nextIdentity).catch(() => null)
|
|
121
120
|
await recordPublishedContinuitySnapshot({ identity: nextIdentity, label: 'restored from agent backup' }).catch(() => null)
|
|
122
121
|
await callbacks.onIdentityComplete(nextIdentity, `ERC-8004 agent restored · #${step.candidate.agentId.toString()}`, 'restore')
|
|
123
122
|
}
|
|
@@ -6,7 +6,7 @@ import {
|
|
|
6
6
|
} from '../../continuity/envelope.js'
|
|
7
7
|
import { catFromIpfs } from '../../storage/ipfs.js'
|
|
8
8
|
import type { Erc8004AgentCandidate } from '../../registry/erc8004.js'
|
|
9
|
-
import {
|
|
9
|
+
import { writeAgentCardFile } from '../../continuity/storage.js'
|
|
10
10
|
import { normalizeApprovedOperatorWallets } from '../shared/operatorWallets.js'
|
|
11
11
|
|
|
12
12
|
export type BackupMetadata = NonNullable<EthagentIdentity['backup']>
|
|
@@ -75,7 +75,7 @@ export function operatorStateFromCandidate(candidate: Erc8004AgentCandidate): Re
|
|
|
75
75
|
}
|
|
76
76
|
}
|
|
77
77
|
|
|
78
|
-
export async function
|
|
78
|
+
export async function restorePublishedAgentCard(
|
|
79
79
|
identity: EthagentIdentity,
|
|
80
80
|
apiUrl: string,
|
|
81
81
|
cid: string | undefined,
|
|
@@ -83,7 +83,7 @@ export async function restorePublishedPublicSkills(
|
|
|
83
83
|
if (!cid) return false
|
|
84
84
|
try {
|
|
85
85
|
const raw = await catFromIpfs(apiUrl, cid)
|
|
86
|
-
await
|
|
86
|
+
await writeAgentCardFile(identity, new TextDecoder().decode(raw))
|
|
87
87
|
return true
|
|
88
88
|
} catch {
|
|
89
89
|
return false
|
|
@@ -12,7 +12,7 @@ import {
|
|
|
12
12
|
restoreSkillsTree,
|
|
13
13
|
writeContinuityFiles,
|
|
14
14
|
} from '../../continuity/storage.js'
|
|
15
|
-
import {
|
|
15
|
+
import { syncAgentCardManifest } from '../../continuity/skills/publicSkillsSync.js'
|
|
16
16
|
import { recordPublishedContinuitySnapshot, updatePublishedContinuitySnapshotContentHashes } from '../../continuity/snapshots.js'
|
|
17
17
|
import { catFromIpfs, DEFAULT_IPFS_API_URL } from '../../storage/ipfs.js'
|
|
18
18
|
import {
|
|
@@ -24,7 +24,7 @@ import { setVaultAddressField } from '../../identityCompat.js'
|
|
|
24
24
|
import type { EffectCallbacks } from '../shared/effects/types.js'
|
|
25
25
|
import { isContinuitySnapshotEnvelope, parseRestorableEnvelope } from './envelopes.js'
|
|
26
26
|
import { restoreMessageForWallet } from './auth.js'
|
|
27
|
-
import { type BackupMetadata, operatorStateFromCandidate,
|
|
27
|
+
import { type BackupMetadata, operatorStateFromCandidate, restorePublishedAgentCard } from './helpers.js'
|
|
28
28
|
|
|
29
29
|
export async function runRecoveryRefetch(
|
|
30
30
|
identity: EthagentIdentity,
|
|
@@ -46,7 +46,7 @@ export async function runRecoveryRefetch(
|
|
|
46
46
|
const raw = await catFromIpfs(apiUrl, candidate.backup.cid)
|
|
47
47
|
const envelope = parseRestorableEnvelope(raw)
|
|
48
48
|
if (!isContinuitySnapshotEnvelope(envelope)) {
|
|
49
|
-
throw new Error('This snapshot is in an unsupported envelope format and cannot be refetched here; use
|
|
49
|
+
throw new Error('This snapshot is in an unsupported envelope format and cannot be refetched here; use Switch Agent')
|
|
50
50
|
}
|
|
51
51
|
const eligibleAddresses: Address[] = [ownerAddress]
|
|
52
52
|
if (isWalletContinuitySnapshotEnvelope(envelope)) {
|
|
@@ -119,10 +119,9 @@ export async function runRecoveryRefetch(
|
|
|
119
119
|
metadataCid: candidate.metadataCid,
|
|
120
120
|
state: refreshedState,
|
|
121
121
|
backup: refreshedBackup,
|
|
122
|
-
...(candidate.publicDiscovery ? {
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
...(candidate.publicDiscovery.agentCardCid ? { agentCardCid: candidate.publicDiscovery.agentCardCid } : {}),
|
|
122
|
+
...(candidate.publicDiscovery?.agentCardCid ? {
|
|
123
|
+
agentCard: {
|
|
124
|
+
cid: candidate.publicDiscovery.agentCardCid,
|
|
126
125
|
...(candidate.publicDiscovery.updatedAt ? { updatedAt: candidate.publicDiscovery.updatedAt } : {}),
|
|
127
126
|
status: 'pinned',
|
|
128
127
|
},
|
|
@@ -133,11 +132,11 @@ export async function runRecoveryRefetch(
|
|
|
133
132
|
await restoreSkillsTree(nextIdentity, payload.skills)
|
|
134
133
|
}
|
|
135
134
|
callbacks.onRestoreProgress?.({ phase: 'finishing', label: 'finalizing refreshed identity...' })
|
|
136
|
-
const
|
|
135
|
+
const agentCardRestored = await restorePublishedAgentCard(nextIdentity, apiUrl, candidate.publicDiscovery?.agentCardCid)
|
|
137
136
|
await ensureIdentityMarkdownScaffold(nextIdentity)
|
|
138
|
-
await
|
|
137
|
+
await syncAgentCardManifest(nextIdentity).catch(() => null)
|
|
139
138
|
await recordPublishedContinuitySnapshot({ identity: nextIdentity, label: 'Refetched Latest Snapshot From Onchain' }).catch(() => null)
|
|
140
|
-
if (
|
|
139
|
+
if (agentCardRestored) {
|
|
141
140
|
const contentHashes = await localContinuitySnapshotContentHashes(nextIdentity)
|
|
142
141
|
await updatePublishedContinuitySnapshotContentHashes(nextIdentity, candidate.backup.cid, contentHashes).catch(() => null)
|
|
143
142
|
}
|