@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.
Files changed (96) hide show
  1. package/.openclaw/AGENTS.md +98 -0
  2. package/.openclaw/extensions/agent-wallet/README.md +127 -0
  3. package/.openclaw/extensions/agent-wallet/index.ts +1520 -0
  4. package/.openclaw/extensions/agent-wallet/openclaw.plugin.json +184 -0
  5. package/.openclaw/extensions/agent-wallet/package.json +11 -0
  6. package/.openclaw/extensions/agent-wallet/skills/wallet-operator/SKILL.md +20 -0
  7. package/CHANGELOG.md +42 -0
  8. package/LICENSE +104 -0
  9. package/README.md +332 -0
  10. package/RELEASING.md +204 -0
  11. package/agent-wallet/.env.example +62 -0
  12. package/agent-wallet/AGENTS.md +129 -0
  13. package/agent-wallet/README.md +527 -0
  14. package/agent-wallet/agent_wallet/__init__.py +11 -0
  15. package/agent-wallet/agent_wallet/approval.py +161 -0
  16. package/agent-wallet/agent_wallet/bootstrap.py +178 -0
  17. package/agent-wallet/agent_wallet/btc_user_wallets.py +217 -0
  18. package/agent-wallet/agent_wallet/config.py +382 -0
  19. package/agent-wallet/agent_wallet/encrypted_storage.py +161 -0
  20. package/agent-wallet/agent_wallet/evm_user_wallets.py +370 -0
  21. package/agent-wallet/agent_wallet/exceptions.py +9 -0
  22. package/agent-wallet/agent_wallet/file_ops.py +34 -0
  23. package/agent-wallet/agent_wallet/http_client.py +25 -0
  24. package/agent-wallet/agent_wallet/models.py +66 -0
  25. package/agent-wallet/agent_wallet/nonce_registry.py +59 -0
  26. package/agent-wallet/agent_wallet/openclaw_adapter.py +5128 -0
  27. package/agent-wallet/agent_wallet/openclaw_cli.py +626 -0
  28. package/agent-wallet/agent_wallet/openclaw_runtime.py +272 -0
  29. package/agent-wallet/agent_wallet/plugin_bundle.py +42 -0
  30. package/agent-wallet/agent_wallet/providers/__init__.py +1 -0
  31. package/agent-wallet/agent_wallet/providers/bags.py +259 -0
  32. package/agent-wallet/agent_wallet/providers/evm_portfolio.py +470 -0
  33. package/agent-wallet/agent_wallet/providers/jupiter.py +567 -0
  34. package/agent-wallet/agent_wallet/providers/kamino.py +215 -0
  35. package/agent-wallet/agent_wallet/providers/lifi.py +277 -0
  36. package/agent-wallet/agent_wallet/providers/solana_rpc.py +470 -0
  37. package/agent-wallet/agent_wallet/providers/wdk_btc_local.py +114 -0
  38. package/agent-wallet/agent_wallet/providers/wdk_evm_local.py +205 -0
  39. package/agent-wallet/agent_wallet/sealed_keys.py +61 -0
  40. package/agent-wallet/agent_wallet/solana_stake.py +103 -0
  41. package/agent-wallet/agent_wallet/solana_tx.py +93 -0
  42. package/agent-wallet/agent_wallet/spending_limits.py +101 -0
  43. package/agent-wallet/agent_wallet/transaction_policy.py +518 -0
  44. package/agent-wallet/agent_wallet/user_wallets.py +355 -0
  45. package/agent-wallet/agent_wallet/validation.py +31 -0
  46. package/agent-wallet/agent_wallet/wallet_layer/__init__.py +1 -0
  47. package/agent-wallet/agent_wallet/wallet_layer/base.py +808 -0
  48. package/agent-wallet/agent_wallet/wallet_layer/base58.py +44 -0
  49. package/agent-wallet/agent_wallet/wallet_layer/factory.py +102 -0
  50. package/agent-wallet/agent_wallet/wallet_layer/solana.py +4252 -0
  51. package/agent-wallet/agent_wallet/wallet_layer/wdk_btc.py +272 -0
  52. package/agent-wallet/agent_wallet/wallet_layer/wdk_evm.py +1628 -0
  53. package/agent-wallet/examples/bootstrap_wallet.py +21 -0
  54. package/agent-wallet/examples/openclaw_runtime_onboarding.py +28 -0
  55. package/agent-wallet/examples/openclaw_user_wallet_example.py +31 -0
  56. package/agent-wallet/examples/openclaw_wallet_adapter_example.py +33 -0
  57. package/agent-wallet/openclaw.plugin.json +138 -0
  58. package/agent-wallet/pyproject.toml +31 -0
  59. package/agent-wallet/scripts/bootstrap_openclaw_btc.py +278 -0
  60. package/agent-wallet/scripts/build_release_bundle.py +188 -0
  61. package/agent-wallet/scripts/finalize_openclaw_local_wallet_config.py +121 -0
  62. package/agent-wallet/scripts/install_agent_wallet.py +505 -0
  63. package/agent-wallet/scripts/install_openclaw_local_config.py +226 -0
  64. package/agent-wallet/scripts/install_openclaw_sealed_keys.py +105 -0
  65. package/agent-wallet/scripts/manage_openclaw_btc_wallet.py +244 -0
  66. package/agent-wallet/scripts/reveal_btc_seed.sh +130 -0
  67. package/agent-wallet/scripts/security_utils.py +37 -0
  68. package/agent-wallet/scripts/setup_btc_wallet.sh +146 -0
  69. package/agent-wallet/scripts/switch_openclaw_wallet_network.py +106 -0
  70. package/agent-wallet/skills/wallet-operator/SKILL.md +128 -0
  71. package/bin/openclaw-agent-wallet.mjs +487 -0
  72. package/install-from-github.sh +134 -0
  73. package/package.json +61 -0
  74. package/setup.sh +40 -0
  75. package/wdk-btc-wallet/README.md +325 -0
  76. package/wdk-btc-wallet/bootstrap.sh +22 -0
  77. package/wdk-btc-wallet/package-lock.json +1839 -0
  78. package/wdk-btc-wallet/package.json +18 -0
  79. package/wdk-btc-wallet/run-local.sh +21 -0
  80. package/wdk-btc-wallet/src/config.js +160 -0
  81. package/wdk-btc-wallet/src/json.js +35 -0
  82. package/wdk-btc-wallet/src/local_vault.js +432 -0
  83. package/wdk-btc-wallet/src/network_state.js +84 -0
  84. package/wdk-btc-wallet/src/server.js +257 -0
  85. package/wdk-btc-wallet/src/wdk_btc_wallet.js +332 -0
  86. package/wdk-evm-wallet/README.md +183 -0
  87. package/wdk-evm-wallet/bootstrap.sh +8 -0
  88. package/wdk-evm-wallet/package-lock.json +2340 -0
  89. package/wdk-evm-wallet/package.json +23 -0
  90. package/wdk-evm-wallet/run-local.sh +12 -0
  91. package/wdk-evm-wallet/src/config.js +274 -0
  92. package/wdk-evm-wallet/src/json.js +35 -0
  93. package/wdk-evm-wallet/src/local_vault.js +430 -0
  94. package/wdk-evm-wallet/src/network_state.js +92 -0
  95. package/wdk-evm-wallet/src/server.js +575 -0
  96. 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
+ }