@swarmclawai/swarmclaw 1.2.8 → 1.2.9
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 +30 -6
- package/package.json +2 -2
- package/src/app/agents/[id]/page.tsx +1 -18
- package/src/app/api/agents/thread-route.test.ts +0 -1
- package/src/app/api/approvals/route.test.ts +6 -22
- package/src/app/api/connectors/route.ts +2 -2
- package/src/app/api/portability/export/route.ts +8 -0
- package/src/app/api/portability/import/route.test.ts +80 -0
- package/src/app/api/portability/import/route.ts +28 -0
- package/src/app/api/settings/route.ts +0 -2
- package/src/app/api/wallets/[id]/route.ts +15 -157
- package/src/app/api/wallets/generate/route.ts +22 -0
- package/src/app/api/wallets/route.test.ts +147 -0
- package/src/app/api/wallets/route.ts +13 -95
- package/src/app/autonomy/page.tsx +2 -57
- package/src/app/protocols/page.tsx +2 -21
- package/src/app/settings/page.tsx +0 -9
- package/src/app/wallets/page.tsx +105 -5
- package/src/cli/index.js +21 -33
- package/src/cli/spec.js +19 -30
- package/src/components/agents/agent-sheet.tsx +2 -40
- package/src/components/agents/inspector-panel.tsx +0 -83
- package/src/components/chat/chat-card.tsx +0 -31
- package/src/components/chat/message-bubble.tsx +1 -108
- package/src/components/connectors/connector-sheet.tsx +25 -1
- package/src/components/layout/sidebar-rail.tsx +6 -10
- package/src/components/projects/project-detail.tsx +3 -35
- package/src/components/projects/tabs/overview-tab.tsx +3 -59
- package/src/components/projects/tabs/work-tab.tsx +7 -77
- package/src/components/protocols/structured-session-launcher.tsx +1 -22
- package/src/components/shared/connector-platform-icon.tsx +1 -0
- package/src/components/tasks/task-card.tsx +4 -34
- package/src/components/tasks/task-sheet.tsx +6 -36
- package/src/components/wallets/wallet-list.tsx +150 -0
- package/src/lib/app/navigation.test.ts +0 -13
- package/src/lib/app/navigation.ts +2 -7
- package/src/lib/app/view-constants.ts +14 -19
- package/src/lib/server/agents/agent-thread-session.ts +0 -1
- package/src/lib/server/agents/delegation-advisory.test.ts +0 -1
- package/src/lib/server/agents/delegation-jobs.test.ts +0 -69
- package/src/lib/server/agents/delegation-jobs.ts +0 -25
- package/src/lib/server/agents/main-agent-loop.ts +1 -49
- package/src/lib/server/agents/subagent-runtime.ts +0 -1
- package/src/lib/server/approval-match.ts +0 -85
- package/src/lib/server/approvals.test.ts +6 -6
- package/src/lib/server/approvals.ts +0 -6
- package/src/lib/server/autonomy/supervisor-reflection.test.ts +0 -1
- package/src/lib/server/builtin-extensions.ts +0 -2
- package/src/lib/server/capability-router.test.ts +0 -2
- package/src/lib/server/chat-execution/chat-execution-tool-events.test.ts +14 -14
- package/src/lib/server/chat-execution/chat-execution-types.ts +0 -2
- package/src/lib/server/chat-execution/chat-execution-utils.ts +0 -2
- package/src/lib/server/chat-execution/chat-streaming-utils.ts +2 -30
- package/src/lib/server/chat-execution/chat-turn-finalization.ts +1 -36
- package/src/lib/server/chat-execution/chat-turn-preparation.ts +2 -22
- package/src/lib/server/chat-execution/iteration-event-handler.ts +0 -24
- package/src/lib/server/chat-execution/message-classifier.test.ts +0 -45
- package/src/lib/server/chat-execution/message-classifier.ts +1 -16
- package/src/lib/server/chat-execution/prompt-builder.test.ts +0 -1
- package/src/lib/server/chat-execution/prompt-builder.ts +0 -30
- package/src/lib/server/chat-execution/prompt-sections.ts +0 -1
- package/src/lib/server/chat-execution/situational-awareness.test.ts +2 -73
- package/src/lib/server/chat-execution/situational-awareness.ts +4 -38
- package/src/lib/server/chat-execution/stream-agent-chat.test.ts +8 -123
- package/src/lib/server/chat-execution/stream-agent-chat.ts +1 -5
- package/src/lib/server/chat-execution/stream-continuation.test.ts +4 -52
- package/src/lib/server/chat-execution/stream-continuation.ts +6 -48
- package/src/lib/server/chatrooms/session-mailbox.ts +0 -10
- package/src/lib/server/chats/chat-session-service.ts +3 -5
- package/src/lib/server/connectors/connector-inbound.ts +0 -1
- package/src/lib/server/connectors/connector-lifecycle.ts +19 -3
- package/src/lib/server/connectors/connector-service.ts +39 -9
- package/src/lib/server/connectors/swarmdock-bidding.ts +74 -0
- package/src/lib/server/connectors/swarmdock-payloads.test.ts +85 -0
- package/src/lib/server/connectors/swarmdock-secret.test.ts +128 -0
- package/src/lib/server/connectors/swarmdock-secret.ts +152 -0
- package/src/lib/server/connectors/swarmdock-tasks.ts +119 -0
- package/src/lib/server/connectors/swarmdock.ts +255 -0
- package/src/lib/server/execution-brief.test.ts +2 -25
- package/src/lib/server/execution-brief.ts +12 -35
- package/src/lib/server/execution-engine/task-attempt.ts +0 -1
- package/src/lib/server/persistence/storage-context.ts +0 -5
- package/src/lib/server/portability/export.ts +109 -0
- package/src/lib/server/portability/import.ts +159 -0
- package/src/lib/server/protocols/protocol-normalization.ts +0 -4
- package/src/lib/server/protocols/protocol-queries.ts +0 -6
- package/src/lib/server/protocols/protocol-run-lifecycle.ts +4 -32
- package/src/lib/server/protocols/protocol-service.ts +0 -1
- package/src/lib/server/protocols/protocol-step-helpers.ts +0 -4
- package/src/lib/server/protocols/protocol-step-processors.ts +0 -6
- package/src/lib/server/protocols/protocol-swarm.ts +0 -2
- package/src/lib/server/protocols/protocol-types.ts +0 -2
- package/src/lib/server/provider-health.ts +0 -9
- package/src/lib/server/runtime/daemon-state/core.ts +0 -9
- package/src/lib/server/runtime/daemon-state.test.ts +0 -35
- package/src/lib/server/runtime/heartbeat-service.ts +3 -23
- package/src/lib/server/runtime/queue/core.ts +11 -33
- package/src/lib/server/runtime/runtime-storage-write-paths.test.ts +6 -6
- package/src/lib/server/runtime/scheduler.ts +0 -13
- package/src/lib/server/runtime/session-run-manager/drain.ts +0 -24
- package/src/lib/server/runtime/session-run-manager/enqueue.ts +0 -1
- package/src/lib/server/runtime/session-run-manager/queries.ts +0 -1
- package/src/lib/server/runtime/session-run-manager/recovery.ts +0 -1
- package/src/lib/server/runtime/session-run-manager.test.ts +0 -28
- package/src/lib/server/session-tools/crud.ts +0 -14
- package/src/lib/server/session-tools/delegate.ts +0 -4
- package/src/lib/server/session-tools/index.ts +0 -4
- package/src/lib/server/session-tools/team-context.ts +0 -3
- package/src/lib/server/storage-normalization.ts +8 -0
- package/src/lib/server/storage.ts +18 -45
- package/src/lib/server/tasks/task-checkout.ts +59 -0
- package/src/lib/server/tasks/task-lifecycle.ts +2 -0
- package/src/lib/server/tasks/task-route-service.ts +4 -26
- package/src/lib/server/tasks/task-service.ts +0 -7
- package/src/lib/server/tool-aliases.ts +0 -1
- package/src/lib/server/tool-capability-policy-advanced.test.ts +4 -4
- package/src/lib/server/tool-capability-policy.ts +0 -2
- package/src/lib/server/tool-planning.ts +0 -12
- package/src/lib/server/universal-tool-access.ts +0 -1
- package/src/lib/server/wallets/wallet-crypto.ts +33 -0
- package/src/lib/server/wallets/wallet-repository.ts +24 -0
- package/src/lib/server/wallets/wallet-service.ts +119 -0
- package/src/lib/server/working-state/extraction.ts +8 -42
- package/src/lib/server/working-state/normalization.ts +10 -103
- package/src/lib/server/working-state/service.ts +12 -21
- package/src/lib/strip-internal-metadata.test.ts +1 -1
- package/src/lib/strip-internal-metadata.ts +1 -1
- package/src/lib/tool-definitions.ts +0 -1
- package/src/lib/validation/schemas.ts +33 -2
- package/src/stores/slices/data-slice.ts +5 -1
- package/src/stores/slices/ui-slice.ts +0 -4
- package/src/types/agent.ts +0 -84
- package/src/types/app-settings.ts +0 -2
- package/src/types/approval.ts +0 -2
- package/src/types/connector.ts +1 -0
- package/src/types/index.ts +1 -1
- package/src/types/message.ts +0 -1
- package/src/types/misc.ts +0 -2
- package/src/types/protocol.ts +0 -2
- package/src/types/run.ts +0 -3
- package/src/types/session.ts +1 -51
- package/src/types/swarmdock.ts +29 -0
- package/src/types/task.ts +7 -3
- package/src/types/working-state.ts +2 -9
- package/src/views/settings/section-runtime-loop.tsx +0 -14
- package/src/app/api/canvas/[sessionId]/route.ts +0 -35
- package/src/app/api/missions/[id]/actions/route.ts +0 -31
- package/src/app/api/missions/[id]/events/route.ts +0 -14
- package/src/app/api/missions/[id]/route.ts +0 -10
- package/src/app/api/missions/route.test.ts +0 -244
- package/src/app/api/missions/route.ts +0 -57
- package/src/app/api/wallets/[id]/approve/route.ts +0 -79
- package/src/app/api/wallets/[id]/balance-history/route.ts +0 -18
- package/src/app/api/wallets/[id]/send/route.ts +0 -113
- package/src/app/api/wallets/[id]/transactions/route.ts +0 -18
- package/src/app/missions/[id]/page.tsx +0 -3
- package/src/app/missions/page.tsx +0 -685
- package/src/components/canvas/canvas-panel.tsx +0 -267
- package/src/components/wallets/wallet-approval-dialog.tsx +0 -107
- package/src/components/wallets/wallet-panel.tsx +0 -1010
- package/src/components/wallets/wallet-section.tsx +0 -260
- package/src/features/missions/queries.ts +0 -23
- package/src/lib/canvas-content.test.ts +0 -360
- package/src/lib/canvas-content.ts +0 -198
- package/src/lib/server/canvas-content.test.ts +0 -32
- package/src/lib/server/canvas-content.ts +0 -6
- package/src/lib/server/ethereum.ts +0 -591
- package/src/lib/server/evm-swap.ts +0 -476
- package/src/lib/server/missions/mission-intent.test.ts +0 -63
- package/src/lib/server/missions/mission-intent.ts +0 -569
- package/src/lib/server/missions/mission-repository.ts +0 -74
- package/src/lib/server/missions/mission-service/actions.ts +0 -6
- package/src/lib/server/missions/mission-service/bindings.ts +0 -9
- package/src/lib/server/missions/mission-service/context.ts +0 -4
- package/src/lib/server/missions/mission-service/core.ts +0 -2271
- package/src/lib/server/missions/mission-service/queries.ts +0 -12
- package/src/lib/server/missions/mission-service/recovery.ts +0 -5
- package/src/lib/server/missions/mission-service/ticks.ts +0 -9
- package/src/lib/server/missions/mission-service.test.ts +0 -888
- package/src/lib/server/missions/mission-service.ts +0 -6
- package/src/lib/server/session-tools/canvas.ts +0 -105
- package/src/lib/server/session-tools/wallet-tool.test.ts +0 -150
- package/src/lib/server/session-tools/wallet.ts +0 -1287
- package/src/lib/server/solana.ts +0 -327
- package/src/lib/server/wallet/wallet-execution.test.ts +0 -198
- package/src/lib/server/wallet/wallet-portfolio.test.ts +0 -98
- package/src/lib/server/wallet/wallet-portfolio.ts +0 -772
- package/src/lib/server/wallet/wallet-service.test.ts +0 -81
- package/src/lib/server/wallet/wallet-service.ts +0 -225
- package/src/lib/wallet/wallet-transactions.test.ts +0 -75
- package/src/lib/wallet/wallet-transactions.ts +0 -43
- package/src/lib/wallet/wallet.test.ts +0 -333
- package/src/lib/wallet/wallet.ts +0 -183
- package/src/types/mission.ts +0 -185
- package/src/views/settings/section-wallets.tsx +0 -35
|
@@ -1,591 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
JsonRpcProvider,
|
|
3
|
-
Interface,
|
|
4
|
-
ParamType,
|
|
5
|
-
Result,
|
|
6
|
-
Wallet,
|
|
7
|
-
getBytes,
|
|
8
|
-
getAddress,
|
|
9
|
-
isAddress,
|
|
10
|
-
keccak256,
|
|
11
|
-
type JsonFragment,
|
|
12
|
-
type TransactionRequest,
|
|
13
|
-
type TypedDataDomain,
|
|
14
|
-
type TypedDataField,
|
|
15
|
-
} from 'ethers'
|
|
16
|
-
|
|
17
|
-
import { decryptKey, encryptKey } from './storage'
|
|
18
|
-
import { errorMessage } from '@/lib/shared-utils'
|
|
19
|
-
|
|
20
|
-
export type EvmNetworkId = 'ethereum' | 'arbitrum' | 'base'
|
|
21
|
-
|
|
22
|
-
export interface EvmNetworkConfig {
|
|
23
|
-
id: EvmNetworkId
|
|
24
|
-
label: string
|
|
25
|
-
chainId: number
|
|
26
|
-
rpcUrl: string
|
|
27
|
-
addressExplorerBaseUrl: string
|
|
28
|
-
transactionExplorerBaseUrl: string
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
export interface EthereumExecutionOptions {
|
|
32
|
-
network?: EvmNetworkId | string | null
|
|
33
|
-
rpcUrl?: string | null
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
export interface EthereumMessageInput {
|
|
37
|
-
message?: string | null
|
|
38
|
-
messageHex?: string | null
|
|
39
|
-
messageBase64?: string | null
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
function serializeEvmValue(value: unknown): unknown {
|
|
43
|
-
if (typeof value === 'bigint') return value.toString()
|
|
44
|
-
if (Array.isArray(value)) return value.map((entry) => serializeEvmValue(entry))
|
|
45
|
-
if (value instanceof Uint8Array) return `0x${Buffer.from(value).toString('hex')}`
|
|
46
|
-
if (value && typeof value === 'object') {
|
|
47
|
-
if (value instanceof Result) {
|
|
48
|
-
return Array.from(value).map((entry) => serializeEvmValue(entry))
|
|
49
|
-
}
|
|
50
|
-
const out: Record<string, unknown> = {}
|
|
51
|
-
for (const [key, entry] of Object.entries(value)) {
|
|
52
|
-
if (/^\d+$/.test(key)) continue
|
|
53
|
-
out[key] = serializeEvmValue(entry)
|
|
54
|
-
}
|
|
55
|
-
return out
|
|
56
|
-
}
|
|
57
|
-
return value
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
const DEFAULT_RPC_URL = process.env.ETHEREUM_RPC_URL || process.env.EVM_RPC_URL || 'https://ethereum-rpc.publicnode.com'
|
|
61
|
-
const DEFAULT_EVM_RPC_TIMEOUT_MS = (() => {
|
|
62
|
-
const parsed = Number.parseInt(process.env.EVM_RPC_TIMEOUT_MS || '20000', 10)
|
|
63
|
-
if (!Number.isFinite(parsed) || parsed <= 0) return 20_000
|
|
64
|
-
return parsed
|
|
65
|
-
})()
|
|
66
|
-
|
|
67
|
-
const EVM_NETWORKS: Record<EvmNetworkId, EvmNetworkConfig> = {
|
|
68
|
-
ethereum: {
|
|
69
|
-
id: 'ethereum',
|
|
70
|
-
label: 'Ethereum',
|
|
71
|
-
chainId: 1,
|
|
72
|
-
rpcUrl: process.env.ETHEREUM_RPC_URL || process.env.EVM_RPC_URL || 'https://ethereum-rpc.publicnode.com',
|
|
73
|
-
addressExplorerBaseUrl: 'https://etherscan.io/address/',
|
|
74
|
-
transactionExplorerBaseUrl: 'https://etherscan.io/tx/',
|
|
75
|
-
},
|
|
76
|
-
arbitrum: {
|
|
77
|
-
id: 'arbitrum',
|
|
78
|
-
label: 'Arbitrum',
|
|
79
|
-
chainId: 42161,
|
|
80
|
-
rpcUrl: process.env.ARBITRUM_RPC_URL || 'https://arbitrum-one-rpc.publicnode.com',
|
|
81
|
-
addressExplorerBaseUrl: 'https://arbiscan.io/address/',
|
|
82
|
-
transactionExplorerBaseUrl: 'https://arbiscan.io/tx/',
|
|
83
|
-
},
|
|
84
|
-
base: {
|
|
85
|
-
id: 'base',
|
|
86
|
-
label: 'Base',
|
|
87
|
-
chainId: 8453,
|
|
88
|
-
rpcUrl: process.env.BASE_RPC_URL || 'https://base-rpc.publicnode.com',
|
|
89
|
-
addressExplorerBaseUrl: 'https://basescan.org/address/',
|
|
90
|
-
transactionExplorerBaseUrl: 'https://basescan.org/tx/',
|
|
91
|
-
},
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
function normalizeHexData(value: string, fieldName: string): string {
|
|
95
|
-
const trimmed = value.trim()
|
|
96
|
-
if (!/^0x[0-9a-fA-F]*$/.test(trimmed)) {
|
|
97
|
-
throw new Error(`${fieldName} must be a 0x-prefixed hex string`)
|
|
98
|
-
}
|
|
99
|
-
return trimmed
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
function parseBigIntField(value: unknown, fieldName: string): bigint | undefined {
|
|
103
|
-
if (value === undefined || value === null || value === '') return undefined
|
|
104
|
-
if (typeof value === 'bigint') return value
|
|
105
|
-
if (typeof value === 'number' && Number.isFinite(value)) return BigInt(Math.trunc(value))
|
|
106
|
-
if (typeof value === 'string') {
|
|
107
|
-
const trimmed = value.trim()
|
|
108
|
-
if (!trimmed) return undefined
|
|
109
|
-
if (/^\d+$/.test(trimmed)) return BigInt(trimmed)
|
|
110
|
-
if (/^0x[0-9a-fA-F]+$/.test(trimmed)) return BigInt(trimmed)
|
|
111
|
-
}
|
|
112
|
-
throw new Error(`${fieldName} must be an integer or hex quantity`)
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
function parseNumberField(value: unknown, fieldName: string): number | undefined {
|
|
116
|
-
if (value === undefined || value === null || value === '') return undefined
|
|
117
|
-
if (typeof value === 'number' && Number.isFinite(value)) return Math.trunc(value)
|
|
118
|
-
if (typeof value === 'bigint') return Number(value)
|
|
119
|
-
if (typeof value === 'string') {
|
|
120
|
-
const trimmed = value.trim()
|
|
121
|
-
if (!trimmed) return undefined
|
|
122
|
-
if (/^\d+$/.test(trimmed)) return Number.parseInt(trimmed, 10)
|
|
123
|
-
if (/^0x[0-9a-fA-F]+$/.test(trimmed)) return Number(BigInt(trimmed))
|
|
124
|
-
}
|
|
125
|
-
throw new Error(`${fieldName} must be an integer`)
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
function normalizeAddressInput(value: string, fieldName: string): string {
|
|
129
|
-
const trimmed = value.trim()
|
|
130
|
-
if (!/^0x[0-9a-fA-F]{40}$/.test(trimmed)) {
|
|
131
|
-
throw new Error(`${fieldName} must be a 20-byte hex address`)
|
|
132
|
-
}
|
|
133
|
-
return getAddress(trimmed.toLowerCase())
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
function normalizeAbiArgument(param: ParamType, value: unknown, fieldName: string): unknown {
|
|
137
|
-
if (param.baseType === 'address') {
|
|
138
|
-
if (typeof value !== 'string') throw new Error(`${fieldName} must be an address string`)
|
|
139
|
-
return normalizeAddressInput(value, fieldName)
|
|
140
|
-
}
|
|
141
|
-
if (param.baseType === 'array') {
|
|
142
|
-
if (!Array.isArray(value)) throw new Error(`${fieldName} must be an array`)
|
|
143
|
-
return value.map((entry, index) => normalizeAbiArgument(param.arrayChildren!, entry, `${fieldName}[${index}]`))
|
|
144
|
-
}
|
|
145
|
-
if (param.baseType === 'tuple') {
|
|
146
|
-
const components = param.components ?? []
|
|
147
|
-
if (Array.isArray(value)) {
|
|
148
|
-
return components.map((component, index) => normalizeAbiArgument(component, value[index], `${fieldName}[${index}]`))
|
|
149
|
-
}
|
|
150
|
-
if (!value || typeof value !== 'object') throw new Error(`${fieldName} must be an object or array for tuple input`)
|
|
151
|
-
return components.map((component, index) => {
|
|
152
|
-
const record = value as Record<string, unknown>
|
|
153
|
-
const componentValue = component.name && component.name in record
|
|
154
|
-
? record[component.name]
|
|
155
|
-
: record[String(index)]
|
|
156
|
-
return normalizeAbiArgument(component, componentValue, `${fieldName}.${component.name || index}`)
|
|
157
|
-
})
|
|
158
|
-
}
|
|
159
|
-
return value
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
function normalizeFunctionArgs(
|
|
163
|
-
fragment: NonNullable<ReturnType<Interface['getFunction']>>,
|
|
164
|
-
args: unknown[] | Record<string, unknown>,
|
|
165
|
-
functionName: string,
|
|
166
|
-
): unknown[] {
|
|
167
|
-
const source = Array.isArray(args) ? args : args && typeof args === 'object' ? args : []
|
|
168
|
-
if (!Array.isArray(source) && fragment.inputs.length === 1 && fragment.inputs[0].baseType === 'tuple') {
|
|
169
|
-
const tupleInput = fragment.inputs[0]
|
|
170
|
-
const hasNamedWrapper = tupleInput.name && tupleInput.name in source
|
|
171
|
-
const hasIndexWrapper = '0' in source
|
|
172
|
-
if (!hasNamedWrapper && !hasIndexWrapper) {
|
|
173
|
-
return [normalizeAbiArgument(tupleInput, source, `${functionName}.args[${tupleInput.name || 0}]`)]
|
|
174
|
-
}
|
|
175
|
-
}
|
|
176
|
-
return fragment.inputs.map((input, index) => {
|
|
177
|
-
const rawValue = Array.isArray(source)
|
|
178
|
-
? source[index]
|
|
179
|
-
: input.name && input.name in source
|
|
180
|
-
? source[input.name]
|
|
181
|
-
: source[String(index)]
|
|
182
|
-
return normalizeAbiArgument(input, rawValue, `${functionName}.args[${input.name || index}]`)
|
|
183
|
-
})
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
function normalizeMessageInput(input: EthereumMessageInput): string | Uint8Array {
|
|
187
|
-
if (typeof input.messageHex === 'string' && input.messageHex.trim()) {
|
|
188
|
-
return getBytes(normalizeHexData(input.messageHex, 'messageHex'))
|
|
189
|
-
}
|
|
190
|
-
if (typeof input.messageBase64 === 'string' && input.messageBase64.trim()) {
|
|
191
|
-
return Uint8Array.from(Buffer.from(input.messageBase64.trim(), 'base64'))
|
|
192
|
-
}
|
|
193
|
-
if (typeof input.message === 'string') return input.message
|
|
194
|
-
throw new Error('message, messageHex, or messageBase64 is required')
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
function normalizeTypedDataDomain(domain: Record<string, unknown>): TypedDataDomain {
|
|
198
|
-
const normalized: TypedDataDomain = { ...domain }
|
|
199
|
-
if (domain.chainId !== undefined) {
|
|
200
|
-
normalized.chainId = parseBigIntField(domain.chainId, 'typed data domain.chainId')
|
|
201
|
-
}
|
|
202
|
-
return normalized
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
function normalizeTypedDataTypes(types: Record<string, unknown>): Record<string, TypedDataField[]> {
|
|
206
|
-
const out: Record<string, TypedDataField[]> = {}
|
|
207
|
-
for (const [key, value] of Object.entries(types)) {
|
|
208
|
-
if (key === 'EIP712Domain') continue
|
|
209
|
-
if (!Array.isArray(value)) throw new Error(`typed data types.${key} must be an array`)
|
|
210
|
-
out[key] = value.map((entry) => {
|
|
211
|
-
if (!entry || typeof entry !== 'object') throw new Error(`typed data types.${key} entries must be objects`)
|
|
212
|
-
const field = entry as Record<string, unknown>
|
|
213
|
-
if (typeof field.name !== 'string' || typeof field.type !== 'string') {
|
|
214
|
-
throw new Error(`typed data types.${key} entries require name and type`)
|
|
215
|
-
}
|
|
216
|
-
return { name: field.name, type: field.type }
|
|
217
|
-
})
|
|
218
|
-
}
|
|
219
|
-
return out
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
function normalizeAbiInput(abi: unknown): ReadonlyArray<string | JsonFragment> {
|
|
223
|
-
if (Array.isArray(abi)) return abi as ReadonlyArray<string | JsonFragment>
|
|
224
|
-
if (typeof abi === 'string') {
|
|
225
|
-
const trimmed = abi.trim()
|
|
226
|
-
if (!trimmed) throw new Error('abi is required')
|
|
227
|
-
if (trimmed.startsWith('[')) {
|
|
228
|
-
const parsed = JSON.parse(trimmed)
|
|
229
|
-
if (!Array.isArray(parsed)) throw new Error('abi JSON must be an array')
|
|
230
|
-
return parsed as ReadonlyArray<string | JsonFragment>
|
|
231
|
-
}
|
|
232
|
-
return [trimmed]
|
|
233
|
-
}
|
|
234
|
-
throw new Error('abi must be an array or JSON string')
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
function normalizeTransactionRequest(tx: Record<string, unknown>): TransactionRequest {
|
|
238
|
-
const normalized: TransactionRequest = {}
|
|
239
|
-
if (tx.to !== undefined && tx.to !== null && tx.to !== '') normalized.to = normalizeAddressInput(String(tx.to), 'transaction.to')
|
|
240
|
-
if (tx.data !== undefined && tx.data !== null && tx.data !== '') normalized.data = normalizeHexData(String(tx.data), 'transaction.data')
|
|
241
|
-
if (tx.value !== undefined) normalized.value = parseBigIntField(tx.value, 'transaction.value')
|
|
242
|
-
if (tx.nonce !== undefined) normalized.nonce = parseNumberField(tx.nonce, 'transaction.nonce')
|
|
243
|
-
if (tx.chainId !== undefined) normalized.chainId = parseNumberField(tx.chainId, 'transaction.chainId')
|
|
244
|
-
if (tx.type !== undefined) normalized.type = parseNumberField(tx.type, 'transaction.type')
|
|
245
|
-
if (tx.gasLimit !== undefined) normalized.gasLimit = parseBigIntField(tx.gasLimit, 'transaction.gasLimit')
|
|
246
|
-
if (tx.gasPrice !== undefined) normalized.gasPrice = parseBigIntField(tx.gasPrice, 'transaction.gasPrice')
|
|
247
|
-
if (tx.maxFeePerGas !== undefined) normalized.maxFeePerGas = parseBigIntField(tx.maxFeePerGas, 'transaction.maxFeePerGas')
|
|
248
|
-
if (tx.maxPriorityFeePerGas !== undefined) normalized.maxPriorityFeePerGas = parseBigIntField(tx.maxPriorityFeePerGas, 'transaction.maxPriorityFeePerGas')
|
|
249
|
-
if (tx.accessList !== undefined) normalized.accessList = tx.accessList as TransactionRequest['accessList']
|
|
250
|
-
return normalized
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
async function withEthereumRpcTimeout<T>(promise: Promise<T>, label: string, timeoutMs = DEFAULT_EVM_RPC_TIMEOUT_MS): Promise<T> {
|
|
254
|
-
let timer: ReturnType<typeof setTimeout> | null = null
|
|
255
|
-
try {
|
|
256
|
-
return await Promise.race([
|
|
257
|
-
promise,
|
|
258
|
-
new Promise<never>((_, reject) => {
|
|
259
|
-
timer = setTimeout(() => reject(new Error(`${label} timed out after ${timeoutMs}ms`)), timeoutMs)
|
|
260
|
-
}),
|
|
261
|
-
])
|
|
262
|
-
} finally {
|
|
263
|
-
if (timer) clearTimeout(timer)
|
|
264
|
-
}
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
async function resolveWalletAndTransaction(
|
|
268
|
-
encryptedPrivateKey: string,
|
|
269
|
-
tx: Record<string, unknown>,
|
|
270
|
-
options?: EthereumExecutionOptions,
|
|
271
|
-
): Promise<{ provider: JsonRpcProvider; wallet: Wallet; txRequest: TransactionRequest; network: EvmNetworkConfig }> {
|
|
272
|
-
const network = getEvmNetworkConfig(options?.network)
|
|
273
|
-
const provider = getProviderForNetwork(options?.network, options?.rpcUrl)
|
|
274
|
-
const wallet = getWalletFromEncrypted(encryptedPrivateKey).connect(provider)
|
|
275
|
-
const fromAddress = typeof tx.from === 'string' ? tx.from.trim() : ''
|
|
276
|
-
if (fromAddress && fromAddress.toLowerCase() !== wallet.address.toLowerCase()) {
|
|
277
|
-
throw new Error(`transaction.from does not match wallet address ${wallet.address}`)
|
|
278
|
-
}
|
|
279
|
-
const txRequest = normalizeTransactionRequest(tx)
|
|
280
|
-
if (txRequest.chainId == null) txRequest.chainId = network.chainId
|
|
281
|
-
const populated = await withEthereumRpcTimeout(
|
|
282
|
-
wallet.populateTransaction(txRequest),
|
|
283
|
-
`populate transaction on ${network.label}`,
|
|
284
|
-
)
|
|
285
|
-
return { provider, wallet, txRequest: populated, network }
|
|
286
|
-
}
|
|
287
|
-
|
|
288
|
-
export function generateEthereumWallet(): { publicKey: string; encryptedPrivateKey: string } {
|
|
289
|
-
const wallet = Wallet.createRandom()
|
|
290
|
-
return {
|
|
291
|
-
publicKey: wallet.address,
|
|
292
|
-
encryptedPrivateKey: encryptKey(wallet.privateKey),
|
|
293
|
-
}
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
export function getWalletFromEncrypted(encryptedPrivateKey: string): Wallet {
|
|
297
|
-
return new Wallet(decryptKey(encryptedPrivateKey))
|
|
298
|
-
}
|
|
299
|
-
|
|
300
|
-
export function normalizeEvmNetwork(value: unknown, fallback: EvmNetworkId = 'ethereum'): EvmNetworkId {
|
|
301
|
-
const normalized = String(value ?? '').trim().toLowerCase()
|
|
302
|
-
if (!normalized) return fallback
|
|
303
|
-
if (normalized === 'ethereum' || normalized === 'eth' || normalized === 'mainnet') return 'ethereum'
|
|
304
|
-
if (normalized === 'arbitrum' || normalized === 'arb' || normalized === 'arbitrum-one') return 'arbitrum'
|
|
305
|
-
if (normalized === 'base') return 'base'
|
|
306
|
-
throw new Error(`Unsupported EVM network: ${String(value)}`)
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
export function getEvmNetworkConfig(value?: unknown): EvmNetworkConfig {
|
|
310
|
-
return EVM_NETWORKS[normalizeEvmNetwork(value)]
|
|
311
|
-
}
|
|
312
|
-
|
|
313
|
-
export function listEvmNetworkConfigs(): EvmNetworkConfig[] {
|
|
314
|
-
return Object.values(EVM_NETWORKS)
|
|
315
|
-
}
|
|
316
|
-
|
|
317
|
-
export function getEvmExplorerUrl(network: EvmNetworkId | string | null | undefined, kind: 'address' | 'transaction', value: string): string {
|
|
318
|
-
const config = getEvmNetworkConfig(network)
|
|
319
|
-
return `${kind === 'address' ? config.addressExplorerBaseUrl : config.transactionExplorerBaseUrl}${value}`
|
|
320
|
-
}
|
|
321
|
-
|
|
322
|
-
export function getProvider(rpcUrl?: string): JsonRpcProvider {
|
|
323
|
-
return new JsonRpcProvider(rpcUrl || DEFAULT_RPC_URL)
|
|
324
|
-
}
|
|
325
|
-
|
|
326
|
-
export function getProviderForNetwork(network?: EvmNetworkId | string | null, rpcUrl?: string | null): JsonRpcProvider {
|
|
327
|
-
return new JsonRpcProvider(rpcUrl || getEvmNetworkConfig(network).rpcUrl)
|
|
328
|
-
}
|
|
329
|
-
|
|
330
|
-
export async function getBalance(address: string, rpcUrl?: string): Promise<bigint> {
|
|
331
|
-
return withEthereumRpcTimeout(getProvider(rpcUrl).getBalance(address), 'get balance')
|
|
332
|
-
}
|
|
333
|
-
|
|
334
|
-
export async function sendEth(
|
|
335
|
-
encryptedPrivateKey: string,
|
|
336
|
-
toAddress: string,
|
|
337
|
-
amountWei: string,
|
|
338
|
-
rpcUrl?: string,
|
|
339
|
-
): Promise<{ signature: string; fee?: string }> {
|
|
340
|
-
const provider = getProvider(rpcUrl)
|
|
341
|
-
const wallet = getWalletFromEncrypted(encryptedPrivateKey).connect(provider)
|
|
342
|
-
const tx = await withEthereumRpcTimeout(wallet.sendTransaction({
|
|
343
|
-
to: toAddress,
|
|
344
|
-
value: BigInt(amountWei),
|
|
345
|
-
}), 'send ETH transaction')
|
|
346
|
-
const receipt = await withEthereumRpcTimeout(tx.wait(), 'wait for ETH transaction receipt')
|
|
347
|
-
return {
|
|
348
|
-
signature: tx.hash,
|
|
349
|
-
fee: receipt?.fee ? receipt.fee.toString() : undefined,
|
|
350
|
-
}
|
|
351
|
-
}
|
|
352
|
-
|
|
353
|
-
export function encodeEthereumContractCall(
|
|
354
|
-
abi: unknown,
|
|
355
|
-
functionName: string,
|
|
356
|
-
args: unknown[] | Record<string, unknown> = [],
|
|
357
|
-
): { data: string; fragment: string } {
|
|
358
|
-
const iface = new Interface(normalizeAbiInput(abi))
|
|
359
|
-
const fragment = iface.getFunction(functionName)
|
|
360
|
-
if (!fragment) throw new Error(`Function not found in ABI: ${functionName}`)
|
|
361
|
-
const normalizedArgs = normalizeFunctionArgs(fragment, args, functionName)
|
|
362
|
-
return {
|
|
363
|
-
data: iface.encodeFunctionData(fragment, normalizedArgs),
|
|
364
|
-
fragment: fragment.format('full'),
|
|
365
|
-
}
|
|
366
|
-
}
|
|
367
|
-
|
|
368
|
-
export async function callEthereumContract(
|
|
369
|
-
encryptedPrivateKey: string,
|
|
370
|
-
input: {
|
|
371
|
-
contractAddress: string
|
|
372
|
-
abi: unknown
|
|
373
|
-
functionName: string
|
|
374
|
-
args?: unknown[] | Record<string, unknown>
|
|
375
|
-
},
|
|
376
|
-
options?: EthereumExecutionOptions,
|
|
377
|
-
): Promise<{
|
|
378
|
-
network: EvmNetworkConfig
|
|
379
|
-
address: string
|
|
380
|
-
fragment: string
|
|
381
|
-
data: string
|
|
382
|
-
rawResult: string
|
|
383
|
-
decoded: unknown
|
|
384
|
-
namedOutputs: Record<string, unknown>
|
|
385
|
-
}> {
|
|
386
|
-
const network = getEvmNetworkConfig(options?.network)
|
|
387
|
-
const provider = getProviderForNetwork(options?.network, options?.rpcUrl)
|
|
388
|
-
const wallet = getWalletFromEncrypted(encryptedPrivateKey)
|
|
389
|
-
const iface = new Interface(normalizeAbiInput(input.abi))
|
|
390
|
-
const fragment = iface.getFunction(input.functionName)
|
|
391
|
-
if (!fragment) throw new Error(`Function not found in ABI: ${input.functionName}`)
|
|
392
|
-
const normalizedArgs = normalizeFunctionArgs(fragment, input.args || [], input.functionName)
|
|
393
|
-
const data = iface.encodeFunctionData(fragment, normalizedArgs)
|
|
394
|
-
const rawResult = await withEthereumRpcTimeout(provider.call({
|
|
395
|
-
to: normalizeAddressInput(input.contractAddress, 'contractAddress'),
|
|
396
|
-
data,
|
|
397
|
-
from: wallet.address,
|
|
398
|
-
}), `call contract ${input.functionName} on ${network.label}`)
|
|
399
|
-
const decodedResult = iface.decodeFunctionResult(fragment, rawResult)
|
|
400
|
-
const decodedValues = Array.from(decodedResult).map((entry) => serializeEvmValue(entry))
|
|
401
|
-
const namedOutputs: Record<string, unknown> = {}
|
|
402
|
-
for (let index = 0; index < fragment.outputs?.length; index += 1) {
|
|
403
|
-
const output = fragment.outputs[index]
|
|
404
|
-
if (!output?.name) continue
|
|
405
|
-
namedOutputs[output.name] = serializeEvmValue(decodedResult[index])
|
|
406
|
-
}
|
|
407
|
-
|
|
408
|
-
return {
|
|
409
|
-
network,
|
|
410
|
-
address: wallet.address,
|
|
411
|
-
fragment: fragment.format('full'),
|
|
412
|
-
data,
|
|
413
|
-
rawResult,
|
|
414
|
-
decoded: decodedValues.length === 1 ? decodedValues[0] : decodedValues,
|
|
415
|
-
namedOutputs,
|
|
416
|
-
}
|
|
417
|
-
}
|
|
418
|
-
|
|
419
|
-
export async function signEthereumMessage(
|
|
420
|
-
encryptedPrivateKey: string,
|
|
421
|
-
input: EthereumMessageInput,
|
|
422
|
-
): Promise<{ signature: string; address: string }> {
|
|
423
|
-
const wallet = getWalletFromEncrypted(encryptedPrivateKey)
|
|
424
|
-
return {
|
|
425
|
-
signature: await wallet.signMessage(normalizeMessageInput(input)),
|
|
426
|
-
address: wallet.address,
|
|
427
|
-
}
|
|
428
|
-
}
|
|
429
|
-
|
|
430
|
-
export async function signEthereumTypedData(
|
|
431
|
-
encryptedPrivateKey: string,
|
|
432
|
-
input: {
|
|
433
|
-
domain: Record<string, unknown>
|
|
434
|
-
types: Record<string, unknown>
|
|
435
|
-
value: Record<string, unknown>
|
|
436
|
-
},
|
|
437
|
-
): Promise<{ signature: string; address: string }> {
|
|
438
|
-
const wallet = getWalletFromEncrypted(encryptedPrivateKey)
|
|
439
|
-
return {
|
|
440
|
-
signature: await wallet.signTypedData(
|
|
441
|
-
normalizeTypedDataDomain(input.domain),
|
|
442
|
-
normalizeTypedDataTypes(input.types),
|
|
443
|
-
input.value,
|
|
444
|
-
),
|
|
445
|
-
address: wallet.address,
|
|
446
|
-
}
|
|
447
|
-
}
|
|
448
|
-
|
|
449
|
-
export async function signEthereumTransaction(
|
|
450
|
-
encryptedPrivateKey: string,
|
|
451
|
-
tx: Record<string, unknown>,
|
|
452
|
-
options?: EthereumExecutionOptions,
|
|
453
|
-
): Promise<{
|
|
454
|
-
signedTransaction: string
|
|
455
|
-
transactionHash: string
|
|
456
|
-
address: string
|
|
457
|
-
chainId: number | null
|
|
458
|
-
network: EvmNetworkConfig
|
|
459
|
-
}> {
|
|
460
|
-
const { wallet, txRequest, network } = await resolveWalletAndTransaction(encryptedPrivateKey, tx, options)
|
|
461
|
-
const signedTransaction = await wallet.signTransaction(txRequest)
|
|
462
|
-
return {
|
|
463
|
-
signedTransaction,
|
|
464
|
-
transactionHash: keccak256(signedTransaction),
|
|
465
|
-
address: wallet.address,
|
|
466
|
-
chainId: txRequest.chainId != null ? Number(txRequest.chainId) : null,
|
|
467
|
-
network,
|
|
468
|
-
}
|
|
469
|
-
}
|
|
470
|
-
|
|
471
|
-
export async function simulateEthereumTransaction(
|
|
472
|
-
encryptedPrivateKey: string,
|
|
473
|
-
tx: Record<string, unknown>,
|
|
474
|
-
options?: EthereumExecutionOptions,
|
|
475
|
-
): Promise<{
|
|
476
|
-
estimateGas?: string
|
|
477
|
-
callResult?: string
|
|
478
|
-
callError?: string
|
|
479
|
-
address: string
|
|
480
|
-
chainId: number | null
|
|
481
|
-
network: EvmNetworkConfig
|
|
482
|
-
}> {
|
|
483
|
-
const { provider, wallet, txRequest, network } = await resolveWalletAndTransaction(encryptedPrivateKey, tx, options)
|
|
484
|
-
let estimateGas: string | undefined
|
|
485
|
-
let callResult: string | undefined
|
|
486
|
-
let callError: string | undefined
|
|
487
|
-
|
|
488
|
-
try {
|
|
489
|
-
estimateGas = (await withEthereumRpcTimeout(
|
|
490
|
-
provider.estimateGas({ ...txRequest, from: wallet.address }),
|
|
491
|
-
`estimate gas on ${network.label}`,
|
|
492
|
-
)).toString()
|
|
493
|
-
} catch (err: unknown) {
|
|
494
|
-
callError = errorMessage(err)
|
|
495
|
-
}
|
|
496
|
-
|
|
497
|
-
try {
|
|
498
|
-
callResult = await withEthereumRpcTimeout(
|
|
499
|
-
provider.call({ ...txRequest, from: wallet.address }),
|
|
500
|
-
`simulate transaction call on ${network.label}`,
|
|
501
|
-
)
|
|
502
|
-
} catch (err: unknown) {
|
|
503
|
-
if (!callError) callError = errorMessage(err)
|
|
504
|
-
}
|
|
505
|
-
|
|
506
|
-
return {
|
|
507
|
-
estimateGas,
|
|
508
|
-
callResult,
|
|
509
|
-
callError,
|
|
510
|
-
address: wallet.address,
|
|
511
|
-
chainId: txRequest.chainId != null ? Number(txRequest.chainId) : null,
|
|
512
|
-
network,
|
|
513
|
-
}
|
|
514
|
-
}
|
|
515
|
-
|
|
516
|
-
export async function sendEthereumTransaction(
|
|
517
|
-
encryptedPrivateKey: string,
|
|
518
|
-
input: {
|
|
519
|
-
transaction?: Record<string, unknown>
|
|
520
|
-
signedTransaction?: string | null
|
|
521
|
-
waitForReceipt?: boolean
|
|
522
|
-
},
|
|
523
|
-
options?: EthereumExecutionOptions,
|
|
524
|
-
): Promise<{
|
|
525
|
-
transactionHash: string
|
|
526
|
-
address: string
|
|
527
|
-
chainId: number | null
|
|
528
|
-
explorerUrl: string
|
|
529
|
-
receipt?: Record<string, unknown> | null
|
|
530
|
-
network: EvmNetworkConfig
|
|
531
|
-
}> {
|
|
532
|
-
const waitForReceipt = input.waitForReceipt === true
|
|
533
|
-
const network = getEvmNetworkConfig(options?.network)
|
|
534
|
-
const provider = getProviderForNetwork(options?.network, options?.rpcUrl)
|
|
535
|
-
const wallet = getWalletFromEncrypted(encryptedPrivateKey).connect(provider)
|
|
536
|
-
|
|
537
|
-
if (typeof input.signedTransaction === 'string' && input.signedTransaction.trim()) {
|
|
538
|
-
const response = await withEthereumRpcTimeout(
|
|
539
|
-
provider.broadcastTransaction(input.signedTransaction.trim()),
|
|
540
|
-
`broadcast signed transaction on ${network.label}`,
|
|
541
|
-
)
|
|
542
|
-
const receipt = waitForReceipt
|
|
543
|
-
? await withEthereumRpcTimeout(response.wait(), `wait for transaction receipt on ${network.label}`)
|
|
544
|
-
: null
|
|
545
|
-
return {
|
|
546
|
-
transactionHash: response.hash,
|
|
547
|
-
address: wallet.address,
|
|
548
|
-
chainId: network.chainId,
|
|
549
|
-
explorerUrl: getEvmExplorerUrl(network.id, 'transaction', response.hash),
|
|
550
|
-
receipt: receipt ? {
|
|
551
|
-
blockHash: receipt.blockHash,
|
|
552
|
-
blockNumber: receipt.blockNumber,
|
|
553
|
-
gasUsed: receipt.gasUsed?.toString?.(),
|
|
554
|
-
fee: receipt.fee?.toString?.(),
|
|
555
|
-
status: receipt.status,
|
|
556
|
-
} : null,
|
|
557
|
-
network,
|
|
558
|
-
}
|
|
559
|
-
}
|
|
560
|
-
|
|
561
|
-
if (!input.transaction || typeof input.transaction !== 'object') {
|
|
562
|
-
throw new Error('transaction or signedTransaction is required')
|
|
563
|
-
}
|
|
564
|
-
|
|
565
|
-
const { txRequest } = await resolveWalletAndTransaction(encryptedPrivateKey, input.transaction, options)
|
|
566
|
-
const response = await withEthereumRpcTimeout(
|
|
567
|
-
wallet.sendTransaction(txRequest),
|
|
568
|
-
`send transaction on ${network.label}`,
|
|
569
|
-
)
|
|
570
|
-
const receipt = waitForReceipt
|
|
571
|
-
? await withEthereumRpcTimeout(response.wait(), `wait for transaction receipt on ${network.label}`)
|
|
572
|
-
: null
|
|
573
|
-
return {
|
|
574
|
-
transactionHash: response.hash,
|
|
575
|
-
address: wallet.address,
|
|
576
|
-
chainId: txRequest.chainId != null ? Number(txRequest.chainId) : network.chainId,
|
|
577
|
-
explorerUrl: getEvmExplorerUrl(network.id, 'transaction', response.hash),
|
|
578
|
-
receipt: receipt ? {
|
|
579
|
-
blockHash: receipt.blockHash,
|
|
580
|
-
blockNumber: receipt.blockNumber,
|
|
581
|
-
gasUsed: receipt.gasUsed?.toString?.(),
|
|
582
|
-
fee: receipt.fee?.toString?.(),
|
|
583
|
-
status: receipt.status,
|
|
584
|
-
} : null,
|
|
585
|
-
network,
|
|
586
|
-
}
|
|
587
|
-
}
|
|
588
|
-
|
|
589
|
-
export function isValidEthereumAddress(address: string): boolean {
|
|
590
|
-
return isAddress(address)
|
|
591
|
-
}
|