@agentlayer.tech/wallet 0.1.26 → 0.1.28

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.
@@ -3765,19 +3765,7 @@ class SolanaWalletBackend(AgentWalletBackend):
3765
3765
  keypair = Keypair.from_bytes(self.signer.export_keypair_bytes())
3766
3766
  try:
3767
3767
  unsigned_transaction = VersionedTransaction.from_bytes(raw_transaction)
3768
- signature = keypair.sign_message(to_bytes_versioned(unsigned_transaction.message))
3769
- signatures = list(unsigned_transaction.signatures)
3770
- if wallet_signer_index >= len(signatures):
3771
- raise WalletBackendError(
3772
- "Provider transaction signer layout is incompatible with local signing."
3773
- )
3774
- signatures[wallet_signer_index] = signature
3775
- signed_transaction = VersionedTransaction.populate(
3776
- unsigned_transaction.message,
3777
- signatures,
3778
- )
3779
- return encode_transaction_base64(bytes(signed_transaction))
3780
- except Exception:
3768
+ except (TypeError, ValueError):
3781
3769
  unsigned_transaction = Transaction.from_bytes(raw_transaction)
3782
3770
  signatures = list(unsigned_transaction.signatures)
3783
3771
  if wallet_signer_index >= len(signatures):
@@ -3789,6 +3777,18 @@ class SolanaWalletBackend(AgentWalletBackend):
3789
3777
  unsigned_transaction.message.recent_blockhash,
3790
3778
  )
3791
3779
  return encode_transaction_base64(bytes(unsigned_transaction))
3780
+ signature = keypair.sign_message(to_bytes_versioned(unsigned_transaction.message))
3781
+ signatures = list(unsigned_transaction.signatures)
3782
+ if wallet_signer_index >= len(signatures):
3783
+ raise WalletBackendError(
3784
+ "Provider transaction signer layout is incompatible with local signing."
3785
+ )
3786
+ signatures[wallet_signer_index] = signature
3787
+ signed_transaction = VersionedTransaction.populate(
3788
+ unsigned_transaction.message,
3789
+ signatures,
3790
+ )
3791
+ return encode_transaction_base64(bytes(signed_transaction))
3792
3792
 
3793
3793
  async def _prepare_jupiter_lend_transaction(
3794
3794
  self,
@@ -3847,9 +3847,11 @@ class SolanaWalletBackend(AgentWalletBackend):
3847
3847
  raise WalletBackendError(
3848
3848
  "This wallet backend is in sign-only mode. Disable sign_only to broadcast transactions."
3849
3849
  )
3850
+ kamino_verified = bool((prepared.get("kamino_safety") or {}).get("verified"))
3850
3851
  submitted = await solana_rpc.send_transaction(
3851
3852
  transaction_base64=str(prepared["transaction_base64"]),
3852
3853
  rpc_url=self.rpc_urls,
3854
+ skip_preflight=source == "kamino" and kamino_verified,
3853
3855
  )
3854
3856
  signature = submitted.get("signature")
3855
3857
  status = None
@@ -3858,6 +3860,8 @@ class SolanaWalletBackend(AgentWalletBackend):
3858
3860
  status = await solana_rpc.wait_for_confirmation(
3859
3861
  signature=signature,
3860
3862
  rpc_url=self.rpc_urls,
3863
+ timeout_seconds=60.0 if source == "kamino" else 20.0,
3864
+ poll_interval_seconds=2.0 if source == "kamino" else 1.0,
3861
3865
  )
3862
3866
  confirmed = status is not None
3863
3867
  return {
@@ -3875,6 +3879,8 @@ class SolanaWalletBackend(AgentWalletBackend):
3875
3879
  "slot": status.get("slot") if status else None,
3876
3880
  "sign_only": self.sign_only,
3877
3881
  "source": source,
3882
+ "simulation": prepared.get("simulation"),
3883
+ "kamino_safety": prepared.get("kamino_safety"),
3878
3884
  }
3879
3885
 
3880
3886
  async def _execute_prepared_jupiter_lend_transaction(self, prepared: dict[str, Any]) -> dict[str, Any]:
@@ -3925,6 +3931,37 @@ class SolanaWalletBackend(AgentWalletBackend):
3925
3931
  matches.append(item)
3926
3932
  return matches
3927
3933
 
3934
+ def _resolve_kamino_obligation_selection(
3935
+ self,
3936
+ *,
3937
+ obligations: list[Any],
3938
+ obligation_address: str | None,
3939
+ action: str,
3940
+ ) -> tuple[list[dict[str, Any]], dict[str, Any] | None]:
3941
+ candidates = [item for item in obligations if isinstance(item, dict)]
3942
+ requested = str(obligation_address or "").strip()
3943
+ if requested:
3944
+ requested = validate_solana_address(requested)
3945
+ for item in candidates:
3946
+ if (
3947
+ _kamino_entry_address(
3948
+ item,
3949
+ "obligationAddress",
3950
+ "obligation",
3951
+ "address",
3952
+ "pubkey",
3953
+ "loanId",
3954
+ )
3955
+ == requested
3956
+ ):
3957
+ return candidates, item
3958
+ raise WalletBackendError(
3959
+ f"Requested obligation_address is not available for Kamino {action} in the selected market."
3960
+ )
3961
+ if len(candidates) == 1:
3962
+ return candidates, candidates[0]
3963
+ return candidates, None
3964
+
3928
3965
  async def _prepare_kamino_lend_transaction(
3929
3966
  self,
3930
3967
  *,
@@ -3933,6 +3970,7 @@ class SolanaWalletBackend(AgentWalletBackend):
3933
3970
  market: str,
3934
3971
  reserve: str,
3935
3972
  amount_ui: str,
3973
+ obligation_address: str | None = None,
3936
3974
  ) -> dict[str, Any]:
3937
3975
  if not self.signer:
3938
3976
  raise WalletBackendError("Solana signer is not configured.")
@@ -3953,12 +3991,49 @@ class SolanaWalletBackend(AgentWalletBackend):
3953
3991
  market_address=market,
3954
3992
  reserve_address=reserve,
3955
3993
  action=f"Kamino {action}",
3994
+ obligation_address=obligation_address,
3956
3995
  loaded_addresses=loaded_addresses,
3957
3996
  )
3958
3997
  signed_transaction_base64 = await self._sign_versioned_provider_transaction(
3959
3998
  transaction_base64=transaction_base64,
3960
3999
  wallet_signer_index=int(verification.get("wallet_signer_index") or 0),
3961
4000
  )
4001
+ simulation_value: dict[str, Any] | None = None
4002
+ kamino_safety: dict[str, Any]
4003
+ try:
4004
+ simulation = await solana_rpc.simulate_transaction(
4005
+ transaction_base64=signed_transaction_base64,
4006
+ rpc_url=self.rpc_urls,
4007
+ commitment=self.commitment,
4008
+ )
4009
+ simulation_value = (
4010
+ simulation.get("value") if isinstance(simulation.get("value"), dict) else {}
4011
+ )
4012
+ if isinstance(simulation_value, dict) and simulation_value.get("err") is not None:
4013
+ raise WalletBackendError(
4014
+ f"Kamino {action} transaction simulation failed.",
4015
+ code="kamino_simulation_failed",
4016
+ details={
4017
+ "simulation": simulation_value,
4018
+ "action": action,
4019
+ "market": market,
4020
+ "reserve": reserve,
4021
+ },
4022
+ )
4023
+ kamino_safety = {
4024
+ "verified": True,
4025
+ "simulation_unavailable": False,
4026
+ }
4027
+ except ProviderError as exc:
4028
+ kamino_safety = {
4029
+ "verified": False,
4030
+ "simulation_unavailable": True,
4031
+ "warning": (
4032
+ "Kamino simulation could not be completed via the configured Solana RPC. "
4033
+ "Proceeding with structural provider verification only."
4034
+ ),
4035
+ "error": str(exc),
4036
+ }
3962
4037
  return {
3963
4038
  "chain": "solana",
3964
4039
  "network": self.network,
@@ -3967,6 +4042,7 @@ class SolanaWalletBackend(AgentWalletBackend):
3967
4042
  "owner": owner,
3968
4043
  "market": market,
3969
4044
  "reserve": reserve,
4045
+ "obligation_address": obligation_address,
3970
4046
  "amount_ui": amount_ui,
3971
4047
  "transaction_base64": signed_transaction_base64,
3972
4048
  "transaction_encoding": "base64",
@@ -3975,15 +4051,30 @@ class SolanaWalletBackend(AgentWalletBackend):
3975
4051
  "broadcasted": False,
3976
4052
  "confirmed": False,
3977
4053
  "verification": verification,
4054
+ "simulation": simulation_value,
4055
+ "kamino_safety": kamino_safety,
3978
4056
  "sign_only": self.sign_only,
3979
4057
  "source": "kamino",
3980
4058
  }
3981
4059
 
4060
+ def _kamino_preview_from_approved(
4061
+ self,
4062
+ approved_preview: dict[str, Any] | None,
4063
+ *,
4064
+ asset_type: str,
4065
+ ) -> dict[str, Any] | None:
4066
+ if not isinstance(approved_preview, dict):
4067
+ return None
4068
+ if str(approved_preview.get("asset_type") or "").strip() != asset_type:
4069
+ return None
4070
+ return dict(approved_preview)
4071
+
3982
4072
  async def preview_kamino_lend_deposit(
3983
4073
  self,
3984
4074
  market: str,
3985
4075
  reserve: str,
3986
4076
  amount_ui: str,
4077
+ obligation_address: str | None = None,
3987
4078
  ) -> dict[str, Any]:
3988
4079
  self._require_mainnet_kamino("Kamino lending")
3989
4080
  owner = await self.get_address()
@@ -4021,8 +4112,13 @@ class SolanaWalletBackend(AgentWalletBackend):
4021
4112
  market: str,
4022
4113
  reserve: str,
4023
4114
  amount_ui: str,
4115
+ obligation_address: str | None = None,
4116
+ approved_preview: dict[str, Any] | None = None,
4024
4117
  ) -> dict[str, Any]:
4025
- preview = await self.preview_kamino_lend_deposit(
4118
+ preview = self._kamino_preview_from_approved(
4119
+ approved_preview,
4120
+ asset_type="kamino-lend-deposit",
4121
+ ) or await self.preview_kamino_lend_deposit(
4026
4122
  market=market,
4027
4123
  reserve=reserve,
4028
4124
  amount_ui=amount_ui,
@@ -4049,11 +4145,14 @@ class SolanaWalletBackend(AgentWalletBackend):
4049
4145
  market: str,
4050
4146
  reserve: str,
4051
4147
  amount_ui: str,
4148
+ obligation_address: str | None = None,
4149
+ approved_preview: dict[str, Any] | None = None,
4052
4150
  ) -> dict[str, Any]:
4053
4151
  prepared = await self.prepare_kamino_lend_deposit(
4054
4152
  market=market,
4055
4153
  reserve=reserve,
4056
4154
  amount_ui=amount_ui,
4155
+ approved_preview=approved_preview,
4057
4156
  )
4058
4157
  result = await self._execute_prepared_provider_transaction(prepared, source="kamino")
4059
4158
  result["build_response"] = prepared.get("build_response")
@@ -4064,6 +4163,7 @@ class SolanaWalletBackend(AgentWalletBackend):
4064
4163
  market: str,
4065
4164
  reserve: str,
4066
4165
  amount_ui: str,
4166
+ obligation_address: str | None = None,
4067
4167
  ) -> dict[str, Any]:
4068
4168
  self._require_mainnet_kamino("Kamino lending")
4069
4169
  owner = await self.get_address()
@@ -4088,6 +4188,19 @@ class SolanaWalletBackend(AgentWalletBackend):
4088
4188
  )
4089
4189
  if not obligation_matches:
4090
4190
  raise WalletBackendError("No Kamino obligation found for the requested reserve.")
4191
+ obligation_options, selected_obligation = self._resolve_kamino_obligation_selection(
4192
+ obligations=obligation_matches,
4193
+ obligation_address=obligation_address,
4194
+ action="withdraw",
4195
+ )
4196
+ selected_obligation_address = _kamino_entry_address(
4197
+ selected_obligation,
4198
+ "obligationAddress",
4199
+ "obligation",
4200
+ "address",
4201
+ "pubkey",
4202
+ "loanId",
4203
+ )
4091
4204
  return {
4092
4205
  "chain": "solana",
4093
4206
  "network": self.network,
@@ -4098,7 +4211,14 @@ class SolanaWalletBackend(AgentWalletBackend):
4098
4211
  "reserve": reserve,
4099
4212
  "amount_ui": amount_ui,
4100
4213
  "reserve_info": reserve_entry,
4101
- "obligations": obligation_matches,
4214
+ "obligations": obligation_options,
4215
+ "obligation_options": [
4216
+ _kamino_entry_address(item, "obligationAddress", "obligation", "address", "pubkey", "loanId")
4217
+ for item in obligation_options
4218
+ if _kamino_entry_address(item, "obligationAddress", "obligation", "address", "pubkey", "loanId")
4219
+ ],
4220
+ "obligation_address": selected_obligation_address or None,
4221
+ "requires_obligation_address": selected_obligation is None and len(obligation_options) > 1,
4102
4222
  "sign_only": self.sign_only,
4103
4223
  "can_send": self.get_capabilities().can_send_transaction,
4104
4224
  "source": "kamino",
@@ -4109,12 +4229,23 @@ class SolanaWalletBackend(AgentWalletBackend):
4109
4229
  market: str,
4110
4230
  reserve: str,
4111
4231
  amount_ui: str,
4232
+ obligation_address: str | None = None,
4233
+ approved_preview: dict[str, Any] | None = None,
4112
4234
  ) -> dict[str, Any]:
4113
- preview = await self.preview_kamino_lend_withdraw(
4235
+ preview = self._kamino_preview_from_approved(
4236
+ approved_preview,
4237
+ asset_type="kamino-lend-withdraw",
4238
+ ) or await self.preview_kamino_lend_withdraw(
4114
4239
  market=market,
4115
4240
  reserve=reserve,
4116
4241
  amount_ui=amount_ui,
4242
+ obligation_address=obligation_address,
4117
4243
  )
4244
+ selected_obligation_address = str(preview.get("obligation_address") or "").strip()
4245
+ if bool(preview.get("requires_obligation_address")) and not selected_obligation_address:
4246
+ raise WalletBackendError(
4247
+ "Kamino withdraw requires obligation_address when multiple obligations match the selected market/reserve."
4248
+ )
4118
4249
  owner = str(preview["owner"])
4119
4250
  build = await kamino.build_lend_withdraw_transaction(
4120
4251
  wallet=owner,
@@ -4128,6 +4259,7 @@ class SolanaWalletBackend(AgentWalletBackend):
4128
4259
  market=str(preview["market"]),
4129
4260
  reserve=str(preview["reserve"]),
4130
4261
  amount_ui=str(preview["amount_ui"]),
4262
+ obligation_address=selected_obligation_address or None,
4131
4263
  )
4132
4264
  prepared["build_response"] = build
4133
4265
  return prepared
@@ -4137,11 +4269,15 @@ class SolanaWalletBackend(AgentWalletBackend):
4137
4269
  market: str,
4138
4270
  reserve: str,
4139
4271
  amount_ui: str,
4272
+ obligation_address: str | None = None,
4273
+ approved_preview: dict[str, Any] | None = None,
4140
4274
  ) -> dict[str, Any]:
4141
4275
  prepared = await self.prepare_kamino_lend_withdraw(
4142
4276
  market=market,
4143
4277
  reserve=reserve,
4144
4278
  amount_ui=amount_ui,
4279
+ obligation_address=obligation_address,
4280
+ approved_preview=approved_preview,
4145
4281
  )
4146
4282
  result = await self._execute_prepared_provider_transaction(prepared, source="kamino")
4147
4283
  result["build_response"] = prepared.get("build_response")
@@ -4152,6 +4288,7 @@ class SolanaWalletBackend(AgentWalletBackend):
4152
4288
  market: str,
4153
4289
  reserve: str,
4154
4290
  amount_ui: str,
4291
+ obligation_address: str | None = None,
4155
4292
  ) -> dict[str, Any]:
4156
4293
  self._require_mainnet_kamino("Kamino lending")
4157
4294
  owner = await self.get_address()
@@ -4172,6 +4309,19 @@ class SolanaWalletBackend(AgentWalletBackend):
4172
4309
  obligations = await self.get_kamino_lend_user_obligations(market=market, user=owner)
4173
4310
  if int(obligations["obligation_count"]) <= 0:
4174
4311
  raise WalletBackendError("Kamino borrow requires an existing obligation in the selected market.")
4312
+ obligation_options, selected_obligation = self._resolve_kamino_obligation_selection(
4313
+ obligations=list(obligations["obligations"]),
4314
+ obligation_address=obligation_address,
4315
+ action="borrow",
4316
+ )
4317
+ selected_obligation_address = _kamino_entry_address(
4318
+ selected_obligation,
4319
+ "obligationAddress",
4320
+ "obligation",
4321
+ "address",
4322
+ "pubkey",
4323
+ "loanId",
4324
+ )
4175
4325
  return {
4176
4326
  "chain": "solana",
4177
4327
  "network": self.network,
@@ -4182,7 +4332,14 @@ class SolanaWalletBackend(AgentWalletBackend):
4182
4332
  "reserve": reserve,
4183
4333
  "amount_ui": amount_ui,
4184
4334
  "reserve_info": reserve_entry,
4185
- "obligations": obligations["obligations"],
4335
+ "obligations": obligation_options,
4336
+ "obligation_options": [
4337
+ _kamino_entry_address(item, "obligationAddress", "obligation", "address", "pubkey", "loanId")
4338
+ for item in obligation_options
4339
+ if _kamino_entry_address(item, "obligationAddress", "obligation", "address", "pubkey", "loanId")
4340
+ ],
4341
+ "obligation_address": selected_obligation_address or None,
4342
+ "requires_obligation_address": selected_obligation is None and len(obligation_options) > 1,
4186
4343
  "sign_only": self.sign_only,
4187
4344
  "can_send": self.get_capabilities().can_send_transaction,
4188
4345
  "source": "kamino",
@@ -4193,12 +4350,23 @@ class SolanaWalletBackend(AgentWalletBackend):
4193
4350
  market: str,
4194
4351
  reserve: str,
4195
4352
  amount_ui: str,
4353
+ obligation_address: str | None = None,
4354
+ approved_preview: dict[str, Any] | None = None,
4196
4355
  ) -> dict[str, Any]:
4197
- preview = await self.preview_kamino_lend_borrow(
4356
+ preview = self._kamino_preview_from_approved(
4357
+ approved_preview,
4358
+ asset_type="kamino-lend-borrow",
4359
+ ) or await self.preview_kamino_lend_borrow(
4198
4360
  market=market,
4199
4361
  reserve=reserve,
4200
4362
  amount_ui=amount_ui,
4363
+ obligation_address=obligation_address,
4201
4364
  )
4365
+ selected_obligation_address = str(preview.get("obligation_address") or "").strip()
4366
+ if bool(preview.get("requires_obligation_address")) and not selected_obligation_address:
4367
+ raise WalletBackendError(
4368
+ "Kamino borrow requires obligation_address when multiple obligations exist in the selected market."
4369
+ )
4202
4370
  owner = str(preview["owner"])
4203
4371
  build = await kamino.build_lend_borrow_transaction(
4204
4372
  wallet=owner,
@@ -4212,6 +4380,7 @@ class SolanaWalletBackend(AgentWalletBackend):
4212
4380
  market=str(preview["market"]),
4213
4381
  reserve=str(preview["reserve"]),
4214
4382
  amount_ui=str(preview["amount_ui"]),
4383
+ obligation_address=selected_obligation_address or None,
4215
4384
  )
4216
4385
  prepared["build_response"] = build
4217
4386
  return prepared
@@ -4221,11 +4390,15 @@ class SolanaWalletBackend(AgentWalletBackend):
4221
4390
  market: str,
4222
4391
  reserve: str,
4223
4392
  amount_ui: str,
4393
+ obligation_address: str | None = None,
4394
+ approved_preview: dict[str, Any] | None = None,
4224
4395
  ) -> dict[str, Any]:
4225
4396
  prepared = await self.prepare_kamino_lend_borrow(
4226
4397
  market=market,
4227
4398
  reserve=reserve,
4228
4399
  amount_ui=amount_ui,
4400
+ obligation_address=obligation_address,
4401
+ approved_preview=approved_preview,
4229
4402
  )
4230
4403
  result = await self._execute_prepared_provider_transaction(prepared, source="kamino")
4231
4404
  result["build_response"] = prepared.get("build_response")
@@ -4236,6 +4409,7 @@ class SolanaWalletBackend(AgentWalletBackend):
4236
4409
  market: str,
4237
4410
  reserve: str,
4238
4411
  amount_ui: str,
4412
+ obligation_address: str | None = None,
4239
4413
  ) -> dict[str, Any]:
4240
4414
  self._require_mainnet_kamino("Kamino lending")
4241
4415
  owner = await self.get_address()
@@ -4260,6 +4434,19 @@ class SolanaWalletBackend(AgentWalletBackend):
4260
4434
  )
4261
4435
  if not obligation_matches:
4262
4436
  raise WalletBackendError("No Kamino debt position found for the requested reserve.")
4437
+ obligation_options, selected_obligation = self._resolve_kamino_obligation_selection(
4438
+ obligations=obligation_matches,
4439
+ obligation_address=obligation_address,
4440
+ action="repay",
4441
+ )
4442
+ selected_obligation_address = _kamino_entry_address(
4443
+ selected_obligation,
4444
+ "obligationAddress",
4445
+ "obligation",
4446
+ "address",
4447
+ "pubkey",
4448
+ "loanId",
4449
+ )
4263
4450
  return {
4264
4451
  "chain": "solana",
4265
4452
  "network": self.network,
@@ -4270,7 +4457,14 @@ class SolanaWalletBackend(AgentWalletBackend):
4270
4457
  "reserve": reserve,
4271
4458
  "amount_ui": amount_ui,
4272
4459
  "reserve_info": reserve_entry,
4273
- "obligations": obligation_matches,
4460
+ "obligations": obligation_options,
4461
+ "obligation_options": [
4462
+ _kamino_entry_address(item, "obligationAddress", "obligation", "address", "pubkey", "loanId")
4463
+ for item in obligation_options
4464
+ if _kamino_entry_address(item, "obligationAddress", "obligation", "address", "pubkey", "loanId")
4465
+ ],
4466
+ "obligation_address": selected_obligation_address or None,
4467
+ "requires_obligation_address": selected_obligation is None and len(obligation_options) > 1,
4274
4468
  "sign_only": self.sign_only,
4275
4469
  "can_send": self.get_capabilities().can_send_transaction,
4276
4470
  "source": "kamino",
@@ -4281,12 +4475,23 @@ class SolanaWalletBackend(AgentWalletBackend):
4281
4475
  market: str,
4282
4476
  reserve: str,
4283
4477
  amount_ui: str,
4478
+ obligation_address: str | None = None,
4479
+ approved_preview: dict[str, Any] | None = None,
4284
4480
  ) -> dict[str, Any]:
4285
- preview = await self.preview_kamino_lend_repay(
4481
+ preview = self._kamino_preview_from_approved(
4482
+ approved_preview,
4483
+ asset_type="kamino-lend-repay",
4484
+ ) or await self.preview_kamino_lend_repay(
4286
4485
  market=market,
4287
4486
  reserve=reserve,
4288
4487
  amount_ui=amount_ui,
4488
+ obligation_address=obligation_address,
4289
4489
  )
4490
+ selected_obligation_address = str(preview.get("obligation_address") or "").strip()
4491
+ if bool(preview.get("requires_obligation_address")) and not selected_obligation_address:
4492
+ raise WalletBackendError(
4493
+ "Kamino repay requires obligation_address when multiple debt obligations match the selected market/reserve."
4494
+ )
4290
4495
  owner = str(preview["owner"])
4291
4496
  build = await kamino.build_lend_repay_transaction(
4292
4497
  wallet=owner,
@@ -4300,6 +4505,7 @@ class SolanaWalletBackend(AgentWalletBackend):
4300
4505
  market=str(preview["market"]),
4301
4506
  reserve=str(preview["reserve"]),
4302
4507
  amount_ui=str(preview["amount_ui"]),
4508
+ obligation_address=selected_obligation_address or None,
4303
4509
  )
4304
4510
  prepared["build_response"] = build
4305
4511
  return prepared
@@ -4309,11 +4515,15 @@ class SolanaWalletBackend(AgentWalletBackend):
4309
4515
  market: str,
4310
4516
  reserve: str,
4311
4517
  amount_ui: str,
4518
+ obligation_address: str | None = None,
4519
+ approved_preview: dict[str, Any] | None = None,
4312
4520
  ) -> dict[str, Any]:
4313
4521
  prepared = await self.prepare_kamino_lend_repay(
4314
4522
  market=market,
4315
4523
  reserve=reserve,
4316
4524
  amount_ui=amount_ui,
4525
+ obligation_address=obligation_address,
4526
+ approved_preview=approved_preview,
4317
4527
  )
4318
4528
  result = await self._execute_prepared_provider_transaction(prepared, source="kamino")
4319
4529
  result["build_response"] = prepared.get("build_response")
@@ -33,6 +33,16 @@ def _lifi_chain_id_for_evm_network(network: str) -> str:
33
33
  return "1"
34
34
 
35
35
 
36
+ def _normalize_x402_typed_data_value(value: Any) -> Any:
37
+ if isinstance(value, (bytes, bytearray)):
38
+ return "0x" + bytes(value).hex()
39
+ if isinstance(value, list):
40
+ return [_normalize_x402_typed_data_value(item) for item in value]
41
+ if isinstance(value, dict):
42
+ return {str(key): _normalize_x402_typed_data_value(item) for key, item in value.items()}
43
+ return value
44
+
45
+
36
46
  def _extract_fee_wei(payload: dict[str, Any]) -> str | None:
37
47
  for key in ("fee", "maxFee", "totalFee", "gasCost", "cost"):
38
48
  value = payload.get(key)
@@ -457,7 +467,7 @@ class WdkEvmLocalWalletBackend(AgentWalletBackend):
457
467
  "domain": domain,
458
468
  "types": types,
459
469
  "primaryType": primary_type,
460
- "message": message,
470
+ "message": _normalize_x402_typed_data_value(message),
461
471
  },
462
472
  )
463
473
  signature = str(data.get("signature") or "").strip()
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "openclaw-agent-wallet"
7
- version = "0.1.26"
7
+ version = "0.1.28"
8
8
  description = "Plugin-friendly wallet backend for OpenClaw agents"
9
9
  requires-python = ">=3.10"
10
10
  dependencies = [
@@ -80,6 +80,7 @@ Use this skill before calling OpenClaw wallet tools. It is the routing guide for
80
80
  - Prefer `mode=intent_preview`, show the intent limits to the user, then after chat confirmation call `mode=intent_execute` with the same semantic params. This confirms risk limits, not a stale quote fingerprint.
81
81
  - Default Solana swap slippage is 300 bps (3%). The backend computes the approved minimum output from the indicative output and slippage, not from a strict RFQ threshold.
82
82
  - The primary execution path uses Jupiter Swap API V2 `/order` + `/execute`; if a JupiterZ/RFQ route fails, the backend retries with a non-JupiterZ route when possible.
83
+ - Metis `/swap` fallback builds use Jupiter dynamic slippage and a bounded `veryHigh` priority fee instead of the old `"auto"` priority mode.
83
84
  - Do not use legacy `execute` for Solana Jupiter swaps in OpenClaw; exact quote-bound approval is too fragile for active markets.
84
85
  - Use for SOL<->SPL or SPL<->SPL on Solana. Do not use LI.FI for Solana-only swaps.
85
86
  - EVM same-chain Velora swap: `swap_evm_tokens`
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agentlayer.tech/wallet",
3
- "version": "0.1.26",
3
+ "version": "0.1.28",
4
4
  "description": "NPM installer for the OpenClaw Agent Wallet local runtime.",
5
5
  "type": "module",
6
6
  "repository": {