@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
package/src/lib/server/solana.ts
DELETED
|
@@ -1,327 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
Connection,
|
|
3
|
-
Keypair,
|
|
4
|
-
LAMPORTS_PER_SOL,
|
|
5
|
-
PublicKey,
|
|
6
|
-
SystemProgram,
|
|
7
|
-
Transaction,
|
|
8
|
-
VersionedTransaction,
|
|
9
|
-
sendAndConfirmTransaction,
|
|
10
|
-
} from '@solana/web3.js'
|
|
11
|
-
import bs58 from 'bs58'
|
|
12
|
-
import nacl from 'tweetnacl'
|
|
13
|
-
|
|
14
|
-
import { decryptKey, encryptKey } from './storage'
|
|
15
|
-
|
|
16
|
-
export type SolanaCluster = 'mainnet-beta' | 'devnet' | 'testnet'
|
|
17
|
-
|
|
18
|
-
export interface SolanaExecutionOptions {
|
|
19
|
-
cluster?: SolanaCluster | string | null
|
|
20
|
-
rpcUrl?: string | null
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
export interface SolanaMessageInput {
|
|
24
|
-
message?: string | null
|
|
25
|
-
messageHex?: string | null
|
|
26
|
-
messageBase64?: string | null
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
const DEFAULT_RPC_URL = process.env.SOLANA_RPC_URL || 'https://api.mainnet-beta.solana.com'
|
|
30
|
-
|
|
31
|
-
function getClusterRpcUrl(cluster: SolanaCluster): string {
|
|
32
|
-
if (cluster === 'devnet') return process.env.SOLANA_DEVNET_RPC_URL || 'https://api.devnet.solana.com'
|
|
33
|
-
if (cluster === 'testnet') return process.env.SOLANA_TESTNET_RPC_URL || 'https://api.testnet.solana.com'
|
|
34
|
-
return process.env.SOLANA_RPC_URL || 'https://api.mainnet-beta.solana.com'
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
function normalizeHexMessage(value: string): Uint8Array {
|
|
38
|
-
const trimmed = value.trim()
|
|
39
|
-
if (!/^0x[0-9a-fA-F]*$/.test(trimmed)) {
|
|
40
|
-
throw new Error('messageHex must be a 0x-prefixed hex string')
|
|
41
|
-
}
|
|
42
|
-
return Uint8Array.from(Buffer.from(trimmed.slice(2), 'hex'))
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
function normalizeMessageBytes(input: SolanaMessageInput): Uint8Array {
|
|
46
|
-
if (typeof input.messageHex === 'string' && input.messageHex.trim()) return normalizeHexMessage(input.messageHex)
|
|
47
|
-
if (typeof input.messageBase64 === 'string' && input.messageBase64.trim()) {
|
|
48
|
-
return Uint8Array.from(Buffer.from(input.messageBase64.trim(), 'base64'))
|
|
49
|
-
}
|
|
50
|
-
if (typeof input.message === 'string') return new TextEncoder().encode(input.message)
|
|
51
|
-
throw new Error('message, messageHex, or messageBase64 is required')
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
function deserializeTransactionBase64(value: string): Transaction | VersionedTransaction {
|
|
55
|
-
const bytes = Buffer.from(value, 'base64')
|
|
56
|
-
try {
|
|
57
|
-
return VersionedTransaction.deserialize(bytes)
|
|
58
|
-
} catch {
|
|
59
|
-
return Transaction.from(bytes)
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
function serializeTransactionBase64(transaction: Transaction | VersionedTransaction): string {
|
|
64
|
-
return Buffer.from(transaction.serialize()).toString('base64')
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
function collectTransactionSignatures(transaction: Transaction | VersionedTransaction): string[] {
|
|
68
|
-
if (transaction instanceof VersionedTransaction) {
|
|
69
|
-
return transaction.signatures
|
|
70
|
-
.map((signature) => bs58.encode(signature))
|
|
71
|
-
.filter(Boolean)
|
|
72
|
-
}
|
|
73
|
-
return transaction.signatures
|
|
74
|
-
.map((entry) => (entry.signature ? bs58.encode(entry.signature) : ''))
|
|
75
|
-
.filter(Boolean)
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
function signTransactionWithWallet(
|
|
79
|
-
encryptedPrivateKey: string,
|
|
80
|
-
transaction: Transaction | VersionedTransaction,
|
|
81
|
-
): { transaction: Transaction | VersionedTransaction; publicKey: string } {
|
|
82
|
-
const keypair = getKeypairFromEncrypted(encryptedPrivateKey)
|
|
83
|
-
if (transaction instanceof VersionedTransaction) {
|
|
84
|
-
transaction.sign([keypair])
|
|
85
|
-
} else {
|
|
86
|
-
transaction.sign(keypair)
|
|
87
|
-
}
|
|
88
|
-
return {
|
|
89
|
-
transaction,
|
|
90
|
-
publicKey: keypair.publicKey.toBase58(),
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
export function generateSolanaKeypair(): { publicKey: string; encryptedPrivateKey: string } {
|
|
95
|
-
const keypair = Keypair.generate()
|
|
96
|
-
const secretKeyBase58 = bs58.encode(keypair.secretKey)
|
|
97
|
-
return {
|
|
98
|
-
publicKey: keypair.publicKey.toBase58(),
|
|
99
|
-
encryptedPrivateKey: encryptKey(secretKeyBase58),
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
export function getKeypairFromEncrypted(encryptedPrivateKey: string): Keypair {
|
|
104
|
-
const secretKeyBase58 = decryptKey(encryptedPrivateKey)
|
|
105
|
-
const secretKey = bs58.decode(secretKeyBase58)
|
|
106
|
-
return Keypair.fromSecretKey(secretKey)
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
export function normalizeSolanaCluster(value: unknown, fallback: SolanaCluster = 'mainnet-beta'): SolanaCluster {
|
|
110
|
-
const normalized = String(value ?? '').trim().toLowerCase()
|
|
111
|
-
if (!normalized) return fallback
|
|
112
|
-
if (normalized === 'mainnet' || normalized === 'mainnet-beta' || normalized === 'solana') return 'mainnet-beta'
|
|
113
|
-
if (normalized === 'devnet') return 'devnet'
|
|
114
|
-
if (normalized === 'testnet') return 'testnet'
|
|
115
|
-
throw new Error(`Unsupported Solana cluster: ${String(value)}`)
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
export function getSolanaClusterLabel(value?: unknown): string {
|
|
119
|
-
const cluster = normalizeSolanaCluster(value)
|
|
120
|
-
if (cluster === 'devnet') return 'Solana Devnet'
|
|
121
|
-
if (cluster === 'testnet') return 'Solana Testnet'
|
|
122
|
-
return 'Solana Mainnet'
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
export function getSolanaExplorerUrl(cluster: SolanaCluster | string | null | undefined, kind: 'address' | 'transaction', value: string): string {
|
|
126
|
-
const normalized = normalizeSolanaCluster(cluster)
|
|
127
|
-
const prefix = kind === 'address' ? 'address' : 'tx'
|
|
128
|
-
const clusterSuffix = normalized === 'mainnet-beta' ? '' : `?cluster=${normalized}`
|
|
129
|
-
return `https://explorer.solana.com/${prefix}/${value}${clusterSuffix}`
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
const CONNECTION_CACHE_MAX = 50
|
|
133
|
-
const connectionCache = new Map<string, Connection>()
|
|
134
|
-
|
|
135
|
-
function getCachedConnection(url: string): Connection {
|
|
136
|
-
let conn = connectionCache.get(url)
|
|
137
|
-
if (!conn) {
|
|
138
|
-
// FIFO eviction if cache exceeds cap
|
|
139
|
-
if (connectionCache.size >= CONNECTION_CACHE_MAX) {
|
|
140
|
-
const firstKey = connectionCache.keys().next().value
|
|
141
|
-
if (firstKey !== undefined) connectionCache.delete(firstKey)
|
|
142
|
-
}
|
|
143
|
-
conn = new Connection(url, {
|
|
144
|
-
commitment: 'confirmed',
|
|
145
|
-
disableRetryOnRateLimit: true,
|
|
146
|
-
})
|
|
147
|
-
connectionCache.set(url, conn)
|
|
148
|
-
}
|
|
149
|
-
return conn
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
export function getConnection(rpcUrl?: string): Connection {
|
|
153
|
-
return getCachedConnection(rpcUrl || DEFAULT_RPC_URL)
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
export function getConnectionForCluster(cluster?: SolanaCluster | string | null, rpcUrl?: string | null): Connection {
|
|
157
|
-
return getCachedConnection(rpcUrl || getClusterRpcUrl(normalizeSolanaCluster(cluster)))
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
export async function getBalance(publicKey: string, rpcUrl?: string): Promise<number> {
|
|
161
|
-
const connection = getConnection(rpcUrl)
|
|
162
|
-
const pk = new PublicKey(publicKey)
|
|
163
|
-
return connection.getBalance(pk)
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
export async function sendSol(
|
|
167
|
-
encryptedPrivateKey: string,
|
|
168
|
-
toAddress: string,
|
|
169
|
-
lamports: number,
|
|
170
|
-
rpcUrl?: string,
|
|
171
|
-
): Promise<{ signature: string; fee: number }> {
|
|
172
|
-
const connection = getConnection(rpcUrl)
|
|
173
|
-
const fromKeypair = getKeypairFromEncrypted(encryptedPrivateKey)
|
|
174
|
-
const toPublicKey = new PublicKey(toAddress)
|
|
175
|
-
|
|
176
|
-
const transaction = new Transaction().add(
|
|
177
|
-
SystemProgram.transfer({
|
|
178
|
-
fromPubkey: fromKeypair.publicKey,
|
|
179
|
-
toPubkey: toPublicKey,
|
|
180
|
-
lamports,
|
|
181
|
-
}),
|
|
182
|
-
)
|
|
183
|
-
|
|
184
|
-
const signature = await sendAndConfirmTransaction(connection, transaction, [fromKeypair])
|
|
185
|
-
|
|
186
|
-
let fee = 5000
|
|
187
|
-
try {
|
|
188
|
-
const txInfo = await connection.getTransaction(signature, { commitment: 'confirmed' })
|
|
189
|
-
if (txInfo?.meta?.fee) fee = txInfo.meta.fee
|
|
190
|
-
} catch {
|
|
191
|
-
// keep default
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
return { signature, fee }
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
export async function signSolanaMessage(
|
|
198
|
-
encryptedPrivateKey: string,
|
|
199
|
-
input: SolanaMessageInput,
|
|
200
|
-
): Promise<{ signature: string; publicKey: string }> {
|
|
201
|
-
const keypair = getKeypairFromEncrypted(encryptedPrivateKey)
|
|
202
|
-
const signature = nacl.sign.detached(normalizeMessageBytes(input), keypair.secretKey)
|
|
203
|
-
return {
|
|
204
|
-
signature: bs58.encode(signature),
|
|
205
|
-
publicKey: keypair.publicKey.toBase58(),
|
|
206
|
-
}
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
export async function signSolanaTransaction(
|
|
210
|
-
encryptedPrivateKey: string,
|
|
211
|
-
transactionBase64: string,
|
|
212
|
-
): Promise<{
|
|
213
|
-
signedTransactionBase64: string
|
|
214
|
-
signatures: string[]
|
|
215
|
-
publicKey: string
|
|
216
|
-
versioned: boolean
|
|
217
|
-
}> {
|
|
218
|
-
const unsignedTx = deserializeTransactionBase64(transactionBase64)
|
|
219
|
-
const { transaction, publicKey } = signTransactionWithWallet(encryptedPrivateKey, unsignedTx)
|
|
220
|
-
return {
|
|
221
|
-
signedTransactionBase64: serializeTransactionBase64(transaction),
|
|
222
|
-
signatures: collectTransactionSignatures(transaction),
|
|
223
|
-
publicKey,
|
|
224
|
-
versioned: transaction instanceof VersionedTransaction,
|
|
225
|
-
}
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
export async function simulateSolanaTransaction(
|
|
229
|
-
encryptedPrivateKey: string,
|
|
230
|
-
transactionBase64: string,
|
|
231
|
-
options?: SolanaExecutionOptions,
|
|
232
|
-
): Promise<{
|
|
233
|
-
signatures: string[]
|
|
234
|
-
publicKey: string
|
|
235
|
-
logs: string[]
|
|
236
|
-
unitsConsumed?: number
|
|
237
|
-
err?: unknown
|
|
238
|
-
versioned: boolean
|
|
239
|
-
}> {
|
|
240
|
-
const unsignedTx = deserializeTransactionBase64(transactionBase64)
|
|
241
|
-
const { transaction, publicKey } = signTransactionWithWallet(encryptedPrivateKey, unsignedTx)
|
|
242
|
-
const connection = getConnectionForCluster(options?.cluster, options?.rpcUrl)
|
|
243
|
-
const simulation = transaction instanceof VersionedTransaction
|
|
244
|
-
? await connection.simulateTransaction(transaction)
|
|
245
|
-
: await connection.simulateTransaction(transaction)
|
|
246
|
-
return {
|
|
247
|
-
signatures: collectTransactionSignatures(transaction),
|
|
248
|
-
publicKey,
|
|
249
|
-
logs: simulation.value.logs || [],
|
|
250
|
-
unitsConsumed: simulation.value.unitsConsumed ?? undefined,
|
|
251
|
-
err: simulation.value.err ?? undefined,
|
|
252
|
-
versioned: transaction instanceof VersionedTransaction,
|
|
253
|
-
}
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
export async function sendSolanaTransaction(
|
|
257
|
-
encryptedPrivateKey: string,
|
|
258
|
-
input: {
|
|
259
|
-
transactionBase64?: string | null
|
|
260
|
-
signedTransactionBase64?: string | null
|
|
261
|
-
waitForConfirmation?: boolean
|
|
262
|
-
},
|
|
263
|
-
options?: SolanaExecutionOptions,
|
|
264
|
-
): Promise<{
|
|
265
|
-
signature: string
|
|
266
|
-
publicKey: string
|
|
267
|
-
explorerUrl: string
|
|
268
|
-
versioned: boolean
|
|
269
|
-
}> {
|
|
270
|
-
const connection = getConnectionForCluster(options?.cluster, options?.rpcUrl)
|
|
271
|
-
const keypair = getKeypairFromEncrypted(encryptedPrivateKey)
|
|
272
|
-
const waitForConfirmation = input.waitForConfirmation !== false
|
|
273
|
-
|
|
274
|
-
let transaction: Transaction | VersionedTransaction
|
|
275
|
-
if (typeof input.signedTransactionBase64 === 'string' && input.signedTransactionBase64.trim()) {
|
|
276
|
-
transaction = deserializeTransactionBase64(input.signedTransactionBase64.trim())
|
|
277
|
-
} else if (typeof input.transactionBase64 === 'string' && input.transactionBase64.trim()) {
|
|
278
|
-
transaction = signTransactionWithWallet(encryptedPrivateKey, deserializeTransactionBase64(input.transactionBase64.trim())).transaction
|
|
279
|
-
} else {
|
|
280
|
-
throw new Error('transactionBase64 or signedTransactionBase64 is required')
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
const raw = transaction.serialize()
|
|
284
|
-
const signature = await connection.sendRawTransaction(raw)
|
|
285
|
-
if (waitForConfirmation) {
|
|
286
|
-
await connection.confirmTransaction(signature, 'confirmed')
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
return {
|
|
290
|
-
signature,
|
|
291
|
-
publicKey: keypair.publicKey.toBase58(),
|
|
292
|
-
explorerUrl: getSolanaExplorerUrl(options?.cluster, 'transaction', signature),
|
|
293
|
-
versioned: transaction instanceof VersionedTransaction,
|
|
294
|
-
}
|
|
295
|
-
}
|
|
296
|
-
|
|
297
|
-
export async function getRecentTransactions(
|
|
298
|
-
publicKey: string,
|
|
299
|
-
limit = 20,
|
|
300
|
-
rpcUrl?: string,
|
|
301
|
-
): Promise<Array<{ signature: string; blockTime: number | null; err: unknown }>> {
|
|
302
|
-
const connection = getConnection(rpcUrl)
|
|
303
|
-
const pk = new PublicKey(publicKey)
|
|
304
|
-
const signatures = await connection.getSignaturesForAddress(pk, { limit })
|
|
305
|
-
return signatures.map((s) => ({
|
|
306
|
-
signature: s.signature,
|
|
307
|
-
blockTime: s.blockTime ?? null,
|
|
308
|
-
err: s.err,
|
|
309
|
-
}))
|
|
310
|
-
}
|
|
311
|
-
|
|
312
|
-
export function isValidSolanaAddress(address: string): boolean {
|
|
313
|
-
try {
|
|
314
|
-
new PublicKey(address)
|
|
315
|
-
return true
|
|
316
|
-
} catch {
|
|
317
|
-
return false
|
|
318
|
-
}
|
|
319
|
-
}
|
|
320
|
-
|
|
321
|
-
export function lamportsToSol(lamports: number): number {
|
|
322
|
-
return lamports / LAMPORTS_PER_SOL
|
|
323
|
-
}
|
|
324
|
-
|
|
325
|
-
export function solToLamports(sol: number): number {
|
|
326
|
-
return Math.round(sol * LAMPORTS_PER_SOL)
|
|
327
|
-
}
|
|
@@ -1,198 +0,0 @@
|
|
|
1
|
-
import assert from 'node:assert/strict'
|
|
2
|
-
import fs from 'node:fs'
|
|
3
|
-
import os from 'node:os'
|
|
4
|
-
import path from 'node:path'
|
|
5
|
-
import { after, before, describe, it } from 'node:test'
|
|
6
|
-
|
|
7
|
-
import type { AgentWallet } from '@/types'
|
|
8
|
-
|
|
9
|
-
const originalEnv = {
|
|
10
|
-
DATA_DIR: process.env.DATA_DIR,
|
|
11
|
-
WORKSPACE_DIR: process.env.WORKSPACE_DIR,
|
|
12
|
-
SWARMCLAW_BUILD_MODE: process.env.SWARMCLAW_BUILD_MODE,
|
|
13
|
-
CREDENTIAL_SECRET: process.env.CREDENTIAL_SECRET,
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
let tempDir = ''
|
|
17
|
-
let encryptKey: typeof import('@/lib/server/storage').encryptKey
|
|
18
|
-
let callEthereumContract: typeof import('@/lib/server/ethereum').callEthereumContract
|
|
19
|
-
let encodeEthereumContractCall: typeof import('@/lib/server/ethereum').encodeEthereumContractCall
|
|
20
|
-
let prepareEvmSwapPlan: typeof import('@/lib/server/evm-swap').prepareEvmSwapPlan
|
|
21
|
-
let signEthereumMessage: typeof import('@/lib/server/ethereum').signEthereumMessage
|
|
22
|
-
let signEthereumTypedData: typeof import('@/lib/server/ethereum').signEthereumTypedData
|
|
23
|
-
let generateSolanaKeypair: typeof import('@/lib/server/solana').generateSolanaKeypair
|
|
24
|
-
let signSolanaMessage: typeof import('@/lib/server/solana').signSolanaMessage
|
|
25
|
-
let signSolanaTransaction: typeof import('@/lib/server/solana').signSolanaTransaction
|
|
26
|
-
let TransactionCtor: typeof import('@solana/web3.js').Transaction
|
|
27
|
-
let SystemProgramNs: typeof import('@solana/web3.js').SystemProgram
|
|
28
|
-
let PublicKeyCtor: typeof import('@solana/web3.js').PublicKey
|
|
29
|
-
|
|
30
|
-
before(async () => {
|
|
31
|
-
tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'swarmclaw-wallet-exec-'))
|
|
32
|
-
process.env.DATA_DIR = path.join(tempDir, 'data')
|
|
33
|
-
process.env.WORKSPACE_DIR = path.join(tempDir, 'workspace')
|
|
34
|
-
process.env.SWARMCLAW_BUILD_MODE = '1'
|
|
35
|
-
process.env.CREDENTIAL_SECRET = '11'.repeat(32)
|
|
36
|
-
fs.mkdirSync(process.env.DATA_DIR, { recursive: true })
|
|
37
|
-
fs.mkdirSync(process.env.WORKSPACE_DIR, { recursive: true })
|
|
38
|
-
|
|
39
|
-
;({ encryptKey } = await import('@/lib/server/storage'))
|
|
40
|
-
;({ callEthereumContract, encodeEthereumContractCall, signEthereumMessage, signEthereumTypedData } = await import('@/lib/server/ethereum'))
|
|
41
|
-
;({ prepareEvmSwapPlan } = await import('@/lib/server/evm-swap'))
|
|
42
|
-
;({ generateSolanaKeypair, signSolanaMessage, signSolanaTransaction } = await import('@/lib/server/solana'))
|
|
43
|
-
;({ Transaction: TransactionCtor, SystemProgram: SystemProgramNs, PublicKey: PublicKeyCtor } = await import('@solana/web3.js'))
|
|
44
|
-
})
|
|
45
|
-
|
|
46
|
-
after(() => {
|
|
47
|
-
if (originalEnv.DATA_DIR === undefined) delete process.env.DATA_DIR
|
|
48
|
-
else process.env.DATA_DIR = originalEnv.DATA_DIR
|
|
49
|
-
if (originalEnv.WORKSPACE_DIR === undefined) delete process.env.WORKSPACE_DIR
|
|
50
|
-
else process.env.WORKSPACE_DIR = originalEnv.WORKSPACE_DIR
|
|
51
|
-
if (originalEnv.SWARMCLAW_BUILD_MODE === undefined) delete process.env.SWARMCLAW_BUILD_MODE
|
|
52
|
-
else process.env.SWARMCLAW_BUILD_MODE = originalEnv.SWARMCLAW_BUILD_MODE
|
|
53
|
-
if (originalEnv.CREDENTIAL_SECRET === undefined) delete process.env.CREDENTIAL_SECRET
|
|
54
|
-
else process.env.CREDENTIAL_SECRET = originalEnv.CREDENTIAL_SECRET
|
|
55
|
-
fs.rmSync(tempDir, { recursive: true, force: true })
|
|
56
|
-
})
|
|
57
|
-
|
|
58
|
-
describe('wallet execution helpers', () => {
|
|
59
|
-
it('encodes ERC-20 contract calldata and signs EVM payloads', async () => {
|
|
60
|
-
const privateKey = '0x59c6995e998f97a5a004497e5d4ab3d89165b0def05d6d33923995df83329538'
|
|
61
|
-
const encrypted = encryptKey(privateKey)
|
|
62
|
-
|
|
63
|
-
const encoded = encodeEthereumContractCall(
|
|
64
|
-
['function approve(address spender,uint256 amount)'],
|
|
65
|
-
'approve',
|
|
66
|
-
['0x000000000000000000000000000000000000dEaD', '1000'],
|
|
67
|
-
)
|
|
68
|
-
assert.equal(encoded.data.startsWith('0x095ea7b3'), true)
|
|
69
|
-
|
|
70
|
-
const encodedFromNamedArgs = encodeEthereumContractCall(
|
|
71
|
-
['function approve(address spender,uint256 amount)'],
|
|
72
|
-
'approve',
|
|
73
|
-
{ spender: '0x000000000000000000000000000000000000dEaD', amount: '1000' },
|
|
74
|
-
)
|
|
75
|
-
assert.equal(encodedFromNamedArgs.data, encoded.data)
|
|
76
|
-
|
|
77
|
-
const encodedTupleArg = encodeEthereumContractCall(
|
|
78
|
-
['function quoteExactInputSingle((address tokenIn,address tokenOut,uint256 amountIn,uint24 fee,uint160 sqrtPriceLimitX96) params) returns (uint256 amountOut)'],
|
|
79
|
-
'quoteExactInputSingle',
|
|
80
|
-
{
|
|
81
|
-
tokenIn: '0x0000000000000000000000000000000000000001',
|
|
82
|
-
tokenOut: '0x0000000000000000000000000000000000000002',
|
|
83
|
-
amountIn: '1000000',
|
|
84
|
-
fee: 500,
|
|
85
|
-
sqrtPriceLimitX96: '0',
|
|
86
|
-
},
|
|
87
|
-
)
|
|
88
|
-
assert.equal(encodedTupleArg.data.startsWith('0xc6a5026a'), true)
|
|
89
|
-
|
|
90
|
-
const signedMessage = await signEthereumMessage(encrypted, { message: 'hello world' })
|
|
91
|
-
assert.equal(signedMessage.address.length, 42)
|
|
92
|
-
assert.equal(signedMessage.signature.startsWith('0x'), true)
|
|
93
|
-
|
|
94
|
-
const signedTypedData = await signEthereumTypedData(encrypted, {
|
|
95
|
-
domain: {
|
|
96
|
-
name: 'SwarmClaw',
|
|
97
|
-
version: '1',
|
|
98
|
-
chainId: 1,
|
|
99
|
-
},
|
|
100
|
-
types: {
|
|
101
|
-
Login: [
|
|
102
|
-
{ name: 'wallet', type: 'address' },
|
|
103
|
-
{ name: 'nonce', type: 'uint256' },
|
|
104
|
-
],
|
|
105
|
-
},
|
|
106
|
-
value: {
|
|
107
|
-
wallet: signedMessage.address,
|
|
108
|
-
nonce: '7',
|
|
109
|
-
},
|
|
110
|
-
})
|
|
111
|
-
assert.equal(signedTypedData.signature.startsWith('0x'), true)
|
|
112
|
-
|
|
113
|
-
const called = await callEthereumContract(encrypted, {
|
|
114
|
-
contractAddress: '0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2',
|
|
115
|
-
abi: ['function name() view returns (string)'],
|
|
116
|
-
functionName: 'name',
|
|
117
|
-
}, {
|
|
118
|
-
network: 'ethereum',
|
|
119
|
-
rpcUrl: 'https://ethereum-rpc.publicnode.com',
|
|
120
|
-
})
|
|
121
|
-
assert.equal(called.decoded, 'Wrapped Ether')
|
|
122
|
-
|
|
123
|
-
const allowance = await callEthereumContract(encrypted, {
|
|
124
|
-
contractAddress: '0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2',
|
|
125
|
-
abi: ['function allowance(address owner,address spender) view returns (uint256)'],
|
|
126
|
-
functionName: 'allowance',
|
|
127
|
-
args: {
|
|
128
|
-
owner: signedMessage.address,
|
|
129
|
-
spender: '0x000000000000000000000000000000000000dEaD',
|
|
130
|
-
},
|
|
131
|
-
}, {
|
|
132
|
-
network: 'ethereum',
|
|
133
|
-
rpcUrl: 'https://ethereum-rpc.publicnode.com',
|
|
134
|
-
})
|
|
135
|
-
assert.equal(typeof allowance.decoded, 'string')
|
|
136
|
-
})
|
|
137
|
-
|
|
138
|
-
it('builds a generic ParaSwap-backed swap plan for Arbitrum without a venue-specific adapter', async () => {
|
|
139
|
-
const privateKey = '0x59c6995e998f97a5a004497e5d4ab3d89165b0def05d6d33923995df83329538'
|
|
140
|
-
const encrypted = encryptKey(privateKey)
|
|
141
|
-
const walletAddress = (await signEthereumMessage(encrypted, { message: 'derive address' })).address
|
|
142
|
-
const wallet: AgentWallet = {
|
|
143
|
-
id: 'wallet_swap_plan',
|
|
144
|
-
agentId: 'agent_wallet',
|
|
145
|
-
chain: 'ethereum',
|
|
146
|
-
publicKey: walletAddress,
|
|
147
|
-
encryptedPrivateKey: encrypted,
|
|
148
|
-
spendingLimitAtomic: '1000000000000000000',
|
|
149
|
-
dailyLimitAtomic: '10000000000000000000',
|
|
150
|
-
requireApproval: true,
|
|
151
|
-
createdAt: Date.now(),
|
|
152
|
-
updatedAt: Date.now(),
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
const plan = await prepareEvmSwapPlan({
|
|
156
|
-
wallet,
|
|
157
|
-
network: 'arbitrum',
|
|
158
|
-
sellToken: 'USDC',
|
|
159
|
-
buyToken: 'ETH',
|
|
160
|
-
sellAmountDisplay: '1',
|
|
161
|
-
skipBalanceCheck: true,
|
|
162
|
-
})
|
|
163
|
-
|
|
164
|
-
assert.equal(plan.provider, 'paraswap')
|
|
165
|
-
assert.equal(plan.network.id, 'arbitrum')
|
|
166
|
-
assert.equal(plan.sellToken.symbol, 'USDC')
|
|
167
|
-
assert.equal(plan.buyToken.symbol, 'ETH')
|
|
168
|
-
assert.equal(plan.sellAmountAtomic, '1000000')
|
|
169
|
-
assert.equal(plan.approvalRequired, true)
|
|
170
|
-
assert.equal(typeof plan.spenderAddress, 'string')
|
|
171
|
-
assert.equal(typeof plan.swapTransaction.to, 'string')
|
|
172
|
-
assert.equal(String(plan.swapTransaction.data || '').startsWith('0x'), true)
|
|
173
|
-
})
|
|
174
|
-
|
|
175
|
-
it('signs Solana messages and legacy transactions offline', async () => {
|
|
176
|
-
const sender = generateSolanaKeypair()
|
|
177
|
-
const recipient = generateSolanaKeypair()
|
|
178
|
-
|
|
179
|
-
const signedMessage = await signSolanaMessage(sender.encryptedPrivateKey, { message: 'solana hello' })
|
|
180
|
-
assert.equal(signedMessage.publicKey, sender.publicKey)
|
|
181
|
-
assert.equal(signedMessage.signature.length > 40, true)
|
|
182
|
-
|
|
183
|
-
const tx = new TransactionCtor()
|
|
184
|
-
tx.feePayer = new PublicKeyCtor(sender.publicKey)
|
|
185
|
-
tx.recentBlockhash = generateSolanaKeypair().publicKey
|
|
186
|
-
tx.add(SystemProgramNs.transfer({
|
|
187
|
-
fromPubkey: new PublicKeyCtor(sender.publicKey),
|
|
188
|
-
toPubkey: new PublicKeyCtor(recipient.publicKey),
|
|
189
|
-
lamports: 1_234,
|
|
190
|
-
}))
|
|
191
|
-
|
|
192
|
-
const unsignedBase64 = Buffer.from(tx.serialize({ requireAllSignatures: false, verifySignatures: false })).toString('base64')
|
|
193
|
-
const signedTx = await signSolanaTransaction(sender.encryptedPrivateKey, unsignedBase64)
|
|
194
|
-
assert.equal(signedTx.publicKey, sender.publicKey)
|
|
195
|
-
assert.equal(signedTx.signatures.length > 0, true)
|
|
196
|
-
assert.equal(typeof signedTx.signedTransactionBase64, 'string')
|
|
197
|
-
})
|
|
198
|
-
})
|
|
@@ -1,98 +0,0 @@
|
|
|
1
|
-
import assert from 'node:assert/strict'
|
|
2
|
-
import { describe, it } from 'node:test'
|
|
3
|
-
import type { AgentWallet } from '@/types'
|
|
4
|
-
|
|
5
|
-
import {
|
|
6
|
-
buildLogDiscoveryRanges,
|
|
7
|
-
estimateDiscoveryStartBlock,
|
|
8
|
-
getKnownEvmTokenContracts,
|
|
9
|
-
parseMetaplexMetadataFields,
|
|
10
|
-
buildEmptyWalletPortfolio,
|
|
11
|
-
resolveWalletPortfolioWithTimeout,
|
|
12
|
-
} from '@/lib/server/wallet/wallet-portfolio'
|
|
13
|
-
|
|
14
|
-
describe('wallet portfolio helpers', () => {
|
|
15
|
-
it('splits large log discovery requests into provider-safe chunks', () => {
|
|
16
|
-
assert.deepEqual(buildLogDiscoveryRanges(10, 10, 50_000), [{ fromBlock: 10, toBlock: 10 }])
|
|
17
|
-
assert.deepEqual(buildLogDiscoveryRanges(1, 120_000, 50_000), [
|
|
18
|
-
{ fromBlock: 1, toBlock: 50_000 },
|
|
19
|
-
{ fromBlock: 50_001, toBlock: 100_000 },
|
|
20
|
-
{ fromBlock: 100_001, toBlock: 120_000 },
|
|
21
|
-
])
|
|
22
|
-
})
|
|
23
|
-
|
|
24
|
-
it('always checks canonical USDC contracts on supported EVM networks', () => {
|
|
25
|
-
assert.equal(
|
|
26
|
-
getKnownEvmTokenContracts('arbitrum').map((address) => address.toLowerCase()).includes('0xaf88d065e77c8cc2239327c5edb3a432268e5831'),
|
|
27
|
-
true,
|
|
28
|
-
)
|
|
29
|
-
assert.equal(
|
|
30
|
-
getKnownEvmTokenContracts('base').map((address) => address.toLowerCase()).includes('0x833589fcd6edb6e08f4c7c32d4f71b54bda02913'),
|
|
31
|
-
true,
|
|
32
|
-
)
|
|
33
|
-
assert.equal(
|
|
34
|
-
getKnownEvmTokenContracts('ethereum').map((address) => address.toLowerCase()).includes('0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48'),
|
|
35
|
-
true,
|
|
36
|
-
)
|
|
37
|
-
})
|
|
38
|
-
|
|
39
|
-
it('scans from wallet age rather than capping discovery to a fixed recent window', () => {
|
|
40
|
-
const now = Date.UTC(2026, 2, 8)
|
|
41
|
-
const latestBlock = 10_000_000
|
|
42
|
-
const newerWalletStart = estimateDiscoveryStartBlock({
|
|
43
|
-
latestBlock,
|
|
44
|
-
walletCreatedAt: now - (7 * 24 * 60 * 60 * 1000),
|
|
45
|
-
avgBlockMs: 12_000,
|
|
46
|
-
maxDiscoveryBlocks: 5_000_000,
|
|
47
|
-
now,
|
|
48
|
-
})
|
|
49
|
-
const olderWalletStart = estimateDiscoveryStartBlock({
|
|
50
|
-
latestBlock,
|
|
51
|
-
walletCreatedAt: now - (30 * 24 * 60 * 60 * 1000),
|
|
52
|
-
avgBlockMs: 12_000,
|
|
53
|
-
maxDiscoveryBlocks: 5_000_000,
|
|
54
|
-
now,
|
|
55
|
-
})
|
|
56
|
-
|
|
57
|
-
assert.equal(olderWalletStart < newerWalletStart, true)
|
|
58
|
-
})
|
|
59
|
-
|
|
60
|
-
it('parses metaplex metadata name and symbol for arbitrary SPL mints', () => {
|
|
61
|
-
const data = Buffer.alloc(1 + 32 + 32 + 32 + 10)
|
|
62
|
-
Buffer.from('Example Token').copy(data, 1 + 32 + 32)
|
|
63
|
-
Buffer.from('EXMPL').copy(data, 1 + 32 + 32 + 32)
|
|
64
|
-
|
|
65
|
-
assert.deepEqual(parseMetaplexMetadataFields(data), {
|
|
66
|
-
name: 'Example Token',
|
|
67
|
-
symbol: 'EXMPL',
|
|
68
|
-
})
|
|
69
|
-
})
|
|
70
|
-
|
|
71
|
-
it('returns stale portfolio data when a live portfolio lookup times out', async () => {
|
|
72
|
-
const wallet: AgentWallet = {
|
|
73
|
-
id: 'wallet-timeout',
|
|
74
|
-
agentId: 'agent-timeout',
|
|
75
|
-
chain: 'ethereum',
|
|
76
|
-
publicKey: '0x0000000000000000000000000000000000000001',
|
|
77
|
-
encryptedPrivateKey: 'secret',
|
|
78
|
-
requireApproval: true,
|
|
79
|
-
spendingLimitAtomic: '1',
|
|
80
|
-
dailyLimitAtomic: '1',
|
|
81
|
-
createdAt: 1,
|
|
82
|
-
updatedAt: 1,
|
|
83
|
-
}
|
|
84
|
-
const stale = buildEmptyWalletPortfolio(wallet)
|
|
85
|
-
stale.balanceAtomic = '123'
|
|
86
|
-
stale.balanceFormatted = '0.000000000000000123'
|
|
87
|
-
stale.balanceDisplay = `${stale.balanceFormatted} ETH`
|
|
88
|
-
|
|
89
|
-
const result = await resolveWalletPortfolioWithTimeout({
|
|
90
|
-
load: () => new Promise<ReturnType<typeof buildEmptyWalletPortfolio>>(() => {}),
|
|
91
|
-
timeoutMs: 5,
|
|
92
|
-
stale,
|
|
93
|
-
label: 'wallet portfolio timeout test',
|
|
94
|
-
})
|
|
95
|
-
|
|
96
|
-
assert.equal(result.balanceAtomic, '123')
|
|
97
|
-
})
|
|
98
|
-
})
|