Skip to content

Commit 2de6d32

Browse files
fix(mpp): prevent key_authorization from being included when key is already provisioned
Two fixes for the 'access key already exists' error on MPP charge payments: 1. pay_charge (session.rs): Strip key_authorization from the signing mode when key_provisioned is true. Previously the charge path passed self.signing_mode directly to TempoCharge::sign_with_options, which always included key_authorization from keys.toml. The session path (create_open_tx) already had this guard, but the charge path did not. 2. 402 retry (transport.rs): Only retry with key_authorization when the error specifically indicates the key is not provisioned ('access key does not exist'). Previously any 402 retry unconditionally called mark_key_not_provisioned(), causing the second attempt to include key_authorization even when the first failure was for a different reason (e.g. wrong currency, insufficient balance). Co-authored-by: Amp <amp@ampcode.com> Amp-Thread-ID: https://ampcode.com/threads/T-019d4989-0b60-72ef-99a0-2d0d9f0bc62c
1 parent 98560b1 commit 2de6d32

File tree

2 files changed

+64
-31
lines changed

2 files changed

+64
-31
lines changed

crates/common/src/provider/mpp/session.rs

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -344,8 +344,24 @@ impl SessionProvider {
344344
use mpp::client::tempo::charge::{SignOptions, TempoCharge};
345345

346346
let charge = TempoCharge::from_challenge(challenge)?;
347-
let options =
348-
SignOptions { signing_mode: Some(self.signing_mode.clone()), ..Default::default() };
347+
348+
// Strip key_authorization from the signing mode when the key is already
349+
// provisioned on-chain. Otherwise the payment tx includes a redundant
350+
// key provisioning call that fails with "access key already exists".
351+
let signing_mode = if *self.key_provisioned.lock().unwrap() {
352+
match &self.signing_mode {
353+
TempoSigningMode::Keychain { wallet, version, .. } => TempoSigningMode::Keychain {
354+
wallet: *wallet,
355+
key_authorization: None,
356+
version: *version,
357+
},
358+
other => other.clone(),
359+
}
360+
} else {
361+
self.signing_mode.clone()
362+
};
363+
364+
let options = SignOptions { signing_mode: Some(signing_mode), ..Default::default() };
349365
let signed = charge.sign_with_options(&self.signer, options).await?;
350366
Ok(signed.into_credential())
351367
}

crates/common/src/provider/mpp/transport.rs

Lines changed: 46 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -271,38 +271,55 @@ where
271271
)));
272272
}
273273

274-
// Retry 402 → try with key_authorization
274+
// Retry 402 → only retry with key_authorization if the error indicates
275+
// the access key is not provisioned on-chain. Unconditionally retrying
276+
// caused "access key already exists" when the 402 was for a different
277+
// reason (e.g. wrong currency, insufficient balance).
275278
if retry_resp.status() == StatusCode::PAYMENT_REQUIRED {
276-
self.provider.mark_key_not_provisioned();
277-
let resolved = self.provider.resolve()?;
279+
let retry_body = retry_resp.bytes().await.map_err(TransportErrorKind::custom)?;
280+
let retry_text = String::from_utf8_lossy(&retry_body);
281+
282+
if retry_text.contains("access key does not exist")
283+
|| retry_text.contains("key is not provisioned")
284+
{
285+
self.provider.mark_key_not_provisioned();
286+
let resolved = self.provider.resolve()?;
287+
288+
if resolved.supports(challenge.method.as_str(), challenge.intent.as_str()) {
289+
debug!(
290+
"MPP 402 indicates key not provisioned, retrying with key_authorization"
291+
);
278292

279-
if resolved.supports(challenge.method.as_str(), challenge.intent.as_str()) {
280-
debug!("first MPP attempt returned 402, retrying with key_authorization");
281-
282-
let credential = resolved.pay(challenge).await.map_err(|e| {
283-
TransportErrorKind::custom(std::io::Error::other(format!(
284-
"MPP payment failed: {e}"
285-
)))
286-
})?;
287-
let auth_header = format_authorization(&credential).map_err(|e| {
288-
TransportErrorKind::custom(std::io::Error::other(format!(
289-
"failed to format MPP credential: {e}"
290-
)))
291-
})?;
292-
293-
let final_resp = self
294-
.client
295-
.post(self.url.clone())
296-
.headers(headers)
297-
.header("content-type", "application/json")
298-
.header(AUTHORIZATION_HEADER, auth_header)
299-
.body(body)
300-
.send()
301-
.await
302-
.map_err(TransportErrorKind::custom)?;
303-
304-
return Self::handle_response(final_resp).await;
293+
let credential = resolved.pay(challenge).await.map_err(|e| {
294+
TransportErrorKind::custom(std::io::Error::other(format!(
295+
"MPP payment failed: {e}"
296+
)))
297+
})?;
298+
let auth_header = format_authorization(&credential).map_err(|e| {
299+
TransportErrorKind::custom(std::io::Error::other(format!(
300+
"failed to format MPP credential: {e}"
301+
)))
302+
})?;
303+
304+
let final_resp = self
305+
.client
306+
.post(self.url.clone())
307+
.headers(headers)
308+
.header("content-type", "application/json")
309+
.header(AUTHORIZATION_HEADER, auth_header)
310+
.body(body)
311+
.send()
312+
.await
313+
.map_err(TransportErrorKind::custom)?;
314+
315+
return Self::handle_response(final_resp).await;
316+
}
305317
}
318+
319+
return Err(TransportErrorKind::http_error(
320+
StatusCode::PAYMENT_REQUIRED.as_u16(),
321+
retry_text.into_owned(),
322+
));
306323
}
307324

308325
Self::handle_response(retry_resp).await

0 commit comments

Comments
 (0)