@agentlayer.tech/wallet 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.openclaw/AGENTS.md +98 -0
- package/.openclaw/extensions/agent-wallet/README.md +127 -0
- package/.openclaw/extensions/agent-wallet/index.ts +1520 -0
- package/.openclaw/extensions/agent-wallet/openclaw.plugin.json +184 -0
- package/.openclaw/extensions/agent-wallet/package.json +11 -0
- package/.openclaw/extensions/agent-wallet/skills/wallet-operator/SKILL.md +20 -0
- package/CHANGELOG.md +42 -0
- package/LICENSE +104 -0
- package/README.md +332 -0
- package/RELEASING.md +204 -0
- package/agent-wallet/.env.example +62 -0
- package/agent-wallet/AGENTS.md +129 -0
- package/agent-wallet/README.md +527 -0
- package/agent-wallet/agent_wallet/__init__.py +11 -0
- package/agent-wallet/agent_wallet/approval.py +161 -0
- package/agent-wallet/agent_wallet/bootstrap.py +178 -0
- package/agent-wallet/agent_wallet/btc_user_wallets.py +217 -0
- package/agent-wallet/agent_wallet/config.py +382 -0
- package/agent-wallet/agent_wallet/encrypted_storage.py +161 -0
- package/agent-wallet/agent_wallet/evm_user_wallets.py +370 -0
- package/agent-wallet/agent_wallet/exceptions.py +9 -0
- package/agent-wallet/agent_wallet/file_ops.py +34 -0
- package/agent-wallet/agent_wallet/http_client.py +25 -0
- package/agent-wallet/agent_wallet/models.py +66 -0
- package/agent-wallet/agent_wallet/nonce_registry.py +59 -0
- package/agent-wallet/agent_wallet/openclaw_adapter.py +5128 -0
- package/agent-wallet/agent_wallet/openclaw_cli.py +626 -0
- package/agent-wallet/agent_wallet/openclaw_runtime.py +272 -0
- package/agent-wallet/agent_wallet/plugin_bundle.py +42 -0
- package/agent-wallet/agent_wallet/providers/__init__.py +1 -0
- package/agent-wallet/agent_wallet/providers/bags.py +259 -0
- package/agent-wallet/agent_wallet/providers/evm_portfolio.py +470 -0
- package/agent-wallet/agent_wallet/providers/jupiter.py +567 -0
- package/agent-wallet/agent_wallet/providers/kamino.py +215 -0
- package/agent-wallet/agent_wallet/providers/lifi.py +277 -0
- package/agent-wallet/agent_wallet/providers/solana_rpc.py +470 -0
- package/agent-wallet/agent_wallet/providers/wdk_btc_local.py +114 -0
- package/agent-wallet/agent_wallet/providers/wdk_evm_local.py +205 -0
- package/agent-wallet/agent_wallet/sealed_keys.py +61 -0
- package/agent-wallet/agent_wallet/solana_stake.py +103 -0
- package/agent-wallet/agent_wallet/solana_tx.py +93 -0
- package/agent-wallet/agent_wallet/spending_limits.py +101 -0
- package/agent-wallet/agent_wallet/transaction_policy.py +518 -0
- package/agent-wallet/agent_wallet/user_wallets.py +355 -0
- package/agent-wallet/agent_wallet/validation.py +31 -0
- package/agent-wallet/agent_wallet/wallet_layer/__init__.py +1 -0
- package/agent-wallet/agent_wallet/wallet_layer/base.py +808 -0
- package/agent-wallet/agent_wallet/wallet_layer/base58.py +44 -0
- package/agent-wallet/agent_wallet/wallet_layer/factory.py +102 -0
- package/agent-wallet/agent_wallet/wallet_layer/solana.py +4252 -0
- package/agent-wallet/agent_wallet/wallet_layer/wdk_btc.py +272 -0
- package/agent-wallet/agent_wallet/wallet_layer/wdk_evm.py +1628 -0
- package/agent-wallet/examples/bootstrap_wallet.py +21 -0
- package/agent-wallet/examples/openclaw_runtime_onboarding.py +28 -0
- package/agent-wallet/examples/openclaw_user_wallet_example.py +31 -0
- package/agent-wallet/examples/openclaw_wallet_adapter_example.py +33 -0
- package/agent-wallet/openclaw.plugin.json +138 -0
- package/agent-wallet/pyproject.toml +31 -0
- package/agent-wallet/scripts/bootstrap_openclaw_btc.py +278 -0
- package/agent-wallet/scripts/build_release_bundle.py +188 -0
- package/agent-wallet/scripts/finalize_openclaw_local_wallet_config.py +121 -0
- package/agent-wallet/scripts/install_agent_wallet.py +505 -0
- package/agent-wallet/scripts/install_openclaw_local_config.py +226 -0
- package/agent-wallet/scripts/install_openclaw_sealed_keys.py +105 -0
- package/agent-wallet/scripts/manage_openclaw_btc_wallet.py +244 -0
- package/agent-wallet/scripts/reveal_btc_seed.sh +130 -0
- package/agent-wallet/scripts/security_utils.py +37 -0
- package/agent-wallet/scripts/setup_btc_wallet.sh +146 -0
- package/agent-wallet/scripts/switch_openclaw_wallet_network.py +106 -0
- package/agent-wallet/skills/wallet-operator/SKILL.md +128 -0
- package/bin/openclaw-agent-wallet.mjs +487 -0
- package/install-from-github.sh +134 -0
- package/package.json +61 -0
- package/setup.sh +40 -0
- package/wdk-btc-wallet/README.md +325 -0
- package/wdk-btc-wallet/bootstrap.sh +22 -0
- package/wdk-btc-wallet/package-lock.json +1839 -0
- package/wdk-btc-wallet/package.json +18 -0
- package/wdk-btc-wallet/run-local.sh +21 -0
- package/wdk-btc-wallet/src/config.js +160 -0
- package/wdk-btc-wallet/src/json.js +35 -0
- package/wdk-btc-wallet/src/local_vault.js +432 -0
- package/wdk-btc-wallet/src/network_state.js +84 -0
- package/wdk-btc-wallet/src/server.js +257 -0
- package/wdk-btc-wallet/src/wdk_btc_wallet.js +332 -0
- package/wdk-evm-wallet/README.md +183 -0
- package/wdk-evm-wallet/bootstrap.sh +8 -0
- package/wdk-evm-wallet/package-lock.json +2340 -0
- package/wdk-evm-wallet/package.json +23 -0
- package/wdk-evm-wallet/run-local.sh +12 -0
- package/wdk-evm-wallet/src/config.js +274 -0
- package/wdk-evm-wallet/src/json.js +35 -0
- package/wdk-evm-wallet/src/local_vault.js +430 -0
- package/wdk-evm-wallet/src/network_state.js +92 -0
- package/wdk-evm-wallet/src/server.js +575 -0
- package/wdk-evm-wallet/src/wdk_evm_wallet.js +4981 -0
|
@@ -0,0 +1,518 @@
|
|
|
1
|
+
"""Local transaction verification helpers for provider-built Solana transactions."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
from agent_wallet.wallet_layer.base import WalletBackendError
|
|
8
|
+
|
|
9
|
+
SYSTEM_PROGRAM_ID = "11111111111111111111111111111111"
|
|
10
|
+
COMPUTE_BUDGET_PROGRAM_ID = "ComputeBudget111111111111111111111111111111"
|
|
11
|
+
TOKEN_PROGRAM_ID = "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA"
|
|
12
|
+
TOKEN_2022_PROGRAM_ID = "TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb"
|
|
13
|
+
ATA_PROGRAM_ID = "ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL"
|
|
14
|
+
MEMO_PROGRAM_ID = "MemoSq4gqABAXKb96qnH8TysNcWxMyWCqXgDLGmfcHr"
|
|
15
|
+
ADDRESS_LOOKUP_TABLE_PROGRAM_ID = "AddressLookupTab1e1111111111111111111111111"
|
|
16
|
+
JUPITER_V6_PROGRAM_ID = "JUP6LkbZbjS1jKKwapdHNy74zcZ3tLUZoi5Nt7NQYjN"
|
|
17
|
+
JUPITER_ULTRA_EXACT_OUT_PROGRAM_ID = "j1o2qRpjcyUwEvwtcfhEQefh773ZgjxcVRry7LDqg5X"
|
|
18
|
+
JUPITER_DCA_PROGRAM_ID = "DCA265Vj8a7wYymQG8LqM3m7A4QeV9hiC7VYh4S6Jsa"
|
|
19
|
+
KAMINO_LEND_PROGRAM_ID = "KLend2g3cP87fffoy8q1mQqGKjrxjC8boSyAYavgmjD"
|
|
20
|
+
NATIVE_SOL_MINT = "So11111111111111111111111111111111111111112"
|
|
21
|
+
DEFAULT_NATIVE_SOL_EXTRA_SPEND_ALLOWANCE_LAMPORTS = 10_000_000
|
|
22
|
+
|
|
23
|
+
CORE_PROGRAM_IDS = {
|
|
24
|
+
SYSTEM_PROGRAM_ID,
|
|
25
|
+
COMPUTE_BUDGET_PROGRAM_ID,
|
|
26
|
+
TOKEN_PROGRAM_ID,
|
|
27
|
+
TOKEN_2022_PROGRAM_ID,
|
|
28
|
+
ATA_PROGRAM_ID,
|
|
29
|
+
MEMO_PROGRAM_ID,
|
|
30
|
+
ADDRESS_LOOKUP_TABLE_PROGRAM_ID,
|
|
31
|
+
}
|
|
32
|
+
SWAP_ALLOWED_PROGRAMS = CORE_PROGRAM_IDS | {
|
|
33
|
+
JUPITER_V6_PROGRAM_ID,
|
|
34
|
+
JUPITER_ULTRA_EXACT_OUT_PROGRAM_ID,
|
|
35
|
+
JUPITER_DCA_PROGRAM_ID,
|
|
36
|
+
}
|
|
37
|
+
RECOGNIZED_JUPITER_SWAP_PROGRAMS = {
|
|
38
|
+
JUPITER_V6_PROGRAM_ID,
|
|
39
|
+
JUPITER_ULTRA_EXACT_OUT_PROGRAM_ID,
|
|
40
|
+
JUPITER_DCA_PROGRAM_ID,
|
|
41
|
+
}
|
|
42
|
+
FORBIDDEN_PROGRAMS = {
|
|
43
|
+
"TokenSwap11111111111111111111111111111111",
|
|
44
|
+
}
|
|
45
|
+
KAMINO_ALLOWED_PROGRAMS = CORE_PROGRAM_IDS | {
|
|
46
|
+
KAMINO_LEND_PROGRAM_ID,
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def _static_account_keys(message: Any) -> list[str]:
|
|
51
|
+
return [str(value) for value in getattr(message, "account_keys", []) or []]
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def _account_keys(message: Any, loaded_addresses: list[str] | None = None) -> list[str]:
|
|
55
|
+
keys = _static_account_keys(message)
|
|
56
|
+
if loaded_addresses:
|
|
57
|
+
keys.extend(str(value) for value in loaded_addresses)
|
|
58
|
+
return keys
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def _compiled_instructions(message: Any) -> list[Any]:
|
|
62
|
+
return list(getattr(message, "instructions", []) or [])
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def _header_required_signatures(message: Any) -> int:
|
|
66
|
+
header = getattr(message, "header", None)
|
|
67
|
+
return int(getattr(header, "num_required_signatures", 0) or 0)
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def _required_signer_keys(message: Any) -> list[str]:
|
|
71
|
+
keys = _static_account_keys(message)
|
|
72
|
+
required = _header_required_signatures(message)
|
|
73
|
+
if required <= 0:
|
|
74
|
+
raise WalletBackendError("Provider transaction does not require any signers.")
|
|
75
|
+
if required > len(keys):
|
|
76
|
+
raise WalletBackendError("Provider transaction signer metadata is inconsistent.")
|
|
77
|
+
return keys[:required]
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def _program_ids(message: Any, loaded_addresses: list[str] | None = None) -> list[str]:
|
|
81
|
+
keys = _account_keys(message, loaded_addresses)
|
|
82
|
+
values: list[str] = []
|
|
83
|
+
for instruction in _compiled_instructions(message):
|
|
84
|
+
index = int(getattr(instruction, "program_id_index", -1))
|
|
85
|
+
if index < 0 or index >= len(keys):
|
|
86
|
+
raise WalletBackendError("Provider transaction contains an invalid program id index.")
|
|
87
|
+
values.append(keys[index])
|
|
88
|
+
return values
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def _assert_program_allowlist(
|
|
92
|
+
program_ids: list[str],
|
|
93
|
+
*,
|
|
94
|
+
allowed_programs: set[str],
|
|
95
|
+
label: str,
|
|
96
|
+
reject_unknown: bool = True,
|
|
97
|
+
) -> list[str]:
|
|
98
|
+
if not program_ids:
|
|
99
|
+
raise WalletBackendError(f"{label} transaction does not include any instructions.")
|
|
100
|
+
|
|
101
|
+
forbidden = [pid for pid in program_ids if pid in FORBIDDEN_PROGRAMS]
|
|
102
|
+
if forbidden:
|
|
103
|
+
raise WalletBackendError(
|
|
104
|
+
f"{label} transaction uses forbidden program ids: {', '.join(sorted(set(forbidden)))}"
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
unknown = [pid for pid in program_ids if pid not in allowed_programs]
|
|
108
|
+
if unknown and reject_unknown:
|
|
109
|
+
raise WalletBackendError(
|
|
110
|
+
f"{label} transaction uses unknown program ids: {', '.join(sorted(set(unknown)))}"
|
|
111
|
+
)
|
|
112
|
+
return sorted(set(unknown))
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def _assert_basic_wallet_binding(
|
|
116
|
+
message: Any,
|
|
117
|
+
*,
|
|
118
|
+
wallet_address: str,
|
|
119
|
+
loaded_addresses: list[str] | None = None,
|
|
120
|
+
) -> dict[str, Any]:
|
|
121
|
+
keys = _account_keys(message, loaded_addresses)
|
|
122
|
+
if not keys:
|
|
123
|
+
raise WalletBackendError("Provider transaction does not include account keys.")
|
|
124
|
+
signer_keys = _required_signer_keys(message)
|
|
125
|
+
if wallet_address not in signer_keys:
|
|
126
|
+
raise WalletBackendError(
|
|
127
|
+
"Provider transaction does not require the connected wallet as an authorized signer."
|
|
128
|
+
)
|
|
129
|
+
if len(signer_keys) > 2:
|
|
130
|
+
raise WalletBackendError(
|
|
131
|
+
"Provider transaction requires unexpected additional signers and was rejected."
|
|
132
|
+
)
|
|
133
|
+
if wallet_address not in keys:
|
|
134
|
+
raise WalletBackendError("Provider transaction is not bound to the connected wallet.")
|
|
135
|
+
return {
|
|
136
|
+
"account_keys": keys,
|
|
137
|
+
"fee_payer": keys[0],
|
|
138
|
+
"required_signer_keys": signer_keys,
|
|
139
|
+
"required_signature_count": len(signer_keys),
|
|
140
|
+
"wallet_signer_index": signer_keys.index(wallet_address),
|
|
141
|
+
"sponsored_fee_payer": keys[0] != wallet_address,
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
def verify_provider_swap_transaction(
|
|
146
|
+
message: Any,
|
|
147
|
+
*,
|
|
148
|
+
wallet_address: str,
|
|
149
|
+
input_mint: str,
|
|
150
|
+
output_mint: str,
|
|
151
|
+
) -> dict[str, Any]:
|
|
152
|
+
binding = _assert_basic_wallet_binding(message, wallet_address=wallet_address)
|
|
153
|
+
keys = binding["account_keys"]
|
|
154
|
+
program_ids = _program_ids(message)
|
|
155
|
+
|
|
156
|
+
# Native SOL routes can be represented via wrapped SOL / temporary accounts, and some
|
|
157
|
+
# aggregator paths do not expose canonical mint addresses directly in account keys.
|
|
158
|
+
# We therefore treat mint-key presence as advisory rather than a hard blocker. The
|
|
159
|
+
# stronger swap-specific safety gate is the signed transaction simulation check below.
|
|
160
|
+
input_mint_present = input_mint in keys
|
|
161
|
+
output_mint_present = output_mint in keys
|
|
162
|
+
|
|
163
|
+
unknown_program_ids = _assert_program_allowlist(
|
|
164
|
+
program_ids,
|
|
165
|
+
allowed_programs=SWAP_ALLOWED_PROGRAMS,
|
|
166
|
+
label="Swap",
|
|
167
|
+
reject_unknown=False,
|
|
168
|
+
)
|
|
169
|
+
recognized_jupiter_program_ids = [
|
|
170
|
+
pid for pid in program_ids if pid in RECOGNIZED_JUPITER_SWAP_PROGRAMS
|
|
171
|
+
]
|
|
172
|
+
return {
|
|
173
|
+
"wallet_address": wallet_address,
|
|
174
|
+
"fee_payer": binding["fee_payer"],
|
|
175
|
+
"required_signer_keys": binding["required_signer_keys"],
|
|
176
|
+
"required_signature_count": binding["required_signature_count"],
|
|
177
|
+
"wallet_signer_index": binding["wallet_signer_index"],
|
|
178
|
+
"sponsored_fee_payer": binding["sponsored_fee_payer"],
|
|
179
|
+
"program_ids": program_ids,
|
|
180
|
+
"unknown_program_ids": unknown_program_ids,
|
|
181
|
+
"recognized_jupiter_program_ids": recognized_jupiter_program_ids,
|
|
182
|
+
"has_recognized_jupiter_program": bool(recognized_jupiter_program_ids),
|
|
183
|
+
"non_core_program_ids": [pid for pid in program_ids if pid not in CORE_PROGRAM_IDS],
|
|
184
|
+
"account_key_count": len(keys),
|
|
185
|
+
"instruction_count": len(_compiled_instructions(message)),
|
|
186
|
+
"input_mint": input_mint,
|
|
187
|
+
"output_mint": output_mint,
|
|
188
|
+
"input_mint_present": input_mint_present,
|
|
189
|
+
"output_mint_present": output_mint_present,
|
|
190
|
+
"verified": True,
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
def _coerce_token_balance_amount(balance: dict[str, Any]) -> int | None:
|
|
195
|
+
ui_token_amount = balance.get("uiTokenAmount")
|
|
196
|
+
if not isinstance(ui_token_amount, dict):
|
|
197
|
+
return None
|
|
198
|
+
amount = ui_token_amount.get("amount")
|
|
199
|
+
try:
|
|
200
|
+
return int(str(amount))
|
|
201
|
+
except (TypeError, ValueError):
|
|
202
|
+
return None
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
def _wallet_token_deltas_by_mint(
|
|
206
|
+
simulation_value: dict[str, Any],
|
|
207
|
+
*,
|
|
208
|
+
wallet_address: str,
|
|
209
|
+
) -> dict[str, int]:
|
|
210
|
+
deltas: dict[tuple[int, str], int] = {}
|
|
211
|
+
for field, multiplier in (("preTokenBalances", -1), ("postTokenBalances", 1)):
|
|
212
|
+
balances = simulation_value.get(field)
|
|
213
|
+
if not isinstance(balances, list):
|
|
214
|
+
continue
|
|
215
|
+
for balance in balances:
|
|
216
|
+
if not isinstance(balance, dict):
|
|
217
|
+
continue
|
|
218
|
+
owner = str(balance.get("owner") or "").strip()
|
|
219
|
+
if owner != wallet_address:
|
|
220
|
+
continue
|
|
221
|
+
mint = str(balance.get("mint") or "").strip()
|
|
222
|
+
if not mint:
|
|
223
|
+
continue
|
|
224
|
+
amount = _coerce_token_balance_amount(balance)
|
|
225
|
+
if amount is None:
|
|
226
|
+
continue
|
|
227
|
+
try:
|
|
228
|
+
account_index = int(balance.get("accountIndex"))
|
|
229
|
+
except (TypeError, ValueError):
|
|
230
|
+
account_index = -1
|
|
231
|
+
key = (account_index, mint)
|
|
232
|
+
deltas[key] = deltas.get(key, 0) + (amount * multiplier)
|
|
233
|
+
|
|
234
|
+
by_mint: dict[str, int] = {}
|
|
235
|
+
for (_, mint), delta in deltas.items():
|
|
236
|
+
by_mint[mint] = by_mint.get(mint, 0) + delta
|
|
237
|
+
return by_mint
|
|
238
|
+
|
|
239
|
+
|
|
240
|
+
def _native_lamport_delta(
|
|
241
|
+
simulation_value: dict[str, Any],
|
|
242
|
+
*,
|
|
243
|
+
wallet_account_index: int | None,
|
|
244
|
+
) -> int | None:
|
|
245
|
+
if wallet_account_index is None or wallet_account_index < 0:
|
|
246
|
+
return None
|
|
247
|
+
pre_balances = simulation_value.get("preBalances")
|
|
248
|
+
post_balances = simulation_value.get("postBalances")
|
|
249
|
+
if not isinstance(pre_balances, list) or not isinstance(post_balances, list):
|
|
250
|
+
return None
|
|
251
|
+
if wallet_account_index >= len(pre_balances) or wallet_account_index >= len(post_balances):
|
|
252
|
+
return None
|
|
253
|
+
try:
|
|
254
|
+
return int(post_balances[wallet_account_index]) - int(pre_balances[wallet_account_index])
|
|
255
|
+
except (TypeError, ValueError):
|
|
256
|
+
return None
|
|
257
|
+
|
|
258
|
+
|
|
259
|
+
def verify_provider_swap_simulation_result(
|
|
260
|
+
simulation_value: dict[str, Any],
|
|
261
|
+
*,
|
|
262
|
+
wallet_address: str,
|
|
263
|
+
wallet_account_index: int | None,
|
|
264
|
+
input_mint: str,
|
|
265
|
+
output_mint: str,
|
|
266
|
+
input_amount_raw: int,
|
|
267
|
+
minimum_output_amount_raw: int,
|
|
268
|
+
native_sol_extra_spend_allowance_lamports: int = (
|
|
269
|
+
DEFAULT_NATIVE_SOL_EXTRA_SPEND_ALLOWANCE_LAMPORTS
|
|
270
|
+
),
|
|
271
|
+
) -> dict[str, Any]:
|
|
272
|
+
"""Validate simulated wallet balance effects for a provider-built swap.
|
|
273
|
+
|
|
274
|
+
Static account keys are too brittle for Jupiter routes that use wrapped SOL,
|
|
275
|
+
shared accounts, intermediate hops, or address lookup tables. Simulation is
|
|
276
|
+
closer to the signing risk: what will this transaction do to this wallet?
|
|
277
|
+
The checks below block only when RPC gives us concrete contradictory data.
|
|
278
|
+
Missing balance metadata is returned as advisory warnings to preserve route
|
|
279
|
+
reliability across provider/RPC response variants.
|
|
280
|
+
"""
|
|
281
|
+
if not isinstance(simulation_value, dict):
|
|
282
|
+
raise WalletBackendError(
|
|
283
|
+
"Provider swap transaction simulation returned an unexpected payload.",
|
|
284
|
+
code="transaction_simulation_invalid",
|
|
285
|
+
)
|
|
286
|
+
|
|
287
|
+
if simulation_value.get("err") is not None:
|
|
288
|
+
raise WalletBackendError(
|
|
289
|
+
"Provider swap transaction simulation failed.",
|
|
290
|
+
code="transaction_simulation_failed",
|
|
291
|
+
details={"simulation": simulation_value},
|
|
292
|
+
)
|
|
293
|
+
|
|
294
|
+
token_deltas = _wallet_token_deltas_by_mint(
|
|
295
|
+
simulation_value,
|
|
296
|
+
wallet_address=wallet_address,
|
|
297
|
+
)
|
|
298
|
+
native_delta = _native_lamport_delta(
|
|
299
|
+
simulation_value,
|
|
300
|
+
wallet_account_index=wallet_account_index,
|
|
301
|
+
)
|
|
302
|
+
warnings: list[str] = []
|
|
303
|
+
enforced_checks: list[str] = ["simulation_err_is_none"]
|
|
304
|
+
|
|
305
|
+
if input_mint == NATIVE_SOL_MINT:
|
|
306
|
+
if native_delta is None:
|
|
307
|
+
warnings.append("native_input_delta_unavailable")
|
|
308
|
+
else:
|
|
309
|
+
max_spend = max(input_amount_raw, 0) + max(
|
|
310
|
+
native_sol_extra_spend_allowance_lamports,
|
|
311
|
+
0,
|
|
312
|
+
)
|
|
313
|
+
if -native_delta > max_spend:
|
|
314
|
+
raise WalletBackendError(
|
|
315
|
+
"Provider swap transaction simulation spends more native SOL than approved.",
|
|
316
|
+
code="swap_simulation_overspend",
|
|
317
|
+
details={
|
|
318
|
+
"input_mint": input_mint,
|
|
319
|
+
"approved_input_amount_raw": str(input_amount_raw),
|
|
320
|
+
"native_delta_lamports": str(native_delta),
|
|
321
|
+
"allowed_extra_lamports": str(
|
|
322
|
+
native_sol_extra_spend_allowance_lamports
|
|
323
|
+
),
|
|
324
|
+
},
|
|
325
|
+
)
|
|
326
|
+
enforced_checks.append("native_input_spend_within_approved_amount")
|
|
327
|
+
else:
|
|
328
|
+
input_delta = token_deltas.get(input_mint)
|
|
329
|
+
if input_delta is None:
|
|
330
|
+
warnings.append("token_input_delta_unavailable")
|
|
331
|
+
elif -input_delta > input_amount_raw:
|
|
332
|
+
raise WalletBackendError(
|
|
333
|
+
"Provider swap transaction simulation spends more input token than approved.",
|
|
334
|
+
code="swap_simulation_overspend",
|
|
335
|
+
details={
|
|
336
|
+
"input_mint": input_mint,
|
|
337
|
+
"approved_input_amount_raw": str(input_amount_raw),
|
|
338
|
+
"input_delta_raw": str(input_delta),
|
|
339
|
+
},
|
|
340
|
+
)
|
|
341
|
+
else:
|
|
342
|
+
enforced_checks.append("token_input_spend_within_approved_amount")
|
|
343
|
+
|
|
344
|
+
if output_mint == NATIVE_SOL_MINT:
|
|
345
|
+
if native_delta is None:
|
|
346
|
+
warnings.append("native_output_delta_unavailable")
|
|
347
|
+
else:
|
|
348
|
+
warnings.append("native_output_delta_is_net_of_fees")
|
|
349
|
+
else:
|
|
350
|
+
output_delta = token_deltas.get(output_mint)
|
|
351
|
+
if output_delta is None:
|
|
352
|
+
warnings.append("token_output_delta_unavailable")
|
|
353
|
+
elif output_delta < minimum_output_amount_raw:
|
|
354
|
+
raise WalletBackendError(
|
|
355
|
+
"Provider swap transaction simulation returns less output token than approved.",
|
|
356
|
+
code="swap_simulation_min_output_not_met",
|
|
357
|
+
details={
|
|
358
|
+
"output_mint": output_mint,
|
|
359
|
+
"minimum_output_amount_raw": str(minimum_output_amount_raw),
|
|
360
|
+
"output_delta_raw": str(output_delta),
|
|
361
|
+
},
|
|
362
|
+
)
|
|
363
|
+
else:
|
|
364
|
+
enforced_checks.append("token_output_meets_approved_minimum")
|
|
365
|
+
|
|
366
|
+
return {
|
|
367
|
+
"verified": True,
|
|
368
|
+
"simulation_err": None,
|
|
369
|
+
"wallet_address": wallet_address,
|
|
370
|
+
"wallet_account_index": wallet_account_index,
|
|
371
|
+
"input_mint": input_mint,
|
|
372
|
+
"output_mint": output_mint,
|
|
373
|
+
"input_amount_raw": str(input_amount_raw),
|
|
374
|
+
"minimum_output_amount_raw": str(minimum_output_amount_raw),
|
|
375
|
+
"token_deltas": {mint: str(delta) for mint, delta in sorted(token_deltas.items())},
|
|
376
|
+
"native_delta_lamports": str(native_delta) if native_delta is not None else None,
|
|
377
|
+
"enforced_checks": enforced_checks,
|
|
378
|
+
"warnings": warnings,
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
|
|
382
|
+
def verify_provider_bags_transaction(
|
|
383
|
+
message: Any,
|
|
384
|
+
*,
|
|
385
|
+
wallet_address: str,
|
|
386
|
+
token_mint: str,
|
|
387
|
+
action: str,
|
|
388
|
+
loaded_addresses: list[str] | None = None,
|
|
389
|
+
) -> dict[str, Any]:
|
|
390
|
+
binding = _assert_basic_wallet_binding(
|
|
391
|
+
message,
|
|
392
|
+
wallet_address=wallet_address,
|
|
393
|
+
loaded_addresses=loaded_addresses,
|
|
394
|
+
)
|
|
395
|
+
keys = binding["account_keys"]
|
|
396
|
+
if token_mint not in keys:
|
|
397
|
+
raise WalletBackendError(
|
|
398
|
+
f"{action} transaction does not reference the expected token mint."
|
|
399
|
+
)
|
|
400
|
+
program_ids = _program_ids(message, loaded_addresses)
|
|
401
|
+
unknown_program_ids = _assert_program_allowlist(
|
|
402
|
+
program_ids,
|
|
403
|
+
allowed_programs=CORE_PROGRAM_IDS,
|
|
404
|
+
label=action,
|
|
405
|
+
reject_unknown=False,
|
|
406
|
+
)
|
|
407
|
+
return {
|
|
408
|
+
"wallet_address": wallet_address,
|
|
409
|
+
"fee_payer": binding["fee_payer"],
|
|
410
|
+
"required_signer_keys": binding["required_signer_keys"],
|
|
411
|
+
"required_signature_count": binding["required_signature_count"],
|
|
412
|
+
"wallet_signer_index": binding["wallet_signer_index"],
|
|
413
|
+
"sponsored_fee_payer": binding["sponsored_fee_payer"],
|
|
414
|
+
"program_ids": program_ids,
|
|
415
|
+
"unknown_program_ids": unknown_program_ids,
|
|
416
|
+
"non_core_program_ids": [pid for pid in program_ids if pid not in CORE_PROGRAM_IDS],
|
|
417
|
+
"account_key_count": len(keys),
|
|
418
|
+
"instruction_count": len(_compiled_instructions(message)),
|
|
419
|
+
"token_mint": token_mint,
|
|
420
|
+
"action": action,
|
|
421
|
+
"verified": True,
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
|
|
425
|
+
def verify_provider_lend_transaction(
|
|
426
|
+
message: Any,
|
|
427
|
+
*,
|
|
428
|
+
wallet_address: str,
|
|
429
|
+
asset_mint: str,
|
|
430
|
+
action: str,
|
|
431
|
+
) -> dict[str, Any]:
|
|
432
|
+
binding = _assert_basic_wallet_binding(message, wallet_address=wallet_address)
|
|
433
|
+
keys = binding["account_keys"]
|
|
434
|
+
if asset_mint not in keys:
|
|
435
|
+
raise WalletBackendError(
|
|
436
|
+
f"{action} transaction does not reference the expected asset mint."
|
|
437
|
+
)
|
|
438
|
+
program_ids = _program_ids(message)
|
|
439
|
+
unknown_program_ids = _assert_program_allowlist(
|
|
440
|
+
program_ids,
|
|
441
|
+
allowed_programs=CORE_PROGRAM_IDS,
|
|
442
|
+
label=action,
|
|
443
|
+
reject_unknown=False,
|
|
444
|
+
)
|
|
445
|
+
return {
|
|
446
|
+
"wallet_address": wallet_address,
|
|
447
|
+
"fee_payer": binding["fee_payer"],
|
|
448
|
+
"required_signer_keys": binding["required_signer_keys"],
|
|
449
|
+
"required_signature_count": binding["required_signature_count"],
|
|
450
|
+
"wallet_signer_index": binding["wallet_signer_index"],
|
|
451
|
+
"sponsored_fee_payer": binding["sponsored_fee_payer"],
|
|
452
|
+
"program_ids": program_ids,
|
|
453
|
+
"unknown_program_ids": unknown_program_ids,
|
|
454
|
+
"non_core_program_ids": [pid for pid in program_ids if pid not in CORE_PROGRAM_IDS],
|
|
455
|
+
"account_key_count": len(keys),
|
|
456
|
+
"instruction_count": len(_compiled_instructions(message)),
|
|
457
|
+
"asset_mint": asset_mint,
|
|
458
|
+
"action": action,
|
|
459
|
+
"verified": True,
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
|
|
463
|
+
def verify_provider_kamino_lend_transaction(
|
|
464
|
+
message: Any,
|
|
465
|
+
*,
|
|
466
|
+
wallet_address: str,
|
|
467
|
+
market_address: str,
|
|
468
|
+
reserve_address: str,
|
|
469
|
+
action: str,
|
|
470
|
+
loaded_addresses: list[str] | None = None,
|
|
471
|
+
) -> dict[str, Any]:
|
|
472
|
+
binding = _assert_basic_wallet_binding(
|
|
473
|
+
message,
|
|
474
|
+
wallet_address=wallet_address,
|
|
475
|
+
loaded_addresses=loaded_addresses,
|
|
476
|
+
)
|
|
477
|
+
keys = binding["account_keys"]
|
|
478
|
+
if market_address not in keys:
|
|
479
|
+
raise WalletBackendError(
|
|
480
|
+
f"{action} transaction does not reference the expected Kamino market."
|
|
481
|
+
)
|
|
482
|
+
if reserve_address not in keys:
|
|
483
|
+
raise WalletBackendError(
|
|
484
|
+
f"{action} transaction does not reference the expected Kamino reserve."
|
|
485
|
+
)
|
|
486
|
+
program_ids = _program_ids(message, loaded_addresses)
|
|
487
|
+
unknown_program_ids = _assert_program_allowlist(
|
|
488
|
+
program_ids,
|
|
489
|
+
allowed_programs=KAMINO_ALLOWED_PROGRAMS,
|
|
490
|
+
label=action,
|
|
491
|
+
reject_unknown=False,
|
|
492
|
+
)
|
|
493
|
+
recognized_kamino_program_ids = [
|
|
494
|
+
pid for pid in program_ids if pid == KAMINO_LEND_PROGRAM_ID
|
|
495
|
+
]
|
|
496
|
+
if not recognized_kamino_program_ids:
|
|
497
|
+
raise WalletBackendError(
|
|
498
|
+
f"{action} transaction does not include the expected Kamino lending program."
|
|
499
|
+
)
|
|
500
|
+
return {
|
|
501
|
+
"wallet_address": wallet_address,
|
|
502
|
+
"fee_payer": binding["fee_payer"],
|
|
503
|
+
"required_signer_keys": binding["required_signer_keys"],
|
|
504
|
+
"required_signature_count": binding["required_signature_count"],
|
|
505
|
+
"wallet_signer_index": binding["wallet_signer_index"],
|
|
506
|
+
"sponsored_fee_payer": binding["sponsored_fee_payer"],
|
|
507
|
+
"program_ids": program_ids,
|
|
508
|
+
"unknown_program_ids": unknown_program_ids,
|
|
509
|
+
"recognized_kamino_program_ids": recognized_kamino_program_ids,
|
|
510
|
+
"has_recognized_kamino_program": True,
|
|
511
|
+
"non_core_program_ids": [pid for pid in program_ids if pid not in CORE_PROGRAM_IDS],
|
|
512
|
+
"account_key_count": len(keys),
|
|
513
|
+
"instruction_count": len(_compiled_instructions(message)),
|
|
514
|
+
"market_address": market_address,
|
|
515
|
+
"reserve_address": reserve_address,
|
|
516
|
+
"action": action,
|
|
517
|
+
"verified": True,
|
|
518
|
+
}
|