@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,81 +0,0 @@
|
|
|
1
|
-
import assert from 'node:assert/strict'
|
|
2
|
-
import { describe, it } from 'node:test'
|
|
3
|
-
|
|
4
|
-
import type { AgentWallet, WalletTransaction } from '@/types'
|
|
5
|
-
|
|
6
|
-
import {
|
|
7
|
-
validateWalletSendLimits,
|
|
8
|
-
walletApprovalsGloballyEnabled,
|
|
9
|
-
walletRequiresApproval,
|
|
10
|
-
} from '@/lib/server/wallet/wallet-service'
|
|
11
|
-
|
|
12
|
-
function buildWallet(overrides: Partial<AgentWallet> = {}): AgentWallet {
|
|
13
|
-
return {
|
|
14
|
-
id: 'wallet-1',
|
|
15
|
-
agentId: 'agent-1',
|
|
16
|
-
chain: 'ethereum',
|
|
17
|
-
publicKey: '0x0000000000000000000000000000000000000001',
|
|
18
|
-
encryptedPrivateKey: 'secret',
|
|
19
|
-
requireApproval: true,
|
|
20
|
-
spendingLimitAtomic: '1000000000000000000',
|
|
21
|
-
dailyLimitAtomic: '1500000000000000000',
|
|
22
|
-
createdAt: 1,
|
|
23
|
-
updatedAt: 1,
|
|
24
|
-
...overrides,
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
function buildTransaction(overrides: Partial<WalletTransaction> = {}): WalletTransaction {
|
|
29
|
-
return {
|
|
30
|
-
id: 'tx-1',
|
|
31
|
-
walletId: 'wallet-1',
|
|
32
|
-
agentId: 'agent-1',
|
|
33
|
-
chain: 'ethereum',
|
|
34
|
-
type: 'send',
|
|
35
|
-
signature: '0xhash',
|
|
36
|
-
fromAddress: '0xfrom',
|
|
37
|
-
toAddress: '0xto',
|
|
38
|
-
amountAtomic: '1000000000000000000',
|
|
39
|
-
status: 'confirmed',
|
|
40
|
-
timestamp: Date.now(),
|
|
41
|
-
...overrides,
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
describe('validateWalletSendLimits', () => {
|
|
46
|
-
it('blocks approvals that would exceed the current daily limit', () => {
|
|
47
|
-
const wallet = buildWallet()
|
|
48
|
-
const transactions = [
|
|
49
|
-
buildTransaction({ id: 'existing', amountAtomic: '1000000000000000000' }),
|
|
50
|
-
]
|
|
51
|
-
|
|
52
|
-
const error = validateWalletSendLimits({
|
|
53
|
-
wallet,
|
|
54
|
-
amountAtomic: '600000000000000000',
|
|
55
|
-
transactions,
|
|
56
|
-
now: Date.now(),
|
|
57
|
-
})
|
|
58
|
-
|
|
59
|
-
assert.match(error || '', /Daily limit exceeded/)
|
|
60
|
-
})
|
|
61
|
-
})
|
|
62
|
-
|
|
63
|
-
describe('wallet approval helpers', () => {
|
|
64
|
-
it('treats missing app setting as globally enabled', () => {
|
|
65
|
-
assert.equal(walletApprovalsGloballyEnabled(undefined), true)
|
|
66
|
-
assert.equal(walletApprovalsGloballyEnabled({}), true)
|
|
67
|
-
})
|
|
68
|
-
|
|
69
|
-
it('lets the global setting disable approval even for approval-required wallets', () => {
|
|
70
|
-
const wallet = buildWallet({ requireApproval: true })
|
|
71
|
-
|
|
72
|
-
assert.equal(walletRequiresApproval(wallet, { walletApprovalsEnabled: true }), true)
|
|
73
|
-
assert.equal(walletRequiresApproval(wallet, { walletApprovalsEnabled: false }), false)
|
|
74
|
-
})
|
|
75
|
-
|
|
76
|
-
it('still respects the per-wallet toggle when global approvals remain enabled', () => {
|
|
77
|
-
const wallet = buildWallet({ requireApproval: false })
|
|
78
|
-
|
|
79
|
-
assert.equal(walletRequiresApproval(wallet, { walletApprovalsEnabled: true }), false)
|
|
80
|
-
})
|
|
81
|
-
})
|
|
@@ -1,225 +0,0 @@
|
|
|
1
|
-
import { genId } from '@/lib/id'
|
|
2
|
-
import {
|
|
3
|
-
formatWalletAmount,
|
|
4
|
-
getWalletAssetSymbol,
|
|
5
|
-
getWalletAtomicAmount,
|
|
6
|
-
getWalletChainOrDefault,
|
|
7
|
-
getWalletDefaultLimitAtomic,
|
|
8
|
-
getWalletLimitAtomic,
|
|
9
|
-
normalizeAtomicString,
|
|
10
|
-
} from '@/lib/wallet/wallet'
|
|
11
|
-
import type { Agent, AgentWallet, WalletChain, WalletTransaction } from '@/types'
|
|
12
|
-
import { dedup } from '@/lib/shared-utils'
|
|
13
|
-
import { loadAgent, loadAgents, loadWalletTransactions, loadWallets, upsertAgent, upsertWallet } from '@/lib/server/storage'
|
|
14
|
-
import { generateEthereumWallet, isValidEthereumAddress, sendEth } from '@/lib/server/ethereum'
|
|
15
|
-
import { generateSolanaKeypair, isValidSolanaAddress, sendSol } from '@/lib/server/solana'
|
|
16
|
-
import { notify } from '@/lib/server/ws-hub'
|
|
17
|
-
import { clearWalletPortfolioCache, getWalletPortfolio, type GetWalletPortfolioOptions, type WalletPortfolio } from '@/lib/server/wallet/wallet-portfolio'
|
|
18
|
-
|
|
19
|
-
function generateWalletCredentials(chain: WalletChain): { publicKey: string; encryptedPrivateKey: string } {
|
|
20
|
-
if (chain === 'ethereum') return generateEthereumWallet()
|
|
21
|
-
return generateSolanaKeypair()
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
export function stripWalletPrivateKey<T extends Record<string, unknown>>(wallet: T): Omit<T, 'encryptedPrivateKey'> {
|
|
25
|
-
return Object.fromEntries(Object.entries(wallet).filter(([key]) => key !== 'encryptedPrivateKey')) as Omit<T, 'encryptedPrivateKey'>
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
export function getAgentWalletIds(agent: Pick<Agent, 'walletIds' | 'walletId'> | null | undefined): string[] {
|
|
29
|
-
const ids = Array.isArray(agent?.walletIds)
|
|
30
|
-
? agent.walletIds.filter((value): value is string => typeof value === 'string' && value.trim().length > 0)
|
|
31
|
-
: []
|
|
32
|
-
const legacy = typeof agent?.walletId === 'string' && agent.walletId.trim()
|
|
33
|
-
? [agent.walletId.trim()]
|
|
34
|
-
: []
|
|
35
|
-
return dedup([...ids, ...legacy])
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
export function getAgentActiveWalletId(
|
|
39
|
-
agent: Pick<Agent, 'walletIds' | 'walletId' | 'activeWalletId'> | null | undefined,
|
|
40
|
-
walletIds = getAgentWalletIds(agent),
|
|
41
|
-
): string | null {
|
|
42
|
-
if (typeof agent?.activeWalletId === 'string' && walletIds.includes(agent.activeWalletId)) return agent.activeWalletId
|
|
43
|
-
if (typeof agent?.walletId === 'string' && walletIds.includes(agent.walletId)) return agent.walletId
|
|
44
|
-
return walletIds[0] || null
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
function syncAgentWalletPointers(agent: Agent, walletIds: string[], activeWalletId?: string | null): Agent {
|
|
48
|
-
const normalizedIds = dedup(walletIds.filter(Boolean))
|
|
49
|
-
const normalizedActive = activeWalletId && normalizedIds.includes(activeWalletId)
|
|
50
|
-
? activeWalletId
|
|
51
|
-
: normalizedIds[0] || null
|
|
52
|
-
agent.walletIds = normalizedIds
|
|
53
|
-
agent.activeWalletId = normalizedActive
|
|
54
|
-
agent.walletId = normalizedActive
|
|
55
|
-
return agent
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
export function linkWalletToAgent(agent: Agent, walletId: string, makeActive = false): Agent {
|
|
59
|
-
const walletIds = getAgentWalletIds(agent)
|
|
60
|
-
if (!walletIds.includes(walletId)) walletIds.push(walletId)
|
|
61
|
-
const activeWalletId = makeActive ? walletId : getAgentActiveWalletId(agent, walletIds)
|
|
62
|
-
return syncAgentWalletPointers(agent, walletIds, activeWalletId)
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
export function unlinkWalletFromAgent(agent: Agent, walletId: string): Agent {
|
|
66
|
-
const walletIds = getAgentWalletIds(agent).filter((id) => id !== walletId)
|
|
67
|
-
const activeWalletId = getAgentActiveWalletId(agent, walletIds)
|
|
68
|
-
return syncAgentWalletPointers(agent, walletIds, activeWalletId)
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
export function setAgentActiveWallet(agent: Agent, walletId: string | null): Agent {
|
|
72
|
-
const walletIds = getAgentWalletIds(agent)
|
|
73
|
-
const activeWalletId = walletId && walletIds.includes(walletId) ? walletId : walletIds[0] || null
|
|
74
|
-
return syncAgentWalletPointers(agent, walletIds, activeWalletId)
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
export function getWalletsByAgentId(agentId: string): AgentWallet[] {
|
|
78
|
-
const wallets = loadWallets() as Record<string, AgentWallet>
|
|
79
|
-
return Object.values(wallets)
|
|
80
|
-
.filter((wallet) => wallet.agentId === agentId)
|
|
81
|
-
.sort((a, b) => a.createdAt - b.createdAt)
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
export function getWalletByAgentId(agentId: string, chain?: WalletChain | null): AgentWallet | null {
|
|
85
|
-
const wallets = getWalletsByAgentId(agentId)
|
|
86
|
-
if (chain) return wallets.find((wallet) => wallet.chain === chain) ?? null
|
|
87
|
-
|
|
88
|
-
const agents = loadAgents()
|
|
89
|
-
const agent = agents[agentId]
|
|
90
|
-
const activeWalletId = getAgentActiveWalletId(agent)
|
|
91
|
-
return wallets.find((wallet) => wallet.id === activeWalletId) ?? wallets[0] ?? null
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
export function walletApprovalsGloballyEnabled(
|
|
95
|
-
settings?: { walletApprovalsEnabled?: boolean | null } | null,
|
|
96
|
-
): boolean {
|
|
97
|
-
return settings?.walletApprovalsEnabled !== false
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
export function walletRequiresApproval(
|
|
101
|
-
wallet: Pick<AgentWallet, 'requireApproval'>,
|
|
102
|
-
settings?: { walletApprovalsEnabled?: boolean | null } | null,
|
|
103
|
-
): boolean {
|
|
104
|
-
return walletApprovalsGloballyEnabled(settings) && wallet.requireApproval !== false
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
export function createAgentWallet(input: {
|
|
108
|
-
agentId: string
|
|
109
|
-
chain?: WalletChain | string | null
|
|
110
|
-
provider?: WalletChain | string | null
|
|
111
|
-
label?: string
|
|
112
|
-
requireApproval?: boolean
|
|
113
|
-
spendingLimitAtomic?: string | number | null
|
|
114
|
-
dailyLimitAtomic?: string | number | null
|
|
115
|
-
}): AgentWallet {
|
|
116
|
-
const agentId = String(input.agentId || '').trim()
|
|
117
|
-
if (!agentId) throw new Error('agentId is required')
|
|
118
|
-
|
|
119
|
-
const agent = loadAgent(agentId)
|
|
120
|
-
if (!agent) throw new Error('Agent not found')
|
|
121
|
-
|
|
122
|
-
const chain = getWalletChainOrDefault(input.chain ?? input.provider, 'solana')
|
|
123
|
-
const existing = getWalletByAgentId(agentId, chain)
|
|
124
|
-
if (existing) throw new Error(`Agent already has a ${chain} wallet`)
|
|
125
|
-
const { publicKey, encryptedPrivateKey } = generateWalletCredentials(chain)
|
|
126
|
-
const id = genId()
|
|
127
|
-
const now = Date.now()
|
|
128
|
-
const wallet: AgentWallet = {
|
|
129
|
-
id,
|
|
130
|
-
agentId,
|
|
131
|
-
chain,
|
|
132
|
-
publicKey,
|
|
133
|
-
encryptedPrivateKey,
|
|
134
|
-
label: typeof input.label === 'string' && input.label.trim() ? input.label.trim() : undefined,
|
|
135
|
-
spendingLimitAtomic: normalizeAtomicString(input.spendingLimitAtomic, getWalletDefaultLimitAtomic(chain, 'perTx')),
|
|
136
|
-
dailyLimitAtomic: normalizeAtomicString(input.dailyLimitAtomic, getWalletDefaultLimitAtomic(chain, 'daily')),
|
|
137
|
-
requireApproval: input.requireApproval !== false,
|
|
138
|
-
createdAt: now,
|
|
139
|
-
updatedAt: now,
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
upsertWallet(id, wallet)
|
|
143
|
-
clearWalletPortfolioCache(id)
|
|
144
|
-
|
|
145
|
-
linkWalletToAgent(agent as any, id, getAgentActiveWalletId(agent as any) == null)
|
|
146
|
-
agent.updatedAt = now
|
|
147
|
-
upsertAgent(agentId, agent)
|
|
148
|
-
|
|
149
|
-
notify('wallets')
|
|
150
|
-
notify('agents')
|
|
151
|
-
|
|
152
|
-
return wallet
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
export async function getWalletBalanceAtomic(wallet: AgentWallet): Promise<string> {
|
|
156
|
-
return (await getWalletPortfolio(wallet)).balanceAtomic
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
export async function getWalletPortfolioSnapshot(
|
|
160
|
-
wallet: AgentWallet,
|
|
161
|
-
options?: GetWalletPortfolioOptions,
|
|
162
|
-
): Promise<WalletPortfolio> {
|
|
163
|
-
return getWalletPortfolio(wallet, options)
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
export function validateWalletSendLimits(params: {
|
|
167
|
-
wallet: AgentWallet
|
|
168
|
-
amountAtomic: string
|
|
169
|
-
transactions?: WalletTransaction[]
|
|
170
|
-
now?: number
|
|
171
|
-
excludeTransactionId?: string
|
|
172
|
-
}): string | null {
|
|
173
|
-
const { wallet } = params
|
|
174
|
-
const amountAtomic = normalizeAtomicString(params.amountAtomic, '0')
|
|
175
|
-
const assetSymbol = getWalletAssetSymbol(wallet.chain)
|
|
176
|
-
|
|
177
|
-
if (BigInt(amountAtomic) <= BigInt(0)) {
|
|
178
|
-
return 'Amount must be positive'
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
const perTxLimitAtomic = getWalletLimitAtomic(wallet, 'perTx')
|
|
182
|
-
if (BigInt(amountAtomic) > BigInt(perTxLimitAtomic)) {
|
|
183
|
-
return `Amount ${formatWalletAmount(wallet.chain, amountAtomic, { maxFractionDigits: 6 })} ${assetSymbol} exceeds per-transaction limit of ${formatWalletAmount(wallet.chain, perTxLimitAtomic, { maxFractionDigits: 6 })} ${assetSymbol}`
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
const dailyLimitAtomic = getWalletLimitAtomic(wallet, 'daily')
|
|
187
|
-
const oneDayAgo = (params.now ?? Date.now()) - 24 * 60 * 60 * 1000
|
|
188
|
-
const transactions = params.transactions
|
|
189
|
-
?? Object.values(loadWalletTransactions() as Record<string, WalletTransaction>)
|
|
190
|
-
const dailySpentAtomic = transactions
|
|
191
|
-
.filter((tx) => tx.walletId === wallet.id)
|
|
192
|
-
.filter((tx) => tx.id !== params.excludeTransactionId)
|
|
193
|
-
.filter((tx) => tx.type === 'send' && tx.status === 'confirmed' && tx.timestamp > oneDayAgo)
|
|
194
|
-
.reduce((sum, tx) => sum + BigInt(getWalletAtomicAmount(tx)), BigInt(0))
|
|
195
|
-
|
|
196
|
-
if (dailySpentAtomic + BigInt(amountAtomic) > BigInt(dailyLimitAtomic)) {
|
|
197
|
-
return `Daily limit exceeded. Spent ${formatWalletAmount(wallet.chain, dailySpentAtomic.toString(), { maxFractionDigits: 6 })} ${assetSymbol} in the last 24h, limit is ${formatWalletAmount(wallet.chain, dailyLimitAtomic, { maxFractionDigits: 6 })} ${assetSymbol}`
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
return null
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
export function isValidWalletAddress(chain: WalletChain, address: string): boolean {
|
|
204
|
-
if (chain === 'ethereum') return isValidEthereumAddress(address)
|
|
205
|
-
return isValidSolanaAddress(address)
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
export async function sendWalletNativeAsset(
|
|
209
|
-
wallet: AgentWallet,
|
|
210
|
-
toAddress: string,
|
|
211
|
-
amountAtomic: string,
|
|
212
|
-
): Promise<{ signature: string; feeAtomic?: string }> {
|
|
213
|
-
if (wallet.chain === 'ethereum') {
|
|
214
|
-
const result = await sendEth(wallet.encryptedPrivateKey, toAddress, amountAtomic)
|
|
215
|
-
clearWalletPortfolioCache(wallet.id)
|
|
216
|
-
return { signature: result.signature, feeAtomic: result.fee }
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
const result = await sendSol(wallet.encryptedPrivateKey, toAddress, Number.parseInt(amountAtomic, 10))
|
|
220
|
-
clearWalletPortfolioCache(wallet.id)
|
|
221
|
-
return {
|
|
222
|
-
signature: result.signature,
|
|
223
|
-
feeAtomic: String(result.fee),
|
|
224
|
-
}
|
|
225
|
-
}
|
|
@@ -1,75 +0,0 @@
|
|
|
1
|
-
import assert from 'node:assert/strict'
|
|
2
|
-
import { describe, it } from 'node:test'
|
|
3
|
-
|
|
4
|
-
import type { WalletTransaction } from '@/types'
|
|
5
|
-
|
|
6
|
-
import {
|
|
7
|
-
filterWalletTransactions,
|
|
8
|
-
getWalletTransactionStatusGroup,
|
|
9
|
-
matchesWalletTransactionFilter,
|
|
10
|
-
matchesWalletTransactionQuery,
|
|
11
|
-
} from '@/lib/wallet/wallet-transactions'
|
|
12
|
-
|
|
13
|
-
function buildTransaction(overrides: Partial<WalletTransaction> = {}): WalletTransaction {
|
|
14
|
-
return {
|
|
15
|
-
id: 'tx-1',
|
|
16
|
-
walletId: 'wallet-1',
|
|
17
|
-
agentId: 'agent-1',
|
|
18
|
-
chain: 'ethereum',
|
|
19
|
-
type: 'swap',
|
|
20
|
-
signature: '0xabc123',
|
|
21
|
-
fromAddress: '0xfrom000000000000000000000000000000000001',
|
|
22
|
-
toAddress: '0xto0000000000000000000000000000000000002',
|
|
23
|
-
amountAtomic: '1000000',
|
|
24
|
-
status: 'confirmed',
|
|
25
|
-
memo: 'Swapped 1 USDC to ETH',
|
|
26
|
-
timestamp: 1,
|
|
27
|
-
...overrides,
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
describe('wallet transaction filters', () => {
|
|
32
|
-
it('groups pending and pending_approval together', () => {
|
|
33
|
-
assert.equal(getWalletTransactionStatusGroup('pending'), 'pending')
|
|
34
|
-
assert.equal(getWalletTransactionStatusGroup('pending_approval'), 'pending')
|
|
35
|
-
assert.equal(getWalletTransactionStatusGroup('confirmed'), 'confirmed')
|
|
36
|
-
assert.equal(getWalletTransactionStatusGroup('failed'), 'failed')
|
|
37
|
-
assert.equal(getWalletTransactionStatusGroup('denied'), 'failed')
|
|
38
|
-
})
|
|
39
|
-
|
|
40
|
-
it('matches filters by type and status group', () => {
|
|
41
|
-
assert.equal(matchesWalletTransactionFilter(buildTransaction({ type: 'swap' }), 'swap'), true)
|
|
42
|
-
assert.equal(matchesWalletTransactionFilter(buildTransaction({ type: 'send' }), 'send'), true)
|
|
43
|
-
assert.equal(matchesWalletTransactionFilter(buildTransaction({ status: 'pending_approval' }), 'pending'), true)
|
|
44
|
-
assert.equal(matchesWalletTransactionFilter(buildTransaction({ status: 'denied' }), 'failed'), true)
|
|
45
|
-
})
|
|
46
|
-
|
|
47
|
-
it('matches search queries against signature, memo, and addresses', () => {
|
|
48
|
-
const tx = buildTransaction()
|
|
49
|
-
assert.equal(matchesWalletTransactionQuery(tx, 'usdc'), true)
|
|
50
|
-
assert.equal(matchesWalletTransactionQuery(tx, '0xabc123'), true)
|
|
51
|
-
assert.equal(matchesWalletTransactionQuery(tx, '0xfrom0000'), true)
|
|
52
|
-
assert.equal(matchesWalletTransactionQuery(tx, 'missing-text'), false)
|
|
53
|
-
})
|
|
54
|
-
|
|
55
|
-
it('filters transactions by combined status/type and search query', () => {
|
|
56
|
-
const transactions = [
|
|
57
|
-
buildTransaction({ id: 'tx-confirmed', status: 'confirmed', memo: 'Swap USDC to ETH' }),
|
|
58
|
-
buildTransaction({ id: 'tx-pending', status: 'pending_approval', memo: 'Awaiting approval' }),
|
|
59
|
-
buildTransaction({ id: 'tx-send', type: 'send', status: 'confirmed', memo: 'Send ETH to treasury' }),
|
|
60
|
-
]
|
|
61
|
-
|
|
62
|
-
assert.deepEqual(
|
|
63
|
-
filterWalletTransactions(transactions, { filter: 'pending' }).map((tx) => tx.id),
|
|
64
|
-
['tx-pending'],
|
|
65
|
-
)
|
|
66
|
-
assert.deepEqual(
|
|
67
|
-
filterWalletTransactions(transactions, { filter: 'send', query: 'treasury' }).map((tx) => tx.id),
|
|
68
|
-
['tx-send'],
|
|
69
|
-
)
|
|
70
|
-
assert.deepEqual(
|
|
71
|
-
filterWalletTransactions(transactions, { filter: 'confirmed', query: 'usdc' }).map((tx) => tx.id),
|
|
72
|
-
['tx-confirmed'],
|
|
73
|
-
)
|
|
74
|
-
})
|
|
75
|
-
})
|
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
import type { WalletTransaction, WalletTransactionStatus } from '@/types'
|
|
2
|
-
|
|
3
|
-
export type WalletTransactionFilter = 'all' | 'confirmed' | 'pending' | 'failed' | 'send' | 'receive' | 'swap'
|
|
4
|
-
|
|
5
|
-
export function getWalletTransactionStatusGroup(status: WalletTransactionStatus): 'confirmed' | 'pending' | 'failed' {
|
|
6
|
-
if (status === 'confirmed') return 'confirmed'
|
|
7
|
-
if (status === 'pending' || status === 'pending_approval') return 'pending'
|
|
8
|
-
return 'failed'
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
export function matchesWalletTransactionFilter(tx: WalletTransaction, filter: WalletTransactionFilter): boolean {
|
|
12
|
-
if (filter === 'all') return true
|
|
13
|
-
if (filter === 'send' || filter === 'receive' || filter === 'swap') return tx.type === filter
|
|
14
|
-
return getWalletTransactionStatusGroup(tx.status) === filter
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
export function matchesWalletTransactionQuery(tx: WalletTransaction, query: string): boolean {
|
|
18
|
-
const normalized = query.trim().toLowerCase()
|
|
19
|
-
if (!normalized) return true
|
|
20
|
-
const haystack = [
|
|
21
|
-
tx.id,
|
|
22
|
-
tx.signature,
|
|
23
|
-
tx.memo || '',
|
|
24
|
-
tx.fromAddress,
|
|
25
|
-
tx.toAddress,
|
|
26
|
-
tx.status,
|
|
27
|
-
tx.type,
|
|
28
|
-
tx.tokenMint || '',
|
|
29
|
-
tx.approvedBy || '',
|
|
30
|
-
]
|
|
31
|
-
.join(' ')
|
|
32
|
-
.toLowerCase()
|
|
33
|
-
return haystack.includes(normalized)
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
export function filterWalletTransactions(
|
|
37
|
-
transactions: WalletTransaction[],
|
|
38
|
-
options?: { filter?: WalletTransactionFilter; query?: string },
|
|
39
|
-
): WalletTransaction[] {
|
|
40
|
-
const filter = options?.filter || 'all'
|
|
41
|
-
const query = options?.query || ''
|
|
42
|
-
return transactions.filter((tx) => matchesWalletTransactionFilter(tx, filter) && matchesWalletTransactionQuery(tx, query))
|
|
43
|
-
}
|