ethagent 3.1.2 → 3.3.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/README.md +15 -16
- package/package.json +1 -1
- package/src/chat/ChatScreen.tsx +25 -2
- package/src/chat/chatTurnOrchestrator.ts +17 -0
- package/src/identity/continuity/history.ts +8 -8
- package/src/identity/continuity/publicSkills.ts +3 -80
- package/src/identity/continuity/skills/frontmatter.ts +4 -1
- package/src/identity/continuity/skills/loadSkills.ts +73 -5
- package/src/identity/continuity/skills/publicSkillsSync.ts +9 -8
- package/src/identity/continuity/skills/scaffold.ts +1 -1
- package/src/identity/continuity/skills/types.ts +1 -1
- 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 -1
- package/src/identity/hub/continuity/effects.ts +17 -39
- package/src/identity/hub/continuity/skills/NewSkillVisibilityScreen.tsx +3 -4
- package/src/identity/hub/continuity/skills/SkillActionsScreen.tsx +3 -4
- package/src/identity/hub/continuity/skills/SkillsTreeScreen.tsx +2 -1
- 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/EnsEditFlow.tsx +19 -5
- package/src/identity/hub/ens/EnsEditReviewScreens.tsx +11 -11
- package/src/identity/hub/ens/EnsEditShared.tsx +2 -6
- package/src/identity/hub/ens/editCopy.ts +1 -3
- package/src/identity/hub/ens/transactions.ts +67 -18
- 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/models/llamacpp.ts +61 -1
- package/src/models/llamacppPreflight.ts +5 -1
- package/src/runtime/compaction.ts +11 -5
- 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/messages.ts +1 -1
- package/src/utils/withRetry.ts +2 -2
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import fs from 'node:fs/promises'
|
|
2
|
+
import path from 'node:path'
|
|
1
3
|
import { atomicWriteText } from '../../../storage/atomicWrite.js'
|
|
2
4
|
import type { EthagentIdentity } from '../../../storage/config.js'
|
|
3
5
|
import type { ContinuityFiles, ContinuitySkillsTree } from '../envelope.js'
|
|
@@ -6,10 +8,10 @@ import {
|
|
|
6
8
|
materializeSkillsTree,
|
|
7
9
|
} from '../skills/loadSkills.js'
|
|
8
10
|
import {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
+
renderAgentCardJsonForIdentity,
|
|
12
|
+
syncAgentCardManifest,
|
|
11
13
|
} from '../skills/publicSkillsSync.js'
|
|
12
|
-
import { defaultContinuityFiles,
|
|
14
|
+
import { defaultContinuityFiles, defaultAgentCardJson } from './defaults.js'
|
|
13
15
|
import {
|
|
14
16
|
ensureContinuityFiles,
|
|
15
17
|
ensureContinuityVault,
|
|
@@ -24,14 +26,14 @@ import type { ContinuityVaultRef, IdentityMarkdownScaffold } from './types.js'
|
|
|
24
26
|
|
|
25
27
|
export async function ensureIdentityMarkdownScaffold(
|
|
26
28
|
identity: EthagentIdentity,
|
|
27
|
-
options: {
|
|
29
|
+
options: { agentCardFallback?: string | (() => Promise<string>) } = {},
|
|
28
30
|
): Promise<IdentityMarkdownScaffold> {
|
|
29
31
|
const privateFiles = await ensureContinuityFiles(identity)
|
|
30
|
-
const
|
|
31
|
-
const
|
|
32
|
+
const agentCard = await ensureAgentCardFile(identity, { fallback: options.agentCardFallback })
|
|
33
|
+
const syncedCard = await syncAgentCardManifest(identity).catch(() => agentCard)
|
|
32
34
|
return {
|
|
33
35
|
...privateFiles,
|
|
34
|
-
'
|
|
36
|
+
'agent-card.json': syncedCard,
|
|
35
37
|
}
|
|
36
38
|
}
|
|
37
39
|
|
|
@@ -43,7 +45,7 @@ export async function writeIdentityMarkdownScaffold(
|
|
|
43
45
|
'SOUL.md': files['SOUL.md'],
|
|
44
46
|
'MEMORY.md': files['MEMORY.md'],
|
|
45
47
|
})
|
|
46
|
-
await
|
|
48
|
+
await writeAgentCardFile(identity, files['agent-card.json'])
|
|
47
49
|
return ref
|
|
48
50
|
}
|
|
49
51
|
|
|
@@ -57,7 +59,7 @@ export async function prepareSyncedIdentityMarkdownScaffold(identity: EthagentId
|
|
|
57
59
|
await ensureIdentityMarkdownScaffold(identity)
|
|
58
60
|
const privateFiles = await readContinuityFiles(identity)
|
|
59
61
|
const privateDefaults = defaultContinuityFiles(identity)
|
|
60
|
-
const
|
|
62
|
+
const agentCardDefault = await renderAgentCardJsonForIdentity(identity)
|
|
61
63
|
return {
|
|
62
64
|
'SOUL.md': syncGeneratedMarkdown(privateFiles['SOUL.md'], privateDefaults['SOUL.md'], [
|
|
63
65
|
{ marker: 'identity' },
|
|
@@ -65,13 +67,13 @@ export async function prepareSyncedIdentityMarkdownScaffold(identity: EthagentId
|
|
|
65
67
|
'MEMORY.md': syncGeneratedMarkdown(privateFiles['MEMORY.md'], privateDefaults['MEMORY.md'], [
|
|
66
68
|
{ marker: 'identity' },
|
|
67
69
|
]),
|
|
68
|
-
'
|
|
70
|
+
'agent-card.json': agentCardDefault,
|
|
69
71
|
}
|
|
70
72
|
}
|
|
71
73
|
|
|
72
|
-
export async function
|
|
73
|
-
await
|
|
74
|
-
return
|
|
74
|
+
export async function prepareSyncedAgentCardJson(identity: EthagentIdentity): Promise<string> {
|
|
75
|
+
await ensureAgentCardFile(identity)
|
|
76
|
+
return renderAgentCardJsonForIdentity(identity)
|
|
75
77
|
}
|
|
76
78
|
|
|
77
79
|
export async function prepareSyncedSkillsTree(identity: EthagentIdentity): Promise<ContinuitySkillsTree> {
|
|
@@ -87,30 +89,40 @@ export async function restoreSkillsTree(
|
|
|
87
89
|
}
|
|
88
90
|
|
|
89
91
|
|
|
90
|
-
export async function
|
|
92
|
+
export async function ensureAgentCardFile(
|
|
91
93
|
identity: EthagentIdentity,
|
|
92
94
|
options: { fallback?: string | (() => Promise<string>) } = {},
|
|
93
95
|
): Promise<string> {
|
|
94
96
|
const ref = await ensureContinuityVault(identity)
|
|
95
|
-
if (await exists(ref.
|
|
97
|
+
if (await exists(ref.agentCardPath)) return readAgentCardFile(identity)
|
|
96
98
|
|
|
97
|
-
const
|
|
98
|
-
|
|
99
|
-
|
|
99
|
+
const legacyPath = path.join(ref.dir, 'skills.json')
|
|
100
|
+
if (await exists(legacyPath)) {
|
|
101
|
+
const legacyContent = await readOrDefault(legacyPath, '')
|
|
102
|
+
if (legacyContent) {
|
|
103
|
+
await atomicWriteText(ref.agentCardPath, ensureTrailingNewline(legacyContent), { mode: 0o644 })
|
|
104
|
+
}
|
|
105
|
+
await fs.rm(legacyPath, { force: true })
|
|
106
|
+
if (await exists(ref.agentCardPath)) return readAgentCardFile(identity)
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const fallback = await resolveAgentCardFallback(identity, options.fallback)
|
|
110
|
+
await atomicWriteText(ref.agentCardPath, ensureTrailingNewline(fallback), { mode: 0o644 })
|
|
111
|
+
return readAgentCardFile(identity)
|
|
100
112
|
}
|
|
101
113
|
|
|
102
|
-
export async function
|
|
114
|
+
export async function readAgentCardFile(identity: EthagentIdentity): Promise<string> {
|
|
103
115
|
const ref = await ensureContinuityVault(identity)
|
|
104
|
-
return readOrDefault(ref.
|
|
116
|
+
return readOrDefault(ref.agentCardPath, defaultAgentCardJson(identity))
|
|
105
117
|
}
|
|
106
118
|
|
|
107
|
-
export async function
|
|
119
|
+
export async function writeAgentCardFile(identity: EthagentIdentity, content: string): Promise<ContinuityVaultRef> {
|
|
108
120
|
const ref = await ensureContinuityVault(identity)
|
|
109
|
-
await atomicWriteText(ref.
|
|
121
|
+
await atomicWriteText(ref.agentCardPath, ensureTrailingNewline(content), { mode: 0o644 })
|
|
110
122
|
return ref
|
|
111
123
|
}
|
|
112
124
|
|
|
113
|
-
async function
|
|
125
|
+
async function resolveAgentCardFallback(
|
|
114
126
|
identity: EthagentIdentity,
|
|
115
127
|
fallback: string | (() => Promise<string>) | undefined,
|
|
116
128
|
): Promise<string> {
|
|
@@ -119,8 +131,8 @@ async function resolvePublicSkillsFallback(
|
|
|
119
131
|
try {
|
|
120
132
|
return await fallback()
|
|
121
133
|
} catch {
|
|
122
|
-
return
|
|
134
|
+
return defaultAgentCardJson(identity)
|
|
123
135
|
}
|
|
124
136
|
}
|
|
125
|
-
return
|
|
137
|
+
return defaultAgentCardJson(identity)
|
|
126
138
|
}
|
|
@@ -2,10 +2,10 @@ import { createHash } from 'node:crypto'
|
|
|
2
2
|
import type { EthagentIdentity } from '../../../storage/config.js'
|
|
3
3
|
import type { ContinuityFiles, ContinuitySkillsTree } from '../envelope.js'
|
|
4
4
|
import { loadSkillsTree } from '../skills/loadSkills.js'
|
|
5
|
-
import {
|
|
5
|
+
import { syncAgentCardManifest } from '../skills/publicSkillsSync.js'
|
|
6
6
|
import { continuityVaultRef } from './paths.js'
|
|
7
7
|
import { exists, readContinuityFiles, statIfExists } from './files.js'
|
|
8
|
-
import {
|
|
8
|
+
import { readAgentCardFile } from './scaffold.js'
|
|
9
9
|
import type { ContinuityPublishState, ContinuitySnapshotContentHashes, ContinuityVaultRef, ContinuityWorkingTreeStatus } from './types.js'
|
|
10
10
|
|
|
11
11
|
export async function continuityVaultStatus(identity: EthagentIdentity): Promise<{ ready: boolean; files: ContinuityVaultRef }> {
|
|
@@ -22,7 +22,7 @@ export async function continuityWorkingTreeStatus(
|
|
|
22
22
|
const stats = await Promise.all([
|
|
23
23
|
statIfExists(ref.soulPath),
|
|
24
24
|
statIfExists(ref.memoryPath),
|
|
25
|
-
statIfExists(ref.
|
|
25
|
+
statIfExists(ref.agentCardPath),
|
|
26
26
|
])
|
|
27
27
|
const newestMs = Math.max(0, ...stats.flatMap(stat => stat ? [stat.mtimeMs] : []))
|
|
28
28
|
const ready = Boolean(stats[0] && stats[1])
|
|
@@ -54,30 +54,30 @@ export async function localContinuitySnapshotContentHashes(
|
|
|
54
54
|
identity: EthagentIdentity,
|
|
55
55
|
): Promise<ContinuitySnapshotContentHashes> {
|
|
56
56
|
const privateFiles = await readContinuityFiles(identity)
|
|
57
|
-
await
|
|
58
|
-
const
|
|
57
|
+
await syncAgentCardManifest(identity).catch(() => undefined)
|
|
58
|
+
const agentCard = await readAgentCardFile(identity)
|
|
59
59
|
const skills = await loadSkillsTree(identity).catch(() => ({} as ContinuitySkillsTree))
|
|
60
|
-
return continuitySnapshotContentHashes(privateFiles,
|
|
60
|
+
return continuitySnapshotContentHashes(privateFiles, agentCard, skills)
|
|
61
61
|
}
|
|
62
62
|
|
|
63
63
|
export function continuitySnapshotContentHashesFromSources(args: {
|
|
64
64
|
privateFiles: ContinuityFiles
|
|
65
|
-
|
|
65
|
+
agentCard: string
|
|
66
66
|
skills: ContinuitySkillsTree
|
|
67
67
|
}): ContinuitySnapshotContentHashes {
|
|
68
|
-
return continuitySnapshotContentHashes(args.privateFiles, args.
|
|
68
|
+
return continuitySnapshotContentHashes(args.privateFiles, args.agentCard, args.skills)
|
|
69
69
|
}
|
|
70
70
|
|
|
71
71
|
function continuitySnapshotContentHashes(
|
|
72
72
|
privateFiles: ContinuityFiles,
|
|
73
|
-
|
|
73
|
+
agentCard: string,
|
|
74
74
|
skills: ContinuitySkillsTree,
|
|
75
75
|
): ContinuitySnapshotContentHashes {
|
|
76
76
|
const skillsHash = hashSkillsTree(skills)
|
|
77
77
|
return {
|
|
78
78
|
'SOUL.md': hashContinuitySnapshotContent(privateFiles['SOUL.md']),
|
|
79
79
|
'MEMORY.md': hashContinuitySnapshotContent(privateFiles['MEMORY.md']),
|
|
80
|
-
'
|
|
80
|
+
'agent-card.json': hashContinuitySnapshotContent(agentCard),
|
|
81
81
|
...(skillsHash ? { 'private-skills': skillsHash } : {}),
|
|
82
82
|
}
|
|
83
83
|
}
|
|
@@ -101,7 +101,7 @@ function equalContinuitySnapshotHashes(
|
|
|
101
101
|
): boolean {
|
|
102
102
|
if (a['SOUL.md'] !== b['SOUL.md']) return false
|
|
103
103
|
if (a['MEMORY.md'] !== b['MEMORY.md']) return false
|
|
104
|
-
if (a['
|
|
104
|
+
if (a['agent-card.json'] !== b['agent-card.json']) return false
|
|
105
105
|
return a['private-skills'] === b['private-skills']
|
|
106
106
|
}
|
|
107
107
|
|
|
@@ -7,16 +7,16 @@ export type ContinuityVaultRef = {
|
|
|
7
7
|
dir: string
|
|
8
8
|
soulPath: string
|
|
9
9
|
memoryPath: string
|
|
10
|
-
|
|
10
|
+
agentCardPath: string
|
|
11
11
|
skillsDir: string
|
|
12
12
|
}
|
|
13
13
|
|
|
14
14
|
export type IdentityMarkdownScaffold = ContinuityFiles & {
|
|
15
|
-
'
|
|
15
|
+
'agent-card.json': string
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
-
export type ContinuitySnapshotFile = PrivateContinuityFile | '
|
|
19
|
-
export type ContinuitySnapshotContentHashes = Partial<Record<ContinuitySnapshotFile, string>> & Record<PrivateContinuityFile | '
|
|
18
|
+
export type ContinuitySnapshotFile = PrivateContinuityFile | 'agent-card.json' | 'private-skills'
|
|
19
|
+
export type ContinuitySnapshotContentHashes = Partial<Record<ContinuitySnapshotFile, string>> & Record<PrivateContinuityFile | 'agent-card.json', string>
|
|
20
20
|
export type ContinuityPublishState = 'not-restored' | 'not-published' | 'verify-needed' | 'local-changes' | 'published'
|
|
21
21
|
export type ContinuityWorkingTreeStatus = {
|
|
22
22
|
ready: boolean
|
|
@@ -17,15 +17,15 @@ export {
|
|
|
17
17
|
export { continuityAgentSnapshot, defaultContinuityFiles } from './storage/defaults.js'
|
|
18
18
|
export {
|
|
19
19
|
ensureIdentityMarkdownScaffold,
|
|
20
|
-
|
|
20
|
+
ensureAgentCardFile,
|
|
21
21
|
prepareSyncedIdentityMarkdownScaffold,
|
|
22
22
|
prepareSyncedSkillsTree,
|
|
23
|
-
|
|
24
|
-
|
|
23
|
+
prepareSyncedAgentCardJson,
|
|
24
|
+
readAgentCardFile,
|
|
25
25
|
restoreSkillsTree,
|
|
26
26
|
syncIdentityMarkdownScaffold,
|
|
27
27
|
writeIdentityMarkdownScaffold,
|
|
28
|
-
|
|
28
|
+
writeAgentCardFile,
|
|
29
29
|
} from './storage/scaffold.js'
|
|
30
30
|
export {
|
|
31
31
|
continuityVaultStatus,
|
|
@@ -1,51 +1,79 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
} as const
|
|
1
|
+
import type { Address } from 'viem'
|
|
2
|
+
import { encodeInteroperableAddress } from './erc7930.js'
|
|
4
3
|
|
|
5
|
-
|
|
4
|
+
export const AGENT_TOKEN_RECORD_KEY = 'org.ethagent.token'
|
|
6
5
|
|
|
7
|
-
export
|
|
8
|
-
|
|
9
|
-
] as const
|
|
10
|
-
|
|
11
|
-
export const AGENT_RECORD_READ_KEY_LIST: readonly string[] = AGENT_RECORD_KEY_LIST
|
|
12
|
-
|
|
13
|
-
export type AgentEnsRecords = {
|
|
14
|
-
token?: string
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
export type AgentEnsRecordState = AgentEnsRecords
|
|
6
|
+
export type AgentEnsRecords = Record<string, string>
|
|
7
|
+
export type AgentEnsRecordState = Record<string, string>
|
|
18
8
|
|
|
19
9
|
export type AgentRecordDiff = {
|
|
20
|
-
key:
|
|
21
|
-
field: keyof AgentEnsRecordState
|
|
10
|
+
key: string
|
|
22
11
|
current: string
|
|
23
12
|
next: string
|
|
24
13
|
changed: boolean
|
|
25
14
|
}
|
|
26
15
|
|
|
27
|
-
|
|
28
|
-
|
|
16
|
+
export type Ensip25KeyArgs = {
|
|
17
|
+
chainId: number
|
|
18
|
+
identityRegistryAddress: Address | string
|
|
19
|
+
agentId: string | bigint
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function buildEnsip25Key(args: Ensip25KeyArgs): string {
|
|
23
|
+
const agentIdStr = typeof args.agentId === 'bigint' ? args.agentId.toString() : args.agentId.trim()
|
|
24
|
+
if (!agentIdStr) throw new Error('agentId is required to build the ENSIP-25 record key')
|
|
25
|
+
if (agentIdStr.includes('[') || agentIdStr.includes(']')) {
|
|
26
|
+
throw new Error('agentId must not contain square brackets per ENSIP-25')
|
|
27
|
+
}
|
|
28
|
+
const registry = encodeInteroperableAddress({
|
|
29
|
+
chainId: args.chainId,
|
|
30
|
+
address: args.identityRegistryAddress as Address,
|
|
31
|
+
})
|
|
32
|
+
return `agent-registration[${registry}][${agentIdStr}]`
|
|
29
33
|
}
|
|
30
34
|
|
|
31
|
-
|
|
32
|
-
|
|
35
|
+
export function buildAgentTokenReferenceValue(args: Ensip25KeyArgs): string {
|
|
36
|
+
const agentIdStr = typeof args.agentId === 'bigint' ? args.agentId.toString() : args.agentId.trim()
|
|
37
|
+
return `eip155:${args.chainId}:${(args.identityRegistryAddress as string).toLowerCase()}:${agentIdStr}`
|
|
33
38
|
}
|
|
34
39
|
|
|
35
|
-
export function
|
|
40
|
+
export function buildAgentEnsRecords(args: {
|
|
41
|
+
chainId: number
|
|
42
|
+
identityRegistryAddress: Address | string
|
|
43
|
+
agentId: string | bigint | undefined
|
|
44
|
+
}): AgentEnsRecords {
|
|
45
|
+
if (args.agentId === undefined || args.agentId === '') return {}
|
|
46
|
+
const ensip25Key = buildEnsip25Key({
|
|
47
|
+
chainId: args.chainId,
|
|
48
|
+
identityRegistryAddress: args.identityRegistryAddress,
|
|
49
|
+
agentId: args.agentId,
|
|
50
|
+
})
|
|
51
|
+
const tokenValue = buildAgentTokenReferenceValue({
|
|
52
|
+
chainId: args.chainId,
|
|
53
|
+
identityRegistryAddress: args.identityRegistryAddress,
|
|
54
|
+
agentId: args.agentId,
|
|
55
|
+
})
|
|
36
56
|
return {
|
|
37
|
-
|
|
57
|
+
[ensip25Key]: '1',
|
|
58
|
+
[AGENT_TOKEN_RECORD_KEY]: tokenValue,
|
|
38
59
|
}
|
|
39
60
|
}
|
|
40
61
|
|
|
62
|
+
export function recordsFromTextMap(text: Record<string, string>): AgentEnsRecordState {
|
|
63
|
+
const out: AgentEnsRecordState = {}
|
|
64
|
+
for (const [key, value] of Object.entries(text)) {
|
|
65
|
+
if (value) out[key] = value
|
|
66
|
+
}
|
|
67
|
+
return out
|
|
68
|
+
}
|
|
69
|
+
|
|
41
70
|
export function diffRecords(current: AgentEnsRecordState, next: AgentEnsRecords): AgentRecordDiff[] {
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
const currentValue = (current[
|
|
45
|
-
const nextValue = (next[
|
|
71
|
+
const keys = new Set<string>([...Object.keys(current), ...Object.keys(next)])
|
|
72
|
+
return Array.from(keys).map(key => {
|
|
73
|
+
const currentValue = (current[key] ?? '').trim()
|
|
74
|
+
const nextValue = (next[key] ?? '').trim()
|
|
46
75
|
return {
|
|
47
76
|
key,
|
|
48
|
-
field,
|
|
49
77
|
current: currentValue,
|
|
50
78
|
next: nextValue,
|
|
51
79
|
changed: currentValue !== nextValue,
|
|
@@ -61,22 +89,10 @@ export function changedRecords(current: AgentEnsRecordState, next: AgentEnsRecor
|
|
|
61
89
|
return out
|
|
62
90
|
}
|
|
63
91
|
|
|
64
|
-
export function
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
export function formatRecordValue(_field: keyof AgentEnsRecordState, value: string): string {
|
|
69
|
-
return value
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
export function buildAgentEnsRecords(args: {
|
|
73
|
-
chainId: number
|
|
74
|
-
identityRegistryAddress: string
|
|
75
|
-
agentId: string | undefined
|
|
76
|
-
}): AgentEnsRecords {
|
|
77
|
-
const records: AgentEnsRecords = {}
|
|
78
|
-
if (args.agentId) {
|
|
79
|
-
records.token = `eip155:${args.chainId}:${args.identityRegistryAddress.toLowerCase()}:${args.agentId}`
|
|
92
|
+
export function clearedRecords(current: AgentEnsRecordState): AgentEnsRecords {
|
|
93
|
+
const out: AgentEnsRecords = {}
|
|
94
|
+
for (const key of Object.keys(current)) {
|
|
95
|
+
out[key] = ''
|
|
80
96
|
}
|
|
81
|
-
return
|
|
97
|
+
return out
|
|
82
98
|
}
|
|
@@ -9,7 +9,6 @@ import {
|
|
|
9
9
|
} from 'viem'
|
|
10
10
|
import { mainnet } from 'viem/chains'
|
|
11
11
|
import {
|
|
12
|
-
AGENT_RECORD_READ_KEY_LIST,
|
|
13
12
|
recordsFromTextMap,
|
|
14
13
|
type AgentEnsRecordState,
|
|
15
14
|
} from '../agentRecords.js'
|
|
@@ -81,9 +80,14 @@ export async function readAddressRecord(client: EnsAutomationReadClient, resolve
|
|
|
81
80
|
}
|
|
82
81
|
}
|
|
83
82
|
|
|
84
|
-
export async function readTextRecords(
|
|
83
|
+
export async function readTextRecords(
|
|
84
|
+
client: EnsAutomationReadClient,
|
|
85
|
+
resolverAddress: Address,
|
|
86
|
+
node: Hex,
|
|
87
|
+
keys: readonly string[],
|
|
88
|
+
): Promise<AgentEnsRecordState> {
|
|
85
89
|
const text: Record<string, string> = {}
|
|
86
|
-
for (const key of
|
|
90
|
+
for (const key of keys) {
|
|
87
91
|
try {
|
|
88
92
|
const value = await client.readContract({
|
|
89
93
|
address: resolverAddress,
|
|
@@ -105,10 +109,3 @@ export function isZero(address: Address): boolean {
|
|
|
105
109
|
export function sameAddress(a: Address, b: Address): boolean {
|
|
106
110
|
return a.toLowerCase() === b.toLowerCase()
|
|
107
111
|
}
|
|
108
|
-
|
|
109
|
-
export function normalizeAgentRecords(records: AgentEnsRecordState): AgentEnsRecordState {
|
|
110
|
-
return {
|
|
111
|
-
...records,
|
|
112
|
-
...(records.token ? { token: records.token.toLowerCase() } : {}),
|
|
113
|
-
}
|
|
114
|
-
}
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { getAddress, namehash, type Address } from 'viem'
|
|
2
2
|
import {
|
|
3
|
+
AGENT_TOKEN_RECORD_KEY,
|
|
3
4
|
buildAgentEnsRecords,
|
|
5
|
+
buildEnsip25Key,
|
|
4
6
|
diffRecords,
|
|
5
7
|
} from '../agentRecords.js'
|
|
6
8
|
import { normalizeEthDomain, splitSubdomainName } from '../ensLookup.js'
|
|
@@ -12,7 +14,6 @@ import {
|
|
|
12
14
|
import {
|
|
13
15
|
createEnsAutomationClient,
|
|
14
16
|
isZero,
|
|
15
|
-
normalizeAgentRecords,
|
|
16
17
|
readAddressRecord,
|
|
17
18
|
readOwner,
|
|
18
19
|
readResolver,
|
|
@@ -225,26 +226,19 @@ export async function preflightEnsSetup(args: EnsSetupPreflightArgs): Promise<En
|
|
|
225
226
|
}
|
|
226
227
|
|
|
227
228
|
const currentAddress = isZero(childResolver) ? null : await readAddressRecord(client, resolverAddress, fullNode)
|
|
228
|
-
const
|
|
229
|
+
const ensip25Key = buildEnsip25Key({
|
|
230
|
+
chainId: args.registry.chainId,
|
|
231
|
+
identityRegistryAddress: args.registry.identityRegistryAddress,
|
|
232
|
+
agentId: String(args.agentId),
|
|
233
|
+
})
|
|
234
|
+
const currentRecords = isZero(childResolver)
|
|
235
|
+
? {}
|
|
236
|
+
: await readTextRecords(client, resolverAddress, fullNode, [ensip25Key, AGENT_TOKEN_RECORD_KEY])
|
|
229
237
|
const nextRecords = buildAgentEnsRecords({
|
|
230
238
|
chainId: args.registry.chainId,
|
|
231
239
|
identityRegistryAddress: args.registry.identityRegistryAddress,
|
|
232
240
|
agentId: String(args.agentId),
|
|
233
241
|
})
|
|
234
|
-
if (currentRecords.token && nextRecords.token && currentRecords.token !== nextRecords.token) {
|
|
235
|
-
return manual(args, {
|
|
236
|
-
rootName,
|
|
237
|
-
label,
|
|
238
|
-
fullName,
|
|
239
|
-
operatorAddress,
|
|
240
|
-
ownerAddress,
|
|
241
|
-
resolverAddress,
|
|
242
|
-
reason: 'token-record-collision',
|
|
243
|
-
detail: `${fullName} already points to another ERC-8004 token`,
|
|
244
|
-
currentRecords,
|
|
245
|
-
nextRecords,
|
|
246
|
-
})
|
|
247
|
-
}
|
|
248
242
|
|
|
249
243
|
const recordDiffs = diffRecords(currentRecords, nextRecords)
|
|
250
244
|
const addressChanged = !currentAddress || !sameAddress(currentAddress, ownerAddress)
|
|
@@ -24,6 +24,7 @@ export {
|
|
|
24
24
|
encodeEnsRecordsTransaction,
|
|
25
25
|
encodeEnsRegistryTransaction,
|
|
26
26
|
} from './ensAutomation/transactions.js'
|
|
27
|
+
export { readAddressRecord } from './ensAutomation/read.js'
|
|
27
28
|
export { isRootEthName } from './ensAutomation/names.js'
|
|
28
29
|
export { preflightDeleteSubdomain } from './ensAutomation/delete.js'
|
|
29
30
|
export { compareOperatorSets } from './ensAutomation/operators.js'
|
|
@@ -11,7 +11,7 @@ export type EncodedEnsRecordTransaction = {
|
|
|
11
11
|
calls: Hex[]
|
|
12
12
|
}
|
|
13
13
|
|
|
14
|
-
export async function
|
|
14
|
+
export async function encodeSetEnsip25TextRecord(
|
|
15
15
|
fullName: string,
|
|
16
16
|
records: Record<string, string>,
|
|
17
17
|
opts: DiscoverOptions = {},
|
|
@@ -16,4 +16,4 @@ export { resolveEnsAddress, readEthagentTextRecords, readResolverAddress } from
|
|
|
16
16
|
export { parseAgentTokenReference } from './ensLookup/tokenReference.js'
|
|
17
17
|
export { discoverOwnedEnsNameDetails, discoverOwnedEnsNames } from './ensLookup/discovery.js'
|
|
18
18
|
export { validateAgentEnsLink } from './ensLookup/validation.js'
|
|
19
|
-
export {
|
|
19
|
+
export { encodeSetEnsip25TextRecord } from './ensLookup/records.js'
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import type { Address } from 'viem'
|
|
2
|
+
|
|
3
|
+
const VERSION = 1
|
|
4
|
+
const CHAIN_TYPE_EIP155 = 0
|
|
5
|
+
const ADDRESS_BYTE_LENGTH = 20
|
|
6
|
+
|
|
7
|
+
export type InteroperableAddressArgs = {
|
|
8
|
+
chainId: number
|
|
9
|
+
address: Address
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function encodeInteroperableAddress(args: InteroperableAddressArgs): `0x${string}` {
|
|
13
|
+
if (!Number.isInteger(args.chainId) || args.chainId <= 0) {
|
|
14
|
+
throw new Error(`invalid chainId for ERC-7930: ${args.chainId}`)
|
|
15
|
+
}
|
|
16
|
+
const addressHex = args.address.toLowerCase().replace(/^0x/, '')
|
|
17
|
+
if (!/^[0-9a-f]{40}$/.test(addressHex)) {
|
|
18
|
+
throw new Error(`invalid address for ERC-7930: ${args.address}`)
|
|
19
|
+
}
|
|
20
|
+
const chainRefBytes = minimalBigEndianBytes(BigInt(args.chainId))
|
|
21
|
+
if (chainRefBytes.length > 0xff) {
|
|
22
|
+
throw new Error(`chainId too large for ERC-7930 single-byte length prefix: ${args.chainId}`)
|
|
23
|
+
}
|
|
24
|
+
return `0x${uint16Hex(VERSION)}${uint16Hex(CHAIN_TYPE_EIP155)}${uint8Hex(chainRefBytes.length)}${bytesHex(chainRefBytes)}${uint8Hex(ADDRESS_BYTE_LENGTH)}${addressHex}` as `0x${string}`
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function uint16Hex(n: number): string {
|
|
28
|
+
return n.toString(16).padStart(4, '0')
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function uint8Hex(n: number): string {
|
|
32
|
+
return n.toString(16).padStart(2, '0')
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function minimalBigEndianBytes(value: bigint): Uint8Array {
|
|
36
|
+
if (value === 0n) return new Uint8Array([0])
|
|
37
|
+
const bytes: number[] = []
|
|
38
|
+
let remaining = value
|
|
39
|
+
while (remaining > 0n) {
|
|
40
|
+
bytes.unshift(Number(remaining & 0xffn))
|
|
41
|
+
remaining >>= 8n
|
|
42
|
+
}
|
|
43
|
+
return new Uint8Array(bytes)
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function bytesHex(bytes: Uint8Array): string {
|
|
47
|
+
return Array.from(bytes).map(b => b.toString(16).padStart(2, '0')).join('')
|
|
48
|
+
}
|
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
runPublicProfileStorageSubmit,
|
|
10
10
|
} from './profile/effects.js'
|
|
11
11
|
import { resolveVaultAddress } from './custody/transactions.js'
|
|
12
|
+
import { readCustodyMode } from './custody/state.js'
|
|
12
13
|
import { WalletApprovalScreen } from './shared/components/WalletApprovalScreen.js'
|
|
13
14
|
import { RebackupStorageScreen } from './continuity/RebackupStorageScreen.js'
|
|
14
15
|
import { BusyScreen } from './shared/components/BusyScreen.js'
|
|
@@ -321,12 +322,14 @@ export const IdentityHubOperationalRoutes: React.FC<IdentityHubOperationalRoutes
|
|
|
321
322
|
|
|
322
323
|
if (step.kind === 'rebackup-signing') {
|
|
323
324
|
const approval = rebackupWalletApprovalView(step.identity, step.profileUpdates)
|
|
325
|
+
const vaultRouted = Boolean(step.vaultAddress)
|
|
326
|
+
&& readCustodyMode(step.identity.state as Record<string, unknown> | undefined) === 'advanced'
|
|
324
327
|
return (
|
|
325
328
|
<WalletApprovalScreen
|
|
326
329
|
title={approval.title}
|
|
327
330
|
subtitle={custodyFlow.renderRebackupSubtitle(
|
|
328
331
|
approval.subtitle,
|
|
329
|
-
|
|
332
|
+
vaultRouted,
|
|
330
333
|
)}
|
|
331
334
|
walletSession={walletSession}
|
|
332
335
|
label={approval.label}
|